[vlc-commits] osx_notifications: remove Growl support, refactor

Marvin Scholz git at videolan.org
Tue Apr 10 01:44:03 CEST 2018


vlc | branch: master | Marvin Scholz <epirat07 at gmail.com> | Tue Apr 10 01:08:05 2018 +0200| [66cdaaaf1fc066d71290936abcf2e27189ac33c2] | committer: Marvin Scholz

osx_notifications: remove Growl support, refactor

As per the discussion on the ML, Growl support is removed for VLC 4.0.

With this refactoring the module is ARC compatible too, simplifying
memory management.

https://mailman.videolan.org/pipermail/vlc-devel/2018-February/117924.html

> http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=66cdaaaf1fc066d71290936abcf2e27189ac33c2
---

 NEWS                               |   3 +
 configure.ac                       |  10 +-
 modules/MODULES_LIST               |   2 +-
 modules/notify/Makefile.am         |   5 +-
 modules/notify/osx_notifications.m | 556 ++++++++++++++++---------------------
 5 files changed, 254 insertions(+), 322 deletions(-)

diff --git a/NEWS b/NEWS
index c585bbe900..6ad7c760fd 100644
--- a/NEWS
+++ b/NEWS
@@ -23,6 +23,9 @@ Video output:
  * Remove evas plugin
  * Remove omxil_vout plugin
 
+macOS:
+ * Remove Growl notification support
+
 
 Changes between 2.2.8 and 3.0.0:
 --------------------------------
diff --git a/configure.ac b/configure.ac
index 734a2e2281..67a883ea9d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -4103,15 +4103,11 @@ dnl
 dnl OS X notification plugin
 dnl
 AC_ARG_ENABLE(osx_notifications,
-  [  --enable-osx-notifications          osx notification plugin (default disabled)],,
+  [AS_HELP_STRING([--enable-osx-notifications],
+    [macOS notification plugin (default disabled)])],,
   [enable_osx_notifications=no])
 AS_IF([test "${enable_osx_notifications}" != "no"], [
-  if test -d ${CONTRIB_DIR}/Growl.framework -o -d ${CONTRIB_DIR}/Frameworks/Growl.framework
-  then
-      VLC_ADD_PLUGIN([osx_notifications])
-      VLC_ADD_LIBS([osx_notifications], [-Wl,-framework,Growl,-framework,Foundation])
-      VLC_ADD_OBJCFLAGS([osx_notifications], [-fobjc-exceptions] )
-  fi
+  VLC_ADD_PLUGIN([osx_notifications])
 ])
 
 dnl
diff --git a/modules/MODULES_LIST b/modules/MODULES_LIST
index 2c0cce560f..0cc554651c 100644
--- a/modules/MODULES_LIST
+++ b/modules/MODULES_LIST
@@ -281,7 +281,7 @@ $Id$
  * opus: a opus audio decoder/packetizer/encoder using the libopus library
  * os2drive: service discovery for OS/2 drives
  * oss: audio output module using the OSS /dev/dsp interface
- * osx_notifications: announce currently playing stream to OS X/Growl
+ * osx_notifications: announce currently playing stream to OS X
  * packetizer_a52: A/52 basic parser/packetizer
  * packetizer_avparser: libavcodec packetizer
  * packetizer_copy: Simple copy packetizer
diff --git a/modules/notify/Makefile.am b/modules/notify/Makefile.am
index 60045aab80..e97bac63e3 100644
--- a/modules/notify/Makefile.am
+++ b/modules/notify/Makefile.am
@@ -1,9 +1,8 @@
 notifydir = $(pluginsdir)/notify
 
 libosx_notifications_plugin_la_SOURCES = notify/osx_notifications.m
-libosx_notifications_plugin_la_OBJCFLAGS = $(AM_OBJCFLAGS) $(OBJCFLAGS_osx_notifications)
-libosx_notifications_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(notifydir)' -Wl,-framework,AppKit
-libosx_notifications_plugin_la_LIBADD = $(LIBS_osx_notifications)
+libosx_notifications_plugin_la_OBJCFLAGS = $(AM_OBJCFLAGS) -fobjc-arc -fobjc-exceptions
+libosx_notifications_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(notifydir)' -Wl,-framework,AppKit,-framework,Foundation
 
 libnotify_plugin_la_SOURCES = notify/notify.c
 libnotify_plugin_la_CFLAGS = $(AM_CFLAGS) $(NOTIFY_CFLAGS)
diff --git a/modules/notify/osx_notifications.m b/modules/notify/osx_notifications.m
index b987d38a5c..8ac215e851 100644
--- a/modules/notify/osx_notifications.m
+++ b/modules/notify/osx_notifications.m
@@ -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 at 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
- *****************************************************************************/
- at interface VLCGrowlDelegate : NSObject <GrowlApplicationBridgeDelegate>
+#pragma mark -
+#pragma mark Class interfaces
+ at 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
- *****************************************************************************/
- at 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
+ at 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()



More information about the vlc-commits mailing list