From f9e29fccf47b4307424ccb4ac1270ed08c4a189c Mon Sep 17 00:00:00 2001 From: John Maguire Date: Thu, 17 Jun 2010 20:31:34 +0000 Subject: [PATCH] Global keyboard shortcuts for Cocoa. Requires "Enable access for assistive devices" set in Universal Access control panel. --- src/CMakeLists.txt | 4 +- src/core/globalshortcuts.cpp | 12 +-- src/core/mac_startup.h | 4 +- src/core/mac_startup.mm | 37 +++----- src/core/macglobalshortcutbackend.h | 51 ++++++++++ src/core/macglobalshortcutbackend.mm | 128 ++++++++++++++++++++++++++ src/core/qxtglobalshortcutbackend.cpp | 2 - 7 files changed, 205 insertions(+), 33 deletions(-) create mode 100644 src/core/macglobalshortcutbackend.h create mode 100644 src/core/macglobalshortcutbackend.mm diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e9c637fae..0c0fa0359 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 2.6) set(CMAKE_C_FLAGS "-Wall") -set(CMAKE_CXX_FLAGS "-Wnon-virtual-dtor -Woverloaded-virtual -Wall") +set(CMAKE_CXX_FLAGS "-Woverloaded-virtual -Wall") include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) include_directories(${SPARKLE}) @@ -356,6 +356,8 @@ endif(ENABLE_VISUALISATIONS) # OSDs if(APPLE) list(APPEND SOURCES widgets/osd_mac.mm) + list(APPEND SOURCES core/macglobalshortcutbackend.mm) + list(APPEND HEADERS core/macglobalshortcutbackend.h) include_directories(${GROWL}/Headers) else(APPLE) if(WIN32) diff --git a/src/core/globalshortcuts.cpp b/src/core/globalshortcuts.cpp index 148832d9b..af0c87995 100644 --- a/src/core/globalshortcuts.cpp +++ b/src/core/globalshortcuts.cpp @@ -16,6 +16,7 @@ #include "globalshortcuts.h" #include "gnomeglobalshortcutbackend.h" +#include "macglobalshortcutbackend.h" #include "qxtglobalshortcutbackend.h" #include "mac_startup.h" @@ -39,11 +40,11 @@ GlobalShortcuts::GlobalShortcuts(QObject *parent) // Create actions AddShortcut("play", tr("Play"), SIGNAL(Play())); AddShortcut("pause", tr("Pause"), SIGNAL(Pause())); - AddShortcut("play_pause", tr("Play/Pause"), SIGNAL(PlayPause()), QKeySequence(Qt::Key_MediaPlay)); - AddShortcut("stop", tr("Stop"), SIGNAL(Stop()), QKeySequence(Qt::Key_MediaStop)); + AddShortcut("play_pause", tr("Play/Pause"), SIGNAL(PlayPause())); //QKeySequence(Qt::Key_MediaPlay)); + AddShortcut("stop", tr("Stop"), SIGNAL(Stop()));// QKeySequence(Qt::Key_MediaStop)); AddShortcut("stop_after", tr("Stop playing after current track"), SIGNAL(StopAfter())); - AddShortcut("next_track", tr("Next track"), SIGNAL(Next()), QKeySequence(Qt::Key_MediaNext)); - AddShortcut("prev_track", tr("Previous track"), SIGNAL(Previous()), QKeySequence(Qt::Key_MediaPrevious)); + AddShortcut("next_track", tr("Next track"), SIGNAL(Next()));// QKeySequence(Qt::Key_MediaNext)); + AddShortcut("prev_track", tr("Previous track"), SIGNAL(Previous()));// QKeySequence(Qt::Key_MediaPrevious)); AddShortcut("inc_volume", tr("Increase volume"), SIGNAL(IncVolume())); AddShortcut("dec_volume", tr("Decrease volume"), SIGNAL(DecVolume())); AddShortcut("mute", tr("Mute"), SIGNAL(Mute())); @@ -57,8 +58,7 @@ GlobalShortcuts::GlobalShortcuts(QObject *parent) #ifndef Q_OS_DARWIN system_backend_ = new QxtGlobalShortcutBackend(this); #else - // Setup global media key shortcuts for mac. - mac::SetShortcutHandler(this); + system_backend_ = new MacGlobalShortcutBackend(this); #endif ReloadSettings(); diff --git a/src/core/mac_startup.h b/src/core/mac_startup.h index 5a99646f9..dbb1bca65 100644 --- a/src/core/mac_startup.h +++ b/src/core/mac_startup.h @@ -1,7 +1,7 @@ #ifndef MAC_STARTUP_H #define MAC_STARTUP_H -class GlobalShortcuts; +class MacGlobalShortcutBackend; class QObject; class PlatformInterface { @@ -16,7 +16,7 @@ class PlatformInterface { namespace mac { void MacMain(); -void SetShortcutHandler(GlobalShortcuts* handler); +void SetShortcutHandler(MacGlobalShortcutBackend* handler); void SetApplicationHandler(PlatformInterface* handler); void CheckForUpdates(); diff --git a/src/core/mac_startup.mm b/src/core/mac_startup.mm index 361ad9a71..36dc1b494 100644 --- a/src/core/mac_startup.mm +++ b/src/core/mac_startup.mm @@ -10,8 +10,11 @@ #import #import +#import + #include "globalshortcuts.h" #include "mac_startup.h" +#include "macglobalshortcutbackend.h" #include #include @@ -22,12 +25,12 @@ // See: http://www.rogueamoeba.com/utm/2007/09/29/apple-keyboard-media-key-event-handling/ @interface MacApplication :NSApplication { - GlobalShortcuts* shortcut_handler_; + MacGlobalShortcutBackend* shortcut_handler_; PlatformInterface* application_handler_; } -- (GlobalShortcuts*) shortcut_handler; -- (void) SetShortcutHandler: (GlobalShortcuts*)handler; +- (MacGlobalShortcutBackend*) shortcut_handler; +- (void) SetShortcutHandler: (MacGlobalShortcutBackend*)handler; - (PlatformInterface*) application_handler; - (void) SetApplicationHandler: (PlatformInterface*)handler; @@ -35,7 +38,11 @@ - (void) mediaKeyEvent: (int)key state: (BOOL)state repeat: (BOOL)repeat; @end -@interface AppDelegate :NSObject { // { +#ifdef MAC_OS_X_VERSION_10_6 +@interface AppDelegate :NSObject { +#else +@interface AppDelegate :NSObject { +#endif PlatformInterface* application_handler_; } @@ -86,11 +93,11 @@ return self; } -- (GlobalShortcuts*) shortcut_handler { +- (MacGlobalShortcutBackend*) shortcut_handler { return shortcut_handler_; } -- (void) SetShortcutHandler: (GlobalShortcuts*)handler { +- (void) SetShortcutHandler: (MacGlobalShortcutBackend*)handler { shortcut_handler_ = handler; } @@ -121,21 +128,7 @@ return; } if (state == 0) { - switch (key) { - case NX_KEYTYPE_PLAY: - // Play pressed. - shortcut_handler_->MacMediaKeyPressed("Play"); - break; - case NX_KEYTYPE_FAST: - // Next pressed. - shortcut_handler_->MacMediaKeyPressed("Next"); - break; - case NX_KEYTYPE_REWIND: - shortcut_handler_->MacMediaKeyPressed("Previous"); - break; - default: - break; - } + shortcut_handler_->MacMediaKeyPressed(key); } } @@ -151,7 +144,7 @@ void MacMain() { [[SUUpdater sharedUpdater] setDelegate: NSApp]; } -void SetShortcutHandler(GlobalShortcuts* handler) { +void SetShortcutHandler(MacGlobalShortcutBackend* handler) { [NSApp SetShortcutHandler: handler]; } diff --git a/src/core/macglobalshortcutbackend.h b/src/core/macglobalshortcutbackend.h new file mode 100644 index 000000000..029e3d45a --- /dev/null +++ b/src/core/macglobalshortcutbackend.h @@ -0,0 +1,51 @@ +/* This file is part of Clementine. + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . +*/ + +#ifndef MACGLOBALSHORTCUTBACKEND_H +#define MACGLOBALSHORTCUTBACKEND_H + +#include "globalshortcutbackend.h" + +#include +#include + +class MacGlobalShortcutBackendPrivate; +class QAction; + +class MacGlobalShortcutBackend : public GlobalShortcutBackend { + Q_OBJECT + +public: + MacGlobalShortcutBackend(GlobalShortcuts* parent); + virtual ~MacGlobalShortcutBackend(); + + void MacMediaKeyPressed(int key); + +protected: + bool DoRegister(); + void DoUnregister(); + +private: + void KeyPressed(const QKeySequence& sequence); + + QMap shortcuts_; + + friend class MacGlobalShortcutBackendPrivate; + + MacGlobalShortcutBackendPrivate* p_; +}; + +#endif // MACGLOBALSHORTCUTBACKEND_H diff --git a/src/core/macglobalshortcutbackend.mm b/src/core/macglobalshortcutbackend.mm new file mode 100644 index 000000000..4f4a55df0 --- /dev/null +++ b/src/core/macglobalshortcutbackend.mm @@ -0,0 +1,128 @@ +/* This file is part of Clementine. + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . +*/ + +#include "macglobalshortcutbackend.h" + +#include "globalshortcuts.h" +#include "mac_startup.h" + +#include + +#include +#include +#include + +#include +#include +#include + +class MacGlobalShortcutBackendPrivate : boost::noncopyable { + public: + explicit MacGlobalShortcutBackendPrivate(MacGlobalShortcutBackend* backend) + : monitor_(nil), + backend_(backend) { + } + + bool Register() { + #ifdef NS_BLOCKS_AVAILABLE + monitor_ = [NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask + handler:^(NSEvent* event) { + qDebug() << __PRETTY_FUNCTION__; + HandleKeyEvent(event); + }]; + return true; + #else + return false; + #endif + } + + void Unregister() { + [NSEvent removeMonitor:monitor_]; + } + + private: + static QKeySequence GetSequence(NSEvent* event) { + NSString* str = [event charactersIgnoringModifiers]; + NSString* lower = [str lowercaseString]; + const char* chars = [lower UTF8String]; + NSUInteger modifiers = [event modifierFlags]; + int key = Qt::Key_A + chars[0] - 'a'; // >.> + if (modifiers & NSShiftKeyMask) { + key += Qt::SHIFT; + } + if (modifiers & NSControlKeyMask) { + key += Qt::META; + } + if (modifiers & NSAlternateKeyMask) { + key += Qt::ALT; + } + if (modifiers & NSCommandKeyMask) { + key += Qt::CTRL; + } + + return QKeySequence(key); + } + + void HandleKeyEvent(NSEvent* event) { + QKeySequence sequence = GetSequence(event); + backend_->KeyPressed(sequence); + } + + id monitor_; + MacGlobalShortcutBackend* backend_; +}; + +MacGlobalShortcutBackend::MacGlobalShortcutBackend(GlobalShortcuts* parent) + : GlobalShortcutBackend(parent), + p_(new MacGlobalShortcutBackendPrivate(this)) { +} + +MacGlobalShortcutBackend::~MacGlobalShortcutBackend() { + delete p_; +} + +bool MacGlobalShortcutBackend::DoRegister() { + mac::SetShortcutHandler(this); + foreach (const GlobalShortcuts::Shortcut& shortcut, manager_->shortcuts().values()) { + shortcuts_[shortcut.action->shortcut()] = shortcut.action; + } + return p_->Register(); +} + +void MacGlobalShortcutBackend::DoUnregister() { + p_->Unregister(); +} + +void MacGlobalShortcutBackend::MacMediaKeyPressed(int key) { + switch (key) { + case NX_KEYTYPE_PLAY: + manager_->shortcuts()["play_pause"].action->trigger(); + break; + case NX_KEYTYPE_FAST: + manager_->shortcuts()["next_track"].action->trigger(); + break; + case NX_KEYTYPE_REWIND: + manager_->shortcuts()["prev_track"].action->trigger(); + break; + } +} + +void MacGlobalShortcutBackend::KeyPressed(const QKeySequence& sequence) { + QAction* action = shortcuts_[sequence]; + if (action) { + action->trigger(); + } +} diff --git a/src/core/qxtglobalshortcutbackend.cpp b/src/core/qxtglobalshortcutbackend.cpp index cd6add2a4..03131fd96 100644 --- a/src/core/qxtglobalshortcutbackend.cpp +++ b/src/core/qxtglobalshortcutbackend.cpp @@ -28,11 +28,9 @@ QxtGlobalShortcutBackend::QxtGlobalShortcutBackend(GlobalShortcuts *parent) bool QxtGlobalShortcutBackend::DoRegister() { qDebug() << __PRETTY_FUNCTION__; -#ifndef Q_OS_DARWIN foreach (const GlobalShortcuts::Shortcut& shortcut, manager_->shortcuts().values()) { AddShortcut(shortcut.action); } -#endif return true; }