Skip to content

Commit

Permalink
keyboard: Add some SDL keycodes for common Xkb keys
Browse files Browse the repository at this point in the history
Add SDL keycodes for keys found commonly found in the default Xkb layout, such as left tab and compose, and keys frequently used for custom modifiers such as Meta, Hyper, and Level5 Shift.

As these keys aren't Unicode code points and don't have associated scancodes (at least on modern keyboards), they are placed in the new extended key code space, with bit 30 set as a flag.
  • Loading branch information
Kontrabant committed Jan 7, 2025
1 parent 6b01cdd commit bb6e101
Show file tree
Hide file tree
Showing 11 changed files with 251 additions and 116 deletions.
13 changes: 12 additions & 1 deletion include/SDL3/SDL_keycode.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,15 @@
* A special exception is the number keys at the top of the keyboard which map
* to SDLK_0...SDLK_9 on AZERTY layouts.
*
* Keys with the `SDLK_EXTENDED_MASK` bit set do not map to a scancode or
* unicode code point.
*
* \since This datatype is available since SDL 3.1.3.
*/
typedef Uint32 SDL_Keycode;

#define SDLK_SCANCODE_MASK (1u<<30)
#define SDLK_EXTENDED_MASK (1u << 29)
#define SDLK_SCANCODE_MASK (1u << 30)
#define SDL_SCANCODE_TO_KEYCODE(X) (X | SDLK_SCANCODE_MASK)
#define SDLK_UNKNOWN 0x00000000u /**< 0 */
#define SDLK_RETURN 0x0000000du /**< '\r' */
Expand Down Expand Up @@ -302,6 +306,13 @@ typedef Uint32 SDL_Keycode;
#define SDLK_SOFTRIGHT 0x40000120u /**< SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SOFTRIGHT) */
#define SDLK_CALL 0x40000121u /**< SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CALL) */
#define SDLK_ENDCALL 0x40000122u /**< SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_ENDCALL) */
#define SDLK_LEFT_TAB 0x20000001u /**< Extended Left Tab key */
#define SDLK_LEVEL5_SHIFT 0x20000002u /**< Extended Level 5 Shift key */
#define SDLK_MULTI_KEY_COMPOSE 0x20000003u /**< Extended Multi-key Compose key */
#define SDLK_LMETA 0x20000004u /**< Extended Left Meta key */
#define SDLK_RMETA 0x20000005u /**< Extended Right Meta key */
#define SDLK_LHYPER 0x20000006u /**< Extended Left Hyper key */
#define SDLK_RHYPER 0x20000007u /**< Extended Right Hyper key */

/**
* Valid key modifiers (possibly OR'd together).
Expand Down
50 changes: 50 additions & 0 deletions src/events/SDL_keymap.c
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,18 @@ static const SDL_Keycode shifted_default_symbols[] = {
SDLK_QUESTION
};

static const struct
{
SDL_Keycode keycode;
SDL_Scancode scancode;
} extended_default_symbols[] = {
{ SDLK_LEFT_TAB, SDL_SCANCODE_TAB },
{ SDLK_MULTI_KEY_COMPOSE, SDL_SCANCODE_APPLICATION }, // Sun keyboards
{ SDLK_LMETA, SDL_SCANCODE_LGUI },
{ SDLK_RMETA, SDL_SCANCODE_RGUI },
{ SDLK_RHYPER, SDL_SCANCODE_APPLICATION }
};

static SDL_Keycode SDL_GetDefaultKeyFromScancode(SDL_Scancode scancode, SDL_Keymod modstate)
{
if (((int)scancode) < SDL_SCANCODE_UNKNOWN || scancode >= SDL_SCANCODE_COUNT) {
Expand Down Expand Up @@ -600,6 +612,16 @@ static SDL_Scancode SDL_GetDefaultScancodeFromKey(SDL_Keycode key, SDL_Keymod *m
return SDL_SCANCODE_UNKNOWN;
}

if (key & SDLK_EXTENDED_MASK) {
for (int i = 0; i < SDL_arraysize(extended_default_symbols); ++i) {
if (extended_default_symbols[i].keycode == key) {
return extended_default_symbols[i].scancode;
}
}

return SDL_SCANCODE_UNKNOWN;
}

if (key & SDLK_SCANCODE_MASK) {
return (SDL_Scancode)(key & ~SDLK_SCANCODE_MASK);
}
Expand Down Expand Up @@ -932,6 +954,16 @@ static const char *SDL_scancode_names[SDL_SCANCODE_COUNT] =
/* 290 */ "EndCall",
};

static const char *SDL_extended_key_names[] = {
"LeftTab", /* 0x01 SDLK_LEFT_TAB */
"Level5Shift", /* 0x02 SDLK_LEVEL5_SHIFT */
"MultiKeyCompose", /* 0x03 SDLK_MULTI_KEY_COMPOSE */
"Left Meta", /* 0x04 SDLK_LMETA */
"Right Meta", /* 0x05 SDLK_RMETA */
"Left Hyper", /* 0x06 SDLK_LHYPER */
"Right Hyper" /* 0x07 SDLK_RHYPER */
};

