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

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

Powered by Google App Engine
This is Rietveld 408576698