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

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

Powered by Google App Engine
This is Rietveld 408576698