Krolyxon
Commit 6d3bbc6

Branches

Refs →

nsxivmain.c

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