From b8ff1677b1cbb4d3e769861d1e14234c6a38a80a Mon Sep 17 00:00:00 2001 From: Bert Date: Tue, 26 Jul 2011 18:01:29 +0200 Subject: [PATCH] Major code refactoring - Configurable key and mouse mappings in config.h - Put event handling code from main.c into events.[ch] --- Makefile | 4 +- config.h | 124 +++++++++-- events.c | 562 ++++++++++++++++++++++++++++++++++++++++++++++ events.h | 67 ++++++ image.c | 6 +- main.c | 650 ++++-------------------------------------------------- options.c | 1 + sxiv.1 | 8 +- thumbs.c | 10 +- types.h | 14 +- util.c | 8 +- window.c | 2 + 12 files changed, 808 insertions(+), 648 deletions(-) create mode 100644 events.c create mode 100644 events.h diff --git a/Makefile b/Makefile index 52622ca..2bdcce3 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ all: sxiv -VERSION = git-20110722 +VERSION = git-20110726 CC = gcc DESTDIR = @@ -9,7 +9,7 @@ CFLAGS = -Wall -pedantic -DVERSION=\"$(VERSION)\" LDFLAGS = LIBS = -lX11 -lImlib2 -SRC = image.c main.c options.c thumbs.c util.c window.c +SRC = events.o image.c main.c options.c thumbs.c util.c window.c OBJ = $(SRC:.c=.o) sxiv: $(OBJ) diff --git a/config.h b/config.h index d852091..aac4244 100644 --- a/config.h +++ b/config.h @@ -1,36 +1,120 @@ -/* default window dimensions (overwritten via -g option): */ +#ifdef _GENERAL_CONFIG + +/* enable external commands (defined below)? 0 = off, 1 = on: */ +enum { EXT_COMMANDS = 0 }; + +#endif +#ifdef _WINDOW_CONFIG + +/* default window dimensions (overwritten via -g option): */ enum { WIN_WIDTH = 800, WIN_HEIGHT = 600 }; -/* default color for window background: * - * (see X(7) "COLOR NAMES" section for valid values) */ +/* default color for window background: * + * (see X(7) "COLOR NAMES" section for valid values) */ static const char * const BG_COLOR = "#999999"; -/* default color for thumbnail selection: */ +/* default color for thumbnail selection: */ static const char * const SEL_COLOR = "#0066FF"; -/* how should images be scaled when they are loaded?: * - * (also controllable via -d/-s/-Z/-z options) * - * SCALE_DOWN: 100%, but fit large images into window, * - * SCALE_FIT: fit all images into window, * - * SCALE_ZOOM: use current zoom level, 100% at startup */ +#endif +#ifdef _IMAGE_CONFIG + +/* how should images be scaled when they are loaded?: * + * (also controllable via -d/-s/-Z/-z options) * + * SCALE_DOWN: 100%, but fit large images into window, * + * SCALE_FIT: fit all images into window, * + * SCALE_ZOOM: use current zoom level, 100% at startup */ static const scalemode_t SCALE_MODE = SCALE_DOWN; -/* levels (percent) to use when zooming via '-' and '+': */ +/* levels (percent) to use when zooming via '-' and '+': */ static const float zoom_levels[] = { 12.5, 25.0, 50.0, 75.0, 100.0, 150.0, 200.0, 400.0, 800.0 }; -/* default dimension of thumbnails (width == height): */ +#endif +#ifdef _THUMBS_CONFIG + +/* default dimension of thumbnails (width == height): */ enum { THUMB_SIZE = 60 }; -/* enable external commands (defined below)? 0=off, 1=on: */ -enum { EXT_COMMANDS = 0 }; +#endif +#ifdef _MAPPINGS_CONFIG -/* external commands and corresponding key mappings: */ -static const command_t commands[] = { - /* ctrl-... reload? command, '#' is replaced by filename */ - { ',', 1, "jpegtran -rotate 270 -copy all -outfile # #" }, - { '.', 1, "jpegtran -rotate 90 -copy all -outfile # #" }, - { '<', 1, "mogrify -rotate -90 #" }, - { '>', 1, "mogrify -rotate +90 #" } +/* keyboard mappings for image and thumbnail mode: */ +static const keymap_t keys[] = { + /* key function argument */ + { XK_q, quit, None }, + { XK_r, reload, None }, + { XK_f, toggle_fullscreen, None }, + { XK_a, toggle_antialias, None }, + { XK_A, toggle_alpha, None }, + { XK_Return, switch_mode, None }, + + { XK_g, first, None }, + { XK_G, last, None }, + { XK_n, navigate, +1 }, + { XK_space, navigate, +1 }, + { XK_p, navigate, -1 }, + { XK_BackSpace, navigate, -1 }, + { XK_bracketright, navigate, +10 }, + { XK_bracketleft, navigate, -10 }, + + { XK_D, remove_image, None }, + + { XK_h, move, DIR_LEFT }, + { XK_Left, move, DIR_LEFT }, + { XK_j, move, DIR_DOWN }, + { XK_Down, move, DIR_DOWN }, + { XK_k, move, DIR_UP }, + { XK_Up, move, DIR_UP }, + { XK_l, move, DIR_RIGHT }, + { XK_Right, move, DIR_RIGHT }, + + { XK_braceleft, scroll, DIR_LEFT }, + { XK_Next, scroll, DIR_DOWN }, + { XK_Prior, scroll, DIR_UP }, + { XK_braceright, scroll, DIR_RIGHT }, + + { XK_H, pan_edge, DIR_LEFT }, + { XK_J, pan_edge, DIR_DOWN }, + { XK_K, pan_edge, DIR_UP }, + { XK_L, pan_edge, DIR_RIGHT }, + + { XK_plus, zoom, +1 }, + { XK_equal, zoom, +1 }, + { XK_KP_Add, zoom, +1 }, + { XK_minus, zoom, -1 }, + { XK_KP_Subtract, zoom, -1 }, + { XK_0, zoom, 0 }, + { XK_KP_0, zoom, 0 }, + { XK_w, fit_to_win, None }, + { XK_W, fit_to_img, None }, + + { XK_less, rotate, DIR_LEFT }, + { XK_greater, rotate, DIR_RIGHT }, }; + +/* external commands and corresponding key mappings: */ +static const command_t commands[] = { + /* ctrl-... reload? command, '#' is replaced by filename */ + { XK_comma, True, "jpegtran -rotate 270 -copy all -outfile # #" }, + { XK_period, True, "jpegtran -rotate 90 -copy all -outfile # #" }, + { XK_less, True, "mogrify -rotate -90 #" }, + { XK_greater, True, "mogrify -rotate +90 #" } +}; + +/* mouse button mappings for image mode: */ +static const button_t buttons[] = { + /* modifier button function argument */ + { None, Button1, navigate, +1 }, + { None, Button3, navigate, -1 }, + { None, Button2, drag, None }, + { None, Button4, move, DIR_UP }, + { None, Button5, move, DIR_DOWN }, + { ShiftMask, Button4, move, DIR_LEFT }, + { ShiftMask, Button5, move, DIR_RIGHT }, + { ControlMask, Button4, zoom, +1 }, + { ControlMask, Button5, zoom, -1 }, +}; + +#endif diff --git a/events.c b/events.c new file mode 100644 index 0000000..daa88ef --- /dev/null +++ b/events.c @@ -0,0 +1,562 @@ +/* sxiv: events.c + * Copyright (c) 2011 Bert Muennich + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#define _GENERAL_CONFIG +#define _MAPPINGS_CONFIG + +#include +#include +#include +#include +#include +#include +#include + +#include "events.h" +#include "image.h" +#include "thumbs.h" +#include "types.h" +#include "util.h" +#include "window.h" +#include "config.h" + +/* timeouts in milliseconds: */ +enum { + TO_WIN_RESIZE = 75, + TO_IMAGE_DRAG = 1, + TO_CURSOR_HIDE = 1500, + TO_THUMBS_LOAD = 200 +}; + +void cleanup(); +void remove_file(int, unsigned char); +void load_image(int); +void update_title(); + +extern appmode_t mode; +extern img_t img; +extern tns_t tns; +extern win_t win; + +extern char **filenames; +extern int filecnt, fileidx; + +int timo_cursor; +int timo_redraw; +unsigned char dragging; +int mox, moy; + +int run_command(const char *cline, Bool reload) { + int fncnt, fnlen; + char *cn, *cmdline; + const char *co, *fname; + pid_t pid; + int ret, status; + + if (!cline || !*cline) + return 0; + + fncnt = 0; + co = cline - 1; + while ((co = strchr(co + 1, '#'))) + fncnt++; + + if (!fncnt) + return 0; + + ret = 0; + fname = filenames[mode == MODE_NORMAL ? fileidx : tns.sel]; + fnlen = strlen(fname); + cn = cmdline = (char*) s_malloc((strlen(cline) + fncnt * (fnlen + 2)) * + sizeof(char)); + + /* replace all '#' with filename */ + for (co = cline; *co; co++) { + if (*co == '#') { + *cn++ = '"'; + strcpy(cn, fname); + cn += fnlen; + *cn++ = '"'; + } else { + *cn++ = *co; + } + } + *cn = '\0'; + + if ((pid = fork()) == 0) { + execlp("/bin/sh", "/bin/sh", "-c", cmdline, NULL); + warn("could not exec: /bin/sh"); + exit(1); + } else if (pid < 0) { + warn("could not fork. command line was: %s", cmdline); + } else if (reload) { + waitpid(pid, &status, 0); + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) + ret = 1; + else + warn("child exited with non-zero return value: %d. command line was: %s", + WEXITSTATUS(status), cmdline); + } + + free(cmdline); + return ret; +} + +void redraw() { + if (mode == MODE_NORMAL) { + img_render(&img, &win); + if (timo_cursor) + win_set_cursor(&win, CURSOR_ARROW); + else if (!dragging) + win_set_cursor(&win, CURSOR_NONE); + } else { + tns_render(&tns, &win); + } + update_title(); + timo_redraw = 0; +} + +void on_keypress(XEvent *ev) { + int i; + XKeyEvent *kev; + KeySym ksym; + char key; + + if (!ev || ev->type != KeyPress) + return; + + kev = &ev->xkey; + XLookupString(kev, &key, 1, &ksym, NULL); + + if (EXT_COMMANDS && (CLEANMASK(kev->state) & ControlMask)) { + for (i = 0; i < LEN(commands); i++) { + if (commands[i].ksym == ksym) { + win_set_cursor(&win, CURSOR_WATCH); + if (run_command(commands[i].cmdline, commands[i].reload)) { + if (mode == MODE_NORMAL) { + if (fileidx < tns.cnt) + tns_load(&tns, fileidx, filenames[fileidx], 1); + img_close(&img, 1); + load_image(fileidx); + } else { + if (!tns_load(&tns, tns.sel, filenames[tns.sel], 0)) { + remove_file(tns.sel, 0); + tns.dirty = 1; + if (tns.sel >= tns.cnt) + tns.sel = tns.cnt - 1; + } + } + redraw(); + } + if (mode == MODE_THUMBS) + win_set_cursor(&win, CURSOR_ARROW); + else if (!timo_cursor) + win_set_cursor(&win, CURSOR_NONE); + return; + } + } + } + + for (i = 0; i < LEN(keys); i++) { + if (ksym == keys[i].ksym && keys[i].handler) { + if (keys[i].handler(ev, keys[i].arg)) + redraw(); + return; + } + } +} + +void on_buttonpress(XEvent *ev) { + int i, sel; + XButtonEvent *bev; + + if (!ev || ev->type != ButtonPress) + return; + + bev = &ev->xbutton; + + if (mode == MODE_NORMAL) { + if (!dragging) { + win_set_cursor(&win, CURSOR_ARROW); + timo_cursor = TO_CURSOR_HIDE; + } + + for (i = 0; i < LEN(buttons); i++) { + if (CLEANMASK(bev->state) == CLEANMASK(buttons[i].mod) && + bev->button == buttons[i].button && buttons[i].handler) + { + if (buttons[i].handler(ev, buttons[i].arg)) + redraw(); + return; + } + } + } else { + /* thumbnail mode */ + switch (bev->button) { + case Button1: + if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) { + if (sel == tns.sel) { + load_image(tns.sel); + mode = MODE_NORMAL; + timo_cursor = TO_CURSOR_HIDE; + } else { + tns_highlight(&tns, &win, tns.sel, False); + tns_highlight(&tns, &win, sel, True); + tns.sel = sel; + } + redraw(); + break; + } + break; + case Button4: + case Button5: + if (tns_scroll(&tns, bev->button == Button4 ? DIR_UP : DIR_DOWN)) + redraw(); + break; + } + } +} + +void on_motionnotify(XEvent *ev) { + XMotionEvent *mev; + + if (!ev || ev->type != MotionNotify) + return; + + mev = &ev->xmotion; + + if (mev->x >= 0 && mev->x <= win.w && mev->y >= 0 && mev->y <= win.h) { + if (img_move(&img, &win, mev->x - mox, mev->y - moy)) + timo_redraw = TO_IMAGE_DRAG; + mox = mev->x; + moy = mev->y; + } +} + +void run() { + int xfd, timeout; + fd_set fds; + struct timeval tt, t0, t1; + XEvent ev; + + dragging = 0; + timo_cursor = mode == MODE_NORMAL ? TO_CURSOR_HIDE : 0; + + redraw(); + + while (1) { + if (mode == MODE_THUMBS && tns.cnt < filecnt) { + /* load thumbnails */ + win_set_cursor(&win, CURSOR_WATCH); + gettimeofday(&t0, 0); + + while (tns.cnt < filecnt && !XPending(win.env.dpy)) { + if (tns_load(&tns, tns.cnt, filenames[tns.cnt], 0)) + tns.cnt++; + else + remove_file(tns.cnt, 0); + gettimeofday(&t1, 0); + if (TIMEDIFF(&t1, &t0) >= TO_THUMBS_LOAD) + break; + } + if (tns.cnt == filecnt) + win_set_cursor(&win, CURSOR_ARROW); + if (!XPending(win.env.dpy)) { + redraw(); + continue; + } else { + timo_redraw = TO_THUMBS_LOAD; + } + } else if (timo_cursor || timo_redraw) { + /* check active timeouts */ + gettimeofday(&t0, 0); + timeout = MIN(timo_cursor + 1, timo_redraw + 1); + MSEC_TO_TIMEVAL(timeout, &tt); + xfd = ConnectionNumber(win.env.dpy); + FD_ZERO(&fds); + FD_SET(xfd, &fds); + + if (!XPending(win.env.dpy)) + select(xfd + 1, &fds, 0, 0, &tt); + gettimeofday(&t1, 0); + timeout = MIN(TIMEDIFF(&t1, &t0), timeout); + + /* timeouts fired? */ + if (timo_cursor) { + timo_cursor = MAX(0, timo_cursor - timeout); + if (!timo_cursor) + win_set_cursor(&win, CURSOR_NONE); + } + if (timo_redraw) { + timo_redraw = MAX(0, timo_redraw - timeout); + if (!timo_redraw) + redraw(); + } + if ((timo_cursor || timo_redraw) && !XPending(win.env.dpy)) + continue; + } + + if (!XNextEvent(win.env.dpy, &ev)) { + switch (ev.type) { + case ButtonPress: + on_buttonpress(&ev); + break; + case ButtonRelease: + if (dragging) { + dragging = 0; + if (mode == MODE_NORMAL) { + win_set_cursor(&win, CURSOR_ARROW); + timo_cursor = TO_CURSOR_HIDE; + } + } + break; + case ClientMessage: + if ((Atom) ev.xclient.data.l[0] == wm_delete_win) + return; + break; + case ConfigureNotify: + if (win_configure(&win, &ev.xconfigure)) { + timo_redraw = TO_WIN_RESIZE; + if (mode == MODE_NORMAL) + img.checkpan = 1; + else + tns.dirty = 1; + } + break; + case KeyPress: + on_keypress(&ev); + break; + case MotionNotify: + if (dragging) { + on_motionnotify(&ev); + } else if (mode == MODE_NORMAL) { + if (!timo_cursor) + win_set_cursor(&win, CURSOR_ARROW); + timo_cursor = TO_CURSOR_HIDE; + } + break; + } + } + } +} + + +/* handler functions for key and button mappings: */ + +int quit(XEvent *e, arg_t a) { + cleanup(); + exit(0); +} + +int reload(XEvent *e, arg_t a) { + if (mode == MODE_NORMAL) { + load_image(fileidx); + return 1; + } else { + return 0; + } +} + +int toggle_fullscreen(XEvent *e, arg_t a) { + win_toggle_fullscreen(&win); + if (mode == MODE_NORMAL) + img.checkpan = 1; + else + tns.dirty = 1; + timo_redraw = TO_WIN_RESIZE; + return 0; +} + +int toggle_antialias(XEvent *e, arg_t a) { + if (mode == MODE_NORMAL) { + img_toggle_antialias(&img); + return 1; + } else { + return 0; + } +} + +int toggle_alpha(XEvent *e, arg_t a) { + if (mode == MODE_NORMAL) { + img.alpha ^= 1; + return 1; + } else { + return 0; + } +} + +int switch_mode(XEvent *e, arg_t a) { + if (mode == MODE_NORMAL) { + if (!tns.thumbs) + tns_init(&tns, filecnt); + img_close(&img, 0); + win_set_cursor(&win, CURSOR_ARROW); + timo_cursor = 0; + tns.sel = fileidx; + tns.dirty = 1; + mode = MODE_THUMBS; + } else { + timo_cursor = TO_CURSOR_HIDE; + load_image(tns.sel); + mode = MODE_NORMAL; + } + return 1; +} + +int navigate(XEvent *e, arg_t n) { + if (mode == MODE_NORMAL) { + n += fileidx; + if (n < 0) + n = 0; + if (n >= filecnt) + n = filecnt - 1; + + if (n != fileidx) { + load_image(n); + return 1; + } + } + return 0; +} + +int first(XEvent *e, arg_t a) { + if (mode == MODE_NORMAL && fileidx != 0) { + load_image(0); + return 1; + } else if (mode == MODE_THUMBS && tns.sel != 0) { + tns.sel = 0; + tns.dirty = 1; + return 1; + } else { + return 0; + } +} + +int last(XEvent *e, arg_t a) { + if (mode == MODE_NORMAL && fileidx != filecnt - 1) { + load_image(filecnt - 1); + return 1; + } else if (mode == MODE_THUMBS && tns.sel != tns.cnt - 1) { + tns.sel = tns.cnt - 1; + tns.dirty = 1; + return 1; + } else { + return 0; + } +} + +int remove_image(XEvent *e, arg_t a) { + if (mode == MODE_NORMAL) { + remove_file(fileidx, 1); + load_image(fileidx >= filecnt ? filecnt - 1 : fileidx); + return 1; + } else if (tns.sel < tns.cnt) { + remove_file(tns.sel, 1); + tns.dirty = 1; + if (tns.sel >= tns.cnt) + tns.sel = tns.cnt - 1; + return 1; + } else { + return 0; + } +} + +int move(XEvent *e, arg_t dir) { + if (mode == MODE_NORMAL) + return img_pan(&img, &win, dir, 0); + else + return tns_move_selection(&tns, &win, dir); +} + +int scroll(XEvent *e, arg_t dir) { + if (mode == MODE_NORMAL) + return img_pan(&img, &win, dir, 1); + else + return 0; +} + +int pan_edge(XEvent *e, arg_t dir) { + if (mode == MODE_NORMAL) + return img_pan_edge(&img, &win, dir); + else + return 0; +} + +int drag(XEvent *e, arg_t a) { + if (mode == MODE_NORMAL) { + mox = e->xbutton.x; + moy = e->xbutton.y; + win_set_cursor(&win, CURSOR_HAND); + timo_cursor = 0; + dragging = 1; + } + return 0; +} + +int rotate(XEvent *e, arg_t dir) { + if (mode == MODE_NORMAL) { + if (dir == DIR_LEFT) { + img_rotate_left(&img, &win); + return 1; + } else if (dir == DIR_RIGHT) { + img_rotate_right(&img, &win); + return 1; + } + } + return 0; +} + +int zoom(XEvent *e, arg_t scale) { + if (mode != MODE_NORMAL) + return 0; + if (scale > 0) + return img_zoom_in(&img, &win); + else if (scale < 0) + return img_zoom_out(&img, &win); + else + return img_zoom(&img, &win, 1.0); +} + +int fit_to_win(XEvent *e, arg_t ret) { + if (mode == MODE_NORMAL) { + if ((ret = img_fit_win(&img, &win))) + img_center(&img, &win); + return ret; + } else { + return 0; + } +} + +int fit_to_img(XEvent *e, arg_t ret) { + int x, y; + unsigned int w, h; + + if (mode == MODE_NORMAL) { + x = MAX(0, win.x + img.x); + y = MAX(0, win.y + img.y); + w = img.w * img.zoom; + h = img.h * img.zoom; + if ((ret = win_moveresize(&win, x, y, w, h))) { + img.x = x - win.x; + img.y = y - win.y; + } + return ret; + } else { + return 0; + } +} diff --git a/events.h b/events.h new file mode 100644 index 0000000..d5ddca7 --- /dev/null +++ b/events.h @@ -0,0 +1,67 @@ +/* sxiv: events.h + * Copyright (c) 2011 Bert Muennich + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef EVENTS_H +#define EVENTS_H + +#include + +typedef struct { + KeySym ksym; + Bool reload; + const char *cmdline; +} command_t; + +typedef int arg_t; + +typedef struct { + KeySym ksym; + int (*handler)(XEvent*, arg_t); + arg_t arg; +} keymap_t; + +typedef struct { + unsigned int mod; + unsigned int button; + int (*handler)(XEvent*, arg_t); + arg_t arg; +} button_t; + +void run(); + +/* handler functions for key and button mappings: */ +int quit(XEvent*, arg_t); +int reload(XEvent*, arg_t); +int toggle_fullscreen(XEvent*, arg_t); +int toggle_antialias(XEvent*, arg_t); +int toggle_alpha(XEvent*, arg_t); +int switch_mode(XEvent*, arg_t); +int navigate(XEvent*, arg_t); +int first(XEvent*, arg_t); +int last(XEvent*, arg_t); +int remove_image(XEvent*, arg_t); +int move(XEvent*, arg_t); +int scroll(XEvent*, arg_t); +int pan_edge(XEvent*, arg_t); +int drag(XEvent*, arg_t); +int rotate(XEvent*, arg_t); +int zoom(XEvent*, arg_t); +int fit_to_win(XEvent*, arg_t); +int fit_to_img(XEvent*, arg_t); + +#endif /* EVENTS_H */ diff --git a/image.c b/image.c index 8a82f32..d03e3c7 100644 --- a/image.c +++ b/image.c @@ -16,6 +16,8 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +#define _IMAGE_CONFIG + #include #include "image.h" @@ -238,7 +240,7 @@ int img_zoom_in(img_t *img, win_t *win) { if (!img || !img->im || !win) return 0; - for (i = 1; i < zl_cnt; ++i) { + for (i = 1; i < zl_cnt; i++) { if (zoom_levels[i] > img->zoom * 100.0) return img_zoom(img, win, zoom_levels[i] / 100.0); } @@ -251,7 +253,7 @@ int img_zoom_out(img_t *img, win_t *win) { if (!img || !img->im || !win) return 0; - for (i = zl_cnt - 2; i >= 0; --i) { + for (i = zl_cnt - 2; i >= 0; i--) { if (zoom_levels[i] < img->zoom * 100.0) return img_zoom(img, win, zoom_levels[i] / 100.0); } diff --git a/main.c b/main.c index 2a80aff..1b98efd 100644 --- a/main.c +++ b/main.c @@ -16,32 +16,22 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#define _XOPEN_SOURCE 700 - #include -#include #include -#include -#include -#include -#include #include +#include -#include -#include -#include - +#include "events.h" #include "image.h" #include "options.h" #include "thumbs.h" -#include "types.h" #include "util.h" #include "window.h" -#include "config.h" -enum { TITLE_LEN = 256, FNAME_CNT = 1024 }; - -void run(); +enum { + TITLE_LEN = 256, + FNAME_CNT = 1024 +}; appmode_t mode; img_t img; @@ -64,6 +54,23 @@ void cleanup() { } } +int check_add_file(char *filename) { + if (!filename) + return 0; + + if (access(filename, R_OK)) { + warn("could not open file: %s", filename); + return 0; + } else { + if (fileidx == filecnt) { + filecnt *= 2; + filenames = (char**) s_realloc(filenames, filecnt * sizeof(char*)); + } + filenames[fileidx++] = filename; + return 1; + } +} + void remove_file(int n, unsigned char silent) { if (n < 0 || n >= filecnt) return; @@ -84,32 +91,32 @@ void remove_file(int n, unsigned char silent) { memset(tns.thumbs + tns.cnt - 1, 0, sizeof(thumb_t)); } - --filecnt; + filecnt--; if (n < tns.cnt) - --tns.cnt; + tns.cnt--; } -int load_image(int new) { +void load_image(int new) { struct stat fstats; - if (new >= 0 && new < filecnt) { - win_set_cursor(&win, CURSOR_WATCH); - img_close(&img, 0); - - while (!img_load(&img, filenames[new])) { - remove_file(new, 0); - if (new >= filecnt) - new = filecnt - 1; - } - fileidx = new; - if (!stat(filenames[new], &fstats)) - filesize = fstats.st_size; - else - filesize = 0; + if (new < 0 || new >= filecnt) + return; - /* cursor is reset in redraw() */ + /* cursor is reset in redraw() */ + win_set_cursor(&win, CURSOR_WATCH); + img_close(&img, 0); + + while (!img_load(&img, filenames[new])) { + remove_file(new, 0); + if (new >= filecnt) + new = filecnt - 1; } - return 1; + + fileidx = new; + if (!stat(filenames[new], &fstats)) + filesize = fstats.st_size; + else + filesize = 0; } void update_title() { @@ -138,23 +145,6 @@ void update_title() { win_set_title(&win, win_title); } -int check_append(char *filename) { - if (!filename) - return 0; - - if (access(filename, R_OK)) { - warn("could not open file: %s", filename); - return 0; - } else { - if (fileidx == filecnt) { - filecnt *= 2; - filenames = (char**) s_realloc(filenames, filecnt * sizeof(char*)); - } - filenames[fileidx++] = filename; - return 1; - } -} - int fncmp(const void *a, const void *b) { return strcoll(*((char* const*) a), *((char* const*) b)); } @@ -187,20 +177,21 @@ int main(int argc, char **argv) { filenames = (char**) s_malloc(filecnt * sizeof(char*)); fileidx = 0; + /* build file list: */ if (options->from_stdin) { while ((len = getline(&filename, &n, stdin)) > 0) { if (filename[len-1] == '\n') filename[len-1] = '\0'; - if (!*filename || !check_append(filename)) + if (!*filename || !check_add_file(filename)) free(filename); filename = NULL; } } else { - for (i = 0; i < options->filecnt; ++i) { + for (i = 0; i < options->filecnt; i++) { filename = options->filenames[i]; if (stat(filename, &fstats) || !S_ISDIR(fstats.st_mode)) { - check_append(filename); + check_add_file(filename); } else { if (!options->recursive) { warn("ignoring directory: %s", filename); @@ -212,7 +203,7 @@ int main(int argc, char **argv) { } start = fileidx; while ((filename = r_readdir(&dir))) { - if (!check_append(filename)) + if (!check_add_file(filename)) free((void*) filename); } r_closedir(&dir); @@ -252,550 +243,3 @@ int main(int argc, char **argv) { return 0; } - -int run_command(const char *cline, Bool reload) { - int fncnt, fnlen; - char *cn, *cmdline; - const char *co, *fname; - pid_t pid; - int ret, status; - - if (!cline || !*cline) - return 0; - - fncnt = 0; - co = cline - 1; - while ((co = strchr(co + 1, '#'))) - ++fncnt; - - if (!fncnt) - return 0; - - ret = 0; - fname = filenames[mode == MODE_NORMAL ? fileidx : tns.sel]; - fnlen = strlen(fname); - cn = cmdline = (char*) s_malloc((strlen(cline) + fncnt * (fnlen + 2)) * - sizeof(char)); - - /* replace all '#' with filename */ - for (co = cline; *co; ++co) { - if (*co == '#') { - *cn++ = '"'; - strcpy(cn, fname); - cn += fnlen; - *cn++ = '"'; - } else { - *cn++ = *co; - } - } - *cn = '\0'; - - if ((pid = fork()) == 0) { - execlp("/bin/sh", "/bin/sh", "-c", cmdline, NULL); - warn("could not exec: /bin/sh"); - exit(1); - } else if (pid < 0) { - warn("could not fork. command line was: %s", cmdline); - } else if (reload) { - waitpid(pid, &status, 0); - if (WIFEXITED(status) && WEXITSTATUS(status) == 0) - ret = 1; - else - warn("child exited with non-zero return value: %d. command line was: %s", - WEXITSTATUS(status), cmdline); - } - - free(cmdline); - return ret; -} - - -/* event handling */ - -/* timeouts in milliseconds: */ -enum { - TO_WIN_RESIZE = 75, - TO_IMAGE_DRAG = 1, - TO_CURSOR_HIDE = 1500, - TO_THUMBS_LOAD = 200 -}; - -int timo_cursor; -int timo_redraw; -unsigned char drag; -int mox, moy; - -void redraw() { - if (mode == MODE_NORMAL) { - img_render(&img, &win); - if (timo_cursor) - win_set_cursor(&win, CURSOR_ARROW); - else if (!drag) - win_set_cursor(&win, CURSOR_NONE); - } else { - tns_render(&tns, &win); - } - update_title(); - timo_redraw = 0; -} - -void on_keypress(XKeyEvent *kev) { - int x, y; - unsigned int w, h; - char key; - KeySym ksym; - int changed, ctrl; - - if (!kev) - return; - - XLookupString(kev, &key, 1, &ksym, NULL); - changed = 0; - ctrl = CLEANMASK(kev->state) & ControlMask; - - /* external commands from commands.h */ - if (EXT_COMMANDS && ctrl) { - for (x = 0; x < LEN(commands); ++x) { - if (commands[x].key == key) { - win_set_cursor(&win, CURSOR_WATCH); - if (run_command(commands[x].cmdline, commands[x].reload)) { - if (mode == MODE_NORMAL) { - if (fileidx < tns.cnt) - tns_load(&tns, fileidx, filenames[fileidx], 1); - img_close(&img, 1); - load_image(fileidx); - } else { - if (!tns_load(&tns, tns.sel, filenames[tns.sel], 0)) { - remove_file(tns.sel, 0); - tns.dirty = 1; - if (tns.sel >= tns.cnt) - tns.sel = tns.cnt - 1; - } - } - redraw(); - } - if (mode == MODE_THUMBS) - win_set_cursor(&win, CURSOR_ARROW); - else if (!timo_cursor) - win_set_cursor(&win, CURSOR_NONE); - return; - } - } - } - - if (mode == MODE_NORMAL) { - switch (ksym) { - /* navigate image list */ - case XK_n: - case XK_space: - if (fileidx + 1 < filecnt) - changed = load_image(fileidx + 1); - break; - case XK_p: - case XK_BackSpace: - if (fileidx > 0) - changed = load_image(fileidx - 1); - break; - case XK_bracketleft: - if (fileidx != 0) - changed = load_image(MAX(0, fileidx - 10)); - break; - case XK_bracketright: - if (fileidx != filecnt - 1) - changed = load_image(MIN(fileidx + 10, filecnt - 1)); - break; - case XK_g: - if (fileidx != 0) - changed = load_image(0); - break; - case XK_G: - if (fileidx != filecnt - 1) - changed = load_image(filecnt - 1); - break; - - /* zooming */ - case XK_plus: - case XK_equal: - case XK_KP_Add: - changed = img_zoom_in(&img, &win); - break; - case XK_minus: - case XK_KP_Subtract: - changed = img_zoom_out(&img, &win); - break; - case XK_0: - case XK_KP_0: - changed = img_zoom(&img, &win, 1.0); - break; - case XK_w: - if ((changed = img_fit_win(&img, &win))) - img_center(&img, &win); - break; - - /* panning */ - case XK_h: - case XK_Left: - changed = img_pan(&img, &win, DIR_LEFT, ctrl); - break; - case XK_j: - case XK_Down: - changed = img_pan(&img, &win, DIR_DOWN, ctrl); - break; - case XK_k: - case XK_Up: - changed = img_pan(&img, &win, DIR_UP, ctrl); - break; - case XK_l: - case XK_Right: - changed = img_pan(&img, &win, DIR_RIGHT, ctrl); - break; - case XK_Prior: - changed = img_pan(&img, &win, DIR_UP, 1); - break; - case XK_Next: - changed = img_pan(&img, &win, DIR_DOWN, 1); - break; - - case XK_H: - changed = img_pan_edge(&img, &win, DIR_LEFT); - break; - case XK_J: - changed = img_pan_edge(&img, &win, DIR_DOWN); - break; - case XK_K: - changed = img_pan_edge(&img, &win, DIR_UP); - break; - case XK_L: - changed = img_pan_edge(&img, &win, DIR_RIGHT); - break; - - /* rotation */ - case XK_less: - img_rotate_left(&img, &win); - changed = 1; - break; - case XK_greater: - img_rotate_right(&img, &win); - changed = 1; - break; - - /* control window */ - case XK_W: - x = MAX(0, win.x + img.x); - y = MAX(0, win.y + img.y); - w = img.w * img.zoom; - h = img.h * img.zoom; - if ((changed = win_moveresize(&win, x, y, w, h))) { - img.x = x - win.x; - img.y = y - win.y; - } - break; - - /* switch to thumbnail mode */ - case XK_Return: - if (!tns.thumbs) - tns_init(&tns, filecnt); - img_close(&img, 0); - mode = MODE_THUMBS; - win_set_cursor(&win, CURSOR_ARROW); - timo_cursor = 0; - tns.sel = fileidx; - changed = tns.dirty = 1; - break; - - /* miscellaneous */ - case XK_a: - img_toggle_antialias(&img); - changed = 1; - break; - case XK_A: - img.alpha ^= 1; - changed = 1; - break; - case XK_D: - remove_file(fileidx, 1); - changed = load_image(fileidx >= filecnt ? filecnt - 1 : fileidx); - break; - case XK_r: - changed = load_image(fileidx); - break; - } - } else { - /* thumbnail mode */ - switch (ksym) { - /* open selected image */ - case XK_Return: - load_image(tns.sel); - mode = MODE_NORMAL; - changed = 1; - break; - - /* move selection */ - case XK_h: - case XK_Left: - changed = tns_move_selection(&tns, &win, DIR_LEFT); - break; - case XK_j: - case XK_Down: - changed = tns_move_selection(&tns, &win, DIR_DOWN); - break; - case XK_k: - case XK_Up: - changed = tns_move_selection(&tns, &win, DIR_UP); - break; - case XK_l: - case XK_Right: - changed = tns_move_selection(&tns, &win, DIR_RIGHT); - break; - case XK_g: - if (tns.sel != 0) { - tns.sel = 0; - changed = tns.dirty = 1; - } - break; - case XK_G: - if (tns.sel != tns.cnt - 1) { - tns.sel = tns.cnt - 1; - changed = tns.dirty = 1; - } - break; - - /* miscellaneous */ - case XK_D: - if (tns.sel < tns.cnt) { - remove_file(tns.sel, 1); - changed = tns.dirty = 1; - if (tns.sel >= tns.cnt) - tns.sel = tns.cnt - 1; - } - break; - } - } - - /* common key mappings */ - switch (ksym) { - case XK_q: - cleanup(); - exit(0); - case XK_f: - win_toggle_fullscreen(&win); - if (mode == MODE_NORMAL) - img.checkpan = 1; - else - tns.dirty = 1; - timo_redraw = TO_WIN_RESIZE; - break; - } - - if (changed) - redraw(); -} - -void on_buttonpress(XButtonEvent *bev) { - int changed, sel; - unsigned int mask; - - if (!bev) - return; - - mask = CLEANMASK(bev->state); - changed = 0; - - if (mode == MODE_NORMAL) { - if (!drag) { - win_set_cursor(&win, CURSOR_ARROW); - timo_cursor = TO_CURSOR_HIDE; - } - - switch (bev->button) { - case Button1: - if (fileidx + 1 < filecnt) - changed = load_image(fileidx + 1); - break; - case Button2: - mox = bev->x; - moy = bev->y; - win_set_cursor(&win, CURSOR_HAND); - timo_cursor = 0; - drag = 1; - break; - case Button3: - if (fileidx > 0) - changed = load_image(fileidx - 1); - break; - case Button4: - if (mask == ControlMask) - changed = img_zoom_in(&img, &win); - else if (mask == ShiftMask) - changed = img_pan(&img, &win, DIR_LEFT, 0); - else - changed = img_pan(&img, &win, DIR_UP, 0); - break; - case Button5: - if (mask == ControlMask) - changed = img_zoom_out(&img, &win); - else if (mask == ShiftMask) - changed = img_pan(&img, &win, DIR_RIGHT, 0); - else - changed = img_pan(&img, &win, DIR_DOWN, 0); - break; - case 6: - changed = img_pan(&img, &win, DIR_LEFT, 0); - break; - case 7: - changed = img_pan(&img, &win, DIR_RIGHT, 0); - break; - } - } else { - /* thumbnail mode */ - switch (bev->button) { - case Button1: - if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) { - if (sel == tns.sel) { - load_image(tns.sel); - mode = MODE_NORMAL; - timo_cursor = TO_CURSOR_HIDE; - } else { - tns_highlight(&tns, &win, tns.sel, False); - tns_highlight(&tns, &win, sel, True); - tns.sel = sel; - } - changed = 1; - break; - } - break; - case Button4: - changed = tns_scroll(&tns, DIR_UP); - break; - case Button5: - changed = tns_scroll(&tns, DIR_DOWN); - break; - } - } - - if (changed) - redraw(); -} - -void on_motionnotify(XMotionEvent *mev) { - if (!mev) - return; - - if (mev->x >= 0 && mev->x <= win.w && mev->y >= 0 && mev->y <= win.h) { - if (img_move(&img, &win, mev->x - mox, mev->y - moy)) - timo_redraw = TO_IMAGE_DRAG; - - mox = mev->x; - moy = mev->y; - } -} - -void run() { - int xfd, timeout; - fd_set fds; - struct timeval tt, t0, t1; - XEvent ev; - - drag = 0; - timo_cursor = mode == MODE_NORMAL ? TO_CURSOR_HIDE : 0; - - redraw(); - - while (1) { - if (mode == MODE_THUMBS && tns.cnt < filecnt) { - win_set_cursor(&win, CURSOR_WATCH); - gettimeofday(&t0, 0); - - while (tns.cnt < filecnt && !XPending(win.env.dpy)) { - if (tns_load(&tns, tns.cnt, filenames[tns.cnt], 0)) - ++tns.cnt; - else - remove_file(tns.cnt, 0); - gettimeofday(&t1, 0); - if (TIMEDIFF(&t1, &t0) >= TO_THUMBS_LOAD) - break; - } - if (tns.cnt == filecnt) - win_set_cursor(&win, CURSOR_ARROW); - if (!XPending(win.env.dpy)) { - redraw(); - continue; - } else { - timo_redraw = TO_THUMBS_LOAD; - } - } else if (timo_cursor || timo_redraw) { - gettimeofday(&t0, 0); - if (timo_cursor && timo_redraw) - timeout = MIN(timo_cursor, timo_redraw); - else if (timo_cursor) - timeout = timo_cursor; - else - timeout = timo_redraw; - MSEC_TO_TIMEVAL(timeout, &tt); - xfd = ConnectionNumber(win.env.dpy); - FD_ZERO(&fds); - FD_SET(xfd, &fds); - - if (!XPending(win.env.dpy)) - select(xfd + 1, &fds, 0, 0, &tt); - gettimeofday(&t1, 0); - timeout = MIN(TIMEDIFF(&t1, &t0), timeout); - - /* timeouts fired? */ - if (timo_cursor) { - timo_cursor = MAX(0, timo_cursor - timeout); - if (!timo_cursor) - win_set_cursor(&win, CURSOR_NONE); - } - if (timo_redraw) { - timo_redraw = MAX(0, timo_redraw - timeout); - if (!timo_redraw) - redraw(); - } - if (!XPending(win.env.dpy) && (timo_cursor || timo_redraw)) - continue; - } - - if (!XNextEvent(win.env.dpy, &ev)) { - switch (ev.type) { - case KeyPress: - on_keypress(&ev.xkey); - break; - case ButtonPress: - on_buttonpress(&ev.xbutton); - break; - case ButtonRelease: - if (ev.xbutton.button == Button2) { - drag = 0; - if (mode == MODE_NORMAL) { - win_set_cursor(&win, CURSOR_ARROW); - timo_cursor = TO_CURSOR_HIDE; - } - } - break; - case MotionNotify: - if (drag) { - on_motionnotify(&ev.xmotion); - } else if (mode == MODE_NORMAL) { - if (!timo_cursor) - win_set_cursor(&win, CURSOR_ARROW); - timo_cursor = TO_CURSOR_HIDE; - } - break; - case ConfigureNotify: - if (win_configure(&win, &ev.xconfigure)) { - timo_redraw = TO_WIN_RESIZE; - if (mode == MODE_NORMAL) - img.checkpan = 1; - else - tns.dirty = 1; - } - break; - case ClientMessage: - if ((Atom) ev.xclient.data.l[0] == wm_delete_win) - return; - break; - } - } - } -} diff --git a/options.c b/options.c index 03c6194..3659601 100644 --- a/options.c +++ b/options.c @@ -17,6 +17,7 @@ */ #define _XOPEN_SOURCE +#define _IMAGE_CONFIG #include #include diff --git a/sxiv.1 b/sxiv.1 index b3e763c..c27dfe7 100644 --- a/sxiv.1 +++ b/sxiv.1 @@ -150,16 +150,16 @@ Pan to top image edge. .B L Pan to right image edge. .TP -.BR Ctrl-h ", " Ctrl-Left +.BR { Pan image one window width left. .TP -.BR Ctrl-j ", " Ctrl-Down ", " PageDn +.BR PageDn Pan image one window height down. .TP -.BR Ctrl-k ", " Ctrl-Up ", " PageUp +.BR PageUp Pan image one window height up. .TP -.BR Ctrl-l ", " Ctrl-Right +.BR } Pan image one window width right. .SS Rotation .TP diff --git a/thumbs.c b/thumbs.c index dd1dba6..5700b87 100644 --- a/thumbs.c +++ b/thumbs.c @@ -16,6 +16,8 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +#define _THUMBS_CONFIG + #include #include #include @@ -205,7 +207,7 @@ void tns_free(tns_t *tns) { return; if (tns->thumbs) { - for (i = 0; i < tns->cnt; ++i) { + for (i = 0; i < tns->cnt; i++) { if (tns->thumbs[i].im) { imlib_context_set_image(tns->thumbs[i].im); imlib_free_image(); @@ -337,7 +339,7 @@ void tns_render(tns_t *tns, win_t *win) { tns->x = x = (win->w - MIN(cnt, tns->cols) * thumb_dim) / 2 + 5; tns->y = y = (win->h - (cnt / tns->cols + r) * thumb_dim) / 2 + 5; - for (i = 0; i < cnt; ++i) { + for (i = 0; i < cnt; i++) { t = &tns->thumbs[tns->first + i]; t->x = x + (THUMB_SIZE - t->w) / 2; t->y = y + (THUMB_SIZE - t->h) / 2; @@ -391,11 +393,11 @@ int tns_move_selection(tns_t *tns, win_t *win, direction_t dir) { switch (dir) { case DIR_LEFT: if (tns->sel > 0) - --tns->sel; + tns->sel--; break; case DIR_RIGHT: if (tns->sel < tns->cnt - 1) - ++tns->sel; + tns->sel++; break; case DIR_UP: if (tns->sel >= tns->cols) diff --git a/types.h b/types.h index 0726286..de6e04d 100644 --- a/types.h +++ b/types.h @@ -2,31 +2,25 @@ #define TYPES_H typedef enum { - MODE_NORMAL = 0, + MODE_NORMAL, MODE_THUMBS } appmode_t; -typedef struct { - char key; - int reload; - const char *cmdline; -} command_t; - typedef enum { - DIR_LEFT = 0, + DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN } direction_t; typedef enum { - SCALE_DOWN = 0, + SCALE_DOWN, SCALE_FIT, SCALE_ZOOM } scalemode_t; typedef enum { - CURSOR_ARROW = 0, + CURSOR_ARROW, CURSOR_NONE, CURSOR_HAND, CURSOR_WATCH diff --git a/util.c b/util.c index 3957629..dee497d 100644 --- a/util.c +++ b/util.c @@ -26,8 +26,10 @@ #include "options.h" #include "util.h" -#define DNAME_CNT 512 -#define FNAME_LEN 1024 +enum { + DNAME_CNT = 512, + FNAME_LEN = 1024 +}; void cleanup(); @@ -78,7 +80,7 @@ void size_readable(float *size, const char **unit) { const char *units[] = { "", "K", "M", "G" }; int i; - for (i = 0; i < LEN(units) && *size > 1024; ++i) + for (i = 0; i < LEN(units) && *size > 1024; i++) *size /= 1024; *unit = units[MIN(i, LEN(units) - 1)]; } diff --git a/window.c b/window.c index 7508641..4da13d5 100644 --- a/window.c +++ b/window.c @@ -16,6 +16,8 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +#define _WINDOW_CONFIG + #include #include