Browse Source

x11: use XInput2 for lower level access to keyboard events

Sam Lantinga 1 year ago
parent
commit
658f3cdcf1

+ 107 - 94
src/video/x11/SDL_x11events.c

@@ -832,6 +832,99 @@ SDL_WindowData *X11_FindWindow(SDL_VideoDevice *_this, Window window)
     return NULL;
 }
 
+void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_KeyboardID keyboardID, XEvent *xevent)
+{
+    SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata;
+    Display *display = videodata->display;
+    KeyCode keycode = xevent->xkey.keycode;
+    KeySym keysym = NoSymbol;
+    char text[SDL_TEXTINPUTEVENT_TEXT_SIZE];
+    Status status = 0;
+    SDL_bool handled_by_ime = SDL_FALSE;
+
+    /* Save the original keycode for dead keys, which are filtered out by
+       the XFilterEvent() call below.
+    */
+    int orig_event_type = xevent->type;
+    KeyCode orig_keycode = xevent->xkey.keycode;
+
+    /* filter events catches XIM events and sends them to the correct handler */
+    if (X11_XFilterEvent(xevent, None)) {
+#if 0
+        printf("Filtered event type = %d display = %d window = %d\n",
+               xevent->type, xevent->xany.display, xevent->xany.window);
+#endif
+        /* Make sure dead key press/release events are sent */
+        /* But only if we're using one of the DBus IMEs, otherwise
+           some XIM IMEs will generate duplicate events */
+#if defined(HAVE_IBUS_IBUS_H) || defined(HAVE_FCITX)
+        SDL_Scancode scancode = videodata->key_layout[orig_keycode];
+        videodata->filter_code = orig_keycode;
+        videodata->filter_time = xevent->xkey.time;
+
+        if (orig_event_type == KeyPress) {
+            SDL_SendKeyboardKey(0, keyboardID, SDL_PRESSED, scancode);
+        } else {
+            SDL_SendKeyboardKey(0, keyboardID, SDL_RELEASED, scancode);
+        }
+#endif
+        return;
+    }
+
+#ifdef DEBUG_XEVENTS
+    printf("window %p: %s (X11 keycode = 0x%X)\n", data, (xevent->type == KeyPress ? "KeyPress" : "KeyRelease"), xevent->xkey.keycode);
+#endif
+#ifdef DEBUG_SCANCODES
+    if (videodata->key_layout[keycode] == SDL_SCANCODE_UNKNOWN && keycode) {
+        int min_keycode, max_keycode;
+        X11_XDisplayKeycodes(display, &min_keycode, &max_keycode);
+        keysym = X11_KeyCodeToSym(_this, keycode, xevent->xkey.state >> 13);
+        SDL_Log("The key you just pressed is not recognized by SDL. To help get this fixed, please report this to the SDL forums/mailing list <https://discourse.libsdl.org/> X11 KeyCode %d (%d), X11 KeySym 0x%lX (%s).\n",
+                keycode, keycode - min_keycode, keysym,
+                X11_XKeysymToString(keysym));
+    }
+#endif /* DEBUG SCANCODES */
+
+    SDL_zeroa(text);
+#ifdef X_HAVE_UTF8_STRING
+    if (windowdata->ic && xevent->type == KeyPress) {
+        X11_Xutf8LookupString(windowdata->ic, &xevent->xkey, text, sizeof(text),
+                              &keysym, &status);
+    } else {
+        XLookupStringAsUTF8(&xevent->xkey, text, sizeof(text), &keysym, NULL);
+    }
+#else
+    XLookupStringAsUTF8(&xevent->xkey, text, sizeof(text), &keysym, NULL);
+#endif
+
+#ifdef SDL_USE_IME
+    if (SDL_TextInputActive()) {
+        handled_by_ime = SDL_IME_ProcessKeyEvent(keysym, keycode, (xevent->type == KeyPress ? SDL_PRESSED : SDL_RELEASED));
+    }
+#endif
+    if (!handled_by_ime) {
+        if (xevent->type == KeyPress) {
+            /* Don't send the key if it looks like a duplicate of a filtered key sent by an IME */
+            if (xevent->xkey.keycode != videodata->filter_code || xevent->xkey.time != videodata->filter_time) {
+                SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, SDL_PRESSED, videodata->key_layout[keycode]);
+            }
+            if (*text) {
+                SDL_SendKeyboardText(text);
+            }
+        } else {
+            if (X11_KeyRepeat(display, xevent)) {
+                /* We're about to get a repeated key down, ignore the key up */
+                return;
+            }
+            SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, SDL_RELEASED, videodata->key_layout[keycode]);
+        }
+    }
+
+    if (xevent->type == KeyPress) {
+        X11_UpdateUserTime(windowdata, xevent->xkey.time);
+    }
+}
+
 void X11_HandleButtonPress(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_MouseID mouseID, int button, const float x, const float y, const unsigned long time)
 {
     SDL_Window *window = windowdata->window;
@@ -923,47 +1016,22 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
     SDL_VideoData *videodata = _this->driverdata;
     Display *display;
     SDL_WindowData *data;
-    int orig_event_type;
-    KeyCode orig_keycode;
     XClientMessageEvent m;
     int i;
 
     SDL_assert(videodata != NULL);
     display = videodata->display;
 
-    /* Save the original keycode for dead keys, which are filtered out by
-       the XFilterEvent() call below.
-    */
-    orig_event_type = xevent->type;
-    if (orig_event_type == KeyPress || orig_event_type == KeyRelease) {
-        orig_keycode = xevent->xkey.keycode;
-    } else {
-        orig_keycode = 0;
-    }
-
     /* filter events catches XIM events and sends them to the correct handler */
-    if (X11_XFilterEvent(xevent, None) == True) {
+    /* Key press/release events are filtered in X11_HandleKeyEvent() */
+    if (xevent->type != KeyPress && xevent->type != KeyRelease) {
+        if (X11_XFilterEvent(xevent, None)) {
 #if 0
-        printf("Filtered event type = %d display = %d window = %d\n",
-               xevent->type, xevent->xany.display, xevent->xany.window);
-#endif
-        /* Make sure dead key press/release events are sent */
-        /* But only if we're using one of the DBus IMEs, otherwise
-           some XIM IMEs will generate duplicate events */
-        if (orig_keycode) {
-#if defined(HAVE_IBUS_IBUS_H) || defined(HAVE_FCITX)
-            SDL_Scancode scancode = videodata->key_layout[orig_keycode];
-            videodata->filter_code = orig_keycode;
-            videodata->filter_time = xevent->xkey.time;
-
-            if (orig_event_type == KeyPress) {
-                SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, SDL_PRESSED, scancode);
-            } else {
-                SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, SDL_RELEASED, scancode);
-            }
+            printf("Filtered event type = %d display = %d window = %d\n",
+                   xevent->type, xevent->xany.display, xevent->xany.window);
 #endif
+            return;
         }
-        return;
     }
 
 #ifdef SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS
@@ -1217,69 +1285,6 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
 #endif /* SDL_VIDEO_DRIVER_X11_XFIXES */
     } break;
 
