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