mac: Implement global keyboard shortcut API.
This commit is contained in:
parent
476f545a67
commit
4b3bd9c3cc
9 changed files with 922 additions and 0 deletions
|
@ -0,0 +1,125 @@
|
|||
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "chrome/browser/ui/shortcut/global_shortcut_listener.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "ui/base/accelerators/accelerator.h"
|
||||
|
||||
using content::BrowserThread;
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace api {
|
||||
|
||||
GlobalShortcutListener::GlobalShortcutListener()
|
||||
: shortcut_handling_suspended_(false) {
|
||||
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
}
|
||||
|
||||
GlobalShortcutListener::~GlobalShortcutListener() {
|
||||
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
||||
}
|
||||
|
||||
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);
|
||||
if (it == accelerator_map_.end())
|
||||
return;
|
||||
// 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 api
|
||||
|
||||
} // namespace atom
|
|
@ -0,0 +1,105 @@
|
|||
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CHROME_BROWSER_UI_SHORTCUT_GLOBAL_SHORTCUT_LISTENER_H_
|
||||
#define CHROME_BROWSER_UI_SHORTCUT_GLOBAL_SHORTCUT_LISTENER_H_
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "ui/events/keycodes/keyboard_codes.h"
|
||||
|
||||
namespace ui {
|
||||
class Accelerator;
|
||||
}
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace api {
|
||||
|
||||
// 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;
|
||||
|
||||
// Returen whether accelerator is registered.
|
||||
virtual bool IsAcceleratorRegistered(const ui::Accelerator& accelerator) = 0;
|
||||
|
||||
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 api
|
||||
|
||||
} // namespace atom
|
||||
#endif // CHROME_BROWSER_UI_SHORTCUT_GLOBAL_SHORTCUT_LISTENER_H_
|
|
@ -0,0 +1,111 @@
|
|||
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CHROME_BROWSER_UI_SHORTCUT_GLOBAL_SHORTCUT_LISTENER_MAC_H_
|
||||
#define CHROME_BROWSER_UI_SHORTCUT_GLOBAL_SHORTCUT_LISTENER_MAC_H_
|
||||
|
||||
#include "chrome/browser/ui/shortcut/global_shortcut_listener.h"
|
||||
|
||||
#include <Carbon/Carbon.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "base/mac/scoped_nsobject.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace api {
|
||||
|
||||
// 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();
|
||||
|
||||
virtual bool IsAcceleratorRegistered(const ui::Accelerator& accelerator) OVERRIDE;
|
||||
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 api
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // CHROME_BROWSER_UI_SHORTCUT_GLOBAL_SHORTCUT_LISTENER_MAC_H_
|
|
@ -0,0 +1,392 @@
|
|||
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "chrome/browser/ui/shortcut/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 atom::api::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 atom {
|
||||
|
||||
namespace api {
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
bool GlobalShortcutListenerMac::IsAcceleratorRegistered(
|
||||
const ui::Accelerator& accelerator) {
|
||||
return accelerator_ids_.find(accelerator) != accelerator_ids_.end();
|
||||
}
|
||||
|
||||
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 api
|
||||
|
||||
} // namespace atom
|
Loading…
Add table
Add a link
Reference in a new issue