Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 | |
| 14 namespace { | |
| 15 // Some helper routines used to construct keyboard event. | |
| 16 | |
| 17 // Return true of WPARAM corresponds to a UP keyboard event. | |
| 18 bool IsKeyUp(WPARAM w_param) { | |
| 19 return (w_param == WM_KEYUP) || (w_param == WM_SYSKEYUP); | |
| 20 } | |
| 21 | |
| 22 // Check if the given bit is set. | |
| 23 bool IsBitSet(ULONG value, ULONG mask) { | |
| 24 return ((value & mask) != 0); | |
| 25 } | |
| 26 | |
| 27 // Construct LPARAM corresponding to the given low level hook callback | |
| 28 // structure. | |
| 29 LPARAM GetLParamFromHookStruct(WPARAM w_param, KBDLLHOOKSTRUCT hook_struct) { | |
| 30 ULONG key_state = 0; | |
| 31 // There is no way to get repeat count so always set it to 1. | |
| 32 key_state = 1; | |
| 33 | |
| 34 // Scan code. | |
| 35 key_state |= (hook_struct.scanCode & 0xFF) << 16; | |
| 36 | |
| 37 // Extended key when the event is received as part window event and so skip | |
| 38 // it. | |
| 39 | |
| 40 // Context code. | |
| 41 key_state |= IsBitSet(hook_struct.flags, LLKHF_ALTDOWN) << 29; | |
| 42 | |
| 43 // Previous key state - set to 1 for KEYUP events. | |
| 44 key_state |= IsKeyUp(w_param) << 30; | |
| 45 | |
| 46 // Transition state. | |
| 47 key_state |= IsBitSet(hook_struct.flags, LLKHF_UP) << 31; | |
| 48 return static_cast<LPARAM>(key_state); | |
| 49 } | |
| 50 | |
| 51 // Return the location independent keycode corresponding to given keycode (e.g. | |
| 52 // return shift when left/right shift is pressed). This is needed as low level | |
| 53 // hooks get location information which is not returned as part of normal window | |
| 54 // keyboard events. | |
| 55 DWORD RemoveLocationOnKeycode(DWORD vk_code) { | |
| 56 // Virtual keycode from low level hook include location while window messages | |
| 57 // does not. So convert them to be without location. | |
| 58 switch (vk_code) { | |
| 59 case VK_LSHIFT: | |
| 60 case VK_RSHIFT: | |
| 61 return VK_SHIFT; | |
| 62 case VK_LCONTROL: | |
| 63 case VK_RCONTROL: | |
| 64 return VK_CONTROL; | |
| 65 case VK_LMENU: | |
| 66 case VK_RMENU: | |
| 67 return VK_MENU; | |
| 68 } | |
| 69 return vk_code; | |
| 70 } | |
| 71 | |
| 72 // Update thread keyboard state so GetKeyboardStatus() works correctly. Thread | |
| 73 // keyboard state is not updated when low level hook is used to intercept | |
| 74 // keyboard events. | |
| 75 bool UpdateThreadKeyboardState() { | |
| 76 const int kKeyboardStateLength = 256; | |
| 77 BYTE keyboard_state[kKeyboardStateLength]; | |
| 78 if (!GetKeyboardState(keyboard_state)) { | |
| 79 DVLOG(ERROR) << "Error getting keyboard state"; | |
| 80 return false; | |
| 81 } | |
| 82 | |
| 83 int keys_to_update[] = {VK_SHIFT, VK_CONTROL, VK_MENU}; | |
| 84 for (int index = 0; index < arraysize(keys_to_update); index++) { | |
| 85 int key = keys_to_update[index]; | |
| 86 SHORT key_state = GetAsyncKeyState(key); | |
| 87 keyboard_state[key] = (IsBitSet(key_state, 0x8000) ? 0x80 : 0) | | |
| 88 (IsBitSet(key_state, 0x1) ? 1 : 0); | |
| 89 } | |
| 90 | |
| 91 if (!SetKeyboardState(keyboard_state)) { | |
| 92 DVLOG(ERROR) << "Error setting keyboard state"; | |
| 93 return false; | |
| 94 } | |
| 95 | |
| 96 return true; | |
| 97 } | |
| 98 } | |
| 99 | |
| 100 namespace views { | |
| 101 | |
| 102 // Maintains low level registration for a window. | |
| 103 class KeyboardInterceptRegistration { | |
| 104 public: | |
| 105 KeyboardInterceptRegistration() : hook_handle_(NULL) {} | |
| 106 | |
| 107 ~KeyboardInterceptRegistration() { | |
| 108 if (hook_handle_ != NULL) | |
| 109 Unhook(); | |
| 110 } | |
| 111 | |
| 112 // Register for low level hook. | |
| 113 bool Hook(HOOKPROC callback_function) { | |
| 114 DCHECK(hook_handle_ == NULL) << "Keyboard hook already registered"; | |
| 115 hook_handle_ = SetWindowsHookEx(WH_KEYBOARD_LL, callback_function, NULL, 0); | |
| 116 if (hook_handle_ == NULL) { | |
| 117 DVLOG(ERROR) << "Error calling SetWindowsHookEx() - GLE = " | |
| 118 << GetLastError(); | |
| 119 return false; | |
| 120 } | |
| 121 return true; | |
| 122 } | |
| 123 | |
| 124 // Unhook registered hook. | |
| 125 bool Unhook() { | |
| 126 DCHECK(hook_handle_ != NULL) << "Unhook called without registring hooks"; | |
| 127 BOOL result = UnhookWindowsHookEx(hook_handle_); | |
| 128 if (!result) { | |
| 129 DVLOG(ERROR) << "Error calling UnhookWindowsHookEx() - GLE = " | |
| 130 << GetLastError(); | |
| 131 return false; | |
| 132 } | |
| 133 hook_handle_ = NULL; | |
| 134 return true; | |
| 135 } | |
| 136 | |
| 137 private: | |
| 138 // Hook returned when it was installed. | |
| 139 HHOOK hook_handle_; | |
| 140 | |
| 141 DISALLOW_COPY_AND_ASSIGN(KeyboardInterceptRegistration); | |
| 142 }; | |
| 143 | |
| 144 // Implements low level hook and manages registration for all the windows. | |
| 145 class LowLevelHookHandler { | |
| 146 public: | |
| 147 // Request all keyboard events to be routed to the given window. | |
| 148 bool Register(HWND window_handle); | |
| 149 | |
| 150 // Release the request for all keyboard events. | |
| 151 void Deregister(HWND window_handle); | |
| 152 | |
| 153 // Get singleton instance. | |
| 154 static LowLevelHookHandler* GetInstance(); | |
| 155 | |
| 156 private: | |
| 157 // Private constructor/destructor so it is accessible only | |
| 158 // DefaultSingletonTraits. | |
| 159 friend struct DefaultSingletonTraits<LowLevelHookHandler>; | |
| 160 LowLevelHookHandler() {} | |
| 161 ~LowLevelHookHandler() {} | |
| 162 | |
| 163 // Check if window handle is registered to intercept keyboard events. | |
| 164 bool IsRegistered(HWND handle); | |
| 165 | |
| 166 // Low level keyboard hook processing related functions. | |
| 167 // Hook callback called from the OS. | |
| 168 static LRESULT CALLBACK | |
| 169 KeyboardHook(int code, WPARAM w_param, LPARAM l_param); | |
| 170 | |
| 171 // There is no lock protecting this list as the low level hook callbacks are | |
| 172 // executed on same thread that registered the hook and there is only one | |
| 173 // thread | |
| 174 // that execute all view code in browser. | |
| 175 base::ScopedPtrHashMap<HWND, KeyboardInterceptRegistration> registrations_; | |
| 176 }; | |
| 177 | |
| 178 // static | |
| 179 LowLevelHookHandler* LowLevelHookHandler::GetInstance() { | |
| 180 return Singleton<LowLevelHookHandler, | |
| 181 DefaultSingletonTraits<LowLevelHookHandler> >::get(); | |
| 182 } | |
| 183 | |
| 184 bool LowLevelHookHandler::Register(HWND window_handle) { | |
|
sky
2014/07/31 21:36:55
Why do we need to support more than one hwnd captu
Sriram
2014/07/31 22:54:17
I wanted to protect against register/unregistered
| |
| 185 if (registrations_.contains(window_handle)) | |
| 186 return false; | |
| 187 | |
| 188 scoped_ptr<KeyboardInterceptRegistration> registration( | |
| 189 new KeyboardInterceptRegistration()); | |
| 190 if (registration->Hook(KeyboardHook)) { | |
| 191 if (registrations_.add(window_handle, registration.Pass()).second) | |
| 192 return true; | |
| 193 } | |
| 194 return false; | |
| 195 } | |
| 196 | |
| 197 void LowLevelHookHandler::Deregister(HWND window_handle) { | |
| 198 registrations_.erase(window_handle); | |
| 199 DVLOG(1) << "Keyboard hook unregistered for handle = " << window_handle; | |
| 200 } | |
| 201 | |
| 202 bool LowLevelHookHandler::IsRegistered(HWND handle) { | |
| 203 return registrations_.contains(handle); | |
| 204 } | |
| 205 | |
| 206 // static | |
| 207 LRESULT CALLBACK | |
| 208 LowLevelHookHandler::KeyboardHook(int code, WPARAM w_param, LPARAM l_param) { | |
| 209 HWND current_active_window = GetActiveWindow(); | |
| 210 if ((code >= 0) && GetInstance()->IsRegistered(current_active_window)) { | |
| 211 UpdateThreadKeyboardState(); | |
| 212 | |
| 213 KBDLLHOOKSTRUCT hook_struct = *reinterpret_cast<KBDLLHOOKSTRUCT*>(l_param); | |
| 214 PostMessage(current_active_window, | |
| 215 w_param, | |
| 216 RemoveLocationOnKeycode(hook_struct.vkCode), | |
| 217 GetLParamFromHookStruct(w_param, hook_struct)); | |
| 218 | |
| 219 return 1; | |
| 220 } | |
| 221 | |
|
sky
2014/07/31 21:36:55
Seems like we should unhook and/or DCHECK if you d
Sriram
2014/07/31 22:54:17
We could get called after we call unhook (Windows
| |
| 222 return CallNextHookEx(NULL, code, w_param, l_param); | |
| 223 } | |
| 224 | |
| 225 DesktopKeyboardCaptureWin::DesktopKeyboardCaptureWin(HWND window_handle) | |
| 226 : window_handle_(window_handle), registered_(false) { | |
| 227 } | |
| 228 | |
| 229 DesktopKeyboardCaptureWin::~DesktopKeyboardCaptureWin() { | |
| 230 if (registered_) | |
| 231 Release(); | |
| 232 } | |
| 233 | |
| 234 bool DesktopKeyboardCaptureWin::Capture() { | |
| 235 DCHECK(!registered_) << "Capture called multiple times"; | |
| 236 registered_ = LowLevelHookHandler::GetInstance()->Register(window_handle_); | |
| 237 return registered_; | |
| 238 } | |
| 239 | |
| 240 void DesktopKeyboardCaptureWin::Release() { | |
| 241 DCHECK(registered_) << "Release called without registring for capture"; | |
| 242 LowLevelHookHandler::GetInstance()->Deregister(window_handle_); | |
| 243 registered_ = false; | |
| 244 } | |
| 245 | |
| 246 } // namespace views | |
| OLD | NEW |