f0356ca197efc8c5fd87b7761f81e9ea23cdbd8f
[vlc.git] / modules / control / dbus / dbus.c
1 /*****************************************************************************
2  * dbus.c : D-Bus control interface
3  *****************************************************************************
4  * Copyright © 2006-2008 Rafaël Carré
5  * Copyright © 2007-2012 Mirsal Ennaime
6  * Copyright © 2009-2012 The VideoLAN team
7  * Copyright © 2013      Alex Merry
8  * $Id$
9  *
10  * Authors:    Rafaël Carré <funman at videolanorg>
11  *             Mirsal Ennaime <mirsal at mirsal fr>
12  *             Alex Merry <dev at randomguy3 me uk>
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  *****************************************************************************/
28
29 /*
30  * D-Bus Specification:
31  *      http://dbus.freedesktop.org/doc/dbus-specification.html
32  * D-Bus low-level C API (libdbus)
33  *      http://dbus.freedesktop.org/doc/dbus/api/html/index.html
34  *  extract:
35  *   "If you use this low-level API directly, you're signing up for some pain."
36  *
37  * MPRIS Specification version 1.0
38  *      http://wiki.xmms2.xmms.se/index.php/MPRIS
39  */
40
41 /*****************************************************************************
42  * Preamble
43  *****************************************************************************/
44
45 #ifdef HAVE_CONFIG_H
46 # include "config.h"
47 #endif
48
49 #include <dbus/dbus.h>
50 #include "dbus_common.h"
51 #include "dbus_root.h"
52 #include "dbus_player.h"
53 #include "dbus_tracklist.h"
54 #include "dbus_introspect.h"
55
56 #include <vlc_common.h>
57 #include <vlc_plugin.h>
58 #include <vlc_interface.h>
59 #include <vlc_playlist.h>
60 #include <vlc_meta.h>
61 #include <vlc_mtime.h>
62 #include <vlc_fs.h>
63
64 #include <assert.h>
65 #include <string.h>
66
67 #include <poll.h>
68 #include <errno.h>
69 #include <unistd.h>
70
71 #define DBUS_MPRIS_BUS_NAME "org.mpris.MediaPlayer2.vlc"
72 #define DBUS_INSTANCE_ID_PREFIX "instance"
73
74 #define SEEK_THRESHOLD 1000 /* µsec */
75
76 /*****************************************************************************
77  * Local prototypes.
78  *****************************************************************************/
79
80 static DBusHandlerResult
81 MPRISEntryPoint ( DBusConnection *p_conn, DBusMessage *p_from, void *p_this );
82
83 static const DBusObjectPathVTable dbus_mpris_vtable = {
84         NULL, MPRISEntryPoint, /* handler function */
85         NULL, NULL, NULL, NULL
86 };
87
88 typedef struct
89 {
90     int signal;
91     int i_node;
92     int i_item;
93 } callback_info_t;
94
95 typedef struct
96 {
97     mtime_t      i_remaining;
98     DBusTimeout *p_timeout;
99 } timeout_info_t;
100
101 enum
102 {
103     PIPE_OUT = 0,
104     PIPE_IN  = 1
105 };
106
107 static int  Open    ( vlc_object_t * );
108 static void Close   ( vlc_object_t * );
109 static void *Run    ( void * );
110
111 static int TrackChange( intf_thread_t * );
112 static int AllCallback( vlc_object_t*, const char*, vlc_value_t, vlc_value_t, void* );
113 static int InputCallback( vlc_object_t*, const char*, vlc_value_t, vlc_value_t, void* );
114
115 static dbus_bool_t add_timeout ( DBusTimeout *p_timeout, void *p_data );
116 static dbus_bool_t add_watch   ( DBusWatch *p_watch, void *p_data );
117
118 static void remove_timeout  ( DBusTimeout *p_timeout, void *p_data );
119 static void remove_watch    ( DBusWatch *p_watch, void *p_data );
120
121 static void timeout_toggled ( DBusTimeout *p_timeout, void *p_data );
122 static void watch_toggled   ( DBusWatch *p_watch, void *p_data );
123
124 static void wakeup_main_loop( void *p_data );
125
126 static int UpdateTimeouts( intf_thread_t *p_intf, mtime_t i_lastrun );
127
128 static void ProcessEvents  ( intf_thread_t    *p_intf,
129                              callback_info_t **p_events,
130                              int               i_events );
131
132 static void ProcessWatches ( intf_thread_t    *p_intf,
133                              DBusWatch       **p_watches,
134                              int               i_watches,
135                              struct pollfd    *p_fds,
136                              int               i_fds );
137
138 static void ProcessTimeouts( intf_thread_t    *p_intf,
139                              DBusTimeout     **p_timeouts,
140                              int               i_timeouts );
141
142 static void DispatchDBusMessages( intf_thread_t *p_intf );
143
144 /*****************************************************************************
145  * Module descriptor
146  *****************************************************************************/
147 vlc_module_begin ()
148     set_shortname( N_("DBus"))
149     set_category( CAT_INTERFACE )
150     set_description( N_("D-Bus control interface") )
151     set_capability( "interface", 0 )
152     set_callbacks( Open, Close )
153 vlc_module_end ()
154
155 /*****************************************************************************
156  * Open: initialize interface
157  *****************************************************************************/
158
159 static int Open( vlc_object_t *p_this )
160 {
161     intf_thread_t   *p_intf = (intf_thread_t*)p_this;
162
163     /* initialisation of the connection */
164     if( !dbus_threads_init_default() )
165         return VLC_EGENERIC;
166
167     intf_sys_t *p_sys  = calloc( 1, sizeof( intf_sys_t ) );
168     if( unlikely(!p_sys) )
169         return VLC_ENOMEM;
170
171     playlist_t      *p_playlist;
172     DBusConnection  *p_conn;
173     p_sys->i_player_caps   = PLAYER_CAPS_NONE;
174     p_sys->i_playing_state = PLAYBACK_STATE_INVALID;
175
176     if( vlc_pipe( p_sys->p_pipe_fds ) )
177     {
178         free( p_sys );
179         msg_Err( p_intf, "Could not create pipe" );
180         return VLC_EGENERIC;
181     }
182
183     DBusError error;
184     dbus_error_init( &error );
185
186     /* connect privately to the session bus
187      * the connection will not be shared with other vlc modules which use dbus,
188      * thus avoiding a whole class of concurrency issues */
189     p_conn = dbus_bus_get_private( DBUS_BUS_SESSION, &error );
190     if( !p_conn )
191     {
192         msg_Err( p_this, "Failed to connect to the D-Bus session daemon: %s",
193                 error.message );
194         dbus_error_free( &error );
195         free( p_sys );
196         return VLC_EGENERIC;
197     }
198
199     dbus_connection_set_exit_on_disconnect( p_conn, FALSE );
200
201     /* register an instance-specific well known name of the form
202      * org.mpris.MediaPlayer2.vlc.instanceXXXX where XXXX is the
203      * current process's pid */
204     size_t i_length = sizeof( DBUS_MPRIS_BUS_NAME ) +
205         sizeof( DBUS_INSTANCE_ID_PREFIX ) + 10;
206
207     char unique_service[i_length];
208
209     snprintf( unique_service, sizeof (unique_service),
210             DBUS_MPRIS_BUS_NAME"."DBUS_INSTANCE_ID_PREFIX"%"PRIu32,
211             (uint32_t)getpid() );
212
213     dbus_bus_request_name( p_conn, unique_service, 0, &error );
214
215     if( dbus_error_is_set( &error ) )
216     {
217         msg_Err( p_this, "Error requesting service name %s: %s",
218                  unique_service, error.message );
219         dbus_error_free( &error );
220         free( p_sys );
221         return VLC_EGENERIC;
222     }
223     msg_Dbg( p_intf, "listening on dbus as: %s", unique_service );
224
225     /* Try to register org.mpris.MediaPlayer2.vlc as well in case we are
226      * the only VLC instance currently connected to the bus */
227     dbus_bus_request_name( p_conn, DBUS_MPRIS_BUS_NAME, 0, NULL );
228
229     /* Register the entry point object path */
230     dbus_connection_register_object_path( p_conn, DBUS_MPRIS_OBJECT_PATH,
231             &dbus_mpris_vtable, p_this );
232
233     dbus_connection_flush( p_conn );
234
235     p_intf->p_sys = p_sys;
236     p_sys->p_conn = p_conn;
237     p_sys->p_events = vlc_array_new();
238     p_sys->p_timeouts = vlc_array_new();
239     p_sys->p_watches = vlc_array_new();
240     vlc_mutex_init( &p_sys->lock );
241
242     p_playlist = pl_Get( p_intf );
243     p_sys->p_playlist = p_playlist;
244
245     var_AddCallback( p_playlist, "activity", AllCallback, p_intf );
246     var_AddCallback( p_playlist, "intf-change", AllCallback, p_intf );
247     var_AddCallback( p_playlist, "volume", AllCallback, p_intf );
248     var_AddCallback( p_playlist, "mute", AllCallback, p_intf );
249     var_AddCallback( p_playlist, "playlist-item-append", AllCallback, p_intf );
250     var_AddCallback( p_playlist, "playlist-item-deleted", AllCallback, p_intf );
251     var_AddCallback( p_playlist, "random", AllCallback, p_intf );
252     var_AddCallback( p_playlist, "repeat", AllCallback, p_intf );
253     var_AddCallback( p_playlist, "loop", AllCallback, p_intf );
254     var_AddCallback( p_playlist, "fullscreen", AllCallback, p_intf );
255
256     if( !dbus_connection_set_timeout_functions( p_conn,
257                                                 add_timeout,
258                                                 remove_timeout,
259                                                 timeout_toggled,
260                                                 p_intf, NULL ) )
261         goto error;
262
263     if( !dbus_connection_set_watch_functions( p_conn,
264                                               add_watch,
265                                               remove_watch,
266                                               watch_toggled,
267                                               p_intf, NULL ) )
268         goto error;
269
270     if( vlc_clone( &p_sys->thread, Run, p_intf, VLC_THREAD_PRIORITY_LOW ) )
271         goto error;
272
273     return VLC_SUCCESS;
274
275 error:
276     /* The dbus connection is private,
277      * so we are responsible for closing it
278      * XXX: Does this make sense when OOM ? */
279     dbus_connection_close( p_sys->p_conn );
280     dbus_connection_unref( p_conn );
281
282     vlc_array_destroy( p_sys->p_events );
283     vlc_array_destroy( p_sys->p_timeouts );
284     vlc_array_destroy( p_sys->p_watches );
285
286     vlc_mutex_destroy( &p_sys->lock );
287
288     free( p_sys );
289     return VLC_ENOMEM;
290 }
291
292 /*****************************************************************************
293  * Close: destroy interface
294  *****************************************************************************/
295
296 static void Close   ( vlc_object_t *p_this )
297 {
298     intf_thread_t   *p_intf     = (intf_thread_t*) p_this;
299     intf_sys_t      *p_sys      = p_intf->p_sys;
300     playlist_t      *p_playlist = p_sys->p_playlist;
301
302     vlc_cancel( p_sys->thread );
303     vlc_join( p_sys->thread, NULL );
304
305     var_DelCallback( p_playlist, "activity", AllCallback, p_intf );
306     var_DelCallback( p_playlist, "intf-change", AllCallback, p_intf );
307     var_DelCallback( p_playlist, "volume", AllCallback, p_intf );
308     var_DelCallback( p_playlist, "mute", AllCallback, p_intf );
309     var_DelCallback( p_playlist, "playlist-item-append", AllCallback, p_intf );
310     var_DelCallback( p_playlist, "playlist-item-deleted", AllCallback, p_intf );
311     var_DelCallback( p_playlist, "random", AllCallback, p_intf );
312     var_DelCallback( p_playlist, "repeat", AllCallback, p_intf );
313     var_DelCallback( p_playlist, "loop", AllCallback, p_intf );
314     var_DelCallback( p_playlist, "fullscreen", AllCallback, p_intf );
315
316     if( p_sys->p_input )
317     {
318         var_DelCallback( p_sys->p_input, "intf-event", InputCallback, p_intf );
319         var_DelCallback( p_sys->p_input, "can-pause", AllCallback, p_intf );
320         var_DelCallback( p_sys->p_input, "can-seek", AllCallback, p_intf );
321         vlc_object_release( p_sys->p_input );
322     }
323
324     /* The dbus connection is private, so we are responsible
325      * for closing it */
326     dbus_connection_close( p_sys->p_conn );
327     dbus_connection_unref( p_sys->p_conn );
328
329     // Free the events array
330     for( int i = 0; i < vlc_array_count( p_sys->p_events ); i++ )
331     {
332         callback_info_t* info = vlc_array_item_at_index( p_sys->p_events, i );
333         free( info );
334     }
335     vlc_mutex_destroy( &p_sys->lock );
336     vlc_array_destroy( p_sys->p_events );
337     vlc_array_destroy( p_sys->p_timeouts );
338     vlc_array_destroy( p_sys->p_watches );
339     free( p_sys );
340 }
341
342 static dbus_bool_t add_timeout( DBusTimeout *p_timeout, void *p_data )
343 {
344     intf_thread_t *p_intf = (intf_thread_t*) p_data;
345     intf_sys_t    *p_sys  = (intf_sys_t*) p_intf->p_sys;
346
347     timeout_info_t *p_info = calloc( 1, sizeof( timeout_info_t ) );
348     p_info->i_remaining = dbus_timeout_get_interval( p_timeout ) * 1000;/* µs */
349     p_info->p_timeout = p_timeout;
350
351     dbus_timeout_set_data( p_timeout, p_info, free );
352
353     vlc_mutex_lock( &p_sys->lock );
354     vlc_array_append( p_sys->p_timeouts, p_timeout );
355     vlc_mutex_unlock( &p_sys->lock );
356
357     return TRUE;
358 }
359
360 static void remove_timeout( DBusTimeout *p_timeout, void *p_data )
361 {
362     intf_thread_t *p_intf = (intf_thread_t*) p_data;
363     intf_sys_t    *p_sys  = (intf_sys_t*) p_intf->p_sys;
364
365     vlc_mutex_lock( &p_sys->lock );
366
367     vlc_array_remove( p_sys->p_timeouts,
368                       vlc_array_index_of_item( p_sys->p_timeouts, p_timeout ) );
369
370     vlc_mutex_unlock( &p_sys->lock );
371 }
372
373 static void timeout_toggled( DBusTimeout *p_timeout, void *p_data )
374 {
375     intf_thread_t *p_intf = (intf_thread_t*) p_data;
376
377     if( dbus_timeout_get_enabled( p_timeout ) )
378         wakeup_main_loop( p_intf );
379 }
380
381 static dbus_bool_t add_watch( DBusWatch *p_watch, void *p_data )
382 {
383     intf_thread_t *p_intf = (intf_thread_t*) p_data;
384     intf_sys_t    *p_sys  = (intf_sys_t*) p_intf->p_sys;
385
386     vlc_mutex_lock( &p_sys->lock );
387     vlc_array_append( p_sys->p_watches, p_watch );
388     vlc_mutex_unlock( &p_sys->lock );
389
390     return TRUE;
391 }
392
393 static void remove_watch( DBusWatch *p_watch, void *p_data )
394 {
395     intf_thread_t *p_intf = (intf_thread_t*) p_data;
396     intf_sys_t    *p_sys  = (intf_sys_t*) p_intf->p_sys;
397
398     vlc_mutex_lock( &p_sys->lock );
399
400     vlc_array_remove( p_sys->p_watches,
401                       vlc_array_index_of_item( p_sys->p_watches, p_watch ) );
402
403     vlc_mutex_unlock( &p_sys->lock );
404 }
405
406 static void watch_toggled( DBusWatch *p_watch, void *p_data )
407 {
408     intf_thread_t *p_intf = (intf_thread_t*) p_data;
409
410     if( dbus_watch_get_enabled( p_watch ) )
411         wakeup_main_loop( p_intf );
412 }
413
414 /**
415  * GetPollFds() fills an array of pollfd data structures with :
416  *  - the set of enabled dbus watches
417  *  - the unix pipe which we use to manually wake up the main loop
418  *
419  * This function must be called with p_sys->lock locked
420  *
421  * @return The number of file descriptors
422  *
423  * @param intf_thread_t *p_intf this interface thread's state
424  * @param struct pollfd *p_fds a pointer to a pollfd array large enough to
425  * contain all the returned data (number of enabled dbus watches + 1)
426  */
427 static int GetPollFds( intf_thread_t *p_intf, struct pollfd *p_fds )
428 {
429     intf_sys_t *p_sys = p_intf->p_sys;
430     int i_fds = 1, i_watches = vlc_array_count( p_sys->p_watches );
431
432     p_fds[0].fd = p_sys->p_pipe_fds[PIPE_OUT];
433     p_fds[0].events = POLLIN | POLLPRI;
434
435     for( int i = 0; i < i_watches; i++ )
436     {
437         DBusWatch *p_watch = NULL;
438         p_watch = vlc_array_item_at_index( p_sys->p_watches, i );
439         if( !dbus_watch_get_enabled( p_watch ) )
440             continue;
441
442         p_fds[i_fds].fd = dbus_watch_get_unix_fd( p_watch );
443         int i_flags = dbus_watch_get_flags( p_watch );
444
445         if( i_flags & DBUS_WATCH_READABLE )
446             p_fds[i_fds].events |= POLLIN | POLLPRI;
447
448         if( i_flags & DBUS_WATCH_WRITABLE )
449             p_fds[i_fds].events |= POLLOUT;
450
451         i_fds++;
452     }
453
454     return i_fds;
455 }
456
457 /**
458  * UpdateTimeouts() updates the remaining time for each timeout and
459  * returns how much time is left until the next timeout.
460  *
461  * This function must be called with p_sys->lock locked
462  *
463  * @return int The time remaining until the next timeout, in milliseconds
464  * or -1 if there are no timeouts
465  *
466  * @param intf_thread_t *p_intf This interface thread's state
467  * @param mtime_t i_loop_interval The time which has elapsed since the last
468  * call to this function
469  */
470 static int UpdateTimeouts( intf_thread_t *p_intf, mtime_t i_loop_interval )
471 {
472     intf_sys_t *p_sys = p_intf->p_sys;
473     mtime_t i_next_timeout = LAST_MDATE;
474     unsigned int i_timeouts = vlc_array_count( p_sys->p_timeouts );
475
476     if( 0 == i_timeouts )
477         return -1;
478
479     for( unsigned int i = 0; i < i_timeouts; i++ )
480     {
481         timeout_info_t *p_info = NULL;
482         DBusTimeout    *p_timeout = NULL;
483         mtime_t         i_interval = 0;
484
485         p_timeout = vlc_array_item_at_index( p_sys->p_timeouts, i );
486         i_interval = dbus_timeout_get_interval( p_timeout ) * 1000; /* µs */
487         p_info = (timeout_info_t*) dbus_timeout_get_data( p_timeout );
488
489         p_info->i_remaining -= __MAX( 0, i_loop_interval ) % i_interval;
490
491         if( !dbus_timeout_get_enabled( p_timeout ) )
492             continue;
493
494         /* The correct poll timeout value is the shortest one
495          * in the dbus timeouts list */
496         i_next_timeout = __MIN( i_next_timeout,
497                                 __MAX( 0, p_info->i_remaining ) );
498     }
499
500     /* next timeout in milliseconds */
501     return i_next_timeout / 1000;
502 }
503
504 /**
505  * ProcessEvents() reacts to a list of events originating from other VLC threads
506  *
507  * This function must be called with p_sys->lock unlocked
508  *
509  * @param intf_thread_t *p_intf This interface thread state
510  * @param callback_info_t *p_events the list of events to process
511  */
512 static void ProcessEvents( intf_thread_t *p_intf,
513                            callback_info_t **p_events, int i_events )
514 {
515     playlist_t *p_playlist = p_intf->p_sys->p_playlist;
516     bool        b_can_play = p_intf->p_sys->b_can_play;
517
518     vlc_dictionary_t player_properties, tracklist_properties, root_properties;
519     vlc_dictionary_init( &player_properties,    0 );
520     vlc_dictionary_init( &tracklist_properties, 0 );
521     vlc_dictionary_init( &root_properties,      0 );
522
523     for( int i = 0; i < i_events; i++ )
524     {
525         switch( p_events[i]->signal )
526         {
527         case SIGNAL_ITEM_CURRENT:
528             TrackChange( p_intf );
529
530             // rate depends on current item
531             if( !vlc_dictionary_has_key( &player_properties, "Rate" ) )
532                 vlc_dictionary_insert( &player_properties, "Rate", NULL );
533
534             vlc_dictionary_insert( &player_properties, "Metadata", NULL );
535             break;
536         case SIGNAL_INTF_CHANGE:
537         case SIGNAL_PLAYLIST_ITEM_APPEND:
538         case SIGNAL_PLAYLIST_ITEM_DELETED:
539             PL_LOCK;
540             b_can_play = playlist_CurrentSize( p_playlist ) > 0;
541             PL_UNLOCK;
542
543             if( b_can_play != p_intf->p_sys->b_can_play )
544             {
545                 p_intf->p_sys->b_can_play = b_can_play;
546                 vlc_dictionary_insert( &player_properties, "CanPlay", NULL );
547             }
548
549             if( !vlc_dictionary_has_key( &tracklist_properties, "Tracks" ) )
550                 vlc_dictionary_insert( &tracklist_properties, "Tracks", NULL );
551             break;
552         case SIGNAL_VOLUME_MUTED:
553         case SIGNAL_VOLUME_CHANGE:
554             vlc_dictionary_insert( &player_properties, "Volume", NULL );
555             break;
556         case SIGNAL_RANDOM:
557             vlc_dictionary_insert( &player_properties, "Shuffle", NULL );
558             break;
559         case SIGNAL_FULLSCREEN:
560             vlc_dictionary_insert( &root_properties, "Fullscreen", NULL );
561             break;
562         case SIGNAL_REPEAT:
563         case SIGNAL_LOOP:
564             vlc_dictionary_insert( &player_properties, "LoopStatus", NULL );
565             break;
566         case SIGNAL_STATE:
567             vlc_dictionary_insert( &player_properties, "PlaybackStatus", NULL );
568             break;
569         case SIGNAL_RATE:
570             vlc_dictionary_insert( &player_properties, "Rate", NULL );
571             break;
572         case SIGNAL_INPUT_METADATA:
573         {
574             input_thread_t *p_input = playlist_CurrentInput( p_playlist );
575             input_item_t   *p_item;
576             if( p_input )
577             {
578                 p_item = input_GetItem( p_input );
579                 vlc_object_release( p_input );
580
581                 if( p_item )
582                     vlc_dictionary_insert( &player_properties,
583                                            "Metadata", NULL );
584             }
585             break;
586         }
587         case SIGNAL_CAN_SEEK:
588             vlc_dictionary_insert( &player_properties, "CanSeek", NULL );
589             break;
590         case SIGNAL_CAN_PAUSE:
591             vlc_dictionary_insert( &player_properties, "CanPause", NULL );
592             break;
593         case SIGNAL_SEEK:
594         {
595             input_thread_t *p_input;
596             input_item_t *p_item;
597             p_input = playlist_CurrentInput( p_intf->p_sys->p_playlist );
598             if( p_input )
599             {
600                 p_item = input_GetItem( p_input );
601                 vlc_object_release( p_input );
602
603                 if( p_item && ( p_item->i_id == p_events[i]->i_item ) )
604                     SeekedEmit( p_intf );
605             }
606             break;
607         }
608         default:
609             assert(0);
610         }
611         free( p_events[i] );
612     }
613
614     if( vlc_dictionary_keys_count( &player_properties ) )
615         PlayerPropertiesChangedEmit( p_intf, &player_properties );
616
617     if( vlc_dictionary_keys_count( &tracklist_properties ) )
618         TrackListPropertiesChangedEmit( p_intf, &tracklist_properties );
619
620     if( vlc_dictionary_keys_count( &root_properties ) )
621         RootPropertiesChangedEmit( p_intf, &root_properties );
622
623     vlc_dictionary_clear( &player_properties,    NULL, NULL );
624     vlc_dictionary_clear( &tracklist_properties, NULL, NULL );
625     vlc_dictionary_clear( &root_properties,      NULL, NULL );
626 }
627
628 /**
629  * ProcessWatches() handles a list of dbus watches after poll() has returned
630  *
631  * This function must be called with p_sys->lock unlocked
632  *
633  * @param intf_thread_t *p_intf This interface thread state
634  * @param DBusWatch **p_watches The list of dbus watches to process
635  * @param int i_watches The size of the p_watches array
636  * @param struct pollfd *p_fds The result of a poll() call
637  * @param int i_fds The number of file descriptors processed by poll()
638  */
639 static void ProcessWatches( intf_thread_t *p_intf,
640                             DBusWatch **p_watches, int i_watches,
641                             struct pollfd *p_fds,  int i_fds )
642 {
643     VLC_UNUSED(p_intf);
644
645     /* Process watches */
646     for( int i = 0; i < i_watches; i++ )
647     {
648         DBusWatch *p_watch = p_watches[i];
649         if( !dbus_watch_get_enabled( p_watch ) )
650             continue;
651
652         for( int j = 0; j < i_fds; j++ )
653         {
654             if( p_fds[j].fd != dbus_watch_get_unix_fd( p_watch ) )
655                 continue;
656
657             int i_flags   = 0;
658             int i_revents = p_fds[j].revents;
659
660             if( i_revents & POLLIN )
661                 i_flags |= DBUS_WATCH_READABLE;
662
663             if( i_revents & POLLOUT )
664                 i_flags |= DBUS_WATCH_WRITABLE;
665
666             if( i_revents & POLLERR )
667                 i_flags |= DBUS_WATCH_ERROR;
668
669             if( i_revents & POLLHUP )
670                 i_flags |= DBUS_WATCH_HANGUP;
671
672             if( i_flags )
673                 dbus_watch_handle( p_watch, i_flags );
674         }
675     }
676 }
677
678 /**
679  * ProcessTimeouts() handles DBus timeouts
680  *
681  * This function must be called with p_sys->lock locked
682  *
683  * @param intf_thread_t *p_intf This interface thread state
684  * @param DBusTimeout **p_timeouts List of timeouts to process
685  * @param int i_timeouts Size of p_timeouts
686  */
687 static void ProcessTimeouts( intf_thread_t *p_intf,
688                              DBusTimeout  **p_timeouts, int i_timeouts )
689 {
690     VLC_UNUSED( p_intf );
691
692     for( int i = 0; i < i_timeouts; i++ )
693     {
694         timeout_info_t *p_info = NULL;
695
696         p_info = (timeout_info_t*) dbus_timeout_get_data( p_timeouts[i] );
697
698         if( !dbus_timeout_get_enabled( p_info->p_timeout ) )
699             continue;
700
701         if( p_info->i_remaining > 0 )
702             continue;
703
704         dbus_timeout_handle( p_info->p_timeout );
705         p_info->i_remaining = dbus_timeout_get_interval( p_info->p_timeout );
706     }
707 }
708
709 /**
710  * DispatchDBusMessages() dispatches incoming dbus messages
711  * (indirectly invoking the callbacks), then it sends outgoing
712  * messages which needs to be sent on the bus (method replies and signals)
713  *
714  * This function must be called with p_sys->lock unlocked
715  *
716  * @param intf_thread_t *p_intf This interface thread state
717  */
718 static void DispatchDBusMessages( intf_thread_t *p_intf )
719 {
720     DBusDispatchStatus status;
721     intf_sys_t *p_sys = p_intf->p_sys;
722
723     /* Dispatch incoming messages */
724     status = dbus_connection_get_dispatch_status( p_sys->p_conn );
725     while( status != DBUS_DISPATCH_COMPLETE )
726     {
727         dbus_connection_dispatch( p_sys->p_conn );
728         status = dbus_connection_get_dispatch_status( p_sys->p_conn );
729     }
730
731     /* Send outgoing data */
732     if( dbus_connection_has_messages_to_send( p_sys->p_conn ) )
733         dbus_connection_flush( p_sys->p_conn );
734 }
735
736 /**
737  * MPRISEntryPoint() routes incoming messages to their respective interface
738  * implementation.
739  *
740  * This function is called during dbus_connection_dispatch()
741  */
742 static DBusHandlerResult
743 MPRISEntryPoint ( DBusConnection *p_conn, DBusMessage *p_from, void *p_this )
744 {
745     const char *psz_target_interface;
746     const char *psz_interface = dbus_message_get_interface( p_from );
747     const char *psz_method    = dbus_message_get_member( p_from );
748
749     DBusError error;
750
751     if( psz_interface && strcmp( psz_interface, DBUS_INTERFACE_PROPERTIES ) )
752         psz_target_interface = psz_interface;
753
754     else
755     {
756         dbus_error_init( &error );
757         dbus_message_get_args( p_from, &error,
758                                DBUS_TYPE_STRING, &psz_target_interface,
759                                DBUS_TYPE_INVALID );
760
761         if( dbus_error_is_set( &error ) )
762         {
763             msg_Err( (vlc_object_t*) p_this, "D-Bus error on %s.%s: %s",
764                                              psz_interface, psz_method,
765                                              error.message );
766             dbus_error_free( &error );
767             return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
768         }
769     }
770
771     if( !strcmp( psz_target_interface, DBUS_INTERFACE_INTROSPECTABLE ) )
772         return handle_introspect( p_conn, p_from, p_this );
773
774     if( !strcmp( psz_target_interface, DBUS_MPRIS_ROOT_INTERFACE ) )
775         return handle_root( p_conn, p_from, p_this );
776
777     if( !strcmp( psz_target_interface, DBUS_MPRIS_PLAYER_INTERFACE ) )
778         return handle_player( p_conn, p_from, p_this );
779
780     if( !strcmp( psz_target_interface, DBUS_MPRIS_TRACKLIST_INTERFACE ) )
781         return handle_tracklist( p_conn, p_from, p_this );
782
783     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
784 }
785
786 /*****************************************************************************
787  * Run: main loop
788  *****************************************************************************/
789
790 static void *Run( void *data )
791 {
792     intf_thread_t *p_intf = data;
793     intf_sys_t    *p_sys = p_intf->p_sys;
794     mtime_t        i_last_run = mdate();
795
796     for( ;; )
797     {
798         int canc = vlc_savecancel();
799         vlc_mutex_lock( &p_sys->lock );
800
801         int i_watches = vlc_array_count( p_sys->p_watches );
802         struct pollfd fds[i_watches];
803         memset(fds, 0, sizeof fds);
804
805         int i_fds = GetPollFds( p_intf, fds );
806
807         mtime_t i_now = mdate(), i_loop_interval = i_now - i_last_run;
808
809         int i_next_timeout = UpdateTimeouts( p_intf, i_loop_interval );
810         i_last_run = i_now;
811
812         vlc_mutex_unlock( &p_sys->lock );
813
814         /* thread cancellation is allowed while the main loop sleeps */
815         vlc_restorecancel( canc );
816
817         int i_pollres = poll( fds, i_fds, i_next_timeout );
818
819         canc = vlc_savecancel();
820
821         if( -1 == i_pollres )
822         { /* XXX: What should we do when poll() fails ? */
823             msg_Err( p_intf, "poll() failed: %s", vlc_strerror_c(errno) );
824             vlc_restorecancel( canc );
825             continue;
826         }
827
828         /* Was the main loop woken up manually ? */
829         if( 0 < i_pollres && ( fds[0].revents & POLLIN ) )
830         {
831             char buf;
832             (void)read( fds[0].fd, &buf, 1 );
833         }
834
835         /* We need to lock the mutex while building lists of events,
836          * timeouts and watches to process but we can't keep the lock while
837          * processing them, or else we risk a deadlock:
838          *
839          * The signal functions could lock mutex X while p_events is locked;
840          * While some other function in vlc (playlist) might lock mutex X
841          * and then set a variable which would call AllCallback(), which itself
842          * needs to lock p_events to add a new event.
843          */
844         vlc_mutex_lock( &p_intf->p_sys->lock );
845
846         /* Get the list of timeouts to process */
847         unsigned int i_timeouts = vlc_array_count( p_sys->p_timeouts );
848         DBusTimeout *p_timeouts[i_timeouts];
849         for( unsigned int i = 0; i < i_timeouts; i++ )
850         {
851             p_timeouts[i] = vlc_array_item_at_index( p_sys->p_timeouts, i );
852         }
853
854         /* Get the list of watches to process */
855         i_watches = vlc_array_count( p_sys->p_watches );
856         DBusWatch *p_watches[i_watches];
857         for( int i = 0; i < i_watches; i++ )
858         {
859             p_watches[i] = vlc_array_item_at_index( p_sys->p_watches, i );
860         }
861
862         /* Get the list of events to process */
863         int i_events = vlc_array_count( p_intf->p_sys->p_events );
864         callback_info_t* p_info[i_events];
865         for( int i = i_events - 1; i >= 0; i-- )
866         {
867             p_info[i] = vlc_array_item_at_index( p_intf->p_sys->p_events, i );
868             vlc_array_remove( p_intf->p_sys->p_events, i );
869         }
870
871         /* now we can release the lock and process what's pending */
872         vlc_mutex_unlock( &p_intf->p_sys->lock );
873
874         ProcessEvents( p_intf, p_info, i_events );
875         ProcessWatches( p_intf, p_watches, i_watches, fds, i_fds );
876
877         ProcessTimeouts( p_intf, p_timeouts, i_timeouts );
878         DispatchDBusMessages( p_intf );
879
880         vlc_restorecancel( canc );
881     }
882     assert(0);
883 }
884
885 static void   wakeup_main_loop( void *p_data )
886 {
887     intf_thread_t *p_intf = (intf_thread_t*) p_data;
888
889     if( !write( p_intf->p_sys->p_pipe_fds[PIPE_IN], "\0", 1 ) )
890         msg_Err( p_intf, "Could not wake up the main loop: %s",
891                  vlc_strerror_c(errno) );
892 }
893
894 /* Flls a callback_info_t data structure in response
895  * to an "intf-event" input event.
896  *
897  * @warning This function executes in the input thread.
898  *
899  * @return VLC_SUCCESS on success, VLC_E* on error.
900  */
901 static int InputCallback( vlc_object_t *p_this, const char *psz_var,
902                           vlc_value_t oldval, vlc_value_t newval, void *data )
903 {
904     input_thread_t *p_input = (input_thread_t *)p_this;
905     intf_thread_t *p_intf = data;
906     intf_sys_t *p_sys = p_intf->p_sys;
907
908     dbus_int32_t i_state = PLAYBACK_STATE_INVALID;
909
910     callback_info_t *p_info = calloc( 1, sizeof( callback_info_t ) );
911     if( unlikely(p_info == NULL) )
912         return VLC_ENOMEM;
913
914     switch( newval.i_int )
915     {
916         case INPUT_EVENT_DEAD:
917         case INPUT_EVENT_ABORT:
918             i_state = PLAYBACK_STATE_STOPPED;
919             break;
920         case INPUT_EVENT_STATE:
921             switch( var_GetInteger( p_input, "state" ) )
922             {
923                 case OPENING_S:
924                 case PLAYING_S:
925                     i_state = PLAYBACK_STATE_PLAYING;
926                     break;
927                 case PAUSE_S:
928                     i_state = PLAYBACK_STATE_PAUSED;
929                     break;
930                 default:
931                     i_state = PLAYBACK_STATE_STOPPED;
932             }
933             break;
934         case INPUT_EVENT_ITEM_META:
935             p_info->signal = SIGNAL_INPUT_METADATA;
936             break;
937         case INPUT_EVENT_RATE:
938             p_info->signal = SIGNAL_RATE;
939             break;
940         case INPUT_EVENT_POSITION:
941         {
942             mtime_t i_now = mdate(), i_pos, i_projected_pos, i_interval;
943             float f_current_rate;
944
945             /* Detect seeks
946              * XXX: This is way more convoluted than it should be... */
947             i_pos = var_GetTime( p_input, "time" );
948
949             if( !p_intf->p_sys->i_last_input_pos_event ||
950                 !( var_GetInteger( p_input, "state" ) == PLAYING_S ) )
951             {
952                 p_intf->p_sys->i_last_input_pos_event = i_now;
953                 p_intf->p_sys->i_last_input_pos = i_pos;
954                 break;
955             }
956
957             f_current_rate = var_GetFloat( p_input, "rate" );
958             i_interval = ( i_now - p_intf->p_sys->i_last_input_pos_event );
959
960             i_projected_pos = p_intf->p_sys->i_last_input_pos +
961                 ( i_interval * f_current_rate );
962
963             p_intf->p_sys->i_last_input_pos_event = i_now;
964             p_intf->p_sys->i_last_input_pos = i_pos;
965
966             if( llabs( i_pos - i_projected_pos ) < SEEK_THRESHOLD )
967                 break;
968
969             p_info->signal = SIGNAL_SEEK;
970             p_info->i_item = input_GetItem( p_input )->i_id;
971             break;
972         }
973         default:
974             free( p_info );
975             return VLC_SUCCESS; /* don't care */
976     }
977
978     vlc_mutex_lock( &p_sys->lock );
979     if( i_state != PLAYBACK_STATE_INVALID &&
980         i_state != p_sys->i_playing_state )
981     {
982         p_sys->i_playing_state = i_state;
983         p_info->signal = SIGNAL_STATE;
984     }
985     if( p_info->signal )
986         vlc_array_append( p_intf->p_sys->p_events, p_info );
987     else
988         free( p_info );
989     vlc_mutex_unlock( &p_intf->p_sys->lock );
990
991     wakeup_main_loop( p_intf );
992
993     (void)psz_var;
994     (void)oldval;
995     return VLC_SUCCESS;
996 }
997
998 // Get all the callbacks
999 static int AllCallback( vlc_object_t *p_this, const char *psz_var,
1000                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
1001 {
1002     intf_thread_t *p_intf = p_data;
1003     callback_info_t info = { .signal = SIGNAL_NONE };
1004
1005     // Wich event is it ?
1006     if( !strcmp( "activity", psz_var ) )
1007         info.signal = SIGNAL_ITEM_CURRENT;
1008     else if( !strcmp( "volume", psz_var ) )
1009     {
1010         if( oldval.f_float != newval.f_float )
1011             info.signal = SIGNAL_VOLUME_CHANGE;
1012     }
1013     else if( !strcmp( "mute", psz_var ) )
1014     {
1015         if( oldval.b_bool != newval.b_bool )
1016             info.signal = SIGNAL_VOLUME_MUTED;
1017     }
1018     else if( !strcmp( "intf-change", psz_var ) )
1019         info.signal = SIGNAL_INTF_CHANGE;
1020     else if( !strcmp( "playlist-item-append", psz_var ) )
1021     {
1022         info.signal = SIGNAL_PLAYLIST_ITEM_APPEND;
1023         info.i_node = ((playlist_add_t*)newval.p_address)->i_node;
1024     }
1025     else if( !strcmp( "playlist-item-deleted", psz_var ) )
1026         info.signal = SIGNAL_PLAYLIST_ITEM_DELETED;
1027     else if( !strcmp( "random", psz_var ) )
1028         info.signal = SIGNAL_RANDOM;
1029     else if( !strcmp( "fullscreen", psz_var ) )
1030         info.signal = SIGNAL_FULLSCREEN;
1031     else if( !strcmp( "repeat", psz_var ) )
1032         info.signal = SIGNAL_REPEAT;
1033     else if( !strcmp( "loop", psz_var ) )
1034         info.signal = SIGNAL_LOOP;
1035     else if( !strcmp( "can-seek", psz_var ) )
1036         info.signal = SIGNAL_CAN_SEEK;
1037     else if( !strcmp( "can-pause", psz_var ) )
1038         info.signal = SIGNAL_CAN_PAUSE;
1039     else
1040         assert(0);
1041
1042     if( info.signal == SIGNAL_NONE )
1043         return VLC_SUCCESS;
1044
1045     callback_info_t *p_info = malloc( sizeof( *p_info ) );
1046     if( unlikely(p_info == NULL) )
1047         return VLC_ENOMEM;
1048
1049     // Append the event
1050     *p_info = info;
1051     vlc_mutex_lock( &p_intf->p_sys->lock );
1052     vlc_array_append( p_intf->p_sys->p_events, p_info );
1053     vlc_mutex_unlock( &p_intf->p_sys->lock );
1054
1055     wakeup_main_loop( p_intf );
1056     (void) p_this;
1057     return VLC_SUCCESS;
1058 }
1059
1060 /*****************************************************************************
1061  * TrackChange: callback on playlist "activity"
1062  *****************************************************************************/
1063 static int TrackChange( intf_thread_t *p_intf )
1064 {
1065     intf_sys_t          *p_sys      = p_intf->p_sys;
1066     playlist_t          *p_playlist = p_sys->p_playlist;
1067     input_thread_t      *p_input    = NULL;
1068     input_item_t        *p_item     = NULL;
1069
1070     if( p_intf->p_sys->b_dead )
1071         return VLC_SUCCESS;
1072
1073     if( p_sys->p_input )
1074     {
1075         var_DelCallback( p_sys->p_input, "intf-event", InputCallback, p_intf );
1076         var_DelCallback( p_sys->p_input, "can-pause", AllCallback, p_intf );
1077         var_DelCallback( p_sys->p_input, "can-seek", AllCallback, p_intf );
1078         vlc_object_release( p_sys->p_input );
1079         p_sys->p_input = NULL;
1080     }
1081
1082     p_sys->b_meta_read = false;
1083
1084     p_input = playlist_CurrentInput( p_playlist );
1085     if( !p_input )
1086     {
1087         return VLC_SUCCESS;
1088     }
1089
1090     p_item = input_GetItem( p_input );
1091     if( !p_item )
1092     {
1093         vlc_object_release( p_input );
1094         return VLC_EGENERIC;
1095     }
1096
1097     if( input_item_IsPreparsed( p_item ) )
1098         p_sys->b_meta_read = true;
1099
1100     p_sys->p_input = p_input;
1101     var_AddCallback( p_input, "intf-event", InputCallback, p_intf );
1102     var_AddCallback( p_input, "can-pause", AllCallback, p_intf );
1103     var_AddCallback( p_input, "can-seek", AllCallback, p_intf );
1104
1105     return VLC_SUCCESS;
1106 }
1107
1108 /**
1109  * DemarshalSetPropertyValue() extracts the new property value from a
1110  * org.freedesktop.DBus.Properties.Set method call message.
1111  *
1112  * @return int VLC_SUCCESS on success
1113  * @param DBusMessage *p_msg a org.freedesktop.DBus.Properties.Set method call
1114  * @param void *p_arg placeholder for the demarshalled value
1115  */
1116 int DemarshalSetPropertyValue( DBusMessage *p_msg, void *p_arg )
1117 {
1118     int  i_type;
1119     bool b_valid_input = FALSE;
1120     DBusMessageIter in_args, variant;
1121     dbus_message_iter_init( p_msg, &in_args );
1122
1123     do
1124     {
1125         i_type = dbus_message_iter_get_arg_type( &in_args );
1126         if( DBUS_TYPE_VARIANT == i_type )
1127         {
1128             dbus_message_iter_recurse( &in_args, &variant );
1129             dbus_message_iter_get_basic( &variant, p_arg );
1130             b_valid_input = TRUE;
1131         }
1132     } while( dbus_message_iter_next( &in_args ) );
1133
1134     return b_valid_input ? VLC_SUCCESS : VLC_EGENERIC;
1135 }
1136
1137 /*****************************************************************************
1138  * GetInputMeta: Fill a DBusMessage with the given input item metadata
1139  *****************************************************************************/
1140
1141 #define ADD_META( entry, type, data ) \
1142     if( data ) { \
1143         dbus_message_iter_open_container( &dict, DBUS_TYPE_DICT_ENTRY, \
1144                 NULL, &dict_entry ); \
1145         dbus_message_iter_append_basic( &dict_entry, DBUS_TYPE_STRING, \
1146                 &ppsz_meta_items[entry] ); \
1147         dbus_message_iter_open_container( &dict_entry, DBUS_TYPE_VARIANT, \
1148                 type##_AS_STRING, &variant ); \
1149         dbus_message_iter_append_basic( &variant, \
1150                 type, \
1151                 & data ); \
1152         dbus_message_iter_close_container( &dict_entry, &variant ); \
1153         dbus_message_iter_close_container( &dict, &dict_entry ); }
1154
1155 #define ADD_VLC_META_STRING( entry, item ) \
1156     { \
1157         char * psz = input_item_Get##item( p_input );\
1158         ADD_META( entry, DBUS_TYPE_STRING, \
1159                   psz ); \
1160         free( psz ); \
1161     }
1162
1163 #define ADD_META_SINGLETON_STRING_LIST( entry, item ) \
1164     { \
1165         char * psz = input_item_Get##item( p_input );\
1166         if( psz ) { \
1167             dbus_message_iter_open_container( &dict, DBUS_TYPE_DICT_ENTRY, \
1168                     NULL, &dict_entry ); \
1169             dbus_message_iter_append_basic( &dict_entry, DBUS_TYPE_STRING, \
1170                     &ppsz_meta_items[entry] ); \
1171             dbus_message_iter_open_container( &dict_entry, DBUS_TYPE_VARIANT, \
1172                     "as", &variant ); \
1173             dbus_message_iter_open_container( &variant, DBUS_TYPE_ARRAY, "s", \
1174                                               &list ); \
1175             dbus_message_iter_append_basic( &list, \
1176                     DBUS_TYPE_STRING, \
1177                     &psz ); \
1178             dbus_message_iter_close_container( &variant, &list ); \
1179             dbus_message_iter_close_container( &dict_entry, &variant ); \
1180             dbus_message_iter_close_container( &dict, &dict_entry ); \
1181         } \
1182         free( psz ); \
1183     }
1184
1185 int GetInputMeta( input_item_t* p_input,
1186                   DBusMessageIter *args )
1187 {
1188     DBusMessageIter dict, dict_entry, variant, list;
1189     /** The duration of the track can be expressed in second, milli-seconds and
1190         µ-seconds */
1191     dbus_int64_t i_mtime = input_item_GetDuration( p_input );
1192     dbus_uint32_t i_time = i_mtime / 1000000;
1193     dbus_int64_t i_length = i_mtime / 1000;
1194     char *psz_trackid;
1195
1196     if( -1 == asprintf( &psz_trackid, MPRIS_TRACKID_FORMAT, p_input->i_id ) )
1197         return VLC_ENOMEM;
1198
1199     const char* ppsz_meta_items[] =
1200     {
1201     "mpris:trackid", "xesam:url", "xesam:title", "xesam:artist", "xesam:album",
1202     "xesam:tracknumber", "vlc:time", "mpris:length", "xesam:genre",
1203     "xesam:userRating", "xesam:contentCreated", "mpris:artUrl", "mb:trackId",
1204     "vlc:audio-bitrate", "vlc:audio-samplerate", "vlc:video-bitrate",
1205     "vlc:audio-codec", "vlc:copyright", "xesam:comment", "vlc:encodedby",
1206     "language", "vlc:length", "vlc:nowplaying", "vlc:publisher", "vlc:setting",
1207     "status", "vlc:url", "vlc:video-codec"
1208     };
1209
1210     dbus_message_iter_open_container( args, DBUS_TYPE_ARRAY, "{sv}", &dict );
1211
1212     ADD_META( 0, DBUS_TYPE_OBJECT_PATH, psz_trackid );
1213     ADD_VLC_META_STRING( 1,  URI );
1214     ADD_VLC_META_STRING( 2,  Title );
1215     ADD_META_SINGLETON_STRING_LIST( 3,  Artist );
1216     ADD_VLC_META_STRING( 4,  Album );
1217     ADD_VLC_META_STRING( 5,  TrackNum );
1218     ADD_META( 6, DBUS_TYPE_UINT32, i_time );
1219     ADD_META( 7, DBUS_TYPE_INT64,  i_mtime );
1220     ADD_META_SINGLETON_STRING_LIST( 8,  Genre );
1221     //ADD_META( 9, DBUS_TYPE_DOUBLE, rating );
1222     ADD_VLC_META_STRING( 10, Date ); // this is supposed to be in ISO 8601 extended format
1223     ADD_VLC_META_STRING( 11, ArtURL );
1224     ADD_VLC_META_STRING( 12, TrackID );
1225
1226     ADD_VLC_META_STRING( 17, Copyright );
1227     ADD_META_SINGLETON_STRING_LIST( 18, Description );
1228     ADD_VLC_META_STRING( 19, EncodedBy );
1229     ADD_VLC_META_STRING( 20, Language );
1230     ADD_META( 21, DBUS_TYPE_INT64, i_length );
1231     ADD_VLC_META_STRING( 22, NowPlaying );
1232     ADD_VLC_META_STRING( 23, Publisher );
1233     ADD_VLC_META_STRING( 24, Setting );
1234     ADD_VLC_META_STRING( 25, URL );
1235
1236     free( psz_trackid );
1237
1238     vlc_mutex_lock( &p_input->lock );
1239     if( p_input->p_meta )
1240     {
1241         int i_status = vlc_meta_GetStatus( p_input->p_meta );
1242         ADD_META( 23, DBUS_TYPE_INT32, i_status );
1243     }
1244     vlc_mutex_unlock( &p_input->lock );
1245
1246     dbus_message_iter_close_container( args, &dict );
1247     return VLC_SUCCESS;
1248 }
1249
1250 int AddProperty( intf_thread_t *p_intf,
1251                  DBusMessageIter *p_container,
1252                  const char* psz_property_name,
1253                  const char* psz_signature,
1254                  int (*pf_marshaller) (intf_thread_t*, DBusMessageIter*) )
1255 {
1256     DBusMessageIter entry, v;
1257
1258     if( !dbus_message_iter_open_container( p_container,
1259                                            DBUS_TYPE_DICT_ENTRY, NULL,
1260                                            &entry ) )
1261         return VLC_ENOMEM;
1262
1263     if( !dbus_message_iter_append_basic( &entry,
1264                                          DBUS_TYPE_STRING,
1265                                          &psz_property_name ) )
1266         return VLC_ENOMEM;
1267
1268     if( !dbus_message_iter_open_container( &entry,
1269                                            DBUS_TYPE_VARIANT, psz_signature,
1270                                            &v ) )
1271         return VLC_ENOMEM;
1272
1273     if( VLC_SUCCESS != pf_marshaller( p_intf, &v ) )
1274         return VLC_ENOMEM;
1275
1276     if( !dbus_message_iter_close_container( &entry, &v) )
1277         return VLC_ENOMEM;
1278
1279     if( !dbus_message_iter_close_container( p_container, &entry ) )
1280         return VLC_ENOMEM;
1281
1282     return VLC_SUCCESS;
1283 }
1284
1285 #undef ADD_META
1286 #undef ADD_VLC_META_STRING
1287
1288