widget/cocoa/TextInputHandler.mm

Thu, 15 Jan 2015 15:59:08 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:59:08 +0100
branch
TOR_BUG_9701
changeset 10
ac0c01689b40
permissions
-rw-r--r--

Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ts=2 sw=2 et tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "mozilla/ArrayUtils.h"
michael@0 8
michael@0 9 #include "TextInputHandler.h"
michael@0 10
michael@0 11 #ifdef MOZ_LOGGING
michael@0 12 #define FORCE_PR_LOG /* Allow logging in the release build */
michael@0 13 #endif // MOZ_LOGGING
michael@0 14 #include "prlog.h"
michael@0 15
michael@0 16 #include "mozilla/MiscEvents.h"
michael@0 17 #include "mozilla/MouseEvents.h"
michael@0 18 #include "mozilla/TextEvents.h"
michael@0 19
michael@0 20 #include "nsChildView.h"
michael@0 21 #include "nsObjCExceptions.h"
michael@0 22 #include "nsBidiUtils.h"
michael@0 23 #include "nsToolkit.h"
michael@0 24 #include "nsCocoaUtils.h"
michael@0 25 #include "WidgetUtils.h"
michael@0 26 #include "nsPrintfCString.h"
michael@0 27
michael@0 28 #ifdef __LP64__
michael@0 29 #include "ComplexTextInputPanel.h"
michael@0 30 #include <objc/runtime.h>
michael@0 31 #endif // __LP64__
michael@0 32
michael@0 33 #ifdef MOZ_LOGGING
michael@0 34 #define FORCE_PR_LOG
michael@0 35 #endif
michael@0 36 #include "prlog.h"
michael@0 37
michael@0 38 #ifndef __LP64__
michael@0 39 enum {
michael@0 40 // Currently focused ChildView (while this TSM document is active).
michael@0 41 // Transient (only set while TSMProcessRawKeyEvent() is processing a key
michael@0 42 // event), and the ChildView will be retained and released around the call
michael@0 43 // to TSMProcessRawKeyEvent() -- so it can be weak.
michael@0 44 kFocusedChildViewTSMDocPropertyTag = 'GKFV', // type ChildView* [WEAK]
michael@0 45 };
michael@0 46
michael@0 47 // Undocumented HIToolbox function used by WebKit to allow Carbon-based IME
michael@0 48 // to work in a Cocoa-based browser (like Safari or Cocoa-widgets Firefox).
michael@0 49 // (Recent WebKit versions actually use a thin wrapper around this function
michael@0 50 // called WKSendKeyEventToTSM().)
michael@0 51 //
michael@0 52 // Calling TSMProcessRawKeyEvent() from ChildView's keyDown: and keyUp:
michael@0 53 // methods (when the ChildView is a plugin view) bypasses Cocoa's IME
michael@0 54 // infrastructure and (instead) causes Carbon TSM events to be sent on each
michael@0 55 // NSKeyDown event. We install a Carbon event handler
michael@0 56 // (PluginKeyEventsHandler()) to catch these events and pass them to Gecko
michael@0 57 // (which in turn passes them to the plugin).
michael@0 58 extern "C" long TSMProcessRawKeyEvent(EventRef carbonEvent);
michael@0 59 #endif // __LP64__
michael@0 60
michael@0 61 using namespace mozilla;
michael@0 62 using namespace mozilla::widget;
michael@0 63
michael@0 64 #ifdef PR_LOGGING
michael@0 65
michael@0 66 PRLogModuleInfo* gLog = nullptr;
michael@0 67
michael@0 68 static const char*
michael@0 69 OnOrOff(bool aBool)
michael@0 70 {
michael@0 71 return aBool ? "ON" : "off";
michael@0 72 }
michael@0 73
michael@0 74 static const char*
michael@0 75 TrueOrFalse(bool aBool)
michael@0 76 {
michael@0 77 return aBool ? "TRUE" : "FALSE";
michael@0 78 }
michael@0 79
michael@0 80 static const char*
michael@0 81 GetKeyNameForNativeKeyCode(unsigned short aNativeKeyCode)
michael@0 82 {
michael@0 83 switch (aNativeKeyCode) {
michael@0 84 case kVK_Escape: return "Escape";
michael@0 85 case kVK_RightCommand: return "Right-Command";
michael@0 86 case kVK_Command: return "Command";
michael@0 87 case kVK_Shift: return "Shift";
michael@0 88 case kVK_CapsLock: return "CapsLock";
michael@0 89 case kVK_Option: return "Option";
michael@0 90 case kVK_Control: return "Control";
michael@0 91 case kVK_RightShift: return "Right-Shift";
michael@0 92 case kVK_RightOption: return "Right-Option";
michael@0 93 case kVK_RightControl: return "Right-Control";
michael@0 94 case kVK_ANSI_KeypadClear: return "Clear";
michael@0 95
michael@0 96 case kVK_F1: return "F1";
michael@0 97 case kVK_F2: return "F2";
michael@0 98 case kVK_F3: return "F3";
michael@0 99 case kVK_F4: return "F4";
michael@0 100 case kVK_F5: return "F5";
michael@0 101 case kVK_F6: return "F6";
michael@0 102 case kVK_F7: return "F7";
michael@0 103 case kVK_F8: return "F8";
michael@0 104 case kVK_F9: return "F9";
michael@0 105 case kVK_F10: return "F10";
michael@0 106 case kVK_F11: return "F11";
michael@0 107 case kVK_F12: return "F12";
michael@0 108 case kVK_F13: return "F13/PrintScreen";
michael@0 109 case kVK_F14: return "F14/ScrollLock";
michael@0 110 case kVK_F15: return "F15/Pause";
michael@0 111
michael@0 112 case kVK_ANSI_Keypad0: return "NumPad-0";
michael@0 113 case kVK_ANSI_Keypad1: return "NumPad-1";
michael@0 114 case kVK_ANSI_Keypad2: return "NumPad-2";
michael@0 115 case kVK_ANSI_Keypad3: return "NumPad-3";
michael@0 116 case kVK_ANSI_Keypad4: return "NumPad-4";
michael@0 117 case kVK_ANSI_Keypad5: return "NumPad-5";
michael@0 118 case kVK_ANSI_Keypad6: return "NumPad-6";
michael@0 119 case kVK_ANSI_Keypad7: return "NumPad-7";
michael@0 120 case kVK_ANSI_Keypad8: return "NumPad-8";
michael@0 121 case kVK_ANSI_Keypad9: return "NumPad-9";
michael@0 122
michael@0 123 case kVK_ANSI_KeypadMultiply: return "NumPad-*";
michael@0 124 case kVK_ANSI_KeypadPlus: return "NumPad-+";
michael@0 125 case kVK_ANSI_KeypadMinus: return "NumPad--";
michael@0 126 case kVK_ANSI_KeypadDecimal: return "NumPad-.";
michael@0 127 case kVK_ANSI_KeypadDivide: return "NumPad-/";
michael@0 128 case kVK_ANSI_KeypadEquals: return "NumPad-=";
michael@0 129 case kVK_ANSI_KeypadEnter: return "NumPad-Enter";
michael@0 130 case kVK_Return: return "Return";
michael@0 131 case kVK_Powerbook_KeypadEnter: return "NumPad-EnterOnPowerBook";
michael@0 132
michael@0 133 case kVK_PC_Insert: return "Insert/Help";
michael@0 134 case kVK_PC_Delete: return "Delete";
michael@0 135 case kVK_Tab: return "Tab";
michael@0 136 case kVK_PC_Backspace: return "Backspace";
michael@0 137 case kVK_Home: return "Home";
michael@0 138 case kVK_End: return "End";
michael@0 139 case kVK_PageUp: return "PageUp";
michael@0 140 case kVK_PageDown: return "PageDown";
michael@0 141 case kVK_LeftArrow: return "LeftArrow";
michael@0 142 case kVK_RightArrow: return "RightArrow";
michael@0 143 case kVK_UpArrow: return "UpArrow";
michael@0 144 case kVK_DownArrow: return "DownArrow";
michael@0 145 case kVK_PC_ContextMenu: return "ContextMenu";
michael@0 146
michael@0 147 case kVK_Function: return "Function";
michael@0 148 case kVK_VolumeUp: return "VolumeUp";
michael@0 149 case kVK_VolumeDown: return "VolumeDown";
michael@0 150 case kVK_Mute: return "Mute";
michael@0 151
michael@0 152 case kVK_ISO_Section: return "ISO_Section";
michael@0 153
michael@0 154 case kVK_JIS_Yen: return "JIS_Yen";
michael@0 155 case kVK_JIS_Underscore: return "JIS_Underscore";
michael@0 156 case kVK_JIS_KeypadComma: return "JIS_KeypadComma";
michael@0 157 case kVK_JIS_Eisu: return "JIS_Eisu";
michael@0 158 case kVK_JIS_Kana: return "JIS_Kana";
michael@0 159
michael@0 160 case kVK_ANSI_A: return "A";
michael@0 161 case kVK_ANSI_B: return "B";
michael@0 162 case kVK_ANSI_C: return "C";
michael@0 163 case kVK_ANSI_D: return "D";
michael@0 164 case kVK_ANSI_E: return "E";
michael@0 165 case kVK_ANSI_F: return "F";
michael@0 166 case kVK_ANSI_G: return "G";
michael@0 167 case kVK_ANSI_H: return "H";
michael@0 168 case kVK_ANSI_I: return "I";
michael@0 169 case kVK_ANSI_J: return "J";
michael@0 170 case kVK_ANSI_K: return "K";
michael@0 171 case kVK_ANSI_L: return "L";
michael@0 172 case kVK_ANSI_M: return "M";
michael@0 173 case kVK_ANSI_N: return "N";
michael@0 174 case kVK_ANSI_O: return "O";
michael@0 175 case kVK_ANSI_P: return "P";
michael@0 176 case kVK_ANSI_Q: return "Q";
michael@0 177 case kVK_ANSI_R: return "R";
michael@0 178 case kVK_ANSI_S: return "S";
michael@0 179 case kVK_ANSI_T: return "T";
michael@0 180 case kVK_ANSI_U: return "U";
michael@0 181 case kVK_ANSI_V: return "V";
michael@0 182 case kVK_ANSI_W: return "W";
michael@0 183 case kVK_ANSI_X: return "X";
michael@0 184 case kVK_ANSI_Y: return "Y";
michael@0 185 case kVK_ANSI_Z: return "Z";
michael@0 186
michael@0 187 case kVK_ANSI_1: return "1";
michael@0 188 case kVK_ANSI_2: return "2";
michael@0 189 case kVK_ANSI_3: return "3";
michael@0 190 case kVK_ANSI_4: return "4";
michael@0 191 case kVK_ANSI_5: return "5";
michael@0 192 case kVK_ANSI_6: return "6";
michael@0 193 case kVK_ANSI_7: return "7";
michael@0 194 case kVK_ANSI_8: return "8";
michael@0 195 case kVK_ANSI_9: return "9";
michael@0 196 case kVK_ANSI_0: return "0";
michael@0 197 case kVK_ANSI_Equal: return "Equal";
michael@0 198 case kVK_ANSI_Minus: return "Minus";
michael@0 199 case kVK_ANSI_RightBracket: return "RightBracket";
michael@0 200 case kVK_ANSI_LeftBracket: return "LeftBracket";
michael@0 201 case kVK_ANSI_Quote: return "Quote";
michael@0 202 case kVK_ANSI_Semicolon: return "Semicolon";
michael@0 203 case kVK_ANSI_Backslash: return "Backslash";
michael@0 204 case kVK_ANSI_Comma: return "Comma";
michael@0 205 case kVK_ANSI_Slash: return "Slash";
michael@0 206 case kVK_ANSI_Period: return "Period";
michael@0 207 case kVK_ANSI_Grave: return "Grave";
michael@0 208
michael@0 209 default: return "undefined";
michael@0 210 }
michael@0 211 }
michael@0 212
michael@0 213 static const char*
michael@0 214 GetCharacters(const NSString* aString)
michael@0 215 {
michael@0 216 nsAutoString str;
michael@0 217 nsCocoaUtils::GetStringForNSString(aString, str);
michael@0 218 if (str.IsEmpty()) {
michael@0 219 return "";
michael@0 220 }
michael@0 221
michael@0 222 nsAutoString escapedStr;
michael@0 223 for (uint32_t i = 0; i < str.Length(); i++) {
michael@0 224 char16_t ch = str[i];
michael@0 225 if (ch < 0x20) {
michael@0 226 nsPrintfCString utf8str("(U+%04X)", ch);
michael@0 227 escapedStr += NS_ConvertUTF8toUTF16(utf8str);
michael@0 228 } else if (ch <= 0x7E) {
michael@0 229 escapedStr += ch;
michael@0 230 } else {
michael@0 231 nsPrintfCString utf8str("(U+%04X)", ch);
michael@0 232 escapedStr += ch;
michael@0 233 escapedStr += NS_ConvertUTF8toUTF16(utf8str);
michael@0 234 }
michael@0 235 }
michael@0 236
michael@0 237 // the result will be freed automatically by cocoa.
michael@0 238 NSString* result = nsCocoaUtils::ToNSString(escapedStr);
michael@0 239 return [result UTF8String];
michael@0 240 }
michael@0 241
michael@0 242 static const char*
michael@0 243 GetCharacters(const CFStringRef aString)
michael@0 244 {
michael@0 245 const NSString* str = reinterpret_cast<const NSString*>(aString);
michael@0 246 return GetCharacters(str);
michael@0 247 }
michael@0 248
michael@0 249 static const char*
michael@0 250 GetNativeKeyEventType(NSEvent* aNativeEvent)
michael@0 251 {
michael@0 252 switch ([aNativeEvent type]) {
michael@0 253 case NSKeyDown: return "NSKeyDown";
michael@0 254 case NSKeyUp: return "NSKeyUp";
michael@0 255 case NSFlagsChanged: return "NSFlagsChanged";
michael@0 256 default: return "not key event";
michael@0 257 }
michael@0 258 }
michael@0 259
michael@0 260 static const char*
michael@0 261 GetGeckoKeyEventType(const WidgetEvent& aEvent)
michael@0 262 {
michael@0 263 switch (aEvent.message) {
michael@0 264 case NS_KEY_DOWN: return "NS_KEY_DOWN";
michael@0 265 case NS_KEY_UP: return "NS_KEY_UP";
michael@0 266 case NS_KEY_PRESS: return "NS_KEY_PRESS";
michael@0 267 default: return "not key event";
michael@0 268 }
michael@0 269 }
michael@0 270
michael@0 271 static const char*
michael@0 272 GetRangeTypeName(uint32_t aRangeType)
michael@0 273 {
michael@0 274 switch (aRangeType) {
michael@0 275 case NS_TEXTRANGE_RAWINPUT:
michael@0 276 return "NS_TEXTRANGE_RAWINPUT";
michael@0 277 case NS_TEXTRANGE_CONVERTEDTEXT:
michael@0 278 return "NS_TEXTRANGE_CONVERTEDTEXT";
michael@0 279 case NS_TEXTRANGE_SELECTEDRAWTEXT:
michael@0 280 return "NS_TEXTRANGE_SELECTEDRAWTEXT";
michael@0 281 case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT:
michael@0 282 return "NS_TEXTRANGE_SELECTEDCONVERTEDTEXT";
michael@0 283 case NS_TEXTRANGE_CARETPOSITION:
michael@0 284 return "NS_TEXTRANGE_CARETPOSITION";
michael@0 285 default:
michael@0 286 return "invalid range type";
michael@0 287 }
michael@0 288 }
michael@0 289
michael@0 290 static const char*
michael@0 291 GetWindowLevelName(NSInteger aWindowLevel)
michael@0 292 {
michael@0 293 switch (aWindowLevel) {
michael@0 294 case kCGBaseWindowLevelKey:
michael@0 295 return "kCGBaseWindowLevelKey (NSNormalWindowLevel)";
michael@0 296 case kCGMinimumWindowLevelKey:
michael@0 297 return "kCGMinimumWindowLevelKey";
michael@0 298 case kCGDesktopWindowLevelKey:
michael@0 299 return "kCGDesktopWindowLevelKey";
michael@0 300 case kCGBackstopMenuLevelKey:
michael@0 301 return "kCGBackstopMenuLevelKey";
michael@0 302 case kCGNormalWindowLevelKey:
michael@0 303 return "kCGNormalWindowLevelKey";
michael@0 304 case kCGFloatingWindowLevelKey:
michael@0 305 return "kCGFloatingWindowLevelKey (NSFloatingWindowLevel)";
michael@0 306 case kCGTornOffMenuWindowLevelKey:
michael@0 307 return "kCGTornOffMenuWindowLevelKey (NSSubmenuWindowLevel, NSTornOffMenuWindowLevel)";
michael@0 308 case kCGDockWindowLevelKey:
michael@0 309 return "kCGDockWindowLevelKey (NSDockWindowLevel)";
michael@0 310 case kCGMainMenuWindowLevelKey:
michael@0 311 return "kCGMainMenuWindowLevelKey (NSMainMenuWindowLevel)";
michael@0 312 case kCGStatusWindowLevelKey:
michael@0 313 return "kCGStatusWindowLevelKey (NSStatusWindowLevel)";
michael@0 314 case kCGModalPanelWindowLevelKey:
michael@0 315 return "kCGModalPanelWindowLevelKey (NSModalPanelWindowLevel)";
michael@0 316 case kCGPopUpMenuWindowLevelKey:
michael@0 317 return "kCGPopUpMenuWindowLevelKey (NSPopUpMenuWindowLevel)";
michael@0 318 case kCGDraggingWindowLevelKey:
michael@0 319 return "kCGDraggingWindowLevelKey";
michael@0 320 case kCGScreenSaverWindowLevelKey:
michael@0 321 return "kCGScreenSaverWindowLevelKey (NSScreenSaverWindowLevel)";
michael@0 322 case kCGMaximumWindowLevelKey:
michael@0 323 return "kCGMaximumWindowLevelKey";
michael@0 324 case kCGOverlayWindowLevelKey:
michael@0 325 return "kCGOverlayWindowLevelKey";
michael@0 326 case kCGHelpWindowLevelKey:
michael@0 327 return "kCGHelpWindowLevelKey";
michael@0 328 case kCGUtilityWindowLevelKey:
michael@0 329 return "kCGUtilityWindowLevelKey";
michael@0 330 case kCGDesktopIconWindowLevelKey:
michael@0 331 return "kCGDesktopIconWindowLevelKey";
michael@0 332 case kCGCursorWindowLevelKey:
michael@0 333 return "kCGCursorWindowLevelKey";
michael@0 334 case kCGNumberOfWindowLevelKeys:
michael@0 335 return "kCGNumberOfWindowLevelKeys";
michael@0 336 default:
michael@0 337 return "unknown window level";
michael@0 338 }
michael@0 339 }
michael@0 340
michael@0 341 #endif // #ifdef PR_LOGGING
michael@0 342
michael@0 343 static bool
michael@0 344 IsControlChar(uint32_t aCharCode)
michael@0 345 {
michael@0 346 return aCharCode < ' ' || aCharCode == 0x7F;
michael@0 347 }
michael@0 348
michael@0 349 static uint32_t gHandlerInstanceCount = 0;
michael@0 350 static TISInputSourceWrapper gCurrentInputSource;
michael@0 351
michael@0 352 static void
michael@0 353 InitLogModule()
michael@0 354 {
michael@0 355 #ifdef PR_LOGGING
michael@0 356 // Clear() is always called when TISInputSourceWrappper is created.
michael@0 357 if (!gLog) {
michael@0 358 gLog = PR_NewLogModule("TextInputHandlerWidgets");
michael@0 359 TextInputHandler::DebugPrintAllKeyboardLayouts();
michael@0 360 IMEInputHandler::DebugPrintAllIMEModes();
michael@0 361 }
michael@0 362 #endif
michael@0 363 }
michael@0 364
michael@0 365 static void
michael@0 366 InitCurrentInputSource()
michael@0 367 {
michael@0 368 if (gHandlerInstanceCount > 0 &&
michael@0 369 !gCurrentInputSource.IsInitializedByCurrentInputSource()) {
michael@0 370 gCurrentInputSource.InitByCurrentInputSource();
michael@0 371 }
michael@0 372 }
michael@0 373
michael@0 374 static void
michael@0 375 FinalizeCurrentInputSource()
michael@0 376 {
michael@0 377 gCurrentInputSource.Clear();
michael@0 378 }
michael@0 379
michael@0 380
michael@0 381 #pragma mark -
michael@0 382
michael@0 383
michael@0 384 /******************************************************************************
michael@0 385 *
michael@0 386 * TISInputSourceWrapper implementation
michael@0 387 *
michael@0 388 ******************************************************************************/
michael@0 389
michael@0 390 // static
michael@0 391 TISInputSourceWrapper&
michael@0 392 TISInputSourceWrapper::CurrentInputSource()
michael@0 393 {
michael@0 394 InitCurrentInputSource();
michael@0 395 return gCurrentInputSource;
michael@0 396 }
michael@0 397
michael@0 398 bool
michael@0 399 TISInputSourceWrapper::TranslateToString(UInt32 aKeyCode, UInt32 aModifiers,
michael@0 400 UInt32 aKbType, nsAString &aStr)
michael@0 401 {
michael@0 402 aStr.Truncate();
michael@0 403
michael@0 404 const UCKeyboardLayout* UCKey = GetUCKeyboardLayout();
michael@0 405
michael@0 406 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 407 ("%p TISInputSourceWrapper::TranslateToString, aKeyCode=0x%X, "
michael@0 408 "aModifiers=0x%X, aKbType=0x%X UCKey=%p\n "
michael@0 409 "Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s",
michael@0 410 this, aKeyCode, aModifiers, aKbType, UCKey,
michael@0 411 OnOrOff(aModifiers & shiftKey), OnOrOff(aModifiers & controlKey),
michael@0 412 OnOrOff(aModifiers & optionKey), OnOrOff(aModifiers & cmdKey),
michael@0 413 OnOrOff(aModifiers & alphaLock),
michael@0 414 OnOrOff(aModifiers & kEventKeyModifierNumLockMask)));
michael@0 415
michael@0 416 NS_ENSURE_TRUE(UCKey, false);
michael@0 417
michael@0 418 UInt32 deadKeyState = 0;
michael@0 419 UniCharCount len;
michael@0 420 UniChar chars[5];
michael@0 421 OSStatus err = ::UCKeyTranslate(UCKey, aKeyCode,
michael@0 422 kUCKeyActionDown, aModifiers >> 8,
michael@0 423 aKbType, kUCKeyTranslateNoDeadKeysMask,
michael@0 424 &deadKeyState, 5, &len, chars);
michael@0 425
michael@0 426 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 427 ("%p TISInputSourceWrapper::TranslateToString, err=0x%X, len=%llu",
michael@0 428 this, err, len));
michael@0 429
michael@0 430 NS_ENSURE_TRUE(err == noErr, false);
michael@0 431 if (len == 0) {
michael@0 432 return true;
michael@0 433 }
michael@0 434 NS_ENSURE_TRUE(EnsureStringLength(aStr, len), false);
michael@0 435 NS_ASSERTION(sizeof(char16_t) == sizeof(UniChar),
michael@0 436 "size of char16_t and size of UniChar are different");
michael@0 437 memcpy(aStr.BeginWriting(), chars, len * sizeof(char16_t));
michael@0 438
michael@0 439 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 440 ("%p TISInputSourceWrapper::TranslateToString, aStr=\"%s\"",
michael@0 441 this, NS_ConvertUTF16toUTF8(aStr).get()));
michael@0 442
michael@0 443 return true;
michael@0 444 }
michael@0 445
michael@0 446 uint32_t
michael@0 447 TISInputSourceWrapper::TranslateToChar(UInt32 aKeyCode, UInt32 aModifiers,
michael@0 448 UInt32 aKbType)
michael@0 449 {
michael@0 450 nsAutoString str;
michael@0 451 if (!TranslateToString(aKeyCode, aModifiers, aKbType, str) ||
michael@0 452 str.Length() != 1) {
michael@0 453 return 0;
michael@0 454 }
michael@0 455 return static_cast<uint32_t>(str.CharAt(0));
michael@0 456 }
michael@0 457
michael@0 458 void
michael@0 459 TISInputSourceWrapper::InitByInputSourceID(const char* aID)
michael@0 460 {
michael@0 461 Clear();
michael@0 462 if (!aID)
michael@0 463 return;
michael@0 464
michael@0 465 CFStringRef idstr = ::CFStringCreateWithCString(kCFAllocatorDefault, aID,
michael@0 466 kCFStringEncodingASCII);
michael@0 467 InitByInputSourceID(idstr);
michael@0 468 ::CFRelease(idstr);
michael@0 469 }
michael@0 470
michael@0 471 void
michael@0 472 TISInputSourceWrapper::InitByInputSourceID(const nsAFlatString &aID)
michael@0 473 {
michael@0 474 Clear();
michael@0 475 if (aID.IsEmpty())
michael@0 476 return;
michael@0 477 CFStringRef idstr = ::CFStringCreateWithCharacters(kCFAllocatorDefault,
michael@0 478 reinterpret_cast<const UniChar*>(aID.get()),
michael@0 479 aID.Length());
michael@0 480 InitByInputSourceID(idstr);
michael@0 481 ::CFRelease(idstr);
michael@0 482 }
michael@0 483
michael@0 484 void
michael@0 485 TISInputSourceWrapper::InitByInputSourceID(const CFStringRef aID)
michael@0 486 {
michael@0 487 Clear();
michael@0 488 if (!aID)
michael@0 489 return;
michael@0 490 const void* keys[] = { kTISPropertyInputSourceID };
michael@0 491 const void* values[] = { aID };
michael@0 492 CFDictionaryRef filter =
michael@0 493 ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
michael@0 494 NS_ASSERTION(filter, "failed to create the filter");
michael@0 495 mInputSourceList = ::TISCreateInputSourceList(filter, true);
michael@0 496 ::CFRelease(filter);
michael@0 497 if (::CFArrayGetCount(mInputSourceList) > 0) {
michael@0 498 mInputSource = static_cast<TISInputSourceRef>(
michael@0 499 const_cast<void *>(::CFArrayGetValueAtIndex(mInputSourceList, 0)));
michael@0 500 if (IsKeyboardLayout()) {
michael@0 501 mKeyboardLayout = mInputSource;
michael@0 502 }
michael@0 503 }
michael@0 504 }
michael@0 505
michael@0 506 void
michael@0 507 TISInputSourceWrapper::InitByLayoutID(SInt32 aLayoutID,
michael@0 508 bool aOverrideKeyboard)
michael@0 509 {
michael@0 510 // NOTE: Doument new layout IDs in TextInputHandler.h when you add ones.
michael@0 511 switch (aLayoutID) {
michael@0 512 case 0:
michael@0 513 InitByInputSourceID("com.apple.keylayout.US");
michael@0 514 break;
michael@0 515 case 1:
michael@0 516 InitByInputSourceID("com.apple.keylayout.Greek");
michael@0 517 break;
michael@0 518 case 2:
michael@0 519 InitByInputSourceID("com.apple.keylayout.German");
michael@0 520 break;
michael@0 521 case 3:
michael@0 522 InitByInputSourceID("com.apple.keylayout.Swedish-Pro");
michael@0 523 break;
michael@0 524 case 4:
michael@0 525 InitByInputSourceID("com.apple.keylayout.DVORAK-QWERTYCMD");
michael@0 526 break;
michael@0 527 case 5:
michael@0 528 InitByInputSourceID("com.apple.keylayout.Thai");
michael@0 529 break;
michael@0 530 case 6:
michael@0 531 InitByInputSourceID("com.apple.keylayout.Arabic");
michael@0 532 break;
michael@0 533 case 7:
michael@0 534 InitByInputSourceID("com.apple.keylayout.French");
michael@0 535 break;
michael@0 536 case 8:
michael@0 537 InitByInputSourceID("com.apple.keylayout.Hebrew");
michael@0 538 break;
michael@0 539 case 9:
michael@0 540 InitByInputSourceID("com.apple.keylayout.Lithuanian");
michael@0 541 break;
michael@0 542 case 10:
michael@0 543 InitByInputSourceID("com.apple.keylayout.Norwegian");
michael@0 544 break;
michael@0 545 case 11:
michael@0 546 InitByInputSourceID("com.apple.keylayout.Spanish");
michael@0 547 break;
michael@0 548 default:
michael@0 549 Clear();
michael@0 550 break;
michael@0 551 }
michael@0 552 mOverrideKeyboard = aOverrideKeyboard;
michael@0 553 }
michael@0 554
michael@0 555 void
michael@0 556 TISInputSourceWrapper::InitByCurrentInputSource()
michael@0 557 {
michael@0 558 Clear();
michael@0 559 mInputSource = ::TISCopyCurrentKeyboardInputSource();
michael@0 560 mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
michael@0 561 if (!mKeyboardLayout) {
michael@0 562 mKeyboardLayout = ::TISCopyCurrentKeyboardLayoutInputSource();
michael@0 563 }
michael@0 564 // If this causes composition, the current keyboard layout may input non-ASCII
michael@0 565 // characters such as Japanese Kana characters or Hangul characters.
michael@0 566 // However, we need to set ASCII characters to DOM key events for consistency
michael@0 567 // with other platforms.
michael@0 568 if (IsOpenedIMEMode()) {
michael@0 569 TISInputSourceWrapper tis(mKeyboardLayout);
michael@0 570 if (!tis.IsASCIICapable()) {
michael@0 571 mKeyboardLayout =
michael@0 572 ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
michael@0 573 }
michael@0 574 }
michael@0 575 }
michael@0 576
michael@0 577 void
michael@0 578 TISInputSourceWrapper::InitByCurrentKeyboardLayout()
michael@0 579 {
michael@0 580 Clear();
michael@0 581 mInputSource = ::TISCopyCurrentKeyboardLayoutInputSource();
michael@0 582 mKeyboardLayout = mInputSource;
michael@0 583 }
michael@0 584
michael@0 585 void
michael@0 586 TISInputSourceWrapper::InitByCurrentASCIICapableInputSource()
michael@0 587 {
michael@0 588 Clear();
michael@0 589 mInputSource = ::TISCopyCurrentASCIICapableKeyboardInputSource();
michael@0 590 mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
michael@0 591 if (mKeyboardLayout) {
michael@0 592 TISInputSourceWrapper tis(mKeyboardLayout);
michael@0 593 if (!tis.IsASCIICapable()) {
michael@0 594 mKeyboardLayout = nullptr;
michael@0 595 }
michael@0 596 }
michael@0 597 if (!mKeyboardLayout) {
michael@0 598 mKeyboardLayout =
michael@0 599 ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
michael@0 600 }
michael@0 601 }
michael@0 602
michael@0 603 void
michael@0 604 TISInputSourceWrapper::InitByCurrentASCIICapableKeyboardLayout()
michael@0 605 {
michael@0 606 Clear();
michael@0 607 mInputSource = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
michael@0 608 mKeyboardLayout = mInputSource;
michael@0 609 }
michael@0 610
michael@0 611 void
michael@0 612 TISInputSourceWrapper::InitByCurrentInputMethodKeyboardLayoutOverride()
michael@0 613 {
michael@0 614 Clear();
michael@0 615 mInputSource = ::TISCopyInputMethodKeyboardLayoutOverride();
michael@0 616 mKeyboardLayout = mInputSource;
michael@0 617 }
michael@0 618
michael@0 619 void
michael@0 620 TISInputSourceWrapper::InitByTISInputSourceRef(TISInputSourceRef aInputSource)
michael@0 621 {
michael@0 622 Clear();
michael@0 623 mInputSource = aInputSource;
michael@0 624 if (IsKeyboardLayout()) {
michael@0 625 mKeyboardLayout = mInputSource;
michael@0 626 }
michael@0 627 }
michael@0 628
michael@0 629 void
michael@0 630 TISInputSourceWrapper::InitByLanguage(CFStringRef aLanguage)
michael@0 631 {
michael@0 632 Clear();
michael@0 633 mInputSource = ::TISCopyInputSourceForLanguage(aLanguage);
michael@0 634 if (IsKeyboardLayout()) {
michael@0 635 mKeyboardLayout = mInputSource;
michael@0 636 }
michael@0 637 }
michael@0 638
michael@0 639 const UCKeyboardLayout*
michael@0 640 TISInputSourceWrapper::GetUCKeyboardLayout()
michael@0 641 {
michael@0 642 NS_ENSURE_TRUE(mKeyboardLayout, nullptr);
michael@0 643 if (mUCKeyboardLayout) {
michael@0 644 return mUCKeyboardLayout;
michael@0 645 }
michael@0 646 CFDataRef uchr = static_cast<CFDataRef>(
michael@0 647 ::TISGetInputSourceProperty(mKeyboardLayout,
michael@0 648 kTISPropertyUnicodeKeyLayoutData));
michael@0 649
michael@0 650 // We should be always able to get the layout here.
michael@0 651 NS_ENSURE_TRUE(uchr, nullptr);
michael@0 652 mUCKeyboardLayout =
michael@0 653 reinterpret_cast<const UCKeyboardLayout*>(CFDataGetBytePtr(uchr));
michael@0 654 return mUCKeyboardLayout;
michael@0 655 }
michael@0 656
michael@0 657 bool
michael@0 658 TISInputSourceWrapper::GetBoolProperty(const CFStringRef aKey)
michael@0 659 {
michael@0 660 CFBooleanRef ret = static_cast<CFBooleanRef>(
michael@0 661 ::TISGetInputSourceProperty(mInputSource, aKey));
michael@0 662 return ::CFBooleanGetValue(ret);
michael@0 663 }
michael@0 664
michael@0 665 bool
michael@0 666 TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey,
michael@0 667 CFStringRef &aStr)
michael@0 668 {
michael@0 669 aStr = static_cast<CFStringRef>(
michael@0 670 ::TISGetInputSourceProperty(mInputSource, aKey));
michael@0 671 return aStr != nullptr;
michael@0 672 }
michael@0 673
michael@0 674 bool
michael@0 675 TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey,
michael@0 676 nsAString &aStr)
michael@0 677 {
michael@0 678 CFStringRef str;
michael@0 679 GetStringProperty(aKey, str);
michael@0 680 nsCocoaUtils::GetStringForNSString((const NSString*)str, aStr);
michael@0 681 return !aStr.IsEmpty();
michael@0 682 }
michael@0 683
michael@0 684 bool
michael@0 685 TISInputSourceWrapper::IsOpenedIMEMode()
michael@0 686 {
michael@0 687 NS_ENSURE_TRUE(mInputSource, false);
michael@0 688 if (!IsIMEMode())
michael@0 689 return false;
michael@0 690 return !IsASCIICapable();
michael@0 691 }
michael@0 692
michael@0 693 bool
michael@0 694 TISInputSourceWrapper::IsIMEMode()
michael@0 695 {
michael@0 696 NS_ENSURE_TRUE(mInputSource, false);
michael@0 697 CFStringRef str;
michael@0 698 GetInputSourceType(str);
michael@0 699 NS_ENSURE_TRUE(str, false);
michael@0 700 return ::CFStringCompare(kTISTypeKeyboardInputMode,
michael@0 701 str, 0) == kCFCompareEqualTo;
michael@0 702 }
michael@0 703
michael@0 704 bool
michael@0 705 TISInputSourceWrapper::IsKeyboardLayout()
michael@0 706 {
michael@0 707 NS_ENSURE_TRUE(mInputSource, false);
michael@0 708 CFStringRef str;
michael@0 709 GetInputSourceType(str);
michael@0 710 NS_ENSURE_TRUE(str, false);
michael@0 711 return ::CFStringCompare(kTISTypeKeyboardLayout,
michael@0 712 str, 0) == kCFCompareEqualTo;
michael@0 713 }
michael@0 714
michael@0 715 bool
michael@0 716 TISInputSourceWrapper::GetLanguageList(CFArrayRef &aLanguageList)
michael@0 717 {
michael@0 718 NS_ENSURE_TRUE(mInputSource, false);
michael@0 719 aLanguageList = static_cast<CFArrayRef>(
michael@0 720 ::TISGetInputSourceProperty(mInputSource,
michael@0 721 kTISPropertyInputSourceLanguages));
michael@0 722 return aLanguageList != nullptr;
michael@0 723 }
michael@0 724
michael@0 725 bool
michael@0 726 TISInputSourceWrapper::GetPrimaryLanguage(CFStringRef &aPrimaryLanguage)
michael@0 727 {
michael@0 728 NS_ENSURE_TRUE(mInputSource, false);
michael@0 729 CFArrayRef langList;
michael@0 730 NS_ENSURE_TRUE(GetLanguageList(langList), false);
michael@0 731 if (::CFArrayGetCount(langList) == 0)
michael@0 732 return false;
michael@0 733 aPrimaryLanguage =
michael@0 734 static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, 0));
michael@0 735 return aPrimaryLanguage != nullptr;
michael@0 736 }
michael@0 737
michael@0 738 bool
michael@0 739 TISInputSourceWrapper::GetPrimaryLanguage(nsAString &aPrimaryLanguage)
michael@0 740 {
michael@0 741 NS_ENSURE_TRUE(mInputSource, false);
michael@0 742 CFStringRef primaryLanguage;
michael@0 743 NS_ENSURE_TRUE(GetPrimaryLanguage(primaryLanguage), false);
michael@0 744 nsCocoaUtils::GetStringForNSString((const NSString*)primaryLanguage,
michael@0 745 aPrimaryLanguage);
michael@0 746 return !aPrimaryLanguage.IsEmpty();
michael@0 747 }
michael@0 748
michael@0 749 bool
michael@0 750 TISInputSourceWrapper::IsForRTLLanguage()
michael@0 751 {
michael@0 752 if (mIsRTL < 0) {
michael@0 753 // Get the input character of the 'A' key of ANSI keyboard layout.
michael@0 754 nsAutoString str;
michael@0 755 bool ret = TranslateToString(kVK_ANSI_A, 0, eKbdType_ANSI, str);
michael@0 756 NS_ENSURE_TRUE(ret, ret);
michael@0 757 char16_t ch = str.IsEmpty() ? char16_t(0) : str.CharAt(0);
michael@0 758 mIsRTL = UCS2_CHAR_IS_BIDI(ch) || ch == 0xD802 || ch == 0xD803;
michael@0 759 }
michael@0 760 return mIsRTL != 0;
michael@0 761 }
michael@0 762
michael@0 763 bool
michael@0 764 TISInputSourceWrapper::IsInitializedByCurrentInputSource()
michael@0 765 {
michael@0 766 return mInputSource == ::TISCopyCurrentKeyboardInputSource();
michael@0 767 }
michael@0 768
michael@0 769 void
michael@0 770 TISInputSourceWrapper::Select()
michael@0 771 {
michael@0 772 if (!mInputSource)
michael@0 773 return;
michael@0 774 ::TISSelectInputSource(mInputSource);
michael@0 775 }
michael@0 776
michael@0 777 void
michael@0 778 TISInputSourceWrapper::Clear()
michael@0 779 {
michael@0 780 // Clear() is always called when TISInputSourceWrappper is created.
michael@0 781 InitLogModule();
michael@0 782
michael@0 783 if (mInputSourceList) {
michael@0 784 ::CFRelease(mInputSourceList);
michael@0 785 }
michael@0 786 mInputSourceList = nullptr;
michael@0 787 mInputSource = nullptr;
michael@0 788 mKeyboardLayout = nullptr;
michael@0 789 mIsRTL = -1;
michael@0 790 mUCKeyboardLayout = nullptr;
michael@0 791 mOverrideKeyboard = false;
michael@0 792 }
michael@0 793
michael@0 794 void
michael@0 795 TISInputSourceWrapper::InitKeyEvent(NSEvent *aNativeKeyEvent,
michael@0 796 WidgetKeyboardEvent& aKeyEvent,
michael@0 797 const nsAString *aInsertString)
michael@0 798 {
michael@0 799 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 800
michael@0 801 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 802 ("%p TISInputSourceWrapper::InitKeyEvent, aNativeKeyEvent=%p, "
michael@0 803 "aKeyEvent.message=%s, aInsertString=%p, IsOpenedIMEMode()=%s",
michael@0 804 this, aNativeKeyEvent, GetGeckoKeyEventType(aKeyEvent), aInsertString,
michael@0 805 TrueOrFalse(IsOpenedIMEMode())));
michael@0 806
michael@0 807 NS_ENSURE_TRUE(aNativeKeyEvent, );
michael@0 808
michael@0 809 nsCocoaUtils::InitInputEvent(aKeyEvent, aNativeKeyEvent);
michael@0 810
michael@0 811 // This is used only while dispatching the event (which is a synchronous
michael@0 812 // call), so there is no need to retain and release this data.
michael@0 813 aKeyEvent.mNativeKeyEvent = aNativeKeyEvent;
michael@0 814
michael@0 815 aKeyEvent.refPoint = LayoutDeviceIntPoint(0, 0);
michael@0 816
michael@0 817 // If a keyboard layout override is set, we also need to force the keyboard
michael@0 818 // type to something ANSI to avoid test failures on machines with JIS
michael@0 819 // keyboards (since the pair of keyboard layout and physical keyboard type
michael@0 820 // form the actual key layout). This assumes that the test setting the
michael@0 821 // override was written assuming an ANSI keyboard.
michael@0 822 UInt32 kbType = mOverrideKeyboard ? eKbdType_ANSI : ::LMGetKbdType();
michael@0 823
michael@0 824 UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
michael@0 825
michael@0 826 bool isPrintableKey = !TextInputHandler::IsSpecialGeckoKey(nativeKeyCode);
michael@0 827 if (isPrintableKey &&
michael@0 828 [aNativeKeyEvent type] != NSKeyDown &&
michael@0 829 [aNativeKeyEvent type] != NSKeyUp) {
michael@0 830 NS_WARNING("Why the printable key doesn't cause NSKeyDown or NSKeyUp?");
michael@0 831 isPrintableKey = false;
michael@0 832 }
michael@0 833
michael@0 834 // Decide what string will be input.
michael@0 835 nsAutoString insertString;
michael@0 836 if (aInsertString) {
michael@0 837 // If the caller expects that the aInsertString will be input, we shouldn't
michael@0 838 // change it.
michael@0 839 insertString = *aInsertString;
michael@0 840 } else if (isPrintableKey) {
michael@0 841 // If IME is open, [aNativeKeyEvent characters] may be a character
michael@0 842 // which will be appended to the composition string. However, especially,
michael@0 843 // while IME is disabled, most users and developers expect the key event
michael@0 844 // works as IME closed. So, we should compute the insertString with
michael@0 845 // the ASCII capable keyboard layout.
michael@0 846 // NOTE: Such keyboard layouts typically change the layout to its ASCII
michael@0 847 // capable layout when Command key is pressed. And we don't worry
michael@0 848 // when Control key is pressed too because it causes inputting
michael@0 849 // control characters.
michael@0 850 if (!aKeyEvent.IsMeta() && !aKeyEvent.IsControl() && IsOpenedIMEMode()) {
michael@0 851 UInt32 state =
michael@0 852 nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
michael@0 853 uint32_t ch = TranslateToChar(nativeKeyCode, state, kbType);
michael@0 854 if (ch) {
michael@0 855 insertString = ch;
michael@0 856 }
michael@0 857 } else {
michael@0 858 // If the caller isn't sure what string will be input, let's use
michael@0 859 // characters of NSEvent.
michael@0 860 nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
michael@0 861 insertString);
michael@0 862 }
michael@0 863
michael@0 864 // If control key is pressed and the eventChars is a non-printable control
michael@0 865 // character, we should convert it to ASCII alphabet.
michael@0 866 if (aKeyEvent.IsControl() &&
michael@0 867 !insertString.IsEmpty() && insertString[0] <= char16_t(26)) {
michael@0 868 insertString = (aKeyEvent.IsShift() ^ aKeyEvent.IsCapsLocked()) ?
michael@0 869 static_cast<char16_t>(insertString[0] + ('A' - 1)) :
michael@0 870 static_cast<char16_t>(insertString[0] + ('a' - 1));
michael@0 871 }
michael@0 872 // If Meta key is pressed, it may cause to switch the keyboard layout like
michael@0 873 // Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
michael@0 874 else if (aKeyEvent.IsMeta() &&
michael@0 875 !(aKeyEvent.IsControl() || aKeyEvent.IsAlt())) {
michael@0 876 UInt32 numLockState =
michael@0 877 aKeyEvent.IsNumLocked() ? kEventKeyModifierNumLockMask : 0;
michael@0 878 UInt32 capsLockState = aKeyEvent.IsCapsLocked() ? alphaLock : 0;
michael@0 879 UInt32 shiftState = aKeyEvent.IsShift() ? shiftKey : 0;
michael@0 880 uint32_t uncmdedChar =
michael@0 881 TranslateToChar(nativeKeyCode, numLockState, kbType);
michael@0 882 uint32_t cmdedChar =
michael@0 883 TranslateToChar(nativeKeyCode, cmdKey | numLockState, kbType);
michael@0 884 // If we can make a good guess at the characters that the user would
michael@0 885 // expect this key combination to produce (with and without Shift) then
michael@0 886 // use those characters. This also corrects for CapsLock.
michael@0 887 uint32_t ch = 0;
michael@0 888 if (uncmdedChar == cmdedChar) {
michael@0 889 // The characters produced with Command seem similar to those without
michael@0 890 // Command.
michael@0 891 ch = TranslateToChar(nativeKeyCode,
michael@0 892 shiftState | capsLockState | numLockState, kbType);
michael@0 893 } else {
michael@0 894 TISInputSourceWrapper USLayout("com.apple.keylayout.US");
michael@0 895 uint32_t uncmdedUSChar =
michael@0 896 USLayout.TranslateToChar(nativeKeyCode, numLockState, kbType);
michael@0 897 // If it looks like characters from US keyboard layout when Command key
michael@0 898 // is pressed, we should compute a character in the layout.
michael@0 899 if (uncmdedUSChar == cmdedChar) {
michael@0 900 ch = USLayout.TranslateToChar(nativeKeyCode,
michael@0 901 shiftState | capsLockState | numLockState, kbType);
michael@0 902 }
michael@0 903 }
michael@0 904
michael@0 905 // If there is a more preferred character for the commanded key event,
michael@0 906 // we should use it.
michael@0 907 if (ch) {
michael@0 908 insertString = ch;
michael@0 909 }
michael@0 910 }
michael@0 911 }
michael@0 912
michael@0 913 // Remove control characters which shouldn't be inputted on editor.
michael@0 914 // XXX Currently, we don't find any cases inserting control characters with
michael@0 915 // printable character. So, just checking first character is enough.
michael@0 916 if (!insertString.IsEmpty() && IsControlChar(insertString[0])) {
michael@0 917 insertString.Truncate();
michael@0 918 }
michael@0 919
michael@0 920 aKeyEvent.keyCode =
michael@0 921 ComputeGeckoKeyCode(nativeKeyCode, kbType, aKeyEvent.IsMeta());
michael@0 922
michael@0 923 switch (nativeKeyCode) {
michael@0 924 case kVK_Command:
michael@0 925 case kVK_Shift:
michael@0 926 case kVK_Option:
michael@0 927 case kVK_Control:
michael@0 928 aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT;
michael@0 929 break;
michael@0 930
michael@0 931 case kVK_RightCommand:
michael@0 932 case kVK_RightShift:
michael@0 933 case kVK_RightOption:
michael@0 934 case kVK_RightControl:
michael@0 935 aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT;
michael@0 936 break;
michael@0 937
michael@0 938 case kVK_ANSI_Keypad0:
michael@0 939 case kVK_ANSI_Keypad1:
michael@0 940 case kVK_ANSI_Keypad2:
michael@0 941 case kVK_ANSI_Keypad3:
michael@0 942 case kVK_ANSI_Keypad4:
michael@0 943 case kVK_ANSI_Keypad5:
michael@0 944 case kVK_ANSI_Keypad6:
michael@0 945 case kVK_ANSI_Keypad7:
michael@0 946 case kVK_ANSI_Keypad8:
michael@0 947 case kVK_ANSI_Keypad9:
michael@0 948 case kVK_ANSI_KeypadMultiply:
michael@0 949 case kVK_ANSI_KeypadPlus:
michael@0 950 case kVK_ANSI_KeypadMinus:
michael@0 951 case kVK_ANSI_KeypadDecimal:
michael@0 952 case kVK_ANSI_KeypadDivide:
michael@0 953 case kVK_ANSI_KeypadEquals:
michael@0 954 case kVK_ANSI_KeypadEnter:
michael@0 955 case kVK_JIS_KeypadComma:
michael@0 956 case kVK_Powerbook_KeypadEnter:
michael@0 957 aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
michael@0 958 break;
michael@0 959
michael@0 960 default:
michael@0 961 aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
michael@0 962 break;
michael@0 963 }
michael@0 964
michael@0 965 aKeyEvent.mIsRepeat =
michael@0 966 ([aNativeKeyEvent type] == NSKeyDown) ? [aNativeKeyEvent isARepeat] : false;
michael@0 967
michael@0 968 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 969 ("%p TISInputSourceWrapper::InitKeyEvent, "
michael@0 970 "shift=%s, ctrl=%s, alt=%s, meta=%s",
michael@0 971 this, OnOrOff(aKeyEvent.IsShift()), OnOrOff(aKeyEvent.IsControl()),
michael@0 972 OnOrOff(aKeyEvent.IsAlt()), OnOrOff(aKeyEvent.IsMeta())));
michael@0 973
michael@0 974 if (aKeyEvent.message == NS_KEY_PRESS &&
michael@0 975 (isPrintableKey || !insertString.IsEmpty())) {
michael@0 976 InitKeyPressEvent(aNativeKeyEvent,
michael@0 977 insertString.IsEmpty() ? 0 : insertString[0],
michael@0 978 aKeyEvent, kbType);
michael@0 979 MOZ_ASSERT(!aKeyEvent.charCode || !IsControlChar(aKeyEvent.charCode),
michael@0 980 "charCode must not be a control character");
michael@0 981 } else {
michael@0 982 aKeyEvent.charCode = 0;
michael@0 983 aKeyEvent.isChar = false; // XXX not used in XP level
michael@0 984
michael@0 985 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 986 ("%p TISInputSourceWrapper::InitKeyEvent, keyCode=0x%X charCode=0x0",
michael@0 987 this, aKeyEvent.keyCode));
michael@0 988 }
michael@0 989
michael@0 990 if (isPrintableKey) {
michael@0 991 aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
michael@0 992 // If insertText calls this method, let's use the string.
michael@0 993 if (aInsertString && !aInsertString->IsEmpty() &&
michael@0 994 !IsControlChar((*aInsertString)[0])) {
michael@0 995 aKeyEvent.mKeyValue = *aInsertString;
michael@0 996 }
michael@0 997 // If meta key is pressed, the printable key layout may be switched from
michael@0 998 // non-ASCII capable layout to ASCII capable, or from Dvorak to QWERTY.
michael@0 999 // KeyboardEvent.key value should be the switched layout's character.
michael@0 1000 else if (aKeyEvent.IsMeta()) {
michael@0 1001 nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
michael@0 1002 aKeyEvent.mKeyValue);
michael@0 1003 }
michael@0 1004 // If control key is pressed, some keys may produce printable character via
michael@0 1005 // [aNativeKeyEvent characters]. Otherwise, translate input character of
michael@0 1006 // the key without control key.
michael@0 1007 else if (aKeyEvent.IsControl()) {
michael@0 1008 nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
michael@0 1009 aKeyEvent.mKeyValue);
michael@0 1010 if (aKeyEvent.mKeyValue.IsEmpty() ||
michael@0 1011 IsControlChar(aKeyEvent.mKeyValue[0])) {
michael@0 1012 NSUInteger cocoaState =
michael@0 1013 [aNativeKeyEvent modifierFlags] & ~NSControlKeyMask;
michael@0 1014 UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
michael@0 1015 aKeyEvent.mKeyValue =
michael@0 1016 TranslateToChar(nativeKeyCode, carbonState, kbType);
michael@0 1017 }
michael@0 1018 }
michael@0 1019 // Otherwise, KeyboardEvent.key expose
michael@0 1020 // [aNativeKeyEvent characters] value. However, if IME is open and the
michael@0 1021 // keyboard layout isn't ASCII capable, exposing the non-ASCII character
michael@0 1022 // doesn't match with other platform's behavior. For the compatibility
michael@0 1023 // with other platform's Gecko, we need to set a translated character.
michael@0 1024 else if (IsOpenedIMEMode()) {
michael@0 1025 UInt32 state =
michael@0 1026 nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
michael@0 1027 aKeyEvent.mKeyValue = TranslateToChar(nativeKeyCode, state, kbType);
michael@0 1028 } else {
michael@0 1029 nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
michael@0 1030 aKeyEvent.mKeyValue);
michael@0 1031 }
michael@0 1032
michael@0 1033 // Last resort. If .key value becomes empty string, we should use
michael@0 1034 // charactersIgnoringModifiers, if it's available.
michael@0 1035 if (aKeyEvent.mKeyValue.IsEmpty() ||
michael@0 1036 IsControlChar(aKeyEvent.mKeyValue[0])) {
michael@0 1037 nsCocoaUtils::GetStringForNSString(
michael@0 1038 [aNativeKeyEvent charactersIgnoringModifiers], aKeyEvent.mKeyValue);
michael@0 1039 // But don't expose it if it's a control character.
michael@0 1040 if (!aKeyEvent.mKeyValue.IsEmpty() &&
michael@0 1041 IsControlChar(aKeyEvent.mKeyValue[0])) {
michael@0 1042 aKeyEvent.mKeyValue.Truncate();
michael@0 1043 }
michael@0 1044 }
michael@0 1045 } else {
michael@0 1046 // Compute the key for non-printable keys and some special printable keys.
michael@0 1047 aKeyEvent.mKeyNameIndex = ComputeGeckoKeyNameIndex(nativeKeyCode);
michael@0 1048 }
michael@0 1049
michael@0 1050 NS_OBJC_END_TRY_ABORT_BLOCK
michael@0 1051 }
michael@0 1052
michael@0 1053 void
michael@0 1054 TISInputSourceWrapper::InitKeyPressEvent(NSEvent *aNativeKeyEvent,
michael@0 1055 char16_t aInsertChar,
michael@0 1056 WidgetKeyboardEvent& aKeyEvent,
michael@0 1057 UInt32 aKbType)
michael@0 1058 {
michael@0 1059 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 1060
michael@0 1061 NS_ASSERTION(aKeyEvent.message == NS_KEY_PRESS,
michael@0 1062 "aKeyEvent must be NS_KEY_PRESS event");
michael@0 1063
michael@0 1064 #ifdef PR_LOGGING
michael@0 1065 if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
michael@0 1066 nsAutoString chars;
michael@0 1067 nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], chars);
michael@0 1068 NS_ConvertUTF16toUTF8 utf8Chars(chars);
michael@0 1069 char16_t expectedChar = static_cast<char16_t>(aInsertChar);
michael@0 1070 NS_ConvertUTF16toUTF8 utf8ExpectedChar(&expectedChar, 1);
michael@0 1071 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1072 ("%p TISInputSourceWrapper::InitKeyPressEvent, aNativeKeyEvent=%p, "
michael@0 1073 "[aNativeKeyEvent characters]=\"%s\", aInsertChar=0x%X(%s), "
michael@0 1074 "aKeyEvent.message=%s, aKbType=0x%X, IsOpenedIMEMode()=%s",
michael@0 1075 this, aNativeKeyEvent, utf8Chars.get(), aInsertChar,
michael@0 1076 utf8ExpectedChar.get(), GetGeckoKeyEventType(aKeyEvent), aKbType,
michael@0 1077 TrueOrFalse(IsOpenedIMEMode())));
michael@0 1078 }
michael@0 1079 #endif // #ifdef PR_LOGGING
michael@0 1080
michael@0 1081 aKeyEvent.isChar = true; // this is not a special key XXX not used in XP
michael@0 1082 aKeyEvent.charCode = aInsertChar;
michael@0 1083 if (aKeyEvent.charCode != 0) {
michael@0 1084 aKeyEvent.keyCode = 0;
michael@0 1085 }
michael@0 1086
michael@0 1087 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1088 ("%p TISInputSourceWrapper::InitKeyPressEvent, "
michael@0 1089 "aKeyEvent.keyCode=0x%X, aKeyEvent.charCode=0x%X",
michael@0 1090 this, aKeyEvent.keyCode, aKeyEvent.charCode));
michael@0 1091
michael@0 1092 if (!aKeyEvent.IsControl() && !aKeyEvent.IsMeta() && !aKeyEvent.IsAlt()) {
michael@0 1093 return;
michael@0 1094 }
michael@0 1095
michael@0 1096 TISInputSourceWrapper USLayout("com.apple.keylayout.US");
michael@0 1097 bool isRomanKeyboardLayout = IsASCIICapable();
michael@0 1098
michael@0 1099 UInt32 key = [aNativeKeyEvent keyCode];
michael@0 1100
michael@0 1101 // Caps lock and num lock modifier state:
michael@0 1102 UInt32 lockState = 0;
michael@0 1103 if ([aNativeKeyEvent modifierFlags] & NSAlphaShiftKeyMask) {
michael@0 1104 lockState |= alphaLock;
michael@0 1105 }
michael@0 1106 if ([aNativeKeyEvent modifierFlags] & NSNumericPadKeyMask) {
michael@0 1107 lockState |= kEventKeyModifierNumLockMask;
michael@0 1108 }
michael@0 1109
michael@0 1110 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1111 ("%p TISInputSourceWrapper::InitKeyPressEvent, "
michael@0 1112 "isRomanKeyboardLayout=%s, key=0x%X",
michael@0 1113 this, TrueOrFalse(isRomanKeyboardLayout), aKbType, key));
michael@0 1114
michael@0 1115 nsString str;
michael@0 1116
michael@0 1117 // normal chars
michael@0 1118 uint32_t unshiftedChar = TranslateToChar(key, lockState, aKbType);
michael@0 1119 UInt32 shiftLockMod = shiftKey | lockState;
michael@0 1120 uint32_t shiftedChar = TranslateToChar(key, shiftLockMod, aKbType);
michael@0 1121
michael@0 1122 // characters generated with Cmd key
michael@0 1123 // XXX we should remove CapsLock state, which changes characters from
michael@0 1124 // Latin to Cyrillic with Russian layout on 10.4 only when Cmd key
michael@0 1125 // is pressed.
michael@0 1126 UInt32 numState = (lockState & ~alphaLock); // only num lock state
michael@0 1127 uint32_t uncmdedChar = TranslateToChar(key, numState, aKbType);
michael@0 1128 UInt32 shiftNumMod = numState | shiftKey;
michael@0 1129 uint32_t uncmdedShiftChar = TranslateToChar(key, shiftNumMod, aKbType);
michael@0 1130 uint32_t uncmdedUSChar = USLayout.TranslateToChar(key, numState, aKbType);
michael@0 1131 UInt32 cmdNumMod = cmdKey | numState;
michael@0 1132 uint32_t cmdedChar = TranslateToChar(key, cmdNumMod, aKbType);
michael@0 1133 UInt32 cmdShiftNumMod = shiftKey | cmdNumMod;
michael@0 1134 uint32_t cmdedShiftChar = TranslateToChar(key, cmdShiftNumMod, aKbType);
michael@0 1135
michael@0 1136 // Is the keyboard layout changed by Cmd key?
michael@0 1137 // E.g., Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
michael@0 1138 bool isCmdSwitchLayout = uncmdedChar != cmdedChar;
michael@0 1139 // Is the keyboard layout for Latin, but Cmd key switches the layout?
michael@0 1140 // I.e., Dvorak-QWERTY
michael@0 1141 bool isDvorakQWERTY = isCmdSwitchLayout && isRomanKeyboardLayout;
michael@0 1142
michael@0 1143 // If the current keyboard is not Dvorak-QWERTY or Cmd is not pressed,
michael@0 1144 // we should append unshiftedChar and shiftedChar for handling the
michael@0 1145 // normal characters. These are the characters that the user is most
michael@0 1146 // likely to associate with this key.
michael@0 1147 if ((unshiftedChar || shiftedChar) &&
michael@0 1148 (!aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
michael@0 1149 AlternativeCharCode altCharCodes(unshiftedChar, shiftedChar);
michael@0 1150 aKeyEvent.alternativeCharCodes.AppendElement(altCharCodes);
michael@0 1151 }
michael@0 1152 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1153 ("%p TISInputSourceWrapper::InitKeyPressEvent, "
michael@0 1154 "aKeyEvent.isMeta=%s, isDvorakQWERTY=%s, "
michael@0 1155 "unshiftedChar=U+%X, shiftedChar=U+%X",
michael@0 1156 this, OnOrOff(aKeyEvent.IsMeta()), TrueOrFalse(isDvorakQWERTY),
michael@0 1157 unshiftedChar, shiftedChar));
michael@0 1158
michael@0 1159 // Most keyboard layouts provide the same characters in the NSEvents
michael@0 1160 // with Command+Shift as with Command. However, with Command+Shift we
michael@0 1161 // want the character on the second level. e.g. With a US QWERTY
michael@0 1162 // layout, we want "?" when the "/","?" key is pressed with
michael@0 1163 // Command+Shift.
michael@0 1164
michael@0 1165 // On a German layout, the OS gives us '/' with Cmd+Shift+SS(eszett)
michael@0 1166 // even though Cmd+SS is 'SS' and Shift+'SS' is '?'. This '/' seems
michael@0 1167 // like a hack to make the Cmd+"?" event look the same as the Cmd+"?"
michael@0 1168 // event on a US keyboard. The user thinks they are typing Cmd+"?", so
michael@0 1169 // we'll prefer the "?" character, replacing charCode with shiftedChar
michael@0 1170 // when Shift is pressed. However, in case there is a layout where the
michael@0 1171 // character unique to Cmd+Shift is the character that the user expects,
michael@0 1172 // we'll send it as an alternative char.
michael@0 1173 bool hasCmdShiftOnlyChar =
michael@0 1174 cmdedChar != cmdedShiftChar && uncmdedShiftChar != cmdedShiftChar;
michael@0 1175 uint32_t originalCmdedShiftChar = cmdedShiftChar;
michael@0 1176
michael@0 1177 // If we can make a good guess at the characters that the user would
michael@0 1178 // expect this key combination to produce (with and without Shift) then
michael@0 1179 // use those characters. This also corrects for CapsLock, which was
michael@0 1180 // ignored above.
michael@0 1181 if (!isCmdSwitchLayout) {
michael@0 1182 // The characters produced with Command seem similar to those without
michael@0 1183 // Command.
michael@0 1184 if (unshiftedChar) {
michael@0 1185 cmdedChar = unshiftedChar;
michael@0 1186 }
michael@0 1187 if (shiftedChar) {
michael@0 1188 cmdedShiftChar = shiftedChar;
michael@0 1189 }
michael@0 1190 } else if (uncmdedUSChar == cmdedChar) {
michael@0 1191 // It looks like characters from a US layout are provided when Command
michael@0 1192 // is down.
michael@0 1193 uint32_t ch = USLayout.TranslateToChar(key, lockState, aKbType);
michael@0 1194 if (ch) {
michael@0 1195 cmdedChar = ch;
michael@0 1196 }
michael@0 1197 ch = USLayout.TranslateToChar(key, shiftLockMod, aKbType);
michael@0 1198 if (ch) {
michael@0 1199 cmdedShiftChar = ch;
michael@0 1200 }
michael@0 1201 }
michael@0 1202
michael@0 1203 // If the current keyboard layout is switched by the Cmd key,
michael@0 1204 // we should append cmdedChar and shiftedCmdChar that are
michael@0 1205 // Latin char for the key.
michael@0 1206 // If the keyboard layout is Dvorak-QWERTY, we should append them only when
michael@0 1207 // command key is pressed because when command key isn't pressed, uncmded
michael@0 1208 // chars have been appended already.
michael@0 1209 if ((cmdedChar || cmdedShiftChar) && isCmdSwitchLayout &&
michael@0 1210 (aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
michael@0 1211 AlternativeCharCode altCharCodes(cmdedChar, cmdedShiftChar);
michael@0 1212 aKeyEvent.alternativeCharCodes.AppendElement(altCharCodes);
michael@0 1213 }
michael@0 1214 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1215 ("%p TISInputSourceWrapper::InitKeyPressEvent, "
michael@0 1216 "hasCmdShiftOnlyChar=%s, isCmdSwitchLayout=%s, isDvorakQWERTY=%s, "
michael@0 1217 "cmdedChar=U+%X, cmdedShiftChar=U+%X",
michael@0 1218 this, TrueOrFalse(hasCmdShiftOnlyChar), TrueOrFalse(isDvorakQWERTY),
michael@0 1219 TrueOrFalse(isDvorakQWERTY), cmdedChar, cmdedShiftChar));
michael@0 1220 // Special case for 'SS' key of German layout. See the comment of
michael@0 1221 // hasCmdShiftOnlyChar definition for the detail.
michael@0 1222 if (hasCmdShiftOnlyChar && originalCmdedShiftChar) {
michael@0 1223 AlternativeCharCode altCharCodes(0, originalCmdedShiftChar);
michael@0 1224 aKeyEvent.alternativeCharCodes.AppendElement(altCharCodes);
michael@0 1225 }
michael@0 1226 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1227 ("%p TISInputSourceWrapper::InitKeyPressEvent, "
michael@0 1228 "hasCmdShiftOnlyChar=%s, originalCmdedShiftChar=U+%X",
michael@0 1229 this, TrueOrFalse(hasCmdShiftOnlyChar), originalCmdedShiftChar));
michael@0 1230
michael@0 1231 NS_OBJC_END_TRY_ABORT_BLOCK
michael@0 1232 }
michael@0 1233
michael@0 1234 uint32_t
michael@0 1235 TISInputSourceWrapper::ComputeGeckoKeyCode(UInt32 aNativeKeyCode,
michael@0 1236 UInt32 aKbType,
michael@0 1237 bool aCmdIsPressed)
michael@0 1238 {
michael@0 1239 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1240 ("%p TISInputSourceWrapper::ComputeGeckoKeyCode, aNativeKeyCode=0x%X, "
michael@0 1241 "aKbType=0x%X, aCmdIsPressed=%s, IsOpenedIMEMode()=%s, "
michael@0 1242 "IsASCIICapable()=%s",
michael@0 1243 this, aNativeKeyCode, aKbType, TrueOrFalse(aCmdIsPressed),
michael@0 1244 TrueOrFalse(IsOpenedIMEMode()), TrueOrFalse(IsASCIICapable())));
michael@0 1245
michael@0 1246 switch (aNativeKeyCode) {
michael@0 1247 case kVK_Space: return NS_VK_SPACE;
michael@0 1248 case kVK_Escape: return NS_VK_ESCAPE;
michael@0 1249
michael@0 1250 // modifiers
michael@0 1251 case kVK_RightCommand:
michael@0 1252 case kVK_Command: return NS_VK_META;
michael@0 1253 case kVK_RightShift:
michael@0 1254 case kVK_Shift: return NS_VK_SHIFT;
michael@0 1255 case kVK_CapsLock: return NS_VK_CAPS_LOCK;
michael@0 1256 case kVK_RightControl:
michael@0 1257 case kVK_Control: return NS_VK_CONTROL;
michael@0 1258 case kVK_RightOption:
michael@0 1259 case kVK_Option: return NS_VK_ALT;
michael@0 1260
michael@0 1261 case kVK_ANSI_KeypadClear: return NS_VK_CLEAR;
michael@0 1262
michael@0 1263 // function keys
michael@0 1264 case kVK_F1: return NS_VK_F1;
michael@0 1265 case kVK_F2: return NS_VK_F2;
michael@0 1266 case kVK_F3: return NS_VK_F3;
michael@0 1267 case kVK_F4: return NS_VK_F4;
michael@0 1268 case kVK_F5: return NS_VK_F5;
michael@0 1269 case kVK_F6: return NS_VK_F6;
michael@0 1270 case kVK_F7: return NS_VK_F7;
michael@0 1271 case kVK_F8: return NS_VK_F8;
michael@0 1272 case kVK_F9: return NS_VK_F9;
michael@0 1273 case kVK_F10: return NS_VK_F10;
michael@0 1274 case kVK_F11: return NS_VK_F11;
michael@0 1275 case kVK_F12: return NS_VK_F12;
michael@0 1276 // case kVK_F13: return NS_VK_F13; // clash with the 3 below
michael@0 1277 // case kVK_F14: return NS_VK_F14;
michael@0 1278 // case kVK_F15: return NS_VK_F15;
michael@0 1279 case kVK_F16: return NS_VK_F16;
michael@0 1280 case kVK_F17: return NS_VK_F17;
michael@0 1281 case kVK_F18: return NS_VK_F18;
michael@0 1282 case kVK_F19: return NS_VK_F19;
michael@0 1283
michael@0 1284 case kVK_PC_Pause: return NS_VK_PAUSE;
michael@0 1285 case kVK_PC_ScrollLock: return NS_VK_SCROLL_LOCK;
michael@0 1286 case kVK_PC_PrintScreen: return NS_VK_PRINTSCREEN;
michael@0 1287
michael@0 1288 // keypad
michael@0 1289 case kVK_ANSI_Keypad0: return NS_VK_NUMPAD0;
michael@0 1290 case kVK_ANSI_Keypad1: return NS_VK_NUMPAD1;
michael@0 1291 case kVK_ANSI_Keypad2: return NS_VK_NUMPAD2;
michael@0 1292 case kVK_ANSI_Keypad3: return NS_VK_NUMPAD3;
michael@0 1293 case kVK_ANSI_Keypad4: return NS_VK_NUMPAD4;
michael@0 1294 case kVK_ANSI_Keypad5: return NS_VK_NUMPAD5;
michael@0 1295 case kVK_ANSI_Keypad6: return NS_VK_NUMPAD6;
michael@0 1296 case kVK_ANSI_Keypad7: return NS_VK_NUMPAD7;
michael@0 1297 case kVK_ANSI_Keypad8: return NS_VK_NUMPAD8;
michael@0 1298 case kVK_ANSI_Keypad9: return NS_VK_NUMPAD9;
michael@0 1299
michael@0 1300 case kVK_ANSI_KeypadMultiply: return NS_VK_MULTIPLY;
michael@0 1301 case kVK_ANSI_KeypadPlus: return NS_VK_ADD;
michael@0 1302 case kVK_ANSI_KeypadMinus: return NS_VK_SUBTRACT;
michael@0 1303 case kVK_ANSI_KeypadDecimal: return NS_VK_DECIMAL;
michael@0 1304 case kVK_ANSI_KeypadDivide: return NS_VK_DIVIDE;
michael@0 1305
michael@0 1306 case kVK_JIS_KeypadComma: return NS_VK_SEPARATOR;
michael@0 1307
michael@0 1308 // IME keys
michael@0 1309 case kVK_JIS_Eisu: return NS_VK_EISU;
michael@0 1310 case kVK_JIS_Kana: return NS_VK_KANA;
michael@0 1311
michael@0 1312 // these may clash with forward delete and help
michael@0 1313 case kVK_PC_Insert: return NS_VK_INSERT;
michael@0 1314 case kVK_PC_Delete: return NS_VK_DELETE;
michael@0 1315
michael@0 1316 case kVK_PC_Backspace: return NS_VK_BACK;
michael@0 1317 case kVK_Tab: return NS_VK_TAB;
michael@0 1318
michael@0 1319 case kVK_Home: return NS_VK_HOME;
michael@0 1320 case kVK_End: return NS_VK_END;
michael@0 1321
michael@0 1322 case kVK_PageUp: return NS_VK_PAGE_UP;
michael@0 1323 case kVK_PageDown: return NS_VK_PAGE_DOWN;
michael@0 1324
michael@0 1325 case kVK_LeftArrow: return NS_VK_LEFT;
michael@0 1326 case kVK_RightArrow: return NS_VK_RIGHT;
michael@0 1327 case kVK_UpArrow: return NS_VK_UP;
michael@0 1328 case kVK_DownArrow: return NS_VK_DOWN;
michael@0 1329
michael@0 1330 case kVK_PC_ContextMenu: return NS_VK_CONTEXT_MENU;
michael@0 1331
michael@0 1332 case kVK_ANSI_1: return NS_VK_1;
michael@0 1333 case kVK_ANSI_2: return NS_VK_2;
michael@0 1334 case kVK_ANSI_3: return NS_VK_3;
michael@0 1335 case kVK_ANSI_4: return NS_VK_4;
michael@0 1336 case kVK_ANSI_5: return NS_VK_5;
michael@0 1337 case kVK_ANSI_6: return NS_VK_6;
michael@0 1338 case kVK_ANSI_7: return NS_VK_7;
michael@0 1339 case kVK_ANSI_8: return NS_VK_8;
michael@0 1340 case kVK_ANSI_9: return NS_VK_9;
michael@0 1341 case kVK_ANSI_0: return NS_VK_0;
michael@0 1342
michael@0 1343 case kVK_ANSI_KeypadEnter:
michael@0 1344 case kVK_Return:
michael@0 1345 case kVK_Powerbook_KeypadEnter: return NS_VK_RETURN;
michael@0 1346 }
michael@0 1347
michael@0 1348 // If Cmd key is pressed, that causes switching keyboard layout temporarily.
michael@0 1349 // E.g., Dvorak-QWERTY. Therefore, if Cmd key is pressed, we should honor it.
michael@0 1350 UInt32 modifiers = aCmdIsPressed ? cmdKey : 0;
michael@0 1351
michael@0 1352 uint32_t charCode = TranslateToChar(aNativeKeyCode, modifiers, aKbType);
michael@0 1353
michael@0 1354 // Special case for Mac. Mac inputs Yen sign (U+00A5) directly instead of
michael@0 1355 // Back slash (U+005C). We should return NS_VK_BACK_SLASH for compatibility
michael@0 1356 // with other platforms.
michael@0 1357 // XXX How about Won sign (U+20A9) which has same problem as Yen sign?
michael@0 1358 if (charCode == 0x00A5) {
michael@0 1359 return NS_VK_BACK_SLASH;
michael@0 1360 }
michael@0 1361
michael@0 1362 uint32_t keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
michael@0 1363 if (keyCode) {
michael@0 1364 return keyCode;
michael@0 1365 }
michael@0 1366
michael@0 1367 // If the unshifed char isn't an ASCII character, use shifted char.
michael@0 1368 charCode = TranslateToChar(aNativeKeyCode, modifiers | shiftKey, aKbType);
michael@0 1369 keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
michael@0 1370 if (keyCode) {
michael@0 1371 return keyCode;
michael@0 1372 }
michael@0 1373
michael@0 1374 // If this is ASCII capable, give up to compute it.
michael@0 1375 if (IsASCIICapable()) {
michael@0 1376 return 0;
michael@0 1377 }
michael@0 1378
michael@0 1379 // Retry with ASCII capable keyboard layout.
michael@0 1380 TISInputSourceWrapper currentKeyboardLayout;
michael@0 1381 currentKeyboardLayout.InitByCurrentASCIICapableKeyboardLayout();
michael@0 1382 NS_ENSURE_TRUE(mInputSource != currentKeyboardLayout.mInputSource, 0);
michael@0 1383 keyCode = currentKeyboardLayout.ComputeGeckoKeyCode(aNativeKeyCode, aKbType,
michael@0 1384 aCmdIsPressed);
michael@0 1385
michael@0 1386 // However, if keyCode isn't for an alphabet keys or a numeric key, we should
michael@0 1387 // ignore it. For example, comma key of Thai layout is same as close-square-
michael@0 1388 // bracket key of US layout and an unicode character key of Thai layout is
michael@0 1389 // same as comma key of US layout. If we return NS_VK_COMMA for latter key,
michael@0 1390 // web application developers cannot distinguish with the former key.
michael@0 1391 return ((keyCode >= NS_VK_A && keyCode <= NS_VK_Z) ||
michael@0 1392 (keyCode >= NS_VK_0 && keyCode <= NS_VK_9)) ? keyCode : 0;
michael@0 1393 }
michael@0 1394
michael@0 1395 // static
michael@0 1396 KeyNameIndex
michael@0 1397 TISInputSourceWrapper::ComputeGeckoKeyNameIndex(UInt32 aNativeKeyCode)
michael@0 1398 {
michael@0 1399 switch (aNativeKeyCode) {
michael@0 1400
michael@0 1401 #define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
michael@0 1402 case aNativeKey: return aKeyNameIndex;
michael@0 1403
michael@0 1404 #include "NativeKeyToDOMKeyName.h"
michael@0 1405
michael@0 1406 #undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
michael@0 1407
michael@0 1408 default:
michael@0 1409 return KEY_NAME_INDEX_Unidentified;
michael@0 1410 }
michael@0 1411 }
michael@0 1412
michael@0 1413
michael@0 1414 #pragma mark -
michael@0 1415
michael@0 1416
michael@0 1417 /******************************************************************************
michael@0 1418 *
michael@0 1419 * TextInputHandler implementation (static methods)
michael@0 1420 *
michael@0 1421 ******************************************************************************/
michael@0 1422
michael@0 1423 NSUInteger TextInputHandler::sLastModifierState = 0;
michael@0 1424
michael@0 1425 // static
michael@0 1426 CFArrayRef
michael@0 1427 TextInputHandler::CreateAllKeyboardLayoutList()
michael@0 1428 {
michael@0 1429 const void* keys[] = { kTISPropertyInputSourceType };
michael@0 1430 const void* values[] = { kTISTypeKeyboardLayout };
michael@0 1431 CFDictionaryRef filter =
michael@0 1432 ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
michael@0 1433 NS_ASSERTION(filter, "failed to create the filter");
michael@0 1434 CFArrayRef list = ::TISCreateInputSourceList(filter, true);
michael@0 1435 ::CFRelease(filter);
michael@0 1436 return list;
michael@0 1437 }
michael@0 1438
michael@0 1439 // static
michael@0 1440 void
michael@0 1441 TextInputHandler::DebugPrintAllKeyboardLayouts()
michael@0 1442 {
michael@0 1443 #ifdef PR_LOGGING
michael@0 1444 if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
michael@0 1445 CFArrayRef list = CreateAllKeyboardLayoutList();
michael@0 1446 PR_LOG(gLog, PR_LOG_ALWAYS, ("Keyboard layout configuration:"));
michael@0 1447 CFIndex idx = ::CFArrayGetCount(list);
michael@0 1448 TISInputSourceWrapper tis;
michael@0 1449 for (CFIndex i = 0; i < idx; ++i) {
michael@0 1450 TISInputSourceRef inputSource = static_cast<TISInputSourceRef>(
michael@0 1451 const_cast<void *>(::CFArrayGetValueAtIndex(list, i)));
michael@0 1452 tis.InitByTISInputSourceRef(inputSource);
michael@0 1453 nsAutoString name, isid;
michael@0 1454 tis.GetLocalizedName(name);
michael@0 1455 tis.GetInputSourceID(isid);
michael@0 1456 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1457 (" %s\t<%s>%s%s\n",
michael@0 1458 NS_ConvertUTF16toUTF8(name).get(),
michael@0 1459 NS_ConvertUTF16toUTF8(isid).get(),
michael@0 1460 tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
michael@0 1461 tis.IsKeyboardLayout() && tis.GetUCKeyboardLayout() ?
michael@0 1462 "" : "\t(uchr is NOT AVAILABLE)"));
michael@0 1463 }
michael@0 1464 ::CFRelease(list);
michael@0 1465 }
michael@0 1466 #endif // #ifdef PR_LOGGING
michael@0 1467 }
michael@0 1468
michael@0 1469
michael@0 1470 #pragma mark -
michael@0 1471
michael@0 1472
michael@0 1473 /******************************************************************************
michael@0 1474 *
michael@0 1475 * TextInputHandler implementation
michael@0 1476 *
michael@0 1477 ******************************************************************************/
michael@0 1478
michael@0 1479 TextInputHandler::TextInputHandler(nsChildView* aWidget,
michael@0 1480 NSView<mozView> *aNativeView) :
michael@0 1481 IMEInputHandler(aWidget, aNativeView)
michael@0 1482 {
michael@0 1483 InitLogModule();
michael@0 1484 [mView installTextInputHandler:this];
michael@0 1485 }
michael@0 1486
michael@0 1487 TextInputHandler::~TextInputHandler()
michael@0 1488 {
michael@0 1489 [mView uninstallTextInputHandler];
michael@0 1490 }
michael@0 1491
michael@0 1492 bool
michael@0 1493 TextInputHandler::HandleKeyDownEvent(NSEvent* aNativeEvent)
michael@0 1494 {
michael@0 1495 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
michael@0 1496
michael@0 1497 if (Destroyed()) {
michael@0 1498 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1499 ("%p TextInputHandler::HandleKeyDownEvent, "
michael@0 1500 "widget has been already destroyed", this));
michael@0 1501 return false;
michael@0 1502 }
michael@0 1503
michael@0 1504 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1505 ("%p TextInputHandler::HandleKeyDownEvent, aNativeEvent=%p, "
michael@0 1506 "type=%s, keyCode=%lld (0x%X), modifierFlags=0x%X, characters=\"%s\", "
michael@0 1507 "charactersIgnoringModifiers=\"%s\"",
michael@0 1508 this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
michael@0 1509 [aNativeEvent keyCode], [aNativeEvent keyCode],
michael@0 1510 [aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]),
michael@0 1511 GetCharacters([aNativeEvent charactersIgnoringModifiers])));
michael@0 1512
michael@0 1513 nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
michael@0 1514
michael@0 1515 KeyEventState* currentKeyEvent = PushKeyEvent(aNativeEvent);
michael@0 1516 AutoKeyEventStateCleaner remover(this);
michael@0 1517
michael@0 1518 if (!IsIMEComposing()) {
michael@0 1519 NSResponder* firstResponder = [[mView window] firstResponder];
michael@0 1520
michael@0 1521 WidgetKeyboardEvent keydownEvent(true, NS_KEY_DOWN, mWidget);
michael@0 1522 InitKeyEvent(aNativeEvent, keydownEvent);
michael@0 1523
michael@0 1524 currentKeyEvent->mKeyDownHandled = DispatchEvent(keydownEvent);
michael@0 1525 if (Destroyed()) {
michael@0 1526 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1527 ("%p TextInputHandler::HandleKeyDownEvent, "
michael@0 1528 "widget was destroyed by keydown event", this));
michael@0 1529 return currentKeyEvent->IsDefaultPrevented();
michael@0 1530 }
michael@0 1531
michael@0 1532 // The key down event may have shifted the focus, in which
michael@0 1533 // case we should not fire the key press.
michael@0 1534 // XXX This is a special code only on Cocoa widget, why is this needed?
michael@0 1535 if (firstResponder != [[mView window] firstResponder]) {
michael@0 1536 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1537 ("%p TextInputHandler::HandleKeyDownEvent, "
michael@0 1538 "view lost focus by keydown event", this));
michael@0 1539 return currentKeyEvent->IsDefaultPrevented();
michael@0 1540 }
michael@0 1541
michael@0 1542 if (currentKeyEvent->IsDefaultPrevented()) {
michael@0 1543 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1544 ("%p TextInputHandler::HandleKeyDownEvent, "
michael@0 1545 "keydown event's default is prevented", this));
michael@0 1546 return true;
michael@0 1547 }
michael@0 1548 }
michael@0 1549
michael@0 1550 // Let Cocoa interpret the key events, caching IsIMEComposing first.
michael@0 1551 bool wasComposing = IsIMEComposing();
michael@0 1552 bool interpretKeyEventsCalled = false;
michael@0 1553 if (IsIMEEnabled() || IsASCIICapableOnly()) {
michael@0 1554 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1555 ("%p TextInputHandler::HandleKeyDownEvent, calling interpretKeyEvents",
michael@0 1556 this));
michael@0 1557 [mView interpretKeyEvents:[NSArray arrayWithObject:aNativeEvent]];
michael@0 1558 interpretKeyEventsCalled = true;
michael@0 1559 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1560 ("%p TextInputHandler::HandleKeyDownEvent, called interpretKeyEvents",
michael@0 1561 this));
michael@0 1562 }
michael@0 1563
michael@0 1564 if (Destroyed()) {
michael@0 1565 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1566 ("%p TextInputHandler::HandleKeyDownEvent, widget was destroyed",
michael@0 1567 this));
michael@0 1568 return currentKeyEvent->IsDefaultPrevented();
michael@0 1569 }
michael@0 1570
michael@0 1571 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1572 ("%p TextInputHandler::HandleKeyDownEvent, wasComposing=%s, "
michael@0 1573 "IsIMEComposing()=%s",
michael@0 1574 this, TrueOrFalse(wasComposing), TrueOrFalse(IsIMEComposing())));
michael@0 1575
michael@0 1576 if (currentKeyEvent->CanDispatchKeyPressEvent() &&
michael@0 1577 !wasComposing && !IsIMEComposing()) {
michael@0 1578 WidgetKeyboardEvent keypressEvent(true, NS_KEY_PRESS, mWidget);
michael@0 1579 InitKeyEvent(aNativeEvent, keypressEvent);
michael@0 1580
michael@0 1581 // If we called interpretKeyEvents and this isn't normal character input
michael@0 1582 // then IME probably ate the event for some reason. We do not want to
michael@0 1583 // send a key press event in that case.
michael@0 1584 // TODO:
michael@0 1585 // There are some other cases which IME eats the current event.
michael@0 1586 // 1. If key events were nested during calling interpretKeyEvents, it means
michael@0 1587 // that IME did something. Then, we should do nothing.
michael@0 1588 // 2. If one or more commands are called like "deleteBackward", we should
michael@0 1589 // dispatch keypress event at that time. Note that the command may have
michael@0 1590 // been a converted or generated action by IME. Then, we shouldn't do
michael@0 1591 // our default action for this key.
michael@0 1592 if (!(interpretKeyEventsCalled &&
michael@0 1593 IsNormalCharInputtingEvent(keypressEvent))) {
michael@0 1594 currentKeyEvent->mKeyPressHandled = DispatchEvent(keypressEvent);
michael@0 1595 currentKeyEvent->mKeyPressDispatched = true;
michael@0 1596 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1597 ("%p TextInputHandler::HandleKeyDownEvent, keypress event dispatched",
michael@0 1598 this));
michael@0 1599 }
michael@0 1600 }
michael@0 1601
michael@0 1602 // Note: mWidget might have become null here. Don't count on it from here on.
michael@0 1603
michael@0 1604 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1605 ("%p TextInputHandler::HandleKeyDownEvent, "
michael@0 1606 "keydown handled=%s, keypress handled=%s, causedOtherKeyEvents=%s",
michael@0 1607 this, TrueOrFalse(currentKeyEvent->mKeyDownHandled),
michael@0 1608 TrueOrFalse(currentKeyEvent->mKeyPressHandled),
michael@0 1609 TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents)));
michael@0 1610 return currentKeyEvent->IsDefaultPrevented();
michael@0 1611
michael@0 1612 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
michael@0 1613 }
michael@0 1614
michael@0 1615 void
michael@0 1616 TextInputHandler::HandleKeyUpEvent(NSEvent* aNativeEvent)
michael@0 1617 {
michael@0 1618 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 1619
michael@0 1620 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1621 ("%p TextInputHandler::HandleKeyUpEvent, aNativeEvent=%p, "
michael@0 1622 "type=%s, keyCode=%lld (0x%X), modifierFlags=0x%X, characters=\"%s\", "
michael@0 1623 "charactersIgnoringModifiers=\"%s\", "
michael@0 1624 "mIgnoreNextKeyUpEvent=%s, IsIMEComposing()=%s",
michael@0 1625 this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
michael@0 1626 [aNativeEvent keyCode], [aNativeEvent keyCode],
michael@0 1627 [aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]),
michael@0 1628 GetCharacters([aNativeEvent charactersIgnoringModifiers]),
michael@0 1629 TrueOrFalse(mIgnoreNextKeyUpEvent), TrueOrFalse(IsIMEComposing())));
michael@0 1630
michael@0 1631 if (mIgnoreNextKeyUpEvent) {
michael@0 1632 mIgnoreNextKeyUpEvent = false;
michael@0 1633 return;
michael@0 1634 }
michael@0 1635
michael@0 1636 if (Destroyed()) {
michael@0 1637 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1638 ("%p TextInputHandler::HandleKeyUpEvent, "
michael@0 1639 "widget has been already destroyed", this));
michael@0 1640 return;
michael@0 1641 }
michael@0 1642
michael@0 1643 // if we don't have any characters we can't generate a keyUp event
michael@0 1644 if (IsIMEComposing()) {
michael@0 1645 return;
michael@0 1646 }
michael@0 1647
michael@0 1648 WidgetKeyboardEvent keyupEvent(true, NS_KEY_UP, mWidget);
michael@0 1649 InitKeyEvent(aNativeEvent, keyupEvent);
michael@0 1650
michael@0 1651 DispatchEvent(keyupEvent);
michael@0 1652
michael@0 1653 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 1654 }
michael@0 1655
michael@0 1656 void
michael@0 1657 TextInputHandler::HandleFlagsChanged(NSEvent* aNativeEvent)
michael@0 1658 {
michael@0 1659 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 1660
michael@0 1661 if (Destroyed()) {
michael@0 1662 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1663 ("%p TextInputHandler::HandleFlagsChanged, "
michael@0 1664 "widget has been already destroyed", this));
michael@0 1665 return;
michael@0 1666 }
michael@0 1667
michael@0 1668 nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
michael@0 1669
michael@0 1670 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1671 ("%p TextInputHandler::HandleFlagsChanged, aNativeEvent=%p, "
michael@0 1672 "type=%s, keyCode=%s (0x%X), modifierFlags=0x%08X, "
michael@0 1673 "sLastModifierState=0x%08X, IsIMEComposing()=%s",
michael@0 1674 this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
michael@0 1675 GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
michael@0 1676 [aNativeEvent modifierFlags], sLastModifierState,
michael@0 1677 TrueOrFalse(IsIMEComposing())));
michael@0 1678
michael@0 1679 MOZ_ASSERT([aNativeEvent type] == NSFlagsChanged);
michael@0 1680
michael@0 1681 NSUInteger diff = [aNativeEvent modifierFlags] ^ sLastModifierState;
michael@0 1682 // Device dependent flags for left-control key, both shift keys, both command
michael@0 1683 // keys and both option keys have been defined in Next's SDK. But we
michael@0 1684 // shouldn't use it directly as far as possible since Cocoa SDK doesn't
michael@0 1685 // define them. Fortunately, we need them only when we dispatch keyup
michael@0 1686 // events. So, we can usually know the actual relation between keyCode and
michael@0 1687 // device dependent flags. However, we need to remove following flags first
michael@0 1688 // since the differences don't indicate modifier key state.
michael@0 1689 // NX_STYLUSPROXIMITYMASK: Probably used for pen like device.
michael@0 1690 // kCGEventFlagMaskNonCoalesced (= NX_NONCOALSESCEDMASK): See the document for
michael@0 1691 // Quartz Event Services.
michael@0 1692 diff &= ~(NX_STYLUSPROXIMITYMASK | kCGEventFlagMaskNonCoalesced);
michael@0 1693
michael@0 1694 switch ([aNativeEvent keyCode]) {
michael@0 1695 // CapsLock state and other modifier states are different:
michael@0 1696 // CapsLock state does not revert when the CapsLock key goes up, as the
michael@0 1697 // modifier state does for other modifier keys on key up.
michael@0 1698 case kVK_CapsLock: {
michael@0 1699 // Fire key down event for caps lock.
michael@0 1700 DispatchKeyEventForFlagsChanged(aNativeEvent, true);
michael@0 1701 // XXX should we fire keyup event too? The keyup event for CapsLock key
michael@0 1702 // is never dispatched on Gecko.
michael@0 1703 // XXX WebKit dispatches keydown event when CapsLock is locked, otherwise,
michael@0 1704 // keyup event. If we do so, we cannot keep the consistency with other
michael@0 1705 // platform's behavior...
michael@0 1706 break;
michael@0 1707 }
michael@0 1708
michael@0 1709 // If the event is caused by pressing or releasing a modifier key, just
michael@0 1710 // dispatch the key's event.
michael@0 1711 case kVK_Shift:
michael@0 1712 case kVK_RightShift:
michael@0 1713 case kVK_Command:
michael@0 1714 case kVK_RightCommand:
michael@0 1715 case kVK_Control:
michael@0 1716 case kVK_RightControl:
michael@0 1717 case kVK_Option:
michael@0 1718 case kVK_RightOption:
michael@0 1719 case kVK_Help: {
michael@0 1720 // We assume that at most one modifier is changed per event if the event
michael@0 1721 // is caused by pressing or releasing a modifier key.
michael@0 1722 bool isKeyDown = ([aNativeEvent modifierFlags] & diff) != 0;
michael@0 1723 DispatchKeyEventForFlagsChanged(aNativeEvent, isKeyDown);
michael@0 1724 // XXX Some applications might send the event with incorrect device-
michael@0 1725 // dependent flags.
michael@0 1726 if (isKeyDown && ((diff & ~NSDeviceIndependentModifierFlagsMask) != 0)) {
michael@0 1727 unsigned short keyCode = [aNativeEvent keyCode];
michael@0 1728 const ModifierKey* modifierKey =
michael@0 1729 GetModifierKeyForDeviceDependentFlags(diff);
michael@0 1730 if (modifierKey && modifierKey->keyCode != keyCode) {
michael@0 1731 // Although, we're not sure the actual cause of this case, the stored
michael@0 1732 // modifier information and the latest key event information may be
michael@0 1733 // mismatched. Then, let's reset the stored information.
michael@0 1734 // NOTE: If this happens, it may fail to handle NSFlagsChanged event
michael@0 1735 // in the default case (below). However, it's the rare case handler
michael@0 1736 // and this case occurs rarely. So, we can ignore the edge case bug.
michael@0 1737 NS_WARNING("Resetting stored modifier key information");
michael@0 1738 mModifierKeys.Clear();
michael@0 1739 modifierKey = nullptr;
michael@0 1740 }
michael@0 1741 if (!modifierKey) {
michael@0 1742 mModifierKeys.AppendElement(ModifierKey(diff, keyCode));
michael@0 1743 }
michael@0 1744 }
michael@0 1745 break;
michael@0 1746 }
michael@0 1747
michael@0 1748 // Currently we don't support Fn key since other browsers don't dispatch
michael@0 1749 // events for it and we don't have keyCode for this key.
michael@0 1750 // It should be supported when we implement .key and .char.
michael@0 1751 case kVK_Function:
michael@0 1752 break;
michael@0 1753
michael@0 1754 // If the event is caused by something else than pressing or releasing a
michael@0 1755 // single modifier key (for example by the app having been deactivated
michael@0 1756 // using command-tab), use the modifiers themselves to determine which
michael@0 1757 // key's event to dispatch, and whether it's a keyup or keydown event.
michael@0 1758 // In all cases we assume one or more modifiers are being deactivated
michael@0 1759 // (never activated) -- otherwise we'd have received one or more events
michael@0 1760 // corresponding to a single modifier key being pressed.
michael@0 1761 default: {
michael@0 1762 NSUInteger modifiers = sLastModifierState;
michael@0 1763 for (int32_t bit = 0; bit < 32; ++bit) {
michael@0 1764 NSUInteger flag = 1 << bit;
michael@0 1765 if (!(diff & flag)) {
michael@0 1766 continue;
michael@0 1767 }
michael@0 1768
michael@0 1769 // Given correct information from the application, a flag change here
michael@0 1770 // will normally be a deactivation (except for some lockable modifiers
michael@0 1771 // such as CapsLock). But some applications (like VNC) can send an
michael@0 1772 // activating event with a zero keyCode. So we need to check for that
michael@0 1773 // here.
michael@0 1774 bool dispatchKeyDown = ((flag & [aNativeEvent modifierFlags]) != 0);
michael@0 1775
michael@0 1776 unsigned short keyCode = 0;
michael@0 1777 if (flag & NSDeviceIndependentModifierFlagsMask) {
michael@0 1778 switch (flag) {
michael@0 1779 case NSAlphaShiftKeyMask:
michael@0 1780 keyCode = kVK_CapsLock;
michael@0 1781 dispatchKeyDown = true;
michael@0 1782 break;
michael@0 1783
michael@0 1784 case NSNumericPadKeyMask:
michael@0 1785 // NSNumericPadKeyMask is fired by VNC a lot. But not all of
michael@0 1786 // these events can really be Clear key events, so we just ignore
michael@0 1787 // them.
michael@0 1788 continue;
michael@0 1789
michael@0 1790 case NSHelpKeyMask:
michael@0 1791 keyCode = kVK_Help;
michael@0 1792 break;
michael@0 1793
michael@0 1794 case NSFunctionKeyMask:
michael@0 1795 // An NSFunctionKeyMask change here will normally be a
michael@0 1796 // deactivation. But sometimes it will be an activation send (by
michael@0 1797 // VNC for example) with a zero keyCode.
michael@0 1798 continue;
michael@0 1799
michael@0 1800 // These cases (NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask
michael@0 1801 // and NSCommandKeyMask) should be handled by the other branch of
michael@0 1802 // the if statement, below (which handles device dependent flags).
michael@0 1803 // However, some applications (like VNC) can send key events without
michael@0 1804 // any device dependent flags, so we handle them here instead.
michael@0 1805 case NSShiftKeyMask:
michael@0 1806 keyCode = (modifiers & 0x0004) ? kVK_RightShift : kVK_Shift;
michael@0 1807 break;
michael@0 1808 case NSControlKeyMask:
michael@0 1809 keyCode = (modifiers & 0x2000) ? kVK_RightControl : kVK_Control;
michael@0 1810 break;
michael@0 1811 case NSAlternateKeyMask:
michael@0 1812 keyCode = (modifiers & 0x0040) ? kVK_RightOption : kVK_Option;
michael@0 1813 break;
michael@0 1814 case NSCommandKeyMask:
michael@0 1815 keyCode = (modifiers & 0x0010) ? kVK_RightCommand : kVK_Command;
michael@0 1816 break;
michael@0 1817
michael@0 1818 default:
michael@0 1819 continue;
michael@0 1820 }
michael@0 1821 } else {
michael@0 1822 const ModifierKey* modifierKey =
michael@0 1823 GetModifierKeyForDeviceDependentFlags(flag);
michael@0 1824 if (!modifierKey) {
michael@0 1825 // See the note above (in the other branch of the if statement)
michael@0 1826 // about the NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask
michael@0 1827 // and NSCommandKeyMask cases.
michael@0 1828 continue;
michael@0 1829 }
michael@0 1830 keyCode = modifierKey->keyCode;
michael@0 1831 }
michael@0 1832
michael@0 1833 // Remove flags
michael@0 1834 modifiers &= ~flag;
michael@0 1835 switch (keyCode) {
michael@0 1836 case kVK_Shift: {
michael@0 1837 const ModifierKey* modifierKey =
michael@0 1838 GetModifierKeyForNativeKeyCode(kVK_RightShift);
michael@0 1839 if (!modifierKey ||
michael@0 1840 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
michael@0 1841 modifiers &= ~NSShiftKeyMask;
michael@0 1842 }
michael@0 1843 break;
michael@0 1844 }
michael@0 1845 case kVK_RightShift: {
michael@0 1846 const ModifierKey* modifierKey =
michael@0 1847 GetModifierKeyForNativeKeyCode(kVK_Shift);
michael@0 1848 if (!modifierKey ||
michael@0 1849 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
michael@0 1850 modifiers &= ~NSShiftKeyMask;
michael@0 1851 }
michael@0 1852 break;
michael@0 1853 }
michael@0 1854 case kVK_Command: {
michael@0 1855 const ModifierKey* modifierKey =
michael@0 1856 GetModifierKeyForNativeKeyCode(kVK_RightCommand);
michael@0 1857 if (!modifierKey ||
michael@0 1858 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
michael@0 1859 modifiers &= ~NSCommandKeyMask;
michael@0 1860 }
michael@0 1861 break;
michael@0 1862 }
michael@0 1863 case kVK_RightCommand: {
michael@0 1864 const ModifierKey* modifierKey =
michael@0 1865 GetModifierKeyForNativeKeyCode(kVK_Command);
michael@0 1866 if (!modifierKey ||
michael@0 1867 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
michael@0 1868 modifiers &= ~NSCommandKeyMask;
michael@0 1869 }
michael@0 1870 break;
michael@0 1871 }
michael@0 1872 case kVK_Control: {
michael@0 1873 const ModifierKey* modifierKey =
michael@0 1874 GetModifierKeyForNativeKeyCode(kVK_RightControl);
michael@0 1875 if (!modifierKey ||
michael@0 1876 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
michael@0 1877 modifiers &= ~NSControlKeyMask;
michael@0 1878 }
michael@0 1879 break;
michael@0 1880 }
michael@0 1881 case kVK_RightControl: {
michael@0 1882 const ModifierKey* modifierKey =
michael@0 1883 GetModifierKeyForNativeKeyCode(kVK_Control);
michael@0 1884 if (!modifierKey ||
michael@0 1885 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
michael@0 1886 modifiers &= ~NSControlKeyMask;
michael@0 1887 }
michael@0 1888 break;
michael@0 1889 }
michael@0 1890 case kVK_Option: {
michael@0 1891 const ModifierKey* modifierKey =
michael@0 1892 GetModifierKeyForNativeKeyCode(kVK_RightOption);
michael@0 1893 if (!modifierKey ||
michael@0 1894 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
michael@0 1895 modifiers &= ~NSAlternateKeyMask;
michael@0 1896 }
michael@0 1897 break;
michael@0 1898 }
michael@0 1899 case kVK_RightOption: {
michael@0 1900 const ModifierKey* modifierKey =
michael@0 1901 GetModifierKeyForNativeKeyCode(kVK_Option);
michael@0 1902 if (!modifierKey ||
michael@0 1903 !(modifiers & modifierKey->GetDeviceDependentFlags())) {
michael@0 1904 modifiers &= ~NSAlternateKeyMask;
michael@0 1905 }
michael@0 1906 break;
michael@0 1907 }
michael@0 1908 case kVK_Help:
michael@0 1909 modifiers &= ~NSHelpKeyMask;
michael@0 1910 break;
michael@0 1911 default:
michael@0 1912 break;
michael@0 1913 }
michael@0 1914
michael@0 1915 NSEvent* event =
michael@0 1916 [NSEvent keyEventWithType:NSFlagsChanged
michael@0 1917 location:[aNativeEvent locationInWindow]
michael@0 1918 modifierFlags:modifiers
michael@0 1919 timestamp:[aNativeEvent timestamp]
michael@0 1920 windowNumber:[aNativeEvent windowNumber]
michael@0 1921 context:[aNativeEvent context]
michael@0 1922 characters:nil
michael@0 1923 charactersIgnoringModifiers:nil
michael@0 1924 isARepeat:NO
michael@0 1925 keyCode:keyCode];
michael@0 1926 DispatchKeyEventForFlagsChanged(event, dispatchKeyDown);
michael@0 1927 if (Destroyed()) {
michael@0 1928 break;
michael@0 1929 }
michael@0 1930
michael@0 1931 // Stop if focus has changed.
michael@0 1932 // Check to see if mView is still the first responder.
michael@0 1933 if (![mView isFirstResponder]) {
michael@0 1934 break;
michael@0 1935 }
michael@0 1936
michael@0 1937 }
michael@0 1938 break;
michael@0 1939 }
michael@0 1940 }
michael@0 1941
michael@0 1942 // Be aware, the widget may have been destroyed.
michael@0 1943 sLastModifierState = [aNativeEvent modifierFlags];
michael@0 1944
michael@0 1945 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 1946 }
michael@0 1947
michael@0 1948 const TextInputHandler::ModifierKey*
michael@0 1949 TextInputHandler::GetModifierKeyForNativeKeyCode(unsigned short aKeyCode) const
michael@0 1950 {
michael@0 1951 for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) {
michael@0 1952 if (mModifierKeys[i].keyCode == aKeyCode) {
michael@0 1953 return &((ModifierKey&)mModifierKeys[i]);
michael@0 1954 }
michael@0 1955 }
michael@0 1956 return nullptr;
michael@0 1957 }
michael@0 1958
michael@0 1959 const TextInputHandler::ModifierKey*
michael@0 1960 TextInputHandler::GetModifierKeyForDeviceDependentFlags(NSUInteger aFlags) const
michael@0 1961 {
michael@0 1962 for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) {
michael@0 1963 if (mModifierKeys[i].GetDeviceDependentFlags() ==
michael@0 1964 (aFlags & ~NSDeviceIndependentModifierFlagsMask)) {
michael@0 1965 return &((ModifierKey&)mModifierKeys[i]);
michael@0 1966 }
michael@0 1967 }
michael@0 1968 return nullptr;
michael@0 1969 }
michael@0 1970
michael@0 1971 void
michael@0 1972 TextInputHandler::DispatchKeyEventForFlagsChanged(NSEvent* aNativeEvent,
michael@0 1973 bool aDispatchKeyDown)
michael@0 1974 {
michael@0 1975 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 1976
michael@0 1977 if (Destroyed()) {
michael@0 1978 return;
michael@0 1979 }
michael@0 1980
michael@0 1981 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 1982 ("%p TextInputHandler::DispatchKeyEventForFlagsChanged, aNativeEvent=%p, "
michael@0 1983 "type=%s, keyCode=%s (0x%X), aDispatchKeyDown=%s, IsIMEComposing()=%s",
michael@0 1984 this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
michael@0 1985 GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
michael@0 1986 TrueOrFalse(aDispatchKeyDown), TrueOrFalse(IsIMEComposing())));
michael@0 1987
michael@0 1988 if ([aNativeEvent type] != NSFlagsChanged || IsIMEComposing()) {
michael@0 1989 return;
michael@0 1990 }
michael@0 1991
michael@0 1992 uint32_t message = aDispatchKeyDown ? NS_KEY_DOWN : NS_KEY_UP;
michael@0 1993
michael@0 1994 NPCocoaEvent cocoaEvent;
michael@0 1995
michael@0 1996 // Fire a key event.
michael@0 1997 WidgetKeyboardEvent keyEvent(true, message, mWidget);
michael@0 1998 InitKeyEvent(aNativeEvent, keyEvent);
michael@0 1999
michael@0 2000 // create event for use by plugins
michael@0 2001 if ([mView isPluginView]) {
michael@0 2002 if ([mView pluginEventModel] == NPEventModelCocoa) {
michael@0 2003 ConvertCocoaKeyEventToNPCocoaEvent(aNativeEvent, cocoaEvent);
michael@0 2004 keyEvent.pluginEvent = &cocoaEvent;
michael@0 2005 }
michael@0 2006 }
michael@0 2007
michael@0 2008 DispatchEvent(keyEvent);
michael@0 2009
michael@0 2010 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 2011 }
michael@0 2012
michael@0 2013 void
michael@0 2014 TextInputHandler::InsertText(NSAttributedString* aAttrString,
michael@0 2015 NSRange* aReplacementRange)
michael@0 2016 {
michael@0 2017 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 2018
michael@0 2019 if (Destroyed()) {
michael@0 2020 return;
michael@0 2021 }
michael@0 2022
michael@0 2023 KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
michael@0 2024
michael@0 2025 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2026 ("%p TextInputHandler::InsertText, aAttrString=\"%s\", "
michael@0 2027 "aReplacementRange=%p { location=%llu, length=%llu }, "
michael@0 2028 "IsIMEComposing()=%s, IgnoreIMEComposition()=%s, "
michael@0 2029 "keyevent=%p, keydownHandled=%s, keypressDispatched=%s, "
michael@0 2030 "causedOtherKeyEvents=%s",
michael@0 2031 this, GetCharacters([aAttrString string]), aReplacementRange,
michael@0 2032 aReplacementRange ? aReplacementRange->location : 0,
michael@0 2033 aReplacementRange ? aReplacementRange->length : 0,
michael@0 2034 TrueOrFalse(IsIMEComposing()), TrueOrFalse(IgnoreIMEComposition()),
michael@0 2035 currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
michael@0 2036 currentKeyEvent ?
michael@0 2037 TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
michael@0 2038 currentKeyEvent ?
michael@0 2039 TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
michael@0 2040 currentKeyEvent ?
michael@0 2041 TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A"));
michael@0 2042
michael@0 2043 if (IgnoreIMEComposition()) {
michael@0 2044 return;
michael@0 2045 }
michael@0 2046
michael@0 2047 InputContext context = mWidget->GetInputContext();
michael@0 2048 bool isEditable = (context.mIMEState.mEnabled == IMEState::ENABLED ||
michael@0 2049 context.mIMEState.mEnabled == IMEState::PASSWORD);
michael@0 2050 NSRange selectedRange = SelectedRange();
michael@0 2051
michael@0 2052 nsAutoString str;
michael@0 2053 nsCocoaUtils::GetStringForNSString([aAttrString string], str);
michael@0 2054 if (!IsIMEComposing() && str.IsEmpty()) {
michael@0 2055 // nothing to do if there is no content which can be removed.
michael@0 2056 if (!isEditable) {
michael@0 2057 return;
michael@0 2058 }
michael@0 2059 // If replacement range is specified, we need to remove the range.
michael@0 2060 // Otherwise, we need to remove the selected range if it's not collapsed.
michael@0 2061 if (aReplacementRange && aReplacementRange->location != NSNotFound) {
michael@0 2062 // nothing to do since the range is collapsed.
michael@0 2063 if (aReplacementRange->length == 0) {
michael@0 2064 return;
michael@0 2065 }
michael@0 2066 // If the replacement range is different from current selected range,
michael@0 2067 // select the range.
michael@0 2068 if (!NSEqualRanges(selectedRange, *aReplacementRange)) {
michael@0 2069 NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
michael@0 2070 }
michael@0 2071 selectedRange = SelectedRange();
michael@0 2072 }
michael@0 2073 NS_ENSURE_TRUE_VOID(selectedRange.location != NSNotFound);
michael@0 2074 if (selectedRange.length == 0) {
michael@0 2075 return; // nothing to do
michael@0 2076 }
michael@0 2077 // If this is caused by a key input, the keypress event which will be
michael@0 2078 // dispatched later should cause the delete. Therefore, nothing to do here.
michael@0 2079 // Although, we're not sure if such case is actually possible.
michael@0 2080 if (!currentKeyEvent) {
michael@0 2081 return;
michael@0 2082 }
michael@0 2083 // Delete the selected range.
michael@0 2084 nsRefPtr<TextInputHandler> kungFuDeathGrip(this);
michael@0 2085 WidgetContentCommandEvent deleteCommandEvent(true,
michael@0 2086 NS_CONTENT_COMMAND_DELETE,
michael@0 2087 mWidget);
michael@0 2088 DispatchEvent(deleteCommandEvent);
michael@0 2089 NS_ENSURE_TRUE_VOID(deleteCommandEvent.mSucceeded);
michael@0 2090 // Be aware! The widget might be destroyed here.
michael@0 2091 return;
michael@0 2092 }
michael@0 2093
michael@0 2094 if (str.Length() != 1 || IsIMEComposing()) {
michael@0 2095 InsertTextAsCommittingComposition(aAttrString, aReplacementRange);
michael@0 2096 return;
michael@0 2097 }
michael@0 2098
michael@0 2099 // Don't let the same event be fired twice when hitting
michael@0 2100 // enter/return! (Bug 420502)
michael@0 2101 if (currentKeyEvent && !currentKeyEvent->CanDispatchKeyPressEvent()) {
michael@0 2102 return;
michael@0 2103 }
michael@0 2104
michael@0 2105 nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
michael@0 2106
michael@0 2107 // If the replacement range is specified, select the range. Then, the
michael@0 2108 // selection will be replaced by the later keypress event.
michael@0 2109 if (isEditable &&
michael@0 2110 aReplacementRange && aReplacementRange->location != NSNotFound &&
michael@0 2111 !NSEqualRanges(selectedRange, *aReplacementRange)) {
michael@0 2112 NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
michael@0 2113 }
michael@0 2114
michael@0 2115 // Dispatch keypress event with char instead of textEvent
michael@0 2116 WidgetKeyboardEvent keypressEvent(true, NS_KEY_PRESS, mWidget);
michael@0 2117 keypressEvent.isChar = IsPrintableChar(str.CharAt(0));
michael@0 2118
michael@0 2119 // Don't set other modifiers from the current event, because here in
michael@0 2120 // -insertText: they've already been taken into account in creating
michael@0 2121 // the input string.
michael@0 2122
michael@0 2123 if (currentKeyEvent) {
michael@0 2124 NSEvent* keyEvent = currentKeyEvent->mKeyEvent;
michael@0 2125 InitKeyEvent(keyEvent, keypressEvent, &str);
michael@0 2126 } else {
michael@0 2127 nsCocoaUtils::InitInputEvent(keypressEvent, static_cast<NSEvent*>(nullptr));
michael@0 2128 if (keypressEvent.isChar) {
michael@0 2129 keypressEvent.charCode = str.CharAt(0);
michael@0 2130 }
michael@0 2131 // Note that insertText is not called only at key pressing.
michael@0 2132 if (!keypressEvent.charCode) {
michael@0 2133 keypressEvent.keyCode =
michael@0 2134 WidgetUtils::ComputeKeyCodeFromChar(keypressEvent.charCode);
michael@0 2135 }
michael@0 2136 }
michael@0 2137
michael@0 2138 // Remove basic modifiers from keypress event because if they are included,
michael@0 2139 // nsPlaintextEditor ignores the event.
michael@0 2140 keypressEvent.modifiers &= ~(MODIFIER_CONTROL |
michael@0 2141 MODIFIER_ALT |
michael@0 2142 MODIFIER_META);
michael@0 2143
michael@0 2144 // TODO:
michael@0 2145 // If mCurrentKeyEvent.mKeyEvent is null and when we implement textInput
michael@0 2146 // event of DOM3 Events, we should dispatch it instead of keypress event.
michael@0 2147 bool keyPressHandled = DispatchEvent(keypressEvent);
michael@0 2148
michael@0 2149 // Note: mWidget might have become null here. Don't count on it from here on.
michael@0 2150
michael@0 2151 if (currentKeyEvent) {
michael@0 2152 currentKeyEvent->mKeyPressHandled = keyPressHandled;
michael@0 2153 currentKeyEvent->mKeyPressDispatched = true;
michael@0 2154 }
michael@0 2155
michael@0 2156 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 2157 }
michael@0 2158
michael@0 2159 bool
michael@0 2160 TextInputHandler::DoCommandBySelector(const char* aSelector)
michael@0 2161 {
michael@0 2162 nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
michael@0 2163
michael@0 2164 KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
michael@0 2165
michael@0 2166 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2167 ("%p TextInputHandler::DoCommandBySelector, aSelector=\"%s\", "
michael@0 2168 "Destroyed()=%s, keydownHandled=%s, keypressHandled=%s, "
michael@0 2169 "causedOtherKeyEvents=%s",
michael@0 2170 this, aSelector ? aSelector : "", TrueOrFalse(Destroyed()),
michael@0 2171 currentKeyEvent ?
michael@0 2172 TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
michael@0 2173 currentKeyEvent ?
michael@0 2174 TrueOrFalse(currentKeyEvent->mKeyPressHandled) : "N/A",
michael@0 2175 currentKeyEvent ?
michael@0 2176 TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A"));
michael@0 2177
michael@0 2178 if (currentKeyEvent && currentKeyEvent->CanDispatchKeyPressEvent()) {
michael@0 2179 WidgetKeyboardEvent keypressEvent(true, NS_KEY_PRESS, mWidget);
michael@0 2180 InitKeyEvent(currentKeyEvent->mKeyEvent, keypressEvent);
michael@0 2181 currentKeyEvent->mKeyPressHandled = DispatchEvent(keypressEvent);
michael@0 2182 currentKeyEvent->mKeyPressDispatched = true;
michael@0 2183 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2184 ("%p TextInputHandler::DoCommandBySelector, keypress event "
michael@0 2185 "dispatched, Destroyed()=%s, keypressHandled=%s",
michael@0 2186 this, TrueOrFalse(Destroyed()),
michael@0 2187 TrueOrFalse(currentKeyEvent->mKeyPressHandled)));
michael@0 2188 }
michael@0 2189
michael@0 2190 return (!Destroyed() && currentKeyEvent &&
michael@0 2191 currentKeyEvent->IsDefaultPrevented());
michael@0 2192 }
michael@0 2193
michael@0 2194
michael@0 2195 #pragma mark -
michael@0 2196
michael@0 2197
michael@0 2198 /******************************************************************************
michael@0 2199 *
michael@0 2200 * IMEInputHandler implementation (static methods)
michael@0 2201 *
michael@0 2202 ******************************************************************************/
michael@0 2203
michael@0 2204 bool IMEInputHandler::sStaticMembersInitialized = false;
michael@0 2205 CFStringRef IMEInputHandler::sLatestIMEOpenedModeInputSourceID = nullptr;
michael@0 2206 IMEInputHandler* IMEInputHandler::sFocusedIMEHandler = nullptr;
michael@0 2207
michael@0 2208 // static
michael@0 2209 void
michael@0 2210 IMEInputHandler::InitStaticMembers()
michael@0 2211 {
michael@0 2212 if (sStaticMembersInitialized)
michael@0 2213 return;
michael@0 2214 sStaticMembersInitialized = true;
michael@0 2215 // We need to check the keyboard layout changes on all applications.
michael@0 2216 CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
michael@0 2217 // XXX Don't we need to remove the observer at shut down?
michael@0 2218 // Mac Dev Center's document doesn't say how to remove the observer if
michael@0 2219 // the second parameter is NULL.
michael@0 2220 ::CFNotificationCenterAddObserver(center, NULL,
michael@0 2221 OnCurrentTextInputSourceChange,
michael@0 2222 kTISNotifySelectedKeyboardInputSourceChanged, NULL,
michael@0 2223 CFNotificationSuspensionBehaviorDeliverImmediately);
michael@0 2224 // Initiailize with the current keyboard layout
michael@0 2225 OnCurrentTextInputSourceChange(NULL, NULL,
michael@0 2226 kTISNotifySelectedKeyboardInputSourceChanged,
michael@0 2227 NULL, NULL);
michael@0 2228 }
michael@0 2229
michael@0 2230 // static
michael@0 2231 void
michael@0 2232 IMEInputHandler::OnCurrentTextInputSourceChange(CFNotificationCenterRef aCenter,
michael@0 2233 void* aObserver,
michael@0 2234 CFStringRef aName,
michael@0 2235 const void* aObject,
michael@0 2236 CFDictionaryRef aUserInfo)
michael@0 2237 {
michael@0 2238 // Cache the latest IME opened mode to sLatestIMEOpenedModeInputSourceID.
michael@0 2239 TISInputSourceWrapper tis;
michael@0 2240 tis.InitByCurrentInputSource();
michael@0 2241 if (tis.IsOpenedIMEMode()) {
michael@0 2242 tis.GetInputSourceID(sLatestIMEOpenedModeInputSourceID);
michael@0 2243 }
michael@0 2244
michael@0 2245 #ifdef PR_LOGGING
michael@0 2246 if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
michael@0 2247 static CFStringRef sLastTIS = nullptr;
michael@0 2248 CFStringRef newTIS;
michael@0 2249 tis.GetInputSourceID(newTIS);
michael@0 2250 if (!sLastTIS ||
michael@0 2251 ::CFStringCompare(sLastTIS, newTIS, 0) != kCFCompareEqualTo) {
michael@0 2252 TISInputSourceWrapper tis1, tis2, tis3, tis4, tis5;
michael@0 2253 tis1.InitByCurrentKeyboardLayout();
michael@0 2254 tis2.InitByCurrentASCIICapableInputSource();
michael@0 2255 tis3.InitByCurrentASCIICapableKeyboardLayout();
michael@0 2256 tis4.InitByCurrentInputMethodKeyboardLayoutOverride();
michael@0 2257 tis5.InitByTISInputSourceRef(tis.GetKeyboardLayoutInputSource());
michael@0 2258 CFStringRef is0 = nullptr, is1 = nullptr, is2 = nullptr, is3 = nullptr,
michael@0 2259 is4 = nullptr, is5 = nullptr, type0 = nullptr,
michael@0 2260 lang0 = nullptr, bundleID0 = nullptr;
michael@0 2261 tis.GetInputSourceID(is0);
michael@0 2262 tis1.GetInputSourceID(is1);
michael@0 2263 tis2.GetInputSourceID(is2);
michael@0 2264 tis3.GetInputSourceID(is3);
michael@0 2265 tis4.GetInputSourceID(is4);
michael@0 2266 tis5.GetInputSourceID(is5);
michael@0 2267 tis.GetInputSourceType(type0);
michael@0 2268 tis.GetPrimaryLanguage(lang0);
michael@0 2269 tis.GetBundleID(bundleID0);
michael@0 2270
michael@0 2271 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2272 ("IMEInputHandler::OnCurrentTextInputSourceChange,\n"
michael@0 2273 " Current Input Source is changed to:\n"
michael@0 2274 " currentInputContext=%p\n"
michael@0 2275 " %s\n"
michael@0 2276 " type=%s %s\n"
michael@0 2277 " overridden keyboard layout=%s\n"
michael@0 2278 " used keyboard layout for translation=%s\n"
michael@0 2279 " primary language=%s\n"
michael@0 2280 " bundle ID=%s\n"
michael@0 2281 " current ASCII capable Input Source=%s\n"
michael@0 2282 " current Keyboard Layout=%s\n"
michael@0 2283 " current ASCII capable Keyboard Layout=%s",
michael@0 2284 [NSTextInputContext currentInputContext], GetCharacters(is0),
michael@0 2285 GetCharacters(type0), tis.IsASCIICapable() ? "- ASCII capable " : "",
michael@0 2286 GetCharacters(is4), GetCharacters(is5),
michael@0 2287 GetCharacters(lang0), GetCharacters(bundleID0),
michael@0 2288 GetCharacters(is2), GetCharacters(is1), GetCharacters(is3)));
michael@0 2289 }
michael@0 2290 sLastTIS = newTIS;
michael@0 2291 }
michael@0 2292 #endif // #ifdef PR_LOGGING
michael@0 2293 }
michael@0 2294
michael@0 2295 // static
michael@0 2296 void
michael@0 2297 IMEInputHandler::FlushPendingMethods(nsITimer* aTimer, void* aClosure)
michael@0 2298 {
michael@0 2299 NS_ASSERTION(aClosure, "aClosure is null");
michael@0 2300 static_cast<IMEInputHandler*>(aClosure)->ExecutePendingMethods();
michael@0 2301 }
michael@0 2302
michael@0 2303 // static
michael@0 2304 CFArrayRef
michael@0 2305 IMEInputHandler::CreateAllIMEModeList()
michael@0 2306 {
michael@0 2307 const void* keys[] = { kTISPropertyInputSourceType };
michael@0 2308 const void* values[] = { kTISTypeKeyboardInputMode };
michael@0 2309 CFDictionaryRef filter =
michael@0 2310 ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
michael@0 2311 NS_ASSERTION(filter, "failed to create the filter");
michael@0 2312 CFArrayRef list = ::TISCreateInputSourceList(filter, true);
michael@0 2313 ::CFRelease(filter);
michael@0 2314 return list;
michael@0 2315 }
michael@0 2316
michael@0 2317 // static
michael@0 2318 void
michael@0 2319 IMEInputHandler::DebugPrintAllIMEModes()
michael@0 2320 {
michael@0 2321 #ifdef PR_LOGGING
michael@0 2322 if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
michael@0 2323 CFArrayRef list = CreateAllIMEModeList();
michael@0 2324 PR_LOG(gLog, PR_LOG_ALWAYS, ("IME mode configuration:"));
michael@0 2325 CFIndex idx = ::CFArrayGetCount(list);
michael@0 2326 TISInputSourceWrapper tis;
michael@0 2327 for (CFIndex i = 0; i < idx; ++i) {
michael@0 2328 TISInputSourceRef inputSource = static_cast<TISInputSourceRef>(
michael@0 2329 const_cast<void *>(::CFArrayGetValueAtIndex(list, i)));
michael@0 2330 tis.InitByTISInputSourceRef(inputSource);
michael@0 2331 nsAutoString name, isid;
michael@0 2332 tis.GetLocalizedName(name);
michael@0 2333 tis.GetInputSourceID(isid);
michael@0 2334 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2335 (" %s\t<%s>%s%s\n",
michael@0 2336 NS_ConvertUTF16toUTF8(name).get(),
michael@0 2337 NS_ConvertUTF16toUTF8(isid).get(),
michael@0 2338 tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
michael@0 2339 tis.IsEnabled() ? "" : "\t(Isn't Enabled)"));
michael@0 2340 }
michael@0 2341 ::CFRelease(list);
michael@0 2342 }
michael@0 2343 #endif // #ifdef PR_LOGGING
michael@0 2344 }
michael@0 2345
michael@0 2346 //static
michael@0 2347 TSMDocumentID
michael@0 2348 IMEInputHandler::GetCurrentTSMDocumentID()
michael@0 2349 {
michael@0 2350 // At least on Mac OS X 10.6.x and 10.7.x, ::TSMGetActiveDocument() has a bug.
michael@0 2351 // The result of ::TSMGetActiveDocument() isn't modified for new active text
michael@0 2352 // input context until [NSTextInputContext currentInputContext] is called.
michael@0 2353 // Therefore, we need to call it here.
michael@0 2354 [NSTextInputContext currentInputContext];
michael@0 2355 return ::TSMGetActiveDocument();
michael@0 2356 }
michael@0 2357
michael@0 2358
michael@0 2359 #pragma mark -
michael@0 2360
michael@0 2361
michael@0 2362 /******************************************************************************
michael@0 2363 *
michael@0 2364 * IMEInputHandler implementation #1
michael@0 2365 * The methods are releated to the pending methods. Some jobs should be
michael@0 2366 * run after the stack is finished, e.g, some methods cannot run the jobs
michael@0 2367 * during processing the focus event. And also some other jobs should be
michael@0 2368 * run at the next focus event is processed.
michael@0 2369 * The pending methods are recorded in mPendingMethods. They are executed
michael@0 2370 * by ExecutePendingMethods via FlushPendingMethods.
michael@0 2371 *
michael@0 2372 ******************************************************************************/
michael@0 2373
michael@0 2374 void
michael@0 2375 IMEInputHandler::NotifyIMEOfFocusChangeInGecko()
michael@0 2376 {
michael@0 2377 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 2378
michael@0 2379 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2380 ("%p IMEInputHandler::NotifyIMEOfFocusChangeInGecko, "
michael@0 2381 "Destroyed()=%s, IsFocused()=%s, inputContext=%p",
michael@0 2382 this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
michael@0 2383 mView ? [mView inputContext] : nullptr));
michael@0 2384
michael@0 2385 if (Destroyed()) {
michael@0 2386 return;
michael@0 2387 }
michael@0 2388
michael@0 2389 if (!IsFocused()) {
michael@0 2390 // retry at next focus event
michael@0 2391 mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
michael@0 2392 return;
michael@0 2393 }
michael@0 2394
michael@0 2395 MOZ_ASSERT(mView);
michael@0 2396 NSTextInputContext* inputContext = [mView inputContext];
michael@0 2397 NS_ENSURE_TRUE_VOID(inputContext);
michael@0 2398
michael@0 2399 // When an <input> element on a XUL <panel> element gets focus from an <input>
michael@0 2400 // element on the opener window of the <panel> element, the owner window
michael@0 2401 // still has native focus. Therefore, IMEs may store the opener window's
michael@0 2402 // level at this time because they don't know the actual focus is moved to
michael@0 2403 // different window. If IMEs try to get the newest window level after the
michael@0 2404 // focus change, we return the window level of the XUL <panel>'s widget.
michael@0 2405 // Therefore, let's emulate the native focus change. Then, IMEs can refresh
michael@0 2406 // the stored window level.
michael@0 2407 [inputContext deactivate];
michael@0 2408 [inputContext activate];
michael@0 2409
michael@0 2410 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 2411 }
michael@0 2412
michael@0 2413 void
michael@0 2414 IMEInputHandler::DiscardIMEComposition()
michael@0 2415 {
michael@0 2416 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 2417
michael@0 2418 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2419 ("%p IMEInputHandler::DiscardIMEComposition, "
michael@0 2420 "Destroyed()=%s, IsFocused()=%s, mView=%p, inputContext=%p",
michael@0 2421 this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
michael@0 2422 mView, mView ? [mView inputContext] : nullptr));
michael@0 2423
michael@0 2424 if (Destroyed()) {
michael@0 2425 return;
michael@0 2426 }
michael@0 2427
michael@0 2428 if (!IsFocused()) {
michael@0 2429 // retry at next focus event
michael@0 2430 mPendingMethods |= kDiscardIMEComposition;
michael@0 2431 return;
michael@0 2432 }
michael@0 2433
michael@0 2434 NS_ENSURE_TRUE_VOID(mView);
michael@0 2435 NSTextInputContext* inputContext = [mView inputContext];
michael@0 2436 NS_ENSURE_TRUE_VOID(inputContext);
michael@0 2437 mIgnoreIMECommit = true;
michael@0 2438 [inputContext discardMarkedText];
michael@0 2439 mIgnoreIMECommit = false;
michael@0 2440
michael@0 2441 NS_OBJC_END_TRY_ABORT_BLOCK
michael@0 2442 }
michael@0 2443
michael@0 2444 void
michael@0 2445 IMEInputHandler::SyncASCIICapableOnly()
michael@0 2446 {
michael@0 2447 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 2448
michael@0 2449 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2450 ("%p IMEInputHandler::SyncASCIICapableOnly, "
michael@0 2451 "Destroyed()=%s, IsFocused()=%s, mIsASCIICapableOnly=%s, "
michael@0 2452 "GetCurrentTSMDocumentID()=%p",
michael@0 2453 this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
michael@0 2454 TrueOrFalse(mIsASCIICapableOnly), GetCurrentTSMDocumentID()));
michael@0 2455
michael@0 2456 if (Destroyed()) {
michael@0 2457 return;
michael@0 2458 }
michael@0 2459
michael@0 2460 if (!IsFocused()) {
michael@0 2461 // retry at next focus event
michael@0 2462 mPendingMethods |= kSyncASCIICapableOnly;
michael@0 2463 return;
michael@0 2464 }
michael@0 2465
michael@0 2466 TSMDocumentID doc = GetCurrentTSMDocumentID();
michael@0 2467 if (!doc) {
michael@0 2468 // retry
michael@0 2469 mPendingMethods |= kSyncASCIICapableOnly;
michael@0 2470 NS_WARNING("Application is active but there is no active document");
michael@0 2471 ResetTimer();
michael@0 2472 return;
michael@0 2473 }
michael@0 2474
michael@0 2475 if (mIsASCIICapableOnly) {
michael@0 2476 CFArrayRef ASCIICapableTISList = ::TISCreateASCIICapableInputSourceList();
michael@0 2477 ::TSMSetDocumentProperty(doc,
michael@0 2478 kTSMDocumentEnabledInputSourcesPropertyTag,
michael@0 2479 sizeof(CFArrayRef),
michael@0 2480 &ASCIICapableTISList);
michael@0 2481 ::CFRelease(ASCIICapableTISList);
michael@0 2482 } else {
michael@0 2483 ::TSMRemoveDocumentProperty(doc,
michael@0 2484 kTSMDocumentEnabledInputSourcesPropertyTag);
michael@0 2485 }
michael@0 2486
michael@0 2487 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 2488 }
michael@0 2489
michael@0 2490 void
michael@0 2491 IMEInputHandler::ResetTimer()
michael@0 2492 {
michael@0 2493 NS_ASSERTION(mPendingMethods != 0,
michael@0 2494 "There are not pending methods, why this is called?");
michael@0 2495 if (mTimer) {
michael@0 2496 mTimer->Cancel();
michael@0 2497 } else {
michael@0 2498 mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
michael@0 2499 NS_ENSURE_TRUE(mTimer, );
michael@0 2500 }
michael@0 2501 mTimer->InitWithFuncCallback(FlushPendingMethods, this, 0,
michael@0 2502 nsITimer::TYPE_ONE_SHOT);
michael@0 2503 }
michael@0 2504
michael@0 2505 void
michael@0 2506 IMEInputHandler::ExecutePendingMethods()
michael@0 2507 {
michael@0 2508 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 2509
michael@0 2510 if (mTimer) {
michael@0 2511 mTimer->Cancel();
michael@0 2512 mTimer = nullptr;
michael@0 2513 }
michael@0 2514
michael@0 2515 if (![[NSApplication sharedApplication] isActive]) {
michael@0 2516 mIsInFocusProcessing = false;
michael@0 2517 // If we're not active, we should retry at focus event
michael@0 2518 return;
michael@0 2519 }
michael@0 2520
michael@0 2521 uint32_t pendingMethods = mPendingMethods;
michael@0 2522 // First, reset the pending method flags because if each methods cannot
michael@0 2523 // run now, they can reentry to the pending flags by theirselves.
michael@0 2524 mPendingMethods = 0;
michael@0 2525
michael@0 2526 if (pendingMethods & kDiscardIMEComposition)
michael@0 2527 DiscardIMEComposition();
michael@0 2528 if (pendingMethods & kSyncASCIICapableOnly)
michael@0 2529 SyncASCIICapableOnly();
michael@0 2530 if (pendingMethods & kNotifyIMEOfFocusChangeInGecko) {
michael@0 2531 NotifyIMEOfFocusChangeInGecko();
michael@0 2532 }
michael@0 2533
michael@0 2534 mIsInFocusProcessing = false;
michael@0 2535
michael@0 2536 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 2537 }
michael@0 2538
michael@0 2539 #pragma mark -
michael@0 2540
michael@0 2541
michael@0 2542 /******************************************************************************
michael@0 2543 *
michael@0 2544 * IMEInputHandler implementation (native event handlers)
michael@0 2545 *
michael@0 2546 ******************************************************************************/
michael@0 2547
michael@0 2548 uint32_t
michael@0 2549 IMEInputHandler::ConvertToTextRangeType(uint32_t aUnderlineStyle,
michael@0 2550 NSRange& aSelectedRange)
michael@0 2551 {
michael@0 2552 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2553 ("%p IMEInputHandler::ConvertToTextRangeType, "
michael@0 2554 "aUnderlineStyle=%llu, aSelectedRange.length=%llu,",
michael@0 2555 this, aUnderlineStyle, aSelectedRange.length));
michael@0 2556
michael@0 2557 // We assume that aUnderlineStyle is NSUnderlineStyleSingle or
michael@0 2558 // NSUnderlineStyleThick. NSUnderlineStyleThick should indicate a selected
michael@0 2559 // clause. Otherwise, should indicate non-selected clause.
michael@0 2560
michael@0 2561 if (aSelectedRange.length == 0) {
michael@0 2562 switch (aUnderlineStyle) {
michael@0 2563 case NSUnderlineStyleSingle:
michael@0 2564 return NS_TEXTRANGE_RAWINPUT;
michael@0 2565 case NSUnderlineStyleThick:
michael@0 2566 return NS_TEXTRANGE_SELECTEDRAWTEXT;
michael@0 2567 default:
michael@0 2568 NS_WARNING("Unexpected line style");
michael@0 2569 return NS_TEXTRANGE_SELECTEDRAWTEXT;
michael@0 2570 }
michael@0 2571 }
michael@0 2572
michael@0 2573 switch (aUnderlineStyle) {
michael@0 2574 case NSUnderlineStyleSingle:
michael@0 2575 return NS_TEXTRANGE_CONVERTEDTEXT;
michael@0 2576 case NSUnderlineStyleThick:
michael@0 2577 return NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
michael@0 2578 default:
michael@0 2579 NS_WARNING("Unexpected line style");
michael@0 2580 return NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
michael@0 2581 }
michael@0 2582 }
michael@0 2583
michael@0 2584 uint32_t
michael@0 2585 IMEInputHandler::GetRangeCount(NSAttributedString *aAttrString)
michael@0 2586 {
michael@0 2587 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
michael@0 2588
michael@0 2589 // Iterate through aAttrString for the NSUnderlineStyleAttributeName and
michael@0 2590 // count the different segments adjusting limitRange as we go.
michael@0 2591 uint32_t count = 0;
michael@0 2592 NSRange effectiveRange;
michael@0 2593 NSRange limitRange = NSMakeRange(0, [aAttrString length]);
michael@0 2594 while (limitRange.length > 0) {
michael@0 2595 [aAttrString attribute:NSUnderlineStyleAttributeName
michael@0 2596 atIndex:limitRange.location
michael@0 2597 longestEffectiveRange:&effectiveRange
michael@0 2598 inRange:limitRange];
michael@0 2599 limitRange =
michael@0 2600 NSMakeRange(NSMaxRange(effectiveRange),
michael@0 2601 NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
michael@0 2602 count++;
michael@0 2603 }
michael@0 2604
michael@0 2605 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2606 ("%p IMEInputHandler::GetRangeCount, aAttrString=\"%s\", count=%llu",
michael@0 2607 this, GetCharacters([aAttrString string]), count));
michael@0 2608
michael@0 2609 return count;
michael@0 2610
michael@0 2611 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
michael@0 2612 }
michael@0 2613
michael@0 2614 already_AddRefed<mozilla::TextRangeArray>
michael@0 2615 IMEInputHandler::CreateTextRangeArray(NSAttributedString *aAttrString,
michael@0 2616 NSRange& aSelectedRange)
michael@0 2617 {
michael@0 2618 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
michael@0 2619
michael@0 2620 // Convert the Cocoa range into the TextRange Array used in Gecko.
michael@0 2621 // Iterate through the attributed string and map the underline attribute to
michael@0 2622 // Gecko IME textrange attributes. We may need to change the code here if
michael@0 2623 // we change the implementation of validAttributesForMarkedText.
michael@0 2624 NSRange limitRange = NSMakeRange(0, [aAttrString length]);
michael@0 2625 uint32_t rangeCount = GetRangeCount(aAttrString);
michael@0 2626 nsRefPtr<mozilla::TextRangeArray> textRangeArray =
michael@0 2627 new mozilla::TextRangeArray();
michael@0 2628 for (uint32_t i = 0; i < rangeCount && limitRange.length > 0; i++) {
michael@0 2629 NSRange effectiveRange;
michael@0 2630 id attributeValue = [aAttrString attribute:NSUnderlineStyleAttributeName
michael@0 2631 atIndex:limitRange.location
michael@0 2632 longestEffectiveRange:&effectiveRange
michael@0 2633 inRange:limitRange];
michael@0 2634
michael@0 2635 TextRange range;
michael@0 2636 range.mStartOffset = effectiveRange.location;
michael@0 2637 range.mEndOffset = NSMaxRange(effectiveRange);
michael@0 2638 range.mRangeType =
michael@0 2639 ConvertToTextRangeType([attributeValue intValue], aSelectedRange);
michael@0 2640 textRangeArray->AppendElement(range);
michael@0 2641
michael@0 2642 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2643 ("%p IMEInputHandler::CreateTextRangeArray, "
michael@0 2644 "range={ mStartOffset=%llu, mEndOffset=%llu, mRangeType=%s }",
michael@0 2645 this, range.mStartOffset, range.mEndOffset,
michael@0 2646 GetRangeTypeName(range.mRangeType)));
michael@0 2647
michael@0 2648 limitRange =
michael@0 2649 NSMakeRange(NSMaxRange(effectiveRange),
michael@0 2650 NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
michael@0 2651 }
michael@0 2652
michael@0 2653 // Get current caret position.
michael@0 2654 TextRange range;
michael@0 2655 range.mStartOffset = aSelectedRange.location + aSelectedRange.length;
michael@0 2656 range.mEndOffset = range.mStartOffset;
michael@0 2657 range.mRangeType = NS_TEXTRANGE_CARETPOSITION;
michael@0 2658 textRangeArray->AppendElement(range);
michael@0 2659
michael@0 2660 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2661 ("%p IMEInputHandler::CreateTextRangeArray, "
michael@0 2662 "range={ mStartOffset=%llu, mEndOffset=%llu, mRangeType=%s }",
michael@0 2663 this, range.mStartOffset, range.mEndOffset,
michael@0 2664 GetRangeTypeName(range.mRangeType)));
michael@0 2665
michael@0 2666 return textRangeArray.forget();
michael@0 2667
michael@0 2668 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
michael@0 2669 }
michael@0 2670
michael@0 2671 bool
michael@0 2672 IMEInputHandler::DispatchTextEvent(const nsString& aText,
michael@0 2673 NSAttributedString* aAttrString,
michael@0 2674 NSRange& aSelectedRange,
michael@0 2675 bool aDoCommit)
michael@0 2676 {
michael@0 2677 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2678 ("%p IMEInputHandler::DispatchTextEvent, "
michael@0 2679 "aText=\"%s\", aAttrString=\"%s\", "
michael@0 2680 "aSelectedRange={ location=%llu, length=%llu }, "
michael@0 2681 "aDoCommit=%s, Destroyed()=%s",
michael@0 2682 this, NS_ConvertUTF16toUTF8(aText).get(),
michael@0 2683 GetCharacters([aAttrString string]),
michael@0 2684 aSelectedRange.location, aSelectedRange.length,
michael@0 2685 TrueOrFalse(aDoCommit), TrueOrFalse(Destroyed())));
michael@0 2686
michael@0 2687 NS_ENSURE_TRUE(!Destroyed(), false);
michael@0 2688
michael@0 2689 nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
michael@0 2690
michael@0 2691 WidgetTextEvent textEvent(true, NS_TEXT_TEXT, mWidget);
michael@0 2692 textEvent.time = PR_IntervalNow();
michael@0 2693 textEvent.theText = aText;
michael@0 2694 if (!aDoCommit) {
michael@0 2695 textEvent.mRanges = CreateTextRangeArray(aAttrString, aSelectedRange);
michael@0 2696 }
michael@0 2697
michael@0 2698 if (textEvent.theText != mLastDispatchedCompositionString) {
michael@0 2699 WidgetCompositionEvent compositionUpdate(true, NS_COMPOSITION_UPDATE,
michael@0 2700 mWidget);
michael@0 2701 compositionUpdate.time = textEvent.time;
michael@0 2702 compositionUpdate.data = textEvent.theText;
michael@0 2703 mLastDispatchedCompositionString = textEvent.theText;
michael@0 2704 DispatchEvent(compositionUpdate);
michael@0 2705 if (mIsInFocusProcessing || Destroyed()) {
michael@0 2706 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2707 ("%p IMEInputHandler::DispatchTextEvent, compositionupdate causes "
michael@0 2708 "aborting the composition, mIsInFocusProcessing=%s, Destryoed()=%s",
michael@0 2709 this, TrueOrFalse(mIsInFocusProcessing), TrueOrFalse(Destroyed())));
michael@0 2710 if (Destroyed()) {
michael@0 2711 return true;
michael@0 2712 }
michael@0 2713 }
michael@0 2714 }
michael@0 2715
michael@0 2716 return DispatchEvent(textEvent);
michael@0 2717 }
michael@0 2718
michael@0 2719 void
michael@0 2720 IMEInputHandler::InitCompositionEvent(WidgetCompositionEvent& aCompositionEvent)
michael@0 2721 {
michael@0 2722 aCompositionEvent.time = PR_IntervalNow();
michael@0 2723 }
michael@0 2724
michael@0 2725 void
michael@0 2726 IMEInputHandler::InsertTextAsCommittingComposition(
michael@0 2727 NSAttributedString* aAttrString,
michael@0 2728 NSRange* aReplacementRange)
michael@0 2729 {
michael@0 2730 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 2731
michael@0 2732 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2733 ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
michael@0 2734 "aAttrString=\"%s\", aReplacementRange=%p { location=%llu, length=%llu }, "
michael@0 2735 "Destroyed()=%s, IsIMEComposing()=%s, "
michael@0 2736 "mMarkedRange={ location=%llu, length=%llu }",
michael@0 2737 this, GetCharacters([aAttrString string]), aReplacementRange,
michael@0 2738 aReplacementRange ? aReplacementRange->location : 0,
michael@0 2739 aReplacementRange ? aReplacementRange->length : 0,
michael@0 2740 TrueOrFalse(Destroyed()), TrueOrFalse(IsIMEComposing()),
michael@0 2741 mMarkedRange.location, mMarkedRange.length));
michael@0 2742
michael@0 2743 if (IgnoreIMECommit()) {
michael@0 2744 MOZ_CRASH("IMEInputHandler::InsertTextAsCommittingComposition() must not"
michael@0 2745 "be called while canceling the composition");
michael@0 2746 }
michael@0 2747
michael@0 2748 if (Destroyed()) {
michael@0 2749 return;
michael@0 2750 }
michael@0 2751
michael@0 2752 // First, commit current composition with the latest composition string if the
michael@0 2753 // replacement range is different from marked range.
michael@0 2754 if (IsIMEComposing() && aReplacementRange &&
michael@0 2755 aReplacementRange->location != NSNotFound &&
michael@0 2756 !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
michael@0 2757 NSString* latestStr =
michael@0 2758 nsCocoaUtils::ToNSString(mLastDispatchedCompositionString);
michael@0 2759 NSAttributedString* attrLatestStr =
michael@0 2760 [[[NSAttributedString alloc] initWithString:latestStr] autorelease];
michael@0 2761 InsertTextAsCommittingComposition(attrLatestStr, nullptr);
michael@0 2762 if (Destroyed()) {
michael@0 2763 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2764 ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
michael@0 2765 "destroyed by commiting composition for setting replacement range",
michael@0 2766 this));
michael@0 2767 return;
michael@0 2768 }
michael@0 2769 }
michael@0 2770
michael@0 2771 nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
michael@0 2772
michael@0 2773 nsString str;
michael@0 2774 nsCocoaUtils::GetStringForNSString([aAttrString string], str);
michael@0 2775
michael@0 2776 if (!IsIMEComposing()) {
michael@0 2777 // If there is no selection and replacement range is specified, set the
michael@0 2778 // range as selection.
michael@0 2779 if (aReplacementRange && aReplacementRange->location != NSNotFound &&
michael@0 2780 !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
michael@0 2781 NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
michael@0 2782 }
michael@0 2783
michael@0 2784 // XXXmnakano Probably, we shouldn't emulate composition in this case.
michael@0 2785 // I think that we should just fire DOM3 textInput event if we implement it.
michael@0 2786 WidgetCompositionEvent compStart(true, NS_COMPOSITION_START, mWidget);
michael@0 2787 InitCompositionEvent(compStart);
michael@0 2788
michael@0 2789 DispatchEvent(compStart);
michael@0 2790 if (Destroyed()) {
michael@0 2791 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2792 ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
michael@0 2793 "destroyed by compositionstart event", this));
michael@0 2794 return;
michael@0 2795 }
michael@0 2796
michael@0 2797 OnStartIMEComposition();
michael@0 2798 }
michael@0 2799
michael@0 2800 NSRange range = NSMakeRange(0, str.Length());
michael@0 2801 DispatchTextEvent(str, aAttrString, range, true);
michael@0 2802 if (Destroyed()) {
michael@0 2803 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2804 ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
michael@0 2805 "destroyed by text event", this));
michael@0 2806 return;
michael@0 2807 }
michael@0 2808
michael@0 2809 OnUpdateIMEComposition([aAttrString string]);
michael@0 2810
michael@0 2811 WidgetCompositionEvent compEnd(true, NS_COMPOSITION_END, mWidget);
michael@0 2812 InitCompositionEvent(compEnd);
michael@0 2813 compEnd.data = mLastDispatchedCompositionString;
michael@0 2814 DispatchEvent(compEnd);
michael@0 2815 if (Destroyed()) {
michael@0 2816 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2817 ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
michael@0 2818 "destroyed by compositionend event", this));
michael@0 2819 return;
michael@0 2820 }
michael@0 2821
michael@0 2822 OnEndIMEComposition();
michael@0 2823
michael@0 2824 mMarkedRange = NSMakeRange(NSNotFound, 0);
michael@0 2825
michael@0 2826 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 2827 }
michael@0 2828
michael@0 2829 void
michael@0 2830 IMEInputHandler::SetMarkedText(NSAttributedString* aAttrString,
michael@0 2831 NSRange& aSelectedRange,
michael@0 2832 NSRange* aReplacementRange)
michael@0 2833 {
michael@0 2834 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 2835
michael@0 2836 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2837 ("%p IMEInputHandler::SetMarkedText, "
michael@0 2838 "aAttrString=\"%s\", aSelectedRange={ location=%llu, length=%llu }, "
michael@0 2839 "aReplacementRange=%p { location=%llu, length=%llu }, "
michael@0 2840 "Destroyed()=%s, IgnoreIMEComposition()=%s, IsIMEComposing()=%s, "
michael@0 2841 "mMarkedRange={ location=%llu, length=%llu }",
michael@0 2842 this, GetCharacters([aAttrString string]),
michael@0 2843 aSelectedRange.location, aSelectedRange.length, aReplacementRange,
michael@0 2844 aReplacementRange ? aReplacementRange->location : 0,
michael@0 2845 aReplacementRange ? aReplacementRange->length : 0,
michael@0 2846 TrueOrFalse(Destroyed()), TrueOrFalse(IgnoreIMEComposition()),
michael@0 2847 TrueOrFalse(IsIMEComposing()),
michael@0 2848 mMarkedRange.location, mMarkedRange.length));
michael@0 2849
michael@0 2850 if (Destroyed() || IgnoreIMEComposition()) {
michael@0 2851 return;
michael@0 2852 }
michael@0 2853
michael@0 2854 nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
michael@0 2855
michael@0 2856 // First, commit current composition with the latest composition string if the
michael@0 2857 // replacement range is different from marked range.
michael@0 2858 if (IsIMEComposing() && aReplacementRange &&
michael@0 2859 aReplacementRange->location != NSNotFound &&
michael@0 2860 !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
michael@0 2861 NSString* latestStr =
michael@0 2862 nsCocoaUtils::ToNSString(mLastDispatchedCompositionString);
michael@0 2863 NSAttributedString* attrLatestStr =
michael@0 2864 [[[NSAttributedString alloc] initWithString:latestStr] autorelease];
michael@0 2865 bool ignoreIMECommit = mIgnoreIMECommit;
michael@0 2866 mIgnoreIMECommit = false;
michael@0 2867 InsertTextAsCommittingComposition(attrLatestStr, nullptr);
michael@0 2868 mIgnoreIMECommit = ignoreIMECommit;
michael@0 2869 if (Destroyed()) {
michael@0 2870 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2871 ("%p IMEInputHandler::SetMarkedText, "
michael@0 2872 "destroyed by commiting composition for setting replacement range",
michael@0 2873 this));
michael@0 2874 return;
michael@0 2875 }
michael@0 2876 }
michael@0 2877
michael@0 2878 nsString str;
michael@0 2879 nsCocoaUtils::GetStringForNSString([aAttrString string], str);
michael@0 2880
michael@0 2881 mMarkedRange.length = str.Length();
michael@0 2882
michael@0 2883 if (!IsIMEComposing() && !str.IsEmpty()) {
michael@0 2884 // If there is no selection and replacement range is specified, set the
michael@0 2885 // range as selection.
michael@0 2886 if (aReplacementRange && aReplacementRange->location != NSNotFound &&
michael@0 2887 !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
michael@0 2888 NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
michael@0 2889 }
michael@0 2890
michael@0 2891 mMarkedRange.location = SelectedRange().location;
michael@0 2892
michael@0 2893 WidgetCompositionEvent compStart(true, NS_COMPOSITION_START, mWidget);
michael@0 2894 InitCompositionEvent(compStart);
michael@0 2895
michael@0 2896 DispatchEvent(compStart);
michael@0 2897 if (Destroyed()) {
michael@0 2898 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2899 ("%p IMEInputHandler::SetMarkedText, "
michael@0 2900 "destroyed by compositionstart event", this));
michael@0 2901 return;
michael@0 2902 }
michael@0 2903
michael@0 2904 OnStartIMEComposition();
michael@0 2905 }
michael@0 2906
michael@0 2907 if (IsIMEComposing()) {
michael@0 2908 OnUpdateIMEComposition([aAttrString string]);
michael@0 2909
michael@0 2910 bool doCommit = str.IsEmpty();
michael@0 2911 DispatchTextEvent(str, aAttrString, aSelectedRange, doCommit);
michael@0 2912 if (Destroyed()) {
michael@0 2913 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2914 ("%p IMEInputHandler::SetMarkedText, "
michael@0 2915 "destroyed by text event", this));
michael@0 2916 return;
michael@0 2917 }
michael@0 2918
michael@0 2919 if (doCommit) {
michael@0 2920 WidgetCompositionEvent compEnd(true, NS_COMPOSITION_END, mWidget);
michael@0 2921 InitCompositionEvent(compEnd);
michael@0 2922 compEnd.data = mLastDispatchedCompositionString;
michael@0 2923 DispatchEvent(compEnd);
michael@0 2924 if (Destroyed()) {
michael@0 2925 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2926 ("%p IMEInputHandler::SetMarkedText, "
michael@0 2927 "destroyed by compositionend event", this));
michael@0 2928 return;
michael@0 2929 }
michael@0 2930 OnEndIMEComposition();
michael@0 2931 }
michael@0 2932 }
michael@0 2933
michael@0 2934 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 2935 }
michael@0 2936
michael@0 2937 NSInteger
michael@0 2938 IMEInputHandler::ConversationIdentifier()
michael@0 2939 {
michael@0 2940 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2941 ("%p IMEInputHandler::ConversationIdentifier, Destroyed()=%s",
michael@0 2942 this, TrueOrFalse(Destroyed())));
michael@0 2943
michael@0 2944 if (Destroyed()) {
michael@0 2945 return reinterpret_cast<NSInteger>(mView);
michael@0 2946 }
michael@0 2947
michael@0 2948 nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
michael@0 2949
michael@0 2950 // NOTE: The size of NSInteger is same as pointer size.
michael@0 2951 WidgetQueryContentEvent textContent(true, NS_QUERY_TEXT_CONTENT, mWidget);
michael@0 2952 textContent.InitForQueryTextContent(0, 0);
michael@0 2953 DispatchEvent(textContent);
michael@0 2954 if (!textContent.mSucceeded) {
michael@0 2955 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2956 ("%p IMEInputHandler::ConversationIdentifier, Failed", this));
michael@0 2957 return reinterpret_cast<NSInteger>(mView);
michael@0 2958 }
michael@0 2959 // XXX This might return same ID as a previously existing editor if the
michael@0 2960 // deleted editor was created at the same address. Is there a better way?
michael@0 2961 return reinterpret_cast<NSInteger>(textContent.mReply.mContentsRoot);
michael@0 2962 }
michael@0 2963
michael@0 2964 NSAttributedString*
michael@0 2965 IMEInputHandler::GetAttributedSubstringFromRange(NSRange& aRange,
michael@0 2966 NSRange* aActualRange)
michael@0 2967 {
michael@0 2968 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
michael@0 2969
michael@0 2970 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2971 ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
michael@0 2972 "aRange={ location=%llu, length=%llu }, aActualRange=%p, Destroyed()=%s",
michael@0 2973 this, aRange.location, aRange.length, aActualRange,
michael@0 2974 TrueOrFalse(Destroyed())));
michael@0 2975
michael@0 2976 if (aActualRange) {
michael@0 2977 *aActualRange = NSMakeRange(NSNotFound, 0);
michael@0 2978 }
michael@0 2979
michael@0 2980 if (Destroyed() || aRange.location == NSNotFound || aRange.length == 0) {
michael@0 2981 return nil;
michael@0 2982 }
michael@0 2983
michael@0 2984 nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
michael@0 2985
michael@0 2986 nsAutoString str;
michael@0 2987 WidgetQueryContentEvent textContent(true, NS_QUERY_TEXT_CONTENT, mWidget);
michael@0 2988 textContent.InitForQueryTextContent(aRange.location, aRange.length);
michael@0 2989 DispatchEvent(textContent);
michael@0 2990
michael@0 2991 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 2992 ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
michael@0 2993 "textContent={ mSucceeded=%s, mReply={ mString=\"%s\", mOffset=%llu } }",
michael@0 2994 this, TrueOrFalse(textContent.mSucceeded),
michael@0 2995 NS_ConvertUTF16toUTF8(textContent.mReply.mString).get(),
michael@0 2996 textContent.mReply.mOffset));
michael@0 2997
michael@0 2998 if (!textContent.mSucceeded) {
michael@0 2999 return nil;
michael@0 3000 }
michael@0 3001
michael@0 3002 NSString* nsstr = nsCocoaUtils::ToNSString(textContent.mReply.mString);
michael@0 3003 NSAttributedString* result =
michael@0 3004 [[[NSAttributedString alloc] initWithString:nsstr
michael@0 3005 attributes:nil] autorelease];
michael@0 3006 if (aActualRange) {
michael@0 3007 aActualRange->location = textContent.mReply.mOffset;
michael@0 3008 aActualRange->length = textContent.mReply.mString.Length();
michael@0 3009 }
michael@0 3010 return result;
michael@0 3011
michael@0 3012 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
michael@0 3013 }
michael@0 3014
michael@0 3015 bool
michael@0 3016 IMEInputHandler::HasMarkedText()
michael@0 3017 {
michael@0 3018 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3019 ("%p IMEInputHandler::HasMarkedText, "
michael@0 3020 "mMarkedRange={ location=%llu, length=%llu }",
michael@0 3021 this, mMarkedRange.location, mMarkedRange.length));
michael@0 3022
michael@0 3023 return (mMarkedRange.location != NSNotFound) && (mMarkedRange.length != 0);
michael@0 3024 }
michael@0 3025
michael@0 3026 NSRange
michael@0 3027 IMEInputHandler::MarkedRange()
michael@0 3028 {
michael@0 3029 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3030 ("%p IMEInputHandler::MarkedRange, "
michael@0 3031 "mMarkedRange={ location=%llu, length=%llu }",
michael@0 3032 this, mMarkedRange.location, mMarkedRange.length));
michael@0 3033
michael@0 3034 if (!HasMarkedText()) {
michael@0 3035 return NSMakeRange(NSNotFound, 0);
michael@0 3036 }
michael@0 3037 return mMarkedRange;
michael@0 3038 }
michael@0 3039
michael@0 3040 NSRange
michael@0 3041 IMEInputHandler::SelectedRange()
michael@0 3042 {
michael@0 3043 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
michael@0 3044
michael@0 3045 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3046 ("%p IMEInputHandler::SelectedRange, Destroyed()=%s, mSelectedRange={ "
michael@0 3047 "location=%llu, length=%llu }",
michael@0 3048 this, TrueOrFalse(Destroyed()), mSelectedRange.location,
michael@0 3049 mSelectedRange.length));
michael@0 3050
michael@0 3051 if (Destroyed()) {
michael@0 3052 return mSelectedRange;
michael@0 3053 }
michael@0 3054
michael@0 3055 if (mSelectedRange.location != NSNotFound) {
michael@0 3056 MOZ_ASSERT(mIMEHasFocus);
michael@0 3057 return mSelectedRange;
michael@0 3058 }
michael@0 3059
michael@0 3060 nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
michael@0 3061
michael@0 3062 WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, mWidget);
michael@0 3063 DispatchEvent(selection);
michael@0 3064
michael@0 3065 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3066 ("%p IMEInputHandler::SelectedRange, selection={ mSucceeded=%s, "
michael@0 3067 "mReply={ mOffset=%llu, mString.Length()=%llu } }",
michael@0 3068 this, TrueOrFalse(selection.mSucceeded), selection.mReply.mOffset,
michael@0 3069 selection.mReply.mString.Length()));
michael@0 3070
michael@0 3071 if (!selection.mSucceeded) {
michael@0 3072 return mSelectedRange;
michael@0 3073 }
michael@0 3074
michael@0 3075 if (mIMEHasFocus) {
michael@0 3076 mSelectedRange.location = selection.mReply.mOffset;
michael@0 3077 mSelectedRange.length = selection.mReply.mString.Length();
michael@0 3078 return mSelectedRange;
michael@0 3079 }
michael@0 3080
michael@0 3081 return NSMakeRange(selection.mReply.mOffset,
michael@0 3082 selection.mReply.mString.Length());
michael@0 3083
michael@0 3084 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(mSelectedRange);
michael@0 3085 }
michael@0 3086
michael@0 3087 NSRect
michael@0 3088 IMEInputHandler::FirstRectForCharacterRange(NSRange& aRange,
michael@0 3089 NSRange* aActualRange)
michael@0 3090 {
michael@0 3091 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
michael@0 3092
michael@0 3093 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3094 ("%p IMEInputHandler::FirstRectForCharacterRange, Destroyed()=%s, "
michael@0 3095 "aRange={ location=%llu, length=%llu }, aActualRange=%p }",
michael@0 3096 this, TrueOrFalse(Destroyed()), aRange.location, aRange.length,
michael@0 3097 aActualRange));
michael@0 3098
michael@0 3099 // XXX this returns first character rect or caret rect, it is limitation of
michael@0 3100 // now. We need more work for returns first line rect. But current
michael@0 3101 // implementation is enough for IMEs.
michael@0 3102
michael@0 3103 NSRect rect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
michael@0 3104 NSRange actualRange = NSMakeRange(NSNotFound, 0);
michael@0 3105 if (aActualRange) {
michael@0 3106 *aActualRange = actualRange;
michael@0 3107 }
michael@0 3108 if (Destroyed() || aRange.location == NSNotFound) {
michael@0 3109 return rect;
michael@0 3110 }
michael@0 3111
michael@0 3112 nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
michael@0 3113
michael@0 3114 nsIntRect r;
michael@0 3115 bool useCaretRect = (aRange.length == 0);
michael@0 3116 if (!useCaretRect) {
michael@0 3117 WidgetQueryContentEvent charRect(true, NS_QUERY_TEXT_RECT, mWidget);
michael@0 3118 charRect.InitForQueryTextRect(aRange.location, 1);
michael@0 3119 DispatchEvent(charRect);
michael@0 3120 if (charRect.mSucceeded) {
michael@0 3121 r = charRect.mReply.mRect;
michael@0 3122 actualRange.location = charRect.mReply.mOffset;
michael@0 3123 actualRange.length = charRect.mReply.mString.Length();
michael@0 3124 } else {
michael@0 3125 useCaretRect = true;
michael@0 3126 }
michael@0 3127 }
michael@0 3128
michael@0 3129 if (useCaretRect) {
michael@0 3130 WidgetQueryContentEvent caretRect(true, NS_QUERY_CARET_RECT, mWidget);
michael@0 3131 caretRect.InitForQueryCaretRect(aRange.location);
michael@0 3132 DispatchEvent(caretRect);
michael@0 3133 if (!caretRect.mSucceeded) {
michael@0 3134 return rect;
michael@0 3135 }
michael@0 3136 r = caretRect.mReply.mRect;
michael@0 3137 r.width = 0;
michael@0 3138 actualRange.location = caretRect.mReply.mOffset;
michael@0 3139 actualRange.length = 0;
michael@0 3140 }
michael@0 3141
michael@0 3142 nsIWidget* rootWidget = mWidget->GetTopLevelWidget();
michael@0 3143 NSWindow* rootWindow =
michael@0 3144 static_cast<NSWindow*>(rootWidget->GetNativeData(NS_NATIVE_WINDOW));
michael@0 3145 NSView* rootView =
michael@0 3146 static_cast<NSView*>(rootWidget->GetNativeData(NS_NATIVE_WIDGET));
michael@0 3147 if (!rootWindow || !rootView) {
michael@0 3148 return rect;
michael@0 3149 }
michael@0 3150 rect = nsCocoaUtils::DevPixelsToCocoaPoints(r, mWidget->BackingScaleFactor());
michael@0 3151 rect = [rootView convertRect:rect toView:nil];
michael@0 3152 rect.origin = [rootWindow convertBaseToScreen:rect.origin];
michael@0 3153
michael@0 3154 if (aActualRange) {
michael@0 3155 *aActualRange = actualRange;
michael@0 3156 }
michael@0 3157
michael@0 3158 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3159 ("%p IMEInputHandler::FirstRectForCharacterRange, "
michael@0 3160 "useCaretRect=%s rect={ x=%f, y=%f, width=%f, height=%f }, "
michael@0 3161 "actualRange={ location=%llu, length=%llu }",
michael@0 3162 this, TrueOrFalse(useCaretRect), rect.origin.x, rect.origin.y,
michael@0 3163 rect.size.width, rect.size.height, actualRange.location,
michael@0 3164 actualRange.length));
michael@0 3165
michael@0 3166 return rect;
michael@0 3167
michael@0 3168 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRect(0.0, 0.0, 0.0, 0.0));
michael@0 3169 }
michael@0 3170
michael@0 3171 NSUInteger
michael@0 3172 IMEInputHandler::CharacterIndexForPoint(NSPoint& aPoint)
michael@0 3173 {
michael@0 3174 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3175 ("%p IMEInputHandler::CharacterIndexForPoint, aPoint={ x=%f, y=%f }",
michael@0 3176 this, aPoint.x, aPoint.y));
michael@0 3177
michael@0 3178 //nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
michael@0 3179
michael@0 3180 // To implement this, we'd have to grovel in text frames looking at text
michael@0 3181 // offsets.
michael@0 3182 return 0;
michael@0 3183 }
michael@0 3184
michael@0 3185 NSArray*
michael@0 3186 IMEInputHandler::GetValidAttributesForMarkedText()
michael@0 3187 {
michael@0 3188 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
michael@0 3189
michael@0 3190 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3191 ("%p IMEInputHandler::GetValidAttributesForMarkedText", this));
michael@0 3192
michael@0 3193 //nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
michael@0 3194
michael@0 3195 //return [NSArray arrayWithObjects:NSUnderlineStyleAttributeName,
michael@0 3196 // NSMarkedClauseSegmentAttributeName,
michael@0 3197 // NSTextInputReplacementRangeAttributeName,
michael@0 3198 // nil];
michael@0 3199 // empty array; we don't support any attributes right now
michael@0 3200 return [NSArray array];
michael@0 3201
michael@0 3202 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
michael@0 3203 }
michael@0 3204
michael@0 3205
michael@0 3206 #pragma mark -
michael@0 3207
michael@0 3208
michael@0 3209 /******************************************************************************
michael@0 3210 *
michael@0 3211 * IMEInputHandler implementation #2
michael@0 3212 *
michael@0 3213 ******************************************************************************/
michael@0 3214
michael@0 3215 IMEInputHandler::IMEInputHandler(nsChildView* aWidget,
michael@0 3216 NSView<mozView> *aNativeView) :
michael@0 3217 PluginTextInputHandler(aWidget, aNativeView),
michael@0 3218 mPendingMethods(0), mIMECompositionString(nullptr),
michael@0 3219 mIsIMEComposing(false), mIsIMEEnabled(true),
michael@0 3220 mIsASCIICapableOnly(false), mIgnoreIMECommit(false),
michael@0 3221 mIsInFocusProcessing(false), mIMEHasFocus(false)
michael@0 3222 {
michael@0 3223 InitStaticMembers();
michael@0 3224
michael@0 3225 mMarkedRange.location = NSNotFound;
michael@0 3226 mMarkedRange.length = 0;
michael@0 3227 mSelectedRange.location = NSNotFound;
michael@0 3228 mSelectedRange.length = 0;
michael@0 3229 }
michael@0 3230
michael@0 3231 IMEInputHandler::~IMEInputHandler()
michael@0 3232 {
michael@0 3233 if (mTimer) {
michael@0 3234 mTimer->Cancel();
michael@0 3235 mTimer = nullptr;
michael@0 3236 }
michael@0 3237 if (sFocusedIMEHandler == this) {
michael@0 3238 sFocusedIMEHandler = nullptr;
michael@0 3239 }
michael@0 3240 }
michael@0 3241
michael@0 3242 void
michael@0 3243 IMEInputHandler::OnFocusChangeInGecko(bool aFocus)
michael@0 3244 {
michael@0 3245 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3246 ("%p IMEInputHandler::OnFocusChangeInGecko, aFocus=%s, Destroyed()=%s, "
michael@0 3247 "sFocusedIMEHandler=%p",
michael@0 3248 this, TrueOrFalse(aFocus), TrueOrFalse(Destroyed()), sFocusedIMEHandler));
michael@0 3249
michael@0 3250 mSelectedRange.location = NSNotFound; // Marking dirty
michael@0 3251 mIMEHasFocus = aFocus;
michael@0 3252
michael@0 3253 // This is called when the native focus is changed and when the native focus
michael@0 3254 // isn't changed but the focus is changed in Gecko.
michael@0 3255 if (!aFocus) {
michael@0 3256 if (sFocusedIMEHandler == this)
michael@0 3257 sFocusedIMEHandler = nullptr;
michael@0 3258 return;
michael@0 3259 }
michael@0 3260
michael@0 3261 sFocusedIMEHandler = this;
michael@0 3262 mIsInFocusProcessing = true;
michael@0 3263
michael@0 3264 // We need to notify IME of focus change in Gecko as native focus change
michael@0 3265 // because the window level of the focused element in Gecko may be changed.
michael@0 3266 mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
michael@0 3267 ResetTimer();
michael@0 3268 }
michael@0 3269
michael@0 3270 bool
michael@0 3271 IMEInputHandler::OnDestroyWidget(nsChildView* aDestroyingWidget)
michael@0 3272 {
michael@0 3273 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3274 ("%p IMEInputHandler::OnDestroyWidget, aDestroyingWidget=%p, "
michael@0 3275 "sFocusedIMEHandler=%p, IsIMEComposing()=%s",
michael@0 3276 this, aDestroyingWidget, sFocusedIMEHandler,
michael@0 3277 TrueOrFalse(IsIMEComposing())));
michael@0 3278
michael@0 3279 // If we're not focused, the focused IMEInputHandler may have been
michael@0 3280 // created by another widget/nsChildView.
michael@0 3281 if (sFocusedIMEHandler && sFocusedIMEHandler != this) {
michael@0 3282 sFocusedIMEHandler->OnDestroyWidget(aDestroyingWidget);
michael@0 3283 }
michael@0 3284
michael@0 3285 if (!PluginTextInputHandler::OnDestroyWidget(aDestroyingWidget)) {
michael@0 3286 return false;
michael@0 3287 }
michael@0 3288
michael@0 3289 if (IsIMEComposing()) {
michael@0 3290 // If our view is in the composition, we should clean up it.
michael@0 3291 CancelIMEComposition();
michael@0 3292 OnEndIMEComposition();
michael@0 3293 }
michael@0 3294
michael@0 3295 mSelectedRange.location = NSNotFound; // Marking dirty
michael@0 3296 mIMEHasFocus = false;
michael@0 3297
michael@0 3298 return true;
michael@0 3299 }
michael@0 3300
michael@0 3301 void
michael@0 3302 IMEInputHandler::OnStartIMEComposition()
michael@0 3303 {
michael@0 3304 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 3305
michael@0 3306 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3307 ("%p IMEInputHandler::OnStartIMEComposition, mView=%p, mWidget=%p"
michael@0 3308 "inputContext=%p, mIsIMEComposing=%s",
michael@0 3309 this, mView, mWidget, mView ? [mView inputContext] : nullptr,
michael@0 3310 TrueOrFalse(mIsIMEComposing)));
michael@0 3311
michael@0 3312 NS_ASSERTION(!mIsIMEComposing, "There is a composition already");
michael@0 3313 mIsIMEComposing = true;
michael@0 3314
michael@0 3315 mLastDispatchedCompositionString.Truncate();
michael@0 3316
michael@0 3317 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 3318 }
michael@0 3319
michael@0 3320 void
michael@0 3321 IMEInputHandler::OnUpdateIMEComposition(NSString* aIMECompositionString)
michael@0 3322 {
michael@0 3323 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 3324
michael@0 3325 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3326 ("%p IMEInputHandler::OnUpdateIMEComposition, mView=%p, mWidget=%p, "
michael@0 3327 "inputContext=%p, mIsIMEComposing=%s, aIMECompositionString=\"%s\"",
michael@0 3328 this, mView, mWidget, mView ? [mView inputContext] : nullptr,
michael@0 3329 TrueOrFalse(mIsIMEComposing), GetCharacters(aIMECompositionString)));
michael@0 3330
michael@0 3331 NS_ASSERTION(mIsIMEComposing, "We're not in composition");
michael@0 3332
michael@0 3333 if (mIMECompositionString)
michael@0 3334 [mIMECompositionString release];
michael@0 3335 mIMECompositionString = [aIMECompositionString retain];
michael@0 3336
michael@0 3337 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 3338 }
michael@0 3339
michael@0 3340 void
michael@0 3341 IMEInputHandler::OnEndIMEComposition()
michael@0 3342 {
michael@0 3343 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 3344
michael@0 3345 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3346 ("%p IMEInputHandler::OnEndIMEComposition, mView=%p, mWidget=%p, "
michael@0 3347 "inputContext=%p, mIsIMEComposing=%s",
michael@0 3348 this, mView, mWidget, mView ? [mView inputContext] : nullptr,
michael@0 3349 TrueOrFalse(mIsIMEComposing)));
michael@0 3350
michael@0 3351 NS_ASSERTION(mIsIMEComposing, "We're not in composition");
michael@0 3352
michael@0 3353 mIsIMEComposing = false;
michael@0 3354
michael@0 3355 if (mIMECompositionString) {
michael@0 3356 [mIMECompositionString release];
michael@0 3357 mIMECompositionString = nullptr;
michael@0 3358 }
michael@0 3359
michael@0 3360 mLastDispatchedCompositionString.Truncate();
michael@0 3361
michael@0 3362 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 3363 }
michael@0 3364
michael@0 3365 void
michael@0 3366 IMEInputHandler::SendCommittedText(NSString *aString)
michael@0 3367 {
michael@0 3368 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 3369
michael@0 3370 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3371 ("%p IMEInputHandler::SendCommittedText, mView=%p, mWidget=%p, "
michael@0 3372 "inputContext=%p, mIsIMEComposing=%s",
michael@0 3373 this, mView, mWidget, mView ? [mView inputContext] : nullptr,
michael@0 3374 TrueOrFalse(mIsIMEComposing), mWidget));
michael@0 3375
michael@0 3376 NS_ENSURE_TRUE(mWidget, );
michael@0 3377 // XXX We should send the string without mView.
michael@0 3378 if (!mView) {
michael@0 3379 return;
michael@0 3380 }
michael@0 3381
michael@0 3382 NSAttributedString* attrStr =
michael@0 3383 [[NSAttributedString alloc] initWithString:aString];
michael@0 3384 [mView insertText:attrStr];
michael@0 3385 [attrStr release];
michael@0 3386
michael@0 3387 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 3388 }
michael@0 3389
michael@0 3390 void
michael@0 3391 IMEInputHandler::KillIMEComposition()
michael@0 3392 {
michael@0 3393 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 3394
michael@0 3395 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3396 ("%p IMEInputHandler::KillIMEComposition, mView=%p, mWidget=%p, "
michael@0 3397 "inputContext=%p, mIsIMEComposing=%s, "
michael@0 3398 "Destroyed()=%s, IsFocused()=%s",
michael@0 3399 this, mView, mWidget, mView ? [mView inputContext] : nullptr,
michael@0 3400 TrueOrFalse(mIsIMEComposing), TrueOrFalse(Destroyed()),
michael@0 3401 TrueOrFalse(IsFocused())));
michael@0 3402
michael@0 3403 if (Destroyed()) {
michael@0 3404 return;
michael@0 3405 }
michael@0 3406
michael@0 3407 if (IsFocused()) {
michael@0 3408 NS_ENSURE_TRUE_VOID(mView);
michael@0 3409 NSTextInputContext* inputContext = [mView inputContext];
michael@0 3410 NS_ENSURE_TRUE_VOID(inputContext);
michael@0 3411 [inputContext discardMarkedText];
michael@0 3412 return;
michael@0 3413 }
michael@0 3414
michael@0 3415 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3416 ("%p IMEInputHandler::KillIMEComposition, Pending...", this));
michael@0 3417
michael@0 3418 // Commit the composition internally.
michael@0 3419 SendCommittedText(mIMECompositionString);
michael@0 3420 NS_ASSERTION(!mIsIMEComposing, "We're still in a composition");
michael@0 3421 // The pending method will be fired by the next focus event.
michael@0 3422 mPendingMethods |= kDiscardIMEComposition;
michael@0 3423
michael@0 3424 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 3425 }
michael@0 3426
michael@0 3427 void
michael@0 3428 IMEInputHandler::CommitIMEComposition()
michael@0 3429 {
michael@0 3430 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 3431
michael@0 3432 if (!IsIMEComposing())
michael@0 3433 return;
michael@0 3434
michael@0 3435 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3436 ("%p IMEInputHandler::CommitIMEComposition, mIMECompositionString=%s",
michael@0 3437 this, GetCharacters(mIMECompositionString)));
michael@0 3438
michael@0 3439 KillIMEComposition();
michael@0 3440
michael@0 3441 if (!IsIMEComposing())
michael@0 3442 return;
michael@0 3443
michael@0 3444 // If the composition is still there, KillIMEComposition only kills the
michael@0 3445 // composition in TSM. We also need to finish the our composition too.
michael@0 3446 SendCommittedText(mIMECompositionString);
michael@0 3447
michael@0 3448 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 3449 }
michael@0 3450
michael@0 3451 void
michael@0 3452 IMEInputHandler::CancelIMEComposition()
michael@0 3453 {
michael@0 3454 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 3455
michael@0 3456 if (!IsIMEComposing())
michael@0 3457 return;
michael@0 3458
michael@0 3459 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3460 ("%p IMEInputHandler::CancelIMEComposition, mIMECompositionString=%s",
michael@0 3461 this, GetCharacters(mIMECompositionString)));
michael@0 3462
michael@0 3463 // For canceling the current composing, we need to ignore the param of
michael@0 3464 // insertText. But this code is ugly...
michael@0 3465 mIgnoreIMECommit = true;
michael@0 3466 KillIMEComposition();
michael@0 3467 mIgnoreIMECommit = false;
michael@0 3468
michael@0 3469 if (!IsIMEComposing())
michael@0 3470 return;
michael@0 3471
michael@0 3472 // If the composition is still there, KillIMEComposition only kills the
michael@0 3473 // composition in TSM. We also need to kill the our composition too.
michael@0 3474 SendCommittedText(@"");
michael@0 3475
michael@0 3476 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 3477 }
michael@0 3478
michael@0 3479 bool
michael@0 3480 IMEInputHandler::IsFocused()
michael@0 3481 {
michael@0 3482 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 3483
michael@0 3484 NS_ENSURE_TRUE(!Destroyed(), false);
michael@0 3485 NSWindow* window = [mView window];
michael@0 3486 NS_ENSURE_TRUE(window, false);
michael@0 3487 return [window firstResponder] == mView &&
michael@0 3488 [window isKeyWindow] &&
michael@0 3489 [[NSApplication sharedApplication] isActive];
michael@0 3490
michael@0 3491 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
michael@0 3492 }
michael@0 3493
michael@0 3494 bool
michael@0 3495 IMEInputHandler::IsIMEOpened()
michael@0 3496 {
michael@0 3497 TISInputSourceWrapper tis;
michael@0 3498 tis.InitByCurrentInputSource();
michael@0 3499 return tis.IsOpenedIMEMode();
michael@0 3500 }
michael@0 3501
michael@0 3502 void
michael@0 3503 IMEInputHandler::SetASCIICapableOnly(bool aASCIICapableOnly)
michael@0 3504 {
michael@0 3505 if (aASCIICapableOnly == mIsASCIICapableOnly)
michael@0 3506 return;
michael@0 3507
michael@0 3508 CommitIMEComposition();
michael@0 3509 mIsASCIICapableOnly = aASCIICapableOnly;
michael@0 3510 SyncASCIICapableOnly();
michael@0 3511 }
michael@0 3512
michael@0 3513 void
michael@0 3514 IMEInputHandler::EnableIME(bool aEnableIME)
michael@0 3515 {
michael@0 3516 if (aEnableIME == mIsIMEEnabled)
michael@0 3517 return;
michael@0 3518
michael@0 3519 CommitIMEComposition();
michael@0 3520 mIsIMEEnabled = aEnableIME;
michael@0 3521 }
michael@0 3522
michael@0 3523 void
michael@0 3524 IMEInputHandler::SetIMEOpenState(bool aOpenIME)
michael@0 3525 {
michael@0 3526 if (!IsFocused() || IsIMEOpened() == aOpenIME)
michael@0 3527 return;
michael@0 3528
michael@0 3529 if (!aOpenIME) {
michael@0 3530 TISInputSourceWrapper tis;
michael@0 3531 tis.InitByCurrentASCIICapableInputSource();
michael@0 3532 tis.Select();
michael@0 3533 return;
michael@0 3534 }
michael@0 3535
michael@0 3536 // If we know the latest IME opened mode, we should select it.
michael@0 3537 if (sLatestIMEOpenedModeInputSourceID) {
michael@0 3538 TISInputSourceWrapper tis;
michael@0 3539 tis.InitByInputSourceID(sLatestIMEOpenedModeInputSourceID);
michael@0 3540 tis.Select();
michael@0 3541 return;
michael@0 3542 }
michael@0 3543
michael@0 3544 // XXX If the current input source is a mode of IME, we should turn on it,
michael@0 3545 // but we haven't found such way...
michael@0 3546
michael@0 3547 // Finally, we should refer the system locale but this is a little expensive,
michael@0 3548 // we shouldn't retry this (if it was succeeded, we already set
michael@0 3549 // sLatestIMEOpenedModeInputSourceID at that time).
michael@0 3550 static bool sIsPrefferredIMESearched = false;
michael@0 3551 if (sIsPrefferredIMESearched)
michael@0 3552 return;
michael@0 3553 sIsPrefferredIMESearched = true;
michael@0 3554 OpenSystemPreferredLanguageIME();
michael@0 3555 }
michael@0 3556
michael@0 3557 void
michael@0 3558 IMEInputHandler::OpenSystemPreferredLanguageIME()
michael@0 3559 {
michael@0 3560 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3561 ("%p IMEInputHandler::OpenSystemPreferredLanguageIME", this));
michael@0 3562
michael@0 3563 CFArrayRef langList = ::CFLocaleCopyPreferredLanguages();
michael@0 3564 if (!langList) {
michael@0 3565 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3566 ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, langList is NULL",
michael@0 3567 this));
michael@0 3568 return;
michael@0 3569 }
michael@0 3570 CFIndex count = ::CFArrayGetCount(langList);
michael@0 3571 for (CFIndex i = 0; i < count; i++) {
michael@0 3572 CFLocaleRef locale =
michael@0 3573 ::CFLocaleCreate(kCFAllocatorDefault,
michael@0 3574 static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, i)));
michael@0 3575 if (!locale) {
michael@0 3576 continue;
michael@0 3577 }
michael@0 3578
michael@0 3579 bool changed = false;
michael@0 3580 CFStringRef lang = static_cast<CFStringRef>(
michael@0 3581 ::CFLocaleGetValue(locale, kCFLocaleLanguageCode));
michael@0 3582 NS_ASSERTION(lang, "lang is null");
michael@0 3583 if (lang) {
michael@0 3584 TISInputSourceWrapper tis;
michael@0 3585 tis.InitByLanguage(lang);
michael@0 3586 if (tis.IsOpenedIMEMode()) {
michael@0 3587 #ifdef PR_LOGGING
michael@0 3588 if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
michael@0 3589 CFStringRef foundTIS;
michael@0 3590 tis.GetInputSourceID(foundTIS);
michael@0 3591 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 3592 ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, "
michael@0 3593 "foundTIS=%s, lang=%s",
michael@0 3594 this, GetCharacters(foundTIS), GetCharacters(lang)));
michael@0 3595 }
michael@0 3596 #endif // #ifdef PR_LOGGING
michael@0 3597 tis.Select();
michael@0 3598 changed = true;
michael@0 3599 }
michael@0 3600 }
michael@0 3601 ::CFRelease(locale);
michael@0 3602 if (changed) {
michael@0 3603 break;
michael@0 3604 }
michael@0 3605 }
michael@0 3606 ::CFRelease(langList);
michael@0 3607 }
michael@0 3608
michael@0 3609
michael@0 3610 #pragma mark -
michael@0 3611
michael@0 3612
michael@0 3613 /******************************************************************************
michael@0 3614 *
michael@0 3615 * PluginTextInputHandler implementation
michael@0 3616 *
michael@0 3617 ******************************************************************************/
michael@0 3618
michael@0 3619 PluginTextInputHandler::PluginTextInputHandler(nsChildView* aWidget,
michael@0 3620 NSView<mozView> *aNativeView) :
michael@0 3621 TextInputHandlerBase(aWidget, aNativeView),
michael@0 3622 mIgnoreNextKeyUpEvent(false),
michael@0 3623 #ifndef __LP64__
michael@0 3624 mPluginTSMDoc(0), mPluginTSMInComposition(false),
michael@0 3625 #endif // #ifndef __LP64__
michael@0 3626 mPluginComplexTextInputRequested(false)
michael@0 3627 {
michael@0 3628 }
michael@0 3629
michael@0 3630 PluginTextInputHandler::~PluginTextInputHandler()
michael@0 3631 {
michael@0 3632 #ifndef __LP64__
michael@0 3633 if (mPluginTSMDoc) {
michael@0 3634 ::DeleteTSMDocument(mPluginTSMDoc);
michael@0 3635 }
michael@0 3636 #endif // #ifndef __LP64__
michael@0 3637 }
michael@0 3638
michael@0 3639 /* static */ void
michael@0 3640 PluginTextInputHandler::ConvertCocoaKeyEventToNPCocoaEvent(
michael@0 3641 NSEvent* aCocoaEvent,
michael@0 3642 NPCocoaEvent& aPluginEvent)
michael@0 3643 {
michael@0 3644 nsCocoaUtils::InitNPCocoaEvent(&aPluginEvent);
michael@0 3645 NSEventType nativeType = [aCocoaEvent type];
michael@0 3646 switch (nativeType) {
michael@0 3647 case NSKeyDown:
michael@0 3648 aPluginEvent.type = NPCocoaEventKeyDown;
michael@0 3649 break;
michael@0 3650 case NSKeyUp:
michael@0 3651 aPluginEvent.type = NPCocoaEventKeyUp;
michael@0 3652 break;
michael@0 3653 case NSFlagsChanged:
michael@0 3654 aPluginEvent.type = NPCocoaEventFlagsChanged;
michael@0 3655 break;
michael@0 3656 default:
michael@0 3657 NS_WARNING("Asked to convert key event of unknown type to Cocoa plugin event!");
michael@0 3658 }
michael@0 3659 aPluginEvent.data.key.modifierFlags = [aCocoaEvent modifierFlags];
michael@0 3660 aPluginEvent.data.key.keyCode = [aCocoaEvent keyCode];
michael@0 3661 // don't try to access character data for flags changed events,
michael@0 3662 // it will raise an exception
michael@0 3663 if (nativeType != NSFlagsChanged) {
michael@0 3664 aPluginEvent.data.key.characters = (NPNSString*)[aCocoaEvent characters];
michael@0 3665 aPluginEvent.data.key.charactersIgnoringModifiers =
michael@0 3666 (NPNSString*)[aCocoaEvent charactersIgnoringModifiers];
michael@0 3667 aPluginEvent.data.key.isARepeat = [aCocoaEvent isARepeat];
michael@0 3668 }
michael@0 3669 }
michael@0 3670
michael@0 3671 #ifndef __LP64__
michael@0 3672
michael@0 3673 EventHandlerRef PluginTextInputHandler::sPluginKeyEventsHandler = NULL;
michael@0 3674
michael@0 3675 /* static */ void
michael@0 3676 PluginTextInputHandler::InstallPluginKeyEventsHandler()
michael@0 3677 {
michael@0 3678 if (sPluginKeyEventsHandler) {
michael@0 3679 return;
michael@0 3680 }
michael@0 3681 static const EventTypeSpec sTSMEvents[] =
michael@0 3682 { { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent } };
michael@0 3683 ::InstallEventHandler(::GetEventDispatcherTarget(),
michael@0 3684 ::NewEventHandlerUPP(PluginKeyEventsHandler),
michael@0 3685 GetEventTypeCount(sTSMEvents),
michael@0 3686 sTSMEvents,
michael@0 3687 NULL,
michael@0 3688 &sPluginKeyEventsHandler);
michael@0 3689 }
michael@0 3690
michael@0 3691 /* static */ void
michael@0 3692 PluginTextInputHandler::RemovePluginKeyEventsHandler()
michael@0 3693 {
michael@0 3694 if (!sPluginKeyEventsHandler) {
michael@0 3695 return;
michael@0 3696 }
michael@0 3697 ::RemoveEventHandler(sPluginKeyEventsHandler);
michael@0 3698 sPluginKeyEventsHandler = NULL;
michael@0 3699 }
michael@0 3700
michael@0 3701 /* static */ void
michael@0 3702 PluginTextInputHandler::SwizzleMethods()
michael@0 3703 {
michael@0 3704 Class IMKInputSessionClass = ::NSClassFromString(@"IMKInputSession");
michael@0 3705 nsToolkit::SwizzleMethods(IMKInputSessionClass, @selector(handleEvent:),
michael@0 3706 @selector(PluginTextInputHandler_IMKInputSession_handleEvent:));
michael@0 3707 nsToolkit::SwizzleMethods(IMKInputSessionClass, @selector(commitComposition),
michael@0 3708 @selector(PluginTextInputHandler_IMKInputSession_commitComposition));
michael@0 3709 nsToolkit::SwizzleMethods(IMKInputSessionClass, @selector(finishSession),
michael@0 3710 @selector(PluginTextInputHandler_IMKInputSession_finishSession));
michael@0 3711 }
michael@0 3712
michael@0 3713 /* static */ OSStatus
michael@0 3714 PluginTextInputHandler::PluginKeyEventsHandler(EventHandlerCallRef aHandlerRef,
michael@0 3715 EventRef aEvent,
michael@0 3716 void *aUserData)
michael@0 3717 {
michael@0 3718 nsAutoreleasePool localPool;
michael@0 3719
michael@0 3720 TSMDocumentID activeDoc = ::TSMGetActiveDocument();
michael@0 3721 NS_ENSURE_TRUE(activeDoc, eventNotHandledErr);
michael@0 3722
michael@0 3723 ChildView *target = nil;
michael@0 3724 OSStatus status = ::TSMGetDocumentProperty(activeDoc,
michael@0 3725 kFocusedChildViewTSMDocPropertyTag,
michael@0 3726 sizeof(ChildView *), nil, &target);
michael@0 3727 NS_ENSURE_TRUE(status == noErr, eventNotHandledErr);
michael@0 3728 NS_ENSURE_TRUE(target, eventNotHandledErr);
michael@0 3729
michael@0 3730 EventRef keyEvent = NULL;
michael@0 3731 status = ::GetEventParameter(aEvent, kEventParamTextInputSendKeyboardEvent,
michael@0 3732 typeEventRef, NULL, sizeof(EventRef), NULL,
michael@0 3733 &keyEvent);
michael@0 3734 NS_ENSURE_TRUE(status == noErr, eventNotHandledErr);
michael@0 3735 NS_ENSURE_TRUE(keyEvent, eventNotHandledErr);
michael@0 3736
michael@0 3737 nsIWidget* widget = [target widget];
michael@0 3738 NS_ENSURE_TRUE(widget, eventNotHandledErr);
michael@0 3739 TextInputHandler* handler =
michael@0 3740 static_cast<nsChildView*>(widget)->GetTextInputHandler();
michael@0 3741 NS_ENSURE_TRUE(handler, eventNotHandledErr);
michael@0 3742 handler->HandleCarbonPluginKeyEvent(keyEvent);
michael@0 3743
michael@0 3744 return noErr;
michael@0 3745 }
michael@0 3746
michael@0 3747 // Called from PluginKeyEventsHandler() (a handler for Carbon TSM events) to
michael@0 3748 // process a Carbon key event for the currently focused plugin. Both Unicode
michael@0 3749 // characters and "Mac encoding characters" (in the MBCS or "multibyte
michael@0 3750 // character system") are (or should be) available from aKeyEvent, but here we
michael@0 3751 // use the MCBS characters. This is how the WebKit does things, and seems to
michael@0 3752 // be what plugins expect.
michael@0 3753 void
michael@0 3754 PluginTextInputHandler::HandleCarbonPluginKeyEvent(EventRef aKeyEvent)
michael@0 3755 {
michael@0 3756 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 3757
michael@0 3758 if (Destroyed()) {
michael@0 3759 return;
michael@0 3760 }
michael@0 3761
michael@0 3762 NS_ASSERTION(mView, "mView must not be NULL");
michael@0 3763
michael@0 3764 nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
michael@0 3765
michael@0 3766 if ([mView pluginEventModel] == NPEventModelCocoa) {
michael@0 3767 UInt32 size;
michael@0 3768 OSStatus status =
michael@0 3769 ::GetEventParameter(aKeyEvent, kEventParamKeyUnicodes, typeUnicodeText,
michael@0 3770 NULL, 0, &size, NULL);
michael@0 3771 NS_ENSURE_TRUE(status == noErr, );
michael@0 3772
michael@0 3773 UniChar* chars = (UniChar*)malloc(size);
michael@0 3774 NS_ENSURE_TRUE(chars, );
michael@0 3775
michael@0 3776 status = ::GetEventParameter(aKeyEvent, kEventParamKeyUnicodes,
michael@0 3777 typeUnicodeText, NULL, size, NULL, chars);
michael@0 3778 if (status != noErr) {
michael@0 3779 free(chars);
michael@0 3780 return;
michael@0 3781 }
michael@0 3782
michael@0 3783 CFStringRef text =
michael@0 3784 ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, chars,
michael@0 3785 (size / sizeof(UniChar)),
michael@0 3786 kCFAllocatorNull);
michael@0 3787 if (!text) {
michael@0 3788 free(chars);
michael@0 3789 return;
michael@0 3790 }
michael@0 3791
michael@0 3792 NPCocoaEvent cocoaTextEvent;
michael@0 3793 nsCocoaUtils::InitNPCocoaEvent(&cocoaTextEvent);
michael@0 3794 cocoaTextEvent.type = NPCocoaEventTextInput;
michael@0 3795 cocoaTextEvent.data.text.text = (NPNSString*)text;
michael@0 3796
michael@0 3797 WidgetPluginEvent pluginEvent(true, NS_PLUGIN_INPUT_EVENT, mWidget);
michael@0 3798 nsCocoaUtils::InitPluginEvent(pluginEvent, cocoaTextEvent);
michael@0 3799 DispatchEvent(pluginEvent);
michael@0 3800
michael@0 3801 ::CFRelease(text);
michael@0 3802 free(chars);
michael@0 3803
michael@0 3804 return;
michael@0 3805 }
michael@0 3806
michael@0 3807 UInt32 numCharCodes;
michael@0 3808 OSStatus status = ::GetEventParameter(aKeyEvent, kEventParamKeyMacCharCodes,
michael@0 3809 typeChar, NULL, 0, &numCharCodes, NULL);
michael@0 3810 NS_ENSURE_TRUE(status == noErr, );
michael@0 3811
michael@0 3812 nsAutoTArray<unsigned char, 3> charCodes;
michael@0 3813 charCodes.SetLength(numCharCodes);
michael@0 3814 status = ::GetEventParameter(aKeyEvent, kEventParamKeyMacCharCodes,
michael@0 3815 typeChar, NULL, numCharCodes, NULL,
michael@0 3816 charCodes.Elements());
michael@0 3817 NS_ENSURE_TRUE(status == noErr, );
michael@0 3818
michael@0 3819 UInt32 modifiers;
michael@0 3820 status = ::GetEventParameter(aKeyEvent, kEventParamKeyModifiers,
michael@0 3821 typeUInt32, NULL, sizeof(modifiers), NULL,
michael@0 3822 &modifiers);
michael@0 3823 NS_ENSURE_TRUE(status == noErr, );
michael@0 3824
michael@0 3825 NSUInteger cocoaModifiers = 0;
michael@0 3826 if (modifiers & shiftKey) {
michael@0 3827 cocoaModifiers |= NSShiftKeyMask;
michael@0 3828 }
michael@0 3829 if (modifiers & controlKey) {
michael@0 3830 cocoaModifiers |= NSControlKeyMask;
michael@0 3831 }
michael@0 3832 if (modifiers & optionKey) {
michael@0 3833 cocoaModifiers |= NSAlternateKeyMask;
michael@0 3834 }
michael@0 3835 if (modifiers & cmdKey) { // Should never happen
michael@0 3836 cocoaModifiers |= NSCommandKeyMask;
michael@0 3837 }
michael@0 3838
michael@0 3839 UInt32 macKeyCode;
michael@0 3840 status = ::GetEventParameter(aKeyEvent, kEventParamKeyCode,
michael@0 3841 typeUInt32, NULL, sizeof(macKeyCode), NULL,
michael@0 3842 &macKeyCode);
michael@0 3843 NS_ENSURE_TRUE(status == noErr, );
michael@0 3844
michael@0 3845 TISInputSourceWrapper& currentInputSource =
michael@0 3846 TISInputSourceWrapper::CurrentInputSource();
michael@0 3847
michael@0 3848 EventRef cloneEvent = ::CopyEvent(aKeyEvent);
michael@0 3849 for (uint32_t i = 0; i < numCharCodes; ++i) {
michael@0 3850 status = ::SetEventParameter(cloneEvent, kEventParamKeyMacCharCodes,
michael@0 3851 typeChar, 1, charCodes.Elements() + i);
michael@0 3852 NS_ENSURE_TRUE(status == noErr, );
michael@0 3853
michael@0 3854 EventRecord eventRec;
michael@0 3855 if (::ConvertEventRefToEventRecord(cloneEvent, &eventRec)) {
michael@0 3856 WidgetKeyboardEvent keydownEvent(true, NS_KEY_DOWN, mWidget);
michael@0 3857 nsCocoaUtils::InitInputEvent(keydownEvent, cocoaModifiers);
michael@0 3858
michael@0 3859 uint32_t keyCode =
michael@0 3860 currentInputSource.ComputeGeckoKeyCode(macKeyCode, ::LMGetKbdType(),
michael@0 3861 keydownEvent.IsMeta());
michael@0 3862 uint32_t charCode(charCodes.ElementAt(i));
michael@0 3863
michael@0 3864 keydownEvent.time = PR_IntervalNow();
michael@0 3865 keydownEvent.pluginEvent = &eventRec;
michael@0 3866 if (IsSpecialGeckoKey(macKeyCode)) {
michael@0 3867 keydownEvent.keyCode = keyCode;
michael@0 3868 } else {
michael@0 3869 // XXX This is wrong. charCode must be 0 for keydown event.
michael@0 3870 keydownEvent.charCode = charCode;
michael@0 3871 keydownEvent.isChar = true;
michael@0 3872 }
michael@0 3873 DispatchEvent(keydownEvent);
michael@0 3874 if (Destroyed()) {
michael@0 3875 break;
michael@0 3876 }
michael@0 3877 }
michael@0 3878 }
michael@0 3879
michael@0 3880 ::ReleaseEvent(cloneEvent);
michael@0 3881
michael@0 3882 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 3883 }
michael@0 3884
michael@0 3885 void
michael@0 3886 PluginTextInputHandler::ActivatePluginTSMDocument()
michael@0 3887 {
michael@0 3888 if (!mPluginTSMDoc) {
michael@0 3889 // Create a TSM document that supports both non-Unicode and Unicode input.
michael@0 3890 // Though ProcessPluginKeyEvent() only sends Mac char codes to
michael@0 3891 // the plugin, this makes the input window behave better when it contains
michael@0 3892 // more than one kind of input (say Hiragana and Romaji). This is what
michael@0 3893 // the OS does when it creates a TSM document for use by an
michael@0 3894 // NSTSMInputContext class.
michael@0 3895 InterfaceTypeList supportedServices;
michael@0 3896 supportedServices[0] = kTextServiceDocumentInterfaceType;
michael@0 3897 supportedServices[1] = kUnicodeDocumentInterfaceType;
michael@0 3898 ::NewTSMDocument(2, supportedServices, &mPluginTSMDoc, 0);
michael@0 3899 // We'll need to use the "input window".
michael@0 3900 ::UseInputWindow(mPluginTSMDoc, YES);
michael@0 3901 ::ActivateTSMDocument(mPluginTSMDoc);
michael@0 3902 } else if (::TSMGetActiveDocument() != mPluginTSMDoc) {
michael@0 3903 ::ActivateTSMDocument(mPluginTSMDoc);
michael@0 3904 }
michael@0 3905 }
michael@0 3906
michael@0 3907 #endif // #ifndef __LP64__
michael@0 3908
michael@0 3909 void
michael@0 3910 PluginTextInputHandler::HandleKeyDownEventForPlugin(NSEvent* aNativeKeyEvent)
michael@0 3911 {
michael@0 3912 if (Destroyed()) {
michael@0 3913 return;
michael@0 3914 }
michael@0 3915
michael@0 3916 NS_ASSERTION(mView, "mView must not be NULL");
michael@0 3917
michael@0 3918 #ifdef __LP64__
michael@0 3919
michael@0 3920 if ([mView pluginEventModel] != NPEventModelCocoa) {
michael@0 3921 return;
michael@0 3922 }
michael@0 3923
michael@0 3924 ComplexTextInputPanel* ctiPanel =
michael@0 3925 [ComplexTextInputPanel sharedComplexTextInputPanel];
michael@0 3926 [ctiPanel adjustTo:mView];
michael@0 3927
michael@0 3928 // If a composition is in progress then simply let the input panel continue
michael@0 3929 // it.
michael@0 3930 if (IsInPluginComposition()) {
michael@0 3931 // Don't send key up events for key downs associated with compositions.
michael@0 3932 mIgnoreNextKeyUpEvent = true;
michael@0 3933
michael@0 3934 NSString* textString = nil;
michael@0 3935 [ctiPanel interpretKeyEvent:aNativeKeyEvent string:&textString];
michael@0 3936 if (textString) {
michael@0 3937 DispatchCocoaNPAPITextEvent(textString);
michael@0 3938 }
michael@0 3939
michael@0 3940 return;
michael@0 3941 }
michael@0 3942
michael@0 3943 // Reset complex text input request flag.
michael@0 3944 mPluginComplexTextInputRequested = false;
michael@0 3945
michael@0 3946 // Send key down event to the plugin.
michael@0 3947 WidgetPluginEvent pluginEvent(true, NS_PLUGIN_INPUT_EVENT, mWidget);
michael@0 3948 NPCocoaEvent cocoaEvent;
michael@0 3949 ConvertCocoaKeyEventToNPCocoaEvent(aNativeKeyEvent, cocoaEvent);
michael@0 3950 nsCocoaUtils::InitPluginEvent(pluginEvent, cocoaEvent);
michael@0 3951 DispatchEvent(pluginEvent);
michael@0 3952 if (Destroyed()) {
michael@0 3953 return;
michael@0 3954 }
michael@0 3955
michael@0 3956 // Start complex text composition if requested.
michael@0 3957 if (mPluginComplexTextInputRequested) {
michael@0 3958 // Don't send key up events for key downs associated with compositions.
michael@0 3959 mIgnoreNextKeyUpEvent = true;
michael@0 3960
michael@0 3961 NSString* textString = nil;
michael@0 3962 [ctiPanel interpretKeyEvent:aNativeKeyEvent string:&textString];
michael@0 3963 if (textString) {
michael@0 3964 DispatchCocoaNPAPITextEvent(textString);
michael@0 3965 }
michael@0 3966 }
michael@0 3967
michael@0 3968 #else // #ifdef __LP64__
michael@0 3969
michael@0 3970 bool wasInComposition = false;
michael@0 3971 if ([mView pluginEventModel] == NPEventModelCocoa) {
michael@0 3972 if (IsInPluginComposition()) {
michael@0 3973 wasInComposition = true;
michael@0 3974
michael@0 3975 // Don't send key up events for key downs associated with compositions.
michael@0 3976 mIgnoreNextKeyUpEvent = true;
michael@0 3977 } else {
michael@0 3978 // Reset complex text input request flag.
michael@0 3979 mPluginComplexTextInputRequested = false;
michael@0 3980
michael@0 3981 // Send key down event to the plugin.
michael@0 3982 WidgetPluginEvent pluginEvent(true, NS_PLUGIN_INPUT_EVENT, mWidget);
michael@0 3983 NPCocoaEvent cocoaEvent;
michael@0 3984 ConvertCocoaKeyEventToNPCocoaEvent(aNativeKeyEvent, cocoaEvent);
michael@0 3985 nsCocoaUtils::InitPluginEvent(pluginEvent, cocoaEvent);
michael@0 3986 DispatchEvent(pluginEvent);
michael@0 3987 if (Destroyed()) {
michael@0 3988 return;
michael@0 3989 }
michael@0 3990
michael@0 3991 // Only continue if plugin wants complex text input.
michael@0 3992 if (!mPluginComplexTextInputRequested) {
michael@0 3993 return;
michael@0 3994 }
michael@0 3995
michael@0 3996 // Don't send key up events for key downs associated with compositions.
michael@0 3997 mIgnoreNextKeyUpEvent = true;
michael@0 3998 }
michael@0 3999
michael@0 4000 // Don't send complex text input to a plugin in Cocoa event mode if
michael@0 4001 // either the Control key or the Command key is pressed -- even if the
michael@0 4002 // plugin has requested it, or we are already in IME composition. This
michael@0 4003 // conforms to our behavior in 64-bit mode and fixes bug 619217.
michael@0 4004 NSUInteger modifierFlags = [aNativeKeyEvent modifierFlags];
michael@0 4005 if ((modifierFlags & NSControlKeyMask) ||
michael@0 4006 (modifierFlags & NSCommandKeyMask)) {
michael@0 4007 return;
michael@0 4008 }
michael@0 4009 }
michael@0 4010
michael@0 4011 // This will take care of all Carbon plugin events and also send Cocoa plugin
michael@0 4012 // text events when NSInputContext is not available (ifndef NP_NO_CARBON).
michael@0 4013 ActivatePluginTSMDocument();
michael@0 4014
michael@0 4015 // We use the active TSM document to pass a pointer to ourselves (the
michael@0 4016 // currently focused ChildView) to PluginKeyEventsHandler(). Because this
michael@0 4017 // pointer is weak, we should retain and release ourselves around the call
michael@0 4018 // to TSMProcessRawKeyEvent().
michael@0 4019 nsAutoRetainCocoaObject kungFuDeathGrip(mView);
michael@0 4020 ::TSMSetDocumentProperty(mPluginTSMDoc,
michael@0 4021 kFocusedChildViewTSMDocPropertyTag,
michael@0 4022 sizeof(ChildView *), &mView);
michael@0 4023 ::TSMProcessRawKeyEvent([aNativeKeyEvent _eventRef]);
michael@0 4024 ::TSMRemoveDocumentProperty(mPluginTSMDoc,
michael@0 4025 kFocusedChildViewTSMDocPropertyTag);
michael@0 4026
michael@0 4027 #endif // #ifdef __LP64__ else
michael@0 4028 }
michael@0 4029
michael@0 4030 void
michael@0 4031 PluginTextInputHandler::HandleKeyUpEventForPlugin(NSEvent* aNativeKeyEvent)
michael@0 4032 {
michael@0 4033 if (mIgnoreNextKeyUpEvent) {
michael@0 4034 mIgnoreNextKeyUpEvent = false;
michael@0 4035 return;
michael@0 4036 }
michael@0 4037
michael@0 4038 if (Destroyed()) {
michael@0 4039 return;
michael@0 4040 }
michael@0 4041
michael@0 4042 NS_ASSERTION(mView, "mView must not be NULL");
michael@0 4043
michael@0 4044 NPEventModel eventModel = [mView pluginEventModel];
michael@0 4045 if (eventModel == NPEventModelCocoa) {
michael@0 4046 // Don't send key up events to Cocoa plugins during composition.
michael@0 4047 if (IsInPluginComposition()) {
michael@0 4048 return;
michael@0 4049 }
michael@0 4050
michael@0 4051 WidgetKeyboardEvent keyupEvent(true, NS_KEY_UP, mWidget);
michael@0 4052 InitKeyEvent(aNativeKeyEvent, keyupEvent);
michael@0 4053 NPCocoaEvent pluginEvent;
michael@0 4054 ConvertCocoaKeyEventToNPCocoaEvent(aNativeKeyEvent, pluginEvent);
michael@0 4055 keyupEvent.pluginEvent = &pluginEvent;
michael@0 4056 DispatchEvent(keyupEvent);
michael@0 4057 return;
michael@0 4058 }
michael@0 4059 }
michael@0 4060
michael@0 4061 bool
michael@0 4062 PluginTextInputHandler::IsInPluginComposition()
michael@0 4063 {
michael@0 4064 return
michael@0 4065 #ifdef __LP64__
michael@0 4066 [[ComplexTextInputPanel sharedComplexTextInputPanel] inComposition] != NO;
michael@0 4067 #else // #ifdef __LP64__
michael@0 4068 mPluginTSMInComposition;
michael@0 4069 #endif // #ifdef __LP64__ else
michael@0 4070 }
michael@0 4071
michael@0 4072 bool
michael@0 4073 PluginTextInputHandler::DispatchCocoaNPAPITextEvent(NSString* aString)
michael@0 4074 {
michael@0 4075 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
michael@0 4076
michael@0 4077 NPCocoaEvent cocoaTextEvent;
michael@0 4078 nsCocoaUtils::InitNPCocoaEvent(&cocoaTextEvent);
michael@0 4079 cocoaTextEvent.type = NPCocoaEventTextInput;
michael@0 4080 cocoaTextEvent.data.text.text = (NPNSString*)aString;
michael@0 4081
michael@0 4082 WidgetPluginEvent pluginEvent(true, NS_PLUGIN_INPUT_EVENT, mWidget);
michael@0 4083 nsCocoaUtils::InitPluginEvent(pluginEvent, cocoaTextEvent);
michael@0 4084 return DispatchEvent(pluginEvent);
michael@0 4085
michael@0 4086 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
michael@0 4087 }
michael@0 4088
michael@0 4089
michael@0 4090 #pragma mark -
michael@0 4091
michael@0 4092
michael@0 4093 #ifndef __LP64__
michael@0 4094
michael@0 4095 /******************************************************************************
michael@0 4096 *
michael@0 4097 * PluginTextInputHandler_IMKInputSession_* implementation
michael@0 4098 *
michael@0 4099 ******************************************************************************/
michael@0 4100
michael@0 4101 // IMKInputSession is an undocumented class in the HIToolbox framework. It's
michael@0 4102 // present on both Leopard and SnowLeopard, and is used at a low level to
michael@0 4103 // process IME input regardless of which high-level API is used (Text Services
michael@0 4104 // Manager or Cocoa). It works the same way in both 32-bit and 64-bit code.
michael@0 4105 @interface NSObject (IMKInputSessionMethodSwizzling)
michael@0 4106 - (BOOL)PluginTextInputHandler_IMKInputSession_handleEvent:(EventRef)theEvent;
michael@0 4107 - (void)PluginTextInputHandler_IMKInputSession_commitComposition;
michael@0 4108 - (void)PluginTextInputHandler_IMKInputSession_finishSession;
michael@0 4109 @end
michael@0 4110
michael@0 4111 @implementation NSObject (IMKInputSessionMethodSwizzling)
michael@0 4112
michael@0 4113 - (BOOL)PluginTextInputHandler_IMKInputSession_handleEvent:(EventRef)theEvent
michael@0 4114 {
michael@0 4115 [self retain];
michael@0 4116 BOOL retval =
michael@0 4117 [self PluginTextInputHandler_IMKInputSession_handleEvent:theEvent];
michael@0 4118 NSUInteger retainCount = [self retainCount];
michael@0 4119 [self release];
michael@0 4120 // Return without doing anything if we've been deleted.
michael@0 4121 if (retainCount == 1) {
michael@0 4122 return retval;
michael@0 4123 }
michael@0 4124
michael@0 4125 NSWindow *mainWindow = [NSApp mainWindow];
michael@0 4126 NSResponder *firstResponder = [mainWindow firstResponder];
michael@0 4127 if (![firstResponder isKindOfClass:[ChildView class]]) {
michael@0 4128 return retval;
michael@0 4129 }
michael@0 4130
michael@0 4131 // 'charactersEntered' is the length (in bytes) of currently "marked text"
michael@0 4132 // -- text that's been entered in IME but not yet committed. If it's
michael@0 4133 // non-zero we're composing text in an IME session; if it's zero we're
michael@0 4134 // not in an IME session.
michael@0 4135 NSInteger entered = 0;
michael@0 4136 object_getInstanceVariable(self, "charactersEntered",
michael@0 4137 (void **) &entered);
michael@0 4138 nsIWidget* widget = [(ChildView*)firstResponder widget];
michael@0 4139 NS_ENSURE_TRUE(widget, retval);
michael@0 4140 TextInputHandler* handler =
michael@0 4141 static_cast<nsChildView*>(widget)->GetTextInputHandler();
michael@0 4142 NS_ENSURE_TRUE(handler, retval);
michael@0 4143 handler->SetPluginTSMInComposition(entered != 0);
michael@0 4144
michael@0 4145 return retval;
michael@0 4146 }
michael@0 4147
michael@0 4148 // This method is called whenever IME input is committed as a result of an
michael@0 4149 // "abnormal" termination -- for example when changing the keyboard focus from
michael@0 4150 // one input field to another.
michael@0 4151 - (void)PluginTextInputHandler_IMKInputSession_commitComposition
michael@0 4152 {
michael@0 4153 NSWindow *mainWindow = [NSApp mainWindow];
michael@0 4154 NSResponder *firstResponder = [mainWindow firstResponder];
michael@0 4155 if ([firstResponder isKindOfClass:[ChildView class]]) {
michael@0 4156 nsIWidget* widget = [(ChildView*)firstResponder widget];
michael@0 4157 if (widget) {
michael@0 4158 TextInputHandler* handler =
michael@0 4159 static_cast<nsChildView*>(widget)->GetTextInputHandler();
michael@0 4160 if (handler) {
michael@0 4161 handler->SetPluginTSMInComposition(false);
michael@0 4162 }
michael@0 4163 }
michael@0 4164 }
michael@0 4165 [self PluginTextInputHandler_IMKInputSession_commitComposition];
michael@0 4166 }
michael@0 4167
michael@0 4168 // This method is called just before we're deallocated.
michael@0 4169 - (void)PluginTextInputHandler_IMKInputSession_finishSession
michael@0 4170 {
michael@0 4171 NSWindow *mainWindow = [NSApp mainWindow];
michael@0 4172 NSResponder *firstResponder = [mainWindow firstResponder];
michael@0 4173 if ([firstResponder isKindOfClass:[ChildView class]]) {
michael@0 4174 nsIWidget* widget = [(ChildView*)firstResponder widget];
michael@0 4175 if (widget) {
michael@0 4176 TextInputHandler* handler =
michael@0 4177 static_cast<nsChildView*>(widget)->GetTextInputHandler();
michael@0 4178 if (handler) {
michael@0 4179 handler->SetPluginTSMInComposition(false);
michael@0 4180 }
michael@0 4181 }
michael@0 4182 }
michael@0 4183 [self PluginTextInputHandler_IMKInputSession_finishSession];
michael@0 4184 }
michael@0 4185
michael@0 4186 @end
michael@0 4187
michael@0 4188 #endif // #ifndef __LP64__
michael@0 4189
michael@0 4190
michael@0 4191 #pragma mark -
michael@0 4192
michael@0 4193
michael@0 4194 /******************************************************************************
michael@0 4195 *
michael@0 4196 * TextInputHandlerBase implementation
michael@0 4197 *
michael@0 4198 ******************************************************************************/
michael@0 4199
michael@0 4200 int32_t TextInputHandlerBase::sSecureEventInputCount = 0;
michael@0 4201
michael@0 4202 TextInputHandlerBase::TextInputHandlerBase(nsChildView* aWidget,
michael@0 4203 NSView<mozView> *aNativeView) :
michael@0 4204 mWidget(aWidget)
michael@0 4205 {
michael@0 4206 gHandlerInstanceCount++;
michael@0 4207 mView = [aNativeView retain];
michael@0 4208 }
michael@0 4209
michael@0 4210 TextInputHandlerBase::~TextInputHandlerBase()
michael@0 4211 {
michael@0 4212 [mView release];
michael@0 4213 if (--gHandlerInstanceCount == 0) {
michael@0 4214 FinalizeCurrentInputSource();
michael@0 4215 }
michael@0 4216 }
michael@0 4217
michael@0 4218 bool
michael@0 4219 TextInputHandlerBase::OnDestroyWidget(nsChildView* aDestroyingWidget)
michael@0 4220 {
michael@0 4221 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 4222 ("%p TextInputHandlerBase::OnDestroyWidget, "
michael@0 4223 "aDestroyingWidget=%p, mWidget=%p",
michael@0 4224 this, aDestroyingWidget, mWidget));
michael@0 4225
michael@0 4226 if (aDestroyingWidget != mWidget) {
michael@0 4227 return false;
michael@0 4228 }
michael@0 4229
michael@0 4230 mWidget = nullptr;
michael@0 4231 return true;
michael@0 4232 }
michael@0 4233
michael@0 4234 bool
michael@0 4235 TextInputHandlerBase::DispatchEvent(WidgetGUIEvent& aEvent)
michael@0 4236 {
michael@0 4237 if (aEvent.message == NS_KEY_PRESS) {
michael@0 4238 WidgetInputEvent& inputEvent = *aEvent.AsInputEvent();
michael@0 4239 if (!inputEvent.IsMeta()) {
michael@0 4240 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 4241 ("%p TextInputHandlerBase::DispatchEvent, hiding mouse cursor", this));
michael@0 4242 [NSCursor setHiddenUntilMouseMoves:YES];
michael@0 4243 }
michael@0 4244 }
michael@0 4245 return mWidget->DispatchWindowEvent(aEvent);
michael@0 4246 }
michael@0 4247
michael@0 4248 void
michael@0 4249 TextInputHandlerBase::InitKeyEvent(NSEvent *aNativeKeyEvent,
michael@0 4250 WidgetKeyboardEvent& aKeyEvent,
michael@0 4251 const nsAString* aInsertString)
michael@0 4252 {
michael@0 4253 NS_ASSERTION(aNativeKeyEvent, "aNativeKeyEvent must not be NULL");
michael@0 4254
michael@0 4255 if (mKeyboardOverride.mOverrideEnabled) {
michael@0 4256 TISInputSourceWrapper tis;
michael@0 4257 tis.InitByLayoutID(mKeyboardOverride.mKeyboardLayout, true);
michael@0 4258 tis.InitKeyEvent(aNativeKeyEvent, aKeyEvent, aInsertString);
michael@0 4259 return;
michael@0 4260 }
michael@0 4261 TISInputSourceWrapper::CurrentInputSource().
michael@0 4262 InitKeyEvent(aNativeKeyEvent, aKeyEvent, aInsertString);
michael@0 4263 }
michael@0 4264
michael@0 4265 nsresult
michael@0 4266 TextInputHandlerBase::SynthesizeNativeKeyEvent(
michael@0 4267 int32_t aNativeKeyboardLayout,
michael@0 4268 int32_t aNativeKeyCode,
michael@0 4269 uint32_t aModifierFlags,
michael@0 4270 const nsAString& aCharacters,
michael@0 4271 const nsAString& aUnmodifiedCharacters)
michael@0 4272 {
michael@0 4273 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 4274
michael@0 4275 static const uint32_t sModifierFlagMap[][2] = {
michael@0 4276 { nsIWidget::CAPS_LOCK, NSAlphaShiftKeyMask },
michael@0 4277 { nsIWidget::SHIFT_L, NSShiftKeyMask | 0x0002 },
michael@0 4278 { nsIWidget::SHIFT_R, NSShiftKeyMask | 0x0004 },
michael@0 4279 { nsIWidget::CTRL_L, NSControlKeyMask | 0x0001 },
michael@0 4280 { nsIWidget::CTRL_R, NSControlKeyMask | 0x2000 },
michael@0 4281 { nsIWidget::ALT_L, NSAlternateKeyMask | 0x0020 },
michael@0 4282 { nsIWidget::ALT_R, NSAlternateKeyMask | 0x0040 },
michael@0 4283 { nsIWidget::COMMAND_L, NSCommandKeyMask | 0x0008 },
michael@0 4284 { nsIWidget::COMMAND_R, NSCommandKeyMask | 0x0010 },
michael@0 4285 { nsIWidget::NUMERIC_KEY_PAD, NSNumericPadKeyMask },
michael@0 4286 { nsIWidget::HELP, NSHelpKeyMask },
michael@0 4287 { nsIWidget::FUNCTION, NSFunctionKeyMask }
michael@0 4288 };
michael@0 4289
michael@0 4290 uint32_t modifierFlags = 0;
michael@0 4291 for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) {
michael@0 4292 if (aModifierFlags & sModifierFlagMap[i][0]) {
michael@0 4293 modifierFlags |= sModifierFlagMap[i][1];
michael@0 4294 }
michael@0 4295 }
michael@0 4296
michael@0 4297 NSInteger windowNumber = [[mView window] windowNumber];
michael@0 4298 bool sendFlagsChangedEvent = IsModifierKey(aNativeKeyCode);
michael@0 4299 NSEventType eventType = sendFlagsChangedEvent ? NSFlagsChanged : NSKeyDown;
michael@0 4300 NSEvent* downEvent =
michael@0 4301 [NSEvent keyEventWithType:eventType
michael@0 4302 location:NSMakePoint(0,0)
michael@0 4303 modifierFlags:modifierFlags
michael@0 4304 timestamp:0
michael@0 4305 windowNumber:windowNumber
michael@0 4306 context:[NSGraphicsContext currentContext]
michael@0 4307 characters:nsCocoaUtils::ToNSString(aCharacters)
michael@0 4308 charactersIgnoringModifiers:nsCocoaUtils::ToNSString(aUnmodifiedCharacters)
michael@0 4309 isARepeat:NO
michael@0 4310 keyCode:aNativeKeyCode];
michael@0 4311
michael@0 4312 NSEvent* upEvent = sendFlagsChangedEvent ?
michael@0 4313 nil : nsCocoaUtils::MakeNewCocoaEventWithType(NSKeyUp, downEvent);
michael@0 4314
michael@0 4315 if (downEvent && (sendFlagsChangedEvent || upEvent)) {
michael@0 4316 KeyboardLayoutOverride currentLayout = mKeyboardOverride;
michael@0 4317 mKeyboardOverride.mKeyboardLayout = aNativeKeyboardLayout;
michael@0 4318 mKeyboardOverride.mOverrideEnabled = true;
michael@0 4319 [NSApp sendEvent:downEvent];
michael@0 4320 if (upEvent) {
michael@0 4321 [NSApp sendEvent:upEvent];
michael@0 4322 }
michael@0 4323 // processKeyDownEvent and keyUp block exceptions so we're sure to
michael@0 4324 // reach here to restore mKeyboardOverride
michael@0 4325 mKeyboardOverride = currentLayout;
michael@0 4326 }
michael@0 4327
michael@0 4328 return NS_OK;
michael@0 4329
michael@0 4330 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 4331 }
michael@0 4332
michael@0 4333 NSInteger
michael@0 4334 TextInputHandlerBase::GetWindowLevel()
michael@0 4335 {
michael@0 4336 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
michael@0 4337
michael@0 4338 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 4339 ("%p TextInputHandlerBase::GetWindowLevel, Destryoed()=%s",
michael@0 4340 this, TrueOrFalse(Destroyed())));
michael@0 4341
michael@0 4342 if (Destroyed()) {
michael@0 4343 return NSNormalWindowLevel;
michael@0 4344 }
michael@0 4345
michael@0 4346 // When an <input> element on a XUL <panel> is focused, the actual focused view
michael@0 4347 // is the panel's parent view (mView). But the editor is displayed on the
michael@0 4348 // popped-up widget's view (editorView). We want the latter's window level.
michael@0 4349 NSView<mozView>* editorView = mWidget->GetEditorView();
michael@0 4350 NS_ENSURE_TRUE(editorView, NSNormalWindowLevel);
michael@0 4351 NSInteger windowLevel = [[editorView window] level];
michael@0 4352
michael@0 4353 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 4354 ("%p TextInputHandlerBase::GetWindowLevel, windowLevel=%s (%X)",
michael@0 4355 this, GetWindowLevelName(windowLevel), windowLevel));
michael@0 4356
michael@0 4357 return windowLevel;
michael@0 4358
michael@0 4359 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNormalWindowLevel);
michael@0 4360 }
michael@0 4361
michael@0 4362 NS_IMETHODIMP
michael@0 4363 TextInputHandlerBase::AttachNativeKeyEvent(WidgetKeyboardEvent& aKeyEvent)
michael@0 4364 {
michael@0 4365 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 4366
michael@0 4367 // Don't try to replace a native event if one already exists.
michael@0 4368 // OS X doesn't have an OS modifier, can't make a native event.
michael@0 4369 if (aKeyEvent.mNativeKeyEvent || aKeyEvent.modifiers & MODIFIER_OS) {
michael@0 4370 return NS_OK;
michael@0 4371 }
michael@0 4372
michael@0 4373 PR_LOG(gLog, PR_LOG_ALWAYS,
michael@0 4374 ("%p TextInputHandlerBase::AttachNativeKeyEvent, key=0x%X, char=0x%X, "
michael@0 4375 "mod=0x%X", this, aKeyEvent.keyCode, aKeyEvent.charCode,
michael@0 4376 aKeyEvent.modifiers));
michael@0 4377
michael@0 4378 NSEventType eventType;
michael@0 4379 if (aKeyEvent.message == NS_KEY_UP) {
michael@0 4380 eventType = NSKeyUp;
michael@0 4381 } else {
michael@0 4382 eventType = NSKeyDown;
michael@0 4383 }
michael@0 4384
michael@0 4385 static const uint32_t sModifierFlagMap[][2] = {
michael@0 4386 { MODIFIER_SHIFT, NSShiftKeyMask },
michael@0 4387 { MODIFIER_CONTROL, NSControlKeyMask },
michael@0 4388 { MODIFIER_ALT, NSAlternateKeyMask },
michael@0 4389 { MODIFIER_ALTGRAPH, NSAlternateKeyMask },
michael@0 4390 { MODIFIER_META, NSCommandKeyMask },
michael@0 4391 { MODIFIER_CAPSLOCK, NSAlphaShiftKeyMask },
michael@0 4392 { MODIFIER_NUMLOCK, NSNumericPadKeyMask }
michael@0 4393 };
michael@0 4394
michael@0 4395 NSUInteger modifierFlags = 0;
michael@0 4396 for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) {
michael@0 4397 if (aKeyEvent.modifiers & sModifierFlagMap[i][0]) {
michael@0 4398 modifierFlags |= sModifierFlagMap[i][1];
michael@0 4399 }
michael@0 4400 }
michael@0 4401
michael@0 4402 NSInteger windowNumber = [[mView window] windowNumber];
michael@0 4403
michael@0 4404 NSString* characters;
michael@0 4405 if (aKeyEvent.charCode) {
michael@0 4406 characters = [NSString stringWithCharacters:
michael@0 4407 reinterpret_cast<const unichar*>(&(aKeyEvent.charCode)) length:1];
michael@0 4408 } else {
michael@0 4409 uint32_t cocoaCharCode =
michael@0 4410 nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(aKeyEvent.keyCode);
michael@0 4411 characters = [NSString stringWithCharacters:
michael@0 4412 reinterpret_cast<const unichar*>(&cocoaCharCode) length:1];
michael@0 4413 }
michael@0 4414
michael@0 4415 aKeyEvent.mNativeKeyEvent =
michael@0 4416 [NSEvent keyEventWithType:eventType
michael@0 4417 location:NSMakePoint(0,0)
michael@0 4418 modifierFlags:modifierFlags
michael@0 4419 timestamp:0
michael@0 4420 windowNumber:windowNumber
michael@0 4421 context:[NSGraphicsContext currentContext]
michael@0 4422 characters:characters
michael@0 4423 charactersIgnoringModifiers:characters
michael@0 4424 isARepeat:NO
michael@0 4425 keyCode:0]; // Native key code not currently needed
michael@0 4426
michael@0 4427 return NS_OK;
michael@0 4428
michael@0 4429 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 4430 }
michael@0 4431
michael@0 4432 bool
michael@0 4433 TextInputHandlerBase::SetSelection(NSRange& aRange)
michael@0 4434 {
michael@0 4435 MOZ_ASSERT(!Destroyed());
michael@0 4436
michael@0 4437 nsRefPtr<TextInputHandlerBase> kungFuDeathGrip(this);
michael@0 4438 WidgetSelectionEvent selectionEvent(true, NS_SELECTION_SET, mWidget);
michael@0 4439 selectionEvent.mOffset = aRange.location;
michael@0 4440 selectionEvent.mLength = aRange.length;
michael@0 4441 selectionEvent.mReversed = false;
michael@0 4442 selectionEvent.mExpandToClusterBoundary = false;
michael@0 4443 DispatchEvent(selectionEvent);
michael@0 4444 NS_ENSURE_TRUE(selectionEvent.mSucceeded, false);
michael@0 4445 return !Destroyed();
michael@0 4446 }
michael@0 4447
michael@0 4448 /* static */ bool
michael@0 4449 TextInputHandlerBase::IsPrintableChar(char16_t aChar)
michael@0 4450 {
michael@0 4451 return (aChar >= 0x20 && aChar <= 0x7E) || aChar >= 0xA0;
michael@0 4452 }
michael@0 4453
michael@0 4454
michael@0 4455 /* static */ bool
michael@0 4456 TextInputHandlerBase::IsSpecialGeckoKey(UInt32 aNativeKeyCode)
michael@0 4457 {
michael@0 4458 // this table is used to determine which keys are special and should not
michael@0 4459 // generate a charCode
michael@0 4460 switch (aNativeKeyCode) {
michael@0 4461 // modifiers - we don't get separate events for these yet
michael@0 4462 case kVK_Escape:
michael@0 4463 case kVK_Shift:
michael@0 4464 case kVK_RightShift:
michael@0 4465 case kVK_Command:
michael@0 4466 case kVK_RightCommand:
michael@0 4467 case kVK_CapsLock:
michael@0 4468 case kVK_Control:
michael@0 4469 case kVK_RightControl:
michael@0 4470 case kVK_Option:
michael@0 4471 case kVK_RightOption:
michael@0 4472 case kVK_ANSI_KeypadClear:
michael@0 4473 case kVK_Function:
michael@0 4474
michael@0 4475 // function keys
michael@0 4476 case kVK_F1:
michael@0 4477 case kVK_F2:
michael@0 4478 case kVK_F3:
michael@0 4479 case kVK_F4:
michael@0 4480 case kVK_F5:
michael@0 4481 case kVK_F6:
michael@0 4482 case kVK_F7:
michael@0 4483 case kVK_F8:
michael@0 4484 case kVK_F9:
michael@0 4485 case kVK_F10:
michael@0 4486 case kVK_F11:
michael@0 4487 case kVK_F12:
michael@0 4488 case kVK_PC_Pause:
michael@0 4489 case kVK_PC_ScrollLock:
michael@0 4490 case kVK_PC_PrintScreen:
michael@0 4491 case kVK_F16:
michael@0 4492 case kVK_F17:
michael@0 4493 case kVK_F18:
michael@0 4494 case kVK_F19:
michael@0 4495
michael@0 4496 case kVK_PC_Insert:
michael@0 4497 case kVK_PC_Delete:
michael@0 4498 case kVK_Tab:
michael@0 4499 case kVK_PC_Backspace:
michael@0 4500 case kVK_PC_ContextMenu:
michael@0 4501
michael@0 4502 case kVK_JIS_Eisu:
michael@0 4503 case kVK_JIS_Kana:
michael@0 4504
michael@0 4505 case kVK_Home:
michael@0 4506 case kVK_End:
michael@0 4507 case kVK_PageUp:
michael@0 4508 case kVK_PageDown:
michael@0 4509 case kVK_LeftArrow:
michael@0 4510 case kVK_RightArrow:
michael@0 4511 case kVK_UpArrow:
michael@0 4512 case kVK_DownArrow:
michael@0 4513 case kVK_Return:
michael@0 4514 case kVK_ANSI_KeypadEnter:
michael@0 4515 case kVK_Powerbook_KeypadEnter:
michael@0 4516 return true;
michael@0 4517 }
michael@0 4518 return false;
michael@0 4519 }
michael@0 4520
michael@0 4521 /* static */ bool
michael@0 4522 TextInputHandlerBase::IsNormalCharInputtingEvent(
michael@0 4523 const WidgetKeyboardEvent& aKeyEvent)
michael@0 4524 {
michael@0 4525 // this is not character inputting event, simply.
michael@0 4526 if (!aKeyEvent.isChar || !aKeyEvent.charCode || aKeyEvent.IsMeta()) {
michael@0 4527 return false;
michael@0 4528 }
michael@0 4529 // if this is unicode char inputting event, we don't need to check
michael@0 4530 // ctrl/alt/command keys
michael@0 4531 if (aKeyEvent.charCode > 0x7F) {
michael@0 4532 return true;
michael@0 4533 }
michael@0 4534 // ASCII chars should be inputted without ctrl/alt/command keys
michael@0 4535 return !aKeyEvent.IsControl() && !aKeyEvent.IsAlt();
michael@0 4536 }
michael@0 4537
michael@0 4538 /* static */ bool
michael@0 4539 TextInputHandlerBase::IsModifierKey(UInt32 aNativeKeyCode)
michael@0 4540 {
michael@0 4541 switch (aNativeKeyCode) {
michael@0 4542 case kVK_CapsLock:
michael@0 4543 case kVK_RightCommand:
michael@0 4544 case kVK_Command:
michael@0 4545 case kVK_Shift:
michael@0 4546 case kVK_Option:
michael@0 4547 case kVK_Control:
michael@0 4548 case kVK_RightShift:
michael@0 4549 case kVK_RightOption:
michael@0 4550 case kVK_RightControl:
michael@0 4551 case kVK_Function:
michael@0 4552 return true;
michael@0 4553 }
michael@0 4554 return false;
michael@0 4555 }
michael@0 4556
michael@0 4557 /* static */ void
michael@0 4558 TextInputHandlerBase::EnableSecureEventInput()
michael@0 4559 {
michael@0 4560 sSecureEventInputCount++;
michael@0 4561 ::EnableSecureEventInput();
michael@0 4562 }
michael@0 4563
michael@0 4564 /* static */ void
michael@0 4565 TextInputHandlerBase::DisableSecureEventInput()
michael@0 4566 {
michael@0 4567 if (!sSecureEventInputCount) {
michael@0 4568 return;
michael@0 4569 }
michael@0 4570 sSecureEventInputCount--;
michael@0 4571 ::DisableSecureEventInput();
michael@0 4572 }
michael@0 4573
michael@0 4574 /* static */ bool
michael@0 4575 TextInputHandlerBase::IsSecureEventInputEnabled()
michael@0 4576 {
michael@0 4577 NS_ASSERTION(!!sSecureEventInputCount == !!::IsSecureEventInputEnabled(),
michael@0 4578 "Some other process has enabled secure event input");
michael@0 4579 return !!sSecureEventInputCount;
michael@0 4580 }
michael@0 4581
michael@0 4582 /* static */ void
michael@0 4583 TextInputHandlerBase::EnsureSecureEventInputDisabled()
michael@0 4584 {
michael@0 4585 while (sSecureEventInputCount) {
michael@0 4586 TextInputHandlerBase::DisableSecureEventInput();
michael@0 4587 }
michael@0 4588 }

mercurial