Files
palemoon27/widget/windows/WinIMEHandler.cpp
T
roytam1 d56ce7399f import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1232506: Make dom/devicestorage really work with e10s. r=alchen (7f95105c5e)
- Bug 1208944 - Part 1. Dispatch CompositionEvent to Plugin. r=masayuki (85c062b417)
- Bug 1208944 - Part 2-a. Handle CompositionEvent on plugin. r=masayuki (ea2cebfca9)
- Bug 1208944 - Part 2-b. Workaround for OSX. r=masayuki (ca401cbc04)
- Bug 1208944 - Part 3. Allow IME window messages on plugin process. r=jmathies (571fd75010)
- Bug 1192844: Accept 0xCC padding in WindowsDllDetourPatcher::CreateTrampoline. r=m_kato (b34b6173d3)
- Bug 1201205 part 1: Add an AutoVirtualProtect helper class to make the next patch easier. r=m_kato (b384bd2412)
- Bug 1201205 part 2: Restore protection on the nop space separately from the function. r=m_kato (a822b2414b)
- Bug 1208944 - Part 4. nsWindowsDllInterceptor supports IMM32 API hook. r=ehsan (9b409ff15a)
- Bug 1208944 - Part 5. Send PluginEvent to content process. r=jmathies (4ee0341190)
- Bug 1208944 - Part 6. Get vaild TextRangeArray on compositionupdate. r=masayuki (18f184931c)
- Bug 1208944 - Part 7. Don't post WM_IME_REQUEST on windowless plugin since we don't convert pointer over process. r=masayuki (6b1a9ce71f)
- Bug 1208944 - Part 8. Don't get selection on start compostion when plugin has foucs. r=masayuki (daf400620e)
- Add Telemetry for the drawing models that plugins use. (bug 1229961 part 1, r=aklotz,vladan) (c9645a68ec)
- Enable direct plugin drawing by default. (bug 1229961 part 2, r=aklotz) (080c9337cb)
- Bug 1213845 - enable osk support on windows 8, but hide it behind a preference, r=jaws (fcb8dfb10b)
- Bug 1192248 - Fix wchar_t/char16_t mismatch WinIMEHandler.cpp. r=masayuki (13e61f3f0c)
- Bug 1226145 - actually check whether the on-screen keyboard is up rather than relying on internal state, r=masayuki (da090b605d)
- Bug 1208944 - Part 9. Hook IMM32 APIs on plugin process. r=masayuki (f15d73721f)
- Bug 1208944 - Part 10-a. Call CallWindowProc when WidgetPluginEvent isn't handled by plugin. r=masayuki (c54eaa50ed)
- Bug 1208944 - Part 10-b. Call DefaultProc When CompositionEvent isn't handled correctly by plugin. r=masayuki (0e77eaa40f)
- Bug 1157046 - Remove ARRAY_LENGTH in favor of MOZ_ARRAY_LENGTH; r=Waldo (a7f8ce3684)
- Bug 1196834 - Add a test that confirms plugin windows are hidden after switching from a remote to local tab. r=roc (a09d4a32af)
- Bug 1208944 - Part 11. Add test. r=jmathies (ed8e4c87d2)
- Bug 1231378 - part 1 - Fix uninitialized members of classes in docshell/*, r=smaug (fcd7615d97)
- Bug 1231378 - part 2 - Fix uninitialized members of classes in dom/*,r=smaug (40b354438f)
- Bug 1231378 - part 3 - Fix uninitialized members of classes in module/libjar and mfbt, r=smaug (ee15d8739a)
- Bug 1231378 - part 4 - Fix uninitialized members of classes in netwerk/widget/storage/uriloader/memory/tools, r=smaug (6aec559dd8)
- Bug 1238082 - Fix mode lines in dom/ipc. r=baku (a49aa3555b)
- Bug 1024149 - Use element size for upload texture when using SVG image. r=jgilbert r=roc (531bdd2406)
- Bug 1233922 - Skip Camera preinit for browser elements. r=fabrice (cd8ffbb112)
- Bug 1159327 - Enable accessibility more broadly with e10s and add an e10s a11y blacklist for clients with known issues. r=tbsaunde (374f49a942)
- Bug 1198459 - Prevent accessibility from initializing in content processes when e10s is running. r=tbsaunde (33bdfccd56)
- Bug 1115956 - Improve notice string for when e10s was disabled for accessibility. r=mconley (e5ff276d9e)
- Bug 1172491 - Let e10s be enabled in safe mode. r=felipe (bd0a3fbbd5)
- Bug 1226487 - Allow e10s to run on Beta. r=mconley (05103c816d)
- Bug 634063 - Use gfxPrefs for some layers acceleration prefs. r=nical (9a20bcd8d2)
- Bug 1198459 - Add support for disabling e10s if a11y was run in the previous session, or run in a session over the previous week. r=felipe (7154a021bd)
- Bug 1171171 - Make sure the graphics preferences are initialized early enough. r=billm (34801701aa)
- Bug 1171171: Move nsAppRunner's gfxPrefs #include out of windows-specific section. r=milan a=KWierso (492fa00f99)
- Bug 1182048 - Part 1: Allow e10s to be enabled,r=vlad (628949ad83)
- Bug 1182048 - Part 2: Implement e10s support for WebVR,r=vlad (a77d280b48)
- Bug 1207221 - Do not prevent the system app from vibrating when it is hidden. r=bz r=dhylands (f3d2306f41)
- Bug 1233902 - Check the TP list in sendBeacon(). r=gcp (bc279755ab)
- Bug 1216207 - Modify navigator.hasFeature method to implement new (9b4571468e)
- Bug 1217187 - Modify navigator.hasFeature method to detect new (d9ea58e65c)
- Bug 1125477 - Part 1: Modify dom/tv mochitest in order to enable e10s test. r=seanlin (7b535fddaa)
- Bug 1125477 - Part 2: Modify permission check of TV Manager API in order to remove dom.testing.tv_enabled_for_hosted_apps. r=seanlin;r=smaug (5719fdf3a7)
- Bug 1125477 - Part3: Remove permission removing code. r=seanlin (614de5dfd1)
- Bug 1223672 - NSec package cannot use device storage API. r=kchen (f1fc6711ea)
- Bug 1224609 - Ensure the primary screen is always returned in RecvScreenForBrowser if all else fails. r=wmccloskey (d562935754)
- Bug 1180288 - Use native filepickers for Graphene. r=khuey (0b9c78ce13)
- Bug 1201394 - Use cached preferences value in ProcessPriorityManager. r=gsvelto (d211492014)
- Bug 1212833 - Delay the MemoryPressure when an application goes to background. r=gsvelto (ce33de4324)
- Bug 1186812 (part 1) - Replace nsBaseHashtable::EnumerateRead() calls in dom/{ipc,plugins}/. r=jimm. (c061bb9d1b)
- Bug 1212984 - HangMonitorChild should delete its Transport. r=billm (3331593b2d)
- Bug 1186812 (part 2) - Replace nsBaseHashtable::EnumerateRead() calls in dom/{ipc,plugins}/. r=jimm. (fdac3f52cb)
- Bug 1215239 - Do not set ownApp for a browser element. r=bholley (e3cc250c3f)
- Bug 1241278 - Change Notification.requestPermission() to return a promise. r=baku (b0a86eeeb5)
- Bug 1249102. Make overrides of WorkerRunnable::PostRun a bit more consistent. r=khuey (a23bf9468f)
- Bug 1209812 (part 6) - Convert all gfxImageFormat values to SurfaceFormat equivalents. r=jrmuizel. (9a5da3597f)
- Bug 1239225 - Remove unused args from TextureImage's constructor and related functions. r=mattwoodrow. (26ff005ecb)
- Bug 1240708 - Various trivial coverity warning fixes. r=kats (74a485c2b8)
- Bug 1240708 - Various trivial coverity warning fixes (part 2). r=kats (34bcd568dc)
- Bug 1219494 - Part 3 gfx/gl with gfxCrash. r=mchang (268968a3d9)
- Bug 1232456 - Create EGL surface through widget; r=snorp (13634bebc6)
- Bug 1232456 - Renew EGL surface using existing compositor widget; r=snorp (2649b088d3)
- Bug 1187322 - Don't require accelerated OpenGL contexts for BasicCompositor on OS X. r=jrmuizel (87f92af2c8)
- Bug 1238753 - Use StartRemoteDrawingInRegion on Mac. r=mattwoodrow (22870ed043)
- Bug 1238755 - Avoid a copy when uploading the BasicCompositor result to a texture. r=mattwoodrow (0b6663ab61)
- Bug 890156 - patch 0.2 - Remove the (unused) aRect parameter from nsBaseWidget::BaseCreate. r=kats (1120763eef)
- Bug 1241983 - Make DOM Identity observe inner-window-destroyed not dom-window-destroyed; r=ferjm (7cf4cf3211)
- Bug 1236282 - Clip color layer drawing in BasicCompositor so that unbounded operators don't erase stuff outside the layer. r=Bas (a7e73d1d8d)
- Bug 1238753 - Make BasicCompositor respect changes to mInvalidRegion through StartRemoteDrawingWithRegion properly. r=mattwoodrow (36e20bccce)
- Bug 1238753 - Don't skip the call to StartRemoteDrawing in from BasicCompositor::BeginFrame if the invalid region is empty. r=mattwoodrow (783b25775f)
- Bug 1239137 - Return early from BasicCompositor::DrawQuad if transformBounds is empty. r=mattwoodrow (0815cfc28a)
- Bug 1239530 (part 1) - Remove PuppetWidget::Scroll(), which is dead. r=kats. (63ab8c32b6)
- Bug 1239530 (part 2) - Use LayoutDevice coordinates in {Start,End}RemoteDrawingInRegion() and related functions. r=kats. (609001767d)
- Bug 1239537 - Remove Compositor::GetWidgetSize(), which is unused. r=mattwoodrow. (25732d37b1)
- Bug 1242293 - Don't call EndRemoteDrawingInRegion if StartRemoteDrawingInRegion returns a null DrawTarget. r=mstange (8e297c3b84)
- import changes from mozilla: Bug 1211642: Whitelist test plugin for async plugin init; r=jimm (f02deaaeb8) (6689ce99c8)
2023-08-15 16:18:14 +08:00

949 lines
31 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "WinIMEHandler.h"
#include "IMMHandler.h"
#include "mozilla/Preferences.h"
#include "nsWindowDefs.h"
#ifdef NS_ENABLE_TSF
#include "TSFTextStore.h"
#endif // #ifdef NS_ENABLE_TSF
#include "nsLookAndFeel.h"
#include "nsWindow.h"
#include "WinUtils.h"
#include "nsIWindowsRegKey.h"
#include "nsIWindowsUIUtils.h"
#include "shellapi.h"
#include "Shlobj.h"
#include "PowrProf.h"
#include "Setupapi.h"
#include "cfgmgr32.h"
const char* kOskPathPrefName = "ui.osk.on_screen_keyboard_path";
const char* kOskEnabled = "ui.osk.enabled";
const char* kOskDetectPhysicalKeyboard = "ui.osk.detect_physical_keyboard";
const char* kOskRequireWin10 = "ui.osk.require_win10";
const char* kOskDebugReason = "ui.osk.debug.keyboardDisplayReason";
namespace mozilla {
namespace widget {
/******************************************************************************
* IMEHandler
******************************************************************************/
bool IMEHandler::sPluginHasFocus = false;
InputContextAction::Cause IMEHandler::sLastContextActionCause =
InputContextAction::CAUSE_UNKNOWN;
#ifdef NS_ENABLE_TSF
bool IMEHandler::sIsInTSFMode = false;
bool IMEHandler::sIsIMMEnabled = true;
decltype(SetInputScopes)* IMEHandler::sSetInputScopes = nullptr;
#endif // #ifdef NS_ENABLE_TSF
static POWER_PLATFORM_ROLE sPowerPlatformRole = PlatformRoleUnspecified;
static bool sDeterminedPowerPlatformRole = false;
// static
void
IMEHandler::Initialize()
{
#ifdef NS_ENABLE_TSF
TSFTextStore::Initialize();
sIsInTSFMode = TSFTextStore::IsInTSFMode();
sIsIMMEnabled =
!sIsInTSFMode || Preferences::GetBool("intl.tsf.support_imm", true);
if (!sIsInTSFMode) {
// When full TSFTextStore is not available, try to use SetInputScopes API
// to enable at least InputScope. Use GET_MODULE_HANDLE_EX_FLAG_PIN to
// ensure that msctf.dll will not be unloaded.
HMODULE module = nullptr;
if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, L"msctf.dll",
&module)) {
sSetInputScopes = reinterpret_cast<decltype(SetInputScopes)*>(
GetProcAddress(module, "SetInputScopes"));
}
}
#endif // #ifdef NS_ENABLE_TSF
IMMHandler::Initialize();
}
// static
void
IMEHandler::Terminate()
{
#ifdef NS_ENABLE_TSF
if (sIsInTSFMode) {
TSFTextStore::Terminate();
sIsInTSFMode = false;
}
#endif // #ifdef NS_ENABLE_TSF
IMMHandler::Terminate();
}
// static
void*
IMEHandler::GetNativeData(nsWindow* aWindow, uint32_t aDataType)
{
if (aDataType == NS_RAW_NATIVE_IME_CONTEXT) {
#ifdef NS_ENABLE_TSF
if (IsTSFAvailable()) {
return TSFTextStore::GetThreadManager();
}
#endif // #ifdef NS_ENABLE_TSF
IMEContext context(aWindow);
if (context.IsValid()) {
return context.get();
}
// If IMC isn't associated with the window, IME is disabled on the window
// now. In such case, we should return default IMC instead.
const IMEContext& defaultIMC = aWindow->DefaultIMC();
if (defaultIMC.IsValid()) {
return defaultIMC.get();
}
// If there is no default IMC, we should return the pointer to the window
// since if we return nullptr, IMEStateManager cannot manage composition
// with TextComposition instance. This is possible if no IME is installed,
// but composition may occur with dead key sequence.
return aWindow;
}
#ifdef NS_ENABLE_TSF
void* result = TSFTextStore::GetNativeData(aDataType);
if (!result || !(*(static_cast<void**>(result)))) {
return nullptr;
}
// XXX During the TSF module test, sIsInTSFMode must be true. After that,
// the value should be restored but currently, there is no way for that.
// When the TSF test is enabled again, we need to fix this. Perhaps,
// sending a message can fix this.
sIsInTSFMode = true;
return result;
#else // #ifdef NS_ENABLE_TSF
return nullptr;
#endif // #ifdef NS_ENABLE_TSF #else
}
// static
bool
IMEHandler::ProcessRawKeyMessage(const MSG& aMsg)
{
#ifdef NS_ENABLE_TSF
if (IsTSFAvailable()) {
return TSFTextStore::ProcessRawKeyMessage(aMsg);
}
#endif // #ifdef NS_ENABLE_TSF
return false; // noting to do in IMM mode.
}
// static
bool
IMEHandler::ProcessMessage(nsWindow* aWindow, UINT aMessage,
WPARAM& aWParam, LPARAM& aLParam,
MSGResult& aResult)
{
#ifdef NS_ENABLE_TSF
if (IsTSFAvailable()) {
TSFTextStore::ProcessMessage(aWindow, aMessage, aWParam, aLParam, aResult);
if (aResult.mConsumed) {
return true;
}
// If we don't support IMM in TSF mode, we don't use IMMHandler.
if (!sIsIMMEnabled) {
return false;
}
// IME isn't implemented with IMM, IMMHandler shouldn't handle any
// messages.
if (!TSFTextStore::IsIMM_IME()) {
return false;
}
}
#endif // #ifdef NS_ENABLE_TSF
return IMMHandler::ProcessMessage(aWindow, aMessage, aWParam, aLParam,
aResult);
}
#ifdef NS_ENABLE_TSF
// static
bool
IMEHandler::IsIMMActive()
{
return TSFTextStore::IsIMM_IME();
}
#endif // #ifdef NS_ENABLE_TSF
// static
bool
IMEHandler::IsComposing()
{
#ifdef NS_ENABLE_TSF
if (IsTSFAvailable()) {
return TSFTextStore::IsComposing() || IMMHandler::IsComposing();
}
#endif // #ifdef NS_ENABLE_TSF
return IMMHandler::IsComposing();
}
// static
bool
IMEHandler::IsComposingOn(nsWindow* aWindow)
{
#ifdef NS_ENABLE_TSF
if (IsTSFAvailable()) {
return TSFTextStore::IsComposingOn(aWindow) ||
IMMHandler::IsComposingOn(aWindow);
}
#endif // #ifdef NS_ENABLE_TSF
return IMMHandler::IsComposingOn(aWindow);
}
// static
nsresult
IMEHandler::NotifyIME(nsWindow* aWindow,
const IMENotification& aIMENotification)
{
#ifdef NS_ENABLE_TSF
if (IsTSFAvailable()) {
switch (aIMENotification.mMessage) {
case NOTIFY_IME_OF_SELECTION_CHANGE: {
nsresult rv = TSFTextStore::OnSelectionChange(aIMENotification);
// If IMM IME is active, we need to notify IMMHandler of updating
// composition change. It will adjust candidate window position or
// composition window position.
bool isIMMActive = IsIMMActive();
if (isIMMActive) {
IMMHandler::OnUpdateComposition(aWindow);
}
IMMHandler::OnSelectionChange(aWindow, aIMENotification, isIMMActive);
return rv;
}
case NOTIFY_IME_OF_COMPOSITION_UPDATE:
// If IMM IME is active, we need to notify IMMHandler of updating
// composition change. It will adjust candidate window position or
// composition window position.
if (IsIMMActive()) {
IMMHandler::OnUpdateComposition(aWindow);
} else {
TSFTextStore::OnUpdateComposition();
}
return NS_OK;
case NOTIFY_IME_OF_TEXT_CHANGE:
return TSFTextStore::OnTextChange(aIMENotification);
case NOTIFY_IME_OF_FOCUS: {
IMMHandler::OnFocusChange(true, aWindow);
nsresult rv =
TSFTextStore::OnFocusChange(true, aWindow,
aWindow->GetInputContext());
IMEHandler::MaybeShowOnScreenKeyboard();
return rv;
}
case NOTIFY_IME_OF_BLUR:
IMEHandler::MaybeDismissOnScreenKeyboard();
IMMHandler::OnFocusChange(false, aWindow);
return TSFTextStore::OnFocusChange(false, aWindow,
aWindow->GetInputContext());
case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
// If IMM IME is active, we should send a mouse button event via IMM.
if (IsIMMActive()) {
return IMMHandler::OnMouseButtonEvent(aWindow, aIMENotification);
}
return TSFTextStore::OnMouseButtonEvent(aIMENotification);
case REQUEST_TO_COMMIT_COMPOSITION:
if (TSFTextStore::IsComposingOn(aWindow)) {
TSFTextStore::CommitComposition(false);
} else if (IsIMMActive()) {
IMMHandler::CommitComposition(aWindow);
}
return NS_OK;
case REQUEST_TO_CANCEL_COMPOSITION:
if (TSFTextStore::IsComposingOn(aWindow)) {
TSFTextStore::CommitComposition(true);
} else if (IsIMMActive()) {
IMMHandler::CancelComposition(aWindow);
}
return NS_OK;
case NOTIFY_IME_OF_POSITION_CHANGE:
return TSFTextStore::OnLayoutChange();
default:
return NS_ERROR_NOT_IMPLEMENTED;
}
}
#endif //NS_ENABLE_TSF
switch (aIMENotification.mMessage) {
case REQUEST_TO_COMMIT_COMPOSITION:
IMMHandler::CommitComposition(aWindow);
return NS_OK;
case REQUEST_TO_CANCEL_COMPOSITION:
IMMHandler::CancelComposition(aWindow);
return NS_OK;
case NOTIFY_IME_OF_POSITION_CHANGE:
case NOTIFY_IME_OF_COMPOSITION_UPDATE:
IMMHandler::OnUpdateComposition(aWindow);
return NS_OK;
case NOTIFY_IME_OF_SELECTION_CHANGE:
IMMHandler::OnSelectionChange(aWindow, aIMENotification, true);
return NS_OK;
case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
return IMMHandler::OnMouseButtonEvent(aWindow, aIMENotification);
case NOTIFY_IME_OF_FOCUS:
IMMHandler::OnFocusChange(true, aWindow);
IMEHandler::MaybeShowOnScreenKeyboard();
return NS_OK;
case NOTIFY_IME_OF_BLUR:
IMEHandler::MaybeDismissOnScreenKeyboard();
IMMHandler::OnFocusChange(false, aWindow);
#ifdef NS_ENABLE_TSF
// If a plugin gets focus while TSF has focus, we need to notify TSF of
// the blur.
if (TSFTextStore::ThinksHavingFocus()) {
return TSFTextStore::OnFocusChange(false, aWindow,
aWindow->GetInputContext());
}
#endif //NS_ENABLE_TSF
return NS_OK;
default:
return NS_ERROR_NOT_IMPLEMENTED;
}
}
// static
nsIMEUpdatePreference
IMEHandler::GetUpdatePreference()
{
// While a plugin has focus, neither TSFTextStore nor IMMHandler needs
// notifications.
if (sPluginHasFocus) {
return nsIMEUpdatePreference();
}
#ifdef NS_ENABLE_TSF
if (IsTSFAvailable()) {
return TSFTextStore::GetIMEUpdatePreference();
}
#endif //NS_ENABLE_TSF
return IMMHandler::GetIMEUpdatePreference();
}
// static
bool
IMEHandler::GetOpenState(nsWindow* aWindow)
{
#ifdef NS_ENABLE_TSF
if (IsTSFAvailable() && !IsIMMActive()) {
return TSFTextStore::GetIMEOpenState();
}
#endif //NS_ENABLE_TSF
IMEContext context(aWindow);
return context.GetOpenState();
}
// static
void
IMEHandler::OnDestroyWindow(nsWindow* aWindow)
{
#ifdef NS_ENABLE_TSF
// We need to do nothing here for TSF. Just restore the default context
// if it's been disassociated.
if (!sIsInTSFMode) {
// MSDN says we need to set IS_DEFAULT to avoid memory leak when we use
// SetInputScopes API. Use an empty string to do this.
SetInputScopeForIMM32(aWindow, EmptyString());
}
#endif // #ifdef NS_ENABLE_TSF
AssociateIMEContext(aWindow, true);
}
// static
void
IMEHandler::SetInputContext(nsWindow* aWindow,
InputContext& aInputContext,
const InputContextAction& aAction)
{
sLastContextActionCause = aAction.mCause;
// FYI: If there is no composition, this call will do nothing.
NotifyIME(aWindow, IMENotification(REQUEST_TO_COMMIT_COMPOSITION));
const InputContext& oldInputContext = aWindow->GetInputContext();
// Assume that SetInputContext() is called only when aWindow has focus.
sPluginHasFocus = (aInputContext.mIMEState.mEnabled == IMEState::PLUGIN);
bool enable = WinUtils::IsIMEEnabled(aInputContext);
bool adjustOpenState = (enable &&
aInputContext.mIMEState.mOpen != IMEState::DONT_CHANGE_OPEN_STATE);
bool open = (adjustOpenState &&
aInputContext.mIMEState.mOpen == IMEState::OPEN);
#ifdef NS_ENABLE_TSF
// Note that even while a plugin has focus, we need to notify TSF of that.
if (sIsInTSFMode) {
TSFTextStore::SetInputContext(aWindow, aInputContext, aAction);
if (IsTSFAvailable()) {
if (sIsIMMEnabled) {
// Associate IME context for IMM-IMEs.
AssociateIMEContext(aWindow, enable);
} else if (oldInputContext.mIMEState.mEnabled == IMEState::PLUGIN) {
// Disassociate the IME context from the window when plugin loses focus
// in pure TSF mode.
AssociateIMEContext(aWindow, false);
}
if (adjustOpenState) {
TSFTextStore::SetIMEOpenState(open);
}
return;
}
} else {
// Set at least InputScope even when TextStore is not available.
SetInputScopeForIMM32(aWindow, aInputContext.mHTMLInputType);
}
#endif // #ifdef NS_ENABLE_TSF
AssociateIMEContext(aWindow, enable);
IMEContext context(aWindow);
if (adjustOpenState) {
context.SetOpenState(open);
}
}
// static
void
IMEHandler::AssociateIMEContext(nsWindow* aWindow, bool aEnable)
{
IMEContext context(aWindow);
if (aEnable) {
context.AssociateDefaultContext();
return;
}
// Don't disassociate the context after the window is destroyed.
if (aWindow->Destroyed()) {
return;
}
context.Disassociate();
}
// static
void
IMEHandler::InitInputContext(nsWindow* aWindow, InputContext& aInputContext)
{
// For a11y, the default enabled state should be 'enabled'.
aInputContext.mIMEState.mEnabled = IMEState::ENABLED;
#ifdef NS_ENABLE_TSF
if (sIsInTSFMode) {
TSFTextStore::SetInputContext(aWindow, aInputContext,
InputContextAction(InputContextAction::CAUSE_UNKNOWN,
InputContextAction::GOT_FOCUS));
// IME context isn't necessary in pure TSF mode.
if (!sIsIMMEnabled) {
AssociateIMEContext(aWindow, false);
}
return;
}
#endif // #ifdef NS_ENABLE_TSF
#ifdef DEBUG
// NOTE: IMC may be null if IMM module isn't installed.
IMEContext context(aWindow);
MOZ_ASSERT(context.IsValid() || !CurrentKeyboardLayoutHasIME());
#endif // #ifdef DEBUG
}
#ifdef DEBUG
// static
bool
IMEHandler::CurrentKeyboardLayoutHasIME()
{
#ifdef NS_ENABLE_TSF
if (sIsInTSFMode) {
return TSFTextStore::CurrentKeyboardLayoutHasIME();
}
#endif // #ifdef NS_ENABLE_TSF
return IMMHandler::IsIMEAvailable();
}
#endif // #ifdef DEBUG
// static
void
IMEHandler::SetInputScopeForIMM32(nsWindow* aWindow,
const nsAString& aHTMLInputType)
{
if (sIsInTSFMode || !sSetInputScopes || aWindow->Destroyed()) {
return;
}
UINT arraySize = 0;
const InputScope* scopes = nullptr;
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html
if (aHTMLInputType.IsEmpty() || aHTMLInputType.EqualsLiteral("text")) {
static const InputScope inputScopes[] = { IS_DEFAULT };
scopes = &inputScopes[0];
arraySize = ArrayLength(inputScopes);
} else if (aHTMLInputType.EqualsLiteral("url")) {
static const InputScope inputScopes[] = { IS_URL };
scopes = &inputScopes[0];
arraySize = ArrayLength(inputScopes);
} else if (aHTMLInputType.EqualsLiteral("search")) {
static const InputScope inputScopes[] = { IS_SEARCH };
scopes = &inputScopes[0];
arraySize = ArrayLength(inputScopes);
} else if (aHTMLInputType.EqualsLiteral("email")) {
static const InputScope inputScopes[] = { IS_EMAIL_SMTPEMAILADDRESS };
scopes = &inputScopes[0];
arraySize = ArrayLength(inputScopes);
} else if (aHTMLInputType.EqualsLiteral("password")) {
static const InputScope inputScopes[] = { IS_PASSWORD };
scopes = &inputScopes[0];
arraySize = ArrayLength(inputScopes);
} else if (aHTMLInputType.EqualsLiteral("datetime") ||
aHTMLInputType.EqualsLiteral("datetime-local")) {
static const InputScope inputScopes[] = {
IS_DATE_FULLDATE, IS_TIME_FULLTIME };
scopes = &inputScopes[0];
arraySize = ArrayLength(inputScopes);
} else if (aHTMLInputType.EqualsLiteral("date") ||
aHTMLInputType.EqualsLiteral("month") ||
aHTMLInputType.EqualsLiteral("week")) {
static const InputScope inputScopes[] = { IS_DATE_FULLDATE };
scopes = &inputScopes[0];
arraySize = ArrayLength(inputScopes);
} else if (aHTMLInputType.EqualsLiteral("time")) {
static const InputScope inputScopes[] = { IS_TIME_FULLTIME };
scopes = &inputScopes[0];
arraySize = ArrayLength(inputScopes);
} else if (aHTMLInputType.EqualsLiteral("tel")) {
static const InputScope inputScopes[] = {
IS_TELEPHONE_FULLTELEPHONENUMBER, IS_TELEPHONE_LOCALNUMBER };
scopes = &inputScopes[0];
arraySize = ArrayLength(inputScopes);
} else if (aHTMLInputType.EqualsLiteral("number")) {
static const InputScope inputScopes[] = { IS_NUMBER };
scopes = &inputScopes[0];
arraySize = ArrayLength(inputScopes);
}
if (scopes && arraySize > 0) {
sSetInputScopes(aWindow->GetWindowHandle(), scopes, arraySize, nullptr, 0,
nullptr, nullptr);
}
}
// static
void
IMEHandler::MaybeShowOnScreenKeyboard()
{
if (sPluginHasFocus ||
!IsWin8OrLater() ||
!Preferences::GetBool(kOskEnabled, true) ||
GetOnScreenKeyboardWindow() ||
IMEHandler::IsKeyboardPresentOnSlate()) {
return;
}
// On Windows 10 we require tablet mode, unless the user has set the relevant
// Windows setting to enable the on-screen keyboard in desktop mode.
// We might be disabled specifically on Win8(.1), so we check that afterwards.
if (IsWin10OrLater()) {
if (!IsInTabletMode() && !AutoInvokeOnScreenKeyboardInDesktopMode()) {
return;
}
}
else if (Preferences::GetBool(kOskRequireWin10, true)) {
return;
}
IMEHandler::ShowOnScreenKeyboard();
}
// static
void
IMEHandler::MaybeDismissOnScreenKeyboard()
{
if (sPluginHasFocus ||
!IsWin8OrLater()) {
return;
}
IMEHandler::DismissOnScreenKeyboard();
}
// static
bool
IMEHandler::WStringStartsWithCaseInsensitive(const std::wstring& aHaystack,
const std::wstring& aNeedle)
{
std::wstring lowerCaseHaystack(aHaystack);
std::wstring lowerCaseNeedle(aNeedle);
std::transform(lowerCaseHaystack.begin(), lowerCaseHaystack.end(),
lowerCaseHaystack.begin(), ::tolower);
std::transform(lowerCaseNeedle.begin(), lowerCaseNeedle.end(),
lowerCaseNeedle.begin(), ::tolower);
return wcsstr(lowerCaseHaystack.c_str(),
lowerCaseNeedle.c_str()) == lowerCaseHaystack.c_str();
}
// Returns true if a physical keyboard is detected on Windows 8 and up.
// Uses the Setup APIs to enumerate the attached keyboards and returns true
// if the keyboard count is 1 or more. While this will work in most cases
// it won't work if there are devices which expose keyboard interfaces which
// are attached to the machine.
// Based on IsKeyboardPresentOnSlate() in Chromium's base/win/win_util.cc.
// static
bool
IMEHandler::IsKeyboardPresentOnSlate()
{
// This function is only supported for Windows 8 and up.
if (!IsWin8OrLater()) {
Preferences::SetString(kOskDebugReason, L"IKPOS: Requires Win8+.");
return true;
}
if (!Preferences::GetBool(kOskDetectPhysicalKeyboard, true)) {
Preferences::SetString(kOskDebugReason, L"IKPOS: Detection disabled.");
return false;
}
// This function should be only invoked for machines with touch screens.
if ((::GetSystemMetrics(SM_DIGITIZER) & NID_INTEGRATED_TOUCH)
!= NID_INTEGRATED_TOUCH) {
Preferences::SetString(kOskDebugReason,
L"IKPOS: Touch screen not found.");
return true;
}
// If the device is docked, the user is treating the device as a PC.
if (::GetSystemMetrics(SM_SYSTEMDOCKED) != 0) {
Preferences::SetString(kOskDebugReason, L"IKPOS: System docked.");
return true;
}
// To determine whether a keyboard is present on the device, we do the
// following:-
// 1. Check whether the device supports auto rotation. If it does then
// it possibly supports flipping from laptop to slate mode. If it
// does not support auto rotation, then we assume it is a desktop
// or a normal laptop and assume that there is a keyboard.
// 2. If the device supports auto rotation, then we get its platform role
// and check the system metric SM_CONVERTIBLESLATEMODE to see if it is
// being used in slate mode. If yes then we return false here to ensure
// that the OSK is displayed.
// 3. If step 1 and 2 fail then we check attached keyboards and return true
// if we find ACPI\* or HID\VID* keyboards.
typedef BOOL (WINAPI* GetAutoRotationState)(PAR_STATE state);
GetAutoRotationState get_rotation_state =
reinterpret_cast<GetAutoRotationState>(::GetProcAddress(
::GetModuleHandleW(L"user32.dll"), "GetAutoRotationState"));
if (get_rotation_state) {
AR_STATE auto_rotation_state = AR_ENABLED;
get_rotation_state(&auto_rotation_state);
// If there is no auto rotation sensor or rotation is not supported in
// the current configuration, then we can assume that this is a desktop
// or a traditional laptop.
if (auto_rotation_state & AR_NOSENSOR) {
Preferences::SetString(kOskDebugReason,
L"IKPOS: Rotation sensor not found.");
return true;
} else if (auto_rotation_state & AR_NOT_SUPPORTED) {
Preferences::SetString(kOskDebugReason,
L"IKPOS: Auto-rotation not supported.");
return true;
}
}
// Check if the device is being used as a laptop or a tablet. This can be
// checked by first checking the role of the device and then the
// corresponding system metric (SM_CONVERTIBLESLATEMODE). If it is being
// used as a tablet then we want the OSK to show up.
typedef POWER_PLATFORM_ROLE (WINAPI* PowerDeterminePlatformRoleEx)(ULONG Version);
if (!sDeterminedPowerPlatformRole) {
sDeterminedPowerPlatformRole = true;
PowerDeterminePlatformRoleEx power_determine_platform_role =
reinterpret_cast<PowerDeterminePlatformRoleEx>(::GetProcAddress(
::LoadLibraryW(L"PowrProf.dll"), "PowerDeterminePlatformRoleEx"));
if (power_determine_platform_role) {
sPowerPlatformRole = power_determine_platform_role(POWER_PLATFORM_ROLE_V2);
} else {
sPowerPlatformRole = PlatformRoleUnspecified;
}
}
// If this is not a mobile or slate (tablet) device, we don't need to
// do anything here.
if (sPowerPlatformRole != PlatformRoleMobile &&
sPowerPlatformRole != PlatformRoleSlate) {
Preferences::SetString(kOskDebugReason, L"IKPOS: PlatformRole is neither Mobile nor Slate.");
return true;
}
// Likewise, if the tablet/mobile isn't in "slate" mode, we should bail:
if (::GetSystemMetrics(SM_CONVERTIBLESLATEMODE) != 0) {
Preferences::SetString(kOskDebugReason, L"IKPOS: ConvertibleSlateMode is non-zero");
return true;
}
// Before we check for a keyboard, we should check if the last input was touch,
// in which case we ignore whether or not a keyboard is present:
if (sLastContextActionCause == InputContextAction::CAUSE_TOUCH) {
Preferences::SetString(kOskDebugReason,
L"IKPOS: Used touch to focus control, ignoring keyboard presence");
return false;
}
const GUID KEYBOARD_CLASS_GUID =
{ 0x4D36E96B, 0xE325, 0x11CE,
{ 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18 } };
// Query for all the keyboard devices.
HDEVINFO device_info =
::SetupDiGetClassDevs(&KEYBOARD_CLASS_GUID, nullptr,
nullptr, DIGCF_PRESENT);
if (device_info == INVALID_HANDLE_VALUE) {
Preferences::SetString(kOskDebugReason, L"IKPOS: No keyboard info.");
return false;
}
// Enumerate all keyboards and look for ACPI\PNP and HID\VID devices. If
// the count is more than 1 we assume that a keyboard is present. This is
// under the assumption that there will always be one keyboard device.
for (DWORD i = 0;; ++i) {
SP_DEVINFO_DATA device_info_data = { 0 };
device_info_data.cbSize = sizeof(device_info_data);
if (!::SetupDiEnumDeviceInfo(device_info, i, &device_info_data)) {
break;
}
// Get the device ID.
wchar_t device_id[MAX_DEVICE_ID_LEN];
CONFIGRET status = ::CM_Get_Device_ID(device_info_data.DevInst,
device_id,
MAX_DEVICE_ID_LEN,
0);
if (status == CR_SUCCESS) {
// To reduce the scope of the hack we only look for ACPI and HID\\VID
// prefixes in the keyboard device ids.
if (IMEHandler::WStringStartsWithCaseInsensitive(device_id,
L"ACPI") ||
IMEHandler::WStringStartsWithCaseInsensitive(device_id,
L"HID\\VID")) {
// The heuristic we are using is to check the count of keyboards and
// return true if the API's report one or more keyboards. Please note
// that this will break for non keyboard devices which expose a
// keyboard PDO.
Preferences::SetString(kOskDebugReason,
L"IKPOS: Keyboard presence confirmed.");
return true;
}
}
}
Preferences::SetString(kOskDebugReason,
L"IKPOS: Lack of keyboard confirmed.");
return false;
}
// static
bool
IMEHandler::IsInTabletMode()
{
nsCOMPtr<nsIWindowsUIUtils>
uiUtils(do_GetService("@mozilla.org/windows-ui-utils;1"));
if (NS_WARN_IF(!uiUtils)) {
Preferences::SetString(kOskDebugReason,
L"IITM: nsIWindowsUIUtils not available.");
return false;
}
bool isInTabletMode = false;
uiUtils->GetInTabletMode(&isInTabletMode);
if (isInTabletMode) {
Preferences::SetString(kOskDebugReason, L"IITM: GetInTabletMode=true.");
} else {
Preferences::SetString(kOskDebugReason, L"IITM: GetInTabletMode=false.");
}
return isInTabletMode;
}
// static
bool
IMEHandler::AutoInvokeOnScreenKeyboardInDesktopMode()
{
nsresult rv;
nsCOMPtr<nsIWindowsRegKey> regKey
(do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv));
if (NS_WARN_IF(NS_FAILED(rv))) {
Preferences::SetString(kOskDebugReason, L"AIOSKIDM: "
L"nsIWindowsRegKey not available");
return false;
}
rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
NS_LITERAL_STRING("SOFTWARE\\Microsoft\\TabletTip\\1.7"),
nsIWindowsRegKey::ACCESS_QUERY_VALUE);
if (NS_FAILED(rv)) {
Preferences::SetString(kOskDebugReason,
L"AIOSKIDM: failed opening regkey.");
return false;
}
// EnableDesktopModeAutoInvoke is an opt-in option from the Windows
// Settings to "Automatically show the touch keyboard in windowed apps
// when there's no keyboard attached to your device." If the user has
// opted-in to this behavior, the tablet-mode requirement is skipped.
uint32_t value;
rv = regKey->ReadIntValue(NS_LITERAL_STRING("EnableDesktopModeAutoInvoke"),
&value);
if (NS_FAILED(rv)) {
Preferences::SetString(kOskDebugReason,
L"AIOSKIDM: failed reading value of regkey.");
return false;
}
if (!!value) {
Preferences::SetString(kOskDebugReason, L"AIOSKIDM: regkey value=true.");
} else {
Preferences::SetString(kOskDebugReason, L"AIOSKIDM: regkey value=false.");
}
return !!value;
}
// Based on DisplayVirtualKeyboard() in Chromium's base/win/win_util.cc.
// static
void
IMEHandler::ShowOnScreenKeyboard()
{
nsAutoString cachedPath;
nsresult result = Preferences::GetString(kOskPathPrefName, &cachedPath);
if (NS_FAILED(result) || cachedPath.IsEmpty()) {
wchar_t path[MAX_PATH];
// The path to TabTip.exe is defined at the following registry key.
// This is pulled out of the 64-bit registry hive directly.
const wchar_t kRegKeyName[] =
L"Software\\Classes\\CLSID\\"
L"{054AAE20-4BEA-4347-8A35-64A533254A9D}\\LocalServer32";
if (!WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE,
kRegKeyName,
nullptr,
path,
sizeof path)) {
return;
}
std::wstring wstrpath(path);
// The path provided by the registry will often contain
// %CommonProgramFiles%, which will need to be replaced if it is present.
size_t commonProgramFilesOffset = wstrpath.find(L"%CommonProgramFiles%");
if (commonProgramFilesOffset != std::wstring::npos) {
// The path read from the registry contains the %CommonProgramFiles%
// environment variable prefix. On 64 bit Windows the
// SHGetKnownFolderPath function returns the common program files path
// with the X86 suffix for the FOLDERID_ProgramFilesCommon value.
// To get the correct path to TabTip.exe we first read the environment
// variable CommonProgramW6432 which points to the desired common
// files path. Failing that we fallback to the SHGetKnownFolderPath API.
// We then replace the %CommonProgramFiles% value with the actual common
// files path found in the process.
std::wstring commonProgramFilesPath;
std::vector<wchar_t> commonProgramFilesPathW6432;
DWORD bufferSize = ::GetEnvironmentVariableW(L"CommonProgramW6432",
nullptr, 0);
if (bufferSize) {
commonProgramFilesPathW6432.resize(bufferSize);
::GetEnvironmentVariableW(L"CommonProgramW6432",
commonProgramFilesPathW6432.data(),
bufferSize);
commonProgramFilesPath =
std::wstring(commonProgramFilesPathW6432.data());
} else {
PWSTR path = nullptr;
HRESULT hres =
WinUtils::SHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0,
nullptr, &path);
if (FAILED(hres) || !path) {
return;
}
commonProgramFilesPath =
static_cast<const wchar_t*>(nsDependentString(path).get());
::CoTaskMemFree(path);
}
wstrpath.replace(commonProgramFilesOffset,
wcslen(L"%CommonProgramFiles%"),
commonProgramFilesPath);
}
cachedPath.Assign(wstrpath.data());
Preferences::SetString(kOskPathPrefName, cachedPath);
}
const char16_t *cachedPathPtr;
cachedPath.GetData(&cachedPathPtr);
ShellExecuteW(nullptr,
L"",
char16ptr_t(cachedPathPtr),
nullptr,
nullptr,
SW_SHOW);
}
// Based on DismissVirtualKeyboard() in Chromium's base/win/win_util.cc.
// static
void
IMEHandler::DismissOnScreenKeyboard()
{
// Dismiss the virtual keyboard if it's open
HWND osk = GetOnScreenKeyboardWindow();
if (osk) {
::PostMessage(osk, WM_SYSCOMMAND, SC_CLOSE, 0);
}
}
// static
HWND
IMEHandler::GetOnScreenKeyboardWindow()
{
const wchar_t kOSKClassName[] = L"IPTip_Main_Window";
HWND osk = ::FindWindowW(kOSKClassName, nullptr);
if (::IsWindow(osk) && ::IsWindowEnabled(osk)) {
return osk;
}
return nullptr;
}
// static
void
IMEHandler::SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm)
{
if (!sPluginHasFocus) {
return;
}
IMMHandler::SetCandidateWindow(aWindow, aForm);
}
// static
void
IMEHandler::DefaultProcOfPluginEvent(nsWindow* aWindow,
const NPEvent* aPluginEvent)
{
if (!sPluginHasFocus) {
return;
}
IMMHandler::DefaultProcOfPluginEvent(aWindow, aPluginEvent);
}
} // namespace widget
} // namespace mozilla