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

Side by Side Diff: ui/views/widget/desktop_aura/desktop_keyboard_capture_win.cc

Issue 297123002: API proposal for chrome.app.window to intercept all keys. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix unit test based on ben's comment along with fix bug exposed due to that Created 6 years, 4 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 (c) 2014 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/views/widget/desktop_aura/desktop_keyboard_capture_win.h"
6
7 #include <map>
8
9 #include "base/containers/scoped_ptr_hash_map.h"
10 #include "base/logging.h"
11 #include "base/macros.h"
12 #include "base/memory/singleton.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/message_loop/message_pump_win.h"
15
16 namespace {
17 // Some helper routines used to construct keyboard event.
18
19 // Return true of WPARAM corresponds to a UP keyboard event.
20 bool IsKeyUp(WPARAM w_param) {
21 return (w_param == WM_KEYUP) || (w_param == WM_SYSKEYUP);
22 }
23
24 // Check if the given bit is set.
25 bool IsBitSet(ULONG value, ULONG mask) {
26 return ((value & mask) != 0);
27 }
28
29 // Return the location independent keycode corresponding to given keycode (e.g.
30 // return shift when left/right shift is pressed). This is needed as low level
31 // hooks get location information which is not returned as part of normal window
32 // keyboard events.
33 DWORD RemoveLocationOnKeycode(DWORD vk_code) {
34 // Virtual keycode from low level hook include location while window messages
35 // does not. So convert them to be without location.
36 switch (vk_code) {
37 case VK_LSHIFT:
38 case VK_RSHIFT:
39 return VK_SHIFT;
40 case VK_LCONTROL:
41 case VK_RCONTROL:
42 return VK_CONTROL;
43 case VK_LMENU:
44 case VK_RMENU:
45 return VK_MENU;
46 }
47 return vk_code;
48 }
49
50 // Construct LPARAM corresponding to the given low level hook callback
51 // structure.
52 LPARAM GetLParamFromHookStruct(WPARAM w_param, KBDLLHOOKSTRUCT* hook_struct) {
53 ULONG key_state = 0;
54 // There is no way to get repeat count so always set it to 1.
55 key_state = 1;
56
57 // Scan code.
58 key_state |= (hook_struct->scanCode & 0xFF) << 16;
59
60 // Extended key when the event is received as part window event and so skip
61 // it.
62
63 // Context code.
64 key_state |= IsBitSet(hook_struct->flags, LLKHF_ALTDOWN) << 29;
65
66 // Previous key state - set to 1 for KEYUP events.
67 key_state |= IsKeyUp(w_param) << 30;
68
69 // Transition state.
70 key_state |= IsBitSet(hook_struct->flags, LLKHF_UP) << 31;
71
72 return static_cast<LPARAM>(key_state);
73 }
74
75 // List of key state that we want to save.
76 int keys_to_save[] = {VK_SHIFT, VK_CONTROL, VK_MENU};
77
78 // Make sure that we are not going to run out of bits saving the state.
79 C_ASSERT((arraysize(keys_to_save) * 2) <= (sizeof(WPARAM) * 8));
80
81 // Save keyboard state to WPARAM so it can be restored later before the keyboard
82 // message is processed in the main thread. This is necessary for
83 // GetKeyboardState() to work as keyboard state will be different by the time
84 // main thread processes the message.
85 WPARAM SaveKeyboardState() {
86 WPARAM value = 0;
87
88 for (int index = 0; index < arraysize(keys_to_save); index++) {
89 value <<= 2;
90 SHORT key_state = GetAsyncKeyState(keys_to_save[index]);
91 value |= ((IsBitSet(key_state, 0x8000) ? 0x2 : 0) |
92 (IsBitSet(key_state, 0x1) ? 0x1 : 0));
93 }
94 return value;
95 }
96
97 // Restore keyboard state based on saved values.
98 bool RestoreKeyboardState(WPARAM w_param) {
99 const int kKeyboardStateLength = 256;
100 BYTE keyboard_state[kKeyboardStateLength];
101 if (!GetKeyboardState(keyboard_state)) {
102 DVLOG(ERROR) << "Error getting keyboard state";
103 return false;
104 }
105
106 // restore in the reverse order of what was saved so we have the right bit for
107 // each key that was saved.
108 for (int index = arraysize(keys_to_save) - 1; index >= 0; index--) {
109 int key = keys_to_save[index];
110 keyboard_state[key] =
111 (IsBitSet(w_param, 0x2) ? 0x80 : 0) | (IsBitSet(w_param, 0x1) ? 1 : 0);
112 w_param >>= 2;
113 }
114
115 if (!SetKeyboardState(keyboard_state)) {
116 DVLOG(ERROR) << "Error setting keyboard state";
117 return false;
118 }
119
120 return true;
121 }
122
123 // Data corresponding to keyboard event.
124 struct KeyboardEventInfo {
125 UINT message_id;
126 WPARAM event_w_param;
127 LPARAM event_l_param;
128 WPARAM keyboard_state_to_restore;
129 };
130 }
sky 2014/08/11 22:42:56 End with // namespace
Sriram 2014/08/11 23:49:56 Done.
131
132 namespace views {
133
134 // Maintains low level registration for a window.
135 class KeyboardInterceptRegistration {
sky 2014/08/11 22:42:56 These classes should be in the anonymous namespace
Sriram 2014/08/11 23:49:57 Done.
136 public:
137 KeyboardInterceptRegistration() : hook_handle_(NULL) {}
138
139 ~KeyboardInterceptRegistration() {
140 if (hook_handle_ != NULL)
141 Unhook();
142 }
143
144 // Register for low level hook.
145 bool Hook(HOOKPROC callback_function) {
146 // Make sure that hook is set from main thread as it has to be valid for
sky 2014/08/11 22:42:56 It's confusing that some of these classes are tota
Sriram 2014/08/11 23:49:57 Done.
147 // the lifetime of the registration.
148 DCHECK(base::MessageLoopForUI::IsCurrent());
149 DCHECK(hook_handle_ == NULL) << "Keyboard hook already registered";
150 hook_handle_ = SetWindowsHookEx(WH_KEYBOARD_LL, callback_function, NULL, 0);
151 if (hook_handle_ == NULL) {
152 DVLOG(ERROR) << "Error calling SetWindowsHookEx() - GLE = "
153 << GetLastError();
154 return false;
155 }
156 return true;
157 }
158
159 // Unhook registered hook.
160 bool Unhook() {
161 DCHECK(hook_handle_ != NULL) << "Unhook called without registring hooks";
162 BOOL result = UnhookWindowsHookEx(hook_handle_);
163 if (!result) {
164 DVLOG(ERROR) << "Error calling UnhookWindowsHookEx() - GLE = "
165 << GetLastError();
166 return false;
167 }
168 hook_handle_ = NULL;
169 return true;
170 }
171
172 bool IsKeyboardEventQueueEmpty() { return keyboard_events_.empty(); }
173
174 // Insert keyboard event in the queue.
175 void QueueKeyboardEvent(const KeyboardEventInfo& info) {
176 keyboard_events_.push(info);
177 }
178
179 KeyboardEventInfo DequeueKeyboardEvent() {
180 KeyboardEventInfo info = keyboard_events_.front();
181 keyboard_events_.pop();
182 return info;
183 }
184
185 private:
186 std::queue<KeyboardEventInfo> keyboard_events_;
187
188 // Hook returned when it was installed.
189 HHOOK hook_handle_;
190
191 DISALLOW_COPY_AND_ASSIGN(KeyboardInterceptRegistration);
192 };
193
194 // Implements low level hook and manages registration for all the windows.
195 class LowLevelHookHandler {
196 public:
197 // Request all keyboard events to be routed to the given window.
198 bool Register(HWND window_handle);
199
200 // Release the request for all keyboard events.
201 void Deregister(HWND window_handle);
202
203 // Get singleton instance.
204 static LowLevelHookHandler* GetInstance();
205
206 private:
207 // Private constructor/destructor so it is accessible only
208 // DefaultSingletonTraits.
209 friend struct DefaultSingletonTraits<LowLevelHookHandler>;
210 LowLevelHookHandler()
211 : message_filter_hook_(NULL),
212 restore_keyboard_state_message_id_(0),
213 inject_keyboard_event_message_id_(0) {
214 restore_keyboard_state_message_id_ =
215 RegisterWindowMessage(L"chrome:restore_keyboard_state");
216 inject_keyboard_event_message_id_ =
217 RegisterWindowMessage(L"chrome:inject_keyboard_event");
218 }
219
220 ~LowLevelHookHandler() { UnregisterMessageFilter(); }
221
222 // Check if window handle is registered to intercept keyboard events.
223 bool IsRegistered(HWND handle);
sky 2014/08/11 22:42:56 AFAICT this isn't used.
Sriram 2014/08/11 23:49:56 Done.
224
225 // Low level keyboard hook processing related functions.
226 // Hook callback called from the OS.
227 static LRESULT CALLBACK
228 KeyboardHook(int code, WPARAM w_param, LPARAM l_param) {
229 return GetInstance()->HandleKeyboardHook(code, w_param, l_param);
230 }
231
232 LRESULT HandleKeyboardHook(int code, WPARAM w_param, LPARAM l_param);
233
234 // Message filter to set keyboard state based on private message.
235 static LRESULT CALLBACK
236 MessageFilterHook(int code, WPARAM w_param, LPARAM l_param) {
237 return GetInstance()->HandleMessageFilterHook(code, w_param, l_param);
238 }
239
240 LRESULT HandleMessageFilterHook(int code, WPARAM w_param, LPARAM l_param);
241
242 bool RegisterMessageFilter();
243 void UnregisterMessageFilter();
244
245 // Hook handle for window message to set keyboard state based on private
246 // message.
247 HHOOK message_filter_hook_;
248
249 // Private window message to set keyboard state for the thread.
250 UINT restore_keyboard_state_message_id_;
251
252 // Private message to inject keyboard event after current injected message is
253 // processed.
254 UINT inject_keyboard_event_message_id_;
255
256 // There is no lock protecting this list as the low level hook callbacks are
257 // executed on same thread that registered the hook and there is only one
258 // thread
259 // that execute all view code in browser.
260 base::ScopedPtrHashMap<HWND, KeyboardInterceptRegistration> registrations_;
261 };
262
263 // static
264 LowLevelHookHandler* LowLevelHookHandler::GetInstance() {
265 return Singleton<LowLevelHookHandler,
266 DefaultSingletonTraits<LowLevelHookHandler> >::get();
267 }
268
269 bool LowLevelHookHandler::Register(HWND window_handle) {
sky 2014/08/11 22:42:56 AFAICT you don't actually use the return value.
Sriram 2014/08/11 23:49:56 Done.
270 if (registrations_.contains(window_handle))
271 return false;
272
273 if (!RegisterMessageFilter()) {
sky 2014/08/11 22:42:56 nit: no {}
Sriram 2014/08/11 23:49:56 Done.
Sriram 2014/08/11 23:49:56 Done.
274 return FALSE;
sky 2014/08/11 22:42:56 false
Sriram 2014/08/11 23:49:56 Done.
275 }
276
277 scoped_ptr<KeyboardInterceptRegistration> registration(
278 new KeyboardInterceptRegistration());
279 if (registration->Hook(KeyboardHook)) {
sky 2014/08/11 22:42:56 Why do we hook for each HWND? Shouldn't we only ev
Sriram 2014/08/11 23:49:56 Currently the scope of register/unregister is cont
280 if (registrations_.add(window_handle, registration.Pass()).second)
281 return true;
282 }
283 return false;
284 }
285
286 void LowLevelHookHandler::Deregister(HWND window_handle) {
287 registrations_.erase(window_handle);
288 if (registrations_.empty())
289 UnregisterMessageFilter();
290
291 DVLOG(1) << "Keyboard hook unregistered for handle = " << window_handle;
292 }
293
294 bool LowLevelHookHandler::IsRegistered(HWND handle) {
295 return registrations_.contains(handle);
296 }
297
298 bool LowLevelHookHandler::RegisterMessageFilter() {
299 if (message_filter_hook_)
300 return TRUE;
sky 2014/08/11 22:42:55 true (in fact go through all make sure you really
Sriram 2014/08/11 23:49:56 Done.
301
302 message_filter_hook_ = SetWindowsHookEx(
303 WH_MSGFILTER, MessageFilterHook, NULL, GetCurrentThreadId());
304 if (message_filter_hook_ == NULL) {
305 DVLOG(ERROR) << "Error calling SetWindowsHookEx() to set message hook, "
306 << "gle = " << GetLastError();
307 return FALSE;
308 }
309 return TRUE;
310 }
311
312 void LowLevelHookHandler::UnregisterMessageFilter() {
313 if (message_filter_hook_ != NULL) {
314 UnhookWindowsHookEx(message_filter_hook_);
315 message_filter_hook_ = NULL;
316 }
317 }
318
319 LRESULT
320 LowLevelHookHandler::HandleMessageFilterHook(int code,
321 WPARAM w_param,
322 LPARAM l_param) {
323 // Ignore if not called from main message loop.
324 if (code != base::MessagePumpForUI::kMessageFilterCode)
325 return FALSE;
326
327 MSG* msg = reinterpret_cast<MSG*>(l_param);
328 if (msg->message == restore_keyboard_state_message_id_) {
329 RestoreKeyboardState(msg->wParam);
330 return TRUE;
331 } else if (msg->message == inject_keyboard_event_message_id_) {
332 KeyboardInterceptRegistration* registration = registrations_.get(msg->hwnd);
333 if (registration) {
334 // Post keyboard state and key event to main thread for processing.
335 KeyboardEventInfo event_info = registration->DequeueKeyboardEvent();
336 PostMessage(msg->hwnd,
337 restore_keyboard_state_message_id_,
338 event_info.keyboard_state_to_restore,
339 static_cast<LPARAM>(0));
340
341 PostMessage(msg->hwnd,
342 event_info.message_id,
343 event_info.event_w_param,
344 event_info.event_l_param);
345
346 if (!registration->IsKeyboardEventQueueEmpty()) {
347 // Post another inject keyboard event if there are more key events to
348 // process after the current injected event is processed.
349 PostMessage(msg->hwnd,
350 inject_keyboard_event_message_id_,
351 static_cast<WPARAM>(0),
352 static_cast<LPARAM>(0));
353 }
354
355 return TRUE;
356 }
357 }
358
359 return FALSE;
360 }
361
362 LRESULT
363 LowLevelHookHandler::HandleKeyboardHook(int code,
364 WPARAM w_param,
365 LPARAM l_param) {
366 HWND current_active_window = GetActiveWindow();
367
368 if (code >= 0) {
369 KeyboardInterceptRegistration* registration =
370 registrations_.get(current_active_window);
371 if (registration) {
sky 2014/08/11 22:42:55 Can you really end up with a situation where there
Sriram 2014/08/11 23:49:56 Yes, this is possible as you can get a callback af
sky 2014/08/12 16:18:47 Are you sure about that? Once you unhook you shoul
Sriram 2014/08/12 16:24:04 See remarks for UnhookWindowsHookEx(): http://msdn
372 PKBDLLHOOKSTRUCT hook_struct =
373 reinterpret_cast<PKBDLLHOOKSTRUCT>(l_param);
374
375 KeyboardEventInfo event_info = {0};
376 event_info.message_id = w_param;
377 event_info.event_w_param = RemoveLocationOnKeycode(hook_struct->vkCode);
378 event_info.event_l_param = GetLParamFromHookStruct(w_param, hook_struct);
379 event_info.keyboard_state_to_restore = SaveKeyboardState();
380
381 bool should_queue_inject_event =
382 registration->IsKeyboardEventQueueEmpty();
383
384 registration->QueueKeyboardEvent(event_info);
385 if (should_queue_inject_event) {
386 PostMessage(current_active_window,
sky 2014/08/11 22:42:56 I don't understand the need to post messages. Can'
Sriram 2014/08/11 23:49:57 Keyboard events needs to be posted so that char ev
sky 2014/08/12 16:18:47 This seems wrong to me. I'm adding ananta to the r
387 inject_keyboard_event_message_id_,
388 static_cast<WPARAM>(0),
389 static_cast<LPARAM>(0));
390 }
391 return 1;
392 }
393 }
394
395 return CallNextHookEx(NULL, code, w_param, l_param);
396 }
397
398 DesktopKeyboardCaptureWin::DesktopKeyboardCaptureWin(HWND window_handle)
399 : window_handle_(window_handle) {
400 LowLevelHookHandler::GetInstance()->Register(window_handle_);
401 }
402
403 DesktopKeyboardCaptureWin::~DesktopKeyboardCaptureWin() {
404 LowLevelHookHandler::GetInstance()->Deregister(window_handle_);
405 }
406
407 } // namespace views
OLDNEW
« no previous file with comments | « ui/views/widget/desktop_aura/desktop_keyboard_capture_win.h ('k') | ui/views/widget/desktop_aura/desktop_native_widget_aura.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698