Krolyxon
Commit c78f55e

Branches

Refs →

nsxivmain.c

594 lines · 13.2 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 gettimeofday(&now, 0);
186 while (i < ARRLEN(timeouts)) {
187 if (timeouts[i].active) {
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;
423
424 redraw();
425
426 while (true) {
427 while (mode == MODE_THUMB && tns.cnt < filecnt &&
428 XPending(win.env.dpy) == 0)
429 {
430 /* load thumbnails */
431 set_timeout(redraw, TO_REDRAW_THUMBS, false);
432 if (tns_load(&tns, tns.cnt, &files[tns.cnt], false, false)) {
433 tns.cnt++;
434 } else {
435 remove_file(tns.cnt, false);
436 if (tns.sel >= tns.cnt)
437 tns.sel--;
438 }
439 if (tns.cnt == filecnt)
440 redraw();
441 else
442 check_timeouts(NULL);
443 }
444
445 while (XPending(win.env.dpy) == 0 && check_timeouts(&timeout)) {
446 /* wait for timeouts */
447 xfd = ConnectionNumber(win.env.dpy);
448 FD_ZERO(&fds);
449 FD_SET(xfd, &fds);
450 select(xfd + 1, &fds, 0, 0, &timeout);
451 }
452
453 XNextEvent(win.env.dpy, &ev);
454 switch (ev.type) {
455 /* handle events */
456 case ButtonPress:
457 on_buttonpress(&ev.xbutton);
458 break;
459 case ClientMessage:
460 if ((Atom) ev.xclient.data.l[0] == wm_delete_win)
461 return;
462 break;
463 case ConfigureNotify:
464 if (win_configure(&win, &ev.xconfigure)) {
465 if (mode == MODE_IMAGE)
466 img.checkpan = true;
467 else
468 tns.dirty = true;
469 if (!resized || win.fullscreen) {
470 redraw();
471 set_timeout(clear_resize, TO_REDRAW_RESIZE, false);
472 resized = true;
473 } else {
474 set_timeout(redraw, TO_REDRAW_RESIZE, false);
475 }
476 }
477 break;
478 case KeyPress:
479 on_keypress(&ev.xkey);
480 break;
481 case MotionNotify:
482 if (mode == MODE_IMAGE) {
483 win_set_cursor(&win, CURSOR_ARROW);
484 set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
485 }
486 break;
487 }
488 }
489}
490
491int fncmp(const void *a, const void *b) {
492 return strcoll(((fileinfo_t*) a)->name, ((fileinfo_t*) b)->name);
493}
494
495int main(int argc, char **argv) {
496 int i, start;
497 size_t n;
498 ssize_t len;
499 char *filename;
500 struct stat fstats;
501 r_dir_t dir;
502
503 parse_options(argc, argv);
504
505 if (options->clean_cache) {
506 tns_init(&tns, 0, NULL);
507 tns_clean_cache(&tns);
508 exit(EXIT_SUCCESS);
509 }
510
511 if (options->filecnt == 0) {
512 print_usage();
513 exit(EXIT_FAILURE);
514 }
515
516 if (options->recursive || options->from_stdin)
517 filecnt = FILENAME_CNT;
518 else
519 filecnt = options->filecnt;
520
521 files = (fileinfo_t*) s_malloc(filecnt * sizeof(fileinfo_t));
522 fileidx = 0;
523
524 /* build file list: */
525 if (options->from_stdin) {
526 filename = NULL;
527 while ((len = get_line(&filename, &n, stdin)) > 0) {
528 if (filename[len-1] == '\n')
529 filename[len-1] = '\0';
530 check_add_file(filename);
531 }
532 if (filename != NULL)
533 free(filename);
534 } else {
535 for (i = 0; i < options->filecnt; i++) {
536 filename = options->filenames[i];
537
538 if (stat(filename, &fstats) < 0) {
539 warn("could not stat file: %s", filename);
540 continue;
541 }
542 if (!S_ISDIR(fstats.st_mode)) {
543 check_add_file(filename);
544 } else {
545 if (!options->recursive) {
546 warn("ignoring directory: %s", filename);
547 continue;
548 }
549 if (r_opendir(&dir, filename) < 0) {
550 warn("could not open directory: %s", filename);
551 continue;
552 }
553 start = fileidx;
554 while ((filename = r_readdir(&dir)) != NULL) {
555 check_add_file(filename);
556 free((void*) filename);
557 }
558 r_closedir(&dir);
559 if (fileidx - start > 1)
560 qsort(files + start, fileidx - start, sizeof(fileinfo_t), fncmp);
561 }
562 }
563 }
564
565 if (fileidx == 0) {
566 fprintf(stderr, "sxiv: no valid image file given, aborting\n");
567 exit(EXIT_FAILURE);
568 }
569
570 filecnt = fileidx;
571 fileidx = options->startnum < filecnt ? options->startnum : 0;
572
573 win_init(&win);
574 img_init(&img, &win);
575
576 if (options->thumb_mode) {
577 mode = MODE_THUMB;
578 tns_init(&tns, filecnt, &win);
579 while (!tns_load(&tns, 0, &files[0], false, false))
580 remove_file(0, false);
581 tns.cnt = 1;
582 } else {
583 mode = MODE_IMAGE;
584 tns.thumbs = NULL;
585 load_image(fileidx);
586 }
587
588 win_open(&win);
589
590 run();
591 cleanup();
592
593 return 0;
594}