| 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 | 
|  |