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..17817c7ae2d504100d00e47d53ffee31502ecb46 |
--- /dev/null |
+++ b/ui/base/win/osk_display_manager.cc |
@@ -0,0 +1,364 @@ |
+// 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() && !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_.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); |
+} |
+ |
+bool OnScreenKeyboardDisplayManager::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(); |
+} |
+ |
+} // namespace ui |