codec: subsdec: fix variable shadowing
[vlc.git] / modules / notify / osx_notifications.m
blob5904d4b73d08cf7d09b5e46a7d1ede84ae2fc2f8
1 /*****************************************************************************
2  * osx_notifications.m : macOS notification plugin
3  *
4  * This plugin provides support for macOS notifications on current playlist
5  * item changes.
6  *****************************************************************************
7  * Copyright © 2008, 2011, 2012, 2015, 2018 the VideoLAN team
8  * $Id$
9  *
10  * Authors: Rafaël Carré <funman@videolanorg>
11  *          Felix Paul Kühne <fkuehne@videolan.org
12  *          Marvin Scholz <epirat07@gmail.com>
13  *
14  * This program is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program; if not, write to the Free Software
26  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
27  */
29 #define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
31 #ifdef HAVE_CONFIG_H
32 # include "config.h"
33 #endif
35 #import <Foundation/Foundation.h>
36 #import <Cocoa/Cocoa.h>
38 #include <vlc_common.h>
39 #include <vlc_plugin.h>
40 #include <vlc_playlist_legacy.h>
41 #include <vlc_input.h>
42 #include <vlc_meta.h>
43 #include <vlc_interface.h>
44 #include <vlc_url.h>
46 #pragma mark -
47 #pragma mark Class interfaces
48 @interface VLCNotificationDelegate : NSObject <NSUserNotificationCenterDelegate>
50     /** Interface thread, required for skipping to the next item */
51     intf_thread_t * _Nonnull interfaceThread;
52     
53     /** Holds the last notification so it can be cleared when the next one is delivered */
54     NSUserNotification * _Nullable lastNotification;
55     
56     /** Indicates if VLC is in foreground */
57     BOOL isInForeground;
60 /**
61  * Initializes a new  VLCNotification Delegate with a given intf_thread_t
62  */
63 - (instancetype)initWithInterfaceThread:(intf_thread_t * _Nonnull)intf_thread;
65 /**
66  * Delegate method called when the current input changed
67  */
68 - (void)currentInputDidChanged:(input_thread_t * _Nonnull)input;
70 @end
73 #pragma mark -
74 #pragma mark Local prototypes
75 struct intf_sys_t
77     void *vlcNotificationDelegate;
80 static int InputCurrent(vlc_object_t *, const char *,
81                         vlc_value_t, vlc_value_t, void *);
84 #pragma mark -
85 #pragma mark C module functions
87  * Open: Initialization of the module
88  */
89 static int Open(vlc_object_t *p_this)
91     intf_thread_t *p_intf = (intf_thread_t *)p_this;
92     playlist_t *p_playlist = pl_Get(p_intf);
93     intf_sys_t *p_sys = p_intf->p_sys = calloc(1, sizeof(intf_sys_t));
95     if (!p_sys)
96         return VLC_ENOMEM;
98     @autoreleasepool {
99         VLCNotificationDelegate *notificationDelegate =
100             [[VLCNotificationDelegate alloc] initWithInterfaceThread:p_intf];
101         
102         if (notificationDelegate == nil) {
103             free(p_sys);
104             return VLC_ENOMEM;
105         }
106         
107         p_sys->vlcNotificationDelegate = (__bridge_retained void*)notificationDelegate;
108     }
110     var_AddCallback(p_playlist, "input-current", InputCurrent, p_intf);
112     return VLC_SUCCESS;
116  * Close: Destruction of the module
117  */
118 static void Close(vlc_object_t *p_this)
120     intf_thread_t *p_intf = (intf_thread_t *)p_this;
121     playlist_t *p_playlist = pl_Get(p_intf);
122     intf_sys_t *p_sys = p_intf->p_sys;
123     
124     // Remove the callback, this must be done here, before deallocating the
125     // notification delegate object
126     var_DelCallback(p_playlist, "input-current", InputCurrent, p_intf);
128     @autoreleasepool {
129         // Transfer ownership of notification delegate object back to ARC
130         VLCNotificationDelegate *notificationDelegate =
131             (__bridge_transfer VLCNotificationDelegate*)p_sys->vlcNotificationDelegate;
133         // Ensure the object is deallocated
134         notificationDelegate = nil;
135     }
137     free(p_sys);
141  * Callback invoked on playlist item change
142  */
143 static int InputCurrent(vlc_object_t *p_this, const char *psz_var,
144                         vlc_value_t oldval, vlc_value_t newval, void *param)
146     intf_thread_t *p_intf = (intf_thread_t *)param;
147     intf_sys_t *p_sys = p_intf->p_sys;
148     input_thread_t *p_input = newval.p_address;
149     VLC_UNUSED(oldval);
151     @autoreleasepool {
152         VLCNotificationDelegate *notificationDelegate =
153             (__bridge VLCNotificationDelegate*)p_sys->vlcNotificationDelegate;
154         
155         [notificationDelegate currentInputDidChanged:(input_thread_t *)p_input];
156     }
158     return VLC_SUCCESS;
162   * Transfers a null-terminated UTF-8 C "string" to a NSString
163   * in a way that the NSString takes ownership of it.
164   *
165   * \warning    After calling this function, passed cStr must not be used anymore!
166   *
167   * \param      cStr  Pointer to a zero-terminated UTF-8 encoded char array
168   *
169   * \return     An NSString instance that uses cStr as internal data storage and
170   *             frees it when done. On error, nil is returned and cStr is freed.
171   */
172 static inline NSString* CharsToNSString(char * _Nullable cStr)
174     if (!cStr)
175         return nil;
177     NSString *resString = [[NSString alloc] initWithBytesNoCopy:cStr
178                                                          length:strlen(cStr)
179                                                        encoding:NSUTF8StringEncoding
180                                                    freeWhenDone:YES];
181     if (unlikely(resString == nil))
182         free(cStr);
184     return resString;
187 #pragma mark -
188 #pragma mark Class implementation
189 @implementation VLCNotificationDelegate
191 - (id)initWithInterfaceThread:(intf_thread_t *)intf_thread
193     self = [super init];
194     
195     if (self) {
196         interfaceThread = intf_thread;
197         
198         // Subscribe to notifications to determine if VLC is in foreground or not
199         [[NSNotificationCenter defaultCenter] addObserver:self
200                                                  selector:@selector(applicationActiveChange:)
201                                                      name:NSApplicationDidBecomeActiveNotification
202                                                    object:nil];
204         [[NSNotificationCenter defaultCenter] addObserver:self
205                                                  selector:@selector(applicationActiveChange:)
206                                                      name:NSApplicationDidResignActiveNotification
207                                                    object:nil];
209         [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];
210     }
211     
212     return self;
215 - (void)currentInputDidChanged:(input_thread_t *)input
217     if (!input)
218         return;
219     
220     input_item_t *item = input_GetItem(input);
221     if (!item)
222         return;
223     
224     // Get title, first try now playing
225     NSString *title = CharsToNSString(input_item_GetNowPlayingFb(item));
227     // Fallback to item title or name
228     if ([title length] == 0)
229         title = CharsToNSString(input_item_GetTitleFbName(item));
231     // If there is still not title, do not notify
232     if (unlikely([title length] == 0))
233         return;
235     // Get artist name
236     NSString *artist = CharsToNSString(input_item_GetArtist(item));
238     // Get album name
239     NSString *album = CharsToNSString(input_item_GetAlbum(item));
241     // Get coverart path
242     NSString *artPath = nil;
244     char *psz_arturl = input_item_GetArtURL(item);
245     if (psz_arturl) {
246         artPath = CharsToNSString(vlc_uri2path(psz_arturl));
247         free(psz_arturl);
248     }
250     // Construct final description string
251     NSString *desc = nil;
253     if (artist && album) {
254         desc = [NSString stringWithFormat:@"%@ – %@", artist, album];
255     } else if (artist) {
256         desc = artist;
257     }
258     
259     // Notify!
260     [self notifyWithTitle:title description:desc imagePath:artPath];
264  * Called when the applications activity status changes
265  */
266 - (void)applicationActiveChange:(NSNotification *)n {
267     if (n.name == NSApplicationDidBecomeActiveNotification)
268         isInForeground = YES;
269     else if (n.name == NSApplicationDidResignActiveNotification)
270         isInForeground = NO;
274  * Called when the user interacts with a notification
275  */
276 - (void)userNotificationCenter:(NSUserNotificationCenter *)center
277        didActivateNotification:(NSUserNotification *)notification
279     // Check if notification button ("Skip") was clicked
280     if (notification.activationType == NSUserNotificationActivationTypeActionButtonClicked) {
281         // Skip to next song
282         playlist_Next(pl_Get(interfaceThread));
283     }
287  * Called when a new notification was delivered
288  */
289 - (void)userNotificationCenter:(NSUserNotificationCenter *)center
290         didDeliverNotification:(NSUserNotification *)notification
292     // Only keep the most recent notification in the Notification Center
293     if (lastNotification)
294         [center removeDeliveredNotification:lastNotification];
296     lastNotification = notification;
300  * Send a notification to the default user notification center
301  */
302 - (void)notifyWithTitle:(NSString * _Nonnull)titleText
303             description:(NSString * _Nullable)descriptionText
304               imagePath:(NSString * _Nullable)imagePath
306     NSImage *image = nil;
308     // Load image if any
309     if (imagePath) {
310         image = [[NSImage alloc] initWithContentsOfFile:imagePath];
311     }
313     // Create notification
314     NSUserNotification *notification = [NSUserNotification new];
316     notification.title              = titleText;
317     notification.subtitle           = descriptionText;
318     notification.hasActionButton    = YES;
319     notification.actionButtonTitle  = [NSString stringWithUTF8String:_("Skip")];
320     
321     // Try to set private properties
322     @try {
323         // Private API to set cover image, see rdar://23148801
324         [notification setValue:image forKey:@"_identityImage"];
325         // Private API to show action button, see rdar://23148733
326         [notification setValue:@(YES) forKey:@"_showsButtons"];
327     } @catch (NSException *exception) {
328         if (exception.name == NSUndefinedKeyException)
329             NSLog(@"VLC macOS notifcations plugin failed to set private notification values.");
330         else
331             @throw exception;
332     }
334     // Send notification
335     [[NSUserNotificationCenter defaultUserNotificationCenter]
336         deliverNotification:notification];
340  * Cleanup
341  */
342 - (void)dealloc
344     [[NSNotificationCenter defaultCenter] removeObserver:self];
346     // Clear a remaining lastNotification in Notification Center, if any
347     if (lastNotification) {
348         [[NSUserNotificationCenter defaultUserNotificationCenter]
349             removeDeliveredNotification:lastNotification];
350         lastNotification = nil;
351     }
354 @end
357 #pragma mark -
358 #pragma mark VLC Module descriptor
360 vlc_module_begin()
361     set_shortname("OSX-Notifications")
362     set_description(N_("macOS notifications plugin"))
363     add_shortcut("growl") // Kept for backwards compatibility
364     set_category(CAT_INTERFACE)
365     set_subcategory(SUBCAT_INTERFACE_CONTROL)
366     set_capability("interface", 0)
367     set_callbacks(Open, Close)
368 vlc_module_end()