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 = <k_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 }