Browse Source

Merge pull request #534 from hokein/hotkey

Implement global shortcut API, fixes #439
Cheng Zhao 10 years ago
parent
commit
9c038a2402

+ 11 - 0
atom.gyp

@@ -20,6 +20,7 @@
       'atom/browser/api/lib/browser-window.coffee',
       'atom/browser/api/lib/content-tracing.coffee',
       'atom/browser/api/lib/dialog.coffee',
+      'atom/browser/api/lib/global-shortcut.coffee',
       'atom/browser/api/lib/ipc.coffee',
       'atom/browser/api/lib/menu.coffee',
       'atom/browser/api/lib/menu-item.coffee',
@@ -54,6 +55,8 @@
       'atom/browser/api/atom_api_auto_updater.h',
       'atom/browser/api/atom_api_content_tracing.cc',
       'atom/browser/api/atom_api_dialog.cc',
+      'atom/browser/api/atom_api_global_shortcut.cc',
+      'atom/browser/api/atom_api_global_shortcut.h',
       'atom/browser/api/atom_api_menu.cc',
       'atom/browser/api/atom_api_menu.h',
       'atom/browser/api/atom_api_menu_views.cc',
@@ -225,6 +228,14 @@
       'atom/renderer/atom_render_view_observer.h',
       'atom/renderer/atom_renderer_client.cc',
       'atom/renderer/atom_renderer_client.h',
+      'chromium_src/chrome/browser/extensions/global_shortcut_listener.cc',
+      'chromium_src/chrome/browser/extensions/global_shortcut_listener.h',
+      'chromium_src/chrome/browser/extensions/global_shortcut_listener_mac.mm',
+      'chromium_src/chrome/browser/extensions/global_shortcut_listener_mac.h',
+      'chromium_src/chrome/browser/extensions/global_shortcut_listener_x11.cc',
+      'chromium_src/chrome/browser/extensions/global_shortcut_listener_x11.h',
+      'chromium_src/chrome/browser/extensions/global_shortcut_listener_win.cc',
+      'chromium_src/chrome/browser/extensions/global_shortcut_listener_win.h',
       'chromium_src/chrome/browser/ui/libgtk2ui/app_indicator_icon_menu.cc',
       'chromium_src/chrome/browser/ui/libgtk2ui/app_indicator_icon_menu.h',
       'chromium_src/chrome/browser/ui/libgtk2ui/gtk2_status_icon.cc',

+ 123 - 0
atom/browser/api/atom_api_global_shortcut.cc

@@ -0,0 +1,123 @@
+// Copyright (c) 2014 GitHub, Inc. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#include "atom/browser/api/atom_api_global_shortcut.h"
+
+#include <string>
+
+#include "atom/browser/ui/accelerator_util.h"
+#include "atom/common/native_mate_converters/function_converter.h"
+#include "native_mate/dictionary.h"
+
+#include "atom/common/node_includes.h"
+
+using extensions::GlobalShortcutListener;
+
+namespace atom {
+
+namespace api {
+
+GlobalShortcut::GlobalShortcut() {
+}
+
+GlobalShortcut::~GlobalShortcut() {
+  UnregisterAll();
+}
+
+void GlobalShortcut::OnKeyPressed(const ui::Accelerator& accelerator) {
+  if (accelerator_callback_map_.find(accelerator) ==
+      accelerator_callback_map_.end()) {
+    // This should never occur, because if it does, GlobalGlobalShortcutListener
+    // notifes us with wrong accelerator.
+    NOTREACHED();
+    return;
+  }
+  accelerator_callback_map_[accelerator].Run();
+}
+
+bool GlobalShortcut::Register(const std::string& keycode,
+    const base::Closure& callback) {
+  ui::Accelerator accelerator;
+  if (!accelerator_util::StringToAccelerator(keycode, &accelerator)) {
+    LOG(ERROR) << keycode << " is invalid.";
+    return false;
+  }
+  if (!GlobalShortcutListener::GetInstance()->RegisterAccelerator(
+      accelerator, this)) {
+    return false;
+  }
+  accelerator_callback_map_[accelerator] = callback;
+  return true;
+}
+
+void GlobalShortcut::Unregister(const std::string& keycode) {
+  ui::Accelerator accelerator;
+  if (!accelerator_util::StringToAccelerator(keycode, &accelerator)) {
+    LOG(ERROR) << "The keycode: " << keycode << " is invalid.";
+    return;
+  }
+  if (accelerator_callback_map_.find(accelerator) ==
+      accelerator_callback_map_.end()) {
+    LOG(ERROR) << "The keycode: " << keycode << " isn't registered yet!";
+    return;
+  }
+  accelerator_callback_map_.erase(accelerator);
+  GlobalShortcutListener::GetInstance()->UnregisterAccelerator(
+      accelerator, this);
+}
+
+void GlobalShortcut::UnregisterAll() {
+  accelerator_callback_map_.clear();
+  GlobalShortcutListener::GetInstance()->UnregisterAccelerators(this);
+}
+
+bool GlobalShortcut::IsRegistered(const std::string& keycode) {
+  ui::Accelerator accelerator;
+  if (!accelerator_util::StringToAccelerator(keycode, &accelerator)) {
+    LOG(ERROR) << "The keycode: " << keycode << " is invalid.";
+    return false;
+  }
+  return accelerator_callback_map_.find(accelerator) !=
+      accelerator_callback_map_.end();
+}
+
+// static
+mate::ObjectTemplateBuilder GlobalShortcut::GetObjectTemplateBuilder(
+    v8::Isolate* isolate) {
+  return mate::ObjectTemplateBuilder(isolate)
+      .SetMethod("register",
+                 base::Bind(&GlobalShortcut::Register,
+                            base::Unretained(this)))
+      .SetMethod("isRegistered",
+                 base::Bind(&GlobalShortcut::IsRegistered,
+                            base::Unretained(this)))
+      .SetMethod("unregister",
+                 base::Bind(&GlobalShortcut::Unregister,
+                            base::Unretained(this)))
+      .SetMethod("unregisterAll",
+                 base::Bind(&GlobalShortcut::UnregisterAll,
+                            base::Unretained(this)));
+}
+
+// static
+mate::Handle<GlobalShortcut> GlobalShortcut::Create(v8::Isolate* isolate) {
+  return CreateHandle(isolate, new GlobalShortcut);
+}
+
+}  // namespace api
+
+}  // namespace atom
+
+namespace {
+
+void Initialize(v8::Handle<v8::Object> exports, v8::Handle<v8::Value> unused,
+                v8::Handle<v8::Context> context, void* priv) {
+  v8::Isolate* isolate = context->GetIsolate();
+  mate::Dictionary dict(isolate, exports);
+  dict.Set("globalShortcut", atom::api::GlobalShortcut::Create(isolate));
+}
+
+}  // namespace
+
+NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_global_shortcut, Initialize)

+ 54 - 0
atom/browser/api/atom_api_global_shortcut.h

