Skip to content

Commit

Permalink
x11: use XInput2 for lower level access to keyboard events
Browse files Browse the repository at this point in the history
  • Loading branch information
slouken committed Mar 25, 2024
1 parent 012fc1e commit 658f3cd
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 102 deletions.
201 changes: 107 additions & 94 deletions src/video/x11/SDL_x11events.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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();
Expand Down
5 changes: 3 additions & 2 deletions src/video/x11/SDL_x11events.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
9 changes: 5 additions & 4 deletions src/video/x11/SDL_x11window.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
39 changes: 38 additions & 1 deletion src/video/x11/SDL_x11xinput2.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/video/x11/SDL_x11xinput2.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_ */

0 comments on commit 658f3cd

Please sign in to comment.