sistema_progs

Programas para customizar o meu entorno de traballo nos meus equipos persoais
Log | Files | Refs

main.c (21154B)


      1 /* Copyright 2011-2013 Bert Muennich
      2  *
      3  * This file is part of sxiv.
      4  *
      5  * sxiv is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU General Public License as published
      7  * by the Free Software Foundation; either version 2 of the License,
      8  * or (at your option) any later version.
      9  *
     10  * sxiv is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  * GNU General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU General Public License
     16  * along with sxiv.  If not, see <http://www.gnu.org/licenses/>.
     17  */
     18 
     19 #include "sxiv.h"
     20 #define _MAPPINGS_CONFIG
     21 #include "config.h"
     22 
     23 #include <stdlib.h>
     24 #include <string.h>
     25 #include <fcntl.h>
     26 #include <unistd.h>
     27 #include <errno.h>
     28 #include <locale.h>
     29 #include <signal.h>
     30 #include <sys/select.h>
     31 #include <sys/stat.h>
     32 #include <sys/wait.h>
     33 #include <time.h>
     34 #include <X11/keysym.h>
     35 #include <X11/XF86keysym.h>
     36 
     37 typedef struct {
     38 	struct timeval when;
     39 	bool active;
     40 	timeout_f handler;
     41 } timeout_t;
     42 
     43 /* timeout handler functions: */
     44 void redraw(void);
     45 void reset_cursor(void);
     46 void animate(void);
     47 void slideshow(void);
     48 void clear_resize(void);
     49 
     50 appmode_t mode;
     51 arl_t arl;
     52 img_t img;
     53 tns_t tns;
     54 win_t win;
     55 
     56 fileinfo_t *files;
     57 int filecnt, fileidx;
     58 int alternate;
     59 int markcnt;
     60 int markidx;
     61 
     62 int prefix;
     63 bool extprefix;
     64 
     65 bool resized = false;
     66 
     67 typedef struct {
     68 	int err;
     69 	char *cmd;
     70 } extcmd_t;
     71 
     72 struct {
     73 	extcmd_t f;
     74 	int fd;
     75 	unsigned int i, lastsep;
     76 	pid_t pid;
     77 } info;
     78 
     79 struct {
     80 	extcmd_t f;
     81 	bool warned;
     82 } keyhandler;
     83 
     84 timeout_t timeouts[] = {
     85 	{ { 0, 0 }, false, redraw       },
     86 	{ { 0, 0 }, false, reset_cursor },
     87 	{ { 0, 0 }, false, animate      },
     88 	{ { 0, 0 }, false, slideshow    },
     89 	{ { 0, 0 }, false, clear_resize },
     90 };
     91 
     92 cursor_t imgcursor[3] = {
     93 	CURSOR_ARROW, CURSOR_ARROW, CURSOR_ARROW
     94 };
     95 
     96 void cleanup(void)
     97 {
     98 	img_close(&img, false);
     99 	arl_cleanup(&arl);
    100 	tns_free(&tns);
    101 	win_close(&win);
    102 }
    103 
    104 void check_add_file(char *filename, bool given)
    105 {
    106 	char *path;
    107 
    108 	if (*filename == '\0')
    109 		return;
    110 
    111 	if (access(filename, R_OK) < 0 ||
    112 	    (path = realpath(filename, NULL)) == NULL)
    113 	{
    114 		if (given)
    115 			error(0, errno, "%s", filename);
    116 		return;
    117 	}
    118 
    119 	if (fileidx == filecnt) {
    120 		filecnt *= 2;
    121 		files = erealloc(files, filecnt * sizeof(*files));
    122 		memset(&files[filecnt/2], 0, filecnt/2 * sizeof(*files));
    123 	}
    124 
    125 	files[fileidx].name = estrdup(filename);
    126 	files[fileidx].path = path;
    127 	if (given)
    128 		files[fileidx].flags |= FF_WARN;
    129 	fileidx++;
    130 }
    131 
    132 void remove_file(int n, bool manual)
    133 {
    134 	if (n < 0 || n >= filecnt)
    135 		return;
    136 
    137 	if (filecnt == 1) {
    138 		if (!manual)
    139 			fprintf(stderr, "sxiv: no more files to display, aborting\n");
    140 		exit(manual ? EXIT_SUCCESS : EXIT_FAILURE);
    141 	}
    142 	if (files[n].flags & FF_MARK)
    143 		markcnt--;
    144 
    145 	if (files[n].path != files[n].name)
    146 		free((void*) files[n].path);
    147 	free((void*) files[n].name);
    148 
    149 	if (n + 1 < filecnt) {
    150 		if (tns.thumbs != NULL) {
    151 			memmove(tns.thumbs + n, tns.thumbs + n + 1, (filecnt - n - 1) *
    152 			        sizeof(*tns.thumbs));
    153 			memset(tns.thumbs + filecnt - 1, 0, sizeof(*tns.thumbs));
    154 		}
    155 		memmove(files + n, files + n + 1, (filecnt - n - 1) * sizeof(*files));
    156 	}
    157 	filecnt--;
    158 	if (fileidx > n || fileidx == filecnt)
    159 		fileidx--;
    160 	if (alternate > n || alternate == filecnt)
    161 		alternate--;
    162 	if (markidx > n || markidx == filecnt)
    163 		markidx--;
    164 }
    165 
    166 void set_timeout(timeout_f handler, int time, bool overwrite)
    167 {
    168 	int i;
    169 
    170 	for (i = 0; i < ARRLEN(timeouts); i++) {
    171 		if (timeouts[i].handler == handler) {
    172 			if (!timeouts[i].active || overwrite) {
    173 				gettimeofday(&timeouts[i].when, 0);
    174 				TV_ADD_MSEC(&timeouts[i].when, time);
    175 				timeouts[i].active = true;
    176 			}
    177 			return;
    178 		}
    179 	}
    180 }
    181 
    182 void reset_timeout(timeout_f handler)
    183 {
    184 	int i;
    185 
    186 	for (i = 0; i < ARRLEN(timeouts); i++) {
    187 		if (timeouts[i].handler == handler) {
    188 			timeouts[i].active = false;
    189 			return;
    190 		}
    191 	}
    192 }
    193 
    194 bool check_timeouts(struct timeval *t)
    195 {
    196 	int i = 0, tdiff, tmin = -1;
    197 	struct timeval now;
    198 
    199 	while (i < ARRLEN(timeouts)) {
    200 		if (timeouts[i].active) {
    201 			gettimeofday(&now, 0);
    202 			tdiff = TV_DIFF(&timeouts[i].when, &now);
    203 			if (tdiff <= 0) {
    204 				timeouts[i].active = false;
    205 				if (timeouts[i].handler != NULL)
    206 					timeouts[i].handler();
    207 				i = tmin = -1;
    208 			} else if (tmin < 0 || tdiff < tmin) {
    209 				tmin = tdiff;
    210 			}
    211 		}
    212 		i++;
    213 	}
    214 	if (tmin > 0 && t != NULL)
    215 		TV_SET_MSEC(t, tmin);
    216 	return tmin > 0;
    217 }
    218 
    219 void close_info(void)
    220 {
    221 	if (info.fd != -1) {
    222 		kill(info.pid, SIGTERM);
    223 		close(info.fd);
    224 		info.fd = -1;
    225 	}
    226 }
    227 
    228 void open_info(void)
    229 {
    230 	int pfd[2];
    231 	char w[12], h[12];
    232 
    233 	if (info.f.err != 0 || info.fd >= 0 || win.bar.h == 0)
    234 		return;
    235 	win.bar.l.buf[0] = '\0';
    236 	if (pipe(pfd) < 0)
    237 		return;
    238 	if ((info.pid = fork()) == 0) {
    239 		close(pfd[0]);
    240 		dup2(pfd[1], 1);
    241 		snprintf(w, sizeof(w), "%d", img.w);
    242 		snprintf(h, sizeof(h), "%d", img.h);
    243 		execl(info.f.cmd, info.f.cmd, files[fileidx].name, w, h, NULL);
    244 		error(EXIT_FAILURE, errno, "exec: %s", info.f.cmd);
    245 	}
    246 	close(pfd[1]);
    247 	if (info.pid < 0) {
    248 		close(pfd[0]);
    249 	} else {
    250 		fcntl(pfd[0], F_SETFL, O_NONBLOCK);
    251 		info.fd = pfd[0];
    252 		info.i = info.lastsep = 0;
    253 	}
    254 }
    255 
    256 void read_info(void)
    257 {
    258 	ssize_t i, n;
    259 	char buf[BAR_L_LEN];
    260 
    261 	while (true) {
    262 		n = read(info.fd, buf, sizeof(buf));
    263 		if (n < 0 && errno == EAGAIN)
    264 			return;
    265 		else if (n == 0)
    266 			goto end;
    267 		for (i = 0; i < n; i++) {
    268 			if (buf[i] == '\n') {
    269 				if (info.lastsep == 0) {
    270 					win.bar.l.buf[info.i++] = ' ';
    271 					info.lastsep = 1;
    272 				}
    273 			} else {
    274 				win.bar.l.buf[info.i++] = buf[i];
    275 				info.lastsep = 0;
    276 			}
    277 			if (info.i + 1 == win.bar.l.size)
    278 				goto end;
    279 		}
    280 	}
    281 end:
    282 	info.i -= info.lastsep;
    283 	win.bar.l.buf[info.i] = '\0';
    284 	win_draw(&win);
    285 	close_info();
    286 }
    287 
    288 void load_image(int new)
    289 {
    290 	bool prev = new < fileidx;
    291 	static int current;
    292 
    293 	if (new < 0 || new >= filecnt)
    294 		return;
    295 
    296 	if (win.xwin != None)
    297 		win_set_cursor(&win, CURSOR_WATCH);
    298 	reset_timeout(slideshow);
    299 
    300 	if (new != current)
    301 		alternate = current;
    302 
    303 	img_close(&img, false);
    304 	while (!img_load(&img, &files[new])) {
    305 		remove_file(new, false);
    306 		if (new >= filecnt)
    307 			new = filecnt - 1;
    308 		else if (new > 0 && prev)
    309 			new--;
    310 	}
    311 	files[new].flags &= ~FF_WARN;
    312 	fileidx = current = new;
    313 
    314 	close_info();
    315 	open_info();
    316 	arl_setup(&arl, files[fileidx].path);
    317 
    318 	if (img.multi.cnt > 0 && img.multi.animate)
    319 		set_timeout(animate, img.multi.frames[img.multi.sel].delay, true);
    320 	else
    321 		reset_timeout(animate);
    322 }
    323 
    324 bool mark_image(int n, bool on)
    325 {
    326 	markidx = n;
    327 	if (!!(files[n].flags & FF_MARK) != on) {
    328 		files[n].flags ^= FF_MARK;
    329 		markcnt += on ? 1 : -1;
    330 		if (mode == MODE_THUMB)
    331 			tns_mark(&tns, n, on);
    332 		return true;
    333 	}
    334 	return false;
    335 }
    336 
    337 void bar_put(win_bar_t *bar, const char *fmt, ...)
    338 {
    339 	size_t len = bar->size - (bar->p - bar->buf), n;
    340 	va_list ap;
    341 
    342 	va_start(ap, fmt);
    343 	n = vsnprintf(bar->p, len, fmt, ap);
    344 	bar->p += MIN(len, n);
    345 	va_end(ap);
    346 }
    347 
    348 #define BAR_SEP "  "
    349 
    350 void update_info(void)
    351 {
    352 	unsigned int i, fn, fw;
    353 	const char * mark;
    354 	win_bar_t *l = &win.bar.l, *r = &win.bar.r;
    355 
    356 	/* update bar contents */
    357 	if (win.bar.h == 0)
    358 		return;
    359 	for (fw = 0, i = filecnt; i > 0; fw++, i /= 10);
    360 	mark = files[fileidx].flags & FF_MARK ? "* " : "";
    361 	l->p = l->buf;
    362 	r->p = r->buf;
    363 	if (mode == MODE_THUMB) {
    364 		if (tns.loadnext < tns.end)
    365 			bar_put(l, "Loading... %0*d", fw, tns.loadnext + 1);
    366 		else if (tns.initnext < filecnt)
    367 			bar_put(l, "Caching... %0*d", fw, tns.initnext + 1);
    368 		else
    369 			strncpy(l->buf, files[fileidx].name, l->size);
    370 		bar_put(r, "%s%0*d/%d", mark, fw, fileidx + 1, filecnt);
    371 	} else {
    372 		bar_put(r, "%s", mark);
    373 		if (img.ss.on) {
    374 			if (img.ss.delay % 10 != 0)
    375 				bar_put(r, "%2.1fs" BAR_SEP, (float)img.ss.delay / 10);
    376 			else
    377 				bar_put(r, "%ds" BAR_SEP, img.ss.delay / 10);
    378 		}
    379 		if (img.gamma != 0)
    380 			bar_put(r, "G%+d" BAR_SEP, img.gamma);
    381 		bar_put(r, "%3d%%" BAR_SEP, (int) (img.zoom * 100.0));
    382 		if (img.multi.cnt > 0) {
    383 			for (fn = 0, i = img.multi.cnt; i > 0; fn++, i /= 10);
    384 			bar_put(r, "%0*d/%d" BAR_SEP, fn, img.multi.sel + 1, img.multi.cnt);
    385 		}
    386 		bar_put(r, "%0*d/%d", fw, fileidx + 1, filecnt);
    387 		if (info.f.err)
    388 			strncpy(l->buf, files[fileidx].name, l->size);
    389 	}
    390 }
    391 
    392 int ptr_third_x(void)
    393 {
    394 	int x, y;
    395 
    396 	win_cursor_pos(&win, &x, &y);
    397 	return MAX(0, MIN(2, (x / (win.w * 0.33))));
    398 }
    399 
    400 void redraw(void)
    401 {
    402 	int t;
    403 
    404 	if (mode == MODE_IMAGE) {
    405 		img_render(&img);
    406 		if (img.ss.on) {
    407 			t = img.ss.delay * 100;
    408 			if (img.multi.cnt > 0 && img.multi.animate)
    409 				t = MAX(t, img.multi.length);
    410 			set_timeout(slideshow, t, false);
    411 		}
    412 	} else {
    413 		tns_render(&tns);
    414 	}
    415 	update_info();
    416 	win_draw(&win);
    417 	reset_timeout(redraw);
    418 	reset_cursor();
    419 }
    420 
    421 void reset_cursor(void)
    422 {
    423 	int c, i;
    424 	cursor_t cursor = CURSOR_NONE;
    425 
    426 	if (mode == MODE_IMAGE) {
    427 		for (i = 0; i < ARRLEN(timeouts); i++) {
    428 			if (timeouts[i].handler == reset_cursor) {
    429 				if (timeouts[i].active) {
    430 					c = ptr_third_x();
    431 					c = MAX(fileidx > 0 ? 0 : 1, c);
    432 					c = MIN(fileidx + 1 < filecnt ? 2 : 1, c);
    433 					cursor = imgcursor[c];
    434 				}
    435 				break;
    436 			}
    437 		}
    438 	} else {
    439 		if (tns.loadnext < tns.end || tns.initnext < filecnt)
    440 			cursor = CURSOR_WATCH;
    441 		else
    442 			cursor = CURSOR_ARROW;
    443 	}
    444 	win_set_cursor(&win, cursor);
    445 }
    446 
    447 void animate(void)
    448 {
    449 	if (img_frame_animate(&img)) {
    450 		redraw();
    451 		set_timeout(animate, img.multi.frames[img.multi.sel].delay, true);
    452 	}
    453 }
    454 
    455 void slideshow(void)
    456 {
    457 	load_image(fileidx + 1 < filecnt ? fileidx + 1 : 0);
    458 	redraw();
    459 }
    460 
    461 void clear_resize(void)
    462 {
    463 	resized = false;
    464 }
    465 
    466 Bool is_input_ev(Display *dpy, XEvent *ev, XPointer arg)
    467 {
    468 	return ev->type == ButtonPress || ev->type == KeyPress;
    469 }
    470 
    471 void run_key_handler(const char *key, unsigned int mask)
    472 {
    473 	pid_t pid;
    474 	FILE *pfs;
    475 	bool marked = mode == MODE_THUMB && markcnt > 0;
    476 	bool changed = false;
    477 	int f, i, pfd[2];
    478 	int fcnt = marked ? markcnt : 1;
    479 	char kstr[32];
    480 	struct stat *oldst, st;
    481 	XEvent dump;
    482 
    483 	if (keyhandler.f.err != 0) {
    484 		if (!keyhandler.warned) {
    485 			error(0, keyhandler.f.err, "%s", keyhandler.f.cmd);
    486 			keyhandler.warned = true;
    487 		}
    488 		return;
    489 	}
    490 	if (key == NULL)
    491 		return;
    492 
    493 	if (pipe(pfd) < 0) {
    494 		error(0, errno, "pipe");
    495 		return;
    496 	}
    497 	if ((pfs = fdopen(pfd[1], "w")) == NULL) {
    498 		error(0, errno, "open pipe");
    499 		close(pfd[0]), close(pfd[1]);
    500 		return;
    501 	}
    502 	oldst = emalloc(fcnt * sizeof(*oldst));
    503 
    504 	close_info();
    505 	strncpy(win.bar.l.buf, "Running key handler...", win.bar.l.size);
    506 	win_draw(&win);
    507 	win_set_cursor(&win, CURSOR_WATCH);
    508 
    509 	snprintf(kstr, sizeof(kstr), "%s%s%s%s",
    510 	         mask & ControlMask ? "C-" : "",
    511 	         mask & Mod1Mask    ? "M-" : "",
    512 	         mask & ShiftMask   ? "S-" : "", key);
    513 
    514 	if ((pid = fork()) == 0) {
    515 		close(pfd[1]);
    516 		dup2(pfd[0], 0);
    517 		execl(keyhandler.f.cmd, keyhandler.f.cmd, kstr, NULL);
    518 		error(EXIT_FAILURE, errno, "exec: %s", keyhandler.f.cmd);
    519 	}
    520 	close(pfd[0]);
    521 	if (pid < 0) {
    522 		error(0, errno, "fork");
    523 		fclose(pfs);
    524 		goto end;
    525 	}
    526 
    527 	for (f = i = 0; f < fcnt; i++) {
    528 		if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) {
    529 			stat(files[i].path, &oldst[f]);
    530 			fprintf(pfs, "%s\n", files[i].name);
    531 			f++;
    532 		}
    533 	}
    534 	fclose(pfs);
    535 	while (waitpid(pid, NULL, 0) == -1 && errno == EINTR);
    536 
    537 	for (f = i = 0; f < fcnt; i++) {
    538 		if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) {
    539 			if (stat(files[i].path, &st) != 0 ||
    540 				  memcmp(&oldst[f].st_mtime, &st.st_mtime, sizeof(st.st_mtime)) != 0)
    541 			{
    542 				if (tns.thumbs != NULL) {
    543 					tns_unload(&tns, i);
    544 					tns.loadnext = MIN(tns.loadnext, i);
    545 				}
    546 				changed = true;
    547 			}
    548 			f++;
    549 		}
    550 	}
    551 	/* drop user input events that occurred while running the key handler */
    552 	while (XCheckIfEvent(win.env.dpy, &dump, is_input_ev, NULL));
    553 
    554 end:
    555 	if (mode == MODE_IMAGE) {
    556 		if (changed) {
    557 			img_close(&img, true);
    558 			load_image(fileidx);
    559 		} else {
    560 			open_info();
    561 		}
    562 	}
    563 	free(oldst);
    564 	reset_cursor();
    565 	redraw();
    566 }
    567 
    568 #define MODMASK(mask) ((mask) & (ShiftMask|ControlMask|Mod1Mask))
    569 
    570 void on_keypress(XKeyEvent *kev)
    571 {
    572 	int i;
    573 	unsigned int sh = 0;
    574 	KeySym ksym, shksym;
    575 	char dummy, key;
    576 	bool dirty = false;
    577 
    578 	XLookupString(kev, &key, 1, &ksym, NULL);
    579 
    580 	if (kev->state & ShiftMask) {
    581 		kev->state &= ~ShiftMask;
    582 		XLookupString(kev, &dummy, 1, &shksym, NULL);
    583 		kev->state |= ShiftMask;
    584 		if (ksym != shksym)
    585 			sh = ShiftMask;
    586 	}
    587 	if (IsModifierKey(ksym))
    588 		return;
    589 	if (ksym == XK_Escape && MODMASK(kev->state) == 0) {
    590 		extprefix = False;
    591 	} else if (extprefix) {
    592 		run_key_handler(XKeysymToString(ksym), kev->state & ~sh);
    593 		extprefix = False;
    594 	} else if (key >= '0' && key <= '9') {
    595 		/* number prefix for commands */
    596 		prefix = prefix * 10 + (int) (key - '0');
    597 		return;
    598 	} else for (i = 0; i < ARRLEN(keys); i++) {
    599 		if (keys[i].ksym == ksym &&
    600 		    MODMASK(keys[i].mask | sh) == MODMASK(kev->state) &&
    601 		    keys[i].cmd >= 0 && keys[i].cmd < CMD_COUNT &&
    602 		    (cmds[keys[i].cmd].mode < 0 || cmds[keys[i].cmd].mode == mode))
    603 		{
    604 			if (cmds[keys[i].cmd].func(keys[i].arg))
    605 				dirty = true;
    606 		}
    607 	}
    608 	if (dirty)
    609 		redraw();
    610 	prefix = 0;
    611 }
    612 
    613 void on_buttonpress(XButtonEvent *bev)
    614 {
    615 	int i, sel;
    616 	bool dirty = false;
    617 	static Time firstclick;
    618 
    619 	if (mode == MODE_IMAGE) {
    620 		set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
    621 		reset_cursor();
    622 
    623 		for (i = 0; i < ARRLEN(buttons); i++) {
    624 			if (buttons[i].button == bev->button &&
    625 			    MODMASK(buttons[i].mask) == MODMASK(bev->state) &&
    626 			    buttons[i].cmd >= 0 && buttons[i].cmd < CMD_COUNT &&
    627 			    (cmds[buttons[i].cmd].mode < 0 || cmds[buttons[i].cmd].mode == mode))
    628 			{
    629 				if (cmds[buttons[i].cmd].func(buttons[i].arg))
    630 					dirty = true;
    631 			}
    632 		}
    633 		if (dirty)
    634 			redraw();
    635 	} else {
    636 		/* thumbnail mode (hard-coded) */
    637 		switch (bev->button) {
    638 			case Button1:
    639 				if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) {
    640 					if (sel != fileidx) {
    641 						tns_highlight(&tns, fileidx, false);
    642 						tns_highlight(&tns, sel, true);
    643 						fileidx = sel;
    644 						firstclick = bev->time;
    645 						redraw();
    646 					} else if (bev->time - firstclick <= TO_DOUBLE_CLICK) {
    647 						mode = MODE_IMAGE;
    648 						set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
    649 						load_image(fileidx);
    650 						redraw();
    651 					} else {
    652 						firstclick = bev->time;
    653 					}
    654 				}
    655 				break;
    656 			case Button3:
    657 				if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) {
    658 					bool on = !(files[sel].flags & FF_MARK);
    659 					XEvent e;
    660 
    661 					for (;;) {
    662 						if (sel >= 0 && mark_image(sel, on))
    663 							redraw();
    664 						XMaskEvent(win.env.dpy,
    665 						           ButtonPressMask | ButtonReleaseMask | PointerMotionMask, &e);
    666 						if (e.type == ButtonPress || e.type == ButtonRelease)
    667 							break;
    668 						while (XCheckTypedEvent(win.env.dpy, MotionNotify, &e));
    669 						sel = tns_translate(&tns, e.xbutton.x, e.xbutton.y);
    670 					}
    671 				}
    672 				break;
    673 			case Button4:
    674 			case Button5:
    675 				if (tns_scroll(&tns, bev->button == Button4 ? DIR_UP : DIR_DOWN,
    676 				               (bev->state & ControlMask) != 0))
    677 					redraw();
    678 				break;
    679 		}
    680 	}
    681 	prefix = 0;
    682 }
    683 
    684 const struct timespec ten_ms = {0, 10000000};
    685 
    686 void run(void)
    687 {
    688 	int xfd;
    689 	fd_set fds;
    690 	struct timeval timeout;
    691 	bool discard, init_thumb, load_thumb, to_set;
    692 	XEvent ev, nextev;
    693 
    694 	while (true) {
    695 		to_set = check_timeouts(&timeout);
    696 		init_thumb = mode == MODE_THUMB && tns.initnext < filecnt;
    697 		load_thumb = mode == MODE_THUMB && tns.loadnext < tns.end;
    698 
    699 		if ((init_thumb || load_thumb || to_set || info.fd != -1 ||
    700 			   arl.fd != -1) && XPending(win.env.dpy) == 0)
    701 		{
    702 			if (load_thumb) {
    703 				set_timeout(redraw, TO_REDRAW_THUMBS, false);
    704 				if (!tns_load(&tns, tns.loadnext, false, false)) {
    705 					remove_file(tns.loadnext, false);
    706 					tns.dirty = true;
    707 				}
    708 				if (tns.loadnext >= tns.end)
    709 					redraw();
    710 			} else if (init_thumb) {
    711 				set_timeout(redraw, TO_REDRAW_THUMBS, false);
    712 				if (!tns_load(&tns, tns.initnext, false, true))
    713 					remove_file(tns.initnext, false);
    714 			} else {
    715 				xfd = ConnectionNumber(win.env.dpy);
    716 				FD_ZERO(&fds);
    717 				FD_SET(xfd, &fds);
    718 				if (info.fd != -1) {
    719 					FD_SET(info.fd, &fds);
    720 					xfd = MAX(xfd, info.fd);
    721 				}
    722 				if (arl.fd != -1) {
    723 					FD_SET(arl.fd, &fds);
    724 					xfd = MAX(xfd, arl.fd);
    725 				}
    726 				select(xfd + 1, &fds, 0, 0, to_set ? &timeout : NULL);
    727 				if (info.fd != -1 && FD_ISSET(info.fd, &fds))
    728 					read_info();
    729 				if (arl.fd != -1 && FD_ISSET(arl.fd, &fds)) {
    730 					if (arl_handle(&arl)) {
    731 						/* when too fast, imlib2 can't load the image */
    732 						nanosleep(&ten_ms, NULL);
    733 						img_close(&img, true);
    734 						load_image(fileidx);
    735 						redraw();
    736 					}
    737 				}
    738 			}
    739 			continue;
    740 		}
    741 
    742 		do {
    743 			XNextEvent(win.env.dpy, &ev);
    744 			discard = false;
    745 			if (XEventsQueued(win.env.dpy, QueuedAlready) > 0) {
    746 				XPeekEvent(win.env.dpy, &nextev);
    747 				switch (ev.type) {
    748 					case ConfigureNotify:
    749 					case MotionNotify:
    750 						discard = ev.type == nextev.type;
    751 						break;
    752 					case KeyPress:
    753 						discard = (nextev.type == KeyPress || nextev.type == KeyRelease)
    754 						          && ev.xkey.keycode == nextev.xkey.keycode;
    755 						break;
    756 				}
    757 			}
    758 		} while (discard);
    759 
    760 		switch (ev.type) {
    761 			/* handle events */
    762 			case ButtonPress:
    763 				on_buttonpress(&ev.xbutton);
    764 				break;
    765 			case ClientMessage:
    766 				if ((Atom) ev.xclient.data.l[0] == atoms[ATOM_WM_DELETE_WINDOW])
    767 					cmds[g_quit].func(0);
    768 				break;
    769 			case ConfigureNotify:
    770 				if (win_configure(&win, &ev.xconfigure)) {
    771 					if (mode == MODE_IMAGE) {
    772 						img.dirty = true;
    773 						img.checkpan = true;
    774 					} else {
    775 						tns.dirty = true;
    776 					}
    777 					if (!resized) {
    778 						redraw();
    779 						set_timeout(clear_resize, TO_REDRAW_RESIZE, false);
    780 						resized = true;
    781 					} else {
    782 						set_timeout(redraw, TO_REDRAW_RESIZE, false);
    783 					}
    784 				}
    785 				break;
    786 			case KeyPress:
    787 				on_keypress(&ev.xkey);
    788 				break;
    789 			case MotionNotify:
    790 				if (mode == MODE_IMAGE) {
    791 					set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
    792 					reset_cursor();
    793 				}
    794 				break;
    795 		}
    796 	}
    797 }
    798 
    799 int fncmp(const void *a, const void *b)
    800 {
    801 	return strcoll(((fileinfo_t*) a)->name, ((fileinfo_t*) b)->name);
    802 }
    803 
    804 void sigchld(int sig)
    805 {
    806 	while (waitpid(-1, NULL, WNOHANG) > 0);
    807 }
    808 
    809 void setup_signal(int sig, void (*handler)(int sig))
    810 {
    811 	struct sigaction sa;
    812 
    813 	sa.sa_handler = handler;
    814 	sigemptyset(&sa.sa_mask);
    815 	sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
    816 	if (sigaction(sig, &sa, 0) == -1)
    817 		error(EXIT_FAILURE, errno, "signal %d", sig);
    818 }
    819 
    820 int main(int argc, char **argv)
    821 {
    822 	int i, start;
    823 	size_t n;
    824 	ssize_t len;
    825 	char *filename;
    826 	const char *homedir, *dsuffix = "";
    827 	struct stat fstats;
    828 	r_dir_t dir;
    829 
    830 	setup_signal(SIGCHLD, sigchld);
    831 	setup_signal(SIGPIPE, SIG_IGN);
    832 
    833 	setlocale(LC_COLLATE, "");
    834 
    835 	parse_options(argc, argv);
    836 
    837 	if (options->clean_cache) {
    838 		tns_init(&tns, NULL, NULL, NULL, NULL);
    839 		tns_clean_cache(&tns);
    840 		exit(EXIT_SUCCESS);
    841 	}
    842 
    843 	if (options->filecnt == 0 && !options->from_stdin) {
    844 		print_usage();
    845 		exit(EXIT_FAILURE);
    846 	}
    847 
    848 	if (options->recursive || options->from_stdin)
    849 		filecnt = 1024;
    850 	else
    851 		filecnt = options->filecnt;
    852 
    853 	files = emalloc(filecnt * sizeof(*files));
    854 	memset(files, 0, filecnt * sizeof(*files));
    855 	fileidx = 0;
    856 
    857 	if (options->from_stdin) {
    858 		n = 0;
    859 		filename = NULL;
    860 		while ((len = getline(&filename, &n, stdin)) > 0) {
    861 			if (filename[len-1] == '\n')
    862 				filename[len-1] = '\0';
    863 			check_add_file(filename, true);
    864 		}
    865 		free(filename);
    866 	}
    867 
    868 	for (i = 0; i < options->filecnt; i++) {
    869 		filename = options->filenames[i];
    870 
    871 		if (stat(filename, &fstats) < 0) {
    872 			error(0, errno, "%s", filename);
    873 			continue;
    874 		}
    875 		if (!S_ISDIR(fstats.st_mode)) {
    876 			check_add_file(filename, true);
    877 		} else {
    878 			if (r_opendir(&dir, filename, options->recursive) < 0) {
    879 				error(0, errno, "%s", filename);
    880 				continue;
    881 			}
    882 			start = fileidx;
    883 			while ((filename = r_readdir(&dir, true)) != NULL) {
    884 				check_add_file(filename, false);
    885 				free((void*) filename);
    886 			}
    887 			r_closedir(&dir);
    888 			if (fileidx - start > 1)
    889 				qsort(files + start, fileidx - start, sizeof(fileinfo_t), fncmp);
    890 		}
    891 	}
    892 
    893 	if (fileidx == 0)
    894 		error(EXIT_FAILURE, 0, "No valid image file given, aborting");
    895 
    896 	filecnt = fileidx;
    897 	fileidx = options->startnum < filecnt ? options->startnum : 0;
    898 
    899 	for (i = 0; i < ARRLEN(buttons); i++) {
    900 		if (buttons[i].cmd == i_cursor_navigate) {
    901 			imgcursor[0] = CURSOR_LEFT;
    902 			imgcursor[2] = CURSOR_RIGHT;
    903 			break;
    904 		}
    905 	}
    906 
    907 	win_init(&win);
    908 	img_init(&img, &win);
    909 	arl_init(&arl);
    910 
    911 	if ((homedir = getenv("XDG_CONFIG_HOME")) == NULL || homedir[0] == '\0') {
    912 		homedir = getenv("HOME");
    913 		dsuffix = "/.config";
    914 	}
    915 	if (homedir != NULL) {
    916 		extcmd_t *cmd[] = { &info.f, &keyhandler.f };
    917 		const char *name[] = { "image-info", "key-handler" };
    918 
    919 		for (i = 0; i < ARRLEN(cmd); i++) {
    920 			n = strlen(homedir) + strlen(dsuffix) + strlen(name[i]) + 12;
    921 			cmd[i]->cmd = (char*) emalloc(n);
    922 			snprintf(cmd[i]->cmd, n, "%s%s/sxiv/exec/%s", homedir, dsuffix, name[i]);
    923 			if (access(cmd[i]->cmd, X_OK) != 0)
    924 				cmd[i]->err = errno;
    925 		}
    926 	} else {
    927 		error(0, 0, "Exec directory not found");
    928 	}
    929 	info.fd = -1;
    930 
    931 	if (options->thumb_mode) {
    932 		mode = MODE_THUMB;
    933 		tns_init(&tns, files, &filecnt, &fileidx, &win);
    934 		while (!tns_load(&tns, fileidx, false, false))
    935 			remove_file(fileidx, false);
    936 	} else {
    937 		mode = MODE_IMAGE;
    938 		tns.thumbs = NULL;
    939 		load_image(fileidx);
    940 	}
    941 	win_open(&win);
    942 	win_set_cursor(&win, CURSOR_WATCH);
    943 
    944 	atexit(cleanup);
    945 
    946 	set_timeout(redraw, 25, false);
    947 
    948 	run();
    949 
    950 	return 0;
    951 }