diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake index 8eda0dfe56..865e28f6d5 100644 --- a/cmake/sdlchecks.cmake +++ b/cmake/sdlchecks.cmake @@ -390,7 +390,7 @@ macro(CheckX11) set(SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS 1) 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) set(HAVE_X11_XCURSOR TRUE) diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake index 03d75e3864..8965e6db48 100644 --- a/include/build_config/SDL_build_config.h.cmake +++ b/include/build_config/SDL_build_config.h.cmake @@ -422,7 +422,7 @@ #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_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_XCURSOR 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XDBE 1 diff --git a/src/video/x11/SDL_x11dyn.h b/src/video/x11/SDL_x11dyn.h index b5bfbf5a09..23b829c2a9 100644 --- a/src/video/x11/SDL_x11dyn.h +++ b/src/video/x11/SDL_x11dyn.h @@ -28,7 +28,7 @@ #include #include -#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM +#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB #include #endif diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index 110da587a6..67128c85b5 100644 --- a/src/video/x11/SDL_x11events.c +++ b/src/video/x11/SDL_x11events.c @@ -248,94 +248,202 @@ static void X11_HandleGenericEvent(SDL_VideoDevice *_this, XEvent *xev) static void X11_UpdateSystemKeyModifiers(SDL_VideoData *viddata) { - Window junk_window; - int x, y; +#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB + 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; - - /* If a modifier was activated by a keypress, it will be tied to the - * specific left/right key that initiated it. Otherwise, the ambiguous - * left/right combo is used. + /* Handle explicit pressed modifier state. This will correct the modifier state + * if common modifier keys were remapped and the modifiers presumed to be set + * during a key press event were incorrect, if the modifier was set to the + * pressed state via means other than pressing the physical key, or if the + * modifier state was set by a keypress before the corresponding key event + * was received. */ - if (xk_modifiers & ShiftMask) { - if (!(viddata->xkb.sdl_modifiers & SDL_KMOD_SHIFT)) { - viddata->xkb.sdl_modifiers |= SDL_KMOD_SHIFT; + if (key_pressed) { + if (viddata->keyboard.pressed_modifiers & ShiftMask) { + 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 { - 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 (!(viddata->xkb.sdl_modifiers & SDL_KMOD_CTRL)) { - viddata->xkb.sdl_modifiers |= SDL_KMOD_CTRL; + /* If a latch or lock was activated by a keypress, the latch/lock will + * be tied to the specific left/right key that initiated it. Otherwise, + * 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 { - viddata->xkb.sdl_modifiers &= ~SDL_KMOD_CTRL; + viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_SHIFT; } - // Mod1 is used for the Alt keys - if (xk_modifiers & Mod1Mask) { - if (!(viddata->xkb.sdl_modifiers & SDL_KMOD_ALT)) { - viddata->xkb.sdl_modifiers |= SDL_KMOD_ALT; + if (viddata->keyboard.locked_modifiers & ControlMask) { + if (viddata->keyboard.sdl_pressed_modifiers & SDL_KMOD_CTRL) { + viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_CTRL; + 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 { - 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 (xk_modifiers & Mod4Mask) { - if (!(viddata->xkb.sdl_modifiers & SDL_KMOD_GUI)) { - viddata->xkb.sdl_modifiers |= SDL_KMOD_GUI; + if (viddata->keyboard.locked_modifiers & viddata->keyboard.alt_mask) { + if (viddata->keyboard.sdl_pressed_modifiers & SDL_KMOD_ALT) { + viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_ALT; + 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 { - viddata->xkb.sdl_modifiers &= ~SDL_KMOD_GUI; + viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_ALT; } - // Mod3 is typically Level 5 shift. - if (xk_modifiers & Mod3Mask) { - viddata->xkb.sdl_modifiers |= SDL_KMOD_LEVEL5; + if (viddata->keyboard.locked_modifiers & viddata->keyboard.gui_mask) { + if (viddata->keyboard.sdl_pressed_modifiers & SDL_KMOD_GUI) { + 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 { - viddata->xkb.sdl_modifiers &= ~SDL_KMOD_LEVEL5; + viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_GUI; } - // Mod5 is typically Level 3 shift (aka AltGr). - if (xk_modifiers & Mod5Mask) { - viddata->xkb.sdl_modifiers |= SDL_KMOD_MODE; + if (viddata->keyboard.locked_modifiers & viddata->keyboard.level3_mask) { + viddata->keyboard.sdl_locked_modifiers |= SDL_KMOD_MODE; } else { - viddata->xkb.sdl_modifiers &= ~SDL_KMOD_MODE; + viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_MODE; } - if (xk_modifiers & LockMask) { - viddata->xkb.sdl_modifiers |= SDL_KMOD_CAPS; + if (viddata->keyboard.locked_modifiers & viddata->keyboard.level5_mask) { + viddata->keyboard.sdl_locked_modifiers |= SDL_KMOD_LEVEL5; } else { - viddata->xkb.sdl_modifiers &= ~SDL_KMOD_CAPS; + viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_LEVEL5; } - if (xk_modifiers & viddata->xkb.numlock_mask) { - viddata->xkb.sdl_modifiers |= SDL_KMOD_NUM; + // Capslock, Numlock, and Scrolllock can only be locked, not pressed. + if (viddata->keyboard.locked_modifiers & LockMask) { + viddata->keyboard.sdl_locked_modifiers |= SDL_KMOD_CAPS; } else { - viddata->xkb.sdl_modifiers &= ~SDL_KMOD_NUM; + viddata->keyboard.sdl_locked_modifiers &= ~SDL_KMOD_CAPS; } - if (xk_modifiers & viddata->xkb.scrolllock_mask) { - viddata->xkb.sdl_modifiers |= SDL_KMOD_SCROLL; + if (viddata->keyboard.locked_modifiers & viddata->keyboard.numlock_mask) { + viddata->keyboard.sdl_locked_modifiers |= SDL_KMOD_NUM; } 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); SDL_Keymod mod = SDL_KMOD_NONE; - bool reconcile = false; /* 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 @@ -378,48 +486,46 @@ static void X11_HandleModifierKeys(SDL_VideoData *viddata, SDL_Scancode scancode case SDLK_NUMLOCKCLEAR: case SDLK_SCROLLLOCK: { - /* For locking modifier keys, query the lock state directly, or we may have to wait until the next - * key press event to know if a lock was actually activated from the key event. - */ - unsigned int cur_mask = viddata->xkb.xkb_modifiers; - X11_UpdateSystemKeyModifiers(viddata); + // XKB provides the latched/locked state explicitly. + if (viddata->keyboard.xkb_enabled) { + /* For locking modifier keys, query the lock state directly, or we may have to wait until the next + * key press event to know if a lock was actually activated from the key event. + */ + unsigned int cur_mask = viddata->keyboard.locked_modifiers; + X11_UpdateSystemKeyModifiers(viddata); - if (viddata->xkb.xkb_modifiers & LockMask) { - cur_mask |= LockMask; - } else { - cur_mask &= ~LockMask; - } - if (viddata->xkb.xkb_modifiers & viddata->xkb.numlock_mask) { - cur_mask |= viddata->xkb.numlock_mask; - } else { - cur_mask &= ~viddata->xkb.numlock_mask; - } - if (viddata->xkb.xkb_modifiers & viddata->xkb.scrolllock_mask) { - cur_mask |= viddata->xkb.scrolllock_mask; - } else { - cur_mask &= ~viddata->xkb.scrolllock_mask; - } + if (viddata->keyboard.locked_modifiers & LockMask) { + cur_mask |= LockMask; + } else { + cur_mask &= ~LockMask; + } + if (viddata->keyboard.locked_modifiers & viddata->keyboard.numlock_mask) { + cur_mask |= viddata->keyboard.numlock_mask; + } else { + cur_mask &= ~viddata->keyboard.numlock_mask; + } + if (viddata->keyboard.locked_modifiers & viddata->keyboard.scrolllock_mask) { + cur_mask |= viddata->keyboard.scrolllock_mask; + } else { + cur_mask &= ~viddata->keyboard.scrolllock_mask; + } - viddata->xkb.xkb_modifiers = cur_mask; - } SDL_FALLTHROUGH; + viddata->keyboard.locked_modifiers = cur_mask; + } + } break; default: - reconcile = true; - break; + return; } if (pressed) { - viddata->xkb.sdl_modifiers |= mod; + viddata->keyboard.sdl_pressed_modifiers |= mod; + viddata->keyboard.sdl_physically_pressed_modifiers |= mod; } else { - viddata->xkb.sdl_modifiers &= ~mod; + viddata->keyboard.sdl_pressed_modifiers &= ~mod; + viddata->keyboard.sdl_physically_pressed_modifiers &= ~mod; } - if (allow_reconciliation) { - if (reconcile) { - X11_ReconcileModifiers(viddata); - } else { - SDL_SetModState(viddata->xkb.sdl_modifiers); - } - } + X11_ReconcileModifiers(viddata, true); } void X11_ReconcileKeyboardState(SDL_VideoDevice *_this) @@ -427,18 +533,25 @@ void X11_ReconcileKeyboardState(SDL_VideoDevice *_this) SDL_VideoData *videodata = _this->internal; Display *display = videodata->display; 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); - keyboardState = SDL_GetKeyboardState(0); - for (keycode = 0; keycode < SDL_arraysize(videodata->key_layout); ++keycode) { - SDL_Scancode scancode = videodata->key_layout[keycode]; - bool x11KeyPressed = (keys[keycode / 8] & (1 << (keycode % 8))) != 0; - bool sdlKeyPressed = keyboardState[scancode]; + for (Uint32 keycode = 0; keycode < SDL_arraysize(videodata->keyboard.key_layout); ++keycode) { + const SDL_Scancode scancode = videodata->keyboard.key_layout[keycode]; + const bool x11KeyPressed = (keys[keycode / 8] & (1 << (keycode % 8))) != 0; - if (x11KeyPressed && !sdlKeyPressed) { + if (x11KeyPressed) { // Only update modifier state for keys that are pressed in another application switch (SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false)) { case SDLK_LCTRL: @@ -451,20 +564,18 @@ void X11_ReconcileKeyboardState(SDL_VideoDevice *_this) case SDLK_RGUI: case SDLK_MODE: 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); break; default: 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_ReconcileModifiers(videodata); + X11_ReconcileModifiers(videodata, false); } 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; bool handled_by_ime = false; 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); #ifdef DEBUG_XEVENTS @@ -951,7 +1062,12 @@ void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_ #endif // DEBUG SCANCODES 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)) { // 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 (pressed) { - X11_HandleModifierKeys(videodata, scancode, true, true); + X11_HandleModifierKeys(videodata, scancode, true); SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, true); 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; } - X11_HandleModifierKeys(videodata, scancode, false, true); + X11_HandleModifierKeys(videodata, scancode, false); SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, false); } } @@ -1225,37 +1341,78 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent) if (!data) { // 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) { #ifdef DEBUG_XEVENTS SDL_Log("window 0x%lx: KeymapNotify!", xevent->xany.window); #endif - if (SDL_GetKeyboardFocus() != NULL) { -#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM - if (videodata->xkb.desc_ptr) { - 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); - } - } + if (!videodata->keyboard.xkb_enabled) { + if (SDL_GetKeyboardFocus() != NULL) { + X11_UpdateKeymap(_this, true); } -#endif - X11_ReconcileKeyboardState(_this); } + + X11_ReconcileKeyboardState(_this); } else if (xevent->type == MappingNotify) { - // Has the keyboard layout changed? - const int request = xevent->xmapping.request; + if (!videodata->keyboard.xkb_enabled) { + // Has the keyboard layout changed? + const int request = xevent->xmapping.request; #ifdef DEBUG_XEVENTS - SDL_Log("window 0x%lx: MappingNotify!", xevent->xany.window); + SDL_Log("window 0x%lx: MappingNotify!", xevent->xany.window); #endif - if ((request == MappingKeyboard) || (request == MappingModifier)) { - X11_XRefreshKeyboardMapping(&xevent->xmapping); - } + if ((request == MappingKeyboard) || (request == MappingModifier)) { + X11_XRefreshKeyboardMapping(&xevent->xmapping); + } - X11_UpdateKeymap(_this, true); + X11_UpdateKeymap(_this, true); + } } else if (xevent->type == PropertyNotify && videodata && videodata->windowlist) { char *name_of_atom = X11_XGetAtomName(display, xevent->xproperty.atom); diff --git a/src/video/x11/SDL_x11keyboard.c b/src/video/x11/SDL_x11keyboard.c index 8406f48671..247d8e0a84 100644 --- a/src/video/x11/SDL_x11keyboard.c +++ b/src/video/x11/SDL_x11keyboard.c @@ -28,7 +28,10 @@ #include "../../events/SDL_scancode_tables_c.h" #include + +#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB #include +#endif #include "../../events/imKStoUCS.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 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); } -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) { SDL_VideoData *data = _this->internal; @@ -146,21 +129,33 @@ bool X11_InitKeyboard(SDL_VideoDevice *_this) int best_distance; int best_index; int distance; - Bool xkb_repeat = 0; -#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM - { - int xkb_major = XkbMajorVersion; - int xkb_minor = XkbMinorVersion; +#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB + int xkb_major = XkbMajorVersion; + int xkb_minor = XkbMinorVersion; - if (X11_XkbQueryExtension(data->display, NULL, &data->xkb.event, NULL, &xkb_major, &xkb_minor)) { - data->xkb.desc_ptr = X11_XkbGetMap(data->display, XkbAllClientInfoMask, XkbUseCoreKbd); - } + if (X11_XkbQueryExtension(data->display, NULL, &data->keyboard.xkb.event, NULL, &xkb_major, &xkb_minor)) { + 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); - } + + // 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 + { + // 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 #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); #endif // This should never happen, but just in case... - if (table_size > (SDL_arraysize(data->key_layout) - min_keycode)) { - 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->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. 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)); } #endif - if (scancode == data->key_layout[i]) { + if (scancode == data->keyboard.key_layout[i]) { continue; } 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 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 - data->key_layout[i] = scancode; + data->keyboard.key_layout[i] = scancode; } } } else { @@ -293,7 +288,7 @@ bool X11_InitKeyboard(SDL_VideoDevice *_this) SDL_Log("scancode = %d (%s)", scancode, SDL_GetScancodeName(scancode)); } #endif - data->key_layout[i] = scancode; + data->keyboard.key_layout[i] = scancode; } } @@ -306,149 +301,235 @@ bool X11_InitKeyboard(SDL_VideoDevice *_this) 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; Display *display = videodata->display; - unsigned num_mask = 0; - int i, j; - XModifierKeymap *xmods; - unsigned n; + unsigned int mod_mask = 0; - xmods = X11_XGetModifierMapping(display); - n = xmods->max_keypermod; - for (i = 3; i < 8; i++) { - for (j = 0; j < n; j++) { - KeyCode kc = xmods->modifiermap[i * n + j]; - if (videodata->key_layout[kc] == SDL_SCANCODE_NUMLOCKCLEAR) { - num_mask = 1 << i; + XModifierKeymap *xmods = X11_XGetModifierMapping(display); + unsigned int n = xmods->max_keypermod; + for (int i = 3; i < 8; i++) { + for (int j = 0; j < n; j++) { + const KeyCode kc = xmods->modifiermap[i * n + j]; + if (videodata->keyboard.key_layout[kc] == scancode) { + mod_mask = 1 << i; break; } } } 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; - Display *display = videodata->display; - unsigned num_mask = 0; - int i, j; - XModifierKeymap *xmods; - unsigned n; + SDL_Keycode keycode = SDL_GetKeyCodeFromKeySym(xkeysym, xkeycode, sdl_mod_mask); - xmods = X11_XGetModifierMapping(display); - n = xmods->max_keypermod; - for (i = 3; i < 8; i++) { - for (j = 0; j < n; j++) { - KeyCode kc = xmods->modifiermap[i * n + j]; - if (videodata->key_layout[kc] == SDL_SCANCODE_SCROLLLOCK) { - num_mask = 1 << i; - break; - } + if (!keycode) { + switch (sdl_scancode) { + case SDL_SCANCODE_RETURN: + keycode = SDLK_RETURN; + 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: + 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) { - 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_Scancode scancode; - SDL_Keymap *keymap = SDL_CreateKeymap(true); -#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM - if (data->xkb.desc_ptr) { +#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB + if (data->keyboard.xkb_enabled) { 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) { - data->xkb.current_group = state.group; + data->keyboard.xkb.current_group = state.group; } - } -#endif - for (int m = 0; m < SDL_arraysize(keymod_masks); ++m) { - for (int i = 0; i < SDL_arraysize(data->key_layout); ++i) { - // Make sure this is a valid scancode - scancode = data->key_layout[i]; + data->keyboard.alt_mask = X11_GetXkbVirtualModifierMask(_this, "Alt"); + if (!data->keyboard.alt_mask) { + data->keyboard.alt_mask = X11_GetXkbVirtualModifierMask(_this, "Meta"); + } + 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) { 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) { - SDL_Keycode keycode = SDL_GetKeyCodeFromKeySym(keysym, i, keymod_masks[m].sdl_mask); + Uint32 effective_group = group; + 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) { - switch (scancode) { - case SDL_SCANCODE_RETURN: - keycode = SDLK_RETURN; - 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; + if (max_key_group && effective_group >= max_key_group) { + const unsigned char action = XkbOutOfRangeGroupAction(key_group_info); + + switch (action) { 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; } } - 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); - data->xkb.scrolllock_mask = X11_GetScrollLockModifierMask(_this); - SDL_SetKeymap(keymap, send_event); + SDL_SetKeymap(data->keyboard.xkb.keymaps[data->keyboard.xkb.current_group], send_event); + } else +#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) { SDL_VideoData *data = _this->internal; -#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM - if (data->xkb.desc_ptr) { - X11_XkbFreeKeyboard(data->xkb.desc_ptr, 0, True); - data->xkb.desc_ptr = NULL; - } +#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLIB + if (data->keyboard.xkb_enabled) { + for (int i = 0; i < XkbNumKbdGroups; ++i) { + 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 + 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) diff --git a/src/video/x11/SDL_x11keyboard.h b/src/video/x11/SDL_x11keyboard.h index a6cd2f7e97..bc053f5fbb 100644 --- a/src/video/x11/SDL_x11keyboard.h +++ b/src/video/x11/SDL_x11keyboard.h @@ -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_HideScreenKeyboard(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_ diff --git a/src/video/x11/SDL_x11sym.h b/src/video/x11/SDL_x11sym.h index df9d59b36e..959aabce8c 100644 --- a/src/video/x11/SDL_x11sym.h +++ b/src/video/x11/SDL_x11sym.h @@ -71,6 +71,7 @@ SDL_X11_SYM(int,XFreePixmap,(Display* a,Pixmap b)) SDL_X11_SYM(void,XFreeStringList,(char** a)) SDL_X11_SYM(char*,XGetAtomName,(Display *a,Atom b)) 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(XModifierKeymap*,XGetModifierMapping,(Display* a)) 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)) #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)) -#if NeedWidePrototypes -SDL_X11_SYM(Bool,XkbLookupKeySym,(Display* a, unsigned int b, unsigned int c, unsigned int* d, KeySym* e)) -#else -SDL_X11_SYM(Bool,XkbLookupKeySym,(Display* a, KeyCode b, unsigned int c, unsigned int* d, KeySym* e)) -#endif +SDL_X11_SYM(KeySym,XkbKeycodeToKeysym,(Display* a, KeyCode b, unsigned int c, unsigned int d)) +SDL_X11_SYM(Bool,XkbSelectEvents,(Display* a, unsigned int b, unsigned int c, unsigned long d)) +SDL_X11_SYM(Bool,XkbSelectEventDetails,(Display* a, unsigned int b, unsigned int c, unsigned long d, unsigned long e)) +SDL_X11_SYM(Status,XkbGetNames,(Display *a, unsigned int b, XkbDescPtr 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(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,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)) #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 SDL_X11_MODULE(UTF8) SDL_X11_SYM(int,Xutf8TextListToTextProperty,(Display* a,char** b,int c,XICCEncodingStyle d,XTextProperty* e)) diff --git a/src/video/x11/SDL_x11video.h b/src/video/x11/SDL_x11video.h index 8d89bf5b76..b5bdb67409 100644 --- a/src/video/x11/SDL_x11video.h +++ b/src/video/x11/SDL_x11video.h @@ -24,6 +24,7 @@ #define SDL_x11video_h_ #include "../SDL_sysvideo.h" +#include "../../events/SDL_keymap_c.h" #include "../../core/linux/SDL_dbus.h" #include "../../core/linux/SDL_ime.h" @@ -125,7 +126,6 @@ struct SDL_VideoData Atom pen_atom_wacom_tool_type; } atoms; - SDL_Scancode key_layout[256]; bool selection_waiting; bool selection_incr_waiting; @@ -144,21 +144,43 @@ struct SDL_VideoData int xrandr_event_base; struct { -#ifdef SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM - XkbDescPtr desc_ptr; + bool xkb_enabled; + 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 - int event; - unsigned int current_group; - unsigned int xkb_modifiers; - - SDL_Keymod sdl_modifiers; + struct + { + KeySym *keysym_map; + int keysyms_per_key; + 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 scrolllock_mask; - } xkb; - - KeyCode filter_code; - Time filter_time; + } keyboard; #ifdef SDL_VIDEO_VULKAN // Vulkan variables only valid if _this->vulkan_config.loader_handle is not NULL