Krolyxon
Commit 26c2179

Branches

Refs →

nsxivmain.c

521 lines · 10.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
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
26#include <X11/Xlib.h>
27#include <X11/Xutil.h>
28#include <X11/keysym.h>
29
30#include "image.h"
31#include "options.h"
32#include "thumbs.h"
33#include "util.h"
34#include "window.h"
35
36typedef enum appmode_e {
37 MODE_NORMAL = 0,
38 MODE_THUMBS
39} appmode_t;
40
41void update_title();
42int check_append(const char*);
43void read_dir_rec(const char*);
44void run();
45
46appmode_t mode;
47img_t img;
48tns_t tns;
49win_t win;
50
51#define DNAME_CNT 512
52#define FNAME_CNT 1024
53const char **filenames;
54int filecnt, fileidx;
55size_t filesize;
56
57#define TITLE_LEN 256
58char win_title[TITLE_LEN];
59
60void cleanup() {
61 static int in = 0;
62
63 if (!in++) {
64 img_free(&img);
65 tns_free(&tns, &win);
66 win_close(&win);
67 }
68}
69
70int load_image() {
71 struct stat fstats;
72
73 if (!stat(filenames[fileidx], &fstats))
74 filesize = fstats.st_size;
75 else
76 filesize = 0;
77
78 return img_load(&img, filenames[fileidx]);
79}
80
81int main(int argc, char **argv) {
82 int i;
83 const char *filename;
84 struct stat fstats;
85
86 parse_options(argc, argv);
87
88 if (!options->filecnt) {
89 print_usage();
90 exit(1);
91 }
92
93 if (options->recursive || options->from_stdin)
94 filecnt = FNAME_CNT;
95 else
96 filecnt = options->filecnt;
97
98 filenames = (const char**) s_malloc(filecnt * sizeof(const char*));
99 fileidx = 0;
100
101 if (options->from_stdin) {
102 while ((filename = readline(stdin))) {
103 if (!*filename || !check_append(filename))
104 free((void*) filename);
105 }
106 } else {
107 for (i = 0; i < options->filecnt; ++i) {
108 filename = options->filenames[i];
109 if (!stat(filename, &fstats) && S_ISDIR(fstats.st_mode)) {
110 if (options->recursive)
111 read_dir_rec(filename);
112 else
113 warn("ignoring directory: %s", filename);
114 } else {
115 check_append(filename);
116 }
117 }
118 }
119
120 filecnt = fileidx;
121 fileidx = 0;
122
123 if (!filecnt) {
124 fprintf(stderr, "sxiv: no valid image filename given, aborting\n");
125 exit(1);
126 }
127
128 win_open(&win);
129 img_init(&img, &win);
130
131 if (options->thumbnails)
132 tns_load(&tns, &win, filenames, filecnt);
133
134 if (options->thumbnails == 2) {
135 mode = MODE_THUMBS;
136 tns.first = tns.sel = 0;
137 tns_render(&tns, &win);
138 } else {
139 mode = MODE_NORMAL;
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 (img.valid) {
157 size = filesize;
158 size_readable(&size, &unit);
159 n = snprintf(win_title, TITLE_LEN, "sxiv: [%d/%d] <%d%%> (%.2f%s) %s",
160 fileidx + 1, filecnt, (int) (img.zoom * 100.0), size, unit,
161 filenames[fileidx]);
162 } else {
163 n = snprintf(win_title, TITLE_LEN, "sxiv: [%d/%d] broken: %s",
164 fileidx + 1, filecnt, filenames[fileidx]);
165 }
166
167 if (n >= TITLE_LEN) {
168 win_title[TITLE_LEN - 2] = '.';
169 win_title[TITLE_LEN - 3] = '.';
170 win_title[TITLE_LEN - 4] = '.';
171 }
172
173 win_set_title(&win, win_title);
174}
175
176int check_append(const char *filename) {
177 if (!filename)
178 return 0;
179
180 if (img_check(filename)) {
181 if (fileidx == filecnt) {
182 filecnt *= 2;
183 filenames = (const char**) s_realloc(filenames,
184 filecnt * sizeof(const char*));
185 }
186 filenames[fileidx++] = filename;
187 return 1;
188 } else {
189 return 0;
190 }
191}
192
193void read_dir_rec(const char *dirname) {
194 char *filename;
195 const char **dirnames;
196 int dircnt, diridx;
197 unsigned char first;
198 size_t len;
199 DIR *dir;
200 struct dirent *dentry;
201 struct stat fstats;
202
203 if (!dirname)
204 return;
205
206 dircnt = DNAME_CNT;
207 diridx = first = 1;
208 dirnames = (const char**) s_malloc(dircnt * sizeof(const char*));
209 dirnames[0] = dirname;
210
211 while (diridx > 0) {
212 dirname = dirnames[--diridx];
213 if (!(dir = opendir(dirname))) {
214 warn("could not open directory: %s", dirname);
215 } else {
216 while ((dentry = readdir(dir))) {
217 if (!strcmp(dentry->d_name, ".") || !strcmp(dentry->d_name, ".."))
218 continue;
219
220 len = strlen(dirname) + strlen(dentry->d_name) + 2;
221 filename = (char*) s_malloc(len * sizeof(char));
222 snprintf(filename, len, "%s/%s", dirname, dentry->d_name);
223
224 if (!stat(filename, &fstats) && S_ISDIR(fstats.st_mode)) {
225 if (diridx == dircnt) {
226 dircnt *= 2;
227 dirnames = (const char**) s_realloc(dirnames,
228 dircnt * sizeof(const char*));
229 }
230 dirnames[diridx++] = filename;
231 } else {
232 if (!check_append(filename))
233 free(filename);
234 }
235 }
236 closedir(dir);
237 }
238
239 if (!first)
240 free((void*) dirname);
241 else
242 first = 0;
243 }
244
245 free(dirnames);
246}
247
248
249/* event handling */
250
251unsigned char timeout;
252int mox, moy;
253
254void redraw() {
255 if (mode == MODE_NORMAL)
256 img_render(&img, &win);
257 else
258 tns_render(&tns, &win);
259 update_title();
260 timeout = 0;
261}
262
263void on_keypress(XKeyEvent *kev) {
264 int x, y;
265 unsigned int w, h;
266 char key;
267 KeySym ksym;
268 int changed;
269
270 if (!kev)
271 return;
272
273 XLookupString(kev, &key, 1, &ksym, NULL);
274 changed = 0;
275
276 switch (ksym) {
277 case XK_Escape:
278 cleanup();
279 exit(2);
280 case XK_q:
281 cleanup();
282 exit(0);
283
284 /* navigate image list */
285 case XK_n:
286 case XK_space:
287 if (fileidx + 1 < filecnt) {
288 ++fileidx;
289 changed = load_image();
290 }
291 break;
292 case XK_p:
293 case XK_BackSpace:
294 if (fileidx > 0) {
295 --fileidx;
296 changed = load_image();
297 }
298 break;
299 case XK_bracketleft:
300 if (fileidx != 0) {
301 fileidx = MAX(0, fileidx - 10);
302 changed = load_image();
303 }
304 break;
305 case XK_bracketright:
306 if (fileidx != filecnt - 1) {
307 fileidx = MIN(fileidx + 10, filecnt - 1);
308 changed = load_image();
309 }
310 break;
311 case XK_g:
312 if (fileidx != 0) {
313 fileidx = 0;
314 changed = load_image();
315 }
316 break;
317 case XK_G:
318 if (fileidx != filecnt - 1) {
319 fileidx = filecnt - 1;
320 changed = load_image();
321 }
322 break;
323
324 /* zooming */
325 case XK_plus:
326 case XK_equal:
327 changed = img_zoom_in(&img);
328 break;
329 case XK_minus:
330 changed = img_zoom_out(&img);
331 break;
332 case XK_0:
333 changed = img_zoom(&img, 1.0);
334 break;
335 case XK_w:
336 if ((changed = img_fit_win(&img, &win)))
337 img_center(&img, &win);
338 break;
339
340 /* panning */
341 case XK_h:
342 case XK_Left:
343 changed = img_pan(&img, &win, PAN_LEFT);
344 break;
345 case XK_j:
346 case XK_Down:
347 changed = img_pan(&img, &win, PAN_DOWN);
348 break;
349 case XK_k:
350 case XK_Up:
351 changed = img_pan(&img, &win, PAN_UP);
352 break;
353 case XK_l:
354 case XK_Right:
355 changed = img_pan(&img, &win, PAN_RIGHT);
356 break;
357
358 /* rotation */
359 case XK_less:
360 img_rotate_left(&img, &win);
361 changed = 1;
362 break;
363 case XK_greater:
364 img_rotate_right(&img, &win);
365 changed = 1;
366 break;
367
368 /* control window */
369 case XK_f:
370 win_toggle_fullscreen(&win);
371 /* render on next configurenotify */
372 break;
373 case XK_W:
374 x = win.x + img.x;
375 y = win.y + img.y;
376 w = img.w * img.zoom;
377 h = img.h * img.zoom;
378 if ((changed = win_moveresize(&win, x, y, w, h))) {
379 img.x = x - win.x;
380 img.y = y - win.y;
381 }
382 break;
383
384 /* miscellaneous */
385 case XK_a:
386 img_toggle_antialias(&img);
387 changed = 1;
388 break;
389 case XK_r:
390 changed = load_image();
391 break;
392 }
393
394 if (changed)
395 redraw();
396}
397
398void on_buttonpress(XButtonEvent *bev) {
399 int changed;
400 unsigned int mask;
401
402 if (!bev)
403 return;
404
405 mask = CLEANMASK(bev->state);
406 changed = 0;
407
408 switch (bev->button) {
409 case Button1:
410 if (fileidx + 1 < filecnt) {
411 ++fileidx;
412 changed = load_image();
413 }
414 break;
415 case Button2:
416 mox = bev->x;
417 moy = bev->y;
418 win_set_cursor(&win, CURSOR_HAND);
419 break;
420 case Button3:
421 if (fileidx > 0) {
422 --fileidx;
423 changed = load_image();
424 }
425 break;
426 case Button4:
427 if (mask == ControlMask)
428 changed = img_zoom_in(&img);
429 else if (mask == ShiftMask)
430 changed = img_pan(&img, &win, PAN_LEFT);
431 else
432 changed = img_pan(&img, &win, PAN_UP);
433 break;
434 case Button5:
435 if (mask == ControlMask)
436 changed = img_zoom_out(&img);
437 else if (mask == ShiftMask)
438 changed = img_pan(&img, &win, PAN_RIGHT);
439 else
440 changed = img_pan(&img, &win, PAN_DOWN);
441 break;
442 case 6:
443 changed = img_pan(&img, &win, PAN_LEFT);
444 break;
445 case 7:
446 changed = img_pan(&img, &win, PAN_RIGHT);
447 break;
448 }
449
450 if (changed)
451 redraw();
452}
453
454void on_motionnotify(XMotionEvent *mev) {
455 if (!mev)
456 return;
457
458 if (mev->x >= 0 && mev->x <= win.w && mev->y >= 0 && mev->y <= win.h) {
459 if (img_move(&img, &win, mev->x - mox, mev->y - moy))
460 timeout = 1;
461
462 mox = mev->x;
463 moy = mev->y;
464 }
465}
466
467void run() {
468 int xfd;
469 fd_set fds;
470 struct timeval t;
471 XEvent ev;
472
473 timeout = 0;
474
475 while (1) {
476 if (timeout) {
477 t.tv_sec = 0;
478 t.tv_usec = 250;
479 xfd = ConnectionNumber(win.env.dpy);
480 FD_ZERO(&fds);
481 FD_SET(xfd, &fds);
482
483 if (!XPending(win.env.dpy) && !select(xfd + 1, &fds, 0, 0, &t)) {
484 timeout = 0;
485 if (mode == MODE_NORMAL)
486 img_render(&img, &win);
487 else
488 tns_render(&tns, &win);
489 }
490 }
491
492 if (!XNextEvent(win.env.dpy, &ev)) {
493 switch (ev.type) {
494 case KeyPress:
495 on_keypress(&ev.xkey);
496 break;
497 case ButtonPress:
498 on_buttonpress(&ev.xbutton);
499 break;
500 case ButtonRelease:
501 if (ev.xbutton.button == Button2)
502 win_set_cursor(&win, CURSOR_ARROW);
503 break;
504 case MotionNotify:
505 on_motionnotify(&ev.xmotion);
506 break;
507 case ConfigureNotify:
508 if (win_configure(&win, &ev.xconfigure)) {
509 timeout = 1;
510 if (mode == MODE_NORMAL)
511 img.checkpan = 1;
512 }
513 break;
514 case ClientMessage:
515 if ((Atom) ev.xclient.data.l[0] == wm_delete_win)
516 return;
517 break;
518 }
519 }
520 }
521}