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