ltk

GUI toolkit for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/ltk.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
Log | Files | Refs | README | LICENSE

event_xlib.c (20886B)


      1 /*
      2  * Copyright (c) 2022-2024 lumidify <nobody@lumidify.org>
      3  *
      4  * Permission to use, copy, modify, and/or distribute this software for any
      5  * purpose with or without fee is hereby granted, provided that the above
      6  * copyright notice and this permission notice appear in all copies.
      7  *
      8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 #include <math.h>
     18 #include <stdint.h>
     19 #include <stdio.h>
     20 #include <stdlib.h>
     21 #include <string.h>
     22 
     23 #include <X11/X.h>
     24 #include <X11/XKBlib.h>
     25 #include <X11/Xlib.h>
     26 #include <X11/Xutil.h>
     27 #include <X11/extensions/XKB.h>
     28 #include <X11/extensions/XKBstr.h>
     29 #include <X11/keysym.h>
     30 
     31 #if USE_XRANDR
     32 #include <X11/extensions/Xrandr.h>
     33 #endif
     34 
     35 #include "clipboard.h"
     36 #include "clipboard_xlib.h"
     37 #include "config.h"
     38 #include "event.h"
     39 #include "eventdefs.h"
     40 #include "graphics.h"
     41 #include "graphics_xlib.h"
     42 #include "memory.h"
     43 #include "util.h"
     44 
     45 LTK_ARRAY_INIT_FUNC_DECL_STATIC(moninfo, struct ltk_moninfo)
     46 LTK_ARRAY_INIT_IMPL_STATIC(moninfo, struct ltk_moninfo)
     47 
     48 #define TEXT_INITIAL_SIZE 128
     49 
     50 static char *text = NULL;
     51 static size_t text_alloc = 0;
     52 static char *cur_kbd = NULL;
     53 /* FIXME: support more buttons?
     54    -> What even makes sense here? Mice can support a bunch
     55    of buttons, but what is sensible here? Just adding
     56    support for higher button numbers would cause problems
     57    when adding other backends (e.g. SDL) that might do
     58    things completely differently. */
     59 /* FIXME: support touch events? */
     60 /* times of last button press/release,
     61    used to implement double/triple-click */
     62 static Time last_button_press[] = {0, 0, 0};
     63 static Time last_button_release[] = {0, 0, 0};
     64 /* positions of last press/release so double/triple-click is
     65    only generated when the position is near enough */
     66 static struct point {
     67 	int x;
     68 	int y;
     69 } press_pos[] = {{0, 0}, {0, 0}, {0, 0}};
     70 static struct point release_pos[] = {{0, 0}, {0, 0}, {0, 0}};
     71 /* stores whether the last button press already was
     72    a double-click to decide if a triple-click should
     73    be generated (same for release) */
     74 static int was_2press[] = {0, 0, 0};
     75 static int was_2release[] = {0, 0, 0};
     76 /* Used to store special next event - currently just
     77    used to implement double/triple-click because the
     78    actual double/triple-click/release event is
     79    generated in addition to the regular press/release */
     80 
     81 static ltk_keysym get_keysym(KeySym sym);
     82 
     83 LTK_ARRAY_INIT_DECL_STATIC(event, ltk_event)
     84 LTK_ARRAY_INIT_IMPL_STATIC(event, ltk_event)
     85 
     86 /* A queue would make more sense here, but it doesn't matter
     87    for the way it's used currently. */
     88 static ltk_array(event) *local_event_stack = NULL;
     89 
     90 void
     91 ltk_events_cleanup(void) {
     92 	ltk_free(text);
     93 	ltk_free(cur_kbd);
     94 	cur_kbd = NULL;
     95 	text = NULL;
     96 	text_alloc = 0;
     97 	/* Note: This assumes that none of the events in the
     98 	   stack need special cleaning. */
     99 	if (local_event_stack)
    100 		ltk_array_destroy(event, local_event_stack);
    101 }
    102 
    103 static ltk_button_type
    104 get_button(unsigned int button) {
    105 	switch (button) {
    106 	case Button1: return LTK_BUTTONL;
    107 	case Button2: return LTK_BUTTONM;
    108 	case Button3: return LTK_BUTTONR;
    109 	default: return LTK_BUTTONL; /* FIXME: what to do here? */
    110 	}
    111 }
    112 
    113 /* FIXME: properly implement modifiers - see SDL and GTK */
    114 static ltk_mod_type
    115 get_modmask(unsigned int state) {
    116 	ltk_mod_type t = 0;
    117 	if (state & ControlMask)
    118 		t |= LTK_MOD_CTRL;
    119 	if (state & ShiftMask)
    120 		t |= LTK_MOD_SHIFT;
    121 	if (state & Mod1Mask)
    122 		t |= LTK_MOD_ALT;
    123 	if (state & Mod4Mask)
    124 		t |= LTK_MOD_SUPER;
    125 	return t;
    126 }
    127 #ifdef X_HAVE_UTF8_STRING
    128 #define LOOKUP_STRING_FUNC Xutf8LookupString
    129 #else
    130 #define LOOKUP_STRING_FUNC XmbLookupString
    131 #endif
    132 
    133 static ltk_event
    134 process_key(ltk_renderwindow *renderwindow, size_t lang_index, XEvent *event, ltk_event_type type) {
    135 	ltk_language_mapping *map = ltk_config_get_language_mapping(lang_index);
    136 	/* FIXME: see comment in keys.c in ledit repository */
    137 	if (!text) {
    138 		text = ltk_malloc(TEXT_INITIAL_SIZE);
    139 		text_alloc = TEXT_INITIAL_SIZE;
    140 	}
    141 	unsigned int state = event->xkey.state;
    142 	event->xkey.state &= ~ControlMask;
    143 	KeySym sym;
    144 	int len = 0;
    145 	Status status;
    146 	if (renderwindow->xic && type == LTK_KEYPRESS_EVENT) {
    147 		len = LOOKUP_STRING_FUNC(renderwindow->xic, &event->xkey, text, text_alloc - 1, &sym, &status);
    148 		if (status == XBufferOverflow) {
    149 			text_alloc = ideal_array_size(text_alloc, len + 1);
    150 			text = ltk_realloc(text, text_alloc);
    151 			len = LOOKUP_STRING_FUNC(renderwindow->xic, &event->xkey, text, text_alloc - 1, &sym, &status);
    152 		}
    153 	} else {
    154 		/* FIXME: anything equivalent to XBufferOverflow here? */
    155 		len = XLookupString(&event->xkey, text, text_alloc - 1, &sym, NULL);
    156 		status = XLookupBoth;
    157 	}
    158 	text[len >= (int)text_alloc ? (int)text_alloc - 1 : len] = '\0';
    159 	char *key_text = (status == XLookupChars || status == XLookupBoth) ? text : NULL;
    160 	char *mapped = key_text;
    161 	/* FIXME: BINARY SEARCH! */
    162 	if (key_text && map) {
    163 		for (size_t i = 0; i < map->mappings_len; i++) {
    164 			if (!strcmp(key_text, map->mappings[i].from)) {
    165 				mapped = map->mappings[i].to;
    166 				break;
    167 			}
    168 		}
    169 	}
    170 	return (ltk_event){.key = {
    171 		.type = type,
    172 		.modmask = get_modmask(state),
    173 		.sym = (status == XLookupKeySym || status == XLookupBoth) ? get_keysym(sym) : LTK_KEY_NONE,
    174 		.text = key_text,
    175 		.mapped = mapped
    176 	}};
    177 }
    178 
    179 void
    180 ltk_generate_keyboard_event(ltk_renderdata *renderdata, ltk_event *event) {
    181 	XkbStateRec s;
    182 	XkbGetState(renderdata->dpy, XkbUseCoreKbd, &s);
    183 	XkbDescPtr desc = XkbGetKeyboard(
    184 	    renderdata->dpy, XkbAllComponentsMask, XkbUseCoreKbd
    185 	);
    186 	char *group = XGetAtomName(
    187 	    renderdata->dpy, desc->names->groups[s.group]
    188 	);
    189 	ltk_free(cur_kbd);
    190 	/* just so the interface is the same for all events and the
    191 	   caller doesn't have to free the contained string(s) */
    192 	cur_kbd = ltk_strdup(group);
    193 	*event = (ltk_event){.keyboard = {
    194 		.type = LTK_KEYBOARDCHANGE_EVENT,
    195 		.new_kbd = cur_kbd
    196 	}};
    197 	XFree(group);
    198 	XkbFreeKeyboard(desc, XkbAllComponentsMask, True);
    199 }
    200 
    201 /* FIXME: Move this to graphics_xlib.c */
    202 /* FIXME: maybe split renderdata into general and specific part since this could be used for all backends */
    203 /* returns 1 if dpi changed, 0 otherwise */
    204 int
    205 ltk_recalc_renderwindow_dpi(ltk_renderwindow *window) {
    206 	ltk_renderdata *renderdata = window->renderdata;
    207 	if (!renderdata->monitors || ltk_array_len(renderdata->monitors) == 0)
    208 		return 0;
    209 	unsigned long maxarea = 0;
    210 	size_t maxindex = 0;
    211 	/* FIXME: should this use the monitor with the largest pixel area (like it
    212 	   currently does) or the largest physical area? */
    213 	for (size_t i = 0; i < ltk_array_len(renderdata->monitors); i++) {
    214 		struct ltk_moninfo *info = &ltk_array_get(renderdata->monitors, i);
    215 		ltk_rect isect = ltk_rect_intersect(
    216 			window->rect,
    217 			(ltk_rect){info->x, info->y, info->width, info->height}
    218 		);
    219 		unsigned long tmp_area = 0;
    220 		if (isect.w > 0 && isect.h > 0 &&
    221 		    (tmp_area = (unsigned long)isect.w * (unsigned long)isect.h) > maxarea) {
    222 			maxarea = tmp_area;
    223 			maxindex = i;
    224 		}
    225 	}
    226 	unsigned int dpi = ltk_array_get(renderdata->monitors, maxindex).dpi;
    227 	if (dpi != window->dpi) {
    228 		window->dpi = dpi;
    229 		return 1;
    230 	}
    231 	return 0;
    232 }
    233 
    234 #if USE_XRANDR
    235 static void
    236 update_monitor_config(ltk_renderdata *renderdata, ltk_renderwindow **windows, size_t num_windows) {
    237 	int nmon;
    238 	ltk_general_config *config = ltk_config_get_general();
    239 	if (!config->mixed_dpi)
    240 		return;
    241 	XRRMonitorInfo *mi = XRRGetMonitors(renderdata->dpy, renderdata->root_window, 1, &nmon);
    242 	if (nmon > 0 && !renderdata->monitors)
    243 		renderdata->monitors = ltk_array_create(moninfo, 1);
    244 	for (int i = 0; i < nmon; i++) {
    245 		struct ltk_moninfo info = {mi[i].x, mi[i].y, mi[i].width, mi[i].height, mi[i].mwidth, mi[i].mheight, 0};
    246 		/* FIXME: This only uses the width for the calculation. It should be the same if using
    247 		   the height, but is that guaranteed? */
    248 		/* FIXME: can width or mwidth ever by negative? */
    249 		info.dpi = (unsigned int)round(config->dpi_scale * (info.width / (info.mwidth / 127.0)));
    250 		/* FIXME: need to adjust default dpi and document */
    251 		/* -> config file dpi should still be regular dpi */
    252 		/* FIXME: check for overflows in the later pixel computation */
    253 		if (info.dpi == 0)
    254 			info.dpi = 10; /* at least prevent issues with zero dpi */
    255 		if ((size_t)i < ltk_array_len(renderdata->monitors))
    256 			ltk_array_get(renderdata->monitors, i) = info;
    257 		else
    258 			ltk_array_append(moninfo, renderdata->monitors, info);
    259 	}
    260 	XRRFreeMonitors(mi);
    261 	for (size_t i = 0; i < num_windows; i++) {
    262 		if (ltk_recalc_renderwindow_dpi(windows[i])) {
    263 			ltk_event e = (ltk_event){.dpichange = {
    264 				.type = LTK_DPICHANGE_EVENT,
    265 				.window_id = i,
    266 				.dpi = windows[i]->dpi
    267 			}};
    268 			ltk_assert(local_event_stack != NULL);
    269 			ltk_array_append(event, local_event_stack, e);
    270 		}
    271 	}
    272 }
    273 #endif
    274 
    275 #define DISTSQ(x0, y0, x1, y1) (((x1) - (x0)) * ((x1) - (x0)) + ((y1) - (y0)) * ((y1) - (y0)))
    276 
    277 void
    278 ltk_events_init(ltk_renderdata *renderdata) {
    279 #if USE_XRANDR
    280 	update_monitor_config(renderdata, NULL, 0);
    281 #else
    282 	(void)renderdata;
    283 #endif
    284 }
    285 
    286 /* return value 0 means valid event returned,
    287    1 means no events pending,
    288    2 means event discarded (need to call again) */
    289 static int
    290 next_event_base(ltk_renderdata *renderdata, ltk_renderwindow **windows, size_t num_windows, ltk_clipboard *clip, size_t lang_index, ltk_event *event) {
    291 	if (!local_event_stack)
    292 		local_event_stack = ltk_array_create(event, 1);
    293 	/* FIXME: I guess it's technically possible that a window is destroyed between two calls
    294 	   to this function, meaning that the window_id in the local event stack isn't correct anymore. */
    295 	if (ltk_array_len(local_event_stack) > 0) {
    296 		*event = ltk_array_pop(event, local_event_stack);
    297 		return 0;
    298 	}
    299 	XEvent xevent;
    300 	if (!XPending(renderdata->dpy))
    301 		return 1;
    302 	XNextEvent(renderdata->dpy, &xevent);
    303 	/* FIXME: support different keyboard mappings for different windows */
    304 	if (renderdata->xkb_supported && xevent.type == renderdata->xkb_event_type) {
    305 		ltk_generate_keyboard_event(renderdata, event);
    306 		return 0;
    307 	}
    308 #if USE_XRANDR
    309 	if (xevent.xany.window == renderdata->root_window) {
    310 		if (!renderdata->xrandr_supported)
    311 			return 2; /* shouldn't normally happen */
    312 		XRRUpdateConfiguration(&xevent);
    313 		if (xevent.type == renderdata->xrandr_event_type + RRScreenChangeNotify) {
    314 			update_monitor_config(renderdata, windows, num_windows);
    315 		}
    316 		return 2;
    317 	}
    318 #endif
    319 	*event = (ltk_event){.any = {.type = LTK_UNKNOWN_EVENT, .window_id = SIZE_MAX}};
    320 	if (XFilterEvent(&xevent, None))
    321 		return 2;
    322 	if (clip && ltk_clipboard_filter_event(clip, &xevent))
    323 		return 2;
    324 	int button = 0;
    325 	size_t window_id = SIZE_MAX;
    326 	for (size_t i = 0; i < num_windows; i++) {
    327 		if (xevent.xany.window == windows[i]->xwindow) {
    328 			window_id = i;
    329 			break;
    330 		}
    331 	}
    332 	switch (xevent.type) {
    333 	case ButtonPress:
    334 		ltk_assert(window_id < num_windows);
    335 		button = xevent.xbutton.button;
    336 		/* FIXME: are the buttons really always defined as exactly these values? */
    337 		if (button >= 1 && button <= 3) {
    338 			if (xevent.xbutton.time - last_button_press[button - 1] <= DOUBLECLICK_TIME &&
    339 			    DISTSQ(press_pos[button - 1].x, press_pos[button - 1].y, xevent.xbutton.x, xevent.xbutton.y) <= DOUBLECLICK_DISTSQ) {
    340 				if (was_2press[button - 1]) {
    341 					/* reset so normal press is sent again next time */
    342 					was_2press[button - 1] = 0;
    343 					last_button_press[button - 1] = 0;
    344 					ltk_array_append_event(local_event_stack, (ltk_event){.button = {
    345 						.type = LTK_3BUTTONPRESS_EVENT,
    346 						.window_id = window_id,
    347 						.button = get_button(button),
    348 						.x = xevent.xbutton.x,
    349 						.y = xevent.xbutton.y
    350 					}});
    351 				} else {
    352 					was_2press[button - 1] = 1;
    353 					last_button_press[button - 1] = xevent.xbutton.time;
    354 					ltk_array_append_event(local_event_stack, (ltk_event){.button = {
    355 						.type = LTK_2BUTTONPRESS_EVENT,
    356 						.window_id = window_id,
    357 						.button = get_button(button),
    358 						.x = xevent.xbutton.x,
    359 						.y = xevent.xbutton.y
    360 					}});
    361 				}
    362 			} else {
    363 				last_button_press[button - 1] = xevent.xbutton.time;
    364 				was_2press[button - 1] = 0;
    365 			}
    366 			*event = (ltk_event){.button = {
    367 				.type = LTK_BUTTONPRESS_EVENT,
    368 				.window_id = window_id,
    369 				.button = get_button(button),
    370 				.x = xevent.xbutton.x,
    371 				.y = xevent.xbutton.y
    372 			}};
    373 			press_pos[button - 1].x = xevent.xbutton.x;
    374 			press_pos[button - 1].y = xevent.xbutton.y;
    375 		} else if (button >= 4 && button <= 7) {
    376 			/* FIXME: compress multiple scroll events into one */
    377 			*event = (ltk_event){.scroll = {
    378 				.type = LTK_SCROLL_EVENT,
    379 				.window_id = window_id,
    380 				.x = xevent.xbutton.x,
    381 				.y = xevent.xbutton.y,
    382 				.dx = 0,
    383 				.dy = 0
    384 			}};
    385 			switch (button) {
    386 			case 4:
    387 				event->scroll.dy = 1;
    388 				break;
    389 			case 5:
    390 				event->scroll.dy = -1;
    391 				break;
    392 			case 6:
    393 				event->scroll.dx = -1;
    394 				break;
    395 			case 7:
    396 				event->scroll.dx = 1;
    397 				break;
    398 			}
    399 		} else {
    400 			return 2;
    401 		}
    402 		break;
    403 	case ButtonRelease:
    404 		ltk_assert(window_id < num_windows);
    405 		button = xevent.xbutton.button;
    406 		if (button >= 1 && button <= 3) {
    407 			if (xevent.xbutton.time - last_button_release[button - 1] <= DOUBLECLICK_TIME &&
    408 			    DISTSQ(release_pos[button - 1].x, release_pos[button - 1].y, xevent.xbutton.x, xevent.xbutton.y) <= DOUBLECLICK_DISTSQ) {
    409 				if (was_2release[button - 1]) {
    410 					/* reset so normal release is sent again next time */
    411 					was_2release[button - 1] = 0;
    412 					last_button_release[button - 1] = 0;
    413 					ltk_array_append_event(local_event_stack, (ltk_event){.button = {
    414 						.type = LTK_3BUTTONRELEASE_EVENT,
    415 						.window_id = window_id,
    416 						.button = get_button(button),
    417 						.x = xevent.xbutton.x,
    418 						.y = xevent.xbutton.y
    419 					}});
    420 				} else {
    421 					was_2release[button - 1] = 1;
    422 					last_button_release[button - 1] = xevent.xbutton.time;
    423 					ltk_array_append_event(local_event_stack, (ltk_event){.button = {
    424 						.type = LTK_2BUTTONRELEASE_EVENT,
    425 						.window_id = window_id,
    426 						.button = get_button(button),
    427 						.x = xevent.xbutton.x,
    428 						.y = xevent.xbutton.y
    429 					}});
    430 				}
    431 			} else {
    432 				last_button_release[button - 1] = xevent.xbutton.time;
    433 				was_2release[button - 1] = 0;
    434 			}
    435 			*event = (ltk_event){.button = {
    436 				.type = LTK_BUTTONRELEASE_EVENT,
    437 				.window_id = window_id,
    438 				.button = get_button(button),
    439 				.x = xevent.xbutton.x,
    440 				.y = xevent.xbutton.y
    441 			}};
    442 			release_pos[button - 1].x = xevent.xbutton.x;
    443 			release_pos[button - 1].y = xevent.xbutton.y;
    444 		} else {
    445 			return 2;
    446 		}
    447 		break;
    448 	case MotionNotify:
    449 		ltk_assert(window_id < num_windows);
    450 		/* FIXME: compress motion events */
    451 		*event = (ltk_event){.motion = {
    452 			.type = LTK_MOTION_EVENT,
    453 			.window_id = window_id,
    454 			.x = xevent.xmotion.x,
    455 			.y = xevent.xmotion.y
    456 		}};
    457 		break;
    458 	case KeyPress:
    459 		ltk_assert(window_id < num_windows);
    460 		*event = process_key(windows[window_id], lang_index, &xevent, LTK_KEYPRESS_EVENT);
    461 		event->any.window_id = window_id;
    462 		break;
    463 	case KeyRelease:
    464 		ltk_assert(window_id < num_windows);
    465 		*event = process_key(windows[window_id], lang_index, &xevent, LTK_KEYRELEASE_EVENT);
    466 		event->any.window_id = window_id;
    467 		break;
    468 	case ConfigureNotify:
    469 		ltk_assert(window_id < num_windows);
    470 		ltk_event cevent = {.configure = {
    471 			.type = LTK_CONFIGURE_EVENT,
    472 			.window_id = window_id,
    473 			.x = xevent.xconfigure.x,
    474 			.y = xevent.xconfigure.y,
    475 			.w = xevent.xconfigure.width,
    476 			.h = xevent.xconfigure.height
    477 		}};
    478 		windows[window_id]->rect = (ltk_rect){
    479 			xevent.xconfigure.x, xevent.xconfigure.y,
    480 			xevent.xconfigure.width, xevent.xconfigure.height
    481 		};
    482 		if (ltk_recalc_renderwindow_dpi(windows[window_id])) {
    483 			*event = (ltk_event){.dpichange = {
    484 				.type = LTK_DPICHANGE_EVENT,
    485 				.window_id = window_id,
    486 				.dpi = windows[window_id]->dpi
    487 			}};
    488 			ltk_array_append(event, local_event_stack, cevent);
    489 		} else {
    490 			*event = cevent;
    491 		}
    492 		break;
    493 	case Expose:
    494 		ltk_assert(window_id < num_windows);
    495 		/* FIXME: ignoring all of these events would make it not
    496 		   work anymore if the actual rectangle wasn't ignored
    497 		   later anyways */
    498 		if (xevent.xexpose.count == 0) {
    499 			*event = (ltk_event){.expose = {
    500 				.type = LTK_EXPOSE_EVENT,
    501 				.window_id = window_id,
    502 				.x = xevent.xexpose.x,
    503 				.y = xevent.xexpose.y,
    504 				.w = xevent.xexpose.width,
    505 				.h = xevent.xexpose.height
    506 			}};
    507 		} else {
    508 			return 2;
    509 		}
    510 		break;
    511 	case ClientMessage:
    512 		ltk_assert(window_id < num_windows);
    513 		if ((Atom)xevent.xclient.data.l[0] == renderdata->wm_delete_msg)
    514 			*event = (ltk_event){.any = {.type = LTK_WINDOWCLOSE_EVENT, .window_id = window_id}};
    515 		else
    516 			return 2;
    517 		break;
    518 	default:
    519 		break;
    520 	}
    521 	/* just in case... */
    522 	event->any.window_id = window_id;
    523 	return 0;
    524 }
    525 
    526 int
    527 ltk_next_event(ltk_renderdata *renderdata, ltk_renderwindow **windows, size_t num_windows, ltk_clipboard *clip, size_t lang_index, ltk_event *event) {
    528 	int ret = 0;
    529 	while ((ret = next_event_base(renderdata, windows, num_windows, clip, lang_index, event)) == 2) {
    530 		/* NOP */
    531 	}
    532 	return ret;
    533 }
    534 
    535 /* FIXME: pre-sort array (current sorting is taken from config.c but doesn't make sense here) */
    536 /* FIXME: can some structure of the X keysyms be abused to make this more efficient */
    537 static struct keysym_mapping {
    538 	KeySym xkeysym;
    539 	ltk_keysym keysym;
    540 } keysym_map[] = {
    541 	{XK_BackSpace, LTK_KEY_BACKSPACE},
    542 	{XK_Begin, LTK_KEY_BEGIN},
    543 	{XK_Break, LTK_KEY_BREAK},
    544 	{XK_Cancel, LTK_KEY_CANCEL},
    545 	{XK_Clear, LTK_KEY_CLEAR},
    546 	{XK_Delete, LTK_KEY_DELETE},
    547 	{XK_Down, LTK_KEY_DOWN},
    548 	{XK_End, LTK_KEY_END},
    549 	{XK_Escape, LTK_KEY_ESCAPE},
    550 	{XK_Execute, LTK_KEY_EXECUTE},
    551 
    552 	{XK_F1, LTK_KEY_F1},
    553 	{XK_F10, LTK_KEY_F10},
    554 	{XK_F11, LTK_KEY_F11},
    555 	{XK_F12, LTK_KEY_F12},
    556 	{XK_F2, LTK_KEY_F2},
    557 	{XK_F3, LTK_KEY_F3},
    558 	{XK_F4, LTK_KEY_F4},
    559 	{XK_F5, LTK_KEY_F5},
    560 	{XK_F6, LTK_KEY_F6},
    561 	{XK_F7, LTK_KEY_F7},
    562 	{XK_F8, LTK_KEY_F8},
    563 	{XK_F9, LTK_KEY_F9},
    564 
    565 	{XK_Find, LTK_KEY_FIND},
    566 	{XK_Help, LTK_KEY_HELP},
    567 	{XK_Home, LTK_KEY_HOME},
    568 	{XK_Insert, LTK_KEY_INSERT},
    569 
    570 	{XK_KP_0, LTK_KEY_KP_0},
    571 	{XK_KP_1, LTK_KEY_KP_1},
    572 	{XK_KP_2, LTK_KEY_KP_2},
    573 	{XK_KP_3, LTK_KEY_KP_3},
    574 	{XK_KP_4, LTK_KEY_KP_4},
    575 	{XK_KP_5, LTK_KEY_KP_5},
    576 	{XK_KP_6, LTK_KEY_KP_6},
    577 	{XK_KP_7, LTK_KEY_KP_7},
    578 	{XK_KP_8, LTK_KEY_KP_8},
    579 	{XK_KP_9, LTK_KEY_KP_9},
    580 	{XK_KP_Add, LTK_KEY_KP_ADD},
    581 	{XK_KP_Begin, LTK_KEY_KP_BEGIN},
    582 	{XK_KP_Decimal, LTK_KEY_KP_DECIMAL},
    583 	{XK_KP_Delete, LTK_KEY_KP_DELETE},
    584 	{XK_KP_Divide, LTK_KEY_KP_DIVIDE},
    585 	{XK_KP_Down, LTK_KEY_KP_DOWN},
    586 	{XK_KP_End, LTK_KEY_KP_END},
    587 	{XK_KP_Enter, LTK_KEY_KP_ENTER},
    588 	{XK_KP_Equal, LTK_KEY_KP_EQUAL},
    589 	{XK_KP_Home, LTK_KEY_KP_HOME},
    590 	{XK_KP_Insert, LTK_KEY_KP_INSERT},
    591 	{XK_KP_Left, LTK_KEY_KP_LEFT},
    592 	{XK_KP_Multiply, LTK_KEY_KP_MULTIPLY},
    593 	{XK_KP_Next, LTK_KEY_KP_NEXT},
    594 	{XK_KP_Page_Down, LTK_KEY_KP_PAGE_DOWN},
    595 	{XK_KP_Page_Up, LTK_KEY_KP_PAGE_UP},
    596 	{XK_KP_Prior, LTK_KEY_KP_PRIOR},
    597 	{XK_KP_Right, LTK_KEY_KP_RIGHT},
    598 	{XK_KP_Separator, LTK_KEY_KP_SEPARATOR},
    599 	{XK_KP_Space, LTK_KEY_KP_SPACE},
    600 	{XK_KP_Subtract, LTK_KEY_KP_SUBTRACT},
    601 	{XK_KP_Tab, LTK_KEY_KP_TAB},
    602 	{XK_KP_Up, LTK_KEY_KP_UP},
    603 
    604 	{XK_Left, LTK_KEY_LEFT},
    605 	{XK_Linefeed, LTK_KEY_LINEFEED},
    606 	{XK_Menu, LTK_KEY_MENU},
    607 	{XK_Mode_switch, LTK_KEY_MODE_SWITCH},
    608 	{XK_Next, LTK_KEY_NEXT},
    609 	{XK_Num_Lock, LTK_KEY_NUM_LOCK},
    610 	{XK_Page_Down, LTK_KEY_PAGE_DOWN},
    611 	{XK_Page_Up, LTK_KEY_PAGE_UP},
    612 	{XK_Pause, LTK_KEY_PAUSE},
    613 	{XK_Print, LTK_KEY_PRINT},
    614 	{XK_Prior, LTK_KEY_PRIOR},
    615 
    616 	{XK_Redo, LTK_KEY_REDO},
    617 	{XK_Return, LTK_KEY_RETURN},
    618 	{XK_Right, LTK_KEY_RIGHT},
    619 	{XK_script_switch, LTK_KEY_SCRIPT_SWITCH},
    620 	{XK_Scroll_Lock, LTK_KEY_SCROLL_LOCK},
    621 	{XK_Select, LTK_KEY_SELECT},
    622 	{XK_space, LTK_KEY_SPACE},
    623 	{XK_Sys_Req, LTK_KEY_SYS_REQ},
    624 	{XK_Tab, LTK_KEY_TAB},
    625 	{XK_ISO_Left_Tab, LTK_KEY_LEFT_TAB},
    626 	{XK_Up, LTK_KEY_UP},
    627 	{XK_Undo, LTK_KEY_UNDO},
    628 };
    629 
    630 static int keysym_map_sorted = 0;
    631 static int
    632 keysym_map_search_helper(const void *keyv, const void *entryv) {
    633 	KeySym sym = *((KeySym*)keyv);
    634 	struct keysym_mapping *m = (struct keysym_mapping *)entryv;
    635 	/* FIXME: branchless compare version? */
    636 	return (sym < m->xkeysym) ? -1 : (sym > m->xkeysym);
    637 }
    638 static int
    639 keysym_map_sort_helper(const void *entry1v, const void *entry2v) {
    640 	struct keysym_mapping *m1 = (struct keysym_mapping *)entry1v;
    641 	struct keysym_mapping *m2 = (struct keysym_mapping *)entry2v;
    642 	return (m1->xkeysym < m2->xkeysym) ? -1 : (m1->xkeysym > m2->xkeysym);
    643 }
    644 
    645 static ltk_keysym
    646 get_keysym(KeySym sym) {
    647 	if (!keysym_map_sorted) {
    648 		qsort(
    649 		    keysym_map, LENGTH(keysym_map),
    650 		    sizeof(keysym_map[0]), &keysym_map_sort_helper
    651 		);
    652 		keysym_map_sorted = 1;
    653 	}
    654 	struct keysym_mapping *m = bsearch(
    655 	    &sym, keysym_map, LENGTH(keysym_map),
    656 	    sizeof(keysym_map[0]), &keysym_map_search_helper
    657 	);
    658 	return m ? m->keysym : LTK_KEY_NONE;
    659 }