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

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 review comments 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 ui {
26
27 static const int kCheckOSKDelayMs = 1000;
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;
sky 2016/05/20 19:57:16 With all the delayed tasks should DetectKeyboard a
ananta 2016/05/20 20:54:18 Currently a new instance of the OnScreenKeyboardDe
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) {
sky 2016/05/20 19:57:16 This function has two very different purposes that
ananta 2016/05/20 20:54:18 Done.
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 static OnScreenKeyboardDisplayManager* instance = nullptr;
242 if (!instance) {
243 instance = new OnScreenKeyboardDisplayManager;
244 ANNOTATE_LEAKING_OBJECT_PTR(instance);
245 }
246 return instance;
247 }
248
249 bool OnScreenKeyboardDisplayManager::DisplayVirtualKeyboard(
250 OnScreenKeyboardObserver* observer) {
251 if (base::win::GetVersion() < base::win::VERSION_WIN8)
252 return false;
253
254 if (base::win::IsKeyboardPresentOnSlate(nullptr))
255 return false;
256
257 if (osk_path_.empty()) {
258 // We need to launch TabTip.exe from the location specified under the
259 // LocalServer32 key for the {{054AAE20-4BEA-4347-8A35-64A533254A9D}}
260 // CLSID.
261 // TabTip.exe is typically found at
262 // c:\program files\common files\microsoft shared\ink on English Windows.
263 // We don't want to launch TabTip.exe from
264 // c:\program files (x86)\common files\microsoft shared\ink. This path is
265 // normally found on 64 bit Windows.
266 base::win::RegKey key(HKEY_LOCAL_MACHINE, kWindows8OSKRegPath,
267 KEY_READ | KEY_WOW64_64KEY);
268 DWORD osk_path_length = 1024;
269 if (key.ReadValue(NULL, base::WriteInto(&osk_path_, osk_path_length),
270 &osk_path_length, NULL) != ERROR_SUCCESS) {
271 DLOG(WARNING) << "Failed to read on screen keyboard path from registry";
272 return false;
273 }
274
275 osk_path_.resize(base::string16::traits_type::length(osk_path_.c_str()));
276
277 osk_path_ = base::ToLowerASCII(osk_path_);
278
279 size_t common_program_files_offset =
280 osk_path_.find(L"%commonprogramfiles%");
281 // Typically the path to TabTip.exe read from the registry will start with
282 // %CommonProgramFiles% which needs to be replaced with the corrsponding
283 // expanded string.
284 // If the path does not begin with %CommonProgramFiles% we use it as is.
285 if (common_program_files_offset != base::string16::npos) {
286 // Preserve the beginning quote in the path.
287 osk_path_.erase(common_program_files_offset,
288 wcslen(L"%commonprogramfiles%"));
289 // The path read from the registry contains the %CommonProgramFiles%
290 // environment variable prefix. On 64 bit Windows the SHGetKnownFolderPath
291 // function returns the common program files path with the X86 suffix for
292 // the FOLDERID_ProgramFilesCommon value.
293 // To get the correct path to TabTip.exe we first read the environment
294 // variable CommonProgramW6432 which points to the desired common
295 // files path. Failing that we fallback to the SHGetKnownFolderPath API.
296
297 // We then replace the %CommonProgramFiles% value with the actual common
298 // files path found in the process.
299 base::string16 common_program_files_path;
300 DWORD buffer_size =
301 GetEnvironmentVariable(L"CommonProgramW6432", nullptr, 0);
302 if (buffer_size) {
303 GetEnvironmentVariable(
304 L"CommonProgramW6432",
305 base::WriteInto(&common_program_files_path, buffer_size),
306 buffer_size);
307 DCHECK(!common_program_files_path.empty());
308 } else {
309 base::win::ScopedCoMem<wchar_t> common_program_files;
310 if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0, nullptr,
311 &common_program_files))) {
312 return false;
313 }
314 common_program_files_path = common_program_files;
315 }
316
317 osk_path_.insert(common_program_files_offset, common_program_files_path);
318 }
319 }
320
321 HINSTANCE ret = ::ShellExecuteW(nullptr, L"", osk_path_.c_str(), nullptr,
322 nullptr, SW_SHOW);
323
324 bool success = reinterpret_cast<intptr_t>(ret) > 32;
325 if (success) {
326 // If multiple calls to DisplayVirtualKeyboard occur one after the other,
327 // the last observer would be the one to get notifications.
328 keyboard_detector_.reset(new OnScreenKeyboardDetector);
329 if (observer)
330 keyboard_detector_->AddObserver(observer);
331 keyboard_detector_->DetectKeyboard(::GetForegroundWindow());
332 }
333 return success;
334 }
335
336 bool OnScreenKeyboardDisplayManager::DismissVirtualKeyboard() {
337 if (base::win::GetVersion() < base::win::VERSION_WIN8)
338 return false;
339
340 return keyboard_detector_ ? keyboard_detector_->DismissKeyboard() : false;
341 }
342
343 void OnScreenKeyboardDisplayManager::RemoveObserver(
344 OnScreenKeyboardObserver* observer) {
345 if (keyboard_detector_)
346 keyboard_detector_->RemoveObserver(observer);
347 }
348
349 } // namespace ui
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698