Krolyxon
Commit e810072

Branches

Refs →

nsxivmain.c

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