The keycode in key events is affected by modifiers by default.

This behavior can be customized with SDL_HINT_KEYCODE_OPTIONS.
This commit is contained in:
Sam Lantinga 2024-06-22 00:04:33 -07:00
parent 1e81424b3d
commit 90034b16dc
6 changed files with 149 additions and 29 deletions

View file

@ -362,6 +362,8 @@ now looks like this:
SDL_Keymod mod = event.key.mod; SDL_Keymod mod = event.key.mod;
``` ```
The keycode in key events is affected by modifiers by default. e.g. pressing the A key would generate the keycode `SDLK_a`, or 'a', and pressing it while holding the shift key would generate the keycode `SDLK_A`, or 'A'. This behavior can be customized with `SDL_HINT_KEYCODE_OPTIONS`.
The gamepad event structures caxis, cbutton, cdevice, ctouchpad, and csensor have been renamed gaxis, gbutton, gdevice, gtouchpad, and gsensor. The gamepad event structures caxis, cbutton, cdevice, ctouchpad, and csensor have been renamed gaxis, gbutton, gdevice, gtouchpad, and gsensor.
The mouseX and mouseY fields of SDL_MouseWheelEvent have been renamed mouse_x and mouse_y. The mouseX and mouseY fields of SDL_MouseWheelEvent have been renamed mouse_x and mouse_y.

View file

