Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(49)

Side by Side Diff: ui/base/win/osk_display_manager.cc

Issue 1986153005: The on screen keyboard on Windows 8+ should not obscure the input field. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: git cl format Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/base/win/osk_display_manager.h"
6
7 #include <windows.h>
8 #include <shellapi.h>
9 #include <shlobj.h>
10 #include <shobjidl.h> // Must be before propkey.
11
12 #include "base/bind.h"
13 #include "base/lazy_instance.h"
14 #include "base/logging.h"
15 #include "base/message_loop/message_loop.h"
16 #include "base/strings/string_util.h"
17 #include "base/win/registry.h"
18 #include "base/win/scoped_co_mem.h"
19 #include "base/win/win_util.h"
20 #include "base/win/windows_version.h"
21 #include "ui/base/win/osk_display_observer.h"
22 #include "ui/display/win/dpi.h"
23 #include "ui/gfx/geometry/dip_util.h"
24
25 namespace ui {
26
27 static const int kCheckOSKDelayMs = 1000;
grt (UTC plus 2) 2016/05/20 14:56:35 put these privates in an unnamed namespace and omi
grt (UTC plus 2) 2016/05/20 14:56:35 nit: use constexpr for compile-time constants (htt
grt (UTC plus 2) 2016/05/20 20:29:24 ping
ananta 2016/05/20 20:54:17 Done.
ananta 2016/05/20 20:54:17 Done.
ananta 2016/05/20 20:54:17 Done.
ananta 2016/05/20 20:54:17 Done.
28 static const int kDismissKeyboardRetryTimeoutMs = 100;
29 static const int kDismissKeyboardMaxRetries = 5;
30
31 static const wchar_t kOSKClassName[] = L"IPTip_Main_Window";
32
33 const wchar_t kWindows8OSKRegPath[] =
34 L"Software\\Classes\\CLSID\\{054AAE20-4BEA-4347-8A35-64A533254A9D}"
35 L"\\LocalServer32";
36
37 // This class provides functionality to detect when the on screen keyboard
38 // is displayed and move the main window up if it is obscured by the keyboard.
39 class OnScreenKeyboardDetector {
40 public:
41 OnScreenKeyboardDetector();
42 ~OnScreenKeyboardDetector();
43
44 // Schedules a delayed task which detects if the on screen keyboard was
45 // displayed.
46 void DetectKeyboard(HWND main_window);
47
48 // Dismisses the on screen keyboard. If a call to display the keyboard was
49 // made, this function waits for the keyboard to become visible by retrying
50 // upto a maximum of kDismissKeyboardMaxRetries.
51 bool DismissKeyboard();
52
53 // Add/Remove keyboard observers.
54 // Please note that this class does not track the |observer| destruction. It
55 // is upto the classes which set up these observers to remove them when they
56 // are destroyed.
57 void AddObserver(OnScreenKeyboardObserver* observer);
58 void RemoveObserver(OnScreenKeyboardObserver* observer);
59
60 private:
61 // Executes as a task and detects if the on screen keyboard is displayed.
62 // The |check_for_activation| parameter controls whether the function detects
63 // whether the keyboard wad displayed or hidden.
64 // Once the OSK is detected, this task runs periodically for the duration for
65 // which the OSK is visible.
66 void CheckOSKState(bool check_for_activation);
67
68 // Notifies observers that the keyboard was displayed.
69 // A recurring task CheckOSKState() is started to detect when the OSK
70 // disappears.
71 void HandleKeyboardVisible();
72
73 // Notifies observers that the keyboard was hidden.
74 // The observer list is cleared out after this notification.
75 void HandleKeyboardHidden();
76
77 // Removes all observers from the list.
78 void ClearObservers();
79
80 // The main window which displays the on screen keyboard.
81 HWND main_window_ = nullptr;
82
83 // Tracks if the keyboard was displayed.
84 bool osk_visible_notification_received_ = false;
85
86 // The keyboard dimensions in pixels.
87 gfx::Rect osk_rect_pixels_;
88
89 // Set to true if a call to DetectKeyboard() was made.
90 bool keyboard_detect_requested_ = false;
91
92 // Contains the number of attempts made to dismiss the keyboard. Please refer
93 // to the DismissKeyboard() function for more information.
94 int keyboard_dismiss_retry_count_ = 0;
95
96 base::ObserverList<OnScreenKeyboardObserver, false> observers_;
97
98 // Should be the last member in the class. Helps ensure that tasks spawned
99 // by this class instance are canceled when it is destroyed.
100 base::WeakPtrFactory<OnScreenKeyboardDetector> keyboard_detector_factory_;
101
102 DISALLOW_COPY_AND_ASSIGN(OnScreenKeyboardDetector);
103 };
104
105 // OnScreenKeyboardDetector member definitions.
106 OnScreenKeyboardDetector::OnScreenKeyboardDetector()
107 : keyboard_detector_factory_(this) {}
108
109 OnScreenKeyboardDetector::~OnScreenKeyboardDetector() {}
110
111 void OnScreenKeyboardDetector::DetectKeyboard(HWND main_window) {
112 main_window_ = main_window;
113 keyboard_detect_requested_ = true;
114 // The keyboard is displayed by TabTip.exe which is launched via a
115 // ShellExecute call in the
116 // OnScreenKeyboardDisplayManager::DisplayVirtualKeyboard() function. We use
117 // a delayed task to check if the keyboard is visible because of the possible
118 // delay between the ShellExecute call and the keyboard becoming visible.
119 base::MessageLoop::current()->PostDelayedTask(
120 FROM_HERE, base::Bind(&OnScreenKeyboardDetector::CheckOSKState,
121 keyboard_detector_factory_.GetWeakPtr(), true),
122 base::TimeDelta::FromMilliseconds(kCheckOSKDelayMs));
123 }
124
125 bool OnScreenKeyboardDetector::DismissKeyboard() {
126 // We dismiss the virtual keyboard by generating the ESC keystroke
127 // programmatically.
128 HWND osk = ::FindWindow(kOSKClassName, nullptr);
129 if (::IsWindow(osk) && ::IsWindowEnabled(osk)) {
130 keyboard_detect_requested_ = false;
131 keyboard_dismiss_retry_count_ = 0;
132 HandleKeyboardHidden();
133 PostMessage(osk, WM_SYSCOMMAND, SC_CLOSE, 0);
134 return true;
135 } else if (keyboard_detect_requested_) {
136 if (keyboard_dismiss_retry_count_ < kDismissKeyboardMaxRetries) {
137 keyboard_dismiss_retry_count_++;
138 // Please refer to the comments in the DetectKeyboard() function for more
139 // information as to why we need a delayed task here.
140 base::MessageLoop::current()->PostDelayedTask(
141 FROM_HERE, base::Bind(base::IgnoreResult(
142 &OnScreenKeyboardDetector::DismissKeyboard),
143 keyboard_detector_factory_.GetWeakPtr()),
144 base::TimeDelta::FromMilliseconds(kDismissKeyboardRetryTimeoutMs));
145 } else {
146 keyboard_dismiss_retry_count_ = 0;
147 }
148 }
149 return false;
150 }
151
152 void OnScreenKeyboardDetector::AddObserver(OnScreenKeyboardObserver* observer) {
153 observers_.AddObserver(observer);
154 }
155
156 void OnScreenKeyboardDetector::RemoveObserver(
157 OnScreenKeyboardObserver* observer) {
158 observers_.RemoveObserver(observer);
159 }
160
161 void OnScreenKeyboardDetector::CheckOSKState(bool check_for_activation) {
162 HWND osk = ::FindWindow(kOSKClassName, nullptr);
163 if (!::IsWindow(osk))
164 return;
165
166 RECT osk_rect = {};
167 ::GetWindowRect(osk, &osk_rect);
168
169 if (check_for_activation) {
170 if (::IsWindowVisible(osk) && ::IsWindowEnabled(osk)) {
171 if (!osk_visible_notification_received_)
172 HandleKeyboardVisible();
173 } else {
174 DVLOG(1) << "OSK did not come up in 1 second. Something wrong.";
175 }
176 } else {
177 // Three cases here.
178 // 1. OSK was hidden because the user dismissed it.
179 // 2. We are no longer in the foreground.
180 // 3. The OSK is still visible.
181 // In the first case we just have to notify the observers that the OSK was
182 // hidden.
183 // In the second case we need to dismiss the OSK which internally will
184 // notify the observers about the OSK being hidden.
185 if (!::IsWindowEnabled(osk)) {
186 if (osk_visible_notification_received_) {
187 if (main_window_ == ::GetForegroundWindow()) {
188 DVLOG(1) << "OSK window hidden while we are in the foreground.";
189 HandleKeyboardHidden();
190 }
191 }
192 } else if (main_window_ != ::GetForegroundWindow()) {
193 if (osk_visible_notification_received_) {
194 DVLOG(1) << "We are no longer in the foreground. Dismising OSK.";
195 DismissKeyboard();
196 }
197 } else {
198 base::MessageLoop::current()->PostDelayedTask(
199 FROM_HERE, base::Bind(&OnScreenKeyboardDetector::CheckOSKState,
200 keyboard_detector_factory_.GetWeakPtr(), false),
201 base::TimeDelta::FromMilliseconds(kCheckOSKDelayMs));
202 }
203 }
204 }
205
206 void OnScreenKeyboardDetector::HandleKeyboardVisible() {
207 DCHECK(!osk_visible_notification_received_);
208 osk_visible_notification_received_ = true;
209
210 FOR_EACH_OBSERVER(OnScreenKeyboardObserver, observers_,
211 OnKeyboardVisible(osk_rect_pixels_));
212
213 // Now that the keyboard is visible, run the task to detect if it was hidden.
214 base::MessageLoop::current()->PostDelayedTask(
215 FROM_HERE, base::Bind(&OnScreenKeyboardDetector::CheckOSKState,
216 keyboard_detector_factory_.GetWeakPtr(), false),
217 base::TimeDelta::FromMilliseconds(kCheckOSKDelayMs));
218 }
219
220 void OnScreenKeyboardDetector::HandleKeyboardHidden() {
221 osk_visible_notification_received_ = false;
222 FOR_EACH_OBSERVER(OnScreenKeyboardObserver, observers_,
223 OnKeyboardHidden(osk_rect_pixels_));
224 ClearObservers();
225 }
226
227 void OnScreenKeyboardDetector::ClearObservers() {
228 base::ObserverListBase<OnScreenKeyboardObserver>::Iterator iter(&observers_);
229 for (OnScreenKeyboardObserver* observer = iter.GetNext(); observer;
230 observer = iter.GetNext()) {
231 RemoveObserver(observer);
232 }
233 }
234
235 // OnScreenKeyboardDisplayManager member definitions.
236 OnScreenKeyboardDisplayManager::OnScreenKeyboardDisplayManager() {}
237
238 OnScreenKeyboardDisplayManager::~OnScreenKeyboardDisplayManager() {}
239
240 OnScreenKeyboardDisplayManager* OnScreenKeyboardDisplayManager::GetInstance() {
241 return base::Singleton<
grt (UTC plus 2) 2016/05/20 14:56:35 since the only access of the singleton is from wit
ananta 2016/05/20 19:32:55 Done.
242 OnScreenKeyboardDisplayManager,
243 base::LeakySingletonTraits<OnScreenKeyboardDisplayManager>>::get();
244 }
245
246 bool OnScreenKeyboardDisplayManager::DisplayVirtualKeyboard(
247 OnScreenKeyboardObserver* observer) {
248 if (base::win::GetVersion() < base::win::VERSION_WIN8)
249 return false;
250
251 if (base::win::IsKeyboardPresentOnSlate(nullptr))
252 return false;
253
254 if (osk_path_.empty()) {
255 // We need to launch TabTip.exe from the location specified under the
256 // LocalServer32 key for the {{054AAE20-4BEA-4347-8A35-64A533254A9D}}
257 // CLSID.
258 // TabTip.exe is typically found at
259 // c:\program files\common files\microsoft shared\ink on English Windows.
260 // We don't want to launch TabTip.exe from
261 // c:\program files (x86)\common files\microsoft shared\ink. This path is
262 // normally found on 64 bit Windows.
263 base::win::RegKey key(HKEY_LOCAL_MACHINE, kWindows8OSKRegPath,
264 KEY_READ | KEY_WOW64_64KEY);
265 DWORD osk_path_length = 1024;
266 if (key.ReadValue(NULL, base::WriteInto(&osk_path_, osk_path_length),
267 &osk_path_length, NULL) != ERROR_SUCCESS) {
268 DLOG(WARNING) << "Failed to read on screen keyboard path from registry";
269 return false;
270 }
271
272 osk_path_.resize(base::string16::traits_type::length(osk_path_.c_str()));
273
274 osk_path_ = base::ToLowerASCII(osk_path_);
275
276 size_t common_program_files_offset =
277 osk_path_.find(L"%commonprogramfiles%");
278 // Typically the path to TabTip.exe read from the registry will start with
279 // %CommonProgramFiles% which needs to be replaced with the corrsponding
280 // expanded string.
281 // If the path does not begin with %CommonProgramFiles% we use it as is.
282 if (common_program_files_offset != base::string16::npos) {
283 // Preserve the beginning quote in the path.
284 osk_path_.erase(common_program_files_offset,
285 wcslen(L"%commonprogramfiles%"));
286 // The path read from the registry contains the %CommonProgramFiles%
287 // environment variable prefix. On 64 bit Windows the SHGetKnownFolderPath
288 // function returns the common program files path with the X86 suffix for
289 // the FOLDERID_ProgramFilesCommon value.
290 // To get the correct path to TabTip.exe we first read the environment
291 // variable CommonProgramW6432 which points to the desired common
292 // files path. Failing that we fallback to the SHGetKnownFolderPath API.
293
294 // We then replace the %CommonProgramFiles% value with the actual common
295 // files path found in the process.
296 base::string16 common_program_files_path;
297 DWORD buffer_size =
298 GetEnvironmentVariable(L"CommonProgramW6432", nullptr, 0);
299 if (buffer_size) {
300 GetEnvironmentVariable(
301 L"CommonProgramW6432",
302 base::WriteInto(&common_program_files_path, buffer_size),
303 buffer_size);
304 DCHECK(!common_program_files_path.empty());
305 } else {
306 base::win::ScopedCoMem<wchar_t> common_program_files;
307 if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0, nullptr,
308 &common_program_files))) {
309 return false;
310 }
311 common_program_files_path = common_program_files;
312 }
313
314 osk_path_.insert(common_program_files_offset, common_program_files_path);
315 }
316 }
317
318 HINSTANCE ret = ::ShellExecuteW(nullptr, L"", osk_path_.c_str(), nullptr,
319 nullptr, SW_SHOW);
320
321 bool success = reinterpret_cast<intptr_t>(ret) > 32;
322 if (success) {
323 // If multiple calls to DisplayVirtualKeyboard occur one after the other,
324 // the last observer would be the one to get notifications.
325 keyboard_detector_.reset(new OnScreenKeyboardDetector);
326 if (observer)
327 keyboard_detector_->AddObserver(observer);
328 keyboard_detector_->DetectKeyboard(::GetForegroundWindow());
329 }
330 return success;
331 }
332
333 bool OnScreenKeyboardDisplayManager::DismissVirtualKeyboard() {
334 if (base::win::GetVersion() < base::win::VERSION_WIN8)
335 return false;
336
337 bool ret = false;
grt (UTC plus 2) 2016/05/20 14:56:35 nit: return keyboard_detector_ ? keyboard_detect
ananta 2016/05/20 19:32:55 Done.
338 if (keyboard_detector_.get())
339 ret = keyboard_detector_->DismissKeyboard();
340 return ret;
341 }
342
343 } // namespace ui
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698