1 /*****************************************************************************
2 * osx_notifications.m : OS X notification plugin
3 *****************************************************************************
6 * Copyright © 2008,2011,2012,2015 the VideoLAN team
9 * Authors: Rafaël Carré <funman@videolanorg>
10 * Felix Paul Kühne <fkuehne@videolan.org
11 * Marvin Scholz <epirat07@gmail.com>
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
29 * Growl specific code, ripped from growlnotify:
31 * Copyright (c) The Growl Project, 2004-2005
32 * All rights reserved.
34 * Redistribution and use in source and binary forms, with or without modification,
35 * are permitted provided that the following conditions are met:
37 * 1. Redistributions of source code must retain the above copyright
38 * notice, this list of conditions and the following disclaimer.
39 * 2. Redistributions in binary form must reproduce the above copyright
40 * notice, this list of conditions and the following disclaimer in the
41 * documentation and/or other materials provided with the distribution.
42 * 3. Neither the name of Growl nor the names of its contributors
43 * may be used to endorse or promote products derived from this software
44 * without specific prior written permission.
46 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
47 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
48 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
49 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
50 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
51 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
52 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
53 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
54 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
55 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
57 *****************************************************************************/
59 /*****************************************************************************
61 *****************************************************************************/
63 #pragma clang diagnostic ignored "-Wunguarded-availability"
69 #import <Foundation/Foundation.h>
70 #import <Cocoa/Cocoa.h>
71 #import <Growl/Growl.h>
73 #define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
74 #include <vlc_common.h>
75 #include <vlc_plugin.h>
76 #include <vlc_playlist.h>
77 #include <vlc_input.h>
79 #include <vlc_interface.h>
82 /*****************************************************************************
83 * intf_sys_t, VLCGrowlDelegate
84 *****************************************************************************/
85 @interface VLCGrowlDelegate : NSObject <GrowlApplicationBridgeDelegate>
87 NSString *applicationName;
88 NSString *notificationType;
89 NSMutableDictionary *registrationDictionary;
92 bool hasNativeNotifications;
93 intf_thread_t *interfaceThread;
96 - (id)initWithInterfaceThread:(intf_thread_t *)thread;
97 - (void)registerToGrowl;
98 - (void)notifyWithTitle:(const char *)title
99 artist:(const char *)artist
100 album:(const char *)album
101 andArtUrl:(const char *)url;
106 VLCGrowlDelegate *o_growl_delegate;
109 /*****************************************************************************
111 *****************************************************************************/
112 static int Open ( vlc_object_t * );
113 static void Close ( vlc_object_t * );
115 static int InputCurrent( vlc_object_t *, const char *,
116 vlc_value_t, vlc_value_t, void * );
118 /*****************************************************************************
120 ****************************************************************************/
122 set_category( CAT_INTERFACE )
123 set_subcategory( SUBCAT_INTERFACE_CONTROL )
124 set_shortname( "OSX-Notifications" )
125 add_shortcut( "growl" )
126 set_description( N_("OS X Notification Plugin") )
127 set_capability( "interface", 0 )
128 set_callbacks( Open, Close )
131 /*****************************************************************************
132 * Open: initialize and create stuff
133 *****************************************************************************/
134 static int Open( vlc_object_t *p_this )
136 intf_thread_t *p_intf = (intf_thread_t *)p_this;
137 playlist_t *p_playlist = pl_Get( p_intf );
138 intf_sys_t *p_sys = p_intf->p_sys = calloc( 1, sizeof(intf_sys_t) );
143 p_sys->o_growl_delegate = [[VLCGrowlDelegate alloc] initWithInterfaceThread:p_intf];
144 if( !p_sys->o_growl_delegate )
150 var_AddCallback( p_playlist, "input-current", InputCurrent, p_intf );
152 [p_sys->o_growl_delegate registerToGrowl];
156 /*****************************************************************************
157 * Close: destroy interface stuff
158 *****************************************************************************/
159 static void Close( vlc_object_t *p_this )
161 intf_thread_t *p_intf = (intf_thread_t *)p_this;
162 playlist_t *p_playlist = pl_Get( p_intf );
163 intf_sys_t *p_sys = p_intf->p_sys;
165 var_DelCallback( p_playlist, "input-current", InputCurrent, p_intf );
167 [GrowlApplicationBridge setGrowlDelegate:nil];
168 [p_sys->o_growl_delegate release];
172 /*****************************************************************************
173 * InputCurrent: Current playlist item changed callback
174 *****************************************************************************/
175 static int InputCurrent( vlc_object_t *p_this, const char *psz_var,
176 vlc_value_t oldval, vlc_value_t newval, void *param )
180 intf_thread_t *p_intf = (intf_thread_t *)param;
181 intf_sys_t *p_sys = p_intf->p_sys;
182 input_thread_t *p_input = newval.p_address;
183 char *psz_title = NULL;
184 char *psz_artist = NULL;
185 char *psz_album = NULL;
186 char *psz_arturl = NULL;
191 input_item_t *p_item = input_GetItem( p_input );
196 psz_title = input_item_GetNowPlayingFb( p_item );
198 psz_title = input_item_GetTitleFbName( p_item );
200 if( EMPTY_STR( psz_title ) )
206 /* Get Artist name */
207 psz_artist = input_item_GetArtist( p_item );
208 if( EMPTY_STR( psz_artist ) )
209 FREENULL( psz_artist );
212 psz_album = input_item_GetAlbum( p_item ) ;
213 if( EMPTY_STR( psz_album ) )
214 FREENULL( psz_album );
217 psz_arturl = input_item_GetArtURL( p_item );
220 char *psz = vlc_uri2path( psz_arturl );
225 [p_sys->o_growl_delegate notifyWithTitle:psz_title
228 andArtUrl:psz_arturl];
237 /*****************************************************************************
239 *****************************************************************************/
240 @implementation VLCGrowlDelegate
242 - (id)initWithInterfaceThread:(intf_thread_t *)thread {
243 if( !( self = [super init] ) )
247 // Subscribe to notifications to determine if VLC is in foreground or not
248 [[NSNotificationCenter defaultCenter] addObserver:self
249 selector:@selector(applicationActiveChange:)
250 name:NSApplicationDidBecomeActiveNotification
253 [[NSNotificationCenter defaultCenter] addObserver:self
254 selector:@selector(applicationActiveChange:)
255 name:NSApplicationDidResignActiveNotification
258 // Start in background
261 // Check for native notification support
262 Class userNotificationClass = NSClassFromString(@"NSUserNotification");
263 Class userNotificationCenterClass = NSClassFromString(@"NSUserNotificationCenter");
264 hasNativeNotifications = (userNotificationClass && userNotificationCenterClass) ? YES : NO;
266 lastNotification = nil;
267 applicationName = nil;
268 notificationType = nil;
269 registrationDictionary = nil;
270 interfaceThread = thread;
277 #pragma clang diagnostic push
278 #pragma clang diagnostic ignored "-Wpartial-availability"
279 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1080
280 // Clear the remaining lastNotification in Notification Center, if any
282 if (lastNotification && hasNativeNotifications) {
283 [NSUserNotificationCenter.defaultUserNotificationCenter
284 removeDeliveredNotification:(NSUserNotification *)lastNotification];
285 [lastNotification release];
287 [[NSNotificationCenter defaultCenter] removeObserver:self];
290 #pragma clang diagnostic pop
292 // Release everything
293 [applicationName release];
294 [notificationType release];
295 [registrationDictionary release];
299 - (void)registerToGrowl
302 applicationName = [[NSString alloc] initWithUTF8String:_( "VLC media player" )];
303 notificationType = [[NSString alloc] initWithUTF8String:_( "New input playing" )];
305 NSArray *defaultAndAllNotifications = [NSArray arrayWithObject: notificationType];
306 registrationDictionary = [[NSMutableDictionary alloc] init];
307 [registrationDictionary setObject:defaultAndAllNotifications
308 forKey:GROWL_NOTIFICATIONS_ALL];
309 [registrationDictionary setObject:defaultAndAllNotifications
310 forKey: GROWL_NOTIFICATIONS_DEFAULT];
312 [GrowlApplicationBridge setGrowlDelegate:self];
314 #pragma clang diagnostic push
315 #pragma clang diagnostic ignored "-Wpartial-availability"
316 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1080
317 if (hasNativeNotifications) {
318 [[NSUserNotificationCenter defaultUserNotificationCenter]
319 setDelegate:(id<NSUserNotificationCenterDelegate>)self];
322 #pragma clang diagnostic pop
326 - (void)notifyWithTitle:(const char *)title
327 artist:(const char *)artist
328 album:(const char *)album
329 andArtUrl:(const char *)url
332 // Do not notify if in foreground
337 NSData *coverImageData = nil;
338 NSImage *coverImage = nil;
341 coverImageData = [NSData dataWithContentsOfFile:[NSString stringWithUTF8String:url]];
342 coverImage = [[NSImage alloc] initWithData:coverImageData];
346 NSString *titleStr = nil;
347 NSString *artistStr = nil;
348 NSString *albumStr = nil;
351 titleStr = [NSString stringWithUTF8String:title];
353 // Without title, notification makes no sense, so return here
354 // title should never be empty, but better check than crash.
355 [coverImage release];
359 artistStr = [NSString stringWithUTF8String:artist];
361 albumStr = [NSString stringWithUTF8String:album];
363 // Notification stuff
364 if ([GrowlApplicationBridge isGrowlRunning]) {
365 // Make the Growl notification string
366 NSString *desc = nil;
368 if (artistStr && albumStr) {
369 desc = [NSString stringWithFormat:@"%@\n%@ [%@]", titleStr, artistStr, albumStr];
370 } else if (artistStr) {
371 desc = [NSString stringWithFormat:@"%@\n%@", titleStr, artistStr];
377 [GrowlApplicationBridge notifyWithTitle:[NSString stringWithUTF8String:_("Now playing")]
379 notificationName:notificationType
380 iconData:coverImageData
384 identifier:@"VLCNowPlayingNotification"];
385 } else if (hasNativeNotifications) {
386 #pragma clang diagnostic push
387 #pragma clang diagnostic ignored "-Wpartial-availability"
388 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1080
389 // Make the OS X notification and string
390 NSUserNotification *notification = [NSUserNotification new];
391 NSString *desc = nil;
393 if (artistStr && albumStr) {
394 desc = [NSString stringWithFormat:@"%@ – %@", artistStr, albumStr];
395 } else if (artistStr) {
399 notification.title = titleStr;
400 notification.subtitle = desc;
401 notification.hasActionButton = YES;
402 notification.actionButtonTitle = [NSString stringWithUTF8String:_("Skip")];
404 // Private APIs to set cover image, see rdar://23148801
405 // and show action button, see rdar://23148733
406 [notification setValue:coverImage forKey:@"_identityImage"];
407 [notification setValue:@(YES) forKey:@"_showsButtons"];
408 [NSUserNotificationCenter.defaultUserNotificationCenter deliverNotification:notification];
409 [notification release];
411 #pragma clang diagnostic pop
415 [coverImage release];
419 /*****************************************************************************
421 *****************************************************************************/
422 - (NSDictionary *)registrationDictionaryForGrowl
424 return registrationDictionary;
427 - (NSString *)applicationNameForGrowl
429 return applicationName;
432 - (void)applicationActiveChange:(NSNotification *)n {
433 if (n.name == NSApplicationDidBecomeActiveNotification)
434 isInForeground = YES;
435 else if (n.name == NSApplicationDidResignActiveNotification)
439 #pragma clang diagnostic push
440 #pragma clang diagnostic ignored "-Wpartial-availability"
441 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1080
442 - (void)userNotificationCenter:(NSUserNotificationCenter *)center
443 didActivateNotification:(NSUserNotification *)notification
446 if (notification.activationType == NSUserNotificationActivationTypeActionButtonClicked) {
447 playlist_Next(pl_Get(interfaceThread));
451 - (void)userNotificationCenter:(NSUserNotificationCenter *)center
452 didDeliverNotification:(NSUserNotification *)notification
454 // Only keep the most recent notification in the Notification Center
455 if (lastNotification) {
456 [center removeDeliveredNotification: (NSUserNotification *)lastNotification];
457 [lastNotification release];
459 [notification retain];
460 lastNotification = notification;
463 #pragma clang diagnostic pop