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