mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
975 lines
32 KiB
C++
975 lines
32 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
|
|
******************************************************************************/
|
|
|
|
nsWindow* IMEHandler::sFocusedWindow = nullptr;
|
|
InputContextAction::Cause IMEHandler::sLastContextActionCause =
|
|
InputContextAction::CAUSE_UNKNOWN;
|
|
bool IMEHandler::sPluginHasFocus = false;
|
|
|
|
#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: {
|
|
sFocusedWindow = aWindow;
|
|
IMMHandler::OnFocusChange(true, aWindow);
|
|
nsresult rv =
|
|
TSFTextStore::OnFocusChange(true, aWindow,
|
|
aWindow->GetInputContext());
|
|
IMEHandler::MaybeShowOnScreenKeyboard();
|
|
return rv;
|
|
}
|
|
case NOTIFY_IME_OF_BLUR:
|
|
sFocusedWindow = nullptr;
|
|
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()) {
|
|
if (!sIsIMMEnabled) {
|
|
return TSFTextStore::GetIMEUpdatePreference();
|
|
}
|
|
// Even if TSF is available, the active IME may be an IMM-IME.
|
|
// Unfortunately, changing the result of GetUpdatePreference() while an
|
|
// editor has focus isn't supported by IMEContentObserver nor
|
|
// ContentCacheInParent. Therefore, we need to request whole notifications
|
|
// which are necessary either IMMHandler or TSFTextStore.
|
|
return IMMHandler::GetIMEUpdatePreference() |
|
|
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)
|
|
{
|
|
// When focus is in remote process, but the window is being destroyed, we
|
|
// need to clean up TSFTextStore here since NOTIFY_IME_OF_BLUR won't reach
|
|
// here because TabParent already lost the reference to the nsWindow when
|
|
// it receives from the remote process.
|
|
if (sFocusedWindow == aWindow) {
|
|
NS_ASSERTION(aWindow->GetInputContext().IsOriginContentProcess(),
|
|
"input context of focused widget should be set from a remote process");
|
|
NotifyIME(aWindow, IMENotification(NOTIFY_IME_OF_BLUR));
|
|
}
|
|
|
|
#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);
|
|
|
|
if (aAction.UserMightRequestOpenVKB()) {
|
|
IMEHandler::MaybeShowOnScreenKeyboard();
|
|
}
|
|
|
|
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
|