Chromium Code Reviews| Index: base/win/osk_display_manager.cc |
| diff --git a/base/win/osk_display_manager.cc b/base/win/osk_display_manager.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..4b0017cf41c6eae42157d31d88718711673dc70b |
| --- /dev/null |
| +++ b/base/win/osk_display_manager.cc |
| @@ -0,0 +1,256 @@ |
| +// Copyright (c) 2016 The Chromium Authors. All rights reserved. |
|
grt (UTC plus 2)
2016/05/18 15:41:19
(c)
|
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "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" |
| + |
| +namespace base { |
| +namespace win { |
| + |
| +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::OnScreenKeyboardDetector() |
| + : main_window_(nullptr), |
| + keyboard_detector_factory_(this), |
| + osk_visible_notification_received_(false) { |
| + memset(&osk_rect_, 0, sizeof(osk_rect_)); |
| +} |
| + |
| +OnScreenKeyboardDetector::~OnScreenKeyboardDetector() { |
| + ClearObservers(); |
|
grt (UTC plus 2)
2016/05/18 15:41:19
is this needed? can you just let the list self-des
ananta
2016/05/19 01:57:11
Done.
|
| +} |
| + |
| +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; |
| + |
| + ::GetWindowRect(osk, &osk_rect_); |
| + if (check_for_activation) { |
| + if (::IsWindowVisible(osk) && ::IsWindowEnabled(osk)) { |
| + if (!osk_visible_notification_received_) { |
|
grt (UTC plus 2)
2016/05/18 15:41:19
nit: omit braces
ananta
2016/05/19 01:57:11
Done.
|
| + HandleKeyboardVisible(); |
| + } |
| + } else { |
| + DVLOG(1) << "OSK did not come up in 1 second. Something wrong."; |
| + } |
| + } else { |
| + // Two cases here. |
|
grt (UTC plus 2)
2016/05/18 15:41:19
looks like the code handles three cases. could you
ananta
2016/05/19 01:57:12
Done.
|
| + // OSK was hidden because the user dismissed it. |
| + // We are no longer in the foreground. |
| + // 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(); |
| + ClearObservers(); |
| + } |
| + } 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_)); |
| + |
| + // 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_)); |
| +} |
| + |
| +void OnScreenKeyboardDetector::ClearObservers() { |
| + base::ObserverListBase<OnScreenKeyboardObserver>::Iterator iter(&observers_); |
| + for (OnScreenKeyboardObserver* observer = iter.GetNext(); observer; |
| + observer = iter.GetNext()) { |
| + RemoveObserver(observer); |
| + } |
| +} |
| + |
| +OnScreenKeyboardDisplayManager* OnScreenKeyboardDisplayManager::GetInstance() { |
| + return base::Singleton<OnScreenKeyboardDisplayManager, |
|
grt (UTC plus 2)
2016/05/18 15:41:19
is a leaky lazy instance appropriate here?
ananta
2016/05/19 01:57:11
That would work as well. Changed.
|
| + base::StaticMemorySingletonTraits< |
| + OnScreenKeyboardDisplayManager>>::get(); |
| +} |
| + |
| +bool OnScreenKeyboardDisplayManager::DisplayVirtualKeyboard( |
| + OnScreenKeyboardObserver* observer) { |
| + if (GetVersion() < VERSION_WIN8) |
| + return false; |
| + |
| + if (IsKeyboardPresentOnSlate(nullptr)) |
| + return false; |
| + |
| + static LazyInstance<string16>::Leaky osk_path = LAZY_INSTANCE_INITIALIZER; |
|
grt (UTC plus 2)
2016/05/18 15:41:19
OnScreenKeyboardDisplayManager is a singleton. why
ananta
2016/05/19 01:57:11
Yes. Moved this function into a class without insp
|
| + |
| + if (osk_path.Get().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. |
| + RegKey key(HKEY_LOCAL_MACHINE, kWindows8OSKRegPath, |
| + KEY_READ | KEY_WOW64_64KEY); |
| + DWORD osk_path_length = 1024; |
| + if (key.ReadValue(NULL, |
| + WriteInto(&osk_path.Get(), osk_path_length), |
|
grt (UTC plus 2)
2016/05/18 15:41:19
note that WriteInto doesn't update the size of the
ananta
2016/05/19 01:57:12
Done.
|
| + &osk_path_length, |
| + NULL) != ERROR_SUCCESS) { |
| + DLOG(WARNING) << "Failed to read on screen keyboard path from registry"; |
| + return false; |
| + } |
| + size_t common_program_files_offset = |
| + osk_path.Get().find(L"%CommonProgramFiles%"); |
|
grt (UTC plus 2)
2016/05/18 15:41:20
case-insensitive?
|
| + // 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 != string16::npos) { |
| + // Preserve the beginning quote in the path. |
| + osk_path.Get().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. |
| + string16 common_program_files_path; |
| + std::unique_ptr<wchar_t[]> common_program_files_wow6432; |
| + DWORD buffer_size = |
| + GetEnvironmentVariable(L"CommonProgramW6432", NULL, 0); |
|
grt (UTC plus 2)
2016/05/18 15:41:19
nullptr
ananta
2016/05/19 01:57:12
Done.
|
| + if (buffer_size) { |
| + common_program_files_wow6432.reset(new wchar_t[buffer_size]); |
| + GetEnvironmentVariable(L"CommonProgramW6432", |
| + common_program_files_wow6432.get(), |
|
grt (UTC plus 2)
2016/05/18 15:41:20
WriteInto(&common_program_files_path, buffer_size)
ananta
2016/05/19 01:57:11
Thanks. done.
|
| + buffer_size); |
| + common_program_files_path = common_program_files_wow6432.get(); |
| + DCHECK(!common_program_files_path.empty()); |
| + } |
| + else { |
| + ScopedCoMem<wchar_t> common_program_files; |
| + if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0, NULL, |
|
grt (UTC plus 2)
2016/05/18 15:41:19
nullptr
ananta
2016/05/19 01:57:11
Done.
|
| + &common_program_files))) { |
| + return false; |
| + } |
| + common_program_files_path = common_program_files; |
| + } |
| + |
| + osk_path.Get().insert(1, common_program_files_path); |
|
grt (UTC plus 2)
2016/05/18 15:41:19
1 -> common_program_files_offset?
ananta
2016/05/19 01:57:12
Yes. done.
|
| + } |
| + } |
| + |
| + HINSTANCE ret = ::ShellExecuteW(NULL, |
|
grt (UTC plus 2)
2016/05/18 15:41:19
nullptr all the things ;-)
ananta
2016/05/19 01:57:12
Done.
|
| + L"", |
| + osk_path.Get().c_str(), |
| + NULL, |
| + NULL, |
| + SW_SHOW); |
| + |
| + bool success = reinterpret_cast<intptr_t>(ret) > 32; |
| + if (success) { |
| + keyboard_detector_.reset(new OnScreenKeyboardDetector); |
|
grt (UTC plus 2)
2016/05/18 15:41:20
since this is a singleton, two callers to DisplayV
ananta
2016/05/19 01:57:11
It does not matter in this use case as the callers
|
| + if (observer) |
| + keyboard_detector_->AddObserver(observer); |
| + keyboard_detector_->DetectKeyboard(::GetForegroundWindow()); |
| + } |
| + return success; |
| +} |
| + |
| +bool OnScreenKeyboardDisplayManager::DismissVirtualKeyboard( |
| + OnScreenKeyboardObserver* observer) { |
| + if (GetVersion() < VERSION_WIN8) |
| + return false; |
| + |
| + DCHECK(keyboard_detector_.get()); |
| + bool ret = keyboard_detector_->DismissKeyboard(); |
| + if (observer) |
| + keyboard_detector_->RemoveObserver(observer); |
| + return ret; |
| +} |
| + |
| +} // namespace win |
| +} // namespace base |
| + |