diff --git a/CMakeLists.txt b/CMakeLists.txt index 80bc3188..aa22cc38 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -344,17 +344,20 @@ if (_GLFW_COCOA AND _GLFW_NSGL) find_library(IOKIT_FRAMEWORK IOKit) find_library(CORE_FOUNDATION_FRAMEWORK CoreFoundation) find_library(CORE_VIDEO_FRAMEWORK CoreVideo) + find_library(CARBON_FRAMEWORK Carbon) mark_as_advanced(COCOA_FRAMEWORK IOKIT_FRAMEWORK CORE_FOUNDATION_FRAMEWORK - CORE_VIDEO_FRAMEWORK) + CORE_VIDEO_FRAMEWORK + CARBON_FRAMEWORK) list(APPEND glfw_LIBRARIES "${COCOA_FRAMEWORK}" "${IOKIT_FRAMEWORK}" "${CORE_FOUNDATION_FRAMEWORK}" - "${CORE_VIDEO_FRAMEWORK}") + "${CORE_VIDEO_FRAMEWORK}" + "${CARBON_FRAMEWORK}") set(glfw_PKG_DEPS "") - set(glfw_PKG_LIBS "-framework Cocoa -framework IOKit -framework CoreFoundation -framework CoreVideo") + set(glfw_PKG_LIBS "-framework Cocoa -framework IOKit -framework CoreFoundation -framework CoreVideo -framework Carbon") endif() #-------------------------------------------------------------------- diff --git a/README.md b/README.md index aeeca6cf..ebcd4865 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,8 @@ used by the tests and examples and are not required to build the library. - Added `glfwSetWindowSizeLimits` and `glfwSetWindowAspectRatio` for setting absolute and relative window size limits + - Added `glfwGetKeyName` for querying the layout-specific name of printable + keys - Added `GLFW_NO_API` for creating window without contexts - Added `GLFW_CONTEXT_NO_ERROR` context hint for `GL_KHR_no_error` support - Added `GLFW_TRUE` and `GLFW_FALSE` as client API independent boolean values diff --git a/docs/input.dox b/docs/input.dox index 844bcce6..e36770ad 100644 --- a/docs/input.dox +++ b/docs/input.dox @@ -192,6 +192,22 @@ void charmods_callback(GLFWwindow* window, unsigned int codepoint, int mods) @endcode +@subsection input_key_name Key names + +If you wish to refer to keys by name, you can query the keyboard layout +dependent name of printable keys with @ref glfwGetKeyName. + +@code +const char* key_name = glfwGetKeyName(GLFW_KEY_W, 0); +show_tutorial_hint("Press %s to move forward", key_name); +@endcode + +This function can handle both [keys and scancodes](@ref input_key). If the +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. + + @section input_mouse Mouse input Mouse input comes in many forms, including cursor motion, button presses and diff --git a/docs/news.dox b/docs/news.dox index 6c68839b..93026658 100644 --- a/docs/news.dox +++ b/docs/news.dox @@ -11,6 +11,12 @@ GLFW now supports setting both absolute and relative window size limits with @ref glfwSetWindowSizeLimits and @ref glfwSetWindowAspectRatio. +@subsection news_32_keyname Localized key names + +GLFW now supports querying the localized name of printable keys with @ref +glfwGetKeyName, either by key token or by scancode. + + @section news_31 New features in 3.1 These are the release highlights. For a full list of changes see the diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index bbc10ee7..81acecdc 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -414,6 +414,7 @@ extern "C" { #define GLFW_KEY_RIGHT_ALT 346 #define GLFW_KEY_RIGHT_SUPER 347 #define GLFW_KEY_MENU 348 + #define GLFW_KEY_LAST GLFW_KEY_MENU /*! @} */ @@ -2588,6 +2589,33 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode); */ GLFWAPI void glfwSetInputMode(GLFWwindow* window, int mode, int value); +/*! @brief Returns the localized name of the specified printable key. + * + * This function returns the localized name of the specified printable key. + * + * If the key is `GLFW_KEY_UNKNOWN`, the scancode is used, otherwise the + * scancode is ignored. + * + * @param[in] key The key to query, or `GLFW_KEY_UNKNOWN`. + * @param[in] scancode The scancode of the key to query. + * @return The localized name of the key. + * + * @par 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 @ref glfwGetKeyName, or until + * the library is terminated. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref input_key_name + * + * @since Added in GLFW 3.2. + * + * @ingroup input + */ +GLFWAPI const char* glfwGetKeyName(int key, int scancode); + /*! @brief Returns the last reported state of a keyboard key for the specified * window. * @@ -2607,6 +2635,8 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* window, int mode, int value); * The [modifier key bit masks](@ref mods) are not key tokens and cannot be * used with this function. * + * __Do not use this function__ to implement [text input](@ref input_char). + * * @param[in] window The desired window. * @param[in] key The desired [keyboard key](@ref keys). `GLFW_KEY_UNKNOWN` is * not a valid key for this function. diff --git a/src/cocoa_init.m b/src/cocoa_init.m index c87eddf6..eb96d5db 100644 --- a/src/cocoa_init.m +++ b/src/cocoa_init.m @@ -72,7 +72,10 @@ static void changeToResourcesDirectory(void) // static void createKeyTables(void) { + int scancode; + memset(_glfw.ns.publicKeys, -1, sizeof(_glfw.ns.publicKeys)); + memset(_glfw.ns.nativeKeys, -1, sizeof(_glfw.ns.nativeKeys)); _glfw.ns.publicKeys[0x1D] = GLFW_KEY_0; _glfw.ns.publicKeys[0x12] = GLFW_KEY_1; @@ -188,6 +191,13 @@ static void createKeyTables(void) _glfw.ns.publicKeys[0x51] = GLFW_KEY_KP_EQUAL; _glfw.ns.publicKeys[0x43] = GLFW_KEY_KP_MULTIPLY; _glfw.ns.publicKeys[0x4E] = GLFW_KEY_KP_SUBTRACT; + + for (scancode = 0; scancode < 256; scancode++) + { + // Store the reverse translation for faster key name lookup + if (_glfw.ns.publicKeys[scancode] >= 0) + _glfw.ns.nativeKeys[_glfw.ns.publicKeys[scancode]] = scancode; + } } @@ -211,6 +221,17 @@ int _glfwPlatformInit(void) CGEventSourceSetLocalEventsSuppressionInterval(_glfw.ns.eventSource, 0.0); + // TODO: Catch kTISNotifySelectedKeyboardInputSourceChanged and update + + _glfw.ns.inputSource = TISCopyCurrentKeyboardLayoutInputSource(); + if (!_glfw.ns.inputSource) + return GLFW_FALSE; + + _glfw.ns.unicodeData = TISGetInputSourceProperty(_glfw.ns.inputSource, + kTISPropertyUnicodeKeyLayoutData); + if (!_glfw.ns.unicodeData) + return GLFW_FALSE; + if (!_glfwInitContextAPI()) return GLFW_FALSE; @@ -222,6 +243,12 @@ int _glfwPlatformInit(void) void _glfwPlatformTerminate(void) { + if (_glfw.ns.inputSource) + { + CFRelease(_glfw.ns.inputSource); + _glfw.ns.inputSource = NULL; + } + if (_glfw.ns.eventSource) { CFRelease(_glfw.ns.eventSource); diff --git a/src/cocoa_platform.h b/src/cocoa_platform.h index 8bba1daa..9e895684 100644 --- a/src/cocoa_platform.h +++ b/src/cocoa_platform.h @@ -30,8 +30,10 @@ #include #if defined(__OBJC__) +#import #import #else +#include #include typedef void* id; #endif @@ -73,13 +75,17 @@ typedef struct _GLFWwindowNS // typedef struct _GLFWlibraryNS { - CGEventSourceRef eventSource; - id delegate; - id autoreleasePool; - id cursor; + CGEventSourceRef eventSource; + id delegate; + id autoreleasePool; + id cursor; + TISInputSourceRef inputSource; + id unicodeData; - short int publicKeys[256]; - char* clipboardString; + char keyName[64]; + short int publicKeys[256]; + short int nativeKeys[GLFW_KEY_LAST + 1]; + char* clipboardString; } _GLFWlibraryNS; diff --git a/src/cocoa_window.m b/src/cocoa_window.m index 7a371495..600cf1e7 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -1168,6 +1168,48 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) CGAssociateMouseAndMouseCursorPosition(true); } +const char* _glfwPlatformGetKeyName(int key, int scancode) +{ + if (key != GLFW_KEY_UNKNOWN) + scancode = _glfw.ns.nativeKeys[key]; + + if (!_glfwIsPrintable(_glfw.ns.publicKeys[scancode])) + return NULL; + + UInt32 deadKeyState = 0; + UniChar characters[8]; + UniCharCount characterCount = 0; + + if (UCKeyTranslate([(NSData*) _glfw.ns.unicodeData bytes], + scancode, + kUCKeyActionDisplay, + 0, + LMGetKbdType(), + kUCKeyTranslateNoDeadKeysBit, + &deadKeyState, + sizeof(characters) / sizeof(characters[0]), + &characterCount, + characters) != noErr) + { + return NULL; + } + + if (!characterCount) + return NULL; + + CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, + characters, + characterCount, + kCFAllocatorNull); + CFStringGetCString(string, + _glfw.ns.keyName, + sizeof(_glfw.ns.keyName), + kCFStringEncodingUTF8); + CFRelease(string); + + return _glfw.ns.keyName; +} + int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const GLFWimage* image, int xhot, int yhot) diff --git a/src/input.c b/src/input.c index 94f491cf..6eac8805 100644 --- a/src/input.c +++ b/src/input.c @@ -223,6 +223,18 @@ void _glfwInputDrop(_GLFWwindow* window, int count, const char** paths) } +////////////////////////////////////////////////////////////////////////// +////// GLFW internal API ////// +////////////////////////////////////////////////////////////////////////// + +GLFWbool _glfwIsPrintable(int key) +{ + return (key >= GLFW_KEY_APOSTROPHE && key <= GLFW_KEY_WORLD_2) || + (key >= GLFW_KEY_KP_0 && key <= GLFW_KEY_KP_ADD) || + key == GLFW_KEY_KP_EQUAL; +} + + ////////////////////////////////////////////////////////////////////////// ////// GLFW public API ////// ////////////////////////////////////////////////////////////////////////// @@ -270,6 +282,12 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value) } } +GLFWAPI const char* glfwGetKeyName(int key, int scancode) +{ + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + return _glfwPlatformGetKeyName(key, scancode); +} + GLFWAPI int glfwGetKey(GLFWwindow* handle, int key) { _GLFWwindow* window = (_GLFWwindow*) handle; diff --git a/src/internal.h b/src/internal.h index a8feea8a..9c6fbaaa 100644 --- a/src/internal.h +++ b/src/internal.h @@ -428,6 +428,11 @@ void _glfwPlatformSetCursorPos(_GLFWwindow* window, double xpos, double ypos); */ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode); +/*! @copydoc glfwGetKeyName + * @ingroup platform + */ +const char* _glfwPlatformGetKeyName(int key, int scancode); + /*! @copydoc glfwGetMonitors * @ingroup platform */ @@ -900,4 +905,8 @@ void _glfwFreeMonitor(_GLFWmonitor* monitor); */ void _glfwFreeMonitors(_GLFWmonitor** monitors, int count); +/*! @ingroup utility + */ +int _glfwIsPrintable(int key); + #endif // _glfw3_internal_h_ diff --git a/src/mir_window.c b/src/mir_window.c index aed69341..1480ab7a 100644 --- a/src/mir_window.c +++ b/src/mir_window.c @@ -802,6 +802,13 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) "Mir: Unsupported function %s", __PRETTY_FUNCTION__); } +const char* _glfwPlatformGetKeyName(int key, int scancode) +{ + _glfwInputError(GLFW_PLATFORM_ERROR, + "Mir: Unsupported function %s", __PRETTY_FUNCTION__); + return NULL; +} + void _glfwPlatformSetClipboardString(_GLFWwindow* window, const char* string) { _glfwInputError(GLFW_PLATFORM_ERROR, diff --git a/src/win32_init.c b/src/win32_init.c index 6d0f90d8..cbb0f600 100644 --- a/src/win32_init.c +++ b/src/win32_init.c @@ -130,7 +130,10 @@ static void terminateLibraries(void) // static void createKeyTables(void) { + int scancode; + memset(_glfw.win32.publicKeys, -1, sizeof(_glfw.win32.publicKeys)); + memset(_glfw.win32.nativeKeys, -1, sizeof(_glfw.win32.nativeKeys)); _glfw.win32.publicKeys[0x00B] = GLFW_KEY_0; _glfw.win32.publicKeys[0x002] = GLFW_KEY_1; @@ -252,6 +255,12 @@ static void createKeyTables(void) _glfw.win32.publicKeys[0x11C] = GLFW_KEY_KP_ENTER; _glfw.win32.publicKeys[0x037] = GLFW_KEY_KP_MULTIPLY; _glfw.win32.publicKeys[0x04A] = GLFW_KEY_KP_SUBTRACT; + + for (scancode = 0; scancode < 512; scancode++) + { + if (_glfw.win32.publicKeys[scancode] > 0) + _glfw.win32.nativeKeys[_glfw.win32.publicKeys[scancode]] = scancode; + } } diff --git a/src/win32_platform.h b/src/win32_platform.h index 718f5a1e..5e64abcd 100644 --- a/src/win32_platform.h +++ b/src/win32_platform.h @@ -182,7 +182,9 @@ typedef struct _GLFWlibraryWin32 { DWORD foregroundLockTimeout; char* clipboardString; + char keyName[64]; short int publicKeys[512]; + short int nativeKeys[GLFW_KEY_LAST + 1]; // winmm.dll struct { diff --git a/src/win32_window.c b/src/win32_window.c index 2b8e5718..8a7211dc 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -1180,6 +1180,30 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) SetCursor(NULL); } +const char* _glfwPlatformGetKeyName(int key, int scancode) +{ + WCHAR name[16]; + + if (key != GLFW_KEY_UNKNOWN) + scancode = _glfw.win32.nativeKeys[key]; + + if (!_glfwIsPrintable(_glfw.win32.publicKeys[scancode])) + return NULL; + + if (!GetKeyNameTextW(scancode << 16, name, sizeof(name) / sizeof(WCHAR))) + return NULL; + + if (!WideCharToMultiByte(CP_UTF8, 0, name, -1, + _glfw.win32.keyName, + sizeof(_glfw.win32.keyName), + NULL, NULL)) + { + return NULL; + } + + return _glfw.win32.keyName; +} + int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const GLFWimage* image, int xhot, int yhot) diff --git a/src/wl_window.c b/src/wl_window.c index fd729ff0..658b93e4 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -436,6 +436,12 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) _glfwPlatformSetCursor(window, window->wl.currentCursor); } +const char* _glfwPlatformGetKeyName(int key, int scancode) +{ + // TODO + return NULL; +} + int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const GLFWimage* image, int xhot, int yhot) diff --git a/src/x11_init.c b/src/x11_init.c index 19847d70..2f492e40 100644 --- a/src/x11_init.c +++ b/src/x11_init.c @@ -234,6 +234,7 @@ static void createKeyTables(void) int scancode, key; memset(_glfw.x11.publicKeys, -1, sizeof(_glfw.x11.publicKeys)); + memset(_glfw.x11.nativeKeys, -1, sizeof(_glfw.x11.nativeKeys)); if (_glfw.x11.xkb.available) { @@ -312,12 +313,16 @@ static void createKeyTables(void) XkbFreeClientMap(desc, 0, True); } - // Translate the un-translated key codes using traditional X11 KeySym - // lookups for (scancode = 0; scancode < 256; scancode++) { + // Translate the un-translated key codes using traditional X11 KeySym + // lookups if (_glfw.x11.publicKeys[scancode] < 0) _glfw.x11.publicKeys[scancode] = translateKeyCode(scancode); + + // Store the reverse translation for faster key name lookup + if (_glfw.x11.publicKeys[scancode] > 0) + _glfw.x11.nativeKeys[_glfw.x11.publicKeys[scancode]] = scancode; } } diff --git a/src/x11_platform.h b/src/x11_platform.h index d778e5b4..bfc0d807 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -121,8 +121,12 @@ typedef struct _GLFWlibraryX11 int errorCode; // Clipboard string (while the selection is owned) char* clipboardString; + // Key name string + char keyName[64]; // X11 keycode to GLFW key LUT short int publicKeys[256]; + // GLFW key to X11 keycode LUT + short int nativeKeys[GLFW_KEY_LAST + 1]; // Window manager atoms Atom WM_PROTOCOLS; diff --git a/src/x11_window.c b/src/x11_window.c index 5154c72a..56c0fe4f 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -1939,6 +1939,34 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) } } +const char* _glfwPlatformGetKeyName(int key, int scancode) +{ + KeySym keysym; + int extra; + + if (!_glfw.x11.xkb.available) + return NULL; + + if (key != GLFW_KEY_UNKNOWN) + scancode = _glfw.x11.nativeKeys[key]; + + if (!_glfwIsPrintable(_glfw.x11.publicKeys[scancode])) + return NULL; + + keysym = XkbKeycodeToKeysym(_glfw.x11.display, scancode, 0, 0); + if (keysym == NoSymbol) + return NULL; + + XkbTranslateKeySym(_glfw.x11.display, &keysym, 0, + _glfw.x11.keyName, sizeof(_glfw.x11.keyName), + &extra); + + if (!strlen(_glfw.x11.keyName)) + return NULL; + + return _glfw.x11.keyName; +} + int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const GLFWimage* image, int xhot, int yhot) diff --git a/tests/events.c b/tests/events.c index 1da8341b..8419918d 100644 --- a/tests/events.c +++ b/tests/events.c @@ -360,12 +360,25 @@ static void scroll_callback(GLFWwindow* window, double x, double y) static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { Slot* slot = glfwGetWindowUserPointer(window); + const char* name = glfwGetKeyName(key, scancode); - printf("%08x to %i at %0.3f: Key 0x%04x Scancode 0x%04x (%s) (with%s) was %s\n", - counter++, slot->number, glfwGetTime(), key, scancode, - get_key_name(key), - get_mods_name(mods), - get_action_name(action)); + if (name) + { + printf("%08x to %i at %0.3f: Key 0x%04x Scancode 0x%04x (%s) (%s) (with%s) was %s\n", + counter++, slot->number, glfwGetTime(), key, scancode, + get_key_name(key), + name, + get_mods_name(mods), + get_action_name(action)); + } + else + { + printf("%08x to %i at %0.3f: Key 0x%04x Scancode 0x%04x (%s) (with%s) was %s\n", + counter++, slot->number, glfwGetTime(), key, scancode, + get_key_name(key), + get_mods_name(mods), + get_action_name(action)); + } if (action != GLFW_PRESS) return;