Skip to content

Commit 43cd428

Browse files
committed
keyboard: Add some SDL keycodes for common Xkb keys
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.
1 parent 6b01cdd commit 43cd428

File tree

11 files changed

+251
-116
lines changed

11 files changed

+251
-116
lines changed

include/SDL3/SDL_keycode.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,15 @@
4747
* A special exception is the number keys at the top of the keyboard which map
4848
* to SDLK_0...SDLK_9 on AZERTY layouts.
4949
*
50+
* Keys with the `SDLK_EXTENDED_MASK` bit set do not map to a scancode or
51+
* unicode code point.
52+
*
5053
* \since This datatype is available since SDL 3.1.3.
5154
*/
5255
typedef Uint32 SDL_Keycode;
5356

54-
#define SDLK_SCANCODE_MASK (1u<<30)
57+
#define SDLK_EXTENDED_MASK (1u << 29)
58+
#define SDLK_SCANCODE_MASK (1u << 30)
5559
#define SDL_SCANCODE_TO_KEYCODE(X) (X | SDLK_SCANCODE_MASK)
5660
#define SDLK_UNKNOWN 0x00000000u /**< 0 */
5761
#define SDLK_RETURN 0x0000000du /**< '\r' */
@@ -302,6 +306,13 @@ typedef Uint32 SDL_Keycode;
302306
#define SDLK_SOFTRIGHT 0x40000120u /**< SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SOFTRIGHT) */
303307
#define SDLK_CALL 0x40000121u /**< SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CALL) */
304308
#define SDLK_ENDCALL 0x40000122u /**< SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_ENDCALL) */
309+
#define SDLK_LEFT_TAB 0x20000001u /**< Extended key Left Tab */
310+
#define SDLK_LEVEL5_SHIFT 0x20000002u /**< Extended key Level 5 Shift */
311+
#define SDLK_MULTI_KEY_COMPOSE 0x20000003u /**< Extended key Multi-key Compose */
312+
#define SDLK_LMETA 0x20000004u /**< Extended key Left Meta */
313+
#define SDLK_RMETA 0x20000005u /**< Extended key Right Meta */
314+
#define SDLK_LHYPER 0x20000006u /**< Extended key Left Hyper */
315+
#define SDLK_RHYPER 0x20000007u /**< Extended key Right Hyper */
305316

306317
/**
307318
* Valid key modifiers (possibly OR'd together).

src/events/SDL_keymap.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,18 @@ static const SDL_Keycode shifted_default_symbols[] = {
189189
SDLK_QUESTION
190190
};
191191

192+
static const struct
193+
{
194+
SDL_Keycode keycode;
195+
SDL_Scancode scancode;
196+
} extended_default_symbols[] = {
197+
{ SDLK_LEFT_TAB, SDL_SCANCODE_TAB },
198+
{ SDLK_MULTI_KEY_COMPOSE, SDL_SCANCODE_APPLICATION }, // Sun keyboards
199+
{ SDLK_LMETA, SDL_SCANCODE_LGUI },
200+
{ SDLK_RMETA, SDL_SCANCODE_RGUI },
201+
{ SDLK_RHYPER, SDL_SCANCODE_APPLICATION }
202+
};
203+
192204
static SDL_Keycode SDL_GetDefaultKeyFromScancode(SDL_Scancode scancode, SDL_Keymod modstate)
193205
{
194206
if (((int)scancode) < SDL_SCANCODE_UNKNOWN || scancode >= SDL_SCANCODE_COUNT) {
@@ -600,6 +612,16 @@ static SDL_Scancode SDL_GetDefaultScancodeFromKey(SDL_Keycode key, SDL_Keymod *m
600612
return SDL_SCANCODE_UNKNOWN;
601613
}
602614

615+
if (key & SDLK_EXTENDED_MASK) {
616+
for (int i = 0; i < SDL_arraysize(extended_default_symbols); ++i) {
617+
if (extended_default_symbols[i].keycode == key) {
618+
return extended_default_symbols[i].scancode;
619+
}
620+
}
621+
622+
return SDL_SCANCODE_UNKNOWN;
623+
}
624+
603625
if (key & SDLK_SCANCODE_MASK) {
604626
return (SDL_Scancode)(key & ~SDLK_SCANCODE_MASK);
605627
}
@@ -932,6 +954,16 @@ static const char *SDL_scancode_names[SDL_SCANCODE_COUNT] =
932954
/* 290 */ "EndCall",
933955
};
934956

