Krolyxon
Commit a309012

Branches

Refs →

nsxivmain.c

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