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