@@ -0,0 +1,54 @@
+// Copyright (c) 2014 GitHub, Inc. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#ifndef ATOM_BROWSER_API_ATOM_API_GLOBAL_SHORTCUT_H_
+#define ATOM_BROWSER_API_ATOM_API_GLOBAL_SHORTCUT_H_
+
+#include <map>
+#include <string>
+
+#include "base/callback.h"
+#include "chrome/browser/extensions/global_shortcut_listener.h"
+#include "native_mate/wrappable.h"
+#include "native_mate/handle.h"
+#include "ui/base/accelerators/accelerator.h"
+
+namespace atom {
+
+namespace api {
+
+class GlobalShortcut : public extensions::GlobalShortcutListener::Observer,
+                       public mate::Wrappable {
+ public:
+  static mate::Handle<GlobalShortcut> Create(v8::Isolate* isolate);
+
+ protected:
+  GlobalShortcut();
+  virtual ~GlobalShortcut();
+
+  // mate::Wrappable implementations:
+  virtual mate::ObjectTemplateBuilder GetObjectTemplateBuilder(
+      v8::Isolate* isolate) OVERRIDE;
+
+ private:
+  typedef std::map<ui::Accelerator, base::Closure> AcceleratorCallbackMap;
+
+  bool Register(const std::string& keycode, const base::Closure& callback);
+  bool IsRegistered(const std::string& keycode);
+  void Unregister(const std::string& keycode);
+  void UnregisterAll();
+
+  // GlobalShortcutListener::Observer implementation.
+  virtual void OnKeyPressed(const ui::Accelerator& accelerator) OVERRIDE;
+
+  AcceleratorCallbackMap accelerator_callback_map_;
+
+  DISALLOW_COPY_AND_ASSIGN(GlobalShortcut);
+};
+
+}  // namespace api
+
+}  // namespace atom
+
+#endif  // ATOM_BROWSER_API_ATOM_API_GLOBAL_SHORTCUT_H_

+ 5 - 0
atom/browser/api/lib/global-shortcut.coffee

@@ -0,0 +1,5 @@
+bindings = process.atomBinding 'global_shortcut'
+
+globalShortcut = bindings.globalShortcut
+
+module.exports = globalShortcut

+ 1 - 0
atom/common/node_bindings.cc

@@ -66,6 +66,7 @@ REFERENCE_MODULE(atom_browser_dialog);
 REFERENCE_MODULE(atom_browser_menu);
 REFERENCE_MODULE(atom_browser_power_monitor);
 REFERENCE_MODULE(atom_browser_protocol);
+REFERENCE_MODULE(atom_browser_global_shortcut);
 REFERENCE_MODULE(atom_browser_tray);
 REFERENCE_MODULE(atom_browser_window);
 REFERENCE_MODULE(atom_common_clipboard);

+ 122 - 0
chromium_src/chrome/browser/extensions/global_shortcut_listener.cc

@@ -0,0 +1,122 @@
+// Copyright (c) 2013 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 "chrome/browser/extensions/global_shortcut_listener.h"
+
+#include "base/logging.h"
+#include "content/public/browser/browser_thread.h"
+#include "ui/base/accelerators/accelerator.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+
+GlobalShortcutListener::GlobalShortcutListener()
+    : shortcut_handling_suspended_(false) {
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+}
+
+GlobalShortcutListener::~GlobalShortcutListener() {
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  DCHECK(accelerator_map_.empty());  // Make sure we've cleaned up.
+}
+
+bool GlobalShortcutListener::RegisterAccelerator(
+    const ui::Accelerator& accelerator, Observer* observer) {
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  if (IsShortcutHandlingSuspended())
+    return false;
+
+  AcceleratorMap::const_iterator it = accelerator_map_.find(accelerator);
+  if (it != accelerator_map_.end()) {
+    // The accelerator has been registered.
+    return false;
+  }
+
+  if (!RegisterAcceleratorImpl(accelerator)) {
+    // If the platform-specific registration fails, mostly likely the shortcut
+    // has been registered by other native applications.
+    return false;
+  }
+
+  if (accelerator_map_.empty())
+    StartListening();
+
+  accelerator_map_[accelerator] = observer;
+  return true;
+}
+
+void GlobalShortcutListener::UnregisterAccelerator(
+    const ui::Accelerator& accelerator, Observer* observer) {
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  if (IsShortcutHandlingSuspended())
+    return;
+
+  AcceleratorMap::iterator it = accelerator_map_.find(accelerator);
+  // We should never get asked to unregister something that we didn't register.
+  DCHECK(it != accelerator_map_.end());
+  // The caller should call this function with the right observer.
+  DCHECK(it->second == observer);
+
+  UnregisterAcceleratorImpl(accelerator);
+  accelerator_map_.erase(it);
+  if (accelerator_map_.empty())
+    StopListening();
+}
+
+void GlobalShortcutListener::UnregisterAccelerators(Observer* observer) {
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  if (IsShortcutHandlingSuspended())
+    return;
+
+  AcceleratorMap::iterator it = accelerator_map_.begin();
+  while (it != accelerator_map_.end()) {
+    if (it->second == observer) {
+      AcceleratorMap::iterator to_remove = it++;
+      UnregisterAccelerator(to_remove->first, observer);
+    } else {
+      ++it;
+    }
+  }
+}
+
+void GlobalShortcutListener::SetShortcutHandlingSuspended(bool suspended) {
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  if (shortcut_handling_suspended_ == suspended)
+    return;
+
+  shortcut_handling_suspended_ = suspended;
+  for (AcceleratorMap::iterator it = accelerator_map_.begin();
+       it != accelerator_map_.end();
+       ++it) {
+    // On Linux, when shortcut handling is suspended we cannot simply early
+    // return in NotifyKeyPressed (similar to what we do for non-global
+    // shortcuts) because we'd eat the keyboard event thereby preventing the
+    // user from setting the shortcut. Therefore we must unregister while
+    // handling is suspended and register when handling resumes.
+    if (shortcut_handling_suspended_)
+      UnregisterAcceleratorImpl(it->first);
+    else
+      RegisterAcceleratorImpl(it->first);
+  }
+}
+
+bool GlobalShortcutListener::IsShortcutHandlingSuspended() const {
+  return shortcut_handling_suspended_;
+}
+
+void GlobalShortcutListener::NotifyKeyPressed(
+    const ui::Accelerator& accelerator) {
+  AcceleratorMap::iterator iter = accelerator_map_.find(accelerator);
+  if (iter == accelerator_map_.end()) {
+    // This should never occur, because if it does, we have failed to unregister
+    // or failed to clean up the map after unregistering the shortcut.
+    NOTREACHED();
+    return;  // No-one is listening to this key.
+  }
+
+  iter->second->OnKeyPressed(accelerator);
+}
+
+}  // namespace extensions

+ 99 - 0
chromium_src/chrome/browser/extensions/global_shortcut_listener.h

