Chromium Code Reviews| Index: ui/views/widget/desktop_aura/desktop_keyboard_capture_win.cc |
| diff --git a/ui/views/widget/desktop_aura/desktop_keyboard_capture_win.cc b/ui/views/widget/desktop_aura/desktop_keyboard_capture_win.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..b1a6ed1c026ed8e31d0304eedb4f29e4dab62a68 |
| --- /dev/null |
| +++ b/ui/views/widget/desktop_aura/desktop_keyboard_capture_win.cc |
| @@ -0,0 +1,246 @@ |
| +// Copyright (c) 2014 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "ui/views/widget/desktop_aura/desktop_keyboard_capture_win.h" |
| + |
| +#include <map> |
| + |
| +#include "base/containers/scoped_ptr_hash_map.h" |
| +#include "base/logging.h" |
| +#include "base/macros.h" |
| +#include "base/memory/singleton.h" |
| + |
| +namespace { |
| +// Some helper routines used to construct keyboard event. |
| + |
| +// Return true of WPARAM corresponds to a UP keyboard event. |
| +bool IsKeyUp(WPARAM w_param) { |
| + return (w_param == WM_KEYUP) || (w_param == WM_SYSKEYUP); |
| +} |
| + |
| +// Check if the given bit is set. |
| +bool IsBitSet(ULONG value, ULONG mask) { |
| + return ((value & mask) != 0); |
| +} |
| + |
| +// Construct LPARAM corresponding to the given low level hook callback |
| +// structure. |
| +LPARAM GetLParamFromHookStruct(WPARAM w_param, KBDLLHOOKSTRUCT hook_struct) { |
| + ULONG key_state = 0; |
| + // There is no way to get repeat count so always set it to 1. |
| + key_state = 1; |
| + |
| + // Scan code. |
| + key_state |= (hook_struct.scanCode & 0xFF) << 16; |
| + |
| + // Extended key when the event is received as part window event and so skip |
| + // it. |
| + |
| + // Context code. |
| + key_state |= IsBitSet(hook_struct.flags, LLKHF_ALTDOWN) << 29; |
| + |
| + // Previous key state - set to 1 for KEYUP events. |
| + key_state |= IsKeyUp(w_param) << 30; |
| + |
| + // Transition state. |
| + key_state |= IsBitSet(hook_struct.flags, LLKHF_UP) << 31; |
| + return static_cast<LPARAM>(key_state); |
| +} |
| + |
| +// Return the location independent keycode corresponding to given keycode (e.g. |
| +// return shift when left/right shift is pressed). This is needed as low level |
| +// hooks get location information which is not returned as part of normal window |
| +// keyboard events. |
| +DWORD RemoveLocationOnKeycode(DWORD vk_code) { |
| + // Virtual keycode from low level hook include location while window messages |
| + // does not. So convert them to be without location. |
| + switch (vk_code) { |
| + case VK_LSHIFT: |
| + case VK_RSHIFT: |
| + return VK_SHIFT; |
| + case VK_LCONTROL: |
| + case VK_RCONTROL: |
| + return VK_CONTROL; |
| + case VK_LMENU: |
| + case VK_RMENU: |
| + return VK_MENU; |
| + } |
| + return vk_code; |
| +} |
| + |
| +// Update thread keyboard state so GetKeyboardStatus() works correctly. Thread |
| +// keyboard state is not updated when low level hook is used to intercept |
| +// keyboard events. |
| +bool UpdateThreadKeyboardState() { |
| + const int kKeyboardStateLength = 256; |
| + BYTE keyboard_state[kKeyboardStateLength]; |
| + if (!GetKeyboardState(keyboard_state)) { |
| + DVLOG(ERROR) << "Error getting keyboard state"; |
| + return false; |
| + } |
| + |
| + int keys_to_update[] = {VK_SHIFT, VK_CONTROL, VK_MENU}; |
| + for (int index = 0; index < arraysize(keys_to_update); index++) { |
| + int key = keys_to_update[index]; |
| + SHORT key_state = GetAsyncKeyState(key); |
| + keyboard_state[key] = (IsBitSet(key_state, 0x8000) ? 0x80 : 0) | |
| + (IsBitSet(key_state, 0x1) ? 1 : 0); |
| + } |
| + |
| + if (!SetKeyboardState(keyboard_state)) { |
| + DVLOG(ERROR) << "Error setting keyboard state"; |
| + return false; |
| + } |
| + |
| + return true; |
| +} |
| +} |
| + |
| +namespace views { |
| + |
| +// Maintains low level registration for a window. |
| +class KeyboardInterceptRegistration { |
| + public: |
| + KeyboardInterceptRegistration() : hook_handle_(NULL) {} |
| + |
| + ~KeyboardInterceptRegistration() { |
| + if (hook_handle_ != NULL) |
| + Unhook(); |
| + } |
| + |
| + // Register for low level hook. |
| + bool Hook(HOOKPROC callback_function) { |
| + DCHECK(hook_handle_ == NULL) << "Keyboard hook already registered"; |
| + hook_handle_ = SetWindowsHookEx(WH_KEYBOARD_LL, callback_function, NULL, 0); |
| + if (hook_handle_ == NULL) { |
| + DVLOG(ERROR) << "Error calling SetWindowsHookEx() - GLE = " |
| + << GetLastError(); |
| + return false; |
| + } |
| + return true; |
| + } |
| + |
| + // Unhook registered hook. |
| + bool Unhook() { |
| + DCHECK(hook_handle_ != NULL) << "Unhook called without registring hooks"; |
| + BOOL result = UnhookWindowsHookEx(hook_handle_); |
| + if (!result) { |
| + DVLOG(ERROR) << "Error calling UnhookWindowsHookEx() - GLE = " |
| + << GetLastError(); |
| + return false; |
| + } |
| + hook_handle_ = NULL; |
| + return true; |
| + } |
| + |
| + private: |
| + // Hook returned when it was installed. |
| + HHOOK hook_handle_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(KeyboardInterceptRegistration); |
| +}; |
| + |
| +// Implements low level hook and manages registration for all the windows. |
| +class LowLevelHookHandler { |
| + public: |
| + // Request all keyboard events to be routed to the given window. |
| + bool Register(HWND window_handle); |
| + |
| + // Release the request for all keyboard events. |
| + void Deregister(HWND window_handle); |
| + |
| + // Get singleton instance. |
| + static LowLevelHookHandler* GetInstance(); |
| + |
| + private: |
| + // Private constructor/destructor so it is accessible only |
| + // DefaultSingletonTraits. |
| + friend struct DefaultSingletonTraits<LowLevelHookHandler>; |
| + LowLevelHookHandler() {} |
| + ~LowLevelHookHandler() {} |
| + |
| + // Check if window handle is registered to intercept keyboard events. |
| + bool IsRegistered(HWND handle); |
| + |
| + // Low level keyboard hook processing related functions. |
| + // Hook callback called from the OS. |
| + static LRESULT CALLBACK |
| + KeyboardHook(int code, WPARAM w_param, LPARAM l_param); |
| + |
| + // There is no lock protecting this list as the low level hook callbacks are |
| + // executed on same thread that registered the hook and there is only one |
| + // thread |
| + // that execute all view code in browser. |
| + base::ScopedPtrHashMap<HWND, KeyboardInterceptRegistration> registrations_; |
| +}; |
| + |
| +// static |
| +LowLevelHookHandler* LowLevelHookHandler::GetInstance() { |
| + return Singleton<LowLevelHookHandler, |
| + DefaultSingletonTraits<LowLevelHookHandler> >::get(); |
| +} |
| + |
| +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
|
| + if (registrations_.contains(window_handle)) |
| + return false; |
| + |
| + scoped_ptr<KeyboardInterceptRegistration> registration( |
| + new KeyboardInterceptRegistration()); |
| + if (registration->Hook(KeyboardHook)) { |
| + if (registrations_.add(window_handle, registration.Pass()).second) |
| + return true; |
| + } |
| + return false; |
| +} |
| + |
| +void LowLevelHookHandler::Deregister(HWND window_handle) { |
| + registrations_.erase(window_handle); |
| + DVLOG(1) << "Keyboard hook unregistered for handle = " << window_handle; |
| +} |
| + |
| +bool LowLevelHookHandler::IsRegistered(HWND handle) { |
| + return registrations_.contains(handle); |
| +} |
| + |
| +// static |
| +LRESULT CALLBACK |
| +LowLevelHookHandler::KeyboardHook(int code, WPARAM w_param, LPARAM l_param) { |
| + HWND current_active_window = GetActiveWindow(); |
| + if ((code >= 0) && GetInstance()->IsRegistered(current_active_window)) { |
| + UpdateThreadKeyboardState(); |
| + |
| + KBDLLHOOKSTRUCT hook_struct = *reinterpret_cast<KBDLLHOOKSTRUCT*>(l_param); |
| + PostMessage(current_active_window, |
| + w_param, |
| + RemoveLocationOnKeycode(hook_struct.vkCode), |
| + GetLParamFromHookStruct(w_param, hook_struct)); |
| + |
| + return 1; |
| + } |
| + |
|
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
|
| + return CallNextHookEx(NULL, code, w_param, l_param); |
| +} |
| + |
| +DesktopKeyboardCaptureWin::DesktopKeyboardCaptureWin(HWND window_handle) |
| + : window_handle_(window_handle), registered_(false) { |
| +} |
| + |
| +DesktopKeyboardCaptureWin::~DesktopKeyboardCaptureWin() { |
| + if (registered_) |
| + Release(); |
| +} |
| + |
| +bool DesktopKeyboardCaptureWin::Capture() { |
| + DCHECK(!registered_) << "Capture called multiple times"; |
| + registered_ = LowLevelHookHandler::GetInstance()->Register(window_handle_); |
| + return registered_; |
| +} |
| + |
| +void DesktopKeyboardCaptureWin::Release() { |
| + DCHECK(registered_) << "Release called without registring for capture"; |
| + LowLevelHookHandler::GetInstance()->Deregister(window_handle_); |
| + registered_ = false; |
| +} |
| + |
| +} // namespace views |