Infobar material design refresh: layout
[chromium-blink-merge.git] / chrome / browser / ui / views / apps / keyboard_hook_handler_win.cc
blobc6096640b1f65e5436bd3743dd64ebe0cf0a6e30
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"
7 #include <map>
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"
18 namespace {
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
39 // keyboard events.
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.
43 switch (vk_code) {
44 case VK_LSHIFT:
45 case VK_RSHIFT:
46 return VK_SHIFT;
47 case VK_LCONTROL:
48 case VK_RCONTROL:
49 return VK_CONTROL;
50 case VK_LMENU:
51 case VK_RMENU:
52 return VK_MENU;
54 return vk_code;
57 // Construct LPARAM corresponding to the given low level hook callback
58 // structure.
59 LPARAM GetLParamFromHookStruct(WPARAM w_param, KBDLLHOOKSTRUCT* hook_struct) {
60 ULONG key_state = 0;
61 // There is no way to get repeat count so always set it to 1.
62 key_state = 1;
64 // Scan code.
65 key_state |= (hook_struct->scanCode & 0xFF) << 16;
67 // Extended key when the event is received as part window event and so skip
68 // it.
70 // Context code.
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;
76 // Transition state.
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() {
93 WPARAM value = 0;
95 for (int index = 0; index < arraysize(kKeysToSave); index++) {
96 value <<= 2;
97 SHORT key_state = GetAsyncKeyState(kKeysToSave[index]);
98 value |= ((IsBitSet(key_state, 0x8000) ? 0x2 : 0) |
99 (IsBitSet(key_state, 0x1) ? 0x1 : 0));
101 return value;
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";
110 return false;
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);
119 w_param >>= 2;
122 if (!SetKeyboardState(keyboard_state)) {
123 DVLOG(ERROR) << "Error setting keyboard state";
124 return false;
127 return true;
130 // Data corresponding to keyboard event.
131 struct KeyboardEventInfo {
132 UINT message_id;
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 {
140 public:
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();
151 private:
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 {
159 public:
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();
169 private:
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);
192 bool EnableHooks();
193 void DisableHooks();
195 // Hook handle for window message to set keyboard state based on private
196 // message.
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
206 // processed.
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
211 // thread
212 // that execute all view code in browser.
213 base::ScopedPtrHashMap<HWND, scoped_ptr<KeyboardInterceptRegistration>>
214 registrations_;
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();
234 return info;
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() {
249 DisableHooks();
252 // static
253 LRESULT CALLBACK
254 LowLevelHookHandler::KeyboardHook(int code, WPARAM w_param, LPARAM l_param) {
255 return GetInstance()->HandleKeyboardHook(code, w_param, l_param);
258 // static
259 LRESULT CALLBACK LowLevelHookHandler::MessageFilterHook(int code,
260 WPARAM w_param,
261 LPARAM l_param) {
262 return GetInstance()->HandleMessageFilterHook(code, w_param, l_param);
265 // static
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))
273 return;
275 if (!EnableHooks())
276 return;
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())
286 DisableHooks();
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());
296 if (keyboard_hook_)
297 return true;
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();
304 return false;
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 = "
312 << GetLastError();
313 DisableHooks();
314 return false;
317 return true;
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;
332 LRESULT
333 LowLevelHookHandler::HandleMessageFilterHook(int code,
334 WPARAM w_param,
335 LPARAM l_param) {
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);
343 return true;
344 } else if (msg->message == inject_keyboard_event_message_id_) {
345 KeyboardInterceptRegistration* registration = registrations_.get(msg->hwnd);
346 if (registration) {
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));
362 return true;
366 return CallNextHookEx(NULL, code, w_param, l_param);
369 LRESULT
370 LowLevelHookHandler::HandleKeyboardHook(int code,
371 WPARAM w_param,
372 LPARAM l_param) {
373 HWND current_active_window = GetForegroundWindow();
375 // Additional check to make sure that the current window is indeed owned by
376 // this proccess.
377 DWORD pid = 0;
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);
385 if (registration) {
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
392 // events).
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));
410 return 1;
414 return CallNextHookEx(NULL, code, w_param, l_param);
416 } // namespace
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));