Krolyxon
Commit ac1300a

Branches

Refs →

nsxivmain.c

572 lines · 12.3 KB · C

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