From 0ca1212691f032e4a3c093ab90801b025392ca6a Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sun, 13 Feb 2011 12:37:03 -0500 Subject: [PATCH] Add some mac-specific code. Tomahawk now opens spiffs from the browser or the finder. There is also commented out code for handling multimedia keys. Thanks to the Clementine project for the cocoa code :) --- admin/mac/Info.plist | 32 ++++- include/tomahawk/tomahawkapp.h | 7 +- src/CMakeLists.osx.txt | 25 ++++ src/CMakeLists.txt | 11 ++ src/config.h.in | 10 ++ src/main.cpp | 44 +++++++ src/tomahawkapp.cpp | 55 ++++++-- src/tomahawkapp_mac.h | 28 ++++ src/tomahawkapp_mac.mm | 229 +++++++++++++++++++++++++++++++++ src/tomahawkapp_macdelegate.h | 25 ++++ 10 files changed, 453 insertions(+), 13 deletions(-) create mode 100644 src/config.h.in create mode 100644 src/tomahawkapp_mac.h create mode 100644 src/tomahawkapp_mac.mm create mode 100644 src/tomahawkapp_macdelegate.h diff --git a/admin/mac/Info.plist b/admin/mac/Info.plist index 3187e4438..e1114d48c 100644 --- a/admin/mac/Info.plist +++ b/admin/mac/Info.plist @@ -23,6 +23,36 @@ CFBundleName Tomahawk LSMinimumSystemVersion - 10.5.0 + 10.5.0 + CFBundleURLTypes + + + CFBundleURLName + Tomahawk URL + CFBundleURLSchemes + + tomahawk + + + + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + xspf + + CFBundleTypeIconFile + Generic.icns + CFBundleTypeMIMETypes + + application/xspf+xml + + CFBundleTypeName + XSPF Playlist + CFBundleTypeRole + Viewer + + diff --git a/include/tomahawk/tomahawkapp.h b/include/tomahawk/tomahawkapp.h index 7f0fc2005..a7be72230 100644 --- a/include/tomahawk/tomahawkapp.h +++ b/include/tomahawk/tomahawkapp.h @@ -4,6 +4,7 @@ #define APP TomahawkApp::instance() #include "headlesscheck.h" +#include "tomahawkapp_mac.h" // for PlatforInterface #include #include @@ -47,7 +48,7 @@ class TomahawkWindow; // this also acts as a a container for important top-level objects // that other parts of the app need to find // (eg, library, pipeline, friends list) -class TomahawkApp : public TOMAHAWK_APPLICATION +class TomahawkApp : public TOMAHAWK_APPLICATION, public Tomahawk::PlatformInterface { Q_OBJECT @@ -69,6 +70,10 @@ public: void addScriptResolver( const QString& scriptPath ); void removeScriptResolver( const QString& scriptPath ); + // PlatformInterface + virtual void activate(); + virtual bool loadUrl( const QString& url ); + signals: void settingsChanged(); diff --git a/src/CMakeLists.osx.txt b/src/CMakeLists.osx.txt index 77590e8b5..256e34746 100644 --- a/src/CMakeLists.osx.txt +++ b/src/CMakeLists.osx.txt @@ -2,6 +2,31 @@ SET( OS_SPECIFIC_LINK_LIBRARIES ${OS_SPECIFIC_LINK_LIBRARIES} ${COREAUDIO_LIBRARY} ${COREFOUNDATION_LIBRARY} + + /System/Library/Frameworks/AppKit.framework + /System/Library/Frameworks/Carbon.framework + /System/Library/Frameworks/DiskArbitration.framework + /System/Library/Frameworks/Foundation.framework + /System/Library/Frameworks/IOKit.framework + rtaudio tomahawklib ) + +if (APPLE) +# find_library(GROWL Growl) +# option(ENABLE_SPARKLE "Sparkle updating" ON) +# find_library(SPARKLE Sparkle) +# if (ENABLE_SPARKLE AND SPARKLE) +# set(HAVE_SPARKLE ON) +# endif (ENABLE_SPARKLE AND SPARKLE) + # Uses Darwin kernel version. + # 9.8.0 -> 10.5/Leopard + # 10.4.0 -> 10.6/Snow Leopard + string(REGEX MATCH "[0-9]+" DARWIN_VERSION ${CMAKE_HOST_SYSTEM_VERSION}) + if (DARWIN_VERSION GREATER 9) + SET(SNOW_LEOPARD 1) + elseif (DARWIN_VERSION GREATER 8) + SET(LEOPARD 1) + endif (DARWIN_VERSION GREATER 9) +endif (APPLE) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d486966e0..b3d616e0a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,6 +57,12 @@ SET( tomahawkSourcesGui ${tomahawkSourcesGui} tomahawkwindow.cpp ) + +IF( APPLE ) + SET( tomahawkHeaders ${tomahawkHeaders} tomahawkapp_mac.h ) + SET( tomahawkSources ${tomahawkSources} tomahawkapp_mac.mm ) +ENDIF( APPLE ) + SET( tomahawkHeaders ${tomahawkHeaders} "${TOMAHAWK_INC_DIR}/tomahawk/tomahawkapp.h" "${TOMAHAWK_INC_DIR}/tomahawk/infosystem.h" @@ -138,6 +144,9 @@ kde4_add_app_icon( tomahawkSources "${CMAKE_CURRENT_SOURCE_DIR}/../data/icons/to qt4_add_resources( RC_SRCS "../resources.qrc" ) qt4_wrap_cpp( tomahawkMoc ${tomahawkHeaders} ) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in + ${CMAKE_CURRENT_BINARY_DIR}/config.h) + SET( final_src ${final_src} ${tomahawkMoc} ${tomahawkSources} ${tomahawkHeaders} ) IF( "${gui}" STREQUAL "no" ) @@ -152,6 +161,8 @@ IF( UNIX AND NOT APPLE ) ENDIF( UNIX AND NOT APPLE ) IF( APPLE ) ADD_EXECUTABLE( tomahawk MACOSX_BUNDLE ${final_src} ) + SET_TARGET_PROPERTIES(tomahawk PROPERTIES MACOSX_BUNDLE_INFO_PLIST "../admin/mac/Info.plist" +) ENDIF( APPLE ) IF( WIN32 ) ADD_EXECUTABLE( tomahawk ${final_src} ) diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 000000000..a06d239a5 --- /dev/null +++ b/src/config.h.in @@ -0,0 +1,10 @@ +#ifndef CONFIG_H_IN +#define CONFIG_H_IN + +#define CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" + +#cmakedefine SNOW_LEOPARD +#cmakedefine LEOPARD +#cmakedefine HAVE_SPARKLE + +#endif // CONFIG_H_IN diff --git a/src/main.cpp b/src/main.cpp index 4519a116e..d755703d2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,24 @@ #include "tomahawk/tomahawkapp.h" +#ifdef Q_WS_MAC +#include "tomahawkapp_mac.h" +#include +static pascal OSErr appleEventHandler( const AppleEvent*, AppleEvent*, long ); +#endif + #include int main( int argc, char *argv[] ) { +#ifdef Q_WS_MAC + // Do Mac specific startup to get media keys working. + // This must go before QApplication initialisation. + Tomahawk::macMain(); + + // used for url handler + AEEventHandlerUPP h = AEEventHandlerUPP( appleEventHandler ); + AEInstallEventHandler( 'GURL', 'GURL', h, 0, false ); + +#endif try { TomahawkApp a( argc, argv ); return a.exec(); @@ -10,3 +26,31 @@ int main( int argc, char *argv[] ) return 0; } } + +#ifdef Q_WS_MAC +static pascal OSErr appleEventHandler( const AppleEvent* e, AppleEvent*, long ) +{ + OSType id = typeWildCard; + AEGetAttributePtr( e, keyEventIDAttr, typeType, 0, &id, sizeof(id), 0 ); + + switch (id) + { + case 'GURL': + { + DescType type; + Size size; + + char buf[1024]; + AEGetParamPtr( e, keyDirectObject, typeChar, &type, &buf, 1023, &size ); + buf[size] = '\0'; + + QString url = QString::fromUtf8( buf ); + static_cast(qApp)->loadUrl( url ); + return noErr; + } + + default: + return unimpErr; + } +} +#endif diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 30cba6dcc..b865d7b4d 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include "artist.h" #include "album.h" @@ -31,6 +33,10 @@ #include #endif +#ifdef Q_WS_MAC + #include "tomahawkapp_mac.h" +#endif + #include #include @@ -176,6 +182,10 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) TomahawkUtils::setNam( new QNetworkAccessManager ); #endif +#ifdef Q_WS_MAC + Tomahawk::setApplicationHandler( this ); +#endif + // Set up proxy if( TomahawkSettings::instance()->proxyType() != QNetworkProxy::NoProxy && !TomahawkSettings::instance()->proxyHost().isEmpty() ) @@ -454,16 +464,20 @@ TomahawkApp::setupSIP() } } -void -TomahawkApp::messageReceived( const QString& msg ) + +void +TomahawkApp::activate() { - qDebug() << "MESSAGE RECEIVED" << msg; - if( msg.isEmpty() ) { - return; - } - - if( msg.contains( "tomahawk://" ) ) { - QString cmd = msg.mid( 11 ); +#ifndef TOMAHAWK_HEADLESS + mainWindow()->show(); +#endif +} + +bool +TomahawkApp::loadUrl( const QString& url ) +{ + if( url.contains( "tomahawk://" ) ) { + QString cmd = url.mid( 11 ); qDebug() << "tomahawk!s" << cmd; if( cmd.startsWith( "load/" ) ) { cmd = cmd.mid( 5 ); @@ -474,7 +488,26 @@ TomahawkApp::messageReceived( const QString& msg ) l->load( QUrl( cmd.mid( 5 ) ) ); } } - - } + } else { + QFile f( url ); + QFileInfo info( f ); + if( f.exists() && info.suffix() == "xspf" ) { + XSPFLoader* l = new XSPFLoader( true, this ); + qDebug() << "Loading spiff:" << url; + l->load( QUrl( url ) ); + } + } + return true; +} + +void +TomahawkApp::messageReceived( const QString& msg ) +{ + qDebug() << "MESSAGE RECEIVED" << msg; + if( msg.isEmpty() ) { + return; + } + + loadUrl( msg ); } diff --git a/src/tomahawkapp_mac.h b/src/tomahawkapp_mac.h new file mode 100644 index 000000000..ce4199677 --- /dev/null +++ b/src/tomahawkapp_mac.h @@ -0,0 +1,28 @@ +#ifndef TOMAHAWKAPP_MAC_H +#define TOMAHAWKAPP_MAC_H + +// this file and tomahawk_app.mm copied and inspired by mac_startup.* in clementine player, +// copyright David Sansome 2010 + +class QString; + +namespace Tomahawk { + +/// Interface between cocoa and tomahawk +class PlatformInterface { + public: + // Called when the application should show itself. + virtual void activate() = 0; + virtual bool loadUrl( const QString& url ) = 0; + + virtual ~PlatformInterface() {} +}; + +void macMain(); +//void setShortcutHandler(GlobalShortcuts* handler); +// used for opening files with tomahawk +void setApplicationHandler(PlatformInterface* handler); + +}; + +#endif diff --git a/src/tomahawkapp_mac.mm b/src/tomahawkapp_mac.mm new file mode 100644 index 000000000..dfc08c34b --- /dev/null +++ b/src/tomahawkapp_mac.mm @@ -0,0 +1,229 @@ +#include "tomahawkapp_mac.h" +#include "tomahawkapp_macdelegate.h" +#include + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +// Capture global media keys on Mac (Cocoa only!) +// See: http://www.rogueamoeba.com/utm/2007/09/29/apple-keyboard-media-key-event-handling/ + +@interface MacApplication :NSApplication { + // MacGlobalShortcutBackend* shortcut_handler_; + Tomahawk::PlatformInterface* application_handler_; +} + +//- (MacGlobalShortcutBackend*) shortcut_handler; +//- (void) SetShortcutHandler: (MacGlobalShortcutBackend*)handler; + +- (Tomahawk::PlatformInterface*) application_handler; +- (void) SetApplicationHandler: (Tomahawk::PlatformInterface*)handler; +- (void)getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent; +//- (void) mediaKeyEvent: (int)key state: (BOOL)state repeat: (BOOL)repeat; +@end + + +@implementation AppDelegate + +- (id) init { + if ((self = [super init])) { + application_handler_ = nil; +// dock_menu_ = nil; + } + return self; +} + +- (id) initWithHandler: (Tomahawk::PlatformInterface*)handler { + application_handler_ = handler; + return self; +} + +- (BOOL) applicationShouldHandleReopen: (NSApplication*)app hasVisibleWindows:(BOOL)flag { + if (application_handler_) { + application_handler_->activate(); + } + return YES; +} +/* +- (void) setDockMenu: (NSMenu*)menu { + dock_menu_ = menu; +} + +- (NSMenu*) applicationDockMenu: (NSApplication*)sender { + return dock_menu_; +} +*/ +- (BOOL) application: (NSApplication*)app openFile:(NSString*)filename { + qDebug() << "Wants to open:" << [filename UTF8String]; + + if (application_handler_->loadUrl(QString::fromUtf8([filename UTF8String]))) { + return YES; + } + + return NO; +} +@end + +@implementation MacApplication + +- (id) init { + if ((self = [super init])) { +// [self SetShortcutHandler:nil]; + [self SetApplicationHandler:nil]; + + NSAppleEventManager *em = [NSAppleEventManager sharedAppleEventManager]; + [em + setEventHandler:self + andSelector:@selector(getUrl:withReplyEvent:) + forEventClass:kInternetEventClass + andEventID:kAEGetURL]; + [em + setEventHandler:self + andSelector:@selector(getUrl:withReplyEvent:) + forEventClass:'WWW!' + andEventID:'OURL']; + NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier]; + OSStatus httpResult = LSSetDefaultHandlerForURLScheme((CFStringRef)@"tomahawk", (CFStringRef)bundleID); + //TODO: Check httpResult and httpsResult for errors + } + return self; +} +/* +- (MacGlobalShortcutBackend*) shortcut_handler { + return shortcut_handler_; +} + +- (void) SetShortcutHandler: (MacGlobalShortcutBackend*)handler { + shortcut_handler_ = handler; +} +*/ +- (Tomahawk::PlatformInterface*) application_handler { + return application_handler_; +} + +- (void) SetApplicationHandler: (Tomahawk::PlatformInterface*)handler { + AppDelegate* delegate = [[AppDelegate alloc] initWithHandler:handler]; + [self setDelegate:delegate]; +} + +-(void) sendEvent: (NSEvent*)event { + if ([event type] == NSSystemDefined && [event subtype] == 8) { + int keycode = (([event data1] & 0xFFFF0000) >> 16); + int keyflags = ([event data1] & 0x0000FFFF); + int keystate = (((keyflags & 0xFF00) >> 8)) == 0xA; + int keyrepeat = (keyflags & 0x1); + + //[self mediaKeyEvent: keycode state: keystate repeat: keyrepeat]; + } + + [super sendEvent: event]; +} +/* +-(void) mediaKeyEvent: (int)key state: (BOOL)state repeat: (BOOL)repeat { + if (!shortcut_handler_) { + return; + } + if (state == 0) { + shortcut_handler_->MacMediaKeyPressed(key); + } +} */ + +- (void)getUrl:(NSAppleEventDescriptor *)event + withReplyEvent:(NSAppleEventDescriptor *)replyEvent +{ + // Get the URL + NSString *urlStr = [[event paramDescriptorForKeyword:keyDirectObject] + stringValue]; + qDebug() << "Wants to open:" << [urlStr UTF8String]; + + //TODO: Your custom URL handling code here +} + +@end + +void Tomahawk::macMain() { + [[NSAutoreleasePool alloc] init]; + // Creates and sets the magic global variable so QApplication will find it. + [MacApplication sharedApplication]; + #ifdef HAVE_SPARKLE + // Creates and sets the magic global variable for Sparkle. + [[SUUpdater sharedUpdater] setDelegate: NSApp]; + #endif +} + +/* +void setShortcutHandler(MacGlobalShortcutBackend* handler) { + [NSApp SetShortcutHandler: handler]; +} +*/ +void Tomahawk::setApplicationHandler(Tomahawk::PlatformInterface* handler) { + [NSApp SetApplicationHandler: handler]; +} + +void CheckForUpdates() { + #ifdef HAVE_SPARKLE + [[SUUpdater sharedUpdater] checkForUpdates: NSApp]; + #endif +} + +QString GetBundlePath() { + CFURLRef app_url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); + CFStringRef mac_path = CFURLCopyFileSystemPath(app_url, kCFURLPOSIXPathStyle); + const char* path = CFStringGetCStringPtr(mac_path, CFStringGetSystemEncoding()); + QString bundle_path = QString::fromUtf8(path); + CFRelease(app_url); + CFRelease(mac_path); + return bundle_path; +} + +QString GetResourcesPath() { + QString bundle_path = GetBundlePath(); + return bundle_path + "/Contents/Resources"; +} + +QString GetApplicationSupportPath() { + NSAutoreleasePool* pool = [NSAutoreleasePool alloc]; + [pool init]; + NSArray* paths = NSSearchPathForDirectoriesInDomains( + NSApplicationSupportDirectory, + NSUserDomainMask, + YES); + QString ret; + if ([paths count] > 0) { + NSString* user_path = [paths objectAtIndex:0]; + ret = QString::fromUtf8([user_path UTF8String]); + } else { + ret = "~/Library/Application Support"; + } + [pool drain]; + return ret; +} + +QString GetMusicDirectory() { + NSAutoreleasePool* pool = [NSAutoreleasePool alloc]; + [pool init]; + NSArray* paths = NSSearchPathForDirectoriesInDomains( + NSMusicDirectory, + NSUserDomainMask, + YES); + QString ret; + if ([paths count] > 0) { + NSString* user_path = [paths objectAtIndex:0]; + ret = QString::fromUtf8([user_path UTF8String]); + } else { + ret = "~/Music"; + } + [pool drain]; + return ret; +} + diff --git a/src/tomahawkapp_macdelegate.h b/src/tomahawkapp_macdelegate.h new file mode 100644 index 000000000..a5884e225 --- /dev/null +++ b/src/tomahawkapp_macdelegate.h @@ -0,0 +1,25 @@ +#import + +#include "config.h" + +// this file copied and inspired by mac_startup.* in clementine player, +// copyright David Sansome 2010 +namespace Tomahawk { + class PlatformInterface; +} + +#ifdef SNOW_LEOPARD +@interface AppDelegate : NSObject { +#else +@interface AppDelegate : NSObject { +#endif + Tomahawk::PlatformInterface* application_handler_; + //NSMenu* dock_menu_; +} + +- (id) initWithHandler: (Tomahawk::PlatformInterface*)handler; +// NSApplicationDelegate +- (BOOL) applicationShouldHandleReopen: (NSApplication*)app hasVisibleWindows:(BOOL)flag; +//- (NSMenu*) applicationDockMenu: (NSApplication*)sender; +//- (void) setDockMenu: (NSMenu*)menu; +@end