Chromium Code Reviews| 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..bc96b36fdf9b5c6d2cf7c4ab6e29bffb2fc9ccbc |
| --- /dev/null |
| +++ b/ui/base/win/osk_display_manager.cc |
| @@ -0,0 +1,325 @@ |
| +// 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/lazy_instance.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 ui { |
| + |
| +static const int kCheckOSKDelayMs = 1000; |
| +static const wchar_t kOSKClassName[] = L"IPTip_Main_Window"; |
| + |
| +const wchar_t kWindows8OSKRegPath[] = |
| + L"Software\\Classes\\CLSID\\{054AAE20-4BEA-4347-8A35-64A533254A9D}" |
| + L"\\LocalServer32"; |
| + |
| +// 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. |
| + 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. |
| + // The |check_for_activation| parameter controls whether the function detects |
| + // whether the keyboard wad displayed or hidden. |
| + // Once the OSK is detected, this task runs periodically for the duration for |
| + // which the OSK is visible. |
| + void CheckOSKState(bool check_for_activation); |
| + |
| + // Notifies observers that the keyboard was displayed. |
| + // A recurring task CheckOSKState() 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 DIPs. |
| + gfx::Rect osk_rect_dip_; |
| + |
| + 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; |
| + base::MessageLoop::current()->PostDelayedTask( |
|
sky
2016/05/19 22:25:08
Please document why we need to delay here.
ananta
2016/05/19 23:15:40
Done.
|
| + FROM_HERE, base::Bind(&OnScreenKeyboardDetector::CheckOSKState, |
| + keyboard_detector_factory_.GetWeakPtr(), true), |
| + 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)) { |
| + HandleKeyboardHidden(); |
| + PostMessage(osk, WM_SYSCOMMAND, SC_CLOSE, 0); |
| + return true; |
| + } |
| + return false; |
| +} |
| + |
| +void OnScreenKeyboardDetector::AddObserver(OnScreenKeyboardObserver* observer) { |
| + observers_.AddObserver(observer); |
| +} |
| + |
| +void OnScreenKeyboardDetector::RemoveObserver( |
| + OnScreenKeyboardObserver* observer) { |
| + observers_.RemoveObserver(observer); |
| +} |
| + |
| +void OnScreenKeyboardDetector::CheckOSKState(bool check_for_activation) { |
| + HWND osk = ::FindWindow(kOSKClassName, nullptr); |
| + if (!::IsWindow(osk)) |
| + return; |
| + |
| + RECT osk_rect = {}; |
| + ::GetWindowRect(osk, &osk_rect); |
| + |
| + osk_rect_dip_ = |
| + gfx::ConvertRectToDIP(display::win::GetDPIScale(), gfx::Rect(osk_rect)); |
|
sky
2016/05/19 22:25:08
Use the functions in ScreenWin that take the hwnd
ananta
2016/05/19 23:15:40
Done.
|
| + |
| + if (check_for_activation) { |
| + if (::IsWindowVisible(osk) && ::IsWindowEnabled(osk)) { |
| + if (!osk_visible_notification_received_) |
| + HandleKeyboardVisible(); |
| + } else { |
| + DVLOG(1) << "OSK did not come up in 1 second. Something wrong."; |
| + } |
| + } else { |
| + // 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::CheckOSKState, |
| + keyboard_detector_factory_.GetWeakPtr(), false), |
| + 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_dip_)); |
| + |
| + // Now that the keyboard is visible, run the task to detect if it was hidden. |
| + base::MessageLoop::current()->PostDelayedTask( |
| + FROM_HERE, base::Bind(&OnScreenKeyboardDetector::CheckOSKState, |
| + keyboard_detector_factory_.GetWeakPtr(), false), |
| + base::TimeDelta::FromMilliseconds(kCheckOSKDelayMs)); |
| +} |
| + |
| +void OnScreenKeyboardDetector::HandleKeyboardHidden() { |
| + osk_visible_notification_received_ = false; |
| + FOR_EACH_OBSERVER(OnScreenKeyboardObserver, observers_, |
| + OnKeyboardHidden(osk_rect_dip_)); |
| + 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() { |
| + return base::Singleton< |
| + OnScreenKeyboardDisplayManager, |
| + base::LeakySingletonTraits<OnScreenKeyboardDisplayManager>>::get(); |
| +} |
| + |
| +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; |
|
sky
2016/05/19 22:25:08
If we return any where in here should we cache the
ananta
2016/05/19 23:15:40
We could. However this is old code and mostly safe
|
| + } |
| + |
| + 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; |
| + |
| + bool ret = false; |
| + if (keyboard_detector_.get()) |
| + ret = keyboard_detector_->DismissKeyboard(); |
| + return ret; |
| +} |
| + |
| +bool DisplayVirtualKeyboard() { |
| + DCHECK(OnScreenKeyboardDisplayManager::GetInstance()); |
| + return OnScreenKeyboardDisplayManager::GetInstance()->DisplayVirtualKeyboard( |
| + nullptr); // !keyboard_observer. |
| +} |
| + |
| +bool DismissVirtualKeyboard() { |
| + DCHECK(OnScreenKeyboardDisplayManager::GetInstance()); |
| + return OnScreenKeyboardDisplayManager::GetInstance() |
| + ->DismissVirtualKeyboard(); |
| +} |
| + |
| +} // namespace ui |