1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "WinIMEHandler.h"
7
8 #include "IMMHandler.h"
9 #include "mozilla/Preferences.h"
10 #include "mozilla/WindowsVersion.h"
11 #include "nsWindowDefs.h"
12 #include "WinTextEventDispatcherListener.h"
13
14 #ifdef NS_ENABLE_TSF
15 #include "TSFTextStore.h"
16 #endif // #ifdef NS_ENABLE_TSF
17
18 #include "nsLookAndFeel.h"
19 #include "nsWindow.h"
20 #include "WinUtils.h"
21 #include "nsIWindowsRegKey.h"
22 #include "nsIWindowsUIUtils.h"
23
24 #include "shellapi.h"
25 #include "shlobj.h"
26 #include "powrprof.h"
27 #include "setupapi.h"
28 #include "cfgmgr32.h"
29
30 const char* kOskPathPrefName = "ui.osk.on_screen_keyboard_path";
31 const char* kOskEnabled = "ui.osk.enabled";
32 const char* kOskDetectPhysicalKeyboard = "ui.osk.detect_physical_keyboard";
33 const char* kOskRequireWin10 = "ui.osk.require_win10";
34 const char* kOskDebugReason = "ui.osk.debug.keyboardDisplayReason";
35
36 namespace mozilla {
37 namespace widget {
38
39 /******************************************************************************
40 * IMEHandler
41 ******************************************************************************/
42
43 nsWindow* IMEHandler::sFocusedWindow = nullptr;
44 InputContextAction::Cause IMEHandler::sLastContextActionCause =
45 InputContextAction::CAUSE_UNKNOWN;
46 bool IMEHandler::sForceDisableCurrentIMM_IME = false;
47 bool IMEHandler::sPluginHasFocus = false;
48
49 #ifdef NS_ENABLE_TSF
50 bool IMEHandler::sIsInTSFMode = false;
51 bool IMEHandler::sIsIMMEnabled = true;
52 bool IMEHandler::sAssociateIMCOnlyWhenIMM_IMEActive = false;
53 decltype(SetInputScopes)* IMEHandler::sSetInputScopes = nullptr;
54 #endif // #ifdef NS_ENABLE_TSF
55
56 static POWER_PLATFORM_ROLE sPowerPlatformRole = PlatformRoleUnspecified;
57 static bool sDeterminedPowerPlatformRole = false;
58
59 // static
Initialize()60 void IMEHandler::Initialize() {
61 #ifdef NS_ENABLE_TSF
62 TSFTextStore::Initialize();
63 sIsInTSFMode = TSFTextStore::IsInTSFMode();
64 sIsIMMEnabled =
65 !sIsInTSFMode || Preferences::GetBool("intl.tsf.support_imm", true);
66 sAssociateIMCOnlyWhenIMM_IMEActive =
67 sIsIMMEnabled &&
68 Preferences::GetBool("intl.tsf.associate_imc_only_when_imm_ime_is_active",
69 false);
70 if (!sIsInTSFMode) {
71 // When full TSFTextStore is not available, try to use SetInputScopes API
72 // to enable at least InputScope. Use GET_MODULE_HANDLE_EX_FLAG_PIN to
73 // ensure that msctf.dll will not be unloaded.
74 HMODULE module = nullptr;
75 if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, L"msctf.dll",
76 &module)) {
77 sSetInputScopes = reinterpret_cast<decltype(SetInputScopes)*>(
78 GetProcAddress(module, "SetInputScopes"));
79 }
80 }
81 #endif // #ifdef NS_ENABLE_TSF
82
83 IMMHandler::Initialize();
84
85 sForceDisableCurrentIMM_IME = IMMHandler::IsActiveIMEInBlockList();
86 }
87
88 // static
Terminate()89 void IMEHandler::Terminate() {
90 #ifdef NS_ENABLE_TSF
91 if (sIsInTSFMode) {
92 TSFTextStore::Terminate();
93 sIsInTSFMode = false;
94 }
95 #endif // #ifdef NS_ENABLE_TSF
96
97 IMMHandler::Terminate();
98 WinTextEventDispatcherListener::Shutdown();
99 }
100
101 // static
GetNativeData(nsWindow * aWindow,uint32_t aDataType)102 void* IMEHandler::GetNativeData(nsWindow* aWindow, uint32_t aDataType) {
103 if (aDataType == NS_RAW_NATIVE_IME_CONTEXT) {
104 #ifdef NS_ENABLE_TSF
105 if (IsTSFAvailable()) {
106 return TSFTextStore::GetThreadManager();
107 }
108 #endif // #ifdef NS_ENABLE_TSF
109 IMEContext context(aWindow);
110 if (context.IsValid()) {
111 return context.get();
112 }
113 // If IMC isn't associated with the window, IME is disabled on the window
114 // now. In such case, we should return default IMC instead.
115 const IMEContext& defaultIMC = aWindow->DefaultIMC();
116 if (defaultIMC.IsValid()) {
117 return defaultIMC.get();
118 }
119 // If there is no default IMC, we should return the pointer to the window
120 // since if we return nullptr, IMEStateManager cannot manage composition
121 // with TextComposition instance. This is possible if no IME is installed,
122 // but composition may occur with dead key sequence.
123 return aWindow;
124 }
125
126 #ifdef NS_ENABLE_TSF
127 void* result = TSFTextStore::GetNativeData(aDataType);
128 if (!result || !(*(static_cast<void**>(result)))) {
129 return nullptr;
130 }
131 // XXX During the TSF module test, sIsInTSFMode must be true. After that,
132 // the value should be restored but currently, there is no way for that.
133 // When the TSF test is enabled again, we need to fix this. Perhaps,
134 // sending a message can fix this.
135 sIsInTSFMode = true;
136 return result;
137 #else // #ifdef NS_ENABLE_TSF
138 return nullptr;
139 #endif // #ifdef NS_ENABLE_TSF #else
140 }
141
142 // static
ProcessRawKeyMessage(const MSG & aMsg)143 bool IMEHandler::ProcessRawKeyMessage(const MSG& aMsg) {
144 #ifdef NS_ENABLE_TSF
145 if (IsTSFAvailable()) {
146 return TSFTextStore::ProcessRawKeyMessage(aMsg);
147 }
148 #endif // #ifdef NS_ENABLE_TSF
149 return false; // noting to do in IMM mode.
150 }
151
152 // static
ProcessMessage(nsWindow * aWindow,UINT aMessage,WPARAM & aWParam,LPARAM & aLParam,MSGResult & aResult)153 bool IMEHandler::ProcessMessage(nsWindow* aWindow, UINT aMessage,
154 WPARAM& aWParam, LPARAM& aLParam,
155 MSGResult& aResult) {
156 if (aMessage == MOZ_WM_DISMISS_ONSCREEN_KEYBOARD) {
157 if (!sFocusedWindow) {
158 DismissOnScreenKeyboard();
159 }
160 return true;
161 }
162
163 #ifdef NS_ENABLE_TSF
164 if (IsTSFAvailable()) {
165 TSFTextStore::ProcessMessage(aWindow, aMessage, aWParam, aLParam, aResult);
166 if (aResult.mConsumed) {
167 return true;
168 }
169 // If we don't support IMM in TSF mode, we don't use IMMHandler.
170 if (!sIsIMMEnabled) {
171 return false;
172 }
173 // IME isn't implemented with IMM, IMMHandler shouldn't handle any
174 // messages.
175 if (!IsIMMActive()) {
176 return false;
177 }
178 }
179 #endif // #ifdef NS_ENABLE_TSF
180
181 bool keepGoing =
182 IMMHandler::ProcessMessage(aWindow, aMessage, aWParam, aLParam, aResult);
183
184 // If user changes active IME to an IME which is listed in our block list,
185 // we should disassociate IMC from the window for preventing the IME to work
186 // and crash.
187 if (aMessage == WM_INPUTLANGCHANGE) {
188 bool disableIME = IMMHandler::IsActiveIMEInBlockList();
189 if (disableIME != sForceDisableCurrentIMM_IME) {
190 bool enable =
191 !disableIME && WinUtils::IsIMEEnabled(aWindow->InputContextRef());
192 AssociateIMEContext(aWindow, enable);
193 sForceDisableCurrentIMM_IME = disableIME;
194 }
195 }
196
197 return keepGoing;
198 }
199
200 #ifdef NS_ENABLE_TSF
201 // static
IsIMMActive()202 bool IMEHandler::IsIMMActive() { return TSFTextStore::IsIMM_IMEActive(); }
203
204 #endif // #ifdef NS_ENABLE_TSF
205
206 // static
IsComposing()207 bool IMEHandler::IsComposing() {
208 #ifdef NS_ENABLE_TSF
209 if (IsTSFAvailable()) {
210 return TSFTextStore::IsComposing() || IMMHandler::IsComposing();
211 }
212 #endif // #ifdef NS_ENABLE_TSF
213
214 return IMMHandler::IsComposing();
215 }
216
217 // static
IsComposingOn(nsWindow * aWindow)218 bool IMEHandler::IsComposingOn(nsWindow* aWindow) {
219 #ifdef NS_ENABLE_TSF
220 if (IsTSFAvailable()) {
221 return TSFTextStore::IsComposingOn(aWindow) ||
222 IMMHandler::IsComposingOn(aWindow);
223 }
224 #endif // #ifdef NS_ENABLE_TSF
225
226 return IMMHandler::IsComposingOn(aWindow);
227 }
228
229 // static
NotifyIME(nsWindow * aWindow,const IMENotification & aIMENotification)230 nsresult IMEHandler::NotifyIME(nsWindow* aWindow,
231 const IMENotification& aIMENotification) {
232 #ifdef NS_ENABLE_TSF
233 if (IsTSFAvailable()) {
234 switch (aIMENotification.mMessage) {
235 case NOTIFY_IME_OF_SELECTION_CHANGE: {
236 nsresult rv = TSFTextStore::OnSelectionChange(aIMENotification);
237 // If IMM IME is active, we need to notify IMMHandler of updating
238 // composition change. It will adjust candidate window position or
239 // composition window position.
240 bool isIMMActive = IsIMMActive();
241 if (isIMMActive) {
242 IMMHandler::OnUpdateComposition(aWindow);
243 }
244 IMMHandler::OnSelectionChange(aWindow, aIMENotification, isIMMActive);
245 return rv;
246 }
247 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
248 // If IMM IME is active, we need to notify IMMHandler of updating
249 // composition change. It will adjust candidate window position or
250 // composition window position.
251 if (IsIMMActive()) {
252 IMMHandler::OnUpdateComposition(aWindow);
253 } else {
254 TSFTextStore::OnUpdateComposition();
255 }
256 return NS_OK;
257 case NOTIFY_IME_OF_TEXT_CHANGE:
258 return TSFTextStore::OnTextChange(aIMENotification);
259 case NOTIFY_IME_OF_FOCUS: {
260 sFocusedWindow = aWindow;
261 IMMHandler::OnFocusChange(true, aWindow);
262 nsresult rv = TSFTextStore::OnFocusChange(true, aWindow,
263 aWindow->GetInputContext());
264 IMEHandler::MaybeShowOnScreenKeyboard();
265 return rv;
266 }
267 case NOTIFY_IME_OF_BLUR:
268 sFocusedWindow = nullptr;
269 IMEHandler::MaybeDismissOnScreenKeyboard(aWindow);
270 IMMHandler::OnFocusChange(false, aWindow);
271 return TSFTextStore::OnFocusChange(false, aWindow,
272 aWindow->GetInputContext());
273 case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
274 // If IMM IME is active, we should send a mouse button event via IMM.
275 if (IsIMMActive()) {
276 return IMMHandler::OnMouseButtonEvent(aWindow, aIMENotification);
277 }
278 return TSFTextStore::OnMouseButtonEvent(aIMENotification);
279 case REQUEST_TO_COMMIT_COMPOSITION:
280 if (TSFTextStore::IsComposingOn(aWindow)) {
281 TSFTextStore::CommitComposition(false);
282 } else if (IsIMMActive()) {
283 IMMHandler::CommitComposition(aWindow);
284 }
285 return NS_OK;
286 case REQUEST_TO_CANCEL_COMPOSITION:
287 if (TSFTextStore::IsComposingOn(aWindow)) {
288 TSFTextStore::CommitComposition(true);
289 } else if (IsIMMActive()) {
290 IMMHandler::CancelComposition(aWindow);
291 }
292 return NS_OK;
293 case NOTIFY_IME_OF_POSITION_CHANGE:
294 return TSFTextStore::OnLayoutChange();
295 default:
296 return NS_ERROR_NOT_IMPLEMENTED;
297 }
298 }
299 #endif // NS_ENABLE_TSF
300
301 switch (aIMENotification.mMessage) {
302 case REQUEST_TO_COMMIT_COMPOSITION:
303 IMMHandler::CommitComposition(aWindow);
304 return NS_OK;
305 case REQUEST_TO_CANCEL_COMPOSITION:
306 IMMHandler::CancelComposition(aWindow);
307 return NS_OK;
308 case NOTIFY_IME_OF_POSITION_CHANGE:
309 case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
310 IMMHandler::OnUpdateComposition(aWindow);
311 return NS_OK;
312 case NOTIFY_IME_OF_SELECTION_CHANGE:
313 IMMHandler::OnSelectionChange(aWindow, aIMENotification, true);
314 return NS_OK;
315 case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
316 return IMMHandler::OnMouseButtonEvent(aWindow, aIMENotification);
317 case NOTIFY_IME_OF_FOCUS:
318 sFocusedWindow = aWindow;
319 IMMHandler::OnFocusChange(true, aWindow);
320 IMEHandler::MaybeShowOnScreenKeyboard();
321 return NS_OK;
322 case NOTIFY_IME_OF_BLUR:
323 sFocusedWindow = nullptr;
324 IMEHandler::MaybeDismissOnScreenKeyboard(aWindow);
325 IMMHandler::OnFocusChange(false, aWindow);
326 #ifdef NS_ENABLE_TSF
327 // If a plugin gets focus while TSF has focus, we need to notify TSF of
328 // the blur.
329 if (TSFTextStore::ThinksHavingFocus()) {
330 return TSFTextStore::OnFocusChange(false, aWindow,
331 aWindow->GetInputContext());
332 }
333 #endif // NS_ENABLE_TSF
334 return NS_OK;
335 default:
336 return NS_ERROR_NOT_IMPLEMENTED;
337 }
338 }
339
340 // static
GetIMENotificationRequests()341 IMENotificationRequests IMEHandler::GetIMENotificationRequests() {
342 // While a plugin has focus, neither TSFTextStore nor IMMHandler needs
343 // notifications.
344 if (sPluginHasFocus) {
345 return IMENotificationRequests();
346 }
347
348 #ifdef NS_ENABLE_TSF
349 if (IsTSFAvailable()) {
350 if (!sIsIMMEnabled) {
351 return TSFTextStore::GetIMENotificationRequests();
352 }
353 // Even if TSF is available, the active IME may be an IMM-IME.
354 // Unfortunately, changing the result of GetIMENotificationRequests() while
355 // an editor has focus isn't supported by IMEContentObserver nor
356 // ContentCacheInParent. Therefore, we need to request whole notifications
357 // which are necessary either IMMHandler or TSFTextStore.
358 return IMMHandler::GetIMENotificationRequests() |
359 TSFTextStore::GetIMENotificationRequests();
360 }
361 #endif // NS_ENABLE_TSF
362
363 return IMMHandler::GetIMENotificationRequests();
364 }
365
366 // static
367 TextEventDispatcherListener*
GetNativeTextEventDispatcherListener()368 IMEHandler::GetNativeTextEventDispatcherListener() {
369 return WinTextEventDispatcherListener::GetInstance();
370 }
371
372 // static
GetOpenState(nsWindow * aWindow)373 bool IMEHandler::GetOpenState(nsWindow* aWindow) {
374 #ifdef NS_ENABLE_TSF
375 if (IsTSFAvailable() && !IsIMMActive()) {
376 return TSFTextStore::GetIMEOpenState();
377 }
378 #endif // NS_ENABLE_TSF
379
380 IMEContext context(aWindow);
381 return context.GetOpenState();
382 }
383
384 // static
OnDestroyWindow(nsWindow * aWindow)385 void IMEHandler::OnDestroyWindow(nsWindow* aWindow) {
386 // When focus is in remote process, but the window is being destroyed, we
387 // need to clean up TSFTextStore here since NOTIFY_IME_OF_BLUR won't reach
388 // here because TabParent already lost the reference to the nsWindow when
389 // it receives from the remote process.
390 if (sFocusedWindow == aWindow) {
391 MOZ_ASSERT(aWindow->GetInputContext().IsOriginContentProcess(),
392 "input context of focused widget should've been set by a remote "
393 "process "
394 "if IME focus isn't cleared before destroying the widget");
395 NotifyIME(aWindow, IMENotification(NOTIFY_IME_OF_BLUR));
396 }
397
398 #ifdef NS_ENABLE_TSF
399 // We need to do nothing here for TSF. Just restore the default context
400 // if it's been disassociated.
401 if (!sIsInTSFMode) {
402 // MSDN says we need to set IS_DEFAULT to avoid memory leak when we use
403 // SetInputScopes API. Use an empty string to do this.
404 SetInputScopeForIMM32(aWindow, EmptyString(), EmptyString());
405 }
406 #endif // #ifdef NS_ENABLE_TSF
407 AssociateIMEContext(aWindow, true);
408 }
409
410 #ifdef NS_ENABLE_TSF
411 // static
NeedsToAssociateIMC()412 bool IMEHandler::NeedsToAssociateIMC() {
413 return !sForceDisableCurrentIMM_IME &&
414 (!sAssociateIMCOnlyWhenIMM_IMEActive || !IsIMMActive());
415 }
416 #endif // #ifdef NS_ENABLE_TSF
417
418 // static
SetInputContext(nsWindow * aWindow,InputContext & aInputContext,const InputContextAction & aAction)419 void IMEHandler::SetInputContext(nsWindow* aWindow, InputContext& aInputContext,
420 const InputContextAction& aAction) {
421 sLastContextActionCause = aAction.mCause;
422 // FYI: If there is no composition, this call will do nothing.
423 NotifyIME(aWindow, IMENotification(REQUEST_TO_COMMIT_COMPOSITION));
424
425 const InputContext& oldInputContext = aWindow->GetInputContext();
426
427 // Assume that SetInputContext() is called only when aWindow has focus.
428 sPluginHasFocus = (aInputContext.mIMEState.mEnabled == IMEState::PLUGIN);
429
430 if (aAction.UserMightRequestOpenVKB()) {
431 IMEHandler::MaybeShowOnScreenKeyboard();
432 }
433
434 bool enable = WinUtils::IsIMEEnabled(aInputContext);
435 bool adjustOpenState = (enable && aInputContext.mIMEState.mOpen !=
436 IMEState::DONT_CHANGE_OPEN_STATE);
437 bool open =
438 (adjustOpenState && aInputContext.mIMEState.mOpen == IMEState::OPEN);
439
440 #ifdef NS_ENABLE_TSF
441 // Note that even while a plugin has focus, we need to notify TSF of that.
442 if (sIsInTSFMode) {
443 TSFTextStore::SetInputContext(aWindow, aInputContext, aAction);
444 if (IsTSFAvailable()) {
445 if (sIsIMMEnabled) {
446 // Associate IMC with aWindow only when it's necessary.
447 AssociateIMEContext(aWindow, enable && NeedsToAssociateIMC());
448 } else if (oldInputContext.mIMEState.mEnabled == IMEState::PLUGIN) {
449 // Disassociate the IME context from the window when plugin loses focus
450 // in pure TSF mode.
451 AssociateIMEContext(aWindow, false);
452 }
453 if (adjustOpenState) {
454 TSFTextStore::SetIMEOpenState(open);
455 }
456 return;
457 }
458 } else {
459 // Set at least InputScope even when TextStore is not available.
460 SetInputScopeForIMM32(aWindow, aInputContext.mHTMLInputType,
461 aInputContext.mHTMLInputInputmode);
462 }
463 #endif // #ifdef NS_ENABLE_TSF
464
465 AssociateIMEContext(aWindow, enable);
466
467 IMEContext context(aWindow);
468 if (adjustOpenState) {
469 context.SetOpenState(open);
470 }
471 }
472
473 // static
AssociateIMEContext(nsWindowBase * aWindowBase,bool aEnable)474 void IMEHandler::AssociateIMEContext(nsWindowBase* aWindowBase, bool aEnable) {
475 IMEContext context(aWindowBase);
476 if (aEnable) {
477 context.AssociateDefaultContext();
478 return;
479 }
480 // Don't disassociate the context after the window is destroyed.
481 if (aWindowBase->Destroyed()) {
482 return;
483 }
484 context.Disassociate();
485 }
486
487 // static
InitInputContext(nsWindow * aWindow,InputContext & aInputContext)488 void IMEHandler::InitInputContext(nsWindow* aWindow,
489 InputContext& aInputContext) {
490 MOZ_ASSERT(aWindow);
491 MOZ_ASSERT(aWindow->GetWindowHandle(),
492 "IMEHandler::SetInputContext() requires non-nullptr HWND");
493
494 static bool sInitialized = false;
495 if (!sInitialized) {
496 sInitialized = true;
497 // Some TIPs like QQ Input (Simplified Chinese) may need normal window
498 // (i.e., windows except message window) when initializing themselves.
499 // Therefore, we need to initialize TSF/IMM modules after first normal
500 // window is created. InitInputContext() should be called immediately
501 // after creating each normal window, so, here is a good place to
502 // initialize these modules.
503 Initialize();
504 }
505
506 // For a11y, the default enabled state should be 'enabled'.
507 aInputContext.mIMEState.mEnabled = IMEState::ENABLED;
508
509 #ifdef NS_ENABLE_TSF
510 if (sIsInTSFMode) {
511 TSFTextStore::SetInputContext(
512 aWindow, aInputContext,
513 InputContextAction(InputContextAction::CAUSE_UNKNOWN,
514 InputContextAction::WIDGET_CREATED));
515 // IME context isn't necessary in pure TSF mode.
516 if (!sIsIMMEnabled) {
517 AssociateIMEContext(aWindow, false);
518 }
519 return;
520 }
521 #endif // #ifdef NS_ENABLE_TSF
522
523 #ifdef DEBUG
524 // NOTE: IMC may be null if IMM module isn't installed.
525 IMEContext context(aWindow);
526 MOZ_ASSERT(context.IsValid() || !CurrentKeyboardLayoutHasIME());
527 #endif // #ifdef DEBUG
528 }
529
530 #ifdef DEBUG
531 // static
CurrentKeyboardLayoutHasIME()532 bool IMEHandler::CurrentKeyboardLayoutHasIME() {
533 #ifdef NS_ENABLE_TSF
534 if (sIsInTSFMode) {
535 return TSFTextStore::CurrentKeyboardLayoutHasIME();
536 }
537 #endif // #ifdef NS_ENABLE_TSF
538
539 return IMMHandler::IsIMEAvailable();
540 }
541 #endif // #ifdef DEBUG
542
543 // static
OnKeyboardLayoutChanged()544 void IMEHandler::OnKeyboardLayoutChanged() {
545 // Be aware, this method won't be called until TSFStaticSink starts to
546 // observe active TIP change. If you need to be notified of this, you
547 // need to create TSFStaticSink::Observe() or something and call it
548 // TSFStaticSink::EnsureInitActiveTIPKeyboard() forcibly.
549
550 if (!sIsIMMEnabled || !IsTSFAvailable()) {
551 return;
552 }
553
554 // We don't need to do anything when sAssociateIMCOnlyWhenIMM_IMEActive is
555 // false because IMContext won't be associated/disassociated when changing
556 // active keyboard layout/IME.
557 if (!sAssociateIMCOnlyWhenIMM_IMEActive) {
558 return;
559 }
560
561 // If there is no TSFTextStore which has focus, i.e., no editor has focus,
562 // nothing to do here.
563 nsWindowBase* windowBase = TSFTextStore::GetEnabledWindowBase();
564 if (!windowBase) {
565 return;
566 }
567
568 // If IME isn't available, nothing to do here.
569 InputContext inputContext = windowBase->GetInputContext();
570 if (!WinUtils::IsIMEEnabled(inputContext)) {
571 return;
572 }
573
574 // Associate or Disassociate IMC if it's necessary.
575 // Note that this does nothing if the window has already associated with or
576 // disassociated from the window.
577 AssociateIMEContext(windowBase, NeedsToAssociateIMC());
578 }
579
580 // static
SetInputScopeForIMM32(nsWindow * aWindow,const nsAString & aHTMLInputType,const nsAString & aHTMLInputInputmode)581 void IMEHandler::SetInputScopeForIMM32(nsWindow* aWindow,
582 const nsAString& aHTMLInputType,
583 const nsAString& aHTMLInputInputmode) {
584 if (sIsInTSFMode || !sSetInputScopes || aWindow->Destroyed()) {
585 return;
586 }
587 UINT arraySize = 0;
588 const InputScope* scopes = nullptr;
589 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html
590 if (aHTMLInputType.IsEmpty() || aHTMLInputType.EqualsLiteral("text")) {
591 if (aHTMLInputInputmode.EqualsLiteral("url")) {
592 static const InputScope inputScopes[] = {IS_URL};
593 scopes = &inputScopes[0];
594 arraySize = ArrayLength(inputScopes);
595 } else if (aHTMLInputInputmode.EqualsLiteral("mozAwesomebar")) {
596 // Even if Awesomebar has focus, user may not input URL directly.
597 // However, on-screen keyboard for URL should be shown because it has
598 // some useful additional keys like ".com" and they are not hindrances
599 // even when inputting non-URL text, e.g., words to search something in
600 // the web. On the other hand, a lot of Microsoft's IMEs and Google
601 // Japanese Input make their open state "closed" automatically if we
602 // notify them of URL as the input scope. However, this is very annoying
603 // for the users when they try to input some words to search the web or
604 // bookmark/history items. Therefore, if they are active, we need to
605 // notify them of the default input scope for avoiding this issue.
606 // FYI: We cannot check active TIP without TSF. Therefore, if it's
607 // not in TSF mode, this will check only if active IMM-IME is Google
608 // Japanese Input. Google Japanese Input is a TIP of TSF basically.
609 // However, if the OS is Win7 or it's installed on Win7 but has not
610 // been updated yet even after the OS is upgraded to Win8 or later,
611 // it's installed as IMM-IME.
612 if (TSFTextStore::ShouldSetInputScopeOfURLBarToDefault()) {
613 static const InputScope inputScopes[] = {IS_DEFAULT};
614 scopes = &inputScopes[0];
615 arraySize = ArrayLength(inputScopes);
616 } else {
617 static const InputScope inputScopes[] = {IS_URL};
618 scopes = &inputScopes[0];
619 arraySize = ArrayLength(inputScopes);
620 }
621 } else if (aHTMLInputInputmode.EqualsLiteral("email")) {
622 static const InputScope inputScopes[] = {IS_EMAIL_SMTPEMAILADDRESS};
623 scopes = &inputScopes[0];
624 arraySize = ArrayLength(inputScopes);
625 } else if (aHTMLInputInputmode.EqualsLiteral("tel")) {
626 static const InputScope inputScopes[] = {
627 IS_TELEPHONE_LOCALNUMBER, IS_TELEPHONE_FULLTELEPHONENUMBER};
628 scopes = &inputScopes[0];
629 arraySize = ArrayLength(inputScopes);
630 } else if (aHTMLInputInputmode.EqualsLiteral("numeric")) {
631 static const InputScope inputScopes[] = {IS_NUMBER};
632 scopes = &inputScopes[0];
633 arraySize = ArrayLength(inputScopes);
634 } else {
635 static const InputScope inputScopes[] = {IS_DEFAULT};
636 scopes = &inputScopes[0];
637 arraySize = ArrayLength(inputScopes);
638 }
639 } else if (aHTMLInputType.EqualsLiteral("url")) {
640 static const InputScope inputScopes[] = {IS_URL};
641 scopes = &inputScopes[0];
642 arraySize = ArrayLength(inputScopes);
643 } else if (aHTMLInputType.EqualsLiteral("search")) {
644 static const InputScope inputScopes[] = {IS_SEARCH};
645 scopes = &inputScopes[0];
646 arraySize = ArrayLength(inputScopes);
647 } else if (aHTMLInputType.EqualsLiteral("email")) {
648 static const InputScope inputScopes[] = {IS_EMAIL_SMTPEMAILADDRESS};
649 scopes = &inputScopes[0];
650 arraySize = ArrayLength(inputScopes);
651 } else if (aHTMLInputType.EqualsLiteral("password")) {
652 static const InputScope inputScopes[] = {IS_PASSWORD};
653 scopes = &inputScopes[0];
654 arraySize = ArrayLength(inputScopes);
655 } else if (aHTMLInputType.EqualsLiteral("datetime") ||
656 aHTMLInputType.EqualsLiteral("datetime-local")) {
657 static const InputScope inputScopes[] = {IS_DATE_FULLDATE,
658 IS_TIME_FULLTIME};
659 scopes = &inputScopes[0];
660 arraySize = ArrayLength(inputScopes);
661 } else if (aHTMLInputType.EqualsLiteral("date") ||
662 aHTMLInputType.EqualsLiteral("month") ||
663 aHTMLInputType.EqualsLiteral("week")) {
664 static const InputScope inputScopes[] = {IS_DATE_FULLDATE};
665 scopes = &inputScopes[0];
666 arraySize = ArrayLength(inputScopes);
667 } else if (aHTMLInputType.EqualsLiteral("time")) {
668 static const InputScope inputScopes[] = {IS_TIME_FULLTIME};
669 scopes = &inputScopes[0];
670 arraySize = ArrayLength(inputScopes);
671 } else if (aHTMLInputType.EqualsLiteral("tel")) {
672 static const InputScope inputScopes[] = {IS_TELEPHONE_FULLTELEPHONENUMBER,
673 IS_TELEPHONE_LOCALNUMBER};
674 scopes = &inputScopes[0];
675 arraySize = ArrayLength(inputScopes);
676 } else if (aHTMLInputType.EqualsLiteral("number")) {
677 static const InputScope inputScopes[] = {IS_NUMBER};
678 scopes = &inputScopes[0];
679 arraySize = ArrayLength(inputScopes);
680 }
681 if (scopes && arraySize > 0) {
682 sSetInputScopes(aWindow->GetWindowHandle(), scopes, arraySize, nullptr, 0,
683 nullptr, nullptr);
684 }
685 }
686
687 // static
MaybeShowOnScreenKeyboard()688 void IMEHandler::MaybeShowOnScreenKeyboard() {
689 if (sPluginHasFocus || !IsWin8OrLater() ||
690 !Preferences::GetBool(kOskEnabled, true) || GetOnScreenKeyboardWindow() ||
691 !IMEHandler::NeedOnScreenKeyboard()) {
692 return;
693 }
694
695 // On Windows 10 we require tablet mode, unless the user has set the relevant
696 // Windows setting to enable the on-screen keyboard in desktop mode.
697 // We might be disabled specifically on Win8(.1), so we check that afterwards.
698 if (IsWin10OrLater()) {
699 if (!IsInTabletMode() && !AutoInvokeOnScreenKeyboardInDesktopMode()) {
700 return;
701 }
702 } else if (Preferences::GetBool(kOskRequireWin10, true)) {
703 return;
704 }
705
706 IMEHandler::ShowOnScreenKeyboard();
707 }
708
709 // static
MaybeDismissOnScreenKeyboard(nsWindow * aWindow)710 void IMEHandler::MaybeDismissOnScreenKeyboard(nsWindow* aWindow) {
711 if (sPluginHasFocus || !IsWin8OrLater()) {
712 return;
713 }
714
715 ::PostMessage(aWindow->GetWindowHandle(), MOZ_WM_DISMISS_ONSCREEN_KEYBOARD, 0,
716 0);
717 }
718
719 // static
WStringStartsWithCaseInsensitive(const std::wstring & aHaystack,const std::wstring & aNeedle)720 bool IMEHandler::WStringStartsWithCaseInsensitive(const std::wstring& aHaystack,
721 const std::wstring& aNeedle) {
722 std::wstring lowerCaseHaystack(aHaystack);
723 std::wstring lowerCaseNeedle(aNeedle);
724 std::transform(lowerCaseHaystack.begin(), lowerCaseHaystack.end(),
725 lowerCaseHaystack.begin(), ::tolower);
726 std::transform(lowerCaseNeedle.begin(), lowerCaseNeedle.end(),
727 lowerCaseNeedle.begin(), ::tolower);
728 return wcsstr(lowerCaseHaystack.c_str(), lowerCaseNeedle.c_str()) ==
729 lowerCaseHaystack.c_str();
730 }
731
732 // Returns false if a physical keyboard is detected on Windows 8 and up,
733 // or there is some other reason why an onscreen keyboard is not necessary.
734 // Returns true if no keyboard is found and this device looks like it needs
735 // an on-screen keyboard for text input.
736 // static
NeedOnScreenKeyboard()737 bool IMEHandler::NeedOnScreenKeyboard() {
738 // This function is only supported for Windows 8 and up.
739 if (!IsWin8OrLater()) {
740 Preferences::SetString(kOskDebugReason, L"IKPOS: Requires Win8+.");
741 return false;
742 }
743
744 if (!Preferences::GetBool(kOskDetectPhysicalKeyboard, true)) {
745 Preferences::SetString(kOskDebugReason, L"IKPOS: Detection disabled.");
746 return true;
747 }
748
749 // If the last focus cause was not user-initiated (ie a result of code
750 // setting focus to an element) then don't auto-show a keyboard. This
751 // avoids cases where the keyboard would pop up "just" because e.g. a
752 // web page chooses to focus a search field on the page, even when that
753 // really isn't what the user is trying to do at that moment.
754 if (!InputContextAction::IsHandlingUserInput(sLastContextActionCause)) {
755 return false;
756 }
757
758 // This function should be only invoked for machines with touch screens.
759 if ((::GetSystemMetrics(SM_DIGITIZER) & NID_INTEGRATED_TOUCH) !=
760 NID_INTEGRATED_TOUCH) {
761 Preferences::SetString(kOskDebugReason, L"IKPOS: Touch screen not found.");
762 return false;
763 }
764
765 // If the device is docked, the user is treating the device as a PC.
766 if (::GetSystemMetrics(SM_SYSTEMDOCKED) != 0) {
767 Preferences::SetString(kOskDebugReason, L"IKPOS: System docked.");
768 return false;
769 }
770
771 // To determine whether a keyboard is present on the device, we do the
772 // following:-
773 // 1. If the platform role is that of a mobile or slate device, check the
774 // system metric SM_CONVERTIBLESLATEMODE to see if it is being used
775 // in slate mode. If it is, also check that the last input was a touch.
776 // If all of this is true, then we should show the on-screen keyboard.
777
778 // 2. If step 1 didn't determine we should show the keyboard, we check if
779 // this device has keyboards attached to it.
780
781 // Check if the device is being used as a laptop or a tablet. This can be
782 // checked by first checking the role of the device and then the
783 // corresponding system metric (SM_CONVERTIBLESLATEMODE). If it is being
784 // used as a tablet then we want the OSK to show up.
785 typedef POWER_PLATFORM_ROLE(WINAPI *
786 PowerDeterminePlatformRoleEx)(ULONG Version);
787 if (!sDeterminedPowerPlatformRole) {
788 sDeterminedPowerPlatformRole = true;
789 PowerDeterminePlatformRoleEx power_determine_platform_role =
790 reinterpret_cast<PowerDeterminePlatformRoleEx>(::GetProcAddress(
791 ::LoadLibraryW(L"PowrProf.dll"), "PowerDeterminePlatformRoleEx"));
792 if (power_determine_platform_role) {
793 sPowerPlatformRole =
794 power_determine_platform_role(POWER_PLATFORM_ROLE_V2);
795 } else {
796 sPowerPlatformRole = PlatformRoleUnspecified;
797 }
798 }
799
800 // If this a mobile or slate (tablet) device, check if it is in slate mode.
801 // If the last input was touch, ignore whether or not a keyboard is present.
802 if ((sPowerPlatformRole == PlatformRoleMobile ||
803 sPowerPlatformRole == PlatformRoleSlate) &&
804 ::GetSystemMetrics(SM_CONVERTIBLESLATEMODE) == 0 &&
805 sLastContextActionCause == InputContextAction::CAUSE_TOUCH) {
806 Preferences::SetString(
807 kOskDebugReason,
808 L"IKPOS: Mobile/Slate Platform role, in slate mode with touch event.");
809 return true;
810 }
811
812 return !IMEHandler::IsKeyboardPresentOnSlate();
813 }
814
815 // Uses the Setup APIs to enumerate the attached keyboards and returns true
816 // if the keyboard count is 1 or more. While this will work in most cases
817 // it won't work if there are devices which expose keyboard interfaces which
818 // are attached to the machine.
819 // Based on IsKeyboardPresentOnSlate() in Chromium's base/win/win_util.cc.
820 // static
IsKeyboardPresentOnSlate()821 bool IMEHandler::IsKeyboardPresentOnSlate() {
822 const GUID KEYBOARD_CLASS_GUID = {
823 0x4D36E96B,
824 0xE325,
825 0x11CE,
826 {0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18}};
827
828 // Query for all the keyboard devices.
829 HDEVINFO device_info = ::SetupDiGetClassDevs(&KEYBOARD_CLASS_GUID, nullptr,
830 nullptr, DIGCF_PRESENT);
831 if (device_info == INVALID_HANDLE_VALUE) {
832 Preferences::SetString(kOskDebugReason, L"IKPOS: No keyboard info.");
833 return false;
834 }
835
836 // Enumerate all keyboards and look for ACPI\PNP and HID\VID devices. If
837 // the count is more than 1 we assume that a keyboard is present. This is
838 // under the assumption that there will always be one keyboard device.
839 for (DWORD i = 0;; ++i) {
840 SP_DEVINFO_DATA device_info_data = {0};
841 device_info_data.cbSize = sizeof(device_info_data);
842 if (!::SetupDiEnumDeviceInfo(device_info, i, &device_info_data)) {
843 break;
844 }
845
846 // Get the device ID.
847 wchar_t device_id[MAX_DEVICE_ID_LEN];
848 CONFIGRET status = ::CM_Get_Device_ID(device_info_data.DevInst, device_id,
849 MAX_DEVICE_ID_LEN, 0);
850 if (status == CR_SUCCESS) {
851 static const std::wstring BT_HID_DEVICE = L"HID\\{00001124";
852 static const std::wstring BT_HOGP_DEVICE = L"HID\\{00001812";
853 // To reduce the scope of the hack we only look for ACPI and HID\\VID
854 // prefixes in the keyboard device ids.
855 if (IMEHandler::WStringStartsWithCaseInsensitive(device_id, L"ACPI") ||
856 IMEHandler::WStringStartsWithCaseInsensitive(device_id,
857 L"HID\\VID") ||
858 IMEHandler::WStringStartsWithCaseInsensitive(device_id,
859 BT_HID_DEVICE) ||
860 IMEHandler::WStringStartsWithCaseInsensitive(device_id,
861 BT_HOGP_DEVICE)) {
862 // The heuristic we are using is to check the count of keyboards and
863 // return true if the API's report one or more keyboards. Please note
864 // that this will break for non keyboard devices which expose a
865 // keyboard PDO.
866 Preferences::SetString(kOskDebugReason,
867 L"IKPOS: Keyboard presence confirmed.");
868 return true;
869 }
870 }
871 }
872 Preferences::SetString(kOskDebugReason,
873 L"IKPOS: Lack of keyboard confirmed.");
874 return false;
875 }
876
877 // static
IsInTabletMode()878 bool IMEHandler::IsInTabletMode() {
879 nsCOMPtr<nsIWindowsUIUtils> uiUtils(
880 do_GetService("@mozilla.org/windows-ui-utils;1"));
881 if (NS_WARN_IF(!uiUtils)) {
882 Preferences::SetString(kOskDebugReason,
883 L"IITM: nsIWindowsUIUtils not available.");
884 return false;
885 }
886 bool isInTabletMode = false;
887 uiUtils->GetInTabletMode(&isInTabletMode);
888 if (isInTabletMode) {
889 Preferences::SetString(kOskDebugReason, L"IITM: GetInTabletMode=true.");
890 } else {
891 Preferences::SetString(kOskDebugReason, L"IITM: GetInTabletMode=false.");
892 }
893 return isInTabletMode;
894 }
895
896 // static
AutoInvokeOnScreenKeyboardInDesktopMode()897 bool IMEHandler::AutoInvokeOnScreenKeyboardInDesktopMode() {
898 nsresult rv;
899 nsCOMPtr<nsIWindowsRegKey> regKey(
900 do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv));
901 if (NS_WARN_IF(NS_FAILED(rv))) {
902 Preferences::SetString(kOskDebugReason,
903 L"AIOSKIDM: "
904 L"nsIWindowsRegKey not available");
905 return false;
906 }
907 rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
908 NS_LITERAL_STRING("SOFTWARE\\Microsoft\\TabletTip\\1.7"),
909 nsIWindowsRegKey::ACCESS_QUERY_VALUE);
910 if (NS_FAILED(rv)) {
911 Preferences::SetString(kOskDebugReason,
912 L"AIOSKIDM: failed opening regkey.");
913 return false;
914 }
915 // EnableDesktopModeAutoInvoke is an opt-in option from the Windows
916 // Settings to "Automatically show the touch keyboard in windowed apps
917 // when there's no keyboard attached to your device." If the user has
918 // opted-in to this behavior, the tablet-mode requirement is skipped.
919 uint32_t value;
920 rv = regKey->ReadIntValue(NS_LITERAL_STRING("EnableDesktopModeAutoInvoke"),
921 &value);
922 if (NS_FAILED(rv)) {
923 Preferences::SetString(kOskDebugReason,
924 L"AIOSKIDM: failed reading value of regkey.");
925 return false;
926 }
927 if (!!value) {
928 Preferences::SetString(kOskDebugReason, L"AIOSKIDM: regkey value=true.");
929 } else {
930 Preferences::SetString(kOskDebugReason, L"AIOSKIDM: regkey value=false.");
931 }
932 return !!value;
933 }
934
935 // Based on DisplayVirtualKeyboard() in Chromium's base/win/win_util.cc.
936 // static
ShowOnScreenKeyboard()937 void IMEHandler::ShowOnScreenKeyboard() {
938 nsAutoString cachedPath;
939 nsresult result = Preferences::GetString(kOskPathPrefName, cachedPath);
940 if (NS_FAILED(result) || cachedPath.IsEmpty()) {
941 wchar_t path[MAX_PATH];
942 // The path to TabTip.exe is defined at the following registry key.
943 // This is pulled out of the 64-bit registry hive directly.
944 const wchar_t kRegKeyName[] =
945 L"Software\\Classes\\CLSID\\"
946 L"{054AAE20-4BEA-4347-8A35-64A533254A9D}\\LocalServer32";
947 if (!WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE, kRegKeyName, nullptr,
948 path, sizeof path)) {
949 return;
950 }
951
952 std::wstring wstrpath(path);
953 // The path provided by the registry will often contain
954 // %CommonProgramFiles%, which will need to be replaced if it is present.
955 size_t commonProgramFilesOffset = wstrpath.find(L"%CommonProgramFiles%");
956 if (commonProgramFilesOffset != std::wstring::npos) {
957 // The path read from the registry contains the %CommonProgramFiles%
958 // environment variable prefix. On 64 bit Windows the
959 // SHGetKnownFolderPath function returns the common program files path
960 // with the X86 suffix for the FOLDERID_ProgramFilesCommon value.
961 // To get the correct path to TabTip.exe we first read the environment
962 // variable CommonProgramW6432 which points to the desired common
963 // files path. Failing that we fallback to the SHGetKnownFolderPath API.
964
965 // We then replace the %CommonProgramFiles% value with the actual common
966 // files path found in the process.
967 std::wstring commonProgramFilesPath;
968 std::vector<wchar_t> commonProgramFilesPathW6432;
969 DWORD bufferSize =
970 ::GetEnvironmentVariableW(L"CommonProgramW6432", nullptr, 0);
971 if (bufferSize) {
972 commonProgramFilesPathW6432.resize(bufferSize);
973 ::GetEnvironmentVariableW(L"CommonProgramW6432",
974 commonProgramFilesPathW6432.data(),
975 bufferSize);
976 commonProgramFilesPath =
977 std::wstring(commonProgramFilesPathW6432.data());
978 } else {
979 PWSTR path = nullptr;
980 HRESULT hres = SHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0,
981 nullptr, &path);
982 if (FAILED(hres) || !path) {
983 return;
984 }
985 commonProgramFilesPath =
986 static_cast<const wchar_t*>(nsDependentString(path).get());
987 ::CoTaskMemFree(path);
988 }
989 wstrpath.replace(commonProgramFilesOffset,
990 wcslen(L"%CommonProgramFiles%"), commonProgramFilesPath);
991 }
992
993 cachedPath.Assign(wstrpath.data());
994 Preferences::SetString(kOskPathPrefName, cachedPath);
995 }
996
997 const char16_t* cachedPathPtr;
998 cachedPath.GetData(&cachedPathPtr);
999 ShellExecuteW(nullptr, L"", char16ptr_t(cachedPathPtr), nullptr, nullptr,
1000 SW_SHOW);
1001 }
1002
1003 // Based on DismissVirtualKeyboard() in Chromium's base/win/win_util.cc.
1004 // static
DismissOnScreenKeyboard()1005 void IMEHandler::DismissOnScreenKeyboard() {
1006 // Dismiss the virtual keyboard if it's open
1007 HWND osk = GetOnScreenKeyboardWindow();
1008 if (osk) {
1009 ::PostMessage(osk, WM_SYSCOMMAND, SC_CLOSE, 0);
1010 }
1011 }
1012
1013 // static
GetOnScreenKeyboardWindow()1014 HWND IMEHandler::GetOnScreenKeyboardWindow() {
1015 const wchar_t kOSKClassName[] = L"IPTip_Main_Window";
1016 HWND osk = ::FindWindowW(kOSKClassName, nullptr);
1017 if (::IsWindow(osk) && ::IsWindowEnabled(osk) && ::IsWindowVisible(osk)) {
1018 return osk;
1019 }
1020 return nullptr;
1021 }
1022
1023 // static
SetCandidateWindow(nsWindow * aWindow,CANDIDATEFORM * aForm)1024 void IMEHandler::SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm) {
1025 if (!sPluginHasFocus) {
1026 return;
1027 }
1028
1029 IMMHandler::SetCandidateWindow(aWindow, aForm);
1030 }
1031
1032 // static
DefaultProcOfPluginEvent(nsWindow * aWindow,const NPEvent * aPluginEvent)1033 void IMEHandler::DefaultProcOfPluginEvent(nsWindow* aWindow,
1034 const NPEvent* aPluginEvent) {
1035 if (!sPluginHasFocus) {
1036 return;
1037 }
1038 IMMHandler::DefaultProcOfPluginEvent(aWindow, aPluginEvent);
1039 }
1040
1041 } // namespace widget
1042 } // namespace mozilla
1043