]> git.sesse.net Git - vlc/blob - modules/misc/notify/growl.m
Move text_renderer/ out of misc/
[vlc] / modules / misc / notify / growl.m
1 /*****************************************************************************
2  * growl.m : growl notification plugin
3  *****************************************************************************
4  * VLC specific code:
5  * 
6  * Copyright © 2008,2011 the VideoLAN team
7  * $Id$
8  *
9  * Authors: Rafaël Carré <funman@videolanorg>
10  *          Felix Paul Kühne <fkuehne@videolan.org
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *
26  * Growl specific code, ripped from growlnotify:
27  *
28  * Copyright (c) The Growl Project, 2004-2005
29  * All rights reserved.
30  *
31  * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
32  *
33  * 1. Redistributions of source code must retain the above copyright
34  notice, this list of conditions and the following disclaimer.
35  * 2. Redistributions in binary form must reproduce the above copyright
36  notice, this list of conditions and the following disclaimer in the
37  documentation and/or other materials provided with the distribution.
38  * 3. Neither the name of Growl nor the names of its contributors
39  may be used to endorse or promote products derived from this software
40  without specific prior written permission.
41  *
42  * 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.
43  *
44  *****************************************************************************/
45
46 /*****************************************************************************
47  * Preamble
48  *****************************************************************************/
49
50 #ifdef HAVE_CONFIG_H
51 # include "config.h"
52 #endif
53
54 #import <CoreFoundation/CoreFoundation.h>
55 #import <Growl/GrowlDefines.h>
56
57 #include <vlc_common.h>
58 #include <vlc_plugin.h>
59 #include <vlc_playlist.h>
60 #include <vlc_meta.h>
61 #include <vlc_interface.h>
62 #include <vlc_url.h>
63
64
65 /*****************************************************************************
66  * intf_sys_t
67  *****************************************************************************/
68 struct intf_sys_t
69 {
70     CFDataRef           default_icon;
71     CFStringRef         app_name;
72     CFStringRef         notification_type;
73     int             i_id;
74     int             i_item_changes;
75 };
76
77 /*****************************************************************************
78  * Local prototypes
79  *****************************************************************************/
80 static int  Open    ( vlc_object_t * );
81 static void Close   ( vlc_object_t * );
82
83 static int ItemChange( vlc_object_t *, const char *,
84                       vlc_value_t, vlc_value_t, void * );
85
86 static void RegisterToGrowl( vlc_object_t * );
87 static void NotifyToGrowl( intf_thread_t *, const char *, CFDataRef );
88
89 static CFDataRef readFile(const char *);
90
91 /*****************************************************************************
92  * Module descriptor
93  ****************************************************************************/
94
95 vlc_module_begin ()
96 set_category( CAT_INTERFACE )
97 set_subcategory( SUBCAT_INTERFACE_CONTROL )
98 set_shortname( "Growl" )
99 set_description( N_("Growl Notification Plugin") )
100 set_capability( "interface", 0 )
101 set_callbacks( Open, Close )
102 vlc_module_end ()
103
104 /*****************************************************************************
105  * Open: initialize and create stuff
106  *****************************************************************************/
107 static int Open( vlc_object_t *p_this )
108 {
109     intf_thread_t *p_intf = (intf_thread_t *)p_this;
110     playlist_t *p_playlist;
111     intf_sys_t    *p_sys;
112     
113     p_sys = p_intf->p_sys = calloc( 1, sizeof(intf_sys_t) );
114     if( !p_sys )
115         return VLC_ENOMEM;
116     
117     p_sys->app_name = CFSTR( "VLC media player" );
118     p_sys->notification_type = CFSTR( "New input playing" );
119     
120     char *data_path = config_GetDataDir ( p_this );
121     char buf[strlen (data_path) + sizeof ("/vlc512x512.png")];
122     snprintf (buf, sizeof (buf), "%s/vlc512x512.png", data_path);
123     msg_Dbg( p_this, "looking for icon at %s", buf );
124     free( data_path );
125     p_sys->default_icon = (CFDataRef) readFile( buf );
126     
127     p_playlist = pl_Get( p_intf );
128     var_AddCallback( p_playlist, "item-change", ItemChange, p_intf );
129     var_AddCallback( p_playlist, "item-current", ItemChange, p_intf );
130     
131     RegisterToGrowl( p_this );
132     return VLC_SUCCESS;
133 }
134
135 /*****************************************************************************
136  * Close: destroy interface stuff
137  *****************************************************************************/
138 static void Close( vlc_object_t *p_this )
139 {
140     intf_thread_t *p_intf = (intf_thread_t *)p_this;
141     playlist_t *p_playlist = pl_Get( p_this );
142     intf_sys_t *p_sys = p_intf->p_sys;
143     
144     var_DelCallback( p_playlist, "item-change", ItemChange, p_intf );
145     var_DelCallback( p_playlist, "item-current", ItemChange, p_intf );
146     
147     CFRelease( p_sys->default_icon );
148     CFRelease( p_sys->app_name );
149     CFRelease( p_sys->notification_type );
150     free( p_sys );
151 }
152
153 /*****************************************************************************
154  * ItemChange: Playlist item change callback
155  *****************************************************************************/
156 static int ItemChange( vlc_object_t *p_this, const char *psz_var,
157                       vlc_value_t oldval, vlc_value_t newval, void *param )
158 {
159     VLC_UNUSED(oldval);
160     
161     intf_thread_t *p_intf = (intf_thread_t *)param;
162     char *psz_tmp           = NULL;
163     char *psz_title         = NULL;
164     char *psz_artist        = NULL;
165     char *psz_album         = NULL;
166     input_item_t *p_item = newval.p_address;
167     
168     bool b_is_item_current = !strcmp( "item-current", psz_var );
169     
170     /* Don't update each time an item has been preparsed */
171     if( b_is_item_current )
172     { /* stores the current input item id */
173         p_intf->p_sys->i_id = p_item->i_id;
174         p_intf->p_sys->i_item_changes = 0;
175         return VLC_SUCCESS;
176     }
177     /* ignore items which weren't pre-parsed yet */
178     else if( !input_item_IsPreparsed(p_item) )
179         return VLC_SUCCESS;
180     else
181     {
182         if( p_item->i_id != p_intf->p_sys->i_id ) { /* "item-change" */
183             p_intf->p_sys->i_item_changes = 0;
184             return VLC_SUCCESS;
185         }
186         /* Some variable bitrate inputs call "item-change" callbacks each time
187          * their length is updated, that is several times per second.
188          * We'll limit the number of changes to 1 per input. */
189         if( p_intf->p_sys->i_item_changes > 0 )
190             return VLC_SUCCESS;
191         
192         p_intf->p_sys->i_item_changes++;
193     }
194     
195     
196     input_thread_t *p_input = playlist_CurrentInput( (playlist_t*)p_this );
197     
198     if( !p_input ) return VLC_SUCCESS;
199     
200     if( p_input->b_dead || !input_GetItem(p_input)->psz_name )
201     {
202         /* Not playing anything ... */
203         vlc_object_release( p_input );
204         return VLC_SUCCESS;
205     }
206     
207     /* Playing something ... */
208     if( input_item_GetNowPlaying( p_item ) )
209         psz_title = input_item_GetNowPlaying( p_item );
210     else
211         psz_title = input_item_GetTitleFbName( p_item );
212     if( EMPTY_STR( psz_title ) )
213     {
214         free( psz_title );
215         vlc_object_release( p_input );
216         return VLC_SUCCESS;
217     }
218     
219     psz_artist = input_item_GetArtist( p_item );
220     if( EMPTY_STR( psz_artist ) ) FREENULL( psz_artist );
221     psz_album = input_item_GetAlbum( p_item ) ;
222     if( EMPTY_STR( psz_album ) ) FREENULL( psz_album );
223     
224     int i_ret;
225     if( psz_artist && psz_album )
226         i_ret = asprintf( &psz_tmp, "%s\n%s [%s]",
227                          psz_title, psz_artist, psz_album );
228     else if( psz_artist )
229         i_ret = asprintf( &psz_tmp, "%s\n%s", psz_title, psz_artist );
230     else
231         i_ret = asprintf(&psz_tmp, "%s", psz_title );
232     
233     if( i_ret == -1 )
234     {
235         free( psz_title );
236         free( psz_artist );
237         free( psz_album );
238         vlc_object_release( p_input );
239         return VLC_ENOMEM;
240     }
241     
242     char *psz_arturl = input_item_GetArtURL( p_item );
243     if( psz_arturl )
244     {
245         char *psz = make_path( psz_arturl );
246         free( psz_arturl );
247         psz_arturl = psz;
248     }
249     CFDataRef art = NULL;
250     if( psz_arturl )
251         art = (CFDataRef) readFile( psz_arturl );
252     
253     free( psz_title );
254     free( psz_artist );
255     free( psz_album );
256     free( psz_arturl );
257     
258     NotifyToGrowl( p_intf, psz_tmp, art );
259     
260     if( art ) CFRelease( art );
261     free( psz_tmp );
262     
263     vlc_object_release( p_input );
264     return VLC_SUCCESS;
265 }
266
267 /*****************************************************************************
268  * RegisterToGrowl
269  *****************************************************************************/
270 static void RegisterToGrowl( vlc_object_t *p_this )
271 {
272     intf_sys_t *p_sys = ((intf_thread_t *)p_this)->p_sys;
273     
274     CFArrayRef defaultAndAllNotifications = CFArrayCreate(
275                                                           kCFAllocatorDefault, (const void **)&(p_sys->notification_type), 1,
276                                                           &kCFTypeArrayCallBacks );
277     
278     CFTypeRef registerKeys[4] = {
279         GROWL_APP_NAME,
280         GROWL_NOTIFICATIONS_ALL,
281         GROWL_NOTIFICATIONS_DEFAULT,
282         GROWL_APP_ICON
283     };
284     
285     CFTypeRef registerValues[4] = {
286         p_sys->app_name,
287         defaultAndAllNotifications,
288         defaultAndAllNotifications,
289         p_sys->default_icon
290     };
291     
292     CFDictionaryRef registerInfo = CFDictionaryCreate(
293                                                       kCFAllocatorDefault, registerKeys, registerValues, 4,
294                                                       &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
295     
296     CFRelease( defaultAndAllNotifications );
297     
298     CFNotificationCenterPostNotificationWithOptions(
299                                                     CFNotificationCenterGetDistributedCenter(),
300                                                     (CFStringRef)GROWL_APP_REGISTRATION, NULL, registerInfo,
301                                                     kCFNotificationPostToAllSessions );
302     CFRelease( registerInfo );
303 }
304
305 static void NotifyToGrowl( intf_thread_t *p_intf, const char *psz_desc, CFDataRef art )
306 {
307     intf_sys_t *p_sys = p_intf->p_sys;
308     
309     CFStringRef title = CFStringCreateWithCString( kCFAllocatorDefault, _("Now playing"), kCFStringEncodingUTF8 );
310     CFStringRef desc = CFStringCreateWithCString( kCFAllocatorDefault, psz_desc, kCFStringEncodingUTF8 );
311     
312     CFMutableDictionaryRef notificationInfo = CFDictionaryCreateMutable(
313                                                                         kCFAllocatorDefault, 5, &kCFTypeDictionaryKeyCallBacks,
314                                                                         &kCFTypeDictionaryValueCallBacks);
315     
316     CFDictionarySetValue( notificationInfo, GROWL_NOTIFICATION_NAME, p_sys->notification_type );
317     CFDictionarySetValue( notificationInfo, GROWL_APP_NAME, p_sys->app_name );
318     CFDictionarySetValue( notificationInfo, GROWL_NOTIFICATION_TITLE, title );
319     CFDictionarySetValue( notificationInfo, GROWL_NOTIFICATION_DESCRIPTION, desc );
320     
321     CFDictionarySetValue( notificationInfo, GROWL_NOTIFICATION_ICON, 
322                          art ? art : p_sys->default_icon );
323     
324     CFRelease( title );
325     CFRelease( desc );
326     
327     CFNotificationCenterPostNotificationWithOptions(
328                                                     CFNotificationCenterGetDistributedCenter(),
329                                                     (CFStringRef)GROWL_NOTIFICATION, NULL, notificationInfo,
330                                                     kCFNotificationPostToAllSessions );
331     
332     CFRelease( notificationInfo );
333 }
334
335 /* Ripped from CFGrowlAdditions.c 
336  * Strangely, this function does exist in Growl shared library, but is not
337  * defined in public header files */
338 static CFDataRef readFile(const char *filename)
339 {
340     CFDataRef data;
341     // read the file into a CFDataRef
342     FILE *fp = fopen(filename, "r");
343     if( !fp )
344         return NULL;
345     
346     fseek(fp, 0, SEEK_END);
347     long dataLength = ftell(fp);
348     fseek(fp, 0, SEEK_SET);
349     unsigned char *fileData = malloc(dataLength);
350     fread(fileData, 1, dataLength, fp);
351     fclose(fp);
352     return CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, fileData,
353                                        dataLength, kCFAllocatorMalloc);
354 }
355