@@ -0,0 +1,99 @@
+// Copyright (c) 2013 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_GLOBAL_SHORTCUT_LISTENER_H_
+#define CHROME_BROWSER_EXTENSIONS_GLOBAL_SHORTCUT_LISTENER_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+
+namespace ui {
+class Accelerator;
+}
+
+namespace extensions {
+
+// Platform-neutral implementation of a class that keeps track of observers and
+// monitors keystrokes. It relays messages to the appropriate observer when a
+// global shortcut has been struck by the user.
+class GlobalShortcutListener {
+ public:
+  class Observer {
+   public:
+    // Called when your global shortcut (|accelerator|) is struck.
+    virtual void OnKeyPressed(const ui::Accelerator& accelerator) = 0;
+  };
+
+  virtual ~GlobalShortcutListener();
+
+  static GlobalShortcutListener* GetInstance();
+
+  // Register an observer for when a certain |accelerator| is struck. Returns
+  // true if register successfully, or false if 1) the specificied |accelerator|
+  // has been registered by another caller or other native applications, or
+  // 2) shortcut handling is suspended.
+  //
+  // Note that we do not support recognizing that an accelerator has been
+  // registered by another application on all platforms. This is a per-platform
+  // consideration.
+  bool RegisterAccelerator(const ui::Accelerator& accelerator,
+                           Observer* observer);
+
+  // Stop listening for the given |accelerator|, does nothing if shortcut
+  // handling is suspended.
+  void UnregisterAccelerator(const ui::Accelerator& accelerator,
+                             Observer* observer);
+
+  // Stop listening for all accelerators of the given |observer|, does nothing
+  // if shortcut handling is suspended.
+  void UnregisterAccelerators(Observer* observer);
+
+  // Suspend/Resume global shortcut handling. Note that when suspending,
+  // RegisterAccelerator/UnregisterAccelerator/UnregisterAccelerators are not
+  // allowed to be called until shortcut handling has been resumed.
+  void SetShortcutHandlingSuspended(bool suspended);
+
+  // Returns whether shortcut handling is currently suspended.
+  bool IsShortcutHandlingSuspended() const;
+
+ protected:
+  GlobalShortcutListener();
+
+  // Called by platform specific implementations of this class whenever a key
+  // is struck. Only called for keys that have an observer registered.
+  void NotifyKeyPressed(const ui::Accelerator& accelerator);
+
+ private:
+  // The following methods are implemented by platform-specific implementations
+  // of this class.
+  //
+  // Start/StopListening are called when transitioning between zero and nonzero
+  // registered accelerators. StartListening will be called after
+  // RegisterAcceleratorImpl and StopListening will be called after
+  // UnregisterAcceleratorImpl.
+  //
+  // For RegisterAcceleratorImpl, implementations return false if registration
+  // did not complete successfully.
+  virtual void StartListening() = 0;
+  virtual void StopListening() = 0;
+  virtual bool RegisterAcceleratorImpl(const ui::Accelerator& accelerator) = 0;
+  virtual void UnregisterAcceleratorImpl(
+      const ui::Accelerator& accelerator) = 0;
+
+  // The map of accelerators that have been successfully registered as global
+  // shortcuts and their observer.
+  typedef std::map<ui::Accelerator, Observer*> AcceleratorMap;
+  AcceleratorMap accelerator_map_;
+
+  // Keeps track of whether shortcut handling is currently suspended.
+  bool shortcut_handling_suspended_;
+
+  DISALLOW_COPY_AND_ASSIGN(GlobalShortcutListener);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_GLOBAL_SHORTCUT_LISTENER_H_

+ 106 - 0
chromium_src/chrome/browser/extensions/global_shortcut_listener_mac.h

@@ -0,0 +1,106 @@
+// Copyright (c) 2013 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_GLOBAL_SHORTCUT_LISTENER_MAC_H_
+#define CHROME_BROWSER_EXTENSIONS_GLOBAL_SHORTCUT_LISTENER_MAC_H_
+
+#include "chrome/browser/extensions/global_shortcut_listener.h"
+
+#include <Carbon/Carbon.h>
+#include <CoreFoundation/CoreFoundation.h>
+
+#include <map>
+
+#include "base/mac/scoped_nsobject.h"
+
+namespace extensions {
+
+// Mac-specific implementation of the GlobalShortcutListener class that
+// listens for global shortcuts. Handles basic keyboard intercepting and
+// forwards its output to the base class for processing.
+//
+// This class does two things:
+// 1. Intercepts media keys. Uses an event tap for intercepting media keys
+// (PlayPause, NextTrack, PreviousTrack).
+// 2. Binds keyboard shortcuts (hot keys). Carbon RegisterEventHotKey API for
+// binding to non-media key global hot keys (eg. Command-Shift-1).
+class GlobalShortcutListenerMac : public GlobalShortcutListener {
+ public:
+  GlobalShortcutListenerMac();
+  virtual ~GlobalShortcutListenerMac();
+
+ private:
+  typedef int KeyId;
+  typedef std::map<ui::Accelerator, KeyId> AcceleratorIdMap;
+  typedef std::map<KeyId, ui::Accelerator> IdAcceleratorMap;
+  typedef std::map<KeyId, EventHotKeyRef> IdHotKeyRefMap;
+
+  // Keyboard event callbacks.
+  void OnHotKeyEvent(EventHotKeyID hot_key_id);
+  bool OnMediaKeyEvent(int key_code);
+
+  // GlobalShortcutListener implementation.
+  virtual void StartListening() OVERRIDE;
+  virtual void StopListening() OVERRIDE;
+  virtual bool RegisterAcceleratorImpl(
+      const ui::Accelerator& accelerator) OVERRIDE;
+  virtual void UnregisterAcceleratorImpl(
+      const ui::Accelerator& accelerator) OVERRIDE;
+
+  // Mac-specific functions for registering hot keys with modifiers.
+  bool RegisterHotKey(const ui::Accelerator& accelerator, KeyId hot_key_id);
+  void UnregisterHotKey(const ui::Accelerator& accelerator);
+
+  // Enable and disable the media key event tap.
+  void StartWatchingMediaKeys();
+  void StopWatchingMediaKeys();
+
+  // Enable and disable the hot key event handler.
+  void StartWatchingHotKeys();
+  void StopWatchingHotKeys();
+
+  // Whether or not any media keys are currently registered.
+  bool IsAnyMediaKeyRegistered();
+
+  // Whether or not any hot keys are currently registered.
+  bool IsAnyHotKeyRegistered();
+
+  // The callback for when an event tap happens.
+  static CGEventRef EventTapCallback(
+      CGEventTapProxy proxy, CGEventType type, CGEventRef event, void* refcon);
+
+  // The callback for when a hot key event happens.
+  static OSStatus HotKeyHandler(
+      EventHandlerCallRef next_handler, EventRef event, void* user_data);
+
+  // Whether this object is listening for global shortcuts.
+  bool is_listening_;
+
+  // The hotkey identifier for the next global shortcut that is added.
+  KeyId hot_key_id_;
+
+  // A map of all hotkeys (media keys and shortcuts) mapping to their
+  // corresponding hotkey IDs. For quickly finding if an accelerator is
+  // registered.
+  AcceleratorIdMap accelerator_ids_;
+
+  // The inverse map for quickly looking up accelerators by hotkey id.
+  IdAcceleratorMap id_accelerators_;
+
+  // Keyboard shortcut IDs to hotkeys map for unregistration.
+  IdHotKeyRefMap id_hot_key_refs_;
+
+  // Event tap for intercepting mac media keys.
+  CFMachPortRef event_tap_;
+  CFRunLoopSourceRef event_tap_source_;
+
+  // Event handler for keyboard shortcut hot keys.
+  EventHandlerRef event_handler_;
+
+  DISALLOW_COPY_AND_ASSIGN(GlobalShortcutListenerMac);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_GLOBAL_SHORTCUT_LISTENER_MAC_H_

+ 383 - 0
chromium_src/chrome/browser/extensions/global_shortcut_listener_mac.mm

@@ -0,0 +1,383 @@
+// Copyright (c) 2013 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 "chrome/browser/extensions/global_shortcut_listener_mac.h"
+
+#include <ApplicationServices/ApplicationServices.h>
+#import <Cocoa/Cocoa.h>
+#include <IOKit/hidsystem/ev_keymap.h>
+
+#import "base/mac/foundation_util.h"
+#include "content/public/browser/browser_thread.h"
+#include "ui/base/accelerators/accelerator.h"
+#include "ui/events/event.h"
+#import "ui/events/keycodes/keyboard_code_conversion_mac.h"
+
+using content::BrowserThread;
+using extensions::GlobalShortcutListenerMac;
+
+namespace {
+
+// The media keys subtype. No official docs found, but widely known.
+// http://lists.apple.com/archives/cocoa-dev/2007/Aug/msg00499.html
+const int kSystemDefinedEventMediaKeysSubtype = 8;
+
+ui::KeyboardCode MediaKeyCodeToKeyboardCode(int key_code) {
+  switch (key_code) {
+    case NX_KEYTYPE_PLAY:
+      return ui::VKEY_MEDIA_PLAY_PAUSE;
+    case NX_KEYTYPE_PREVIOUS:
+    case NX_KEYTYPE_REWIND:
+      return ui::VKEY_MEDIA_PREV_TRACK;
+    case NX_KEYTYPE_NEXT:
+    case NX_KEYTYPE_FAST:
+      return ui::VKEY_MEDIA_NEXT_TRACK;
+  }
+  return ui::VKEY_UNKNOWN;
+}
+
+bool IsMediaKey(const ui::Accelerator& accelerator) {
+  if (accelerator.modifiers() != 0)
+    return false;
+  return (accelerator.key_code() == ui::VKEY_MEDIA_NEXT_TRACK ||
+          accelerator.key_code() == ui::VKEY_MEDIA_PREV_TRACK ||
+          accelerator.key_code() == ui::VKEY_MEDIA_PLAY_PAUSE ||
+          accelerator.key_code() == ui::VKEY_MEDIA_STOP);
+}
+
+}  // namespace
+
+namespace extensions {
+
+// static
+GlobalShortcutListener* GlobalShortcutListener::GetInstance() {
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  static GlobalShortcutListenerMac* instance =
+      new GlobalShortcutListenerMac();
+  return instance;
+}
+
+GlobalShortcutListenerMac::GlobalShortcutListenerMac()
+    : is_listening_(false),
+      hot_key_id_(0),
+      event_tap_(NULL),
+      event_tap_source_(NULL),
+      event_handler_(NULL) {
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+}
+
+GlobalShortcutListenerMac::~GlobalShortcutListenerMac() {
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+  // By this point, UnregisterAccelerator should have been called for all
+  // keyboard shortcuts. Still we should clean up.
+  if (is_listening_)
+    StopListening();
+
+  // If keys are still registered, make sure we stop the tap. Again, this
+  // should never happen.
+  if (IsAnyMediaKeyRegistered())
+    StopWatchingMediaKeys();
+
+  if (IsAnyHotKeyRegistered())
+    StopWatchingHotKeys();
+}
+
+void GlobalShortcutListenerMac::StartListening() {
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+  DCHECK(!accelerator_ids_.empty());
+  DCHECK(!id_accelerators_.empty());
+  DCHECK(!is_listening_);
+
+  is_listening_ = true;
+}
+
+void GlobalShortcutListenerMac::StopListening() {
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+  DCHECK(accelerator_ids_.empty());  // Make sure the set is clean.
+  DCHECK(id_accelerators_.empty());
+  DCHECK(is_listening_);
+
+  is_listening_ = false;
+}
+
+void GlobalShortcutListenerMac::OnHotKeyEvent(EventHotKeyID hot_key_id) {
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+  // This hot key should be registered.
+  DCHECK(id_accelerators_.find(hot_key_id.id) != id_accelerators_.end());
+  // Look up the accelerator based on this hot key ID.
+  const ui::Accelerator& accelerator = id_accelerators_[hot_key_id.id];
+  NotifyKeyPressed(accelerator);
+}
+
+bool GlobalShortcutListenerMac::OnMediaKeyEvent(int media_key_code) {
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  ui::KeyboardCode key_code = MediaKeyCodeToKeyboardCode(media_key_code);
+  // Create an accelerator corresponding to the keyCode.
+  ui::Accelerator accelerator(key_code, 0);
+  // Look for a match with a bound hot_key.
+  if (accelerator_ids_.find(accelerator) != accelerator_ids_.end()) {
+    // If matched, callback to the event handling system.
+    NotifyKeyPressed(accelerator);
+    return true;
+  }
+  return false;
+}
+
+bool GlobalShortcutListenerMac::RegisterAcceleratorImpl(
+    const ui::Accelerator& accelerator) {
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  DCHECK(accelerator_ids_.find(accelerator) == accelerator_ids_.end());
+
+  if (IsMediaKey(accelerator)) {
+    if (!IsAnyMediaKeyRegistered()) {
+      // If this is the first media key registered, start the event tap.
+      StartWatchingMediaKeys();
+    }
+  } else {
+    // Register hot_key if they are non-media keyboard shortcuts.
+    if (!RegisterHotKey(accelerator, hot_key_id_))
+      return false;
+
+    if (!IsAnyHotKeyRegistered()) {
+      StartWatchingHotKeys();
+    }
+  }
+
+  // Store the hotkey-ID mappings we will need for lookup later.
+  id_accelerators_[hot_key_id_] = accelerator;
+  accelerator_ids_[accelerator] = hot_key_id_;
+  ++hot_key_id_;
+  return true;
+}
+
+void GlobalShortcutListenerMac::UnregisterAcceleratorImpl(
+    const ui::Accelerator& accelerator) {
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  DCHECK(accelerator_ids_.find(accelerator) != accelerator_ids_.end());
+
+  // Unregister the hot_key if it's a keyboard shortcut.
+  if (!IsMediaKey(accelerator))
+    UnregisterHotKey(accelerator);
+
+  // Remove hot_key from the mappings.
+  KeyId key_id = accelerator_ids_[accelerator];
+  id_accelerators_.erase(key_id);
+  accelerator_ids_.erase(accelerator);
+
+  if (IsMediaKey(accelerator)) {
+    // If we unregistered a media key, and now no media keys are registered,
+    // stop the media key tap.
+    if (!IsAnyMediaKeyRegistered())
+      StopWatchingMediaKeys();
+  } else {
+    // If we unregistered a hot key, and no more hot keys are registered, remove
+    // the hot key handler.
+    if (!IsAnyHotKeyRegistered()) {
+      StopWatchingHotKeys();
+    }
+  }
+}
+
+bool GlobalShortcutListenerMac::RegisterHotKey(
+    const ui::Accelerator& accelerator, KeyId hot_key_id) {
+  EventHotKeyID event_hot_key_id;
+
+  // Signature uniquely identifies the application that owns this hot_key.
+  event_hot_key_id.signature = base::mac::CreatorCodeForApplication();
+  event_hot_key_id.id = hot_key_id;
+
+  // Translate ui::Accelerator modifiers to cmdKey, altKey, etc.
+  int modifiers = 0;
+  modifiers |= (accelerator.IsShiftDown() ? shiftKey : 0);
+  modifiers |= (accelerator.IsCtrlDown() ? controlKey : 0);
+  modifiers |= (accelerator.IsAltDown() ? optionKey : 0);
+  modifiers |= (accelerator.IsCmdDown() ? cmdKey : 0);
+
+  int key_code = ui::MacKeyCodeForWindowsKeyCode(accelerator.key_code(), 0,
+      NULL, NULL);
+
+  // Register the event hot key.
+  EventHotKeyRef hot_key_ref;
+  OSStatus status = RegisterEventHotKey(key_code, modifiers, event_hot_key_id,
+      GetApplicationEventTarget(), 0, &hot_key_ref);
+  if (status != noErr)
+    return false;
+
+  id_hot_key_refs_[hot_key_id] = hot_key_ref;
+  return true;
+}
+
+void GlobalShortcutListenerMac::UnregisterHotKey(
+    const ui::Accelerator& accelerator) {
+  // Ensure this accelerator is already registered.
+  DCHECK(accelerator_ids_.find(accelerator) != accelerator_ids_.end());
+  // Get the ref corresponding to this accelerator.
+  KeyId key_id = accelerator_ids_[accelerator];
+  EventHotKeyRef ref = id_hot_key_refs_[key_id];
+  // Unregister the event hot key.
+  UnregisterEventHotKey(ref);
+
+  // Remove the event from the mapping.
+  id_hot_key_refs_.erase(key_id);
+}
+
+void GlobalShortcutListenerMac::StartWatchingMediaKeys() {
+  // Make sure there's no existing event tap.
+  DCHECK(event_tap_ == NULL);
+  DCHECK(event_tap_source_ == NULL);
+
+  // Add an event tap to intercept the system defined media key events.
+  event_tap_ = CGEventTapCreate(kCGSessionEventTap,
+      kCGHeadInsertEventTap,
+      kCGEventTapOptionDefault,
+      CGEventMaskBit(NX_SYSDEFINED),
+      EventTapCallback,
+      this);
+  if (event_tap_ == NULL) {
+    LOG(ERROR) << "Error: failed to create event tap.";
+    return;
+  }
+
+  event_tap_source_ = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault,
+      event_tap_, 0);
+  if (event_tap_source_ == NULL) {
+    LOG(ERROR) << "Error: failed to create new run loop source.";
+    return;
+  }
+
+  CFRunLoopAddSource(CFRunLoopGetCurrent(), event_tap_source_,
+      kCFRunLoopCommonModes);
+}
+
+void GlobalShortcutListenerMac::StopWatchingMediaKeys() {
+  CFRunLoopRemoveSource(CFRunLoopGetCurrent(), event_tap_source_,
+      kCFRunLoopCommonModes);
+  // Ensure both event tap and source are initialized.
+  DCHECK(event_tap_ != NULL);
+  DCHECK(event_tap_source_ != NULL);
+
+  // Invalidate the event tap.
+  CFMachPortInvalidate(event_tap_);
+  CFRelease(event_tap_);
+  event_tap_ = NULL;
+
+  // Release the event tap source.
+  CFRelease(event_tap_source_);
+  event_tap_source_ = NULL;
+}
+
+void GlobalShortcutListenerMac::StartWatchingHotKeys() {
+  DCHECK(!event_handler_);
+  EventHandlerUPP hot_key_function = NewEventHandlerUPP(HotKeyHandler);
+  EventTypeSpec event_type;
+  event_type.eventClass = kEventClassKeyboard;
+  event_type.eventKind = kEventHotKeyPressed;
+  InstallApplicationEventHandler(
+      hot_key_function, 1, &event_type, this, &event_handler_);
+}
+
+void GlobalShortcutListenerMac::StopWatchingHotKeys() {
+  DCHECK(event_handler_);
+  RemoveEventHandler(event_handler_);
+  event_handler_ = NULL;
+}
+
+bool GlobalShortcutListenerMac::IsAnyMediaKeyRegistered() {
+  // Iterate through registered accelerators, looking for media keys.
+  AcceleratorIdMap::iterator it;
+  for (it = accelerator_ids_.begin(); it != accelerator_ids_.end(); ++it) {
+    if (IsMediaKey(it->first))
+      return true;
+  }
+  return false;
+}
+
+bool GlobalShortcutListenerMac::IsAnyHotKeyRegistered() {
+  AcceleratorIdMap::iterator it;
+  for (it = accelerator_ids_.begin(); it != accelerator_ids_.end(); ++it) {
+    if (!IsMediaKey(it->first))
+      return true;
+  }
+  return false;
+}
+
+// Processed events should propagate if they aren't handled by any listeners.
+// For events that don't matter, this handler should return as quickly as
+// possible.
+// Returning event causes the event to propagate to other applications.
+// Returning NULL prevents the event from propagating.
+// static
+CGEventRef GlobalShortcutListenerMac::EventTapCallback(
+    CGEventTapProxy proxy, CGEventType type, CGEventRef event, void* refcon) {
+  GlobalShortcutListenerMac* shortcut_listener =
+      static_cast<GlobalShortcutListenerMac*>(refcon);
+
+  // Handle the timeout case by re-enabling the tap.
+  if (type == kCGEventTapDisabledByTimeout) {
+    CGEventTapEnable(shortcut_listener->event_tap_, TRUE);
+    return event;
+  }
+
+  // Convert the CGEvent to an NSEvent for access to the data1 field.
+  NSEvent* ns_event = [NSEvent eventWithCGEvent:event];
+  if (ns_event == nil) {
+    return event;
+  }
+
+  // Ignore events that are not system defined media keys.
+  if (type != NX_SYSDEFINED ||
+      [ns_event type] != NSSystemDefined ||
+      [ns_event subtype] != kSystemDefinedEventMediaKeysSubtype) {
+    return event;
+  }
+
+  NSInteger data1 = [ns_event data1];
+  // Ignore media keys that aren't previous, next and play/pause.
+  // Magical constants are from http://weblog.rogueamoeba.com/2007/09/29/
+  int key_code = (data1 & 0xFFFF0000) >> 16;
+  if (key_code != NX_KEYTYPE_PLAY && key_code != NX_KEYTYPE_NEXT &&
+      key_code != NX_KEYTYPE_PREVIOUS && key_code != NX_KEYTYPE_FAST &&
+      key_code != NX_KEYTYPE_REWIND) {
+    return event;
+  }
+
+  int key_flags = data1 & 0x0000FFFF;
+  bool is_key_pressed = ((key_flags & 0xFF00) >> 8) == 0xA;
+
+  // If the key wasn't pressed (eg. was released), ignore this event.
+  if (!is_key_pressed)
+    return event;
+
+  // Now we have a media key that we care about. Send it to the caller.
+  bool was_handled = shortcut_listener->OnMediaKeyEvent(key_code);
+
+  // Prevent event from proagating to other apps if handled by Chrome.
+  if (was_handled)
+    return NULL;
+
+  // By default, pass the event through.
+  return event;
+}
+
+// static
+OSStatus GlobalShortcutListenerMac::HotKeyHandler(
+    EventHandlerCallRef next_handler, EventRef event, void* user_data) {
+  // Extract the hotkey from the event.
+  EventHotKeyID hot_key_id;
+  OSStatus result = GetEventParameter(event, kEventParamDirectObject,
+      typeEventHotKeyID, NULL, sizeof(hot_key_id), NULL, &hot_key_id);
+  if (result != noErr)
+    return result;
+
+  GlobalShortcutListenerMac* shortcut_listener =
+      static_cast<GlobalShortcutListenerMac*>(user_data);
+  shortcut_listener->OnHotKeyEvent(hot_key_id);
+  return noErr;
+}
+
+}  // namespace extensions

+ 105 - 0
chromium_src/chrome/browser/extensions/global_shortcut_listener_win.cc

@@ -0,0 +1,105 @@
+// Copyright (c) 2013 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 "chrome/browser/extensions/global_shortcut_listener_win.h"
+
+#include "base/win/win_util.h"
+#include "content/public/browser/browser_thread.h"
+#include "ui/base/accelerators/accelerator.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/keycodes/keyboard_code_conversion_win.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+
+// static
+GlobalShortcutListener* GlobalShortcutListener::GetInstance() {
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  static GlobalShortcutListenerWin* instance =
+      new GlobalShortcutListenerWin();
+  return instance;
+}
+
+GlobalShortcutListenerWin::GlobalShortcutListenerWin()
+    : is_listening_(false) {
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+}
+
+GlobalShortcutListenerWin::~GlobalShortcutListenerWin() {
+  if (is_listening_)
+    StopListening();
+}
+
+void GlobalShortcutListenerWin::StartListening() {
+  DCHECK(!is_listening_);  // Don't start twice.
+  DCHECK(!hotkey_ids_.empty());  // Also don't start if no hotkey is registered.
+  gfx::SingletonHwnd::GetInstance()->AddObserver(this);
+  is_listening_ = true;
+}
+
+void GlobalShortcutListenerWin::StopListening() {
+  DCHECK(is_listening_);  // No point if we are not already listening.
+  DCHECK(hotkey_ids_.empty());  // Make sure the map is clean before ending.
+  gfx::SingletonHwnd::GetInstance()->RemoveObserver(this);
+  is_listening_ = false;
+}
+
+void GlobalShortcutListenerWin::OnWndProc(HWND hwnd,
+                                          UINT message,
+                                          WPARAM wparam,
+                                          LPARAM lparam) {
+  if (message != WM_HOTKEY)
+    return;
+
+  int key_code = HIWORD(lparam);
+  int modifiers = 0;
+  modifiers |= (LOWORD(lparam) & MOD_SHIFT) ? ui::EF_SHIFT_DOWN : 0;
+  modifiers |= (LOWORD(lparam) & MOD_ALT) ? ui::EF_ALT_DOWN : 0;
+  modifiers |= (LOWORD(lparam) & MOD_CONTROL) ? ui::EF_CONTROL_DOWN : 0;
+  ui::Accelerator accelerator(
+      ui::KeyboardCodeForWindowsKeyCode(key_code), modifiers);
+
+  NotifyKeyPressed(accelerator);
+}
+
+bool GlobalShortcutListenerWin::RegisterAcceleratorImpl(
+    const ui::Accelerator& accelerator) {
+  DCHECK(hotkey_ids_.find(accelerator) == hotkey_ids_.end());
+
+  int modifiers = 0;
+  modifiers |= accelerator.IsShiftDown() ? MOD_SHIFT : 0;
+  modifiers |= accelerator.IsCtrlDown() ? MOD_CONTROL : 0;
+  modifiers |= accelerator.IsAltDown() ? MOD_ALT : 0;
+  static int hotkey_id = 0;
+  bool success = !!RegisterHotKey(
+      gfx::SingletonHwnd::GetInstance()->hwnd(),
+      hotkey_id,
+      modifiers,
+      accelerator.key_code());
+
+  if (!success) {
+    // Most likely error: 1409 (Hotkey already registered).
+    return false;
+  }
+
+  hotkey_ids_[accelerator] = hotkey_id++;
+  return true;
+}
+
+void GlobalShortcutListenerWin::UnregisterAcceleratorImpl(
+    const ui::Accelerator& accelerator) {
+  HotkeyIdMap::iterator it = hotkey_ids_.find(accelerator);
+  DCHECK(it != hotkey_ids_.end());
+
+  bool success = !!UnregisterHotKey(
+      gfx::SingletonHwnd::GetInstance()->hwnd(), it->second);
+  // This call should always succeed, as long as we pass in the right HWND and
+  // an id we've used to register before.
+  DCHECK(success);
+
+  hotkey_ids_.erase(it);
+}
+
+}  // namespace extensions

+ 51 - 0
chromium_src/chrome/browser/extensions/global_shortcut_listener_win.h

@@ -0,0 +1,51 @@
+// Copyright (c) 2013 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_GLOBAL_SHORTCUT_LISTENER_WIN_H_
+#define CHROME_BROWSER_EXTENSIONS_GLOBAL_SHORTCUT_LISTENER_WIN_H_
+
+#include <windows.h>
+
+#include "chrome/browser/extensions/global_shortcut_listener.h"
+#include "ui/gfx/win/singleton_hwnd.h"
+
+namespace extensions {
+
+// Windows-specific implementation of the GlobalShortcutListener class that
+// listens for global shortcuts. Handles setting up a keyboard hook and
+// forwarding its output to the base class for processing.
+class GlobalShortcutListenerWin : public GlobalShortcutListener,
+                                  public gfx::SingletonHwnd::Observer {
+ public:
+  GlobalShortcutListenerWin();
+  virtual ~GlobalShortcutListenerWin();
+
+ private:
+  // The implementation of our Window Proc, called by SingletonHwnd.
+  virtual void OnWndProc(HWND hwnd,
+                         UINT message,
+                         WPARAM wparam,
+                         LPARAM lparam) OVERRIDE;
+
+  // GlobalShortcutListener implementation.
+  virtual void StartListening() OVERRIDE;
+  virtual void StopListening() OVERRIDE;
+  virtual bool RegisterAcceleratorImpl(
+      const ui::Accelerator& accelerator) OVERRIDE;
+  virtual void UnregisterAcceleratorImpl(
+      const ui::Accelerator& accelerator) OVERRIDE;
+
+  // Whether this object is listening for global shortcuts.
+  bool is_listening_;
+
+  // A map of registered accelerators and their registration ids.
+  typedef std::map<ui::Accelerator, int> HotkeyIdMap;
+  HotkeyIdMap hotkey_ids_;
+
+  DISALLOW_COPY_AND_ASSIGN(GlobalShortcutListenerWin);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_GLOBAL_SHORTCUT_LISTENER_WIN_H_

+ 181 - 0
chromium_src/chrome/browser/extensions/global_shortcut_listener_x11.cc

@@ -0,0 +1,181 @@
+// Copyright 2013 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 "chrome/browser/extensions/global_shortcut_listener_x11.h"
+
+#include "content/public/browser/browser_thread.h"
+#include "ui/base/accelerators/accelerator.h"
+#include "ui/events/keycodes/keyboard_code_conversion_x.h"
+#include "ui/gfx/x/x11_error_tracker.h"
+#include "ui/gfx/x/x11_types.h"
+
+#if defined(TOOLKIT_GTK)
+#include <gdk/gdkx.h>
+#else
+#include "base/message_loop/message_pump_x11.h"
+#endif
+
+using content::BrowserThread;
+
+namespace {
+
+// The modifiers masks used for grabing keys. Due to XGrabKey only working on
+// exact modifiers, we need to grab all key combination including zero or more
+// of the following: Num lock, Caps lock and Scroll lock. So that we can make
+// sure the behavior of global shortcuts is consistent on all platforms.
+const unsigned int kModifiersMasks[] = {
+  0,                                // No additional modifier.
+  Mod2Mask,                         // Num lock
+  LockMask,                         // Caps lock
+  Mod5Mask,                         // Scroll lock
+  Mod2Mask | LockMask,
+  Mod2Mask | Mod5Mask,
+  LockMask | Mod5Mask,
+  Mod2Mask | LockMask | Mod5Mask
+};
+
+int GetNativeModifiers(const ui::Accelerator& accelerator) {
+  int modifiers = 0;
+  modifiers |= accelerator.IsShiftDown() ? ShiftMask : 0;
+  modifiers |= accelerator.IsCtrlDown() ? ControlMask : 0;
+  modifiers |= accelerator.IsAltDown() ? Mod1Mask : 0;
+
+  return modifiers;
+}
+
+}  // namespace
+
+namespace extensions {
+
+// static
+GlobalShortcutListener* GlobalShortcutListener::GetInstance() {
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  static GlobalShortcutListenerX11* instance =
+      new GlobalShortcutListenerX11();
+  return instance;
+}
+
+GlobalShortcutListenerX11::GlobalShortcutListenerX11()
+    : is_listening_(false),
+      x_display_(gfx::GetXDisplay()),
+      x_root_window_(DefaultRootWindow(x_display_)) {
+  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+}
+
+GlobalShortcutListenerX11::~GlobalShortcutListenerX11() {
+  if (is_listening_)
+    StopListening();
+}
+
+void GlobalShortcutListenerX11::StartListening() {
+  DCHECK(!is_listening_);  // Don't start twice.
+  DCHECK(!registered_hot_keys_.empty());  // Also don't start if no hotkey is
+                                          // registered.
+#if defined(TOOLKIT_GTK)
+  gdk_window_add_filter(gdk_get_default_root_window(),
+                        &GlobalShortcutListenerX11::OnXEventThunk,
+                        this);
+#else
+  base::MessagePumpX11::Current()->AddDispatcherForRootWindow(this);
+#endif
+
+  is_listening_ = true;
+}
+
+void GlobalShortcutListenerX11::StopListening() {
+  DCHECK(is_listening_);  // No point if we are not already listening.
+  DCHECK(registered_hot_keys_.empty());  // Make sure the set is clean before
+                                         // ending.
+
+#if defined(TOOLKIT_GTK)
+  gdk_window_remove_filter(NULL,
+                           &GlobalShortcutListenerX11::OnXEventThunk,
+                           this);
+#else
+  base::MessagePumpX11::Current()->RemoveDispatcherForRootWindow(this);
+#endif
+
+  is_listening_ = false;
+}
+
+#if !defined(TOOLKIT_GTK)
+uint32_t GlobalShortcutListenerX11::Dispatch(const base::NativeEvent& event) {
+  if (event->type == KeyPress)
+    OnXKeyPressEvent(event);
+
+  return POST_DISPATCH_NONE;
+}
+#endif
+
+bool GlobalShortcutListenerX11::RegisterAcceleratorImpl(
+    const ui::Accelerator& accelerator) {
+  DCHECK(registered_hot_keys_.find(accelerator) == registered_hot_keys_.end());
+
+  int modifiers = GetNativeModifiers(accelerator);
+  KeyCode keycode = XKeysymToKeycode(x_display_,
+      XKeysymForWindowsKeyCode(accelerator.key_code(), false));
+  gfx::X11ErrorTracker err_tracker;
+
+  // Because XGrabKey only works on the exact modifiers mask, we should register
+  // our hot keys with modifiers that we want to ignore, including Num lock,
+  // Caps lock, Scroll lock. See comment about |kModifiersMasks|.
+  for (size_t i = 0; i < arraysize(kModifiersMasks); ++i) {
+    XGrabKey(x_display_, keycode, modifiers | kModifiersMasks[i],
+             x_root_window_, False, GrabModeAsync, GrabModeAsync);
+  }
+
+  if (err_tracker.FoundNewError()) {
+    // We may have part of the hotkeys registered, clean up.
+    for (size_t i = 0; i < arraysize(kModifiersMasks); ++i) {
+      XUngrabKey(x_display_, keycode, modifiers | kModifiersMasks[i],
+                 x_root_window_);
+    }
+
+    return false;
+  }
+
+  registered_hot_keys_.insert(accelerator);
+  return true;
+}
+
+void GlobalShortcutListenerX11::UnregisterAcceleratorImpl(
+    const ui::Accelerator& accelerator) {
+  DCHECK(registered_hot_keys_.find(accelerator) != registered_hot_keys_.end());
+
+  int modifiers = GetNativeModifiers(accelerator);
+  KeyCode keycode = XKeysymToKeycode(x_display_,
+      XKeysymForWindowsKeyCode(accelerator.key_code(), false));
+
+  for (size_t i = 0; i < arraysize(kModifiersMasks); ++i) {
+    XUngrabKey(x_display_, keycode, modifiers | kModifiersMasks[i],
+               x_root_window_);
+  }
+  registered_hot_keys_.erase(accelerator);
+}
+
+#if defined(TOOLKIT_GTK)
+GdkFilterReturn GlobalShortcutListenerX11::OnXEvent(GdkXEvent* gdk_x_event,
+                                                    GdkEvent* gdk_event) {
+  XEvent* x_event = static_cast<XEvent*>(gdk_x_event);
+  if (x_event->type == KeyPress)
+    OnXKeyPressEvent(x_event);
+
+  return GDK_FILTER_CONTINUE;
+}
+#endif
+
+void GlobalShortcutListenerX11::OnXKeyPressEvent(::XEvent* x_event) {
+  DCHECK(x_event->type == KeyPress);
+  int modifiers = 0;
+  modifiers |= (x_event->xkey.state & ShiftMask) ? ui::EF_SHIFT_DOWN : 0;
+  modifiers |= (x_event->xkey.state & ControlMask) ? ui::EF_CONTROL_DOWN : 0;
+  modifiers |= (x_event->xkey.state & Mod1Mask) ? ui::EF_ALT_DOWN : 0;
+
+  ui::Accelerator accelerator(
+      ui::KeyboardCodeFromXKeyEvent(x_event), modifiers);
+  if (registered_hot_keys_.find(accelerator) != registered_hot_keys_.end())
+    NotifyKeyPressed(accelerator);
+}
+
+}  // namespace extensions

+ 73 - 0
chromium_src/chrome/browser/extensions/global_shortcut_listener_x11.h

@@ -0,0 +1,73 @@
+// Copyright 2013 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_GLOBAL_SHORTCUT_LISTENER_X11_H_
+#define CHROME_BROWSER_EXTENSIONS_GLOBAL_SHORTCUT_LISTENER_X11_H_
+
+#include <X11/Xlib.h>
+#include <set>
+
+#include "base/message_loop/message_pump_dispatcher.h"
+#include "chrome/browser/extensions/global_shortcut_listener.h"
+
+#if defined(TOOLKIT_GTK)
+#include <gtk/gtk.h>
+#include "ui/base/gtk/gtk_signal.h"
+#endif  // defined(TOOLKIT_GTK)
+
+namespace extensions {
+
+// X11-specific implementation of the GlobalShortcutListener class that
+// listens for global shortcuts. Handles basic keyboard intercepting and
+// forwards its output to the base class for processing.
+class GlobalShortcutListenerX11
+    :
+#if !defined(TOOLKIT_GTK)
+      public base::MessagePumpDispatcher,
+#endif
+      public GlobalShortcutListener {
+ public:
+  GlobalShortcutListenerX11();
+  virtual ~GlobalShortcutListenerX11();
+
+#if !defined(TOOLKIT_GTK)
+  // base::MessagePumpDispatcher implementation.
+  virtual uint32_t Dispatch(const base::NativeEvent& event) OVERRIDE;
+#endif
+
+ private:
+  // GlobalShortcutListener implementation.
+  virtual void StartListening() OVERRIDE;
+  virtual void StopListening() OVERRIDE;
+  virtual bool RegisterAcceleratorImpl(
+      const ui::Accelerator& accelerator) OVERRIDE;
+  virtual void UnregisterAcceleratorImpl(
+      const ui::Accelerator& accelerator) OVERRIDE;
+
+#if defined(TOOLKIT_GTK)
+  // Callback for XEvents of the default root window.
+  CHROMEG_CALLBACK_1(GlobalShortcutListenerX11, GdkFilterReturn,
+                     OnXEvent, GdkXEvent*, GdkEvent*);
+#endif
+
+  // Invoked when a global shortcut is pressed.
+  void OnXKeyPressEvent(::XEvent* x_event);
+
+  // Whether this object is listening for global shortcuts.
+  bool is_listening_;
+
+  // The x11 default display and the native root window.
+  ::Display* x_display_;
+  ::Window x_root_window_;
+
+  // A set of registered accelerators.
+  typedef std::set<ui::Accelerator> RegisteredHotKeys;
+  RegisteredHotKeys registered_hot_keys_;
+
+  DISALLOW_COPY_AND_ASSIGN(GlobalShortcutListenerX11);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_GLOBAL_SHORTCUT_LISTENER_X11_H_

+ 1 - 0
docs/README.md

@@ -22,6 +22,7 @@ Modules for browser side:
 * [power-monitor](api/power-monitor.md)
 * [protocol](api/protocol.md)
 * [tray](api/tray.md)
+* [shortcut](api/shortcut.md)
 
 Modules for web page:
 

+ 67 - 0
docs/api/shortcut.md

@@ -0,0 +1,67 @@
+# shortcut
+
+A `Shortcut` presents a global keyboard shortcut in operating system. If a 
+`Shortcut` is registered in app, the app will receive an `active` event when
+user presses the shortcut. Note that it is global, even your app does not get 
+focused, it still works.
+
+
+```javascript
+var Shortcut = require('shortcut');
+
+shortcut = new Shortcut('ctrl+a');
+shortcut.setKey('ctrl+s');
+shortcut.on('active', function() { console.log('ctrl+s pressed'); });
+shortcut.on('failed', function() { console.log("failed"); });
+shortcut.register();
+```
+
+## Class: Shortcut 
+
+`Shortcut` is an [EventEmitter](event-emitter).
+
+### new Shortcut(keycode)
+
+* `keycode` String
+
+Creates a new `Shortcut` associated with the `keycode`.
+
+`keycode` is a string to specify shortcut key, such as "ctrl+shift+a".
+
+A `keycode` consists of modifier and key two parts:
+
+__Modifiers__: control(ctrl), command(cmd), alt, shift, commandorcontrol(cmdorctrl).
+
+__Supported keys__: 0-9, a-z, up, down, left, right, home, end, pagedown, pageup,
+insert, delete, esc, space, backspace, tab, f1-f12, volumeup, volumedown, media 
+keys(medianextrack, mediaprevioustrack, mediastop, mediaplaypause).
+
+### Event: active
+
+Emitted when a registered `shortcut` is pressed by user.
+
+### Event: failed
+
+Emitted when the keycode of `shortcut` is invalid.
+
+### Shortcut.setKey(keycode)
+
+* `keycode` String 
+
+Set new `keycode` to a `Shortcut`. Note that this operation will override previous
+keycode and will unregister the `Shortcut`, developer should register the
+`Shortcut` again after `setKey`.
+
+### Shortcut.register
+
+Register a `Shortcut` to operating system.
+
+### Shortcut.unregister
+
+Unregister a `Shortcut` to operating system.
+
+### Shortcut.isRegistered
+
+Return whether the shortcut is registered.
+
+[event-emitter]: http://nodejs.org/api/events.html#events_class_events_eventemitter