diff --git a/glfw/cocoa_init.m b/glfw/cocoa_init.m index 2585fe349..feebbabae 100644 --- a/glfw/cocoa_init.m +++ b/glfw/cocoa_init.m @@ -208,6 +208,9 @@ static GLFWbool updateUnicodeDataNS(void) _glfw.ns.unicodeData = nil; } + for (_GLFWwindow *window = _glfw.windowListHead; window; window = window->next) + window->ns.deadKeyState = 0; + _glfw.ns.inputSource = TISCopyCurrentKeyboardLayoutInputSource(); if (!_glfw.ns.inputSource) { diff --git a/glfw/cocoa_platform.h b/glfw/cocoa_platform.h index ee18cbb51..e33fbb652 100644 --- a/glfw/cocoa_platform.h +++ b/glfw/cocoa_platform.h @@ -101,6 +101,8 @@ typedef struct _GLFWwindowNS // The text input filter callback GLFWcocoatextinputfilterfun textInputFilterCallback; + // Dead key state + UInt32 deadKeyState; } _GLFWwindowNS; // Cocoa-specific global data diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index c24c12990..decd437db 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -719,20 +719,98 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; [super updateTrackingAreas]; } +static inline UInt32 +convert_cocoa_to_carbon_modifiers(NSUInteger flags) { + UInt32 mods = 0; + if (flags & NSEventModifierFlagShift) + mods |= shiftKey; + if (flags & NSEventModifierFlagControl) + mods |= controlKey; + if (flags & NSEventModifierFlagOption) + mods |= optionKey; + if (flags & NSEventModifierFlagCommand) + mods |= cmdKey; + if (flags & NSEventModifierFlagCapsLock) + mods |= alphaLock; + + return (mods >> 8) & 0xFF; +} + +static inline void +convert_utf16_to_utf8(UniChar *src, UniCharCount src_length, char *dest, size_t dest_sz) { + CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, + src, + src_length, + kCFAllocatorNull); + CFStringGetCString(string, + dest, + dest_sz, + kCFStringEncodingUTF8); + CFRelease(string); +} + +static inline GLFWbool +is_ascii_control_char(char x) { + return x == 0 || (1 <= x && x <= 31) || x == 127; +} + - (void)keyDown:(NSEvent *)event { const unsigned int scancode = [event keyCode]; + const NSUInteger flags = [event modifierFlags]; + const int mods = translateFlags(flags); const int key = translateKey(scancode, GLFW_TRUE); - const int mods = translateFlags([event modifierFlags]); + const GLFWbool process_text = !window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, scancode) != 1; _glfw.ns.text[0] = 0; - if (!window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, scancode) != 1) { - // this will call insertText with the text for this event, if any - [self interpretKeyEvents:[NSArray arrayWithObject:event]]; - if ((1 <= _glfw.ns.text[0] && _glfw.ns.text[0] <= 31) || (unsigned)_glfw.ns.text[0] == 127) _glfw.ns.text[0] = 0; // dont send text for ascii control codes + if (!_glfw.ns.unicodeData) { + // Using the cocoa API for key handling is disabled, as there is no + // reliable way to handle dead keys using it. Only use it if the + // keyboard unicode data is not available. + if (process_text) { + // this will call insertText with the text for this event, if any + [self interpretKeyEvents:[NSArray arrayWithObject:event]]; + } + } else { + static UniChar text[256]; + UniCharCount char_count = 0; + if (UCKeyTranslate( + [(NSData*) _glfw.ns.unicodeData bytes], + scancode, + kUCKeyActionDown, + convert_cocoa_to_carbon_modifiers(flags), + LMGetKbdType(), + (process_text ? 0 : kUCKeyTranslateNoDeadKeysMask), + &(window->ns.deadKeyState), + sizeof(text)/sizeof(text[0]), + &char_count, + text + ) != noErr) { + debug_key(@"UCKeyTranslate failed for scancode: 0x%x (%s) %s\n", scancode, safe_name_for_scancode(scancode), format_mods(mods)); + window->ns.deadKeyState = 0; + return; + } + if (process_text) { + // We check if cocoa wants to insert text, as UCKeyTranslate + // inserts text even when the cmd key is pressed. For instance, + // cmd+a will result in the text a. + [self interpretKeyEvents:[NSArray arrayWithObject:event]]; + debug_key(@"char_count: %lu cocoa text: %s\n", char_count, format_text(_glfw.ns.text)); + GLFWbool cocoa_wants_to_insert_text = !is_ascii_control_char(_glfw.ns.text[0]); + _glfw.ns.text[0] = 0; + if (char_count && cocoa_wants_to_insert_text) convert_utf16_to_utf8(text, char_count, _glfw.ns.text, sizeof(_glfw.ns.text)); + } else { + window->ns.deadKeyState = 0; + } + if (window->ns.deadKeyState && char_count == 0) { + debug_key(@"Ignoring dead key. deadKeyState: 0x%x scancode: 0x%x (%s) %s\n", + window->ns.deadKeyState, scancode, safe_name_for_scancode(scancode), format_mods(mods)); + return; + } } - debug_key(@"scancode: 0x%x (%s)%stext: %s glfw_key: %s\n", + debug_key(@"scancode: 0x%x (%s) %stext: %s glfw_key: %s\n", scancode, safe_name_for_scancode(scancode), format_mods(mods), format_text(_glfw.ns.text), _glfwGetKeyName(key)); + if (is_ascii_control_char(_glfw.ns.text[0])) _glfw.ns.text[0] = 0; // dont send text for ascii control codes _glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods, _glfw.ns.text, 0); } @@ -1235,6 +1313,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig) { + window->ns.deadKeyState = 0; if (!initializeAppKit()) return GLFW_FALSE; @@ -1779,16 +1858,7 @@ const char* _glfwPlatformGetScancodeName(int scancode) if (!characterCount) return NULL; - CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, - characters, - characterCount, - kCFAllocatorNull); - CFStringGetCString(string, - _glfw.ns.keyName, - sizeof(_glfw.ns.keyName), - kCFStringEncodingUTF8); - CFRelease(string); - + convert_utf16_to_utf8(characters, characterCount, _glfw.ns.keyName, sizeof(_glfw.ns.keyName)); return _glfw.ns.keyName; }