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