1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/ArrayUtils.h"
9 #include "TextInputHandler.h"
13 #include "mozilla/MiscEvents.h"
14 #include "mozilla/MouseEvents.h"
15 #include "mozilla/TextEvents.h"
17 #include "nsChildView.h"
18 #include "nsObjCExceptions.h"
19 #include "nsBidiUtils.h"
20 #include "nsToolkit.h"
21 #include "nsCocoaUtils.h"
22 #include "WidgetUtils.h"
23 #include "nsPrintfCString.h"
24 #include "ComplexTextInputPanel.h"
26 using namespace mozilla;
27 using namespace mozilla::widget;
31 PRLogModuleInfo* gLog = nullptr;
36 return aBool ? "ON" : "off";
40 TrueOrFalse(bool aBool)
42 return aBool ? "TRUE" : "FALSE";
46 GetKeyNameForNativeKeyCode(unsigned short aNativeKeyCode)
48 switch (aNativeKeyCode) {
49 case kVK_Escape: return "Escape";
50 case kVK_RightCommand: return "Right-Command";
51 case kVK_Command: return "Command";
52 case kVK_Shift: return "Shift";
53 case kVK_CapsLock: return "CapsLock";
54 case kVK_Option: return "Option";
55 case kVK_Control: return "Control";
56 case kVK_RightShift: return "Right-Shift";
57 case kVK_RightOption: return "Right-Option";
58 case kVK_RightControl: return "Right-Control";
59 case kVK_ANSI_KeypadClear: return "Clear";
61 case kVK_F1: return "F1";
62 case kVK_F2: return "F2";
63 case kVK_F3: return "F3";
64 case kVK_F4: return "F4";
65 case kVK_F5: return "F5";
66 case kVK_F6: return "F6";
67 case kVK_F7: return "F7";
68 case kVK_F8: return "F8";
69 case kVK_F9: return "F9";
70 case kVK_F10: return "F10";
71 case kVK_F11: return "F11";
72 case kVK_F12: return "F12";
73 case kVK_F13: return "F13/PrintScreen";
74 case kVK_F14: return "F14/ScrollLock";
75 case kVK_F15: return "F15/Pause";
77 case kVK_ANSI_Keypad0: return "NumPad-0";
78 case kVK_ANSI_Keypad1: return "NumPad-1";
79 case kVK_ANSI_Keypad2: return "NumPad-2";
80 case kVK_ANSI_Keypad3: return "NumPad-3";
81 case kVK_ANSI_Keypad4: return "NumPad-4";
82 case kVK_ANSI_Keypad5: return "NumPad-5";
83 case kVK_ANSI_Keypad6: return "NumPad-6";
84 case kVK_ANSI_Keypad7: return "NumPad-7";
85 case kVK_ANSI_Keypad8: return "NumPad-8";
86 case kVK_ANSI_Keypad9: return "NumPad-9";
88 case kVK_ANSI_KeypadMultiply: return "NumPad-*";
89 case kVK_ANSI_KeypadPlus: return "NumPad-+";
90 case kVK_ANSI_KeypadMinus: return "NumPad--";
91 case kVK_ANSI_KeypadDecimal: return "NumPad-.";
92 case kVK_ANSI_KeypadDivide: return "NumPad-/";
93 case kVK_ANSI_KeypadEquals: return "NumPad-=";
94 case kVK_ANSI_KeypadEnter: return "NumPad-Enter";
95 case kVK_Return: return "Return";
96 case kVK_Powerbook_KeypadEnter: return "NumPad-EnterOnPowerBook";
98 case kVK_PC_Insert: return "Insert/Help";
99 case kVK_PC_Delete: return "Delete";
100 case kVK_Tab: return "Tab";
101 case kVK_PC_Backspace: return "Backspace";
102 case kVK_Home: return "Home";
103 case kVK_End: return "End";
104 case kVK_PageUp: return "PageUp";
105 case kVK_PageDown: return "PageDown";
106 case kVK_LeftArrow: return "LeftArrow";
107 case kVK_RightArrow: return "RightArrow";
108 case kVK_UpArrow: return "UpArrow";
109 case kVK_DownArrow: return "DownArrow";
110 case kVK_PC_ContextMenu: return "ContextMenu";
112 case kVK_Function: return "Function";
113 case kVK_VolumeUp: return "VolumeUp";
114 case kVK_VolumeDown: return "VolumeDown";
115 case kVK_Mute: return "Mute";
117 case kVK_ISO_Section: return "ISO_Section";
119 case kVK_JIS_Yen: return "JIS_Yen";
120 case kVK_JIS_Underscore: return "JIS_Underscore";
121 case kVK_JIS_KeypadComma: return "JIS_KeypadComma";
122 case kVK_JIS_Eisu: return "JIS_Eisu";
123 case kVK_JIS_Kana: return "JIS_Kana";
125 case kVK_ANSI_A: return "A";
126 case kVK_ANSI_B: return "B";
127 case kVK_ANSI_C: return "C";
128 case kVK_ANSI_D: return "D";
129 case kVK_ANSI_E: return "E";
130 case kVK_ANSI_F: return "F";
131 case kVK_ANSI_G: return "G";
132 case kVK_ANSI_H: return "H";
133 case kVK_ANSI_I: return "I";
134 case kVK_ANSI_J: return "J";
135 case kVK_ANSI_K: return "K";
136 case kVK_ANSI_L: return "L";
137 case kVK_ANSI_M: return "M";
138 case kVK_ANSI_N: return "N";
139 case kVK_ANSI_O: return "O";
140 case kVK_ANSI_P: return "P";
141 case kVK_ANSI_Q: return "Q";
142 case kVK_ANSI_R: return "R";
143 case kVK_ANSI_S: return "S";
144 case kVK_ANSI_T: return "T";
145 case kVK_ANSI_U: return "U";
146 case kVK_ANSI_V: return "V";
147 case kVK_ANSI_W: return "W";
148 case kVK_ANSI_X: return "X";
149 case kVK_ANSI_Y: return "Y";
150 case kVK_ANSI_Z: return "Z";
152 case kVK_ANSI_1: return "1";
153 case kVK_ANSI_2: return "2";
154 case kVK_ANSI_3: return "3";
155 case kVK_ANSI_4: return "4";
156 case kVK_ANSI_5: return "5";
157 case kVK_ANSI_6: return "6";
158 case kVK_ANSI_7: return "7";
159 case kVK_ANSI_8: return "8";
160 case kVK_ANSI_9: return "9";
161 case kVK_ANSI_0: return "0";
162 case kVK_ANSI_Equal: return "Equal";
163 case kVK_ANSI_Minus: return "Minus";
164 case kVK_ANSI_RightBracket: return "RightBracket";
165 case kVK_ANSI_LeftBracket: return "LeftBracket";
166 case kVK_ANSI_Quote: return "Quote";
167 case kVK_ANSI_Semicolon: return "Semicolon";
168 case kVK_ANSI_Backslash: return "Backslash";
169 case kVK_ANSI_Comma: return "Comma";
170 case kVK_ANSI_Slash: return "Slash";
171 case kVK_ANSI_Period: return "Period";
172 case kVK_ANSI_Grave: return "Grave";
174 default: return "undefined";
179 GetCharacters(const NSString* aString)
182 nsCocoaUtils::GetStringForNSString(aString, str);
187 nsAutoString escapedStr;
188 for (uint32_t i = 0; i < str.Length(); i++) {
189 char16_t ch = str[i];
191 nsPrintfCString utf8str("(U+%04X)", ch);
192 escapedStr += NS_ConvertUTF8toUTF16(utf8str);
193 } else if (ch <= 0x7E) {
196 nsPrintfCString utf8str("(U+%04X)", ch);
198 escapedStr += NS_ConvertUTF8toUTF16(utf8str);
202 // the result will be freed automatically by cocoa.
203 NSString* result = nsCocoaUtils::ToNSString(escapedStr);
204 return [result UTF8String];
208 GetCharacters(const CFStringRef aString)
210 const NSString* str = reinterpret_cast<const NSString*>(aString);
211 return GetCharacters(str);
215 GetNativeKeyEventType(NSEvent* aNativeEvent)
217 switch ([aNativeEvent type]) {
218 case NSKeyDown: return "NSKeyDown";
219 case NSKeyUp: return "NSKeyUp";
220 case NSFlagsChanged: return "NSFlagsChanged";
221 default: return "not key event";
226 GetGeckoKeyEventType(const WidgetEvent& aEvent)
228 switch (aEvent.message) {
229 case NS_KEY_DOWN: return "NS_KEY_DOWN";
230 case NS_KEY_UP: return "NS_KEY_UP";
231 case NS_KEY_PRESS: return "NS_KEY_PRESS";
232 default: return "not key event";
237 GetRangeTypeName(uint32_t aRangeType)
239 switch (aRangeType) {
240 case NS_TEXTRANGE_RAWINPUT:
241 return "NS_TEXTRANGE_RAWINPUT";
242 case NS_TEXTRANGE_CONVERTEDTEXT:
243 return "NS_TEXTRANGE_CONVERTEDTEXT";
244 case NS_TEXTRANGE_SELECTEDRAWTEXT:
245 return "NS_TEXTRANGE_SELECTEDRAWTEXT";
246 case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT:
247 return "NS_TEXTRANGE_SELECTEDCONVERTEDTEXT";
248 case NS_TEXTRANGE_CARETPOSITION:
249 return "NS_TEXTRANGE_CARETPOSITION";
251 return "invalid range type";
256 GetWindowLevelName(NSInteger aWindowLevel)
258 switch (aWindowLevel) {
259 case kCGBaseWindowLevelKey:
260 return "kCGBaseWindowLevelKey (NSNormalWindowLevel)";
261 case kCGMinimumWindowLevelKey:
262 return "kCGMinimumWindowLevelKey";
263 case kCGDesktopWindowLevelKey:
264 return "kCGDesktopWindowLevelKey";
265 case kCGBackstopMenuLevelKey:
266 return "kCGBackstopMenuLevelKey";
267 case kCGNormalWindowLevelKey:
268 return "kCGNormalWindowLevelKey";
269 case kCGFloatingWindowLevelKey:
270 return "kCGFloatingWindowLevelKey (NSFloatingWindowLevel)";
271 case kCGTornOffMenuWindowLevelKey:
272 return "kCGTornOffMenuWindowLevelKey (NSSubmenuWindowLevel, NSTornOffMenuWindowLevel)";
273 case kCGDockWindowLevelKey:
274 return "kCGDockWindowLevelKey (NSDockWindowLevel)";
275 case kCGMainMenuWindowLevelKey:
276 return "kCGMainMenuWindowLevelKey (NSMainMenuWindowLevel)";
277 case kCGStatusWindowLevelKey:
278 return "kCGStatusWindowLevelKey (NSStatusWindowLevel)";
279 case kCGModalPanelWindowLevelKey:
280 return "kCGModalPanelWindowLevelKey (NSModalPanelWindowLevel)";
281 case kCGPopUpMenuWindowLevelKey:
282 return "kCGPopUpMenuWindowLevelKey (NSPopUpMenuWindowLevel)";
283 case kCGDraggingWindowLevelKey:
284 return "kCGDraggingWindowLevelKey";
285 case kCGScreenSaverWindowLevelKey:
286 return "kCGScreenSaverWindowLevelKey (NSScreenSaverWindowLevel)";
287 case kCGMaximumWindowLevelKey:
288 return "kCGMaximumWindowLevelKey";
289 case kCGOverlayWindowLevelKey:
290 return "kCGOverlayWindowLevelKey";
291 case kCGHelpWindowLevelKey:
292 return "kCGHelpWindowLevelKey";
293 case kCGUtilityWindowLevelKey:
294 return "kCGUtilityWindowLevelKey";
295 case kCGDesktopIconWindowLevelKey:
296 return "kCGDesktopIconWindowLevelKey";
297 case kCGCursorWindowLevelKey:
298 return "kCGCursorWindowLevelKey";
299 case kCGNumberOfWindowLevelKeys:
300 return "kCGNumberOfWindowLevelKeys";
302 return "unknown window level";
306 #endif // #ifdef PR_LOGGING
309 IsControlChar(uint32_t aCharCode)
311 return aCharCode < ' ' || aCharCode == 0x7F;
314 static uint32_t gHandlerInstanceCount = 0;
315 static TISInputSourceWrapper gCurrentInputSource;
321 // Clear() is always called when TISInputSourceWrappper is created.
323 gLog = PR_NewLogModule("TextInputHandlerWidgets");
324 TextInputHandler::DebugPrintAllKeyboardLayouts();
325 IMEInputHandler::DebugPrintAllIMEModes();
331 InitCurrentInputSource()
333 if (gHandlerInstanceCount > 0 &&
334 !gCurrentInputSource.IsInitializedByCurrentInputSource()) {
335 gCurrentInputSource.InitByCurrentInputSource();
340 FinalizeCurrentInputSource()
342 gCurrentInputSource.Clear();
349 /******************************************************************************
351 * TISInputSourceWrapper implementation
353 ******************************************************************************/
356 TISInputSourceWrapper&
357 TISInputSourceWrapper::CurrentInputSource()
359 InitCurrentInputSource();
360 return gCurrentInputSource;
364 TISInputSourceWrapper::TranslateToString(UInt32 aKeyCode, UInt32 aModifiers,
365 UInt32 aKbType, nsAString &aStr)
369 const UCKeyboardLayout* UCKey = GetUCKeyboardLayout();
371 PR_LOG(gLog, PR_LOG_ALWAYS,
372 ("%p TISInputSourceWrapper::TranslateToString, aKeyCode=0x%X, "
373 "aModifiers=0x%X, aKbType=0x%X UCKey=%p\n "
374 "Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s",
375 this, aKeyCode, aModifiers, aKbType, UCKey,
376 OnOrOff(aModifiers & shiftKey), OnOrOff(aModifiers & controlKey),
377 OnOrOff(aModifiers & optionKey), OnOrOff(aModifiers & cmdKey),
378 OnOrOff(aModifiers & alphaLock),
379 OnOrOff(aModifiers & kEventKeyModifierNumLockMask)));
381 NS_ENSURE_TRUE(UCKey, false);
383 UInt32 deadKeyState = 0;
386 OSStatus err = ::UCKeyTranslate(UCKey, aKeyCode,
387 kUCKeyActionDown, aModifiers >> 8,
388 aKbType, kUCKeyTranslateNoDeadKeysMask,
389 &deadKeyState, 5, &len, chars);
391 PR_LOG(gLog, PR_LOG_ALWAYS,
392 ("%p TISInputSourceWrapper::TranslateToString, err=0x%X, len=%llu",
395 NS_ENSURE_TRUE(err == noErr, false);
399 NS_ENSURE_TRUE(EnsureStringLength(aStr, len), false);
400 NS_ASSERTION(sizeof(char16_t) == sizeof(UniChar),
401 "size of char16_t and size of UniChar are different");
402 memcpy(aStr.BeginWriting(), chars, len * sizeof(char16_t));
404 PR_LOG(gLog, PR_LOG_ALWAYS,
405 ("%p TISInputSourceWrapper::TranslateToString, aStr=\"%s\"",
406 this, NS_ConvertUTF16toUTF8(aStr).get()));
412 TISInputSourceWrapper::TranslateToChar(UInt32 aKeyCode, UInt32 aModifiers,
416 if (!TranslateToString(aKeyCode, aModifiers, aKbType, str) ||
420 return static_cast<uint32_t>(str.CharAt(0));
424 TISInputSourceWrapper::InitByInputSourceID(const char* aID)
430 CFStringRef idstr = ::CFStringCreateWithCString(kCFAllocatorDefault, aID,
431 kCFStringEncodingASCII);
432 InitByInputSourceID(idstr);
437 TISInputSourceWrapper::InitByInputSourceID(const nsAFlatString &aID)
442 CFStringRef idstr = ::CFStringCreateWithCharacters(kCFAllocatorDefault,
443 reinterpret_cast<const UniChar*>(aID.get()),
445 InitByInputSourceID(idstr);
450 TISInputSourceWrapper::InitByInputSourceID(const CFStringRef aID)
455 const void* keys[] = { kTISPropertyInputSourceID };
456 const void* values[] = { aID };
457 CFDictionaryRef filter =
458 ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
459 NS_ASSERTION(filter, "failed to create the filter");
460 mInputSourceList = ::TISCreateInputSourceList(filter, true);
462 if (::CFArrayGetCount(mInputSourceList) > 0) {
463 mInputSource = static_cast<TISInputSourceRef>(
464 const_cast<void *>(::CFArrayGetValueAtIndex(mInputSourceList, 0)));
465 if (IsKeyboardLayout()) {
466 mKeyboardLayout = mInputSource;
472 TISInputSourceWrapper::InitByLayoutID(SInt32 aLayoutID,
473 bool aOverrideKeyboard)
475 // NOTE: Doument new layout IDs in TextInputHandler.h when you add ones.
478 InitByInputSourceID("com.apple.keylayout.US");
481 InitByInputSourceID("com.apple.keylayout.Greek");
484 InitByInputSourceID("com.apple.keylayout.German");
487 InitByInputSourceID("com.apple.keylayout.Swedish-Pro");
490 InitByInputSourceID("com.apple.keylayout.DVORAK-QWERTYCMD");
493 InitByInputSourceID("com.apple.keylayout.Thai");
496 InitByInputSourceID("com.apple.keylayout.Arabic");
499 InitByInputSourceID("com.apple.keylayout.French");
502 InitByInputSourceID("com.apple.keylayout.Hebrew");
505 InitByInputSourceID("com.apple.keylayout.Lithuanian");
508 InitByInputSourceID("com.apple.keylayout.Norwegian");
511 InitByInputSourceID("com.apple.keylayout.Spanish");
517 mOverrideKeyboard = aOverrideKeyboard;
521 TISInputSourceWrapper::InitByCurrentInputSource()
524 mInputSource = ::TISCopyCurrentKeyboardInputSource();
525 mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
526 if (!mKeyboardLayout) {
527 mKeyboardLayout = ::TISCopyCurrentKeyboardLayoutInputSource();
529 // If this causes composition, the current keyboard layout may input non-ASCII
530 // characters such as Japanese Kana characters or Hangul characters.
531 // However, we need to set ASCII characters to DOM key events for consistency
532 // with other platforms.
533 if (IsOpenedIMEMode()) {
534 TISInputSourceWrapper tis(mKeyboardLayout);
535 if (!tis.IsASCIICapable()) {
537 ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
543 TISInputSourceWrapper::InitByCurrentKeyboardLayout()
546 mInputSource = ::TISCopyCurrentKeyboardLayoutInputSource();
547 mKeyboardLayout = mInputSource;
551 TISInputSourceWrapper::InitByCurrentASCIICapableInputSource()
554 mInputSource = ::TISCopyCurrentASCIICapableKeyboardInputSource();
555 mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
556 if (mKeyboardLayout) {
557 TISInputSourceWrapper tis(mKeyboardLayout);
558 if (!tis.IsASCIICapable()) {
559 mKeyboardLayout = nullptr;
562 if (!mKeyboardLayout) {
564 ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
569 TISInputSourceWrapper::InitByCurrentASCIICapableKeyboardLayout()
572 mInputSource = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
573 mKeyboardLayout = mInputSource;
577 TISInputSourceWrapper::InitByCurrentInputMethodKeyboardLayoutOverride()
580 mInputSource = ::TISCopyInputMethodKeyboardLayoutOverride();
581 mKeyboardLayout = mInputSource;
585 TISInputSourceWrapper::InitByTISInputSourceRef(TISInputSourceRef aInputSource)
588 mInputSource = aInputSource;
589 if (IsKeyboardLayout()) {
590 mKeyboardLayout = mInputSource;
595 TISInputSourceWrapper::InitByLanguage(CFStringRef aLanguage)
598 mInputSource = ::TISCopyInputSourceForLanguage(aLanguage);
599 if (IsKeyboardLayout()) {
600 mKeyboardLayout = mInputSource;
604 const UCKeyboardLayout*
605 TISInputSourceWrapper::GetUCKeyboardLayout()
607 NS_ENSURE_TRUE(mKeyboardLayout, nullptr);
608 if (mUCKeyboardLayout) {
609 return mUCKeyboardLayout;
611 CFDataRef uchr = static_cast<CFDataRef>(
612 ::TISGetInputSourceProperty(mKeyboardLayout,
613 kTISPropertyUnicodeKeyLayoutData));
615 // We should be always able to get the layout here.
616 NS_ENSURE_TRUE(uchr, nullptr);
618 reinterpret_cast<const UCKeyboardLayout*>(CFDataGetBytePtr(uchr));
619 return mUCKeyboardLayout;
623 TISInputSourceWrapper::GetBoolProperty(const CFStringRef aKey)
625 CFBooleanRef ret = static_cast<CFBooleanRef>(
626 ::TISGetInputSourceProperty(mInputSource, aKey));
627 return ::CFBooleanGetValue(ret);
631 TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey,
634 aStr = static_cast<CFStringRef>(
635 ::TISGetInputSourceProperty(mInputSource, aKey));
636 return aStr != nullptr;
640 TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey,
644 GetStringProperty(aKey, str);
645 nsCocoaUtils::GetStringForNSString((const NSString*)str, aStr);
646 return !aStr.IsEmpty();
650 TISInputSourceWrapper::IsOpenedIMEMode()
652 NS_ENSURE_TRUE(mInputSource, false);
655 return !IsASCIICapable();
659 TISInputSourceWrapper::IsIMEMode()
661 NS_ENSURE_TRUE(mInputSource, false);
663 GetInputSourceType(str);
664 NS_ENSURE_TRUE(str, false);
665 return ::CFStringCompare(kTISTypeKeyboardInputMode,
666 str, 0) == kCFCompareEqualTo;
670 TISInputSourceWrapper::IsKeyboardLayout()
672 NS_ENSURE_TRUE(mInputSource, false);
674 GetInputSourceType(str);
675 NS_ENSURE_TRUE(str, false);
676 return ::CFStringCompare(kTISTypeKeyboardLayout,
677 str, 0) == kCFCompareEqualTo;
681 TISInputSourceWrapper::GetLanguageList(CFArrayRef &aLanguageList)
683 NS_ENSURE_TRUE(mInputSource, false);
684 aLanguageList = static_cast<CFArrayRef>(
685 ::TISGetInputSourceProperty(mInputSource,
686 kTISPropertyInputSourceLanguages));
687 return aLanguageList != nullptr;
691 TISInputSourceWrapper::GetPrimaryLanguage(CFStringRef &aPrimaryLanguage)
693 NS_ENSURE_TRUE(mInputSource, false);
695 NS_ENSURE_TRUE(GetLanguageList(langList), false);
696 if (::CFArrayGetCount(langList) == 0)
699 static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, 0));
700 return aPrimaryLanguage != nullptr;
704 TISInputSourceWrapper::GetPrimaryLanguage(nsAString &aPrimaryLanguage)
706 NS_ENSURE_TRUE(mInputSource, false);
707 CFStringRef primaryLanguage;
708 NS_ENSURE_TRUE(GetPrimaryLanguage(primaryLanguage), false);
709 nsCocoaUtils::GetStringForNSString((const NSString*)primaryLanguage,
711 return !aPrimaryLanguage.IsEmpty();
715 TISInputSourceWrapper::IsForRTLLanguage()
718 // Get the input character of the 'A' key of ANSI keyboard layout.
720 bool ret = TranslateToString(kVK_ANSI_A, 0, eKbdType_ANSI, str);
721 NS_ENSURE_TRUE(ret, ret);
722 char16_t ch = str.IsEmpty() ? char16_t(0) : str.CharAt(0);
723 mIsRTL = UCS2_CHAR_IS_BIDI(ch) || ch == 0xD802 || ch == 0xD803;
729 TISInputSourceWrapper::IsInitializedByCurrentInputSource()
731 return mInputSource == ::TISCopyCurrentKeyboardInputSource();
735 TISInputSourceWrapper::Select()
739 ::TISSelectInputSource(mInputSource);
743 TISInputSourceWrapper::Clear()
745 // Clear() is always called when TISInputSourceWrappper is created.
748 if (mInputSourceList) {
749 ::CFRelease(mInputSourceList);
751 mInputSourceList = nullptr;
752 mInputSource = nullptr;
753 mKeyboardLayout = nullptr;
755 mUCKeyboardLayout = nullptr;
756 mOverrideKeyboard = false;
760 TISInputSourceWrapper::InitKeyEvent(NSEvent *aNativeKeyEvent,
761 WidgetKeyboardEvent& aKeyEvent,
762 const nsAString *aInsertString)
764 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
766 PR_LOG(gLog, PR_LOG_ALWAYS,
767 ("%p TISInputSourceWrapper::InitKeyEvent, aNativeKeyEvent=%p, "
768 "aKeyEvent.message=%s, aInsertString=%p, IsOpenedIMEMode()=%s",
769 this, aNativeKeyEvent, GetGeckoKeyEventType(aKeyEvent), aInsertString,
770 TrueOrFalse(IsOpenedIMEMode())));
772 NS_ENSURE_TRUE(aNativeKeyEvent, );
774 nsCocoaUtils::InitInputEvent(aKeyEvent, aNativeKeyEvent);
776 // This is used only while dispatching the event (which is a synchronous
777 // call), so there is no need to retain and release this data.
778 aKeyEvent.mNativeKeyEvent = aNativeKeyEvent;
780 // Fill in fields used for Cocoa NPAPI plugins
781 if ([aNativeKeyEvent type] == NSKeyDown ||
782 [aNativeKeyEvent type] == NSKeyUp) {
783 aKeyEvent.mNativeKeyCode = [aNativeKeyEvent keyCode];
784 aKeyEvent.mNativeModifierFlags = [aNativeKeyEvent modifierFlags];
785 nsAutoString nativeChars;
786 nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], nativeChars);
787 aKeyEvent.mNativeCharacters.Assign(nativeChars);
788 nsAutoString nativeCharsIgnoringModifiers;
789 nsCocoaUtils::GetStringForNSString([aNativeKeyEvent charactersIgnoringModifiers], nativeCharsIgnoringModifiers);
790 aKeyEvent.mNativeCharactersIgnoringModifiers.Assign(nativeCharsIgnoringModifiers);
791 } else if ([aNativeKeyEvent type] == NSFlagsChanged) {
792 aKeyEvent.mNativeKeyCode = [aNativeKeyEvent keyCode];
793 aKeyEvent.mNativeModifierFlags = [aNativeKeyEvent modifierFlags];
796 aKeyEvent.refPoint = LayoutDeviceIntPoint(0, 0);
798 // If a keyboard layout override is set, we also need to force the keyboard
799 // type to something ANSI to avoid test failures on machines with JIS
800 // keyboards (since the pair of keyboard layout and physical keyboard type
801 // form the actual key layout). This assumes that the test setting the
802 // override was written assuming an ANSI keyboard.
803 UInt32 kbType = mOverrideKeyboard ? eKbdType_ANSI : ::LMGetKbdType();
805 UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
807 bool isPrintableKey = !TextInputHandler::IsSpecialGeckoKey(nativeKeyCode);
808 if (isPrintableKey &&
809 [aNativeKeyEvent type] != NSKeyDown &&
810 [aNativeKeyEvent type] != NSKeyUp) {
811 NS_WARNING("Why the printable key doesn't cause NSKeyDown or NSKeyUp?");
812 isPrintableKey = false;
815 // Decide what string will be input.
816 nsAutoString insertString;
818 // If the caller expects that the aInsertString will be input, we shouldn't
820 insertString = *aInsertString;
821 } else if (isPrintableKey) {
822 // If IME is open, [aNativeKeyEvent characters] may be a character
823 // which will be appended to the composition string. However, especially,
824 // while IME is disabled, most users and developers expect the key event
825 // works as IME closed. So, we should compute the insertString with
826 // the ASCII capable keyboard layout.
827 // NOTE: Such keyboard layouts typically change the layout to its ASCII
828 // capable layout when Command key is pressed. And we don't worry
829 // when Control key is pressed too because it causes inputting
830 // control characters.
831 if (!aKeyEvent.IsMeta() && !aKeyEvent.IsControl() && IsOpenedIMEMode()) {
833 nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
834 uint32_t ch = TranslateToChar(nativeKeyCode, state, kbType);
839 // If the caller isn't sure what string will be input, let's use
840 // characters of NSEvent.
841 nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
845 // If control key is pressed and the eventChars is a non-printable control
846 // character, we should convert it to ASCII alphabet.
847 if (aKeyEvent.IsControl() &&
848 !insertString.IsEmpty() && insertString[0] <= char16_t(26)) {
849 insertString = (aKeyEvent.IsShift() ^ aKeyEvent.IsCapsLocked()) ?
850 static_cast<char16_t>(insertString[0] + ('A' - 1)) :
851 static_cast<char16_t>(insertString[0] + ('a' - 1));
853 // If Meta key is pressed, it may cause to switch the keyboard layout like
854 // Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
855 else if (aKeyEvent.IsMeta() &&
856 !(aKeyEvent.IsControl() || aKeyEvent.IsAlt())) {
857 UInt32 numLockState =
858 aKeyEvent.IsNumLocked() ? kEventKeyModifierNumLockMask : 0;
859 UInt32 capsLockState = aKeyEvent.IsCapsLocked() ? alphaLock : 0;
860 UInt32 shiftState = aKeyEvent.IsShift() ? shiftKey : 0;
861 uint32_t uncmdedChar =
862 TranslateToChar(nativeKeyCode, numLockState, kbType);
864 TranslateToChar(nativeKeyCode, cmdKey | numLockState, kbType);
865 // If we can make a good guess at the characters that the user would
866 // expect this key combination to produce (with and without Shift) then
867 // use those characters. This also corrects for CapsLock.
869 if (uncmdedChar == cmdedChar) {
870 // The characters produced with Command seem similar to those without
872 ch = TranslateToChar(nativeKeyCode,
873 shiftState | capsLockState | numLockState, kbType);
875 TISInputSourceWrapper USLayout("com.apple.keylayout.US");
876 uint32_t uncmdedUSChar =
877 USLayout.TranslateToChar(nativeKeyCode, numLockState, kbType);
878 // If it looks like characters from US keyboard layout when Command key
879 // is pressed, we should compute a character in the layout.
880 if (uncmdedUSChar == cmdedChar) {
881 ch = USLayout.TranslateToChar(nativeKeyCode,
882 shiftState | capsLockState | numLockState, kbType);
886 // If there is a more preferred character for the commanded key event,
894 // Remove control characters which shouldn't be inputted on editor.
895 // XXX Currently, we don't find any cases inserting control characters with
896 // printable character. So, just checking first character is enough.
897 if (!insertString.IsEmpty() && IsControlChar(insertString[0])) {
898 insertString.Truncate();
902 ComputeGeckoKeyCode(nativeKeyCode, kbType, aKeyEvent.IsMeta());
904 switch (nativeKeyCode) {
909 aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT;
912 case kVK_RightCommand:
914 case kVK_RightOption:
915 case kVK_RightControl:
916 aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT;
919 case kVK_ANSI_Keypad0:
920 case kVK_ANSI_Keypad1:
921 case kVK_ANSI_Keypad2:
922 case kVK_ANSI_Keypad3:
923 case kVK_ANSI_Keypad4:
924 case kVK_ANSI_Keypad5:
925 case kVK_ANSI_Keypad6:
926 case kVK_ANSI_Keypad7:
927 case kVK_ANSI_Keypad8:
928 case kVK_ANSI_Keypad9:
929 case kVK_ANSI_KeypadMultiply:
930 case kVK_ANSI_KeypadPlus:
931 case kVK_ANSI_KeypadMinus:
932 case kVK_ANSI_KeypadDecimal:
933 case kVK_ANSI_KeypadDivide:
934 case kVK_ANSI_KeypadEquals:
935 case kVK_ANSI_KeypadEnter:
936 case kVK_JIS_KeypadComma:
937 case kVK_Powerbook_KeypadEnter:
938 aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
942 aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
946 aKeyEvent.mIsRepeat =
947 ([aNativeKeyEvent type] == NSKeyDown) ? [aNativeKeyEvent isARepeat] : false;
949 PR_LOG(gLog, PR_LOG_ALWAYS,
950 ("%p TISInputSourceWrapper::InitKeyEvent, "
951 "shift=%s, ctrl=%s, alt=%s, meta=%s",
952 this, OnOrOff(aKeyEvent.IsShift()), OnOrOff(aKeyEvent.IsControl()),
953 OnOrOff(aKeyEvent.IsAlt()), OnOrOff(aKeyEvent.IsMeta())));
955 if (aKeyEvent.message == NS_KEY_PRESS &&
956 (isPrintableKey || !insertString.IsEmpty())) {
957 InitKeyPressEvent(aNativeKeyEvent,
958 insertString.IsEmpty() ? 0 : insertString[0],
960 MOZ_ASSERT(!aKeyEvent.charCode || !IsControlChar(aKeyEvent.charCode),
961 "charCode must not be a control character");
963 aKeyEvent.charCode = 0;
964 aKeyEvent.isChar = false; // XXX not used in XP level
966 PR_LOG(gLog, PR_LOG_ALWAYS,
967 ("%p TISInputSourceWrapper::InitKeyEvent, keyCode=0x%X charCode=0x0",
968 this, aKeyEvent.keyCode));
971 if (isPrintableKey) {
972 aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
973 // If insertText calls this method, let's use the string.
974 if (aInsertString && !aInsertString->IsEmpty() &&
975 !IsControlChar((*aInsertString)[0])) {
976 aKeyEvent.mKeyValue = *aInsertString;
978 // If meta key is pressed, the printable key layout may be switched from
979 // non-ASCII capable layout to ASCII capable, or from Dvorak to QWERTY.
980 // KeyboardEvent.key value should be the switched layout's character.
981 else if (aKeyEvent.IsMeta()) {
982 nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
983 aKeyEvent.mKeyValue);
985 // If control key is pressed, some keys may produce printable character via
986 // [aNativeKeyEvent characters]. Otherwise, translate input character of
987 // the key without control key.
988 else if (aKeyEvent.IsControl()) {
989 nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
990 aKeyEvent.mKeyValue);
991 if (aKeyEvent.mKeyValue.IsEmpty() ||
992 IsControlChar(aKeyEvent.mKeyValue[0])) {
993 NSUInteger cocoaState =
994 [aNativeKeyEvent modifierFlags] & ~NSControlKeyMask;
995 UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
996 aKeyEvent.mKeyValue =
997 TranslateToChar(nativeKeyCode, carbonState, kbType);
1000 // Otherwise, KeyboardEvent.key expose
1001 // [aNativeKeyEvent characters] value. However, if IME is open and the
1002 // keyboard layout isn't ASCII capable, exposing the non-ASCII character
1003 // doesn't match with other platform's behavior. For the compatibility
1004 // with other platform's Gecko, we need to set a translated character.
1005 else if (IsOpenedIMEMode()) {
1007 nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
1008 aKeyEvent.mKeyValue = TranslateToChar(nativeKeyCode, state, kbType);
1010 nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
1011 aKeyEvent.mKeyValue);
1014 // Last resort. If .key value becomes empty string, we should use
1015 // charactersIgnoringModifiers, if it's available.
1016 if (aKeyEvent.mKeyValue.IsEmpty() ||
1017 IsControlChar(aKeyEvent.mKeyValue[0])) {
1018 nsCocoaUtils::GetStringForNSString(
1019 [aNativeKeyEvent charactersIgnoringModifiers], aKeyEvent.mKeyValue);
1020 // But don't expose it if it's a control character.
1021 if (!aKeyEvent.mKeyValue.IsEmpty() &&
1022 IsControlChar(aKeyEvent.mKeyValue[0])) {
1023 aKeyEvent.mKeyValue.Truncate();
1027 // Compute the key for non-printable keys and some special printable keys.
1028 aKeyEvent.mKeyNameIndex = ComputeGeckoKeyNameIndex(nativeKeyCode);
1031 aKeyEvent.mCodeNameIndex = ComputeGeckoCodeNameIndex(nativeKeyCode);
1032 MOZ_ASSERT(aKeyEvent.mCodeNameIndex != CODE_NAME_INDEX_USE_STRING);
1034 NS_OBJC_END_TRY_ABORT_BLOCK
1038 TISInputSourceWrapper::InitKeyPressEvent(NSEvent *aNativeKeyEvent,
1039 char16_t aInsertChar,
1040 WidgetKeyboardEvent& aKeyEvent,
1043 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1045 NS_ASSERTION(aKeyEvent.message == NS_KEY_PRESS,
1046 "aKeyEvent must be NS_KEY_PRESS event");
1049 if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
1051 nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], chars);
1052 NS_ConvertUTF16toUTF8 utf8Chars(chars);
1053 char16_t expectedChar = static_cast<char16_t>(aInsertChar);
1054 NS_ConvertUTF16toUTF8 utf8ExpectedChar(&expectedChar, 1);
1055 PR_LOG(gLog, PR_LOG_ALWAYS,
1056 ("%p TISInputSourceWrapper::InitKeyPressEvent, aNativeKeyEvent=%p, "
1057 "[aNativeKeyEvent characters]=\"%s\", aInsertChar=0x%X(%s), "
1058 "aKeyEvent.message=%s, aKbType=0x%X, IsOpenedIMEMode()=%s",
1059 this, aNativeKeyEvent, utf8Chars.get(), aInsertChar,
1060 utf8ExpectedChar.get(), GetGeckoKeyEventType(aKeyEvent), aKbType,
1061 TrueOrFalse(IsOpenedIMEMode())));
1063 #endif // #ifdef PR_LOGGING
1065 aKeyEvent.isChar = true; // this is not a special key XXX not used in XP
1066 aKeyEvent.charCode = aInsertChar;
1067 if (aKeyEvent.charCode != 0) {
1068 aKeyEvent.keyCode = 0;
1071 PR_LOG(gLog, PR_LOG_ALWAYS,
1072 ("%p TISInputSourceWrapper::InitKeyPressEvent, "
1073 "aKeyEvent.keyCode=0x%X, aKeyEvent.charCode=0x%X",
1074 this, aKeyEvent.keyCode, aKeyEvent.charCode));
1076 if (!aKeyEvent.IsControl() && !aKeyEvent.IsMeta() && !aKeyEvent.IsAlt()) {
1080 TISInputSourceWrapper USLayout("com.apple.keylayout.US");
1081 bool isRomanKeyboardLayout = IsASCIICapable();
1083 UInt32 key = [aNativeKeyEvent keyCode];
1085 // Caps lock and num lock modifier state:
1086 UInt32 lockState = 0;
1087 if ([aNativeKeyEvent modifierFlags] & NSAlphaShiftKeyMask) {
1088 lockState |= alphaLock;
1090 if ([aNativeKeyEvent modifierFlags] & NSNumericPadKeyMask) {
1091 lockState |= kEventKeyModifierNumLockMask;
1094 PR_LOG(gLog, PR_LOG_ALWAYS,
1095 ("%p TISInputSourceWrapper::InitKeyPressEvent, "
1096 "isRomanKeyboardLayout=%s, key=0x%X",
1097 this, TrueOrFalse(isRomanKeyboardLayout), aKbType, key));
1102 uint32_t unshiftedChar = TranslateToChar(key, lockState, aKbType);
1103 UInt32 shiftLockMod = shiftKey | lockState;
1104 uint32_t shiftedChar = TranslateToChar(key, shiftLockMod, aKbType);
1106 // characters generated with Cmd key
1107 // XXX we should remove CapsLock state, which changes characters from
1108 // Latin to Cyrillic with Russian layout on 10.4 only when Cmd key
1110 UInt32 numState = (lockState & ~alphaLock); // only num lock state
1111 uint32_t uncmdedChar = TranslateToChar(key, numState, aKbType);
1112 UInt32 shiftNumMod = numState | shiftKey;
1113 uint32_t uncmdedShiftChar = TranslateToChar(key, shiftNumMod, aKbType);
1114 uint32_t uncmdedUSChar = USLayout.TranslateToChar(key, numState, aKbType);
1115 UInt32 cmdNumMod = cmdKey | numState;
1116 uint32_t cmdedChar = TranslateToChar(key, cmdNumMod, aKbType);
1117 UInt32 cmdShiftNumMod = shiftKey | cmdNumMod;
1118 uint32_t cmdedShiftChar = TranslateToChar(key, cmdShiftNumMod, aKbType);
1120 // Is the keyboard layout changed by Cmd key?
1121 // E.g., Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
1122 bool isCmdSwitchLayout = uncmdedChar != cmdedChar;
1123 // Is the keyboard layout for Latin, but Cmd key switches the layout?
1124 // I.e., Dvorak-QWERTY
1125 bool isDvorakQWERTY = isCmdSwitchLayout && isRomanKeyboardLayout;
1127 // If the current keyboard is not Dvorak-QWERTY or Cmd is not pressed,
1128 // we should append unshiftedChar and shiftedChar for handling the
1129 // normal characters. These are the characters that the user is most
1130 // likely to associate with this key.
1131 if ((unshiftedChar || shiftedChar) &&
1132 (!aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
1133 AlternativeCharCode altCharCodes(unshiftedChar, shiftedChar);
1134 aKeyEvent.alternativeCharCodes.AppendElement(altCharCodes);
1136 PR_LOG(gLog, PR_LOG_ALWAYS,
1137 ("%p TISInputSourceWrapper::InitKeyPressEvent, "
1138 "aKeyEvent.isMeta=%s, isDvorakQWERTY=%s, "
1139 "unshiftedChar=U+%X, shiftedChar=U+%X",
1140 this, OnOrOff(aKeyEvent.IsMeta()), TrueOrFalse(isDvorakQWERTY),
1141 unshiftedChar, shiftedChar));
1143 // Most keyboard layouts provide the same characters in the NSEvents
1144 // with Command+Shift as with Command. However, with Command+Shift we
1145 // want the character on the second level. e.g. With a US QWERTY
1146 // layout, we want "?" when the "/","?" key is pressed with
1149 // On a German layout, the OS gives us '/' with Cmd+Shift+SS(eszett)
1150 // even though Cmd+SS is 'SS' and Shift+'SS' is '?'. This '/' seems
1151 // like a hack to make the Cmd+"?" event look the same as the Cmd+"?"
1152 // event on a US keyboard. The user thinks they are typing Cmd+"?", so
1153 // we'll prefer the "?" character, replacing charCode with shiftedChar
1154 // when Shift is pressed. However, in case there is a layout where the
1155 // character unique to Cmd+Shift is the character that the user expects,
1156 // we'll send it as an alternative char.
1157 bool hasCmdShiftOnlyChar =
1158 cmdedChar != cmdedShiftChar && uncmdedShiftChar != cmdedShiftChar;
1159 uint32_t originalCmdedShiftChar = cmdedShiftChar;
1161 // If we can make a good guess at the characters that the user would
1162 // expect this key combination to produce (with and without Shift) then
1163 // use those characters. This also corrects for CapsLock, which was
1165 if (!isCmdSwitchLayout) {
1166 // The characters produced with Command seem similar to those without
1168 if (unshiftedChar) {
1169 cmdedChar = unshiftedChar;
1172 cmdedShiftChar = shiftedChar;
1174 } else if (uncmdedUSChar == cmdedChar) {
1175 // It looks like characters from a US layout are provided when Command
1177 uint32_t ch = USLayout.TranslateToChar(key, lockState, aKbType);
1181 ch = USLayout.TranslateToChar(key, shiftLockMod, aKbType);
1183 cmdedShiftChar = ch;
1187 // If the current keyboard layout is switched by the Cmd key,
1188 // we should append cmdedChar and shiftedCmdChar that are
1189 // Latin char for the key.
1190 // If the keyboard layout is Dvorak-QWERTY, we should append them only when
1191 // command key is pressed because when command key isn't pressed, uncmded
1192 // chars have been appended already.
1193 if ((cmdedChar || cmdedShiftChar) && isCmdSwitchLayout &&
1194 (aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
1195 AlternativeCharCode altCharCodes(cmdedChar, cmdedShiftChar);
1196 aKeyEvent.alternativeCharCodes.AppendElement(altCharCodes);
1198 PR_LOG(gLog, PR_LOG_ALWAYS,
1199 ("%p TISInputSourceWrapper::InitKeyPressEvent, "
1200 "hasCmdShiftOnlyChar=%s, isCmdSwitchLayout=%s, isDvorakQWERTY=%s, "
1201 "cmdedChar=U+%X, cmdedShiftChar=U+%X",
1202 this, TrueOrFalse(hasCmdShiftOnlyChar), TrueOrFalse(isDvorakQWERTY),
1203 TrueOrFalse(isDvorakQWERTY), cmdedChar, cmdedShiftChar));
1204 // Special case for 'SS' key of German layout. See the comment of
1205 // hasCmdShiftOnlyChar definition for the detail.
1206 if (hasCmdShiftOnlyChar && originalCmdedShiftChar) {
1207 AlternativeCharCode altCharCodes(0, originalCmdedShiftChar);
1208 aKeyEvent.alternativeCharCodes.AppendElement(altCharCodes);
1210 PR_LOG(gLog, PR_LOG_ALWAYS,
1211 ("%p TISInputSourceWrapper::InitKeyPressEvent, "
1212 "hasCmdShiftOnlyChar=%s, originalCmdedShiftChar=U+%X",
1213 this, TrueOrFalse(hasCmdShiftOnlyChar), originalCmdedShiftChar));
1215 NS_OBJC_END_TRY_ABORT_BLOCK
1219 TISInputSourceWrapper::ComputeGeckoKeyCode(UInt32 aNativeKeyCode,
1223 PR_LOG(gLog, PR_LOG_ALWAYS,
1224 ("%p TISInputSourceWrapper::ComputeGeckoKeyCode, aNativeKeyCode=0x%X, "
1225 "aKbType=0x%X, aCmdIsPressed=%s, IsOpenedIMEMode()=%s, "
1226 "IsASCIICapable()=%s",
1227 this, aNativeKeyCode, aKbType, TrueOrFalse(aCmdIsPressed),
1228 TrueOrFalse(IsOpenedIMEMode()), TrueOrFalse(IsASCIICapable())));
1230 switch (aNativeKeyCode) {
1231 case kVK_Space: return NS_VK_SPACE;
1232 case kVK_Escape: return NS_VK_ESCAPE;
1235 case kVK_RightCommand:
1236 case kVK_Command: return NS_VK_META;
1237 case kVK_RightShift:
1238 case kVK_Shift: return NS_VK_SHIFT;
1239 case kVK_CapsLock: return NS_VK_CAPS_LOCK;
1240 case kVK_RightControl:
1241 case kVK_Control: return NS_VK_CONTROL;
1242 case kVK_RightOption:
1243 case kVK_Option: return NS_VK_ALT;
1245 case kVK_ANSI_KeypadClear: return NS_VK_CLEAR;
1248 case kVK_F1: return NS_VK_F1;
1249 case kVK_F2: return NS_VK_F2;
1250 case kVK_F3: return NS_VK_F3;
1251 case kVK_F4: return NS_VK_F4;
1252 case kVK_F5: return NS_VK_F5;
1253 case kVK_F6: return NS_VK_F6;
1254 case kVK_F7: return NS_VK_F7;
1255 case kVK_F8: return NS_VK_F8;
1256 case kVK_F9: return NS_VK_F9;
1257 case kVK_F10: return NS_VK_F10;
1258 case kVK_F11: return NS_VK_F11;
1259 case kVK_F12: return NS_VK_F12;
1260 // case kVK_F13: return NS_VK_F13; // clash with the 3 below
1261 // case kVK_F14: return NS_VK_F14;
1262 // case kVK_F15: return NS_VK_F15;
1263 case kVK_F16: return NS_VK_F16;
1264 case kVK_F17: return NS_VK_F17;
1265 case kVK_F18: return NS_VK_F18;
1266 case kVK_F19: return NS_VK_F19;
1268 case kVK_PC_Pause: return NS_VK_PAUSE;
1269 case kVK_PC_ScrollLock: return NS_VK_SCROLL_LOCK;
1270 case kVK_PC_PrintScreen: return NS_VK_PRINTSCREEN;
1273 case kVK_ANSI_Keypad0: return NS_VK_NUMPAD0;
1274 case kVK_ANSI_Keypad1: return NS_VK_NUMPAD1;
1275 case kVK_ANSI_Keypad2: return NS_VK_NUMPAD2;
1276 case kVK_ANSI_Keypad3: return NS_VK_NUMPAD3;
1277 case kVK_ANSI_Keypad4: return NS_VK_NUMPAD4;
1278 case kVK_ANSI_Keypad5: return NS_VK_NUMPAD5;
1279 case kVK_ANSI_Keypad6: return NS_VK_NUMPAD6;
1280 case kVK_ANSI_Keypad7: return NS_VK_NUMPAD7;
1281 case kVK_ANSI_Keypad8: return NS_VK_NUMPAD8;
1282 case kVK_ANSI_Keypad9: return NS_VK_NUMPAD9;
1284 case kVK_ANSI_KeypadMultiply: return NS_VK_MULTIPLY;
1285 case kVK_ANSI_KeypadPlus: return NS_VK_ADD;
1286 case kVK_ANSI_KeypadMinus: return NS_VK_SUBTRACT;
1287 case kVK_ANSI_KeypadDecimal: return NS_VK_DECIMAL;
1288 case kVK_ANSI_KeypadDivide: return NS_VK_DIVIDE;
1290 case kVK_JIS_KeypadComma: return NS_VK_SEPARATOR;
1293 case kVK_JIS_Eisu: return NS_VK_EISU;
1294 case kVK_JIS_Kana: return NS_VK_KANA;
1296 // these may clash with forward delete and help
1297 case kVK_PC_Insert: return NS_VK_INSERT;
1298 case kVK_PC_Delete: return NS_VK_DELETE;
1300 case kVK_PC_Backspace: return NS_VK_BACK;
1301 case kVK_Tab: return NS_VK_TAB;
1303 case kVK_Home: return NS_VK_HOME;
1304 case kVK_End: return NS_VK_END;
1306 case kVK_PageUp: return NS_VK_PAGE_UP;
1307 case kVK_PageDown: return NS_VK_PAGE_DOWN;
1309 case kVK_LeftArrow: return NS_VK_LEFT;
1310 case kVK_RightArrow: return NS_VK_RIGHT;
1311 case kVK_UpArrow: return NS_VK_UP;
1312 case kVK_DownArrow: return NS_VK_DOWN;
1314 case kVK_PC_ContextMenu: return NS_VK_CONTEXT_MENU;
1316 case kVK_ANSI_1: return NS_VK_1;
1317 case kVK_ANSI_2: return NS_VK_2;
1318 case kVK_ANSI_3: return NS_VK_3;
1319 case kVK_ANSI_4: return NS_VK_4;
1320 case kVK_ANSI_5: return NS_VK_5;
1321 case kVK_ANSI_6: return NS_VK_6;
1322 case kVK_ANSI_7: return NS_VK_7;
1323 case kVK_ANSI_8: return NS_VK_8;
1324 case kVK_ANSI_9: return NS_VK_9;
1325 case kVK_ANSI_0: return NS_VK_0;
1327 case kVK_ANSI_KeypadEnter:
1329 case kVK_Powerbook_KeypadEnter: return NS_VK_RETURN;
1332 // If Cmd key is pressed, that causes switching keyboard layout temporarily.
1333 // E.g., Dvorak-QWERTY. Therefore, if Cmd key is pressed, we should honor it.
1334 UInt32 modifiers = aCmdIsPressed ? cmdKey : 0;
1336 uint32_t charCode = TranslateToChar(aNativeKeyCode, modifiers, aKbType);
1338 // Special case for Mac. Mac inputs Yen sign (U+00A5) directly instead of
1339 // Back slash (U+005C). We should return NS_VK_BACK_SLASH for compatibility
1340 // with other platforms.
1341 // XXX How about Won sign (U+20A9) which has same problem as Yen sign?
1342 if (charCode == 0x00A5) {
1343 return NS_VK_BACK_SLASH;
1346 uint32_t keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
1351 // If the unshifed char isn't an ASCII character, use shifted char.
1352 charCode = TranslateToChar(aNativeKeyCode, modifiers | shiftKey, aKbType);
1353 keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
1358 // If this is ASCII capable, give up to compute it.
1359 if (IsASCIICapable()) {
1363 // Retry with ASCII capable keyboard layout.
1364 TISInputSourceWrapper currentKeyboardLayout;
1365 currentKeyboardLayout.InitByCurrentASCIICapableKeyboardLayout();
1366 NS_ENSURE_TRUE(mInputSource != currentKeyboardLayout.mInputSource, 0);
1367 keyCode = currentKeyboardLayout.ComputeGeckoKeyCode(aNativeKeyCode, aKbType,
1370 // However, if keyCode isn't for an alphabet keys or a numeric key, we should
1371 // ignore it. For example, comma key of Thai layout is same as close-square-
1372 // bracket key of US layout and an unicode character key of Thai layout is
1373 // same as comma key of US layout. If we return NS_VK_COMMA for latter key,
1374 // web application developers cannot distinguish with the former key.
1375 return ((keyCode >= NS_VK_A && keyCode <= NS_VK_Z) ||
1376 (keyCode >= NS_VK_0 && keyCode <= NS_VK_9)) ? keyCode : 0;
1381 TISInputSourceWrapper::ComputeGeckoKeyNameIndex(UInt32 aNativeKeyCode)
1384 // When unsupported keys like Convert, Nonconvert of Japanese keyboard is
1386 // on 10.6.x, 'A' key event is fired (and also actually 'a' is inserted).
1387 // on 10.7.x, Nothing happens.
1388 // on 10.8.x, Nothing happens.
1389 // on 10.9.x, FlagsChanged event is fired with keyCode 0xFF.
1390 switch (aNativeKeyCode) {
1392 #define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
1393 case aNativeKey: return aKeyNameIndex;
1395 #include "NativeKeyToDOMKeyName.h"
1397 #undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
1400 return KEY_NAME_INDEX_Unidentified;
1406 TISInputSourceWrapper::ComputeGeckoCodeNameIndex(UInt32 aNativeKeyCode)
1408 switch (aNativeKeyCode) {
1410 #define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
1411 case aNativeKey: return aCodeNameIndex;
1413 #include "NativeKeyToDOMCodeName.h"
1415 #undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
1418 return CODE_NAME_INDEX_UNKNOWN;
1426 /******************************************************************************
1428 * TextInputHandler implementation (static methods)
1430 ******************************************************************************/
1432 NSUInteger TextInputHandler::sLastModifierState = 0;
1436 TextInputHandler::CreateAllKeyboardLayoutList()
1438 const void* keys[] = { kTISPropertyInputSourceType };
1439 const void* values[] = { kTISTypeKeyboardLayout };
1440 CFDictionaryRef filter =
1441 ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
1442 NS_ASSERTION(filter, "failed to create the filter");
1443 CFArrayRef list = ::TISCreateInputSourceList(filter, true);
1444 ::CFRelease(filter);
1450 TextInputHandler::DebugPrintAllKeyboardLayouts()
1453 if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
1454 CFArrayRef list = CreateAllKeyboardLayoutList();
1455 PR_LOG(gLog, PR_LOG_ALWAYS, ("Keyboard layout configuration:"));
1456 CFIndex idx = ::CFArrayGetCount(list);
1457 TISInputSourceWrapper tis;
1458 for (CFIndex i = 0; i < idx; ++i) {
1459 TISInputSourceRef inputSource = static_cast<TISInputSourceRef>(
1460 const_cast<void *>(::CFArrayGetValueAtIndex(list, i)));
1461 tis.InitByTISInputSourceRef(inputSource);
1462 nsAutoString name, isid;
1463 tis.GetLocalizedName(name);
1464 tis.GetInputSourceID(isid);
1465 PR_LOG(gLog, PR_LOG_ALWAYS,
1467 NS_ConvertUTF16toUTF8(name).get(),
1468 NS_ConvertUTF16toUTF8(isid).get(),
1469 tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
1470 tis.IsKeyboardLayout() && tis.GetUCKeyboardLayout() ?
1471 "" : "\t(uchr is NOT AVAILABLE)"));
1475 #endif // #ifdef PR_LOGGING
1482 /******************************************************************************
1484 * TextInputHandler implementation
1486 ******************************************************************************/
1488 TextInputHandler::TextInputHandler(nsChildView* aWidget,
1489 NSView<mozView> *aNativeView) :
1490 IMEInputHandler(aWidget, aNativeView)
1493 [mView installTextInputHandler:this];
1496 TextInputHandler::~TextInputHandler()
1498 [mView uninstallTextInputHandler];
1502 TextInputHandler::HandleKeyDownEvent(NSEvent* aNativeEvent)
1504 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
1507 PR_LOG(gLog, PR_LOG_ALWAYS,
1508 ("%p TextInputHandler::HandleKeyDownEvent, "
1509 "widget has been already destroyed", this));
1513 PR_LOG(gLog, PR_LOG_ALWAYS,
1514 ("%p TextInputHandler::HandleKeyDownEvent, aNativeEvent=%p, "
1515 "type=%s, keyCode=%lld (0x%X), modifierFlags=0x%X, characters=\"%s\", "
1516 "charactersIgnoringModifiers=\"%s\"",
1517 this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
1518 [aNativeEvent keyCode], [aNativeEvent keyCode],
1519 [aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]),
1520 GetCharacters([aNativeEvent charactersIgnoringModifiers])));
1522 nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
1524 KeyEventState* currentKeyEvent = PushKeyEvent(aNativeEvent);
1525 AutoKeyEventStateCleaner remover(this);
1527 ComplexTextInputPanel* ctiPanel = ComplexTextInputPanel::GetSharedComplexTextInputPanel();
1528 if (ctiPanel && ctiPanel->IsInComposition()) {
1529 nsAutoString committed;
1530 ctiPanel->InterpretKeyEvent(aNativeEvent, committed);
1531 if (!committed.IsEmpty()) {
1532 WidgetKeyboardEvent imeEvent(true, NS_KEY_DOWN, mWidget);
1533 InitKeyEvent(aNativeEvent, imeEvent);
1534 imeEvent.mPluginTextEventString.Assign(committed);
1535 DispatchEvent(imeEvent);
1540 if (mWidget->IsPluginFocused() || !IsIMEComposing()) {
1541 NSResponder* firstResponder = [[mView window] firstResponder];
1543 WidgetKeyboardEvent keydownEvent(true, NS_KEY_DOWN, mWidget);
1544 InitKeyEvent(aNativeEvent, keydownEvent);
1546 currentKeyEvent->mKeyDownHandled = DispatchEvent(keydownEvent);
1548 PR_LOG(gLog, PR_LOG_ALWAYS,
1549 ("%p TextInputHandler::HandleKeyDownEvent, "
1550 "widget was destroyed by keydown event", this));
1551 return currentKeyEvent->IsDefaultPrevented();
1554 // The key down event may have shifted the focus, in which
1555 // case we should not fire the key press.
1556 // XXX This is a special code only on Cocoa widget, why is this needed?
1557 if (firstResponder != [[mView window] firstResponder]) {
1558 PR_LOG(gLog, PR_LOG_ALWAYS,
1559 ("%p TextInputHandler::HandleKeyDownEvent, "
1560 "view lost focus by keydown event", this));
1561 return currentKeyEvent->IsDefaultPrevented();
1564 if (currentKeyEvent->IsDefaultPrevented()) {
1565 PR_LOG(gLog, PR_LOG_ALWAYS,
1566 ("%p TextInputHandler::HandleKeyDownEvent, "
1567 "keydown event's default is prevented", this));
1572 // None of what follows is needed for plugin keyboard input. In fact it
1573 // may cause trouble -- for example the call to [mView interpretKeyEvents:]
1574 // can, in e10s mode, cause each key typed to appear twice in an IME
1576 if (mWidget->IsPluginFocused()) {
1580 // Let Cocoa interpret the key events, caching IsIMEComposing first.
1581 bool wasComposing = IsIMEComposing();
1582 bool interpretKeyEventsCalled = false;
1583 if (IsIMEEnabled() || IsASCIICapableOnly()) {
1584 PR_LOG(gLog, PR_LOG_ALWAYS,
1585 ("%p TextInputHandler::HandleKeyDownEvent, calling interpretKeyEvents",
1587 [mView interpretKeyEvents:[NSArray arrayWithObject:aNativeEvent]];
1588 interpretKeyEventsCalled = true;
1589 PR_LOG(gLog, PR_LOG_ALWAYS,
1590 ("%p TextInputHandler::HandleKeyDownEvent, called interpretKeyEvents",
1595 PR_LOG(gLog, PR_LOG_ALWAYS,
1596 ("%p TextInputHandler::HandleKeyDownEvent, widget was destroyed",
1598 return currentKeyEvent->IsDefaultPrevented();
1601 PR_LOG(gLog, PR_LOG_ALWAYS,
1602 ("%p TextInputHandler::HandleKeyDownEvent, wasComposing=%s, "
1603 "IsIMEComposing()=%s",
1604 this, TrueOrFalse(wasComposing), TrueOrFalse(IsIMEComposing())));
1606 if (currentKeyEvent->CanDispatchKeyPressEvent() &&
1607 !wasComposing && !IsIMEComposing()) {
1608 WidgetKeyboardEvent keypressEvent(true, NS_KEY_PRESS, mWidget);
1609 InitKeyEvent(aNativeEvent, keypressEvent);
1611 // If we called interpretKeyEvents and this isn't normal character input
1612 // then IME probably ate the event for some reason. We do not want to
1613 // send a key press event in that case.
1615 // There are some other cases which IME eats the current event.
1616 // 1. If key events were nested during calling interpretKeyEvents, it means
1617 // that IME did something. Then, we should do nothing.
1618 // 2. If one or more commands are called like "deleteBackward", we should
1619 // dispatch keypress event at that time. Note that the command may have
1620 // been a converted or generated action by IME. Then, we shouldn't do
1621 // our default action for this key.
1622 if (!(interpretKeyEventsCalled &&
1623 IsNormalCharInputtingEvent(keypressEvent))) {
1624 currentKeyEvent->mKeyPressHandled = DispatchEvent(keypressEvent);
1625 currentKeyEvent->mKeyPressDispatched = true;
1626 PR_LOG(gLog, PR_LOG_ALWAYS,
1627 ("%p TextInputHandler::HandleKeyDownEvent, keypress event dispatched",
1632 // Note: mWidget might have become null here. Don't count on it from here on.
1634 PR_LOG(gLog, PR_LOG_ALWAYS,
1635 ("%p TextInputHandler::HandleKeyDownEvent, "
1636 "keydown handled=%s, keypress handled=%s, causedOtherKeyEvents=%s",
1637 this, TrueOrFalse(currentKeyEvent->mKeyDownHandled),
1638 TrueOrFalse(currentKeyEvent->mKeyPressHandled),
1639 TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents)));
1640 return currentKeyEvent->IsDefaultPrevented();
1642 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
1646 TextInputHandler::HandleKeyUpEvent(NSEvent* aNativeEvent)
1648 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1650 PR_LOG(gLog, PR_LOG_ALWAYS,
1651 ("%p TextInputHandler::HandleKeyUpEvent, aNativeEvent=%p, "
1652 "type=%s, keyCode=%lld (0x%X), modifierFlags=0x%X, characters=\"%s\", "
1653 "charactersIgnoringModifiers=\"%s\", "
1654 "IsIMEComposing()=%s",
1655 this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
1656 [aNativeEvent keyCode], [aNativeEvent keyCode],
1657 [aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]),
1658 GetCharacters([aNativeEvent charactersIgnoringModifiers]),
1659 TrueOrFalse(IsIMEComposing())));
1662 PR_LOG(gLog, PR_LOG_ALWAYS,
1663 ("%p TextInputHandler::HandleKeyUpEvent, "
1664 "widget has been already destroyed", this));
1668 // if we don't have any characters we can't generate a keyUp event
1669 if (IsIMEComposing()) {
1673 WidgetKeyboardEvent keyupEvent(true, NS_KEY_UP, mWidget);
1674 InitKeyEvent(aNativeEvent, keyupEvent);
1676 DispatchEvent(keyupEvent);
1678 NS_OBJC_END_TRY_ABORT_BLOCK;
1682 TextInputHandler::HandleFlagsChanged(NSEvent* aNativeEvent)
1684 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1687 PR_LOG(gLog, PR_LOG_ALWAYS,
1688 ("%p TextInputHandler::HandleFlagsChanged, "
1689 "widget has been already destroyed", this));
1693 nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
1695 PR_LOG(gLog, PR_LOG_ALWAYS,
1696 ("%p TextInputHandler::HandleFlagsChanged, aNativeEvent=%p, "
1697 "type=%s, keyCode=%s (0x%X), modifierFlags=0x%08X, "
1698 "sLastModifierState=0x%08X, IsIMEComposing()=%s",
1699 this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
1700 GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
1701 [aNativeEvent modifierFlags], sLastModifierState,
1702 TrueOrFalse(IsIMEComposing())));
1704 MOZ_ASSERT([aNativeEvent type] == NSFlagsChanged);
1706 NSUInteger diff = [aNativeEvent modifierFlags] ^ sLastModifierState;
1707 // Device dependent flags for left-control key, both shift keys, both command
1708 // keys and both option keys have been defined in Next's SDK. But we
1709 // shouldn't use it directly as far as possible since Cocoa SDK doesn't
1710 // define them. Fortunately, we need them only when we dispatch keyup
1711 // events. So, we can usually know the actual relation between keyCode and
1712 // device dependent flags. However, we need to remove following flags first
1713 // since the differences don't indicate modifier key state.
1714 // NX_STYLUSPROXIMITYMASK: Probably used for pen like device.
1715 // kCGEventFlagMaskNonCoalesced (= NX_NONCOALSESCEDMASK): See the document for
1716 // Quartz Event Services.
1717 diff &= ~(NX_STYLUSPROXIMITYMASK | kCGEventFlagMaskNonCoalesced);
1719 switch ([aNativeEvent keyCode]) {
1720 // CapsLock state and other modifier states are different:
1721 // CapsLock state does not revert when the CapsLock key goes up, as the
1722 // modifier state does for other modifier keys on key up.
1723 case kVK_CapsLock: {
1724 // Fire key down event for caps lock.
1725 DispatchKeyEventForFlagsChanged(aNativeEvent, true);
1726 // XXX should we fire keyup event too? The keyup event for CapsLock key
1727 // is never dispatched on Gecko.
1728 // XXX WebKit dispatches keydown event when CapsLock is locked, otherwise,
1729 // keyup event. If we do so, we cannot keep the consistency with other
1730 // platform's behavior...
1734 // If the event is caused by pressing or releasing a modifier key, just
1735 // dispatch the key's event.
1737 case kVK_RightShift:
1739 case kVK_RightCommand:
1741 case kVK_RightControl:
1743 case kVK_RightOption:
1745 // We assume that at most one modifier is changed per event if the event
1746 // is caused by pressing or releasing a modifier key.
1747 bool isKeyDown = ([aNativeEvent modifierFlags] & diff) != 0;
1748 DispatchKeyEventForFlagsChanged(aNativeEvent, isKeyDown);
1749 // XXX Some applications might send the event with incorrect device-
1751 if (isKeyDown && ((diff & ~NSDeviceIndependentModifierFlagsMask) != 0)) {
1752 unsigned short keyCode = [aNativeEvent keyCode];
1753 const ModifierKey* modifierKey =
1754 GetModifierKeyForDeviceDependentFlags(diff);
1755 if (modifierKey && modifierKey->keyCode != keyCode) {
1756 // Although, we're not sure the actual cause of this case, the stored
1757 // modifier information and the latest key event information may be
1758 // mismatched. Then, let's reset the stored information.
1759 // NOTE: If this happens, it may fail to handle NSFlagsChanged event
1760 // in the default case (below). However, it's the rare case handler
1761 // and this case occurs rarely. So, we can ignore the edge case bug.
1762 NS_WARNING("Resetting stored modifier key information");
1763 mModifierKeys.Clear();
1764 modifierKey = nullptr;
1767 mModifierKeys.AppendElement(ModifierKey(diff, keyCode));
1773 // Currently we don't support Fn key since other browsers don't dispatch
1774 // events for it and we don't have keyCode for this key.
1775 // It should be supported when we implement .key and .char.
1779 // If the event is caused by something else than pressing or releasing a
1780 // single modifier key (for example by the app having been deactivated
1781 // using command-tab), use the modifiers themselves to determine which
1782 // key's event to dispatch, and whether it's a keyup or keydown event.
1783 // In all cases we assume one or more modifiers are being deactivated
1784 // (never activated) -- otherwise we'd have received one or more events
1785 // corresponding to a single modifier key being pressed.
1787 NSUInteger modifiers = sLastModifierState;
1788 for (int32_t bit = 0; bit < 32; ++bit) {
1789 NSUInteger flag = 1 << bit;
1790 if (!(diff & flag)) {
1794 // Given correct information from the application, a flag change here
1795 // will normally be a deactivation (except for some lockable modifiers
1796 // such as CapsLock). But some applications (like VNC) can send an
1797 // activating event with a zero keyCode. So we need to check for that
1799 bool dispatchKeyDown = ((flag & [aNativeEvent modifierFlags]) != 0);
1801 unsigned short keyCode = 0;
1802 if (flag & NSDeviceIndependentModifierFlagsMask) {
1804 case NSAlphaShiftKeyMask:
1805 keyCode = kVK_CapsLock;
1806 dispatchKeyDown = true;
1809 case NSNumericPadKeyMask:
1810 // NSNumericPadKeyMask is fired by VNC a lot. But not all of
1811 // these events can really be Clear key events, so we just ignore
1819 case NSFunctionKeyMask:
1820 // An NSFunctionKeyMask change here will normally be a
1821 // deactivation. But sometimes it will be an activation send (by
1822 // VNC for example) with a zero keyCode.
1825 // These cases (NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask
1826 // and NSCommandKeyMask) should be handled by the other branch of
1827 // the if statement, below (which handles device dependent flags).
1828 // However, some applications (like VNC) can send key events without
1829 // any device dependent flags, so we handle them here instead.
1830 case NSShiftKeyMask:
1831 keyCode = (modifiers & 0x0004) ? kVK_RightShift : kVK_Shift;
1833 case NSControlKeyMask:
1834 keyCode = (modifiers & 0x2000) ? kVK_RightControl : kVK_Control;
1836 case NSAlternateKeyMask:
1837 keyCode = (modifiers & 0x0040) ? kVK_RightOption : kVK_Option;
1839 case NSCommandKeyMask:
1840 keyCode = (modifiers & 0x0010) ? kVK_RightCommand : kVK_Command;
1847 const ModifierKey* modifierKey =
1848 GetModifierKeyForDeviceDependentFlags(flag);
1850 // See the note above (in the other branch of the if statement)
1851 // about the NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask
1852 // and NSCommandKeyMask cases.
1855 keyCode = modifierKey->keyCode;
1862 const ModifierKey* modifierKey =
1863 GetModifierKeyForNativeKeyCode(kVK_RightShift);
1865 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1866 modifiers &= ~NSShiftKeyMask;
1870 case kVK_RightShift: {
1871 const ModifierKey* modifierKey =
1872 GetModifierKeyForNativeKeyCode(kVK_Shift);
1874 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1875 modifiers &= ~NSShiftKeyMask;
1880 const ModifierKey* modifierKey =
1881 GetModifierKeyForNativeKeyCode(kVK_RightCommand);
1883 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1884 modifiers &= ~NSCommandKeyMask;
1888 case kVK_RightCommand: {
1889 const ModifierKey* modifierKey =
1890 GetModifierKeyForNativeKeyCode(kVK_Command);
1892 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1893 modifiers &= ~NSCommandKeyMask;
1898 const ModifierKey* modifierKey =
1899 GetModifierKeyForNativeKeyCode(kVK_RightControl);
1901 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1902 modifiers &= ~NSControlKeyMask;
1906 case kVK_RightControl: {
1907 const ModifierKey* modifierKey =
1908 GetModifierKeyForNativeKeyCode(kVK_Control);
1910 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1911 modifiers &= ~NSControlKeyMask;
1916 const ModifierKey* modifierKey =
1917 GetModifierKeyForNativeKeyCode(kVK_RightOption);
1919 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1920 modifiers &= ~NSAlternateKeyMask;
1924 case kVK_RightOption: {
1925 const ModifierKey* modifierKey =
1926 GetModifierKeyForNativeKeyCode(kVK_Option);
1928 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
1929 modifiers &= ~NSAlternateKeyMask;
1934 modifiers &= ~NSHelpKeyMask;
1941 [NSEvent keyEventWithType:NSFlagsChanged
1942 location:[aNativeEvent locationInWindow]
1943 modifierFlags:modifiers
1944 timestamp:[aNativeEvent timestamp]
1945 windowNumber:[aNativeEvent windowNumber]
1946 context:[aNativeEvent context]
1948 charactersIgnoringModifiers:nil
1951 DispatchKeyEventForFlagsChanged(event, dispatchKeyDown);
1956 // Stop if focus has changed.
1957 // Check to see if mView is still the first responder.
1958 if (![mView isFirstResponder]) {
1967 // Be aware, the widget may have been destroyed.
1968 sLastModifierState = [aNativeEvent modifierFlags];
1970 NS_OBJC_END_TRY_ABORT_BLOCK;
1973 const TextInputHandler::ModifierKey*
1974 TextInputHandler::GetModifierKeyForNativeKeyCode(unsigned short aKeyCode) const
1976 for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) {
1977 if (mModifierKeys[i].keyCode == aKeyCode) {
1978 return &((ModifierKey&)mModifierKeys[i]);
1984 const TextInputHandler::ModifierKey*
1985 TextInputHandler::GetModifierKeyForDeviceDependentFlags(NSUInteger aFlags) const
1987 for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) {
1988 if (mModifierKeys[i].GetDeviceDependentFlags() ==
1989 (aFlags & ~NSDeviceIndependentModifierFlagsMask)) {
1990 return &((ModifierKey&)mModifierKeys[i]);
1997 TextInputHandler::DispatchKeyEventForFlagsChanged(NSEvent* aNativeEvent,
1998 bool aDispatchKeyDown)
2000 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2006 PR_LOG(gLog, PR_LOG_ALWAYS,
2007 ("%p TextInputHandler::DispatchKeyEventForFlagsChanged, aNativeEvent=%p, "
2008 "type=%s, keyCode=%s (0x%X), aDispatchKeyDown=%s, IsIMEComposing()=%s",
2009 this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
2010 GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
2011 TrueOrFalse(aDispatchKeyDown), TrueOrFalse(IsIMEComposing())));
2013 if ([aNativeEvent type] != NSFlagsChanged || IsIMEComposing()) {
2017 uint32_t message = aDispatchKeyDown ? NS_KEY_DOWN : NS_KEY_UP;
2019 // Fire a key event.
2020 WidgetKeyboardEvent keyEvent(true, message, mWidget);
2021 InitKeyEvent(aNativeEvent, keyEvent);
2023 // Attach a plugin event, in case keyEvent gets dispatched to a plugin. Only
2024 // one field is needed -- the type. The other fields can be constructed as
2025 // the need arises. But Gecko doesn't have anything equivalent to the
2026 // NPCocoaEventFlagsChanged type, and this needs to be passed accurately to
2027 // any plugin to which this event is sent.
2028 NPCocoaEvent cocoaEvent;
2029 nsCocoaUtils::InitNPCocoaEvent(&cocoaEvent);
2030 cocoaEvent.type = NPCocoaEventFlagsChanged;
2031 keyEvent.mPluginEvent.Copy(cocoaEvent);
2033 DispatchEvent(keyEvent);
2035 NS_OBJC_END_TRY_ABORT_BLOCK;
2039 TextInputHandler::InsertText(NSAttributedString* aAttrString,
2040 NSRange* aReplacementRange)
2042 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2048 KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
2050 PR_LOG(gLog, PR_LOG_ALWAYS,
2051 ("%p TextInputHandler::InsertText, aAttrString=\"%s\", "
2052 "aReplacementRange=%p { location=%llu, length=%llu }, "
2053 "IsIMEComposing()=%s, IgnoreIMEComposition()=%s, "
2054 "keyevent=%p, keydownHandled=%s, keypressDispatched=%s, "
2055 "causedOtherKeyEvents=%s",
2056 this, GetCharacters([aAttrString string]), aReplacementRange,
2057 aReplacementRange ? aReplacementRange->location : 0,
2058 aReplacementRange ? aReplacementRange->length : 0,
2059 TrueOrFalse(IsIMEComposing()), TrueOrFalse(IgnoreIMEComposition()),
2060 currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
2062 TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
2064 TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
2066 TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A"));
2068 if (IgnoreIMEComposition()) {
2072 InputContext context = mWidget->GetInputContext();
2073 bool isEditable = (context.mIMEState.mEnabled == IMEState::ENABLED ||
2074 context.mIMEState.mEnabled == IMEState::PASSWORD);
2075 NSRange selectedRange = SelectedRange();
2078 nsCocoaUtils::GetStringForNSString([aAttrString string], str);
2079 if (!IsIMEComposing() && str.IsEmpty()) {
2080 // nothing to do if there is no content which can be removed.
2084 // If replacement range is specified, we need to remove the range.
2085 // Otherwise, we need to remove the selected range if it's not collapsed.
2086 if (aReplacementRange && aReplacementRange->location != NSNotFound) {
2087 // nothing to do since the range is collapsed.
2088 if (aReplacementRange->length == 0) {
2091 // If the replacement range is different from current selected range,
2092 // select the range.
2093 if (!NSEqualRanges(selectedRange, *aReplacementRange)) {
2094 NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
2096 selectedRange = SelectedRange();
2098 NS_ENSURE_TRUE_VOID(selectedRange.location != NSNotFound);
2099 if (selectedRange.length == 0) {
2100 return; // nothing to do
2102 // If this is caused by a key input, the keypress event which will be
2103 // dispatched later should cause the delete. Therefore, nothing to do here.
2104 // Although, we're not sure if such case is actually possible.
2105 if (!currentKeyEvent) {
2108 // Delete the selected range.
2109 nsRefPtr<TextInputHandler> kungFuDeathGrip(this);
2110 WidgetContentCommandEvent deleteCommandEvent(true,
2111 NS_CONTENT_COMMAND_DELETE,
2113 DispatchEvent(deleteCommandEvent);
2114 NS_ENSURE_TRUE_VOID(deleteCommandEvent.mSucceeded);
2115 // Be aware! The widget might be destroyed here.
2119 if (str.Length() != 1 || IsIMEComposing()) {
2120 InsertTextAsCommittingComposition(aAttrString, aReplacementRange);
2124 // Don't let the same event be fired twice when hitting
2125 // enter/return! (Bug 420502)
2126 if (currentKeyEvent && !currentKeyEvent->CanDispatchKeyPressEvent()) {
2130 nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
2132 // If the replacement range is specified, select the range. Then, the
2133 // selection will be replaced by the later keypress event.
2135 aReplacementRange && aReplacementRange->location != NSNotFound &&
2136 !NSEqualRanges(selectedRange, *aReplacementRange)) {
2137 NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
2140 // Dispatch keypress event with char instead of compositionchange event
2141 WidgetKeyboardEvent keypressEvent(true, NS_KEY_PRESS, mWidget);
2142 keypressEvent.isChar = IsPrintableChar(str.CharAt(0));
2144 // Don't set other modifiers from the current event, because here in
2145 // -insertText: they've already been taken into account in creating
2146 // the input string.
2148 if (currentKeyEvent) {
2149 NSEvent* keyEvent = currentKeyEvent->mKeyEvent;
2150 InitKeyEvent(keyEvent, keypressEvent, &str);
2152 nsCocoaUtils::InitInputEvent(keypressEvent, static_cast<NSEvent*>(nullptr));
2153 if (keypressEvent.isChar) {
2154 keypressEvent.charCode = str.CharAt(0);
2156 // Note that insertText is not called only at key pressing.
2157 if (!keypressEvent.charCode) {
2158 keypressEvent.keyCode =
2159 WidgetUtils::ComputeKeyCodeFromChar(keypressEvent.charCode);
2163 // Remove basic modifiers from keypress event because if they are included,
2164 // nsPlaintextEditor ignores the event.
2165 keypressEvent.modifiers &= ~(MODIFIER_CONTROL |
2170 // If mCurrentKeyEvent.mKeyEvent is null and when we implement textInput
2171 // event of DOM3 Events, we should dispatch it instead of keypress event.
2172 bool keyPressHandled = DispatchEvent(keypressEvent);
2174 // Note: mWidget might have become null here. Don't count on it from here on.
2176 if (currentKeyEvent) {
2177 currentKeyEvent->mKeyPressHandled = keyPressHandled;
2178 currentKeyEvent->mKeyPressDispatched = true;
2181 NS_OBJC_END_TRY_ABORT_BLOCK;
2185 TextInputHandler::DoCommandBySelector(const char* aSelector)
2187 nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
2189 KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
2191 PR_LOG(gLog, PR_LOG_ALWAYS,
2192 ("%p TextInputHandler::DoCommandBySelector, aSelector=\"%s\", "
2193 "Destroyed()=%s, keydownHandled=%s, keypressHandled=%s, "
2194 "causedOtherKeyEvents=%s",
2195 this, aSelector ? aSelector : "", TrueOrFalse(Destroyed()),
2197 TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
2199 TrueOrFalse(currentKeyEvent->mKeyPressHandled) : "N/A",
2201 TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A"));
2203 if (currentKeyEvent && currentKeyEvent->CanDispatchKeyPressEvent()) {
2204 WidgetKeyboardEvent keypressEvent(true, NS_KEY_PRESS, mWidget);
2205 InitKeyEvent(currentKeyEvent->mKeyEvent, keypressEvent);
2206 currentKeyEvent->mKeyPressHandled = DispatchEvent(keypressEvent);
2207 currentKeyEvent->mKeyPressDispatched = true;
2208 PR_LOG(gLog, PR_LOG_ALWAYS,
2209 ("%p TextInputHandler::DoCommandBySelector, keypress event "
2210 "dispatched, Destroyed()=%s, keypressHandled=%s",
2211 this, TrueOrFalse(Destroyed()),
2212 TrueOrFalse(currentKeyEvent->mKeyPressHandled)));
2215 return (!Destroyed() && currentKeyEvent &&
2216 currentKeyEvent->IsDefaultPrevented());
2223 /******************************************************************************
2225 * IMEInputHandler implementation (static methods)
2227 ******************************************************************************/
2229 bool IMEInputHandler::sStaticMembersInitialized = false;
2230 CFStringRef IMEInputHandler::sLatestIMEOpenedModeInputSourceID = nullptr;
2231 IMEInputHandler* IMEInputHandler::sFocusedIMEHandler = nullptr;
2235 IMEInputHandler::InitStaticMembers()
2237 if (sStaticMembersInitialized)
2239 sStaticMembersInitialized = true;
2240 // We need to check the keyboard layout changes on all applications.
2241 CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
2242 // XXX Don't we need to remove the observer at shut down?
2243 // Mac Dev Center's document doesn't say how to remove the observer if
2244 // the second parameter is NULL.
2245 ::CFNotificationCenterAddObserver(center, NULL,
2246 OnCurrentTextInputSourceChange,
2247 kTISNotifySelectedKeyboardInputSourceChanged, NULL,
2248 CFNotificationSuspensionBehaviorDeliverImmediately);
2249 // Initiailize with the current keyboard layout
2250 OnCurrentTextInputSourceChange(NULL, NULL,
2251 kTISNotifySelectedKeyboardInputSourceChanged,
2257 IMEInputHandler::OnCurrentTextInputSourceChange(CFNotificationCenterRef aCenter,
2260 const void* aObject,
2261 CFDictionaryRef aUserInfo)
2263 // Cache the latest IME opened mode to sLatestIMEOpenedModeInputSourceID.
2264 TISInputSourceWrapper tis;
2265 tis.InitByCurrentInputSource();
2266 if (tis.IsOpenedIMEMode()) {
2267 tis.GetInputSourceID(sLatestIMEOpenedModeInputSourceID);
2271 if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
2272 static CFStringRef sLastTIS = nullptr;
2274 tis.GetInputSourceID(newTIS);
2276 ::CFStringCompare(sLastTIS, newTIS, 0) != kCFCompareEqualTo) {
2277 TISInputSourceWrapper tis1, tis2, tis3, tis4, tis5;
2278 tis1.InitByCurrentKeyboardLayout();
2279 tis2.InitByCurrentASCIICapableInputSource();
2280 tis3.InitByCurrentASCIICapableKeyboardLayout();
2281 tis4.InitByCurrentInputMethodKeyboardLayoutOverride();
2282 tis5.InitByTISInputSourceRef(tis.GetKeyboardLayoutInputSource());
2283 CFStringRef is0 = nullptr, is1 = nullptr, is2 = nullptr, is3 = nullptr,
2284 is4 = nullptr, is5 = nullptr, type0 = nullptr,
2285 lang0 = nullptr, bundleID0 = nullptr;
2286 tis.GetInputSourceID(is0);
2287 tis1.GetInputSourceID(is1);
2288 tis2.GetInputSourceID(is2);
2289 tis3.GetInputSourceID(is3);
2290 tis4.GetInputSourceID(is4);
2291 tis5.GetInputSourceID(is5);
2292 tis.GetInputSourceType(type0);
2293 tis.GetPrimaryLanguage(lang0);
2294 tis.GetBundleID(bundleID0);
2296 PR_LOG(gLog, PR_LOG_ALWAYS,
2297 ("IMEInputHandler::OnCurrentTextInputSourceChange,\n"
2298 " Current Input Source is changed to:\n"
2299 " currentInputContext=%p\n"
2302 " overridden keyboard layout=%s\n"
2303 " used keyboard layout for translation=%s\n"
2304 " primary language=%s\n"
2306 " current ASCII capable Input Source=%s\n"
2307 " current Keyboard Layout=%s\n"
2308 " current ASCII capable Keyboard Layout=%s",
2309 [NSTextInputContext currentInputContext], GetCharacters(is0),
2310 GetCharacters(type0), tis.IsASCIICapable() ? "- ASCII capable " : "",
2311 GetCharacters(is4), GetCharacters(is5),
2312 GetCharacters(lang0), GetCharacters(bundleID0),
2313 GetCharacters(is2), GetCharacters(is1), GetCharacters(is3)));
2317 #endif // #ifdef PR_LOGGING
2322 IMEInputHandler::FlushPendingMethods(nsITimer* aTimer, void* aClosure)
2324 NS_ASSERTION(aClosure, "aClosure is null");
2325 static_cast<IMEInputHandler*>(aClosure)->ExecutePendingMethods();
2330 IMEInputHandler::CreateAllIMEModeList()
2332 const void* keys[] = { kTISPropertyInputSourceType };
2333 const void* values[] = { kTISTypeKeyboardInputMode };
2334 CFDictionaryRef filter =
2335 ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
2336 NS_ASSERTION(filter, "failed to create the filter");
2337 CFArrayRef list = ::TISCreateInputSourceList(filter, true);
2338 ::CFRelease(filter);
2344 IMEInputHandler::DebugPrintAllIMEModes()
2347 if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
2348 CFArrayRef list = CreateAllIMEModeList();
2349 PR_LOG(gLog, PR_LOG_ALWAYS, ("IME mode configuration:"));
2350 CFIndex idx = ::CFArrayGetCount(list);
2351 TISInputSourceWrapper tis;
2352 for (CFIndex i = 0; i < idx; ++i) {
2353 TISInputSourceRef inputSource = static_cast<TISInputSourceRef>(
2354 const_cast<void *>(::CFArrayGetValueAtIndex(list, i)));
2355 tis.InitByTISInputSourceRef(inputSource);
2356 nsAutoString name, isid;
2357 tis.GetLocalizedName(name);
2358 tis.GetInputSourceID(isid);
2359 PR_LOG(gLog, PR_LOG_ALWAYS,
2361 NS_ConvertUTF16toUTF8(name).get(),
2362 NS_ConvertUTF16toUTF8(isid).get(),
2363 tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
2364 tis.IsEnabled() ? "" : "\t(Isn't Enabled)"));
2368 #endif // #ifdef PR_LOGGING
2373 IMEInputHandler::GetCurrentTSMDocumentID()
2375 // At least on Mac OS X 10.6.x and 10.7.x, ::TSMGetActiveDocument() has a bug.
2376 // The result of ::TSMGetActiveDocument() isn't modified for new active text
2377 // input context until [NSTextInputContext currentInputContext] is called.
2378 // Therefore, we need to call it here.
2379 [NSTextInputContext currentInputContext];
2380 return ::TSMGetActiveDocument();
2387 /******************************************************************************
2389 * IMEInputHandler implementation #1
2390 * The methods are releated to the pending methods. Some jobs should be
2391 * run after the stack is finished, e.g, some methods cannot run the jobs
2392 * during processing the focus event. And also some other jobs should be
2393 * run at the next focus event is processed.
2394 * The pending methods are recorded in mPendingMethods. They are executed
2395 * by ExecutePendingMethods via FlushPendingMethods.
2397 ******************************************************************************/
2400 IMEInputHandler::NotifyIMEOfFocusChangeInGecko()
2402 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2404 PR_LOG(gLog, PR_LOG_ALWAYS,
2405 ("%p IMEInputHandler::NotifyIMEOfFocusChangeInGecko, "
2406 "Destroyed()=%s, IsFocused()=%s, inputContext=%p",
2407 this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
2408 mView ? [mView inputContext] : nullptr));
2415 // retry at next focus event
2416 mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
2421 NSTextInputContext* inputContext = [mView inputContext];
2422 NS_ENSURE_TRUE_VOID(inputContext);
2424 // When an <input> element on a XUL <panel> element gets focus from an <input>
2425 // element on the opener window of the <panel> element, the owner window
2426 // still has native focus. Therefore, IMEs may store the opener window's
2427 // level at this time because they don't know the actual focus is moved to
2428 // different window. If IMEs try to get the newest window level after the
2429 // focus change, we return the window level of the XUL <panel>'s widget.
2430 // Therefore, let's emulate the native focus change. Then, IMEs can refresh
2431 // the stored window level.
2432 [inputContext deactivate];
2433 [inputContext activate];
2435 NS_OBJC_END_TRY_ABORT_BLOCK;
2439 IMEInputHandler::DiscardIMEComposition()
2441 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2443 PR_LOG(gLog, PR_LOG_ALWAYS,
2444 ("%p IMEInputHandler::DiscardIMEComposition, "
2445 "Destroyed()=%s, IsFocused()=%s, mView=%p, inputContext=%p",
2446 this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
2447 mView, mView ? [mView inputContext] : nullptr));
2454 // retry at next focus event
2455 mPendingMethods |= kDiscardIMEComposition;
2459 NS_ENSURE_TRUE_VOID(mView);
2460 NSTextInputContext* inputContext = [mView inputContext];
2461 NS_ENSURE_TRUE_VOID(inputContext);
2462 mIgnoreIMECommit = true;
2463 [inputContext discardMarkedText];
2464 mIgnoreIMECommit = false;
2466 NS_OBJC_END_TRY_ABORT_BLOCK
2470 IMEInputHandler::SyncASCIICapableOnly()
2472 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2474 PR_LOG(gLog, PR_LOG_ALWAYS,
2475 ("%p IMEInputHandler::SyncASCIICapableOnly, "
2476 "Destroyed()=%s, IsFocused()=%s, mIsASCIICapableOnly=%s, "
2477 "GetCurrentTSMDocumentID()=%p",
2478 this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
2479 TrueOrFalse(mIsASCIICapableOnly), GetCurrentTSMDocumentID()));
2486 // retry at next focus event
2487 mPendingMethods |= kSyncASCIICapableOnly;
2491 TSMDocumentID doc = GetCurrentTSMDocumentID();
2494 mPendingMethods |= kSyncASCIICapableOnly;
2495 NS_WARNING("Application is active but there is no active document");
2500 if (mIsASCIICapableOnly) {
2501 CFArrayRef ASCIICapableTISList = ::TISCreateASCIICapableInputSourceList();
2502 ::TSMSetDocumentProperty(doc,
2503 kTSMDocumentEnabledInputSourcesPropertyTag,
2505 &ASCIICapableTISList);
2506 ::CFRelease(ASCIICapableTISList);
2508 ::TSMRemoveDocumentProperty(doc,
2509 kTSMDocumentEnabledInputSourcesPropertyTag);
2512 NS_OBJC_END_TRY_ABORT_BLOCK;
2516 IMEInputHandler::ResetTimer()
2518 NS_ASSERTION(mPendingMethods != 0,
2519 "There are not pending methods, why this is called?");
2523 mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
2524 NS_ENSURE_TRUE(mTimer, );
2526 mTimer->InitWithFuncCallback(FlushPendingMethods, this, 0,
2527 nsITimer::TYPE_ONE_SHOT);
2531 IMEInputHandler::ExecutePendingMethods()
2533 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2540 if (![[NSApplication sharedApplication] isActive]) {
2541 mIsInFocusProcessing = false;
2542 // If we're not active, we should retry at focus event
2546 uint32_t pendingMethods = mPendingMethods;
2547 // First, reset the pending method flags because if each methods cannot
2548 // run now, they can reentry to the pending flags by theirselves.
2549 mPendingMethods = 0;
2551 if (pendingMethods & kDiscardIMEComposition)
2552 DiscardIMEComposition();
2553 if (pendingMethods & kSyncASCIICapableOnly)
2554 SyncASCIICapableOnly();
2555 if (pendingMethods & kNotifyIMEOfFocusChangeInGecko) {
2556 NotifyIMEOfFocusChangeInGecko();
2559 mIsInFocusProcessing = false;
2561 NS_OBJC_END_TRY_ABORT_BLOCK;
2567 /******************************************************************************
2569 * IMEInputHandler implementation (native event handlers)
2571 ******************************************************************************/
2574 IMEInputHandler::ConvertToTextRangeType(uint32_t aUnderlineStyle,
2575 NSRange& aSelectedRange)
2577 PR_LOG(gLog, PR_LOG_ALWAYS,
2578 ("%p IMEInputHandler::ConvertToTextRangeType, "
2579 "aUnderlineStyle=%llu, aSelectedRange.length=%llu,",
2580 this, aUnderlineStyle, aSelectedRange.length));
2582 // We assume that aUnderlineStyle is NSUnderlineStyleSingle or
2583 // NSUnderlineStyleThick. NSUnderlineStyleThick should indicate a selected
2584 // clause. Otherwise, should indicate non-selected clause.
2586 if (aSelectedRange.length == 0) {
2587 switch (aUnderlineStyle) {
2588 case NSUnderlineStyleSingle:
2589 return NS_TEXTRANGE_RAWINPUT;
2590 case NSUnderlineStyleThick:
2591 return NS_TEXTRANGE_SELECTEDRAWTEXT;
2593 NS_WARNING("Unexpected line style");
2594 return NS_TEXTRANGE_SELECTEDRAWTEXT;
2598 switch (aUnderlineStyle) {
2599 case NSUnderlineStyleSingle:
2600 return NS_TEXTRANGE_CONVERTEDTEXT;
2601 case NSUnderlineStyleThick:
2602 return NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
2604 NS_WARNING("Unexpected line style");
2605 return NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
2610 IMEInputHandler::GetRangeCount(NSAttributedString *aAttrString)
2612 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
2614 // Iterate through aAttrString for the NSUnderlineStyleAttributeName and
2615 // count the different segments adjusting limitRange as we go.
2617 NSRange effectiveRange;
2618 NSRange limitRange = NSMakeRange(0, [aAttrString length]);
2619 while (limitRange.length > 0) {
2620 [aAttrString attribute:NSUnderlineStyleAttributeName
2621 atIndex:limitRange.location
2622 longestEffectiveRange:&effectiveRange
2623 inRange:limitRange];
2625 NSMakeRange(NSMaxRange(effectiveRange),
2626 NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
2630 PR_LOG(gLog, PR_LOG_ALWAYS,
2631 ("%p IMEInputHandler::GetRangeCount, aAttrString=\"%s\", count=%llu",
2632 this, GetCharacters([aAttrString string]), count));
2636 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
2639 already_AddRefed<mozilla::TextRangeArray>
2640 IMEInputHandler::CreateTextRangeArray(NSAttributedString *aAttrString,
2641 NSRange& aSelectedRange)
2643 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
2645 // Convert the Cocoa range into the TextRange Array used in Gecko.
2646 // Iterate through the attributed string and map the underline attribute to
2647 // Gecko IME textrange attributes. We may need to change the code here if
2648 // we change the implementation of validAttributesForMarkedText.
2649 NSRange limitRange = NSMakeRange(0, [aAttrString length]);
2650 uint32_t rangeCount = GetRangeCount(aAttrString);
2651 nsRefPtr<mozilla::TextRangeArray> textRangeArray =
2652 new mozilla::TextRangeArray();
2653 for (uint32_t i = 0; i < rangeCount && limitRange.length > 0; i++) {
2654 NSRange effectiveRange;
2655 id attributeValue = [aAttrString attribute:NSUnderlineStyleAttributeName
2656 atIndex:limitRange.location
2657 longestEffectiveRange:&effectiveRange
2658 inRange:limitRange];
2661 range.mStartOffset = effectiveRange.location;
2662 range.mEndOffset = NSMaxRange(effectiveRange);
2664 ConvertToTextRangeType([attributeValue intValue], aSelectedRange);
2665 textRangeArray->AppendElement(range);
2667 PR_LOG(gLog, PR_LOG_ALWAYS,
2668 ("%p IMEInputHandler::CreateTextRangeArray, "
2669 "range={ mStartOffset=%llu, mEndOffset=%llu, mRangeType=%s }",
2670 this, range.mStartOffset, range.mEndOffset,
2671 GetRangeTypeName(range.mRangeType)));
2674 NSMakeRange(NSMaxRange(effectiveRange),
2675 NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
2678 // Get current caret position.
2680 range.mStartOffset = aSelectedRange.location + aSelectedRange.length;
2681 range.mEndOffset = range.mStartOffset;
2682 range.mRangeType = NS_TEXTRANGE_CARETPOSITION;
2683 textRangeArray->AppendElement(range);
2685 PR_LOG(gLog, PR_LOG_ALWAYS,
2686 ("%p IMEInputHandler::CreateTextRangeArray, "
2687 "range={ mStartOffset=%llu, mEndOffset=%llu, mRangeType=%s }",
2688 this, range.mStartOffset, range.mEndOffset,
2689 GetRangeTypeName(range.mRangeType)));
2691 return textRangeArray.forget();
2693 NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
2697 IMEInputHandler::DispatchCompositionChangeEvent(const nsString& aText,
2698 NSAttributedString* aAttrString,
2699 NSRange& aSelectedRange)
2701 PR_LOG(gLog, PR_LOG_ALWAYS,
2702 ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
2703 "aText=\"%s\", aAttrString=\"%s\", "
2704 "aSelectedRange={ location=%llu, length=%llu }, "
2706 this, NS_ConvertUTF16toUTF8(aText).get(),
2707 GetCharacters([aAttrString string]),
2708 aSelectedRange.location, aSelectedRange.length,
2709 TrueOrFalse(Destroyed())));
2711 NS_ENSURE_TRUE(!Destroyed(), false);
2713 nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
2715 WidgetCompositionEvent compositionChangeEvent(true, NS_COMPOSITION_CHANGE,
2717 compositionChangeEvent.time = PR_IntervalNow();
2718 compositionChangeEvent.mData = aText;
2719 compositionChangeEvent.mRanges =
2720 CreateTextRangeArray(aAttrString, aSelectedRange);
2721 return DispatchEvent(compositionChangeEvent);
2725 IMEInputHandler::DispatchCompositionCommitEvent(const nsAString* aCommitString)
2727 PR_LOG(gLog, PR_LOG_ALWAYS,
2728 ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
2729 "aCommitString=0x%p (\"%s\"), Destroyed()=%s",
2730 this, aCommitString,
2731 aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : "",
2732 TrueOrFalse(Destroyed())));
2734 if (NS_WARN_IF(Destroyed())) {
2738 nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
2741 aCommitString ? NS_COMPOSITION_COMMIT : NS_COMPOSITION_COMMIT_AS_IS;
2742 WidgetCompositionEvent compositionCommitEvent(true, message, mWidget);
2743 compositionCommitEvent.time = PR_IntervalNow();
2744 if (aCommitString) {
2745 compositionCommitEvent.mData = *aCommitString;
2747 return DispatchEvent(compositionCommitEvent);
2751 IMEInputHandler::InitCompositionEvent(WidgetCompositionEvent& aCompositionEvent)
2753 aCompositionEvent.time = PR_IntervalNow();
2757 IMEInputHandler::InsertTextAsCommittingComposition(
2758 NSAttributedString* aAttrString,
2759 NSRange* aReplacementRange)
2761 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2763 PR_LOG(gLog, PR_LOG_ALWAYS,
2764 ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
2765 "aAttrString=\"%s\", aReplacementRange=%p { location=%llu, length=%llu }, "
2766 "Destroyed()=%s, IsIMEComposing()=%s, "
2767 "mMarkedRange={ location=%llu, length=%llu }",
2768 this, GetCharacters([aAttrString string]), aReplacementRange,
2769 aReplacementRange ? aReplacementRange->location : 0,
2770 aReplacementRange ? aReplacementRange->length : 0,
2771 TrueOrFalse(Destroyed()), TrueOrFalse(IsIMEComposing()),
2772 mMarkedRange.location, mMarkedRange.length));
2774 if (IgnoreIMECommit()) {
2775 MOZ_CRASH("IMEInputHandler::InsertTextAsCommittingComposition() must not"
2776 "be called while canceling the composition");
2783 // First, commit current composition with the latest composition string if the
2784 // replacement range is different from marked range.
2785 if (IsIMEComposing() && aReplacementRange &&
2786 aReplacementRange->location != NSNotFound &&
2787 !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
2788 DispatchCompositionCommitEvent();
2790 PR_LOG(gLog, PR_LOG_ALWAYS,
2791 ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
2792 "destroyed by commiting composition for setting replacement range",
2796 OnEndIMEComposition();
2799 nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
2802 nsCocoaUtils::GetStringForNSString([aAttrString string], str);
2804 if (!IsIMEComposing()) {
2805 // If there is no selection and replacement range is specified, set the
2806 // range as selection.
2807 if (aReplacementRange && aReplacementRange->location != NSNotFound &&
2808 !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
2809 NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
2812 // XXXmnakano Probably, we shouldn't emulate composition in this case.
2813 // I think that we should just fire DOM3 textInput event if we implement it.
2814 WidgetCompositionEvent compStart(true, NS_COMPOSITION_START, mWidget);
2815 InitCompositionEvent(compStart);
2817 DispatchEvent(compStart);
2819 PR_LOG(gLog, PR_LOG_ALWAYS,
2820 ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
2821 "destroyed by compositionstart event", this));
2825 OnStartIMEComposition();
2828 DispatchCompositionCommitEvent(&str);
2830 PR_LOG(gLog, PR_LOG_ALWAYS,
2831 ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
2832 "destroyed by compositioncommit event", this));
2836 OnEndIMEComposition();
2838 mMarkedRange = NSMakeRange(NSNotFound, 0);
2840 NS_OBJC_END_TRY_ABORT_BLOCK;
2844 IMEInputHandler::SetMarkedText(NSAttributedString* aAttrString,
2845 NSRange& aSelectedRange,
2846 NSRange* aReplacementRange)
2848 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
2850 PR_LOG(gLog, PR_LOG_ALWAYS,
2851 ("%p IMEInputHandler::SetMarkedText, "
2852 "aAttrString=\"%s\", aSelectedRange={ location=%llu, length=%llu }, "
2853 "aReplacementRange=%p { location=%llu, length=%llu }, "
2854 "Destroyed()=%s, IgnoreIMEComposition()=%s, IsIMEComposing()=%s, "
2855 "mMarkedRange={ location=%llu, length=%llu }",
2856 this, GetCharacters([aAttrString string]),
2857 aSelectedRange.location, aSelectedRange.length, aReplacementRange,
2858 aReplacementRange ? aReplacementRange->location : 0,
2859 aReplacementRange ? aReplacementRange->length : 0,
2860 TrueOrFalse(Destroyed()), TrueOrFalse(IgnoreIMEComposition()),
2861 TrueOrFalse(IsIMEComposing()),
2862 mMarkedRange.location, mMarkedRange.length));
2864 if (Destroyed() || IgnoreIMEComposition()) {
2868 nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
2870 // First, commit current composition with the latest composition string if the
2871 // replacement range is different from marked range.
2872 if (IsIMEComposing() && aReplacementRange &&
2873 aReplacementRange->location != NSNotFound &&
2874 !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
2875 bool ignoreIMECommit = mIgnoreIMECommit;
2876 mIgnoreIMECommit = false;
2877 DispatchCompositionCommitEvent();
2878 mIgnoreIMECommit = ignoreIMECommit;
2880 PR_LOG(gLog, PR_LOG_ALWAYS,
2881 ("%p IMEInputHandler::SetMarkedText, "
2882 "destroyed by commiting composition for setting replacement range",
2886 OnEndIMEComposition();
2890 nsCocoaUtils::GetStringForNSString([aAttrString string], str);
2892 mMarkedRange.length = str.Length();
2894 if (!IsIMEComposing() && !str.IsEmpty()) {
2895 // If there is no selection and replacement range is specified, set the
2896 // range as selection.
2897 if (aReplacementRange && aReplacementRange->location != NSNotFound &&
2898 !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
2899 NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
2902 mMarkedRange.location = SelectedRange().location;
2904 WidgetCompositionEvent compStart(true, NS_COMPOSITION_START, mWidget);
2905 InitCompositionEvent(compStart);
2907 DispatchEvent(compStart);
2909 PR_LOG(gLog, PR_LOG_ALWAYS,
2910 ("%p IMEInputHandler::SetMarkedText, "
2911 "destroyed by compositionstart event", this));
2915 OnStartIMEComposition();
2918 if (!IsIMEComposing()) {
2922 if (!str.IsEmpty()) {
2923 OnUpdateIMEComposition([aAttrString string]);
2925 DispatchCompositionChangeEvent(str, aAttrString, aSelectedRange);
2927 PR_LOG(gLog, PR_LOG_ALWAYS,
2928 ("%p IMEInputHandler::SetMarkedText, "
2929 "destroyed by compositionchange event", this));
2934 // If the composition string becomes empty string, we should commit
2935 // current composition.
2936 DispatchCompositionCommitEvent(&EmptyString());
2938 PR_LOG(gLog, PR_LOG_ALWAYS,
2939 ("%p IMEInputHandler::SetMarkedText, "
2940 "destroyed by compositioncommit event", this));
2943 OnEndIMEComposition();
2945 NS_OBJC_END_TRY_ABORT_BLOCK;
2949 IMEInputHandler::ConversationIdentifier()
2951 PR_LOG(gLog, PR_LOG_ALWAYS,
2952 ("%p IMEInputHandler::ConversationIdentifier, Destroyed()=%s",
2953 this, TrueOrFalse(Destroyed())));
2956 return reinterpret_cast<NSInteger>(mView);
2959 nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
2961 // NOTE: The size of NSInteger is same as pointer size.
2962 WidgetQueryContentEvent textContent(true, NS_QUERY_TEXT_CONTENT, mWidget);
2963 textContent.InitForQueryTextContent(0, 0);
2964 DispatchEvent(textContent);
2965 if (!textContent.mSucceeded) {
2966 PR_LOG(gLog, PR_LOG_ALWAYS,
2967 ("%p IMEInputHandler::ConversationIdentifier, Failed", this));
2968 return reinterpret_cast<NSInteger>(mView);
2970 // XXX This might return same ID as a previously existing editor if the
2971 // deleted editor was created at the same address. Is there a better way?
2972 return reinterpret_cast<NSInteger>(textContent.mReply.mContentsRoot);
2976 IMEInputHandler::GetAttributedSubstringFromRange(NSRange& aRange,
2977 NSRange* aActualRange)
2979 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
2981 PR_LOG(gLog, PR_LOG_ALWAYS,
2982 ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
2983 "aRange={ location=%llu, length=%llu }, aActualRange=%p, Destroyed()=%s",
2984 this, aRange.location, aRange.length, aActualRange,
2985 TrueOrFalse(Destroyed())));
2988 *aActualRange = NSMakeRange(NSNotFound, 0);
2991 if (Destroyed() || aRange.location == NSNotFound || aRange.length == 0) {
2995 nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
2998 WidgetQueryContentEvent textContent(true, NS_QUERY_TEXT_CONTENT, mWidget);
2999 textContent.InitForQueryTextContent(aRange.location, aRange.length);
3000 DispatchEvent(textContent);
3002 PR_LOG(gLog, PR_LOG_ALWAYS,
3003 ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
3004 "textContent={ mSucceeded=%s, mReply={ mString=\"%s\", mOffset=%llu } }",
3005 this, TrueOrFalse(textContent.mSucceeded),
3006 NS_ConvertUTF16toUTF8(textContent.mReply.mString).get(),
3007 textContent.mReply.mOffset));
3009 if (!textContent.mSucceeded) {
3013 NSString* nsstr = nsCocoaUtils::ToNSString(textContent.mReply.mString);
3014 NSAttributedString* result =
3015 [[[NSAttributedString alloc] initWithString:nsstr
3016 attributes:nil] autorelease];
3018 aActualRange->location = textContent.mReply.mOffset;
3019 aActualRange->length = textContent.mReply.mString.Length();
3023 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
3027 IMEInputHandler::HasMarkedText()
3029 PR_LOG(gLog, PR_LOG_ALWAYS,
3030 ("%p IMEInputHandler::HasMarkedText, "
3031 "mMarkedRange={ location=%llu, length=%llu }",
3032 this, mMarkedRange.location, mMarkedRange.length));
3034 return (mMarkedRange.location != NSNotFound) && (mMarkedRange.length != 0);
3038 IMEInputHandler::MarkedRange()
3040 PR_LOG(gLog, PR_LOG_ALWAYS,
3041 ("%p IMEInputHandler::MarkedRange, "
3042 "mMarkedRange={ location=%llu, length=%llu }",
3043 this, mMarkedRange.location, mMarkedRange.length));
3045 if (!HasMarkedText()) {
3046 return NSMakeRange(NSNotFound, 0);
3048 return mMarkedRange;
3052 IMEInputHandler::SelectedRange()
3054 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3056 PR_LOG(gLog, PR_LOG_ALWAYS,
3057 ("%p IMEInputHandler::SelectedRange, Destroyed()=%s, mSelectedRange={ "
3058 "location=%llu, length=%llu }",
3059 this, TrueOrFalse(Destroyed()), mSelectedRange.location,
3060 mSelectedRange.length));
3063 return mSelectedRange;
3066 if (mSelectedRange.location != NSNotFound) {
3067 MOZ_ASSERT(mIMEHasFocus);
3068 return mSelectedRange;
3071 nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
3073 WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, mWidget);
3074 DispatchEvent(selection);
3076 PR_LOG(gLog, PR_LOG_ALWAYS,
3077 ("%p IMEInputHandler::SelectedRange, selection={ mSucceeded=%s, "
3078 "mReply={ mOffset=%llu, mString.Length()=%llu } }",
3079 this, TrueOrFalse(selection.mSucceeded), selection.mReply.mOffset,
3080 selection.mReply.mString.Length()));
3082 if (!selection.mSucceeded) {
3083 return mSelectedRange;
3087 mSelectedRange.location = selection.mReply.mOffset;
3088 mSelectedRange.length = selection.mReply.mString.Length();
3089 return mSelectedRange;
3092 return NSMakeRange(selection.mReply.mOffset,
3093 selection.mReply.mString.Length());
3095 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(mSelectedRange);
3099 IMEInputHandler::FirstRectForCharacterRange(NSRange& aRange,
3100 NSRange* aActualRange)
3102 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3104 PR_LOG(gLog, PR_LOG_ALWAYS,
3105 ("%p IMEInputHandler::FirstRectForCharacterRange, Destroyed()=%s, "
3106 "aRange={ location=%llu, length=%llu }, aActualRange=%p }",
3107 this, TrueOrFalse(Destroyed()), aRange.location, aRange.length,
3110 // XXX this returns first character rect or caret rect, it is limitation of
3111 // now. We need more work for returns first line rect. But current
3112 // implementation is enough for IMEs.
3114 NSRect rect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
3115 NSRange actualRange = NSMakeRange(NSNotFound, 0);
3117 *aActualRange = actualRange;
3119 if (Destroyed() || aRange.location == NSNotFound) {
3123 nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
3126 bool useCaretRect = (aRange.length == 0);
3127 if (!useCaretRect) {
3128 WidgetQueryContentEvent charRect(true, NS_QUERY_TEXT_RECT, mWidget);
3129 charRect.InitForQueryTextRect(aRange.location, 1);
3130 DispatchEvent(charRect);
3131 if (charRect.mSucceeded) {
3132 r = charRect.mReply.mRect;
3133 actualRange.location = charRect.mReply.mOffset;
3134 actualRange.length = charRect.mReply.mString.Length();
3136 useCaretRect = true;
3141 WidgetQueryContentEvent caretRect(true, NS_QUERY_CARET_RECT, mWidget);
3142 caretRect.InitForQueryCaretRect(aRange.location);
3143 DispatchEvent(caretRect);
3144 if (!caretRect.mSucceeded) {
3147 r = caretRect.mReply.mRect;
3149 actualRange.location = caretRect.mReply.mOffset;
3150 actualRange.length = 0;
3153 nsIWidget* rootWidget = mWidget->GetTopLevelWidget();
3154 NSWindow* rootWindow =
3155 static_cast<NSWindow*>(rootWidget->GetNativeData(NS_NATIVE_WINDOW));
3157 static_cast<NSView*>(rootWidget->GetNativeData(NS_NATIVE_WIDGET));
3158 if (!rootWindow || !rootView) {
3161 rect = nsCocoaUtils::DevPixelsToCocoaPoints(r, mWidget->BackingScaleFactor());
3162 rect = [rootView convertRect:rect toView:nil];
3163 rect.origin = [rootWindow convertBaseToScreen:rect.origin];
3166 *aActualRange = actualRange;
3169 PR_LOG(gLog, PR_LOG_ALWAYS,
3170 ("%p IMEInputHandler::FirstRectForCharacterRange, "
3171 "useCaretRect=%s rect={ x=%f, y=%f, width=%f, height=%f }, "
3172 "actualRange={ location=%llu, length=%llu }",
3173 this, TrueOrFalse(useCaretRect), rect.origin.x, rect.origin.y,
3174 rect.size.width, rect.size.height, actualRange.location,
3175 actualRange.length));
3179 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRect(0.0, 0.0, 0.0, 0.0));
3183 IMEInputHandler::CharacterIndexForPoint(NSPoint& aPoint)
3185 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3187 PR_LOG(gLog, PR_LOG_ALWAYS,
3188 ("%p IMEInputHandler::CharacterIndexForPoint, aPoint={ x=%f, y=%f }",
3189 this, aPoint.x, aPoint.y));
3191 NSWindow* mainWindow = [NSApp mainWindow];
3192 if (!mWidget || !mainWindow) {
3196 WidgetQueryContentEvent charAt(true, NS_QUERY_CHARACTER_AT_POINT, mWidget);
3197 NSPoint ptInWindow = [mainWindow convertScreenToBase:aPoint];
3198 NSPoint ptInView = [mView convertPoint:ptInWindow fromView:nil];
3200 static_cast<int32_t>(ptInView.x) * mWidget->BackingScaleFactor();
3202 static_cast<int32_t>(ptInView.y) * mWidget->BackingScaleFactor();
3203 mWidget->DispatchWindowEvent(charAt);
3204 if (!charAt.mSucceeded ||
3205 charAt.mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND ||
3206 charAt.mReply.mOffset >= static_cast<uint32_t>(NSNotFound)) {
3210 return charAt.mReply.mOffset;
3212 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNotFound);
3216 IMEInputHandler::GetValidAttributesForMarkedText()
3218 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
3220 PR_LOG(gLog, PR_LOG_ALWAYS,
3221 ("%p IMEInputHandler::GetValidAttributesForMarkedText", this));
3223 //nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
3225 //return [NSArray arrayWithObjects:NSUnderlineStyleAttributeName,
3226 // NSMarkedClauseSegmentAttributeName,
3227 // NSTextInputReplacementRangeAttributeName,
3229 // empty array; we don't support any attributes right now
3230 return [NSArray array];
3232 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
3239 /******************************************************************************
3241 * IMEInputHandler implementation #2
3243 ******************************************************************************/
3245 IMEInputHandler::IMEInputHandler(nsChildView* aWidget,
3246 NSView<mozView> *aNativeView) :
3247 TextInputHandlerBase(aWidget, aNativeView),
3248 mPendingMethods(0), mIMECompositionString(nullptr),
3249 mIsIMEComposing(false), mIsIMEEnabled(true),
3250 mIsASCIICapableOnly(false), mIgnoreIMECommit(false),
3251 mIsInFocusProcessing(false), mIMEHasFocus(false)
3253 InitStaticMembers();
3255 mMarkedRange.location = NSNotFound;
3256 mMarkedRange.length = 0;
3257 mSelectedRange.location = NSNotFound;
3258 mSelectedRange.length = 0;
3261 IMEInputHandler::~IMEInputHandler()
3267 if (sFocusedIMEHandler == this) {
3268 sFocusedIMEHandler = nullptr;
3273 IMEInputHandler::OnFocusChangeInGecko(bool aFocus)
3275 PR_LOG(gLog, PR_LOG_ALWAYS,
3276 ("%p IMEInputHandler::OnFocusChangeInGecko, aFocus=%s, Destroyed()=%s, "
3277 "sFocusedIMEHandler=%p",
3278 this, TrueOrFalse(aFocus), TrueOrFalse(Destroyed()), sFocusedIMEHandler));
3280 mSelectedRange.location = NSNotFound; // Marking dirty
3281 mIMEHasFocus = aFocus;
3283 // This is called when the native focus is changed and when the native focus
3284 // isn't changed but the focus is changed in Gecko.
3286 if (sFocusedIMEHandler == this)
3287 sFocusedIMEHandler = nullptr;
3291 sFocusedIMEHandler = this;
3292 mIsInFocusProcessing = true;
3294 // We need to notify IME of focus change in Gecko as native focus change
3295 // because the window level of the focused element in Gecko may be changed.
3296 mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
3301 IMEInputHandler::OnDestroyWidget(nsChildView* aDestroyingWidget)
3303 PR_LOG(gLog, PR_LOG_ALWAYS,
3304 ("%p IMEInputHandler::OnDestroyWidget, aDestroyingWidget=%p, "
3305 "sFocusedIMEHandler=%p, IsIMEComposing()=%s",
3306 this, aDestroyingWidget, sFocusedIMEHandler,
3307 TrueOrFalse(IsIMEComposing())));
3309 // If we're not focused, the focused IMEInputHandler may have been
3310 // created by another widget/nsChildView.
3311 if (sFocusedIMEHandler && sFocusedIMEHandler != this) {
3312 sFocusedIMEHandler->OnDestroyWidget(aDestroyingWidget);
3315 if (!TextInputHandlerBase::OnDestroyWidget(aDestroyingWidget)) {
3319 if (IsIMEComposing()) {
3320 // If our view is in the composition, we should clean up it.
3321 CancelIMEComposition();
3322 OnEndIMEComposition();
3325 mSelectedRange.location = NSNotFound; // Marking dirty
3326 mIMEHasFocus = false;
3332 IMEInputHandler::OnStartIMEComposition()
3334 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3336 PR_LOG(gLog, PR_LOG_ALWAYS,
3337 ("%p IMEInputHandler::OnStartIMEComposition, mView=%p, mWidget=%p"
3338 "inputContext=%p, mIsIMEComposing=%s",
3339 this, mView, mWidget, mView ? [mView inputContext] : nullptr,
3340 TrueOrFalse(mIsIMEComposing)));
3342 NS_ASSERTION(!mIsIMEComposing, "There is a composition already");
3343 mIsIMEComposing = true;
3345 NS_OBJC_END_TRY_ABORT_BLOCK;
3349 IMEInputHandler::OnUpdateIMEComposition(NSString* aIMECompositionString)
3351 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3353 PR_LOG(gLog, PR_LOG_ALWAYS,
3354 ("%p IMEInputHandler::OnUpdateIMEComposition, mView=%p, mWidget=%p, "
3355 "inputContext=%p, mIsIMEComposing=%s, aIMECompositionString=\"%s\"",
3356 this, mView, mWidget, mView ? [mView inputContext] : nullptr,
3357 TrueOrFalse(mIsIMEComposing), GetCharacters(aIMECompositionString)));
3359 NS_ASSERTION(mIsIMEComposing, "We're not in composition");
3361 if (mIMECompositionString)
3362 [mIMECompositionString release];
3363 mIMECompositionString = [aIMECompositionString retain];
3365 NS_OBJC_END_TRY_ABORT_BLOCK;
3369 IMEInputHandler::OnEndIMEComposition()
3371 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3373 PR_LOG(gLog, PR_LOG_ALWAYS,
3374 ("%p IMEInputHandler::OnEndIMEComposition, mView=%p, mWidget=%p, "
3375 "inputContext=%p, mIsIMEComposing=%s",
3376 this, mView, mWidget, mView ? [mView inputContext] : nullptr,
3377 TrueOrFalse(mIsIMEComposing)));
3379 NS_ASSERTION(mIsIMEComposing, "We're not in composition");
3381 mIsIMEComposing = false;
3383 if (mIMECompositionString) {
3384 [mIMECompositionString release];
3385 mIMECompositionString = nullptr;
3388 NS_OBJC_END_TRY_ABORT_BLOCK;
3392 IMEInputHandler::SendCommittedText(NSString *aString)
3394 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3396 PR_LOG(gLog, PR_LOG_ALWAYS,
3397 ("%p IMEInputHandler::SendCommittedText, mView=%p, mWidget=%p, "
3398 "inputContext=%p, mIsIMEComposing=%s",
3399 this, mView, mWidget, mView ? [mView inputContext] : nullptr,
3400 TrueOrFalse(mIsIMEComposing), mWidget));
3402 NS_ENSURE_TRUE(mWidget, );
3403 // XXX We should send the string without mView.
3408 NSAttributedString* attrStr =
3409 [[NSAttributedString alloc] initWithString:aString];
3410 [mView insertText:attrStr];
3413 NS_OBJC_END_TRY_ABORT_BLOCK;
3417 IMEInputHandler::KillIMEComposition()
3419 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3421 PR_LOG(gLog, PR_LOG_ALWAYS,
3422 ("%p IMEInputHandler::KillIMEComposition, mView=%p, mWidget=%p, "
3423 "inputContext=%p, mIsIMEComposing=%s, "
3424 "Destroyed()=%s, IsFocused()=%s",
3425 this, mView, mWidget, mView ? [mView inputContext] : nullptr,
3426 TrueOrFalse(mIsIMEComposing), TrueOrFalse(Destroyed()),
3427 TrueOrFalse(IsFocused())));
3434 NS_ENSURE_TRUE_VOID(mView);
3435 NSTextInputContext* inputContext = [mView inputContext];
3436 NS_ENSURE_TRUE_VOID(inputContext);
3437 [inputContext discardMarkedText];
3441 PR_LOG(gLog, PR_LOG_ALWAYS,
3442 ("%p IMEInputHandler::KillIMEComposition, Pending...", this));
3444 // Commit the composition internally.
3445 SendCommittedText(mIMECompositionString);
3446 NS_ASSERTION(!mIsIMEComposing, "We're still in a composition");
3447 // The pending method will be fired by the next focus event.
3448 mPendingMethods |= kDiscardIMEComposition;
3450 NS_OBJC_END_TRY_ABORT_BLOCK;
3454 IMEInputHandler::CommitIMEComposition()
3456 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3458 if (!IsIMEComposing())
3461 PR_LOG(gLog, PR_LOG_ALWAYS,
3462 ("%p IMEInputHandler::CommitIMEComposition, mIMECompositionString=%s",
3463 this, GetCharacters(mIMECompositionString)));
3465 KillIMEComposition();
3467 if (!IsIMEComposing())
3470 // If the composition is still there, KillIMEComposition only kills the
3471 // composition in TSM. We also need to finish the our composition too.
3472 SendCommittedText(mIMECompositionString);
3474 NS_OBJC_END_TRY_ABORT_BLOCK;
3478 IMEInputHandler::CancelIMEComposition()
3480 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3482 if (!IsIMEComposing())
3485 PR_LOG(gLog, PR_LOG_ALWAYS,
3486 ("%p IMEInputHandler::CancelIMEComposition, mIMECompositionString=%s",
3487 this, GetCharacters(mIMECompositionString)));
3489 // For canceling the current composing, we need to ignore the param of
3490 // insertText. But this code is ugly...
3491 mIgnoreIMECommit = true;
3492 KillIMEComposition();
3493 mIgnoreIMECommit = false;
3495 if (!IsIMEComposing())
3498 // If the composition is still there, KillIMEComposition only kills the
3499 // composition in TSM. We also need to kill the our composition too.
3500 SendCommittedText(@"");
3502 NS_OBJC_END_TRY_ABORT_BLOCK;
3506 IMEInputHandler::IsFocused()
3508 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
3510 NS_ENSURE_TRUE(!Destroyed(), false);
3511 NSWindow* window = [mView window];
3512 NS_ENSURE_TRUE(window, false);
3513 return [window firstResponder] == mView &&
3514 [window isKeyWindow] &&
3515 [[NSApplication sharedApplication] isActive];
3517 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
3521 IMEInputHandler::IsIMEOpened()
3523 TISInputSourceWrapper tis;
3524 tis.InitByCurrentInputSource();
3525 return tis.IsOpenedIMEMode();
3529 IMEInputHandler::SetASCIICapableOnly(bool aASCIICapableOnly)
3531 if (aASCIICapableOnly == mIsASCIICapableOnly)
3534 CommitIMEComposition();
3535 mIsASCIICapableOnly = aASCIICapableOnly;
3536 SyncASCIICapableOnly();
3540 IMEInputHandler::EnableIME(bool aEnableIME)
3542 if (aEnableIME == mIsIMEEnabled)
3545 CommitIMEComposition();
3546 mIsIMEEnabled = aEnableIME;
3550 IMEInputHandler::SetIMEOpenState(bool aOpenIME)
3552 if (!IsFocused() || IsIMEOpened() == aOpenIME)
3556 TISInputSourceWrapper tis;
3557 tis.InitByCurrentASCIICapableInputSource();
3562 // If we know the latest IME opened mode, we should select it.
3563 if (sLatestIMEOpenedModeInputSourceID) {
3564 TISInputSourceWrapper tis;
3565 tis.InitByInputSourceID(sLatestIMEOpenedModeInputSourceID);
3570 // XXX If the current input source is a mode of IME, we should turn on it,
3571 // but we haven't found such way...
3573 // Finally, we should refer the system locale but this is a little expensive,
3574 // we shouldn't retry this (if it was succeeded, we already set
3575 // sLatestIMEOpenedModeInputSourceID at that time).
3576 static bool sIsPrefferredIMESearched = false;
3577 if (sIsPrefferredIMESearched)
3579 sIsPrefferredIMESearched = true;
3580 OpenSystemPreferredLanguageIME();
3584 IMEInputHandler::OpenSystemPreferredLanguageIME()
3586 PR_LOG(gLog, PR_LOG_ALWAYS,
3587 ("%p IMEInputHandler::OpenSystemPreferredLanguageIME", this));
3589 CFArrayRef langList = ::CFLocaleCopyPreferredLanguages();
3591 PR_LOG(gLog, PR_LOG_ALWAYS,
3592 ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, langList is NULL",
3596 CFIndex count = ::CFArrayGetCount(langList);
3597 for (CFIndex i = 0; i < count; i++) {
3598 CFLocaleRef locale =
3599 ::CFLocaleCreate(kCFAllocatorDefault,
3600 static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, i)));
3605 bool changed = false;
3606 CFStringRef lang = static_cast<CFStringRef>(
3607 ::CFLocaleGetValue(locale, kCFLocaleLanguageCode));
3608 NS_ASSERTION(lang, "lang is null");
3610 TISInputSourceWrapper tis;
3611 tis.InitByLanguage(lang);
3612 if (tis.IsOpenedIMEMode()) {
3614 if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
3615 CFStringRef foundTIS;
3616 tis.GetInputSourceID(foundTIS);
3617 PR_LOG(gLog, PR_LOG_ALWAYS,
3618 ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, "
3619 "foundTIS=%s, lang=%s",
3620 this, GetCharacters(foundTIS), GetCharacters(lang)));
3622 #endif // #ifdef PR_LOGGING
3627 ::CFRelease(locale);
3632 ::CFRelease(langList);
3639 /******************************************************************************
3641 * TextInputHandlerBase implementation
3643 ******************************************************************************/
3645 int32_t TextInputHandlerBase::sSecureEventInputCount = 0;
3647 TextInputHandlerBase::TextInputHandlerBase(nsChildView* aWidget,
3648 NSView<mozView> *aNativeView) :
3651 gHandlerInstanceCount++;
3652 mView = [aNativeView retain];
3655 TextInputHandlerBase::~TextInputHandlerBase()
3658 if (--gHandlerInstanceCount == 0) {
3659 FinalizeCurrentInputSource();
3664 TextInputHandlerBase::OnDestroyWidget(nsChildView* aDestroyingWidget)
3666 PR_LOG(gLog, PR_LOG_ALWAYS,
3667 ("%p TextInputHandlerBase::OnDestroyWidget, "
3668 "aDestroyingWidget=%p, mWidget=%p",
3669 this, aDestroyingWidget, mWidget));
3671 if (aDestroyingWidget != mWidget) {
3680 TextInputHandlerBase::DispatchEvent(WidgetGUIEvent& aEvent)
3682 if (aEvent.message == NS_KEY_PRESS) {
3683 WidgetInputEvent& inputEvent = *aEvent.AsInputEvent();
3684 if (!inputEvent.IsMeta()) {
3685 PR_LOG(gLog, PR_LOG_ALWAYS,
3686 ("%p TextInputHandlerBase::DispatchEvent, hiding mouse cursor", this));
3687 [NSCursor setHiddenUntilMouseMoves:YES];
3690 return mWidget->DispatchWindowEvent(aEvent);
3694 TextInputHandlerBase::InitKeyEvent(NSEvent *aNativeKeyEvent,
3695 WidgetKeyboardEvent& aKeyEvent,
3696 const nsAString* aInsertString)
3698 NS_ASSERTION(aNativeKeyEvent, "aNativeKeyEvent must not be NULL");
3700 if (mKeyboardOverride.mOverrideEnabled) {
3701 TISInputSourceWrapper tis;
3702 tis.InitByLayoutID(mKeyboardOverride.mKeyboardLayout, true);
3703 tis.InitKeyEvent(aNativeKeyEvent, aKeyEvent, aInsertString);
3706 TISInputSourceWrapper::CurrentInputSource().
3707 InitKeyEvent(aNativeKeyEvent, aKeyEvent, aInsertString);
3711 TextInputHandlerBase::SynthesizeNativeKeyEvent(
3712 int32_t aNativeKeyboardLayout,
3713 int32_t aNativeKeyCode,
3714 uint32_t aModifierFlags,
3715 const nsAString& aCharacters,
3716 const nsAString& aUnmodifiedCharacters)
3718 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
3720 static const uint32_t sModifierFlagMap[][2] = {
3721 { nsIWidget::CAPS_LOCK, NSAlphaShiftKeyMask },
3722 { nsIWidget::SHIFT_L, NSShiftKeyMask | 0x0002 },
3723 { nsIWidget::SHIFT_R, NSShiftKeyMask | 0x0004 },
3724 { nsIWidget::CTRL_L, NSControlKeyMask | 0x0001 },
3725 { nsIWidget::CTRL_R, NSControlKeyMask | 0x2000 },
3726 { nsIWidget::ALT_L, NSAlternateKeyMask | 0x0020 },
3727 { nsIWidget::ALT_R, NSAlternateKeyMask | 0x0040 },
3728 { nsIWidget::COMMAND_L, NSCommandKeyMask | 0x0008 },
3729 { nsIWidget::COMMAND_R, NSCommandKeyMask | 0x0010 },
3730 { nsIWidget::NUMERIC_KEY_PAD, NSNumericPadKeyMask },
3731 { nsIWidget::HELP, NSHelpKeyMask },
3732 { nsIWidget::FUNCTION, NSFunctionKeyMask }
3735 uint32_t modifierFlags = 0;
3736 for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) {
3737 if (aModifierFlags & sModifierFlagMap[i][0]) {
3738 modifierFlags |= sModifierFlagMap[i][1];
3742 NSInteger windowNumber = [[mView window] windowNumber];
3743 bool sendFlagsChangedEvent = IsModifierKey(aNativeKeyCode);
3744 NSEventType eventType = sendFlagsChangedEvent ? NSFlagsChanged : NSKeyDown;
3745 NSEvent* downEvent =
3746 [NSEvent keyEventWithType:eventType
3747 location:NSMakePoint(0,0)
3748 modifierFlags:modifierFlags
3750 windowNumber:windowNumber
3751 context:[NSGraphicsContext currentContext]
3752 characters:nsCocoaUtils::ToNSString(aCharacters)
3753 charactersIgnoringModifiers:nsCocoaUtils::ToNSString(aUnmodifiedCharacters)
3755 keyCode:aNativeKeyCode];
3757 NSEvent* upEvent = sendFlagsChangedEvent ?
3758 nil : nsCocoaUtils::MakeNewCocoaEventWithType(NSKeyUp, downEvent);
3760 if (downEvent && (sendFlagsChangedEvent || upEvent)) {
3761 KeyboardLayoutOverride currentLayout = mKeyboardOverride;
3762 mKeyboardOverride.mKeyboardLayout = aNativeKeyboardLayout;
3763 mKeyboardOverride.mOverrideEnabled = true;
3764 [NSApp sendEvent:downEvent];
3766 [NSApp sendEvent:upEvent];
3768 // processKeyDownEvent and keyUp block exceptions so we're sure to
3769 // reach here to restore mKeyboardOverride
3770 mKeyboardOverride = currentLayout;
3775 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
3779 TextInputHandlerBase::GetWindowLevel()
3781 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
3783 PR_LOG(gLog, PR_LOG_ALWAYS,
3784 ("%p TextInputHandlerBase::GetWindowLevel, Destryoed()=%s",
3785 this, TrueOrFalse(Destroyed())));
3788 return NSNormalWindowLevel;
3791 // When an <input> element on a XUL <panel> is focused, the actual focused view
3792 // is the panel's parent view (mView). But the editor is displayed on the
3793 // popped-up widget's view (editorView). We want the latter's window level.
3794 NSView<mozView>* editorView = mWidget->GetEditorView();
3795 NS_ENSURE_TRUE(editorView, NSNormalWindowLevel);
3796 NSInteger windowLevel = [[editorView window] level];
3798 PR_LOG(gLog, PR_LOG_ALWAYS,
3799 ("%p TextInputHandlerBase::GetWindowLevel, windowLevel=%s (%X)",
3800 this, GetWindowLevelName(windowLevel), windowLevel));
3804 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNormalWindowLevel);
3808 TextInputHandlerBase::AttachNativeKeyEvent(WidgetKeyboardEvent& aKeyEvent)
3810 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
3812 // Don't try to replace a native event if one already exists.
3813 // OS X doesn't have an OS modifier, can't make a native event.
3814 if (aKeyEvent.mNativeKeyEvent || aKeyEvent.modifiers & MODIFIER_OS) {
3818 PR_LOG(gLog, PR_LOG_ALWAYS,
3819 ("%p TextInputHandlerBase::AttachNativeKeyEvent, key=0x%X, char=0x%X, "
3820 "mod=0x%X", this, aKeyEvent.keyCode, aKeyEvent.charCode,
3821 aKeyEvent.modifiers));
3823 NSEventType eventType;
3824 if (aKeyEvent.message == NS_KEY_UP) {
3825 eventType = NSKeyUp;
3827 eventType = NSKeyDown;
3830 static const uint32_t sModifierFlagMap[][2] = {
3831 { MODIFIER_SHIFT, NSShiftKeyMask },
3832 { MODIFIER_CONTROL, NSControlKeyMask },
3833 { MODIFIER_ALT, NSAlternateKeyMask },
3834 { MODIFIER_ALTGRAPH, NSAlternateKeyMask },
3835 { MODIFIER_META, NSCommandKeyMask },
3836 { MODIFIER_CAPSLOCK, NSAlphaShiftKeyMask },
3837 { MODIFIER_NUMLOCK, NSNumericPadKeyMask }
3840 NSUInteger modifierFlags = 0;
3841 for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) {
3842 if (aKeyEvent.modifiers & sModifierFlagMap[i][0]) {
3843 modifierFlags |= sModifierFlagMap[i][1];
3847 NSInteger windowNumber = [[mView window] windowNumber];
3849 NSString* characters;
3850 if (aKeyEvent.charCode) {
3851 characters = [NSString stringWithCharacters:
3852 reinterpret_cast<const unichar*>(&(aKeyEvent.charCode)) length:1];
3854 uint32_t cocoaCharCode =
3855 nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(aKeyEvent.keyCode);
3856 characters = [NSString stringWithCharacters:
3857 reinterpret_cast<const unichar*>(&cocoaCharCode) length:1];
3860 aKeyEvent.mNativeKeyEvent =
3861 [NSEvent keyEventWithType:eventType
3862 location:NSMakePoint(0,0)
3863 modifierFlags:modifierFlags
3865 windowNumber:windowNumber
3866 context:[NSGraphicsContext currentContext]
3867 characters:characters
3868 charactersIgnoringModifiers:characters
3870 keyCode:0]; // Native key code not currently needed
3874 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
3878 TextInputHandlerBase::SetSelection(NSRange& aRange)
3880 MOZ_ASSERT(!Destroyed());
3882 nsRefPtr<TextInputHandlerBase> kungFuDeathGrip(this);
3883 WidgetSelectionEvent selectionEvent(true, NS_SELECTION_SET, mWidget);
3884 selectionEvent.mOffset = aRange.location;
3885 selectionEvent.mLength = aRange.length;
3886 selectionEvent.mReversed = false;
3887 selectionEvent.mExpandToClusterBoundary = false;
3888 DispatchEvent(selectionEvent);
3889 NS_ENSURE_TRUE(selectionEvent.mSucceeded, false);
3890 return !Destroyed();
3894 TextInputHandlerBase::IsPrintableChar(char16_t aChar)
3896 return (aChar >= 0x20 && aChar <= 0x7E) || aChar >= 0xA0;
3901 TextInputHandlerBase::IsSpecialGeckoKey(UInt32 aNativeKeyCode)
3903 // this table is used to determine which keys are special and should not
3904 // generate a charCode
3905 switch (aNativeKeyCode) {
3906 // modifiers - we don't get separate events for these yet
3909 case kVK_RightShift:
3911 case kVK_RightCommand:
3914 case kVK_RightControl:
3916 case kVK_RightOption:
3917 case kVK_ANSI_KeypadClear:
3934 case kVK_PC_ScrollLock:
3935 case kVK_PC_PrintScreen:
3944 case kVK_PC_Backspace:
3945 case kVK_PC_ContextMenu:
3955 case kVK_RightArrow:
3959 case kVK_ANSI_KeypadEnter:
3960 case kVK_Powerbook_KeypadEnter:
3967 TextInputHandlerBase::IsNormalCharInputtingEvent(
3968 const WidgetKeyboardEvent& aKeyEvent)
3970 // this is not character inputting event, simply.
3971 if (!aKeyEvent.isChar || !aKeyEvent.charCode || aKeyEvent.IsMeta()) {
3974 // if this is unicode char inputting event, we don't need to check
3975 // ctrl/alt/command keys
3976 if (aKeyEvent.charCode > 0x7F) {
3979 // ASCII chars should be inputted without ctrl/alt/command keys
3980 return !aKeyEvent.IsControl() && !aKeyEvent.IsAlt();
3984 TextInputHandlerBase::IsModifierKey(UInt32 aNativeKeyCode)
3986 switch (aNativeKeyCode) {
3988 case kVK_RightCommand:
3993 case kVK_RightShift:
3994 case kVK_RightOption:
3995 case kVK_RightControl:
4003 TextInputHandlerBase::EnableSecureEventInput()
4005 sSecureEventInputCount++;
4006 ::EnableSecureEventInput();
4010 TextInputHandlerBase::DisableSecureEventInput()
4012 if (!sSecureEventInputCount) {
4015 sSecureEventInputCount--;
4016 ::DisableSecureEventInput();
4020 TextInputHandlerBase::IsSecureEventInputEnabled()
4022 NS_ASSERTION(!!sSecureEventInputCount == !!::IsSecureEventInputEnabled(),
4023 "Some other process has enabled secure event input");
4024 return !!sSecureEventInputCount;
4028 TextInputHandlerBase::EnsureSecureEventInputDisabled()
4030 while (sSecureEventInputCount) {
4031 TextInputHandlerBase::DisableSecureEventInput();