-        /* Key press/release? */
-    case KeyPress:
-    case KeyRelease:
-    {
-        KeyCode keycode = xevent->xkey.keycode;
-        KeySym keysym = NoSymbol;
-        char text[SDL_TEXTINPUTEVENT_TEXT_SIZE];
-        Status status = 0;
-        SDL_bool handled_by_ime = SDL_FALSE;
-
-#ifdef DEBUG_XEVENTS
-        printf("window %p: %s (X11 keycode = 0x%X)\n", data, (xevent->type == KeyPress ? "KeyPress" : "KeyRelease"), xevent->xkey.keycode);
-#endif
-#ifdef DEBUG_SCANCODES
-        if (videodata->key_layout[keycode] == SDL_SCANCODE_UNKNOWN && keycode) {
-            int min_keycode, max_keycode;
-            X11_XDisplayKeycodes(display, &min_keycode, &max_keycode);
-            keysym = X11_KeyCodeToSym(_this, keycode, xevent->xkey.state >> 13);
-            SDL_Log("The key you just pressed is not recognized by SDL. To help get this fixed, please report this to the SDL forums/mailing list <https://discourse.libsdl.org/> X11 KeyCode %d (%d), X11 KeySym 0x%lX (%s).\n",
-                    keycode, keycode - min_keycode, keysym,
-                    X11_XKeysymToString(keysym));
-        }
-#endif /* DEBUG SCANCODES */
-
-        SDL_zeroa(text);
-#ifdef X_HAVE_UTF8_STRING
-        if (data->ic && xevent->type == KeyPress) {
-            X11_Xutf8LookupString(data->ic, &xevent->xkey, text, sizeof(text),
-                                  &keysym, &status);
-        } else {
-            XLookupStringAsUTF8(&xevent->xkey, text, sizeof(text), &keysym, NULL);
-        }
-#else
-        XLookupStringAsUTF8(&xevent->xkey, text, sizeof(text), &keysym, NULL);
-#endif
-
-#ifdef SDL_USE_IME
-        if (SDL_TextInputActive()) {
-            handled_by_ime = SDL_IME_ProcessKeyEvent(keysym, keycode, (xevent->type == KeyPress ? SDL_PRESSED : SDL_RELEASED));
-        }
-#endif
-        if (!handled_by_ime) {
-            if (xevent->type == KeyPress) {
-                /* Don't send the key if it looks like a duplicate of a filtered key sent by an IME */
-                if (xevent->xkey.keycode != videodata->filter_code || xevent->xkey.time != videodata->filter_time) {
-                    SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, SDL_PRESSED, videodata->key_layout[keycode]);
-                }
-                if (*text) {
-                    SDL_SendKeyboardText(text);
-                }
-            } else {
-                if (X11_KeyRepeat(display, xevent)) {
-                    /* We're about to get a repeated key down, ignore the key up */
-                    break;
-                }
-                SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, SDL_RELEASED, videodata->key_layout[keycode]);
-            }
-        }
-
-        if (xevent->type == KeyPress) {
-            X11_UpdateUserTime(data, xevent->xkey.time);
-        }
-    } break;
 
         /* Have we been iconified? */
     case UnmapNotify:
