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