bool SDL_SetScancodeName(SDL_Scancode scancode, const char *name)
{
if (((int)scancode) < SDL_SCANCODE_UNKNOWN || scancode >= SDL_SCANCODE_COUNT) {
Expand Down Expand Up @@ -990,6 +1022,17 @@ const char *SDL_GetKeyName(SDL_Keycode key)
return SDL_GetScancodeName((SDL_Scancode)(key & ~SDLK_SCANCODE_MASK));
}

if (key & SDLK_EXTENDED_MASK) {
const SDL_Keycode idx = (key & ~SDLK_EXTENDED_MASK);
if (idx > 0 && (idx - 1) < SDL_arraysize(SDL_extended_key_names)) {
return SDL_extended_key_names[idx - 1];
}

// Key out of name index bounds.
SDL_InvalidParamError("key");
return "";
}

switch (key) {
case SDLK_RETURN:
return SDL_GetScancodeName(SDL_SCANCODE_RETURN);
Expand Down Expand Up @@ -1087,5 +1130,12 @@ SDL_Keycode SDL_GetKeyFromName(const char *name)
return key;
}

// Check the extended key names
for (SDL_Keycode i = 0; i < SDL_arraysize(SDL_extended_key_names); ++i) {
if (SDL_strcasecmp(name, SDL_extended_key_names[i]) == 0) {
return (i + 1) & SDLK_EXTENDED_MASK;
}
}

return SDL_GetKeyFromScancode(SDL_GetScancodeFromName(name), SDL_KMOD_NONE, false);
}
68 changes: 68 additions & 0 deletions src/events/SDL_keysym_to_keycode.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <[email protected]>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"

#if defined(SDL_VIDEO_DRIVER_WAYLAND) || defined(SDL_VIDEO_DRIVER_X11)

#include "SDL_keyboard_c.h"
#include "SDL_keysym_to_scancode_c.h"
#include "imKStoUCS.h"


// Extended key code mappings
static const struct
{
Uint32 keysym;
SDL_Keycode keycode;
} keysym_to_keycode_table[] = {
{ 0xfe03, SDLK_MODE }, // XK_ISO_Level3_Shift
{ 0xfe11, SDLK_LEVEL5_SHIFT }, // XK_ISO_Level5_Shift
{ 0xfe20, SDLK_LEFT_TAB }, // XK_ISO_Left_Tab
{ 0xff20, SDLK_MULTI_KEY_COMPOSE }, // XK_Multi_key
{ 0xffe7, SDLK_LMETA }, // XK_Meta_L
{ 0xffe8, SDLK_RMETA }, // XK_Meta_R
{ 0xffed, SDLK_LHYPER }, // XK_Hyper_L
{ 0xffee, SDLK_RHYPER }, // XK_Hyper_R
};

SDL_Keycode SDL_GetKeyCodeFromKeySym(Uint32 keysym, Uint32 keycode, SDL_Keymod modifiers)
{
SDL_Keycode sdl_keycode = SDL_KeySymToUcs4(keysym);

if (!sdl_keycode) {
for (int i = 0; i < SDL_arraysize(keysym_to_keycode_table); ++i) {
if (keysym == keysym_to_keycode_table[i].keysym) {
return keysym_to_keycode_table[i].keycode;
}
}
}

if (!sdl_keycode) {
const SDL_Scancode scancode = SDL_GetScancodeFromKeySym(keysym, keycode);
if (scancode != SDL_SCANCODE_UNKNOWN) {
sdl_keycode = SDL_GetKeymapKeycode(NULL, scancode, modifiers);
}
}

return sdl_keycode;
}

#endif // SDL_VIDEO_DRIVER_WAYLAND || SDL_VIDEO_DRIVER_X11
28 changes: 28 additions & 0 deletions src/events/SDL_keysym_to_keycode_c.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <[email protected]>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/

#ifndef SDL_keysym_to_keycode_c_h_
#define SDL_keysym_to_keycode_c_h_

// Convert a keysym to an SDL key code
extern SDL_Keycode SDL_GetKeyCodeFromKeySym(Uint32 keysym, Uint32 keycode, SDL_Keymod modifiers);

