comparison DPF-Prymula-audioplugins/dpf/distrho/extra/sofd/libsofd.c @ 3:84e66ea83026

DPF-Prymula-audioplugins-0.231015-2
author prymula <prymula76@outlook.com>
date Mon, 16 Oct 2023 21:53:34 +0200
parents
children
comparison
equal deleted inserted replaced
2:cf2cb71d31dd 3:84e66ea83026
1 /* libSOFD - Simple Open File Dialog [for X11 without toolkit]
2 *
3 * Copyright (C) 2014 Robin Gareus <robin@gareus.org>
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 * THE SOFTWARE.
22 */
23
24 /* Test and example:
25 * gcc -Wall -D SOFD_TEST -g -o sofd libsofd.c -lX11
26 *
27 * public API documentation and example code at the bottom of this file
28 *
29 * This small lib may one day include openGL rendering and
30 * wayland window support, but not today. Today we celebrate
31 * 30 years of X11.
32 */
33
34 #ifdef SOFD_TEST
35 #define HAVE_X11
36 #include "libsofd.h"
37 #endif
38
39 #include <stdio.h>
40 #include <stdint.h>
41 #include <string.h>
42 #include <stdlib.h>
43 #include <unistd.h>
44 #include <libgen.h>
45 #include <time.h>
46 #include <sys/types.h>
47 #include <sys/stat.h>
48 #include <assert.h>
49
50 #if defined(__clang__)
51 # pragma clang diagnostic push
52 # pragma clang diagnostic ignored "-Wnarrowing"
53 #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
54 # pragma GCC diagnostic push
55 # pragma GCC diagnostic ignored "-Wnarrowing"
56 #endif
57
58 // shared 'recently used' implementation
59 // sadly, xbel does not qualify as simple.
60 // hence we use a simple format alike the
61 // gtk-bookmark list (one file per line)
62
63 #define MAX_RECENT_ENTRIES 24
64 #define MAX_RECENT_AGE (15552000) // 180 days (in sec)
65
66 typedef struct {
67 char path[1024];
68 time_t atime;
69 } FibRecentFile;
70
71 static FibRecentFile *_recentlist = NULL;
72 static unsigned int _recentcnt = 0;
73 static uint8_t _recentlock = 0;
74
75 static int fib_isxdigit (const char x) {
76 if (
77 (x >= '0' && x <= '9')
78 ||
79 (x >= 'a' && x <= 'f')
80 ||
81 (x >= 'A' && x <= 'F')
82 ) return 1;
83 return 0;
84 }
85
86 static void decode_3986 (char *str) {
87 int len = strlen (str);
88 int idx = 0;
89 while (idx + 2 < len) {
90 char *in = &str[idx];
91 if (('%' == *in) && fib_isxdigit (in[1]) && fib_isxdigit (in[2])) {
92 char hexstr[3];
93 hexstr[0] = in[1];
94 hexstr[1] = in[2];
95 hexstr[2] = 0;
96 long hex = strtol (hexstr, NULL, 16);
97 *in = hex;
98 memmove (&str[idx+1], &str[idx + 3], len - idx - 2);
99 len -= 2;
100 }
101 ++idx;
102 }
103 }
104
105 static char *encode_3986 (const char *str) {
106 size_t alloc, newlen;
107 char *ns = NULL;
108 unsigned char in;
109 size_t i = 0;
110 size_t length;
111
112 if (!str) return strdup ("");
113
114 alloc = strlen (str) + 1;
115 newlen = alloc;
116
117 ns = (char*) malloc (alloc);
118
119 length = alloc;
120 while (--length) {
121 in = *str;
122
123 switch (in) {
124 case '0': case '1': case '2': case '3': case '4':
125 case '5': case '6': case '7': case '8': case '9':
126 case 'a': case 'b': case 'c': case 'd': case 'e':
127 case 'f': case 'g': case 'h': case 'i': case 'j':
128 case 'k': case 'l': case 'm': case 'n': case 'o':
129 case 'p': case 'q': case 'r': case 's': case 't':
130 case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
131 case 'A': case 'B': case 'C': case 'D': case 'E':
132 case 'F': case 'G': case 'H': case 'I': case 'J':
133 case 'K': case 'L': case 'M': case 'N': case 'O':
134 case 'P': case 'Q': case 'R': case 'S': case 'T':
135 case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
136 case '_': case '~': case '.': case '-':
137 case '/': case ',': // XXX not in RFC3986
138 ns[i++] = in;
139 break;
140 default:
141 newlen += 2; /* this'll become a %XX */
142 if (newlen > alloc) {
143 alloc *= 2;
144 ns = (char*) realloc (ns, alloc);
145 }
146 snprintf (&ns[i], 4, "%%%02X", in);
147 i += 3;
148 break;
149 }
150 ++str;
151 }
152 ns[i] = 0;
153 return ns;
154 }
155
156 void x_fib_free_recent () {
157 free (_recentlist);
158 _recentlist = NULL;
159 _recentcnt = 0;
160 }
161
162 static int cmp_recent (const void *p1, const void *p2) {
163 FibRecentFile *a = (FibRecentFile*) p1;
164 FibRecentFile *b = (FibRecentFile*) p2;
165 if (a->atime == b->atime) return 0;
166 return a->atime < b->atime;
167 }
168
169 int x_fib_add_recent (const char *path, time_t atime) {
170 unsigned int i;
171 struct stat fs;
172 if (_recentlock) { return -1; }
173 if (access (path, R_OK)) {
174 return -1;
175 }
176 if (stat (path, &fs)) {
177 return -1;
178 }
179 if (!S_ISREG (fs.st_mode)) {
180 return -1;
181 }
182 if (atime == 0) atime = time (NULL);
183 if (MAX_RECENT_AGE > 0 && atime + MAX_RECENT_AGE < time (NULL)) {
184 return -1;
185 }
186
187 for (i = 0; i < _recentcnt; ++i) {
188 if (!strcmp (_recentlist[i].path, path)) {
189 if (_recentlist[i].atime < atime) {
190 _recentlist[i].atime = atime;
191 }
192 qsort (_recentlist, _recentcnt, sizeof(FibRecentFile), cmp_recent);
193 return _recentcnt;
194 }
195 }
196 _recentlist = (FibRecentFile*)realloc (_recentlist, (_recentcnt + 1) * sizeof(FibRecentFile));
197 _recentlist[_recentcnt].atime = atime;
198 strcpy (_recentlist[_recentcnt].path, path);
199 qsort (_recentlist, _recentcnt + 1, sizeof(FibRecentFile), cmp_recent);
200
201 if (_recentcnt >= MAX_RECENT_ENTRIES) {
202 return (_recentcnt);
203 }
204 return (++_recentcnt);
205 }
206
207 #ifdef PATHSEP
208 #undef PATHSEP
209 #endif
210
211 #ifdef PLATFORM_WINDOWS
212 #define DIRSEP '\\'
213 #else
214 #define DIRSEP '/'
215 #endif
216
217 static void mkpath(const char *dir) {
218 char tmp[1024];
219 char *p;
220 size_t len;
221
222 snprintf (tmp, sizeof(tmp), "%s", dir);
223 len = strlen(tmp);
224 if (tmp[len - 1] == '/')
225 tmp[len - 1] = 0;
226 for (p = tmp + 1; *p; ++p)
227 if(*p == DIRSEP) {
228 *p = 0;
229 #ifdef PLATFORM_WINDOWS
230 mkdir(tmp);
231 #else
232 mkdir(tmp, 0755);
233 #endif
234 *p = DIRSEP;
235 }
236 #ifdef PLATFORM_WINDOWS
237 mkdir(tmp);
238 #else
239 mkdir(tmp, 0755);
240 #endif
241 }
242
243 int x_fib_save_recent (const char *fn) {
244 if (_recentlock) { return -1; }
245 if (!fn) { return -1; }
246 if (_recentcnt < 1 || !_recentlist) { return -1; }
247 unsigned int i;
248 char *dn = strdup (fn);
249 mkpath (dirname (dn));
250 free (dn);
251
252 FILE *rf = fopen (fn, "w");
253 if (!rf) return -1;
254
255 qsort (_recentlist, _recentcnt, sizeof(FibRecentFile), cmp_recent);
256 for (i = 0; i < _recentcnt; ++i) {
257 char *n = encode_3986 (_recentlist[i].path);
258 fprintf (rf, "%s %lu\n", n, _recentlist[i].atime);
259 free (n);
260 }
261 fclose (rf);
262 return 0;
263 }
264
265 int x_fib_load_recent (const char *fn) {
266 char tmp[1024];
267 if (_recentlock) { return -1; }
268 if (!fn) { return -1; }
269 x_fib_free_recent ();
270 if (access (fn, R_OK)) {
271 return -1;
272 }
273 FILE *rf = fopen (fn, "r");
274 if (!rf) return -1;
275 while (fgets (tmp, sizeof(tmp), rf)
276 && strlen (tmp) > 1
277 && strlen (tmp) < sizeof(tmp))
278 {
279 char *s;
280 tmp[strlen (tmp) - 1] = '\0'; // strip newline
281 if (!(s = strchr (tmp, ' '))) { // find name <> atime sep
282 continue;
283 }
284 *s = '\0';
285 time_t t = atol (++s);
286 decode_3986 (tmp);
287 x_fib_add_recent (tmp, t);
288 }
289 fclose (rf);
290 return 0;
291 }
292
293 unsigned int x_fib_recent_count () {
294 return _recentcnt;
295 }
296
297 const char *x_fib_recent_at (unsigned int i) {
298 if (i >= _recentcnt)
299 return NULL;
300 return _recentlist[i].path;
301 }
302
303 #ifdef PLATFORM_WINDOWS
304 #define PATHSEP "\\"
305 #else
306 #define PATHSEP "/"
307 #endif
308
309 const char *x_fib_recent_file(const char *appname) {
310 static char recent_file[1024];
311 assert(!strchr(appname, '/'));
312 const char *xdg = getenv("XDG_DATA_HOME");
313 if (xdg && (strlen(xdg) + strlen(appname) + 10) < sizeof(recent_file)) {
314 sprintf(recent_file, "%s" PATHSEP "%s" PATHSEP "recent", xdg, appname);
315 return recent_file;
316 }
317 #ifdef PLATFORM_WINDOWS
318 const char * homedrive = getenv("HOMEDRIVE");
319 const char * homepath = getenv("HOMEPATH");
320 if (homedrive && homepath && (strlen(homedrive) + strlen(homepath) + strlen(appname) + 29) < PATH_MAX) {
321 sprintf(recent_file, "%s%s" PATHSEP "Application Data" PATHSEP "%s" PATHSEP "recent.txt", homedrive, homepath, appname);
322 return recent_file;
323 }
324 #elif defined PLATFORM_OSX
325 const char *home = getenv("HOME");
326 if (home && (strlen(home) + strlen(appname) + 29) < sizeof(recent_file)) {
327 sprintf(recent_file, "%s/Library/Preferences/%s/recent", home, appname);
328 return recent_file;
329 }
330 #else
331 const char *home = getenv("HOME");
332 if (home && (strlen(home) + strlen(appname) + 22) < sizeof(recent_file)) {
333 sprintf(recent_file, "%s/.local/share/%s/recent", home, appname);
334 return recent_file;
335 }
336 #endif
337 return NULL;
338 }
339
340 #ifdef HAVE_X11
341 #include <dirent.h>
342
343 #include <X11/Xlib.h>
344 #include <X11/Xatom.h>
345 #include <X11/Xutil.h>
346 #include <X11/keysym.h>
347 #include <X11/Xos.h>
348
349 #if defined(__linux__) || defined(__linux)
350 #define HAVE_MNTENT
351 #include <mntent.h>
352 #endif
353
354 #ifndef MIN
355 #define MIN(A,B) ( (A) < (B) ? (A) : (B) )
356 #endif
357
358 #ifndef MAX
359 #define MAX(A,B) ( (A) < (B) ? (B) : (A) )
360 #endif
361
362 static Window _fib_win = 0;
363 static GC _fib_gc = 0;
364 static XColor _c_gray0, _c_gray1, _c_gray2, _c_gray3, _c_gray4, _c_gray5;
365 static Font _fibfont = 0;
366 static Pixmap _pixbuffer = None;
367
368 static int _fib_width = 100;
369 static int _fib_height = 100;
370 static int _btn_w = 0;
371 static int _btn_span = 0;
372 static double _scalefactor = 1;
373
374 static int _fib_font_height = 0;
375 static int _fib_dir_indent = 0;
376 static int _fib_spc_norm = 0;
377 static int _fib_font_ascent = 0;
378 static int _fib_font_vsep = 0;
379 static int _fib_font_size_width = 0;
380 static int _fib_font_time_width = 0;
381 static int _fib_place_width = 0;
382
383 static int _scrl_f = 0;
384 static int _scrl_y0 = -1;
385 static int _scrl_y1 = -1;
386 static int _scrl_my = -1;
387 static int _scrl_mf = -1;
388 static int _view_p = -1;
389
390 static int _fsel = -1;
391 static int _hov_b = -1;
392 static int _hov_f = -1;
393 static int _hov_p = -1;
394 static int _hov_h = -1;
395 static int _hov_l = -1;
396 static int _hov_s = -1;
397 static int _sort = 0;
398 static int _columns = 0;
399 static int _fib_filter_fn = 1;
400 static int _fib_hidden_fn = 0;
401 static int _fib_show_places = 0;
402
403 static uint8_t _fib_mapped = 0;
404 static uint8_t _fib_resized = 0;
405 static unsigned long _dblclk = 0;
406
407 static int _status = -2;
408 static char _rv_open[1024] = "";
409
410 static char _fib_cfg_custom_places[1024] = "";
411 static char _fib_cfg_custom_font[256] = "";
412 static char _fib_cfg_title[128] = "xjadeo - Open Video File";
413
414 typedef struct {
415 char name[256];
416 int x0;
417 int xw;
418 } FibPathButton;
419
420 typedef struct {
421 char name[256];
422 char strtime[32];
423 char strsize[32];
424 int ssizew;
425 off_t size;
426 time_t mtime;
427 uint8_t flags; // 2: selected, 4: isdir 8: recent-entry
428 FibRecentFile *rfp;
429 } FibFileEntry;
430
431 typedef struct {
432 char text[24];
433 uint8_t flags; // 2: selected, 4: toggle, 8 disable
434 int x0;
435 int tw;
436 int xw;
437 void (*callback)(Display*);
438 } FibButton;
439
440 typedef struct {
441 char name[256];
442 char path[1024];
443 uint8_t flags; // 1: hover, 2: selected, 4:add sep
444 } FibPlace;
445
446 static char _cur_path[1024] = "";
447 static FibFileEntry *_dirlist = NULL;
448 static FibPathButton *_pathbtn = NULL;
449 static FibPlace *_placelist = NULL;
450 static int _dircount = 0;
451 static int _pathparts = 0;
452 static int _placecnt = 0;
453
454 static FibButton _btn_ok;
455 static FibButton _btn_cancel;
456 static FibButton _btn_filter;
457 static FibButton _btn_places;
458 static FibButton _btn_hidden;
459 static FibButton *_btns[] = {&_btn_places, &_btn_filter, &_btn_hidden, &_btn_cancel, &_btn_ok};
460
461 static int (*_fib_filter_function)(const char *filename);
462
463 /* hardcoded layout */
464 #define DSEP 6 // px; horiz space beween elements, also l+r margin for file-list
465 #define PSEP 4 // px; horiz space beween paths
466 #define FILECOLUMN (17 * _fib_dir_indent) //px; min width of file-column
467 #define LISTTOP 2.7 //em; top of the file-browser list
468 #define LISTBOT 4.75 //em; bottom of the file-browers list
469 #define BTNBTMMARGIN 0.75 //em; height/margin of the button row
470 #define BTNPADDING 2 // px - only used for open/cancel buttons
471 #define SCROLLBARW (3 + (_fib_spc_norm&~1)) //px; - should be SCROLLBARW = (N * 2 + 3)
472 #define SCROLLBOXH 10 //px; arrow box top+bottom
473 #define PLACESW _fib_place_width //px;
474 #define PLACESWMAX (15 *_fib_spc_norm) //px;
475 #define PATHBTNTOP _fib_font_vsep //px; offset by (_fib_font_ascent);
476 #define FAREAMRGB 3 //px; base L+R margin
477 #define FAREAMRGR (FAREAMRGB + 1) //px; right margin of file-area + 1 (line width)
478 #define FAREAMRGL (_fib_show_places ? PLACESW / _scalefactor + FAREAMRGB : FAREAMRGB) //px; left margin of file-area
479 #define TEXTSEP 4 //px;
480 #define FAREATEXTL (FAREAMRGL + TEXTSEP) //px; filename text-left FAREAMRGL + TEXTSEP
481 #define SORTBTNOFF -10 //px;
482
483 #ifndef DBLCLKTME
484 #define DBLCLKTME 200 //msec; double click time
485 #endif
486
487 #define DRAW_OUTLINE
488 #define DOUBLE_BUFFER
489 #define LIST_ENTRY_HOVER
490
491 static int query_font_geometry (Display *dpy, GC gc, const char *txt, int *w, int *h, int *a, int *d) {
492 XCharStruct text_structure;
493 int font_direction, font_ascent, font_descent;
494 XFontStruct *fontinfo = XQueryFont (dpy, XGContextFromGC (gc));
495
496 if (!fontinfo) { return -1; }
497 XTextExtents (fontinfo, txt, strlen (txt), &font_direction, &font_ascent, &font_descent, &text_structure);
498 if (w) *w = XTextWidth (fontinfo, txt, strlen (txt));
499 if (h) *h = text_structure.ascent + text_structure.descent;
500 if (a) *a = text_structure.ascent;
501 if (d) *d = text_structure.descent;
502 #ifndef DISTRHO_OS_HAIKU // FIXME
503 XFreeFontInfo (NULL, fontinfo, 1);
504 #endif
505 return 0;
506 }
507
508 static void VDrawRectangle (Display *dpy, Drawable d, GC gc, int x, int y, unsigned int w, unsigned int h) {
509 #ifdef DRAW_OUTLINE
510 XSetForeground (dpy, gc, _c_gray5.pixel);
511 XDrawLine (dpy, d, gc, x + 1, y + h, x + w, y + h);
512 XDrawLine (dpy, d, gc, x + w, y + 1, x + w, y + h);
513 XDrawLine (dpy, d, gc, x + 1, y, x + w, y);
514 XDrawLine (dpy, d, gc, x, y + 1, x, y + h);
515 #else
516 const unsigned long blackColor = BlackPixel (dpy, DefaultScreen (dpy));
517 XSetForeground (dpy, _fib_gc, blackColor);
518 XDrawRectangle (dpy, d, gc, x, y, w, h);
519 #endif
520 }
521
522 static void fib_expose (Display *dpy, Window realwin) {
523 int i;
524 XID win;
525 if (!_fib_mapped) return;
526
527 if (_fib_resized
528 #ifdef DOUBLE_BUFFER
529 || !_pixbuffer
530 #endif
531 )
532 {
533 #ifdef DOUBLE_BUFFER
534 unsigned int w = 0, h = 0;
535 if (_pixbuffer != None) {
536 Window ignored_w;
537 int ignored_i;
538 unsigned int ignored_u;
539 XGetGeometry(dpy, _pixbuffer, &ignored_w, &ignored_i, &ignored_i, &w, &h, &ignored_u, &ignored_u);
540 if (_fib_width != (int)w || _fib_height != (int)h) {
541 XFreePixmap (dpy, _pixbuffer);
542 _pixbuffer = None;
543 }
544 }
545 if (_pixbuffer == None) {
546 XWindowAttributes wa;
547 XGetWindowAttributes (dpy, realwin, &wa);
548 _pixbuffer = XCreatePixmap (dpy, realwin, _fib_width, _fib_height, wa.depth);
549 }
550 #endif
551 if (_pixbuffer != None) {
552 XSetForeground (dpy, _fib_gc, _c_gray1.pixel);
553 XFillRectangle (dpy, _pixbuffer, _fib_gc, 0, 0, _fib_width, _fib_height);
554 } else {
555 XSetForeground (dpy, _fib_gc, _c_gray1.pixel);
556 XFillRectangle (dpy, realwin, _fib_gc, 0, 0, _fib_width, _fib_height);
557 }
558 _fib_resized = 0;
559 }
560
561 if (_pixbuffer == None) {
562 win = realwin;
563 } else {
564 win = _pixbuffer;
565 }
566
567 // Top Row: dirs and up navigation
568
569 int ppw = 0;
570 int ppx = FAREAMRGB * _scalefactor;
571
572 for (i = _pathparts - 1; i >= 0; --i) {
573 ppw += _pathbtn[i].xw + PSEP * _scalefactor;
574 if (ppw >= _fib_width - PSEP * _scalefactor - _pathbtn[0].xw - FAREAMRGB * _scalefactor) break; // XXX, first change is from "/" to "<", NOOP
575 }
576 ++i;
577 // border-less "<" parent/up, IFF space is limited
578 if (i > 0) {
579 if (0 == _hov_p || (_hov_p > 0 && _hov_p < _pathparts - 1)) {
580 XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
581 } else {
582 XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
583 }
584 XDrawString (dpy, win, _fib_gc, ppx, PATHBTNTOP, "<", 1);
585 ppx += _pathbtn[0].xw + PSEP * _scalefactor;
586 if (i == _pathparts) --i;
587 }
588
589 _view_p = i;
590
591 while (i < _pathparts) {
592 if (i == _hov_p) {
593 XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
594 } else {
595 XSetForeground (dpy, _fib_gc, _c_gray2.pixel);
596 }
597 XFillRectangle (dpy, win, _fib_gc,
598 ppx + 1, PATHBTNTOP - _fib_font_ascent,
599 _pathbtn[i].xw - 1, _fib_font_height);
600 VDrawRectangle (dpy, win, _fib_gc,
601 ppx, PATHBTNTOP - _fib_font_ascent,
602 _pathbtn[i].xw, _fib_font_height);
603 XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
604 XDrawString (dpy, win, _fib_gc, ppx + 1 + BTNPADDING, PATHBTNTOP,
605 _pathbtn[i].name, strlen (_pathbtn[i].name));
606 _pathbtn[i].x0 = ppx; // current position
607 ppx += _pathbtn[i].xw + PSEP * _scalefactor;
608 ++i;
609 }
610
611 // middle, scroll list of file names
612 const int ltop = LISTTOP * _fib_font_vsep;
613 const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep;
614 const int fsel_height = 4 * _scalefactor + llen * _fib_font_vsep;
615 const int fsel_width = _fib_width - (FAREAMRGL + FAREAMRGR) * _scalefactor - (llen < _dircount ? SCROLLBARW * _scalefactor : 0);
616 const int t_x = FAREATEXTL * _scalefactor;
617 int t_s = FAREATEXTL * _scalefactor + fsel_width;
618 int t_t = FAREATEXTL * _scalefactor + fsel_width;
619
620 // check which colums can be visible
621 // depending on available width of window.
622 _columns = 0;
623 if (fsel_width > FILECOLUMN + _fib_font_size_width + _fib_font_time_width) {
624 _columns |= 2;
625 t_s = FAREAMRGL * _scalefactor + fsel_width - _fib_font_time_width - TEXTSEP * _scalefactor;
626 }
627 if (fsel_width > FILECOLUMN + _fib_font_size_width) {
628 _columns |= 1;
629 t_t = t_s - _fib_font_size_width - TEXTSEP * _scalefactor;
630 }
631
632 int fstop = _scrl_f; // first entry in scroll position
633 const int ttop = ltop - _fib_font_height + _fib_font_ascent;
634
635 if (fstop > 0 && fstop + llen > _dircount) {
636 fstop = MAX (0, _dircount - llen);
637 _scrl_f = fstop;
638 }
639
640 // list header
641 XSetForeground (dpy, _fib_gc, _c_gray3.pixel);
642 XFillRectangle (dpy, win, _fib_gc, FAREAMRGL * _scalefactor, ltop - _fib_font_vsep, fsel_width, _fib_font_vsep);
643
644 // draw background of file list
645 XSetForeground (dpy, _fib_gc, _c_gray2.pixel);
646 XFillRectangle (dpy, win, _fib_gc, FAREAMRGL * _scalefactor, ltop, fsel_width, fsel_height);
647
648 #ifdef DRAW_OUTLINE
649 VDrawRectangle (dpy, win, _fib_gc,
650 FAREAMRGL * _scalefactor,
651 ltop - _fib_font_vsep - 1,
652 _fib_width - (FAREAMRGL + FAREAMRGR) * _scalefactor,
653 fsel_height + _fib_font_vsep + 1);
654 #endif
655
656 switch (_hov_h) {
657 case 1:
658 XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
659 XFillRectangle (dpy, win, _fib_gc,
660 t_x + _fib_dir_indent - (TEXTSEP - 1) * _scalefactor,
661 ltop - _fib_font_vsep,
662 t_t - t_x - _fib_dir_indent - 1 * _scalefactor,
663 _fib_font_vsep);
664 break;
665 case 2:
666 XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
667 XFillRectangle (dpy, win, _fib_gc,
668 t_t - (TEXTSEP - 1) * _scalefactor,
669 ltop - _fib_font_vsep,
670 _fib_font_size_width + (TEXTSEP - 1) * _scalefactor,
671 _fib_font_vsep);
672 break;
673 case 3:
674 XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
675 XFillRectangle (dpy, win, _fib_gc,
676 t_s - (TEXTSEP - 1) * _scalefactor,
677 ltop - _fib_font_vsep,
678 TEXTSEP * 2 * _scalefactor + _fib_font_time_width - 1 * _scalefactor,
679 _fib_font_vsep);
680 break;
681 default:
682 break;
683 }
684
685 // column headings and sort order
686 int arp = MAX (2 * _scalefactor, _fib_font_height / 5); // arrow scale
687 const int trioff = _fib_font_height - _fib_font_ascent - arp + 1 * _scalefactor;
688 XPoint ptri[4] = { {0, ttop - trioff }, {arp, -arp - arp - 1 * _scalefactor}, {-arp - arp, 0}, {arp, arp + arp + 1 * _scalefactor}};
689 if (_sort & 1) {
690 ptri[0].y = ttop -arp - arp - 1 * _scalefactor;
691 ptri[1].y *= -1;
692 ptri[3].y *= -1;
693 }
694 switch (_sort) {
695 case 0:
696 case 1:
697 ptri[0].x = t_t + (SORTBTNOFF + 2) * _scalefactor - arp;
698 XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
699 XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious);
700 XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious);
701 break;
702 case 2:
703 case 3:
704 if (_columns & 1) {
705 ptri[0].x = t_s + (SORTBTNOFF + 2) * _scalefactor - arp;
706 XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
707 XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious);
708 XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious);
709 }
710 break;
711 case 4:
712 case 5:
713 if (_columns & 2) {
714 ptri[0].x = FAREATEXTL * _scalefactor + fsel_width + (SORTBTNOFF + 2) * _scalefactor - arp;
715 XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
716 XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious);
717 XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious);
718 }
719 break;
720 }
721
722 #if 0 // bottom header bottom border
723 XSetForeground (dpy, _fib_gc, _c_gray5.pixel);
724 XSetLineAttributes (dpy, _fib_gc, 1, LineOnOffDash, CapButt, JoinMiter);
725 XDrawLine (dpy, win, _fib_gc,
726 FAREAMRGL + 1, ltop,
727 FAREAMRGL + fsel_width, ltop);
728 XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter);
729 #endif
730
731 XSetForeground (dpy, _fib_gc, _c_gray2.pixel);
732 XDrawLine (dpy, win, _fib_gc,
733 t_x + _fib_dir_indent - TEXTSEP * _scalefactor, ltop - _fib_font_vsep + 3 * _scalefactor,
734 t_x + _fib_dir_indent - TEXTSEP * _scalefactor, ltop - 3 * _scalefactor);
735
736 XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
737 XDrawString (dpy, win, _fib_gc, t_x + _fib_dir_indent, ttop, "Name", 4);
738
739 if (_columns & 1) {
740 XSetForeground (dpy, _fib_gc, _c_gray2.pixel);
741 XDrawLine (dpy, win, _fib_gc,
742 t_t - TEXTSEP * _scalefactor, ltop - _fib_font_vsep + 3 * _scalefactor,
743 t_t - TEXTSEP * _scalefactor, ltop - 3 * _scalefactor);
744 XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
745 XDrawString (dpy, win, _fib_gc, t_t, ttop, "Size", 4);
746 }
747
748 if (_columns & 2) {
749 XSetForeground (dpy, _fib_gc, _c_gray2.pixel);
750 XDrawLine (dpy, win, _fib_gc,
751 t_s - TEXTSEP * _scalefactor, ltop - _fib_font_vsep + 3 * _scalefactor,
752 t_s - TEXTSEP * _scalefactor, ltop - 3 * _scalefactor);
753 XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
754 if (_pathparts > 0)
755 XDrawString (dpy, win, _fib_gc, t_s, ttop, "Last Modified", 13);
756 else
757 XDrawString (dpy, win, _fib_gc, t_s, ttop, "Last Used", 9);
758 }
759
760 // scrollbar sep
761 if (llen < _dircount) {
762 const int sx0 = _fib_width - (SCROLLBARW + FAREAMRGR) * _scalefactor;
763 XSetForeground (dpy, _fib_gc, _c_gray2.pixel);
764 XDrawLine (dpy, win, _fib_gc,
765 sx0 - 1, ltop - _fib_font_vsep,
766 #ifdef DRAW_OUTLINE
767 sx0 - 1, ltop + fsel_height
768 #else
769 sx0 - 1, ltop - 1
770 #endif
771 );
772 }
773
774 // clip area for file-name
775 XRectangle clp = {(FAREAMRGL + 1) * _scalefactor, ltop,
776 t_t - (FAREAMRGL + TEXTSEP * 2 + 1) * _scalefactor, fsel_height};
777
778 // list files in view
779 for (i = 0; i < llen; ++i) {
780 const int j = i + fstop;
781 if (j >= _dircount) break;
782
783 const int t_y = ltop + (i+1) * _fib_font_vsep - 4;
784
785 if (_dirlist[j].flags & 2) {
786 XSetForeground (dpy, _fib_gc, _c_gray5.pixel);
787 XFillRectangle (dpy, win, _fib_gc,
788 FAREAMRGL * _scalefactor, t_y - _fib_font_ascent, fsel_width, _fib_font_height);
789 }
790 /*
791 if (_hov_f == j && !(_dirlist[j].flags & 2)) {
792 XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
793 }
794 */
795 if (_dirlist[j].flags & 4) {
796 XSetForeground (dpy, _fib_gc, (_dirlist[j].flags & 2) ? _c_gray3.pixel : _c_gray5.pixel);
797 XDrawString (dpy, win, _fib_gc, t_x, t_y, "D", 1);
798 }
799 XSetClipRectangles (dpy, _fib_gc, 0, 0, &clp, 1, Unsorted);
800 XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
801 XDrawString (dpy, win, _fib_gc,
802 t_x + _fib_dir_indent, t_y,
803 _dirlist[j].name, strlen (_dirlist[j].name));
804 XSetClipMask (dpy, _fib_gc, None);
805
806 if (_columns & 1) // right-aligned 'size'
807 XDrawString (dpy, win, _fib_gc,
808 t_s - (TEXTSEP + 2) * _scalefactor - _dirlist[j].ssizew, t_y,
809 _dirlist[j].strsize, strlen (_dirlist[j].strsize));
810 if (_columns & 2)
811 XDrawString (dpy, win, _fib_gc,
812 t_s, t_y,
813 _dirlist[j].strtime, strlen (_dirlist[j].strtime));
814 }
815
816 // scrollbar
817 if (llen < _dircount) {
818 float sl = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH) * _scalefactor) / (float) _dircount;
819 sl = MAX ((8. * _scalefactor / llen), sl); // 8px min height of scroller
820 const int sy1 = llen * sl;
821 const float mx = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH) * _scalefactor - sy1) / (float)(_dircount - llen);
822 const int sy0 = fstop * mx;
823 const int sx0 = _fib_width - (SCROLLBARW + FAREAMRGR) * _scalefactor;
824 const int stop = ltop - _fib_font_vsep;
825
826 _scrl_y0 = stop + SCROLLBOXH * _scalefactor + sy0;
827 _scrl_y1 = _scrl_y0 + sy1;
828
829 assert (fstop + llen <= _dircount);
830 // scroll-bar background
831 XSetForeground (dpy, _fib_gc, _c_gray1.pixel);
832 XFillRectangle (dpy, win, _fib_gc, sx0, stop, SCROLLBARW * _scalefactor, fsel_height + _fib_font_vsep);
833
834 // scroller
835 if (_hov_s == 0 || _hov_s == 1 || _hov_s == 2) {
836 XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
837 } else {
838 XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
839 }
840 XFillRectangle (dpy, win, _fib_gc, sx0 + 1 * _scalefactor, stop + SCROLLBOXH * _scalefactor + sy0, (SCROLLBARW - 2) * _scalefactor, sy1);
841
842 int scrw = (SCROLLBARW -3) / 2 * _scalefactor;
843 // arrows top and bottom
844 if (_hov_s == 1) {
845 XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
846 } else {
847 XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
848 }
849 XPoint ptst[4] = { {sx0 + 1 * _scalefactor, stop + 8 * _scalefactor}, {scrw, -7 * _scalefactor}, {scrw, 7 * _scalefactor}, {-2 * scrw, 0}};
850 XFillPolygon (dpy, win, _fib_gc, ptst, 3, Convex, CoordModePrevious);
851 XDrawLines (dpy, win, _fib_gc, ptst, 4, CoordModePrevious);
852
853 if (_hov_s == 2) {
854 XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
855 } else {
856 XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
857 }
858 XPoint ptsb[4] = { {sx0 + 1 * _scalefactor, ltop + fsel_height - 9 * _scalefactor}, {2*scrw, 0}, {-scrw, 7 * _scalefactor}, {-scrw, -7 * _scalefactor}};
859 XFillPolygon (dpy, win, _fib_gc, ptsb, 3, Convex, CoordModePrevious);
860 XDrawLines (dpy, win, _fib_gc, ptsb, 4, CoordModePrevious);
861 } else {
862 _scrl_y0 = _scrl_y1 = -1;
863 }
864
865 if (_fib_show_places) {
866 assert (_placecnt > 0);
867
868 // heading
869 XSetForeground (dpy, _fib_gc, _c_gray3.pixel);
870 XFillRectangle (dpy, win, _fib_gc, FAREAMRGB * _scalefactor, ltop - _fib_font_vsep, PLACESW - TEXTSEP * _scalefactor, _fib_font_vsep);
871
872 // body
873 XSetForeground (dpy, _fib_gc, _c_gray2.pixel);
874 XFillRectangle (dpy, win, _fib_gc, FAREAMRGB * _scalefactor, ltop, PLACESW - TEXTSEP * _scalefactor, fsel_height);
875
876 #ifdef DRAW_OUTLINE
877 VDrawRectangle (dpy, win, _fib_gc, FAREAMRGB * _scalefactor, ltop - _fib_font_vsep -1, PLACESW - TEXTSEP * _scalefactor, fsel_height + _fib_font_vsep + 1);
878 #endif
879
880 XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
881 XDrawString (dpy, win, _fib_gc, (FAREAMRGB + TEXTSEP) * _scalefactor, ttop, "Places", 6);
882
883 XRectangle pclip = {(FAREAMRGB + 1) * _scalefactor, ltop, PLACESW - (TEXTSEP + 1) * _scalefactor, fsel_height};
884 XSetClipRectangles (dpy, _fib_gc, 0, 0, &pclip, 1, Unsorted);
885 const int plx = (FAREAMRGB + TEXTSEP) * _scalefactor;
886 for (i = 0; i < llen && i < _placecnt; ++i) {
887 const int ply = ltop + (i+1) * _fib_font_vsep - 4 * _scalefactor;
888 if (i == _hov_l) {
889 XSetForeground (dpy, _fib_gc, _c_gray5.pixel);
890 XFillRectangle (dpy, win, _fib_gc, FAREAMRGB * _scalefactor, ltop + i * _fib_font_vsep, PLACESW - TEXTSEP * _scalefactor, _fib_font_vsep);
891 }
892 XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
893 XDrawString (dpy, win, _fib_gc,
894 plx, ply,
895 _placelist[i].name, strlen (_placelist[i].name));
896 if (_placelist[i].flags & 4) {
897 XSetForeground (dpy, _fib_gc, _c_gray5.pixel);
898 const int plly = ply - _fib_font_ascent + _fib_font_height + 1 * _scalefactor;
899 const int pllx0 = FAREAMRGB * _scalefactor;
900 const int pllx1 = FAREAMRGB * _scalefactor + PLACESW - TEXTSEP * _scalefactor;
901 XDrawLine (dpy, win, _fib_gc, pllx0, plly, pllx1, plly);
902 }
903 }
904 XSetClipMask (dpy, _fib_gc, None);
905
906 if (_placecnt > llen) {
907 const int plly = ltop + fsel_height - _fib_font_height + _fib_font_ascent;
908 const int pllx0 = FAREAMRGB * _scalefactor + (PLACESW - TEXTSEP * _scalefactor) * .75;
909 const int pllx1 = FAREAMRGB * _scalefactor + PLACESW - TEXTSEP * 2 * _scalefactor;
910
911 XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
912 XSetLineAttributes (dpy, _fib_gc, 1 * _scalefactor, LineOnOffDash, CapButt, JoinMiter);
913 XDrawLine (dpy, win, _fib_gc, pllx0, plly, pllx1, plly);
914 XSetLineAttributes (dpy, _fib_gc, 1 * _scalefactor, LineSolid, CapButt, JoinMiter);
915 }
916 }
917
918 // Bottom Buttons
919 const int numb = sizeof(_btns) / sizeof(FibButton*);
920 int xtra = _fib_width - _btn_span;
921 const int cbox = _fib_font_ascent - 2 * _scalefactor;
922 const int bbase = _fib_height - BTNBTMMARGIN * _fib_font_vsep - BTNPADDING * _scalefactor;
923 const int cblw = cbox > 20 * _scalefactor
924 ? 5 * _scalefactor
925 : (cbox > 9 * _scalefactor ? 3 : 1) * _scalefactor;
926
927 int bx = FAREAMRGB * _scalefactor;
928 for (i = 0; i < numb; ++i) {
929 if (_btns[i]->flags & 8) { continue; }
930 if (_btns[i]->flags & 4) {
931 // checkbutton
932 const int cby0 = bbase - cbox + (1 + BTNPADDING) * _scalefactor;
933 if (i == _hov_b) {
934 XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
935 } else {
936 XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
937 }
938 XDrawRectangle (dpy, win, _fib_gc,
939 bx, cby0 - 1, cbox + 1, cbox + 1);
940
941 XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
942 XDrawString (dpy, win, _fib_gc, BTNPADDING * _scalefactor + bx + _fib_font_ascent, bbase + (BTNPADDING + 1) * _scalefactor,
943 _btns[i]->text, strlen (_btns[i]->text));
944
945 if (i == _hov_b) {
946 XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
947 } else {
948 /* if (_btns[i]->flags & 2) {
949 XSetForeground (dpy, _fib_gc, _c_gray1.pixel);
950 } else */ {
951 XSetForeground (dpy, _fib_gc, _c_gray2.pixel);
952 }
953 }
954 XFillRectangle (dpy, win, _fib_gc,
955 bx+1, cby0, cbox, cbox);
956
957 if (_btns[i]->flags & 2) {
958 XSetLineAttributes (dpy, _fib_gc, cblw, LineSolid, CapRound, JoinMiter);
959 XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
960 XDrawLine (dpy, win, _fib_gc,
961 bx + 2, cby0 + 1,
962 bx + cbox - 1, cby0 + cbox - 2);
963 XDrawLine (dpy, win, _fib_gc,
964 bx + cbox - 1, cby0 + 1,
965 bx + 2, cby0 + cbox - 2);
966 XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter);
967 }
968 } else {
969 if (xtra > 0) {
970 bx += xtra;
971 xtra = 0;
972 }
973 // pushbutton
974
975 uint8_t can_hover = 1; // special case
976 if (_btns[i] == &_btn_ok) {
977 if (_fsel < 0 || _fsel >= _dircount) {
978 can_hover = 0;
979 }
980 }
981
982 if (can_hover && i == _hov_b) {
983 XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
984 } else {
985 XSetForeground (dpy, _fib_gc, _c_gray2.pixel);
986 }
987 XFillRectangle (dpy, win, _fib_gc,
988 bx + 1, bbase - _fib_font_ascent,
989 _btn_w - 1, _fib_font_height + BTNPADDING * 2 * _scalefactor);
990 VDrawRectangle (dpy, win, _fib_gc,
991 bx, bbase - _fib_font_ascent,
992 _btn_w, _fib_font_height + BTNPADDING * 2 * _scalefactor);
993 XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
994 XDrawString (dpy, win, _fib_gc, bx + (_btn_w - _btns[i]->tw) * .5, 1 + bbase + BTNPADDING * _scalefactor,
995 _btns[i]->text, strlen (_btns[i]->text));
996 }
997 _btns[i]->x0 = bx;
998 bx += _btns[i]->xw + DSEP * _scalefactor;
999 }
1000
1001 if (_pixbuffer != None) {
1002 XCopyArea(dpy, _pixbuffer, realwin, _fib_gc, 0, 0, _fib_width, _fib_height, 0, 0);
1003 }
1004 XFlush (dpy);
1005 }
1006
1007 static void fib_reset () {
1008 _hov_p = _hov_f = _hov_h = _hov_l = -1;
1009 _scrl_f = 0;
1010 _fib_resized = 1;
1011 }
1012
1013 static int cmp_n_up (const void *p1, const void *p2) {
1014 FibFileEntry *a = (FibFileEntry*) p1;
1015 FibFileEntry *b = (FibFileEntry*) p2;
1016 if ((a->flags & 4) && !(b->flags & 4)) return -1;
1017 if (!(a->flags & 4) && (b->flags & 4)) return 1;
1018 return strcmp (a->name, b->name);
1019 }
1020
1021 static int cmp_n_down (const void *p1, const void *p2) {
1022 FibFileEntry *a = (FibFileEntry*) p1;
1023 FibFileEntry *b = (FibFileEntry*) p2;
1024 if ((a->flags & 4) && !(b->flags & 4)) return -1;
1025 if (!(a->flags & 4) && (b->flags & 4)) return 1;
1026 return strcmp (b->name, a->name);
1027 }
1028
1029 static int cmp_t_up (const void *p1, const void *p2) {
1030 FibFileEntry *a = (FibFileEntry*) p1;
1031 FibFileEntry *b = (FibFileEntry*) p2;
1032 if ((a->flags & 4) && !(b->flags & 4)) return -1;
1033 if (!(a->flags & 4) && (b->flags & 4)) return 1;
1034 if (a->mtime == b->mtime) return 0;
1035 return a->mtime > b->mtime ? -1 : 1;
1036 }
1037
1038 static int cmp_t_down (const void *p1, const void *p2) {
1039 FibFileEntry *a = (FibFileEntry*) p1;
1040 FibFileEntry *b = (FibFileEntry*) p2;
1041 if ((a->flags & 4) && !(b->flags & 4)) return -1;
1042 if (!(a->flags & 4) && (b->flags & 4)) return 1;
1043 if (a->mtime == b->mtime) return 0;
1044 return a->mtime > b->mtime ? 1 : -1;
1045 }
1046
1047 static int cmp_s_up (const void *p1, const void *p2) {
1048 FibFileEntry *a = (FibFileEntry*) p1;
1049 FibFileEntry *b = (FibFileEntry*) p2;
1050 if ((a->flags & 4) && (b->flags & 4)) return 0; // dir, no size, retain order
1051 if ((a->flags & 4) && !(b->flags & 4)) return -1;
1052 if (!(a->flags & 4) && (b->flags & 4)) return 1;
1053 if (a->size == b->size) return 0;
1054 return a->size > b->size ? -1 : 1;
1055 }
1056
1057 static int cmp_s_down (const void *p1, const void *p2) {
1058 FibFileEntry *a = (FibFileEntry*) p1;
1059 FibFileEntry *b = (FibFileEntry*) p2;
1060 if ((a->flags & 4) && (b->flags & 4)) return 0; // dir, no size, retain order
1061 if ((a->flags & 4) && !(b->flags & 4)) return -1;
1062 if (!(a->flags & 4) && (b->flags & 4)) return 1;
1063 if (a->size == b->size) return 0;
1064 return a->size > b->size ? 1 : -1;
1065 }
1066
1067 static void fmt_size (Display *dpy, FibFileEntry *f) {
1068 if (f->size > 10995116277760) {
1069 sprintf (f->strsize, "%.0f TB", f->size / 1099511627776.f);
1070 }
1071 if (f->size > 1099511627776) {
1072 sprintf (f->strsize, "%.1f TB", f->size / 1099511627776.f);
1073 }
1074 else if (f->size > 10737418240) {
1075 sprintf (f->strsize, "%.0f GB", f->size / 1073741824.f);
1076 }
1077 else if (f->size > 1073741824) {
1078 sprintf (f->strsize, "%.1f GB", f->size / 1073741824.f);
1079 }
1080 else if (f->size > 10485760) {
1081 sprintf (f->strsize, "%.0f MB", f->size / 1048576.f);
1082 }
1083 else if (f->size > 1048576) {
1084 sprintf (f->strsize, "%.1f MB", f->size / 1048576.f);
1085 }
1086 else if (f->size > 10240) {
1087 sprintf (f->strsize, "%.0f KB", f->size / 1024.f);
1088 }
1089 else if (f->size >= 1000) {
1090 sprintf (f->strsize, "%.1f KB", f->size / 1024.f);
1091 }
1092 else {
1093 sprintf (f->strsize, "%.0f B", f->size / 1.f);
1094 }
1095 int sw = 0;
1096 query_font_geometry (dpy, _fib_gc, f->strsize, &sw, NULL, NULL, NULL);
1097 if (sw > _fib_font_size_width) {
1098 _fib_font_size_width = sw;
1099 }
1100 f->ssizew = sw;
1101 }
1102
1103 static void fmt_time (Display *dpy, FibFileEntry *f) {
1104 struct tm *tmp;
1105 tmp = localtime (&f->mtime);
1106 if (!tmp) {
1107 return;
1108 }
1109 strftime (f->strtime, sizeof(f->strtime), "%F %H:%M", tmp);
1110
1111 int tw = 0;
1112 query_font_geometry (dpy, _fib_gc, f->strtime, &tw, NULL, NULL, NULL);
1113 if (tw > _fib_font_time_width) {
1114 _fib_font_time_width = tw;
1115 }
1116 }
1117
1118 static void fib_resort (const char * sel) {
1119 if (_dircount < 1) { return; }
1120 int (*sortfn)(const void *p1, const void *p2);
1121 switch (_sort) {
1122 case 1: sortfn = &cmp_n_down; break;
1123 case 2: sortfn = &cmp_s_down; break;
1124 case 3: sortfn = &cmp_s_up; break;
1125 case 4: sortfn = &cmp_t_down; break;
1126 case 5: sortfn = &cmp_t_up; break;
1127 default:
1128 sortfn = &cmp_n_up;
1129 break;
1130 }
1131 qsort (_dirlist, _dircount, sizeof(_dirlist[0]), sortfn);
1132 int i;
1133 for (i = 0; i < _dircount && sel; ++i) {
1134 if (!strcmp (_dirlist[i].name, sel)) {
1135 _fsel = i;
1136 break;
1137 }
1138 }
1139 }
1140
1141 static void fib_select (Display *dpy, int item) {
1142 if (_fsel >= 0) {
1143 _dirlist[_fsel].flags &= ~2;
1144 }
1145 _fsel = item;
1146 if (_fsel >= 0 && _fsel < _dircount) {
1147 _dirlist[_fsel].flags |= 2;
1148 const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep;
1149 if (_fsel < _scrl_f) {
1150 _scrl_f = _fsel;
1151 }
1152 else if (_fsel >= _scrl_f + llen) {
1153 _scrl_f = 1 + _fsel - llen;
1154 }
1155 } else {
1156 _fsel = -1;
1157 }
1158
1159 fib_expose (dpy, _fib_win);
1160 }
1161
1162 static inline int fib_filter (const char *name) {
1163 if (_fib_filter_function) {
1164 return _fib_filter_function (name);
1165 } else {
1166 return 1;
1167 }
1168 }
1169
1170 static void fib_pre_opendir (Display *dpy) {
1171 if (_dirlist) free (_dirlist);
1172 if (_pathbtn) free (_pathbtn);
1173 _dirlist = NULL;
1174 _pathbtn = NULL;
1175 _dircount = 0;
1176 _pathparts = 0;
1177 query_font_geometry (dpy, _fib_gc, "Size ", &_fib_font_size_width, NULL, NULL, NULL);
1178 fib_reset ();
1179 _fsel = -1;
1180 }
1181
1182 static void fib_post_opendir (Display *dpy, const char *sel) {
1183 if (_dircount > 0)
1184 _fsel = 0; // select first
1185 else
1186 _fsel = -1;
1187 fib_resort (sel);
1188
1189 if (_dircount > 0 && _fsel >= 0) {
1190 fib_select (dpy, _fsel);
1191 } else {
1192 fib_expose (dpy, _fib_win);
1193 }
1194 }
1195
1196 static int fib_dirlistadd (Display *dpy, const int i, const char* path, const char *name, time_t mtime) {
1197 char tp[1024];
1198 struct stat fs;
1199 if (!_fib_hidden_fn && name[0] == '.') return -1;
1200 if (!strcmp (name, ".")) return -1;
1201 if (!strcmp (name, "..")) return -1;
1202 strcpy (tp, path);
1203 strcat (tp, name);
1204 if (access (tp, R_OK)) {
1205 return -1;
1206 }
1207 if (stat (tp, &fs)) {
1208 return -1;
1209 }
1210 assert (i < _dircount); // could happen if dir changes while we're reading.
1211 if (i >= _dircount) return -1;
1212 if (S_ISDIR (fs.st_mode)) {
1213 _dirlist[i].flags |= 4;
1214 }
1215 else if (S_ISREG (fs.st_mode)) {
1216 if (!fib_filter (name)) return -1;
1217 }
1218 #if 0 // only needed with lstat()
1219 else if (S_ISLNK (fs.st_mode)) {
1220 if (!fib_filter (name)) return -1;
1221 }
1222 #endif
1223 else {
1224 return -1;
1225 }
1226 strcpy (_dirlist[i].name, name);
1227 _dirlist[i].mtime = mtime > 0 ? mtime : fs.st_mtime;
1228 _dirlist[i].size = fs.st_size;
1229 if (!(_dirlist[i].flags & 4))
1230 fmt_size (dpy, &_dirlist[i]);
1231 fmt_time (dpy, &_dirlist[i]);
1232 return 0;
1233 }
1234
1235 static int fib_openrecent (Display *dpy, const char *sel) {
1236 int i;
1237 unsigned int j;
1238 assert (_recentcnt > 0);
1239 fib_pre_opendir (dpy);
1240 query_font_geometry (dpy, _fib_gc, "Last Used", &_fib_font_time_width, NULL, NULL, NULL);
1241 _dirlist = (FibFileEntry*) calloc (_recentcnt, sizeof(FibFileEntry));
1242 _dircount = _recentcnt;
1243 for (j = 0, i = 0; j < _recentcnt; ++j) {
1244 char base[1024];
1245 char *s = strrchr (_recentlist[j].path, '/');
1246 if (!s || !*++s) continue;
1247 size_t len = (s - _recentlist[j].path);
1248 strncpy (base, _recentlist[j].path, len);
1249 base[len] = '\0';
1250 if (!fib_dirlistadd (dpy, i, base, s, _recentlist[j].atime)) {
1251 _dirlist[i].rfp = &_recentlist[j];
1252 _dirlist[i].flags |= 8;
1253 ++i;
1254 }
1255 }
1256 _dircount = i;
1257 fib_post_opendir (dpy, sel);
1258 return _dircount;
1259 }
1260
1261 static int fib_opendir (Display *dpy, const char* path, const char *sel) {
1262 char *t0, *t1;
1263 int i;
1264
1265 assert (path);
1266
1267 if (strlen (path) == 0 && _recentcnt > 0) { // XXX we should use a better indication for this
1268 strcpy (_cur_path, "");
1269 return fib_openrecent (dpy, sel);
1270 }
1271
1272 assert (strlen (path) < sizeof(_cur_path) -1);
1273 assert (strlen (path) > 0);
1274 assert (strstr (path, "//") == NULL);
1275 assert (path[0] == '/');
1276
1277 fib_pre_opendir (dpy);
1278
1279 query_font_geometry (dpy, _fib_gc, "Last Modified", &_fib_font_time_width, NULL, NULL, NULL);
1280 DIR *dir = opendir (path);
1281 if (!dir) {
1282 strcpy (_cur_path, "/");
1283 } else {
1284 int i;
1285 struct dirent *de;
1286 if (path != _cur_path)
1287 strcpy (_cur_path, path);
1288
1289 if (_cur_path[strlen (_cur_path) -1] != '/')
1290 strcat (_cur_path, "/");
1291
1292 while ((de = readdir (dir))) {
1293 if (!_fib_hidden_fn && de->d_name[0] == '.') continue;
1294 ++_dircount;
1295 }
1296
1297 if (_dircount > 0)
1298 _dirlist = (FibFileEntry*) calloc (_dircount, sizeof(FibFileEntry));
1299
1300 rewinddir (dir);
1301
1302 i = 0;
1303 while ((de = readdir (dir))) {
1304 if (!fib_dirlistadd (dpy, i, _cur_path, de->d_name, 0))
1305 ++i;
1306 }
1307 _dircount = i;
1308 closedir (dir);
1309 }
1310
1311 t0 = _cur_path;
1312 while (*t0 && (t0 = strchr (t0, '/'))) {
1313 ++_pathparts;
1314 ++t0;
1315 }
1316 assert (_pathparts > 0);
1317 _pathbtn = (FibPathButton*) calloc (_pathparts + 1, sizeof(FibPathButton));
1318
1319 t1 = _cur_path;
1320 i = 0;
1321 while (*t1 && (t0 = strchr (t1, '/'))) {
1322 if (i == 0) {
1323 strcpy (_pathbtn[i].name, "/");
1324 } else {
1325 *t0 = 0;
1326 strcpy (_pathbtn[i].name, t1);
1327 }
1328 query_font_geometry (dpy, _fib_gc, _pathbtn[i].name, &_pathbtn[i].xw, NULL, NULL, NULL);
1329 _pathbtn[i].xw += BTNPADDING + BTNPADDING;
1330 *t0 = '/';
1331 t1 = t0 + 1;
1332 ++i;
1333 }
1334 fib_post_opendir (dpy, sel);
1335 return _dircount;
1336 }
1337
1338 static int fib_open (Display *dpy, int item) {
1339 char tp[1024];
1340 if (_dirlist[item].flags & 8) {
1341 assert (_dirlist[item].rfp);
1342 strcpy (_rv_open, _dirlist[item].rfp->path);
1343 _status = 1;
1344 return 0;
1345 }
1346 strcpy (tp, _cur_path);
1347 strcat (tp, _dirlist[item].name);
1348 if (_dirlist[item].flags & 4) {
1349 fib_opendir (dpy, tp, NULL);
1350 return 0;
1351 } else {
1352 _status = 1;
1353 strcpy (_rv_open, tp);
1354 }
1355 return 0;
1356 }
1357
1358 static void cb_cancel (Display *dpy) {
1359 _status = -1;
1360
1361 // unused
1362 return; (void)dpy;
1363 }
1364
1365 static void cb_open (Display *dpy) {
1366 if (_fsel >= 0 && _fsel < _dircount) {
1367 fib_open (dpy, _fsel);
1368 }
1369 }
1370
1371 static void sync_button_states () {
1372 if (_fib_show_places)
1373 _btn_places.flags |= 2;
1374 else
1375 _btn_places.flags &= ~2;
1376 if (_fib_filter_fn) // inverse -> show all
1377 _btn_filter.flags &= ~2;
1378 else
1379 _btn_filter.flags |= 2;
1380 if (_fib_hidden_fn)
1381 _btn_hidden.flags |= 2;
1382 else
1383 _btn_hidden.flags &= ~2;
1384 }
1385
1386 static void cb_places (Display *dpy) {
1387 _fib_show_places = ! _fib_show_places;
1388 if (_placecnt < 1)
1389 _fib_show_places = 0;
1390 sync_button_states ();
1391 _fib_resized = 1;
1392 fib_expose (dpy, _fib_win);
1393 }
1394
1395 static void cb_filter (Display *dpy) {
1396 _fib_filter_fn = ! _fib_filter_fn;
1397 sync_button_states ();
1398 char *sel = _fsel >= 0 ? strdup (_dirlist[_fsel].name) : NULL;
1399 fib_opendir (dpy, _cur_path, sel);
1400 free (sel);
1401 }
1402
1403 static void cb_hidden (Display *dpy) {
1404 _fib_hidden_fn = ! _fib_hidden_fn;
1405 sync_button_states ();
1406 char *sel = _fsel >= 0 ? strdup (_dirlist[_fsel].name) : NULL;
1407 fib_opendir (dpy, _cur_path, sel);
1408 free (sel);
1409 }
1410
1411 static int fib_widget_at_pos (Display *dpy, int x, int y, int *it) {
1412 const int btop = _fib_height - BTNBTMMARGIN * _fib_font_vsep - _fib_font_ascent - BTNPADDING * _scalefactor;
1413 const int bbot = btop + _fib_font_height + BTNPADDING * 2 * _scalefactor;
1414 const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep;
1415 const int ltop = LISTTOP * _fib_font_vsep;
1416 const int fbot = ltop + 4 * _scalefactor + llen * _fib_font_vsep;
1417 const int ptop = PATHBTNTOP - _fib_font_ascent;
1418 assert (it);
1419
1420 // paths at top
1421 if (y > ptop && y < ptop + _fib_font_height && _view_p >= 0 && _pathparts > 0) {
1422 int i = _view_p;
1423 *it = -1;
1424 if (i > 0) { // special case '<'
1425 if (x > FAREAMRGB * _scalefactor && x <= FAREAMRGB * _scalefactor + _pathbtn[0].xw) {
1426 *it = _view_p - 1;
1427 i = _pathparts;
1428 }
1429 }
1430 while (i < _pathparts) {
1431 if (x >= _pathbtn[i].x0 && x <= _pathbtn[i].x0 + _pathbtn[i].xw) {
1432 *it = i;
1433 break;
1434 }
1435 ++i;
1436 }
1437 assert (*it < _pathparts);
1438 if (*it >= 0) return 1;
1439 else return 0;
1440 }
1441
1442 // buttons at bottom
1443 if (y > btop && y < bbot) {
1444 size_t i;
1445 *it = -1;
1446 for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) {
1447 const int bx = _btns[i]->x0;
1448 if (_btns[i]->flags & 8) { continue; }
1449 if (x > bx && x < bx + _btns[i]->xw) {
1450 *it = i;
1451 }
1452 }
1453 if (*it >= 0) return 3;
1454 else return 0;
1455 }
1456
1457 // main file area
1458 if (y >= ltop - _fib_font_vsep && y < fbot && x > FAREAMRGL * _scalefactor && x < _fib_width - FAREAMRGR * _scalefactor) {
1459 // scrollbar
1460 if (_scrl_y0 > 0 && x >= _fib_width - (FAREAMRGR + SCROLLBARW) * _scalefactor && x <= _fib_width - FAREAMRGR * _scalefactor) {
1461 if (y >= _scrl_y0 && y < _scrl_y1) {
1462 *it = 0;
1463 } else if (y >= _scrl_y1) {
1464 *it = 2;
1465 } else {
1466 *it = 1;
1467 }
1468 return 4;
1469 }
1470 // file-list
1471 else if (y >= ltop) {
1472 const int item = (y - ltop) / _fib_font_vsep + _scrl_f;
1473 *it = -1;
1474 if (item >= 0 && item < _dircount) {
1475 *it = item;
1476 }
1477 if (*it >= 0) return 2;
1478 else return 0;
1479 }
1480 else {
1481 *it = -1;
1482 const int fsel_width = _fib_width - (FAREAMRGL + FAREAMRGR) * _scalefactor - (llen < _dircount ? SCROLLBARW * _scalefactor : 0);
1483 const int t_s = FAREAMRGL * _scalefactor + fsel_width - _fib_font_time_width - TEXTSEP * 2 * _scalefactor;
1484 const int t_t = FAREAMRGL * _scalefactor + fsel_width - TEXTSEP * _scalefactor - _fib_font_size_width - ((_columns & 2) ? ( _fib_font_time_width + TEXTSEP * 2 * _scalefactor) : 0);
1485 if (x >= fsel_width + FAREAMRGL * _scalefactor) ;
1486 else if ((_columns & 2) && x >= t_s) *it = 3;
1487 else if ((_columns & 1) && x >= t_t) *it = 2;
1488 else if (x >= FAREATEXTL * _scalefactor + _fib_dir_indent - TEXTSEP * _scalefactor) *it = 1;
1489 if (*it >= 0) return 5;
1490 else return 0;
1491 }
1492 }
1493
1494 // places list
1495 if (_fib_show_places && y >= ltop && y < fbot && x > FAREAMRGB * _scalefactor && x < (FAREAMRGL - FAREAMRGB) * _scalefactor) {
1496 const int item = (y - ltop) / _fib_font_vsep;
1497 *it = -1;
1498 if (item >= 0 && item < _placecnt) {
1499 *it = item;
1500 }
1501 if (*it >= 0) return 6;
1502 else return 0;
1503 }
1504
1505 return 0;
1506
1507 // unused
1508 (void)dpy;
1509 }
1510
1511 static void fib_update_hover (Display *dpy, int need_expose, const int type, const int item) {
1512 int hov_p = -1;
1513 int hov_b = -1;
1514 int hov_h = -1;
1515 int hov_s = -1;
1516 #ifdef LIST_ENTRY_HOVER
1517 int hov_f = -1;
1518 int hov_l = -1;
1519 #endif
1520
1521 switch (type) {
1522 case 1: hov_p = item; break;
1523 case 3: hov_b = item; break;
1524 case 4: hov_s = item; break;
1525 case 5: hov_h = item; break;
1526 #ifdef LIST_ENTRY_HOVER
1527 case 6: hov_l = item; break;
1528 case 2: hov_f = item; break;
1529 #endif
1530 default: break;
1531 }
1532 #ifdef LIST_ENTRY_HOVER
1533 if (hov_f != _hov_f) { _hov_f = hov_f; need_expose = 1; }
1534 if (hov_l != _hov_l) { _hov_l = hov_l; need_expose = 1; }
1535 #endif
1536 if (hov_b != _hov_b) { _hov_b = hov_b; need_expose = 1; }
1537 if (hov_p != _hov_p) { _hov_p = hov_p; need_expose = 1; }
1538 if (hov_h != _hov_h) { _hov_h = hov_h; need_expose = 1; }
1539 if (hov_s != _hov_s) { _hov_s = hov_s; need_expose = 1; }
1540
1541 if (need_expose) {
1542 fib_expose (dpy, _fib_win);
1543 }
1544 }
1545
1546 static void fib_motion (Display *dpy, int x, int y) {
1547 int it = -1;
1548
1549 if (_scrl_my >= 0) {
1550 const int sdiff = y - _scrl_my;
1551 const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep;
1552 const int fsel_height = 4 + llen * _fib_font_vsep;
1553 const float sl = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH)) / (float) _dircount;
1554
1555 int news = _scrl_mf + sdiff / sl;
1556 if (news < 0) news = 0;
1557 if (news >= (_dircount - llen)) news = _dircount - llen;
1558 if (news != _scrl_f) {
1559 _scrl_f = news;
1560 fib_expose (dpy, _fib_win);
1561 }
1562 return;
1563 }
1564
1565 const int type = fib_widget_at_pos (dpy, x, y, &it);
1566 fib_update_hover (dpy, 0, type, it);
1567 }
1568
1569 static void fib_mousedown (Display *dpy, int x, int y, int btn, unsigned long time) {
1570 int it;
1571 switch (fib_widget_at_pos (dpy, x, y, &it)) {
1572 case 4: // scrollbar
1573 if (btn == 1) {
1574 _dblclk = 0;
1575 if (it == 0) {
1576 _scrl_my = y;
1577 _scrl_mf = _scrl_f;
1578 } else {
1579 int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep;
1580 if (llen < 2) llen = 2;
1581 int news = _scrl_f;
1582 if (it == 1) {
1583 news -= llen - 1;
1584 } else {
1585 news += llen - 1;
1586 }
1587 if (news < 0) news = 0;
1588 if (news >= (_dircount - llen)) news = _dircount - llen;
1589 if (news != _scrl_f && _scrl_y0 >= 0) {
1590 assert (news >=0);
1591 _scrl_f = news;
1592 fib_update_hover (dpy, 1, 4, it);
1593 }
1594 }
1595 }
1596 break;
1597 case 2: // file-list
1598 if (btn == 4 || btn == 5) {
1599 const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep;
1600 int news = _scrl_f + ((btn == 4) ? - 1 : 1);
1601 if (news < 0) news = 0;
1602 if (news >= (_dircount - llen)) news = _dircount - llen;
1603 if (news != _scrl_f && _scrl_y0 >= 0) {
1604 assert (news >=0);
1605 _scrl_f = news;
1606 fib_update_hover (dpy, 1, 0, 0);
1607 }
1608 _dblclk = 0;
1609 }
1610 else if (btn == 1 && it >= 0 && it < _dircount) {
1611 if (_fsel == it) {
1612 if (time - _dblclk < DBLCLKTME) {
1613 fib_open (dpy, it);
1614 _dblclk = 0;
1615 }
1616 _dblclk = time;
1617 } else {
1618 fib_select (dpy, it);
1619 _dblclk = time;
1620 }
1621 /*
1622 if (_fsel >= 0) {
1623 if (!(_dirlist[_fsel].flags & 4));
1624 }
1625 */
1626 }
1627 break;
1628 case 1: // paths
1629 assert (_fsel < _dircount);
1630 assert (it >= 0 && it < _pathparts);
1631 {
1632 int i = 0;
1633 char path[1024] = "/";
1634 while (++i <= it) {
1635 strcat (path, _pathbtn[i].name);
1636 strcat (path, "/");
1637 }
1638 char *sel = NULL;
1639 if (i < _pathparts)
1640 sel = strdup (_pathbtn[i].name);
1641 else if (i == _pathparts && _fsel >= 0)
1642 sel = strdup (_dirlist[_fsel].name);
1643 fib_opendir (dpy, path, sel);
1644 free (sel);
1645 }
1646 break;
1647 case 3: // btn
1648 if (btn == 1 && _btns[it]->callback) {
1649 _btns[it]->callback (dpy);
1650 }
1651 break;
1652 case 5: // sort
1653 if (btn == 1) {
1654 switch (it) {
1655 case 1: if (_sort == 0) _sort = 1; else _sort = 0; break;
1656 case 2: if (_sort == 2) _sort = 3; else _sort = 2; break;
1657 case 3: if (_sort == 4) _sort = 5; else _sort = 4; break;
1658 }
1659 if (_fsel >= 0) {
1660 assert (_dirlist && _dircount >= _fsel);
1661 _dirlist[_fsel].flags &= ~2;
1662 char *sel = strdup (_dirlist[_fsel].name);
1663 fib_resort (sel);
1664 free (sel);
1665 } else {
1666 fib_resort (NULL);
1667 _fsel = -1;
1668 }
1669 fib_reset ();
1670 _hov_h = it;
1671 fib_select (dpy, _fsel);
1672 }
1673 break;
1674 case 6:
1675 if (btn == 1 && it >= 0 && it < _placecnt) {
1676 fib_opendir (dpy, _placelist[it].path, NULL);
1677 }
1678 break;
1679 default:
1680 break;
1681 }
1682 }
1683
1684 static void fib_mouseup (Display *dpy, int x, int y, int btn, unsigned long time) {
1685 _scrl_my = -1;
1686
1687 // unused
1688 return; (void)dpy; (void)x; (void)y; (void)btn; (void)time;
1689 }
1690
1691 static void add_place_raw (Display *dpy, const char *name, const char *path) {
1692 _placelist = (FibPlace*) realloc (_placelist, (_placecnt + 1) * sizeof(FibPlace));
1693 strcpy (_placelist[_placecnt].path, path);
1694 strcpy (_placelist[_placecnt].name, name);
1695 _placelist[_placecnt].flags = 0;
1696
1697 int sw = -1;
1698 query_font_geometry (dpy, _fib_gc, name, &sw, NULL, NULL, NULL);
1699 if (sw > _fib_place_width) {
1700 _fib_place_width = sw;
1701 }
1702 ++_placecnt;
1703 }
1704
1705 static int add_place_places (Display *dpy, const char *name, const char *url) {
1706 char const * path;
1707 struct stat fs;
1708 int i;
1709 if (!url || strlen (url) < 1) return -1;
1710 if (!name || strlen (name) < 1) return -1;
1711 if (url[0] == '/') {
1712 path = url;
1713 }
1714 else if (!strncmp (url, "file:///", 8)) {
1715 path = &url[7];
1716 }
1717 else {
1718 return -1;
1719 }
1720
1721 if (access (path, R_OK)) {
1722 return -1;
1723 }
1724 if (stat (path, &fs)) {
1725 return -1;
1726 }
1727 if (!S_ISDIR (fs.st_mode)) {
1728 return -1;
1729 }
1730
1731 for (i = 0; i < _placecnt; ++i) {
1732 if (!strcmp (path, _placelist[i].path)) {
1733 return -1;
1734 }
1735 }
1736 add_place_raw (dpy, name, path);
1737 return 0;
1738 }
1739
1740 static int parse_gtk_bookmarks (Display *dpy, const char *fn) {
1741 char tmp[1024];
1742 if (access (fn, R_OK)) {
1743 return -1;
1744 }
1745 FILE *bm = fopen (fn, "r");
1746 if (!bm) return -1;
1747 int found = 0;
1748 while (fgets (tmp, sizeof(tmp), bm)
1749 && strlen (tmp) > 1
1750 && strlen (tmp) < sizeof(tmp))
1751 {
1752 char *s, *n;
1753 tmp[strlen (tmp) - 1] = '\0'; // strip newline
1754 if ((s = strchr (tmp, ' '))) {
1755 *s = '\0';
1756 n = strdup (++s);
1757 decode_3986 (tmp);
1758 if (!add_place_places (dpy, n, tmp)) {
1759 ++found;
1760 }
1761 free (n);
1762 } else if ((s = strrchr (tmp, '/'))) {
1763 n = strdup (++s);
1764 decode_3986 (tmp);
1765 if (!add_place_places (dpy, n, tmp)) {
1766 ++found;
1767 }
1768 free (n);
1769 }
1770 }
1771 fclose (bm);
1772 return found;
1773 }
1774
1775 #ifdef HAVE_MNTENT
1776 static const char *ignore_mountpoints[] = {
1777 "/bin", "/boot", "/dev", "/etc",
1778 "/lib", "/live", "/mnt", "/opt",
1779 "/root", "/sbin", "/srv", "/tmp",
1780 "/usr", "/var", "/proc", "/sbin",
1781 "/net", "/sys"
1782 };
1783
1784 static const char *ignore_fs[] = {
1785 "auto", "autofs",
1786 "debugfs", "devfs",
1787 "devpts", "ecryptfs",
1788 "fusectl", "kernfs",
1789 "linprocfs", "proc",
1790 "ptyfs", "rootfs",
1791 "selinuxfs", "sysfs",
1792 "tmpfs", "usbfs",
1793 "nfsd", "rpc_pipefs",
1794 };
1795
1796 static const char *ignore_devices[] = {
1797 "binfmt_", "devpts",
1798 "gvfs", "none",
1799 "nfsd", "sunrpc",
1800 "/dev/loop", "/dev/vn"
1801 };
1802
1803 static int check_mount (const char *mountpoint, const char *fs, const char *device) {
1804 size_t i;
1805 if (!mountpoint || !fs || !device) return -1;
1806 //printf("%s %s %s\n", mountpoint, fs, device);
1807 for (i = 0 ; i < sizeof(ignore_mountpoints) / sizeof(char*); ++i) {
1808 if (!strncmp (mountpoint, ignore_mountpoints[i], strlen (ignore_mountpoints[i]))) {
1809 return 1;
1810 }
1811 }
1812 if (!strncmp (mountpoint, "/home", 5)) {
1813 return 1;
1814 }
1815 for (i = 0 ; i < sizeof(ignore_fs) / sizeof(char*); ++i) {
1816 if (!strncmp (fs, ignore_fs[i], strlen (ignore_fs[i]))) {
1817 return 1;
1818 }
1819 }
1820 for (i = 0 ; i < sizeof(ignore_devices) / sizeof(char*); ++i) {
1821 if (!strncmp (device, ignore_devices[i], strlen (ignore_devices[i]))) {
1822 return 1;
1823 }
1824 }
1825 return 0;
1826 }
1827
1828 static int read_mtab (Display *dpy, const char *mtab) {
1829 FILE *mt = fopen (mtab, "r");
1830 if (!mt) return -1;
1831 int found = 0;
1832 struct mntent *mntent;
1833 while ((mntent = getmntent (mt)) != NULL) {
1834 char *s;
1835 if (check_mount (mntent->mnt_dir, mntent->mnt_type, mntent->mnt_fsname))
1836 continue;
1837
1838 if ((s = strrchr (mntent->mnt_dir, '/'))) {
1839 ++s;
1840 } else {
1841 s = mntent->mnt_dir;
1842 }
1843 if (!add_place_places (dpy, s, mntent->mnt_dir)) {
1844 ++found;
1845 }
1846 }
1847 fclose (mt);
1848 return found;
1849 }
1850 #endif
1851
1852 static void populate_places (Display *dpy) {
1853 char tmp[1024];
1854 int spacer = -1;
1855 if (_placecnt > 0) return;
1856 _fib_place_width = 0;
1857
1858 if (_recentcnt > 0) {
1859 add_place_raw (dpy, "Recently Used", "");
1860 _placelist[0].flags |= 4;
1861 }
1862
1863 add_place_places (dpy, "Home", getenv ("HOME"));
1864
1865 if (getenv ("HOME")) {
1866 strcpy (tmp, getenv ("HOME"));
1867 strcat (tmp, "/Desktop");
1868 add_place_places (dpy, "Desktop", tmp);
1869 }
1870
1871 add_place_places (dpy, "Filesystem", "/");
1872
1873 if (_placecnt > 0) spacer = _placecnt -1;
1874
1875 if (strlen (_fib_cfg_custom_places) > 0) {
1876 parse_gtk_bookmarks (dpy, _fib_cfg_custom_places);
1877 }
1878
1879 #ifdef HAVE_MNTENT
1880 if (read_mtab (dpy, "/proc/mounts") < 1) {
1881 read_mtab (dpy, "/etc/mtab");
1882 }
1883 #endif
1884
1885 int parsed_bookmarks = 0;
1886 if (!parsed_bookmarks && getenv ("HOME")) {
1887 strcpy (tmp, getenv ("HOME"));
1888 strcat (tmp, "/.gtk-bookmarks");
1889 if (parse_gtk_bookmarks (dpy, tmp) > 0) {
1890 parsed_bookmarks = 1;
1891 }
1892 }
1893 if (!parsed_bookmarks && getenv ("XDG_CONFIG_HOME")) {
1894 strcpy (tmp, getenv ("XDG_CONFIG_HOME"));
1895 strcat (tmp, "/gtk-3.0/bookmarks");
1896 if (parse_gtk_bookmarks (dpy, tmp) > 0) {
1897 parsed_bookmarks = 1;
1898 }
1899 }
1900 if (!parsed_bookmarks && getenv ("HOME")) {
1901 strcpy (tmp, getenv ("HOME"));
1902 strcat (tmp, "/.config/gtk-3.0/bookmarks");
1903 if (parse_gtk_bookmarks (dpy, tmp) > 0) {
1904 parsed_bookmarks = 1;
1905 }
1906 }
1907 if (_fib_place_width > 0) {
1908 _fib_place_width = MIN (_fib_place_width + TEXTSEP + _fib_dir_indent /*extra*/ , PLACESWMAX);
1909 }
1910 if (spacer > 0 && spacer < _placecnt -1) {
1911 _placelist[ spacer ].flags |= 4;
1912 }
1913 }
1914
1915 static uint8_t font_err = 0;
1916 static int x_error_handler (Display *d, XErrorEvent *e) {
1917 font_err = 1;
1918 return 0;
1919
1920 // unused
1921 (void)d; (void)e;
1922 }
1923
1924 int x_fib_show (Display *dpy, Window parent, int x, int y, double scalefactor) {
1925 if (_fib_win) {
1926 XSetInputFocus (dpy, _fib_win, RevertToParent, CurrentTime);
1927 return -1;
1928 }
1929
1930 _status = 0;
1931 _rv_open[0] = '\0';
1932
1933 Colormap colormap = DefaultColormap (dpy, DefaultScreen (dpy));
1934 _c_gray1.flags = DoRed | DoGreen | DoBlue;
1935 _c_gray0.red = _c_gray0.green = _c_gray0.blue = 0x5000; // 95% hover prelight
1936 _c_gray1.red = _c_gray1.green = _c_gray1.blue = 0x1100; // 93% window bg, scrollbar-fg
1937 _c_gray2.red = _c_gray2.green = _c_gray2.blue = 0x1c00; // 83% button & list bg
1938 _c_gray3.red = _c_gray3.green = _c_gray3.blue = 0x0a00; // 75% heading + scrollbar-bg
1939 _c_gray4.red = _c_gray4.green = _c_gray4.blue = 0xd600; // 40% prelight text, sep lines
1940 _c_gray5.red = _c_gray5.green = _c_gray5.blue = 0x3000; // 20% 3D border
1941
1942 if (!XAllocColor (dpy, colormap, &_c_gray0)) return -1;
1943 if (!XAllocColor (dpy, colormap, &_c_gray1)) return -1;
1944 if (!XAllocColor (dpy, colormap, &_c_gray2)) return -1;
1945 if (!XAllocColor (dpy, colormap, &_c_gray3)) return -1;
1946 if (!XAllocColor (dpy, colormap, &_c_gray4)) return -1;
1947 if (!XAllocColor (dpy, colormap, &_c_gray5)) return -1;
1948
1949 XSetWindowAttributes attr;
1950 memset (&attr, 0, sizeof(XSetWindowAttributes));
1951 attr.border_pixel = _c_gray2.pixel;
1952
1953 attr.event_mask = ExposureMask | KeyPressMask
1954 | ButtonPressMask | ButtonReleaseMask
1955 | ConfigureNotify | StructureNotifyMask
1956 | PointerMotionMask | LeaveWindowMask;
1957
1958 _fib_win = XCreateWindow (
1959 dpy, DefaultRootWindow (dpy),
1960 x, y, _fib_width * scalefactor, _fib_height * scalefactor,
1961 1, CopyFromParent, InputOutput, CopyFromParent,
1962 CWEventMask | CWBorderPixel, &attr);
1963
1964 _scalefactor = scalefactor;
1965
1966 if (!_fib_win) { return 1; }
1967
1968 if (parent)
1969 XSetTransientForHint (dpy, _fib_win, parent);
1970
1971 XStoreName (dpy, _fib_win, "Select File");
1972
1973 Atom wmDelete = XInternAtom (dpy, "WM_DELETE_WINDOW", True);
1974 XSetWMProtocols (dpy, _fib_win, &wmDelete, 1);
1975
1976 _fib_gc = XCreateGC (dpy, _fib_win, 0, NULL);
1977 XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter);
1978 const char dl[1] = {1};
1979 XSetDashes (dpy, _fib_gc, 0, dl, 1);
1980
1981 int (*handler)(Display *, XErrorEvent *) = XSetErrorHandler (&x_error_handler);
1982
1983 #define _XTESTFONT(FN) \
1984 { \
1985 font_err = 0; \
1986 _fibfont = XLoadFont (dpy, FN); \
1987 XSetFont (dpy, _fib_gc, _fibfont); \
1988 XSync (dpy, False); \
1989 }
1990
1991 font_err = 1;
1992 if (getenv ("XJFONT")) _XTESTFONT (getenv ("XJFONT"));
1993 if (font_err && strlen (_fib_cfg_custom_font) > 0) _XTESTFONT (_fib_cfg_custom_font);
1994 if (scalefactor >= 2.5) {
1995 if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-18-*-*-*-*-*-*-*");
1996 if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-18-*-*-*-*-*-*-*");
1997 if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-20-*-*-*-*-*-*-*");
1998 if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*");
1999 } else if (scalefactor >= 2) {
2000 if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-16-*-*-*-*-*-*-*");
2001 if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-16-*-*-*-*-*-*-*");
2002 if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*");
2003 if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-16-*-*-*-*-*-*-*");
2004 } else if (scalefactor >= 1.5) {
2005 if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-14-*-*-*-*-*-*-*");
2006 if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-14-*-*-*-*-*-*-*");
2007 if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-15-*-*-*-*-*-*-*");
2008 if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-14-*-*-*-*-*-*-*");
2009 } else {
2010 if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-12-*-*-*-*-*-*-*");
2011 if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-12-*-*-*-*-*-*-*");
2012 if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-13-*-*-*-*-*-*-*");
2013 if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-12-*-*-*-*-*-*-*");
2014 }
2015 if (font_err) _fibfont = None;
2016 XSync (dpy, False);
2017 XSetErrorHandler (handler);
2018
2019 if (_fib_font_height == 0) { // 1st time only
2020 query_font_geometry (dpy, _fib_gc, "D ", &_fib_dir_indent, NULL, NULL, NULL);
2021 query_font_geometry (dpy, _fib_gc, "_", &_fib_spc_norm, NULL, NULL, NULL);
2022 if (query_font_geometry (dpy, _fib_gc, "|0Yy", NULL, &_fib_font_height, &_fib_font_ascent, NULL)) {
2023 XFreeGC (dpy, _fib_gc);
2024 XDestroyWindow (dpy, _fib_win);
2025 _fib_win = 0;
2026 return -1;
2027 }
2028 _fib_font_height += 3 * scalefactor;
2029 _fib_font_ascent += 2 * scalefactor;
2030 _fib_font_vsep = _fib_font_height + 2 * scalefactor;
2031 }
2032
2033 populate_places (dpy);
2034
2035 strcpy (_btn_ok.text, "Open");
2036 strcpy (_btn_cancel.text, "Cancel");
2037 strcpy (_btn_filter.text, "List All Files");
2038 strcpy (_btn_places.text, "Show Places");
2039 strcpy (_btn_hidden.text, "Show Hidden");
2040
2041 _btn_ok.callback = &cb_open;
2042 _btn_cancel.callback = &cb_cancel;
2043 _btn_filter.callback = &cb_filter;
2044 _btn_places.callback = &cb_places;
2045 _btn_hidden.callback = &cb_hidden;
2046 _btn_filter.flags |= 4;
2047 _btn_places.flags |= 4;
2048 _btn_hidden.flags |= 4;
2049
2050 if (!_fib_filter_function) {
2051 _btn_filter.flags |= 8;
2052 }
2053
2054 size_t i;
2055 int btncnt = 0;
2056 _btn_w = 0;
2057 _btn_span = 0;
2058 for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) {
2059 if (_btns[i]->flags & 8) { continue; }
2060 query_font_geometry (dpy, _fib_gc, _btns[i]->text, &_btns[i]->tw, NULL, NULL, NULL);
2061 if (_btns[i]->flags & 4) {
2062 _btn_span += _btns[i]->tw + _fib_font_ascent + TEXTSEP * scalefactor;
2063 } else {
2064 ++btncnt;
2065 if (_btns[i]->tw > _btn_w)
2066 _btn_w = _btns[i]->tw;
2067 }
2068 }
2069
2070 _btn_w += (BTNPADDING + BTNPADDING + TEXTSEP + TEXTSEP + TEXTSEP) * scalefactor;
2071 _btn_span += _btn_w * btncnt + DSEP * scalefactor * (i - 1) + (FAREAMRGR + FAREAMRGB) * scalefactor;
2072
2073 for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) {
2074 if (_btns[i]->flags & 8) { continue; }
2075 if (_btns[i]->flags & 4) {
2076 _btns[i]->xw = _btns[i]->tw + _fib_font_ascent + TEXTSEP * scalefactor;
2077 } else {
2078 _btns[i]->xw = _btn_w;
2079 }
2080 }
2081
2082 sync_button_states () ;
2083
2084 _fib_height = _fib_font_vsep * 15.8 * (1.0 + (scalefactor - 1.0) / 2.0);
2085 _fib_width = MAX (_btn_span, 480 * scalefactor);
2086
2087 XResizeWindow (dpy, _fib_win, _fib_width, _fib_height);
2088
2089 XTextProperty x_wname, x_iname;
2090 XSizeHints hints;
2091 XWMHints wmhints;
2092
2093 hints.flags = PSize | PMinSize;
2094 hints.min_width = _btn_span;
2095 hints.min_height = 8 * _fib_font_vsep;
2096
2097 char *w_name = & _fib_cfg_title[0];
2098
2099 wmhints.input = True;
2100 wmhints.flags = InputHint;
2101 if (XStringListToTextProperty (&w_name, 1, &x_wname) &&
2102 XStringListToTextProperty (&w_name, 1, &x_iname))
2103 {
2104 XSetWMProperties (dpy, _fib_win, &x_wname, &x_iname, NULL, 0, &hints, &wmhints, NULL);
2105 XFree (x_wname.value);
2106 XFree (x_iname.value);
2107 }
2108
2109 XSetWindowBackground (dpy, _fib_win, _c_gray1.pixel);
2110
2111 _fib_mapped = 0;
2112 XMapRaised (dpy, _fib_win);
2113
2114 if (!strlen (_cur_path) || !fib_opendir (dpy, _cur_path, NULL)) {
2115 fib_opendir (dpy, getenv ("HOME") ? getenv ("HOME") : "/", NULL);
2116 }
2117
2118 #if 0
2119 XGrabPointer (dpy, _fib_win, True,
2120 ButtonReleaseMask | ButtonPressMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | StructureNotifyMask,
2121 GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
2122 XGrabKeyboard (dpy, _fib_win, True, GrabModeAsync, GrabModeAsync, CurrentTime);
2123 //XSetInputFocus (dpy, parent, RevertToNone, CurrentTime);
2124 #endif
2125 _recentlock = 1;
2126 return 0;
2127 }
2128
2129 void x_fib_close (Display *dpy) {
2130 if (!_fib_win) return;
2131 XFreeGC (dpy, _fib_gc);
2132 XDestroyWindow (dpy, _fib_win);
2133 _fib_win = 0;
2134 free (_dirlist);
2135 _dirlist = NULL;
2136 free (_pathbtn);
2137 _pathbtn = NULL;
2138 if (_fibfont != None) XUnloadFont (dpy, _fibfont);
2139 _fibfont = None;
2140 free (_placelist);
2141 _placelist = NULL;
2142 _dircount = 0;
2143 _pathparts = 0;
2144 _placecnt = 0;
2145 if (_pixbuffer != None) XFreePixmap (dpy, _pixbuffer);
2146 _pixbuffer = None;
2147 Colormap colormap = DefaultColormap (dpy, DefaultScreen (dpy));
2148 XFreeColors (dpy, colormap, &_c_gray0.pixel, 1, 0);
2149 XFreeColors (dpy, colormap, &_c_gray1.pixel, 1, 0);
2150 XFreeColors (dpy, colormap, &_c_gray2.pixel, 1, 0);
2151 XFreeColors (dpy, colormap, &_c_gray3.pixel, 1, 0);
2152 XFreeColors (dpy, colormap, &_c_gray4.pixel, 1, 0);
2153 XFreeColors (dpy, colormap, &_c_gray5.pixel, 1, 0);
2154 _recentlock = 0;
2155 }
2156
2157 int x_fib_handle_events (Display *dpy, XEvent *event) {
2158 if (!_fib_win) return 0;
2159 if (_status) return 0;
2160 if (event->xany.window != _fib_win) {
2161 return 0;
2162 }
2163
2164 switch (event->type) {
2165 case MapNotify:
2166 _fib_mapped = 1;
2167 break;
2168 case UnmapNotify:
2169 _fib_mapped = 0;
2170 break;
2171 case LeaveNotify:
2172 fib_update_hover (dpy, 1, 0, 0);
2173 break;
2174 case ClientMessage:
2175 if (!strcmp (XGetAtomName (dpy, event->xclient.message_type), "WM_PROTOCOLS")) {
2176 _status = -1;
2177 }
2178 break;
2179 case ConfigureNotify:
2180 if (
2181 (event->xconfigure.width > 1 && event->xconfigure.height > 1)
2182 &&
2183 (event->xconfigure.width != _fib_width || event->xconfigure.height != _fib_height)
2184 )
2185 {
2186 _fib_width = event->xconfigure.width;
2187 _fib_height = event->xconfigure.height;
2188 _fib_resized = 1;
2189 }
2190 break;
2191 case Expose:
2192 if (event->xexpose.count == 0) {
2193 fib_expose (dpy, event->xany.window);
2194 }
2195 break;
2196 case MotionNotify:
2197 fib_motion (dpy, event->xmotion.x, event->xmotion.y);
2198 if (event->xmotion.is_hint == NotifyHint) {
2199 XGetMotionEvents (dpy, event->xany.window, CurrentTime, CurrentTime, NULL);
2200 }
2201 break;
2202 case ButtonPress:
2203 fib_mousedown (dpy, event->xbutton.x, event->xbutton.y, event->xbutton.button, event->xbutton.time);
2204 break;
2205 case ButtonRelease:
2206 fib_mouseup (dpy, event->xbutton.x, event->xbutton.y, event->xbutton.button, event->xbutton.time);
2207 break;
2208 case KeyRelease:
2209 break;
2210 case KeyPress:
2211 {
2212 KeySym key;
2213 char buf[100];
2214 static XComposeStatus stat;
2215 XLookupString (&event->xkey, buf, sizeof(buf), &key, &stat);
2216 switch (key) {
2217 case XK_Escape:
2218 _status = -1;
2219 break;
2220 case XK_Up:
2221 if (_fsel > 0) {
2222 fib_select (dpy, _fsel - 1);
2223 }
2224 break;
2225 case XK_Down:
2226 if (_fsel < _dircount -1) {
2227 fib_select ( dpy, _fsel + 1);
2228 }
2229 break;
2230 case XK_Page_Up:
2231 if (_fsel > 0) {
2232 int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep;
2233 if (llen < 1) llen = 1; else --llen;
2234 int fs = MAX (0, _fsel - llen);
2235 fib_select ( dpy, fs);
2236 }
2237 break;
2238 case XK_Page_Down:
2239 if (_fsel < _dircount) {
2240 int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep;
2241 if (llen < 1) llen = 1; else --llen;
2242 int fs = MIN (_dircount - 1, _fsel + llen);
2243 fib_select ( dpy, fs);
2244 }
2245 break;
2246 case XK_Left:
2247 if (_pathparts > 1) {
2248 int i = 0;
2249 char path[1024] = "/";
2250 while (++i < _pathparts - 1) {
2251 strcat (path, _pathbtn[i].name);
2252 strcat (path, "/");
2253 }
2254 char *sel = strdup (_pathbtn[_pathparts-1].name);
2255 fib_opendir (dpy, path, sel);
2256 free (sel);
2257 }
2258 break;
2259 case XK_Right:
2260 if (_fsel >= 0 && _fsel < _dircount) {
2261 if (_dirlist[_fsel].flags & 4) {
2262 cb_open (dpy);
2263 }
2264 }
2265 break;
2266 case XK_Return:
2267 cb_open (dpy);
2268 break;
2269 default:
2270 if ((key >= XK_a && key <= XK_z) || (key >= XK_0 && key <= XK_9)) {
2271 int i;
2272 for (i = 0; i < _dircount; ++i) {
2273 int j = (_fsel + i + 1) % _dircount;
2274 char kcmp = _dirlist[j].name[0];
2275 if (kcmp > 0x40 && kcmp <= 0x5A) kcmp |= 0x20;
2276 if (kcmp == (char)key) {
2277 fib_select ( dpy, j);
2278 break;
2279 }
2280 }
2281 }
2282 break;
2283 }
2284 }
2285 break;
2286 }
2287
2288 if (_status) {
2289 x_fib_close (dpy);
2290 }
2291 return _status;
2292 }
2293
2294 int x_fib_status () {
2295 return _status;
2296 }
2297
2298 int x_fib_configure (int k, const char *v) {
2299 if (_fib_win) { return -1; }
2300 switch (k) {
2301 case 0:
2302 if (strlen (v) >= sizeof(_cur_path) -1) return -2;
2303 if (strlen (v) < 1) return -2;
2304 if (v[0] != '/') return -2;
2305 if (strstr (v, "//")) return -2;
2306 strncpy (_cur_path, v, sizeof(_cur_path));
2307 break;
2308 case 1:
2309 if (strlen (v) >= sizeof(_fib_cfg_title) -1) return -2;
2310 strncpy (_fib_cfg_title, v, sizeof(_fib_cfg_title));
2311 break;
2312 case 2:
2313 if (strlen (v) >= sizeof(_fib_cfg_custom_font) -1) return -2;
2314 strncpy (_fib_cfg_custom_font, v, sizeof(_fib_cfg_custom_font));
2315 break;
2316 case 3:
2317 if (strlen (v) >= sizeof(_fib_cfg_custom_places) -1) return -2;
2318 strncpy (_fib_cfg_custom_places, v, sizeof(_fib_cfg_custom_places));
2319 break;
2320 default:
2321 return -2;
2322 }
2323 return 0;
2324 }
2325
2326 int x_fib_cfg_buttons (int k, int v) {
2327 if (_fib_win) { return -1; }
2328 switch (k) {
2329 case 1:
2330 if (v < 0) {
2331 _btn_hidden.flags |= 8;
2332 } else {
2333 _btn_hidden.flags &= ~8;
2334 }
2335 if (v == 1) {
2336 _btn_hidden.flags |= 2;
2337 _fib_hidden_fn = 1;
2338 } else if (v == 0) {
2339 _btn_hidden.flags &= 2;
2340 _fib_hidden_fn = 0;
2341 }
2342 break;
2343 case 2:
2344 if (v < 0) {
2345 _btn_places.flags |= 8;
2346 } else {
2347 _btn_places.flags &= ~8;
2348 }
2349 if (v == 1) {
2350 _btn_places.flags |= 2;
2351 _fib_show_places = 1;
2352 } else if (v == 0) {
2353 _btn_places.flags &= ~2;
2354 _fib_show_places = 0;
2355 }
2356 break;
2357 case 3:
2358 // NB. filter button is automatically hidden
2359 // IFF the filter-function is NULL.
2360 if (v < 0) {
2361 _btn_filter.flags |= 8;
2362 } else {
2363 _btn_filter.flags &= ~8;
2364 }
2365 if (v == 1) {
2366 _btn_filter.flags &= ~2; // inverse - 'show all' = !filter
2367 _fib_filter_fn = 1;
2368 } else if (v == 0) {
2369 _btn_filter.flags |= 2;
2370 _fib_filter_fn = 0;
2371 }
2372 break;
2373 default:
2374 return -2;
2375 }
2376 return 0;
2377 }
2378
2379 int x_fib_cfg_filter_callback (int (*cb)(const char*)) {
2380 if (_fib_win) { return -1; }
2381 _fib_filter_function = cb;
2382 return 0;
2383 }
2384
2385 char *x_fib_filename () {
2386 if (_status > 0 && !_fib_win)
2387 return strdup (_rv_open);
2388 else
2389 return NULL;
2390 }
2391 #endif // HAVE_X11
2392
2393 #if defined(__clang__)
2394 # pragma clang diagnostic pop
2395 #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
2396 # pragma GCC diagnostic pop
2397 #endif
2398
2399 /* example usage */
2400 #ifdef SOFD_TEST
2401
2402 static int fib_filter_movie_filename (const char *name) {
2403 if (!_fib_filter_fn) return 1;
2404 const int l3 = strlen (name) - 3;
2405 const int l4 = l3 - 1;
2406 const int l5 = l4 - 1;
2407 const int l6 = l5 - 1;
2408 const int l9 = l6 - 3;
2409 if (
2410 (l4 > 0 && (
2411 !strcasecmp (&name[l4], ".avi")
2412 || !strcasecmp (&name[l4], ".mov")
2413 || !strcasecmp (&name[l4], ".ogg")
2414 || !strcasecmp (&name[l4], ".ogv")
2415 || !strcasecmp (&name[l4], ".mpg")
2416 || !strcasecmp (&name[l4], ".mov")
2417 || !strcasecmp (&name[l4], ".mp4")
2418 || !strcasecmp (&name[l4], ".mkv")
2419 || !strcasecmp (&name[l4], ".vob")
2420 || !strcasecmp (&name[l4], ".asf")
2421 || !strcasecmp (&name[l4], ".avs")
2422 || !strcasecmp (&name[l4], ".dts")
2423 || !strcasecmp (&name[l4], ".flv")
2424 || !strcasecmp (&name[l4], ".m4v")
2425 )) ||
2426 (l5 > 0 && (
2427 !strcasecmp (&name[l5], ".h264")
2428 || !strcasecmp (&name[l5], ".webm")
2429 )) ||
2430 (l6 > 0 && (
2431 !strcasecmp (&name[l6], ".dirac")
2432 )) ||
2433 (l9 > 0 && (
2434 !strcasecmp (&name[l9], ".matroska")
2435 )) ||
2436 (l3 > 0 && (
2437 !strcasecmp (&name[l3], ".dv")
2438 || !strcasecmp (&name[l3], ".ts")
2439 ))
2440 )
2441 {
2442 return 1;
2443 }
2444 return 0;
2445 }
2446
2447 int main (int argc, char **argv) {
2448 Display* dpy = XOpenDisplay (0);
2449 if (!dpy) return -1;
2450
2451 x_fib_cfg_filter_callback (fib_filter_movie_filename);
2452 x_fib_configure (1, "Open Movie File");
2453 x_fib_load_recent ("/tmp/sofd.recent");
2454 x_fib_show (dpy, 0, 300, 300);
2455
2456 while (1) {
2457 XEvent event;
2458 while (XPending (dpy) > 0) {
2459 XNextEvent (dpy, &event);
2460 if (x_fib_handle_events (dpy, &event)) {
2461 if (x_fib_status () > 0) {
2462 char *fn = x_fib_filename ();
2463 printf ("OPEN '%s'\n", fn);
2464 x_fib_add_recent (fn, time (NULL));
2465 free (fn);
2466 }
2467 }
2468 }
2469 if (x_fib_status ()) {
2470 break;
2471 }
2472 usleep (80000);
2473 }
2474 x_fib_close (dpy);
2475
2476 x_fib_save_recent ("/tmp/sofd.recent");
2477
2478 x_fib_free_recent ();
2479 XCloseDisplay (dpy);
2480 return 0;
2481 }
2482 #endif