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..1889551f4eb9ea4727f37badac30b21f4cf51b30 |
| --- /dev/null |
| +++ b/ui/base/win/osk_display_manager.cc |
| @@ -0,0 +1,276 @@ |
| +// 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/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"; |
| + |
| +// OnScreenKeyboardDetector member definitions. |
| +OnScreenKeyboardDetector::OnScreenKeyboardDetector() |
| + : main_window_(nullptr), |
| + osk_visible_notification_received_(false), |
| + keyboard_detector_factory_(this) { |
| +} |
| + |
| +OnScreenKeyboardDetector::~OnScreenKeyboardDetector() { |
| +} |
| + |
| +void OnScreenKeyboardDetector::DetectKeyboard(HWND main_window) { |
| + main_window_ = main_window; |
| + base::MessageLoop::current()->PostDelayedTask(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)); |
| + |
| + 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; |
| + } |
| + |
| + 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) { |
| + keyboard_detector_.reset(new OnScreenKeyboardDetector); |
|
ncarter (slow)
2016/05/19 17:45:55
Is this the right behavior, if keyboard_detector_
ananta
2016/05/19 19:33:38
In the ideal case we want a list of detectors. How
|
| + 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; |
| + |
| + DCHECK(keyboard_detector_.get()); |
|
ncarter (slow)
2016/05/19 17:45:55
It seems like we'll crash if DisplayVirtualKeyboar
|
| + bool 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(); // !keyboard_observer. |
|
ncarter (slow)
2016/05/19 17:45:55
This comment seems out of date.
ananta
2016/05/19 19:33:38
Done.
|
| +} |
| + |
| +} // namespace ui |