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.
This commit is contained in:
Camilla Löwy 2020-05-11 21:50:56 +02:00
parent d7ae90a790
commit 761d7e7c4e
20 changed files with 387 additions and 39 deletions

View File

@ -119,6 +119,10 @@ information on what to include when reporting a bug.
## Changelog ## 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`, - Added `GLFW_RESIZE_NWSE_CURSOR`, `GLFW_RESIZE_NESW_CURSOR`,
`GLFW_RESIZE_ALL_CURSOR` and `GLFW_NOT_ALLOWED_CURSOR` cursor shapes (#427) `GLFW_RESIZE_ALL_CURSOR` and `GLFW_NOT_ALLOWED_CURSOR` cursor shapes (#427)
- Added `GLFW_RESIZE_EW_CURSOR` alias for `GLFW_HRESIZE_CURSOR` (#427) - Added `GLFW_RESIZE_EW_CURSOR` alias for `GLFW_HRESIZE_CURSOR` (#427)

View File

@ -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 ignored. This matches the behavior of the key callback, meaning the callback
arguments can always be passed unmodified to this function. 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 @section input_mouse Mouse input

View File

@ -9,6 +9,15 @@
@subsection features_34 New features in version 3.4 @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 @subsubsection standard_cursors_34 More standard cursors
GLFW now provides the standard cursor shapes @ref GLFW_RESIZE_NWSE_CURSOR and GLFW now provides the standard cursor shapes @ref GLFW_RESIZE_NWSE_CURSOR and

View File

@ -1270,6 +1270,23 @@ typedef struct GLFWcursor GLFWcursor;
*/ */
typedef void (* GLFWerrorfun)(int,const char*); 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. /*! @brief The function pointer type for window position callbacks.
* *
* This is the function pointer type for window position callbacks. A window * 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 * non-printable keys are the same across layouts but depend on the application
* language and should be localized along with other user interface text. * 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] key The key to query, or `GLFW_KEY_UNKNOWN`.
* @param[in] scancode The scancode of the key to query. * @param[in] scancode The scancode of the key to query.
* @return The UTF-8 encoded, layout-specific name of the key, or `NULL`. * @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 * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR. * 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 * @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. * 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); 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 /*! @brief Returns the last reported state of a keyboard key for the specified
* window. * window.
* *

View File

@ -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 // Load HIToolbox.framework and the TIS symbols we need from it
// //
static GLFWbool initializeTIS(void) static GLFWbool initializeTIS(void)
@ -354,6 +322,15 @@ static GLFWbool initializeTIS(void)
CFStringRef* kPropertyUnicodeKeyLayoutData = CFStringRef* kPropertyUnicodeKeyLayoutData =
CFBundleGetDataPointerForName(_glfw.ns.tis.bundle, CFBundleGetDataPointerForName(_glfw.ns.tis.bundle,
CFSTR("kTISPropertyUnicodeKeyLayoutData")); 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 = _glfw.ns.tis.CopyCurrentKeyboardLayoutInputSource =
CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle, CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle,
CFSTR("TISCopyCurrentKeyboardLayoutInputSource")); CFSTR("TISCopyCurrentKeyboardLayoutInputSource"));
@ -365,6 +342,9 @@ static GLFWbool initializeTIS(void)
CFSTR("LMGetKbdType")); CFSTR("LMGetKbdType"));
if (!kPropertyUnicodeKeyLayoutData || if (!kPropertyUnicodeKeyLayoutData ||
!kPropertyInputSourceID ||
!kPropertyLocalizedName ||
!TISCopyCurrentKeyboardInputSource ||
!TISCopyCurrentKeyboardLayoutInputSource || !TISCopyCurrentKeyboardLayoutInputSource ||
!TISGetInputSourceProperty || !TISGetInputSourceProperty ||
!LMGetKbdType) !LMGetKbdType)
@ -376,8 +356,18 @@ static GLFWbool initializeTIS(void)
_glfw.ns.tis.kPropertyUnicodeKeyLayoutData = _glfw.ns.tis.kPropertyUnicodeKeyLayoutData =
*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 @interface GLFWHelper : NSObject
@ -387,7 +377,28 @@ static GLFWbool initializeTIS(void)
- (void)selectedKeyboardInputSourceChanged:(NSObject* )object - (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 - (void)doNothing:(id)object
@ -567,11 +578,17 @@ void _glfwPlatformTerminate(void)
{ {
@autoreleasepool { @autoreleasepool {
if (_glfw.ns.keyboardLayout)
{
CFRelease(_glfw.ns.keyboardLayout);
_glfw.ns.keyboardLayout = NULL;
_glfw.ns.unicodeData = NULL;
}
if (_glfw.ns.inputSource) if (_glfw.ns.inputSource)
{ {
CFRelease(_glfw.ns.inputSource); CFRelease(_glfw.ns.inputSource);
_glfw.ns.inputSource = NULL; _glfw.ns.inputSource = NULL;
_glfw.ns.unicodeData = nil;
} }
if (_glfw.ns.eventSource) if (_glfw.ns.eventSource)
@ -603,6 +620,7 @@ void _glfwPlatformTerminate(void)
[NSEvent removeMonitor:_glfw.ns.keyUpMonitor]; [NSEvent removeMonitor:_glfw.ns.keyUpMonitor];
free(_glfw.ns.clipboardString); free(_glfw.ns.clipboardString);
free(_glfw.ns.keyboardLayoutName);
_glfwTerminateNSGL(); _glfwTerminateNSGL();
_glfwTerminateJoysticksNS(); _glfwTerminateJoysticksNS();

View File

@ -103,6 +103,10 @@ typedef VkResult (APIENTRY *PFN_vkCreateMetalSurfaceEXT)(VkInstance,const VkMeta
// HIToolbox.framework pointer typedefs // HIToolbox.framework pointer typedefs
#define kTISPropertyUnicodeKeyLayoutData _glfw.ns.tis.kPropertyUnicodeKeyLayoutData #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); typedef TISInputSourceRef (*PFN_TISCopyCurrentKeyboardLayoutInputSource)(void);
#define TISCopyCurrentKeyboardLayoutInputSource _glfw.ns.tis.CopyCurrentKeyboardLayoutInputSource #define TISCopyCurrentKeyboardLayoutInputSource _glfw.ns.tis.CopyCurrentKeyboardLayoutInputSource
typedef void* (*PFN_TISGetInputSourceProperty)(TISInputSourceRef,CFStringRef); typedef void* (*PFN_TISGetInputSourceProperty)(TISInputSourceRef,CFStringRef);
@ -144,8 +148,9 @@ typedef struct _GLFWlibraryNS
id delegate; id delegate;
GLFWbool cursorHidden; GLFWbool cursorHidden;
TISInputSourceRef inputSource; TISInputSourceRef inputSource;
TISInputSourceRef keyboardLayout;
IOHIDManagerRef hidManager; IOHIDManagerRef hidManager;
id unicodeData; void* unicodeData;
id helper; id helper;
id keyUpMonitor; id keyUpMonitor;
id nibObjects; id nibObjects;
@ -154,6 +159,7 @@ typedef struct _GLFWlibraryNS
short int keycodes[256]; short int keycodes[256];
short int scancodes[GLFW_KEY_LAST + 1]; short int scancodes[GLFW_KEY_LAST + 1];
char* clipboardString; char* clipboardString;
char* keyboardLayoutName;
CGPoint cascadePoint; CGPoint cascadePoint;
// Where to place the cursor when re-enabled // Where to place the cursor when re-enabled
double restoreCursorPosX, restoreCursorPosY; double restoreCursorPosX, restoreCursorPosY;
@ -162,10 +168,13 @@ typedef struct _GLFWlibraryNS
struct { struct {
CFBundleRef bundle; CFBundleRef bundle;
PFN_TISCopyCurrentKeyboardInputSource CopyCurrentKeyboardInputSource;
PFN_TISCopyCurrentKeyboardLayoutInputSource CopyCurrentKeyboardLayoutInputSource; PFN_TISCopyCurrentKeyboardLayoutInputSource CopyCurrentKeyboardLayoutInputSource;
PFN_TISGetInputSourceProperty GetInputSourceProperty; PFN_TISGetInputSourceProperty GetInputSourceProperty;
PFN_LMGetKbdType GetKbdType; PFN_LMGetKbdType GetKbdType;
CFStringRef kPropertyUnicodeKeyLayoutData; CFStringRef kPropertyUnicodeKeyLayoutData;
CFStringRef kPropertyInputSourceID;
CFStringRef kPropertyLocalizedName;
} tis; } tis;
} _GLFWlibraryNS; } _GLFWlibraryNS;

View File

@ -1516,6 +1516,12 @@ const char* _glfwPlatformGetScancodeName(int scancode)
return NULL; return NULL;
} }
if (!_glfw.ns.unicodeData)
{
_glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Keyboard Unicode data missing");
return NULL;
}
const int key = _glfw.ns.keycodes[scancode]; const int key = _glfw.ns.keycodes[scancode];
UInt32 deadKeyState = 0; UInt32 deadKeyState = 0;
@ -1559,6 +1565,26 @@ int _glfwPlatformGetKeyScancode(int key)
return _glfw.ns.scancodes[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, int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
const GLFWimage* image, const GLFWimage* image,
int xhot, int yhot) int xhot, int yhot)

View File

@ -256,6 +256,14 @@ static GLFWbool parseMapping(_GLFWmapping* mapping, const char* string)
////// GLFW event API ////// ////// 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 // Notifies shared code of a physical key event
// //
void _glfwInputKey(_GLFWwindow* window, int key, int scancode, int action, int mods) void _glfwInputKey(_GLFWwindow* window, int key, int scancode, int action, int mods)
@ -626,6 +634,19 @@ GLFWAPI int glfwGetKeyScancode(int key)
return _glfwPlatformGetKeyScancode(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) GLFWAPI int glfwGetKey(GLFWwindow* handle, int key)
{ {
_GLFWwindow* window = (_GLFWwindow*) handle; _GLFWwindow* window = (_GLFWwindow*) handle;

View File

@ -572,6 +572,7 @@ struct _GLFWlibrary
struct { struct {
GLFWmonitorfun monitor; GLFWmonitorfun monitor;
GLFWjoystickfun joystick; GLFWjoystickfun joystick;
GLFWkeyboardlayoutfun layout;
} callbacks; } callbacks;
// This is defined in the window API's platform.h // 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); const char* _glfwPlatformGetScancodeName(int scancode);
int _glfwPlatformGetKeyScancode(int key); int _glfwPlatformGetKeyScancode(int key);
const char* _glfwPlatformGetKeyboardLayoutName(void);
void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor); void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor);
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos); void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos);
@ -717,6 +719,7 @@ void _glfwInputWindowDamage(_GLFWwindow* window);
void _glfwInputWindowCloseRequest(_GLFWwindow* window); void _glfwInputWindowCloseRequest(_GLFWwindow* window);
void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor); void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor);
void _glfwInputKeyboardLayout(void);
void _glfwInputKey(_GLFWwindow* window, void _glfwInputKey(_GLFWwindow* window,
int key, int scancode, int action, int mods); int key, int scancode, int action, int mods);
void _glfwInputChar(_GLFWwindow* window, void _glfwInputChar(_GLFWwindow* window,

View File

@ -310,6 +310,11 @@ int _glfwPlatformGetKeyScancode(int key)
return -1; return -1;
} }
const char* _glfwPlatformGetKeyboardLayoutName(void)
{
return "";
}
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
{ {
} }

View File

@ -602,6 +602,7 @@ void _glfwPlatformTerminate(void)
SPIF_SENDCHANGE); SPIF_SENDCHANGE);
free(_glfw.win32.clipboardString); free(_glfw.win32.clipboardString);
free(_glfw.win32.keyboardLayoutName);
free(_glfw.win32.rawInput); free(_glfw.win32.rawInput);
_glfwTerminateWGL(); _glfwTerminateWGL();

View File

@ -331,6 +331,7 @@ typedef struct _GLFWlibraryWin32
DWORD foregroundLockTimeout; DWORD foregroundLockTimeout;
int acquiredMonitorCount; int acquiredMonitorCount;
char* clipboardString; char* clipboardString;
char* keyboardLayoutName;
short int keycodes[512]; short int keycodes[512];
short int scancodes[GLFW_KEY_LAST + 1]; short int scancodes[GLFW_KEY_LAST + 1];
char keynames[GLFW_KEY_LAST + 1][5]; char keynames[GLFW_KEY_LAST + 1][5];

View File

@ -645,6 +645,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
case WM_INPUTLANGCHANGE: case WM_INPUTLANGCHANGE:
{ {
_glfwUpdateKeyNamesWin32(); _glfwUpdateKeyNamesWin32();
_glfwInputKeyboardLayout();
break; break;
} }
@ -2042,6 +2043,48 @@ int _glfwPlatformGetKeyScancode(int key)
return _glfw.win32.scancodes[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, int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
const GLFWimage* image, const GLFWimage* image,
int xhot, int yhot) int xhot, int yhot)

View File

@ -640,6 +640,12 @@ static void keyboardHandleModifiers(void* data,
if (mask & _glfw.wl.xkb.numLockMask) if (mask & _glfw.wl.xkb.numLockMask)
modifiers |= GLFW_MOD_NUM_LOCK; modifiers |= GLFW_MOD_NUM_LOCK;
_glfw.wl.xkb.modifiers = modifiers; _glfw.wl.xkb.modifiers = modifiers;
if (_glfw.wl.xkb.group != group)
{
_glfw.wl.xkb.group = group;
_glfwInputKeyboardLayout();
}
} }
#ifdef WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION #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_dlsym(_glfw.wl.xkb.handle, "xkb_state_update_mask");
_glfw.wl.xkb.state_serialize_mods = (PFN_xkb_state_serialize_mods) _glfw.wl.xkb.state_serialize_mods = (PFN_xkb_state_serialize_mods)
_glfw_dlsym(_glfw.wl.xkb.handle, "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 #ifdef HAVE_XKBCOMMON_COMPOSE_H
_glfw.wl.xkb.compose_table_new_from_locale = (PFN_xkb_compose_table_new_from_locale) _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); free(_glfw.wl.clipboardString);
if (_glfw.wl.clipboardSendString) if (_glfw.wl.clipboardSendString)
free(_glfw.wl.clipboardSendString); free(_glfw.wl.clipboardSendString);
if (_glfw.wl.keyboardLayoutName)
free(_glfw.wl.keyboardLayoutName);
} }
const char* _glfwPlatformGetVersionString(void) const char* _glfwPlatformGetVersionString(void)

View File

@ -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 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 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 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_new _glfw.wl.xkb.context_new
#define xkb_context_unref _glfw.wl.xkb.context_unref #define xkb_context_unref _glfw.wl.xkb.context_unref
#define xkb_keymap_new_from_string _glfw.wl.xkb.keymap_new_from_string #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_key_get_syms _glfw.wl.xkb.state_key_get_syms
#define xkb_state_update_mask _glfw.wl.xkb.state_update_mask #define xkb_state_update_mask _glfw.wl.xkb.state_update_mask
#define xkb_state_serialize_mods _glfw.wl.xkb.state_serialize_mods #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 #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); 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; size_t clipboardSize;
char* clipboardSendString; char* clipboardSendString;
size_t clipboardSendSize; size_t clipboardSendSize;
char* keyboardLayoutName;
int timerfd; int timerfd;
short int keycodes[256]; short int keycodes[256];
short int scancodes[GLFW_KEY_LAST + 1]; short int scancodes[GLFW_KEY_LAST + 1];
@ -280,6 +283,7 @@ typedef struct _GLFWlibraryWayland
xkb_mod_mask_t capsLockMask; xkb_mod_mask_t capsLockMask;
xkb_mod_mask_t numLockMask; xkb_mod_mask_t numLockMask;
unsigned int modifiers; unsigned int modifiers;
xkb_layout_index_t group;
PFN_xkb_context_new context_new; PFN_xkb_context_new context_new;
PFN_xkb_context_unref context_unref; 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_key_get_syms state_key_get_syms;
PFN_xkb_state_update_mask state_update_mask; PFN_xkb_state_update_mask state_update_mask;
PFN_xkb_state_serialize_mods state_serialize_mods; PFN_xkb_state_serialize_mods state_serialize_mods;
PFN_xkb_keymap_layout_get_name keymap_layout_get_name;
#ifdef HAVE_XKBCOMMON_COMPOSE_H #ifdef HAVE_XKBCOMMON_COMPOSE_H
PFN_xkb_compose_table_new_from_locale compose_table_new_from_locale; PFN_xkb_compose_table_new_from_locale compose_table_new_from_locale;

View File

@ -1194,6 +1194,29 @@ int _glfwPlatformGetKeyScancode(int key)
return _glfw.wl.scancodes[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, int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
const GLFWimage* image, const GLFWimage* image,
int xhot, int yhot) int xhot, int yhot)

View File

@ -1153,6 +1153,8 @@ int _glfwPlatformInit(void)
_glfw_dlsym(_glfw.x11.xlib.handle, "XFreeCursor"); _glfw_dlsym(_glfw.x11.xlib.handle, "XFreeCursor");
_glfw.x11.xlib.FreeEventData = (PFN_XFreeEventData) _glfw.x11.xlib.FreeEventData = (PFN_XFreeEventData)
_glfw_dlsym(_glfw.x11.xlib.handle, "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.x11.xlib.GetErrorText = (PFN_XGetErrorText)
_glfw_dlsym(_glfw.x11.xlib.handle, "XGetErrorText"); _glfw_dlsym(_glfw.x11.xlib.handle, "XGetErrorText");
_glfw.x11.xlib.GetEventData = (PFN_XGetEventData) _glfw.x11.xlib.GetEventData = (PFN_XGetEventData)
@ -1263,6 +1265,8 @@ int _glfwPlatformInit(void)
_glfw_dlsym(_glfw.x11.xlib.handle, "XVisualIDFromVisual"); _glfw_dlsym(_glfw.x11.xlib.handle, "XVisualIDFromVisual");
_glfw.x11.xlib.WarpPointer = (PFN_XWarpPointer) _glfw.x11.xlib.WarpPointer = (PFN_XWarpPointer)
_glfw_dlsym(_glfw.x11.xlib.handle, "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.x11.xkb.FreeKeyboard = (PFN_XkbFreeKeyboard)
_glfw_dlsym(_glfw.x11.xlib.handle, "XkbFreeKeyboard"); _glfw_dlsym(_glfw.x11.xlib.handle, "XkbFreeKeyboard");
_glfw.x11.xkb.FreeNames = (PFN_XkbFreeNames) _glfw.x11.xkb.FreeNames = (PFN_XkbFreeNames)
@ -1376,6 +1380,9 @@ void _glfwPlatformTerminate(void)
free(_glfw.x11.primarySelectionString); free(_glfw.x11.primarySelectionString);
free(_glfw.x11.clipboardString); free(_glfw.x11.clipboardString);
if (_glfw.x11.keyboardLayoutName)
XFree(_glfw.x11.keyboardLayoutName);
XUnregisterIMInstantiateCallback(_glfw.x11.display, XUnregisterIMInstantiateCallback(_glfw.x11.display,
NULL, NULL, NULL, NULL, NULL, NULL,
inputMethodInstantiateCallback, inputMethodInstantiateCallback,

View File

@ -76,6 +76,7 @@ typedef int (* PFN_XFree)(void*);
typedef int (* PFN_XFreeColormap)(Display*,Colormap); typedef int (* PFN_XFreeColormap)(Display*,Colormap);
typedef int (* PFN_XFreeCursor)(Display*,Cursor); typedef int (* PFN_XFreeCursor)(Display*,Cursor);
typedef void (* PFN_XFreeEventData)(Display*,XGenericEventCookie*); typedef void (* PFN_XFreeEventData)(Display*,XGenericEventCookie*);
typedef char* (* PFN_XGetAtomName)(Display*,Atom);
typedef int (* PFN_XGetErrorText)(Display*,int,char*,int); typedef int (* PFN_XGetErrorText)(Display*,int,char*,int);
typedef Bool (* PFN_XGetEventData)(Display*,XGenericEventCookie*); typedef Bool (* PFN_XGetEventData)(Display*,XGenericEventCookie*);
typedef char* (* PFN_XGetICValues)(XIC,...); 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 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_XkbFreeKeyboard)(XkbDescPtr,unsigned int,Bool);
typedef void (* PFN_XkbFreeNames)(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 XkbDescPtr (* PFN_XkbGetMap)(Display*,unsigned int,unsigned int);
typedef Status (* PFN_XkbGetNames)(Display*,unsigned int,XkbDescPtr); typedef Status (* PFN_XkbGetNames)(Display*,unsigned int,XkbDescPtr);
typedef Status (* PFN_XkbGetState)(Display*,unsigned int,XkbStatePtr); 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 XFreeColormap _glfw.x11.xlib.FreeColormap
#define XFreeCursor _glfw.x11.xlib.FreeCursor #define XFreeCursor _glfw.x11.xlib.FreeCursor
#define XFreeEventData _glfw.x11.xlib.FreeEventData #define XFreeEventData _glfw.x11.xlib.FreeEventData
#define XGetAtomName _glfw.x11.xlib.GetAtomName
#define XGetErrorText _glfw.x11.xlib.GetErrorText #define XGetErrorText _glfw.x11.xlib.GetErrorText
#define XGetEventData _glfw.x11.xlib.GetEventData #define XGetEventData _glfw.x11.xlib.GetEventData
#define XGetICValues _glfw.x11.xlib.GetICValues #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 XUnsetICFocus _glfw.x11.xlib.UnsetICFocus
#define XVisualIDFromVisual _glfw.x11.xlib.VisualIDFromVisual #define XVisualIDFromVisual _glfw.x11.xlib.VisualIDFromVisual
#define XWarpPointer _glfw.x11.xlib.WarpPointer #define XWarpPointer _glfw.x11.xlib.WarpPointer
#define XkbAllocKeyboard _glfw.x11.xkb.AllocKeyboard
#define XkbFreeKeyboard _glfw.x11.xkb.FreeKeyboard #define XkbFreeKeyboard _glfw.x11.xkb.FreeKeyboard
#define XkbFreeNames _glfw.x11.xkb.FreeNames #define XkbFreeNames _glfw.x11.xkb.FreeNames
#define XkbGetMap _glfw.x11.xkb.GetMap #define XkbGetMap _glfw.x11.xkb.GetMap
@ -442,6 +446,7 @@ typedef struct _GLFWlibraryX11
short int keycodes[256]; short int keycodes[256];
// GLFW key to X11 keycode LUT // GLFW key to X11 keycode LUT
short int scancodes[GLFW_KEY_LAST + 1]; short int scancodes[GLFW_KEY_LAST + 1];
char* keyboardLayoutName;
// Where to place the cursor when re-enabled // Where to place the cursor when re-enabled
double restoreCursorPosX, restoreCursorPosY; double restoreCursorPosX, restoreCursorPosY;
// The window whose disabled cursor mode is active // The window whose disabled cursor mode is active
@ -533,6 +538,7 @@ typedef struct _GLFWlibraryX11
PFN_XFreeColormap FreeColormap; PFN_XFreeColormap FreeColormap;
PFN_XFreeCursor FreeCursor; PFN_XFreeCursor FreeCursor;
PFN_XFreeEventData FreeEventData; PFN_XFreeEventData FreeEventData;
PFN_XGetAtomName GetAtomName;
PFN_XGetErrorText GetErrorText; PFN_XGetErrorText GetErrorText;
PFN_XGetEventData GetEventData; PFN_XGetEventData GetEventData;
PFN_XGetICValues GetICValues; PFN_XGetICValues GetICValues;
@ -638,6 +644,7 @@ typedef struct _GLFWlibraryX11
int major; int major;
int minor; int minor;
unsigned int group; unsigned int group;
PFN_XkbAllocKeyboard AllocKeyboard;
PFN_XkbFreeKeyboard FreeKeyboard; PFN_XkbFreeKeyboard FreeKeyboard;
PFN_XkbFreeNames FreeNames; PFN_XkbFreeNames FreeNames;
PFN_XkbGetMap GetMap; PFN_XkbGetMap GetMap;

View File

@ -1184,6 +1184,7 @@ static void processEvent(XEvent *event)
(((XkbEvent*) event)->state.changed & XkbGroupStateMask)) (((XkbEvent*) event)->state.changed & XkbGroupStateMask))
{ {
_glfw.x11.xkb.group = ((XkbEvent*) event)->state.group; _glfw.x11.xkb.group = ((XkbEvent*) event)->state.group;
_glfwInputKeyboardLayout();
} }
} }
} }
@ -2920,6 +2921,42 @@ int _glfwPlatformGetKeyScancode(int key)
return _glfw.x11.scancodes[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, int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
const GLFWimage* image, const GLFWimage* image,
int xhot, int yhot) int xhot, int yhot)

View File

@ -465,6 +465,12 @@ static void drop_callback(GLFWwindow* window, int count, const char* paths[])
printf(" %i: \"%s\"\n", i, paths[i]); 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) static void monitor_callback(GLFWmonitor* monitor, int event)
{ {
if (event == GLFW_CONNECTED) if (event == GLFW_CONNECTED)
@ -546,6 +552,7 @@ int main(int argc, char** argv)
glfwSetMonitorCallback(monitor_callback); glfwSetMonitorCallback(monitor_callback);
glfwSetJoystickCallback(joystick_callback); glfwSetJoystickCallback(joystick_callback);
glfwSetKeyboardLayoutCallback(keyboard_layout_callback);
while ((ch = getopt(argc, argv, "hfn:")) != -1) while ((ch = getopt(argc, argv, "hfn:")) != -1)
{ {