x11: Modernize and optimize key handling

- Use modern Xkb functions where appropriate and cleanly separate the modern and legacy paths.
- Remove the deprecated XKeycodeToKeysym function in favor of directly querying the keymap on the legacy path.
- Look up virtual modifiers by name on the Xkb path to better handle remapping (equivalent to the modifier handling under Wayland).
- Optimize keymap creation on the Xkb path to cut keymap build times and enable fast group switching  (equivalent to keymap handling on Wayland).
- Enable and handle Xkb events to handle changes to the group, mapping, and modifier states. This is more reliable than using the legacy events (group changes may not arrive if the window lacks pointer focus), and better handles cases where modifiers are latched, locked, or activated externally rather than physically pressed.
This commit is contained in:
Frank Praznik
2025-07-24 15:11:20 -04:00
committed by Sam Lantinga
parent 67e5130441
commit f439e44771
8 changed files with 551 additions and 305 deletions

View File

@@ -390,7 +390,7 @@ macro(CheckX11)
set(SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS 1) set(SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS 1)
endif() endif()
check_symbol_exists(XkbLookupKeySym "X11/Xlib.h;X11/XKBlib.h" SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM) check_include_file("X11/XKBlib.h" SDL_VIDEO_DRIVER_X11_HAS_XKBLIB)
if(SDL_X11_XCURSOR AND HAVE_XCURSOR_H AND XCURSOR_LIB) if(SDL_X11_XCURSOR AND HAVE_XCURSOR_H AND XCURSOR_LIB)
set(HAVE_X11_XCURSOR TRUE) set(HAVE_X11_XCURSOR TRUE)

View File

@@ -422,7 +422,7 @@
#cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR @SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR@ #cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR @SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR@
#cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS @SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS@ #cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS @SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS@
#cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST @SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST@ #cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST @SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST@
#cmakedefine SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM 1 #cmakedefine SDL_VIDEO_DRIVER_X11_HAS_XKBLIB 1
#cmakedefine SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS 1 #cmakedefine SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS 1
#cmakedefine SDL_VIDEO_DRIVER_X11_XCURSOR 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XCURSOR 1
#cmakedefine SDL_VIDEO_DRIVER_X11_XDBE 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XDBE 1

View File

@@ -28,7 +28,7 @@
#include <X11/Xatom.h> #include <X11/Xatom.h>
#include <X11/Xresource.h> #include <X11/Xresource.h>
#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM #ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB
#include <X11/XKBlib.h> #include <X11/XKBlib.h>
#endif #endif

View File

