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.
diff --git a/README.md b/README.md
index c30e8a4..377b8bc 100644
--- a/README.md
+++ b/README.md
@@ -119,6 +119,10 @@
## 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 331f971..f3b23cc 100644
--- a/docs/input.dox
+++ b/docs/input.dox
@@ -230,6 +230,32 @@
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 4dd7279..ac649c5 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 e077a0c..7d36025 100644
--- a/include/GLFW/glfw3.h
+++ b/include/GLFW/glfw3.h
@@ -1270,6 +1270,23 @@
*/
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 @@
* 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 @@
* @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 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 434e5be..d4460fe 100644
--- a/src/cocoa_init.m
+++ b/src/cocoa_init.m
@@ -305,38 +305,6 @@
}
}
-// 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 @@
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 @@
CFSTR("LMGetKbdType"));
if (!kPropertyUnicodeKeyLayoutData ||
+ !kPropertyInputSourceID ||
+ !kPropertyLocalizedName ||
+ !TISCopyCurrentKeyboardInputSource ||
!TISCopyCurrentKeyboardLayoutInputSource ||
!TISGetInputSourceProperty ||
!LMGetKbdType)
@@ -376,8 +356,18 @@
_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 @@
- (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 @@
{
@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 @@
[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 b18b99c..cf68cfc 100644
--- a/src/cocoa_platform.h
+++ b/src/cocoa_platform.h
@@ -103,6 +103,10 @@
// 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 @@
id delegate;
GLFWbool cursorHidden;
TISInputSourceRef inputSource;
+ TISInputSourceRef keyboardLayout;
IOHIDManagerRef hidManager;
- id unicodeData;
+ void* unicodeData;
id helper;
id keyUpMonitor;
id nibObjects;
@@ -154,6 +159,7 @@
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 @@
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 45837dc..8d7be16 100644
--- a/src/cocoa_window.m
+++ b/src/cocoa_window.m
@@ -1516,6 +1516,12 @@
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 @@
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 f616309..e4d62ab 100644
--- a/src/input.c
+++ b/src/input.c
@@ -256,6 +256,14 @@
////// 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 @@
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 6d7587c..b24e858 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -572,6 +572,7 @@
struct {
GLFWmonitorfun monitor;
GLFWjoystickfun joystick;
+ GLFWkeyboardlayoutfun layout;
} callbacks;
// This is defined in the window API's platform.h
@@ -612,6 +613,7 @@
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 _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 936400d..106cf19 100644
--- a/src/null_window.c
+++ b/src/null_window.c
@@ -310,6 +310,11 @@
return -1;
}
+const char* _glfwPlatformGetKeyboardLayoutName(void)
+{
+ return "";
+}
+
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
{
}
diff --git a/src/win32_init.c b/src/win32_init.c
index 260e888..54d428a 100644
--- a/src/win32_init.c
+++ b/src/win32_init.c
@@ -602,6 +602,7 @@
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 2b00b00..7b0a89a 100644
--- a/src/win32_platform.h
+++ b/src/win32_platform.h
@@ -331,6 +331,7 @@
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 0ae0998..78eb2cb 100644
--- a/src/win32_window.c
+++ b/src/win32_window.c
@@ -645,6 +645,7 @@
case WM_INPUTLANGCHANGE:
{
_glfwUpdateKeyNamesWin32();
+ _glfwInputKeyboardLayout();
break;
}
@@ -2042,6 +2043,48 @@
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 558ff8a..3cfd8cf 100644
--- a/src/wl_init.c
+++ b/src/wl_init.c
@@ -640,6 +640,12 @@
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 @@
_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 @@
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 542cc78..1069230 100644
--- a/src/wl_platform.h
+++ b/src/wl_platform.h
@@ -117,6 +117,7 @@
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 @@
#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 @@
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 @@
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 @@
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 c8dde30..2b1fa78 100644
--- a/src/wl_window.c
+++ b/src/wl_window.c
@@ -1194,6 +1194,29 @@
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 e5bd578..40ad25c 100644
--- a/src/x11_init.c
+++ b/src/x11_init.c
@@ -1153,6 +1153,8 @@
_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 @@
_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 @@
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 fe82a72..9b19e12 100644
--- a/src/x11_platform.h
+++ b/src/x11_platform.h
@@ -76,6 +76,7 @@
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 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 @@
#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 @@
#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 @@
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 @@
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 @@
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 d6e6bf3..011e00f 100644
--- a/src/x11_window.c
+++ b/src/x11_window.c
@@ -1184,6 +1184,7 @@
(((XkbEvent*) event)->state.changed & XkbGroupStateMask))
{
_glfw.x11.xkb.group = ((XkbEvent*) event)->state.group;
+ _glfwInputKeyboardLayout();
}
}
}
@@ -2920,6 +2921,42 @@
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 86a63df..969b14b 100644
--- a/tests/events.c
+++ b/tests/events.c
@@ -465,6 +465,12 @@
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 @@
glfwSetMonitorCallback(monitor_callback);
glfwSetJoystickCallback(joystick_callback);
+ glfwSetKeyboardLayoutCallback(keyboard_layout_callback);
while ((ch = getopt(argc, argv, "hfn:")) != -1)
{