Bug 1007063 - Show virtual (on-screen) keyboard in Windows 8 and higher when text fields are focused if physical keyboards are not present. r=jimm r=masayuki r=m_kato

This commit is contained in:
Jared Wein 2015-08-05 10:38:51 -04:00
parent ef1b5ef89b
commit 1e58f6dddf
6 changed files with 343 additions and 4 deletions

View File

@ -3225,6 +3225,18 @@ pref("ui.window_class_override", "");
// page back/forward actions, or if pinch-to-zoom does not work.
pref("ui.elantech_gesture_hacks.enabled", -1);
// Show the Windows on-screen keyboard (osk.exe) when a text field is focused.
#ifdef RELEASE_BUILD
pref("ui.osk.enabled", false);
#else
pref("ui.osk.enabled", true);
#endif
// Only show the on-screen keyboard if there are no physical keyboards attached
// to the device.
pref("ui.osk.detect_physical_keyboard", true);
// Path to TabTip.exe on local machine. Cached for performance reasons.
pref("ui.osk.on_screen_keyboard_path", "");
# XP_WIN
#endif

View File

@ -18,6 +18,7 @@ def Libxul(name):
DELAYLOAD_DLLS += [
'comdlg32.dll',
'dbghelp.dll',
'PowrProf.dll',
'psapi.dll',
'rasapi32.dll',
'rasdlg.dll',

View File

@ -13,9 +13,20 @@
#include "TSFTextStore.h"
#endif // #ifdef NS_ENABLE_TSF
#include "nsLookAndFeel.h"
#include "nsWindow.h"
#include "WinUtils.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";
namespace mozilla {
namespace widget {
@ -27,6 +38,7 @@ namespace widget {
bool IMEHandler::sIsInTSFMode = false;
bool IMEHandler::sIsIMMEnabled = true;
bool IMEHandler::sPluginHasFocus = false;
bool IMEHandler::sShowingOnScreenKeyboard = false;
decltype(SetInputScopes)* IMEHandler::sSetInputScopes = nullptr;
#endif // #ifdef NS_ENABLE_TSF
@ -197,11 +209,16 @@ IMEHandler::NotifyIME(nsWindow* aWindow,
return NS_OK;
case NOTIFY_IME_OF_TEXT_CHANGE:
return TSFTextStore::OnTextChange(aIMENotification);
case NOTIFY_IME_OF_FOCUS:
case NOTIFY_IME_OF_FOCUS: {
IMMHandler::OnFocusChange(true, aWindow);
return TSFTextStore::OnFocusChange(true, aWindow,
aWindow->GetInputContext());
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());
@ -251,8 +268,10 @@ IMEHandler::NotifyIME(nsWindow* aWindow,
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
@ -502,5 +521,272 @@ IMEHandler::SetInputScopeForIMM32(nsWindow* aWindow,
}
}
// static
void
IMEHandler::MaybeShowOnScreenKeyboard()
{
if (sPluginHasFocus) {
return;
}
IMEHandler::ShowOnScreenKeyboard();
}
// static
void
IMEHandler::MaybeDismissOnScreenKeyboard()
{
if (sPluginHasFocus) {
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()) {
return true;
}
if (!Preferences::GetBool(kOskDetectPhysicalKeyboard, true)) {
// Detection for physical keyboard has been disabled for testing.
return false;
}
// This function should be only invoked for machines with touch screens.
if ((::GetSystemMetrics(SM_DIGITIZER) & NID_INTEGRATED_TOUCH)
!= NID_INTEGRATED_TOUCH) {
return true;
}
// If the device is docked, the user is treating the device as a PC.
if (::GetSystemMetrics(SM_SYSTEMDOCKED) != 0) {
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 ((auto_rotation_state & AR_NOSENSOR) ||
(auto_rotation_state & AR_NOT_SUPPORTED)) {
// 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.
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* PowerDeterminePlatformRole)();
PowerDeterminePlatformRole power_determine_platform_role =
reinterpret_cast<PowerDeterminePlatformRole>(::GetProcAddress(
::LoadLibraryW(L"PowrProf.dll"), "PowerDeterminePlatformRole"));
if (power_determine_platform_role) {
POWER_PLATFORM_ROLE role = power_determine_platform_role();
if (((role == PlatformRoleMobile) || (role == PlatformRoleSlate)) &&
(::GetSystemMetrics(SM_CONVERTIBLESLATEMODE) == 0)) {
return false;
}
}
const GUID KEYBOARD_CLASS_GUID =
{ 0x4D36E96B, 0xE325, 0x11CE,
{ 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18 } };
bool result = false;
// Query for all the keyboard devices.
HDEVINFO device_info =
::SetupDiGetClassDevs(&KEYBOARD_CLASS_GUID, nullptr,
nullptr, DIGCF_PRESENT);
if (device_info == INVALID_HANDLE_VALUE) {
return result;
}
// 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.
result = true;
}
}
}
return result;
}
// Based on DisplayVirtualKeyboard() in Chromium's base/win/win_util.cc.
// static
void
IMEHandler::ShowOnScreenKeyboard()
{
if (!IsWin8OrLater() ||
!Preferences::GetBool(kOskEnabled, true) ||
sShowingOnScreenKeyboard ||
IMEHandler::IsKeyboardPresentOnSlate()) {
return;
}
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,
0,
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 = ::SHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0,
nullptr, &path);
if (FAILED(hres) || !path) {
return;
}
commonProgramFilesPath = nsDependentString(path).get();
::CoTaskMemFree(path);
}
wstrpath.replace(commonProgramFilesOffset,
wcslen(L"%CommonProgramFiles%"),
commonProgramFilesPath);
}
cachedPath.Assign(wstrpath.data());
Preferences::SetString(kOskPathPrefName, cachedPath);
}
LPCWSTR cachedPathPtr;
cachedPath.GetData(&cachedPathPtr);
HINSTANCE ret = ::ShellExecuteW(nullptr,
L"",
cachedPathPtr,
nullptr,
nullptr,
SW_SHOW);
sShowingOnScreenKeyboard = true;
}
// Based on DismissVirtualKeyboard() in Chromium's base/win/win_util.cc.
// static
void
IMEHandler::DismissOnScreenKeyboard()
{
if (!IsWin8OrLater() ||
!sShowingOnScreenKeyboard) {
return;
}
sShowingOnScreenKeyboard = false;
// Dismiss the virtual keyboard by generating the ESC keystroke
// programmatically.
const wchar_t kOSKClassName[] = L"IPTip_Main_Window";
HWND osk = ::FindWindowW(kOSKClassName, nullptr);
if (::IsWindow(osk) && ::IsWindowEnabled(osk)) {
::PostMessage(osk, WM_SYSCOMMAND, SC_CLOSE, 0);
}
}
} // namespace widget
} // namespace mozilla

