michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: michael@0: #include "TextInputHandler.h" michael@0: michael@0: #ifdef MOZ_LOGGING michael@0: #define FORCE_PR_LOG /* Allow logging in the release build */ michael@0: #endif // MOZ_LOGGING michael@0: #include "prlog.h" michael@0: michael@0: #include "mozilla/MiscEvents.h" michael@0: #include "mozilla/MouseEvents.h" michael@0: #include "mozilla/TextEvents.h" michael@0: michael@0: #include "nsChildView.h" michael@0: #include "nsObjCExceptions.h" michael@0: #include "nsBidiUtils.h" michael@0: #include "nsToolkit.h" michael@0: #include "nsCocoaUtils.h" michael@0: #include "WidgetUtils.h" michael@0: #include "nsPrintfCString.h" michael@0: michael@0: #ifdef __LP64__ michael@0: #include "ComplexTextInputPanel.h" michael@0: #include michael@0: #endif // __LP64__ michael@0: michael@0: #ifdef MOZ_LOGGING michael@0: #define FORCE_PR_LOG michael@0: #endif michael@0: #include "prlog.h" michael@0: michael@0: #ifndef __LP64__ michael@0: enum { michael@0: // Currently focused ChildView (while this TSM document is active). michael@0: // Transient (only set while TSMProcessRawKeyEvent() is processing a key michael@0: // event), and the ChildView will be retained and released around the call michael@0: // to TSMProcessRawKeyEvent() -- so it can be weak. michael@0: kFocusedChildViewTSMDocPropertyTag = 'GKFV', // type ChildView* [WEAK] michael@0: }; michael@0: michael@0: // Undocumented HIToolbox function used by WebKit to allow Carbon-based IME michael@0: // to work in a Cocoa-based browser (like Safari or Cocoa-widgets Firefox). michael@0: // (Recent WebKit versions actually use a thin wrapper around this function michael@0: // called WKSendKeyEventToTSM().) michael@0: // michael@0: // Calling TSMProcessRawKeyEvent() from ChildView's keyDown: and keyUp: michael@0: // methods (when the ChildView is a plugin view) bypasses Cocoa's IME michael@0: // infrastructure and (instead) causes Carbon TSM events to be sent on each michael@0: // NSKeyDown event. We install a Carbon event handler michael@0: // (PluginKeyEventsHandler()) to catch these events and pass them to Gecko michael@0: // (which in turn passes them to the plugin). michael@0: extern "C" long TSMProcessRawKeyEvent(EventRef carbonEvent); michael@0: #endif // __LP64__ michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::widget; michael@0: michael@0: #ifdef PR_LOGGING michael@0: michael@0: PRLogModuleInfo* gLog = nullptr; michael@0: michael@0: static const char* michael@0: OnOrOff(bool aBool) michael@0: { michael@0: return aBool ? "ON" : "off"; michael@0: } michael@0: michael@0: static const char* michael@0: TrueOrFalse(bool aBool) michael@0: { michael@0: return aBool ? "TRUE" : "FALSE"; michael@0: } michael@0: michael@0: static const char* michael@0: GetKeyNameForNativeKeyCode(unsigned short aNativeKeyCode) michael@0: { michael@0: switch (aNativeKeyCode) { michael@0: case kVK_Escape: return "Escape"; michael@0: case kVK_RightCommand: return "Right-Command"; michael@0: case kVK_Command: return "Command"; michael@0: case kVK_Shift: return "Shift"; michael@0: case kVK_CapsLock: return "CapsLock"; michael@0: case kVK_Option: return "Option"; michael@0: case kVK_Control: return "Control"; michael@0: case kVK_RightShift: return "Right-Shift"; michael@0: case kVK_RightOption: return "Right-Option"; michael@0: case kVK_RightControl: return "Right-Control"; michael@0: case kVK_ANSI_KeypadClear: return "Clear"; michael@0: michael@0: case kVK_F1: return "F1"; michael@0: case kVK_F2: return "F2"; michael@0: case kVK_F3: return "F3"; michael@0: case kVK_F4: return "F4"; michael@0: case kVK_F5: return "F5"; michael@0: case kVK_F6: return "F6"; michael@0: case kVK_F7: return "F7"; michael@0: case kVK_F8: return "F8"; michael@0: case kVK_F9: return "F9"; michael@0: case kVK_F10: return "F10"; michael@0: case kVK_F11: return "F11"; michael@0: case kVK_F12: return "F12"; michael@0: case kVK_F13: return "F13/PrintScreen"; michael@0: case kVK_F14: return "F14/ScrollLock"; michael@0: case kVK_F15: return "F15/Pause"; michael@0: michael@0: case kVK_ANSI_Keypad0: return "NumPad-0"; michael@0: case kVK_ANSI_Keypad1: return "NumPad-1"; michael@0: case kVK_ANSI_Keypad2: return "NumPad-2"; michael@0: case kVK_ANSI_Keypad3: return "NumPad-3"; michael@0: case kVK_ANSI_Keypad4: return "NumPad-4"; michael@0: case kVK_ANSI_Keypad5: return "NumPad-5"; michael@0: case kVK_ANSI_Keypad6: return "NumPad-6"; michael@0: case kVK_ANSI_Keypad7: return "NumPad-7"; michael@0: case kVK_ANSI_Keypad8: return "NumPad-8"; michael@0: case kVK_ANSI_Keypad9: return "NumPad-9"; michael@0: michael@0: case kVK_ANSI_KeypadMultiply: return "NumPad-*"; michael@0: case kVK_ANSI_KeypadPlus: return "NumPad-+"; michael@0: case kVK_ANSI_KeypadMinus: return "NumPad--"; michael@0: case kVK_ANSI_KeypadDecimal: return "NumPad-."; michael@0: case kVK_ANSI_KeypadDivide: return "NumPad-/"; michael@0: case kVK_ANSI_KeypadEquals: return "NumPad-="; michael@0: case kVK_ANSI_KeypadEnter: return "NumPad-Enter"; michael@0: case kVK_Return: return "Return"; michael@0: case kVK_Powerbook_KeypadEnter: return "NumPad-EnterOnPowerBook"; michael@0: michael@0: case kVK_PC_Insert: return "Insert/Help"; michael@0: case kVK_PC_Delete: return "Delete"; michael@0: case kVK_Tab: return "Tab"; michael@0: case kVK_PC_Backspace: return "Backspace"; michael@0: case kVK_Home: return "Home"; michael@0: case kVK_End: return "End"; michael@0: case kVK_PageUp: return "PageUp"; michael@0: case kVK_PageDown: return "PageDown"; michael@0: case kVK_LeftArrow: return "LeftArrow"; michael@0: case kVK_RightArrow: return "RightArrow"; michael@0: case kVK_UpArrow: return "UpArrow"; michael@0: case kVK_DownArrow: return "DownArrow"; michael@0: case kVK_PC_ContextMenu: return "ContextMenu"; michael@0: michael@0: case kVK_Function: return "Function"; michael@0: case kVK_VolumeUp: return "VolumeUp"; michael@0: case kVK_VolumeDown: return "VolumeDown"; michael@0: case kVK_Mute: return "Mute"; michael@0: michael@0: case kVK_ISO_Section: return "ISO_Section"; michael@0: michael@0: case kVK_JIS_Yen: return "JIS_Yen"; michael@0: case kVK_JIS_Underscore: return "JIS_Underscore"; michael@0: case kVK_JIS_KeypadComma: return "JIS_KeypadComma"; michael@0: case kVK_JIS_Eisu: return "JIS_Eisu"; michael@0: case kVK_JIS_Kana: return "JIS_Kana"; michael@0: michael@0: case kVK_ANSI_A: return "A"; michael@0: case kVK_ANSI_B: return "B"; michael@0: case kVK_ANSI_C: return "C"; michael@0: case kVK_ANSI_D: return "D"; michael@0: case kVK_ANSI_E: return "E"; michael@0: case kVK_ANSI_F: return "F"; michael@0: case kVK_ANSI_G: return "G"; michael@0: case kVK_ANSI_H: return "H"; michael@0: case kVK_ANSI_I: return "I"; michael@0: case kVK_ANSI_J: return "J"; michael@0: case kVK_ANSI_K: return "K"; michael@0: case kVK_ANSI_L: return "L"; michael@0: case kVK_ANSI_M: return "M"; michael@0: case kVK_ANSI_N: return "N"; michael@0: case kVK_ANSI_O: return "O"; michael@0: case kVK_ANSI_P: return "P"; michael@0: case kVK_ANSI_Q: return "Q"; michael@0: case kVK_ANSI_R: return "R"; michael@0: case kVK_ANSI_S: return "S"; michael@0: case kVK_ANSI_T: return "T"; michael@0: case kVK_ANSI_U: return "U"; michael@0: case kVK_ANSI_V: return "V"; michael@0: case kVK_ANSI_W: return "W"; michael@0: case kVK_ANSI_X: return "X"; michael@0: case kVK_ANSI_Y: return "Y"; michael@0: case kVK_ANSI_Z: return "Z"; michael@0: michael@0: case kVK_ANSI_1: return "1"; michael@0: case kVK_ANSI_2: return "2"; michael@0: case kVK_ANSI_3: return "3"; michael@0: case kVK_ANSI_4: return "4"; michael@0: case kVK_ANSI_5: return "5"; michael@0: case kVK_ANSI_6: return "6"; michael@0: case kVK_ANSI_7: return "7"; michael@0: case kVK_ANSI_8: return "8"; michael@0: case kVK_ANSI_9: return "9"; michael@0: case kVK_ANSI_0: return "0"; michael@0: case kVK_ANSI_Equal: return "Equal"; michael@0: case kVK_ANSI_Minus: return "Minus"; michael@0: case kVK_ANSI_RightBracket: return "RightBracket"; michael@0: case kVK_ANSI_LeftBracket: return "LeftBracket"; michael@0: case kVK_ANSI_Quote: return "Quote"; michael@0: case kVK_ANSI_Semicolon: return "Semicolon"; michael@0: case kVK_ANSI_Backslash: return "Backslash"; michael@0: case kVK_ANSI_Comma: return "Comma"; michael@0: case kVK_ANSI_Slash: return "Slash"; michael@0: case kVK_ANSI_Period: return "Period"; michael@0: case kVK_ANSI_Grave: return "Grave"; michael@0: michael@0: default: return "undefined"; michael@0: } michael@0: } michael@0: michael@0: static const char* michael@0: GetCharacters(const NSString* aString) michael@0: { michael@0: nsAutoString str; michael@0: nsCocoaUtils::GetStringForNSString(aString, str); michael@0: if (str.IsEmpty()) { michael@0: return ""; michael@0: } michael@0: michael@0: nsAutoString escapedStr; michael@0: for (uint32_t i = 0; i < str.Length(); i++) { michael@0: char16_t ch = str[i]; michael@0: if (ch < 0x20) { michael@0: nsPrintfCString utf8str("(U+%04X)", ch); michael@0: escapedStr += NS_ConvertUTF8toUTF16(utf8str); michael@0: } else if (ch <= 0x7E) { michael@0: escapedStr += ch; michael@0: } else { michael@0: nsPrintfCString utf8str("(U+%04X)", ch); michael@0: escapedStr += ch; michael@0: escapedStr += NS_ConvertUTF8toUTF16(utf8str); michael@0: } michael@0: } michael@0: michael@0: // the result will be freed automatically by cocoa. michael@0: NSString* result = nsCocoaUtils::ToNSString(escapedStr); michael@0: return [result UTF8String]; michael@0: } michael@0: michael@0: static const char* michael@0: GetCharacters(const CFStringRef aString) michael@0: { michael@0: const NSString* str = reinterpret_cast(aString); michael@0: return GetCharacters(str); michael@0: } michael@0: michael@0: static const char* michael@0: GetNativeKeyEventType(NSEvent* aNativeEvent) michael@0: { michael@0: switch ([aNativeEvent type]) { michael@0: case NSKeyDown: return "NSKeyDown"; michael@0: case NSKeyUp: return "NSKeyUp"; michael@0: case NSFlagsChanged: return "NSFlagsChanged"; michael@0: default: return "not key event"; michael@0: } michael@0: } michael@0: michael@0: static const char* michael@0: GetGeckoKeyEventType(const WidgetEvent& aEvent) michael@0: { michael@0: switch (aEvent.message) { michael@0: case NS_KEY_DOWN: return "NS_KEY_DOWN"; michael@0: case NS_KEY_UP: return "NS_KEY_UP"; michael@0: case NS_KEY_PRESS: return "NS_KEY_PRESS"; michael@0: default: return "not key event"; michael@0: } michael@0: } michael@0: michael@0: static const char* michael@0: GetRangeTypeName(uint32_t aRangeType) michael@0: { michael@0: switch (aRangeType) { michael@0: case NS_TEXTRANGE_RAWINPUT: michael@0: return "NS_TEXTRANGE_RAWINPUT"; michael@0: case NS_TEXTRANGE_CONVERTEDTEXT: michael@0: return "NS_TEXTRANGE_CONVERTEDTEXT"; michael@0: case NS_TEXTRANGE_SELECTEDRAWTEXT: michael@0: return "NS_TEXTRANGE_SELECTEDRAWTEXT"; michael@0: case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT: michael@0: return "NS_TEXTRANGE_SELECTEDCONVERTEDTEXT"; michael@0: case NS_TEXTRANGE_CARETPOSITION: michael@0: return "NS_TEXTRANGE_CARETPOSITION"; michael@0: default: michael@0: return "invalid range type"; michael@0: } michael@0: } michael@0: michael@0: static const char* michael@0: GetWindowLevelName(NSInteger aWindowLevel) michael@0: { michael@0: switch (aWindowLevel) { michael@0: case kCGBaseWindowLevelKey: michael@0: return "kCGBaseWindowLevelKey (NSNormalWindowLevel)"; michael@0: case kCGMinimumWindowLevelKey: michael@0: return "kCGMinimumWindowLevelKey"; michael@0: case kCGDesktopWindowLevelKey: michael@0: return "kCGDesktopWindowLevelKey"; michael@0: case kCGBackstopMenuLevelKey: michael@0: return "kCGBackstopMenuLevelKey"; michael@0: case kCGNormalWindowLevelKey: michael@0: return "kCGNormalWindowLevelKey"; michael@0: case kCGFloatingWindowLevelKey: michael@0: return "kCGFloatingWindowLevelKey (NSFloatingWindowLevel)"; michael@0: case kCGTornOffMenuWindowLevelKey: michael@0: return "kCGTornOffMenuWindowLevelKey (NSSubmenuWindowLevel, NSTornOffMenuWindowLevel)"; michael@0: case kCGDockWindowLevelKey: michael@0: return "kCGDockWindowLevelKey (NSDockWindowLevel)"; michael@0: case kCGMainMenuWindowLevelKey: michael@0: return "kCGMainMenuWindowLevelKey (NSMainMenuWindowLevel)"; michael@0: case kCGStatusWindowLevelKey: michael@0: return "kCGStatusWindowLevelKey (NSStatusWindowLevel)"; michael@0: case kCGModalPanelWindowLevelKey: michael@0: return "kCGModalPanelWindowLevelKey (NSModalPanelWindowLevel)"; michael@0: case kCGPopUpMenuWindowLevelKey: michael@0: return "kCGPopUpMenuWindowLevelKey (NSPopUpMenuWindowLevel)"; michael@0: case kCGDraggingWindowLevelKey: michael@0: return "kCGDraggingWindowLevelKey"; michael@0: case kCGScreenSaverWindowLevelKey: michael@0: return "kCGScreenSaverWindowLevelKey (NSScreenSaverWindowLevel)"; michael@0: case kCGMaximumWindowLevelKey: michael@0: return "kCGMaximumWindowLevelKey"; michael@0: case kCGOverlayWindowLevelKey: michael@0: return "kCGOverlayWindowLevelKey"; michael@0: case kCGHelpWindowLevelKey: michael@0: return "kCGHelpWindowLevelKey"; michael@0: case kCGUtilityWindowLevelKey: michael@0: return "kCGUtilityWindowLevelKey"; michael@0: case kCGDesktopIconWindowLevelKey: michael@0: return "kCGDesktopIconWindowLevelKey"; michael@0: case kCGCursorWindowLevelKey: michael@0: return "kCGCursorWindowLevelKey"; michael@0: case kCGNumberOfWindowLevelKeys: michael@0: return "kCGNumberOfWindowLevelKeys"; michael@0: default: michael@0: return "unknown window level"; michael@0: } michael@0: } michael@0: michael@0: #endif // #ifdef PR_LOGGING michael@0: michael@0: static bool michael@0: IsControlChar(uint32_t aCharCode) michael@0: { michael@0: return aCharCode < ' ' || aCharCode == 0x7F; michael@0: } michael@0: michael@0: static uint32_t gHandlerInstanceCount = 0; michael@0: static TISInputSourceWrapper gCurrentInputSource; michael@0: michael@0: static void michael@0: InitLogModule() michael@0: { michael@0: #ifdef PR_LOGGING michael@0: // Clear() is always called when TISInputSourceWrappper is created. michael@0: if (!gLog) { michael@0: gLog = PR_NewLogModule("TextInputHandlerWidgets"); michael@0: TextInputHandler::DebugPrintAllKeyboardLayouts(); michael@0: IMEInputHandler::DebugPrintAllIMEModes(); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: static void michael@0: InitCurrentInputSource() michael@0: { michael@0: if (gHandlerInstanceCount > 0 && michael@0: !gCurrentInputSource.IsInitializedByCurrentInputSource()) { michael@0: gCurrentInputSource.InitByCurrentInputSource(); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: FinalizeCurrentInputSource() michael@0: { michael@0: gCurrentInputSource.Clear(); michael@0: } michael@0: michael@0: michael@0: #pragma mark - michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * michael@0: * TISInputSourceWrapper implementation michael@0: * michael@0: ******************************************************************************/ michael@0: michael@0: // static michael@0: TISInputSourceWrapper& michael@0: TISInputSourceWrapper::CurrentInputSource() michael@0: { michael@0: InitCurrentInputSource(); michael@0: return gCurrentInputSource; michael@0: } michael@0: michael@0: bool michael@0: TISInputSourceWrapper::TranslateToString(UInt32 aKeyCode, UInt32 aModifiers, michael@0: UInt32 aKbType, nsAString &aStr) michael@0: { michael@0: aStr.Truncate(); michael@0: michael@0: const UCKeyboardLayout* UCKey = GetUCKeyboardLayout(); michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TISInputSourceWrapper::TranslateToString, aKeyCode=0x%X, " michael@0: "aModifiers=0x%X, aKbType=0x%X UCKey=%p\n " michael@0: "Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s", michael@0: this, aKeyCode, aModifiers, aKbType, UCKey, michael@0: OnOrOff(aModifiers & shiftKey), OnOrOff(aModifiers & controlKey), michael@0: OnOrOff(aModifiers & optionKey), OnOrOff(aModifiers & cmdKey), michael@0: OnOrOff(aModifiers & alphaLock), michael@0: OnOrOff(aModifiers & kEventKeyModifierNumLockMask))); michael@0: michael@0: NS_ENSURE_TRUE(UCKey, false); michael@0: michael@0: UInt32 deadKeyState = 0; michael@0: UniCharCount len; michael@0: UniChar chars[5]; michael@0: OSStatus err = ::UCKeyTranslate(UCKey, aKeyCode, michael@0: kUCKeyActionDown, aModifiers >> 8, michael@0: aKbType, kUCKeyTranslateNoDeadKeysMask, michael@0: &deadKeyState, 5, &len, chars); michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TISInputSourceWrapper::TranslateToString, err=0x%X, len=%llu", michael@0: this, err, len)); michael@0: michael@0: NS_ENSURE_TRUE(err == noErr, false); michael@0: if (len == 0) { michael@0: return true; michael@0: } michael@0: NS_ENSURE_TRUE(EnsureStringLength(aStr, len), false); michael@0: NS_ASSERTION(sizeof(char16_t) == sizeof(UniChar), michael@0: "size of char16_t and size of UniChar are different"); michael@0: memcpy(aStr.BeginWriting(), chars, len * sizeof(char16_t)); michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TISInputSourceWrapper::TranslateToString, aStr=\"%s\"", michael@0: this, NS_ConvertUTF16toUTF8(aStr).get())); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: uint32_t michael@0: TISInputSourceWrapper::TranslateToChar(UInt32 aKeyCode, UInt32 aModifiers, michael@0: UInt32 aKbType) michael@0: { michael@0: nsAutoString str; michael@0: if (!TranslateToString(aKeyCode, aModifiers, aKbType, str) || michael@0: str.Length() != 1) { michael@0: return 0; michael@0: } michael@0: return static_cast(str.CharAt(0)); michael@0: } michael@0: michael@0: void michael@0: TISInputSourceWrapper::InitByInputSourceID(const char* aID) michael@0: { michael@0: Clear(); michael@0: if (!aID) michael@0: return; michael@0: michael@0: CFStringRef idstr = ::CFStringCreateWithCString(kCFAllocatorDefault, aID, michael@0: kCFStringEncodingASCII); michael@0: InitByInputSourceID(idstr); michael@0: ::CFRelease(idstr); michael@0: } michael@0: michael@0: void michael@0: TISInputSourceWrapper::InitByInputSourceID(const nsAFlatString &aID) michael@0: { michael@0: Clear(); michael@0: if (aID.IsEmpty()) michael@0: return; michael@0: CFStringRef idstr = ::CFStringCreateWithCharacters(kCFAllocatorDefault, michael@0: reinterpret_cast(aID.get()), michael@0: aID.Length()); michael@0: InitByInputSourceID(idstr); michael@0: ::CFRelease(idstr); michael@0: } michael@0: michael@0: void michael@0: TISInputSourceWrapper::InitByInputSourceID(const CFStringRef aID) michael@0: { michael@0: Clear(); michael@0: if (!aID) michael@0: return; michael@0: const void* keys[] = { kTISPropertyInputSourceID }; michael@0: const void* values[] = { aID }; michael@0: CFDictionaryRef filter = michael@0: ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL); michael@0: NS_ASSERTION(filter, "failed to create the filter"); michael@0: mInputSourceList = ::TISCreateInputSourceList(filter, true); michael@0: ::CFRelease(filter); michael@0: if (::CFArrayGetCount(mInputSourceList) > 0) { michael@0: mInputSource = static_cast( michael@0: const_cast(::CFArrayGetValueAtIndex(mInputSourceList, 0))); michael@0: if (IsKeyboardLayout()) { michael@0: mKeyboardLayout = mInputSource; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: TISInputSourceWrapper::InitByLayoutID(SInt32 aLayoutID, michael@0: bool aOverrideKeyboard) michael@0: { michael@0: // NOTE: Doument new layout IDs in TextInputHandler.h when you add ones. michael@0: switch (aLayoutID) { michael@0: case 0: michael@0: InitByInputSourceID("com.apple.keylayout.US"); michael@0: break; michael@0: case 1: michael@0: InitByInputSourceID("com.apple.keylayout.Greek"); michael@0: break; michael@0: case 2: michael@0: InitByInputSourceID("com.apple.keylayout.German"); michael@0: break; michael@0: case 3: michael@0: InitByInputSourceID("com.apple.keylayout.Swedish-Pro"); michael@0: break; michael@0: case 4: michael@0: InitByInputSourceID("com.apple.keylayout.DVORAK-QWERTYCMD"); michael@0: break; michael@0: case 5: michael@0: InitByInputSourceID("com.apple.keylayout.Thai"); michael@0: break; michael@0: case 6: michael@0: InitByInputSourceID("com.apple.keylayout.Arabic"); michael@0: break; michael@0: case 7: michael@0: InitByInputSourceID("com.apple.keylayout.French"); michael@0: break; michael@0: case 8: michael@0: InitByInputSourceID("com.apple.keylayout.Hebrew"); michael@0: break; michael@0: case 9: michael@0: InitByInputSourceID("com.apple.keylayout.Lithuanian"); michael@0: break; michael@0: case 10: michael@0: InitByInputSourceID("com.apple.keylayout.Norwegian"); michael@0: break; michael@0: case 11: michael@0: InitByInputSourceID("com.apple.keylayout.Spanish"); michael@0: break; michael@0: default: michael@0: Clear(); michael@0: break; michael@0: } michael@0: mOverrideKeyboard = aOverrideKeyboard; michael@0: } michael@0: michael@0: void michael@0: TISInputSourceWrapper::InitByCurrentInputSource() michael@0: { michael@0: Clear(); michael@0: mInputSource = ::TISCopyCurrentKeyboardInputSource(); michael@0: mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride(); michael@0: if (!mKeyboardLayout) { michael@0: mKeyboardLayout = ::TISCopyCurrentKeyboardLayoutInputSource(); michael@0: } michael@0: // If this causes composition, the current keyboard layout may input non-ASCII michael@0: // characters such as Japanese Kana characters or Hangul characters. michael@0: // However, we need to set ASCII characters to DOM key events for consistency michael@0: // with other platforms. michael@0: if (IsOpenedIMEMode()) { michael@0: TISInputSourceWrapper tis(mKeyboardLayout); michael@0: if (!tis.IsASCIICapable()) { michael@0: mKeyboardLayout = michael@0: ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: TISInputSourceWrapper::InitByCurrentKeyboardLayout() michael@0: { michael@0: Clear(); michael@0: mInputSource = ::TISCopyCurrentKeyboardLayoutInputSource(); michael@0: mKeyboardLayout = mInputSource; michael@0: } michael@0: michael@0: void michael@0: TISInputSourceWrapper::InitByCurrentASCIICapableInputSource() michael@0: { michael@0: Clear(); michael@0: mInputSource = ::TISCopyCurrentASCIICapableKeyboardInputSource(); michael@0: mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride(); michael@0: if (mKeyboardLayout) { michael@0: TISInputSourceWrapper tis(mKeyboardLayout); michael@0: if (!tis.IsASCIICapable()) { michael@0: mKeyboardLayout = nullptr; michael@0: } michael@0: } michael@0: if (!mKeyboardLayout) { michael@0: mKeyboardLayout = michael@0: ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: TISInputSourceWrapper::InitByCurrentASCIICapableKeyboardLayout() michael@0: { michael@0: Clear(); michael@0: mInputSource = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); michael@0: mKeyboardLayout = mInputSource; michael@0: } michael@0: michael@0: void michael@0: TISInputSourceWrapper::InitByCurrentInputMethodKeyboardLayoutOverride() michael@0: { michael@0: Clear(); michael@0: mInputSource = ::TISCopyInputMethodKeyboardLayoutOverride(); michael@0: mKeyboardLayout = mInputSource; michael@0: } michael@0: michael@0: void michael@0: TISInputSourceWrapper::InitByTISInputSourceRef(TISInputSourceRef aInputSource) michael@0: { michael@0: Clear(); michael@0: mInputSource = aInputSource; michael@0: if (IsKeyboardLayout()) { michael@0: mKeyboardLayout = mInputSource; michael@0: } michael@0: } michael@0: michael@0: void michael@0: TISInputSourceWrapper::InitByLanguage(CFStringRef aLanguage) michael@0: { michael@0: Clear(); michael@0: mInputSource = ::TISCopyInputSourceForLanguage(aLanguage); michael@0: if (IsKeyboardLayout()) { michael@0: mKeyboardLayout = mInputSource; michael@0: } michael@0: } michael@0: michael@0: const UCKeyboardLayout* michael@0: TISInputSourceWrapper::GetUCKeyboardLayout() michael@0: { michael@0: NS_ENSURE_TRUE(mKeyboardLayout, nullptr); michael@0: if (mUCKeyboardLayout) { michael@0: return mUCKeyboardLayout; michael@0: } michael@0: CFDataRef uchr = static_cast( michael@0: ::TISGetInputSourceProperty(mKeyboardLayout, michael@0: kTISPropertyUnicodeKeyLayoutData)); michael@0: michael@0: // We should be always able to get the layout here. michael@0: NS_ENSURE_TRUE(uchr, nullptr); michael@0: mUCKeyboardLayout = michael@0: reinterpret_cast(CFDataGetBytePtr(uchr)); michael@0: return mUCKeyboardLayout; michael@0: } michael@0: michael@0: bool michael@0: TISInputSourceWrapper::GetBoolProperty(const CFStringRef aKey) michael@0: { michael@0: CFBooleanRef ret = static_cast( michael@0: ::TISGetInputSourceProperty(mInputSource, aKey)); michael@0: return ::CFBooleanGetValue(ret); michael@0: } michael@0: michael@0: bool michael@0: TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey, michael@0: CFStringRef &aStr) michael@0: { michael@0: aStr = static_cast( michael@0: ::TISGetInputSourceProperty(mInputSource, aKey)); michael@0: return aStr != nullptr; michael@0: } michael@0: michael@0: bool michael@0: TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey, michael@0: nsAString &aStr) michael@0: { michael@0: CFStringRef str; michael@0: GetStringProperty(aKey, str); michael@0: nsCocoaUtils::GetStringForNSString((const NSString*)str, aStr); michael@0: return !aStr.IsEmpty(); michael@0: } michael@0: michael@0: bool michael@0: TISInputSourceWrapper::IsOpenedIMEMode() michael@0: { michael@0: NS_ENSURE_TRUE(mInputSource, false); michael@0: if (!IsIMEMode()) michael@0: return false; michael@0: return !IsASCIICapable(); michael@0: } michael@0: michael@0: bool michael@0: TISInputSourceWrapper::IsIMEMode() michael@0: { michael@0: NS_ENSURE_TRUE(mInputSource, false); michael@0: CFStringRef str; michael@0: GetInputSourceType(str); michael@0: NS_ENSURE_TRUE(str, false); michael@0: return ::CFStringCompare(kTISTypeKeyboardInputMode, michael@0: str, 0) == kCFCompareEqualTo; michael@0: } michael@0: michael@0: bool michael@0: TISInputSourceWrapper::IsKeyboardLayout() michael@0: { michael@0: NS_ENSURE_TRUE(mInputSource, false); michael@0: CFStringRef str; michael@0: GetInputSourceType(str); michael@0: NS_ENSURE_TRUE(str, false); michael@0: return ::CFStringCompare(kTISTypeKeyboardLayout, michael@0: str, 0) == kCFCompareEqualTo; michael@0: } michael@0: michael@0: bool michael@0: TISInputSourceWrapper::GetLanguageList(CFArrayRef &aLanguageList) michael@0: { michael@0: NS_ENSURE_TRUE(mInputSource, false); michael@0: aLanguageList = static_cast( michael@0: ::TISGetInputSourceProperty(mInputSource, michael@0: kTISPropertyInputSourceLanguages)); michael@0: return aLanguageList != nullptr; michael@0: } michael@0: michael@0: bool michael@0: TISInputSourceWrapper::GetPrimaryLanguage(CFStringRef &aPrimaryLanguage) michael@0: { michael@0: NS_ENSURE_TRUE(mInputSource, false); michael@0: CFArrayRef langList; michael@0: NS_ENSURE_TRUE(GetLanguageList(langList), false); michael@0: if (::CFArrayGetCount(langList) == 0) michael@0: return false; michael@0: aPrimaryLanguage = michael@0: static_cast(::CFArrayGetValueAtIndex(langList, 0)); michael@0: return aPrimaryLanguage != nullptr; michael@0: } michael@0: michael@0: bool michael@0: TISInputSourceWrapper::GetPrimaryLanguage(nsAString &aPrimaryLanguage) michael@0: { michael@0: NS_ENSURE_TRUE(mInputSource, false); michael@0: CFStringRef primaryLanguage; michael@0: NS_ENSURE_TRUE(GetPrimaryLanguage(primaryLanguage), false); michael@0: nsCocoaUtils::GetStringForNSString((const NSString*)primaryLanguage, michael@0: aPrimaryLanguage); michael@0: return !aPrimaryLanguage.IsEmpty(); michael@0: } michael@0: michael@0: bool michael@0: TISInputSourceWrapper::IsForRTLLanguage() michael@0: { michael@0: if (mIsRTL < 0) { michael@0: // Get the input character of the 'A' key of ANSI keyboard layout. michael@0: nsAutoString str; michael@0: bool ret = TranslateToString(kVK_ANSI_A, 0, eKbdType_ANSI, str); michael@0: NS_ENSURE_TRUE(ret, ret); michael@0: char16_t ch = str.IsEmpty() ? char16_t(0) : str.CharAt(0); michael@0: mIsRTL = UCS2_CHAR_IS_BIDI(ch) || ch == 0xD802 || ch == 0xD803; michael@0: } michael@0: return mIsRTL != 0; michael@0: } michael@0: michael@0: bool michael@0: TISInputSourceWrapper::IsInitializedByCurrentInputSource() michael@0: { michael@0: return mInputSource == ::TISCopyCurrentKeyboardInputSource(); michael@0: } michael@0: michael@0: void michael@0: TISInputSourceWrapper::Select() michael@0: { michael@0: if (!mInputSource) michael@0: return; michael@0: ::TISSelectInputSource(mInputSource); michael@0: } michael@0: michael@0: void michael@0: TISInputSourceWrapper::Clear() michael@0: { michael@0: // Clear() is always called when TISInputSourceWrappper is created. michael@0: InitLogModule(); michael@0: michael@0: if (mInputSourceList) { michael@0: ::CFRelease(mInputSourceList); michael@0: } michael@0: mInputSourceList = nullptr; michael@0: mInputSource = nullptr; michael@0: mKeyboardLayout = nullptr; michael@0: mIsRTL = -1; michael@0: mUCKeyboardLayout = nullptr; michael@0: mOverrideKeyboard = false; michael@0: } michael@0: michael@0: void michael@0: TISInputSourceWrapper::InitKeyEvent(NSEvent *aNativeKeyEvent, michael@0: WidgetKeyboardEvent& aKeyEvent, michael@0: const nsAString *aInsertString) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TISInputSourceWrapper::InitKeyEvent, aNativeKeyEvent=%p, " michael@0: "aKeyEvent.message=%s, aInsertString=%p, IsOpenedIMEMode()=%s", michael@0: this, aNativeKeyEvent, GetGeckoKeyEventType(aKeyEvent), aInsertString, michael@0: TrueOrFalse(IsOpenedIMEMode()))); michael@0: michael@0: NS_ENSURE_TRUE(aNativeKeyEvent, ); michael@0: michael@0: nsCocoaUtils::InitInputEvent(aKeyEvent, aNativeKeyEvent); michael@0: michael@0: // This is used only while dispatching the event (which is a synchronous michael@0: // call), so there is no need to retain and release this data. michael@0: aKeyEvent.mNativeKeyEvent = aNativeKeyEvent; michael@0: michael@0: aKeyEvent.refPoint = LayoutDeviceIntPoint(0, 0); michael@0: michael@0: // If a keyboard layout override is set, we also need to force the keyboard michael@0: // type to something ANSI to avoid test failures on machines with JIS michael@0: // keyboards (since the pair of keyboard layout and physical keyboard type michael@0: // form the actual key layout). This assumes that the test setting the michael@0: // override was written assuming an ANSI keyboard. michael@0: UInt32 kbType = mOverrideKeyboard ? eKbdType_ANSI : ::LMGetKbdType(); michael@0: michael@0: UInt32 nativeKeyCode = [aNativeKeyEvent keyCode]; michael@0: michael@0: bool isPrintableKey = !TextInputHandler::IsSpecialGeckoKey(nativeKeyCode); michael@0: if (isPrintableKey && michael@0: [aNativeKeyEvent type] != NSKeyDown && michael@0: [aNativeKeyEvent type] != NSKeyUp) { michael@0: NS_WARNING("Why the printable key doesn't cause NSKeyDown or NSKeyUp?"); michael@0: isPrintableKey = false; michael@0: } michael@0: michael@0: // Decide what string will be input. michael@0: nsAutoString insertString; michael@0: if (aInsertString) { michael@0: // If the caller expects that the aInsertString will be input, we shouldn't michael@0: // change it. michael@0: insertString = *aInsertString; michael@0: } else if (isPrintableKey) { michael@0: // If IME is open, [aNativeKeyEvent characters] may be a character michael@0: // which will be appended to the composition string. However, especially, michael@0: // while IME is disabled, most users and developers expect the key event michael@0: // works as IME closed. So, we should compute the insertString with michael@0: // the ASCII capable keyboard layout. michael@0: // NOTE: Such keyboard layouts typically change the layout to its ASCII michael@0: // capable layout when Command key is pressed. And we don't worry michael@0: // when Control key is pressed too because it causes inputting michael@0: // control characters. michael@0: if (!aKeyEvent.IsMeta() && !aKeyEvent.IsControl() && IsOpenedIMEMode()) { michael@0: UInt32 state = michael@0: nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]); michael@0: uint32_t ch = TranslateToChar(nativeKeyCode, state, kbType); michael@0: if (ch) { michael@0: insertString = ch; michael@0: } michael@0: } else { michael@0: // If the caller isn't sure what string will be input, let's use michael@0: // characters of NSEvent. michael@0: nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], michael@0: insertString); michael@0: } michael@0: michael@0: // If control key is pressed and the eventChars is a non-printable control michael@0: // character, we should convert it to ASCII alphabet. michael@0: if (aKeyEvent.IsControl() && michael@0: !insertString.IsEmpty() && insertString[0] <= char16_t(26)) { michael@0: insertString = (aKeyEvent.IsShift() ^ aKeyEvent.IsCapsLocked()) ? michael@0: static_cast(insertString[0] + ('A' - 1)) : michael@0: static_cast(insertString[0] + ('a' - 1)); michael@0: } michael@0: // If Meta key is pressed, it may cause to switch the keyboard layout like michael@0: // Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY. michael@0: else if (aKeyEvent.IsMeta() && michael@0: !(aKeyEvent.IsControl() || aKeyEvent.IsAlt())) { michael@0: UInt32 numLockState = michael@0: aKeyEvent.IsNumLocked() ? kEventKeyModifierNumLockMask : 0; michael@0: UInt32 capsLockState = aKeyEvent.IsCapsLocked() ? alphaLock : 0; michael@0: UInt32 shiftState = aKeyEvent.IsShift() ? shiftKey : 0; michael@0: uint32_t uncmdedChar = michael@0: TranslateToChar(nativeKeyCode, numLockState, kbType); michael@0: uint32_t cmdedChar = michael@0: TranslateToChar(nativeKeyCode, cmdKey | numLockState, kbType); michael@0: // If we can make a good guess at the characters that the user would michael@0: // expect this key combination to produce (with and without Shift) then michael@0: // use those characters. This also corrects for CapsLock. michael@0: uint32_t ch = 0; michael@0: if (uncmdedChar == cmdedChar) { michael@0: // The characters produced with Command seem similar to those without michael@0: // Command. michael@0: ch = TranslateToChar(nativeKeyCode, michael@0: shiftState | capsLockState | numLockState, kbType); michael@0: } else { michael@0: TISInputSourceWrapper USLayout("com.apple.keylayout.US"); michael@0: uint32_t uncmdedUSChar = michael@0: USLayout.TranslateToChar(nativeKeyCode, numLockState, kbType); michael@0: // If it looks like characters from US keyboard layout when Command key michael@0: // is pressed, we should compute a character in the layout. michael@0: if (uncmdedUSChar == cmdedChar) { michael@0: ch = USLayout.TranslateToChar(nativeKeyCode, michael@0: shiftState | capsLockState | numLockState, kbType); michael@0: } michael@0: } michael@0: michael@0: // If there is a more preferred character for the commanded key event, michael@0: // we should use it. michael@0: if (ch) { michael@0: insertString = ch; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Remove control characters which shouldn't be inputted on editor. michael@0: // XXX Currently, we don't find any cases inserting control characters with michael@0: // printable character. So, just checking first character is enough. michael@0: if (!insertString.IsEmpty() && IsControlChar(insertString[0])) { michael@0: insertString.Truncate(); michael@0: } michael@0: michael@0: aKeyEvent.keyCode = michael@0: ComputeGeckoKeyCode(nativeKeyCode, kbType, aKeyEvent.IsMeta()); michael@0: michael@0: switch (nativeKeyCode) { michael@0: case kVK_Command: michael@0: case kVK_Shift: michael@0: case kVK_Option: michael@0: case kVK_Control: michael@0: aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT; michael@0: break; michael@0: michael@0: case kVK_RightCommand: michael@0: case kVK_RightShift: michael@0: case kVK_RightOption: michael@0: case kVK_RightControl: michael@0: aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT; michael@0: break; michael@0: michael@0: case kVK_ANSI_Keypad0: michael@0: case kVK_ANSI_Keypad1: michael@0: case kVK_ANSI_Keypad2: michael@0: case kVK_ANSI_Keypad3: michael@0: case kVK_ANSI_Keypad4: michael@0: case kVK_ANSI_Keypad5: michael@0: case kVK_ANSI_Keypad6: michael@0: case kVK_ANSI_Keypad7: michael@0: case kVK_ANSI_Keypad8: michael@0: case kVK_ANSI_Keypad9: michael@0: case kVK_ANSI_KeypadMultiply: michael@0: case kVK_ANSI_KeypadPlus: michael@0: case kVK_ANSI_KeypadMinus: michael@0: case kVK_ANSI_KeypadDecimal: michael@0: case kVK_ANSI_KeypadDivide: michael@0: case kVK_ANSI_KeypadEquals: michael@0: case kVK_ANSI_KeypadEnter: michael@0: case kVK_JIS_KeypadComma: michael@0: case kVK_Powerbook_KeypadEnter: michael@0: aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD; michael@0: break; michael@0: michael@0: default: michael@0: aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD; michael@0: break; michael@0: } michael@0: michael@0: aKeyEvent.mIsRepeat = michael@0: ([aNativeKeyEvent type] == NSKeyDown) ? [aNativeKeyEvent isARepeat] : false; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TISInputSourceWrapper::InitKeyEvent, " michael@0: "shift=%s, ctrl=%s, alt=%s, meta=%s", michael@0: this, OnOrOff(aKeyEvent.IsShift()), OnOrOff(aKeyEvent.IsControl()), michael@0: OnOrOff(aKeyEvent.IsAlt()), OnOrOff(aKeyEvent.IsMeta()))); michael@0: michael@0: if (aKeyEvent.message == NS_KEY_PRESS && michael@0: (isPrintableKey || !insertString.IsEmpty())) { michael@0: InitKeyPressEvent(aNativeKeyEvent, michael@0: insertString.IsEmpty() ? 0 : insertString[0], michael@0: aKeyEvent, kbType); michael@0: MOZ_ASSERT(!aKeyEvent.charCode || !IsControlChar(aKeyEvent.charCode), michael@0: "charCode must not be a control character"); michael@0: } else { michael@0: aKeyEvent.charCode = 0; michael@0: aKeyEvent.isChar = false; // XXX not used in XP level michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TISInputSourceWrapper::InitKeyEvent, keyCode=0x%X charCode=0x0", michael@0: this, aKeyEvent.keyCode)); michael@0: } michael@0: michael@0: if (isPrintableKey) { michael@0: aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING; michael@0: // If insertText calls this method, let's use the string. michael@0: if (aInsertString && !aInsertString->IsEmpty() && michael@0: !IsControlChar((*aInsertString)[0])) { michael@0: aKeyEvent.mKeyValue = *aInsertString; michael@0: } michael@0: // If meta key is pressed, the printable key layout may be switched from michael@0: // non-ASCII capable layout to ASCII capable, or from Dvorak to QWERTY. michael@0: // KeyboardEvent.key value should be the switched layout's character. michael@0: else if (aKeyEvent.IsMeta()) { michael@0: nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], michael@0: aKeyEvent.mKeyValue); michael@0: } michael@0: // If control key is pressed, some keys may produce printable character via michael@0: // [aNativeKeyEvent characters]. Otherwise, translate input character of michael@0: // the key without control key. michael@0: else if (aKeyEvent.IsControl()) { michael@0: nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], michael@0: aKeyEvent.mKeyValue); michael@0: if (aKeyEvent.mKeyValue.IsEmpty() || michael@0: IsControlChar(aKeyEvent.mKeyValue[0])) { michael@0: NSUInteger cocoaState = michael@0: [aNativeKeyEvent modifierFlags] & ~NSControlKeyMask; michael@0: UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState); michael@0: aKeyEvent.mKeyValue = michael@0: TranslateToChar(nativeKeyCode, carbonState, kbType); michael@0: } michael@0: } michael@0: // Otherwise, KeyboardEvent.key expose michael@0: // [aNativeKeyEvent characters] value. However, if IME is open and the michael@0: // keyboard layout isn't ASCII capable, exposing the non-ASCII character michael@0: // doesn't match with other platform's behavior. For the compatibility michael@0: // with other platform's Gecko, we need to set a translated character. michael@0: else if (IsOpenedIMEMode()) { michael@0: UInt32 state = michael@0: nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]); michael@0: aKeyEvent.mKeyValue = TranslateToChar(nativeKeyCode, state, kbType); michael@0: } else { michael@0: nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], michael@0: aKeyEvent.mKeyValue); michael@0: } michael@0: michael@0: // Last resort. If .key value becomes empty string, we should use michael@0: // charactersIgnoringModifiers, if it's available. michael@0: if (aKeyEvent.mKeyValue.IsEmpty() || michael@0: IsControlChar(aKeyEvent.mKeyValue[0])) { michael@0: nsCocoaUtils::GetStringForNSString( michael@0: [aNativeKeyEvent charactersIgnoringModifiers], aKeyEvent.mKeyValue); michael@0: // But don't expose it if it's a control character. michael@0: if (!aKeyEvent.mKeyValue.IsEmpty() && michael@0: IsControlChar(aKeyEvent.mKeyValue[0])) { michael@0: aKeyEvent.mKeyValue.Truncate(); michael@0: } michael@0: } michael@0: } else { michael@0: // Compute the key for non-printable keys and some special printable keys. michael@0: aKeyEvent.mKeyNameIndex = ComputeGeckoKeyNameIndex(nativeKeyCode); michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK michael@0: } michael@0: michael@0: void michael@0: TISInputSourceWrapper::InitKeyPressEvent(NSEvent *aNativeKeyEvent, michael@0: char16_t aInsertChar, michael@0: WidgetKeyboardEvent& aKeyEvent, michael@0: UInt32 aKbType) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: NS_ASSERTION(aKeyEvent.message == NS_KEY_PRESS, michael@0: "aKeyEvent must be NS_KEY_PRESS event"); michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) { michael@0: nsAutoString chars; michael@0: nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], chars); michael@0: NS_ConvertUTF16toUTF8 utf8Chars(chars); michael@0: char16_t expectedChar = static_cast(aInsertChar); michael@0: NS_ConvertUTF16toUTF8 utf8ExpectedChar(&expectedChar, 1); michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TISInputSourceWrapper::InitKeyPressEvent, aNativeKeyEvent=%p, " michael@0: "[aNativeKeyEvent characters]=\"%s\", aInsertChar=0x%X(%s), " michael@0: "aKeyEvent.message=%s, aKbType=0x%X, IsOpenedIMEMode()=%s", michael@0: this, aNativeKeyEvent, utf8Chars.get(), aInsertChar, michael@0: utf8ExpectedChar.get(), GetGeckoKeyEventType(aKeyEvent), aKbType, michael@0: TrueOrFalse(IsOpenedIMEMode()))); michael@0: } michael@0: #endif // #ifdef PR_LOGGING michael@0: michael@0: aKeyEvent.isChar = true; // this is not a special key XXX not used in XP michael@0: aKeyEvent.charCode = aInsertChar; michael@0: if (aKeyEvent.charCode != 0) { michael@0: aKeyEvent.keyCode = 0; michael@0: } michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TISInputSourceWrapper::InitKeyPressEvent, " michael@0: "aKeyEvent.keyCode=0x%X, aKeyEvent.charCode=0x%X", michael@0: this, aKeyEvent.keyCode, aKeyEvent.charCode)); michael@0: michael@0: if (!aKeyEvent.IsControl() && !aKeyEvent.IsMeta() && !aKeyEvent.IsAlt()) { michael@0: return; michael@0: } michael@0: michael@0: TISInputSourceWrapper USLayout("com.apple.keylayout.US"); michael@0: bool isRomanKeyboardLayout = IsASCIICapable(); michael@0: michael@0: UInt32 key = [aNativeKeyEvent keyCode]; michael@0: michael@0: // Caps lock and num lock modifier state: michael@0: UInt32 lockState = 0; michael@0: if ([aNativeKeyEvent modifierFlags] & NSAlphaShiftKeyMask) { michael@0: lockState |= alphaLock; michael@0: } michael@0: if ([aNativeKeyEvent modifierFlags] & NSNumericPadKeyMask) { michael@0: lockState |= kEventKeyModifierNumLockMask; michael@0: } michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TISInputSourceWrapper::InitKeyPressEvent, " michael@0: "isRomanKeyboardLayout=%s, key=0x%X", michael@0: this, TrueOrFalse(isRomanKeyboardLayout), aKbType, key)); michael@0: michael@0: nsString str; michael@0: michael@0: // normal chars michael@0: uint32_t unshiftedChar = TranslateToChar(key, lockState, aKbType); michael@0: UInt32 shiftLockMod = shiftKey | lockState; michael@0: uint32_t shiftedChar = TranslateToChar(key, shiftLockMod, aKbType); michael@0: michael@0: // characters generated with Cmd key michael@0: // XXX we should remove CapsLock state, which changes characters from michael@0: // Latin to Cyrillic with Russian layout on 10.4 only when Cmd key michael@0: // is pressed. michael@0: UInt32 numState = (lockState & ~alphaLock); // only num lock state michael@0: uint32_t uncmdedChar = TranslateToChar(key, numState, aKbType); michael@0: UInt32 shiftNumMod = numState | shiftKey; michael@0: uint32_t uncmdedShiftChar = TranslateToChar(key, shiftNumMod, aKbType); michael@0: uint32_t uncmdedUSChar = USLayout.TranslateToChar(key, numState, aKbType); michael@0: UInt32 cmdNumMod = cmdKey | numState; michael@0: uint32_t cmdedChar = TranslateToChar(key, cmdNumMod, aKbType); michael@0: UInt32 cmdShiftNumMod = shiftKey | cmdNumMod; michael@0: uint32_t cmdedShiftChar = TranslateToChar(key, cmdShiftNumMod, aKbType); michael@0: michael@0: // Is the keyboard layout changed by Cmd key? michael@0: // E.g., Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY. michael@0: bool isCmdSwitchLayout = uncmdedChar != cmdedChar; michael@0: // Is the keyboard layout for Latin, but Cmd key switches the layout? michael@0: // I.e., Dvorak-QWERTY michael@0: bool isDvorakQWERTY = isCmdSwitchLayout && isRomanKeyboardLayout; michael@0: michael@0: // If the current keyboard is not Dvorak-QWERTY or Cmd is not pressed, michael@0: // we should append unshiftedChar and shiftedChar for handling the michael@0: // normal characters. These are the characters that the user is most michael@0: // likely to associate with this key. michael@0: if ((unshiftedChar || shiftedChar) && michael@0: (!aKeyEvent.IsMeta() || !isDvorakQWERTY)) { michael@0: AlternativeCharCode altCharCodes(unshiftedChar, shiftedChar); michael@0: aKeyEvent.alternativeCharCodes.AppendElement(altCharCodes); michael@0: } michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TISInputSourceWrapper::InitKeyPressEvent, " michael@0: "aKeyEvent.isMeta=%s, isDvorakQWERTY=%s, " michael@0: "unshiftedChar=U+%X, shiftedChar=U+%X", michael@0: this, OnOrOff(aKeyEvent.IsMeta()), TrueOrFalse(isDvorakQWERTY), michael@0: unshiftedChar, shiftedChar)); michael@0: michael@0: // Most keyboard layouts provide the same characters in the NSEvents michael@0: // with Command+Shift as with Command. However, with Command+Shift we michael@0: // want the character on the second level. e.g. With a US QWERTY michael@0: // layout, we want "?" when the "/","?" key is pressed with michael@0: // Command+Shift. michael@0: michael@0: // On a German layout, the OS gives us '/' with Cmd+Shift+SS(eszett) michael@0: // even though Cmd+SS is 'SS' and Shift+'SS' is '?'. This '/' seems michael@0: // like a hack to make the Cmd+"?" event look the same as the Cmd+"?" michael@0: // event on a US keyboard. The user thinks they are typing Cmd+"?", so michael@0: // we'll prefer the "?" character, replacing charCode with shiftedChar michael@0: // when Shift is pressed. However, in case there is a layout where the michael@0: // character unique to Cmd+Shift is the character that the user expects, michael@0: // we'll send it as an alternative char. michael@0: bool hasCmdShiftOnlyChar = michael@0: cmdedChar != cmdedShiftChar && uncmdedShiftChar != cmdedShiftChar; michael@0: uint32_t originalCmdedShiftChar = cmdedShiftChar; michael@0: michael@0: // If we can make a good guess at the characters that the user would michael@0: // expect this key combination to produce (with and without Shift) then michael@0: // use those characters. This also corrects for CapsLock, which was michael@0: // ignored above. michael@0: if (!isCmdSwitchLayout) { michael@0: // The characters produced with Command seem similar to those without michael@0: // Command. michael@0: if (unshiftedChar) { michael@0: cmdedChar = unshiftedChar; michael@0: } michael@0: if (shiftedChar) { michael@0: cmdedShiftChar = shiftedChar; michael@0: } michael@0: } else if (uncmdedUSChar == cmdedChar) { michael@0: // It looks like characters from a US layout are provided when Command michael@0: // is down. michael@0: uint32_t ch = USLayout.TranslateToChar(key, lockState, aKbType); michael@0: if (ch) { michael@0: cmdedChar = ch; michael@0: } michael@0: ch = USLayout.TranslateToChar(key, shiftLockMod, aKbType); michael@0: if (ch) { michael@0: cmdedShiftChar = ch; michael@0: } michael@0: } michael@0: michael@0: // If the current keyboard layout is switched by the Cmd key, michael@0: // we should append cmdedChar and shiftedCmdChar that are michael@0: // Latin char for the key. michael@0: // If the keyboard layout is Dvorak-QWERTY, we should append them only when michael@0: // command key is pressed because when command key isn't pressed, uncmded michael@0: // chars have been appended already. michael@0: if ((cmdedChar || cmdedShiftChar) && isCmdSwitchLayout && michael@0: (aKeyEvent.IsMeta() || !isDvorakQWERTY)) { michael@0: AlternativeCharCode altCharCodes(cmdedChar, cmdedShiftChar); michael@0: aKeyEvent.alternativeCharCodes.AppendElement(altCharCodes); michael@0: } michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TISInputSourceWrapper::InitKeyPressEvent, " michael@0: "hasCmdShiftOnlyChar=%s, isCmdSwitchLayout=%s, isDvorakQWERTY=%s, " michael@0: "cmdedChar=U+%X, cmdedShiftChar=U+%X", michael@0: this, TrueOrFalse(hasCmdShiftOnlyChar), TrueOrFalse(isDvorakQWERTY), michael@0: TrueOrFalse(isDvorakQWERTY), cmdedChar, cmdedShiftChar)); michael@0: // Special case for 'SS' key of German layout. See the comment of michael@0: // hasCmdShiftOnlyChar definition for the detail. michael@0: if (hasCmdShiftOnlyChar && originalCmdedShiftChar) { michael@0: AlternativeCharCode altCharCodes(0, originalCmdedShiftChar); michael@0: aKeyEvent.alternativeCharCodes.AppendElement(altCharCodes); michael@0: } michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TISInputSourceWrapper::InitKeyPressEvent, " michael@0: "hasCmdShiftOnlyChar=%s, originalCmdedShiftChar=U+%X", michael@0: this, TrueOrFalse(hasCmdShiftOnlyChar), originalCmdedShiftChar)); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK michael@0: } michael@0: michael@0: uint32_t michael@0: TISInputSourceWrapper::ComputeGeckoKeyCode(UInt32 aNativeKeyCode, michael@0: UInt32 aKbType, michael@0: bool aCmdIsPressed) michael@0: { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TISInputSourceWrapper::ComputeGeckoKeyCode, aNativeKeyCode=0x%X, " michael@0: "aKbType=0x%X, aCmdIsPressed=%s, IsOpenedIMEMode()=%s, " michael@0: "IsASCIICapable()=%s", michael@0: this, aNativeKeyCode, aKbType, TrueOrFalse(aCmdIsPressed), michael@0: TrueOrFalse(IsOpenedIMEMode()), TrueOrFalse(IsASCIICapable()))); michael@0: michael@0: switch (aNativeKeyCode) { michael@0: case kVK_Space: return NS_VK_SPACE; michael@0: case kVK_Escape: return NS_VK_ESCAPE; michael@0: michael@0: // modifiers michael@0: case kVK_RightCommand: michael@0: case kVK_Command: return NS_VK_META; michael@0: case kVK_RightShift: michael@0: case kVK_Shift: return NS_VK_SHIFT; michael@0: case kVK_CapsLock: return NS_VK_CAPS_LOCK; michael@0: case kVK_RightControl: michael@0: case kVK_Control: return NS_VK_CONTROL; michael@0: case kVK_RightOption: michael@0: case kVK_Option: return NS_VK_ALT; michael@0: michael@0: case kVK_ANSI_KeypadClear: return NS_VK_CLEAR; michael@0: michael@0: // function keys michael@0: case kVK_F1: return NS_VK_F1; michael@0: case kVK_F2: return NS_VK_F2; michael@0: case kVK_F3: return NS_VK_F3; michael@0: case kVK_F4: return NS_VK_F4; michael@0: case kVK_F5: return NS_VK_F5; michael@0: case kVK_F6: return NS_VK_F6; michael@0: case kVK_F7: return NS_VK_F7; michael@0: case kVK_F8: return NS_VK_F8; michael@0: case kVK_F9: return NS_VK_F9; michael@0: case kVK_F10: return NS_VK_F10; michael@0: case kVK_F11: return NS_VK_F11; michael@0: case kVK_F12: return NS_VK_F12; michael@0: // case kVK_F13: return NS_VK_F13; // clash with the 3 below michael@0: // case kVK_F14: return NS_VK_F14; michael@0: // case kVK_F15: return NS_VK_F15; michael@0: case kVK_F16: return NS_VK_F16; michael@0: case kVK_F17: return NS_VK_F17; michael@0: case kVK_F18: return NS_VK_F18; michael@0: case kVK_F19: return NS_VK_F19; michael@0: michael@0: case kVK_PC_Pause: return NS_VK_PAUSE; michael@0: case kVK_PC_ScrollLock: return NS_VK_SCROLL_LOCK; michael@0: case kVK_PC_PrintScreen: return NS_VK_PRINTSCREEN; michael@0: michael@0: // keypad michael@0: case kVK_ANSI_Keypad0: return NS_VK_NUMPAD0; michael@0: case kVK_ANSI_Keypad1: return NS_VK_NUMPAD1; michael@0: case kVK_ANSI_Keypad2: return NS_VK_NUMPAD2; michael@0: case kVK_ANSI_Keypad3: return NS_VK_NUMPAD3; michael@0: case kVK_ANSI_Keypad4: return NS_VK_NUMPAD4; michael@0: case kVK_ANSI_Keypad5: return NS_VK_NUMPAD5; michael@0: case kVK_ANSI_Keypad6: return NS_VK_NUMPAD6; michael@0: case kVK_ANSI_Keypad7: return NS_VK_NUMPAD7; michael@0: case kVK_ANSI_Keypad8: return NS_VK_NUMPAD8; michael@0: case kVK_ANSI_Keypad9: return NS_VK_NUMPAD9; michael@0: michael@0: case kVK_ANSI_KeypadMultiply: return NS_VK_MULTIPLY; michael@0: case kVK_ANSI_KeypadPlus: return NS_VK_ADD; michael@0: case kVK_ANSI_KeypadMinus: return NS_VK_SUBTRACT; michael@0: case kVK_ANSI_KeypadDecimal: return NS_VK_DECIMAL; michael@0: case kVK_ANSI_KeypadDivide: return NS_VK_DIVIDE; michael@0: michael@0: case kVK_JIS_KeypadComma: return NS_VK_SEPARATOR; michael@0: michael@0: // IME keys michael@0: case kVK_JIS_Eisu: return NS_VK_EISU; michael@0: case kVK_JIS_Kana: return NS_VK_KANA; michael@0: michael@0: // these may clash with forward delete and help michael@0: case kVK_PC_Insert: return NS_VK_INSERT; michael@0: case kVK_PC_Delete: return NS_VK_DELETE; michael@0: michael@0: case kVK_PC_Backspace: return NS_VK_BACK; michael@0: case kVK_Tab: return NS_VK_TAB; michael@0: michael@0: case kVK_Home: return NS_VK_HOME; michael@0: case kVK_End: return NS_VK_END; michael@0: michael@0: case kVK_PageUp: return NS_VK_PAGE_UP; michael@0: case kVK_PageDown: return NS_VK_PAGE_DOWN; michael@0: michael@0: case kVK_LeftArrow: return NS_VK_LEFT; michael@0: case kVK_RightArrow: return NS_VK_RIGHT; michael@0: case kVK_UpArrow: return NS_VK_UP; michael@0: case kVK_DownArrow: return NS_VK_DOWN; michael@0: michael@0: case kVK_PC_ContextMenu: return NS_VK_CONTEXT_MENU; michael@0: michael@0: case kVK_ANSI_1: return NS_VK_1; michael@0: case kVK_ANSI_2: return NS_VK_2; michael@0: case kVK_ANSI_3: return NS_VK_3; michael@0: case kVK_ANSI_4: return NS_VK_4; michael@0: case kVK_ANSI_5: return NS_VK_5; michael@0: case kVK_ANSI_6: return NS_VK_6; michael@0: case kVK_ANSI_7: return NS_VK_7; michael@0: case kVK_ANSI_8: return NS_VK_8; michael@0: case kVK_ANSI_9: return NS_VK_9; michael@0: case kVK_ANSI_0: return NS_VK_0; michael@0: michael@0: case kVK_ANSI_KeypadEnter: michael@0: case kVK_Return: michael@0: case kVK_Powerbook_KeypadEnter: return NS_VK_RETURN; michael@0: } michael@0: michael@0: // If Cmd key is pressed, that causes switching keyboard layout temporarily. michael@0: // E.g., Dvorak-QWERTY. Therefore, if Cmd key is pressed, we should honor it. michael@0: UInt32 modifiers = aCmdIsPressed ? cmdKey : 0; michael@0: michael@0: uint32_t charCode = TranslateToChar(aNativeKeyCode, modifiers, aKbType); michael@0: michael@0: // Special case for Mac. Mac inputs Yen sign (U+00A5) directly instead of michael@0: // Back slash (U+005C). We should return NS_VK_BACK_SLASH for compatibility michael@0: // with other platforms. michael@0: // XXX How about Won sign (U+20A9) which has same problem as Yen sign? michael@0: if (charCode == 0x00A5) { michael@0: return NS_VK_BACK_SLASH; michael@0: } michael@0: michael@0: uint32_t keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode); michael@0: if (keyCode) { michael@0: return keyCode; michael@0: } michael@0: michael@0: // If the unshifed char isn't an ASCII character, use shifted char. michael@0: charCode = TranslateToChar(aNativeKeyCode, modifiers | shiftKey, aKbType); michael@0: keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode); michael@0: if (keyCode) { michael@0: return keyCode; michael@0: } michael@0: michael@0: // If this is ASCII capable, give up to compute it. michael@0: if (IsASCIICapable()) { michael@0: return 0; michael@0: } michael@0: michael@0: // Retry with ASCII capable keyboard layout. michael@0: TISInputSourceWrapper currentKeyboardLayout; michael@0: currentKeyboardLayout.InitByCurrentASCIICapableKeyboardLayout(); michael@0: NS_ENSURE_TRUE(mInputSource != currentKeyboardLayout.mInputSource, 0); michael@0: keyCode = currentKeyboardLayout.ComputeGeckoKeyCode(aNativeKeyCode, aKbType, michael@0: aCmdIsPressed); michael@0: michael@0: // However, if keyCode isn't for an alphabet keys or a numeric key, we should michael@0: // ignore it. For example, comma key of Thai layout is same as close-square- michael@0: // bracket key of US layout and an unicode character key of Thai layout is michael@0: // same as comma key of US layout. If we return NS_VK_COMMA for latter key, michael@0: // web application developers cannot distinguish with the former key. michael@0: return ((keyCode >= NS_VK_A && keyCode <= NS_VK_Z) || michael@0: (keyCode >= NS_VK_0 && keyCode <= NS_VK_9)) ? keyCode : 0; michael@0: } michael@0: michael@0: // static michael@0: KeyNameIndex michael@0: TISInputSourceWrapper::ComputeGeckoKeyNameIndex(UInt32 aNativeKeyCode) michael@0: { michael@0: switch (aNativeKeyCode) { michael@0: michael@0: #define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \ michael@0: case aNativeKey: return aKeyNameIndex; michael@0: michael@0: #include "NativeKeyToDOMKeyName.h" michael@0: michael@0: #undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX michael@0: michael@0: default: michael@0: return KEY_NAME_INDEX_Unidentified; michael@0: } michael@0: } michael@0: michael@0: michael@0: #pragma mark - michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * michael@0: * TextInputHandler implementation (static methods) michael@0: * michael@0: ******************************************************************************/ michael@0: michael@0: NSUInteger TextInputHandler::sLastModifierState = 0; michael@0: michael@0: // static michael@0: CFArrayRef michael@0: TextInputHandler::CreateAllKeyboardLayoutList() michael@0: { michael@0: const void* keys[] = { kTISPropertyInputSourceType }; michael@0: const void* values[] = { kTISTypeKeyboardLayout }; michael@0: CFDictionaryRef filter = michael@0: ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL); michael@0: NS_ASSERTION(filter, "failed to create the filter"); michael@0: CFArrayRef list = ::TISCreateInputSourceList(filter, true); michael@0: ::CFRelease(filter); michael@0: return list; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: TextInputHandler::DebugPrintAllKeyboardLayouts() michael@0: { michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) { michael@0: CFArrayRef list = CreateAllKeyboardLayoutList(); michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, ("Keyboard layout configuration:")); michael@0: CFIndex idx = ::CFArrayGetCount(list); michael@0: TISInputSourceWrapper tis; michael@0: for (CFIndex i = 0; i < idx; ++i) { michael@0: TISInputSourceRef inputSource = static_cast( michael@0: const_cast(::CFArrayGetValueAtIndex(list, i))); michael@0: tis.InitByTISInputSourceRef(inputSource); michael@0: nsAutoString name, isid; michael@0: tis.GetLocalizedName(name); michael@0: tis.GetInputSourceID(isid); michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: (" %s\t<%s>%s%s\n", michael@0: NS_ConvertUTF16toUTF8(name).get(), michael@0: NS_ConvertUTF16toUTF8(isid).get(), michael@0: tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)", michael@0: tis.IsKeyboardLayout() && tis.GetUCKeyboardLayout() ? michael@0: "" : "\t(uchr is NOT AVAILABLE)")); michael@0: } michael@0: ::CFRelease(list); michael@0: } michael@0: #endif // #ifdef PR_LOGGING michael@0: } michael@0: michael@0: michael@0: #pragma mark - michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * michael@0: * TextInputHandler implementation michael@0: * michael@0: ******************************************************************************/ michael@0: michael@0: TextInputHandler::TextInputHandler(nsChildView* aWidget, michael@0: NSView *aNativeView) : michael@0: IMEInputHandler(aWidget, aNativeView) michael@0: { michael@0: InitLogModule(); michael@0: [mView installTextInputHandler:this]; michael@0: } michael@0: michael@0: TextInputHandler::~TextInputHandler() michael@0: { michael@0: [mView uninstallTextInputHandler]; michael@0: } michael@0: michael@0: bool michael@0: TextInputHandler::HandleKeyDownEvent(NSEvent* aNativeEvent) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: if (Destroyed()) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandler::HandleKeyDownEvent, " michael@0: "widget has been already destroyed", this)); michael@0: return false; michael@0: } michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandler::HandleKeyDownEvent, aNativeEvent=%p, " michael@0: "type=%s, keyCode=%lld (0x%X), modifierFlags=0x%X, characters=\"%s\", " michael@0: "charactersIgnoringModifiers=\"%s\"", michael@0: this, aNativeEvent, GetNativeKeyEventType(aNativeEvent), michael@0: [aNativeEvent keyCode], [aNativeEvent keyCode], michael@0: [aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]), michael@0: GetCharacters([aNativeEvent charactersIgnoringModifiers]))); michael@0: michael@0: nsRefPtr kungFuDeathGrip(mWidget); michael@0: michael@0: KeyEventState* currentKeyEvent = PushKeyEvent(aNativeEvent); michael@0: AutoKeyEventStateCleaner remover(this); michael@0: michael@0: if (!IsIMEComposing()) { michael@0: NSResponder* firstResponder = [[mView window] firstResponder]; michael@0: michael@0: WidgetKeyboardEvent keydownEvent(true, NS_KEY_DOWN, mWidget); michael@0: InitKeyEvent(aNativeEvent, keydownEvent); michael@0: michael@0: currentKeyEvent->mKeyDownHandled = DispatchEvent(keydownEvent); michael@0: if (Destroyed()) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandler::HandleKeyDownEvent, " michael@0: "widget was destroyed by keydown event", this)); michael@0: return currentKeyEvent->IsDefaultPrevented(); michael@0: } michael@0: michael@0: // The key down event may have shifted the focus, in which michael@0: // case we should not fire the key press. michael@0: // XXX This is a special code only on Cocoa widget, why is this needed? michael@0: if (firstResponder != [[mView window] firstResponder]) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandler::HandleKeyDownEvent, " michael@0: "view lost focus by keydown event", this)); michael@0: return currentKeyEvent->IsDefaultPrevented(); michael@0: } michael@0: michael@0: if (currentKeyEvent->IsDefaultPrevented()) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandler::HandleKeyDownEvent, " michael@0: "keydown event's default is prevented", this)); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: // Let Cocoa interpret the key events, caching IsIMEComposing first. michael@0: bool wasComposing = IsIMEComposing(); michael@0: bool interpretKeyEventsCalled = false; michael@0: if (IsIMEEnabled() || IsASCIICapableOnly()) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandler::HandleKeyDownEvent, calling interpretKeyEvents", michael@0: this)); michael@0: [mView interpretKeyEvents:[NSArray arrayWithObject:aNativeEvent]]; michael@0: interpretKeyEventsCalled = true; michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandler::HandleKeyDownEvent, called interpretKeyEvents", michael@0: this)); michael@0: } michael@0: michael@0: if (Destroyed()) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandler::HandleKeyDownEvent, widget was destroyed", michael@0: this)); michael@0: return currentKeyEvent->IsDefaultPrevented(); michael@0: } michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandler::HandleKeyDownEvent, wasComposing=%s, " michael@0: "IsIMEComposing()=%s", michael@0: this, TrueOrFalse(wasComposing), TrueOrFalse(IsIMEComposing()))); michael@0: michael@0: if (currentKeyEvent->CanDispatchKeyPressEvent() && michael@0: !wasComposing && !IsIMEComposing()) { michael@0: WidgetKeyboardEvent keypressEvent(true, NS_KEY_PRESS, mWidget); michael@0: InitKeyEvent(aNativeEvent, keypressEvent); michael@0: michael@0: // If we called interpretKeyEvents and this isn't normal character input michael@0: // then IME probably ate the event for some reason. We do not want to michael@0: // send a key press event in that case. michael@0: // TODO: michael@0: // There are some other cases which IME eats the current event. michael@0: // 1. If key events were nested during calling interpretKeyEvents, it means michael@0: // that IME did something. Then, we should do nothing. michael@0: // 2. If one or more commands are called like "deleteBackward", we should michael@0: // dispatch keypress event at that time. Note that the command may have michael@0: // been a converted or generated action by IME. Then, we shouldn't do michael@0: // our default action for this key. michael@0: if (!(interpretKeyEventsCalled && michael@0: IsNormalCharInputtingEvent(keypressEvent))) { michael@0: currentKeyEvent->mKeyPressHandled = DispatchEvent(keypressEvent); michael@0: currentKeyEvent->mKeyPressDispatched = true; michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandler::HandleKeyDownEvent, keypress event dispatched", michael@0: this)); michael@0: } michael@0: } michael@0: michael@0: // Note: mWidget might have become null here. Don't count on it from here on. michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandler::HandleKeyDownEvent, " michael@0: "keydown handled=%s, keypress handled=%s, causedOtherKeyEvents=%s", michael@0: this, TrueOrFalse(currentKeyEvent->mKeyDownHandled), michael@0: TrueOrFalse(currentKeyEvent->mKeyPressHandled), michael@0: TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents))); michael@0: return currentKeyEvent->IsDefaultPrevented(); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); michael@0: } michael@0: michael@0: void michael@0: TextInputHandler::HandleKeyUpEvent(NSEvent* aNativeEvent) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandler::HandleKeyUpEvent, aNativeEvent=%p, " michael@0: "type=%s, keyCode=%lld (0x%X), modifierFlags=0x%X, characters=\"%s\", " michael@0: "charactersIgnoringModifiers=\"%s\", " michael@0: "mIgnoreNextKeyUpEvent=%s, IsIMEComposing()=%s", michael@0: this, aNativeEvent, GetNativeKeyEventType(aNativeEvent), michael@0: [aNativeEvent keyCode], [aNativeEvent keyCode], michael@0: [aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]), michael@0: GetCharacters([aNativeEvent charactersIgnoringModifiers]), michael@0: TrueOrFalse(mIgnoreNextKeyUpEvent), TrueOrFalse(IsIMEComposing()))); michael@0: michael@0: if (mIgnoreNextKeyUpEvent) { michael@0: mIgnoreNextKeyUpEvent = false; michael@0: return; michael@0: } michael@0: michael@0: if (Destroyed()) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandler::HandleKeyUpEvent, " michael@0: "widget has been already destroyed", this)); michael@0: return; michael@0: } michael@0: michael@0: // if we don't have any characters we can't generate a keyUp event michael@0: if (IsIMEComposing()) { michael@0: return; michael@0: } michael@0: michael@0: WidgetKeyboardEvent keyupEvent(true, NS_KEY_UP, mWidget); michael@0: InitKeyEvent(aNativeEvent, keyupEvent); michael@0: michael@0: DispatchEvent(keyupEvent); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void michael@0: TextInputHandler::HandleFlagsChanged(NSEvent* aNativeEvent) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (Destroyed()) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandler::HandleFlagsChanged, " michael@0: "widget has been already destroyed", this)); michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr kungFuDeathGrip(mWidget); michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandler::HandleFlagsChanged, aNativeEvent=%p, " michael@0: "type=%s, keyCode=%s (0x%X), modifierFlags=0x%08X, " michael@0: "sLastModifierState=0x%08X, IsIMEComposing()=%s", michael@0: this, aNativeEvent, GetNativeKeyEventType(aNativeEvent), michael@0: GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode], michael@0: [aNativeEvent modifierFlags], sLastModifierState, michael@0: TrueOrFalse(IsIMEComposing()))); michael@0: michael@0: MOZ_ASSERT([aNativeEvent type] == NSFlagsChanged); michael@0: michael@0: NSUInteger diff = [aNativeEvent modifierFlags] ^ sLastModifierState; michael@0: // Device dependent flags for left-control key, both shift keys, both command michael@0: // keys and both option keys have been defined in Next's SDK. But we michael@0: // shouldn't use it directly as far as possible since Cocoa SDK doesn't michael@0: // define them. Fortunately, we need them only when we dispatch keyup michael@0: // events. So, we can usually know the actual relation between keyCode and michael@0: // device dependent flags. However, we need to remove following flags first michael@0: // since the differences don't indicate modifier key state. michael@0: // NX_STYLUSPROXIMITYMASK: Probably used for pen like device. michael@0: // kCGEventFlagMaskNonCoalesced (= NX_NONCOALSESCEDMASK): See the document for michael@0: // Quartz Event Services. michael@0: diff &= ~(NX_STYLUSPROXIMITYMASK | kCGEventFlagMaskNonCoalesced); michael@0: michael@0: switch ([aNativeEvent keyCode]) { michael@0: // CapsLock state and other modifier states are different: michael@0: // CapsLock state does not revert when the CapsLock key goes up, as the michael@0: // modifier state does for other modifier keys on key up. michael@0: case kVK_CapsLock: { michael@0: // Fire key down event for caps lock. michael@0: DispatchKeyEventForFlagsChanged(aNativeEvent, true); michael@0: // XXX should we fire keyup event too? The keyup event for CapsLock key michael@0: // is never dispatched on Gecko. michael@0: // XXX WebKit dispatches keydown event when CapsLock is locked, otherwise, michael@0: // keyup event. If we do so, we cannot keep the consistency with other michael@0: // platform's behavior... michael@0: break; michael@0: } michael@0: michael@0: // If the event is caused by pressing or releasing a modifier key, just michael@0: // dispatch the key's event. michael@0: case kVK_Shift: michael@0: case kVK_RightShift: michael@0: case kVK_Command: michael@0: case kVK_RightCommand: michael@0: case kVK_Control: michael@0: case kVK_RightControl: michael@0: case kVK_Option: michael@0: case kVK_RightOption: michael@0: case kVK_Help: { michael@0: // We assume that at most one modifier is changed per event if the event michael@0: // is caused by pressing or releasing a modifier key. michael@0: bool isKeyDown = ([aNativeEvent modifierFlags] & diff) != 0; michael@0: DispatchKeyEventForFlagsChanged(aNativeEvent, isKeyDown); michael@0: // XXX Some applications might send the event with incorrect device- michael@0: // dependent flags. michael@0: if (isKeyDown && ((diff & ~NSDeviceIndependentModifierFlagsMask) != 0)) { michael@0: unsigned short keyCode = [aNativeEvent keyCode]; michael@0: const ModifierKey* modifierKey = michael@0: GetModifierKeyForDeviceDependentFlags(diff); michael@0: if (modifierKey && modifierKey->keyCode != keyCode) { michael@0: // Although, we're not sure the actual cause of this case, the stored michael@0: // modifier information and the latest key event information may be michael@0: // mismatched. Then, let's reset the stored information. michael@0: // NOTE: If this happens, it may fail to handle NSFlagsChanged event michael@0: // in the default case (below). However, it's the rare case handler michael@0: // and this case occurs rarely. So, we can ignore the edge case bug. michael@0: NS_WARNING("Resetting stored modifier key information"); michael@0: mModifierKeys.Clear(); michael@0: modifierKey = nullptr; michael@0: } michael@0: if (!modifierKey) { michael@0: mModifierKeys.AppendElement(ModifierKey(diff, keyCode)); michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: michael@0: // Currently we don't support Fn key since other browsers don't dispatch michael@0: // events for it and we don't have keyCode for this key. michael@0: // It should be supported when we implement .key and .char. michael@0: case kVK_Function: michael@0: break; michael@0: michael@0: // If the event is caused by something else than pressing or releasing a michael@0: // single modifier key (for example by the app having been deactivated michael@0: // using command-tab), use the modifiers themselves to determine which michael@0: // key's event to dispatch, and whether it's a keyup or keydown event. michael@0: // In all cases we assume one or more modifiers are being deactivated michael@0: // (never activated) -- otherwise we'd have received one or more events michael@0: // corresponding to a single modifier key being pressed. michael@0: default: { michael@0: NSUInteger modifiers = sLastModifierState; michael@0: for (int32_t bit = 0; bit < 32; ++bit) { michael@0: NSUInteger flag = 1 << bit; michael@0: if (!(diff & flag)) { michael@0: continue; michael@0: } michael@0: michael@0: // Given correct information from the application, a flag change here michael@0: // will normally be a deactivation (except for some lockable modifiers michael@0: // such as CapsLock). But some applications (like VNC) can send an michael@0: // activating event with a zero keyCode. So we need to check for that michael@0: // here. michael@0: bool dispatchKeyDown = ((flag & [aNativeEvent modifierFlags]) != 0); michael@0: michael@0: unsigned short keyCode = 0; michael@0: if (flag & NSDeviceIndependentModifierFlagsMask) { michael@0: switch (flag) { michael@0: case NSAlphaShiftKeyMask: michael@0: keyCode = kVK_CapsLock; michael@0: dispatchKeyDown = true; michael@0: break; michael@0: michael@0: case NSNumericPadKeyMask: michael@0: // NSNumericPadKeyMask is fired by VNC a lot. But not all of michael@0: // these events can really be Clear key events, so we just ignore michael@0: // them. michael@0: continue; michael@0: michael@0: case NSHelpKeyMask: michael@0: keyCode = kVK_Help; michael@0: break; michael@0: michael@0: case NSFunctionKeyMask: michael@0: // An NSFunctionKeyMask change here will normally be a michael@0: // deactivation. But sometimes it will be an activation send (by michael@0: // VNC for example) with a zero keyCode. michael@0: continue; michael@0: michael@0: // These cases (NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask michael@0: // and NSCommandKeyMask) should be handled by the other branch of michael@0: // the if statement, below (which handles device dependent flags). michael@0: // However, some applications (like VNC) can send key events without michael@0: // any device dependent flags, so we handle them here instead. michael@0: case NSShiftKeyMask: michael@0: keyCode = (modifiers & 0x0004) ? kVK_RightShift : kVK_Shift; michael@0: break; michael@0: case NSControlKeyMask: michael@0: keyCode = (modifiers & 0x2000) ? kVK_RightControl : kVK_Control; michael@0: break; michael@0: case NSAlternateKeyMask: michael@0: keyCode = (modifiers & 0x0040) ? kVK_RightOption : kVK_Option; michael@0: break; michael@0: case NSCommandKeyMask: michael@0: keyCode = (modifiers & 0x0010) ? kVK_RightCommand : kVK_Command; michael@0: break; michael@0: michael@0: default: michael@0: continue; michael@0: } michael@0: } else { michael@0: const ModifierKey* modifierKey = michael@0: GetModifierKeyForDeviceDependentFlags(flag); michael@0: if (!modifierKey) { michael@0: // See the note above (in the other branch of the if statement) michael@0: // about the NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask michael@0: // and NSCommandKeyMask cases. michael@0: continue; michael@0: } michael@0: keyCode = modifierKey->keyCode; michael@0: } michael@0: michael@0: // Remove flags michael@0: modifiers &= ~flag; michael@0: switch (keyCode) { michael@0: case kVK_Shift: { michael@0: const ModifierKey* modifierKey = michael@0: GetModifierKeyForNativeKeyCode(kVK_RightShift); michael@0: if (!modifierKey || michael@0: !(modifiers & modifierKey->GetDeviceDependentFlags())) { michael@0: modifiers &= ~NSShiftKeyMask; michael@0: } michael@0: break; michael@0: } michael@0: case kVK_RightShift: { michael@0: const ModifierKey* modifierKey = michael@0: GetModifierKeyForNativeKeyCode(kVK_Shift); michael@0: if (!modifierKey || michael@0: !(modifiers & modifierKey->GetDeviceDependentFlags())) { michael@0: modifiers &= ~NSShiftKeyMask; michael@0: } michael@0: break; michael@0: } michael@0: case kVK_Command: { michael@0: const ModifierKey* modifierKey = michael@0: GetModifierKeyForNativeKeyCode(kVK_RightCommand); michael@0: if (!modifierKey || michael@0: !(modifiers & modifierKey->GetDeviceDependentFlags())) { michael@0: modifiers &= ~NSCommandKeyMask; michael@0: } michael@0: break; michael@0: } michael@0: case kVK_RightCommand: { michael@0: const ModifierKey* modifierKey = michael@0: GetModifierKeyForNativeKeyCode(kVK_Command); michael@0: if (!modifierKey || michael@0: !(modifiers & modifierKey->GetDeviceDependentFlags())) { michael@0: modifiers &= ~NSCommandKeyMask; michael@0: } michael@0: break; michael@0: } michael@0: case kVK_Control: { michael@0: const ModifierKey* modifierKey = michael@0: GetModifierKeyForNativeKeyCode(kVK_RightControl); michael@0: if (!modifierKey || michael@0: !(modifiers & modifierKey->GetDeviceDependentFlags())) { michael@0: modifiers &= ~NSControlKeyMask; michael@0: } michael@0: break; michael@0: } michael@0: case kVK_RightControl: { michael@0: const ModifierKey* modifierKey = michael@0: GetModifierKeyForNativeKeyCode(kVK_Control); michael@0: if (!modifierKey || michael@0: !(modifiers & modifierKey->GetDeviceDependentFlags())) { michael@0: modifiers &= ~NSControlKeyMask; michael@0: } michael@0: break; michael@0: } michael@0: case kVK_Option: { michael@0: const ModifierKey* modifierKey = michael@0: GetModifierKeyForNativeKeyCode(kVK_RightOption); michael@0: if (!modifierKey || michael@0: !(modifiers & modifierKey->GetDeviceDependentFlags())) { michael@0: modifiers &= ~NSAlternateKeyMask; michael@0: } michael@0: break; michael@0: } michael@0: case kVK_RightOption: { michael@0: const ModifierKey* modifierKey = michael@0: GetModifierKeyForNativeKeyCode(kVK_Option); michael@0: if (!modifierKey || michael@0: !(modifiers & modifierKey->GetDeviceDependentFlags())) { michael@0: modifiers &= ~NSAlternateKeyMask; michael@0: } michael@0: break; michael@0: } michael@0: case kVK_Help: michael@0: modifiers &= ~NSHelpKeyMask; michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: NSEvent* event = michael@0: [NSEvent keyEventWithType:NSFlagsChanged michael@0: location:[aNativeEvent locationInWindow] michael@0: modifierFlags:modifiers michael@0: timestamp:[aNativeEvent timestamp] michael@0: windowNumber:[aNativeEvent windowNumber] michael@0: context:[aNativeEvent context] michael@0: characters:nil michael@0: charactersIgnoringModifiers:nil michael@0: isARepeat:NO michael@0: keyCode:keyCode]; michael@0: DispatchKeyEventForFlagsChanged(event, dispatchKeyDown); michael@0: if (Destroyed()) { michael@0: break; michael@0: } michael@0: michael@0: // Stop if focus has changed. michael@0: // Check to see if mView is still the first responder. michael@0: if (![mView isFirstResponder]) { michael@0: break; michael@0: } michael@0: michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Be aware, the widget may have been destroyed. michael@0: sLastModifierState = [aNativeEvent modifierFlags]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: const TextInputHandler::ModifierKey* michael@0: TextInputHandler::GetModifierKeyForNativeKeyCode(unsigned short aKeyCode) const michael@0: { michael@0: for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) { michael@0: if (mModifierKeys[i].keyCode == aKeyCode) { michael@0: return &((ModifierKey&)mModifierKeys[i]); michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: const TextInputHandler::ModifierKey* michael@0: TextInputHandler::GetModifierKeyForDeviceDependentFlags(NSUInteger aFlags) const michael@0: { michael@0: for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) { michael@0: if (mModifierKeys[i].GetDeviceDependentFlags() == michael@0: (aFlags & ~NSDeviceIndependentModifierFlagsMask)) { michael@0: return &((ModifierKey&)mModifierKeys[i]); michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: TextInputHandler::DispatchKeyEventForFlagsChanged(NSEvent* aNativeEvent, michael@0: bool aDispatchKeyDown) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (Destroyed()) { michael@0: return; michael@0: } michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandler::DispatchKeyEventForFlagsChanged, aNativeEvent=%p, " michael@0: "type=%s, keyCode=%s (0x%X), aDispatchKeyDown=%s, IsIMEComposing()=%s", michael@0: this, aNativeEvent, GetNativeKeyEventType(aNativeEvent), michael@0: GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode], michael@0: TrueOrFalse(aDispatchKeyDown), TrueOrFalse(IsIMEComposing()))); michael@0: michael@0: if ([aNativeEvent type] != NSFlagsChanged || IsIMEComposing()) { michael@0: return; michael@0: } michael@0: michael@0: uint32_t message = aDispatchKeyDown ? NS_KEY_DOWN : NS_KEY_UP; michael@0: michael@0: NPCocoaEvent cocoaEvent; michael@0: michael@0: // Fire a key event. michael@0: WidgetKeyboardEvent keyEvent(true, message, mWidget); michael@0: InitKeyEvent(aNativeEvent, keyEvent); michael@0: michael@0: // create event for use by plugins michael@0: if ([mView isPluginView]) { michael@0: if ([mView pluginEventModel] == NPEventModelCocoa) { michael@0: ConvertCocoaKeyEventToNPCocoaEvent(aNativeEvent, cocoaEvent); michael@0: keyEvent.pluginEvent = &cocoaEvent; michael@0: } michael@0: } michael@0: michael@0: DispatchEvent(keyEvent); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void michael@0: TextInputHandler::InsertText(NSAttributedString* aAttrString, michael@0: NSRange* aReplacementRange) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (Destroyed()) { michael@0: return; michael@0: } michael@0: michael@0: KeyEventState* currentKeyEvent = GetCurrentKeyEvent(); michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandler::InsertText, aAttrString=\"%s\", " michael@0: "aReplacementRange=%p { location=%llu, length=%llu }, " michael@0: "IsIMEComposing()=%s, IgnoreIMEComposition()=%s, " michael@0: "keyevent=%p, keydownHandled=%s, keypressDispatched=%s, " michael@0: "causedOtherKeyEvents=%s", michael@0: this, GetCharacters([aAttrString string]), aReplacementRange, michael@0: aReplacementRange ? aReplacementRange->location : 0, michael@0: aReplacementRange ? aReplacementRange->length : 0, michael@0: TrueOrFalse(IsIMEComposing()), TrueOrFalse(IgnoreIMEComposition()), michael@0: currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr, michael@0: currentKeyEvent ? michael@0: TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A", michael@0: currentKeyEvent ? michael@0: TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A", michael@0: currentKeyEvent ? michael@0: TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A")); michael@0: michael@0: if (IgnoreIMEComposition()) { michael@0: return; michael@0: } michael@0: michael@0: InputContext context = mWidget->GetInputContext(); michael@0: bool isEditable = (context.mIMEState.mEnabled == IMEState::ENABLED || michael@0: context.mIMEState.mEnabled == IMEState::PASSWORD); michael@0: NSRange selectedRange = SelectedRange(); michael@0: michael@0: nsAutoString str; michael@0: nsCocoaUtils::GetStringForNSString([aAttrString string], str); michael@0: if (!IsIMEComposing() && str.IsEmpty()) { michael@0: // nothing to do if there is no content which can be removed. michael@0: if (!isEditable) { michael@0: return; michael@0: } michael@0: // If replacement range is specified, we need to remove the range. michael@0: // Otherwise, we need to remove the selected range if it's not collapsed. michael@0: if (aReplacementRange && aReplacementRange->location != NSNotFound) { michael@0: // nothing to do since the range is collapsed. michael@0: if (aReplacementRange->length == 0) { michael@0: return; michael@0: } michael@0: // If the replacement range is different from current selected range, michael@0: // select the range. michael@0: if (!NSEqualRanges(selectedRange, *aReplacementRange)) { michael@0: NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange)); michael@0: } michael@0: selectedRange = SelectedRange(); michael@0: } michael@0: NS_ENSURE_TRUE_VOID(selectedRange.location != NSNotFound); michael@0: if (selectedRange.length == 0) { michael@0: return; // nothing to do michael@0: } michael@0: // If this is caused by a key input, the keypress event which will be michael@0: // dispatched later should cause the delete. Therefore, nothing to do here. michael@0: // Although, we're not sure if such case is actually possible. michael@0: if (!currentKeyEvent) { michael@0: return; michael@0: } michael@0: // Delete the selected range. michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: WidgetContentCommandEvent deleteCommandEvent(true, michael@0: NS_CONTENT_COMMAND_DELETE, michael@0: mWidget); michael@0: DispatchEvent(deleteCommandEvent); michael@0: NS_ENSURE_TRUE_VOID(deleteCommandEvent.mSucceeded); michael@0: // Be aware! The widget might be destroyed here. michael@0: return; michael@0: } michael@0: michael@0: if (str.Length() != 1 || IsIMEComposing()) { michael@0: InsertTextAsCommittingComposition(aAttrString, aReplacementRange); michael@0: return; michael@0: } michael@0: michael@0: // Don't let the same event be fired twice when hitting michael@0: // enter/return! (Bug 420502) michael@0: if (currentKeyEvent && !currentKeyEvent->CanDispatchKeyPressEvent()) { michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr kungFuDeathGrip(mWidget); michael@0: michael@0: // If the replacement range is specified, select the range. Then, the michael@0: // selection will be replaced by the later keypress event. michael@0: if (isEditable && michael@0: aReplacementRange && aReplacementRange->location != NSNotFound && michael@0: !NSEqualRanges(selectedRange, *aReplacementRange)) { michael@0: NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange)); michael@0: } michael@0: michael@0: // Dispatch keypress event with char instead of textEvent michael@0: WidgetKeyboardEvent keypressEvent(true, NS_KEY_PRESS, mWidget); michael@0: keypressEvent.isChar = IsPrintableChar(str.CharAt(0)); michael@0: michael@0: // Don't set other modifiers from the current event, because here in michael@0: // -insertText: they've already been taken into account in creating michael@0: // the input string. michael@0: michael@0: if (currentKeyEvent) { michael@0: NSEvent* keyEvent = currentKeyEvent->mKeyEvent; michael@0: InitKeyEvent(keyEvent, keypressEvent, &str); michael@0: } else { michael@0: nsCocoaUtils::InitInputEvent(keypressEvent, static_cast(nullptr)); michael@0: if (keypressEvent.isChar) { michael@0: keypressEvent.charCode = str.CharAt(0); michael@0: } michael@0: // Note that insertText is not called only at key pressing. michael@0: if (!keypressEvent.charCode) { michael@0: keypressEvent.keyCode = michael@0: WidgetUtils::ComputeKeyCodeFromChar(keypressEvent.charCode); michael@0: } michael@0: } michael@0: michael@0: // Remove basic modifiers from keypress event because if they are included, michael@0: // nsPlaintextEditor ignores the event. michael@0: keypressEvent.modifiers &= ~(MODIFIER_CONTROL | michael@0: MODIFIER_ALT | michael@0: MODIFIER_META); michael@0: michael@0: // TODO: michael@0: // If mCurrentKeyEvent.mKeyEvent is null and when we implement textInput michael@0: // event of DOM3 Events, we should dispatch it instead of keypress event. michael@0: bool keyPressHandled = DispatchEvent(keypressEvent); michael@0: michael@0: // Note: mWidget might have become null here. Don't count on it from here on. michael@0: michael@0: if (currentKeyEvent) { michael@0: currentKeyEvent->mKeyPressHandled = keyPressHandled; michael@0: currentKeyEvent->mKeyPressDispatched = true; michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: bool michael@0: TextInputHandler::DoCommandBySelector(const char* aSelector) michael@0: { michael@0: nsRefPtr kungFuDeathGrip(mWidget); michael@0: michael@0: KeyEventState* currentKeyEvent = GetCurrentKeyEvent(); michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandler::DoCommandBySelector, aSelector=\"%s\", " michael@0: "Destroyed()=%s, keydownHandled=%s, keypressHandled=%s, " michael@0: "causedOtherKeyEvents=%s", michael@0: this, aSelector ? aSelector : "", TrueOrFalse(Destroyed()), michael@0: currentKeyEvent ? michael@0: TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A", michael@0: currentKeyEvent ? michael@0: TrueOrFalse(currentKeyEvent->mKeyPressHandled) : "N/A", michael@0: currentKeyEvent ? michael@0: TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A")); michael@0: michael@0: if (currentKeyEvent && currentKeyEvent->CanDispatchKeyPressEvent()) { michael@0: WidgetKeyboardEvent keypressEvent(true, NS_KEY_PRESS, mWidget); michael@0: InitKeyEvent(currentKeyEvent->mKeyEvent, keypressEvent); michael@0: currentKeyEvent->mKeyPressHandled = DispatchEvent(keypressEvent); michael@0: currentKeyEvent->mKeyPressDispatched = true; michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandler::DoCommandBySelector, keypress event " michael@0: "dispatched, Destroyed()=%s, keypressHandled=%s", michael@0: this, TrueOrFalse(Destroyed()), michael@0: TrueOrFalse(currentKeyEvent->mKeyPressHandled))); michael@0: } michael@0: michael@0: return (!Destroyed() && currentKeyEvent && michael@0: currentKeyEvent->IsDefaultPrevented()); michael@0: } michael@0: michael@0: michael@0: #pragma mark - michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * michael@0: * IMEInputHandler implementation (static methods) michael@0: * michael@0: ******************************************************************************/ michael@0: michael@0: bool IMEInputHandler::sStaticMembersInitialized = false; michael@0: CFStringRef IMEInputHandler::sLatestIMEOpenedModeInputSourceID = nullptr; michael@0: IMEInputHandler* IMEInputHandler::sFocusedIMEHandler = nullptr; michael@0: michael@0: // static michael@0: void michael@0: IMEInputHandler::InitStaticMembers() michael@0: { michael@0: if (sStaticMembersInitialized) michael@0: return; michael@0: sStaticMembersInitialized = true; michael@0: // We need to check the keyboard layout changes on all applications. michael@0: CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter(); michael@0: // XXX Don't we need to remove the observer at shut down? michael@0: // Mac Dev Center's document doesn't say how to remove the observer if michael@0: // the second parameter is NULL. michael@0: ::CFNotificationCenterAddObserver(center, NULL, michael@0: OnCurrentTextInputSourceChange, michael@0: kTISNotifySelectedKeyboardInputSourceChanged, NULL, michael@0: CFNotificationSuspensionBehaviorDeliverImmediately); michael@0: // Initiailize with the current keyboard layout michael@0: OnCurrentTextInputSourceChange(NULL, NULL, michael@0: kTISNotifySelectedKeyboardInputSourceChanged, michael@0: NULL, NULL); michael@0: } michael@0: michael@0: // static michael@0: void michael@0: IMEInputHandler::OnCurrentTextInputSourceChange(CFNotificationCenterRef aCenter, michael@0: void* aObserver, michael@0: CFStringRef aName, michael@0: const void* aObject, michael@0: CFDictionaryRef aUserInfo) michael@0: { michael@0: // Cache the latest IME opened mode to sLatestIMEOpenedModeInputSourceID. michael@0: TISInputSourceWrapper tis; michael@0: tis.InitByCurrentInputSource(); michael@0: if (tis.IsOpenedIMEMode()) { michael@0: tis.GetInputSourceID(sLatestIMEOpenedModeInputSourceID); michael@0: } michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) { michael@0: static CFStringRef sLastTIS = nullptr; michael@0: CFStringRef newTIS; michael@0: tis.GetInputSourceID(newTIS); michael@0: if (!sLastTIS || michael@0: ::CFStringCompare(sLastTIS, newTIS, 0) != kCFCompareEqualTo) { michael@0: TISInputSourceWrapper tis1, tis2, tis3, tis4, tis5; michael@0: tis1.InitByCurrentKeyboardLayout(); michael@0: tis2.InitByCurrentASCIICapableInputSource(); michael@0: tis3.InitByCurrentASCIICapableKeyboardLayout(); michael@0: tis4.InitByCurrentInputMethodKeyboardLayoutOverride(); michael@0: tis5.InitByTISInputSourceRef(tis.GetKeyboardLayoutInputSource()); michael@0: CFStringRef is0 = nullptr, is1 = nullptr, is2 = nullptr, is3 = nullptr, michael@0: is4 = nullptr, is5 = nullptr, type0 = nullptr, michael@0: lang0 = nullptr, bundleID0 = nullptr; michael@0: tis.GetInputSourceID(is0); michael@0: tis1.GetInputSourceID(is1); michael@0: tis2.GetInputSourceID(is2); michael@0: tis3.GetInputSourceID(is3); michael@0: tis4.GetInputSourceID(is4); michael@0: tis5.GetInputSourceID(is5); michael@0: tis.GetInputSourceType(type0); michael@0: tis.GetPrimaryLanguage(lang0); michael@0: tis.GetBundleID(bundleID0); michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("IMEInputHandler::OnCurrentTextInputSourceChange,\n" michael@0: " Current Input Source is changed to:\n" michael@0: " currentInputContext=%p\n" michael@0: " %s\n" michael@0: " type=%s %s\n" michael@0: " overridden keyboard layout=%s\n" michael@0: " used keyboard layout for translation=%s\n" michael@0: " primary language=%s\n" michael@0: " bundle ID=%s\n" michael@0: " current ASCII capable Input Source=%s\n" michael@0: " current Keyboard Layout=%s\n" michael@0: " current ASCII capable Keyboard Layout=%s", michael@0: [NSTextInputContext currentInputContext], GetCharacters(is0), michael@0: GetCharacters(type0), tis.IsASCIICapable() ? "- ASCII capable " : "", michael@0: GetCharacters(is4), GetCharacters(is5), michael@0: GetCharacters(lang0), GetCharacters(bundleID0), michael@0: GetCharacters(is2), GetCharacters(is1), GetCharacters(is3))); michael@0: } michael@0: sLastTIS = newTIS; michael@0: } michael@0: #endif // #ifdef PR_LOGGING michael@0: } michael@0: michael@0: // static michael@0: void michael@0: IMEInputHandler::FlushPendingMethods(nsITimer* aTimer, void* aClosure) michael@0: { michael@0: NS_ASSERTION(aClosure, "aClosure is null"); michael@0: static_cast(aClosure)->ExecutePendingMethods(); michael@0: } michael@0: michael@0: // static michael@0: CFArrayRef michael@0: IMEInputHandler::CreateAllIMEModeList() michael@0: { michael@0: const void* keys[] = { kTISPropertyInputSourceType }; michael@0: const void* values[] = { kTISTypeKeyboardInputMode }; michael@0: CFDictionaryRef filter = michael@0: ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL); michael@0: NS_ASSERTION(filter, "failed to create the filter"); michael@0: CFArrayRef list = ::TISCreateInputSourceList(filter, true); michael@0: ::CFRelease(filter); michael@0: return list; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: IMEInputHandler::DebugPrintAllIMEModes() michael@0: { michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) { michael@0: CFArrayRef list = CreateAllIMEModeList(); michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, ("IME mode configuration:")); michael@0: CFIndex idx = ::CFArrayGetCount(list); michael@0: TISInputSourceWrapper tis; michael@0: for (CFIndex i = 0; i < idx; ++i) { michael@0: TISInputSourceRef inputSource = static_cast( michael@0: const_cast(::CFArrayGetValueAtIndex(list, i))); michael@0: tis.InitByTISInputSourceRef(inputSource); michael@0: nsAutoString name, isid; michael@0: tis.GetLocalizedName(name); michael@0: tis.GetInputSourceID(isid); michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: (" %s\t<%s>%s%s\n", michael@0: NS_ConvertUTF16toUTF8(name).get(), michael@0: NS_ConvertUTF16toUTF8(isid).get(), michael@0: tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)", michael@0: tis.IsEnabled() ? "" : "\t(Isn't Enabled)")); michael@0: } michael@0: ::CFRelease(list); michael@0: } michael@0: #endif // #ifdef PR_LOGGING michael@0: } michael@0: michael@0: //static michael@0: TSMDocumentID michael@0: IMEInputHandler::GetCurrentTSMDocumentID() michael@0: { michael@0: // At least on Mac OS X 10.6.x and 10.7.x, ::TSMGetActiveDocument() has a bug. michael@0: // The result of ::TSMGetActiveDocument() isn't modified for new active text michael@0: // input context until [NSTextInputContext currentInputContext] is called. michael@0: // Therefore, we need to call it here. michael@0: [NSTextInputContext currentInputContext]; michael@0: return ::TSMGetActiveDocument(); michael@0: } michael@0: michael@0: michael@0: #pragma mark - michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * michael@0: * IMEInputHandler implementation #1 michael@0: * The methods are releated to the pending methods. Some jobs should be michael@0: * run after the stack is finished, e.g, some methods cannot run the jobs michael@0: * during processing the focus event. And also some other jobs should be michael@0: * run at the next focus event is processed. michael@0: * The pending methods are recorded in mPendingMethods. They are executed michael@0: * by ExecutePendingMethods via FlushPendingMethods. michael@0: * michael@0: ******************************************************************************/ michael@0: michael@0: void michael@0: IMEInputHandler::NotifyIMEOfFocusChangeInGecko() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::NotifyIMEOfFocusChangeInGecko, " michael@0: "Destroyed()=%s, IsFocused()=%s, inputContext=%p", michael@0: this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()), michael@0: mView ? [mView inputContext] : nullptr)); michael@0: michael@0: if (Destroyed()) { michael@0: return; michael@0: } michael@0: michael@0: if (!IsFocused()) { michael@0: // retry at next focus event michael@0: mPendingMethods |= kNotifyIMEOfFocusChangeInGecko; michael@0: return; michael@0: } michael@0: michael@0: MOZ_ASSERT(mView); michael@0: NSTextInputContext* inputContext = [mView inputContext]; michael@0: NS_ENSURE_TRUE_VOID(inputContext); michael@0: michael@0: // When an element on a XUL element gets focus from an michael@0: // element on the opener window of the element, the owner window michael@0: // still has native focus. Therefore, IMEs may store the opener window's michael@0: // level at this time because they don't know the actual focus is moved to michael@0: // different window. If IMEs try to get the newest window level after the michael@0: // focus change, we return the window level of the XUL 's widget. michael@0: // Therefore, let's emulate the native focus change. Then, IMEs can refresh michael@0: // the stored window level. michael@0: [inputContext deactivate]; michael@0: [inputContext activate]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void michael@0: IMEInputHandler::DiscardIMEComposition() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::DiscardIMEComposition, " michael@0: "Destroyed()=%s, IsFocused()=%s, mView=%p, inputContext=%p", michael@0: this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()), michael@0: mView, mView ? [mView inputContext] : nullptr)); michael@0: michael@0: if (Destroyed()) { michael@0: return; michael@0: } michael@0: michael@0: if (!IsFocused()) { michael@0: // retry at next focus event michael@0: mPendingMethods |= kDiscardIMEComposition; michael@0: return; michael@0: } michael@0: michael@0: NS_ENSURE_TRUE_VOID(mView); michael@0: NSTextInputContext* inputContext = [mView inputContext]; michael@0: NS_ENSURE_TRUE_VOID(inputContext); michael@0: mIgnoreIMECommit = true; michael@0: [inputContext discardMarkedText]; michael@0: mIgnoreIMECommit = false; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK michael@0: } michael@0: michael@0: void michael@0: IMEInputHandler::SyncASCIICapableOnly() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::SyncASCIICapableOnly, " michael@0: "Destroyed()=%s, IsFocused()=%s, mIsASCIICapableOnly=%s, " michael@0: "GetCurrentTSMDocumentID()=%p", michael@0: this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()), michael@0: TrueOrFalse(mIsASCIICapableOnly), GetCurrentTSMDocumentID())); michael@0: michael@0: if (Destroyed()) { michael@0: return; michael@0: } michael@0: michael@0: if (!IsFocused()) { michael@0: // retry at next focus event michael@0: mPendingMethods |= kSyncASCIICapableOnly; michael@0: return; michael@0: } michael@0: michael@0: TSMDocumentID doc = GetCurrentTSMDocumentID(); michael@0: if (!doc) { michael@0: // retry michael@0: mPendingMethods |= kSyncASCIICapableOnly; michael@0: NS_WARNING("Application is active but there is no active document"); michael@0: ResetTimer(); michael@0: return; michael@0: } michael@0: michael@0: if (mIsASCIICapableOnly) { michael@0: CFArrayRef ASCIICapableTISList = ::TISCreateASCIICapableInputSourceList(); michael@0: ::TSMSetDocumentProperty(doc, michael@0: kTSMDocumentEnabledInputSourcesPropertyTag, michael@0: sizeof(CFArrayRef), michael@0: &ASCIICapableTISList); michael@0: ::CFRelease(ASCIICapableTISList); michael@0: } else { michael@0: ::TSMRemoveDocumentProperty(doc, michael@0: kTSMDocumentEnabledInputSourcesPropertyTag); michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void michael@0: IMEInputHandler::ResetTimer() michael@0: { michael@0: NS_ASSERTION(mPendingMethods != 0, michael@0: "There are not pending methods, why this is called?"); michael@0: if (mTimer) { michael@0: mTimer->Cancel(); michael@0: } else { michael@0: mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); michael@0: NS_ENSURE_TRUE(mTimer, ); michael@0: } michael@0: mTimer->InitWithFuncCallback(FlushPendingMethods, this, 0, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: michael@0: void michael@0: IMEInputHandler::ExecutePendingMethods() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (mTimer) { michael@0: mTimer->Cancel(); michael@0: mTimer = nullptr; michael@0: } michael@0: michael@0: if (![[NSApplication sharedApplication] isActive]) { michael@0: mIsInFocusProcessing = false; michael@0: // If we're not active, we should retry at focus event michael@0: return; michael@0: } michael@0: michael@0: uint32_t pendingMethods = mPendingMethods; michael@0: // First, reset the pending method flags because if each methods cannot michael@0: // run now, they can reentry to the pending flags by theirselves. michael@0: mPendingMethods = 0; michael@0: michael@0: if (pendingMethods & kDiscardIMEComposition) michael@0: DiscardIMEComposition(); michael@0: if (pendingMethods & kSyncASCIICapableOnly) michael@0: SyncASCIICapableOnly(); michael@0: if (pendingMethods & kNotifyIMEOfFocusChangeInGecko) { michael@0: NotifyIMEOfFocusChangeInGecko(); michael@0: } michael@0: michael@0: mIsInFocusProcessing = false; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: #pragma mark - michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * michael@0: * IMEInputHandler implementation (native event handlers) michael@0: * michael@0: ******************************************************************************/ michael@0: michael@0: uint32_t michael@0: IMEInputHandler::ConvertToTextRangeType(uint32_t aUnderlineStyle, michael@0: NSRange& aSelectedRange) michael@0: { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::ConvertToTextRangeType, " michael@0: "aUnderlineStyle=%llu, aSelectedRange.length=%llu,", michael@0: this, aUnderlineStyle, aSelectedRange.length)); michael@0: michael@0: // We assume that aUnderlineStyle is NSUnderlineStyleSingle or michael@0: // NSUnderlineStyleThick. NSUnderlineStyleThick should indicate a selected michael@0: // clause. Otherwise, should indicate non-selected clause. michael@0: michael@0: if (aSelectedRange.length == 0) { michael@0: switch (aUnderlineStyle) { michael@0: case NSUnderlineStyleSingle: michael@0: return NS_TEXTRANGE_RAWINPUT; michael@0: case NSUnderlineStyleThick: michael@0: return NS_TEXTRANGE_SELECTEDRAWTEXT; michael@0: default: michael@0: NS_WARNING("Unexpected line style"); michael@0: return NS_TEXTRANGE_SELECTEDRAWTEXT; michael@0: } michael@0: } michael@0: michael@0: switch (aUnderlineStyle) { michael@0: case NSUnderlineStyleSingle: michael@0: return NS_TEXTRANGE_CONVERTEDTEXT; michael@0: case NSUnderlineStyleThick: michael@0: return NS_TEXTRANGE_SELECTEDCONVERTEDTEXT; michael@0: default: michael@0: NS_WARNING("Unexpected line style"); michael@0: return NS_TEXTRANGE_SELECTEDCONVERTEDTEXT; michael@0: } michael@0: } michael@0: michael@0: uint32_t michael@0: IMEInputHandler::GetRangeCount(NSAttributedString *aAttrString) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: // Iterate through aAttrString for the NSUnderlineStyleAttributeName and michael@0: // count the different segments adjusting limitRange as we go. michael@0: uint32_t count = 0; michael@0: NSRange effectiveRange; michael@0: NSRange limitRange = NSMakeRange(0, [aAttrString length]); michael@0: while (limitRange.length > 0) { michael@0: [aAttrString attribute:NSUnderlineStyleAttributeName michael@0: atIndex:limitRange.location michael@0: longestEffectiveRange:&effectiveRange michael@0: inRange:limitRange]; michael@0: limitRange = michael@0: NSMakeRange(NSMaxRange(effectiveRange), michael@0: NSMaxRange(limitRange) - NSMaxRange(effectiveRange)); michael@0: count++; michael@0: } michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::GetRangeCount, aAttrString=\"%s\", count=%llu", michael@0: this, GetCharacters([aAttrString string]), count)); michael@0: michael@0: return count; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0); michael@0: } michael@0: michael@0: already_AddRefed michael@0: IMEInputHandler::CreateTextRangeArray(NSAttributedString *aAttrString, michael@0: NSRange& aSelectedRange) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: // Convert the Cocoa range into the TextRange Array used in Gecko. michael@0: // Iterate through the attributed string and map the underline attribute to michael@0: // Gecko IME textrange attributes. We may need to change the code here if michael@0: // we change the implementation of validAttributesForMarkedText. michael@0: NSRange limitRange = NSMakeRange(0, [aAttrString length]); michael@0: uint32_t rangeCount = GetRangeCount(aAttrString); michael@0: nsRefPtr textRangeArray = michael@0: new mozilla::TextRangeArray(); michael@0: for (uint32_t i = 0; i < rangeCount && limitRange.length > 0; i++) { michael@0: NSRange effectiveRange; michael@0: id attributeValue = [aAttrString attribute:NSUnderlineStyleAttributeName michael@0: atIndex:limitRange.location michael@0: longestEffectiveRange:&effectiveRange michael@0: inRange:limitRange]; michael@0: michael@0: TextRange range; michael@0: range.mStartOffset = effectiveRange.location; michael@0: range.mEndOffset = NSMaxRange(effectiveRange); michael@0: range.mRangeType = michael@0: ConvertToTextRangeType([attributeValue intValue], aSelectedRange); michael@0: textRangeArray->AppendElement(range); michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::CreateTextRangeArray, " michael@0: "range={ mStartOffset=%llu, mEndOffset=%llu, mRangeType=%s }", michael@0: this, range.mStartOffset, range.mEndOffset, michael@0: GetRangeTypeName(range.mRangeType))); michael@0: michael@0: limitRange = michael@0: NSMakeRange(NSMaxRange(effectiveRange), michael@0: NSMaxRange(limitRange) - NSMaxRange(effectiveRange)); michael@0: } michael@0: michael@0: // Get current caret position. michael@0: TextRange range; michael@0: range.mStartOffset = aSelectedRange.location + aSelectedRange.length; michael@0: range.mEndOffset = range.mStartOffset; michael@0: range.mRangeType = NS_TEXTRANGE_CARETPOSITION; michael@0: textRangeArray->AppendElement(range); michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::CreateTextRangeArray, " michael@0: "range={ mStartOffset=%llu, mEndOffset=%llu, mRangeType=%s }", michael@0: this, range.mStartOffset, range.mEndOffset, michael@0: GetRangeTypeName(range.mRangeType))); michael@0: michael@0: return textRangeArray.forget(); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: bool michael@0: IMEInputHandler::DispatchTextEvent(const nsString& aText, michael@0: NSAttributedString* aAttrString, michael@0: NSRange& aSelectedRange, michael@0: bool aDoCommit) michael@0: { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::DispatchTextEvent, " michael@0: "aText=\"%s\", aAttrString=\"%s\", " michael@0: "aSelectedRange={ location=%llu, length=%llu }, " michael@0: "aDoCommit=%s, Destroyed()=%s", michael@0: this, NS_ConvertUTF16toUTF8(aText).get(), michael@0: GetCharacters([aAttrString string]), michael@0: aSelectedRange.location, aSelectedRange.length, michael@0: TrueOrFalse(aDoCommit), TrueOrFalse(Destroyed()))); michael@0: michael@0: NS_ENSURE_TRUE(!Destroyed(), false); michael@0: michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: michael@0: WidgetTextEvent textEvent(true, NS_TEXT_TEXT, mWidget); michael@0: textEvent.time = PR_IntervalNow(); michael@0: textEvent.theText = aText; michael@0: if (!aDoCommit) { michael@0: textEvent.mRanges = CreateTextRangeArray(aAttrString, aSelectedRange); michael@0: } michael@0: michael@0: if (textEvent.theText != mLastDispatchedCompositionString) { michael@0: WidgetCompositionEvent compositionUpdate(true, NS_COMPOSITION_UPDATE, michael@0: mWidget); michael@0: compositionUpdate.time = textEvent.time; michael@0: compositionUpdate.data = textEvent.theText; michael@0: mLastDispatchedCompositionString = textEvent.theText; michael@0: DispatchEvent(compositionUpdate); michael@0: if (mIsInFocusProcessing || Destroyed()) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::DispatchTextEvent, compositionupdate causes " michael@0: "aborting the composition, mIsInFocusProcessing=%s, Destryoed()=%s", michael@0: this, TrueOrFalse(mIsInFocusProcessing), TrueOrFalse(Destroyed()))); michael@0: if (Destroyed()) { michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return DispatchEvent(textEvent); michael@0: } michael@0: michael@0: void michael@0: IMEInputHandler::InitCompositionEvent(WidgetCompositionEvent& aCompositionEvent) michael@0: { michael@0: aCompositionEvent.time = PR_IntervalNow(); michael@0: } michael@0: michael@0: void michael@0: IMEInputHandler::InsertTextAsCommittingComposition( michael@0: NSAttributedString* aAttrString, michael@0: NSRange* aReplacementRange) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::InsertTextAsCommittingComposition, " michael@0: "aAttrString=\"%s\", aReplacementRange=%p { location=%llu, length=%llu }, " michael@0: "Destroyed()=%s, IsIMEComposing()=%s, " michael@0: "mMarkedRange={ location=%llu, length=%llu }", michael@0: this, GetCharacters([aAttrString string]), aReplacementRange, michael@0: aReplacementRange ? aReplacementRange->location : 0, michael@0: aReplacementRange ? aReplacementRange->length : 0, michael@0: TrueOrFalse(Destroyed()), TrueOrFalse(IsIMEComposing()), michael@0: mMarkedRange.location, mMarkedRange.length)); michael@0: michael@0: if (IgnoreIMECommit()) { michael@0: MOZ_CRASH("IMEInputHandler::InsertTextAsCommittingComposition() must not" michael@0: "be called while canceling the composition"); michael@0: } michael@0: michael@0: if (Destroyed()) { michael@0: return; michael@0: } michael@0: michael@0: // First, commit current composition with the latest composition string if the michael@0: // replacement range is different from marked range. michael@0: if (IsIMEComposing() && aReplacementRange && michael@0: aReplacementRange->location != NSNotFound && michael@0: !NSEqualRanges(MarkedRange(), *aReplacementRange)) { michael@0: NSString* latestStr = michael@0: nsCocoaUtils::ToNSString(mLastDispatchedCompositionString); michael@0: NSAttributedString* attrLatestStr = michael@0: [[[NSAttributedString alloc] initWithString:latestStr] autorelease]; michael@0: InsertTextAsCommittingComposition(attrLatestStr, nullptr); michael@0: if (Destroyed()) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::InsertTextAsCommittingComposition, " michael@0: "destroyed by commiting composition for setting replacement range", michael@0: this)); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: michael@0: nsString str; michael@0: nsCocoaUtils::GetStringForNSString([aAttrString string], str); michael@0: michael@0: if (!IsIMEComposing()) { michael@0: // If there is no selection and replacement range is specified, set the michael@0: // range as selection. michael@0: if (aReplacementRange && aReplacementRange->location != NSNotFound && michael@0: !NSEqualRanges(SelectedRange(), *aReplacementRange)) { michael@0: NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange)); michael@0: } michael@0: michael@0: // XXXmnakano Probably, we shouldn't emulate composition in this case. michael@0: // I think that we should just fire DOM3 textInput event if we implement it. michael@0: WidgetCompositionEvent compStart(true, NS_COMPOSITION_START, mWidget); michael@0: InitCompositionEvent(compStart); michael@0: michael@0: DispatchEvent(compStart); michael@0: if (Destroyed()) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::InsertTextAsCommittingComposition, " michael@0: "destroyed by compositionstart event", this)); michael@0: return; michael@0: } michael@0: michael@0: OnStartIMEComposition(); michael@0: } michael@0: michael@0: NSRange range = NSMakeRange(0, str.Length()); michael@0: DispatchTextEvent(str, aAttrString, range, true); michael@0: if (Destroyed()) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::InsertTextAsCommittingComposition, " michael@0: "destroyed by text event", this)); michael@0: return; michael@0: } michael@0: michael@0: OnUpdateIMEComposition([aAttrString string]); michael@0: michael@0: WidgetCompositionEvent compEnd(true, NS_COMPOSITION_END, mWidget); michael@0: InitCompositionEvent(compEnd); michael@0: compEnd.data = mLastDispatchedCompositionString; michael@0: DispatchEvent(compEnd); michael@0: if (Destroyed()) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::InsertTextAsCommittingComposition, " michael@0: "destroyed by compositionend event", this)); michael@0: return; michael@0: } michael@0: michael@0: OnEndIMEComposition(); michael@0: michael@0: mMarkedRange = NSMakeRange(NSNotFound, 0); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void michael@0: IMEInputHandler::SetMarkedText(NSAttributedString* aAttrString, michael@0: NSRange& aSelectedRange, michael@0: NSRange* aReplacementRange) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::SetMarkedText, " michael@0: "aAttrString=\"%s\", aSelectedRange={ location=%llu, length=%llu }, " michael@0: "aReplacementRange=%p { location=%llu, length=%llu }, " michael@0: "Destroyed()=%s, IgnoreIMEComposition()=%s, IsIMEComposing()=%s, " michael@0: "mMarkedRange={ location=%llu, length=%llu }", michael@0: this, GetCharacters([aAttrString string]), michael@0: aSelectedRange.location, aSelectedRange.length, aReplacementRange, michael@0: aReplacementRange ? aReplacementRange->location : 0, michael@0: aReplacementRange ? aReplacementRange->length : 0, michael@0: TrueOrFalse(Destroyed()), TrueOrFalse(IgnoreIMEComposition()), michael@0: TrueOrFalse(IsIMEComposing()), michael@0: mMarkedRange.location, mMarkedRange.length)); michael@0: michael@0: if (Destroyed() || IgnoreIMEComposition()) { michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: michael@0: // First, commit current composition with the latest composition string if the michael@0: // replacement range is different from marked range. michael@0: if (IsIMEComposing() && aReplacementRange && michael@0: aReplacementRange->location != NSNotFound && michael@0: !NSEqualRanges(MarkedRange(), *aReplacementRange)) { michael@0: NSString* latestStr = michael@0: nsCocoaUtils::ToNSString(mLastDispatchedCompositionString); michael@0: NSAttributedString* attrLatestStr = michael@0: [[[NSAttributedString alloc] initWithString:latestStr] autorelease]; michael@0: bool ignoreIMECommit = mIgnoreIMECommit; michael@0: mIgnoreIMECommit = false; michael@0: InsertTextAsCommittingComposition(attrLatestStr, nullptr); michael@0: mIgnoreIMECommit = ignoreIMECommit; michael@0: if (Destroyed()) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::SetMarkedText, " michael@0: "destroyed by commiting composition for setting replacement range", michael@0: this)); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: nsString str; michael@0: nsCocoaUtils::GetStringForNSString([aAttrString string], str); michael@0: michael@0: mMarkedRange.length = str.Length(); michael@0: michael@0: if (!IsIMEComposing() && !str.IsEmpty()) { michael@0: // If there is no selection and replacement range is specified, set the michael@0: // range as selection. michael@0: if (aReplacementRange && aReplacementRange->location != NSNotFound && michael@0: !NSEqualRanges(SelectedRange(), *aReplacementRange)) { michael@0: NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange)); michael@0: } michael@0: michael@0: mMarkedRange.location = SelectedRange().location; michael@0: michael@0: WidgetCompositionEvent compStart(true, NS_COMPOSITION_START, mWidget); michael@0: InitCompositionEvent(compStart); michael@0: michael@0: DispatchEvent(compStart); michael@0: if (Destroyed()) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::SetMarkedText, " michael@0: "destroyed by compositionstart event", this)); michael@0: return; michael@0: } michael@0: michael@0: OnStartIMEComposition(); michael@0: } michael@0: michael@0: if (IsIMEComposing()) { michael@0: OnUpdateIMEComposition([aAttrString string]); michael@0: michael@0: bool doCommit = str.IsEmpty(); michael@0: DispatchTextEvent(str, aAttrString, aSelectedRange, doCommit); michael@0: if (Destroyed()) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::SetMarkedText, " michael@0: "destroyed by text event", this)); michael@0: return; michael@0: } michael@0: michael@0: if (doCommit) { michael@0: WidgetCompositionEvent compEnd(true, NS_COMPOSITION_END, mWidget); michael@0: InitCompositionEvent(compEnd); michael@0: compEnd.data = mLastDispatchedCompositionString; michael@0: DispatchEvent(compEnd); michael@0: if (Destroyed()) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::SetMarkedText, " michael@0: "destroyed by compositionend event", this)); michael@0: return; michael@0: } michael@0: OnEndIMEComposition(); michael@0: } michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: NSInteger michael@0: IMEInputHandler::ConversationIdentifier() michael@0: { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::ConversationIdentifier, Destroyed()=%s", michael@0: this, TrueOrFalse(Destroyed()))); michael@0: michael@0: if (Destroyed()) { michael@0: return reinterpret_cast(mView); michael@0: } michael@0: michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: michael@0: // NOTE: The size of NSInteger is same as pointer size. michael@0: WidgetQueryContentEvent textContent(true, NS_QUERY_TEXT_CONTENT, mWidget); michael@0: textContent.InitForQueryTextContent(0, 0); michael@0: DispatchEvent(textContent); michael@0: if (!textContent.mSucceeded) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::ConversationIdentifier, Failed", this)); michael@0: return reinterpret_cast(mView); michael@0: } michael@0: // XXX This might return same ID as a previously existing editor if the michael@0: // deleted editor was created at the same address. Is there a better way? michael@0: return reinterpret_cast(textContent.mReply.mContentsRoot); michael@0: } michael@0: michael@0: NSAttributedString* michael@0: IMEInputHandler::GetAttributedSubstringFromRange(NSRange& aRange, michael@0: NSRange* aActualRange) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::GetAttributedSubstringFromRange, " michael@0: "aRange={ location=%llu, length=%llu }, aActualRange=%p, Destroyed()=%s", michael@0: this, aRange.location, aRange.length, aActualRange, michael@0: TrueOrFalse(Destroyed()))); michael@0: michael@0: if (aActualRange) { michael@0: *aActualRange = NSMakeRange(NSNotFound, 0); michael@0: } michael@0: michael@0: if (Destroyed() || aRange.location == NSNotFound || aRange.length == 0) { michael@0: return nil; michael@0: } michael@0: michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: michael@0: nsAutoString str; michael@0: WidgetQueryContentEvent textContent(true, NS_QUERY_TEXT_CONTENT, mWidget); michael@0: textContent.InitForQueryTextContent(aRange.location, aRange.length); michael@0: DispatchEvent(textContent); michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::GetAttributedSubstringFromRange, " michael@0: "textContent={ mSucceeded=%s, mReply={ mString=\"%s\", mOffset=%llu } }", michael@0: this, TrueOrFalse(textContent.mSucceeded), michael@0: NS_ConvertUTF16toUTF8(textContent.mReply.mString).get(), michael@0: textContent.mReply.mOffset)); michael@0: michael@0: if (!textContent.mSucceeded) { michael@0: return nil; michael@0: } michael@0: michael@0: NSString* nsstr = nsCocoaUtils::ToNSString(textContent.mReply.mString); michael@0: NSAttributedString* result = michael@0: [[[NSAttributedString alloc] initWithString:nsstr michael@0: attributes:nil] autorelease]; michael@0: if (aActualRange) { michael@0: aActualRange->location = textContent.mReply.mOffset; michael@0: aActualRange->length = textContent.mReply.mString.Length(); michael@0: } michael@0: return result; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: bool michael@0: IMEInputHandler::HasMarkedText() michael@0: { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::HasMarkedText, " michael@0: "mMarkedRange={ location=%llu, length=%llu }", michael@0: this, mMarkedRange.location, mMarkedRange.length)); michael@0: michael@0: return (mMarkedRange.location != NSNotFound) && (mMarkedRange.length != 0); michael@0: } michael@0: michael@0: NSRange michael@0: IMEInputHandler::MarkedRange() michael@0: { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::MarkedRange, " michael@0: "mMarkedRange={ location=%llu, length=%llu }", michael@0: this, mMarkedRange.location, mMarkedRange.length)); michael@0: michael@0: if (!HasMarkedText()) { michael@0: return NSMakeRange(NSNotFound, 0); michael@0: } michael@0: return mMarkedRange; michael@0: } michael@0: michael@0: NSRange michael@0: IMEInputHandler::SelectedRange() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::SelectedRange, Destroyed()=%s, mSelectedRange={ " michael@0: "location=%llu, length=%llu }", michael@0: this, TrueOrFalse(Destroyed()), mSelectedRange.location, michael@0: mSelectedRange.length)); michael@0: michael@0: if (Destroyed()) { michael@0: return mSelectedRange; michael@0: } michael@0: michael@0: if (mSelectedRange.location != NSNotFound) { michael@0: MOZ_ASSERT(mIMEHasFocus); michael@0: return mSelectedRange; michael@0: } michael@0: michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: michael@0: WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, mWidget); michael@0: DispatchEvent(selection); michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::SelectedRange, selection={ mSucceeded=%s, " michael@0: "mReply={ mOffset=%llu, mString.Length()=%llu } }", michael@0: this, TrueOrFalse(selection.mSucceeded), selection.mReply.mOffset, michael@0: selection.mReply.mString.Length())); michael@0: michael@0: if (!selection.mSucceeded) { michael@0: return mSelectedRange; michael@0: } michael@0: michael@0: if (mIMEHasFocus) { michael@0: mSelectedRange.location = selection.mReply.mOffset; michael@0: mSelectedRange.length = selection.mReply.mString.Length(); michael@0: return mSelectedRange; michael@0: } michael@0: michael@0: return NSMakeRange(selection.mReply.mOffset, michael@0: selection.mReply.mString.Length()); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(mSelectedRange); michael@0: } michael@0: michael@0: NSRect michael@0: IMEInputHandler::FirstRectForCharacterRange(NSRange& aRange, michael@0: NSRange* aActualRange) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::FirstRectForCharacterRange, Destroyed()=%s, " michael@0: "aRange={ location=%llu, length=%llu }, aActualRange=%p }", michael@0: this, TrueOrFalse(Destroyed()), aRange.location, aRange.length, michael@0: aActualRange)); michael@0: michael@0: // XXX this returns first character rect or caret rect, it is limitation of michael@0: // now. We need more work for returns first line rect. But current michael@0: // implementation is enough for IMEs. michael@0: michael@0: NSRect rect = NSMakeRect(0.0, 0.0, 0.0, 0.0); michael@0: NSRange actualRange = NSMakeRange(NSNotFound, 0); michael@0: if (aActualRange) { michael@0: *aActualRange = actualRange; michael@0: } michael@0: if (Destroyed() || aRange.location == NSNotFound) { michael@0: return rect; michael@0: } michael@0: michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: michael@0: nsIntRect r; michael@0: bool useCaretRect = (aRange.length == 0); michael@0: if (!useCaretRect) { michael@0: WidgetQueryContentEvent charRect(true, NS_QUERY_TEXT_RECT, mWidget); michael@0: charRect.InitForQueryTextRect(aRange.location, 1); michael@0: DispatchEvent(charRect); michael@0: if (charRect.mSucceeded) { michael@0: r = charRect.mReply.mRect; michael@0: actualRange.location = charRect.mReply.mOffset; michael@0: actualRange.length = charRect.mReply.mString.Length(); michael@0: } else { michael@0: useCaretRect = true; michael@0: } michael@0: } michael@0: michael@0: if (useCaretRect) { michael@0: WidgetQueryContentEvent caretRect(true, NS_QUERY_CARET_RECT, mWidget); michael@0: caretRect.InitForQueryCaretRect(aRange.location); michael@0: DispatchEvent(caretRect); michael@0: if (!caretRect.mSucceeded) { michael@0: return rect; michael@0: } michael@0: r = caretRect.mReply.mRect; michael@0: r.width = 0; michael@0: actualRange.location = caretRect.mReply.mOffset; michael@0: actualRange.length = 0; michael@0: } michael@0: michael@0: nsIWidget* rootWidget = mWidget->GetTopLevelWidget(); michael@0: NSWindow* rootWindow = michael@0: static_cast(rootWidget->GetNativeData(NS_NATIVE_WINDOW)); michael@0: NSView* rootView = michael@0: static_cast(rootWidget->GetNativeData(NS_NATIVE_WIDGET)); michael@0: if (!rootWindow || !rootView) { michael@0: return rect; michael@0: } michael@0: rect = nsCocoaUtils::DevPixelsToCocoaPoints(r, mWidget->BackingScaleFactor()); michael@0: rect = [rootView convertRect:rect toView:nil]; michael@0: rect.origin = [rootWindow convertBaseToScreen:rect.origin]; michael@0: michael@0: if (aActualRange) { michael@0: *aActualRange = actualRange; michael@0: } michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::FirstRectForCharacterRange, " michael@0: "useCaretRect=%s rect={ x=%f, y=%f, width=%f, height=%f }, " michael@0: "actualRange={ location=%llu, length=%llu }", michael@0: this, TrueOrFalse(useCaretRect), rect.origin.x, rect.origin.y, michael@0: rect.size.width, rect.size.height, actualRange.location, michael@0: actualRange.length)); michael@0: michael@0: return rect; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRect(0.0, 0.0, 0.0, 0.0)); michael@0: } michael@0: michael@0: NSUInteger michael@0: IMEInputHandler::CharacterIndexForPoint(NSPoint& aPoint) michael@0: { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::CharacterIndexForPoint, aPoint={ x=%f, y=%f }", michael@0: this, aPoint.x, aPoint.y)); michael@0: michael@0: //nsRefPtr kungFuDeathGrip(this); michael@0: michael@0: // To implement this, we'd have to grovel in text frames looking at text michael@0: // offsets. michael@0: return 0; michael@0: } michael@0: michael@0: NSArray* michael@0: IMEInputHandler::GetValidAttributesForMarkedText() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::GetValidAttributesForMarkedText", this)); michael@0: michael@0: //nsRefPtr kungFuDeathGrip(this); michael@0: michael@0: //return [NSArray arrayWithObjects:NSUnderlineStyleAttributeName, michael@0: // NSMarkedClauseSegmentAttributeName, michael@0: // NSTextInputReplacementRangeAttributeName, michael@0: // nil]; michael@0: // empty array; we don't support any attributes right now michael@0: return [NSArray array]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: michael@0: #pragma mark - michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * michael@0: * IMEInputHandler implementation #2 michael@0: * michael@0: ******************************************************************************/ michael@0: michael@0: IMEInputHandler::IMEInputHandler(nsChildView* aWidget, michael@0: NSView *aNativeView) : michael@0: PluginTextInputHandler(aWidget, aNativeView), michael@0: mPendingMethods(0), mIMECompositionString(nullptr), michael@0: mIsIMEComposing(false), mIsIMEEnabled(true), michael@0: mIsASCIICapableOnly(false), mIgnoreIMECommit(false), michael@0: mIsInFocusProcessing(false), mIMEHasFocus(false) michael@0: { michael@0: InitStaticMembers(); michael@0: michael@0: mMarkedRange.location = NSNotFound; michael@0: mMarkedRange.length = 0; michael@0: mSelectedRange.location = NSNotFound; michael@0: mSelectedRange.length = 0; michael@0: } michael@0: michael@0: IMEInputHandler::~IMEInputHandler() michael@0: { michael@0: if (mTimer) { michael@0: mTimer->Cancel(); michael@0: mTimer = nullptr; michael@0: } michael@0: if (sFocusedIMEHandler == this) { michael@0: sFocusedIMEHandler = nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: IMEInputHandler::OnFocusChangeInGecko(bool aFocus) michael@0: { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::OnFocusChangeInGecko, aFocus=%s, Destroyed()=%s, " michael@0: "sFocusedIMEHandler=%p", michael@0: this, TrueOrFalse(aFocus), TrueOrFalse(Destroyed()), sFocusedIMEHandler)); michael@0: michael@0: mSelectedRange.location = NSNotFound; // Marking dirty michael@0: mIMEHasFocus = aFocus; michael@0: michael@0: // This is called when the native focus is changed and when the native focus michael@0: // isn't changed but the focus is changed in Gecko. michael@0: if (!aFocus) { michael@0: if (sFocusedIMEHandler == this) michael@0: sFocusedIMEHandler = nullptr; michael@0: return; michael@0: } michael@0: michael@0: sFocusedIMEHandler = this; michael@0: mIsInFocusProcessing = true; michael@0: michael@0: // We need to notify IME of focus change in Gecko as native focus change michael@0: // because the window level of the focused element in Gecko may be changed. michael@0: mPendingMethods |= kNotifyIMEOfFocusChangeInGecko; michael@0: ResetTimer(); michael@0: } michael@0: michael@0: bool michael@0: IMEInputHandler::OnDestroyWidget(nsChildView* aDestroyingWidget) michael@0: { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::OnDestroyWidget, aDestroyingWidget=%p, " michael@0: "sFocusedIMEHandler=%p, IsIMEComposing()=%s", michael@0: this, aDestroyingWidget, sFocusedIMEHandler, michael@0: TrueOrFalse(IsIMEComposing()))); michael@0: michael@0: // If we're not focused, the focused IMEInputHandler may have been michael@0: // created by another widget/nsChildView. michael@0: if (sFocusedIMEHandler && sFocusedIMEHandler != this) { michael@0: sFocusedIMEHandler->OnDestroyWidget(aDestroyingWidget); michael@0: } michael@0: michael@0: if (!PluginTextInputHandler::OnDestroyWidget(aDestroyingWidget)) { michael@0: return false; michael@0: } michael@0: michael@0: if (IsIMEComposing()) { michael@0: // If our view is in the composition, we should clean up it. michael@0: CancelIMEComposition(); michael@0: OnEndIMEComposition(); michael@0: } michael@0: michael@0: mSelectedRange.location = NSNotFound; // Marking dirty michael@0: mIMEHasFocus = false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: IMEInputHandler::OnStartIMEComposition() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::OnStartIMEComposition, mView=%p, mWidget=%p" michael@0: "inputContext=%p, mIsIMEComposing=%s", michael@0: this, mView, mWidget, mView ? [mView inputContext] : nullptr, michael@0: TrueOrFalse(mIsIMEComposing))); michael@0: michael@0: NS_ASSERTION(!mIsIMEComposing, "There is a composition already"); michael@0: mIsIMEComposing = true; michael@0: michael@0: mLastDispatchedCompositionString.Truncate(); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void michael@0: IMEInputHandler::OnUpdateIMEComposition(NSString* aIMECompositionString) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::OnUpdateIMEComposition, mView=%p, mWidget=%p, " michael@0: "inputContext=%p, mIsIMEComposing=%s, aIMECompositionString=\"%s\"", michael@0: this, mView, mWidget, mView ? [mView inputContext] : nullptr, michael@0: TrueOrFalse(mIsIMEComposing), GetCharacters(aIMECompositionString))); michael@0: michael@0: NS_ASSERTION(mIsIMEComposing, "We're not in composition"); michael@0: michael@0: if (mIMECompositionString) michael@0: [mIMECompositionString release]; michael@0: mIMECompositionString = [aIMECompositionString retain]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void michael@0: IMEInputHandler::OnEndIMEComposition() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::OnEndIMEComposition, mView=%p, mWidget=%p, " michael@0: "inputContext=%p, mIsIMEComposing=%s", michael@0: this, mView, mWidget, mView ? [mView inputContext] : nullptr, michael@0: TrueOrFalse(mIsIMEComposing))); michael@0: michael@0: NS_ASSERTION(mIsIMEComposing, "We're not in composition"); michael@0: michael@0: mIsIMEComposing = false; michael@0: michael@0: if (mIMECompositionString) { michael@0: [mIMECompositionString release]; michael@0: mIMECompositionString = nullptr; michael@0: } michael@0: michael@0: mLastDispatchedCompositionString.Truncate(); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void michael@0: IMEInputHandler::SendCommittedText(NSString *aString) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::SendCommittedText, mView=%p, mWidget=%p, " michael@0: "inputContext=%p, mIsIMEComposing=%s", michael@0: this, mView, mWidget, mView ? [mView inputContext] : nullptr, michael@0: TrueOrFalse(mIsIMEComposing), mWidget)); michael@0: michael@0: NS_ENSURE_TRUE(mWidget, ); michael@0: // XXX We should send the string without mView. michael@0: if (!mView) { michael@0: return; michael@0: } michael@0: michael@0: NSAttributedString* attrStr = michael@0: [[NSAttributedString alloc] initWithString:aString]; michael@0: [mView insertText:attrStr]; michael@0: [attrStr release]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void michael@0: IMEInputHandler::KillIMEComposition() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::KillIMEComposition, mView=%p, mWidget=%p, " michael@0: "inputContext=%p, mIsIMEComposing=%s, " michael@0: "Destroyed()=%s, IsFocused()=%s", michael@0: this, mView, mWidget, mView ? [mView inputContext] : nullptr, michael@0: TrueOrFalse(mIsIMEComposing), TrueOrFalse(Destroyed()), michael@0: TrueOrFalse(IsFocused()))); michael@0: michael@0: if (Destroyed()) { michael@0: return; michael@0: } michael@0: michael@0: if (IsFocused()) { michael@0: NS_ENSURE_TRUE_VOID(mView); michael@0: NSTextInputContext* inputContext = [mView inputContext]; michael@0: NS_ENSURE_TRUE_VOID(inputContext); michael@0: [inputContext discardMarkedText]; michael@0: return; michael@0: } michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::KillIMEComposition, Pending...", this)); michael@0: michael@0: // Commit the composition internally. michael@0: SendCommittedText(mIMECompositionString); michael@0: NS_ASSERTION(!mIsIMEComposing, "We're still in a composition"); michael@0: // The pending method will be fired by the next focus event. michael@0: mPendingMethods |= kDiscardIMEComposition; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void michael@0: IMEInputHandler::CommitIMEComposition() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!IsIMEComposing()) michael@0: return; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::CommitIMEComposition, mIMECompositionString=%s", michael@0: this, GetCharacters(mIMECompositionString))); michael@0: michael@0: KillIMEComposition(); michael@0: michael@0: if (!IsIMEComposing()) michael@0: return; michael@0: michael@0: // If the composition is still there, KillIMEComposition only kills the michael@0: // composition in TSM. We also need to finish the our composition too. michael@0: SendCommittedText(mIMECompositionString); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void michael@0: IMEInputHandler::CancelIMEComposition() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!IsIMEComposing()) michael@0: return; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::CancelIMEComposition, mIMECompositionString=%s", michael@0: this, GetCharacters(mIMECompositionString))); michael@0: michael@0: // For canceling the current composing, we need to ignore the param of michael@0: // insertText. But this code is ugly... michael@0: mIgnoreIMECommit = true; michael@0: KillIMEComposition(); michael@0: mIgnoreIMECommit = false; michael@0: michael@0: if (!IsIMEComposing()) michael@0: return; michael@0: michael@0: // If the composition is still there, KillIMEComposition only kills the michael@0: // composition in TSM. We also need to kill the our composition too. michael@0: SendCommittedText(@""); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: bool michael@0: IMEInputHandler::IsFocused() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: NS_ENSURE_TRUE(!Destroyed(), false); michael@0: NSWindow* window = [mView window]; michael@0: NS_ENSURE_TRUE(window, false); michael@0: return [window firstResponder] == mView && michael@0: [window isKeyWindow] && michael@0: [[NSApplication sharedApplication] isActive]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); michael@0: } michael@0: michael@0: bool michael@0: IMEInputHandler::IsIMEOpened() michael@0: { michael@0: TISInputSourceWrapper tis; michael@0: tis.InitByCurrentInputSource(); michael@0: return tis.IsOpenedIMEMode(); michael@0: } michael@0: michael@0: void michael@0: IMEInputHandler::SetASCIICapableOnly(bool aASCIICapableOnly) michael@0: { michael@0: if (aASCIICapableOnly == mIsASCIICapableOnly) michael@0: return; michael@0: michael@0: CommitIMEComposition(); michael@0: mIsASCIICapableOnly = aASCIICapableOnly; michael@0: SyncASCIICapableOnly(); michael@0: } michael@0: michael@0: void michael@0: IMEInputHandler::EnableIME(bool aEnableIME) michael@0: { michael@0: if (aEnableIME == mIsIMEEnabled) michael@0: return; michael@0: michael@0: CommitIMEComposition(); michael@0: mIsIMEEnabled = aEnableIME; michael@0: } michael@0: michael@0: void michael@0: IMEInputHandler::SetIMEOpenState(bool aOpenIME) michael@0: { michael@0: if (!IsFocused() || IsIMEOpened() == aOpenIME) michael@0: return; michael@0: michael@0: if (!aOpenIME) { michael@0: TISInputSourceWrapper tis; michael@0: tis.InitByCurrentASCIICapableInputSource(); michael@0: tis.Select(); michael@0: return; michael@0: } michael@0: michael@0: // If we know the latest IME opened mode, we should select it. michael@0: if (sLatestIMEOpenedModeInputSourceID) { michael@0: TISInputSourceWrapper tis; michael@0: tis.InitByInputSourceID(sLatestIMEOpenedModeInputSourceID); michael@0: tis.Select(); michael@0: return; michael@0: } michael@0: michael@0: // XXX If the current input source is a mode of IME, we should turn on it, michael@0: // but we haven't found such way... michael@0: michael@0: // Finally, we should refer the system locale but this is a little expensive, michael@0: // we shouldn't retry this (if it was succeeded, we already set michael@0: // sLatestIMEOpenedModeInputSourceID at that time). michael@0: static bool sIsPrefferredIMESearched = false; michael@0: if (sIsPrefferredIMESearched) michael@0: return; michael@0: sIsPrefferredIMESearched = true; michael@0: OpenSystemPreferredLanguageIME(); michael@0: } michael@0: michael@0: void michael@0: IMEInputHandler::OpenSystemPreferredLanguageIME() michael@0: { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::OpenSystemPreferredLanguageIME", this)); michael@0: michael@0: CFArrayRef langList = ::CFLocaleCopyPreferredLanguages(); michael@0: if (!langList) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, langList is NULL", michael@0: this)); michael@0: return; michael@0: } michael@0: CFIndex count = ::CFArrayGetCount(langList); michael@0: for (CFIndex i = 0; i < count; i++) { michael@0: CFLocaleRef locale = michael@0: ::CFLocaleCreate(kCFAllocatorDefault, michael@0: static_cast(::CFArrayGetValueAtIndex(langList, i))); michael@0: if (!locale) { michael@0: continue; michael@0: } michael@0: michael@0: bool changed = false; michael@0: CFStringRef lang = static_cast( michael@0: ::CFLocaleGetValue(locale, kCFLocaleLanguageCode)); michael@0: NS_ASSERTION(lang, "lang is null"); michael@0: if (lang) { michael@0: TISInputSourceWrapper tis; michael@0: tis.InitByLanguage(lang); michael@0: if (tis.IsOpenedIMEMode()) { michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) { michael@0: CFStringRef foundTIS; michael@0: tis.GetInputSourceID(foundTIS); michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, " michael@0: "foundTIS=%s, lang=%s", michael@0: this, GetCharacters(foundTIS), GetCharacters(lang))); michael@0: } michael@0: #endif // #ifdef PR_LOGGING michael@0: tis.Select(); michael@0: changed = true; michael@0: } michael@0: } michael@0: ::CFRelease(locale); michael@0: if (changed) { michael@0: break; michael@0: } michael@0: } michael@0: ::CFRelease(langList); michael@0: } michael@0: michael@0: michael@0: #pragma mark - michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * michael@0: * PluginTextInputHandler implementation michael@0: * michael@0: ******************************************************************************/ michael@0: michael@0: PluginTextInputHandler::PluginTextInputHandler(nsChildView* aWidget, michael@0: NSView *aNativeView) : michael@0: TextInputHandlerBase(aWidget, aNativeView), michael@0: mIgnoreNextKeyUpEvent(false), michael@0: #ifndef __LP64__ michael@0: mPluginTSMDoc(0), mPluginTSMInComposition(false), michael@0: #endif // #ifndef __LP64__ michael@0: mPluginComplexTextInputRequested(false) michael@0: { michael@0: } michael@0: michael@0: PluginTextInputHandler::~PluginTextInputHandler() michael@0: { michael@0: #ifndef __LP64__ michael@0: if (mPluginTSMDoc) { michael@0: ::DeleteTSMDocument(mPluginTSMDoc); michael@0: } michael@0: #endif // #ifndef __LP64__ michael@0: } michael@0: michael@0: /* static */ void michael@0: PluginTextInputHandler::ConvertCocoaKeyEventToNPCocoaEvent( michael@0: NSEvent* aCocoaEvent, michael@0: NPCocoaEvent& aPluginEvent) michael@0: { michael@0: nsCocoaUtils::InitNPCocoaEvent(&aPluginEvent); michael@0: NSEventType nativeType = [aCocoaEvent type]; michael@0: switch (nativeType) { michael@0: case NSKeyDown: michael@0: aPluginEvent.type = NPCocoaEventKeyDown; michael@0: break; michael@0: case NSKeyUp: michael@0: aPluginEvent.type = NPCocoaEventKeyUp; michael@0: break; michael@0: case NSFlagsChanged: michael@0: aPluginEvent.type = NPCocoaEventFlagsChanged; michael@0: break; michael@0: default: michael@0: NS_WARNING("Asked to convert key event of unknown type to Cocoa plugin event!"); michael@0: } michael@0: aPluginEvent.data.key.modifierFlags = [aCocoaEvent modifierFlags]; michael@0: aPluginEvent.data.key.keyCode = [aCocoaEvent keyCode]; michael@0: // don't try to access character data for flags changed events, michael@0: // it will raise an exception michael@0: if (nativeType != NSFlagsChanged) { michael@0: aPluginEvent.data.key.characters = (NPNSString*)[aCocoaEvent characters]; michael@0: aPluginEvent.data.key.charactersIgnoringModifiers = michael@0: (NPNSString*)[aCocoaEvent charactersIgnoringModifiers]; michael@0: aPluginEvent.data.key.isARepeat = [aCocoaEvent isARepeat]; michael@0: } michael@0: } michael@0: michael@0: #ifndef __LP64__ michael@0: michael@0: EventHandlerRef PluginTextInputHandler::sPluginKeyEventsHandler = NULL; michael@0: michael@0: /* static */ void michael@0: PluginTextInputHandler::InstallPluginKeyEventsHandler() michael@0: { michael@0: if (sPluginKeyEventsHandler) { michael@0: return; michael@0: } michael@0: static const EventTypeSpec sTSMEvents[] = michael@0: { { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent } }; michael@0: ::InstallEventHandler(::GetEventDispatcherTarget(), michael@0: ::NewEventHandlerUPP(PluginKeyEventsHandler), michael@0: GetEventTypeCount(sTSMEvents), michael@0: sTSMEvents, michael@0: NULL, michael@0: &sPluginKeyEventsHandler); michael@0: } michael@0: michael@0: /* static */ void michael@0: PluginTextInputHandler::RemovePluginKeyEventsHandler() michael@0: { michael@0: if (!sPluginKeyEventsHandler) { michael@0: return; michael@0: } michael@0: ::RemoveEventHandler(sPluginKeyEventsHandler); michael@0: sPluginKeyEventsHandler = NULL; michael@0: } michael@0: michael@0: /* static */ void michael@0: PluginTextInputHandler::SwizzleMethods() michael@0: { michael@0: Class IMKInputSessionClass = ::NSClassFromString(@"IMKInputSession"); michael@0: nsToolkit::SwizzleMethods(IMKInputSessionClass, @selector(handleEvent:), michael@0: @selector(PluginTextInputHandler_IMKInputSession_handleEvent:)); michael@0: nsToolkit::SwizzleMethods(IMKInputSessionClass, @selector(commitComposition), michael@0: @selector(PluginTextInputHandler_IMKInputSession_commitComposition)); michael@0: nsToolkit::SwizzleMethods(IMKInputSessionClass, @selector(finishSession), michael@0: @selector(PluginTextInputHandler_IMKInputSession_finishSession)); michael@0: } michael@0: michael@0: /* static */ OSStatus michael@0: PluginTextInputHandler::PluginKeyEventsHandler(EventHandlerCallRef aHandlerRef, michael@0: EventRef aEvent, michael@0: void *aUserData) michael@0: { michael@0: nsAutoreleasePool localPool; michael@0: michael@0: TSMDocumentID activeDoc = ::TSMGetActiveDocument(); michael@0: NS_ENSURE_TRUE(activeDoc, eventNotHandledErr); michael@0: michael@0: ChildView *target = nil; michael@0: OSStatus status = ::TSMGetDocumentProperty(activeDoc, michael@0: kFocusedChildViewTSMDocPropertyTag, michael@0: sizeof(ChildView *), nil, &target); michael@0: NS_ENSURE_TRUE(status == noErr, eventNotHandledErr); michael@0: NS_ENSURE_TRUE(target, eventNotHandledErr); michael@0: michael@0: EventRef keyEvent = NULL; michael@0: status = ::GetEventParameter(aEvent, kEventParamTextInputSendKeyboardEvent, michael@0: typeEventRef, NULL, sizeof(EventRef), NULL, michael@0: &keyEvent); michael@0: NS_ENSURE_TRUE(status == noErr, eventNotHandledErr); michael@0: NS_ENSURE_TRUE(keyEvent, eventNotHandledErr); michael@0: michael@0: nsIWidget* widget = [target widget]; michael@0: NS_ENSURE_TRUE(widget, eventNotHandledErr); michael@0: TextInputHandler* handler = michael@0: static_cast(widget)->GetTextInputHandler(); michael@0: NS_ENSURE_TRUE(handler, eventNotHandledErr); michael@0: handler->HandleCarbonPluginKeyEvent(keyEvent); michael@0: michael@0: return noErr; michael@0: } michael@0: michael@0: // Called from PluginKeyEventsHandler() (a handler for Carbon TSM events) to michael@0: // process a Carbon key event for the currently focused plugin. Both Unicode michael@0: // characters and "Mac encoding characters" (in the MBCS or "multibyte michael@0: // character system") are (or should be) available from aKeyEvent, but here we michael@0: // use the MCBS characters. This is how the WebKit does things, and seems to michael@0: // be what plugins expect. michael@0: void michael@0: PluginTextInputHandler::HandleCarbonPluginKeyEvent(EventRef aKeyEvent) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (Destroyed()) { michael@0: return; michael@0: } michael@0: michael@0: NS_ASSERTION(mView, "mView must not be NULL"); michael@0: michael@0: nsRefPtr kungFuDeathGrip(mWidget); michael@0: michael@0: if ([mView pluginEventModel] == NPEventModelCocoa) { michael@0: UInt32 size; michael@0: OSStatus status = michael@0: ::GetEventParameter(aKeyEvent, kEventParamKeyUnicodes, typeUnicodeText, michael@0: NULL, 0, &size, NULL); michael@0: NS_ENSURE_TRUE(status == noErr, ); michael@0: michael@0: UniChar* chars = (UniChar*)malloc(size); michael@0: NS_ENSURE_TRUE(chars, ); michael@0: michael@0: status = ::GetEventParameter(aKeyEvent, kEventParamKeyUnicodes, michael@0: typeUnicodeText, NULL, size, NULL, chars); michael@0: if (status != noErr) { michael@0: free(chars); michael@0: return; michael@0: } michael@0: michael@0: CFStringRef text = michael@0: ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, chars, michael@0: (size / sizeof(UniChar)), michael@0: kCFAllocatorNull); michael@0: if (!text) { michael@0: free(chars); michael@0: return; michael@0: } michael@0: michael@0: NPCocoaEvent cocoaTextEvent; michael@0: nsCocoaUtils::InitNPCocoaEvent(&cocoaTextEvent); michael@0: cocoaTextEvent.type = NPCocoaEventTextInput; michael@0: cocoaTextEvent.data.text.text = (NPNSString*)text; michael@0: michael@0: WidgetPluginEvent pluginEvent(true, NS_PLUGIN_INPUT_EVENT, mWidget); michael@0: nsCocoaUtils::InitPluginEvent(pluginEvent, cocoaTextEvent); michael@0: DispatchEvent(pluginEvent); michael@0: michael@0: ::CFRelease(text); michael@0: free(chars); michael@0: michael@0: return; michael@0: } michael@0: michael@0: UInt32 numCharCodes; michael@0: OSStatus status = ::GetEventParameter(aKeyEvent, kEventParamKeyMacCharCodes, michael@0: typeChar, NULL, 0, &numCharCodes, NULL); michael@0: NS_ENSURE_TRUE(status == noErr, ); michael@0: michael@0: nsAutoTArray charCodes; michael@0: charCodes.SetLength(numCharCodes); michael@0: status = ::GetEventParameter(aKeyEvent, kEventParamKeyMacCharCodes, michael@0: typeChar, NULL, numCharCodes, NULL, michael@0: charCodes.Elements()); michael@0: NS_ENSURE_TRUE(status == noErr, ); michael@0: michael@0: UInt32 modifiers; michael@0: status = ::GetEventParameter(aKeyEvent, kEventParamKeyModifiers, michael@0: typeUInt32, NULL, sizeof(modifiers), NULL, michael@0: &modifiers); michael@0: NS_ENSURE_TRUE(status == noErr, ); michael@0: michael@0: NSUInteger cocoaModifiers = 0; michael@0: if (modifiers & shiftKey) { michael@0: cocoaModifiers |= NSShiftKeyMask; michael@0: } michael@0: if (modifiers & controlKey) { michael@0: cocoaModifiers |= NSControlKeyMask; michael@0: } michael@0: if (modifiers & optionKey) { michael@0: cocoaModifiers |= NSAlternateKeyMask; michael@0: } michael@0: if (modifiers & cmdKey) { // Should never happen michael@0: cocoaModifiers |= NSCommandKeyMask; michael@0: } michael@0: michael@0: UInt32 macKeyCode; michael@0: status = ::GetEventParameter(aKeyEvent, kEventParamKeyCode, michael@0: typeUInt32, NULL, sizeof(macKeyCode), NULL, michael@0: &macKeyCode); michael@0: NS_ENSURE_TRUE(status == noErr, ); michael@0: michael@0: TISInputSourceWrapper& currentInputSource = michael@0: TISInputSourceWrapper::CurrentInputSource(); michael@0: michael@0: EventRef cloneEvent = ::CopyEvent(aKeyEvent); michael@0: for (uint32_t i = 0; i < numCharCodes; ++i) { michael@0: status = ::SetEventParameter(cloneEvent, kEventParamKeyMacCharCodes, michael@0: typeChar, 1, charCodes.Elements() + i); michael@0: NS_ENSURE_TRUE(status == noErr, ); michael@0: michael@0: EventRecord eventRec; michael@0: if (::ConvertEventRefToEventRecord(cloneEvent, &eventRec)) { michael@0: WidgetKeyboardEvent keydownEvent(true, NS_KEY_DOWN, mWidget); michael@0: nsCocoaUtils::InitInputEvent(keydownEvent, cocoaModifiers); michael@0: michael@0: uint32_t keyCode = michael@0: currentInputSource.ComputeGeckoKeyCode(macKeyCode, ::LMGetKbdType(), michael@0: keydownEvent.IsMeta()); michael@0: uint32_t charCode(charCodes.ElementAt(i)); michael@0: michael@0: keydownEvent.time = PR_IntervalNow(); michael@0: keydownEvent.pluginEvent = &eventRec; michael@0: if (IsSpecialGeckoKey(macKeyCode)) { michael@0: keydownEvent.keyCode = keyCode; michael@0: } else { michael@0: // XXX This is wrong. charCode must be 0 for keydown event. michael@0: keydownEvent.charCode = charCode; michael@0: keydownEvent.isChar = true; michael@0: } michael@0: DispatchEvent(keydownEvent); michael@0: if (Destroyed()) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: ::ReleaseEvent(cloneEvent); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void michael@0: PluginTextInputHandler::ActivatePluginTSMDocument() michael@0: { michael@0: if (!mPluginTSMDoc) { michael@0: // Create a TSM document that supports both non-Unicode and Unicode input. michael@0: // Though ProcessPluginKeyEvent() only sends Mac char codes to michael@0: // the plugin, this makes the input window behave better when it contains michael@0: // more than one kind of input (say Hiragana and Romaji). This is what michael@0: // the OS does when it creates a TSM document for use by an michael@0: // NSTSMInputContext class. michael@0: InterfaceTypeList supportedServices; michael@0: supportedServices[0] = kTextServiceDocumentInterfaceType; michael@0: supportedServices[1] = kUnicodeDocumentInterfaceType; michael@0: ::NewTSMDocument(2, supportedServices, &mPluginTSMDoc, 0); michael@0: // We'll need to use the "input window". michael@0: ::UseInputWindow(mPluginTSMDoc, YES); michael@0: ::ActivateTSMDocument(mPluginTSMDoc); michael@0: } else if (::TSMGetActiveDocument() != mPluginTSMDoc) { michael@0: ::ActivateTSMDocument(mPluginTSMDoc); michael@0: } michael@0: } michael@0: michael@0: #endif // #ifndef __LP64__ michael@0: michael@0: void michael@0: PluginTextInputHandler::HandleKeyDownEventForPlugin(NSEvent* aNativeKeyEvent) michael@0: { michael@0: if (Destroyed()) { michael@0: return; michael@0: } michael@0: michael@0: NS_ASSERTION(mView, "mView must not be NULL"); michael@0: michael@0: #ifdef __LP64__ michael@0: michael@0: if ([mView pluginEventModel] != NPEventModelCocoa) { michael@0: return; michael@0: } michael@0: michael@0: ComplexTextInputPanel* ctiPanel = michael@0: [ComplexTextInputPanel sharedComplexTextInputPanel]; michael@0: [ctiPanel adjustTo:mView]; michael@0: michael@0: // If a composition is in progress then simply let the input panel continue michael@0: // it. michael@0: if (IsInPluginComposition()) { michael@0: // Don't send key up events for key downs associated with compositions. michael@0: mIgnoreNextKeyUpEvent = true; michael@0: michael@0: NSString* textString = nil; michael@0: [ctiPanel interpretKeyEvent:aNativeKeyEvent string:&textString]; michael@0: if (textString) { michael@0: DispatchCocoaNPAPITextEvent(textString); michael@0: } michael@0: michael@0: return; michael@0: } michael@0: michael@0: // Reset complex text input request flag. michael@0: mPluginComplexTextInputRequested = false; michael@0: michael@0: // Send key down event to the plugin. michael@0: WidgetPluginEvent pluginEvent(true, NS_PLUGIN_INPUT_EVENT, mWidget); michael@0: NPCocoaEvent cocoaEvent; michael@0: ConvertCocoaKeyEventToNPCocoaEvent(aNativeKeyEvent, cocoaEvent); michael@0: nsCocoaUtils::InitPluginEvent(pluginEvent, cocoaEvent); michael@0: DispatchEvent(pluginEvent); michael@0: if (Destroyed()) { michael@0: return; michael@0: } michael@0: michael@0: // Start complex text composition if requested. michael@0: if (mPluginComplexTextInputRequested) { michael@0: // Don't send key up events for key downs associated with compositions. michael@0: mIgnoreNextKeyUpEvent = true; michael@0: michael@0: NSString* textString = nil; michael@0: [ctiPanel interpretKeyEvent:aNativeKeyEvent string:&textString]; michael@0: if (textString) { michael@0: DispatchCocoaNPAPITextEvent(textString); michael@0: } michael@0: } michael@0: michael@0: #else // #ifdef __LP64__ michael@0: michael@0: bool wasInComposition = false; michael@0: if ([mView pluginEventModel] == NPEventModelCocoa) { michael@0: if (IsInPluginComposition()) { michael@0: wasInComposition = true; michael@0: michael@0: // Don't send key up events for key downs associated with compositions. michael@0: mIgnoreNextKeyUpEvent = true; michael@0: } else { michael@0: // Reset complex text input request flag. michael@0: mPluginComplexTextInputRequested = false; michael@0: michael@0: // Send key down event to the plugin. michael@0: WidgetPluginEvent pluginEvent(true, NS_PLUGIN_INPUT_EVENT, mWidget); michael@0: NPCocoaEvent cocoaEvent; michael@0: ConvertCocoaKeyEventToNPCocoaEvent(aNativeKeyEvent, cocoaEvent); michael@0: nsCocoaUtils::InitPluginEvent(pluginEvent, cocoaEvent); michael@0: DispatchEvent(pluginEvent); michael@0: if (Destroyed()) { michael@0: return; michael@0: } michael@0: michael@0: // Only continue if plugin wants complex text input. michael@0: if (!mPluginComplexTextInputRequested) { michael@0: return; michael@0: } michael@0: michael@0: // Don't send key up events for key downs associated with compositions. michael@0: mIgnoreNextKeyUpEvent = true; michael@0: } michael@0: michael@0: // Don't send complex text input to a plugin in Cocoa event mode if michael@0: // either the Control key or the Command key is pressed -- even if the michael@0: // plugin has requested it, or we are already in IME composition. This michael@0: // conforms to our behavior in 64-bit mode and fixes bug 619217. michael@0: NSUInteger modifierFlags = [aNativeKeyEvent modifierFlags]; michael@0: if ((modifierFlags & NSControlKeyMask) || michael@0: (modifierFlags & NSCommandKeyMask)) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // This will take care of all Carbon plugin events and also send Cocoa plugin michael@0: // text events when NSInputContext is not available (ifndef NP_NO_CARBON). michael@0: ActivatePluginTSMDocument(); michael@0: michael@0: // We use the active TSM document to pass a pointer to ourselves (the michael@0: // currently focused ChildView) to PluginKeyEventsHandler(). Because this michael@0: // pointer is weak, we should retain and release ourselves around the call michael@0: // to TSMProcessRawKeyEvent(). michael@0: nsAutoRetainCocoaObject kungFuDeathGrip(mView); michael@0: ::TSMSetDocumentProperty(mPluginTSMDoc, michael@0: kFocusedChildViewTSMDocPropertyTag, michael@0: sizeof(ChildView *), &mView); michael@0: ::TSMProcessRawKeyEvent([aNativeKeyEvent _eventRef]); michael@0: ::TSMRemoveDocumentProperty(mPluginTSMDoc, michael@0: kFocusedChildViewTSMDocPropertyTag); michael@0: michael@0: #endif // #ifdef __LP64__ else michael@0: } michael@0: michael@0: void michael@0: PluginTextInputHandler::HandleKeyUpEventForPlugin(NSEvent* aNativeKeyEvent) michael@0: { michael@0: if (mIgnoreNextKeyUpEvent) { michael@0: mIgnoreNextKeyUpEvent = false; michael@0: return; michael@0: } michael@0: michael@0: if (Destroyed()) { michael@0: return; michael@0: } michael@0: michael@0: NS_ASSERTION(mView, "mView must not be NULL"); michael@0: michael@0: NPEventModel eventModel = [mView pluginEventModel]; michael@0: if (eventModel == NPEventModelCocoa) { michael@0: // Don't send key up events to Cocoa plugins during composition. michael@0: if (IsInPluginComposition()) { michael@0: return; michael@0: } michael@0: michael@0: WidgetKeyboardEvent keyupEvent(true, NS_KEY_UP, mWidget); michael@0: InitKeyEvent(aNativeKeyEvent, keyupEvent); michael@0: NPCocoaEvent pluginEvent; michael@0: ConvertCocoaKeyEventToNPCocoaEvent(aNativeKeyEvent, pluginEvent); michael@0: keyupEvent.pluginEvent = &pluginEvent; michael@0: DispatchEvent(keyupEvent); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: PluginTextInputHandler::IsInPluginComposition() michael@0: { michael@0: return michael@0: #ifdef __LP64__ michael@0: [[ComplexTextInputPanel sharedComplexTextInputPanel] inComposition] != NO; michael@0: #else // #ifdef __LP64__ michael@0: mPluginTSMInComposition; michael@0: #endif // #ifdef __LP64__ else michael@0: } michael@0: michael@0: bool michael@0: PluginTextInputHandler::DispatchCocoaNPAPITextEvent(NSString* aString) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: NPCocoaEvent cocoaTextEvent; michael@0: nsCocoaUtils::InitNPCocoaEvent(&cocoaTextEvent); michael@0: cocoaTextEvent.type = NPCocoaEventTextInput; michael@0: cocoaTextEvent.data.text.text = (NPNSString*)aString; michael@0: michael@0: WidgetPluginEvent pluginEvent(true, NS_PLUGIN_INPUT_EVENT, mWidget); michael@0: nsCocoaUtils::InitPluginEvent(pluginEvent, cocoaTextEvent); michael@0: return DispatchEvent(pluginEvent); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); michael@0: } michael@0: michael@0: michael@0: #pragma mark - michael@0: michael@0: michael@0: #ifndef __LP64__ michael@0: michael@0: /****************************************************************************** michael@0: * michael@0: * PluginTextInputHandler_IMKInputSession_* implementation michael@0: * michael@0: ******************************************************************************/ michael@0: michael@0: // IMKInputSession is an undocumented class in the HIToolbox framework. It's michael@0: // present on both Leopard and SnowLeopard, and is used at a low level to michael@0: // process IME input regardless of which high-level API is used (Text Services michael@0: // Manager or Cocoa). It works the same way in both 32-bit and 64-bit code. michael@0: @interface NSObject (IMKInputSessionMethodSwizzling) michael@0: - (BOOL)PluginTextInputHandler_IMKInputSession_handleEvent:(EventRef)theEvent; michael@0: - (void)PluginTextInputHandler_IMKInputSession_commitComposition; michael@0: - (void)PluginTextInputHandler_IMKInputSession_finishSession; michael@0: @end michael@0: michael@0: @implementation NSObject (IMKInputSessionMethodSwizzling) michael@0: michael@0: - (BOOL)PluginTextInputHandler_IMKInputSession_handleEvent:(EventRef)theEvent michael@0: { michael@0: [self retain]; michael@0: BOOL retval = michael@0: [self PluginTextInputHandler_IMKInputSession_handleEvent:theEvent]; michael@0: NSUInteger retainCount = [self retainCount]; michael@0: [self release]; michael@0: // Return without doing anything if we've been deleted. michael@0: if (retainCount == 1) { michael@0: return retval; michael@0: } michael@0: michael@0: NSWindow *mainWindow = [NSApp mainWindow]; michael@0: NSResponder *firstResponder = [mainWindow firstResponder]; michael@0: if (![firstResponder isKindOfClass:[ChildView class]]) { michael@0: return retval; michael@0: } michael@0: michael@0: // 'charactersEntered' is the length (in bytes) of currently "marked text" michael@0: // -- text that's been entered in IME but not yet committed. If it's michael@0: // non-zero we're composing text in an IME session; if it's zero we're michael@0: // not in an IME session. michael@0: NSInteger entered = 0; michael@0: object_getInstanceVariable(self, "charactersEntered", michael@0: (void **) &entered); michael@0: nsIWidget* widget = [(ChildView*)firstResponder widget]; michael@0: NS_ENSURE_TRUE(widget, retval); michael@0: TextInputHandler* handler = michael@0: static_cast(widget)->GetTextInputHandler(); michael@0: NS_ENSURE_TRUE(handler, retval); michael@0: handler->SetPluginTSMInComposition(entered != 0); michael@0: michael@0: return retval; michael@0: } michael@0: michael@0: // This method is called whenever IME input is committed as a result of an michael@0: // "abnormal" termination -- for example when changing the keyboard focus from michael@0: // one input field to another. michael@0: - (void)PluginTextInputHandler_IMKInputSession_commitComposition michael@0: { michael@0: NSWindow *mainWindow = [NSApp mainWindow]; michael@0: NSResponder *firstResponder = [mainWindow firstResponder]; michael@0: if ([firstResponder isKindOfClass:[ChildView class]]) { michael@0: nsIWidget* widget = [(ChildView*)firstResponder widget]; michael@0: if (widget) { michael@0: TextInputHandler* handler = michael@0: static_cast(widget)->GetTextInputHandler(); michael@0: if (handler) { michael@0: handler->SetPluginTSMInComposition(false); michael@0: } michael@0: } michael@0: } michael@0: [self PluginTextInputHandler_IMKInputSession_commitComposition]; michael@0: } michael@0: michael@0: // This method is called just before we're deallocated. michael@0: - (void)PluginTextInputHandler_IMKInputSession_finishSession michael@0: { michael@0: NSWindow *mainWindow = [NSApp mainWindow]; michael@0: NSResponder *firstResponder = [mainWindow firstResponder]; michael@0: if ([firstResponder isKindOfClass:[ChildView class]]) { michael@0: nsIWidget* widget = [(ChildView*)firstResponder widget]; michael@0: if (widget) { michael@0: TextInputHandler* handler = michael@0: static_cast(widget)->GetTextInputHandler(); michael@0: if (handler) { michael@0: handler->SetPluginTSMInComposition(false); michael@0: } michael@0: } michael@0: } michael@0: [self PluginTextInputHandler_IMKInputSession_finishSession]; michael@0: } michael@0: michael@0: @end michael@0: michael@0: #endif // #ifndef __LP64__ michael@0: michael@0: michael@0: #pragma mark - michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * michael@0: * TextInputHandlerBase implementation michael@0: * michael@0: ******************************************************************************/ michael@0: michael@0: int32_t TextInputHandlerBase::sSecureEventInputCount = 0; michael@0: michael@0: TextInputHandlerBase::TextInputHandlerBase(nsChildView* aWidget, michael@0: NSView *aNativeView) : michael@0: mWidget(aWidget) michael@0: { michael@0: gHandlerInstanceCount++; michael@0: mView = [aNativeView retain]; michael@0: } michael@0: michael@0: TextInputHandlerBase::~TextInputHandlerBase() michael@0: { michael@0: [mView release]; michael@0: if (--gHandlerInstanceCount == 0) { michael@0: FinalizeCurrentInputSource(); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: TextInputHandlerBase::OnDestroyWidget(nsChildView* aDestroyingWidget) michael@0: { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandlerBase::OnDestroyWidget, " michael@0: "aDestroyingWidget=%p, mWidget=%p", michael@0: this, aDestroyingWidget, mWidget)); michael@0: michael@0: if (aDestroyingWidget != mWidget) { michael@0: return false; michael@0: } michael@0: michael@0: mWidget = nullptr; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: TextInputHandlerBase::DispatchEvent(WidgetGUIEvent& aEvent) michael@0: { michael@0: if (aEvent.message == NS_KEY_PRESS) { michael@0: WidgetInputEvent& inputEvent = *aEvent.AsInputEvent(); michael@0: if (!inputEvent.IsMeta()) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandlerBase::DispatchEvent, hiding mouse cursor", this)); michael@0: [NSCursor setHiddenUntilMouseMoves:YES]; michael@0: } michael@0: } michael@0: return mWidget->DispatchWindowEvent(aEvent); michael@0: } michael@0: michael@0: void michael@0: TextInputHandlerBase::InitKeyEvent(NSEvent *aNativeKeyEvent, michael@0: WidgetKeyboardEvent& aKeyEvent, michael@0: const nsAString* aInsertString) michael@0: { michael@0: NS_ASSERTION(aNativeKeyEvent, "aNativeKeyEvent must not be NULL"); michael@0: michael@0: if (mKeyboardOverride.mOverrideEnabled) { michael@0: TISInputSourceWrapper tis; michael@0: tis.InitByLayoutID(mKeyboardOverride.mKeyboardLayout, true); michael@0: tis.InitKeyEvent(aNativeKeyEvent, aKeyEvent, aInsertString); michael@0: return; michael@0: } michael@0: TISInputSourceWrapper::CurrentInputSource(). michael@0: InitKeyEvent(aNativeKeyEvent, aKeyEvent, aInsertString); michael@0: } michael@0: michael@0: nsresult michael@0: TextInputHandlerBase::SynthesizeNativeKeyEvent( michael@0: int32_t aNativeKeyboardLayout, michael@0: int32_t aNativeKeyCode, michael@0: uint32_t aModifierFlags, michael@0: const nsAString& aCharacters, michael@0: const nsAString& aUnmodifiedCharacters) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: static const uint32_t sModifierFlagMap[][2] = { michael@0: { nsIWidget::CAPS_LOCK, NSAlphaShiftKeyMask }, michael@0: { nsIWidget::SHIFT_L, NSShiftKeyMask | 0x0002 }, michael@0: { nsIWidget::SHIFT_R, NSShiftKeyMask | 0x0004 }, michael@0: { nsIWidget::CTRL_L, NSControlKeyMask | 0x0001 }, michael@0: { nsIWidget::CTRL_R, NSControlKeyMask | 0x2000 }, michael@0: { nsIWidget::ALT_L, NSAlternateKeyMask | 0x0020 }, michael@0: { nsIWidget::ALT_R, NSAlternateKeyMask | 0x0040 }, michael@0: { nsIWidget::COMMAND_L, NSCommandKeyMask | 0x0008 }, michael@0: { nsIWidget::COMMAND_R, NSCommandKeyMask | 0x0010 }, michael@0: { nsIWidget::NUMERIC_KEY_PAD, NSNumericPadKeyMask }, michael@0: { nsIWidget::HELP, NSHelpKeyMask }, michael@0: { nsIWidget::FUNCTION, NSFunctionKeyMask } michael@0: }; michael@0: michael@0: uint32_t modifierFlags = 0; michael@0: for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) { michael@0: if (aModifierFlags & sModifierFlagMap[i][0]) { michael@0: modifierFlags |= sModifierFlagMap[i][1]; michael@0: } michael@0: } michael@0: michael@0: NSInteger windowNumber = [[mView window] windowNumber]; michael@0: bool sendFlagsChangedEvent = IsModifierKey(aNativeKeyCode); michael@0: NSEventType eventType = sendFlagsChangedEvent ? NSFlagsChanged : NSKeyDown; michael@0: NSEvent* downEvent = michael@0: [NSEvent keyEventWithType:eventType michael@0: location:NSMakePoint(0,0) michael@0: modifierFlags:modifierFlags michael@0: timestamp:0 michael@0: windowNumber:windowNumber michael@0: context:[NSGraphicsContext currentContext] michael@0: characters:nsCocoaUtils::ToNSString(aCharacters) michael@0: charactersIgnoringModifiers:nsCocoaUtils::ToNSString(aUnmodifiedCharacters) michael@0: isARepeat:NO michael@0: keyCode:aNativeKeyCode]; michael@0: michael@0: NSEvent* upEvent = sendFlagsChangedEvent ? michael@0: nil : nsCocoaUtils::MakeNewCocoaEventWithType(NSKeyUp, downEvent); michael@0: michael@0: if (downEvent && (sendFlagsChangedEvent || upEvent)) { michael@0: KeyboardLayoutOverride currentLayout = mKeyboardOverride; michael@0: mKeyboardOverride.mKeyboardLayout = aNativeKeyboardLayout; michael@0: mKeyboardOverride.mOverrideEnabled = true; michael@0: [NSApp sendEvent:downEvent]; michael@0: if (upEvent) { michael@0: [NSApp sendEvent:upEvent]; michael@0: } michael@0: // processKeyDownEvent and keyUp block exceptions so we're sure to michael@0: // reach here to restore mKeyboardOverride michael@0: mKeyboardOverride = currentLayout; michael@0: } michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: NSInteger michael@0: TextInputHandlerBase::GetWindowLevel() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandlerBase::GetWindowLevel, Destryoed()=%s", michael@0: this, TrueOrFalse(Destroyed()))); michael@0: michael@0: if (Destroyed()) { michael@0: return NSNormalWindowLevel; michael@0: } michael@0: michael@0: // When an element on a XUL is focused, the actual focused view michael@0: // is the panel's parent view (mView). But the editor is displayed on the michael@0: // popped-up widget's view (editorView). We want the latter's window level. michael@0: NSView* editorView = mWidget->GetEditorView(); michael@0: NS_ENSURE_TRUE(editorView, NSNormalWindowLevel); michael@0: NSInteger windowLevel = [[editorView window] level]; michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandlerBase::GetWindowLevel, windowLevel=%s (%X)", michael@0: this, GetWindowLevelName(windowLevel), windowLevel)); michael@0: michael@0: return windowLevel; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNormalWindowLevel); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: TextInputHandlerBase::AttachNativeKeyEvent(WidgetKeyboardEvent& aKeyEvent) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: // Don't try to replace a native event if one already exists. michael@0: // OS X doesn't have an OS modifier, can't make a native event. michael@0: if (aKeyEvent.mNativeKeyEvent || aKeyEvent.modifiers & MODIFIER_OS) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("%p TextInputHandlerBase::AttachNativeKeyEvent, key=0x%X, char=0x%X, " michael@0: "mod=0x%X", this, aKeyEvent.keyCode, aKeyEvent.charCode, michael@0: aKeyEvent.modifiers)); michael@0: michael@0: NSEventType eventType; michael@0: if (aKeyEvent.message == NS_KEY_UP) { michael@0: eventType = NSKeyUp; michael@0: } else { michael@0: eventType = NSKeyDown; michael@0: } michael@0: michael@0: static const uint32_t sModifierFlagMap[][2] = { michael@0: { MODIFIER_SHIFT, NSShiftKeyMask }, michael@0: { MODIFIER_CONTROL, NSControlKeyMask }, michael@0: { MODIFIER_ALT, NSAlternateKeyMask }, michael@0: { MODIFIER_ALTGRAPH, NSAlternateKeyMask }, michael@0: { MODIFIER_META, NSCommandKeyMask }, michael@0: { MODIFIER_CAPSLOCK, NSAlphaShiftKeyMask }, michael@0: { MODIFIER_NUMLOCK, NSNumericPadKeyMask } michael@0: }; michael@0: michael@0: NSUInteger modifierFlags = 0; michael@0: for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) { michael@0: if (aKeyEvent.modifiers & sModifierFlagMap[i][0]) { michael@0: modifierFlags |= sModifierFlagMap[i][1]; michael@0: } michael@0: } michael@0: michael@0: NSInteger windowNumber = [[mView window] windowNumber]; michael@0: michael@0: NSString* characters; michael@0: if (aKeyEvent.charCode) { michael@0: characters = [NSString stringWithCharacters: michael@0: reinterpret_cast(&(aKeyEvent.charCode)) length:1]; michael@0: } else { michael@0: uint32_t cocoaCharCode = michael@0: nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(aKeyEvent.keyCode); michael@0: characters = [NSString stringWithCharacters: michael@0: reinterpret_cast(&cocoaCharCode) length:1]; michael@0: } michael@0: michael@0: aKeyEvent.mNativeKeyEvent = michael@0: [NSEvent keyEventWithType:eventType michael@0: location:NSMakePoint(0,0) michael@0: modifierFlags:modifierFlags michael@0: timestamp:0 michael@0: windowNumber:windowNumber michael@0: context:[NSGraphicsContext currentContext] michael@0: characters:characters michael@0: charactersIgnoringModifiers:characters michael@0: isARepeat:NO michael@0: keyCode:0]; // Native key code not currently needed michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: bool michael@0: TextInputHandlerBase::SetSelection(NSRange& aRange) michael@0: { michael@0: MOZ_ASSERT(!Destroyed()); michael@0: michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: WidgetSelectionEvent selectionEvent(true, NS_SELECTION_SET, mWidget); michael@0: selectionEvent.mOffset = aRange.location; michael@0: selectionEvent.mLength = aRange.length; michael@0: selectionEvent.mReversed = false; michael@0: selectionEvent.mExpandToClusterBoundary = false; michael@0: DispatchEvent(selectionEvent); michael@0: NS_ENSURE_TRUE(selectionEvent.mSucceeded, false); michael@0: return !Destroyed(); michael@0: } michael@0: michael@0: /* static */ bool michael@0: TextInputHandlerBase::IsPrintableChar(char16_t aChar) michael@0: { michael@0: return (aChar >= 0x20 && aChar <= 0x7E) || aChar >= 0xA0; michael@0: } michael@0: michael@0: michael@0: /* static */ bool michael@0: TextInputHandlerBase::IsSpecialGeckoKey(UInt32 aNativeKeyCode) michael@0: { michael@0: // this table is used to determine which keys are special and should not michael@0: // generate a charCode michael@0: switch (aNativeKeyCode) { michael@0: // modifiers - we don't get separate events for these yet michael@0: case kVK_Escape: michael@0: case kVK_Shift: michael@0: case kVK_RightShift: michael@0: case kVK_Command: michael@0: case kVK_RightCommand: michael@0: case kVK_CapsLock: michael@0: case kVK_Control: michael@0: case kVK_RightControl: michael@0: case kVK_Option: michael@0: case kVK_RightOption: michael@0: case kVK_ANSI_KeypadClear: michael@0: case kVK_Function: michael@0: michael@0: // function keys michael@0: case kVK_F1: michael@0: case kVK_F2: michael@0: case kVK_F3: michael@0: case kVK_F4: michael@0: case kVK_F5: michael@0: case kVK_F6: michael@0: case kVK_F7: michael@0: case kVK_F8: michael@0: case kVK_F9: michael@0: case kVK_F10: michael@0: case kVK_F11: michael@0: case kVK_F12: michael@0: case kVK_PC_Pause: michael@0: case kVK_PC_ScrollLock: michael@0: case kVK_PC_PrintScreen: michael@0: case kVK_F16: michael@0: case kVK_F17: michael@0: case kVK_F18: michael@0: case kVK_F19: michael@0: michael@0: case kVK_PC_Insert: michael@0: case kVK_PC_Delete: michael@0: case kVK_Tab: michael@0: case kVK_PC_Backspace: michael@0: case kVK_PC_ContextMenu: michael@0: michael@0: case kVK_JIS_Eisu: michael@0: case kVK_JIS_Kana: michael@0: michael@0: case kVK_Home: michael@0: case kVK_End: michael@0: case kVK_PageUp: michael@0: case kVK_PageDown: michael@0: case kVK_LeftArrow: michael@0: case kVK_RightArrow: michael@0: case kVK_UpArrow: michael@0: case kVK_DownArrow: michael@0: case kVK_Return: michael@0: case kVK_ANSI_KeypadEnter: michael@0: case kVK_Powerbook_KeypadEnter: michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: /* static */ bool michael@0: TextInputHandlerBase::IsNormalCharInputtingEvent( michael@0: const WidgetKeyboardEvent& aKeyEvent) michael@0: { michael@0: // this is not character inputting event, simply. michael@0: if (!aKeyEvent.isChar || !aKeyEvent.charCode || aKeyEvent.IsMeta()) { michael@0: return false; michael@0: } michael@0: // if this is unicode char inputting event, we don't need to check michael@0: // ctrl/alt/command keys michael@0: if (aKeyEvent.charCode > 0x7F) { michael@0: return true; michael@0: } michael@0: // ASCII chars should be inputted without ctrl/alt/command keys michael@0: return !aKeyEvent.IsControl() && !aKeyEvent.IsAlt(); michael@0: } michael@0: michael@0: /* static */ bool michael@0: TextInputHandlerBase::IsModifierKey(UInt32 aNativeKeyCode) michael@0: { michael@0: switch (aNativeKeyCode) { michael@0: case kVK_CapsLock: michael@0: case kVK_RightCommand: michael@0: case kVK_Command: michael@0: case kVK_Shift: michael@0: case kVK_Option: michael@0: case kVK_Control: michael@0: case kVK_RightShift: michael@0: case kVK_RightOption: michael@0: case kVK_RightControl: michael@0: case kVK_Function: michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: /* static */ void michael@0: TextInputHandlerBase::EnableSecureEventInput() michael@0: { michael@0: sSecureEventInputCount++; michael@0: ::EnableSecureEventInput(); michael@0: } michael@0: michael@0: /* static */ void michael@0: TextInputHandlerBase::DisableSecureEventInput() michael@0: { michael@0: if (!sSecureEventInputCount) { michael@0: return; michael@0: } michael@0: sSecureEventInputCount--; michael@0: ::DisableSecureEventInput(); michael@0: } michael@0: michael@0: /* static */ bool michael@0: TextInputHandlerBase::IsSecureEventInputEnabled() michael@0: { michael@0: NS_ASSERTION(!!sSecureEventInputCount == !!::IsSecureEventInputEnabled(), michael@0: "Some other process has enabled secure event input"); michael@0: return !!sSecureEventInputCount; michael@0: } michael@0: michael@0: /* static */ void michael@0: TextInputHandlerBase::EnsureSecureEventInputDisabled() michael@0: { michael@0: while (sSecureEventInputCount) { michael@0: TextInputHandlerBase::DisableSecureEventInput(); michael@0: } michael@0: }