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
, scoped_ptr
<KeyboardInterceptRegistration
>>
216 DISALLOW_COPY_AND_ASSIGN(LowLevelHookHandler
);
219 KeyboardInterceptRegistration::KeyboardInterceptRegistration() {
222 bool KeyboardInterceptRegistration::IsKeyboardEventQueueEmpty() {
223 return keyboard_events_
.empty();
226 void KeyboardInterceptRegistration::QueueKeyboardEvent(
227 const KeyboardEventInfo
& info
) {
228 keyboard_events_
.push(info
);
231 KeyboardEventInfo
KeyboardInterceptRegistration::DequeueKeyboardEvent() {
232 KeyboardEventInfo info
= keyboard_events_
.front();
233 keyboard_events_
.pop();
237 LowLevelHookHandler::LowLevelHookHandler()
238 : message_filter_hook_(NULL
),
239 keyboard_hook_(NULL
),
240 restore_keyboard_state_message_id_(0),
241 inject_keyboard_event_message_id_(0) {
242 restore_keyboard_state_message_id_
=
243 RegisterWindowMessage(L
"chrome:restore_keyboard_state");
244 inject_keyboard_event_message_id_
=
245 RegisterWindowMessage(L
"chrome:inject_keyboard_event");
248 LowLevelHookHandler::~LowLevelHookHandler() {
254 LowLevelHookHandler::KeyboardHook(int code
, WPARAM w_param
, LPARAM l_param
) {
255 return GetInstance()->HandleKeyboardHook(code
, w_param
, l_param
);
259 LRESULT CALLBACK
LowLevelHookHandler::MessageFilterHook(int code
,
262 return GetInstance()->HandleMessageFilterHook(code
, w_param
, l_param
);
266 LowLevelHookHandler
* LowLevelHookHandler::GetInstance() {
267 return Singleton
<LowLevelHookHandler
,
268 DefaultSingletonTraits
<LowLevelHookHandler
>>::get();
271 void LowLevelHookHandler::Register(HWND window_handle
) {
272 if (registrations_
.contains(window_handle
))
278 scoped_ptr
<KeyboardInterceptRegistration
> registration(
279 new KeyboardInterceptRegistration());
280 registrations_
.add(window_handle
, registration
.Pass());
283 void LowLevelHookHandler::Deregister(HWND window_handle
) {
284 registrations_
.erase(window_handle
);
285 if (registrations_
.empty())
288 DVLOG(1) << "Keyboard hook unregistered for handle = " << window_handle
;
291 bool LowLevelHookHandler::EnableHooks() {
292 // Make sure that hook is set from main thread as it has to be valid for
293 // the lifetime of the registration.
294 DCHECK(base::MessageLoopForUI::IsCurrent());
299 message_filter_hook_
= SetWindowsHookEx(WH_MSGFILTER
, MessageFilterHook
, NULL
,
300 GetCurrentThreadId());
301 if (message_filter_hook_
== NULL
) {
302 DVLOG(ERROR
) << "Error calling SetWindowsHookEx() to set message hook, "
303 << "gle = " << GetLastError();
307 DCHECK(keyboard_hook_
== NULL
) << "Keyboard hook already registered";
309 keyboard_hook_
= SetWindowsHookEx(WH_KEYBOARD_LL
, KeyboardHook
, NULL
, 0);
310 if (keyboard_hook_
== NULL
) {
311 DVLOG(ERROR
) << "Error calling SetWindowsHookEx() - GLE = "
320 void LowLevelHookHandler::DisableHooks() {
321 if (keyboard_hook_
!= NULL
) {
322 UnhookWindowsHookEx(keyboard_hook_
);
323 keyboard_hook_
= NULL
;
326 if (message_filter_hook_
!= NULL
) {
327 UnhookWindowsHookEx(message_filter_hook_
);
328 message_filter_hook_
= NULL
;
333 LowLevelHookHandler::HandleMessageFilterHook(int code
,
336 // Ignore if not called from main message loop.
337 if (code
!= base::MessagePumpForUI::kMessageFilterCode
)
338 return CallNextHookEx(NULL
, code
, w_param
, l_param
);
340 MSG
* msg
= reinterpret_cast<MSG
*>(l_param
);
341 if (msg
->message
== restore_keyboard_state_message_id_
) {
342 RestoreKeyboardState(msg
->wParam
);
344 } else if (msg
->message
== inject_keyboard_event_message_id_
) {
345 KeyboardInterceptRegistration
* registration
= registrations_
.get(msg
->hwnd
);
347 // Post keyboard state and key event to main thread for processing.
348 KeyboardEventInfo event_info
= registration
->DequeueKeyboardEvent();
349 PostMessage(msg
->hwnd
, restore_keyboard_state_message_id_
,
350 event_info
.keyboard_state_to_restore
, static_cast<LPARAM
>(0));
352 PostMessage(msg
->hwnd
, event_info
.message_id
, event_info
.event_w_param
,
353 event_info
.event_l_param
);
355 if (!registration
->IsKeyboardEventQueueEmpty()) {
356 // Post another inject keyboard event if there are more key events to
357 // process after the current injected event is processed.
358 PostMessage(msg
->hwnd
, inject_keyboard_event_message_id_
,
359 static_cast<WPARAM
>(0), static_cast<LPARAM
>(0));
366 return CallNextHookEx(NULL
, code
, w_param
, l_param
);
370 LowLevelHookHandler::HandleKeyboardHook(int code
,
373 HWND current_active_window
= GetForegroundWindow();
375 // Additional check to make sure that the current window is indeed owned by
378 ::GetWindowThreadProcessId(current_active_window
, &pid
);
379 if (!pid
|| (pid
!= ::GetCurrentProcessId()))
380 return CallNextHookEx(NULL
, code
, w_param
, l_param
);
382 if ((code
>= 0) && (current_active_window
!= NULL
)) {
383 KeyboardInterceptRegistration
* registration
=
384 registrations_
.get(current_active_window
);
386 // Save keyboard state to queue and post message to handle keyboard event
387 // if the queue is not empty. It is done this way as keyboard state should
388 // be preserved until char event corresponding to the keyboard event is
389 // handled (so correct alt/shift/control key state is set). Also
390 // SendMessage() cannot be used as it would bypass both message loop
391 // delegates and TransalateMessage() calls (which will inserts char
393 PKBDLLHOOKSTRUCT hook_struct
=
394 reinterpret_cast<PKBDLLHOOKSTRUCT
>(l_param
);
396 KeyboardEventInfo event_info
= {0};
397 event_info
.message_id
= w_param
;
398 event_info
.event_w_param
= RemoveLocationOnKeycode(hook_struct
->vkCode
);
399 event_info
.event_l_param
= GetLParamFromHookStruct(w_param
, hook_struct
);
400 event_info
.keyboard_state_to_restore
= SaveKeyboardState();
402 bool should_queue_inject_event
=
403 registration
->IsKeyboardEventQueueEmpty();
405 registration
->QueueKeyboardEvent(event_info
);
406 if (should_queue_inject_event
) {
407 PostMessage(current_active_window
, inject_keyboard_event_message_id_
,
408 static_cast<WPARAM
>(0), static_cast<LPARAM
>(0));
414 return CallNextHookEx(NULL
, code
, w_param
, l_param
);
418 KeyboardHookHandler
* KeyboardHookHandler::GetInstance() {
419 return Singleton
<KeyboardHookHandler
,
420 DefaultSingletonTraits
<KeyboardHookHandler
>>::get();
423 void KeyboardHookHandler::Register(views::Widget
* widget
) {
424 LowLevelHookHandler::GetInstance()->Register(GetWindowHandle(widget
));
427 void KeyboardHookHandler::Deregister(views::Widget
* widget
) {
428 LowLevelHookHandler::GetInstance()->Deregister(GetWindowHandle(widget
));