Krolyxon
Commit 6e575b0

Branches

Refs →

nsxivmain.c

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