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