diff --git a/include/SDL3/SDL_keycode.h b/include/SDL3/SDL_keycode.h index e4d6024eb..cb155a5a1 100644 --- a/include/SDL3/SDL_keycode.h +++ b/include/SDL3/SDL_keycode.h @@ -313,6 +313,7 @@ typedef Uint16 SDL_Keymod; #define SDL_KMOD_NONE 0x0000u /**< no modifier is applicable. */ #define SDL_KMOD_LSHIFT 0x0001u /**< the left Shift key is down. */ #define SDL_KMOD_RSHIFT 0x0002u /**< the right Shift key is down. */ +#define SDL_KMOD_LEVEL5 0x0004u /**< the Level 5 Shift key is down. */ #define SDL_KMOD_LCTRL 0x0040u /**< the left Ctrl (Control) key is down. */ #define SDL_KMOD_RCTRL 0x0080u /**< the right Ctrl (Control) key is down. */ #define SDL_KMOD_LALT 0x0100u /**< the left Alt key is down. */ diff --git a/src/events/SDL_keymap.c b/src/events/SDL_keymap.c index 96896b2c0..1aeb57c6c 100644 --- a/src/events/SDL_keymap.c +++ b/src/events/SDL_keymap.c @@ -50,8 +50,8 @@ SDL_Keymap *SDL_CreateKeymap(void) static SDL_Keymod NormalizeModifierStateForKeymap(SDL_Keymod modstate) { - // The modifiers that affect the keymap are: SHIFT, CAPS, ALT, and MODE - modstate &= (SDL_KMOD_SHIFT | SDL_KMOD_CAPS | SDL_KMOD_ALT | SDL_KMOD_MODE); + // The modifiers that affect the keymap are: SHIFT, CAPS, ALT, MODE, and LEVEL5 + modstate &= (SDL_KMOD_SHIFT | SDL_KMOD_CAPS | SDL_KMOD_ALT | SDL_KMOD_MODE | SDL_KMOD_LEVEL5); // If either right or left Shift are set, set both in the output if (modstate & SDL_KMOD_SHIFT) { diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c index dd60884e0..335d5595d 100644 --- a/src/test/SDL_test_common.c +++ b/src/test/SDL_test_common.c @@ -958,6 +958,9 @@ static void SDLTest_PrintModStateFlag(char *text, size_t maxlen, SDL_Keymod flag case SDL_KMOD_RSHIFT: SDL_snprintfcat(text, maxlen, "RSHIFT"); break; + case SDL_KMOD_LEVEL5: + SDL_snprintfcat(text, maxlen, "LEVEL5"); + break; case SDL_KMOD_LCTRL: SDL_snprintfcat(text, maxlen, "LCTRL"); break; @@ -999,6 +1002,7 @@ static void SDLTest_PrintModState(char *text, size_t maxlen, SDL_Keymod keymod) const SDL_Keymod kmod_flags[] = { SDL_KMOD_LSHIFT, SDL_KMOD_RSHIFT, + SDL_KMOD_LEVEL5, SDL_KMOD_LCTRL, SDL_KMOD_RCTRL, SDL_KMOD_LALT, diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 6784bf080..b92c84a12 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -68,9 +68,14 @@ // Weston uses a ratio of 10 units per scroll tick #define WAYLAND_WHEEL_AXIS_UNIT 10 -// xkbcommon as of 1.4.1 doesn't have a name macro for the mode key -#ifndef XKB_MOD_NAME_MODE -#define XKB_MOD_NAME_MODE "Mod5" +// "Mod5" is typically level 3 shift, which SDL calls SDL_KMOD_MODE (AltGr). +#ifndef XKB_MOD_NAME_MOD5 +#define XKB_MOD_NAME_MOD5 "Mod5" +#endif + +// "Mod3" is typically level 5 shift, but is often remapped. +#ifndef XKB_MOD_NAME_MOD3 +#define XKB_MOD_NAME_MOD3 "Mod3" #endif // Keyboard and mouse names to match XWayland @@ -1267,28 +1272,46 @@ static void Wayland_keymap_iter(struct xkb_keymap *keymap, xkb_keycode_t key, vo { Wayland_Keymap *sdlKeymap = (Wayland_Keymap *)data; const xkb_keysym_t *syms; - SDL_Scancode scancode; - - scancode = SDL_GetScancodeFromTable(SDL_SCANCODE_TABLE_XFREE86_2, (key - 8)); + const SDL_Scancode scancode = SDL_GetScancodeFromTable(SDL_SCANCODE_TABLE_XFREE86_2, (key - 8)); if (scancode == SDL_SCANCODE_UNKNOWN) { return; } if (WAYLAND_xkb_state_key_get_syms(sdlKeymap->state, key, &syms) > 0) { uint32_t keycode = SDL_KeySymToUcs4(syms[0]); + bool key_is_unknown = false; if (!keycode) { - const SDL_Scancode sc = SDL_GetScancodeFromKeySym(syms[0], key); - - // Note: The default SDL scancode table sets this to right alt instead of AltGr/Mode, so handle it separately. - if (syms[0] != XKB_KEY_ISO_Level3_Shift) { - keycode = SDL_GetKeymapKeycode(NULL, sc, sdlKeymap->modstate); - } else { + switch (syms[0]) { + // The default SDL scancode table sets this to right alt instead of AltGr/Mode, so handle it separately. + case XKB_KEY_ISO_Level3_Shift: keycode = SDLK_MODE; + break; + + /* The default SDL scancode table sets Meta L/R to the GUI keys, and Hyper R to app menu, which is + * correct as far as physical key placement goes, but these keys are functionally distinct from the + * default keycodes SDL returns for the scancodes, so they are set to unknown. + * + * SDL has no scancode mapping for Hyper L or Level 5 Shift. + */ + case XKB_KEY_Meta_L: + case XKB_KEY_Meta_R: + case XKB_KEY_Hyper_L: + case XKB_KEY_Hyper_R: + case XKB_KEY_ISO_Level5_Shift: + keycode = SDLK_UNKNOWN; + key_is_unknown = true; + break; + + default: + { + const SDL_Scancode sc = SDL_GetScancodeFromKeySym(syms[0], key); + keycode = SDL_GetKeymapKeycode(NULL, sc, sdlKeymap->modstate); + } break; } } - if (!keycode) { + if (!keycode && !key_is_unknown) { switch (scancode) { case SDL_SCANCODE_RETURN: keycode = SDLK_RETURN; @@ -1326,10 +1349,18 @@ static void Wayland_UpdateKeymap(struct SDL_WaylandInput *input) { SDL_KMOD_SHIFT, input->xkb.idx_shift }, { SDL_KMOD_CAPS, input->xkb.idx_caps }, { SDL_KMOD_SHIFT | SDL_KMOD_CAPS, input->xkb.idx_shift | input->xkb.idx_caps }, - { SDL_KMOD_MODE, input->xkb.idx_mode }, - { SDL_KMOD_MODE | SDL_KMOD_SHIFT, input->xkb.idx_mode | input->xkb.idx_shift }, - { SDL_KMOD_MODE | SDL_KMOD_CAPS, input->xkb.idx_mode | input->xkb.idx_caps }, - { SDL_KMOD_MODE | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, input->xkb.idx_mode | input->xkb.idx_shift | input->xkb.idx_caps } + { SDL_KMOD_MODE, input->xkb.idx_mod5 }, + { SDL_KMOD_MODE | SDL_KMOD_SHIFT, input->xkb.idx_mod5 | input->xkb.idx_shift }, + { SDL_KMOD_MODE | SDL_KMOD_CAPS, input->xkb.idx_mod5 | input->xkb.idx_caps }, + { SDL_KMOD_MODE | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, input->xkb.idx_mod5 | input->xkb.idx_shift | input->xkb.idx_caps }, + { SDL_KMOD_LEVEL5, input->xkb.idx_mod3 }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_SHIFT, input->xkb.idx_mod3 | input->xkb.idx_shift }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_CAPS, input->xkb.idx_mod3 | input->xkb.idx_caps }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, input->xkb.idx_mod3 | input->xkb.idx_shift | input->xkb.idx_caps }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE, input->xkb.idx_mod3 | input->xkb.idx_mod5 }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_SHIFT, input->xkb.idx_mod3 | input->xkb.idx_mod5 | input->xkb.idx_shift }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_CAPS, input->xkb.idx_mod3 | input->xkb.idx_mod5 | input->xkb.idx_caps }, + { SDL_KMOD_LEVEL5 | SDL_KMOD_MODE | SDL_KMOD_SHIFT | SDL_KMOD_CAPS, input->xkb.idx_mod3 | input->xkb.idx_mod5 | input->xkb.idx_shift | input->xkb.idx_caps }, }; if (!input->keyboard_is_virtual) { @@ -1350,7 +1381,7 @@ static void Wayland_UpdateKeymap(struct SDL_WaylandInput *input) for (int i = 0; i < SDL_arraysize(keymod_masks); ++i) { keymap.modstate = keymod_masks[i].sdl_mask; WAYLAND_xkb_state_update_mask(keymap.state, - keymod_masks[i].xkb_mask & (input->xkb.idx_shift | input->xkb.idx_mode), 0, keymod_masks[i].xkb_mask & input->xkb.idx_caps, + keymod_masks[i].xkb_mask & (input->xkb.idx_shift | input->xkb.idx_mod5 | input->xkb.idx_mod3), 0, keymod_masks[i].xkb_mask & input->xkb.idx_caps, 0, 0, input->xkb.current_group); WAYLAND_xkb_keymap_key_for_each(input->xkb.keymap, Wayland_keymap_iter, @@ -1413,7 +1444,8 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, input->xkb.idx_ctrl = 1 << GET_MOD_INDEX(CTRL); input->xkb.idx_alt = 1 << GET_MOD_INDEX(ALT); input->xkb.idx_gui = 1 << GET_MOD_INDEX(LOGO); - input->xkb.idx_mode = 1 << GET_MOD_INDEX(MODE); + input->xkb.idx_mod3 = 1 << GET_MOD_INDEX(MOD3); + input->xkb.idx_mod5 = 1 << GET_MOD_INDEX(MOD5); input->xkb.idx_num = 1 << GET_MOD_INDEX(NUM); input->xkb.idx_caps = 1 << GET_MOD_INDEX(CAPS); #undef GET_MOD_INDEX @@ -1488,59 +1520,77 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, * Virtual keyboards can have arbitrary layouts, arbitrary scancodes/keycodes, etc... * Key presses from these devices must be looked up by their keysym value. */ -static SDL_Scancode Wayland_get_scancode_from_key(struct SDL_WaylandInput *input, uint32_t key) +static void Wayland_get_scancode_from_key(struct SDL_WaylandInput *input, uint32_t keycode, SDL_Scancode *scancode) { - SDL_Scancode scancode = SDL_SCANCODE_UNKNOWN; + const xkb_keysym_t *syms; if (!input->keyboard_is_virtual) { - scancode = SDL_GetScancodeFromTable(SDL_SCANCODE_TABLE_XFREE86_2, key - 8); + *scancode = SDL_GetScancodeFromTable(SDL_SCANCODE_TABLE_XFREE86_2, keycode); } else { - const xkb_keysym_t *syms; - if (WAYLAND_xkb_keymap_key_get_syms_by_level(input->xkb.keymap, key, input->xkb.current_group, 0, &syms) > 0) { - scancode = SDL_GetScancodeFromKeySym(syms[0], key); + if (WAYLAND_xkb_keymap_key_get_syms_by_level(input->xkb.keymap, keycode + 8, input->xkb.current_group, 0, &syms) > 0) { + *scancode = SDL_GetScancodeFromKeySym(syms[0], keycode + 8); } } - - return scancode; } -static void Wayland_ReconcileModifiers(struct SDL_WaylandInput *input) +static void Wayland_ReconcileModifiers(struct SDL_WaylandInput *input, bool key_pressed) { - // Handle pressed modifiers for virtual keyboards that may not send keystrokes. - if (input->keyboard_is_virtual) { + /* 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, or if the modifier was set to the + * pressed state via means other than pressing the physical key. + */ + if (!key_pressed) { if (input->xkb.wl_pressed_modifiers & input->xkb.idx_shift) { - input->pressed_modifiers |= SDL_KMOD_SHIFT; + if (!(input->pressed_modifiers & SDL_KMOD_SHIFT)) { + input->pressed_modifiers |= SDL_KMOD_SHIFT; + } } else { input->pressed_modifiers &= ~SDL_KMOD_SHIFT; } if (input->xkb.wl_pressed_modifiers & input->xkb.idx_ctrl) { - input->pressed_modifiers |= SDL_KMOD_CTRL; + if (!(input->pressed_modifiers & SDL_KMOD_CTRL)) { + input->pressed_modifiers |= SDL_KMOD_CTRL; + } } else { input->pressed_modifiers &= ~SDL_KMOD_CTRL; } if (input->xkb.wl_pressed_modifiers & input->xkb.idx_alt) { - input->pressed_modifiers |= SDL_KMOD_ALT; + if (!(input->pressed_modifiers & SDL_KMOD_ALT)) { + input->pressed_modifiers |= SDL_KMOD_ALT; + } } else { input->pressed_modifiers &= ~SDL_KMOD_ALT; } if (input->xkb.wl_pressed_modifiers & input->xkb.idx_gui) { - input->pressed_modifiers |= SDL_KMOD_GUI; + if (!(input->pressed_modifiers & SDL_KMOD_GUI)) { + input->pressed_modifiers |= SDL_KMOD_GUI; + } } else { input->pressed_modifiers &= ~SDL_KMOD_GUI; } - if (input->xkb.wl_pressed_modifiers & input->xkb.idx_mode) { - input->pressed_modifiers |= SDL_KMOD_MODE; + if (input->xkb.wl_pressed_modifiers & input->xkb.idx_mod3) { + if (!(input->pressed_modifiers & SDL_KMOD_LEVEL5)) { + input->pressed_modifiers |= SDL_KMOD_LEVEL5; + } + } else { + input->pressed_modifiers &= ~SDL_KMOD_LEVEL5; + } + + if (input->xkb.wl_pressed_modifiers & input->xkb.idx_mod5) { + if (!(input->pressed_modifiers & SDL_KMOD_MODE)) { + input->pressed_modifiers |= SDL_KMOD_MODE; + } } else { input->pressed_modifiers &= ~SDL_KMOD_MODE; } } - /* - * If a latch or lock was activated by a keypress, the latch/lock will + /* 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. * @@ -1591,7 +1641,16 @@ static void Wayland_ReconcileModifiers(struct SDL_WaylandInput *input) input->locked_modifiers &= ~SDL_KMOD_GUI; } - if (input->xkb.wl_locked_modifiers & input->xkb.idx_mode) { + /* The Mod3 modifier corresponds to no particular SDL keycode, so it is + * only activated by the backend modifier callback. + */ + if (input->xkb.wl_locked_modifiers & input->xkb.idx_mod3) { + input->locked_modifiers |= SDL_KMOD_LEVEL5; + } else { + input->locked_modifiers &= ~SDL_KMOD_LEVEL5; + } + + if (input->xkb.wl_locked_modifiers & input->xkb.idx_mod5) { input->locked_modifiers |= SDL_KMOD_MODE; } else { input->locked_modifiers &= ~SDL_KMOD_MODE; @@ -1618,6 +1677,12 @@ static void Wayland_HandleModifierKeys(struct SDL_WaylandInput *input, SDL_Scanc const SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false); SDL_Keymod mod; + /* 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 + * keys here, as the explicit modifier event won't arrive until after the + * keypress event. If this is wrong, it will be corrected when the explicit + * modifier state is sent at a later time. + */ switch (keycode) { case SDLK_LSHIFT: mod = SDL_KMOD_LSHIFT; @@ -1656,7 +1721,7 @@ static void Wayland_HandleModifierKeys(struct SDL_WaylandInput *input, SDL_Scanc input->pressed_modifiers &= ~mod; } - Wayland_ReconcileModifiers(input); + Wayland_ReconcileModifiers(input, true); } static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, @@ -1694,7 +1759,9 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, window->last_focus_event_time_ns = timestamp; wl_array_for_each (key, keys) { - const SDL_Scancode scancode = Wayland_get_scancode_from_key(input, *key + 8); + SDL_Scancode scancode; + + Wayland_get_scancode_from_key(input, *key, &scancode); const SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false); switch (keycode) { @@ -1842,7 +1909,7 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard, keyboard_input_get_text(text, input, key, false, &handled_by_ime); } - scancode = Wayland_get_scancode_from_key(input, key + 8); + Wayland_get_scancode_from_key(input, key, &scancode); Wayland_HandleModifierKeys(input, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED); Uint64 timestamp = Wayland_GetKeyboardTimestamp(input, time); SDL_SendKeyboardKeyIgnoreModifiers(timestamp, input->keyboard_id, key, scancode, (state == WL_KEYBOARD_KEY_STATE_PRESSED)); @@ -1878,7 +1945,7 @@ static void keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, input->xkb.wl_pressed_modifiers = mods_depressed; input->xkb.wl_locked_modifiers = mods_latched | mods_locked; - Wayland_ReconcileModifiers(input); + Wayland_ReconcileModifiers(input, false); // If a key is repeating, update the text to apply the modifier. if (keyboard_repeat_is_set(&input->keyboard_repeat)) { diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h index 5073472da..2f11fe508 100644 --- a/src/video/wayland/SDL_waylandevents_c.h +++ b/src/video/wayland/SDL_waylandevents_c.h @@ -114,7 +114,8 @@ struct SDL_WaylandInput uint32_t idx_ctrl; uint32_t idx_alt; uint32_t idx_gui; - uint32_t idx_mode; + uint32_t idx_mod3; + uint32_t idx_mod5; uint32_t idx_num; uint32_t idx_caps;