Merge pull request #534 from hokein/hotkey
Implement global shortcut API, fixes #439
This commit is contained in:
commit
9c038a2402
15 changed files with 1382 additions and 0 deletions
11
atom.gyp
11
atom.gyp
|
@ -20,6 +20,7 @@
|
||||||
'atom/browser/api/lib/browser-window.coffee',
|
'atom/browser/api/lib/browser-window.coffee',
|
||||||
'atom/browser/api/lib/content-tracing.coffee',
|
'atom/browser/api/lib/content-tracing.coffee',
|
||||||
'atom/browser/api/lib/dialog.coffee',
|
'atom/browser/api/lib/dialog.coffee',
|
||||||
|
'atom/browser/api/lib/global-shortcut.coffee',
|
||||||
'atom/browser/api/lib/ipc.coffee',
|
'atom/browser/api/lib/ipc.coffee',
|
||||||
'atom/browser/api/lib/menu.coffee',
|
'atom/browser/api/lib/menu.coffee',
|
||||||
'atom/browser/api/lib/menu-item.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_auto_updater.h',
|
||||||
'atom/browser/api/atom_api_content_tracing.cc',
|
'atom/browser/api/atom_api_content_tracing.cc',
|
||||||
'atom/browser/api/atom_api_dialog.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.cc',
|
||||||
'atom/browser/api/atom_api_menu.h',
|
'atom/browser/api/atom_api_menu.h',
|
||||||
'atom/browser/api/atom_api_menu_views.cc',
|
'atom/browser/api/atom_api_menu_views.cc',
|
||||||
|
@ -225,6 +228,14 @@
|
||||||
'atom/renderer/atom_render_view_observer.h',
|
'atom/renderer/atom_render_view_observer.h',
|
||||||
'atom/renderer/atom_renderer_client.cc',
|
'atom/renderer/atom_renderer_client.cc',
|
||||||
'atom/renderer/atom_renderer_client.h',
|
'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.cc',
|
||||||
'chromium_src/chrome/browser/ui/libgtk2ui/app_indicator_icon_menu.h',
|
'chromium_src/chrome/browser/ui/libgtk2ui/app_indicator_icon_menu.h',
|
||||||
'chromium_src/chrome/browser/ui/libgtk2ui/gtk2_status_icon.cc',
|
'chromium_src/chrome/browser/ui/libgtk2ui/gtk2_status_icon.cc',
|
||||||
|
|
123
atom/browser/api/atom_api_global_shortcut.cc
Normal file
123
atom/browser/api/atom_api_global_shortcut.cc
Normal 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)
|
54
atom/browser/api/atom_api_global_shortcut.h
Normal file
54
atom/browser/api/atom_api_global_shortcut.h
Normal 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_
|
5
atom/browser/api/lib/global-shortcut.coffee
Normal file
5
atom/browser/api/lib/global-shortcut.coffee
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
bindings = process.atomBinding 'global_shortcut'
|
||||||
|
|
||||||
|
globalShortcut = bindings.globalShortcut
|
||||||
|
|
||||||
|
module.exports = globalShortcut
|
|
@ -66,6 +66,7 @@ REFERENCE_MODULE(atom_browser_dialog);
|
||||||
REFERENCE_MODULE(atom_browser_menu);
|
REFERENCE_MODULE(atom_browser_menu);
|
||||||
REFERENCE_MODULE(atom_browser_power_monitor);
|
REFERENCE_MODULE(atom_browser_power_monitor);
|
||||||
REFERENCE_MODULE(atom_browser_protocol);
|
REFERENCE_MODULE(atom_browser_protocol);
|
||||||
|
REFERENCE_MODULE(atom_browser_global_shortcut);
|
||||||
REFERENCE_MODULE(atom_browser_tray);
|
REFERENCE_MODULE(atom_browser_tray);
|
||||||
REFERENCE_MODULE(atom_browser_window);
|
REFERENCE_MODULE(atom_browser_window);
|
||||||
REFERENCE_MODULE(atom_common_clipboard);
|
REFERENCE_MODULE(atom_common_clipboard);
|
||||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -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_
|
|
@ -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
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -22,6 +22,7 @@ Modules for browser side:
|
||||||
* [power-monitor](api/power-monitor.md)
|
* [power-monitor](api/power-monitor.md)
|
||||||
* [protocol](api/protocol.md)
|
* [protocol](api/protocol.md)
|
||||||
* [tray](api/tray.md)
|
* [tray](api/tray.md)
|
||||||
|
* [shortcut](api/shortcut.md)
|
||||||
|
|
||||||
Modules for web page:
|
Modules for web page:
|
||||||
|
|
||||||
|
|
67
docs/api/shortcut.md
Normal file
67
docs/api/shortcut.md
Normal 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
|
Loading…
Reference in a new issue