Krolyxon
Commit 2bf8f2a

Branches

Refs →

nsxivmain.c

943 lines · 20.7 KB · C

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