Krolyxon
Commit 3f25b90

Branches

Refs →

nsxivmain.c

650 lines · 14.0 KB · C

1/* sxiv: main.c
2 * Copyright (c) 2012 Bert Muennich <be.muennich at googlemail.com>
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19#define _POSIX_C_SOURCE 200112L
20#define _MAPPINGS_CONFIG
21
22#include <stdlib.h>
23#include <stdio.h>
24#include <string.h>
25#include <unistd.h>
26#include <sys/select.h>
27#include <sys/stat.h>
28#include <sys/time.h>
29#include <X11/keysym.h>
30
31#include "types.h"
32#include "commands.h"
33#include "image.h"
34#include "options.h"
35#include "thumbs.h"
36#include "util.h"
37#include "window.h"
38#include "config.h"
39
40enum {
41 BAR_L_LEN = 512,
42 BAR_R_LEN = 64,
43 FILENAME_CNT = 1024,
44 TITLE_LEN = 256
45};
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 clear_resize(void);
58
59appmode_t mode;
60img_t img;
61tns_t tns;
62win_t win;
63
64fileinfo_t *files;
65int filecnt, fileidx;
66int alternate;
67
68int prefix;
69
70bool resized = false;
71
72const char * const INFO_SCRIPT = ".sxiv/exec/image-info";
73char *info_script;
74
75struct {
76 char l[BAR_L_LEN];
77 char r[BAR_R_LEN];
78} bar;
79
80timeout_t timeouts[] = {
81 { { 0, 0 }, false, redraw },
82 { { 0, 0 }, false, reset_cursor },
83 { { 0, 0 }, false, animate },
84 { { 0, 0 }, false, clear_resize },
85};
86
87void cleanup(void) {
88 static bool in = false;
89
90 if (!in) {
91 in = true;
92 img_close(&img, false);
93 tns_free(&tns);
94 win_close(&win);
95 }
96}
97
98void check_add_file(char *filename) {
99 const char *bn;
100
101 if (filename == NULL || *filename == '\0')
102 return;
103
104 if (access(filename, R_OK) < 0) {
105 warn("could not open file: %s", filename);
106 return;
107 }
108
109 if (fileidx == filecnt) {
110 filecnt *= 2;
111 files = (fileinfo_t*) s_realloc(files, filecnt * sizeof(fileinfo_t));
112 }
113 if (*filename != '/') {
114 files[fileidx].path = absolute_path(filename);
115 if (files[fileidx].path == NULL) {
116 warn("could not get absolute path of file: %s\n", filename);
117 return;
118 }
119 }
120 files[fileidx].loaded = false;
121 files[fileidx].name = s_strdup(filename);
122 if (*filename == '/')
123 files[fileidx].path = files[fileidx].name;
124 if ((bn = strrchr(files[fileidx].name , '/')) != NULL && bn[1] != '\0')
125 files[fileidx].base = ++bn;
126 else
127 files[fileidx].base = files[fileidx].name;
128 fileidx++;
129}
130
131void remove_file(int n, bool manual) {
132 if (n < 0 || n >= filecnt)
133 return;
134
135 if (filecnt == 1) {
136 if (!manual)
137 fprintf(stderr, "sxiv: no more files to display, aborting\n");
138 cleanup();
139 exit(manual ? EXIT_SUCCESS : EXIT_FAILURE);
140 }
141
142 if (files[n].path != files[n].name)
143 free((void*) files[n].path);
144 free((void*) files[n].name);
145
146 if (n + 1 < filecnt)
147 memmove(files + n, files + n + 1, (filecnt - n - 1) * sizeof(fileinfo_t));
148 if (n + 1 < tns.cnt) {
149 memmove(tns.thumbs + n, tns.thumbs + n + 1, (tns.cnt - n - 1) *
150 sizeof(thumb_t));
151 memset(tns.thumbs + tns.cnt - 1, 0, sizeof(thumb_t));
152 }
153
154 filecnt--;
155 if (n < tns.cnt)
156 tns.cnt--;
157}
158
159void set_timeout(timeout_f handler, int time, bool overwrite) {
160 int i;
161
162 for (i = 0; i < ARRLEN(timeouts); i++) {
163 if (timeouts[i].handler == handler) {
164 if (!timeouts[i].active || overwrite) {
165 gettimeofday(&timeouts[i].when, 0);
166 TV_ADD_MSEC(&timeouts[i].when, time);
167 timeouts[i].active = true;
168 }
169 return;
170 }
171 }
172}
173
174void reset_timeout(timeout_f handler) {
175 int i;
176
177 for (i = 0; i < ARRLEN(timeouts); i++) {
178 if (timeouts[i].handler == handler) {
179 timeouts[i].active = false;
180 return;
181 }
182 }
183}
184
185bool check_timeouts(struct timeval *t) {
186 int i = 0, tdiff, tmin = -1;
187 struct timeval now;
188
189 while (i < ARRLEN(timeouts)) {
190 if (timeouts[i].active) {
191 gettimeofday(&now, 0);
192 tdiff = TV_DIFF(&timeouts[i].when, &now);
193 if (tdiff <= 0) {
194 timeouts[i].active = false;
195 if (timeouts[i].handler != NULL)
196 timeouts[i].handler();
197 i = tmin = -1;
198 } else if (tmin < 0 || tdiff < tmin) {
199 tmin = tdiff;
200 }
201 }
202 i++;
203 }
204 if (tmin > 0 && t != NULL)
205 TV_SET_MSEC(t, tmin);
206 return tmin > 0;
207}
208
209void read_info(void) {
210 char cmd[4096];
211 FILE *outp;
212 int c, i = 0, n = sizeof(bar.l) - 1;
213 bool lastsep = false;
214
215 if (info_script != NULL) {
216 snprintf(cmd, sizeof(cmd), "%s \"%s\"", info_script, files[fileidx].name);
217 outp = popen(cmd, "r");
218 if (outp == NULL)
219 goto end;
220 while (i < n && (c = fgetc(outp)) != EOF) {
221 if (c == '\n') {
222 if (!lastsep) {
223 bar.l[i++] = ' ';
224 lastsep = true;
225 }
226 } else {
227 bar.l[i++] = c;
228 lastsep = false;
229 }
230 }
231 pclose(outp);
232 }
233end:
234 if (lastsep)
235 i--;
236 bar.l[i] = '\0';
237}
238
239void load_image(int new) {
240 if (new < 0 || new >= filecnt)
241 return;
242
243 win_set_cursor(&win, CURSOR_WATCH);
244
245 img_close(&img, false);
246 while (!img_load(&img, &files[new])) {
247 remove_file(new, false);
248 if (new >= filecnt)
249 new = filecnt - 1;
250 }
251
252 files[new].loaded = true;
253 alternate = fileidx;
254 fileidx = new;
255
256 read_info();
257
258 if (img.multi.cnt > 0 && img.multi.animate)
259 set_timeout(animate, img.multi.frames[img.multi.sel].delay, true);
260 else
261 reset_timeout(animate);
262}
263
264void update_info(void) {
265 unsigned int i, fn, fw, n, len = sizeof(bar.r);
266 int sel;
267 char *t = bar.r, title[TITLE_LEN];
268 bool ow_info;
269
270 for (fw = 0, i = filecnt; i > 0; fw++, i /= 10);
271 sel = mode == MODE_IMAGE ? fileidx : tns.sel;
272
273 if (mode == MODE_THUMB) {
274 win_set_title(&win, "sxiv");
275
276 if (tns.cnt == filecnt) {
277 n = snprintf(t, len, "%0*d/%d", fw, sel + 1, filecnt);
278 ow_info = true;
279 } else {
280 snprintf(bar.l, sizeof(bar.l), "Loading... %0*d/%d",
281 fw, tns.cnt, filecnt);
282 bar.r[0] = '\0';
283 ow_info = false;
284 }
285 } else {
286 snprintf(title, sizeof(title), "sxiv - %s", files[sel].name);
287 win_set_title(&win, title);
288
289 n = snprintf(t, len, "%3d%% ", (int) (img.zoom * 100.0));
290 if (img.multi.cnt > 0) {
291 for (fn = 0, i = img.multi.cnt; i > 0; fn++, i /= 10);
292 n += snprintf(t + n, len - n, "(%0*d/%d) ",
293 fn, img.multi.sel + 1, img.multi.cnt);
294 }
295 n += snprintf(t + n, len - n, "%0*d/%d", fw, sel + 1, filecnt);
296 ow_info = bar.l[0] == '\0';
297 }
298 if (ow_info) {
299 fn = strlen(files[sel].name);
300 if (fn < sizeof(bar.l) &&
301 win_textwidth(files[sel].name, fn, true) +
302 win_textwidth(bar.r, n, true) < win.w)
303 {
304 strncpy(bar.l, files[sel].name, sizeof(bar.l));
305 } else {
306 strncpy(bar.l, files[sel].base, sizeof(bar.l));
307 }
308 }
309 win_set_bar_info(&win, bar.l, bar.r);
310}
311
312void redraw(void) {
313 if (mode == MODE_IMAGE)
314 img_render(&img);
315 else
316 tns_render(&tns);
317 update_info();
318 win_draw(&win);
319 reset_timeout(redraw);
320 reset_cursor();
321}
322
323void reset_cursor(void) {
324 int i;
325 cursor_t cursor = CURSOR_NONE;
326
327 if (mode == MODE_IMAGE) {
328 for (i = 0; i < ARRLEN(timeouts); i++) {
329 if (timeouts[i].handler == reset_cursor) {
330 if (timeouts[i].active)
331 cursor = CURSOR_ARROW;
332 break;
333 }
334 }
335 } else {
336 if (tns.cnt != filecnt)
337 cursor = CURSOR_WATCH;
338 else
339 cursor = CURSOR_ARROW;
340 }
341 win_set_cursor(&win, cursor);
342}
343
344void animate(void) {
345 if (img_frame_animate(&img, false)) {
346 redraw();
347 set_timeout(animate, img.multi.frames[img.multi.sel].delay, true);
348 }
349}
350
351void clear_resize(void) {
352 resized = false;
353}
354
355bool keymask(const keymap_t *k, unsigned int state) {
356 return (k->ctrl ? ControlMask : 0) == (state & ControlMask);
357}
358
359bool buttonmask(const button_t *b, unsigned int state) {
360 return ((b->ctrl ? ControlMask : 0) | (b->shift ? ShiftMask : 0)) ==
361 (state & (ControlMask | ShiftMask));
362}
363
364void on_keypress(XKeyEvent *kev) {
365 int i;
366 KeySym ksym;
367 char key;
368
369 if (kev == NULL)
370 return;
371
372 XLookupString(kev, &key, 1, &ksym, NULL);
373
374 if ((ksym == XK_Escape || (key >= '0' && key <= '9')) &&
375 (kev->state & ControlMask) == 0)
376 {
377 /* number prefix for commands */
378 prefix = ksym == XK_Escape ? 0 : prefix * 10 + (int) (key - '0');
379 return;
380 }
381
382 for (i = 0; i < ARRLEN(keys); i++) {
383 if (keys[i].ksym == ksym && keymask(&keys[i], kev->state)) {
384 if (keys[i].cmd != NULL && keys[i].cmd(keys[i].arg))
385 redraw();
386 prefix = 0;
387 return;
388 }
389 }
390}
391
392void on_buttonpress(XButtonEvent *bev) {
393 int i, sel;
394
395 if (bev == NULL)
396 return;
397
398 if (mode == MODE_IMAGE) {
399 win_set_cursor(&win, CURSOR_ARROW);
400 set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
401
402 for (i = 0; i < ARRLEN(buttons); i++) {
403 if (buttons[i].button == bev->button &&
404 buttonmask(&buttons[i], bev->state))
405 {
406 if (buttons[i].cmd != NULL && buttons[i].cmd(buttons[i].arg))
407 redraw();
408 return;
409 }
410 }
411 } else {
412 /* thumbnail mode (hard-coded) */
413 switch (bev->button) {
414 case Button1:
415 if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) {
416 if (sel == tns.sel) {
417 mode = MODE_IMAGE;
418 set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
419 load_image(tns.sel);
420 } else {
421 tns_highlight(&tns, tns.sel, false);
422 tns_highlight(&tns, sel, true);
423 tns.sel = sel;
424 }
425 redraw();
426 break;
427 }
428 break;
429 case Button4:
430 case Button5:
431 if (tns_scroll(&tns, bev->button == Button4 ? DIR_UP : DIR_DOWN,
432 (bev->state & ControlMask) != 0))
433 redraw();
434 break;
435 }
436 }
437}
438
439void run(void) {
440 int xfd;
441 fd_set fds;
442 struct timeval timeout;
443 XEvent ev, nextev;
444 bool discard;
445
446 redraw();
447
448 while (true) {
449 while (mode == MODE_THUMB && tns.cnt < filecnt &&
450 XPending(win.env.dpy) == 0)
451 {
452 /* load thumbnails */
453 set_timeout(redraw, TO_REDRAW_THUMBS, false);
454 if (tns_load(&tns, tns.cnt, &files[tns.cnt], false, false)) {
455 tns.cnt++;
456 } else {
457 remove_file(tns.cnt, false);
458 if (tns.sel >= tns.cnt)
459 tns.sel--;
460 }
461 if (tns.cnt == filecnt)
462 redraw();
463 else
464 check_timeouts(NULL);
465 }
466
467 while (XPending(win.env.dpy) == 0 && check_timeouts(&timeout)) {
468 /* wait for timeouts */
469 xfd = ConnectionNumber(win.env.dpy);
470 FD_ZERO(&fds);
471 FD_SET(xfd, &fds);
472 select(xfd + 1, &fds, 0, 0, &timeout);
473 }
474
475 do {
476 XNextEvent(win.env.dpy, &ev);
477 discard = false;
478 if (XEventsQueued(win.env.dpy, QueuedAlready) > 0) {
479 XPeekEvent(win.env.dpy, &nextev);
480 switch (ev.type) {
481 case ConfigureNotify:
482 discard = ev.type == nextev.type;
483 break;
484 case KeyPress:
485 discard = (nextev.type == KeyPress || nextev.type == KeyRelease)
486 && ev.xkey.keycode == nextev.xkey.keycode;
487 break;
488 }
489 }
490 } while (discard);
491
492 switch (ev.type) {
493 /* handle events */
494 case ButtonPress:
495 on_buttonpress(&ev.xbutton);
496 break;
497 case ClientMessage:
498 if ((Atom) ev.xclient.data.l[0] == wm_delete_win)
499 return;
500 break;
501 case ConfigureNotify:
502 if (win_configure(&win, &ev.xconfigure)) {
503 if (mode == MODE_IMAGE) {
504 img.dirty = true;
505 img.checkpan = true;
506 } else {
507 tns.dirty = true;
508 }
509 if (!resized || win.fullscreen) {
510 redraw();
511 set_timeout(clear_resize, TO_REDRAW_RESIZE, false);
512 resized = true;
513 } else {
514 set_timeout(redraw, TO_REDRAW_RESIZE, false);
515 }
516 }
517 break;
518 case Expose:
519 win_expose(&win, &ev.xexpose);
520 break;
521 case KeyPress:
522 on_keypress(&ev.xkey);
523 break;
524 case MotionNotify:
525 if (mode == MODE_IMAGE) {
526 win_set_cursor(&win, CURSOR_ARROW);
527 set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
528 }
529 break;
530 }
531 }
532}
533
534int fncmp(const void *a, const void *b) {
535 return strcoll(((fileinfo_t*) a)->name, ((fileinfo_t*) b)->name);
536}
537
538int main(int argc, char **argv) {
539 int i, start;
540 size_t n;
541 ssize_t len;
542 char *filename;
543 const char *homedir;
544 struct stat fstats;
545 r_dir_t dir;
546
547 parse_options(argc, argv);
548
549 if (options->clean_cache) {
550 tns_init(&tns, 0, NULL);
551 tns_clean_cache(&tns);
552 exit(EXIT_SUCCESS);
553 }
554
555 if (options->filecnt == 0) {
556 print_usage();
557 exit(EXIT_FAILURE);
558 }
559
560 if (options->recursive || options->from_stdin)
561 filecnt = FILENAME_CNT;
562 else
563 filecnt = options->filecnt;
564
565 files = (fileinfo_t*) s_malloc(filecnt * sizeof(fileinfo_t));
566 fileidx = 0;
567
568 /* build file list: */
569 if (options->from_stdin) {
570 filename = NULL;
571 while ((len = get_line(&filename, &n, stdin)) > 0) {
572 if (filename[len-1] == '\n')
573 filename[len-1] = '\0';
574 check_add_file(filename);
575 }
576 if (filename != NULL)
577 free(filename);
578 } else {
579 for (i = 0; i < options->filecnt; i++) {
580 filename = options->filenames[i];
581
582 if (stat(filename, &fstats) < 0) {
583 warn("could not stat file: %s", filename);
584 continue;
585 }
586 if (!S_ISDIR(fstats.st_mode)) {
587 check_add_file(filename);
588 } else {
589 if (!options->recursive) {
590 warn("ignoring directory: %s", filename);
591 continue;
592 }
593 if (r_opendir(&dir, filename) < 0) {
594 warn("could not open directory: %s", filename);
595 continue;
596 }
597 start = fileidx;
598 while ((filename = r_readdir(&dir)) != NULL) {
599 check_add_file(filename);
600 free((void*) filename);
601 }
602 r_closedir(&dir);
603 if (fileidx - start > 1)
604 qsort(files + start, fileidx - start, sizeof(fileinfo_t), fncmp);
605 }
606 }
607 }
608
609 if (fileidx == 0) {
610 fprintf(stderr, "sxiv: no valid image file given, aborting\n");
611 exit(EXIT_FAILURE);
612 }
613
614 filecnt = fileidx;
615 fileidx = options->startnum < filecnt ? options->startnum : 0;
616
617 win_init(&win);
618 img_init(&img, &win);
619
620 if ((homedir = getenv("HOME")) == NULL) {
621 warn("could not locate home directory");
622 } else {
623 len = strlen(homedir) + strlen(INFO_SCRIPT) + 2;
624 info_script = (char*) s_malloc(len);
625 snprintf(info_script, len, "%s/%s", homedir, INFO_SCRIPT);
626 if (access(info_script, X_OK) != 0) {
627 free(info_script);
628 info_script = NULL;
629 }
630 }
631
632 if (options->thumb_mode) {
633 mode = MODE_THUMB;
634 tns_init(&tns, filecnt, &win);
635 while (!tns_load(&tns, 0, &files[0], false, false))
636 remove_file(0, false);
637 tns.cnt = 1;
638 } else {
639 mode = MODE_IMAGE;
640 tns.thumbs = NULL;
641 load_image(fileidx);
642 }
643
644 win_open(&win);
645
646 run();
647 cleanup();
648
649 return 0;
650}