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