@@ -248,94 +248,202 @@ static void X11_HandleGenericEvent(SDL_VideoDevice *_this, XEvent *xev)
static void X11_UpdateSystemKeyModifiers(SDL_VideoData *viddata) static void X11_UpdateSystemKeyModifiers(SDL_VideoData *viddata)
{ {
Window junk_window; #ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB
int x, y; if (viddata->keyboard.xkb_enabled) {
XkbStateRec xkb_state;
if (X11_XkbGetState(viddata->display, XkbUseCoreKbd, &xkb_state) == Success) {
viddata->keyboard.pressed_modifiers = xkb_state.base_mods;
viddata->keyboard.locked_modifiers = xkb_state.latched_mods | xkb_state.locked_mods;
}
} else
#endif
{
Window junk_window;
int x, y;
unsigned int mod_mask;
X11_XQueryPointer(viddata->display, DefaultRootWindow(viddata->display), &junk_window, &junk_window, &x, &y, &x, &y, &viddata->xkb.xkb_modifiers); X11_XQueryPointer(viddata->display, DefaultRootWindow(viddata->display), &junk_window, &junk_window, &x, &y, &x, &y, &mod_mask);
viddata->keyboard.pressed_modifiers = mod_mask & (ShiftMask | ControlMask | Mod1Mask | Mod3Mask | Mod4Mask | Mod5Mask);
viddata->keyboard.locked_modifiers = mod_mask & (LockMask | viddata->keyboard.numlock_mask | viddata->keyboard.scrolllock_mask);
}
} }
static void X11_ReconcileModifiers(SDL_VideoData *viddata) static void X11_ReconcileModifiers(SDL_VideoData *viddata, bool key_pressed)
{ {
const Uint32 xk_modifiers = viddata->xkb.xkb_modifiers; /* Handle explicit pressed modifier state. This will correct the modifier state
* if common modifier keys were remapped and the modifiers presumed to be set
/* If a modifier was activated by a keypress, it will be tied to the * during a key press event were incorrect, if the modifier was set to the
* specific left/right key that initiated it. Otherwise, the ambiguous * pressed state via means other than pressing the physical key, or if the
* left/right combo is used. * modifier state was set by a keypress before the corresponding key event
* was received.
*/ */
if (xk_modifiers & ShiftMask) { if (key_pressed) {
if (!(viddata->xkb.sdl_modifiers & SDL_KMOD_SHIFT)) { if (viddata->keyboard.pressed_modifiers & ShiftMask) {
viddata->xkb.sdl_modifiers |= SDL_KMOD_SHIFT; if (viddata->keyboard.sdl_physically_pressed_modifiers & SDL_KMOD_SHIFT) {
viddata->keyboard.sdl_pressed_modifiers &= ~SDL_KMOD_SHIFT;
viddata->keyboard.sdl_pressed_modifiers |= (viddata->keyboard.sdl_physically_pressed_modifiers & SDL_KMOD_SHIFT);
}
}
if (viddata->keyboard.pressed_modifiers & ControlMask) {
if (viddata->keyboard.sdl_physically_pressed_modifiers & SDL_KMOD_CTRL) {
viddata->keyboard.sdl_pressed_modifiers &= ~SDL_KMOD_CTRL;
viddata->keyboard.sdl_pressed_modifiers |= (viddata->keyboard.sdl_physically_pressed_modifiers & SDL_KMOD_CTRL);
}
}
if (viddata->keyboard.pressed_modifiers & viddata->keyboard.alt_mask) {
if (viddata->keyboard.sdl_physically_pressed_modifiers & SDL_KMOD_ALT) {
viddata->keyboard.sdl_pressed_modifiers &= ~SDL_KMOD_ALT;
viddata->keyboard.sdl_pressed_modifiers |= (viddata->keyboard.sdl_physically_pressed_modifiers & SDL_KMOD_ALT);
}
}
if (viddata->keyboard.pressed_modifiers & viddata->keyboard.gui_mask) {
if (viddata->keyboard.sdl_physically_pressed_modifiers & SDL_KMOD_GUI) {
viddata->keyboard.sdl_pressed_modifiers &= ~SDL_KMOD_GUI;
viddata->keyboard.sdl_pressed_modifiers |= (viddata->keyboard.sdl_physically_pressed_modifiers & SDL_KMOD_GUI);
}
} }
} else { } else {
viddata->xkb.sdl_modifiers &= ~SDL_KMOD_SHIFT; if (viddata->keyboard.pressed_modifiers & ShiftMask) {
if (!(viddata->keyboard.sdl_pressed_modifiers & SDL_KMOD_SHIFT)) {
viddata->keyboard.sdl_pressed_modifiers |= SDL_KMOD_SHIFT;
}
} else {
viddata->keyboard.sdl_pressed_modifiers &= ~SDL_KMOD_SHIFT;
}
if (viddata->keyboard.pressed_modifiers & ControlMask) {
if (!(viddata->keyboard.sdl_pressed_modifiers & SDL_KMOD_CTRL)) {
viddata->keyboard.sdl_pressed_modifiers |= SDL_KMOD_CTRL;
}
} else {
viddata->keyboard.sdl_pressed_modifiers &= ~SDL_KMOD_CTRL;
}
if (viddata->keyboard.pressed_modifiers & viddata->keyboard.alt_mask) {
if (!(viddata->keyboard.sdl_pressed_modifiers & SDL_KMOD_ALT)) {
viddata->keyboard.sdl_pressed_modifiers |= SDL_KMOD_ALT;
}
} else {
viddata->keyboard.sdl_pressed_modifiers &= ~SDL_KMOD_ALT;
}
if (viddata->keyboard.pressed_modifiers & viddata->keyboard.gui_mask) {
if (!(viddata->keyboard.sdl_pressed_modifiers & SDL_KMOD_GUI)) {
viddata->keyboard.sdl_pressed_modifiers |= SDL_KMOD_GUI;
}
} else {
viddata->keyboard.sdl_pressed_modifiers &= ~SDL_KMOD_GUI;
}
if (viddata->keyboard.pressed_modifiers & viddata->keyboard.level3_mask) {
if (!(viddata->keyboard.sdl_pressed_modifiers & SDL_KMOD_MODE)) {
viddata->keyboard.sdl_pressed_modifiers |= SDL_KMOD_MODE;
}
} else {
viddata->keyboard.sdl_pressed_modifiers &= ~SDL_KMOD_MODE;
}
if (viddata->keyboard.pressed_modifiers & viddata->keyboard.level5_mask) {
if (!(viddata->keyboard.sdl_pressed_modifiers & SDL_KMOD_LEVEL5)) {
viddata->keyboard.sdl_pressed_modifiers |= SDL_KMOD_LEVEL5;
}
} else {
viddata->keyboard.sdl_pressed_modifiers &= ~SDL_KMOD_LEVEL5;
}
} }
if (xk_modifiers & ControlMask) { /* If a latch or lock was activated by a keypress, the latch/lock will
if (!(viddata->xkb.sdl_modifiers & SDL_KMOD_CTRL)) { * be tied to the specific left/right key that initiated it. Otherwise,
viddata->xkb.sdl_modifiers |= SDL_KMOD_CTRL; * the ambiguous left/right combo is used.
*
* The modifier will remain active until the latch/lock is released by
* the system.
*/
if (viddata->keyboard.locked_modifiers & ShiftMask) {
if (viddata->keyboard.sdl_pressed_modifiers & SDL_KMOD_SHIFT) {
viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_SHIFT;
viddata->keyboard.sdl_locked_modifiers |= (viddata->keyboard.sdl_pressed_modifiers & SDL_KMOD_SHIFT);
} else if (!(viddata->keyboard.sdl_locked_modifiers & SDL_KMOD_SHIFT)) {
viddata->keyboard.sdl_locked_modifiers |= SDL_KMOD_SHIFT;
} }
} else { } else {
viddata->xkb.sdl_modifiers &= ~SDL_KMOD_CTRL; viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_SHIFT;
} }
// Mod1 is used for the Alt keys if (viddata->keyboard.locked_modifiers & ControlMask) {
if (xk_modifiers & Mod1Mask) { if (viddata->keyboard.sdl_pressed_modifiers & SDL_KMOD_CTRL) {
if (!(viddata->xkb.sdl_modifiers & SDL_KMOD_ALT)) { viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_CTRL;
viddata->xkb.sdl_modifiers |= SDL_KMOD_ALT; viddata->keyboard.sdl_locked_modifiers |= (viddata->keyboard.sdl_pressed_modifiers & SDL_KMOD_CTRL);
} else if (!(viddata->keyboard.sdl_locked_modifiers & SDL_KMOD_CTRL)) {
viddata->keyboard.sdl_locked_modifiers |= SDL_KMOD_CTRL;
} }
} else { } else {
viddata->xkb.sdl_modifiers &= ~SDL_KMOD_ALT; viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_CTRL;
} }
// Mod4 is used for the Super (aka GUI/Logo) keys. if (viddata->keyboard.locked_modifiers & viddata->keyboard.alt_mask) {
if (xk_modifiers & Mod4Mask) { if (viddata->keyboard.sdl_pressed_modifiers & SDL_KMOD_ALT) {
if (!(viddata->xkb.sdl_modifiers & SDL_KMOD_GUI)) { viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_ALT;
viddata->xkb.sdl_modifiers |= SDL_KMOD_GUI; viddata->keyboard.sdl_locked_modifiers |= (viddata->keyboard.sdl_pressed_modifiers & SDL_KMOD_ALT);
} else if (!(viddata->keyboard.sdl_locked_modifiers & SDL_KMOD_ALT)) {
viddata->keyboard.sdl_locked_modifiers |= SDL_KMOD_ALT;
} }
} else { } else {
viddata->xkb.sdl_modifiers &= ~SDL_KMOD_GUI; viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_ALT;
} }
// Mod3 is typically Level 5 shift. if (viddata->keyboard.locked_modifiers & viddata->keyboard.gui_mask) {
if (xk_modifiers & Mod3Mask) { if (viddata->keyboard.sdl_pressed_modifiers & SDL_KMOD_GUI) {
viddata->xkb.sdl_modifiers |= SDL_KMOD_LEVEL5; viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_GUI;
viddata->keyboard.sdl_locked_modifiers |= (viddata->keyboard.sdl_pressed_modifiers & SDL_KMOD_GUI);
} else if (!(viddata->keyboard.sdl_locked_modifiers & SDL_KMOD_GUI)) {
viddata->keyboard.sdl_locked_modifiers |= SDL_KMOD_GUI;
}
} else { } else {
viddata->xkb.sdl_modifiers &= ~SDL_KMOD_LEVEL5; viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_GUI;
} }
// Mod5 is typically Level 3 shift (aka AltGr). if (viddata->keyboard.locked_modifiers & viddata->keyboard.level3_mask) {
if (xk_modifiers & Mod5Mask) { viddata->keyboard.sdl_locked_modifiers |= SDL_KMOD_MODE;
viddata->xkb.sdl_modifiers |= SDL_KMOD_MODE;
} else { } else {
viddata->xkb.sdl_modifiers &= ~SDL_KMOD_MODE; viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_MODE;
} }
if (xk_modifiers & LockMask) { if (viddata->keyboard.locked_modifiers & viddata->keyboard.level5_mask) {
viddata->xkb.sdl_modifiers |= SDL_KMOD_CAPS; viddata->keyboard.sdl_locked_modifiers |= SDL_KMOD_LEVEL5;
} else { } else {
viddata->xkb.sdl_modifiers &= ~SDL_KMOD_CAPS; viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_LEVEL5;
} }
if (xk_modifiers & viddata->xkb.numlock_mask) { // Capslock, Numlock, and Scrolllock can only be locked, not pressed.
viddata->xkb.sdl_modifiers |= SDL_KMOD_NUM; if (viddata->keyboard.locked_modifiers & LockMask) {
viddata->keyboard.sdl_locked_modifiers |= SDL_KMOD_CAPS;
} else { } else {
viddata->xkb.sdl_modifiers &= ~SDL_KMOD_NUM; viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_CAPS;
} }
if (xk_modifiers & viddata->xkb.scrolllock_mask) { if (viddata->keyboard.locked_modifiers & viddata->keyboard.numlock_mask) {
viddata->xkb.sdl_modifiers |= SDL_KMOD_SCROLL; viddata->keyboard.sdl_locked_modifiers |= SDL_KMOD_NUM;
} else { } else {
viddata->xkb.sdl_modifiers &= ~SDL_KMOD_SCROLL; viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_NUM;
} }
SDL_SetModState(viddata->xkb.sdl_modifiers); if (viddata->keyboard.locked_modifiers & viddata->keyboard.scrolllock_mask) {
viddata->keyboard.sdl_locked_modifiers |= SDL_KMOD_SCROLL;
} else {
viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_SCROLL;
}
SDL_SetModState(viddata->keyboard.sdl_pressed_modifiers | viddata->keyboard.sdl_locked_modifiers);
} }
static void X11_HandleModifierKeys(SDL_VideoData *viddata, SDL_Scancode scancode, bool pressed, bool allow_reconciliation) static void X11_HandleModifierKeys(SDL_VideoData *viddata, SDL_Scancode scancode, bool pressed)
{ {
const SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false); const SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false);
SDL_Keymod mod = SDL_KMOD_NONE; SDL_Keymod mod = SDL_KMOD_NONE;
bool reconcile = false;
/* SDL clients expect modifier state to be activated at the same time as the /* SDL clients expect modifier state to be activated at the same time as the
* source keypress, so we set pressed modifier state with the usual modifier * source keypress, so we set pressed modifier state with the usual modifier
@@ -378,48 +486,46 @@ static void X11_HandleModifierKeys(SDL_VideoData *viddata, SDL_Scancode scancode
case SDLK_NUMLOCKCLEAR: case SDLK_NUMLOCKCLEAR:
case SDLK_SCROLLLOCK: case SDLK_SCROLLLOCK:
{ {
/* For locking modifier keys, query the lock state directly, or we may have to wait until the next // XKB provides the latched/locked state explicitly.
* key press event to know if a lock was actually activated from the key event. if (viddata->keyboard.xkb_enabled) {
*/ /* For locking modifier keys, query the lock state directly, or we may have to wait until the next
unsigned int cur_mask = viddata->xkb.xkb_modifiers; * key press event to know if a lock was actually activated from the key event.
X11_UpdateSystemKeyModifiers(viddata); */
unsigned int cur_mask = viddata->keyboard.locked_modifiers;
X11_UpdateSystemKeyModifiers(viddata);
if (viddata->xkb.xkb_modifiers & LockMask) { if (viddata->keyboard.locked_modifiers & LockMask) {
cur_mask |= LockMask; cur_mask |= LockMask;
} else { } else {
cur_mask &= ~LockMask; cur_mask &= ~LockMask;
} }
if (viddata->xkb.xkb_modifiers & viddata->xkb.numlock_mask) { if (viddata->keyboard.locked_modifiers & viddata->keyboard.numlock_mask) {
cur_mask |= viddata->xkb.numlock_mask; cur_mask |= viddata->keyboard.numlock_mask;
} else { } else {
cur_mask &= ~viddata->xkb.numlock_mask; cur_mask &= ~viddata->keyboard.numlock_mask;
} }
if (viddata->xkb.xkb_modifiers & viddata->xkb.scrolllock_mask) { if (viddata->keyboard.locked_modifiers & viddata->keyboard.scrolllock_mask) {
cur_mask |= viddata->xkb.scrolllock_mask; cur_mask |= viddata->keyboard.scrolllock_mask;
} else { } else {
cur_mask &= ~viddata->xkb.scrolllock_mask; cur_mask &= ~viddata->keyboard.scrolllock_mask;
} }
viddata->xkb.xkb_modifiers = cur_mask; viddata->keyboard.locked_modifiers = cur_mask;
} SDL_FALLTHROUGH; }
} break;
default: default:
reconcile = true; return;
break;
} }
if (pressed) { if (pressed) {
viddata->xkb.sdl_modifiers |= mod; viddata->keyboard.sdl_pressed_modifiers |= mod;
viddata->keyboard.sdl_physically_pressed_modifiers |= mod;
} else { } else {
viddata->xkb.sdl_modifiers &= ~mod; viddata->keyboard.sdl_pressed_modifiers &= ~mod;
viddata->keyboard.sdl_physically_pressed_modifiers &= ~mod;
} }
if (allow_reconciliation) { X11_ReconcileModifiers(viddata, true);
if (reconcile) {
X11_ReconcileModifiers(viddata);
} else {
SDL_SetModState(viddata->xkb.sdl_modifiers);
}
}
} }
void X11_ReconcileKeyboardState(SDL_VideoDevice *_this) void X11_ReconcileKeyboardState(SDL_VideoDevice *_this)
@@ -427,18 +533,25 @@ void X11_ReconcileKeyboardState(SDL_VideoDevice *_this)
SDL_VideoData *videodata = _this->internal; SDL_VideoData *videodata = _this->internal;
Display *display = videodata->display; Display *display = videodata->display;
char keys[32]; char keys[32];
int keycode;
const bool *keyboardState; // Rebuild the modifier state in case it changed while focus was lost.
X11_UpdateSystemKeyModifiers(videodata);
X11_ReconcileModifiers(videodata, false);
// Keep caps, num, and scroll, but clear the others until we have updated key state.
videodata->keyboard.sdl_pressed_modifiers = 0;
videodata->keyboard.sdl_physically_pressed_modifiers = 0;
videodata->keyboard.sdl_locked_modifiers &= SDL_KMOD_CAPS | SDL_KMOD_NUM | SDL_KMOD_SCROLL;
videodata->keyboard.pressed_modifiers = 0;
videodata->keyboard.locked_modifiers &= LockMask | videodata->keyboard.numlock_mask| videodata->keyboard.scrolllock_mask;
X11_XQueryKeymap(display, keys); X11_XQueryKeymap(display, keys);
keyboardState = SDL_GetKeyboardState(0); for (Uint32 keycode = 0; keycode < SDL_arraysize(videodata->keyboard.key_layout); ++keycode) {
for (keycode = 0; keycode < SDL_arraysize(videodata->key_layout); ++keycode) { const SDL_Scancode scancode = videodata->keyboard.key_layout[keycode];
SDL_Scancode scancode = videodata->key_layout[keycode]; const bool x11KeyPressed = (keys[keycode / 8] & (1 << (keycode % 8))) != 0;
bool x11KeyPressed = (keys[keycode / 8] & (1 << (keycode % 8))) != 0;
bool sdlKeyPressed = keyboardState[scancode];
if (x11KeyPressed && !sdlKeyPressed) { if (x11KeyPressed) {
// Only update modifier state for keys that are pressed in another application // Only update modifier state for keys that are pressed in another application
switch (SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false)) { switch (SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false)) {
case SDLK_LCTRL: case SDLK_LCTRL:
@@ -451,20 +564,18 @@ void X11_ReconcileKeyboardState(SDL_VideoDevice *_this)
case SDLK_RGUI: case SDLK_RGUI:
case SDLK_MODE: case SDLK_MODE:
case SDLK_LEVEL5_SHIFT: case SDLK_LEVEL5_SHIFT:
X11_HandleModifierKeys(videodata, scancode, true, false); X11_HandleModifierKeys(videodata, scancode, true);
SDL_SendKeyboardKeyIgnoreModifiers(0, SDL_GLOBAL_KEYBOARD_ID, keycode, scancode, true); SDL_SendKeyboardKeyIgnoreModifiers(0, SDL_GLOBAL_KEYBOARD_ID, keycode, scancode, true);
break; break;
default: default:
break; break;
} }
} else if (!x11KeyPressed && sdlKeyPressed) {
X11_HandleModifierKeys(videodata, scancode, false, false);
SDL_SendKeyboardKeyIgnoreModifiers(0, SDL_GLOBAL_KEYBOARD_ID, keycode, scancode, false);
} }
} }
// Update the latched/locked state for modifiers other than Caps, Num, and Scroll lock.
X11_UpdateSystemKeyModifiers(videodata); X11_UpdateSystemKeyModifiers(videodata);
X11_ReconcileModifiers(videodata); X11_ReconcileModifiers(videodata, false);
} }
static void X11_DispatchFocusIn(SDL_VideoDevice *_this, SDL_WindowData *data) static void X11_DispatchFocusIn(SDL_VideoDevice *_this, SDL_WindowData *data)
@@ -933,7 +1044,7 @@ void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_
Status status = 0; Status status = 0;
bool handled_by_ime = false; bool handled_by_ime = false;
bool pressed = (xevent->type == KeyPress); bool pressed = (xevent->type == KeyPress);
SDL_Scancode scancode = videodata->key_layout[keycode]; SDL_Scancode scancode = videodata->keyboard.key_layout[keycode];
Uint64 timestamp = X11_GetEventTimestamp(xevent->xkey.time); Uint64 timestamp = X11_GetEventTimestamp(xevent->xkey.time);
#ifdef DEBUG_XEVENTS #ifdef DEBUG_XEVENTS
@@ -951,7 +1062,12 @@ void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_
#endif // DEBUG SCANCODES #endif // DEBUG SCANCODES
text[0] = '\0'; text[0] = '\0';
videodata->xkb.xkb_modifiers = xevent->xkey.state;
// XKB updates the modifiers explicitly via a state event.
if (!videodata->keyboard.xkb_enabled) {
videodata->keyboard.pressed_modifiers = xevent->xkey.state & (ShiftMask | ControlMask | Mod1Mask | Mod3Mask | Mod4Mask | Mod5Mask);
videodata->keyboard.locked_modifiers = xevent->xkey.state & (LockMask | videodata->keyboard.numlock_mask | videodata->keyboard.scrolllock_mask);
}
if (SDL_TextInputActive(windowdata->window)) { if (SDL_TextInputActive(windowdata->window)) {
// filter events catches XIM events and sends them to the correct handler // filter events catches XIM events and sends them to the correct handler
@@ -979,7 +1095,7 @@ void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_
if (!handled_by_ime) { if (!handled_by_ime) {
if (pressed) { if (pressed) {
X11_HandleModifierKeys(videodata, scancode, true, true); X11_HandleModifierKeys(videodata, scancode, true);
SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, true); SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, true);
if (*text && !(SDL_GetModState() & (SDL_KMOD_CTRL | SDL_KMOD_ALT))) { if (*text && !(SDL_GetModState() & (SDL_KMOD_CTRL | SDL_KMOD_ALT))) {
@@ -993,7 +1109,7 @@ void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_
return; return;
} }
X11_HandleModifierKeys(videodata, scancode, false, true); X11_HandleModifierKeys(videodata, scancode, false);
SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, false); SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, false);
} }
} }
@@ -1225,37 +1341,78 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
if (!data) { if (!data) {
// The window for KeymapNotify, etc events is 0 // The window for KeymapNotify, etc events is 0
#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB
if (videodata->keyboard.xkb_enabled && xevent->type == videodata->keyboard.xkb.event) {
XkbEvent *xkbevent = (XkbEvent *)xevent;
switch (xkbevent->any.xkb_type) {
case XkbStateNotify:
{
#ifdef DEBUG_XEVENTS
SDL_Log("window 0x%lx: XkbStateNotify!", xevent->xany.window);
#endif
if ((xkbevent->state.changed & XkbGroupStateMask) && xkbevent->state.group != videodata->keyboard.xkb.current_group) {
videodata->keyboard.xkb.current_group = xkbevent->state.group;
SDL_SetKeymap(videodata->keyboard.xkb.keymaps[videodata->keyboard.xkb.current_group], true);
}
if (xkbevent->state.changed & XkbModifierStateMask) {
videodata->keyboard.pressed_modifiers = xkbevent->state.base_mods;
videodata->keyboard.locked_modifiers = xkbevent->state.latched_mods | xkbevent->state.locked_mods;
X11_ReconcileModifiers(videodata, false);
}
} break;
case XkbMapNotify:
#ifdef DEBUG_XEVENTS
SDL_Log("window 0x%lx: XkbMapNotify!", xevent->xany.window);
SDL_FALLTHROUGH;
#endif
case XkbNewKeyboardNotify:
{
#ifdef DEBUG_XEVENTS
if (xkbevent->any.xkb_type == XkbNewKeyboardNotify) {
SDL_Log("window 0x%lx: XkbNewKeyboardNotify!", xevent->xany.window);
}
#endif
X11_XkbRefreshKeyboardMapping(&xkbevent->map);
// Don't redundantly rebuild the keymap if this is a duplicate event.
if (xkbevent->any.serial != videodata->keyboard.xkb.last_map_serial) {
videodata->keyboard.xkb.last_map_serial = xkbevent->any.serial;
X11_UpdateKeymap(_this, true);
}
} break;
default:
break;
}
} else
#endif
if (xevent->type == KeymapNotify) { if (xevent->type == KeymapNotify) {
#ifdef DEBUG_XEVENTS #ifdef DEBUG_XEVENTS
SDL_Log("window 0x%lx: KeymapNotify!", xevent->xany.window); SDL_Log("window 0x%lx: KeymapNotify!", xevent->xany.window);
#endif #endif
if (SDL_GetKeyboardFocus() != NULL) { if (!videodata->keyboard.xkb_enabled) {
#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM if (SDL_GetKeyboardFocus() != NULL) {
if (videodata->xkb.desc_ptr) { X11_UpdateKeymap(_this, true);
XkbStateRec state;
if (X11_XkbGetState(videodata->display, XkbUseCoreKbd, &state) == Success) {
if (state.group != videodata->xkb.current_group) {
// Only rebuild the keymap if the layout has changed.
videodata->xkb.current_group = state.group;
X11_UpdateKeymap(_this, true);
}
}
} }
#endif
X11_ReconcileKeyboardState(_this);
} }
X11_ReconcileKeyboardState(_this);
} else if (xevent->type == MappingNotify) { } else if (xevent->type == MappingNotify) {
// Has the keyboard layout changed? if (!videodata->keyboard.xkb_enabled) {
const int request = xevent->xmapping.request; // Has the keyboard layout changed?
const int request = xevent->xmapping.request;
#ifdef DEBUG_XEVENTS #ifdef DEBUG_XEVENTS
SDL_Log("window 0x%lx: MappingNotify!", xevent->xany.window); SDL_Log("window 0x%lx: MappingNotify!", xevent->xany.window);
#endif #endif
if ((request == MappingKeyboard) || (request == MappingModifier)) { if ((request == MappingKeyboard) || (request == MappingModifier)) {
X11_XRefreshKeyboardMapping(&xevent->xmapping); X11_XRefreshKeyboardMapping(&xevent->xmapping);
} }
X11_UpdateKeymap(_this, true); X11_UpdateKeymap(_this, true);
}
} else if (xevent->type == PropertyNotify && videodata && videodata->windowlist) { } else if (xevent->type == PropertyNotify && videodata && videodata->windowlist) {
char *name_of_atom = X11_XGetAtomName(display, xevent->xproperty.atom); char *name_of_atom = X11_XGetAtomName(display, xevent->xproperty.atom);

View File

@@ -28,7 +28,10 @@
#include "../../events/SDL_scancode_tables_c.h" #include "../../events/SDL_scancode_tables_c.h"
#include <X11/keysym.h> #include <X11/keysym.h>
#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB
#include <X11/XKBlib.h> #include <X11/XKBlib.h>
#endif
#include "../../events/imKStoUCS.h" #include "../../events/imKStoUCS.h"
#include "../../events/SDL_keysym_to_scancode_c.h" #include "../../events/SDL_keysym_to_scancode_c.h"
@@ -70,6 +73,28 @@ static bool X11_ScancodeIsRemappable(SDL_Scancode scancode)
} }
} }
static KeySym X11_KeyCodeToSym(SDL_VideoDevice *_this, KeyCode keycode, unsigned int group, unsigned int level)
{
SDL_VideoData *data = _this->internal;
KeySym keysym;
#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB
if (data->keyboard.xkb_enabled) {
keysym = X11_XkbKeycodeToKeysym(data->display, keycode, group, level);
} else
#endif
{
// TODO: Handle groups on the legacy path.
if (keycode >= data->keyboard.core.min_keycode && keycode <= data->keyboard.core.max_keycode) {
keysym = data->keyboard.core.keysym_map[(keycode - data->keyboard.core.min_keycode) * data->keyboard.core.keysyms_per_key];
} else {
keysym = NoSymbol;
}
}
return keysym;
}
// This function only correctly maps letters and numbers for keyboards in US QWERTY layout // This function only correctly maps letters and numbers for keyboards in US QWERTY layout
static SDL_Scancode X11_KeyCodeToSDLScancode(SDL_VideoDevice *_this, KeyCode keycode) static SDL_Scancode X11_KeyCodeToSDLScancode(SDL_VideoDevice *_this, KeyCode keycode)
{ {
@@ -82,48 +107,6 @@ static SDL_Scancode X11_KeyCodeToSDLScancode(SDL_VideoDevice *_this, KeyCode key
return SDL_GetScancodeFromKeySym(keysym, keycode); return SDL_GetScancodeFromKeySym(keysym, keycode);
} }
KeySym X11_KeyCodeToSym(SDL_VideoDevice *_this, KeyCode keycode, unsigned char group, unsigned int mod_mask)
{
SDL_VideoData *data = _this->internal;
KeySym keysym;
unsigned int mods_ret[16];
SDL_zero(mods_ret);
#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM
if (data->xkb.desc_ptr) {
int num_groups = XkbKeyNumGroups(data->xkb.desc_ptr, keycode);
unsigned char info = XkbKeyGroupInfo(data->xkb.desc_ptr, keycode);
if (num_groups && group >= num_groups) {
int action = XkbOutOfRangeGroupAction(info);
if (action == XkbRedirectIntoRange) {
group = XkbOutOfRangeGroupNumber(info);
if (group >= num_groups) {
group = 0;
}
} else if (action == XkbClampIntoRange) {
group = num_groups - 1;
} else {
group %= num_groups;
}
}
if (X11_XkbLookupKeySym(data->display, keycode, XkbBuildCoreState(mod_mask, group), mods_ret, &keysym) == NoSymbol) {
keysym = NoSymbol;
}
} else
#endif
{
// TODO: Handle groups and modifiers on the legacy path.
keysym = X11_XKeycodeToKeysym(data->display, keycode, 0);
}
return keysym;
}
bool X11_InitKeyboard(SDL_VideoDevice *_this) bool X11_InitKeyboard(SDL_VideoDevice *_this)
{ {
SDL_VideoData *data = _this->internal; SDL_VideoData *data = _this->internal;
@@ -146,21 +129,33 @@ bool X11_InitKeyboard(SDL_VideoDevice *_this)
int best_distance; int best_distance;
int best_index; int best_index;
int distance; int distance;
Bool xkb_repeat = 0;
#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM #ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB
{ int xkb_major = XkbMajorVersion;
int xkb_major = XkbMajorVersion; int xkb_minor = XkbMinorVersion;
int xkb_minor = XkbMinorVersion;
if (X11_XkbQueryExtension(data->display, NULL, &data->xkb.event, NULL, &xkb_major, &xkb_minor)) { if (X11_XkbQueryExtension(data->display, NULL, &data->keyboard.xkb.event, NULL, &xkb_major, &xkb_minor)) {
data->xkb.desc_ptr = X11_XkbGetMap(data->display, XkbAllClientInfoMask, XkbUseCoreKbd); Bool xkb_repeat = 0;
} data->keyboard.xkb_enabled = true;
data->keyboard.xkb.desc_ptr = X11_XkbGetMap(data->display, XkbAllClientInfoMask, XkbUseCoreKbd);
// This will remove KeyRelease events for held keys // This will remove KeyRelease events for held keys.
X11_XkbSetDetectableAutoRepeat(data->display, True, &xkb_repeat); X11_XkbSetDetectableAutoRepeat(data->display, True, &xkb_repeat);
}
// Enable the key mapping and state events.
X11_XkbSelectEvents(data->display, XkbUseCoreKbd,
XkbNewKeyboardNotifyMask | XkbMapNotifyMask | XkbStateNotifyMask,
XkbNewKeyboardNotifyMask | XkbMapNotifyMask | XkbStateNotifyMask);
X11_XkbSelectEventDetails(data->display, XkbUseCoreKbd, XkbStateNotify, XkbGroupStateMask | XkbModifierStateMask, XkbGroupStateMask | XkbModifierStateMask);
} else
#endif #endif
{
// If XKB isn't available, initialize the legacy path.
X11_XDisplayKeycodes(data->display, &data->keyboard.core.min_keycode, &data->keyboard.core.max_keycode);
data->keyboard.core.keysym_map = X11_XGetKeyboardMapping(data->display, data->keyboard.core.min_keycode,
data->keyboard.core.max_keycode - data->keyboard.core.min_keycode,
&data->keyboard.core.keysyms_per_key);
}
// Open a connection to the X input manager // Open a connection to the X input manager
#ifdef X_HAVE_UTF8_STRING #ifdef X_HAVE_UTF8_STRING
@@ -242,10 +237,10 @@ bool X11_InitKeyboard(SDL_VideoDevice *_this)
SDL_Log("Using scancode set %d, min_keycode = %d, max_keycode = %d, table_size = %d", best_index, min_keycode, max_keycode, table_size); SDL_Log("Using scancode set %d, min_keycode = %d, max_keycode = %d, table_size = %d", best_index, min_keycode, max_keycode, table_size);
#endif #endif
// This should never happen, but just in case... // This should never happen, but just in case...
if (table_size > (SDL_arraysize(data->key_layout) - min_keycode)) { if (table_size > (SDL_arraysize(data->keyboard.key_layout) - min_keycode)) {
table_size = (SDL_arraysize(data->key_layout) - min_keycode); table_size = (SDL_arraysize(data->keyboard.key_layout) - min_keycode);
} }
SDL_memcpy(&data->key_layout[min_keycode], table, sizeof(SDL_Scancode) * table_size); SDL_memcpy(&data->keyboard.key_layout[min_keycode], table, sizeof(SDL_Scancode) * table_size);
/* Scancodes represent physical locations on the keyboard, unaffected by keyboard mapping. /* Scancodes represent physical locations on the keyboard, unaffected by keyboard mapping.
However, there are a number of extended scancodes that have no standard location, so use However, there are a number of extended scancodes that have no standard location, so use
@@ -261,7 +256,7 @@ bool X11_InitKeyboard(SDL_VideoDevice *_this)
(unsigned int)sym, sym == NoSymbol ? "NoSymbol" : X11_XKeysymToString(sym)); (unsigned int)sym, sym == NoSymbol ? "NoSymbol" : X11_XKeysymToString(sym));
} }
#endif #endif
if (scancode == data->key_layout[i]) { if (scancode == data->keyboard.key_layout[i]) {
continue; continue;
} }
if ((SDL_GetKeymapKeycode(NULL, scancode, SDL_KMOD_NONE) & (SDLK_SCANCODE_MASK | SDLK_EXTENDED_MASK)) && X11_ScancodeIsRemappable(scancode)) { if ((SDL_GetKeymapKeycode(NULL, scancode, SDL_KMOD_NONE) & (SDLK_SCANCODE_MASK | SDLK_EXTENDED_MASK)) && X11_ScancodeIsRemappable(scancode)) {
@@ -269,7 +264,7 @@ bool X11_InitKeyboard(SDL_VideoDevice *_this)
#ifdef DEBUG_KEYBOARD #ifdef DEBUG_KEYBOARD
SDL_Log("Changing scancode, was %d (%s), now %d (%s)", data->key_layout[i], SDL_GetScancodeName(data->key_layout[i]), scancode, SDL_GetScancodeName(scancode)); SDL_Log("Changing scancode, was %d (%s), now %d (%s)", data->key_layout[i], SDL_GetScancodeName(data->key_layout[i]), scancode, SDL_GetScancodeName(scancode));
#endif #endif
data->key_layout[i] = scancode; data->keyboard.key_layout[i] = scancode;
} }
} }
} else { } else {
@@ -293,7 +288,7 @@ bool X11_InitKeyboard(SDL_VideoDevice *_this)
SDL_Log("scancode = %d (%s)", scancode, SDL_GetScancodeName(scancode)); SDL_Log("scancode = %d (%s)", scancode, SDL_GetScancodeName(scancode));
} }
#endif #endif
data->key_layout[i] = scancode; data->keyboard.key_layout[i] = scancode;
} }
} }
@@ -306,149 +301,235 @@ bool X11_InitKeyboard(SDL_VideoDevice *_this)
return true; return true;
} }
static unsigned X11_GetNumLockModifierMask(SDL_VideoDevice *_this) #ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB
static unsigned int X11_GetXkbVirtualModifierMask(SDL_VideoDevice *_this, const char *vmod_name)
{
SDL_VideoData *videodata = _this->internal;
unsigned int mod_mask = 0;
if (videodata->keyboard.xkb_enabled) {
Atom vmod = X11_XInternAtom(videodata->display, vmod_name, True);
if (vmod != None) {
for (int i = 0; i < XkbNumVirtualMods; ++i) {
if (vmod == videodata->keyboard.xkb.desc_ptr->names->vmods[i]) {
mod_mask = videodata->keyboard.xkb.desc_ptr->server->vmods[i];
break;
}
}
}
}
return mod_mask;
}
#endif
static unsigned X11_GetXModifierMask(SDL_VideoDevice *_this, SDL_Scancode scancode)
{ {
SDL_VideoData *videodata = _this->internal; SDL_VideoData *videodata = _this->internal;
Display *display = videodata->display; Display *display = videodata->display;
unsigned num_mask = 0; unsigned int mod_mask = 0;
int i, j;
XModifierKeymap *xmods;
unsigned n;
xmods = X11_XGetModifierMapping(display); XModifierKeymap *xmods = X11_XGetModifierMapping(display);
n = xmods->max_keypermod; unsigned int n = xmods->max_keypermod;
for (i = 3; i < 8; i++) { for (int i = 3; i < 8; i++) {
for (j = 0; j < n; j++) { for (int j = 0; j < n; j++) {
KeyCode kc = xmods->modifiermap[i * n + j]; const KeyCode kc = xmods->modifiermap[i * n + j];
if (videodata->key_layout[kc] == SDL_SCANCODE_NUMLOCKCLEAR) { if (videodata->keyboard.key_layout[kc] == scancode) {
num_mask = 1 << i; mod_mask = 1 << i;
break; break;
} }
} }
} }
X11_XFreeModifiermap(xmods); X11_XFreeModifiermap(xmods);
return num_mask; return mod_mask;
} }
static unsigned X11_GetScrollLockModifierMask(SDL_VideoDevice *_this) static void X11_AddKeymapEntry(SDL_Keymap *keymap, Uint32 xkeycode, KeySym xkeysym, SDL_Scancode sdl_scancode, SDL_Keymod sdl_mod_mask)
{ {
SDL_VideoData *videodata = _this->internal; SDL_Keycode keycode = SDL_GetKeyCodeFromKeySym(xkeysym, xkeycode, sdl_mod_mask);
Display *display = videodata->display;
unsigned num_mask = 0;
int i, j;
XModifierKeymap *xmods;
unsigned n;
xmods = X11_XGetModifierMapping(display); if (!keycode) {
n = xmods->max_keypermod; switch (sdl_scancode) {
for (i = 3; i < 8; i++) { case SDL_SCANCODE_RETURN:
for (j = 0; j < n; j++) { keycode = SDLK_RETURN;
KeyCode kc = xmods->modifiermap[i * n + j]; break;
if (videodata->key_layout[kc] == SDL_SCANCODE_SCROLLLOCK) { case SDL_SCANCODE_ESCAPE:
num_mask = 1 << i; keycode = SDLK_ESCAPE;
break; break;
} case SDL_SCANCODE_BACKSPACE:
keycode = SDLK_BACKSPACE;
break;
case SDL_SCANCODE_DELETE:
keycode = SDLK_DELETE;
break;
default:
keycode = SDL_SCANCODE_TO_KEYCODE(sdl_scancode);
break;
} }
} }
X11_XFreeModifiermap(xmods);
return num_mask; SDL_SetKeymapEntry(keymap, sdl_scancode, sdl_mod_mask, keycode);
} }
void X11_UpdateKeymap(SDL_VideoDevice *_this, bool send_event) void X11_UpdateKeymap(SDL_VideoDevice *_this, bool send_event)
{ {
struct Keymod_masks
{
SDL_Keymod sdl_mask;
unsigned int xkb_mask;
} const keymod_masks[] = {
{ SDL_KMOD_NONE, 0 },
{ SDL_KMOD_SHIFT, ShiftMask },
{ SDL_KMOD_CAPS, LockMask },
{ SDL_KMOD_SHIFT | SDL_KMOD_CAPS, ShiftMask | LockMask },
{ SDL_KMOD_MODE, Mod5Mask },
{ SDL_KMOD_MODE | SDL_KMOD_SHIFT, Mod5Mask | ShiftMask },
{ SDL_KMOD_MODE | SDL_KMOD_CAPS, Mod5Mask | LockMask },
{ SDL_KMOD_MODE | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, Mod5Mask | ShiftMask | LockMask },
{ SDL_KMOD_LEVEL5, Mod3Mask },
{ SDL_KMOD_LEVEL5 | SDL_KMOD_SHIFT, Mod3Mask | ShiftMask },
{ SDL_KMOD_LEVEL5 | SDL_KMOD_CAPS, Mod3Mask | LockMask },
{ SDL_KMOD_LEVEL5 | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, Mod3Mask | ShiftMask | LockMask },
{ SDL_KMOD_LEVEL5 | SDL_KMOD_MODE, Mod5Mask | Mod3Mask },
{ SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_SHIFT, Mod3Mask | Mod5Mask | ShiftMask },
{ SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_CAPS, Mod3Mask | Mod5Mask | LockMask },
{ SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, Mod3Mask | Mod5Mask | ShiftMask | LockMask }
};
SDL_VideoData *data = _this->internal; SDL_VideoData *data = _this->internal;
SDL_Scancode scancode;
SDL_Keymap *keymap = SDL_CreateKeymap(true);
#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM #ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB
if (data->xkb.desc_ptr) { if (data->keyboard.xkb_enabled) {
XkbStateRec state; XkbStateRec state;
X11_XkbGetUpdatedMap(data->display, XkbAllClientInfoMask, data->xkb.desc_ptr);
SDL_SetKeymap(NULL, false);
for (unsigned int i = 0; i < XkbNumKbdGroups; ++i) {
SDL_DestroyKeymap(data->keyboard.xkb.keymaps[i]);
data->keyboard.xkb.keymaps[i] = SDL_CreateKeymap(false);
}
X11_XkbGetNames(data->display, XkbVirtualModNamesMask, data->keyboard.xkb.desc_ptr);
X11_XkbGetUpdatedMap(data->display, XkbAllClientInfoMask | XkbVirtualModsMask, data->keyboard.xkb.desc_ptr);
if (X11_XkbGetState(data->display, XkbUseCoreKbd, &state) == Success) { if (X11_XkbGetState(data->display, XkbUseCoreKbd, &state) == Success) {
data->xkb.current_group = state.group; data->keyboard.xkb.current_group = state.group;
} }
}
#endif
for (int m = 0; m < SDL_arraysize(keymod_masks); ++m) { data->keyboard.alt_mask = X11_GetXkbVirtualModifierMask(_this, "Alt");
for (int i = 0; i < SDL_arraysize(data->key_layout); ++i) { if (!data->keyboard.alt_mask) {
// Make sure this is a valid scancode data->keyboard.alt_mask = X11_GetXkbVirtualModifierMask(_this, "Meta");
scancode = data->key_layout[i]; }
data->keyboard.gui_mask = X11_GetXkbVirtualModifierMask(_this, "Super");
data->keyboard.level3_mask = X11_GetXkbVirtualModifierMask(_this, "LevelThree");
data->keyboard.level5_mask = X11_GetXkbVirtualModifierMask(_this, "LevelFive");
data->keyboard.numlock_mask = X11_GetXkbVirtualModifierMask(_this, "NumLock");
data->keyboard.scrolllock_mask = X11_GetXkbVirtualModifierMask(_this, "ScrollLock");
const Uint32 valid_mod_mask = ShiftMask | LockMask | data->keyboard.alt_mask | data->keyboard.level3_mask | data->keyboard.level5_mask;
for (Uint32 xkeycode = data->keyboard.xkb.desc_ptr->min_key_code; xkeycode < data->keyboard.xkb.desc_ptr->max_key_code; ++xkeycode) {
const SDL_Scancode scancode = data->keyboard.key_layout[xkeycode];
if (scancode == SDL_SCANCODE_UNKNOWN) { if (scancode == SDL_SCANCODE_UNKNOWN) {
continue; continue;
} }
const KeySym keysym = X11_KeyCodeToSym(_this, i, data->xkb.current_group, keymod_masks[m].xkb_mask); for (Uint32 group = 0; group < XkbNumKbdGroups; ++group) {
SDL_Keymap *keymap = data->keyboard.xkb.keymaps[group];
if (keysym != NoSymbol) { Uint32 effective_group = group;
SDL_Keycode keycode = SDL_GetKeyCodeFromKeySym(keysym, i, keymod_masks[m].sdl_mask); const unsigned char max_key_group = XkbKeyNumGroups(data->keyboard.xkb.desc_ptr, xkeycode);
const unsigned char key_group_info = XkbKeyGroupInfo(data->keyboard.xkb.desc_ptr, xkeycode);
if (!keycode) { if (max_key_group && effective_group >= max_key_group) {
switch (scancode) { const unsigned char action = XkbOutOfRangeGroupAction(key_group_info);
case SDL_SCANCODE_RETURN:
keycode = SDLK_RETURN; switch (action) {
break;
case SDL_SCANCODE_ESCAPE:
keycode = SDLK_ESCAPE;
break;
case SDL_SCANCODE_BACKSPACE:
keycode = SDLK_BACKSPACE;
break;
case SDL_SCANCODE_DELETE:
keycode = SDLK_DELETE;
break;
default: default:
keycode = SDL_SCANCODE_TO_KEYCODE(scancode); effective_group %= max_key_group;
break;
case XkbClampIntoRange:
effective_group = max_key_group - 1;
break;
case XkbRedirectIntoRange:
effective_group = XkbOutOfRangeGroupNumber(key_group_info);
if (effective_group >= max_key_group) {
effective_group = 0;
}
break; break;
} }
} }
SDL_SetKeymapEntry(keymap, scancode, keymod_masks[m].sdl_mask, keycode); XkbKeyTypePtr key_type = XkbKeyKeyType(data->keyboard.xkb.desc_ptr, xkeycode, effective_group);
for (Uint32 level = 0; level < key_type->num_levels; ++level) {
const KeySym keysym = X11_KeyCodeToSym(_this, xkeycode, effective_group, level);
if (keysym != NoSymbol) {
bool key_added = false;
for (int map_idx = 0; map_idx < key_type->map_count; ++map_idx) {
if (key_type->map[map_idx].active && key_type->map[map_idx].level == level) {
const unsigned int xkb_mod_mask = key_type->map[map_idx].mods.mask;
if ((xkb_mod_mask | valid_mod_mask) == valid_mod_mask) {
const SDL_Keymod sdl_mod_mask = (xkb_mod_mask & ShiftMask ? SDL_KMOD_SHIFT : 0) |
(xkb_mod_mask & LockMask ? SDL_KMOD_CAPS : 0) |
(xkb_mod_mask & data->keyboard.alt_mask ? SDL_KMOD_ALT : 0) |
(xkb_mod_mask & data->keyboard.level3_mask ? SDL_KMOD_MODE : 0) |
(xkb_mod_mask & data->keyboard.level5_mask ? SDL_KMOD_LEVEL5 : 0);
X11_AddKeymapEntry(keymap, xkeycode, keysym, scancode, sdl_mod_mask);
key_added = true;
}
}
}
// Add the unmodified key for level 0.
if (!level && !key_added) {
X11_AddKeymapEntry(keymap, xkeycode, keysym, scancode, 0);
}
}
}
} }
} }
}
data->xkb.numlock_mask = X11_GetNumLockModifierMask(_this); SDL_SetKeymap(data->keyboard.xkb.keymaps[data->keyboard.xkb.current_group], send_event);
data->xkb.scrolllock_mask = X11_GetScrollLockModifierMask(_this); } else
SDL_SetKeymap(keymap, send_event); #endif
{
SDL_Keymap *keymap = SDL_CreateKeymap(true);
if (send_event) {
if (data->keyboard.core.keysym_map) {
X11_XFree(data->keyboard.core.keysym_map);
}
X11_XDisplayKeycodes(data->display, &data->keyboard.core.min_keycode, &data->keyboard.core.max_keycode);
data->keyboard.core.keysym_map = X11_XGetKeyboardMapping(data->display, data->keyboard.core.min_keycode,
data->keyboard.core.max_keycode - data->keyboard.core.min_keycode,
&data->keyboard.core.keysyms_per_key);
}
for (Uint32 xkeycode = data->keyboard.core.min_keycode; xkeycode <= data->keyboard.core.max_keycode; ++xkeycode) {
const SDL_Scancode scancode = data->keyboard.key_layout[xkeycode];
if (scancode == SDL_SCANCODE_UNKNOWN) {
continue;
}
const KeySym keysym = X11_KeyCodeToSym(_this, xkeycode, 0, 0);
if (keysym != NoSymbol) {
X11_AddKeymapEntry(keymap, xkeycode, keysym, scancode, 0);
}
}
data->keyboard.alt_mask = Mod1Mask; // Alt or Meta
data->keyboard.gui_mask = Mod4Mask; // Super
data->keyboard.level3_mask = Mod5Mask; // Note: Not a typo, Mod5 = level 3 shift, and Mod3 = level 5 shift.
data->keyboard.level5_mask = Mod3Mask;
data->keyboard.numlock_mask = X11_GetXModifierMask(_this, SDL_SCANCODE_NUMLOCKCLEAR);
data->keyboard.scrolllock_mask = X11_GetXModifierMask(_this, SDL_SCANCODE_SCROLLLOCK);
SDL_SetKeymap(keymap, send_event);
}
} }
void X11_QuitKeyboard(SDL_VideoDevice *_this) void X11_QuitKeyboard(SDL_VideoDevice *_this)
{ {
SDL_VideoData *data = _this->internal; SDL_VideoData *data = _this->internal;
#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM #ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB
if (data->xkb.desc_ptr) { if (data->keyboard.xkb_enabled) {
X11_XkbFreeKeyboard(data->xkb.desc_ptr, 0, True); for (int i = 0; i < XkbNumKbdGroups; ++i) {
data->xkb.desc_ptr = NULL; SDL_DestroyKeymap(data->keyboard.xkb.keymaps[i]);
} data->keyboard.xkb.keymaps[i] = NULL;
}
if (data->keyboard.xkb_enabled) {
X11_XkbFreeKeyboard(data->keyboard.xkb.desc_ptr, 0, True);
data->keyboard.xkb.desc_ptr = NULL;
}
} else
#endif #endif
if (data->keyboard.core.keysym_map) {
X11_XFree(data->keyboard.core.keysym_map);
data->keyboard.core.keysym_map = NULL;
}
} }
void X11_ClearComposition(SDL_WindowData *data) void X11_ClearComposition(SDL_WindowData *data)

View File

@@ -35,6 +35,5 @@ extern bool X11_HasScreenKeyboardSupport(SDL_VideoDevice *_this);
extern void X11_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props); extern void X11_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
extern void X11_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window); extern void X11_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window);
extern bool X11_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window); extern bool X11_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window);
extern KeySym X11_KeyCodeToSym(SDL_VideoDevice *_this, KeyCode, unsigned char group, unsigned int mod_mask);
#endif // SDL_x11keyboard_h_ #endif // SDL_x11keyboard_h_

View File

@@ -71,6 +71,7 @@ SDL_X11_SYM(int,XFreePixmap,(Display* a,Pixmap b))
SDL_X11_SYM(void,XFreeStringList,(char** a)) SDL_X11_SYM(void,XFreeStringList,(char** a))
SDL_X11_SYM(char*,XGetAtomName,(Display *a,Atom b)) SDL_X11_SYM(char*,XGetAtomName,(Display *a,Atom b))
SDL_X11_SYM(int,XGetInputFocus,(Display *a,Window *b,int *c)) SDL_X11_SYM(int,XGetInputFocus,(Display *a,Window *b,int *c))
SDL_X11_SYM(KeySym*,XGetKeyboardMapping,(Display *a, KeyCode b, int c, int *d))
SDL_X11_SYM(int,XGetErrorDatabaseText,(Display* a,_Xconst char* b,_Xconst char* c,_Xconst char* d,char* e,int f)) SDL_X11_SYM(int,XGetErrorDatabaseText,(Display* a,_Xconst char* b,_Xconst char* c,_Xconst char* d,char* e,int f))
SDL_X11_SYM(XModifierKeymap*,XGetModifierMapping,(Display* a)) SDL_X11_SYM(XModifierKeymap*,XGetModifierMapping,(Display* a))
SDL_X11_SYM(int,XGetPointerControl,(Display* a,int* b,int* c,int* d)) SDL_X11_SYM(int,XGetPointerControl,(Display* a,int* b,int* c,int* d))
@@ -196,35 +197,21 @@ SDL_X11_SYM(Bool,XGetEventData,(Display* a,XGenericEventCookie* b))
SDL_X11_SYM(void,XFreeEventData,(Display* a,XGenericEventCookie* b)) SDL_X11_SYM(void,XFreeEventData,(Display* a,XGenericEventCookie* b))
#endif #endif
#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM #ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB
SDL_X11_SYM(Bool,XkbQueryExtension,(Display* a,int * b,int * c,int * d,int * e, int *f)) SDL_X11_SYM(Bool,XkbQueryExtension,(Display* a,int * b,int * c,int * d,int * e, int *f))
#if NeedWidePrototypes SDL_X11_SYM(KeySym,XkbKeycodeToKeysym,(Display* a, KeyCode b, unsigned int c, unsigned int d))
SDL_X11_SYM(Bool,XkbLookupKeySym,(Display* a, unsigned int b, unsigned int c, unsigned int* d, KeySym* e)) SDL_X11_SYM(Bool,XkbSelectEvents,(Display* a, unsigned int b, unsigned int c, unsigned long d))
#else SDL_X11_SYM(Bool,XkbSelectEventDetails,(Display* a, unsigned int b, unsigned int c, unsigned long d, unsigned long e))
SDL_X11_SYM(Bool,XkbLookupKeySym,(Display* a, KeyCode b, unsigned int c, unsigned int* d, KeySym* e)) SDL_X11_SYM(Status,XkbGetNames,(Display *a, unsigned int b, XkbDescPtr c))
#endif
SDL_X11_SYM(Status,XkbGetState,(Display* a,unsigned int b,XkbStatePtr c)) SDL_X11_SYM(Status,XkbGetState,(Display* a,unsigned int b,XkbStatePtr c))
SDL_X11_SYM(Status,XkbGetUpdatedMap,(Display* a,unsigned int b,XkbDescPtr c)) SDL_X11_SYM(Status,XkbGetUpdatedMap,(Display* a,unsigned int b,XkbDescPtr c))
SDL_X11_SYM(XkbDescPtr,XkbGetMap,(Display* a,unsigned int b,unsigned int c)) SDL_X11_SYM(XkbDescPtr,XkbGetMap,(Display* a,unsigned int b,unsigned int c))
SDL_X11_SYM(void,XkbFreeClientMap,(XkbDescPtr a,unsigned int b, Bool c)) SDL_X11_SYM(void,XkbFreeClientMap,(XkbDescPtr a,unsigned int b, Bool c))
SDL_X11_SYM(void,XkbFreeKeyboard,(XkbDescPtr a,unsigned int b, Bool c)) SDL_X11_SYM(void,XkbFreeKeyboard,(XkbDescPtr a,unsigned int b, Bool c))
SDL_X11_SYM(Status,XkbRefreshKeyboardMapping,(XkbMapNotifyEvent *a))
SDL_X11_SYM(Bool,XkbSetDetectableAutoRepeat,(Display* a, Bool b, Bool* c)) SDL_X11_SYM(Bool,XkbSetDetectableAutoRepeat,(Display* a, Bool b, Bool* c))
#endif #endif
// XKeycodeToKeysym is a deprecated function
#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
#if NeedWidePrototypes
SDL_X11_SYM(KeySym,XKeycodeToKeysym,(Display* a,unsigned int b,int c))
#else
SDL_X11_SYM(KeySym,XKeycodeToKeysym,(Display* a,KeyCode b,int c))
#endif
#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA
#pragma GCC diagnostic pop
#endif
#ifdef X_HAVE_UTF8_STRING #ifdef X_HAVE_UTF8_STRING
SDL_X11_MODULE(UTF8) SDL_X11_MODULE(UTF8)
SDL_X11_SYM(int,Xutf8TextListToTextProperty,(Display* a,char** b,int c,XICCEncodingStyle d,XTextProperty* e)) SDL_X11_SYM(int,Xutf8TextListToTextProperty,(Display* a,char** b,int c,XICCEncodingStyle d,XTextProperty* e))

View File

@@ -24,6 +24,7 @@
#define SDL_x11video_h_ #define SDL_x11video_h_
#include "../SDL_sysvideo.h" #include "../SDL_sysvideo.h"
#include "../../events/SDL_keymap_c.h"
#include "../../core/linux/SDL_dbus.h" #include "../../core/linux/SDL_dbus.h"
#include "../../core/linux/SDL_ime.h" #include "../../core/linux/SDL_ime.h"
@@ -125,7 +126,6 @@ struct SDL_VideoData
Atom pen_atom_wacom_tool_type; Atom pen_atom_wacom_tool_type;
} atoms; } atoms;
SDL_Scancode key_layout[256];
bool selection_waiting; bool selection_waiting;
bool selection_incr_waiting; bool selection_incr_waiting;
@@ -144,21 +144,43 @@ struct SDL_VideoData
int xrandr_event_base; int xrandr_event_base;
struct struct
{ {
#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM bool xkb_enabled;
XkbDescPtr desc_ptr; SDL_Scancode key_layout[256];
union
{
#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB
struct
{
XkbDescPtr desc_ptr;
SDL_Keymap *keymaps[XkbNumKbdGroups];
unsigned long last_map_serial;
int event;
Uint32 current_group;
} xkb; // Modern XKB keyboard handling
#endif #endif
int event; struct
unsigned int current_group; {
unsigned int xkb_modifiers; KeySym *keysym_map;
int keysyms_per_key;
SDL_Keymod sdl_modifiers; int min_keycode;
int max_keycode;
} core; // Legacy core keyboard handling
};
Uint32 pressed_modifiers;
Uint32 locked_modifiers;
SDL_Keymod sdl_pressed_modifiers;
SDL_Keymod sdl_physically_pressed_modifiers;
SDL_Keymod sdl_locked_modifiers;
// Virtual modifiers looked up by name.
Uint32 alt_mask;
Uint32 gui_mask;
Uint32 level3_mask;
Uint32 level5_mask;
Uint32 numlock_mask; Uint32 numlock_mask;
Uint32 scrolllock_mask; Uint32 scrolllock_mask;
} xkb; } keyboard;
KeyCode filter_code;
Time filter_time;
#ifdef SDL_VIDEO_VULKAN #ifdef SDL_VIDEO_VULKAN
// Vulkan variables only valid if _this->vulkan_config.loader_handle is not NULL // Vulkan variables only valid if _this->vulkan_config.loader_handle is not NULL