957+
static const char *SDL_extended_key_names[] = {
958+
"LeftTab", /* 0x01 SDLK_LEFT_TAB */
959+
"Level5Shift", /* 0x02 SDLK_LEVEL5_SHIFT */
960+
"MultiKeyCompose", /* 0x03 SDLK_MULTI_KEY_COMPOSE */
961+
"Left Meta", /* 0x04 SDLK_LMETA */
962+
"Right Meta", /* 0x05 SDLK_RMETA */
963+
"Left Hyper", /* 0x06 SDLK_LHYPER */
964+
"Right Hyper" /* 0x07 SDLK_RHYPER */
965+
};
966+
935967
bool SDL_SetScancodeName(SDL_Scancode scancode, const char *name)
936968
{
937969
if (((int)scancode) < SDL_SCANCODE_UNKNOWN || scancode >= SDL_SCANCODE_COUNT) {
@@ -990,6 +1022,17 @@ const char *SDL_GetKeyName(SDL_Keycode key)
9901022
return SDL_GetScancodeName((SDL_Scancode)(key & ~SDLK_SCANCODE_MASK));
9911023
}
9921024

1025+
if (key & SDLK_EXTENDED_MASK) {
1026+
const SDL_Keycode idx = (key & ~SDLK_EXTENDED_MASK);
1027+
if (idx > 0 && (idx - 1) < SDL_arraysize(SDL_extended_key_names)) {
1028+
return SDL_extended_key_names[idx - 1];
1029+
}
1030+
1031+
// Key out of name index bounds.
1032+
SDL_InvalidParamError("key");
1033+
return "";
1034+
}
1035+
9931036
switch (key) {
9941037
case SDLK_RETURN:
9951038
return SDL_GetScancodeName(SDL_SCANCODE_RETURN);
@@ -1087,5 +1130,12 @@ SDL_Keycode SDL_GetKeyFromName(const char *name)
10871130
return key;
10881131
}
10891132

1133+
// Check the extended key names
1134+
for (SDL_Keycode i = 0; i < SDL_arraysize(SDL_extended_key_names); ++i) {
1135+
if (SDL_strcasecmp(name, SDL_extended_key_names[i]) == 0) {
1136+
return (i + 1) & SDLK_EXTENDED_MASK;
1137+
}
1138+
}
1139+
10901140
return SDL_GetKeyFromScancode(SDL_GetScancodeFromName(name), SDL_KMOD_NONE, false);
10911141
}

