Krolyxon
Commit c5f6b2c

Branches

Refs →

nsxivmain.c

692 lines · 14.2 KB · C

1/* sxiv: main.c
2 * Copyright (c) 2011 Bert Muennich <muennich at informatik.hu-berlin.de>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 */
18
19#include <stdlib.h>
20#include <stdio.h>
21#include <string.h>
22#include <dirent.h>
23#include <sys/select.h>
24#include <sys/stat.h>
25#include <sys/time.h>
26
27#include <X11/Xlib.h>
28#include <X11/Xutil.h>
29#include <X11/keysym.h>
30
31#include "image.h"
32#include "options.h"
33#include "thumbs.h"
34#include "util.h"
35#include "window.h"
36
37typedef enum appmode_e {
38 MODE_NORMAL = 0,
39 MODE_THUMBS
40} appmode_t;
41
42void update_title();
43int check_append(const char*);
44void read_dir_rec(const char*);
45void run();
46
47appmode_t mode;
48img_t img;
49tns_t tns;
50win_t win;
51
52#define DNAME_CNT 512
53#define FNAME_CNT 1024
54const char **filenames;
55int filecnt, fileidx;
56size_t filesize;
57
58#define TITLE_LEN 256
59char win_title[TITLE_LEN];
60
61void cleanup() {
62 static int in = 0;
63
64 if (!in++) {
65 img_close(&img);
66 img_free(&img);
67 tns_free(&tns, &win);
68 win_close(&win);
69 }
70}
71
72int load_image() {
73 struct stat fstats;
74
75 img_close(&img);
76
77 if (!stat(filenames[fileidx], &fstats))
78 filesize = fstats.st_size;
79 else
80 filesize = 0;
81
82 return img_load(&img, filenames[fileidx]);
83}
84
85int main(int argc, char **argv) {
86 int i;
87 const char *filename;
88 struct stat fstats;
89
90 parse_options(argc, argv);
91
92 if (!options->filecnt) {
93 print_usage();
94 exit(1);
95 }
96
97 if (options->recursive || options->from_stdin)
98 filecnt = FNAME_CNT;
99 else
100 filecnt = options->filecnt;
101
102 filenames = (const char**) s_malloc(filecnt * sizeof(const char*));
103 fileidx = 0;
104
105 if (options->from_stdin) {
106 while ((filename = readline(stdin))) {
107 if (!*filename || !check_append(filename))
108 free((void*) filename);
109 }
110 } else {
111 for (i = 0; i < options->filecnt; ++i) {
112 filename = options->filenames[i];
113 if (!stat(filename, &fstats) && S_ISDIR(fstats.st_mode)) {
114 if (options->recursive)
115 read_dir_rec(filename);
116 else
117 warn("ignoring directory: %s", filename);
118 } else {
119 check_append(filename);
120 }
121 }
122 }
123
124 filecnt = fileidx;
125 fileidx = 0;
126
127 if (!filecnt) {
128 fprintf(stderr, "sxiv: no valid image filename given, aborting\n");
129 exit(1);
130 }
131
132 win_open(&win);
133 img_init(&img, &win);
134
135 if (options->thumbnails) {
136 mode = MODE_THUMBS;
137 tns_init(&tns, filecnt);
138 win_clear(&win);
139 win_draw(&win);
140 } else {
141 mode = MODE_NORMAL;
142 tns.thumbs = NULL;
143 load_image();
144 img_render(&img, &win);
145 }
146
147 update_title();
148 run();
149 cleanup();
150
151 return 0;
152}
153
154void update_title() {
155 int n;
156 float size;
157 const char *unit;
158
159 if (mode == MODE_THUMBS) {
160 n = snprintf(win_title, TITLE_LEN, "sxiv: [%d/%d] %s",
161 tns.cnt ? tns.sel + 1 : 0, tns.cnt,
162 tns.cnt ? filenames[tns.sel] : "");
163 } else {
164 if (img.im) {
165 size = filesize;
166 size_readable(&size, &unit);
167 n = snprintf(win_title, TITLE_LEN, "sxiv: [%d/%d] <%d%%> (%.2f%s) %s",
168 fileidx + 1, filecnt, (int) (img.zoom * 100.0), size, unit,
169 filenames[fileidx]);
170 } else {
171 n = snprintf(win_title, TITLE_LEN, "sxiv: [%d/%d] broken: %s",
172 fileidx + 1, filecnt, filenames[fileidx]);
173 }
174 }
175
176 if (n >= TITLE_LEN) {
177 win_title[TITLE_LEN - 2] = '.';
178 win_title[TITLE_LEN - 3] = '.';
179 win_title[TITLE_LEN - 4] = '.';
180 }
181
182 win_set_title(&win, win_title);
183}
184
185int check_append(const char *filename) {
186 if (!filename)
187 return 0;
188
189 if (img_check(filename)) {
190 if (fileidx == filecnt) {
191 filecnt *= 2;
192 filenames = (const char**) s_realloc(filenames,
193 filecnt * sizeof(const char*));
194 }
195 filenames[fileidx++] = filename;
196 return 1;
197 } else {
198 return 0;
199 }
200}
201
202int fncmp(const void *a, const void *b) {
203 return strcoll(*((char* const*) a), *((char* const*) b));
204}
205
206void read_dir_rec(const char *dirname) {
207 char *filename;
208 const char **dirnames;
209 int dircnt, diridx;
210 int fcnt, fstart;
211 unsigned char first;
212 size_t len;
213 DIR *dir;
214 struct dirent *dentry;
215 struct stat fstats;
216
217 if (!dirname)
218 return;
219
220 dircnt = DNAME_CNT;
221 diridx = first = 1;
222 dirnames = (const char**) s_malloc(dircnt * sizeof(const char*));
223 dirnames[0] = dirname;
224
225 fcnt = 0;
226 fstart = fileidx;
227
228 while (diridx > 0) {
229 dirname = dirnames[--diridx];
230 if (!(dir = opendir(dirname))) {
231 warn("could not open directory: %s", dirname);
232 } else {
233 while ((dentry = readdir(dir))) {
234 if (!strcmp(dentry->d_name, ".") || !strcmp(dentry->d_name, ".."))
235 continue;
236
237 len = strlen(dirname) + strlen(dentry->d_name) + 2;
238 filename = (char*) s_malloc(len * sizeof(char));
239 snprintf(filename, len, "%s/%s", dirname, dentry->d_name);
240
241 if (!stat(filename, &fstats) && S_ISDIR(fstats.st_mode)) {
242 if (diridx == dircnt) {
243 dircnt *= 2;
244 dirnames = (const char**) s_realloc(dirnames,
245 dircnt * sizeof(const char*));
246 }
247 dirnames[diridx++] = filename;
248 } else {
249 if (check_append(filename))
250 ++fcnt;
251 else
252 free(filename);
253 }
254 }
255 closedir(dir);
256 }
257
258 if (!first)
259 free((void*) dirname);
260 else
261 first = 0;
262 }
263
264 if (fcnt > 1)
265 qsort(filenames + fstart, fcnt, sizeof(char*), fncmp);
266
267 free(dirnames);
268}
269
270
271/* event handling */
272
273#define TO_WIN_RESIZE 75000;
274#define TO_IMAGE_DRAG 1000;
275#define TO_CURSOR_HIDE 1500000;
276#define TO_THUMBS_LOAD 75000;
277int timo_cursor;
278int timo_redraw;
279
280unsigned char drag;
281int mox, moy;
282
283void redraw() {
284 if (mode == MODE_NORMAL)
285 img_render(&img, &win);
286 else
287 tns_render(&tns, &win);
288 update_title();
289 timo_redraw = 0;
290}
291
292void on_keypress(XKeyEvent *kev) {
293 int x, y;
294 unsigned int w, h;
295 char key;
296 KeySym ksym;
297 int changed;
298
299 if (!kev)
300 return;
301
302 XLookupString(kev, &key, 1, &ksym, NULL);
303 changed = 0;
304
305 if (mode == MODE_NORMAL) {
306 switch (ksym) {
307 /* navigate image list */
308 case XK_n:
309 case XK_space:
310 if (fileidx + 1 < filecnt) {
311 ++fileidx;
312 changed = load_image();
313 }
314 break;
315 case XK_p:
316 case XK_BackSpace:
317 if (fileidx > 0) {
318 --fileidx;
319 changed = load_image();
320 }
321 break;
322 case XK_bracketleft:
323 if (fileidx != 0) {
324 fileidx = MAX(0, fileidx - 10);
325 changed = load_image();
326 }
327 break;
328 case XK_bracketright:
329 if (fileidx != filecnt - 1) {
330 fileidx = MIN(fileidx + 10, filecnt - 1);
331 changed = load_image();
332 }
333 break;
334 case XK_g:
335 if (fileidx != 0) {
336 fileidx = 0;
337 changed = load_image();
338 }
339 break;
340 case XK_G:
341 if (fileidx != filecnt - 1) {
342 fileidx = filecnt - 1;
343 changed = load_image();
344 }
345 break;
346
347 /* zooming */
348 case XK_plus:
349 case XK_equal:
350 changed = img_zoom_in(&img);
351 break;
352 case XK_minus:
353 changed = img_zoom_out(&img);
354 break;
355 case XK_0:
356 changed = img_zoom(&img, 1.0);
357 break;
358 case XK_w:
359 if ((changed = img_fit_win(&img, &win)))
360 img_center(&img, &win);
361 break;
362
363 /* panning */
364 case XK_h:
365 case XK_Left:
366 changed = img_pan(&img, &win, PAN_LEFT);
367 break;
368 case XK_j:
369 case XK_Down:
370 changed = img_pan(&img, &win, PAN_DOWN);
371 break;
372 case XK_k:
373 case XK_Up:
374 changed = img_pan(&img, &win, PAN_UP);
375 break;
376 case XK_l:
377 case XK_Right:
378 changed = img_pan(&img, &win, PAN_RIGHT);
379 break;
380
381 /* rotation */
382 case XK_less:
383 img_rotate_left(&img, &win);
384 changed = 1;
385 break;
386 case XK_greater:
387 img_rotate_right(&img, &win);
388 changed = 1;
389 break;
390
391 /* control window */
392 case XK_W:
393 x = MAX(0, win.x + img.x);
394 y = MAX(0, win.y + img.y);
395 w = img.w * img.zoom;
396 h = img.h * img.zoom;
397 if ((changed = win_moveresize(&win, x, y, w, h))) {
398 img.x = x - win.x;
399 img.y = y - win.y;
400 }
401 break;
402
403 /* switch to thumbnail mode */
404 case XK_Return:
405 if (!tns.thumbs)
406 tns_init(&tns, filecnt);
407 img_close(&img);
408 mode = MODE_THUMBS;
409 win_set_cursor(&win, CURSOR_ARROW);
410 timo_cursor = 0;
411 tns.sel = fileidx;
412 changed = tns.dirty = 1;
413 break;
414
415 /* miscellaneous */
416 case XK_a:
417 img_toggle_antialias(&img);
418 changed = 1;
419 break;
420 case XK_r:
421 changed = load_image();
422 break;
423 }
424 } else {
425 /* thumbnail mode */
426 switch (ksym) {
427 /* open selected image */
428 case XK_Return:
429 fileidx = tns.sel;
430 load_image();
431 mode = MODE_NORMAL;
432 win_set_cursor(&win, CURSOR_NONE);
433 changed = 1;
434 break;
435
436 /* move selection */
437 case XK_h:
438 case XK_Left:
439 changed = tns_move_selection(&tns, &win, TNS_LEFT);
440 break;
441 case XK_j:
442 case XK_Down:
443 changed = tns_move_selection(&tns, &win, TNS_DOWN);
444 break;
445 case XK_k:
446 case XK_Up:
447 changed = tns_move_selection(&tns, &win, TNS_UP);
448 break;
449 case XK_l:
450 case XK_Right:
451 changed = tns_move_selection(&tns, &win, TNS_RIGHT);
452 break;
453 case XK_g:
454 if (tns.sel != 0) {
455 tns.sel = 0;
456 changed = tns.dirty = 1;
457 }
458 break;
459 case XK_G:
460 if (tns.sel != tns.cnt - 1) {
461 tns.sel = tns.cnt - 1;
462 changed = tns.dirty = 1;
463 }
464 }
465 }
466
467 /* common key mappings */
468 switch (ksym) {
469 case XK_Escape:
470 cleanup();
471 exit(2);
472 case XK_q:
473 cleanup();
474 exit(0);
475
476 case XK_f:
477 win_toggle_fullscreen(&win);
478 /* render on next configurenotify */
479 break;
480 }
481
482 if (changed)
483 redraw();
484}
485
486void on_buttonpress(XButtonEvent *bev) {
487 int changed, sel;
488 unsigned int mask;
489
490 if (!bev)
491 return;
492
493 mask = CLEANMASK(bev->state);
494 changed = 0;
495
496 if (mode == MODE_NORMAL) {
497 switch (bev->button) {
498 case Button1:
499 if (fileidx + 1 < filecnt) {
500 ++fileidx;
501 changed = load_image();
502 }
503 break;
504 case Button2:
505 mox = bev->x;
506 moy = bev->y;
507 win_set_cursor(&win, CURSOR_HAND);
508 timo_cursor = 0;
509 drag = 1;
510 break;
511 case Button3:
512 if (fileidx > 0) {
513 --fileidx;
514 changed = load_image();
515 }
516 break;
517 case Button4:
518 if (mask == ControlMask)
519 changed = img_zoom_in(&img);
520 else if (mask == ShiftMask)
521 changed = img_pan(&img, &win, PAN_LEFT);
522 else
523 changed = img_pan(&img, &win, PAN_UP);
524 break;
525 case Button5:
526 if (mask == ControlMask)
527 changed = img_zoom_out(&img);
528 else if (mask == ShiftMask)
529 changed = img_pan(&img, &win, PAN_RIGHT);
530 else
531 changed = img_pan(&img, &win, PAN_DOWN);
532 break;
533 case 6:
534 changed = img_pan(&img, &win, PAN_LEFT);
535 break;
536 case 7:
537 changed = img_pan(&img, &win, PAN_RIGHT);
538 break;
539 }
540 } else {
541 /* thumbnail mode */
542 switch (bev->button) {
543 case Button1:
544 if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) {
545 if (sel == tns.sel) {
546 fileidx = tns.sel;
547 load_image();
548 mode = MODE_NORMAL;
549 timo_cursor = TO_CURSOR_HIDE;
550 } else {
551 tns_highlight(&tns, &win, tns.sel, False);
552 tns_highlight(&tns, &win, sel, True);
553 tns.sel = sel;
554 }
555 changed = 1;
556 break;
557 }
558 break;
559 case Button4:
560 changed = tns_scroll(&tns, TNS_UP);
561 break;
562 case Button5:
563 changed = tns_scroll(&tns, TNS_DOWN);
564 break;
565 }
566 }
567
568 if (changed)
569 redraw();
570}
571
572void on_motionnotify(XMotionEvent *mev) {
573 if (!mev)
574 return;
575
576 if (mev->x >= 0 && mev->x <= win.w && mev->y >= 0 && mev->y <= win.h) {
577 if (img_move(&img, &win, mev->x - mox, mev->y - moy))
578 timo_redraw = TO_IMAGE_DRAG;
579
580 mox = mev->x;
581 moy = mev->y;
582 }
583}
584
585void run() {
586 int xfd, timeout;
587 fd_set fds;
588 struct timeval tt, t0, t1;
589 XEvent ev;
590
591 timo_cursor = timo_redraw = 0;
592 drag = 0;
593
594 if (mode == MODE_NORMAL)
595 timo_cursor = TO_CURSOR_HIDE;
596
597 while (1) {
598 if (mode == MODE_THUMBS && tns.cnt < filecnt) {
599 win_set_cursor(&win, CURSOR_WATCH);
600 gettimeofday(&t0, 0);
601
602 while (!XPending(win.env.dpy) && tns.cnt < filecnt) {
603 tns_load(&tns, &win, filenames[tns.cnt]);
604 gettimeofday(&t1, 0);
605 if (TV_TO_DOUBLE(t1) - TV_TO_DOUBLE(t0) >= 0.25)
606 break;
607 }
608 if (tns.cnt == filecnt)
609 win_set_cursor(&win, CURSOR_ARROW);
610 if (!XPending(win.env.dpy)) {
611 redraw();
612 continue;
613 } else {
614 timo_redraw = TO_THUMBS_LOAD;
615 }
616 } else if (timo_cursor || timo_redraw) {
617 gettimeofday(&t0, 0);
618 if (timo_cursor && timo_redraw)
619 timeout = MIN(timo_cursor, timo_redraw);
620 else if (timo_cursor)
621 timeout = timo_cursor;
622 else
623 timeout = timo_redraw;
624 tt.tv_sec = timeout / 1000000;
625 tt.tv_usec = timeout % 1000000;
626 xfd = ConnectionNumber(win.env.dpy);
627 FD_ZERO(&fds);
628 FD_SET(xfd, &fds);
629
630 if (!XPending(win.env.dpy))
631 select(xfd + 1, &fds, 0, 0, &tt);
632 gettimeofday(&t1, 0);
633 timeout = MIN((TV_TO_DOUBLE(t1) - TV_TO_DOUBLE(t0)) * 1000000, timeout);
634
635 /* timeouts fired? */
636 if (timo_cursor) {
637 timo_cursor = MAX(0, timo_cursor - timeout);
638 if (!timo_cursor)
639 win_set_cursor(&win, CURSOR_NONE);
640 }
641 if (timo_redraw) {
642 timo_redraw = MAX(0, timo_redraw - timeout);
643 if (!timo_redraw)
644 redraw();
645 }
646 if (!XPending(win.env.dpy) && (timo_cursor || timo_redraw))
647 continue;
648 }
649
650 if (!XNextEvent(win.env.dpy, &ev)) {
651 switch (ev.type) {
652 case KeyPress:
653 on_keypress(&ev.xkey);
654 break;
655 case ButtonPress:
656 on_buttonpress(&ev.xbutton);
657 break;
658 case ButtonRelease:
659 if (ev.xbutton.button == Button2) {
660 drag = 0;
661 if (mode == MODE_NORMAL) {
662 win_set_cursor(&win, CURSOR_ARROW);
663 timo_cursor = TO_CURSOR_HIDE;
664 }
665 }
666 break;
667 case MotionNotify:
668 if (drag) {
669 on_motionnotify(&ev.xmotion);
670 } else if (mode == MODE_NORMAL) {
671 if (!timo_cursor)
672 win_set_cursor(&win, CURSOR_ARROW);
673 timo_cursor = TO_CURSOR_HIDE;
674 }
675 break;
676 case ConfigureNotify:
677 if (win_configure(&win, &ev.xconfigure)) {
678 timo_redraw = TO_WIN_RESIZE;
679 if (mode == MODE_NORMAL)
680 img.checkpan = 1;
681 else
682 tns.dirty = 1;
683 }
684 break;
685 case ClientMessage:
686 if ((Atom) ev.xclient.data.l[0] == wm_delete_win)
687 return;
688 break;
689 }
690 }
691 }
692}