DPF

DISTRHO Plugin Framework
Log | Files | Refs | Submodules | README | LICENSE

libsofd.c (70883B)


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