URI: 
       event_xlib.c - ltk - GUI toolkit for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/ltk.git (encrypted, but very slow)
  HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR 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 }