1 // Copyright 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.
5 #include "chrome/browser/ui/views/apps/keyboard_hook_handler.h"
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 #include "ui/aura/window.h"
16 #include "ui/aura/window_tree_host.h"
19 // Some helper routines used to construct keyboard event.
21 // Return true of WPARAM corresponds to a UP keyboard event.
22 bool IsKeyUp(WPARAM w_param
) {
23 return (w_param
== WM_KEYUP
) || (w_param
== WM_SYSKEYUP
);
26 // Check if the given bit is set.
27 bool IsBitSet(ULONG value
, ULONG mask
) {
28 return ((value
& mask
) != 0);
31 // Get Window handle from widget.
32 HWND
GetWindowHandle(views::Widget
* widget
) {
33 return widget
->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
36 // Return the location independent keycode corresponding to given keycode (e.g.
37 // return shift when left/right shift is pressed). This is needed as low level
38 // hooks get location information which is not returned as part of normal window
40 DWORD
RemoveLocationOnKeycode(DWORD vk_code
) {
41 // Virtual keycode from low level hook include location while window messages
42 // does not. So convert them to be without location.
57 // Construct LPARAM corresponding to the given low level hook callback
59 LPARAM
GetLParamFromHookStruct(WPARAM w_param
, KBDLLHOOKSTRUCT
* hook_struct
) {
61 // There is no way to get repeat count so always set it to 1.
65 key_state
|= (hook_struct
->scanCode
& 0xFF) << 16;
67 // Extended key when the event is received as part window event and so skip
71 key_state
|= IsBitSet(hook_struct
->flags
, LLKHF_ALTDOWN
) << 29;
73 // Previous key state - set to 1 for KEYUP events.
74 key_state
|= IsKeyUp(w_param
) << 30;
77 key_state
|= IsBitSet(hook_struct
->flags
, LLKHF_UP
) << 31;
79 return static_cast<LPARAM
>(key_state
);
82 // List of key state that we want to save.
83 const int kKeysToSave
[] = {VK_SHIFT
, VK_CONTROL
, VK_MENU
};
85 // Make sure that we are not going to run out of bits saving the state.
86 C_ASSERT((arraysize(kKeysToSave
) * 2) <= (sizeof(WPARAM
) * 8));
88 // Save keyboard state to WPARAM so it can be restored later before the keyboard
89 // message is processed in the main thread. This is necessary for
90 // GetKeyboardState() to work as keyboard state will be different by the time
91 // main thread processes the message.
92 WPARAM
SaveKeyboardState() {
95 for (int index
= 0; index
< arraysize(kKeysToSave
); index
++) {
97 SHORT key_state
= GetAsyncKeyState(kKeysToSave
[index
]);
98 value
|= ((IsBitSet(key_state
, 0x8000) ? 0x2 : 0) |
99 (IsBitSet(key_state
, 0x1) ? 0x1 : 0));
104 // Restore keyboard state based on saved values.
105 bool RestoreKeyboardState(WPARAM w_param
) {
106 const int kKeyboardStateLength
= 256;
107 BYTE keyboard_state
[kKeyboardStateLength
];
108 if (!GetKeyboardState(keyboard_state
)) {
109 DVLOG(ERROR
) << "Error getting keyboard state";
113 // restore in the reverse order of what was saved so we have the right bit for
114 // each key that was saved.
115 for (int index
= arraysize(kKeysToSave
) - 1; index
>= 0; index
--) {
116 int key
= kKeysToSave
[index
];
117 keyboard_state
[key
] =
118 (IsBitSet(w_param
, 0x2) ? 0x80 : 0) | (IsBitSet(w_param
, 0x1) ? 1 : 0);
122 if (!SetKeyboardState(keyboard_state
)) {
123 DVLOG(ERROR
) << "Error setting keyboard state";
130 // Data corresponding to keyboard event.
131 struct KeyboardEventInfo
{
133 WPARAM event_w_param
;
134 LPARAM event_l_param
;
135 WPARAM keyboard_state_to_restore
;
138 // Maintains low level registration for a window.
139 class KeyboardInterceptRegistration
{
141 KeyboardInterceptRegistration();
143 // Are there any keyboard events queued.
144 bool IsKeyboardEventQueueEmpty();
146 // Insert keyboard event in the queue.
147 void QueueKeyboardEvent(const KeyboardEventInfo
& info
);
149 KeyboardEventInfo
DequeueKeyboardEvent();
152 std::queue
<KeyboardEventInfo
> keyboard_events_
;
154 DISALLOW_COPY_AND_ASSIGN(KeyboardInterceptRegistration
);
157 // Implements low level hook and manages registration for all the windows.
158 class LowLevelHookHandler
{
160 // Request all keyboard events to be routed to the given window.
161 void Register(HWND window_handle
);
163 // Release the request for all keyboard events.
164 void Deregister(HWND window_handle
);
166 // Get singleton instance.
167 static LowLevelHookHandler
* GetInstance();
170 // Private constructor/destructor so it is accessible only
171 // DefaultSingletonTraits.
172 friend struct DefaultSingletonTraits
<LowLevelHookHandler
>;
173 LowLevelHookHandler();
175 ~LowLevelHookHandler();
177 // Low level keyboard hook processing related functions.
178 // Hook callback called from the OS.
179 static LRESULT CALLBACK
180 KeyboardHook(int code
, WPARAM w_param
, LPARAM l_param
);
182 // Low level keyboard hook handler.
183 LRESULT
HandleKeyboardHook(int code
, WPARAM w_param
, LPARAM l_param
);
185 // Message filter to set keyboard state based on private message.
186 static LRESULT CALLBACK
187 MessageFilterHook(int code
, WPARAM w_param
, LPARAM l_param
);
189 // Message filter handler.
190 LRESULT
HandleMessageFilterHook(int code
, WPARAM w_param
, LPARAM l_param
);
195 // Hook handle for window message to set keyboard state based on private
197 HHOOK message_filter_hook_
;
199 // Hook handle for low level keyboard hook.
200 HHOOK keyboard_hook_
;
202 // Private window message to set keyboard state for the thread.
203 UINT restore_keyboard_state_message_id_
;
205 // Private message to inject keyboard event after current injected message is
207 UINT inject_keyboard_event_message_id_
;
209 // There is no lock protecting this list as the low level hook callbacks are
210 // executed on same thread that registered the hook and there is only one
212 // that execute all view code in browser.
213 base::ScopedPtrHashMap
<HWND
, KeyboardInterceptRegistration
> registrations_
;
215 DISALLOW_COPY_AND_ASSIGN(LowLevelHookHandler
);
218 KeyboardInterceptRegistration::KeyboardInterceptRegistration() {
221 bool KeyboardInterceptRegistration::IsKeyboardEventQueueEmpty() {
222 return keyboard_events_
.empty();
225 void KeyboardInterceptRegistration::QueueKeyboardEvent(
226 const KeyboardEventInfo
& info
) {
227 keyboard_events_
.push(info
);
230 KeyboardEventInfo
KeyboardInterceptRegistration::DequeueKeyboardEvent() {
231 KeyboardEventInfo info
= keyboard_events_
.front();
232 keyboard_events_
.pop();
236 LowLevelHookHandler::LowLevelHookHandler()
237 : message_filter_hook_(NULL
),
238 keyboard_hook_(NULL
),
239 restore_keyboard_state_message_id_(0),
240 inject_keyboard_event_message_id_(0) {
241 restore_keyboard_state_message_id_
=
242 RegisterWindowMessage(L
"chrome:restore_keyboard_state");
243 inject_keyboard_event_message_id_
=
244 RegisterWindowMessage(L
"chrome:inject_keyboard_event");
247 LowLevelHookHandler::~LowLevelHookHandler() {
253 LowLevelHookHandler::KeyboardHook(int code
, WPARAM w_param
, LPARAM l_param
) {
254 return GetInstance()->HandleKeyboardHook(code
, w_param
, l_param
);
258 LRESULT CALLBACK
LowLevelHookHandler::MessageFilterHook(int code
,
261 return GetInstance()->HandleMessageFilterHook(code
, w_param
, l_param
);
265 LowLevelHookHandler
* LowLevelHookHandler::GetInstance() {
266 return Singleton
<LowLevelHookHandler
,
267 DefaultSingletonTraits
<LowLevelHookHandler
>>::get();
270 void LowLevelHookHandler::Register(HWND window_handle
) {
271 if (registrations_
.contains(window_handle
))
277 scoped_ptr
<KeyboardInterceptRegistration
> registration(
278 new KeyboardInterceptRegistration());
279 registrations_
.add(window_handle
, registration
.Pass());
282 void LowLevelHookHandler::Deregister(HWND window_handle
) {
283 registrations_
.erase(window_handle
);
284 if (registrations_
.empty())
287 DVLOG(1) << "Keyboard hook unregistered for handle = " << window_handle
;
290 bool LowLevelHookHandler::EnableHooks() {
291 // Make sure that hook is set from main thread as it has to be valid for
292 // the lifetime of the registration.
293 DCHECK(base::MessageLoopForUI::IsCurrent());
298 message_filter_hook_
= SetWindowsHookEx(WH_MSGFILTER
, MessageFilterHook
, NULL
,
299 GetCurrentThreadId());
300 if (message_filter_hook_
== NULL
) {
301 DVLOG(ERROR
) << "Error calling SetWindowsHookEx() to set message hook, "
302 << "gle = " << GetLastError();
306 DCHECK(keyboard_hook_
== NULL
) << "Keyboard hook already registered";
308 keyboard_hook_
= SetWindowsHookEx(WH_KEYBOARD_LL
, KeyboardHook
, NULL
, 0);
309 if (keyboard_hook_
== NULL
) {
310 DVLOG(ERROR
) << "Error calling SetWindowsHookEx() - GLE = "
319 void LowLevelHookHandler::DisableHooks() {
320 if (keyboard_hook_
!= NULL
) {
321 UnhookWindowsHookEx(keyboard_hook_
);
322 keyboard_hook_
= NULL
;
325 if (message_filter_hook_
!= NULL
) {
326 UnhookWindowsHookEx(message_filter_hook_
);
327 message_filter_hook_
= NULL
;
332 LowLevelHookHandler::HandleMessageFilterHook(int code
,
335 // Ignore if not called from main message loop.
336 if (code
!= base::MessagePumpForUI::kMessageFilterCode
)
337 return CallNextHookEx(NULL
, code
, w_param
, l_param
);
339 MSG
* msg
= reinterpret_cast<MSG
*>(l_param
);
340 if (msg
->message
== restore_keyboard_state_message_id_
) {
341 RestoreKeyboardState(msg
->wParam
);
343 } else if (msg
->message
== inject_keyboard_event_message_id_
) {
344 KeyboardInterceptRegistration
* registration
= registrations_
.get(msg
->hwnd
);
346 // Post keyboard state and key event to main thread for processing.
347 KeyboardEventInfo event_info
= registration
->DequeueKeyboardEvent();
348 PostMessage(msg
->hwnd
, restore_keyboard_state_message_id_
,
349 event_info
.keyboard_state_to_restore
, static_cast<LPARAM
>(0));
351 PostMessage(msg
->hwnd
, event_info
.message_id
, event_info
.event_w_param
,
352 event_info
.event_l_param
);
354 if (!registration
->IsKeyboardEventQueueEmpty()) {
355 // Post another inject keyboard event if there are more key events to
356 // process after the current injected event is processed.
357 PostMessage(msg
->hwnd
, inject_keyboard_event_message_id_
,
358 static_cast<WPARAM
>(0), static_cast<LPARAM
>(0));
365 return CallNextHookEx(NULL
, code
, w_param
, l_param
);
369 LowLevelHookHandler::HandleKeyboardHook(int code
,
372 HWND current_active_window
= GetForegroundWindow();
374 // Additional check to make sure that the current window is indeed owned by
377 ::GetWindowThreadProcessId(current_active_window
, &pid
);
378 if (!pid
|| (pid
!= ::GetCurrentProcessId()))
379 return CallNextHookEx(NULL
, code
, w_param
, l_param
);
381 if ((code
>= 0) && (current_active_window
!= NULL
)) {
382 KeyboardInterceptRegistration
* registration
=
383 registrations_
.get(current_active_window
);
385 // Save keyboard state to queue and post message to handle keyboard event
386 // if the queue is not empty. It is done this way as keyboard state should
387 // be preserved until char event corresponding to the keyboard event is
388 // handled (so correct alt/shift/control key state is set). Also
389 // SendMessage() cannot be used as it would bypass both message loop
390 // delegates and TransalateMessage() calls (which will inserts char
392 PKBDLLHOOKSTRUCT hook_struct
=
393 reinterpret_cast<PKBDLLHOOKSTRUCT
>(l_param
);
395 KeyboardEventInfo event_info
= {0};
396 event_info
.message_id
= w_param
;
397 event_info
.event_w_param
= RemoveLocationOnKeycode(hook_struct
->vkCode
);
398 event_info
.event_l_param
= GetLParamFromHookStruct(w_param
, hook_struct
);
399 event_info
.keyboard_state_to_restore
= SaveKeyboardState();
401 bool should_queue_inject_event
=
402 registration
->IsKeyboardEventQueueEmpty();
404 registration
->QueueKeyboardEvent(event_info
);
405 if (should_queue_inject_event
) {
406 PostMessage(current_active_window
, inject_keyboard_event_message_id_
,
407 static_cast<WPARAM
>(0), static_cast<LPARAM
>(0));
413 return CallNextHookEx(NULL
, code
, w_param
, l_param
);
417 KeyboardHookHandler
* KeyboardHookHandler::GetInstance() {
418 return Singleton
<KeyboardHookHandler
,
419 DefaultSingletonTraits
<KeyboardHookHandler
>>::get();
422 void KeyboardHookHandler::Register(views::Widget
* widget
) {
423 LowLevelHookHandler::GetInstance()->Register(GetWindowHandle(widget
));
426 void KeyboardHookHandler::Deregister(views::Widget
* widget
) {
427 LowLevelHookHandler::GetInstance()->Deregister(GetWindowHandle(widget
));