900 lines
26 KiB
C
900 lines
26 KiB
C
/*
|
|
Simple DirectMedia Layer
|
|
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
|
|
|
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"
|
|
|
|
// General keyboard handling code for SDL
|
|
|
|
#include "SDL_events_c.h"
|
|
#include "SDL_keymap_c.h"
|
|
#include "../video/SDL_sysvideo.h"
|
|
|
|
// #define DEBUG_KEYBOARD
|
|
|
|
// Global keyboard information
|
|
|
|
#define KEYBOARD_HARDWARE 0x01
|
|
#define KEYBOARD_VIRTUAL 0x02
|
|
#define KEYBOARD_AUTORELEASE 0x04
|
|
#define KEYBOARD_IGNOREMODIFIERS 0x08
|
|
|
|
#define KEYBOARD_SOURCE_MASK (KEYBOARD_HARDWARE | KEYBOARD_AUTORELEASE)
|
|
|
|
#define KEYCODE_OPTION_HIDE_NUMPAD 0x01
|
|
#define KEYCODE_OPTION_FRENCH_NUMBERS 0x02
|
|
#define KEYCODE_OPTION_LATIN_LETTERS 0x04
|
|
#define DEFAULT_KEYCODE_OPTIONS (KEYCODE_OPTION_FRENCH_NUMBERS | KEYCODE_OPTION_LATIN_LETTERS)
|
|
|
|
typedef struct SDL_KeyboardInstance
|
|
{
|
|
SDL_KeyboardID instance_id;
|
|
char *name;
|
|
} SDL_KeyboardInstance;
|
|
|
|
typedef struct SDL_Keyboard
|
|
{
|
|
// Data common to all keyboards
|
|
SDL_Window *focus;
|
|
SDL_Keymod modstate;
|
|
Uint8 keysource[SDL_SCANCODE_COUNT];
|
|
SDL_bool keystate[SDL_SCANCODE_COUNT];
|
|
SDL_Keymap *keymap;
|
|
bool french_numbers;
|
|
bool latin_letters;
|
|
bool thai_keyboard;
|
|
Uint32 keycode_options;
|
|
bool autorelease_pending;
|
|
Uint64 hardware_timestamp;
|
|
int next_reserved_scancode;
|
|
} SDL_Keyboard;
|
|
|
|
static SDL_Keyboard SDL_keyboard;
|
|
static int SDL_keyboard_count;
|
|
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, "none")) {
|
|
if (SDL_strstr(hint, "hide_numpad")) {
|
|
keyboard->keycode_options |= KEYCODE_OPTION_HIDE_NUMPAD;
|
|
}
|
|
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
|
|
bool SDL_InitKeyboard(void)
|
|
{
|
|
SDL_AddHintCallback(SDL_HINT_KEYCODE_OPTIONS,
|
|
SDL_KeycodeOptionsChanged, &SDL_keyboard);
|
|
return true;
|
|
}
|
|
|
|
bool SDL_IsKeyboard(Uint16 vendor, Uint16 product, int num_keys)
|
|
{
|
|
const int REAL_KEYBOARD_KEY_COUNT = 50;
|
|
if (num_keys > 0 && num_keys < REAL_KEYBOARD_KEY_COUNT) {
|
|
return false;
|
|
}
|
|
|
|
// Eventually we'll have a blacklist of devices that enumerate as keyboards but aren't really
|
|
return true;
|
|
}
|
|
|
|
static int SDL_GetKeyboardIndex(SDL_KeyboardID keyboardID)
|
|
{
|
|
for (int i = 0; i < SDL_keyboard_count; ++i) {
|
|
if (keyboardID == SDL_keyboards[i].instance_id) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void SDL_AddKeyboard(SDL_KeyboardID keyboardID, const char *name, bool send_event)
|
|
{
|
|
int keyboard_index = SDL_GetKeyboardIndex(keyboardID);
|
|
if (keyboard_index >= 0) {
|
|
// We already know about this keyboard
|
|
return;
|
|
}
|
|
|
|
SDL_assert(keyboardID != 0);
|
|
|
|
SDL_KeyboardInstance *keyboards = (SDL_KeyboardInstance *)SDL_realloc(SDL_keyboards, (SDL_keyboard_count + 1) * sizeof(*keyboards));
|
|
if (!keyboards) {
|
|
return;
|
|
}
|
|
SDL_KeyboardInstance *instance = &keyboards[SDL_keyboard_count];
|
|
instance->instance_id = keyboardID;
|
|
instance->name = SDL_strdup(name ? name : "");
|
|
SDL_keyboards = keyboards;
|
|
++SDL_keyboard_count;
|
|
|
|
if (send_event) {
|
|
SDL_Event event;
|
|
SDL_zero(event);
|
|
event.type = SDL_EVENT_KEYBOARD_ADDED;
|
|
event.kdevice.which = keyboardID;
|
|
SDL_PushEvent(&event);
|
|
}
|
|
}
|
|
|
|
void SDL_RemoveKeyboard(SDL_KeyboardID keyboardID, bool send_event)
|
|
{
|
|
int keyboard_index = SDL_GetKeyboardIndex(keyboardID);
|
|
if (keyboard_index < 0) {
|
|
// We don't know about this keyboard
|
|
return;
|
|
}
|
|
|
|
SDL_free(SDL_keyboards[keyboard_index].name);
|
|
|
|
if (keyboard_index != SDL_keyboard_count - 1) {
|
|
SDL_memcpy(&SDL_keyboards[keyboard_index], &SDL_keyboards[keyboard_index + 1], (SDL_keyboard_count - keyboard_index - 1) * sizeof(SDL_keyboards[keyboard_index]));
|
|
}
|
|
--SDL_keyboard_count;
|
|
|
|
if (send_event) {
|
|
SDL_Event event;
|
|
SDL_zero(event);
|
|
event.type = SDL_EVENT_KEYBOARD_REMOVED;
|
|
event.kdevice.which = keyboardID;
|
|
SDL_PushEvent(&event);
|
|
}
|
|
}
|
|
|
|
SDL_bool SDL_HasKeyboard(void)
|
|
{
|
|
return (SDL_keyboard_count > 0);
|
|
}
|
|
|
|
SDL_KeyboardID *SDL_GetKeyboards(int *count)
|
|
{
|
|
int i;
|
|
SDL_KeyboardID *keyboards;
|
|
|
|
keyboards = (SDL_JoystickID *)SDL_malloc((SDL_keyboard_count + 1) * sizeof(*keyboards));
|
|
if (keyboards) {
|
|
if (count) {
|
|
*count = SDL_keyboard_count;
|
|
}
|
|
|
|
for (i = 0; i < SDL_keyboard_count; ++i) {
|
|
keyboards[i] = SDL_keyboards[i].instance_id;
|
|
}
|
|
keyboards[i] = 0;
|
|
} else {
|
|
if (count) {
|
|
*count = 0;
|
|
}
|
|
}
|
|
|
|
return keyboards;
|
|
}
|
|
|
|
const char *SDL_GetKeyboardNameForID(SDL_KeyboardID instance_id)
|
|
{
|
|
int keyboard_index = SDL_GetKeyboardIndex(instance_id);
|
|
if (keyboard_index < 0) {
|
|
return NULL;
|
|
}
|
|
return SDL_GetPersistentString(SDL_keyboards[keyboard_index].name);
|
|
}
|
|
|
|
void SDL_ResetKeyboard(void)
|
|
{
|
|
SDL_Keyboard *keyboard = &SDL_keyboard;
|
|
int scancode;
|
|
|
|
#ifdef DEBUG_KEYBOARD
|
|
printf("Resetting keyboard\n");
|
|
#endif
|
|
for (scancode = SDL_SCANCODE_UNKNOWN; scancode < SDL_SCANCODE_COUNT; ++scancode) {
|
|
if (keyboard->keystate[scancode]) {
|
|
SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, (SDL_Scancode)scancode, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
SDL_Keymap *SDL_GetCurrentKeymap(void)
|
|
{
|
|
SDL_Keyboard *keyboard = &SDL_keyboard;
|
|
|
|
if (keyboard->thai_keyboard) {
|
|
// Thai keyboards are QWERTY plus Thai characters, use the default QWERTY keymap
|
|
return NULL;
|
|
}
|
|
|
|
if ((keyboard->keycode_options & KEYCODE_OPTION_LATIN_LETTERS) &&
|
|
!keyboard->latin_letters) {
|
|
// We'll use the default QWERTY keymap
|
|
return NULL;
|
|
}
|
|
|
|
return keyboard->keymap;
|
|
}
|
|
|
|
void SDL_SetKeymap(SDL_Keymap *keymap, bool send_event)
|
|
{
|
|
SDL_Keyboard *keyboard = &SDL_keyboard;
|
|
|
|
if (keyboard->keymap) {
|
|
SDL_DestroyKeymap(keyboard->keymap);
|
|
}
|
|
|
|
keyboard->keymap = keymap;
|
|
|
|
// Detect French number row (all symbols)
|
|
keyboard->french_numbers = 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 = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Detect non-Latin keymap
|
|
keyboard->thai_keyboard = false;
|
|
keyboard->latin_letters = false;
|
|
for (int i = SDL_SCANCODE_A; i <= SDL_SCANCODE_D; ++i) {
|
|
SDL_Keycode key = SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_NONE);
|
|
if (key <= 0xFF) {
|
|
keyboard->latin_letters = true;
|
|
break;
|
|
}
|
|
|
|
if (key >= 0x0E00 && key <= 0x0E7F) {
|
|
keyboard->thai_keyboard = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (send_event) {
|
|
SDL_SendKeymapChangedEvent();
|
|
}
|
|
}
|
|
|
|
static SDL_Scancode GetNextReservedScancode(void)
|
|
{
|
|
SDL_Keyboard *keyboard = &SDL_keyboard;
|
|
SDL_Scancode scancode;
|
|
|
|
if (keyboard->next_reserved_scancode && keyboard->next_reserved_scancode < SDL_SCANCODE_RESERVED + 100) {
|
|
scancode = (SDL_Scancode)keyboard->next_reserved_scancode;
|
|
} else {
|
|
scancode = SDL_SCANCODE_RESERVED;
|
|
}
|
|
keyboard->next_reserved_scancode = (int)scancode + 1;
|
|
|
|
return scancode;
|
|
}
|
|
|
|
static void SetKeymapEntry(SDL_Scancode scancode, SDL_Keymod modstate, SDL_Keycode keycode)
|
|
{
|
|
SDL_Keyboard *keyboard = &SDL_keyboard;
|
|
|
|
if (!keyboard->keymap) {
|
|
keyboard->keymap = SDL_CreateKeymap();
|
|
}
|
|
|
|
SDL_SetKeymapEntry(keyboard->keymap, scancode, modstate, keycode);
|
|
}
|
|
|
|
SDL_Window *SDL_GetKeyboardFocus(void)
|
|
{
|
|
SDL_Keyboard *keyboard = &SDL_keyboard;
|
|
|
|
return keyboard->focus;
|
|
}
|
|
|
|
bool SDL_SetKeyboardFocus(SDL_Window *window)
|
|
{
|
|
SDL_VideoDevice *video = SDL_GetVideoDevice();
|
|
SDL_Keyboard *keyboard = &SDL_keyboard;
|
|
|
|
if (window) {
|
|
if (!SDL_ObjectValid(window, SDL_OBJECT_TYPE_WINDOW) || window->is_destroying) {
|
|
return SDL_SetError("Invalid window");
|
|
}
|
|
}
|
|
|
|
if (keyboard->focus && !window) {
|
|
// We won't get anymore keyboard messages, so reset keyboard state
|
|
SDL_ResetKeyboard();
|
|
}
|
|
|
|
// See if the current window has lost focus
|
|
if (keyboard->focus && keyboard->focus != window) {
|
|
SDL_SendWindowEvent(keyboard->focus, SDL_EVENT_WINDOW_FOCUS_LOST, 0, 0);
|
|
|
|
// Ensures IME compositions are committed
|
|
if (SDL_TextInputActive(keyboard->focus)) {
|
|
if (video && video->StopTextInput) {
|
|
video->StopTextInput(video, keyboard->focus);
|
|
}
|
|
}
|
|
}
|
|
|
|
keyboard->focus = window;
|
|
|
|
if (keyboard->focus) {
|
|
SDL_SendWindowEvent(keyboard->focus, SDL_EVENT_WINDOW_FOCUS_GAINED, 0, 0);
|
|
|
|
if (SDL_TextInputActive(keyboard->focus)) {
|
|
if (video && video->StartTextInput) {
|
|
video->StartTextInput(video, keyboard->focus, keyboard->focus->text_input_props);
|
|
}
|
|
}
|
|
}
|
|
|
|
SDL_UpdateRelativeMouseMode();
|
|
|
|
return true;
|
|
}
|
|
|
|
static SDL_Keycode SDL_ConvertNumpadKeycode(SDL_Keycode keycode, bool numlock)
|
|
{
|
|
switch (keycode) {
|
|
case SDLK_KP_DIVIDE:
|
|
return SDLK_SLASH;
|
|
case SDLK_KP_MULTIPLY:
|
|
return SDLK_ASTERISK;
|
|
case SDLK_KP_MINUS:
|
|
return SDLK_MINUS;
|
|
case SDLK_KP_PLUS:
|
|
return SDLK_PLUS;
|
|
case SDLK_KP_ENTER:
|
|
return SDLK_RETURN;
|
|
case SDLK_KP_1:
|
|
return numlock ? SDLK_1 : SDLK_END;
|
|
case SDLK_KP_2:
|
|
return numlock ? SDLK_2 : SDLK_DOWN;
|
|
case SDLK_KP_3:
|
|
return numlock ? SDLK_3 : SDLK_PAGEDOWN;
|
|
case SDLK_KP_4:
|
|
return numlock ? SDLK_4 : SDLK_LEFT;
|
|
case SDLK_KP_5:
|
|
return numlock ? SDLK_5 : SDLK_CLEAR;
|
|
case SDLK_KP_6:
|
|
return numlock ? SDLK_6 : SDLK_RIGHT;
|
|
case SDLK_KP_7:
|
|
return numlock ? SDLK_7 : SDLK_HOME;
|
|
case SDLK_KP_8:
|
|
return numlock ? SDLK_8 : SDLK_UP;
|
|
case SDLK_KP_9:
|
|
return numlock ? SDLK_9 : SDLK_PAGEUP;
|
|
case SDLK_KP_0:
|
|
return numlock ? SDLK_0 : SDLK_INSERT;
|
|
case SDLK_KP_PERIOD:
|
|
return numlock ? SDLK_PERIOD : SDLK_DELETE;
|
|
case SDLK_KP_EQUALS:
|
|
return SDLK_EQUALS;
|
|
case SDLK_KP_COMMA:
|
|
return SDLK_COMMA;
|
|
case SDLK_KP_EQUALSAS400:
|
|
return SDLK_EQUALS;
|
|
case SDLK_KP_LEFTPAREN:
|
|
return SDLK_LEFTPAREN;
|
|
case SDLK_KP_RIGHTPAREN:
|
|
return SDLK_RIGHTPAREN;
|
|
case SDLK_KP_LEFTBRACE:
|
|
return SDLK_LEFTBRACE;
|
|
case SDLK_KP_RIGHTBRACE:
|
|
return SDLK_RIGHTBRACE;
|
|
case SDLK_KP_TAB:
|
|
return SDLK_TAB;
|
|
case SDLK_KP_BACKSPACE:
|
|
return SDLK_BACKSPACE;
|
|
case SDLK_KP_A:
|
|
return SDLK_A;
|
|
case SDLK_KP_B:
|
|
return SDLK_B;
|
|
case SDLK_KP_C:
|
|
return SDLK_C;
|
|
case SDLK_KP_D:
|
|
return SDLK_D;
|
|
case SDLK_KP_E:
|
|
return SDLK_E;
|
|
case SDLK_KP_F:
|
|
return SDLK_F;
|
|
case SDLK_KP_PERCENT:
|
|
return SDLK_PERCENT;
|
|
case SDLK_KP_LESS:
|
|
return SDLK_LESS;
|
|
case SDLK_KP_GREATER:
|
|
return SDLK_GREATER;
|
|
case SDLK_KP_AMPERSAND:
|
|
return SDLK_AMPERSAND;
|
|
case SDLK_KP_COLON:
|
|
return SDLK_COLON;
|
|
case SDLK_KP_HASH:
|
|
return SDLK_HASH;
|
|
case SDLK_KP_SPACE:
|
|
return SDLK_SPACE;
|
|
case SDLK_KP_AT:
|
|
return SDLK_AT;
|
|
case SDLK_KP_EXCLAM:
|
|
return SDLK_EXCLAIM;
|
|
case SDLK_KP_PLUSMINUS:
|
|
return SDLK_PLUSMINUS;
|
|
default:
|
|
return keycode;
|
|
}
|
|
}
|
|
|
|
SDL_Keycode SDL_GetKeyFromScancode(SDL_Scancode scancode, SDL_Keymod modstate, SDL_bool key_event)
|
|
{
|
|
SDL_Keyboard *keyboard = &SDL_keyboard;
|
|
|
|
if (key_event) {
|
|
SDL_Keymap *keymap = SDL_GetCurrentKeymap();
|
|
bool numlock = (modstate & SDL_KMOD_NUM) != 0;
|
|
SDL_Keycode keycode;
|
|
|
|
// We won't be applying any modifiers by default
|
|
modstate = SDL_KMOD_NONE;
|
|
|
|
if ((keyboard->keycode_options & KEYCODE_OPTION_FRENCH_NUMBERS) &&
|
|
keyboard->french_numbers &&
|
|
(scancode >= SDL_SCANCODE_1 && scancode <= SDL_SCANCODE_0)) {
|
|
// Add the shift state to generate a numeric keycode
|
|
modstate |= SDL_KMOD_SHIFT;
|
|
}
|
|
|
|
keycode = SDL_GetKeymapKeycode(keymap, scancode, modstate);
|
|
|
|
if (keyboard->keycode_options & KEYCODE_OPTION_HIDE_NUMPAD) {
|
|
keycode = SDL_ConvertNumpadKeycode(keycode, numlock);
|
|
}
|
|
return keycode;
|
|
}
|
|
|
|
return SDL_GetKeymapKeycode(keyboard->keymap, scancode, modstate);
|
|
}
|
|
|
|
SDL_Scancode SDL_GetScancodeFromKey(SDL_Keycode key, SDL_Keymod *modstate)
|
|
{
|
|
SDL_Keyboard *keyboard = &SDL_keyboard;
|
|
|
|
return SDL_GetKeymapScancode(keyboard->keymap, key, modstate);
|
|
}
|
|
|
|
static bool SDL_SendKeyboardKeyInternal(Uint64 timestamp, Uint32 flags, SDL_KeyboardID keyboardID, int rawcode, SDL_Scancode scancode, bool down)
|
|
{
|
|
SDL_Keyboard *keyboard = &SDL_keyboard;
|
|
bool posted = false;
|
|
SDL_Keycode keycode = SDLK_UNKNOWN;
|
|
Uint32 type;
|
|
bool repeat = false;
|
|
const Uint8 source = flags & KEYBOARD_SOURCE_MASK;
|
|
|
|
#ifdef DEBUG_KEYBOARD
|
|
printf("The '%s' key has been %s\n", SDL_GetScancodeName(scancode), down ? "pressed" : "released");
|
|
#endif
|
|
|
|
// Figure out what type of event this is
|
|
if (down) {
|
|
type = SDL_EVENT_KEY_DOWN;
|
|
} else {
|
|
type = SDL_EVENT_KEY_UP;
|
|
}
|
|
|
|
if (scancode > SDL_SCANCODE_UNKNOWN && scancode < SDL_SCANCODE_COUNT) {
|
|
// Drop events that don't change state
|
|
if (down) {
|
|
if (keyboard->keystate[scancode]) {
|
|
if (!(keyboard->keysource[scancode] & source)) {
|
|
keyboard->keysource[scancode] |= source;
|
|
return false;
|
|
}
|
|
repeat = true;
|
|
}
|
|
keyboard->keysource[scancode] |= source;
|
|
} else {
|
|
if (!keyboard->keystate[scancode]) {
|
|
return false;
|
|
}
|
|
keyboard->keysource[scancode] = 0;
|
|
}
|
|
|
|
// Update internal keyboard state
|
|
keyboard->keystate[scancode] = down;
|
|
|
|
keycode = SDL_GetKeyFromScancode(scancode, keyboard->modstate, true);
|
|
|
|
} else if (rawcode == 0) {
|
|
// Nothing to do!
|
|
return false;
|
|
}
|
|
|
|
if (source == KEYBOARD_HARDWARE) {
|
|
keyboard->hardware_timestamp = SDL_GetTicks();
|
|
} else if (source == KEYBOARD_AUTORELEASE) {
|
|
keyboard->autorelease_pending = true;
|
|
}
|
|
|
|
// Update modifiers state if applicable
|
|
if (!(flags & KEYBOARD_IGNOREMODIFIERS) && !repeat) {
|
|
SDL_Keymod modifier;
|
|
|
|
switch (keycode) {
|
|
case SDLK_LCTRL:
|
|
modifier = SDL_KMOD_LCTRL;
|
|
break;
|
|
case SDLK_RCTRL:
|
|
modifier = SDL_KMOD_RCTRL;
|
|
break;
|
|
case SDLK_LSHIFT:
|
|
modifier = SDL_KMOD_LSHIFT;
|
|
break;
|
|
case SDLK_RSHIFT:
|
|
modifier = SDL_KMOD_RSHIFT;
|
|
break;
|
|
case SDLK_LALT:
|
|
modifier = SDL_KMOD_LALT;
|
|
break;
|
|
case SDLK_RALT:
|
|
modifier = SDL_KMOD_RALT;
|
|
break;
|
|
case SDLK_LGUI:
|
|
modifier = SDL_KMOD_LGUI;
|
|
break;
|
|
case SDLK_RGUI:
|
|
modifier = SDL_KMOD_RGUI;
|
|
break;
|
|
case SDLK_MODE:
|
|
modifier = SDL_KMOD_MODE;
|
|
break;
|
|
default:
|
|
modifier = SDL_KMOD_NONE;
|
|
break;
|
|
}
|
|
if (SDL_EVENT_KEY_DOWN == type) {
|
|
switch (keycode) {
|
|
case SDLK_NUMLOCKCLEAR:
|
|
keyboard->modstate ^= SDL_KMOD_NUM;
|
|
break;
|
|
case SDLK_CAPSLOCK:
|
|
keyboard->modstate ^= SDL_KMOD_CAPS;
|
|
break;
|
|
case SDLK_SCROLLLOCK:
|
|
keyboard->modstate ^= SDL_KMOD_SCROLL;
|
|
break;
|
|
default:
|
|
keyboard->modstate |= modifier;
|
|
break;
|
|
}
|
|
} else {
|
|
keyboard->modstate &= ~modifier;
|
|
}
|
|
}
|
|
|
|
// Post the event, if desired
|
|
if (SDL_EventEnabled(type)) {
|
|
SDL_Event event;
|
|
event.type = type;
|
|
event.common.timestamp = timestamp;
|
|
event.key.scancode = scancode;
|
|
event.key.key = keycode;
|
|
event.key.mod = keyboard->modstate;
|
|
event.key.raw = (Uint16)rawcode;
|
|
event.key.down = down;
|
|
event.key.repeat = repeat;
|
|
event.key.windowID = keyboard->focus ? keyboard->focus->id : 0;
|
|
event.key.which = keyboardID;
|
|
posted = SDL_PushEvent(&event);
|
|
}
|
|
|
|
/* If the keyboard is grabbed and the grabbed window is in full-screen,
|
|
minimize the window when we receive Alt+Tab, unless the application
|
|
has explicitly opted out of this behavior. */
|
|
if (keycode == SDLK_TAB && down &&
|
|
(keyboard->modstate & SDL_KMOD_ALT) &&
|
|
keyboard->focus &&
|
|
(keyboard->focus->flags & SDL_WINDOW_KEYBOARD_GRABBED) &&
|
|
(keyboard->focus->flags & SDL_WINDOW_FULLSCREEN) &&
|
|
SDL_GetHintBoolean(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED, true)) {
|
|
/* We will temporarily forfeit our grab by minimizing our window,
|
|
allowing the user to escape the application */
|
|
SDL_MinimizeWindow(keyboard->focus);
|
|
}
|
|
|
|
return posted;
|
|
}
|
|
|
|
void SDL_SendKeyboardUnicodeKey(Uint64 timestamp, Uint32 ch)
|
|
{
|
|
SDL_Keyboard *keyboard = &SDL_keyboard;
|
|
SDL_Keymod modstate = SDL_KMOD_NONE;
|
|
SDL_Scancode scancode = SDL_GetKeymapScancode(keyboard->keymap, ch, &modstate);
|
|
|
|
// Make sure we have this keycode in our keymap
|
|
if (scancode == SDL_SCANCODE_UNKNOWN && ch < SDLK_SCANCODE_MASK) {
|
|
scancode = GetNextReservedScancode();
|
|
SetKeymapEntry(scancode, modstate, ch);
|
|
}
|
|
|
|
if (modstate & SDL_KMOD_SHIFT) {
|
|
// If the character uses shift, press shift down
|
|
SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_VIRTUAL, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_LSHIFT, true);
|
|
}
|
|
|
|
// Send a keydown and keyup for the character
|
|
SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_VIRTUAL, SDL_GLOBAL_KEYBOARD_ID, 0, scancode, true);
|
|
SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_VIRTUAL, SDL_GLOBAL_KEYBOARD_ID, 0, scancode, false);
|
|
|
|
if (modstate & SDL_KMOD_SHIFT) {
|
|
// If the character uses shift, release shift
|
|
SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_VIRTUAL, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_LSHIFT, false);
|
|
}
|
|
}
|
|
|
|
bool SDL_SendKeyboardKey(Uint64 timestamp, SDL_KeyboardID keyboardID, int rawcode, SDL_Scancode scancode, bool down)
|
|
{
|
|
return SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_HARDWARE, keyboardID, rawcode, scancode, down);
|
|
}
|
|
|
|
bool SDL_SendKeyboardKeyAndKeycode(Uint64 timestamp, SDL_KeyboardID keyboardID, int rawcode, SDL_Scancode scancode, SDL_Keycode keycode, bool down)
|
|
{
|
|
if (down) {
|
|
// Make sure we have this keycode in our keymap
|
|
SetKeymapEntry(scancode, SDL_GetModState(), keycode);
|
|
}
|
|
|
|
return SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_HARDWARE, keyboardID, rawcode, scancode, down);
|
|
}
|
|
|
|
bool SDL_SendKeyboardKeyIgnoreModifiers(Uint64 timestamp, SDL_KeyboardID keyboardID, int rawcode, SDL_Scancode scancode, bool down)
|
|
{
|
|
return SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_HARDWARE | KEYBOARD_IGNOREMODIFIERS, keyboardID, rawcode, scancode, down);
|
|
}
|
|
|
|
bool SDL_SendKeyboardKeyAutoRelease(Uint64 timestamp, SDL_Scancode scancode)
|
|
{
|
|
return SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_AUTORELEASE, SDL_GLOBAL_KEYBOARD_ID, 0, scancode, false);
|
|
}
|
|
|
|
void SDL_ReleaseAutoReleaseKeys(void)
|
|
{
|
|
SDL_Keyboard *keyboard = &SDL_keyboard;
|
|
int scancode;
|
|
|
|
if (keyboard->autorelease_pending) {
|
|
for (scancode = SDL_SCANCODE_UNKNOWN; scancode < SDL_SCANCODE_COUNT; ++scancode) {
|
|
if (keyboard->keysource[scancode] == KEYBOARD_AUTORELEASE) {
|
|
SDL_SendKeyboardKeyInternal(0, KEYBOARD_AUTORELEASE, SDL_GLOBAL_KEYBOARD_ID, 0, (SDL_Scancode)scancode, false);
|
|
}
|
|
}
|
|
keyboard->autorelease_pending = false;
|
|
}
|
|
|
|
if (keyboard->hardware_timestamp) {
|
|
// Keep hardware keyboard "active" for 250 ms
|
|
if (SDL_GetTicks() >= keyboard->hardware_timestamp + 250) {
|
|
keyboard->hardware_timestamp = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SDL_HardwareKeyboardKeyPressed(void)
|
|
{
|
|
SDL_Keyboard *keyboard = &SDL_keyboard;
|
|
int scancode;
|
|
|
|
for (scancode = SDL_SCANCODE_UNKNOWN; scancode < SDL_SCANCODE_COUNT; ++scancode) {
|
|
if (keyboard->keysource[scancode] & KEYBOARD_HARDWARE) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return keyboard->hardware_timestamp ? true : false;
|
|
}
|
|
|
|
void SDL_SendKeyboardText(const char *text)
|
|
{
|
|
SDL_Keyboard *keyboard = &SDL_keyboard;
|
|
|
|
if (!SDL_TextInputActive(keyboard->focus)) {
|
|
return;
|
|
}
|
|
|
|
if (!text || !*text) {
|
|
return;
|
|
}
|
|
|
|
// Don't post text events for unprintable characters
|
|
if (SDL_iscntrl((unsigned char)*text)) {
|
|
return;
|
|
}
|
|
|
|
// Post the event, if desired
|
|
if (SDL_EventEnabled(SDL_EVENT_TEXT_INPUT)) {
|
|
SDL_Event event;
|
|
event.type = SDL_EVENT_TEXT_INPUT;
|
|
event.common.timestamp = 0;
|
|
event.text.windowID = keyboard->focus ? keyboard->focus->id : 0;
|
|
event.text.text = SDL_CreateTemporaryString(text);
|
|
if (!event.text.text) {
|
|
return;
|
|
}
|
|
SDL_PushEvent(&event);
|
|
}
|
|
}
|
|
|
|
void SDL_SendEditingText(const char *text, int start, int length)
|
|
{
|
|
SDL_Keyboard *keyboard = &SDL_keyboard;
|
|
|
|
if (!SDL_TextInputActive(keyboard->focus)) {
|
|
return;
|
|
}
|
|
|
|
if (!text) {
|
|
return;
|
|
}
|
|
|
|
// Post the event, if desired
|
|
if (SDL_EventEnabled(SDL_EVENT_TEXT_EDITING)) {
|
|
SDL_Event event;
|
|
|
|
event.type = SDL_EVENT_TEXT_EDITING;
|
|
event.common.timestamp = 0;
|
|
event.edit.windowID = keyboard->focus ? keyboard->focus->id : 0;
|
|
event.edit.start = start;
|
|
event.edit.length = length;
|
|
event.edit.text = SDL_CreateTemporaryString(text);
|
|
if (!event.edit.text) {
|
|
return;
|
|
}
|
|
SDL_PushEvent(&event);
|
|
}
|
|
}
|
|
|
|
static const char * const *CreateCandidatesForEvent(char **candidates, int num_candidates)
|
|
{
|
|
const char **event_candidates;
|
|
int i;
|
|
char *ptr;
|
|
size_t total_length = (num_candidates + 1) * sizeof(*event_candidates);
|
|
|
|
for (i = 0; i < num_candidates; ++i) {
|
|
size_t length = SDL_strlen(candidates[i]) + 1;
|
|
|
|
total_length += length;
|
|
}
|
|
|
|
event_candidates = (const char **)SDL_AllocateTemporaryMemory(total_length);
|
|
if (!event_candidates) {
|
|
return NULL;
|
|
}
|
|
ptr = (char *)(event_candidates + (num_candidates + 1));
|
|
|
|
for (i = 0; i < num_candidates; ++i) {
|
|
size_t length = SDL_strlen(candidates[i]) + 1;
|
|
|
|
event_candidates[i] = ptr;
|
|
SDL_memcpy(ptr, candidates[i], length);
|
|
ptr += length;
|
|
}
|
|
event_candidates[i] = NULL;
|
|
|
|
return event_candidates;
|
|
}
|
|
|
|
void SDL_SendEditingTextCandidates(char **candidates, int num_candidates, int selected_candidate, bool horizontal)
|
|
{
|
|
SDL_Keyboard *keyboard = &SDL_keyboard;
|
|
|
|
if (!SDL_TextInputActive(keyboard->focus)) {
|
|
return;
|
|
}
|
|
|
|
// Post the event, if desired
|
|
if (SDL_EventEnabled(SDL_EVENT_TEXT_EDITING_CANDIDATES)) {
|
|
SDL_Event event;
|
|
|
|
event.type = SDL_EVENT_TEXT_EDITING_CANDIDATES;
|
|
event.common.timestamp = 0;
|
|
event.edit.windowID = keyboard->focus ? keyboard->focus->id : 0;
|
|
if (num_candidates > 0) {
|
|
const char * const *event_candidates = CreateCandidatesForEvent(candidates, num_candidates);
|
|
if (!event_candidates) {
|
|
return;
|
|
}
|
|
event.edit_candidates.candidates = event_candidates;
|
|
event.edit_candidates.num_candidates = num_candidates;
|
|
event.edit_candidates.selected_candidate = selected_candidate;
|
|
event.edit_candidates.horizontal = horizontal;
|
|
} else {
|
|
event.edit_candidates.candidates = NULL;
|
|
event.edit_candidates.num_candidates = 0;
|
|
event.edit_candidates.selected_candidate = -1;
|
|
event.edit_candidates.horizontal = false;
|
|
}
|
|
SDL_PushEvent(&event);
|
|
}
|
|
}
|
|
|
|
void SDL_QuitKeyboard(void)
|
|
{
|
|
for (int i = SDL_keyboard_count; i--;) {
|
|
SDL_RemoveKeyboard(SDL_keyboards[i].instance_id, false);
|
|
}
|
|
SDL_free(SDL_keyboards);
|
|
SDL_keyboards = NULL;
|
|
|
|
if (SDL_keyboard.keymap) {
|
|
SDL_DestroyKeymap(SDL_keyboard.keymap);
|
|
SDL_keyboard.keymap = NULL;
|
|
}
|
|
|
|
SDL_RemoveHintCallback(SDL_HINT_KEYCODE_OPTIONS,
|
|
SDL_KeycodeOptionsChanged, &SDL_keyboard);
|
|
}
|
|
|
|
const SDL_bool *SDL_GetKeyboardState(int *numkeys)
|
|
{
|
|
SDL_Keyboard *keyboard = &SDL_keyboard;
|
|
|
|
if (numkeys != (int *)0) {
|
|
*numkeys = SDL_SCANCODE_COUNT;
|
|
}
|
|
return keyboard->keystate;
|
|
}
|
|
|
|
SDL_Keymod SDL_GetModState(void)
|
|
{
|
|
SDL_Keyboard *keyboard = &SDL_keyboard;
|
|
|
|
return keyboard->modstate;
|
|
}
|
|
|
|
void SDL_SetModState(SDL_Keymod modstate)
|
|
{
|
|
SDL_Keyboard *keyboard = &SDL_keyboard;
|
|
|
|
keyboard->modstate = modstate;
|
|
}
|
|
|
|
// Note that SDL_ToggleModState() is not a public API. SDL_SetModState() is.
|
|
void SDL_ToggleModState(const SDL_Keymod modstate, const bool toggle)
|
|
{
|
|
SDL_Keyboard *keyboard = &SDL_keyboard;
|
|
if (toggle) {
|
|
keyboard->modstate |= modstate;
|
|
} else {
|
|
keyboard->modstate &= ~modstate;
|
|
}
|
|
}
|
|
|