#endif // SDL_keysym_to_scancode_c_h_
1 change: 1 addition & 0 deletions src/events/SDL_keysym_to_scancode.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ static const struct {
{ 0xFF62, SDL_SCANCODE_EXECUTE }, // XK_Execute
{ 0xFFEE, SDL_SCANCODE_APPLICATION }, // XK_Hyper_R
{ 0xFE03, SDL_SCANCODE_RALT }, // XK_ISO_Level3_Shift
{ 0xFE20, SDL_SCANCODE_TAB }, // XK_ISO_Left_Tab
{ 0xFFEB, SDL_SCANCODE_LGUI }, // XK_Super_L
{ 0xFFEC, SDL_SCANCODE_RGUI }, // XK_Super_R
{ 0xFF7E, SDL_SCANCODE_MODE }, // XK_Mode_switch
Expand Down
3 changes: 3 additions & 0 deletions src/events/SDL_keysym_to_scancode_c.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@
// This function only correctly maps letters and numbers for keyboards in US QWERTY layout
extern SDL_Scancode SDL_GetScancodeFromKeySym(Uint32 keysym, Uint32 keycode);

// Convert a keysym to an extended SDL key code
extern SDL_Keycode SDL_GetExtendedKeyCodeFromKeySym(Uint32 keysym);

#endif // SDL_keysym_to_scancode_c_h_
64 changes: 18 additions & 46 deletions src/video/wayland/SDL_waylandevents.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "../../core/unix/SDL_poll.h"
#include "../../events/SDL_events_c.h"
#include "../../events/SDL_scancode_tables_c.h"
#include "../../events/SDL_keysym_to_keycode_c.h"
#include "../../core/linux/SDL_system_theme.h"
#include "../SDL_sysvideo.h"

Expand Down Expand Up @@ -1278,40 +1279,9 @@ static void Wayland_keymap_iter(struct xkb_keymap *keymap, xkb_keycode_t key, vo
}

if (WAYLAND_xkb_state_key_get_syms(sdlKeymap->state, key, &syms) > 0) {
uint32_t keycode = SDL_KeySymToUcs4(syms[0]);
bool key_is_unknown = false;
SDL_Keycode keycode = SDL_GetKeyCodeFromKeySym(syms[0], key, sdlKeymap->modstate);

if (!keycode) {
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 && !key_is_unknown) {
switch (scancode) {
case SDL_SCANCODE_RETURN:
keycode = SDLK_RETURN;
Expand All @@ -1322,9 +1292,6 @@ static void Wayland_keymap_iter(struct xkb_keymap *keymap, xkb_keycode_t key, vo
case SDL_SCANCODE_BACKSPACE:
keycode = SDLK_BACKSPACE;
break;
case SDL_SCANCODE_TAB:
keycode = SDLK_TAB;
break;
case SDL_SCANCODE_DELETE:
keycode = SDLK_DELETE;
break;
Expand Down Expand Up @@ -1520,17 +1487,20 @@ 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 void Wayland_get_scancode_from_key(struct SDL_WaylandInput *input, uint32_t keycode, SDL_Scancode *scancode)
static SDL_Scancode Wayland_GetScancodeForKey(struct SDL_WaylandInput *input, uint32_t key)
{
const xkb_keysym_t *syms;
SDL_Scancode scancode = SDL_SCANCODE_UNKNOWN;

if (!input->keyboard_is_virtual) {
*scancode = SDL_GetScancodeFromTable(SDL_SCANCODE_TABLE_XFREE86_2, keycode);
scancode = SDL_GetScancodeFromTable(SDL_SCANCODE_TABLE_XFREE86_2, key);
} else {
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);
const xkb_keysym_t *syms;
if (WAYLAND_xkb_keymap_key_get_syms_by_level(input->xkb.keymap, key + 8, input->xkb.current_group, 0, &syms) > 0) {
scancode = SDL_GetScancodeFromKeySym(syms[0], key);
}
}

return scancode;
}

static void Wayland_ReconcileModifiers(struct SDL_WaylandInput *input, bool key_pressed)
Expand Down Expand Up @@ -1711,6 +1681,9 @@ static void Wayland_HandleModifierKeys(struct SDL_WaylandInput *input, SDL_Scanc
case SDLK_MODE:
mod = SDL_KMOD_MODE;
break;
case SDLK_LEVEL5_SHIFT:
mod = SDL_KMOD_LEVEL5;
break;
default:
return;
}
Expand Down Expand Up @@ -1759,9 +1732,7 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard,
window->last_focus_event_time_ns = timestamp;

wl_array_for_each (key, keys) {
SDL_Scancode scancode;

Wayland_get_scancode_from_key(input, *key, &scancode);
const SDL_Scancode scancode = Wayland_GetScancodeForKey(input, *key);
const SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false);

switch (keycode) {
Expand All @@ -1774,6 +1745,7 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard,
case SDLK_LGUI:
case SDLK_RGUI:
case SDLK_MODE:
case SDLK_LEVEL5_SHIFT:
Wayland_HandleModifierKeys(input, scancode, true);
SDL_SendKeyboardKeyIgnoreModifiers(timestamp, input->keyboard_id, *key, scancode, true);
break;
Expand Down Expand Up @@ -1883,7 +1855,6 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
{
struct SDL_WaylandInput *input = data;
enum wl_keyboard_key_state state = state_w;
SDL_Scancode scancode = SDL_SCANCODE_UNKNOWN;
char text[8];
bool has_text = false;
bool handled_by_ime = false;
Expand All @@ -1909,10 +1880,11 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
keyboard_input_get_text(text, input, key, false, &handled_by_ime);
}

Wayland_get_scancode_from_key(input, key, &scancode);
const SDL_Scancode scancode = Wayland_GetScancodeForKey(input, key);
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));

SDL_SendKeyboardKeyIgnoreModifiers(timestamp, input->keyboard_id, key, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED);

if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
if (has_text && !(SDL_GetModState() & SDL_KMOD_CTRL)) {
Expand Down
Loading

0 comments on commit bb6e101

Please sign in to comment.