| Index: ui/base/win/osk_display_manager.cc
|
| diff --git a/ui/base/win/osk_display_manager.cc b/ui/base/win/osk_display_manager.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0a81906229e20694eeb2e8fd4de4981a0d89bd36
|
| --- /dev/null
|
| +++ b/ui/base/win/osk_display_manager.cc
|
| @@ -0,0 +1,359 @@
|
| +// 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/win/osk_display_manager.h"
|
| +
|
| +#include <windows.h>
|
| +#include <shellapi.h>
|
| +#include <shlobj.h>
|
| +#include <shobjidl.h> // Must be before propkey.
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/debug/leak_annotations.h"
|
| +#include "base/logging.h"
|
| +#include "base/message_loop/message_loop.h"
|
| +#include "base/strings/string_util.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/win/osk_display_observer.h"
|
| +#include "ui/display/win/dpi.h"
|
| +#include "ui/gfx/geometry/dip_util.h"
|
| +
|
| +namespace {
|
| +
|
| +constexpr int kCheckOSKDelayMs = 1000;
|
| +constexpr int kDismissKeyboardRetryTimeoutMs = 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();
|
| + ~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.
|
| + bool DismissKeyboard();
|
| +
|
| + // Add/Remove keyboard observers.
|
| + // Please note that this class does not track the |observer| destruction. It
|
| + // is upto the classes which set up these observers to remove them when they
|
| + // are destroyed.
|
| + void AddObserver(OnScreenKeyboardObserver* observer);
|
| + void RemoveObserver(OnScreenKeyboardObserver* observer);
|
| +
|
| + private:
|
| + // 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();
|
| +
|
| + // Notifies observers that the keyboard was hidden.
|
| + // The observer list is cleared out after this notification.
|
| + void HandleKeyboardHidden();
|
| +
|
| + // Removes all observers from the list.
|
| + void ClearObservers();
|
| +
|
| + // 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;
|
| +
|
| + // The keyboard dimensions in pixels.
|
| + gfx::Rect osk_rect_pixels_;
|
| +
|
| + // 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;
|
| +
|
| + base::ObserverList<OnScreenKeyboardObserver, false> observers_;
|
| +
|
| + // 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()
|
| + : 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::MessageLoop::current()->PostDelayedTask(
|
| + FROM_HERE, base::Bind(&OnScreenKeyboardDetector::CheckIfKeyboardVisible,
|
| + keyboard_detector_factory_.GetWeakPtr()),
|
| + base::TimeDelta::FromMilliseconds(kCheckOSKDelayMs));
|
| +}
|
| +
|
| +bool OnScreenKeyboardDetector::DismissKeyboard() {
|
| + // We dismiss the virtual keyboard by generating the ESC keystroke
|
| + // programmatically.
|
| + 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 true;
|
| + } else 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::MessageLoop::current()->PostDelayedTask(
|
| + FROM_HERE, base::Bind(base::IgnoreResult(
|
| + &OnScreenKeyboardDetector::DismissKeyboard),
|
| + keyboard_detector_factory_.GetWeakPtr()),
|
| + base::TimeDelta::FromMilliseconds(kDismissKeyboardRetryTimeoutMs));
|
| + } else {
|
| + keyboard_dismiss_retry_count_ = 0;
|
| + }
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +void OnScreenKeyboardDetector::AddObserver(OnScreenKeyboardObserver* observer) {
|
| + observers_.AddObserver(observer);
|
| +}
|
| +
|
| +void OnScreenKeyboardDetector::RemoveObserver(
|
| + OnScreenKeyboardObserver* observer) {
|
| + observers_.RemoveObserver(observer);
|
| +}
|
| +
|
| +void OnScreenKeyboardDetector::CheckIfKeyboardVisible() {
|
| + HWND osk = ::FindWindow(kOSKClassName, nullptr);
|
| + if (!::IsWindow(osk))
|
| + return;
|
| +
|
| + RECT osk_rect = {};
|
| + ::GetWindowRect(osk, &osk_rect);
|
| + osk_rect_pixels_ = gfx::Rect(osk_rect);
|
| + if (::IsWindowVisible(osk) && ::IsWindowEnabled(osk)) {
|
| + if (!osk_visible_notification_received_)
|
| + HandleKeyboardVisible();
|
| + } else {
|
| + DVLOG(1) << "OSK did not come up in 1 second. 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::MessageLoop::current()->PostDelayedTask(
|
| + FROM_HERE, base::Bind(&OnScreenKeyboardDetector::HideIfNecessary,
|
| + keyboard_detector_factory_.GetWeakPtr()),
|
| + base::TimeDelta::FromMilliseconds(kCheckOSKDelayMs));
|
| + }
|
| +}
|
| +
|
| +void OnScreenKeyboardDetector::HandleKeyboardVisible() {
|
| + DCHECK(!osk_visible_notification_received_);
|
| + osk_visible_notification_received_ = true;
|
| +
|
| + FOR_EACH_OBSERVER(OnScreenKeyboardObserver, observers_,
|
| + OnKeyboardVisible(osk_rect_pixels_));
|
| +
|
| + // Now that the keyboard is visible, run the task to detect if it was hidden.
|
| + base::MessageLoop::current()->PostDelayedTask(
|
| + FROM_HERE, base::Bind(&OnScreenKeyboardDetector::HideIfNecessary,
|
| + keyboard_detector_factory_.GetWeakPtr()),
|
| + base::TimeDelta::FromMilliseconds(kCheckOSKDelayMs));
|
| +}
|
| +
|
| +void OnScreenKeyboardDetector::HandleKeyboardHidden() {
|
| + osk_visible_notification_received_ = false;
|
| + FOR_EACH_OBSERVER(OnScreenKeyboardObserver, observers_,
|
| + OnKeyboardHidden(osk_rect_pixels_));
|
| + ClearObservers();
|
| +}
|
| +
|
| +void OnScreenKeyboardDetector::ClearObservers() {
|
| + base::ObserverListBase<OnScreenKeyboardObserver>::Iterator iter(&observers_);
|
| + for (OnScreenKeyboardObserver* observer = iter.GetNext(); observer;
|
| + observer = iter.GetNext()) {
|
| + RemoveObserver(observer);
|
| + }
|
| +}
|
| +
|
| +// OnScreenKeyboardDisplayManager member definitions.
|
| +OnScreenKeyboardDisplayManager::OnScreenKeyboardDisplayManager() {}
|
| +
|
| +OnScreenKeyboardDisplayManager::~OnScreenKeyboardDisplayManager() {}
|
| +
|
| +OnScreenKeyboardDisplayManager* OnScreenKeyboardDisplayManager::GetInstance() {
|
| + static OnScreenKeyboardDisplayManager* instance = nullptr;
|
| + if (!instance) {
|
| + instance = new OnScreenKeyboardDisplayManager;
|
| + ANNOTATE_LEAKING_OBJECT_PTR(instance);
|
| + }
|
| + return instance;
|
| +}
|
| +
|
| +bool OnScreenKeyboardDisplayManager::DisplayVirtualKeyboard(
|
| + OnScreenKeyboardObserver* observer) {
|
| + if (base::win::GetVersion() < base::win::VERSION_WIN8)
|
| + return false;
|
| +
|
| + if (base::win::IsKeyboardPresentOnSlate(nullptr))
|
| + return false;
|
| +
|
| + if (osk_path_.empty()) {
|
| + // 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(NULL, base::WriteInto(&osk_path_, osk_path_length),
|
| + &osk_path_length, NULL) != ERROR_SUCCESS) {
|
| + DLOG(WARNING) << "Failed to read on screen keyboard path from registry";
|
| + 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);
|
| + }
|
| + }
|
| +
|
| + 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_.reset(new OnScreenKeyboardDetector);
|
| + if (observer)
|
| + keyboard_detector_->AddObserver(observer);
|
| + keyboard_detector_->DetectKeyboard(::GetForegroundWindow());
|
| + }
|
| + return success;
|
| +}
|
| +
|
| +bool OnScreenKeyboardDisplayManager::DismissVirtualKeyboard() {
|
| + if (base::win::GetVersion() < base::win::VERSION_WIN8)
|
| + return false;
|
| +
|
| + return keyboard_detector_ ? keyboard_detector_->DismissKeyboard() : false;
|
| +}
|
| +
|
| +void OnScreenKeyboardDisplayManager::RemoveObserver(
|
| + OnScreenKeyboardObserver* observer) {
|
| + if (keyboard_detector_)
|
| + keyboard_detector_->RemoveObserver(observer);
|
| +}
|
| +
|
| +} // namespace ui
|
|
|