View File

@ -120,9 +120,28 @@ private:
// Additionally, IME context is always disassociated from focused window.
static bool sIsIMMEnabled;
static bool sPluginHasFocus;
static bool sShowingOnScreenKeyboard;
static bool IsTSFAvailable() { return (sIsInTSFMode && !sPluginHasFocus); }
static bool IsIMMActive();
static void MaybeShowOnScreenKeyboard();
static void MaybeDismissOnScreenKeyboard();
static bool WStringStartsWithCaseInsensitive(const std::wstring& aHaystack,
const std::wstring& aNeedle);
static bool IsKeyboardPresentOnSlate();
/**
* Show the Windows on-screen keyboard. Only allowed for
* chrome documents and Windows 8 and higher.
*/
static void ShowOnScreenKeyboard();
/**
* Dismiss the Windows on-screen keyboard. Only allowed for
* Windows 8 and higher.
*/
static void DismissOnScreenKeyboard();
#endif // #ifdef NS_ENABLE_TSF
};

View File

@ -670,7 +670,8 @@ WinUtils::GetRegistryKey(HKEY aRoot,
::RegQueryValueExW(key, aValueName, nullptr, &type, (BYTE*) aBuffer,
&aBufferLength);
::RegCloseKey(key);
if (result != ERROR_SUCCESS || type != REG_SZ) {
if (result != ERROR_SUCCESS ||
(type != REG_SZ && type != REG_EXPAND_SZ)) {
return false;
}
if (aBuffer) {

View File

@ -21,6 +21,26 @@
#define NID_READY 0x00000080
#endif
/*
* Tablet mode detection
*/
#ifndef SM_SYSTEMDOCKED
#define SM_CONVERTIBLESLATEMODE 0x00002003
#define SM_SYSTEMDOCKED 0x00002004
typedef enum _AR_STATE
{
AR_ENABLED = 0x0,
AR_DISABLED = 0x1,
AR_SUPPRESSED = 0x2,
AR_REMOTESESSION = 0x4,
AR_MULTIMON = 0x8,
AR_NOSENSOR = 0x10,
AR_NOT_SUPPORTED = 0x20,
AR_DOCKED = 0x40,
AR_LAPTOP = 0x80
} AR_STATE, *PAR_STATE;
#endif
class nsLookAndFeel: public nsXPLookAndFeel {
static OperatingSystemVersion GetOperatingSystemVersion();
public: