1// Copyright (c) 2013 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 "chrome/browser/extensions/global_shortcut_listener_win.h"
6
7#include "base/win/win_util.h"
8#include "content/public/browser/browser_thread.h"
9#include "ui/base/accelerators/accelerator.h"
10#include "ui/events/event_constants.h"
11#include "ui/events/keycodes/keyboard_code_conversion_win.h"
12
13using content::BrowserThread;
14
15namespace {
16
17static base::LazyInstance<extensions::GlobalShortcutListenerWin> instance =
18    LAZY_INSTANCE_INITIALIZER;
19
20}  // namespace
21
22namespace extensions {
23
24// static
25GlobalShortcutListener* GlobalShortcutListener::GetInstance() {
26  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
27  return instance.Pointer();
28}
29
30GlobalShortcutListenerWin::GlobalShortcutListenerWin()
31    : is_listening_(false) {
32  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
33}
34
35GlobalShortcutListenerWin::~GlobalShortcutListenerWin() {
36  if (is_listening_)
37    StopListening();
38}
39
40void GlobalShortcutListenerWin::StartListening() {
41  DCHECK(!is_listening_);  // Don't start twice.
42  DCHECK(!hotkey_ids_.empty());  // Also don't start if no hotkey is registered.
43  gfx::SingletonHwnd::GetInstance()->AddObserver(this);
44  is_listening_ = true;
45}
46
47void GlobalShortcutListenerWin::StopListening() {
48  DCHECK(is_listening_);  // No point if we are not already listening.
49  DCHECK(hotkey_ids_.empty());  // Make sure the map is clean before ending.
50  gfx::SingletonHwnd::GetInstance()->RemoveObserver(this);
51  is_listening_ = false;
52}
53
54void GlobalShortcutListenerWin::OnWndProc(HWND hwnd,
55                                          UINT message,
56                                          WPARAM wparam,
57                                          LPARAM lparam) {
58  if (message != WM_HOTKEY)
59    return;
60
61  int key_code = HIWORD(lparam);
62  int modifiers = 0;
63  modifiers |= (LOWORD(lparam) & MOD_SHIFT) ? ui::EF_SHIFT_DOWN : 0;
64  modifiers |= (LOWORD(lparam) & MOD_ALT) ? ui::EF_ALT_DOWN : 0;
65  modifiers |= (LOWORD(lparam) & MOD_CONTROL) ? ui::EF_CONTROL_DOWN : 0;
66  ui::Accelerator accelerator(
67      ui::KeyboardCodeForWindowsKeyCode(key_code), modifiers);
68
69  instance.Get().NotifyKeyPressed(accelerator);
70}
71
72void GlobalShortcutListenerWin::RegisterAccelerator(
73    const ui::Accelerator& accelerator,
74    GlobalShortcutListener::Observer* observer) {
75  if (hotkey_ids_.find(accelerator) != hotkey_ids_.end()) {
76    // The shortcut has already been registered. Some shortcuts, such as
77    // MediaKeys can have multiple targets, all keyed off of the same
78    // accelerator.
79    return;
80  }
81
82  int modifiers = 0;
83  modifiers |= accelerator.IsShiftDown() ? MOD_SHIFT : 0;
84  modifiers |= accelerator.IsCtrlDown() ? MOD_CONTROL : 0;
85  modifiers |= accelerator.IsAltDown() ? MOD_ALT : 0;
86  static int hotkey_id = 0;
87  bool success = !!RegisterHotKey(
88      gfx::SingletonHwnd::GetInstance()->hwnd(),
89      hotkey_id,
90      modifiers,
91      accelerator.key_code());
92
93  if (!success) {
94    // Most likely error: 1409 (Hotkey already registered).
95    LOG(ERROR) << "RegisterHotKey failed, error: " << GetLastError();
96    return;
97  }
98
99  hotkey_ids_[accelerator] = hotkey_id++;
100  GlobalShortcutListener::RegisterAccelerator(accelerator, observer);
101}
102
103void GlobalShortcutListenerWin::UnregisterAccelerator(
104    const ui::Accelerator& accelerator,
105    GlobalShortcutListener::Observer* observer) {
106  // We may get asked to unregister something that we couldn't register (for
107  // example if the shortcut was already taken by another app), so we
108  // need to handle that gracefully.
109  HotkeyIdMap::iterator it = hotkey_ids_.find(accelerator);
110  if (it == hotkey_ids_.end())
111    return;
112
113  bool success = !!UnregisterHotKey(
114      gfx::SingletonHwnd::GetInstance()->hwnd(), it->second);
115  // This call should always succeed, as long as we pass in the right HWND and
116  // an id we've used to register before.
117  DCHECK(success);
118
119  hotkey_ids_.erase(it);
120  GlobalShortcutListener::UnregisterAccelerator(accelerator, observer);
121}
122
123}  // namespace extensions
124