@@ -1511,11 +1516,19 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
     } break;
 
     /* Use XInput2 instead of the xevents API if possible, for:
+       - KeyPress
+       - KeyRelease
        - MotionNotify
        - ButtonPress
        - ButtonRelease
        XInput2 has more precise information, e.g., to distinguish different input devices. */
 #ifndef SDL_VIDEO_DRIVER_X11_XINPUT2
+    case KeyPress:
+    case KeyRelease:
+    {
+        X11_HandleKeyEvent(_this, data, SDL_GLOBAL_KEYBOARD_ID, xevent);
+    } break;
+
     case MotionNotify:
     {
         SDL_Mouse *mouse = SDL_GetMouse();

+ 3 - 2
src/video/x11/SDL_x11events.h

@@ -29,8 +29,9 @@ extern void X11_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window);
 extern int X11_SuspendScreenSaver(SDL_VideoDevice *_this);
 extern void X11_ReconcileKeyboardState(SDL_VideoDevice *_this);
 extern void X11_GetBorderValues(SDL_WindowData *data);
-extern void X11_HandleButtonPress(SDL_VideoDevice *_this, SDL_WindowData *wdata, SDL_MouseID mouseID, int button, const float x, const float y, const unsigned long time);
-extern void X11_HandleButtonRelease(SDL_VideoDevice *_this, SDL_WindowData *wdata, SDL_MouseID mouseID, int button);
+extern void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_KeyboardID keyboardID, XEvent *xevent);
+extern void X11_HandleButtonPress(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_MouseID mouseID, int button, const float x, const float y, const unsigned long time);
+extern void X11_HandleButtonRelease(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_MouseID mouseID, int button);
 extern SDL_WindowData *X11_FindWindow(SDL_VideoDevice *_this, Window window);
 extern SDL_bool X11_ProcessHitTest(SDL_VideoDevice *_this, SDL_WindowData *data, const float x, const float y, SDL_bool force_new_result);
 extern SDL_bool X11_TriggerHitTestAction(SDL_VideoDevice *_this, SDL_WindowData *data, const float x, const float y);

+ 5 - 4
src/video/x11/SDL_x11window.c

