Merge pull request #534 from hokein/hotkey

Implement global shortcut API, fixes #439
This commit is contained in:
Cheng Zhao 2014-08-03 22:34:59 +08:00
commit 9c038a2402
15 changed files with 1382 additions and 0 deletions

View file

@ -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',

View file

@ -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)

View file

@ -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_

View file

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

View file

@ -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);

View file

@ -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

View file

@ -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_

View file

@ -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_

View file

@ -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

View file

@ -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

View file

@ -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_

View file

@ -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

View file

@ -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_

View file

@ -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
docs/api/shortcut.md Normal file
View file

@ -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