|
|
|
@ -1,9 +1,10 @@ |
|
|
|
/***************************************************************************** |
|
|
|
* osx_notifications.m : OS X notification plugin |
|
|
|
***************************************************************************** |
|
|
|
* VLC specific code: |
|
|
|
* osx_notifications.m : macOS notification plugin |
|
|
|
* |
|
|
|
* Copyright © 2008,2011,2012,2015 the VideoLAN team |
|
|
|
* This plugin provides support for macOS notifications on current playlist |
|
|
|
* item changes. |
|
|
|
***************************************************************************** |
|
|
|
* Copyright © 2008, 2011, 2012, 2015, 2018 the VideoLAN team |
|
|
|
* $Id$ |
|
|
|
* |
|
|
|
* Authors: Rafaël Carré <funman@videolanorg> |
|
|
|
@ -23,42 +24,9 @@ |
|
|
|
* You should have received a copy of the GNU General Public License |
|
|
|
* along with this program; if not, write to the Free Software |
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. |
|
|
|
* |
|
|
|
* --- |
|
|
|
* |
|
|
|
* Growl specific code, ripped from growlnotify: |
|
|
|
* |
|
|
|
* Copyright (c) The Growl Project, 2004-2005 |
|
|
|
* All rights reserved. |
|
|
|
* |
|
|
|
* Redistribution and use in source and binary forms, with or without modification, |
|
|
|
* are permitted provided that the following conditions are met: |
|
|
|
* |
|
|
|
* 1. Redistributions of source code must retain the above copyright |
|
|
|
* notice, this list of conditions and the following disclaimer. |
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright |
|
|
|
* notice, this list of conditions and the following disclaimer in the |
|
|
|
* documentation and/or other materials provided with the distribution. |
|
|
|
* 3. Neither the name of Growl nor the names of its contributors |
|
|
|
* may be used to endorse or promote products derived from this software |
|
|
|
* without specific prior written permission. |
|
|
|
* |
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS |
|
|
|
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|
|
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE |
|
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
|
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
|
|
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
|
|
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
|
|
* |
|
|
|
*****************************************************************************/ |
|
|
|
*/ |
|
|
|
|
|
|
|
/***************************************************************************** |
|
|
|
* Preamble |
|
|
|
*****************************************************************************/ |
|
|
|
#define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS |
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H |
|
|
|
# include "config.h" |
|
|
|
@ -66,9 +34,7 @@ |
|
|
|
|
|
|
|
#import <Foundation/Foundation.h> |
|
|
|
#import <Cocoa/Cocoa.h> |
|
|
|
#import <Growl/Growl.h> |
|
|
|
|
|
|
|
#define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS |
|
|
|
#include <vlc_common.h> |
|
|
|
#include <vlc_plugin.h> |
|
|
|
#include <vlc_playlist.h> |
|
|
|
@ -77,170 +43,158 @@ |
|
|
|
#include <vlc_interface.h> |
|
|
|
#include <vlc_url.h> |
|
|
|
|
|
|
|
/***************************************************************************** |
|
|
|
* intf_sys_t, VLCGrowlDelegate |
|
|
|
*****************************************************************************/ |
|
|
|
@interface VLCGrowlDelegate : NSObject <GrowlApplicationBridgeDelegate> |
|
|
|
#pragma mark - |
|
|
|
#pragma mark Class interfaces |
|
|
|
@interface VLCNotificationDelegate : NSObject <NSUserNotificationCenterDelegate> |
|
|
|
{ |
|
|
|
NSString *applicationName; |
|
|
|
NSString *notificationType; |
|
|
|
NSMutableDictionary *registrationDictionary; |
|
|
|
id lastNotification; |
|
|
|
bool isInForeground; |
|
|
|
intf_thread_t *interfaceThread; |
|
|
|
/** Interface thread, required for skipping to the next item */ |
|
|
|
intf_thread_t * _Nonnull interfaceThread; |
|
|
|
|
|
|
|
/** Holds the last notification so it can be cleared when the next one is delivered */ |
|
|
|
NSUserNotification * _Nullable lastNotification; |
|
|
|
|
|
|
|
/** Indicates if VLC is in foreground */ |
|
|
|
BOOL isInForeground; |
|
|
|
} |
|
|
|
|
|
|
|
- (id)initWithInterfaceThread:(intf_thread_t *)thread; |
|
|
|
- (void)registerToGrowl; |
|
|
|
- (void)notifyWithTitle:(const char *)title |
|
|
|
artist:(const char *)artist |
|
|
|
album:(const char *)album |
|
|
|
andArtUrl:(const char *)url; |
|
|
|
/** |
|
|
|
* Initializes a new VLCNotification Delegate with a given intf_thread_t |
|
|
|
*/ |
|
|
|
- (instancetype)initWithInterfaceThread:(intf_thread_t * _Nonnull)intf_thread; |
|
|
|
|
|
|
|
/** |
|
|
|
* Delegate method called when the current input changed |
|
|
|
*/ |
|
|
|
- (void)currentInputDidChanged:(input_thread_t * _Nonnull)input; |
|
|
|
|
|
|
|
@end |
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - |
|
|
|
#pragma mark Local prototypes |
|
|
|
struct intf_sys_t |
|
|
|
{ |
|
|
|
VLCGrowlDelegate *o_growl_delegate; |
|
|
|
void *vlcNotificationDelegate; |
|
|
|
}; |
|
|
|
|
|
|
|
/***************************************************************************** |
|
|
|
* Local prototypes |
|
|
|
*****************************************************************************/ |
|
|
|
static int Open ( vlc_object_t * ); |
|
|
|
static void Close ( vlc_object_t * ); |
|
|
|
|
|
|
|
static int InputCurrent( vlc_object_t *, const char *, |
|
|
|
vlc_value_t, vlc_value_t, void * ); |
|
|
|
static int InputCurrent(vlc_object_t *, const char *, |
|
|
|
vlc_value_t, vlc_value_t, void *); |
|
|
|
|
|
|
|
/***************************************************************************** |
|
|
|
* Module descriptor |
|
|
|
****************************************************************************/ |
|
|
|
vlc_module_begin () |
|
|
|
set_category( CAT_INTERFACE ) |
|
|
|
set_subcategory( SUBCAT_INTERFACE_CONTROL ) |
|
|
|
set_shortname( "OSX-Notifications" ) |
|
|
|
add_shortcut( "growl" ) |
|
|
|
set_description( N_("OS X Notification Plugin") ) |
|
|
|
set_capability( "interface", 0 ) |
|
|
|
set_callbacks( Open, Close ) |
|
|
|
vlc_module_end () |
|
|
|
|
|
|
|
/***************************************************************************** |
|
|
|
* Open: initialize and create stuff |
|
|
|
*****************************************************************************/ |
|
|
|
static int Open( vlc_object_t *p_this ) |
|
|
|
#pragma mark - |
|
|
|
#pragma mark C module functions |
|
|
|
/* |
|
|
|
* Open: Initialization of the module |
|
|
|
*/ |
|
|
|
static int Open(vlc_object_t *p_this) |
|
|
|
{ |
|
|
|
intf_thread_t *p_intf = (intf_thread_t *)p_this; |
|
|
|
playlist_t *p_playlist = pl_Get( p_intf ); |
|
|
|
intf_sys_t *p_sys = p_intf->p_sys = calloc( 1, sizeof(intf_sys_t) ); |
|
|
|
playlist_t *p_playlist = pl_Get(p_intf); |
|
|
|
intf_sys_t *p_sys = p_intf->p_sys = calloc(1, sizeof(intf_sys_t)); |
|
|
|
|
|
|
|
if( !p_sys ) |
|
|
|
if (!p_sys) |
|
|
|
return VLC_ENOMEM; |
|
|
|
|
|
|
|
p_sys->o_growl_delegate = [[VLCGrowlDelegate alloc] initWithInterfaceThread:p_intf]; |
|
|
|
if( !p_sys->o_growl_delegate ) |
|
|
|
{ |
|
|
|
free( p_sys ); |
|
|
|
return VLC_ENOMEM; |
|
|
|
@autoreleasepool { |
|
|
|
VLCNotificationDelegate *notificationDelegate = |
|
|
|
[[VLCNotificationDelegate alloc] initWithInterfaceThread:p_intf]; |
|
|
|
|
|
|
|
if (notificationDelegate == nil) { |
|
|
|
free(p_sys); |
|
|
|
return VLC_ENOMEM; |
|
|
|
} |
|
|
|
|
|
|
|
p_sys->vlcNotificationDelegate = (__bridge_retained void*)notificationDelegate; |
|
|
|
} |
|
|
|
|
|
|
|
var_AddCallback( p_playlist, "input-current", InputCurrent, p_intf ); |
|
|
|
var_AddCallback(p_playlist, "input-current", InputCurrent, p_intf); |
|
|
|
|
|
|
|
[p_sys->o_growl_delegate registerToGrowl]; |
|
|
|
return VLC_SUCCESS; |
|
|
|
} |
|
|
|
|
|
|
|
/***************************************************************************** |
|
|
|
* Close: destroy interface stuff |
|
|
|
*****************************************************************************/ |
|
|
|
static void Close( vlc_object_t *p_this ) |
|
|
|
/* |
|
|
|
* Close: Destruction of the module |
|
|
|
*/ |
|
|
|
static void Close(vlc_object_t *p_this) |
|
|
|
{ |
|
|
|
intf_thread_t *p_intf = (intf_thread_t *)p_this; |
|
|
|
playlist_t *p_playlist = pl_Get( p_intf ); |
|
|
|
playlist_t *p_playlist = pl_Get(p_intf); |
|
|
|
intf_sys_t *p_sys = p_intf->p_sys; |
|
|
|
|
|
|
|
// Remove the callback, this must be done here, before deallocating the |
|
|
|
// notification delegate object |
|
|
|
var_DelCallback(p_playlist, "input-current", InputCurrent, p_intf); |
|
|
|
|
|
|
|
var_DelCallback( p_playlist, "input-current", InputCurrent, p_intf ); |
|
|
|
@autoreleasepool { |
|
|
|
// Transfer ownership of notification delegate object back to ARC |
|
|
|
VLCNotificationDelegate *notificationDelegate = |
|
|
|
(__bridge_transfer VLCNotificationDelegate*)p_sys->vlcNotificationDelegate; |
|
|
|
|
|
|
|
// Ensure the object is deallocated |
|
|
|
notificationDelegate = nil; |
|
|
|
} |
|
|
|
|
|
|
|
[GrowlApplicationBridge setGrowlDelegate:nil]; |
|
|
|
[p_sys->o_growl_delegate release]; |
|
|
|
free( p_sys ); |
|
|
|
free(p_sys); |
|
|
|
} |
|
|
|
|
|
|
|
/***************************************************************************** |
|
|
|
* InputCurrent: Current playlist item changed callback |
|
|
|
*****************************************************************************/ |
|
|
|
static int InputCurrent( vlc_object_t *p_this, const char *psz_var, |
|
|
|
vlc_value_t oldval, vlc_value_t newval, void *param ) |
|
|
|
/* |
|
|
|
* Callback invoked on playlist item change |
|
|
|
*/ |
|
|
|
static int InputCurrent(vlc_object_t *p_this, const char *psz_var, |
|
|
|
vlc_value_t oldval, vlc_value_t newval, void *param) |
|
|
|
{ |
|
|
|
VLC_UNUSED(oldval); |
|
|
|
|
|
|
|
intf_thread_t *p_intf = (intf_thread_t *)param; |
|
|
|
intf_sys_t *p_sys = p_intf->p_sys; |
|
|
|
input_thread_t *p_input = newval.p_address; |
|
|
|
char *psz_title = NULL; |
|
|
|
char *psz_artist = NULL; |
|
|
|
char *psz_album = NULL; |
|
|
|
char *psz_arturl = NULL; |
|
|
|
|
|
|
|
if( !p_input ) |
|
|
|
return VLC_SUCCESS; |
|
|
|
|
|
|
|
input_item_t *p_item = input_GetItem( p_input ); |
|
|
|
if( !p_item ) |
|
|
|
return VLC_SUCCESS; |
|
|
|
|
|
|
|
/* Get title */ |
|
|
|
psz_title = input_item_GetNowPlayingFb( p_item ); |
|
|
|
if( !psz_title ) |
|
|
|
psz_title = input_item_GetTitleFbName( p_item ); |
|
|
|
|
|
|
|
if( EMPTY_STR( psz_title ) ) |
|
|
|
{ |
|
|
|
free( psz_title ); |
|
|
|
return VLC_SUCCESS; |
|
|
|
} |
|
|
|
VLC_UNUSED(oldval); |
|
|
|
|
|
|
|
/* Get Artist name */ |
|
|
|
psz_artist = input_item_GetArtist( p_item ); |
|
|
|
if( EMPTY_STR( psz_artist ) ) |
|
|
|
FREENULL( psz_artist ); |
|
|
|
|
|
|
|
/* Get Album name */ |
|
|
|
psz_album = input_item_GetAlbum( p_item ) ; |
|
|
|
if( EMPTY_STR( psz_album ) ) |
|
|
|
FREENULL( psz_album ); |
|
|
|
|
|
|
|
/* Get Art path */ |
|
|
|
psz_arturl = input_item_GetArtURL( p_item ); |
|
|
|
if( psz_arturl ) |
|
|
|
{ |
|
|
|
char *psz = vlc_uri2path( psz_arturl ); |
|
|
|
free( psz_arturl ); |
|
|
|
psz_arturl = psz; |
|
|
|
@autoreleasepool { |
|
|
|
VLCNotificationDelegate *notificationDelegate = |
|
|
|
(__bridge VLCNotificationDelegate*)p_sys->vlcNotificationDelegate; |
|
|
|
|
|
|
|
[notificationDelegate currentInputDidChanged:(input_thread_t *)p_input]; |
|
|
|
} |
|
|
|
|
|
|
|
[p_sys->o_growl_delegate notifyWithTitle:psz_title |
|
|
|
artist:psz_artist |
|
|
|
album:psz_album |
|
|
|
andArtUrl:psz_arturl]; |
|
|
|
|
|
|
|
free( psz_title ); |
|
|
|
free( psz_artist ); |
|
|
|
free( psz_album ); |
|
|
|
free( psz_arturl ); |
|
|
|
return VLC_SUCCESS; |
|
|
|
} |
|
|
|
|
|
|
|
/***************************************************************************** |
|
|
|
* VLCGrowlDelegate |
|
|
|
*****************************************************************************/ |
|
|
|
@implementation VLCGrowlDelegate |
|
|
|
|
|
|
|
- (id)initWithInterfaceThread:(intf_thread_t *)thread { |
|
|
|
if( !( self = [super init] ) ) |
|
|
|
/** |
|
|
|
* Transfers a null-terminated UTF-8 C "string" to a NSString |
|
|
|
* in a way that the NSString takes ownership of it. |
|
|
|
* |
|
|
|
* \warning After calling this function, passed cStr must not be used anymore! |
|
|
|
* |
|
|
|
* \param cStr Pointer to a zero-terminated UTF-8 encoded char array |
|
|
|
* |
|
|
|
* \return An NSString instance that uses cStr as internal data storage and |
|
|
|
* frees it when done. On error, nil is returned and cStr is freed. |
|
|
|
*/ |
|
|
|
static inline NSString* CharsToNSString(char * _Nullable cStr) |
|
|
|
{ |
|
|
|
if (!cStr) |
|
|
|
return nil; |
|
|
|
|
|
|
|
@autoreleasepool { |
|
|
|
NSString *resString = [[NSString alloc] initWithBytesNoCopy:cStr |
|
|
|
length:strlen(cStr) |
|
|
|
encoding:NSUTF8StringEncoding |
|
|
|
freeWhenDone:YES]; |
|
|
|
if (unlikely(resString == nil)) |
|
|
|
free(cStr); |
|
|
|
|
|
|
|
return resString; |
|
|
|
} |
|
|
|
|
|
|
|
#pragma mark - |
|
|
|
#pragma mark Class implementation |
|
|
|
@implementation VLCNotificationDelegate |
|
|
|
|
|
|
|
- (id)initWithInterfaceThread:(intf_thread_t *)intf_thread |
|
|
|
{ |
|
|
|
self = [super init]; |
|
|
|
|
|
|
|
if (self) { |
|
|
|
interfaceThread = intf_thread; |
|
|
|
|
|
|
|
// Subscribe to notifications to determine if VLC is in foreground or not |
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
|
|
selector:@selector(applicationActiveChange:) |
|
|
|
@ -251,159 +205,64 @@ static int InputCurrent( vlc_object_t *p_this, const char *psz_var, |
|
|
|
selector:@selector(applicationActiveChange:) |
|
|
|
name:NSApplicationDidResignActiveNotification |
|
|
|
object:nil]; |
|
|
|
} |
|
|
|
// Start in background |
|
|
|
isInForeground = NO; |
|
|
|
|
|
|
|
lastNotification = nil; |
|
|
|
applicationName = nil; |
|
|
|
notificationType = nil; |
|
|
|
registrationDictionary = nil; |
|
|
|
interfaceThread = thread; |
|
|
|
|
|
|
|
return self; |
|
|
|
} |
|
|
|
|
|
|
|
- (void)dealloc |
|
|
|
{ |
|
|
|
// Clear the remaining lastNotification in Notification Center, if any |
|
|
|
@autoreleasepool { |
|
|
|
if (lastNotification) { |
|
|
|
[NSUserNotificationCenter.defaultUserNotificationCenter |
|
|
|
removeDeliveredNotification:(NSUserNotification *)lastNotification]; |
|
|
|
[lastNotification release]; |
|
|
|
} |
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self]; |
|
|
|
[[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self]; |
|
|
|
} |
|
|
|
|
|
|
|
// Release everything |
|
|
|
[applicationName release]; |
|
|
|
[notificationType release]; |
|
|
|
[registrationDictionary release]; |
|
|
|
[super dealloc]; |
|
|
|
|
|
|
|
return self; |
|
|
|
} |
|
|
|
|
|
|
|
- (void)registerToGrowl |
|
|
|
- (void)currentInputDidChanged:(input_thread_t *)input |
|
|
|
{ |
|
|
|
@autoreleasepool { |
|
|
|
applicationName = [[NSString alloc] initWithUTF8String:_( "VLC media player" )]; |
|
|
|
notificationType = [[NSString alloc] initWithUTF8String:_( "New input playing" )]; |
|
|
|
|
|
|
|
NSArray *defaultAndAllNotifications = [NSArray arrayWithObject: notificationType]; |
|
|
|
registrationDictionary = [[NSMutableDictionary alloc] init]; |
|
|
|
[registrationDictionary setObject:defaultAndAllNotifications |
|
|
|
forKey:GROWL_NOTIFICATIONS_ALL]; |
|
|
|
[registrationDictionary setObject:defaultAndAllNotifications |
|
|
|
forKey: GROWL_NOTIFICATIONS_DEFAULT]; |
|
|
|
|
|
|
|
[GrowlApplicationBridge setGrowlDelegate:self]; |
|
|
|
|
|
|
|
[[NSUserNotificationCenter defaultUserNotificationCenter] |
|
|
|
setDelegate:(id<NSUserNotificationCenterDelegate>)self]; |
|
|
|
if (!input) |
|
|
|
return; |
|
|
|
|
|
|
|
input_item_t *item = input_GetItem(input); |
|
|
|
if (!item) |
|
|
|
return; |
|
|
|
|
|
|
|
// Get title, first try now playing |
|
|
|
NSString *title = CharsToNSString(input_item_GetNowPlayingFb(item)); |
|
|
|
|
|
|
|
// Fallback to item title or name |
|
|
|
if ([title length] == 0) |
|
|
|
title = CharsToNSString(input_item_GetTitleFbName(item)); |
|
|
|
|
|
|
|
// If there is still not title, do not notify |
|
|
|
if (unlikely([title length] == 0)) |
|
|
|
return; |
|
|
|
|
|
|
|
// Get artist name |
|
|
|
NSString *artist = CharsToNSString(input_item_GetArtist(item)); |
|
|
|
|
|
|
|
// Get album name |
|
|
|
NSString *album = CharsToNSString(input_item_GetAlbum(item)); |
|
|
|
|
|
|
|
// Get coverart path |
|
|
|
NSString *artPath = nil; |
|
|
|
|
|
|
|
char *psz_arturl = input_item_GetArtURL(item); |
|
|
|
if (psz_arturl) { |
|
|
|
artPath = CharsToNSString(vlc_uri2path(psz_arturl)); |
|
|
|
free(psz_arturl); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
- (void)notifyWithTitle:(const char *)title |
|
|
|
artist:(const char *)artist |
|
|
|
album:(const char *)album |
|
|
|
andArtUrl:(const char *)url |
|
|
|
{ |
|
|
|
@autoreleasepool { |
|
|
|
// Do not notify if in foreground |
|
|
|
if (isInForeground) |
|
|
|
return; |
|
|
|
|
|
|
|
// Init Cover |
|
|
|
NSData *coverImageData = nil; |
|
|
|
NSImage *coverImage = nil; |
|
|
|
|
|
|
|
if (url) { |
|
|
|
coverImageData = [NSData dataWithContentsOfFile:[NSString stringWithUTF8String:url]]; |
|
|
|
coverImage = [[NSImage alloc] initWithData:coverImageData]; |
|
|
|
} |
|
|
|
|
|
|
|
// Init Track info |
|
|
|
NSString *titleStr = nil; |
|
|
|
NSString *artistStr = nil; |
|
|
|
NSString *albumStr = nil; |
|
|
|
|
|
|
|
if (title) { |
|
|
|
titleStr = [NSString stringWithUTF8String:title]; |
|
|
|
} else { |
|
|
|
// Without title, notification makes no sense, so return here |
|
|
|
// title should never be empty, but better check than crash. |
|
|
|
[coverImage release]; |
|
|
|
return; |
|
|
|
} |
|
|
|
if (artist) |
|
|
|
artistStr = [NSString stringWithUTF8String:artist]; |
|
|
|
if (album) |
|
|
|
albumStr = [NSString stringWithUTF8String:album]; |
|
|
|
|
|
|
|
// Notification stuff |
|
|
|
if ([GrowlApplicationBridge isGrowlRunning]) { |
|
|
|
// Make the Growl notification string |
|
|
|
NSString *desc = nil; |
|
|
|
|
|
|
|
if (artistStr && albumStr) { |
|
|
|
desc = [NSString stringWithFormat:@"%@\n%@ [%@]", titleStr, artistStr, albumStr]; |
|
|
|
} else if (artistStr) { |
|
|
|
desc = [NSString stringWithFormat:@"%@\n%@", titleStr, artistStr]; |
|
|
|
} else { |
|
|
|
desc = titleStr; |
|
|
|
} |
|
|
|
|
|
|
|
// Send notification |
|
|
|
[GrowlApplicationBridge notifyWithTitle:[NSString stringWithUTF8String:_("Now playing")] |
|
|
|
description:desc |
|
|
|
notificationName:notificationType |
|
|
|
iconData:coverImageData |
|
|
|
priority:0 |
|
|
|
isSticky:NO |
|
|
|
clickContext:nil |
|
|
|
identifier:@"VLCNowPlayingNotification"]; |
|
|
|
} else { |
|
|
|
// Make the OS X notification and string |
|
|
|
NSUserNotification *notification = [NSUserNotification new]; |
|
|
|
NSString *desc = nil; |
|
|
|
|
|
|
|
if (artistStr && albumStr) { |
|
|
|
desc = [NSString stringWithFormat:@"%@ – %@", artistStr, albumStr]; |
|
|
|
} else if (artistStr) { |
|
|
|
desc = artistStr; |
|
|
|
} |
|
|
|
|
|
|
|
notification.title = titleStr; |
|
|
|
notification.subtitle = desc; |
|
|
|
notification.hasActionButton = YES; |
|
|
|
notification.actionButtonTitle = [NSString stringWithUTF8String:_("Skip")]; |
|
|
|
|
|
|
|
// Private APIs to set cover image, see rdar://23148801 |
|
|
|
// and show action button, see rdar://23148733 |
|
|
|
[notification setValue:coverImage forKey:@"_identityImage"]; |
|
|
|
[notification setValue:@(YES) forKey:@"_showsButtons"]; |
|
|
|
[NSUserNotificationCenter.defaultUserNotificationCenter deliverNotification:notification]; |
|
|
|
[notification release]; |
|
|
|
} |
|
|
|
// Construct final description string |
|
|
|
NSString *desc = nil; |
|
|
|
|
|
|
|
// Release stuff |
|
|
|
[coverImage release]; |
|
|
|
if (artist && album) { |
|
|
|
desc = [NSString stringWithFormat:@"%@ – %@", artist, album]; |
|
|
|
} else if (artist) { |
|
|
|
desc = artist; |
|
|
|
} |
|
|
|
|
|
|
|
// Notify! |
|
|
|
[self notifyWithTitle:title description:desc imagePath:artPath]; |
|
|
|
} |
|
|
|
|
|
|
|
/***************************************************************************** |
|
|
|
* Delegate methods |
|
|
|
*****************************************************************************/ |
|
|
|
- (NSDictionary *)registrationDictionaryForGrowl |
|
|
|
{ |
|
|
|
return registrationDictionary; |
|
|
|
} |
|
|
|
|
|
|
|
- (NSString *)applicationNameForGrowl |
|
|
|
{ |
|
|
|
return applicationName; |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
* Called when the applications activity status changes |
|
|
|
*/ |
|
|
|
- (void)applicationActiveChange:(NSNotification *)n { |
|
|
|
if (n.name == NSApplicationDidBecomeActiveNotification) |
|
|
|
isInForeground = YES; |
|
|
|
@ -411,24 +270,99 @@ static int InputCurrent( vlc_object_t *p_this, const char *psz_var, |
|
|
|
isInForeground = NO; |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
* Called when the user interacts with a notification |
|
|
|
*/ |
|
|
|
- (void)userNotificationCenter:(NSUserNotificationCenter *)center |
|
|
|
didActivateNotification:(NSUserNotification *)notification |
|
|
|
{ |
|
|
|
// Skip to next song |
|
|
|
// Check if notification button ("Skip") was clicked |
|
|
|
if (notification.activationType == NSUserNotificationActivationTypeActionButtonClicked) { |
|
|
|
// Skip to next song |
|
|
|
playlist_Next(pl_Get(interfaceThread)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
* Called when a new notification was delivered |
|
|
|
*/ |
|
|
|
- (void)userNotificationCenter:(NSUserNotificationCenter *)center |
|
|
|
didDeliverNotification:(NSUserNotification *)notification |
|
|
|
{ |
|
|
|
// Only keep the most recent notification in the Notification Center |
|
|
|
if (lastNotification) |
|
|
|
[center removeDeliveredNotification:lastNotification]; |
|
|
|
|
|
|
|
lastNotification = notification; |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
* Send a notification to the default user notification center |
|
|
|
*/ |
|
|
|
- (void)notifyWithTitle:(NSString * _Nonnull)titleText |
|
|
|
description:(NSString * _Nullable)descriptionText |
|
|
|
imagePath:(NSString * _Nullable)imagePath |
|
|
|
{ |
|
|
|
NSImage *image = nil; |
|
|
|
|
|
|
|
// Load image if any |
|
|
|
if (imagePath) { |
|
|
|
image = [[NSImage alloc] initWithContentsOfFile:imagePath]; |
|
|
|
} |
|
|
|
|
|
|
|
// Create notification |
|
|
|
NSUserNotification *notification = [NSUserNotification new]; |
|
|
|
|
|
|
|
notification.title = titleText; |
|
|
|
notification.subtitle = descriptionText; |
|
|
|
notification.hasActionButton = YES; |
|
|
|
notification.actionButtonTitle = [NSString stringWithUTF8String:_("Skip")]; |
|
|
|
|
|
|
|
// Try to set private properties |
|
|
|
@try { |
|
|
|
// Private API to set cover image, see rdar://23148801 |
|
|
|
[notification setValue:image forKey:@"_identityImage"]; |
|
|
|
// Private API to show action button, see rdar://23148733 |
|
|
|
[notification setValue:@(YES) forKey:@"_showsButtons"]; |
|
|
|
} @catch (NSException *exception) { |
|
|
|
if (exception.name == NSUndefinedKeyException) |
|
|
|
NSLog(@"VLC macOS notifcations plugin failed to set private notification values."); |
|
|
|
else |
|
|
|
@throw exception; |
|
|
|
} |
|
|
|
|
|
|
|
// Send notification |
|
|
|
[[NSUserNotificationCenter defaultUserNotificationCenter] |
|
|
|
deliverNotification:notification]; |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
* Cleanup |
|
|
|
*/ |
|
|
|
- (void)dealloc |
|
|
|
{ |
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self]; |
|
|
|
|
|
|
|
// Clear a remaining lastNotification in Notification Center, if any |
|
|
|
if (lastNotification) { |
|
|
|
[center removeDeliveredNotification: (NSUserNotification *)lastNotification]; |
|
|
|
[lastNotification release]; |
|
|
|
[[NSUserNotificationCenter defaultUserNotificationCenter] |
|
|
|
removeDeliveredNotification:lastNotification]; |
|
|
|
lastNotification = nil; |
|
|
|
} |
|
|
|
[notification retain]; |
|
|
|
lastNotification = notification; |
|
|
|
} |
|
|
|
|
|
|
|
@end |
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - |
|
|
|
#pragma mark VLC Module descriptor |
|
|
|
|
|
|
|
vlc_module_begin() |
|
|
|
set_shortname("OSX-Notifications") |
|
|
|
set_description(N_("macOS notifications plugin")) |
|
|
|
add_shortcut("growl") // Kept for backwards compatibility |
|
|
|
set_category(CAT_INTERFACE) |
|
|
|
set_subcategory(SUBCAT_INTERFACE_CONTROL) |
|
|
|
set_capability("interface", 0) |
|
|
|
set_callbacks(Open, Close) |
|
|
|
vlc_module_end() |
|
|
|
|