blob: 60fca60db2b7834431e75beaba1674bf5eba1844 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/base/ime/win/on_screen_keyboard_display_manager_tab_tip.h"
#include <windows.h>
#include <shellapi.h>
#include <shlobj.h>
#include <shobjidl.h> // Must be before propkey.
#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/win/registry.h"
#include "base/win/scoped_co_mem.h"
#include "base/win/win_util.h"
#include "base/win/windows_version.h"
#include "ui/base/ime/input_method_keyboard_controller_observer.h"
#include "ui/base/win/hidden_window.h"
#include "ui/display/win/screen_win.h"
#include "ui/gfx/geometry/dip_util.h"
namespace {
constexpr base::TimeDelta kCheckOSKDelay =
base::TimeDelta::FromMilliseconds(1000);
constexpr base::TimeDelta kDismissKeyboardRetryTimeout =
base::TimeDelta::FromMilliseconds(100);
constexpr int kDismissKeyboardMaxRetries = 5;
constexpr wchar_t kOSKClassName[] = L"IPTip_Main_Window";
constexpr wchar_t kWindows8OSKRegPath[] =
L"Software\\Classes\\CLSID\\{054AAE20-4BEA-4347-8A35-64A533254A9D}"
L"\\LocalServer32";
} // namespace
namespace ui {
// This class provides functionality to detect when the on screen keyboard
// is displayed and move the main window up if it is obscured by the keyboard.
class OnScreenKeyboardDetector {
public:
OnScreenKeyboardDetector(
OnScreenKeyboardDisplayManagerTabTip* display_manager);
~OnScreenKeyboardDetector();
// Schedules a delayed task which detects if the on screen keyboard was
// displayed.
void DetectKeyboard(HWND main_window);
// Dismisses the on screen keyboard. If a call to display the keyboard was
// made, this function waits for the keyboard to become visible by retrying
// upto a maximum of kDismissKeyboardMaxRetries.
void DismissKeyboard();
// Returns true if the osk is visible.
static bool IsKeyboardVisible();
private:
// Returns the occluded rect in dips.
gfx::Rect GetOccludedRect();
// Executes as a task and detects if the on screen keyboard is displayed.
// Once the keyboard is displayed it schedules the HideIfNecessary() task to
// detect when the keyboard is or should be hidden.
void CheckIfKeyboardVisible();
// Executes as a task and detects if the keyboard was hidden or should be
// hidden.
void HideIfNecessary();
// Notifies observers that the keyboard was displayed.
// A recurring task HideIfNecessary() is started to detect when the OSK
// disappears.
void HandleKeyboardVisible(const gfx::Rect& occluded_rect);
// Notifies observers that the keyboard was hidden.
// The observer list is cleared out after this notification.
void HandleKeyboardHidden();
OnScreenKeyboardDisplayManagerTabTip* display_manager_;
// The main window which displays the on screen keyboard.
HWND main_window_ = nullptr;
// Tracks if the keyboard was displayed.
bool osk_visible_notification_received_ = false;
// Set to true if a call to DetectKeyboard() was made.
bool keyboard_detect_requested_ = false;
// Contains the number of attempts made to dismiss the keyboard. Please refer
// to the DismissKeyboard() function for more information.
int keyboard_dismiss_retry_count_ = 0;
// Should be the last member in the class. Helps ensure that tasks spawned
// by this class instance are canceled when it is destroyed.
base::WeakPtrFactory<OnScreenKeyboardDetector> keyboard_detector_factory_;
DISALLOW_COPY_AND_ASSIGN(OnScreenKeyboardDetector);
};
// OnScreenKeyboardDetector member definitions.
OnScreenKeyboardDetector::OnScreenKeyboardDetector(
OnScreenKeyboardDisplayManagerTabTip* display_manager)
: display_manager_(display_manager), keyboard_detector_factory_(this) {}
OnScreenKeyboardDetector::~OnScreenKeyboardDetector() {}
void OnScreenKeyboardDetector::DetectKeyboard(HWND main_window) {
main_window_ = main_window;
keyboard_detect_requested_ = true;
// The keyboard is displayed by TabTip.exe which is launched via a
// ShellExecute call in the
// OnScreenKeyboardDisplayManager::DisplayVirtualKeyboard() function. We use
// a delayed task to check if the keyboard is visible because of the possible
// delay between the ShellExecute call and the keyboard becoming visible.
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&OnScreenKeyboardDetector::CheckIfKeyboardVisible,
keyboard_detector_factory_.GetWeakPtr()),
kCheckOSKDelay);
}
void OnScreenKeyboardDetector::DismissKeyboard() {
// We dismiss the virtual keyboard by generating the SC_CLOSE.
HWND osk = ::FindWindow(kOSKClassName, nullptr);
if (::IsWindow(osk) && ::IsWindowEnabled(osk)) {
keyboard_detect_requested_ = false;
keyboard_dismiss_retry_count_ = 0;
HandleKeyboardHidden();
PostMessage(osk, WM_SYSCOMMAND, SC_CLOSE, 0);
return;
}
if (keyboard_detect_requested_) {
if (keyboard_dismiss_retry_count_ < kDismissKeyboardMaxRetries) {
keyboard_dismiss_retry_count_++;
// Please refer to the comments in the DetectKeyboard() function for more
// information as to why we need a delayed task here.
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(
base::IgnoreResult(&OnScreenKeyboardDetector::DismissKeyboard),
keyboard_detector_factory_.GetWeakPtr()),
kDismissKeyboardRetryTimeout);
} else {
keyboard_dismiss_retry_count_ = 0;
}
}
}
// static
bool OnScreenKeyboardDetector::IsKeyboardVisible() {
HWND osk = ::FindWindow(kOSKClassName, nullptr);
if (!::IsWindow(osk))
return false;
return ::IsWindowVisible(osk) && ::IsWindowEnabled(osk);
}
gfx::Rect OnScreenKeyboardDetector::GetOccludedRect() {
gfx::Rect occluded_rect;
HWND osk = ::FindWindow(kOSKClassName, nullptr);
if (!::IsWindow(osk) || !::IsWindowVisible(osk) || !::IsWindowEnabled(osk))
return occluded_rect;
RECT osk_rect = {};
RECT main_window_rect = {};
if (!::GetWindowRect(osk, &osk_rect) ||
!::GetWindowRect(main_window_, &main_window_rect)) {
return occluded_rect;
}
gfx::Rect gfx_osk_rect(osk_rect);
gfx::Rect gfx_main_window_rect(main_window_rect);
gfx_osk_rect.Intersect(gfx_main_window_rect);
return display::win::ScreenWin::ScreenToDIPRect(main_window_, gfx_osk_rect);
}
void OnScreenKeyboardDetector::CheckIfKeyboardVisible() {
gfx::Rect occluded_rect = GetOccludedRect();
if (!occluded_rect.IsEmpty()) {
if (!osk_visible_notification_received_)
HandleKeyboardVisible(occluded_rect);
} else {
DVLOG(1) << "OSK did not come up. Something wrong.";
}
}
void OnScreenKeyboardDetector::HideIfNecessary() {
HWND osk = ::FindWindow(kOSKClassName, nullptr);
if (!::IsWindow(osk))
return;
// Three cases here.
// 1. OSK was hidden because the user dismissed it.
// 2. We are no longer in the foreground.
// 3. The OSK is still visible.
// In the first case we just have to notify the observers that the OSK was
// hidden.
// In the second case we need to dismiss the OSK which internally will
// notify the observers about the OSK being hidden.
if (!::IsWindowEnabled(osk)) {
if (osk_visible_notification_received_) {
if (main_window_ == ::GetForegroundWindow()) {
DVLOG(1) << "OSK window hidden while we are in the foreground.";
HandleKeyboardHidden();
}
}
} else if (main_window_ != ::GetForegroundWindow()) {
if (osk_visible_notification_received_) {
DVLOG(1) << "We are no longer in the foreground. Dismising OSK.";
DismissKeyboard();
}
} else {
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&OnScreenKeyboardDetector::HideIfNecessary,
keyboard_detector_factory_.GetWeakPtr()),
kCheckOSKDelay);
}
}
void OnScreenKeyboardDetector::HandleKeyboardVisible(
const gfx::Rect& occluded_rect) {
DCHECK(!osk_visible_notification_received_);
osk_visible_notification_received_ = true;
display_manager_->NotifyKeyboardVisible(occluded_rect);
// Now that the keyboard is visible, run the task to detect if it was hidden.
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&OnScreenKeyboardDetector::HideIfNecessary,
keyboard_detector_factory_.GetWeakPtr()),
kCheckOSKDelay);
}
void OnScreenKeyboardDetector::HandleKeyboardHidden() {
osk_visible_notification_received_ = false;
display_manager_->NotifyKeyboardHidden();
}
// OnScreenKeyboardDisplayManagerTabTip member definitions.
OnScreenKeyboardDisplayManagerTabTip::OnScreenKeyboardDisplayManagerTabTip(
HWND hwnd)
: hwnd_(hwnd) {
DCHECK_GE(base::win::GetVersion(), base::win::Version::WIN8);
}
OnScreenKeyboardDisplayManagerTabTip::~OnScreenKeyboardDisplayManagerTabTip() {}
bool OnScreenKeyboardDisplayManagerTabTip::DisplayVirtualKeyboard() {
if (base::win::IsKeyboardPresentOnSlate(nullptr, ui::GetHiddenWindow()))
return false;
if (osk_path_.empty() && !GetOSKPath(&osk_path_)) {
DLOG(WARNING) << "Failed to get on screen keyboard path from registry";
return false;
}
HINSTANCE ret = ::ShellExecuteW(nullptr, L"", osk_path_.c_str(), nullptr,
nullptr, SW_SHOW);
bool success = reinterpret_cast<intptr_t>(ret) > 32;
if (success) {
// If multiple calls to DisplayVirtualKeyboard occur one after the other,
// the last observer would be the one to get notifications.
keyboard_detector_ = std::make_unique<OnScreenKeyboardDetector>(this);
keyboard_detector_->DetectKeyboard(hwnd_);
}
return success;
}
void OnScreenKeyboardDisplayManagerTabTip::DismissVirtualKeyboard() {
if (keyboard_detector_)
keyboard_detector_->DismissKeyboard();
}
void OnScreenKeyboardDisplayManagerTabTip::AddObserver(
InputMethodKeyboardControllerObserver* observer) {
observers_.AddObserver(observer);
}
void OnScreenKeyboardDisplayManagerTabTip::RemoveObserver(
InputMethodKeyboardControllerObserver* observer) {
observers_.RemoveObserver(observer);
}
bool OnScreenKeyboardDisplayManagerTabTip::GetOSKPath(
base::string16* osk_path) {
DCHECK(osk_path);
// We need to launch TabTip.exe from the location specified under the
// LocalServer32 key for the {{054AAE20-4BEA-4347-8A35-64A533254A9D}}
// CLSID.
// TabTip.exe is typically found at
// c:\program files\common files\microsoft shared\ink on English Windows.
// We don't want to launch TabTip.exe from
// c:\program files (x86)\common files\microsoft shared\ink. This path is
// normally found on 64 bit Windows.
base::win::RegKey key(HKEY_LOCAL_MACHINE, kWindows8OSKRegPath,
KEY_READ | KEY_WOW64_64KEY);
DWORD osk_path_length = 1024;
if (key.ReadValue(nullptr, base::WriteInto(osk_path, osk_path_length),
&osk_path_length, nullptr) != ERROR_SUCCESS) {
return false;
}
osk_path->resize(base::string16::traits_type::length(osk_path->c_str()));
*osk_path = base::ToLowerASCII(*osk_path);
size_t common_program_files_offset = osk_path->find(L"%commonprogramfiles%");
// Typically the path to TabTip.exe read from the registry will start with
// %CommonProgramFiles% which needs to be replaced with the corrsponding
// expanded string.
// If the path does not begin with %CommonProgramFiles% we use it as is.
if (common_program_files_offset != base::string16::npos) {
// Preserve the beginning quote in the path.
osk_path->erase(common_program_files_offset,
wcslen(L"%commonprogramfiles%"));
// 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.
base::string16 common_program_files_path;
DWORD buffer_size =
GetEnvironmentVariable(L"CommonProgramW6432", nullptr, 0);
if (buffer_size) {
GetEnvironmentVariable(
L"CommonProgramW6432",
base::WriteInto(&common_program_files_path, buffer_size),
buffer_size);
DCHECK(!common_program_files_path.empty());
} else {
base::win::ScopedCoMem<wchar_t> common_program_files;
if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0, nullptr,
&common_program_files))) {
return false;
}
common_program_files_path = common_program_files;
}
osk_path->insert(common_program_files_offset, common_program_files_path);
}
return !osk_path->empty();
}
bool OnScreenKeyboardDisplayManagerTabTip::IsKeyboardVisible() {
return OnScreenKeyboardDetector::IsKeyboardVisible();
}
void OnScreenKeyboardDisplayManagerTabTip::NotifyKeyboardVisible(
const gfx::Rect& occluded_rect) {
for (InputMethodKeyboardControllerObserver& observer : observers_)
observer.OnKeyboardVisible(occluded_rect);
}
void OnScreenKeyboardDisplayManagerTabTip::NotifyKeyboardHidden() {
for (InputMethodKeyboardControllerObserver& observer : observers_)
observer.OnKeyboardHidden();
}
} // namespace ui