@ -2003,6 +2003,28 @@ extern "C" {
*/ */
#define SDL_HINT_JOYSTICK_ZERO_CENTERED_DEVICES "SDL_JOYSTICK_ZERO_CENTERED_DEVICES" #define SDL_HINT_JOYSTICK_ZERO_CENTERED_DEVICES "SDL_JOYSTICK_ZERO_CENTERED_DEVICES"
/**
* A variable that controls keycode representation in keyboard events.
*
* This variable is a comma separated set of options for translating keycodes in events:
*
* - "unmodified": The keycode is the symbol generated by pressing the key without any modifiers applied. e.g. Shift+A would yield the keycode SDLK_a, or 'a'.
* - "modified": The keycode is the symbol generated by pressing the key with modifiers applied. e.g. Shift+A would yield the keycode SDLK_A, or 'A'.
* - "french_numbers": The number row on French keyboards is inverted, so pressing the 1 key would yield the keycode SDLK_1, or '1', instead of SDLK_AMPERSAND, or '&'
* - "latin_letters": For keyboards using non-Latin letters, such as Russian or Thai, the letter keys generate keycodes as though it had an en_US layout. e.g. pressing the key associated with SDL_SCANCODE_A on a Russian keyboard would yield 'a' instead of 'ф'.
*
* The default value for this hint is equivalent to "modified,french_numbers"
*
* Some platforms like Emscripten only provide modified keycodes and the options are not used.
*
* These options do not affect the return value of SDL_GetKeyFromScancode() or SDL_GetScancodeFromKey(), they just apply to the keycode included in key events.
*
* This hint can be set anytime.
*
* \since This hint is available since SDL 3.0.0.
*/
#define SDL_HINT_KEYCODE_OPTIONS "SDL_KEYCODE_OPTIONS"
/** /**
* A variable that controls what KMSDRM device to use. * A variable that controls what KMSDRM device to use.
* *

View file

@ -30,16 +30,18 @@
/* Global keyboard information */ /* Global keyboard information */
typedef enum #define KEYBOARD_HARDWARE 0x01
{ #define KEYBOARD_VIRTUAL 0x02
KEYBOARD_HARDWARE = 0x01, #define KEYBOARD_AUTORELEASE 0x04
KEYBOARD_VIRTUAL = 0x02, #define KEYBOARD_IGNOREMODIFIERS 0x0
KEYBOARD_AUTORELEASE = 0x04,
KEYBOARD_IGNOREMODIFIERS = 0x08
} SDL_KeyboardFlags;
#define KEYBOARD_SOURCE_MASK (KEYBOARD_HARDWARE | KEYBOARD_AUTORELEASE) #define KEYBOARD_SOURCE_MASK (KEYBOARD_HARDWARE | KEYBOARD_AUTORELEASE)
#define KEYCODE_OPTION_APPLY_MODIFIERS 0x01
#define KEYCODE_OPTION_FRENCH_NUMBERS 0x02
#define KEYCODE_OPTION_LATIN_LETTERS 0x04
#define DEFAULT_KEYCODE_OPTIONS (KEYCODE_OPTION_APPLY_MODIFIERS | KEYCODE_OPTION_FRENCH_NUMBERS)
typedef struct SDL_KeyboardInstance typedef struct SDL_KeyboardInstance
{ {
SDL_KeyboardID instance_id; SDL_KeyboardID instance_id;
@ -54,6 +56,9 @@ typedef struct SDL_Keyboard
Uint8 keysource[SDL_NUM_SCANCODES]; Uint8 keysource[SDL_NUM_SCANCODES];
Uint8 keystate[SDL_NUM_SCANCODES]; Uint8 keystate[SDL_NUM_SCANCODES];
SDL_Keymap *keymap; SDL_Keymap *keymap;
SDL_bool french_numbers;
SDL_bool non_latin_letters;
Uint32 keycode_options;
SDL_bool autorelease_pending; SDL_bool autorelease_pending;
Uint64 hardware_timestamp; Uint64 hardware_timestamp;
} SDL_Keyboard; } SDL_Keyboard;
@ -62,9 +67,33 @@ static SDL_Keyboard SDL_keyboard;
static int SDL_keyboard_count; static int SDL_keyboard_count;
static SDL_KeyboardInstance *SDL_keyboards; static SDL_KeyboardInstance *SDL_keyboards;
static void SDLCALL SDL_KeycodeOptionsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
SDL_Keyboard *keyboard = (SDL_Keyboard *)userdata;
if (hint && *hint) {
keyboard->keycode_options = 0;
if (SDL_strstr(hint, "unmodified")) {
keyboard->keycode_options &= ~KEYCODE_OPTION_APPLY_MODIFIERS;
} else if (SDL_strstr(hint, "modified")) {
keyboard->keycode_options |= KEYCODE_OPTION_APPLY_MODIFIERS;
}
if (SDL_strstr(hint, "french_numbers")) {
keyboard->keycode_options |= KEYCODE_OPTION_FRENCH_NUMBERS;
}
if (SDL_strstr(hint, "latin_letters")) {
keyboard->keycode_options |= KEYCODE_OPTION_LATIN_LETTERS;
}
} else {
keyboard->keycode_options = DEFAULT_KEYCODE_OPTIONS;
}
}
/* Public functions */ /* Public functions */
int SDL_InitKeyboard(void) int SDL_InitKeyboard(void)
{ {
SDL_AddHintCallback(SDL_HINT_KEYCODE_OPTIONS,
SDL_KeycodeOptionsChanged, &SDL_keyboard);
return 0; return 0;
} }
@ -205,6 +234,25 @@ void SDL_SetKeymap(SDL_Keymap *keymap, SDL_bool send_event)
keyboard->keymap = keymap; keyboard->keymap = keymap;
// Detect French number row (all symbols)
keyboard->french_numbers = SDL_TRUE;
for (int i = SDL_SCANCODE_1; i <= SDL_SCANCODE_0; ++i) {
if (SDL_isdigit(SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_NONE)) ||
!SDL_isdigit(SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_SHIFT))) {
keyboard->french_numbers = SDL_FALSE;
break;
}
}
// Detect non-Latin keymap
keyboard->non_latin_letters = SDL_TRUE;
for (int i = SDL_SCANCODE_A; i <= SDL_SCANCODE_D; ++i) {
if (SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_NONE) <= 0xFF) {
keyboard->non_latin_letters = SDL_FALSE;
break;
}
}
if (send_event) { if (send_event) {
SDL_SendKeymapChangedEvent(); SDL_SendKeymapChangedEvent();
} }
@ -276,6 +324,40 @@ int SDL_SetKeyboardFocus(SDL_Window *window)
return 0; return 0;
} }
static SDL_Keycode SDL_GetEventKeycode(SDL_Keyboard *keyboard, SDL_Scancode scancode, SDL_Keymod modstate)
{
SDL_Keycode keycode;
if (scancode >= SDL_SCANCODE_A && scancode <= SDL_SCANCODE_Z) {
if (keyboard->non_latin_letters && (keyboard->keycode_options & KEYCODE_OPTION_LATIN_LETTERS)) {
if (keyboard->keycode_options & KEYCODE_OPTION_APPLY_MODIFIERS) {
keycode = SDL_GetDefaultKeyFromScancode(scancode, modstate);
} else {
keycode = SDL_GetDefaultKeyFromScancode(scancode, SDL_KMOD_NONE);
}
return keycode;
}
}
if (scancode >= SDL_SCANCODE_1 && scancode <= SDL_SCANCODE_0) {
if (keyboard->french_numbers && (keyboard->keycode_options & KEYCODE_OPTION_FRENCH_NUMBERS)) {
// Invert the shift state to generate the correct keycode
if (modstate & SDL_KMOD_SHIFT) {
modstate &= ~SDL_KMOD_SHIFT;
} else {
modstate |= SDL_KMOD_SHIFT;
}
}
}
if (keyboard->keycode_options & KEYCODE_OPTION_APPLY_MODIFIERS) {
keycode = SDL_GetKeyFromScancode(scancode, modstate);
} else {
keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE);
}
return keycode;
}
static int SDL_SendKeyboardKeyInternal(Uint64 timestamp, Uint32 flags, SDL_KeyboardID keyboardID, int rawcode, SDL_Scancode scancode, SDL_Keycode keycode, Uint8 state) static int SDL_SendKeyboardKeyInternal(Uint64 timestamp, Uint32 flags, SDL_KeyboardID keyboardID, int rawcode, SDL_Scancode scancode, SDL_Keycode keycode, Uint8 state)
{ {
SDL_Keyboard *keyboard = &SDL_keyboard; SDL_Keyboard *keyboard = &SDL_keyboard;
@ -325,7 +407,7 @@ static int SDL_SendKeyboardKeyInternal(Uint64 timestamp, Uint32 flags, SDL_Keybo
keyboard->keystate[scancode] = state; keyboard->keystate[scancode] = state;
if (keycode == SDLK_UNKNOWN) { if (keycode == SDLK_UNKNOWN) {
keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE); keycode = SDL_GetEventKeycode(keyboard, scancode, keyboard->modstate);
} }
} else if (keycode == SDLK_UNKNOWN && rawcode == 0) { } else if (keycode == SDLK_UNKNOWN && rawcode == 0) {
@ -589,6 +671,9 @@ void SDL_QuitKeyboard(void)
SDL_DestroyKeymap(SDL_keyboard.keymap); SDL_DestroyKeymap(SDL_keyboard.keymap);
SDL_keyboard.keymap = NULL; SDL_keyboard.keymap = NULL;
} }
SDL_DelHintCallback(SDL_HINT_KEYCODE_OPTIONS,
SDL_KeycodeOptionsChanged, &SDL_keyboard);
} }
const Uint8 *SDL_GetKeyboardState(int *numkeys) const Uint8 *SDL_GetKeyboardState(int *numkeys)