src/events/SDL_keysym_to_keycode.c

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
Simple DirectMedia Layer
3+
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4+
5+
This software is provided 'as-is', without any express or implied
6+
warranty. In no event will the authors be held liable for any damages
7+
arising from the use of this software.
8+
9+
Permission is granted to anyone to use this software for any purpose,
10+
including commercial applications, and to alter it and redistribute it
11+
freely, subject to the following restrictions:
12+
13+
1. The origin of this software must not be misrepresented; you must not
14+
claim that you wrote the original software. If you use this software
15+
in a product, an acknowledgment in the product documentation would be
16+
appreciated but is not required.
17+
2. Altered source versions must be plainly marked as such, and must not be
18+
misrepresented as being the original software.
19+
3. This notice may not be removed or altered from any source distribution.
20+
*/
21+
#include "SDL_internal.h"
22+
23+
#if defined(SDL_VIDEO_DRIVER_WAYLAND) || defined(SDL_VIDEO_DRIVER_X11)
24+
25+
#include "SDL_keyboard_c.h"
26+
#include "SDL_keysym_to_scancode_c.h"
27+
#include "imKStoUCS.h"
28+
29+
30+
// Extended key code mappings
31+
static const struct
32+
{
33+
Uint32 keysym;
34+
SDL_Keycode keycode;
35+
} keysym_to_keycode_table[] = {
36+
{ 0xfe03, SDLK_MODE }, // XK_ISO_Level3_Shift
37+
{ 0xfe11, SDLK_LEVEL5_SHIFT }, // XK_ISO_Level5_Shift
38+
{ 0xfe20, SDLK_LEFT_TAB }, // XK_ISO_Left_Tab
39+
{ 0xff20, SDLK_MULTI_KEY_COMPOSE }, // XK_Multi_key
40+
{ 0xffe7, SDLK_LMETA }, // XK_Meta_L
41+
{ 0xffe8, SDLK_RMETA }, // XK_Meta_R
42+
{ 0xffed, SDLK_LHYPER }, // XK_Hyper_L
43+
{ 0xffee, SDLK_RHYPER }, // XK_Hyper_R
44+
};
45+
46+
SDL_Keycode SDL_GetKeyCodeFromKeySym(Uint32 keysym, Uint32 keycode, SDL_Keymod modifiers)
47+
{
48+
SDL_Keycode sdl_keycode = SDL_KeySymToUcs4(keysym);
49+
50+
if (!sdl_keycode) {
51+
for (int i = 0; i < SDL_arraysize(keysym_to_keycode_table); ++i) {
52+
if (keysym == keysym_to_keycode_table[i].keysym) {
53+
return keysym_to_keycode_table[i].keycode;
54+
}
55+
}
56+
}
57+
58+
if (!sdl_keycode) {
59+
const SDL_Scancode scancode = SDL_GetScancodeFromKeySym(keysym, keycode);
60+
if (scancode != SDL_SCANCODE_UNKNOWN) {
61+
sdl_keycode = SDL_GetKeymapKeycode(NULL, scancode, modifiers);
62+
}
63+
}
64+
65+
return sdl_keycode;
66+
}
67+
68+
#endif // SDL_VIDEO_DRIVER_WAYLAND || SDL_VIDEO_DRIVER_X11

src/events/SDL_keysym_to_keycode_c.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
Simple DirectMedia Layer
3+
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4+
5+
This software is provided 'as-is', without any express or implied
6+
warranty. In no event will the authors be held liable for any damages
7+
arising from the use of this software.
8+
9+
Permission is granted to anyone to use this software for any purpose,
10+
including commercial applications, and to alter it and redistribute it
11+
freely, subject to the following restrictions:
12+
13+
1. The origin of this software must not be misrepresented; you must not
14+
claim that you wrote the original software. If you use this software
15+
in a product, an acknowledgment in the product documentation would be
16+
appreciated but is not required.
17+
2. Altered source versions must be plainly marked as such, and must not be
18+
misrepresented as being the original software.
19+
3. This notice may not be removed or altered from any source distribution.
20+
*/
21+
22+
#ifndef SDL_keysym_to_keycode_c_h_
23+
#define SDL_keysym_to_keycode_c_h_
24+
25+
// Convert a keysym to an SDL key code
26+
extern SDL_Keycode SDL_GetKeyCodeFromKeySym(Uint32 keysym, Uint32 keycode, SDL_Keymod modifiers);
27+
28+
#endif // SDL_keysym_to_scancode_c_h_

