1 /* keyevents.c
2 
3 Copyright (C) 1999-2003 Tom Gilbert.
4 Copyright (C) 2010-2020 Daniel Friesel.
5 
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to
8 deal in the Software without restriction, including without limitation the
9 rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 sell copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12 
13 The above copyright notice and this permission notice shall be included in
14 all copies of the Software and its documentation and acknowledgment shall be
15 given in the documentation and software packages that this Software was
16 used.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
22 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 
25 */
26 
27 #include "feh.h"
28 #include "thumbnail.h"
29 #include "filelist.h"
30 #include "winwidget.h"
31 #include "options.h"
32 #include <termios.h>
33 
34 struct __fehkey keys[EVENT_LIST_END];
35 struct termios old_term_settings;
36 unsigned char control_via_stdin = 0;
37 
setup_stdin()38 void setup_stdin() {
39 	struct termios ctrl;
40 
41 	control_via_stdin = 1;
42 
43 	if (tcgetattr(STDIN_FILENO, &old_term_settings) == -1)
44 		eprintf("tcgetattr failed");
45 	if (tcgetattr(STDIN_FILENO, &ctrl) == -1)
46 		eprintf("tcgetattr failed");
47 
48 	ctrl.c_iflag &= ~(PARMRK | ISTRIP
49 			| INLCR | IGNCR | IXON);
50 	ctrl.c_lflag &= ~(ECHO | ICANON | IEXTEN);
51 	ctrl.c_cflag &= ~(CSIZE | PARENB);
52 	ctrl.c_cflag |= CS8;
53 
54 	if (tcsetattr(STDIN_FILENO, TCSANOW, &ctrl) == -1)
55 		eprintf("tcsetattr failed");
56 }
57 
restore_stdin()58 void restore_stdin() {
59 	if (tcsetattr(STDIN_FILENO, TCSANOW, &old_term_settings) == -1)
60 		eprintf("tcsetattr failed");
61 }
62 
feh_set_kb(char * name,unsigned int s0,unsigned int y0,unsigned int s1,unsigned int y1,unsigned int s2,unsigned int y2)63 static void feh_set_kb(char *name, unsigned int s0, unsigned int y0,
64 		unsigned int s1, unsigned int y1, unsigned int s2, unsigned int y2) {
65 	static int key_index = 0;
66 	fehkey *key = &keys[key_index];
67 	key->keystates[0] = s0;
68 	key->keystates[1] = s1;
69 	key->keystates[2] = s2;
70 	key->keysyms[0] = y0;
71 	key->keysyms[1] = y1;
72 	key->keysyms[2] = y2;
73 	key->state = 0;
74 	key->button = 0;
75 	key->name = name;
76 	key_index++;
77 }
78 
ignore_space(int keysym)79 static inline int ignore_space(int keysym) {
80 	/*
81 	 * Passing values which do not fit inside a signed 8bit char to isprint,
82 	 * isspace and the likes is undefined behaviour... which glibc (for some
83 	 * values) implements as a segmentation fault. So let's not do that.
84 	 */
85 	return ((keysym <= 127) && (keysym >= -128) && isprint(keysym) && !isspace(keysym));
86 }
87 
feh_set_parse_kb_partial(fehkey * key,int index,char * ks)88 static void feh_set_parse_kb_partial(fehkey *key, int index, char *ks) {
89 	char *cur = ks;
90 	int mod = 0;
91 
92 	if (!*ks) {
93 		key->keysyms[index] = 0;
94 		return;
95 	}
96 
97 	while (cur[1] == '-') {
98 		switch (cur[0]) {
99 			case 'C':
100 				mod |= ControlMask;
101 				break;
102 			case 'S':
103 				mod |= ShiftMask;
104 				break;
105 			case '1':
106 				mod |= Mod1Mask;
107 				break;
108 			case '4':
109 				mod |= Mod4Mask;
110 				break;
111 			default:
112 				weprintf("keys: invalid modifier %c in \"%s\"", cur[0], ks);
113 				break;
114 		}
115 		cur += 2;
116 	}
117 
118 	key->keysyms[index] = XStringToKeysym(cur);
119 	if (ignore_space(key->keysyms[index]))
120 		mod &= ~ShiftMask;
121 	key->keystates[index] = mod;
122 
123 	if (key->keysyms[index] == NoSymbol)
124 		weprintf("keys: Invalid keysym: %s", cur);
125 }
126 
init_keyevents(void)127 void init_keyevents(void) {
128 	char *home = NULL;
129 	char *confhome = NULL;
130 	char *confpath = NULL;
131 	char line[128];
132 	char action[32], k1[32], k2[32], k3[32];
133 	fehkey *cur_kb = NULL;
134 	FILE *conf = NULL;
135 	int read = 0;
136 
137 	/*
138 	 * The feh_set_kb statements must have the same order as the key_action
139 	 * enum.
140 	 */
141 
142 	feh_set_kb("menu_close" , 0, XK_Escape    , 0, 0            , 0, 0);
143 	feh_set_kb("menu_parent", 0, XK_Left      , 0, 0            , 0, 0);
144 	feh_set_kb("menu_down", 0, XK_Down      , 0, 0            , 0, 0);
145 	feh_set_kb("menu_up", 0, XK_Up        , 0, 0            , 0, 0);
146 	feh_set_kb("menu_child", 0, XK_Right     , 0, 0            , 0, 0);
147 	feh_set_kb("menu_select", 0, XK_Return    , 0, XK_space     , 0, 0);
148 	feh_set_kb("scroll_left",0, XK_KP_Left   , 4, XK_Left      , 0, 0);
149 	feh_set_kb("scroll_right", 0,XK_KP_Right  , 4, XK_Right     , 0, 0);
150 	feh_set_kb("scroll_down",0, XK_KP_Down   , 4, XK_Down      , 0, 0);
151 	feh_set_kb("scroll_up",  0, XK_KP_Up     , 4, XK_Up        , 0, 0);
152 	feh_set_kb("scroll_left_page" , 8, XK_Left , 0, 0          , 0, 0);
153 	feh_set_kb("scroll_right_page", 8, XK_Right, 0, 0          , 0, 0);
154 	feh_set_kb("scroll_down_page" , 8, XK_Down , 0, 0          , 0, 0);
155 	feh_set_kb("scroll_up_page" , 8, XK_Up   , 0, 0          , 0, 0);
156 	feh_set_kb("prev_img"  , 0, XK_Left      , 0, XK_p         , 0, XK_BackSpace);
157 	feh_set_kb("next_img"  , 0, XK_Right     , 0, XK_n         , 0, XK_space);
158 	feh_set_kb("jump_back" , 0, XK_Page_Up   , 0, XK_KP_Page_Up, 0, 0);
159 	feh_set_kb("jump_fwd"  , 0, XK_Page_Down , 0, XK_KP_Page_Down,0,0);
160 	feh_set_kb("prev_dir"  , 0, XK_bracketleft, 0, 0           , 0, 0);
161 	feh_set_kb("next_dir"  , 0, XK_bracketright, 0, 0          , 0, 0);
162 	feh_set_kb("jump_random" ,0, XK_z         , 0, 0            , 0, 0);
163 	feh_set_kb("quit"      , 0, XK_Escape    , 0, XK_q         , 0, 0);
164 	feh_set_kb("close"     , 0, XK_x         , 0, 0            , 0, 0);
165 	feh_set_kb("remove"    , 0, XK_Delete    , 0, 0            , 0, 0);
166 	feh_set_kb("delete"    , 4, XK_Delete    , 0, 0            , 0, 0);
167 	feh_set_kb("jump_first" , 0, XK_Home      , 0, XK_KP_Home   , 0, 0);
168 	feh_set_kb("jump_last" , 0, XK_End       , 0, XK_KP_End    , 0, 0);
169 	feh_set_kb("action_0"  , 0, XK_Return    , 0, XK_0         , 0, XK_KP_0);
170 	feh_set_kb("action_1"  , 0, XK_1         , 0, XK_KP_1      , 0, 0);
171 	feh_set_kb("action_2"  , 0, XK_2         , 0, XK_KP_2      , 0, 0);
172 	feh_set_kb("action_3"  , 0, XK_3         , 0, XK_KP_3      , 0, 0);
173 	feh_set_kb("action_4"  , 0, XK_4         , 0, XK_KP_4      , 0, 0);
174 	feh_set_kb("action_5"  , 0, XK_5         , 0, XK_KP_5      , 0, 0);
175 	feh_set_kb("action_6"  , 0, XK_6         , 0, XK_KP_6      , 0, 0);
176 	feh_set_kb("action_7"  , 0, XK_7         , 0, XK_KP_7      , 0, 0);
177 	feh_set_kb("action_8"  , 0, XK_8         , 0, XK_KP_8      , 0, 0);
178 	feh_set_kb("action_9"  , 0, XK_9         , 0, XK_KP_9      , 0, 0);
179 	feh_set_kb("zoom_in"   , 0, XK_Up        , 0, XK_KP_Add    , 0, 0);
180 	feh_set_kb("zoom_out"  , 0, XK_Down      , 0, XK_KP_Subtract,0, 0);
181 	feh_set_kb("zoom_default" , 0, XK_KP_Multiply, 0, XK_asterisk,0, 0);
182 	feh_set_kb("zoom_fit"  , 0, XK_KP_Divide , 0, XK_slash     , 0, 0);
183 	feh_set_kb("zoom_fill" , 0, XK_exclam    , 0, 0            , 0, 0);
184 	feh_set_kb("size_to_image" , 0, XK_w      , 0, 0            , 0, 0);
185 	feh_set_kb("render"    , 0, XK_KP_Begin  , 0, XK_R         , 0, 0);
186 	feh_set_kb("toggle_actions" , 0, XK_a, 0, 0, 0, 0);
187 	feh_set_kb("toggle_aliasing" , 0, XK_A, 0, 0, 0, 0);
188 	feh_set_kb("toggle_auto_zoom" , 0, XK_Z, 0, 0, 0, 0);
189 #ifdef HAVE_LIBEXIF
190 	feh_set_kb("toggle_exif" , 0, XK_e, 0, 0, 0, 0);
191 #endif
192 	feh_set_kb("toggle_filenames" , 0, XK_d, 0, 0, 0, 0);
193 	feh_set_kb("toggle_info" , 0, XK_i, 0, 0, 0, 0);
194 	feh_set_kb("toggle_pointer" , 0, XK_o, 0, 0, 0, 0);
195 	feh_set_kb("toggle_caption" , 0, XK_c, 0, 0, 0, 0);
196 	feh_set_kb("toggle_pause" , 0, XK_h, 0, 0, 0, 0);
197 	feh_set_kb("toggle_menu" , 0, XK_m, 0, 0, 0, 0);
198 	feh_set_kb("toggle_fullscreen" , 0, XK_f, 0, 0, 0, 0);
199 	feh_set_kb("reload_image" , 0, XK_r, 0, 0, 0, 0);
200 	feh_set_kb("save_image" , 0, XK_s, 0, 0, 0, 0);
201 	feh_set_kb("save_filelist" , 0, XK_L, 0, 0, 0, 0);
202 	feh_set_kb("orient_1" , 0, XK_greater, 0, 0, 0, 0);
203 	feh_set_kb("orient_3" , 0, XK_less, 0, 0, 0, 0);
204 	feh_set_kb("flip" , 0, XK_underscore, 0, 0, 0, 0);
205 	feh_set_kb("mirror" , 0, XK_bar, 0, 0, 0, 0);
206 	feh_set_kb("reload_minus" , 0, XK_minus, 0, 0, 0, 0);
207 	feh_set_kb("reload_plus" , 0, XK_plus, 0, 0, 0, 0);
208 	feh_set_kb("toggle_keep_vp" , 0, XK_k, 0, 0, 0, 0);
209 	feh_set_kb("toggle_fixed_geometry" , 0, XK_g, 0, 0, 0, 0);
210 	feh_set_kb("pan" , 0, 0, 0, 0, 0, 0);
211 	feh_set_kb("zoom" , 0, 0, 0, 0, 0, 0);
212 	feh_set_kb("blur" , 0, 0, 0, 0, 0, 0);
213 	feh_set_kb("rotate" , 0, 0, 0, 0, 0, 0);
214 
215 	home = getenv("HOME");
216 	confhome = getenv("XDG_CONFIG_HOME");
217 
218 	if (confhome)
219 		confpath = estrjoin("/", confhome, "feh/keys", NULL);
220 	else if (home)
221 		confpath = estrjoin("/", home, ".config/feh/keys", NULL);
222 	else
223 		return;
224 
225 	conf = fopen(confpath, "r");
226 
227 	free(confpath);
228 
229 	if (!conf && ((conf = fopen("/etc/feh/keys", "r")) == NULL))
230 		return;
231 
232 	while (fgets(line, sizeof(line), conf)) {
233 		*action = '\0';
234 		*k1 = '\0';
235 		*k2 = '\0';
236 		*k3 = '\0';
237 		cur_kb = NULL;
238 
239 		read = sscanf(line, "%31s %31s %31s %31s\n",
240 			(char *) &action, (char *) &k1, (char* ) &k2, (char *) &k3);
241 
242 		if ((read == EOF) || (read == 0) || (line[0] == '#'))
243 			continue;
244 
245 		cur_kb = feh_str_to_kb(action);
246 
247 		if (cur_kb) {
248 			feh_set_parse_kb_partial(cur_kb, 0, k1);
249 			feh_set_parse_kb_partial(cur_kb, 1, k2);
250 			feh_set_parse_kb_partial(cur_kb, 2, k3);
251 		} else {
252 			weprintf("keys: Invalid action: %s", action);
253 		}
254 	}
255 	fclose(conf);
256 }
257 
feh_is_kp(unsigned int key_index,unsigned int state,unsigned int sym,unsigned int button)258 static short feh_is_kp(unsigned int key_index, unsigned int state,
259 		unsigned int sym, unsigned int button) {
260 	int i;
261 
262 	if (sym != NoSymbol) {
263 		for (i = 0; i < 3; i++) {
264 			if (
265 					(keys[key_index].keysyms[i] == sym) &&
266 					(keys[key_index].keystates[i] == state))
267 				return 1;
268 			else if (keys[key_index].keysyms[i] == 0)
269 				return 0;
270 		}
271 		return 0;
272 	}
273 	if ((keys[key_index].state == state)
274 			&& (keys[key_index].button == button)) {
275 		return 1;
276 	}
277 	return 0;
278 }
279 
feh_event_invoke_action(winwidget winwid,unsigned char action)280 void feh_event_invoke_action(winwidget winwid, unsigned char action)
281 {
282 	struct stat st;
283 	if (opt.actions[action]) {
284 		if (opt.slideshow) {
285 			feh_action_run(FEH_FILE(winwid->file->data), opt.actions[action], winwid);
286 
287 			if (opt.hold_actions[action])
288 				feh_reload_image(winwid, 1, 1);
289 			else if (stat(FEH_FILE(winwid->file->data)->filename, &st) == -1)
290 				feh_filelist_image_remove(winwid, 0);
291 			else
292 				slideshow_change_image(winwid, SLIDE_NEXT, 1);
293 
294 		} else if ((winwid->type == WIN_TYPE_SINGLE)
295 				|| (winwid->type == WIN_TYPE_THUMBNAIL_VIEWER)) {
296 			feh_action_run(FEH_FILE(winwid->file->data), opt.actions[action], winwid);
297 
298 			if (opt.hold_actions[action])
299 				feh_reload_image(winwid, 1, 1);
300 			else
301 				winwidget_destroy(winwid);
302 		} else if (winwid->type == WIN_TYPE_THUMBNAIL) {
303 			feh_file *thumbfile;
304 			thumbfile = feh_thumbnail_get_selected_file();
305 
306 			if (thumbfile) {
307 				feh_action_run(thumbfile, opt.actions[action], winwid);
308 
309 				if (!opt.hold_actions[action])
310 					feh_thumbnail_mark_removed(thumbfile, 0);
311 			}
312 		}
313 	}
314 	return;
315 }
316 
feh_event_handle_stdin()317 void feh_event_handle_stdin()
318 {
319 	char stdin_buf[2];
320 	static char is_esc = 0;
321 	KeySym keysym = NoSymbol;
322 	if (read(STDIN_FILENO, &stdin_buf, 1) == -1) {
323 		control_via_stdin = 0;
324 		if (isatty(STDIN_FILENO) && getpgrp() == (tcgetpgrp(STDIN_FILENO))) {
325 			weprintf("reading a command from stdin failed - disabling control via stdin");
326 			restore_stdin();
327 		}
328 		return;
329 	}
330 	stdin_buf[1] = '\0';
331 
332 	// escape?
333 	if (stdin_buf[0] == 0x1b) {
334 		is_esc = 1;
335 		return;
336 	}
337 	if ((is_esc == 1) && (stdin_buf[0] == '[')) {
338 		is_esc = 2;
339 		return;
340 	}
341 
342 	if (stdin_buf[0] == ' ')
343 		keysym = XK_space;
344 	else if (stdin_buf[0] == '\n')
345 		keysym = XK_Return;
346 	else if ((stdin_buf[0] == '\b') || (stdin_buf[0] == 127))
347 		keysym = XK_BackSpace;
348 	else if (is_esc == 2) {
349 		if (stdin_buf[0] == 'A')
350 			keysym = XK_Up;
351 		else if (stdin_buf[0] == 'B')
352 			keysym = XK_Down;
353 		else if (stdin_buf[0] == 'C')
354 			keysym = XK_Right;
355 		else if (stdin_buf[0] == 'D')
356 			keysym = XK_Left;
357 		is_esc = 0;
358 	}
359 	else
360 		keysym = XStringToKeysym(stdin_buf);
361 
362 	if (window_num)
363 		feh_event_handle_generic(windows[0], is_esc * Mod1Mask, keysym, 0);
364 
365 	is_esc = 0;
366 }
367 
feh_event_handle_keypress(XEvent * ev)368 void feh_event_handle_keypress(XEvent * ev)
369 {
370 	int state;
371 	char kbuf[20];
372 	KeySym keysym;
373 	XKeyEvent *kev;
374 	winwidget winwid = NULL;
375 	feh_menu_item *selected_item;
376 	feh_menu *selected_menu;
377 
378 	winwid = winwidget_get_from_window(ev->xkey.window);
379 
380 	/* nuke dupe events, unless we're typing text */
381 	if (winwid && !winwid->caption_entry) {
382 		while (XCheckTypedWindowEvent(disp, ev->xkey.window, KeyPress, ev));
383 	}
384 
385 	kev = (XKeyEvent *) ev;
386 	XLookupString(&ev->xkey, (char *) kbuf, sizeof(kbuf), &keysym, NULL);
387 	state = kev->state & (ControlMask | ShiftMask | Mod1Mask | Mod4Mask);
388 
389 	if (ignore_space(keysym))
390 		state &= ~ShiftMask;
391 
392 	/* menus are showing, so this is a menu control keypress */
393 	if (ev->xbutton.window == menu_cover) {
394 		selected_item = feh_menu_find_selected_r(menu_root, &selected_menu);
395 		if (feh_is_kp(EVENT_menu_close, state, keysym, 0))
396 			feh_menu_hide(menu_root, True);
397 		else if (feh_is_kp(EVENT_menu_parent, state, keysym, 0))
398 			feh_menu_select_parent(selected_menu);
399 		else if (feh_is_kp(EVENT_menu_down, state, keysym, 0))
400 			feh_menu_select_next(selected_menu, selected_item);
401 		else if (feh_is_kp(EVENT_menu_up, state, keysym, 0))
402 			feh_menu_select_prev(selected_menu, selected_item);
403 		else if (feh_is_kp(EVENT_menu_child, state, keysym, 0))
404 			feh_menu_select_submenu(selected_menu);
405 		else if (feh_is_kp(EVENT_menu_select, state, keysym, 0))
406 			feh_menu_item_activate(selected_menu, selected_item);
407 		return;
408 	}
409 
410 	if (winwid == NULL)
411 		return;
412 
413 	feh_event_handle_generic(winwid, state, keysym, 0);
414 }
415 
feh_str_to_kb(char * action)416 fehkey *feh_str_to_kb(char *action)
417 {
418 	for (unsigned int i = 0; i < EVENT_LIST_END; i++) {
419 		if (!strcmp(action, keys[i].name)) {
420 			return &keys[i];
421 		}
422 	}
423 	return NULL;
424 }
425 
feh_event_handle_generic(winwidget winwid,unsigned int state,KeySym keysym,unsigned int button)426 void feh_event_handle_generic(winwidget winwid, unsigned int state, KeySym keysym, unsigned int button) {
427 	int curr_screen = 0;
428 
429 	if (winwid->caption_entry && (keysym != NoSymbol)) {
430 		switch (keysym) {
431 		case XK_Return:
432 			if (state & ControlMask) {
433 				/* insert actual newline */
434 				ESTRAPPEND(FEH_FILE(winwid->file->data)->caption, "\n");
435 				winwidget_render_image_cached(winwid);
436 			} else {
437 				/* finish caption entry, write to captions file */
438 				FILE *fp;
439 				char *caption_filename;
440 				caption_filename =
441 					build_caption_filename(FEH_FILE(winwid->file->data), 1);
442 				winwid->caption_entry = 0;
443 				winwidget_render_image_cached(winwid);
444 				XFreePixmap(disp, winwid->bg_pmap_cache);
445 				winwid->bg_pmap_cache = 0;
446 				fp = fopen(caption_filename, "w");
447 				if (!fp) {
448 					eprintf("couldn't write to captions file %s:", caption_filename);
449 					return;
450 				}
451 				fprintf(fp, "%s", FEH_FILE(winwid->file->data)->caption);
452 				free(caption_filename);
453 				fclose(fp);
454 			}
455 			break;
456 		case XK_Escape:
457 			/* cancel, revert caption */
458 			winwid->caption_entry = 0;
459 			free(FEH_FILE(winwid->file->data)->caption);
460 			FEH_FILE(winwid->file->data)->caption = NULL;
461 			winwidget_render_image_cached(winwid);
462 			XFreePixmap(disp, winwid->bg_pmap_cache);
463 			winwid->bg_pmap_cache = 0;
464 			break;
465 		case XK_BackSpace:
466 			/* backspace */
467 			ESTRTRUNC(FEH_FILE(winwid->file->data)->caption, 1);
468 			winwidget_render_image_cached(winwid);
469 			break;
470 		default:
471 			if (isascii(keysym)) {
472 				/* append to caption */
473 				ESTRAPPEND_CHAR(FEH_FILE(winwid->file->data)->caption, keysym);
474 				winwidget_render_image_cached(winwid);
475 			}
476 			break;
477 		}
478 		return;
479 	}
480 
481 	if (feh_is_kp(EVENT_next_img, state, keysym, button)) {
482 		if (opt.slideshow)
483 			slideshow_change_image(winwid, SLIDE_NEXT, 1);
484 		else if (winwid->type == WIN_TYPE_THUMBNAIL)
485 			feh_thumbnail_select_next(winwid, 1);
486 	}
487 	else if (feh_is_kp(EVENT_prev_img, state, keysym, button)) {
488 		if (opt.slideshow)
489 			slideshow_change_image(winwid, SLIDE_PREV, 1);
490 		else if (winwid->type == WIN_TYPE_THUMBNAIL)
491 			feh_thumbnail_select_prev(winwid, 1);
492 	}
493 	else if (feh_is_kp(EVENT_scroll_right, state, keysym, button)) {
494 		winwid->im_x -= opt.scroll_step;;
495 		winwidget_sanitise_offsets(winwid);
496 		winwidget_render_image(winwid, 0, 1);
497 	}
498 	else if (feh_is_kp(EVENT_scroll_left, state, keysym, button)) {
499 		winwid->im_x += opt.scroll_step;
500 		winwidget_sanitise_offsets(winwid);
501 		winwidget_render_image(winwid, 0, 1);
502 	}
503 	else if (feh_is_kp(EVENT_scroll_down, state, keysym, button)) {
504 		winwid->im_y -= opt.scroll_step;
505 		winwidget_sanitise_offsets(winwid);
506 		winwidget_render_image(winwid, 0, 1);
507 	}
508 	else if (feh_is_kp(EVENT_scroll_up, state, keysym, button)) {
509 		winwid->im_y += opt.scroll_step;
510 		winwidget_sanitise_offsets(winwid);
511 		winwidget_render_image(winwid, 0, 1);
512 	}
513 	else if (feh_is_kp(EVENT_scroll_right_page, state, keysym, button)) {
514 		winwid->im_x -= winwid->w;
515 		winwidget_sanitise_offsets(winwid);
516 		winwidget_render_image(winwid, 0, 0);
517 	}
518 	else if (feh_is_kp(EVENT_scroll_left_page, state, keysym, button)) {
519 		winwid->im_x += winwid->w;
520 		winwidget_sanitise_offsets(winwid);
521 		winwidget_render_image(winwid, 0, 0);
522 	}
523 	else if (feh_is_kp(EVENT_scroll_down_page, state, keysym, button)) {
524 		winwid->im_y -= winwid->h;
525 		winwidget_sanitise_offsets(winwid);
526 		winwidget_render_image(winwid, 0, 0);
527 	}
528 	else if (feh_is_kp(EVENT_scroll_up_page, state, keysym, button)) {
529 		winwid->im_y += winwid->h;
530 		winwidget_sanitise_offsets(winwid);
531 		winwidget_render_image(winwid, 0, 0);
532 	}
533 	else if (feh_is_kp(EVENT_jump_back, state, keysym, button)) {
534 		if (opt.slideshow)
535 			slideshow_change_image(winwid, SLIDE_JUMP_BACK, 1);
536 		else if (winwid->type == WIN_TYPE_THUMBNAIL)
537 			feh_thumbnail_select_prev(winwid, 10);
538 	}
539 	else if (feh_is_kp(EVENT_jump_fwd, state, keysym, button)) {
540 		if (opt.slideshow)
541 			slideshow_change_image(winwid, SLIDE_JUMP_FWD, 1);
542 		else if (winwid->type == WIN_TYPE_THUMBNAIL)
543 			feh_thumbnail_select_next(winwid, 10);
544 	}
545 	else if (feh_is_kp(EVENT_next_dir, state, keysym, button)) {
546 		if (opt.slideshow)
547 			slideshow_change_image(winwid, SLIDE_JUMP_NEXT_DIR, 1);
548 	}
549 	else if (feh_is_kp(EVENT_prev_dir, state, keysym, button)) {
550 		if (opt.slideshow)
551 			slideshow_change_image(winwid, SLIDE_JUMP_PREV_DIR, 1);
552 	}
553 	else if (feh_is_kp(EVENT_quit, state, keysym, button)) {
554 		winwidget_destroy_all();
555 	}
556 	else if (feh_is_kp(EVENT_delete, state, keysym, button)) {
557 		if (winwid->type == WIN_TYPE_THUMBNAIL_VIEWER)
558 			feh_thumbnail_mark_removed(FEH_FILE(winwid->file->data), 1);
559 		feh_filelist_image_remove(winwid, 1);
560 	}
561 	else if (feh_is_kp(EVENT_remove, state, keysym, button)) {
562 		if (winwid->type == WIN_TYPE_THUMBNAIL_VIEWER)
563 			feh_thumbnail_mark_removed(FEH_FILE(winwid->file->data), 0);
564 		feh_filelist_image_remove(winwid, 0);
565 	}
566 	else if (feh_is_kp(EVENT_jump_first, state, keysym, button)) {
567 		if (opt.slideshow)
568 			slideshow_change_image(winwid, SLIDE_FIRST, 1);
569 	}
570 	else if (feh_is_kp(EVENT_jump_last, state, keysym, button)) {
571 		if (opt.slideshow)
572 			slideshow_change_image(winwid, SLIDE_LAST, 1);
573 	}
574 	else if (feh_is_kp(EVENT_action_0, state, keysym, button)) {
575 		feh_event_invoke_action(winwid, 0);
576 	}
577 	else if (feh_is_kp(EVENT_action_1, state, keysym, button)) {
578 		feh_event_invoke_action(winwid, 1);
579 	}
580 	else if (feh_is_kp(EVENT_action_2, state, keysym, button)) {
581 		feh_event_invoke_action(winwid, 2);
582 	}
583 	else if (feh_is_kp(EVENT_action_3, state, keysym, button)) {
584 		feh_event_invoke_action(winwid, 3);
585 	}
586 	else if (feh_is_kp(EVENT_action_4, state, keysym, button)) {
587 		feh_event_invoke_action(winwid, 4);
588 	}
589 	else if (feh_is_kp(EVENT_action_5, state, keysym, button)) {
590 		feh_event_invoke_action(winwid, 5);
591 	}
592 	else if (feh_is_kp(EVENT_action_6, state, keysym, button)) {
593 		feh_event_invoke_action(winwid, 6);
594 	}
595 	else if (feh_is_kp(EVENT_action_7, state, keysym, button)) {
596 		feh_event_invoke_action(winwid, 7);
597 	}
598 	else if (feh_is_kp(EVENT_action_8, state, keysym, button)) {
599 		feh_event_invoke_action(winwid, 8);
600 	}
601 	else if (feh_is_kp(EVENT_action_9, state, keysym, button)) {
602 		feh_event_invoke_action(winwid, 9);
603 	}
604 	else if (feh_is_kp(EVENT_zoom_in, state, keysym, button)) {
605 		winwid->old_zoom = winwid->zoom;
606 		winwid->zoom = winwid->zoom * 1.25;
607 
608 		if (winwid->zoom > ZOOM_MAX)
609 			winwid->zoom = ZOOM_MAX;
610 
611 		winwid->im_x = (winwid->w / 2) - (((winwid->w / 2) - winwid->im_x) /
612 			winwid->old_zoom * winwid->zoom);
613 		winwid->im_y = (winwid->h / 2) - (((winwid->h / 2) - winwid->im_y) /
614 			winwid->old_zoom * winwid->zoom);
615 		winwidget_sanitise_offsets(winwid);
616 		winwidget_render_image(winwid, 0, 0);
617 	}
618 	else if (feh_is_kp(EVENT_zoom_out, state, keysym, button)) {
619 		winwid->old_zoom = winwid->zoom;
620 		winwid->zoom = winwid->zoom * 0.80;
621 
622 		if (winwid->zoom < ZOOM_MIN)
623 			winwid->zoom = ZOOM_MIN;
624 
625 		winwid->im_x = (winwid->w / 2) - (((winwid->w / 2) - winwid->im_x) /
626 			winwid->old_zoom * winwid->zoom);
627 		winwid->im_y = (winwid->h / 2) - (((winwid->h / 2) - winwid->im_y) /
628 			winwid->old_zoom * winwid->zoom);
629 		winwidget_sanitise_offsets(winwid);
630 		winwidget_render_image(winwid, 0, 0);
631 	}
632 	else if (feh_is_kp(EVENT_zoom_default, state, keysym, button)) {
633 		winwid->zoom = 1.0;
634 		winwidget_center_image(winwid);
635 		winwidget_render_image(winwid, 0, 0);
636 	}
637 	else if (feh_is_kp(EVENT_zoom_fit, state, keysym, button)) {
638 		feh_calc_needed_zoom(&winwid->zoom, winwid->im_w, winwid->im_h, winwid->w, winwid->h);
639 		winwidget_center_image(winwid);
640 		winwidget_render_image(winwid, 0, 0);
641 	}
642 	else if (feh_is_kp(EVENT_zoom_fill, state, keysym, button)) {
643 		int save_zoom = opt.zoom_mode;
644 		opt.zoom_mode = ZOOM_MODE_FILL;
645 		feh_calc_needed_zoom(&winwid->zoom, winwid->im_w, winwid->im_h, winwid->w, winwid->h);
646 		winwidget_center_image(winwid);
647 		winwidget_render_image(winwid, 0, 0);
648 		opt.zoom_mode = save_zoom;
649 	}
650 	else if (feh_is_kp(EVENT_render, state, keysym, button)) {
651 		if (winwid->type == WIN_TYPE_THUMBNAIL)
652 			feh_thumbnail_show_selected();
653 		else
654 			winwidget_render_image(winwid, 0, 0);
655 	}
656 	else if (feh_is_kp(EVENT_toggle_actions, state, keysym, button)) {
657 		opt.draw_actions = !opt.draw_actions;
658 		winwidget_rerender_all(0);
659 	}
660 	else if (feh_is_kp(EVENT_toggle_aliasing, state, keysym, button)) {
661 		opt.force_aliasing = !opt.force_aliasing;
662 		winwid->force_aliasing = !winwid->force_aliasing;
663 		winwidget_render_image(winwid, 0, 0);
664 	}
665 	else if (feh_is_kp(EVENT_toggle_auto_zoom, state, keysym, button)) {
666 		opt.zoom_mode = (opt.zoom_mode == 0 ? ZOOM_MODE_MAX : 0);
667 		winwidget_rerender_all(1);
668 	}
669 	else if (feh_is_kp(EVENT_toggle_filenames, state, keysym, button)) {
670 		opt.draw_filename = !opt.draw_filename;
671 		winwidget_rerender_all(0);
672 	}
673 #ifdef HAVE_LIBEXIF
674 	else if (feh_is_kp(EVENT_toggle_exif, state, keysym, button)) {
675 		opt.draw_exif = !opt.draw_exif;
676 		winwidget_rerender_all(0);
677 	}
678 #endif
679 	else if (feh_is_kp(EVENT_toggle_info, state, keysym, button)) {
680 		opt.draw_info = !opt.draw_info;
681 		winwidget_rerender_all(0);
682 	}
683 	else if (feh_is_kp(EVENT_toggle_pointer, state, keysym, button)) {
684 		winwidget_set_pointer(winwid, opt.hide_pointer);
685 		opt.hide_pointer = !opt.hide_pointer;
686 	}
687 	else if (feh_is_kp(EVENT_jump_random, state, keysym, button)) {
688 		if (winwid->type == WIN_TYPE_THUMBNAIL)
689 			feh_thumbnail_select_next(winwid, random() % (filelist_len - 1));
690 		else
691 			slideshow_change_image(winwid, SLIDE_RAND, 1);
692 	}
693 	else if (feh_is_kp(EVENT_toggle_caption, state, keysym, button)) {
694 		if (opt.caption_path && path_is_url(FEH_FILE(winwid->file->data)->filename)) {
695 			im_weprintf(winwid, "Caption entry is not supported on URLs");
696 		}
697 		else if (opt.caption_path) {
698 			/*
699 			 * editing captions in slideshow mode does not make any sense
700 			 * at all; this is just in case someone accidentally does it...
701 			 */
702 			if (opt.slideshow_delay)
703 				opt.paused = 1;
704 			winwid->caption_entry = 1;
705 		}
706 		winwidget_render_image(winwid, 0, 0);
707 	}
708 	else if (feh_is_kp(EVENT_reload_image, state, keysym, button)) {
709 		feh_reload_image(winwid, 0, 0);
710 	}
711 	else if (feh_is_kp(EVENT_toggle_pause, state, keysym, button)) {
712 		slideshow_pause_toggle(winwid);
713 		/* We need to re-render the image to update the info string immediately. */
714 		winwidget_render_image(winwid, 0, 0);
715 	}
716 	else if (feh_is_kp(EVENT_save_image, state, keysym, button)) {
717 		slideshow_save_image(winwid);
718 	}
719 	else if (feh_is_kp(EVENT_save_filelist, state, keysym, button)) {
720 		if ((winwid->type == WIN_TYPE_THUMBNAIL)
721 				|| (winwid->type == WIN_TYPE_THUMBNAIL_VIEWER))
722 			weprintf("Filelist saving is not supported in thumbnail mode");
723 		else
724 			feh_save_filelist();
725 	}
726 	else if (feh_is_kp(EVENT_size_to_image, state, keysym, button)) {
727 		winwidget_size_to_image(winwid);
728 	}
729 	else if (!opt.no_menus && feh_is_kp(EVENT_toggle_menu, state, keysym, button)) {
730 		winwidget_show_menu(winwid);
731 	}
732 	else if (feh_is_kp(EVENT_close, state, keysym, button)) {
733 		winwidget_destroy(winwid);
734 	}
735 	else if (feh_is_kp(EVENT_orient_1, state, keysym, button)) {
736 		feh_edit_inplace(winwid, 1);
737 	}
738 	else if (feh_is_kp(EVENT_orient_3, state, keysym, button)) {
739 		feh_edit_inplace(winwid, 3);
740 	}
741 	else if (feh_is_kp(EVENT_flip, state, keysym, button)) {
742 		feh_edit_inplace(winwid, INPLACE_EDIT_FLIP);
743 	}
744 	else if (feh_is_kp(EVENT_mirror, state, keysym, button)) {
745 		feh_edit_inplace(winwid, INPLACE_EDIT_MIRROR);
746 	}
747 	else if (feh_is_kp(EVENT_toggle_fullscreen, state, keysym, button)) {
748 #ifdef HAVE_LIBXINERAMA
749 		if (opt.xinerama && xinerama_screens) {
750 			int i, rect[4];
751 
752 			winwidget_get_geometry(winwid, rect);
753 			for (i = 0; i < num_xinerama_screens; i++) {
754 				xinerama_screen = 0;
755 				if (XY_IN_RECT(rect[0], rect[1],
756 							xinerama_screens[i].x_org,
757 							xinerama_screens[i].y_org,
758 							xinerama_screens[i].width,
759 							xinerama_screens[i].height)) {
760 					curr_screen = xinerama_screen = i;
761 					break;
762 				}
763 			}
764 			if (opt.xinerama_index >= 0)
765 				curr_screen = xinerama_screen = opt.xinerama_index;
766 		}
767 #endif				/* HAVE_LIBXINERAMA */
768 		winwid->full_screen = !winwid->full_screen;
769 		winwidget_destroy_xwin(winwid);
770 		winwidget_create_window(winwid, winwid->im_w, winwid->im_h);
771 		winwidget_render_image(winwid, 1, 0);
772 		winwidget_show(winwid);
773 #ifdef HAVE_LIBXINERAMA
774 		/* if we have xinerama and we're using it, then full screen the window
775 		 * on the head that the window was active on */
776 		if (winwid->full_screen == TRUE && opt.xinerama && xinerama_screens) {
777 			xinerama_screen = curr_screen;
778 			winwidget_move(winwid,
779 					xinerama_screens[curr_screen].x_org, xinerama_screens[curr_screen].y_org);
780 		}
781 #endif				/* HAVE_LIBXINERAMA */
782 	}
783 	else if (feh_is_kp(EVENT_reload_plus, state, keysym, button)){
784 		if (opt.reload < SLIDESHOW_RELOAD_MAX)
785 			opt.reload++;
786 		else if (opt.verbose)
787 			weprintf("Cannot set RELOAD higher than %f seconds.", opt.reload);
788 	}
789 	else if (feh_is_kp(EVENT_reload_minus, state, keysym, button)) {
790 		if (opt.reload > 1)
791 			opt.reload--;
792 		else if (opt.verbose)
793 			weprintf("Cannot set RELOAD lower than 1 second.");
794 	}
795 	else if (feh_is_kp(EVENT_toggle_keep_vp, state, keysym, button)) {
796 		opt.keep_zoom_vp = !opt.keep_zoom_vp;
797 	}
798 	else if (feh_is_kp(EVENT_toggle_fixed_geometry, state, keysym, button)) {
799 		if (opt.geom_flags & ((WidthValue | HeightValue))) {
800 			opt.geom_flags &= ~(WidthValue | HeightValue);
801 		} else {
802 			opt.geom_flags |= (WidthValue | HeightValue);
803 			opt.geom_w = winwid->w;
804 			opt.geom_h = winwid->h;
805 		}
806 		winwidget_render_image(winwid, 1, 0);
807 	}
808 	return;
809 }
810