View file

@ -2198,6 +2198,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
} }
} }
break; break;
case SDLK_O:
case SDLK_o: case SDLK_o:
if (withControl) { if (withControl) {
/* Ctrl-O (or Ctrl-Shift-O) changes window opacity. */ /* Ctrl-O (or Ctrl-Shift-O) changes window opacity. */
@ -2215,6 +2216,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
} }
} }
break; break;
case SDLK_H:
case SDLK_h: case SDLK_h:
if (withControl) { if (withControl) {
/* Ctrl-H changes cursor visibility. */ /* Ctrl-H changes cursor visibility. */
@ -2225,6 +2227,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
} }
} }
break; break;
case SDLK_C:
case SDLK_c: case SDLK_c:
if (withAlt) { if (withAlt) {
/* Alt-C copy awesome text to the primary selection! */ /* Alt-C copy awesome text to the primary selection! */
@ -2250,6 +2253,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
break; break;
} }
break; break;
case SDLK_V:
case SDLK_v: case SDLK_v:
if (withAlt) { if (withAlt) {
/* Alt-V paste awesome text from the primary selection! */ /* Alt-V paste awesome text from the primary selection! */
@ -2277,6 +2281,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
} }
} }
break; break;
case SDLK_F:
case SDLK_f: case SDLK_f:
if (withControl) { if (withControl) {
/* Ctrl-F flash the window */ /* Ctrl-F flash the window */
@ -2286,6 +2291,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
} }
} }
break; break;
case SDLK_G:
case SDLK_g: case SDLK_g:
if (withControl) { if (withControl) {
/* Ctrl-G toggle mouse grab */ /* Ctrl-G toggle mouse grab */
@ -2295,6 +2301,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
} }
} }
break; break;
case SDLK_K:
case SDLK_k: case SDLK_k:
if (withControl) { if (withControl) {
/* Ctrl-K toggle keyboard grab */ /* Ctrl-K toggle keyboard grab */
@ -2304,6 +2311,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
} }
} }
break; break;
case SDLK_M:
case SDLK_m: case SDLK_m:
if (withControl) { if (withControl) {
/* Ctrl-M maximize */ /* Ctrl-M maximize */
@ -2326,12 +2334,14 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
} }
} }
break; break;
case SDLK_R:
case SDLK_r: case SDLK_r:
if (withControl) { if (withControl) {
/* Ctrl-R toggle mouse relative mode */ /* Ctrl-R toggle mouse relative mode */
SDL_SetRelativeMouseMode(!SDL_GetRelativeMouseMode()); SDL_SetRelativeMouseMode(!SDL_GetRelativeMouseMode());
} }
break; break;
case SDLK_T:
case SDLK_t: case SDLK_t:
if (withControl) { if (withControl) {
/* Ctrl-T toggle topmost mode */ /* Ctrl-T toggle topmost mode */
@ -2346,6 +2356,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
} }
} }
break; break;
case SDLK_Z:
case SDLK_z: case SDLK_z:
if (withControl) { if (withControl) {
/* Ctrl-Z minimize */ /* Ctrl-Z minimize */
@ -2385,6 +2396,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
} }
break; break;
case SDLK_B:
case SDLK_b: case SDLK_b:
if (withControl) { if (withControl) {
/* Ctrl-B toggle window border */ /* Ctrl-B toggle window border */
@ -2396,6 +2408,7 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event
} }
} }
break; break;
case SDLK_A:
case SDLK_a: case SDLK_a:
if (withControl) { if (withControl) {
/* Ctrl-A toggle aspect ratio */ /* Ctrl-A toggle aspect ratio */

View file

@ -234,7 +234,7 @@ static void loop(void)
SDL_Log("Cleared audio stream"); SDL_Log("Cleared audio stream");
} else if (sym == SDLK_s) { } else if (sym == SDLK_s) {
queue_audio(); queue_audio();
} else if (sym == SDLK_d) { } else if (sym == SDLK_d || sym == SDLK_D) {
float amount = 1.0f; float amount = 1.0f;
amount *= (e.key.mod & SDL_KMOD_CTRL) ? 10.0f : 1.0f; amount *= (e.key.mod & SDL_KMOD_CTRL) ? 10.0f : 1.0f;
amount *= (e.key.mod & SDL_KMOD_SHIFT) ? 10.0f : 1.0f; amount *= (e.key.mod & SDL_KMOD_SHIFT) ? 10.0f : 1.0f;

View file

@ -226,27 +226,25 @@ static void loop(void *arg)
break; break;
case SDL_EVENT_KEY_DOWN: case SDL_EVENT_KEY_DOWN:
switch (event.key.key) { switch (event.key.key) {
case 'l': case SDLK_L:
if (event.key.mod & SDL_KMOD_SHIFT) { num_lines = 0;
num_lines = 0;
} else {
add_line(
(float)SDL_rand_n(640),
(float)SDL_rand_n(480),
(float)SDL_rand_n(640),
(float)SDL_rand_n(480));
}
break; break;
case 'r': case SDLK_l:
if (event.key.mod & SDL_KMOD_SHIFT) { add_line(
num_rects = 0; (float)SDL_rand_n(640),
} else { (float)SDL_rand_n(480),
add_rect( (float)SDL_rand_n(640),
(float)SDL_rand_n(640), (float)SDL_rand_n(480));
(float)SDL_rand_n(480), break;
(float)SDL_rand_n(640), case SDLK_R:
(float)SDL_rand_n(480)); num_rects = 0;
} break;
case SDLK_r:
add_rect(
(float)SDL_rand_n(640),
(float)SDL_rand_n(480),
(float)SDL_rand_n(640),
(float)SDL_rand_n(480));
break; break;
default: default:
break; break;