@@ -794,16 +794,17 @@ int X11_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesI
     X11_Xinput2SelectTouch(_this, window);
 
     {
+        unsigned int x11_keyboard_events = KeyPressMask | KeyReleaseMask;
         unsigned int x11_pointer_events = ButtonPressMask | ButtonReleaseMask | PointerMotionMask;
-        if (X11_Xinput2SelectMouse(_this, window)) {
-            /* If XInput2 can handle pointer events, we don't track them here */
+        if (X11_Xinput2SelectMouseAndKeyboard(_this, window)) {
+            /* If XInput2 can handle pointer and keyboard events, we don't track them here */
+            x11_keyboard_events = 0;
             x11_pointer_events = 0;
         }
 
         X11_XSelectInput(display, w,
                          (FocusChangeMask | EnterWindowMask | LeaveWindowMask | ExposureMask |
-                          x11_pointer_events |
-                          KeyPressMask | KeyReleaseMask |
+                          x11_keyboard_events | x11_pointer_events |
                           PropertyChangeMask | StructureNotifyMask |
                           KeymapStateMask | fevent));
     }

+ 38 - 1
src/video/x11/SDL_x11xinput2.c

@@ -423,6 +423,41 @@ int X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
         X11_InitPen(_this);
     } break;
 
+    case XI_KeyPress:
+    case XI_KeyRelease:
+    {
+        const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data;
+        SDL_WindowData *windowdata = X11_FindWindow(_this, xev->event);
+        XEvent xevent;
+
+        if (xev->deviceid != xev->sourceid) {
+            /* Discard events from "Master" devices to avoid duplicates. */
+            return 1;
+        }
+
+        if (cookie->evtype == XI_KeyPress) {
+            xevent.type = KeyPress;
+        } else {
+            xevent.type = KeyRelease;
+        }
+        xevent.xkey.serial = xev->serial;
+        xevent.xkey.send_event = xev->send_event;
+        xevent.xkey.display = xev->display;
+        xevent.xkey.window = xev->event;
+        xevent.xkey.root = xev->root;
+        xevent.xkey.subwindow = xev->child;
+        xevent.xkey.time = xev->time;
+        xevent.xkey.x = xev->event_x;
+        xevent.xkey.y = xev->event_y;
+        xevent.xkey.x_root = xev->root_x;
+        xevent.xkey.y_root = xev->root_y;
+        xevent.xkey.state = xev->mods.effective;
+        xevent.xkey.keycode = xev->detail;
+        xevent.xkey.same_screen = 1;
+
+        X11_HandleKeyEvent(_this, windowdata, (SDL_KeyboardID)xev->sourceid, &xevent);
+    } break;
+
     case XI_RawButtonPress:
     case XI_RawButtonRelease:
 #ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
@@ -608,7 +643,7 @@ int X11_Xinput2IsInitialized(void)
 #endif
 }
 
-SDL_bool X11_Xinput2SelectMouse(SDL_VideoDevice *_this, SDL_Window *window)
+SDL_bool X11_Xinput2SelectMouseAndKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
 {
 #ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
     const SDL_VideoData *data = (SDL_VideoData *)_this->driverdata;
@@ -620,6 +655,8 @@ SDL_bool X11_Xinput2SelectMouse(SDL_VideoDevice *_this, SDL_Window *window)
     eventmask.mask = mask;
     eventmask.deviceid = XIAllDevices;
 
+    XISetMask(mask, XI_KeyPress);
+    XISetMask(mask, XI_KeyRelease);
     XISetMask(mask, XI_ButtonPress);
     XISetMask(mask, XI_ButtonRelease);
     XISetMask(mask, XI_Motion);

+ 1 - 1
src/video/x11/SDL_x11xinput2.h

@@ -38,6 +38,6 @@ extern int X11_Xinput2IsMultitouchSupported(void);
 extern void X11_Xinput2SelectTouch(SDL_VideoDevice *_this, SDL_Window *window);
 extern void X11_Xinput2GrabTouch(SDL_VideoDevice *_this, SDL_Window *window);
 extern void X11_Xinput2UngrabTouch(SDL_VideoDevice *_this, SDL_Window *window);
-extern SDL_bool X11_Xinput2SelectMouse(SDL_VideoDevice *_this, SDL_Window *window);
+extern SDL_bool X11_Xinput2SelectMouseAndKeyboard(SDL_VideoDevice *_this, SDL_Window *window);
 
 #endif /* SDL_x11xinput2_h_ */