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