Krolyxon
Commit 5106472

Branches

Refs →

nsxivmain.c

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