diff --git a/core/bind/core_bind.cpp b/core/bind/core_bind.cpp index fc376f73e9b..9ee452ba289 100644 --- a/core/bind/core_bind.cpp +++ b/core/bind/core_bind.cpp @@ -573,6 +573,10 @@ String _OS::keyboard_get_layout_name(int p_index) const { return OS::get_singleton()->keyboard_get_layout_name(p_index); } +uint32_t _OS::keyboard_get_scancode_from_physical(uint32_t p_scancode) const { + return OS::get_singleton()->keyboard_get_scancode_from_physical(p_scancode); +} + String _OS::get_model_name() const { return OS::get_singleton()->get_model_name(); } @@ -1355,6 +1359,7 @@ void _OS::_bind_methods() { ClassDB::bind_method(D_METHOD("keyboard_set_current_layout", "index"), &_OS::keyboard_set_current_layout); ClassDB::bind_method(D_METHOD("keyboard_get_layout_language", "index"), &_OS::keyboard_get_layout_language); ClassDB::bind_method(D_METHOD("keyboard_get_layout_name", "index"), &_OS::keyboard_get_layout_name); + ClassDB::bind_method(D_METHOD("keyboard_get_scancode_from_physical", "scancode"), &_OS::keyboard_get_scancode_from_physical); ClassDB::bind_method(D_METHOD("can_draw"), &_OS::can_draw); ClassDB::bind_method(D_METHOD("is_userfs_persistent"), &_OS::is_userfs_persistent); diff --git a/core/bind/core_bind.h b/core/bind/core_bind.h index 60b5fb61bc3..d2dcd570c8d 100644 --- a/core/bind/core_bind.h +++ b/core/bind/core_bind.h @@ -263,6 +263,7 @@ public: void keyboard_set_current_layout(int p_index); String keyboard_get_layout_language(int p_index) const; String keyboard_get_layout_name(int p_index) const; + uint32_t keyboard_get_scancode_from_physical(uint32_t p_scancode) const; String get_model_name() const; diff --git a/core/os/os.cpp b/core/os/os.cpp index 709295aa3e6..bdac38412c6 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -215,6 +215,10 @@ int OS::get_virtual_keyboard_height() const { return 0; } +uint32_t OS::keyboard_get_scancode_from_physical(uint32_t p_scancode) const { + return p_scancode; +} + void OS::set_cursor_shape(CursorShape p_shape) { } diff --git a/core/os/os.h b/core/os/os.h index 2370a5ec12b..e44710b452d 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -409,6 +409,7 @@ public: // returns height of the currently shown virtual keyboard (0 if keyboard is hidden) virtual int get_virtual_keyboard_height() const; + virtual uint32_t keyboard_get_scancode_from_physical(uint32_t p_scancode) const; virtual void set_cursor_shape(CursorShape p_shape); virtual CursorShape get_cursor_shape() const; diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index 85f41654244..8bcd88b4b04 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -711,6 +711,14 @@ [b]Note:[/b] This method is implemented on Linux, macOS and Windows. + + + + + Converts a physical (US QWERTY) [code]scancode[/code] to one in the active keyboard layout. + [b]Note:[/b] This method is implemented on Linux, macOS and Windows. + + diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index 53aa7c9184f..f31234a2527 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -264,6 +264,7 @@ public: virtual void keyboard_set_current_layout(int p_index); virtual String keyboard_get_layout_language(int p_index) const; virtual String keyboard_get_layout_name(int p_index) const; + virtual uint32_t keyboard_get_scancode_from_physical(uint32_t p_scancode) const; virtual void move_window_to_foreground(); diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 4e51a62e6f0..b05370d5424 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -940,145 +940,154 @@ static bool isNumpadKey(unsigned int key) { return false; } -// Translates a OS X keycode to a Godot keycode -// -static int translateKey(unsigned int key) { - // Keyboard symbol translation table - static const unsigned int table[128] = { - /* 00 */ KEY_A, - /* 01 */ KEY_S, - /* 02 */ KEY_D, - /* 03 */ KEY_F, - /* 04 */ KEY_H, - /* 05 */ KEY_G, - /* 06 */ KEY_Z, - /* 07 */ KEY_X, - /* 08 */ KEY_C, - /* 09 */ KEY_V, - /* 0a */ KEY_SECTION, /* ISO Section */ - /* 0b */ KEY_B, - /* 0c */ KEY_Q, - /* 0d */ KEY_W, - /* 0e */ KEY_E, - /* 0f */ KEY_R, - /* 10 */ KEY_Y, - /* 11 */ KEY_T, - /* 12 */ KEY_1, - /* 13 */ KEY_2, - /* 14 */ KEY_3, - /* 15 */ KEY_4, - /* 16 */ KEY_6, - /* 17 */ KEY_5, - /* 18 */ KEY_EQUAL, - /* 19 */ KEY_9, - /* 1a */ KEY_7, - /* 1b */ KEY_MINUS, - /* 1c */ KEY_8, - /* 1d */ KEY_0, - /* 1e */ KEY_BRACERIGHT, - /* 1f */ KEY_O, - /* 20 */ KEY_U, - /* 21 */ KEY_BRACELEFT, - /* 22 */ KEY_I, - /* 23 */ KEY_P, - /* 24 */ KEY_ENTER, - /* 25 */ KEY_L, - /* 26 */ KEY_J, - /* 27 */ KEY_APOSTROPHE, - /* 28 */ KEY_K, - /* 29 */ KEY_SEMICOLON, - /* 2a */ KEY_BACKSLASH, - /* 2b */ KEY_COMMA, - /* 2c */ KEY_SLASH, - /* 2d */ KEY_N, - /* 2e */ KEY_M, - /* 2f */ KEY_PERIOD, - /* 30 */ KEY_TAB, - /* 31 */ KEY_SPACE, - /* 32 */ KEY_QUOTELEFT, - /* 33 */ KEY_BACKSPACE, - /* 34 */ KEY_UNKNOWN, - /* 35 */ KEY_ESCAPE, - /* 36 */ KEY_META, - /* 37 */ KEY_META, - /* 38 */ KEY_SHIFT, - /* 39 */ KEY_CAPSLOCK, - /* 3a */ KEY_ALT, - /* 3b */ KEY_CONTROL, - /* 3c */ KEY_SHIFT, - /* 3d */ KEY_ALT, - /* 3e */ KEY_CONTROL, - /* 3f */ KEY_UNKNOWN, /* Function */ - /* 40 */ KEY_UNKNOWN, /* F17 */ - /* 41 */ KEY_KP_PERIOD, - /* 42 */ KEY_UNKNOWN, - /* 43 */ KEY_KP_MULTIPLY, - /* 44 */ KEY_UNKNOWN, - /* 45 */ KEY_KP_ADD, - /* 46 */ KEY_UNKNOWN, - /* 47 */ KEY_NUMLOCK, /* Really KeypadClear... */ - /* 48 */ KEY_VOLUMEUP, /* VolumeUp */ - /* 49 */ KEY_VOLUMEDOWN, /* VolumeDown */ - /* 4a */ KEY_VOLUMEMUTE, /* Mute */ - /* 4b */ KEY_KP_DIVIDE, - /* 4c */ KEY_KP_ENTER, - /* 4d */ KEY_UNKNOWN, - /* 4e */ KEY_KP_SUBTRACT, - /* 4f */ KEY_UNKNOWN, /* F18 */ - /* 50 */ KEY_UNKNOWN, /* F19 */ - /* 51 */ KEY_EQUAL, /* KeypadEqual */ - /* 52 */ KEY_KP_0, - /* 53 */ KEY_KP_1, - /* 54 */ KEY_KP_2, - /* 55 */ KEY_KP_3, - /* 56 */ KEY_KP_4, - /* 57 */ KEY_KP_5, - /* 58 */ KEY_KP_6, - /* 59 */ KEY_KP_7, - /* 5a */ KEY_UNKNOWN, /* F20 */ - /* 5b */ KEY_KP_8, - /* 5c */ KEY_KP_9, - /* 5d */ KEY_YEN, /* JIS Yen */ - /* 5e */ KEY_UNDERSCORE, /* JIS Underscore */ - /* 5f */ KEY_COMMA, /* JIS KeypadComma */ - /* 60 */ KEY_F5, - /* 61 */ KEY_F6, - /* 62 */ KEY_F7, - /* 63 */ KEY_F3, - /* 64 */ KEY_F8, - /* 65 */ KEY_F9, - /* 66 */ KEY_UNKNOWN, /* JIS Eisu */ - /* 67 */ KEY_F11, - /* 68 */ KEY_UNKNOWN, /* JIS Kana */ - /* 69 */ KEY_F13, - /* 6a */ KEY_F16, - /* 6b */ KEY_F14, - /* 6c */ KEY_UNKNOWN, - /* 6d */ KEY_F10, - /* 6e */ KEY_MENU, - /* 6f */ KEY_F12, - /* 70 */ KEY_UNKNOWN, - /* 71 */ KEY_F15, - /* 72 */ KEY_INSERT, /* Really Help... */ - /* 73 */ KEY_HOME, - /* 74 */ KEY_PAGEUP, - /* 75 */ KEY_DELETE, - /* 76 */ KEY_F4, - /* 77 */ KEY_END, - /* 78 */ KEY_F2, - /* 79 */ KEY_PAGEDOWN, - /* 7a */ KEY_F1, - /* 7b */ KEY_LEFT, - /* 7c */ KEY_RIGHT, - /* 7d */ KEY_DOWN, - /* 7e */ KEY_UP, - /* 7f */ KEY_UNKNOWN, - }; +// Keyboard symbol translation table +static const unsigned int _osx_to_godot_table[128] = { + /* 00 */ KEY_A, + /* 01 */ KEY_S, + /* 02 */ KEY_D, + /* 03 */ KEY_F, + /* 04 */ KEY_H, + /* 05 */ KEY_G, + /* 06 */ KEY_Z, + /* 07 */ KEY_X, + /* 08 */ KEY_C, + /* 09 */ KEY_V, + /* 0a */ KEY_SECTION, /* ISO Section */ + /* 0b */ KEY_B, + /* 0c */ KEY_Q, + /* 0d */ KEY_W, + /* 0e */ KEY_E, + /* 0f */ KEY_R, + /* 10 */ KEY_Y, + /* 11 */ KEY_T, + /* 12 */ KEY_1, + /* 13 */ KEY_2, + /* 14 */ KEY_3, + /* 15 */ KEY_4, + /* 16 */ KEY_6, + /* 17 */ KEY_5, + /* 18 */ KEY_EQUAL, + /* 19 */ KEY_9, + /* 1a */ KEY_7, + /* 1b */ KEY_MINUS, + /* 1c */ KEY_8, + /* 1d */ KEY_0, + /* 1e */ KEY_BRACERIGHT, + /* 1f */ KEY_O, + /* 20 */ KEY_U, + /* 21 */ KEY_BRACELEFT, + /* 22 */ KEY_I, + /* 23 */ KEY_P, + /* 24 */ KEY_ENTER, + /* 25 */ KEY_L, + /* 26 */ KEY_J, + /* 27 */ KEY_APOSTROPHE, + /* 28 */ KEY_K, + /* 29 */ KEY_SEMICOLON, + /* 2a */ KEY_BACKSLASH, + /* 2b */ KEY_COMMA, + /* 2c */ KEY_SLASH, + /* 2d */ KEY_N, + /* 2e */ KEY_M, + /* 2f */ KEY_PERIOD, + /* 30 */ KEY_TAB, + /* 31 */ KEY_SPACE, + /* 32 */ KEY_QUOTELEFT, + /* 33 */ KEY_BACKSPACE, + /* 34 */ KEY_UNKNOWN, + /* 35 */ KEY_ESCAPE, + /* 36 */ KEY_META, + /* 37 */ KEY_META, + /* 38 */ KEY_SHIFT, + /* 39 */ KEY_CAPSLOCK, + /* 3a */ KEY_ALT, + /* 3b */ KEY_CONTROL, + /* 3c */ KEY_SHIFT, + /* 3d */ KEY_ALT, + /* 3e */ KEY_CONTROL, + /* 3f */ KEY_UNKNOWN, /* Function */ + /* 40 */ KEY_UNKNOWN, /* F17 */ + /* 41 */ KEY_KP_PERIOD, + /* 42 */ KEY_UNKNOWN, + /* 43 */ KEY_KP_MULTIPLY, + /* 44 */ KEY_UNKNOWN, + /* 45 */ KEY_KP_ADD, + /* 46 */ KEY_UNKNOWN, + /* 47 */ KEY_NUMLOCK, /* Really KeypadClear... */ + /* 48 */ KEY_VOLUMEUP, /* VolumeUp */ + /* 49 */ KEY_VOLUMEDOWN, /* VolumeDown */ + /* 4a */ KEY_VOLUMEMUTE, /* Mute */ + /* 4b */ KEY_KP_DIVIDE, + /* 4c */ KEY_KP_ENTER, + /* 4d */ KEY_UNKNOWN, + /* 4e */ KEY_KP_SUBTRACT, + /* 4f */ KEY_UNKNOWN, /* F18 */ + /* 50 */ KEY_UNKNOWN, /* F19 */ + /* 51 */ KEY_EQUAL, /* KeypadEqual */ + /* 52 */ KEY_KP_0, + /* 53 */ KEY_KP_1, + /* 54 */ KEY_KP_2, + /* 55 */ KEY_KP_3, + /* 56 */ KEY_KP_4, + /* 57 */ KEY_KP_5, + /* 58 */ KEY_KP_6, + /* 59 */ KEY_KP_7, + /* 5a */ KEY_UNKNOWN, /* F20 */ + /* 5b */ KEY_KP_8, + /* 5c */ KEY_KP_9, + /* 5d */ KEY_YEN, /* JIS Yen */ + /* 5e */ KEY_UNDERSCORE, /* JIS Underscore */ + /* 5f */ KEY_COMMA, /* JIS KeypadComma */ + /* 60 */ KEY_F5, + /* 61 */ KEY_F6, + /* 62 */ KEY_F7, + /* 63 */ KEY_F3, + /* 64 */ KEY_F8, + /* 65 */ KEY_F9, + /* 66 */ KEY_UNKNOWN, /* JIS Eisu */ + /* 67 */ KEY_F11, + /* 68 */ KEY_UNKNOWN, /* JIS Kana */ + /* 69 */ KEY_F13, + /* 6a */ KEY_F16, + /* 6b */ KEY_F14, + /* 6c */ KEY_UNKNOWN, + /* 6d */ KEY_F10, + /* 6e */ KEY_MENU, + /* 6f */ KEY_F12, + /* 70 */ KEY_UNKNOWN, + /* 71 */ KEY_F15, + /* 72 */ KEY_INSERT, /* Really Help... */ + /* 73 */ KEY_HOME, + /* 74 */ KEY_PAGEUP, + /* 75 */ KEY_DELETE, + /* 76 */ KEY_F4, + /* 77 */ KEY_END, + /* 78 */ KEY_F2, + /* 79 */ KEY_PAGEDOWN, + /* 7a */ KEY_F1, + /* 7b */ KEY_LEFT, + /* 7c */ KEY_RIGHT, + /* 7d */ KEY_DOWN, + /* 7e */ KEY_UP, + /* 7f */ KEY_UNKNOWN, +}; +// Translates a OS X keycode to a Godot keycode +static int translateKey(unsigned int key) { if (key >= 128) return KEY_UNKNOWN; - return table[key]; + return _osx_to_godot_table[key]; +} + +// Translates a Godot keycode back to a OSX keycode +static unsigned int unmapKey(int key) { + for (int i = 0; i <= 126; i++) { + if (_osx_to_godot_table[i] == key) { + return i; + } + } + return 127; } struct _KeyCodeMap { @@ -3210,6 +3219,17 @@ String OS_OSX::keyboard_get_layout_name(int p_index) const { return kbd_layouts[p_index].name; } +uint32_t OS_OSX::keyboard_get_scancode_from_physical(uint32_t p_scancode) const { + if (p_scancode == KEY_PAUSE) { + return p_scancode; + } + + unsigned int modifiers = p_scancode & KEY_MODIFIER_MASK; + unsigned int scancode_no_mod = p_scancode & KEY_CODE_MASK; + unsigned int osx_scancode = unmapKey((uint32_t)scancode_no_mod); + return (uint32_t)(remapKey(osx_scancode, 0) | modifiers); +} + void OS_OSX::process_events() { while (true) { NSEvent *event = [NSApp diff --git a/platform/windows/key_mapping_windows.cpp b/platform/windows/key_mapping_windows.cpp index a3cbb6675db..2be61302cf4 100644 --- a/platform/windows/key_mapping_windows.cpp +++ b/platform/windows/key_mapping_windows.cpp @@ -347,6 +347,16 @@ unsigned int KeyMappingWindows::get_keysym(unsigned int p_code) { return KEY_UNKNOWN; } +unsigned int KeyMappingWindows::get_scancode(unsigned int p_keycode) { + for (int i = 0; _scancode_to_keycode[i].keysym != KEY_UNKNOWN; i++) { + if (_scancode_to_keycode[i].keysym == p_keycode) { + return _scancode_to_keycode[i].keycode; + } + } + + return 0; +} + unsigned int KeyMappingWindows::get_scansym(unsigned int p_code, bool p_extended) { unsigned int keycode = KEY_UNKNOWN; for (int i = 0; _scancode_to_keycode[i].keysym != KEY_UNKNOWN; i++) { diff --git a/platform/windows/key_mapping_windows.h b/platform/windows/key_mapping_windows.h index 9929b72f388..76f3c90ab18 100644 --- a/platform/windows/key_mapping_windows.h +++ b/platform/windows/key_mapping_windows.h @@ -42,6 +42,7 @@ class KeyMappingWindows { public: static unsigned int get_keysym(unsigned int p_code); + static unsigned int get_scancode(unsigned int p_keycode); static unsigned int get_scansym(unsigned int p_code, bool p_extended); static bool is_extended_key(unsigned int p_code); }; diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 95488a15160..487cc3a39c5 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -3237,6 +3237,42 @@ String OS_Windows::keyboard_get_layout_language(int p_index) const { return String(buf).substr(0, 2); } +uint32_t OS_Windows::keyboard_get_scancode_from_physical(uint32_t p_scancode) const { + unsigned int modifiers = p_scancode & KEY_MODIFIER_MASK; + uint32_t scancode_no_mod = (uint32_t)(p_scancode & KEY_CODE_MASK); + + if (scancode_no_mod == KEY_PRINT || + scancode_no_mod == KEY_KP_ADD || + scancode_no_mod == KEY_KP_5 || + (scancode_no_mod >= KEY_0 && scancode_no_mod <= KEY_9)) { + return p_scancode; + } + + unsigned int scancode = KeyMappingWindows::get_scancode(scancode_no_mod); + if (scancode == 0) { + return p_scancode; + } + + HKL current_layout = GetKeyboardLayout(0); + UINT vk = MapVirtualKeyEx(scancode, MAPVK_VSC_TO_VK, current_layout); + if (vk == 0) { + return p_scancode; + } + + UINT char_code = MapVirtualKeyEx(vk, MAPVK_VK_TO_CHAR, current_layout) & 0x7FFF; + // Unlike a similar Linux/BSD check which matches full Latin-1 range, + // we limit these to ASCII to fix some layouts, including Arabic ones + if (char_code >= 32 && char_code <= 127) { + // Godot uses 'braces' instead of 'brackets' + if (char_code == KEY_BRACKETLEFT || char_code == KEY_BRACKETRIGHT) { + char_code += 32; + } + return (uint32_t)(char_code | modifiers); + } + + return (uint32_t)(KeyMappingWindows::get_keysym(vk) | modifiers); +} + String _get_full_layout_name_from_registry(HKL p_layout) { String id = "SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\" + String::num_int64((int64_t)p_layout, 16, false).lpad(8, "0"); String ret; diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 688c77860fb..6dea669f50d 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -527,6 +527,7 @@ public: virtual void keyboard_set_current_layout(int p_index); virtual String keyboard_get_layout_language(int p_index) const; virtual String keyboard_get_layout_name(int p_index) const; + virtual uint32_t keyboard_get_scancode_from_physical(uint32_t p_scancode) const; virtual void enable_for_stealing_focus(ProcessID pid); virtual void move_window_to_foreground(); diff --git a/platform/x11/key_mapping_x11.cpp b/platform/x11/key_mapping_x11.cpp index 985aa4d0ef4..073b8f1bfc0 100644 --- a/platform/x11/key_mapping_x11.cpp +++ b/platform/x11/key_mapping_x11.cpp @@ -310,6 +310,18 @@ unsigned int KeyMappingX11::get_scancode(unsigned int p_code) { return keycode; } +unsigned int KeyMappingX11::get_xlibcode(unsigned int p_keysym) { + unsigned int code = 0; + for (int i = 0; _scancode_to_keycode[i].keysym != KEY_UNKNOWN; i++) { + if (_scancode_to_keycode[i].keysym == p_keysym) { + code = _scancode_to_keycode[i].keycode; + break; + } + } + + return code; +} + unsigned int KeyMappingX11::get_keycode(KeySym p_keysym) { // kinda bruteforce.. could optimize. diff --git a/platform/x11/key_mapping_x11.h b/platform/x11/key_mapping_x11.h index 2539d59ea34..7769d85c4bb 100644 --- a/platform/x11/key_mapping_x11.h +++ b/platform/x11/key_mapping_x11.h @@ -45,6 +45,7 @@ class KeyMappingX11 { public: static unsigned int get_keycode(KeySym p_keysym); + static unsigned int get_xlibcode(unsigned int p_keysym); static unsigned int get_scancode(unsigned int p_code); static KeySym get_keysym(unsigned int p_code); static unsigned int get_unicode_from_keysym(KeySym p_keysym); diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp index 922e60fd422..6aa99a163ef 100644 --- a/platform/x11/os_x11.cpp +++ b/platform/x11/os_x11.cpp @@ -4104,6 +4104,24 @@ String OS_X11::keyboard_get_layout_name(int p_index) const { return ret; } +uint32_t OS_X11::keyboard_get_scancode_from_physical(uint32_t p_scancode) const { + unsigned int modifiers = p_scancode & KEY_MODIFIER_MASK; + unsigned int scancode_no_mod = p_scancode & KEY_CODE_MASK; + unsigned int xkeycode = KeyMappingX11::get_xlibcode((uint32_t)scancode_no_mod); + KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, 0, 0); + if (xkeysym >= 'a' && xkeysym <= 'z') { + xkeysym -= ('a' - 'A'); + } + + uint32_t key = KeyMappingX11::get_keycode(xkeysym); + // If not found, fallback to QWERTY. + // This should match the behavior of the event pump + if (key == 0) { + return p_scancode; + } + return (uint32_t)(key | modifiers); +} + void OS_X11::update_real_mouse_position() { Window root_return, child_return; int root_x, root_y, win_x, win_y; diff --git a/platform/x11/os_x11.h b/platform/x11/os_x11.h index 1ee5e6d51f6..6b9d32516ee 100644 --- a/platform/x11/os_x11.h +++ b/platform/x11/os_x11.h @@ -361,6 +361,7 @@ public: virtual void keyboard_set_current_layout(int p_index); virtual String keyboard_get_layout_language(int p_index) const; virtual String keyboard_get_layout_name(int p_index) const; + virtual uint32_t keyboard_get_scancode_from_physical(uint32_t p_scancode) const; void update_real_mouse_position(); OS_X11();