- Configurable key and mouse mappings in config.h - Put event handling code from main.c into events.[ch]master
@@ -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) | |||
@@ -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 | |||
/* 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 }, | |||
/* external commands and corresponding key mappings: */ | |||
{ 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 */ | |||
{ ',', 1, "jpegtran -rotate 270 -copy all -outfile # #" }, | |||
{ '.', 1, "jpegtran -rotate 90 -copy all -outfile # #" }, | |||
{ '<', 1, "mogrify -rotate -90 #" }, | |||
{ '>', 1, "mogrify -rotate +90 #" } | |||
/* 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 |
@@ -0,0 +1,562 @@ | |||
/* sxiv: events.c | |||
* Copyright (c) 2011 Bert Muennich <muennich at informatik.hu-berlin.de> | |||
* | |||
* 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 <stdlib.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
#include <sys/time.h> | |||
#include <sys/wait.h> | |||
#include <X11/keysym.h> | |||
#include <X11/Xutil.h> | |||
#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; | |||
} | |||
} |
@@ -0,0 +1,67 @@ | |||
/* sxiv: events.h | |||
* Copyright (c) 2011 Bert Muennich <muennich at informatik.hu-berlin.de> | |||
* | |||
* 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 <X11/Xlib.h> | |||
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 */ |
@@ -16,6 +16,8 @@ | |||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |||
*/ | |||
#define _IMAGE_CONFIG | |||
#include <unistd.h> | |||
#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); | |||
} | |||
@@ -16,32 +16,22 @@ | |||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |||
*/ | |||
#define _XOPEN_SOURCE 700 | |||
#include <stdlib.h> | |||
#include <stdio.h> | |||
#include <string.h> | |||
#include <sys/select.h> | |||
#include <sys/stat.h> | |||
#include <sys/time.h> | |||
#include <sys/wait.h> | |||
#include <unistd.h> | |||
#include <sys/stat.h> | |||
#include <X11/Xlib.h> | |||
#include <X11/Xutil.h> | |||
#include <X11/keysym.h> | |||
#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; | |||
} | |||
} | |||
} | |||
} |
@@ -17,6 +17,7 @@ | |||
*/ | |||
#define _XOPEN_SOURCE | |||
#define _IMAGE_CONFIG | |||
#include <stdlib.h> | |||
#include <string.h> | |||
@@ -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 | |||
@@ -16,6 +16,8 @@ | |||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |||
*/ | |||
#define _THUMBS_CONFIG | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <sys/time.h> | |||
@@ -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) | |||
@@ -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 | |||
@@ -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)]; | |||
} | |||
@@ -16,6 +16,8 @@ | |||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |||
*/ | |||
#define _WINDOW_CONFIG | |||
#include <string.h> | |||
#include <X11/Xutil.h> | |||