Krolyxon
Commit 4cfb8a2

Branches

Refs →

nsxivmain.c

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