Merge pull request #642 from hokein/master

Add Volume keys support in global-shortcut API, fix #630.
This commit is contained in:
Cheng Zhao 2014-09-10 10:30:26 +09:00
commit 92b5dab3f9
2 changed files with 57 additions and 42 deletions

View file

@ -21,8 +21,8 @@ namespace extensions {
// forwards its output to the base class for processing. // forwards its output to the base class for processing.
// //
// This class does two things: // This class does two things:
// 1. Intercepts media keys. Uses an event tap for intercepting media keys // 1. Intercepts media/volume keys. Uses an event tap for intercepting media keys
// (PlayPause, NextTrack, PreviousTrack). // (PlayPause, NextTrack, PreviousTrack) and volume keys(VolumeUp, VolumeDown, VolumeMute).
// 2. Binds keyboard shortcuts (hot keys). Carbon RegisterEventHotKey API for // 2. Binds keyboard shortcuts (hot keys). Carbon RegisterEventHotKey API for
// binding to non-media key global hot keys (eg. Command-Shift-1). // binding to non-media key global hot keys (eg. Command-Shift-1).
class GlobalShortcutListenerMac : public GlobalShortcutListener { class GlobalShortcutListenerMac : public GlobalShortcutListener {
@ -38,7 +38,7 @@ class GlobalShortcutListenerMac : public GlobalShortcutListener {
// Keyboard event callbacks. // Keyboard event callbacks.
void OnHotKeyEvent(EventHotKeyID hot_key_id); void OnHotKeyEvent(EventHotKeyID hot_key_id);
bool OnMediaKeyEvent(int key_code); bool OnMediaOrVolumeKeyEvent(int key_code);
// GlobalShortcutListener implementation. // GlobalShortcutListener implementation.
virtual void StartListening() OVERRIDE; virtual void StartListening() OVERRIDE;
@ -52,16 +52,16 @@ class GlobalShortcutListenerMac : public GlobalShortcutListener {
bool RegisterHotKey(const ui::Accelerator& accelerator, KeyId hot_key_id); bool RegisterHotKey(const ui::Accelerator& accelerator, KeyId hot_key_id);
void UnregisterHotKey(const ui::Accelerator& accelerator); void UnregisterHotKey(const ui::Accelerator& accelerator);
// Enable and disable the media key event tap. // Enable and disable the media/volume key event tap.
void StartWatchingMediaKeys(); void StartWatchingMediaOrVolumeKeys();
void StopWatchingMediaKeys(); void StopWatchingMediaOrVolumeKeys();
// Enable and disable the hot key event handler. // Enable and disable the hot key event handler.
void StartWatchingHotKeys(); void StartWatchingHotKeys();
void StopWatchingHotKeys(); void StopWatchingHotKeys();
// Whether or not any media keys are currently registered. // Whether or not any media/volume keys are currently registered.
bool IsAnyMediaKeyRegistered(); bool IsAnyMediaOrVolumeKeyRegistered();
// Whether or not any hot keys are currently registered. // Whether or not any hot keys are currently registered.
bool IsAnyHotKeyRegistered(); bool IsAnyHotKeyRegistered();
@ -80,7 +80,7 @@ class GlobalShortcutListenerMac : public GlobalShortcutListener {
// The hotkey identifier for the next global shortcut that is added. // The hotkey identifier for the next global shortcut that is added.
KeyId hot_key_id_; KeyId hot_key_id_;
// A map of all hotkeys (media keys and shortcuts) mapping to their // A map of all hotkeys (media/volume keys and shortcuts) mapping to their
// corresponding hotkey IDs. For quickly finding if an accelerator is // corresponding hotkey IDs. For quickly finding if an accelerator is
// registered. // registered.
AcceleratorIdMap accelerator_ids_; AcceleratorIdMap accelerator_ids_;
@ -91,7 +91,7 @@ class GlobalShortcutListenerMac : public GlobalShortcutListener {
// Keyboard shortcut IDs to hotkeys map for unregistration. // Keyboard shortcut IDs to hotkeys map for unregistration.
IdHotKeyRefMap id_hot_key_refs_; IdHotKeyRefMap id_hot_key_refs_;
// Event tap for intercepting mac media keys. // Event tap for intercepting mac media/volume keys.
CFMachPortRef event_tap_; CFMachPortRef event_tap_;
CFRunLoopSourceRef event_tap_source_; CFRunLoopSourceRef event_tap_source_;

View file

@ -19,11 +19,11 @@ using extensions::GlobalShortcutListenerMac;
namespace { namespace {
// The media keys subtype. No official docs found, but widely known. // The media/volume keys subtype. No official docs found, but widely known.
// http://lists.apple.com/archives/cocoa-dev/2007/Aug/msg00499.html // http://lists.apple.com/archives/cocoa-dev/2007/Aug/msg00499.html
const int kSystemDefinedEventMediaKeysSubtype = 8; const int kSystemDefinedEventMediaAndVolumeKeysSubtype = 8;
ui::KeyboardCode MediaKeyCodeToKeyboardCode(int key_code) { ui::KeyboardCode MediaOrVolumeKeyCodeToKeyboardCode(int key_code) {
switch (key_code) { switch (key_code) {
case NX_KEYTYPE_PLAY: case NX_KEYTYPE_PLAY:
return ui::VKEY_MEDIA_PLAY_PAUSE; return ui::VKEY_MEDIA_PLAY_PAUSE;
@ -33,17 +33,26 @@ ui::KeyboardCode MediaKeyCodeToKeyboardCode(int key_code) {
case NX_KEYTYPE_NEXT: case NX_KEYTYPE_NEXT:
case NX_KEYTYPE_FAST: case NX_KEYTYPE_FAST:
return ui::VKEY_MEDIA_NEXT_TRACK; return ui::VKEY_MEDIA_NEXT_TRACK;
case NX_KEYTYPE_SOUND_UP:
return ui::VKEY_VOLUME_UP;
case NX_KEYTYPE_SOUND_DOWN:
return ui::VKEY_VOLUME_DOWN;
case NX_KEYTYPE_MUTE:
return ui::VKEY_VOLUME_MUTE;
} }
return ui::VKEY_UNKNOWN; return ui::VKEY_UNKNOWN;
} }
bool IsMediaKey(const ui::Accelerator& accelerator) { bool IsMediaOrVolumeKey(const ui::Accelerator& accelerator) {
if (accelerator.modifiers() != 0) if (accelerator.modifiers() != 0)
return false; return false;
return (accelerator.key_code() == ui::VKEY_MEDIA_NEXT_TRACK || return (accelerator.key_code() == ui::VKEY_MEDIA_NEXT_TRACK ||
accelerator.key_code() == ui::VKEY_MEDIA_PREV_TRACK || accelerator.key_code() == ui::VKEY_MEDIA_PREV_TRACK ||
accelerator.key_code() == ui::VKEY_MEDIA_PLAY_PAUSE || accelerator.key_code() == ui::VKEY_MEDIA_PLAY_PAUSE ||
accelerator.key_code() == ui::VKEY_MEDIA_STOP); accelerator.key_code() == ui::VKEY_MEDIA_STOP ||
accelerator.key_code() == ui::VKEY_VOLUME_UP ||
accelerator.key_code() == ui::VKEY_VOLUME_DOWN ||
accelerator.key_code() == ui::VKEY_VOLUME_MUTE);
} }
} // namespace } // namespace
@ -77,8 +86,8 @@ GlobalShortcutListenerMac::~GlobalShortcutListenerMac() {
// If keys are still registered, make sure we stop the tap. Again, this // If keys are still registered, make sure we stop the tap. Again, this
// should never happen. // should never happen.
if (IsAnyMediaKeyRegistered()) if (IsAnyMediaOrVolumeKeyRegistered())
StopWatchingMediaKeys(); StopWatchingMediaOrVolumeKeys();
if (IsAnyHotKeyRegistered()) if (IsAnyHotKeyRegistered())
StopWatchingHotKeys(); StopWatchingHotKeys();
@ -114,9 +123,11 @@ void GlobalShortcutListenerMac::OnHotKeyEvent(EventHotKeyID hot_key_id) {
NotifyKeyPressed(accelerator); NotifyKeyPressed(accelerator);
} }
bool GlobalShortcutListenerMac::OnMediaKeyEvent(int media_key_code) { bool GlobalShortcutListenerMac::OnMediaOrVolumeKeyEvent(
int media_or_volume_key_code) {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ui::KeyboardCode key_code = MediaKeyCodeToKeyboardCode(media_key_code); ui::KeyboardCode key_code = MediaOrVolumeKeyCodeToKeyboardCode(
media_or_volume_key_code);
// Create an accelerator corresponding to the keyCode. // Create an accelerator corresponding to the keyCode.
ui::Accelerator accelerator(key_code, 0); ui::Accelerator accelerator(key_code, 0);
// Look for a match with a bound hot_key. // Look for a match with a bound hot_key.
@ -133,10 +144,10 @@ bool GlobalShortcutListenerMac::RegisterAcceleratorImpl(
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(accelerator_ids_.find(accelerator) == accelerator_ids_.end()); DCHECK(accelerator_ids_.find(accelerator) == accelerator_ids_.end());
if (IsMediaKey(accelerator)) { if (IsMediaOrVolumeKey(accelerator)) {
if (!IsAnyMediaKeyRegistered()) { if (!IsAnyMediaOrVolumeKeyRegistered()) {
// If this is the first media key registered, start the event tap. // If this is the first media/volume key registered, start the event tap.
StartWatchingMediaKeys(); StartWatchingMediaOrVolumeKeys();
} }
} else { } else {
// Register hot_key if they are non-media keyboard shortcuts. // Register hot_key if they are non-media keyboard shortcuts.
@ -161,7 +172,7 @@ void GlobalShortcutListenerMac::UnregisterAcceleratorImpl(
DCHECK(accelerator_ids_.find(accelerator) != accelerator_ids_.end()); DCHECK(accelerator_ids_.find(accelerator) != accelerator_ids_.end());
// Unregister the hot_key if it's a keyboard shortcut. // Unregister the hot_key if it's a keyboard shortcut.
if (!IsMediaKey(accelerator)) if (!IsMediaOrVolumeKey(accelerator))
UnregisterHotKey(accelerator); UnregisterHotKey(accelerator);
// Remove hot_key from the mappings. // Remove hot_key from the mappings.
@ -169,11 +180,11 @@ void GlobalShortcutListenerMac::UnregisterAcceleratorImpl(
id_accelerators_.erase(key_id); id_accelerators_.erase(key_id);
accelerator_ids_.erase(accelerator); accelerator_ids_.erase(accelerator);
if (IsMediaKey(accelerator)) { if (IsMediaOrVolumeKey(accelerator)) {
// If we unregistered a media key, and now no media keys are registered, // If we unregistered a media/volume key, and now no media/volume keys are registered,
// stop the media key tap. // stop the media/volume key tap.
if (!IsAnyMediaKeyRegistered()) if (!IsAnyMediaOrVolumeKeyRegistered())
StopWatchingMediaKeys(); StopWatchingMediaOrVolumeKeys();
} else { } else {
// If we unregistered a hot key, and no more hot keys are registered, remove // If we unregistered a hot key, and no more hot keys are registered, remove
// the hot key handler. // the hot key handler.
@ -226,12 +237,12 @@ void GlobalShortcutListenerMac::UnregisterHotKey(
id_hot_key_refs_.erase(key_id); id_hot_key_refs_.erase(key_id);
} }
void GlobalShortcutListenerMac::StartWatchingMediaKeys() { void GlobalShortcutListenerMac::StartWatchingMediaOrVolumeKeys() {
// Make sure there's no existing event tap. // Make sure there's no existing event tap.
DCHECK(event_tap_ == NULL); DCHECK(event_tap_ == NULL);
DCHECK(event_tap_source_ == NULL); DCHECK(event_tap_source_ == NULL);
// Add an event tap to intercept the system defined media key events. // Add an event tap to intercept the system defined media/volume key events.
event_tap_ = CGEventTapCreate(kCGSessionEventTap, event_tap_ = CGEventTapCreate(kCGSessionEventTap,
kCGHeadInsertEventTap, kCGHeadInsertEventTap,
kCGEventTapOptionDefault, kCGEventTapOptionDefault,
@ -254,7 +265,7 @@ void GlobalShortcutListenerMac::StartWatchingMediaKeys() {
kCFRunLoopCommonModes); kCFRunLoopCommonModes);
} }
void GlobalShortcutListenerMac::StopWatchingMediaKeys() { void GlobalShortcutListenerMac::StopWatchingMediaOrVolumeKeys() {
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), event_tap_source_, CFRunLoopRemoveSource(CFRunLoopGetCurrent(), event_tap_source_,
kCFRunLoopCommonModes); kCFRunLoopCommonModes);
// Ensure both event tap and source are initialized. // Ensure both event tap and source are initialized.
@ -287,11 +298,11 @@ void GlobalShortcutListenerMac::StopWatchingHotKeys() {
event_handler_ = NULL; event_handler_ = NULL;
} }
bool GlobalShortcutListenerMac::IsAnyMediaKeyRegistered() { bool GlobalShortcutListenerMac::IsAnyMediaOrVolumeKeyRegistered() {
// Iterate through registered accelerators, looking for media keys. // Iterate through registered accelerators, looking for media/volume keys.
AcceleratorIdMap::iterator it; AcceleratorIdMap::iterator it;
for (it = accelerator_ids_.begin(); it != accelerator_ids_.end(); ++it) { for (it = accelerator_ids_.begin(); it != accelerator_ids_.end(); ++it) {
if (IsMediaKey(it->first)) if (IsMediaOrVolumeKey(it->first))
return true; return true;
} }
return false; return false;
@ -300,7 +311,7 @@ bool GlobalShortcutListenerMac::IsAnyMediaKeyRegistered() {
bool GlobalShortcutListenerMac::IsAnyHotKeyRegistered() { bool GlobalShortcutListenerMac::IsAnyHotKeyRegistered() {
AcceleratorIdMap::iterator it; AcceleratorIdMap::iterator it;
for (it = accelerator_ids_.begin(); it != accelerator_ids_.end(); ++it) { for (it = accelerator_ids_.begin(); it != accelerator_ids_.end(); ++it) {
if (!IsMediaKey(it->first)) if (!IsMediaOrVolumeKey(it->first))
return true; return true;
} }
return false; return false;
@ -329,20 +340,24 @@ CGEventRef GlobalShortcutListenerMac::EventTapCallback(
return event; return event;
} }
// Ignore events that are not system defined media keys. // Ignore events that are not system defined media/volume keys.
if (type != NX_SYSDEFINED || if (type != NX_SYSDEFINED ||
[ns_event type] != NSSystemDefined || [ns_event type] != NSSystemDefined ||
[ns_event subtype] != kSystemDefinedEventMediaKeysSubtype) { [ns_event subtype] != kSystemDefinedEventMediaAndVolumeKeysSubtype) {
return event; return event;
} }
NSInteger data1 = [ns_event data1]; NSInteger data1 = [ns_event data1];
// Ignore media keys that aren't previous, next and play/pause. // Ignore media keys that aren't previous, next and play/pause and
// volume keys that aren't up, down and mute.
// Magical constants are from http://weblog.rogueamoeba.com/2007/09/29/ // Magical constants are from http://weblog.rogueamoeba.com/2007/09/29/
int key_code = (data1 & 0xFFFF0000) >> 16; int key_code = (data1 & 0xFFFF0000) >> 16;
if (key_code != NX_KEYTYPE_PLAY && key_code != NX_KEYTYPE_NEXT && 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_PREVIOUS && key_code != NX_KEYTYPE_FAST &&
key_code != NX_KEYTYPE_REWIND) { key_code != NX_KEYTYPE_REWIND &&
key_code != NX_KEYTYPE_SOUND_UP &&
key_code != NX_KEYTYPE_SOUND_DOWN &&
key_code != NX_KEYTYPE_MUTE) {
return event; return event;
} }
@ -353,8 +368,8 @@ CGEventRef GlobalShortcutListenerMac::EventTapCallback(
if (!is_key_pressed) if (!is_key_pressed)
return event; return event;
// Now we have a media key that we care about. Send it to the caller. // Now we have a media/volume key that we care about. Send it to the caller.
bool was_handled = shortcut_listener->OnMediaKeyEvent(key_code); bool was_handled = shortcut_listener->OnMediaOrVolumeKeyEvent(key_code);
// Prevent event from proagating to other apps if handled by Chrome. // Prevent event from proagating to other apps if handled by Chrome.
if (was_handled) if (was_handled)