From 761d7e7c4e6a4297f090b5f377862258981dc538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Camilla=20L=C3=B6wy?= Date: Mon, 11 May 2020 21:50:56 +0200 Subject: [PATCH] Add keyboard layout support This adds a keyboard layout switch callback and a query for the human-readable name of the current layout. Fixes #1201. --- README.md | 4 ++ docs/input.dox | 26 +++++++++++++ docs/news.dox | 9 +++++ include/GLFW/glfw3.h | 92 ++++++++++++++++++++++++++++++++++++++++++-- src/cocoa_init.m | 88 +++++++++++++++++++++++++----------------- src/cocoa_platform.h | 11 +++++- src/cocoa_window.m | 26 +++++++++++++ src/input.c | 21 ++++++++++ src/internal.h | 3 ++ src/null_window.c | 5 +++ src/win32_init.c | 1 + src/win32_platform.h | 1 + src/win32_window.c | 43 +++++++++++++++++++++ src/wl_init.c | 10 +++++ src/wl_platform.h | 5 +++ src/wl_window.c | 23 +++++++++++ src/x11_init.c | 7 ++++ src/x11_platform.h | 7 ++++ src/x11_window.c | 37 ++++++++++++++++++ tests/events.c | 7 ++++ 20 files changed, 387 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index c30e8a47..377b8bc4 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,10 @@ information on what to include when reporting a bug. ## Changelog + - Added `glfwGetKeyboardLayoutName` for querying the name of the current + keyboard layout (#1201) + - Added `glfwSetKeyboardLayoutCallback` and `GLFWkeyboardlayoutfun` for + receiving keyboard layout events (#1201) - Added `GLFW_RESIZE_NWSE_CURSOR`, `GLFW_RESIZE_NESW_CURSOR`, `GLFW_RESIZE_ALL_CURSOR` and `GLFW_NOT_ALLOWED_CURSOR` cursor shapes (#427) - Added `GLFW_RESIZE_EW_CURSOR` alias for `GLFW_HRESIZE_CURSOR` (#427) diff --git a/docs/input.dox b/docs/input.dox index 331f9718..f3b23cc3 100644 --- a/docs/input.dox +++ b/docs/input.dox @@ -230,6 +230,32 @@ specified key is `GLFW_KEY_UNKNOWN` then the scancode is used, otherwise it is ignored. This matches the behavior of the key callback, meaning the callback arguments can always be passed unmodified to this function. +The name of a key will not change unless the keyboard layout changes. + + +@subsection keyboard_layout Keyboard layout + +The human-readable name of the current keyboard layout is returned by @ref +glfwGetKeyboardLayoutName. + +If you wish to be notified when the keyboard layout changes, set a keyboard +layout callback. + +@code +glfwSetKeyboardLayoutCallback(keyboard_layout_callback); +@endcode + +The callback is called when the new layout takes effect for the application, +which on some platforms may not happen until one of its windows gets input +focus. + +@code +void keyboard_layout_callback(void) +{ + update_keyboard_layout_name(glfwGetKeyboardLayoutName()); +} +@endcode + @section input_mouse Mouse input diff --git a/docs/news.dox b/docs/news.dox index 4dd72795..ac649c59 100644 --- a/docs/news.dox +++ b/docs/news.dox @@ -9,6 +9,15 @@ @subsection features_34 New features in version 3.4 +@subsubsection keyboard_layout_34 Keyboard layouts + +GLFW can now notify when the keyboard layout has changed with @ref +glfwSetKeyboardLayoutCallback and provides the human-readable name of the +current layout with @ref glfwGetKeyboardLayoutName. + +For more information, see @ref keyboard_layout. + + @subsubsection standard_cursors_34 More standard cursors GLFW now provides the standard cursor shapes @ref GLFW_RESIZE_NWSE_CURSOR and diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index e077a0c4..7d36025e 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -1270,6 +1270,23 @@ typedef struct GLFWcursor GLFWcursor; */ typedef void (* GLFWerrorfun)(int,const char*); +/*! @brief The function pointer type for keyboard layout callbacks. + * + * This is the function pointer type for keyboard layout callbacks. A keyboard + * layout callback function has the following signature: + * @code + * void callback_name(void); + * @endcode + * + * @sa @ref keyboard_layout + * @sa @ref glfwSetKeyboardLayoutCallback + * + * @since Added in version 3.4. + * + * @ingroup input + */ +typedef void (* GLFWkeyboardlayoutfun)(void); + /*! @brief The function pointer type for window position callbacks. * * This is the function pointer type for window position callbacks. A window @@ -4265,6 +4282,11 @@ GLFWAPI int glfwRawMouseMotionSupported(void); * non-printable keys are the same across layouts but depend on the application * language and should be localized along with other user interface text. * + * The contents of the returned string may change when a keyboard + * layout change event is received. Set a + * [keyboard layout](@ref keyboard_layout) callback to be notified when the + * layout changes. + * * @param[in] key The key to query, or `GLFW_KEY_UNKNOWN`. * @param[in] scancode The scancode of the key to query. * @return The UTF-8 encoded, layout-specific name of the key, or `NULL`. @@ -4272,9 +4294,6 @@ GLFWAPI int glfwRawMouseMotionSupported(void); * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * - * @remark The contents of the returned string may change when a keyboard - * layout change event is received. - * * @pointer_lifetime The returned string is allocated and freed by GLFW. You * should not free it yourself. It is valid until the library is terminated. * @@ -4312,6 +4331,73 @@ GLFWAPI const char* glfwGetKeyName(int key, int scancode); */ GLFWAPI int glfwGetKeyScancode(int key); +/*! @brief Returns the human-readable name of the current keyboard layout. + * + * This function returns the human-readable name, encoded as UTF-8, of the + * current keyboard layout. On some platforms this may not be updated until + * one of the application's windows gets input focus. + * + * The keyboard layout name is intended to be shown to the user during text + * input, especially in full screen applications. + * + * The name may be localized into the current operating system UI language. It + * is provided by the operating system and may not be identical for a given + * layout across platforms. + * + * @return The UTF-8 encoded name of the current keyboard layout, or `NULL` if + * an [error](@ref error_handling) occurred. + * + * @errors Possible errors include @ref GLFW_PLATFORM_ERROR and @ref + * GLFW_NOT_INITIALIZED. + * + * @pointer_lifetime The returned string is allocated and freed by GLFW. You + * should not free it yourself. It is valid until the next call to this + * function or the library is terminated. + * + * @thread_safety This function must only be called from the main thread. + * + * @sa @ref keyboard_layout + * @sa @ref glfwSetKeyboardLayoutCallback + * + * @since Added in version 3.4. + * + * @ingroup input + */ +GLFWAPI const char* glfwGetKeyboardLayoutName(void); + +/*! @brief Sets the keyboard layout callback. + * + * This function sets the keyboard layout callback, which is called when the + * keyboard layout is changed. The name of the current layout is returned by + * @ref glfwGetKeyboardLayoutName. + * + * On some platforms the keyboard layout event may not arrive until one of the + * application's windows get input focus. Layout changes may not be reported + * while other applications have input focus. + * + * @param[in] callback The new callback, or `NULL` to remove the currently set + * callback. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). + * + * @callback_signature + * @code + * void function_name(void) + * @endcode + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. + * + * @thread_safety This function must only be called from the main thread. + * + * @sa @ref keyboard_layout + * @sa @ref glfwGetKeyboardLayoutName + * + * @since Added in version 3.4. + * + * @ingroup input + */ +GLFWAPI GLFWkeyboardlayoutfun glfwSetKeyboardLayoutCallback(GLFWkeyboardlayoutfun callback); + /*! @brief Returns the last reported state of a keyboard key for the specified * window. * diff --git a/src/cocoa_init.m b/src/cocoa_init.m index 434e5beb..d4460feb 100644 --- a/src/cocoa_init.m +++ b/src/cocoa_init.m @@ -305,38 +305,6 @@ static void createKeyTables(void) } } -// Retrieve Unicode data for the current keyboard layout -// -static GLFWbool updateUnicodeDataNS(void) -{ - if (_glfw.ns.inputSource) - { - CFRelease(_glfw.ns.inputSource); - _glfw.ns.inputSource = NULL; - _glfw.ns.unicodeData = nil; - } - - _glfw.ns.inputSource = TISCopyCurrentKeyboardLayoutInputSource(); - if (!_glfw.ns.inputSource) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Cocoa: Failed to retrieve keyboard layout input source"); - return GLFW_FALSE; - } - - _glfw.ns.unicodeData = - TISGetInputSourceProperty(_glfw.ns.inputSource, - kTISPropertyUnicodeKeyLayoutData); - if (!_glfw.ns.unicodeData) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Cocoa: Failed to retrieve keyboard layout Unicode data"); - return GLFW_FALSE; - } - - return GLFW_TRUE; -} - // Load HIToolbox.framework and the TIS symbols we need from it // static GLFWbool initializeTIS(void) @@ -354,6 +322,15 @@ static GLFWbool initializeTIS(void) CFStringRef* kPropertyUnicodeKeyLayoutData = CFBundleGetDataPointerForName(_glfw.ns.tis.bundle, CFSTR("kTISPropertyUnicodeKeyLayoutData")); + CFStringRef* kPropertyInputSourceID = + CFBundleGetDataPointerForName(_glfw.ns.tis.bundle, + CFSTR("kTISPropertyInputSourceID")); + CFStringRef* kPropertyLocalizedName = + CFBundleGetDataPointerForName(_glfw.ns.tis.bundle, + CFSTR("kTISPropertyLocalizedName")); + _glfw.ns.tis.CopyCurrentKeyboardInputSource = + CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle, + CFSTR("TISCopyCurrentKeyboardInputSource")); _glfw.ns.tis.CopyCurrentKeyboardLayoutInputSource = CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle, CFSTR("TISCopyCurrentKeyboardLayoutInputSource")); @@ -365,6 +342,9 @@ static GLFWbool initializeTIS(void) CFSTR("LMGetKbdType")); if (!kPropertyUnicodeKeyLayoutData || + !kPropertyInputSourceID || + !kPropertyLocalizedName || + !TISCopyCurrentKeyboardInputSource || !TISCopyCurrentKeyboardLayoutInputSource || !TISGetInputSourceProperty || !LMGetKbdType) @@ -376,8 +356,18 @@ static GLFWbool initializeTIS(void) _glfw.ns.tis.kPropertyUnicodeKeyLayoutData = *kPropertyUnicodeKeyLayoutData; + _glfw.ns.tis.kPropertyInputSourceID = + *kPropertyInputSourceID; + _glfw.ns.tis.kPropertyLocalizedName = + *kPropertyLocalizedName; - return updateUnicodeDataNS(); + _glfw.ns.inputSource = TISCopyCurrentKeyboardInputSource(); + _glfw.ns.keyboardLayout = TISCopyCurrentKeyboardLayoutInputSource(); + _glfw.ns.unicodeData = + TISGetInputSourceProperty(_glfw.ns.keyboardLayout, + kTISPropertyUnicodeKeyLayoutData); + + return GLFW_TRUE; } @interface GLFWHelper : NSObject @@ -387,7 +377,28 @@ static GLFWbool initializeTIS(void) - (void)selectedKeyboardInputSourceChanged:(NSObject* )object { - updateUnicodeDataNS(); + // The keyboard layout is needed for Unicode data which is the source of + // GLFW key names on Cocoa (the generic input source may not have this) + CFRelease(_glfw.ns.keyboardLayout); + _glfw.ns.keyboardLayout = TISCopyCurrentKeyboardLayoutInputSource(); + _glfw.ns.unicodeData = + TISGetInputSourceProperty(_glfw.ns.keyboardLayout, + kTISPropertyUnicodeKeyLayoutData); + + // The generic input source may be something higher level than a keyboard + // layout and if so will provide a better layout name than the layout source + const TISInputSourceRef source = TISCopyCurrentKeyboardInputSource(); + const CFStringRef newID = + TISGetInputSourceProperty(source, kTISPropertyInputSourceID); + const CFStringRef oldID = + TISGetInputSourceProperty(_glfw.ns.inputSource, kTISPropertyInputSourceID); + const CFComparisonResult result = CFStringCompare(oldID, newID, 0); + CFRelease(_glfw.ns.inputSource); + _glfw.ns.inputSource = source; + + // Filter events as we may receive more than one per input source switch + if (result != kCFCompareEqualTo) + _glfwInputKeyboardLayout(); } - (void)doNothing:(id)object @@ -567,11 +578,17 @@ void _glfwPlatformTerminate(void) { @autoreleasepool { + if (_glfw.ns.keyboardLayout) + { + CFRelease(_glfw.ns.keyboardLayout); + _glfw.ns.keyboardLayout = NULL; + _glfw.ns.unicodeData = NULL; + } + if (_glfw.ns.inputSource) { CFRelease(_glfw.ns.inputSource); _glfw.ns.inputSource = NULL; - _glfw.ns.unicodeData = nil; } if (_glfw.ns.eventSource) @@ -603,6 +620,7 @@ void _glfwPlatformTerminate(void) [NSEvent removeMonitor:_glfw.ns.keyUpMonitor]; free(_glfw.ns.clipboardString); + free(_glfw.ns.keyboardLayoutName); _glfwTerminateNSGL(); _glfwTerminateJoysticksNS(); diff --git a/src/cocoa_platform.h b/src/cocoa_platform.h index b18b99cb..cf68cfc5 100644 --- a/src/cocoa_platform.h +++ b/src/cocoa_platform.h @@ -103,6 +103,10 @@ typedef VkResult (APIENTRY *PFN_vkCreateMetalSurfaceEXT)(VkInstance,const VkMeta // HIToolbox.framework pointer typedefs #define kTISPropertyUnicodeKeyLayoutData _glfw.ns.tis.kPropertyUnicodeKeyLayoutData +#define kTISPropertyInputSourceID _glfw.ns.tis.kPropertyInputSourceID +#define kTISPropertyLocalizedName _glfw.ns.tis.kPropertyLocalizedName +typedef TISInputSourceRef (*PFN_TISCopyCurrentKeyboardInputSource)(void); +#define TISCopyCurrentKeyboardInputSource _glfw.ns.tis.CopyCurrentKeyboardInputSource typedef TISInputSourceRef (*PFN_TISCopyCurrentKeyboardLayoutInputSource)(void); #define TISCopyCurrentKeyboardLayoutInputSource _glfw.ns.tis.CopyCurrentKeyboardLayoutInputSource typedef void* (*PFN_TISGetInputSourceProperty)(TISInputSourceRef,CFStringRef); @@ -144,8 +148,9 @@ typedef struct _GLFWlibraryNS id delegate; GLFWbool cursorHidden; TISInputSourceRef inputSource; + TISInputSourceRef keyboardLayout; IOHIDManagerRef hidManager; - id unicodeData; + void* unicodeData; id helper; id keyUpMonitor; id nibObjects; @@ -154,6 +159,7 @@ typedef struct _GLFWlibraryNS short int keycodes[256]; short int scancodes[GLFW_KEY_LAST + 1]; char* clipboardString; + char* keyboardLayoutName; CGPoint cascadePoint; // Where to place the cursor when re-enabled double restoreCursorPosX, restoreCursorPosY; @@ -162,10 +168,13 @@ typedef struct _GLFWlibraryNS struct { CFBundleRef bundle; + PFN_TISCopyCurrentKeyboardInputSource CopyCurrentKeyboardInputSource; PFN_TISCopyCurrentKeyboardLayoutInputSource CopyCurrentKeyboardLayoutInputSource; PFN_TISGetInputSourceProperty GetInputSourceProperty; PFN_LMGetKbdType GetKbdType; CFStringRef kPropertyUnicodeKeyLayoutData; + CFStringRef kPropertyInputSourceID; + CFStringRef kPropertyLocalizedName; } tis; } _GLFWlibraryNS; diff --git a/src/cocoa_window.m b/src/cocoa_window.m index 45837dc1..8d7be165 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -1516,6 +1516,12 @@ const char* _glfwPlatformGetScancodeName(int scancode) return NULL; } + if (!_glfw.ns.unicodeData) + { + _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Keyboard Unicode data missing"); + return NULL; + } + const int key = _glfw.ns.keycodes[scancode]; UInt32 deadKeyState = 0; @@ -1559,6 +1565,26 @@ int _glfwPlatformGetKeyScancode(int key) return _glfw.ns.scancodes[key]; } +const char* _glfwPlatformGetKeyboardLayoutName(void) +{ + TISInputSourceRef source = TISCopyCurrentKeyboardInputSource(); + NSString* name = (__bridge NSString*) + TISGetInputSourceProperty(source, kTISPropertyLocalizedName); + if (!name) + { + CFRelease(source); + _glfwInputError(GLFW_PLATFORM_ERROR, + "Cocoa: Failed to retrieve keyboard layout name"); + return NULL; + } + + free(_glfw.ns.keyboardLayoutName); + _glfw.ns.keyboardLayoutName = _glfw_strdup([name UTF8String]); + + CFRelease(source); + return _glfw.ns.keyboardLayoutName; +} + int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const GLFWimage* image, int xhot, int yhot) diff --git a/src/input.c b/src/input.c index f6163093..e4d62ab7 100644 --- a/src/input.c +++ b/src/input.c @@ -256,6 +256,14 @@ static GLFWbool parseMapping(_GLFWmapping* mapping, const char* string) ////// GLFW event API ////// ////////////////////////////////////////////////////////////////////////// +// Notifies shared code of a keyboard layout change event +// +void _glfwInputKeyboardLayout(void) +{ + if (_glfw.callbacks.layout) + _glfw.callbacks.layout(); +} + // Notifies shared code of a physical key event // void _glfwInputKey(_GLFWwindow* window, int key, int scancode, int action, int mods) @@ -626,6 +634,19 @@ GLFWAPI int glfwGetKeyScancode(int key) return _glfwPlatformGetKeyScancode(key); } +GLFWAPI const char* glfwGetKeyboardLayoutName(void) +{ + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + return _glfwPlatformGetKeyboardLayoutName(); +} + +GLFWAPI GLFWkeyboardlayoutfun glfwSetKeyboardLayoutCallback(GLFWkeyboardlayoutfun cbfun) +{ + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + _GLFW_SWAP_POINTERS(_glfw.callbacks.layout, cbfun); + return cbfun; +} + GLFWAPI int glfwGetKey(GLFWwindow* handle, int key) { _GLFWwindow* window = (_GLFWwindow*) handle; diff --git a/src/internal.h b/src/internal.h index 6d7587c8..b24e858b 100644 --- a/src/internal.h +++ b/src/internal.h @@ -572,6 +572,7 @@ struct _GLFWlibrary struct { GLFWmonitorfun monitor; GLFWjoystickfun joystick; + GLFWkeyboardlayoutfun layout; } callbacks; // This is defined in the window API's platform.h @@ -612,6 +613,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor); const char* _glfwPlatformGetScancodeName(int scancode); int _glfwPlatformGetKeyScancode(int key); +const char* _glfwPlatformGetKeyboardLayoutName(void); void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor); void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos); @@ -717,6 +719,7 @@ void _glfwInputWindowDamage(_GLFWwindow* window); void _glfwInputWindowCloseRequest(_GLFWwindow* window); void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor); +void _glfwInputKeyboardLayout(void); void _glfwInputKey(_GLFWwindow* window, int key, int scancode, int action, int mods); void _glfwInputChar(_GLFWwindow* window, diff --git a/src/null_window.c b/src/null_window.c index 936400d3..106cf19e 100644 --- a/src/null_window.c +++ b/src/null_window.c @@ -310,6 +310,11 @@ int _glfwPlatformGetKeyScancode(int key) return -1; } +const char* _glfwPlatformGetKeyboardLayoutName(void) +{ + return ""; +} + void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) { } diff --git a/src/win32_init.c b/src/win32_init.c index 260e888e..54d428ab 100644 --- a/src/win32_init.c +++ b/src/win32_init.c @@ -602,6 +602,7 @@ void _glfwPlatformTerminate(void) SPIF_SENDCHANGE); free(_glfw.win32.clipboardString); + free(_glfw.win32.keyboardLayoutName); free(_glfw.win32.rawInput); _glfwTerminateWGL(); diff --git a/src/win32_platform.h b/src/win32_platform.h index 2b00b001..7b0a89a1 100644 --- a/src/win32_platform.h +++ b/src/win32_platform.h @@ -331,6 +331,7 @@ typedef struct _GLFWlibraryWin32 DWORD foregroundLockTimeout; int acquiredMonitorCount; char* clipboardString; + char* keyboardLayoutName; short int keycodes[512]; short int scancodes[GLFW_KEY_LAST + 1]; char keynames[GLFW_KEY_LAST + 1][5]; diff --git a/src/win32_window.c b/src/win32_window.c index 0ae0998a..78eb2cb6 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -645,6 +645,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, case WM_INPUTLANGCHANGE: { _glfwUpdateKeyNamesWin32(); + _glfwInputKeyboardLayout(); break; } @@ -2042,6 +2043,48 @@ int _glfwPlatformGetKeyScancode(int key) return _glfw.win32.scancodes[key]; } +const char* _glfwPlatformGetKeyboardLayoutName(void) +{ + WCHAR klid[KL_NAMELENGTH]; + int size; + LCID lcid; + WCHAR* language; + + if (!GetKeyboardLayoutNameW(klid)) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Win32: Failed to retrieve keyboard layout name"); + return NULL; + } + + // NOTE: We only care about the language part of the keyboard layout ID + lcid = MAKELCID(LANGIDFROMLCID(wcstoul(klid, NULL, 16)), 0); + + size = GetLocaleInfoW(lcid, LOCALE_SLANGUAGE, NULL, 0); + if (!size) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Win32: Failed to retrieve keyboard layout name length"); + return NULL; + } + + language = calloc(size, sizeof(WCHAR)); + + if (!GetLocaleInfoW(lcid, LOCALE_SLANGUAGE, language, size)) + { + free(language); + _glfwInputError(GLFW_PLATFORM_ERROR, + "Win32: Failed to translate keyboard layout name"); + return NULL; + } + + free(_glfw.win32.keyboardLayoutName); + _glfw.win32.keyboardLayoutName = _glfwCreateUTF8FromWideStringWin32(language); + free(language); + + return _glfw.win32.keyboardLayoutName; +} + int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const GLFWimage* image, int xhot, int yhot) diff --git a/src/wl_init.c b/src/wl_init.c index 558ff8a8..3cfd8cf9 100644 --- a/src/wl_init.c +++ b/src/wl_init.c @@ -640,6 +640,12 @@ static void keyboardHandleModifiers(void* data, if (mask & _glfw.wl.xkb.numLockMask) modifiers |= GLFW_MOD_NUM_LOCK; _glfw.wl.xkb.modifiers = modifiers; + + if (_glfw.wl.xkb.group != group) + { + _glfw.wl.xkb.group = group; + _glfwInputKeyboardLayout(); + } } #ifdef WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION @@ -1101,6 +1107,8 @@ int _glfwPlatformInit(void) _glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_update_mask"); _glfw.wl.xkb.state_serialize_mods = (PFN_xkb_state_serialize_mods) _glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_serialize_mods"); + _glfw.wl.xkb.keymap_layout_get_name = (PFN_xkb_keymap_layout_get_name) + _glfw_dlsym(_glfw.wl.xkb.handle, "xkb_keymap_layout_get_name"); #ifdef HAVE_XKBCOMMON_COMPOSE_H _glfw.wl.xkb.compose_table_new_from_locale = (PFN_xkb_compose_table_new_from_locale) @@ -1300,6 +1308,8 @@ void _glfwPlatformTerminate(void) free(_glfw.wl.clipboardString); if (_glfw.wl.clipboardSendString) free(_glfw.wl.clipboardSendString); + if (_glfw.wl.keyboardLayoutName) + free(_glfw.wl.keyboardLayoutName); } const char* _glfwPlatformGetVersionString(void) diff --git a/src/wl_platform.h b/src/wl_platform.h index 542cc78d..10692304 100644 --- a/src/wl_platform.h +++ b/src/wl_platform.h @@ -117,6 +117,7 @@ typedef void (* PFN_xkb_state_unref)(struct xkb_state*); typedef int (* PFN_xkb_state_key_get_syms)(struct xkb_state*, xkb_keycode_t, const xkb_keysym_t**); typedef enum xkb_state_component (* PFN_xkb_state_update_mask)(struct xkb_state*, xkb_mod_mask_t, xkb_mod_mask_t, xkb_mod_mask_t, xkb_layout_index_t, xkb_layout_index_t, xkb_layout_index_t); typedef xkb_mod_mask_t (* PFN_xkb_state_serialize_mods)(struct xkb_state*, enum xkb_state_component); +typedef const char * (* PFN_xkb_keymap_layout_get_name)(struct xkb_keymap*,xkb_layout_index_t); #define xkb_context_new _glfw.wl.xkb.context_new #define xkb_context_unref _glfw.wl.xkb.context_unref #define xkb_keymap_new_from_string _glfw.wl.xkb.keymap_new_from_string @@ -128,6 +129,7 @@ typedef xkb_mod_mask_t (* PFN_xkb_state_serialize_mods)(struct xkb_state*, enum #define xkb_state_key_get_syms _glfw.wl.xkb.state_key_get_syms #define xkb_state_update_mask _glfw.wl.xkb.state_update_mask #define xkb_state_serialize_mods _glfw.wl.xkb.state_serialize_mods +#define xkb_keymap_layout_get_name _glfw.wl.xkb.keymap_layout_get_name #ifdef HAVE_XKBCOMMON_COMPOSE_H typedef struct xkb_compose_table* (* PFN_xkb_compose_table_new_from_locale)(struct xkb_context*, const char*, enum xkb_compose_compile_flags); @@ -259,6 +261,7 @@ typedef struct _GLFWlibraryWayland size_t clipboardSize; char* clipboardSendString; size_t clipboardSendSize; + char* keyboardLayoutName; int timerfd; short int keycodes[256]; short int scancodes[GLFW_KEY_LAST + 1]; @@ -280,6 +283,7 @@ typedef struct _GLFWlibraryWayland xkb_mod_mask_t capsLockMask; xkb_mod_mask_t numLockMask; unsigned int modifiers; + xkb_layout_index_t group; PFN_xkb_context_new context_new; PFN_xkb_context_unref context_unref; @@ -292,6 +296,7 @@ typedef struct _GLFWlibraryWayland PFN_xkb_state_key_get_syms state_key_get_syms; PFN_xkb_state_update_mask state_update_mask; PFN_xkb_state_serialize_mods state_serialize_mods; + PFN_xkb_keymap_layout_get_name keymap_layout_get_name; #ifdef HAVE_XKBCOMMON_COMPOSE_H PFN_xkb_compose_table_new_from_locale compose_table_new_from_locale; diff --git a/src/wl_window.c b/src/wl_window.c index c8dde30a..2b1fa781 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -1194,6 +1194,29 @@ int _glfwPlatformGetKeyScancode(int key) return _glfw.wl.scancodes[key]; } +const char* _glfwPlatformGetKeyboardLayoutName(void) +{ + if (!_glfw.wl.xkb.keymap) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Wayland: Keymap missing"); + return NULL; + } + + const char* name = xkb_keymap_layout_get_name(_glfw.wl.xkb.keymap, + _glfw.wl.xkb.group); + if (!name) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Wayland: Failed to query keyboard layout name"); + return NULL; + } + + free(_glfw.wl.keyboardLayoutName); + _glfw.wl.keyboardLayoutName = _glfw_strdup(name); + return _glfw.wl.keyboardLayoutName; +} + int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const GLFWimage* image, int xhot, int yhot) diff --git a/src/x11_init.c b/src/x11_init.c index e5bd578d..40ad25cf 100644 --- a/src/x11_init.c +++ b/src/x11_init.c @@ -1153,6 +1153,8 @@ int _glfwPlatformInit(void) _glfw_dlsym(_glfw.x11.xlib.handle, "XFreeCursor"); _glfw.x11.xlib.FreeEventData = (PFN_XFreeEventData) _glfw_dlsym(_glfw.x11.xlib.handle, "XFreeEventData"); + _glfw.x11.xlib.GetAtomName = (PFN_XGetAtomName) + _glfw_dlsym(_glfw.x11.xlib.handle, "XGetAtomName"); _glfw.x11.xlib.GetErrorText = (PFN_XGetErrorText) _glfw_dlsym(_glfw.x11.xlib.handle, "XGetErrorText"); _glfw.x11.xlib.GetEventData = (PFN_XGetEventData) @@ -1263,6 +1265,8 @@ int _glfwPlatformInit(void) _glfw_dlsym(_glfw.x11.xlib.handle, "XVisualIDFromVisual"); _glfw.x11.xlib.WarpPointer = (PFN_XWarpPointer) _glfw_dlsym(_glfw.x11.xlib.handle, "XWarpPointer"); + _glfw.x11.xkb.AllocKeyboard = (PFN_XkbAllocKeyboard) + _glfw_dlsym(_glfw.x11.xlib.handle, "XkbAllocKeyboard"); _glfw.x11.xkb.FreeKeyboard = (PFN_XkbFreeKeyboard) _glfw_dlsym(_glfw.x11.xlib.handle, "XkbFreeKeyboard"); _glfw.x11.xkb.FreeNames = (PFN_XkbFreeNames) @@ -1376,6 +1380,9 @@ void _glfwPlatformTerminate(void) free(_glfw.x11.primarySelectionString); free(_glfw.x11.clipboardString); + if (_glfw.x11.keyboardLayoutName) + XFree(_glfw.x11.keyboardLayoutName); + XUnregisterIMInstantiateCallback(_glfw.x11.display, NULL, NULL, NULL, inputMethodInstantiateCallback, diff --git a/src/x11_platform.h b/src/x11_platform.h index fe82a72b..9b19e122 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -76,6 +76,7 @@ typedef int (* PFN_XFree)(void*); typedef int (* PFN_XFreeColormap)(Display*,Colormap); typedef int (* PFN_XFreeCursor)(Display*,Cursor); typedef void (* PFN_XFreeEventData)(Display*,XGenericEventCookie*); +typedef char* (* PFN_XGetAtomName)(Display*,Atom); typedef int (* PFN_XGetErrorText)(Display*,int,char*,int); typedef Bool (* PFN_XGetEventData)(Display*,XGenericEventCookie*); typedef char* (* PFN_XGetICValues)(XIC,...); @@ -133,6 +134,7 @@ typedef VisualID (* PFN_XVisualIDFromVisual)(Visual*); typedef int (* PFN_XWarpPointer)(Display*,Window,Window,int,int,unsigned int,unsigned int,int,int); typedef void (* PFN_XkbFreeKeyboard)(XkbDescPtr,unsigned int,Bool); typedef void (* PFN_XkbFreeNames)(XkbDescPtr,unsigned int,Bool); +typedef XkbDescPtr (* PFN_XkbAllocKeyboard)(void); typedef XkbDescPtr (* PFN_XkbGetMap)(Display*,unsigned int,unsigned int); typedef Status (* PFN_XkbGetNames)(Display*,unsigned int,XkbDescPtr); typedef Status (* PFN_XkbGetState)(Display*,unsigned int,XkbStatePtr); @@ -176,6 +178,7 @@ typedef void (* PFN_Xutf8SetWMProperties)(Display*,Window,const char*,const char #define XFreeColormap _glfw.x11.xlib.FreeColormap #define XFreeCursor _glfw.x11.xlib.FreeCursor #define XFreeEventData _glfw.x11.xlib.FreeEventData +#define XGetAtomName _glfw.x11.xlib.GetAtomName #define XGetErrorText _glfw.x11.xlib.GetErrorText #define XGetEventData _glfw.x11.xlib.GetEventData #define XGetICValues _glfw.x11.xlib.GetICValues @@ -231,6 +234,7 @@ typedef void (* PFN_Xutf8SetWMProperties)(Display*,Window,const char*,const char #define XUnsetICFocus _glfw.x11.xlib.UnsetICFocus #define XVisualIDFromVisual _glfw.x11.xlib.VisualIDFromVisual #define XWarpPointer _glfw.x11.xlib.WarpPointer +#define XkbAllocKeyboard _glfw.x11.xkb.AllocKeyboard #define XkbFreeKeyboard _glfw.x11.xkb.FreeKeyboard #define XkbFreeNames _glfw.x11.xkb.FreeNames #define XkbGetMap _glfw.x11.xkb.GetMap @@ -442,6 +446,7 @@ typedef struct _GLFWlibraryX11 short int keycodes[256]; // GLFW key to X11 keycode LUT short int scancodes[GLFW_KEY_LAST + 1]; + char* keyboardLayoutName; // Where to place the cursor when re-enabled double restoreCursorPosX, restoreCursorPosY; // The window whose disabled cursor mode is active @@ -533,6 +538,7 @@ typedef struct _GLFWlibraryX11 PFN_XFreeColormap FreeColormap; PFN_XFreeCursor FreeCursor; PFN_XFreeEventData FreeEventData; + PFN_XGetAtomName GetAtomName; PFN_XGetErrorText GetErrorText; PFN_XGetEventData GetEventData; PFN_XGetICValues GetICValues; @@ -638,6 +644,7 @@ typedef struct _GLFWlibraryX11 int major; int minor; unsigned int group; + PFN_XkbAllocKeyboard AllocKeyboard; PFN_XkbFreeKeyboard FreeKeyboard; PFN_XkbFreeNames FreeNames; PFN_XkbGetMap GetMap; diff --git a/src/x11_window.c b/src/x11_window.c index d6e6bf37..011e00ff 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -1184,6 +1184,7 @@ static void processEvent(XEvent *event) (((XkbEvent*) event)->state.changed & XkbGroupStateMask)) { _glfw.x11.xkb.group = ((XkbEvent*) event)->state.group; + _glfwInputKeyboardLayout(); } } } @@ -2920,6 +2921,42 @@ int _glfwPlatformGetKeyScancode(int key) return _glfw.x11.scancodes[key]; } +const char* _glfwPlatformGetKeyboardLayoutName(void) +{ + if (!_glfw.x11.xkb.available) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "X11: XKB extension required for keyboard layout names"); + return NULL; + } + + XkbStateRec state = {0}; + XkbGetState(_glfw.x11.display, XkbUseCoreKbd, &state); + + XkbDescPtr desc = XkbAllocKeyboard(); + if (XkbGetNames(_glfw.x11.display, XkbGroupNamesMask, desc) != Success) + { + XkbFreeKeyboard(desc, 0, True); + _glfwInputError(GLFW_PLATFORM_ERROR, + "X11: Failed to retrieve keyboard layout names"); + return NULL; + } + + const Atom atom = desc->names->groups[state.group]; + XkbFreeKeyboard(desc, 0, True); + + if (atom == None) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "X11: Name missing for current keyboard layout"); + return NULL; + } + + free(_glfw.x11.keyboardLayoutName); + _glfw.x11.keyboardLayoutName = XGetAtomName(_glfw.x11.display, atom); + return _glfw.x11.keyboardLayoutName; +} + int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const GLFWimage* image, int xhot, int yhot) diff --git a/tests/events.c b/tests/events.c index 86a63df2..969b14b0 100644 --- a/tests/events.c +++ b/tests/events.c @@ -465,6 +465,12 @@ static void drop_callback(GLFWwindow* window, int count, const char* paths[]) printf(" %i: \"%s\"\n", i, paths[i]); } +static void keyboard_layout_callback(void) +{ + printf("%08x at %0.3f: Keyboard layout changed to \'%s\'\n", + counter++, glfwGetTime(), glfwGetKeyboardLayoutName()); +} + static void monitor_callback(GLFWmonitor* monitor, int event) { if (event == GLFW_CONNECTED) @@ -546,6 +552,7 @@ int main(int argc, char** argv) glfwSetMonitorCallback(monitor_callback); glfwSetJoystickCallback(joystick_callback); + glfwSetKeyboardLayoutCallback(keyboard_layout_callback); while ((ch = getopt(argc, argv, "hfn:")) != -1) {