URI:
       tevent_xlib.c - ltk - Socket-based GUI for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       tevent_xlib.c (13941B)
       ---
            1 #include <stdio.h>
            2 
            3 #include <X11/XKBlib.h>
            4 #include <X11/extensions/XKBrules.h>
            5 
            6 #include "util.h"
            7 #include "memory.h"
            8 #include "graphics.h"
            9 #include "xlib_shared.h"
           10 #include "config.h"
           11 #include "clipboard_xlib.h"
           12 
           13 #define TEXT_INITIAL_SIZE 128
           14 
           15 static char *text = NULL;
           16 static size_t text_alloc = 0;
           17 static char *cur_kbd = NULL;
           18 /* FIXME: support more buttons?
           19    -> What even makes sense here? Mice can support a bunch
           20    of buttons, but what is sensible here? Just adding
           21    support for higher button numbers would cause problems
           22    when adding other backends (e.g. SDL) that might do
           23    things completely differently. */
           24 /* FIXME: support touch events? */
           25 /* times of last button press/release,
           26    used to implement double/triple-click */
           27 static Time last_button_press[] = {0, 0, 0};
           28 static Time last_button_release[] = {0, 0, 0};
           29 /* positions of last press/release so double/triple-click is
           30    only generated when the position is near enough */
           31 static struct point {
           32         int x;
           33         int y;
           34 } press_pos[] = {{0, 0}, {0, 0}, {0, 0}};
           35 static struct point release_pos[] = {{0, 0}, {0, 0}, {0, 0}};
           36 /* stores whether the last button press already was
           37    a double-click to decide if a triple-click should
           38    be generated (same for release) */
           39 static int was_2press[] = {0, 0, 0};
           40 static int was_2release[] = {0, 0, 0};
           41 /* Used to store special next event - currently just
           42    used to implement double/triple-click because the
           43    actual double/triple-click/release event is
           44    generated in addition to the regular press/release */
           45 static int next_event_valid = 0;
           46 static ltk_button_event next_event;
           47 
           48 static ltk_keysym get_keysym(KeySym sym);
           49 
           50 void
           51 ltk_events_cleanup(void) {
           52         ltk_free(text);
           53         ltk_free(cur_kbd);
           54         cur_kbd = NULL;
           55         text = NULL;
           56         text_alloc = 0;
           57 }
           58 
           59 static ltk_button_type
           60 get_button(unsigned int button) {
           61         switch (button) {
           62         case Button1: return LTK_BUTTONL;
           63         case Button2: return LTK_BUTTONM;
           64         case Button3: return LTK_BUTTONR;
           65         default: return LTK_BUTTONL; /* FIXME: what to do here? */
           66         }
           67 }
           68 
           69 /* FIXME: properly implement modifiers - see SDL and GTK */
           70 static ltk_mod_type
           71 get_modmask(unsigned int state) {
           72         ltk_mod_type t = 0;
           73         if (state & ControlMask)
           74                 t |= LTK_MOD_CTRL;
           75         if (state & ShiftMask)
           76                 t |= LTK_MOD_SHIFT;
           77         if (state & Mod1Mask)
           78                 t |= LTK_MOD_ALT;
           79         if (state & Mod4Mask)
           80                 t |= LTK_MOD_SUPER;
           81         return t;
           82 }
           83 #ifdef X_HAVE_UTF8_STRING
           84 #define LOOKUP_STRING_FUNC Xutf8LookupString
           85 #else
           86 #define LOOKUP_STRING_FUNC XmbLookupString
           87 #endif
           88 
           89 static ltk_event
           90 process_key(ltk_renderdata *renderdata, size_t lang_index, XEvent *event, ltk_event_type type) {
           91         ltk_language_mapping *map = ltk_config_get_language_mapping(lang_index);
           92         /* FIXME: see comment in keys.c in ledit repository */
           93         if (!text) {
           94                 text = ltk_malloc(TEXT_INITIAL_SIZE);
           95                 text_alloc = TEXT_INITIAL_SIZE;
           96         }
           97         unsigned int state = event->xkey.state;
           98         event->xkey.state &= ~ControlMask;
           99         KeySym sym;
          100         int len = 0;
          101         Status status;
          102         if (renderdata->xic && type == LTK_KEYPRESS_EVENT) {
          103                 len = LOOKUP_STRING_FUNC(renderdata->xic, &event->xkey, text, text_alloc - 1, &sym, &status);
          104                 if (status == XBufferOverflow) {
          105                         text_alloc = ideal_array_size(text_alloc, len + 1);
          106                         text = ltk_realloc(text, text_alloc);
          107                         len = LOOKUP_STRING_FUNC(renderdata->xic, &event->xkey, text, text_alloc - 1, &sym, &status);
          108                 }
          109         } else {
          110                 /* FIXME: anything equivalent to XBufferOverflow here? */
          111                 len = XLookupString(&event->xkey, text, text_alloc - 1, &sym, NULL);
          112                 status = XLookupBoth;
          113         }
          114         text[len >= (int)text_alloc ? (int)text_alloc - 1 : len] = '\0';
          115         char *key_text = (status == XLookupChars || status == XLookupBoth) ? text : NULL;
          116         char *mapped = key_text;
          117         /* FIXME: BINARY SEARCH! */
          118         if (key_text && map) {
          119                 for (size_t i = 0; i < map->mappings_len; i++) {
          120                         if (!strcmp(key_text, map->mappings[i].from)) {
          121                                 mapped = map->mappings[i].to;
          122                                 break;
          123                         }
          124                 }
          125         }
          126         return (ltk_event){.key = {
          127                 .type = type,
          128                 .modmask = get_modmask(state),
          129                 .sym = (status == XLookupKeySym || status == XLookupBoth) ? get_keysym(sym) : LTK_KEY_NONE,
          130                 .text = key_text,
          131                 .mapped = mapped
          132         }};
          133 }
          134 
          135 void
          136 ltk_generate_keyboard_event(ltk_renderdata *renderdata, ltk_event *event) {
          137         XkbStateRec s;
          138         XkbGetState(renderdata->dpy, XkbUseCoreKbd, &s);
          139         XkbDescPtr desc = XkbGetKeyboard(
          140             renderdata->dpy, XkbAllComponentsMask, XkbUseCoreKbd
          141         );
          142         char *group = XGetAtomName(
          143             renderdata->dpy, desc->names->groups[s.group]
          144         );
          145         ltk_free(cur_kbd);
          146         /* just so the interface is the same for all events and the
          147            caller doesn't have to free the contained string(s) */
          148         cur_kbd = ltk_strdup(group);
          149         *event = (ltk_event){.keyboard = {
          150                 .type = LTK_KEYBOARDCHANGE_EVENT,
          151                 .new_kbd = cur_kbd
          152         }};
          153         XFree(group);
          154         XkbFreeKeyboard(desc, XkbAllComponentsMask, True);
          155 }
          156 
          157 #define DISTSQ(x0, y0, x1, y1) (((x1) - (x0)) * ((x1) - (x0)) + ((y1) - (y0)) * ((y1) - (y0)))
          158 /* return value 0 means valid event returned,
          159    1 means no events pending,
          160    2 means event discarded (need to call again) */
          161 static int
          162 next_event_base(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_index, ltk_event *event) {
          163         if (next_event_valid) {
          164                 next_event_valid = 0;
          165                 *event = (ltk_event){.button = next_event};
          166                 return 0;
          167         }
          168         XEvent xevent;
          169         if (!XPending(renderdata->dpy))
          170                 return 1;
          171         XNextEvent(renderdata->dpy, &xevent);
          172         if (renderdata->xkb_supported && xevent.type == renderdata->xkb_event_type) {
          173                 ltk_generate_keyboard_event(renderdata, event);
          174                 return 0;
          175         }
          176         *event = (ltk_event){.type = LTK_UNKNOWN_EVENT};
          177         if (XFilterEvent(&xevent, None))
          178                 return 2;
          179         if (clip && ltk_clipboard_filter_event(clip, &xevent))
          180                 return 2;
          181         int button = 0;
          182         switch (xevent.type) {
          183         case ButtonPress:
          184                 button = xevent.xbutton.button;
          185                 /* FIXME: are the buttons really always defined as exactly these values? */
          186                 if (button >= 1 && button <= 3) {
          187                         if (xevent.xbutton.time - last_button_press[button] <= DOUBLECLICK_TIME &&
          188                             DISTSQ(press_pos[button].x, press_pos[button].y, xevent.xbutton.x, xevent.xbutton.y) <= DOUBLECLICK_DISTSQ) {
          189                                 if (was_2press[button]) {
          190                                         /* reset so normal press is sent again next time */
          191                                         was_2press[button] = 0;
          192                                         last_button_press[button] = 0;
          193                                         next_event = (ltk_button_event){
          194                                                 .type = LTK_3BUTTONPRESS_EVENT,
          195                                                 .button = get_button(button),
          196                                                 .x = xevent.xbutton.x,
          197                                                 .y = xevent.xbutton.y
          198                                         };
          199                                 } else {
          200                                         was_2press[button] = 1;
          201                                         last_button_press[button] = xevent.xbutton.time;
          202                                         next_event = (ltk_button_event){
          203                                                 .type = LTK_2BUTTONPRESS_EVENT,
          204                                                 .button = get_button(button),
          205                                                 .x = xevent.xbutton.x,
          206                                                 .y = xevent.xbutton.y
          207                                         };
          208                                 }
          209                                 next_event_valid = 1;
          210                         } else {
          211                                 last_button_press[button] = xevent.xbutton.time;
          212                                 was_2press[button] = 0;
          213                         }
          214                         *event = (ltk_event){.button = {
          215                                 .type = LTK_BUTTONPRESS_EVENT,
          216                                 .button = get_button(button),
          217                                 .x = xevent.xbutton.x,
          218                                 .y = xevent.xbutton.y
          219                         }};
          220                         press_pos[button].x = xevent.xbutton.x;
          221                         press_pos[button].y = xevent.xbutton.y;
          222                 } else if (button >= 4 && button <= 7) {
          223                         /* FIXME: compress multiple scroll events into one */
          224                         *event = (ltk_event){.scroll = {
          225                                 .type = LTK_SCROLL_EVENT,
          226                                 .x = xevent.xbutton.x,
          227                                 .y = xevent.xbutton.y,
          228                                 .dx = 0,
          229                                 .dy = 0
          230                         }};
          231                         switch (button) {
          232                         case 4:
          233                                 event->scroll.dy = 1;
          234                                 break;
          235                         case 5:
          236                                 event->scroll.dy = -1;
          237                                 break;
          238                         case 6:
          239                                 event->scroll.dx = -1;
          240                                 break;
          241                         case 7:
          242                                 event->scroll.dx = 1;
          243                                 break;
          244                         }
          245                 } else {
          246                         return 2;
          247                 }
          248                 break;
          249         case ButtonRelease:
          250                 button = xevent.xbutton.button;
          251                 if (button >= 1 && button <= 3) {
          252                         if (xevent.xbutton.time - last_button_release[button] <= DOUBLECLICK_TIME &&
          253                             DISTSQ(release_pos[button].x, release_pos[button].y, xevent.xbutton.x, xevent.xbutton.y) <= DOUBLECLICK_DISTSQ) {
          254                                 if (was_2release[button]) {
          255                                         /* reset so normal release is sent again next time */
          256                                         was_2release[button] = 0;
          257                                         last_button_release[button] = 0;
          258                                         next_event = (ltk_button_event){
          259                                                 .type = LTK_3BUTTONRELEASE_EVENT,
          260                                                 .button = get_button(button),
          261                                                 .x = xevent.xbutton.x,
          262                                                 .y = xevent.xbutton.y
          263                                         };
          264                                 } else {
          265                                         was_2release[button] = 1;
          266                                         last_button_release[button] = xevent.xbutton.time;
          267                                         next_event = (ltk_button_event){
          268                                                 .type = LTK_2BUTTONRELEASE_EVENT,
          269                                                 .button = get_button(button),
          270                                                 .x = xevent.xbutton.x,
          271                                                 .y = xevent.xbutton.y
          272                                         };
          273                                 }
          274                                 next_event_valid = 1;
          275                         } else {
          276                                 last_button_release[button] = xevent.xbutton.time;
          277                                 was_2release[button] = 0;
          278                         }
          279                         *event = (ltk_event){.button = {
          280                                 .type = LTK_BUTTONRELEASE_EVENT,
          281                                 .button = get_button(button),
          282                                 .x = xevent.xbutton.x,
          283                                 .y = xevent.xbutton.y
          284                         }};
          285                         release_pos[button].x = xevent.xbutton.x;
          286                         release_pos[button].y = xevent.xbutton.y;
          287                 } else {
          288                         return 2;
          289                 }
          290                 break;
          291         case MotionNotify:
          292                 /* FIXME: compress motion events */
          293                 *event = (ltk_event){.motion = {
          294                         .type = LTK_MOTION_EVENT,
          295                         .x = xevent.xmotion.x,
          296                         .y = xevent.xmotion.y
          297                 }};
          298                 break;
          299         case KeyPress:
          300                 *event = process_key(renderdata, lang_index, &xevent, LTK_KEYPRESS_EVENT);
          301                 break;
          302         case KeyRelease:
          303                 *event = process_key(renderdata, lang_index, &xevent, LTK_KEYRELEASE_EVENT);
          304                 break;
          305         case ConfigureNotify:
          306                 *event = (ltk_event){.configure = {
          307                         .type = LTK_CONFIGURE_EVENT,
          308                         .x = xevent.xconfigure.x,
          309                         .y = xevent.xconfigure.y,
          310                         .w = xevent.xconfigure.width,
          311                         .h = xevent.xconfigure.height
          312                 }};
          313                 break;
          314         case Expose:
          315                 /* FIXME: ignoring all of these events would make it not
          316                    work anymore if the actual rectangle wasn't ignored
          317                    later anyways */
          318                 if (xevent.xexpose.count == 0) {
          319                         *event = (ltk_event){.expose = {
          320                                 .type = LTK_EXPOSE_EVENT,
          321                                 .x = xevent.xexpose.x,
          322                                 .y = xevent.xexpose.y,
          323                                 .w = xevent.xexpose.width,
          324                                 .h = xevent.xexpose.height
          325                         }};
          326                 } else {
          327                         return 2;
          328                 }
          329                 break;
          330         case ClientMessage:
          331                 if ((Atom)xevent.xclient.data.l[0] == renderdata->wm_delete_msg)
          332                         *event = (ltk_event){.type = LTK_WINDOWCLOSE_EVENT};
          333                 else
          334                         return 2;
          335                 break;
          336         default:
          337                 break;
          338         }
          339         return 0;
          340 }
          341 
          342 int
          343 ltk_next_event(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_index, ltk_event *event) {
          344         int ret = 0;
          345         while ((ret = next_event_base(renderdata, clip, lang_index, event)) == 2) {
          346                 /* NOP */
          347         }
          348         return ret;
          349 }
          350 
          351 /* FIXME: pre-sort array (current sorting is taken from config.c but doesn't make sense here) */
          352 /* FIXME: can some structure of the X keysyms be abused to make this more efficient */
          353 static struct keysym_mapping {
          354         KeySym xkeysym;
          355         ltk_keysym keysym;
          356 } keysym_map[] = {
          357         {XK_BackSpace, LTK_KEY_BACKSPACE},
          358         {XK_Begin, LTK_KEY_BEGIN},
          359         {XK_Break, LTK_KEY_BREAK},
          360         {XK_Cancel, LTK_KEY_CANCEL},
          361         {XK_Clear, LTK_KEY_CLEAR},
          362         {XK_Delete, LTK_KEY_DELETE},
          363         {XK_Down, LTK_KEY_DOWN},
          364         {XK_End, LTK_KEY_END},
          365         {XK_Escape, LTK_KEY_ESCAPE},
          366         {XK_Execute, LTK_KEY_EXECUTE},
          367 
          368         {XK_F1, LTK_KEY_F1},
          369         {XK_F10, LTK_KEY_F10},
          370         {XK_F11, LTK_KEY_F11},
          371         {XK_F12, LTK_KEY_F12},
          372         {XK_F2, LTK_KEY_F2},
          373         {XK_F3, LTK_KEY_F3},
          374         {XK_F4, LTK_KEY_F4},
          375         {XK_F5, LTK_KEY_F5},
          376         {XK_F6, LTK_KEY_F6},
          377         {XK_F7, LTK_KEY_F7},
          378         {XK_F8, LTK_KEY_F8},
          379         {XK_F9, LTK_KEY_F9},
          380 
          381         {XK_Find, LTK_KEY_FIND},
          382         {XK_Help, LTK_KEY_HELP},
          383         {XK_Home, LTK_KEY_HOME},
          384         {XK_Insert, LTK_KEY_INSERT},
          385 
          386         {XK_KP_0, LTK_KEY_KP_0},
          387         {XK_KP_1, LTK_KEY_KP_1},
          388         {XK_KP_2, LTK_KEY_KP_2},
          389         {XK_KP_3, LTK_KEY_KP_3},
          390         {XK_KP_4, LTK_KEY_KP_4},
          391         {XK_KP_5, LTK_KEY_KP_5},
          392         {XK_KP_6, LTK_KEY_KP_6},
          393         {XK_KP_7, LTK_KEY_KP_7},
          394         {XK_KP_8, LTK_KEY_KP_8},
          395         {XK_KP_9, LTK_KEY_KP_9},
          396         {XK_KP_Add, LTK_KEY_KP_ADD},
          397         {XK_KP_Begin, LTK_KEY_KP_BEGIN},
          398         {XK_KP_Decimal, LTK_KEY_KP_DECIMAL},
          399         {XK_KP_Delete, LTK_KEY_KP_DELETE},
          400         {XK_KP_Divide, LTK_KEY_KP_DIVIDE},
          401         {XK_KP_Down, LTK_KEY_KP_DOWN},
          402         {XK_KP_End, LTK_KEY_KP_END},
          403         {XK_KP_Enter, LTK_KEY_KP_ENTER},
          404         {XK_KP_Equal, LTK_KEY_KP_EQUAL},
          405         {XK_KP_Home, LTK_KEY_KP_HOME},
          406         {XK_KP_Insert, LTK_KEY_KP_INSERT},
          407         {XK_KP_Left, LTK_KEY_KP_LEFT},
          408         {XK_KP_Multiply, LTK_KEY_KP_MULTIPLY},
          409         {XK_KP_Next, LTK_KEY_KP_NEXT},
          410         {XK_KP_Page_Down, LTK_KEY_KP_PAGE_DOWN},
          411         {XK_KP_Page_Up, LTK_KEY_KP_PAGE_UP},
          412         {XK_KP_Prior, LTK_KEY_KP_PRIOR},
          413         {XK_KP_Right, LTK_KEY_KP_RIGHT},
          414         {XK_KP_Separator, LTK_KEY_KP_SEPARATOR},
          415         {XK_KP_Space, LTK_KEY_KP_SPACE},
          416         {XK_KP_Subtract, LTK_KEY_KP_SUBTRACT},
          417         {XK_KP_Tab, LTK_KEY_KP_TAB},
          418         {XK_KP_Up, LTK_KEY_KP_UP},
          419 
          420         {XK_Left, LTK_KEY_LEFT},
          421         {XK_Linefeed, LTK_KEY_LINEFEED},
          422         {XK_Menu, LTK_KEY_MENU},
          423         {XK_Mode_switch, LTK_KEY_MODE_SWITCH},
          424         {XK_Next, LTK_KEY_NEXT},
          425         {XK_Num_Lock, LTK_KEY_NUM_LOCK},
          426         {XK_Page_Down, LTK_KEY_PAGE_DOWN},
          427         {XK_Page_Up, LTK_KEY_PAGE_UP},
          428         {XK_Pause, LTK_KEY_PAUSE},
          429         {XK_Print, LTK_KEY_PRINT},
          430         {XK_Prior, LTK_KEY_PRIOR},
          431 
          432         {XK_Redo, LTK_KEY_REDO},
          433         {XK_Return, LTK_KEY_RETURN},
          434         {XK_Right, LTK_KEY_RIGHT},
          435         {XK_script_switch, LTK_KEY_SCRIPT_SWITCH},
          436         {XK_Scroll_Lock, LTK_KEY_SCROLL_LOCK},
          437         {XK_Select, LTK_KEY_SELECT},
          438         {XK_space, LTK_KEY_SPACE},
          439         {XK_Sys_Req, LTK_KEY_SYS_REQ},
          440         {XK_Tab, LTK_KEY_TAB},
          441         {XK_Up, LTK_KEY_UP},
          442         {XK_Undo, LTK_KEY_UNDO},
          443 };
          444 
          445 static int keysym_map_sorted = 0;
          446 static int
          447 keysym_map_search_helper(const void *keyv, const void *entryv) {
          448         KeySym sym = *((KeySym*)keyv);
          449         struct keysym_mapping *m = (struct keysym_mapping *)entryv;
          450         /* FIXME: branchless compare version? */
          451         return (sym < m->xkeysym) ? -1 : (sym > m->xkeysym);
          452 }
          453 static int
          454 keysym_map_sort_helper(const void *entry1v, const void *entry2v) {
          455         struct keysym_mapping *m1 = (struct keysym_mapping *)entry1v;
          456         struct keysym_mapping *m2 = (struct keysym_mapping *)entry2v;
          457         return (m1->xkeysym < m2->xkeysym) ? -1 : (m1->xkeysym > m2->xkeysym);
          458 }
          459 
          460 static ltk_keysym
          461 get_keysym(KeySym sym) {
          462         if (!keysym_map_sorted) {
          463                 qsort(
          464                     keysym_map, LENGTH(keysym_map),
          465                     sizeof(keysym_map[0]), &keysym_map_sort_helper
          466                 );
          467                 keysym_map_sorted = 1;
          468         }
          469         struct keysym_mapping *m = bsearch(
          470             &sym, keysym_map, LENGTH(keysym_map),
          471             sizeof(keysym_map[0]), &keysym_map_search_helper
          472         );
          473         return m ? m->keysym : LTK_KEY_NONE;
          474 }