src/events/SDL_keysym_to_scancode.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ static const struct {
4545
{ 0xFF62, SDL_SCANCODE_EXECUTE }, // XK_Execute
4646
{ 0xFFEE, SDL_SCANCODE_APPLICATION }, // XK_Hyper_R
4747
{ 0xFE03, SDL_SCANCODE_RALT }, // XK_ISO_Level3_Shift
48+
{ 0xFE20, SDL_SCANCODE_TAB }, // XK_ISO_Left_Tab
4849
{ 0xFFEB, SDL_SCANCODE_LGUI }, // XK_Super_L
4950
{ 0xFFEC, SDL_SCANCODE_RGUI }, // XK_Super_R
5051
{ 0xFF7E, SDL_SCANCODE_MODE }, // XK_Mode_switch

src/events/SDL_keysym_to_scancode_c.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,7 @@
2525
// This function only correctly maps letters and numbers for keyboards in US QWERTY layout
2626
extern SDL_Scancode SDL_GetScancodeFromKeySym(Uint32 keysym, Uint32 keycode);
2727

28+
// Convert a keysym to an extended SDL key code
29+
extern SDL_Keycode SDL_GetExtendedKeyCodeFromKeySym(Uint32 keysym);
30+
2831
#endif // SDL_keysym_to_scancode_c_h_

src/video/wayland/SDL_waylandevents.c

Lines changed: 18 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "../../core/unix/SDL_poll.h"
2727
#include "../../events/SDL_events_c.h"
2828
#include "../../events/SDL_scancode_tables_c.h"
29+
#include "../../events/SDL_keysym_to_keycode_c.h"
2930
#include "../../core/linux/SDL_system_theme.h"
3031
#include "../SDL_sysvideo.h"
3132

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

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

12841284
if (!keycode) {
1285-
switch (syms[0]) {
1286-
// The default SDL scancode table sets this to right alt instead of AltGr/Mode, so handle it separately.
1287-
case XKB_KEY_ISO_Level3_Shift:
1288-
keycode = SDLK_MODE;
1289-
break;
1290-
1291-
/* The default SDL scancode table sets Meta L/R to the GUI keys, and Hyper R to app menu, which is
1292-
* correct as far as physical key placement goes, but these keys are functionally distinct from the
1293-
* default keycodes SDL returns for the scancodes, so they are set to unknown.
1294-
*
1295-
* SDL has no scancode mapping for Hyper L or Level 5 Shift.
1296-
*/
1297-
case XKB_KEY_Meta_L:
1298-
case XKB_KEY_Meta_R:
1299-
case XKB_KEY_Hyper_L:
1300-
case XKB_KEY_Hyper_R:
1301-
case XKB_KEY_ISO_Level5_Shift:
1302-
keycode = SDLK_UNKNOWN;
1303-
key_is_unknown = true;
1304-
break;
1305-
1306-
default:
1307-
{
1308-
const SDL_Scancode sc = SDL_GetScancodeFromKeySym(syms[0], key);
1309-
keycode = SDL_GetKeymapKeycode(NULL, sc, sdlKeymap->modstate);
1310-
} break;
1311-
}
1312-
}
1313-
1314-
if (!keycode && !key_is_unknown) {
13151285
switch (scancode) {
13161286
case SDL_SCANCODE_RETURN:
13171287
keycode = SDLK_RETURN;
@@ -1322,9 +1292,6 @@ static void Wayland_keymap_iter(struct xkb_keymap *keymap, xkb_keycode_t key, vo
13221292
case SDL_SCANCODE_BACKSPACE:
13231293
keycode = SDLK_BACKSPACE;
13241294
break;
1325-
case SDL_SCANCODE_TAB:
1326-
keycode = SDLK_TAB;
1327-
break;
13281295
case SDL_SCANCODE_DELETE:
13291296
keycode = SDLK_DELETE;
13301297
break;
@@ -1520,17 +1487,20 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard,
15201487
* Virtual keyboards can have arbitrary layouts, arbitrary scancodes/keycodes, etc...
15211488
* Key presses from these devices must be looked up by their keysym value.
15221489
*/
1523-
static void Wayland_get_scancode_from_key(struct SDL_WaylandInput *input, uint32_t keycode, SDL_Scancode *scancode)
1490+
static SDL_Scancode Wayland_GetScancodeForKey(struct SDL_WaylandInput *input, uint32_t key)
15241491
{
1525-
const xkb_keysym_t *syms;
1492+
SDL_Scancode scancode = SDL_SCANCODE_UNKNOWN;
15261493

15271494
if (!input->keyboard_is_virtual) {
1528-
*scancode = SDL_GetScancodeFromTable(SDL_SCANCODE_TABLE_XFREE86_2, keycode);
1495+
scancode = SDL_GetScancodeFromTable(SDL_SCANCODE_TABLE_XFREE86_2, key);
15291496
} else {
1530-
if (WAYLAND_xkb_keymap_key_get_syms_by_level(input->xkb.keymap, keycode + 8, input->xkb.current_group, 0, &syms) > 0) {
1531-
*scancode = SDL_GetScancodeFromKeySym(syms[0], keycode + 8);
1497+
const xkb_keysym_t *syms;
1498+
if (WAYLAND_xkb_keymap_key_get_syms_by_level(input->xkb.keymap, key + 8, input->xkb.current_group, 0, &syms) > 0) {
1499+
scancode = SDL_GetScancodeFromKeySym(syms[0], key);
15321500
}
15331501
}
1502+
1503+
return scancode;
15341504
}
15351505

15361506
static void Wayland_ReconcileModifiers(struct SDL_WaylandInput *input, bool key_pressed)
@@ -1711,6 +1681,9 @@ static void Wayland_HandleModifierKeys(struct SDL_WaylandInput *input, SDL_Scanc
17111681
case SDLK_MODE:
17121682
mod = SDL_KMOD_MODE;
17131683
break;
1684+
case SDLK_LEVEL5_SHIFT:
1685+
mod = SDL_KMOD_LEVEL5;
1686+
break;
17141687
default:
17151688
return;
17161689
}
@@ -1759,9 +1732,7 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard,
17591732
window->last_focus_event_time_ns = timestamp;
17601733

17611734
wl_array_for_each (key, keys) {
1762-
SDL_Scancode scancode;
1763-
1764-
Wayland_get_scancode_from_key(input, *key, &scancode);
1735+
const SDL_Scancode scancode = Wayland_GetScancodeForKey(input, *key);
17651736
const SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false);
17661737

17671738
switch (keycode) {
@@ -1774,6 +1745,7 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard,
17741745
case SDLK_LGUI:
17751746
case SDLK_RGUI:
17761747
case SDLK_MODE:
1748+
case SDLK_LEVEL5_SHIFT:
17771749
Wayland_HandleModifierKeys(input, scancode, true);
17781750
SDL_SendKeyboardKeyIgnoreModifiers(timestamp, input->keyboard_id, *key, scancode, true);
17791751
break;
@@ -1883,7 +1855,6 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
18831855
{
18841856
struct SDL_WaylandInput *input = data;
18851857
enum wl_keyboard_key_state state = state_w;
1886-
SDL_Scancode scancode = SDL_SCANCODE_UNKNOWN;
18871858
char text[8];
18881859
bool has_text = false;
18891860
bool handled_by_ime = false;
@@ -1909,10 +1880,11 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
19091880
keyboard_input_get_text(text, input, key, false, &handled_by_ime);
19101881
}
19111882

1912-
Wayland_get_scancode_from_key(input, key, &scancode);
1883+
const SDL_Scancode scancode = Wayland_GetScancodeForKey(input, key);
19131884
Wayland_HandleModifierKeys(input, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED);
19141885
Uint64 timestamp = Wayland_GetKeyboardTimestamp(input, time);
1915-
SDL_SendKeyboardKeyIgnoreModifiers(timestamp, input->keyboard_id, key, scancode, (state == WL_KEYBOARD_KEY_STATE_PRESSED));
1886+
1887+
SDL_SendKeyboardKeyIgnoreModifiers(timestamp, input->keyboard_id, key, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED);
19161888

19171889
if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
19181890
if (has_text && !(SDL_GetModState() & SDL_KMOD_CTRL)) {

0 commit comments

Comments
 (0)