qml: fix scrollbar in NetworkHomeDisplay
[vlc.git] / modules / control / dbus / dbus.c
1 /*****************************************************************************
2  * dbus.c : D-Bus control interface
3  *****************************************************************************
4  * Copyright © 2006-2008 Rafaël Carré
5  * Copyright © 2007-2012 Mirsal Ennaime
6  * Copyright © 2009-2012 The VideoLAN team
7  * Copyright © 2013      Alex Merry
8  *
9  * Authors:    Rafaël Carré <funman at videolanorg>
10  *             Mirsal Ennaime <mirsal at mirsal fr>
11  *             Alex Merry <dev at randomguy3 me uk>
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26  *****************************************************************************/
27
28 /*
29  * D-Bus Specification:
30  *      http://dbus.freedesktop.org/doc/dbus-specification.html
31  * D-Bus low-level C API (libdbus)
32  *      http://dbus.freedesktop.org/doc/dbus/api/html/index.html
33  *  extract:
34  *   "If you use this low-level API directly, you're signing up for some pain."
35  *
36  * MPRIS Specification version 1.0
37  *      http://wiki.xmms2.xmms.se/index.php/MPRIS
38  */
39
40 /*****************************************************************************
41  * Preamble
42  *****************************************************************************/
43
44 #ifdef HAVE_CONFIG_H
45 # include "config.h"
46 #endif
47
48 #include <dbus/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 #define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
56 #include <vlc_common.h>
57 #include <vlc_plugin.h>
58 #include <vlc_interface.h>
59 #include <vlc_meta.h>
60 #include <vlc_tick.h>
61 #include <vlc_fs.h>
62
63 #include <assert.h>
64 #include <limits.h>
65 #include <string.h>
66
67 #include <poll.h>
68 #include <errno.h>
69 #include <unistd.h>
70
71 #define DBUS_MPRIS_BUS_NAME "org.mpris.MediaPlayer2.vlc"
72 #define DBUS_INSTANCE_ID_PREFIX "instance"
73
74 #define SEEK_THRESHOLD 1000 /* µsec */
75 #define EVENTS_DELAY INT64_C(100000) /* 100 ms */
76
77 /*****************************************************************************
78  * Local prototypes.
79  *****************************************************************************/
80
81 static DBusHandlerResult
82 MPRISEntryPoint ( DBusConnection *p_conn, DBusMessage *p_from, void *p_this );
83
84 static const DBusObjectPathVTable dbus_mpris_vtable = {
85         NULL, MPRISEntryPoint, /* handler function */
86         NULL, NULL, NULL, NULL
87 };
88
89 typedef struct
90 {
91     int signal;
92 } callback_info_t;
93
94 enum
95 {
96     PIPE_OUT = 0,
97     PIPE_IN  = 1
98 };
99
100 static int  Open    ( vlc_object_t * );
101 static void Close   ( vlc_object_t * );
102 static void *Run    ( void * );
103
104 static int TrackChange( intf_thread_t * );
105
106 static dbus_bool_t add_timeout(DBusTimeout *, void *);
107 static void remove_timeout(DBusTimeout *, void *);
108 static void toggle_timeout(DBusTimeout *, void *);
109
110 static dbus_bool_t add_watch   ( DBusWatch *p_watch, void *p_data );
111 static void remove_watch    ( DBusWatch *p_watch, 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 void ProcessEvents  ( intf_thread_t    *p_intf,
117                              callback_info_t **p_events,
118                              int               i_events );
119
120 static void ProcessWatches ( intf_thread_t    *p_intf,
121                              DBusWatch       **p_watches,
122                              int               i_watches,
123                              struct pollfd    *p_fds,
124                              int               i_fds );
125
126 static void DispatchDBusMessages( intf_thread_t *p_intf );
127
128
129 static void playlist_on_items_added(vlc_playlist_t *,
130                                     size_t, vlc_playlist_item_t *const [],
131                                     size_t, void *);
132 static void playlist_on_items_removed(vlc_playlist_t *,
133                                       size_t, size_t, void *);
134 static void playlist_on_playback_repeat_changed(vlc_playlist_t *,
135                                                 enum vlc_playlist_playback_repeat,
136                                                 void *);
137 static void playlist_on_playback_order_changed(vlc_playlist_t *,
138                                                enum vlc_playlist_playback_order,
139                                                void *);
140 static void playlist_on_current_index_changed(vlc_playlist_t *,
141                                               ssize_t, void *);
142
143 static void player_on_state_changed(vlc_player_t *,
144                                     enum vlc_player_state, void *);
145 static void player_on_error_changed(vlc_player_t *,
146                                     enum vlc_player_error, void *);
147 static void player_on_rate_changed(vlc_player_t *, float, void *);
148 static void player_on_capabilities_changed(vlc_player_t *, int, int, void *);
149 static void player_on_media_meta_changed(vlc_player_t *,
150                                          input_item_t *, void *);
151
152 static void player_aout_on_volume_changed(audio_output_t *, float, void *);
153 static void player_aout_on_mute_changed(audio_output_t *, bool, void *);
154
155 static void player_vout_on_fullscreen_changed(vout_thread_t *, bool, void *);
156 static void player_timer_on_discontinuity(vlc_tick_t system_dae, void *data);
157 static void player_timer_on_update(const struct vlc_player_timer_point *, void *);
158
159 /*****************************************************************************
160  * Module descriptor
161  *****************************************************************************/
162 vlc_module_begin ()
163     set_shortname( N_("DBus"))
164     set_category( CAT_INTERFACE )
165     set_description( N_("D-Bus control interface") )
166     set_capability( "interface", 0 )
167     set_callbacks( Open, Close )
168 vlc_module_end ()
169
170 /*****************************************************************************
171  * Open: initialize interface
172  *****************************************************************************/
173
174 static int Open( vlc_object_t *p_this )
175 {
176     intf_thread_t   *p_intf = (intf_thread_t*)p_this;
177     vlc_object_t *vlc = VLC_OBJECT(vlc_object_instance(p_this));
178     vlc_playlist_t *playlist = NULL;
179
180     /* initialisation of the connection */
181     if( !dbus_threads_init_default() )
182         return VLC_EGENERIC;
183
184     intf_sys_t *p_sys  = calloc( 1, sizeof( intf_sys_t ) );
185     if( unlikely(!p_sys) )
186         return VLC_ENOMEM;
187
188     DBusConnection  *p_conn;
189     p_sys->i_player_caps   = PLAYER_CAPS_NONE;
190     p_sys->i_playing_state = PLAYBACK_STATE_INVALID;
191
192     if( vlc_pipe( p_sys->p_pipe_fds ) )
193     {
194         free( p_sys );
195         msg_Err( p_intf, "Could not create pipe" );
196         return VLC_EGENERIC;
197     }
198
199     DBusError error;
200     dbus_error_init( &error );
201
202     /* connect privately to the session bus
203      * the connection will not be shared with other vlc modules which use dbus,
204      * thus avoiding a whole class of concurrency issues */
205     p_conn = dbus_bus_get_private( DBUS_BUS_SESSION, &error );
206     if( !p_conn )
207     {
208         msg_Err( p_this, "Failed to connect to the D-Bus session daemon: %s",
209                 error.message );
210         dbus_error_free( &error );
211         goto dbus_connection_failure;
212     }
213
214     dbus_connection_set_exit_on_disconnect( p_conn, FALSE );
215
216     /* Register the entry point object path */
217     dbus_connection_register_object_path( p_conn, DBUS_MPRIS_OBJECT_PATH,
218             &dbus_mpris_vtable, p_this );
219
220     /* Try to register org.mpris.MediaPlayer2.vlc */
221     const unsigned bus_flags = DBUS_NAME_FLAG_DO_NOT_QUEUE;
222     var_Create(vlc, "dbus-mpris-name", VLC_VAR_STRING);
223     if( dbus_bus_request_name( p_conn, DBUS_MPRIS_BUS_NAME, bus_flags, NULL )
224                                      != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER )
225     {
226         /* Register an instance-specific well known name of the form
227          * org.mpris.MediaPlayer2.vlc.instanceXXXX where XXXX is the
228          * current Process ID */
229         char unique_service[sizeof( DBUS_MPRIS_BUS_NAME ) +
230                             sizeof( DBUS_INSTANCE_ID_PREFIX ) + 10];
231
232         snprintf( unique_service, sizeof (unique_service),
233                   DBUS_MPRIS_BUS_NAME"."DBUS_INSTANCE_ID_PREFIX"%"PRIu32,
234                   (uint32_t)getpid() );
235
236         if( dbus_bus_request_name( p_conn, unique_service, bus_flags, NULL )
237                                      == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER )
238         {
239             msg_Dbg( p_intf, "listening on dbus as: %s", unique_service );
240             var_SetString(vlc, "dbus-mpris-name", unique_service);
241         }
242     }
243     else
244     {
245         msg_Dbg( p_intf, "listening on dbus as: %s", DBUS_MPRIS_BUS_NAME );
246         var_SetString(vlc, "dbus-mpris-name", DBUS_MPRIS_BUS_NAME);
247     }
248     dbus_connection_flush( p_conn );
249
250     p_intf->p_sys = p_sys;
251     p_sys->p_conn = p_conn;
252     vlc_array_init( &p_sys->events );
253     vlc_array_init( &p_sys->timeouts );
254     vlc_array_init( &p_sys->watches );
255     vlc_mutex_init( &p_sys->lock );
256
257     p_sys->playlist = playlist = vlc_intf_GetMainPlaylist(p_intf);
258     vlc_player_t *player = vlc_playlist_GetPlayer(playlist);
259
260     vlc_playlist_Lock(playlist);
261
262     static struct vlc_playlist_callbacks const playlist_cbs =
263     {
264         .on_items_added = playlist_on_items_added,
265         .on_items_removed = playlist_on_items_removed,
266         .on_playback_repeat_changed = playlist_on_playback_repeat_changed,
267         .on_playback_order_changed = playlist_on_playback_order_changed,
268         .on_current_index_changed = playlist_on_current_index_changed,
269     };
270     p_sys->playlist_listener =
271         vlc_playlist_AddListener(playlist, &playlist_cbs, p_intf, false);
272     if (!p_sys->playlist_listener)
273         goto playlist_listener_failure;
274
275     static struct vlc_player_cbs const player_cbs =
276     {
277         .on_state_changed = player_on_state_changed,
278         .on_error_changed = player_on_error_changed,
279         .on_rate_changed = player_on_rate_changed,
280         .on_capabilities_changed = player_on_capabilities_changed,
281         .on_media_meta_changed = player_on_media_meta_changed,
282     };
283     p_sys->player_listener =
284         vlc_player_AddListener(player, &player_cbs, p_intf);
285     if (!p_sys->player_listener)
286         goto player_listener_failure;
287
288     static struct vlc_player_aout_cbs const player_aout_cbs =
289     {
290         .on_volume_changed = player_aout_on_volume_changed,
291         .on_mute_changed = player_aout_on_mute_changed
292     };
293     p_sys->player_aout_listener =
294         vlc_player_aout_AddListener(player, &player_aout_cbs, p_intf);
295     if (!p_sys->player_aout_listener)
296         goto player_aout_listener_failure;
297
298     static struct vlc_player_vout_cbs const player_vout_cbs =
299     {
300         .on_fullscreen_changed = player_vout_on_fullscreen_changed,
301     };
302     p_sys->player_vout_listener =
303         vlc_player_vout_AddListener(player, &player_vout_cbs, p_intf);
304     if (!p_sys->player_vout_listener)
305         goto player_vout_listener_failure;
306
307     static struct vlc_player_timer_cbs const player_timer_cbs =
308     {
309         .on_update = player_timer_on_update,
310         .on_discontinuity = player_timer_on_discontinuity,
311     };
312     p_sys->player_timer =
313         vlc_player_AddTimer(player, VLC_TICK_INVALID, &player_timer_cbs, p_intf);
314     if (!p_sys->player_timer)
315         goto player_timer_failure;
316
317     vlc_playlist_Unlock(playlist);
318
319     if( !dbus_connection_set_timeout_functions( p_conn,
320                                                 add_timeout,
321                                                 remove_timeout,
322                                                 toggle_timeout,
323                                                 p_intf, NULL ) )
324         goto late_failure;
325
326     if( !dbus_connection_set_watch_functions( p_conn,
327                                               add_watch,
328                                               remove_watch,
329                                               watch_toggled,
330                                               p_intf, NULL ) )
331         goto late_failure;
332
333     if( vlc_clone( &p_sys->thread, Run, p_intf, VLC_THREAD_PRIORITY_LOW ) )
334         goto late_failure;
335
336     return VLC_SUCCESS;
337
338 late_failure:
339     vlc_playlist_Lock(playlist);
340     vlc_player_RemoveTimer(player, p_sys->player_timer);
341 player_timer_failure:
342     vlc_player_vout_RemoveListener(player, p_sys->player_vout_listener);
343 player_vout_listener_failure:
344     vlc_player_aout_RemoveListener(player, p_sys->player_aout_listener);
345 player_aout_listener_failure:
346     vlc_player_RemoveListener(player, p_sys->player_listener);
347 player_listener_failure:
348     vlc_playlist_RemoveListener(playlist, p_sys->playlist_listener);
349 playlist_listener_failure:
350     vlc_playlist_Unlock(playlist);
351
352     var_Destroy(vlc, "dbus-mpris-name");
353     /* The dbus connection is private,
354      * so we are responsible for closing it
355      * XXX: Does this make sense when OOM ? */
356     dbus_connection_close( p_sys->p_conn );
357     dbus_connection_unref( p_conn );
358
359 dbus_connection_failure:
360     vlc_close( p_sys->p_pipe_fds[1] );
361     vlc_close( p_sys->p_pipe_fds[0] );
362
363     free( p_sys );
364     return VLC_EGENERIC;
365 }
366
367 /*****************************************************************************
368  * Close: destroy interface
369  *****************************************************************************/
370
371 static void Close   ( vlc_object_t *p_this )
372 {
373     intf_thread_t   *p_intf     = (intf_thread_t*) p_this;
374     intf_sys_t      *p_sys      = p_intf->p_sys;
375     vlc_playlist_t  *playlist = p_sys->playlist;
376
377     vlc_cancel( p_sys->thread );
378     vlc_join( p_sys->thread, NULL );
379
380     vlc_player_t *player = vlc_playlist_GetPlayer(playlist);
381     vlc_playlist_Lock(playlist);
382     vlc_player_RemoveTimer(player, p_sys->player_timer);
383     vlc_player_vout_RemoveListener(player, p_sys->player_vout_listener);
384     vlc_player_aout_RemoveListener(player, p_sys->player_aout_listener);
385     vlc_player_RemoveListener(player, p_sys->player_listener);
386     vlc_playlist_RemoveListener(playlist, p_sys->playlist_listener);
387     vlc_playlist_Unlock(playlist);
388
389     /* The dbus connection is private, so we are responsible
390      * for closing it */
391     dbus_connection_close( p_sys->p_conn );
392     dbus_connection_unref( p_sys->p_conn );
393
394     // Free the events array
395     for( size_t i = 0; i < vlc_array_count( &p_sys->events ); i++ )
396     {
397         callback_info_t* info = vlc_array_item_at_index( &p_sys->events, i );
398         free( info );
399     }
400     vlc_array_clear( &p_sys->events );
401     vlc_array_clear( &p_sys->timeouts );
402     vlc_array_clear( &p_sys->watches );
403     vlc_close( p_sys->p_pipe_fds[1] );
404     vlc_close( p_sys->p_pipe_fds[0] );
405     free( p_sys );
406 }
407
408 static dbus_bool_t add_timeout(DBusTimeout *to, void *data)
409 {
410     intf_thread_t *intf = data;
411     intf_sys_t *sys = intf->p_sys;
412
413     vlc_tick_t *expiry = malloc(sizeof (*expiry));
414     if (unlikely(expiry == NULL))
415         return FALSE;
416
417     dbus_timeout_set_data(to, expiry, free);
418
419     vlc_mutex_lock(&sys->lock);
420     vlc_array_append_or_abort(&sys->timeouts, to);
421     vlc_mutex_unlock(&sys->lock);
422
423     return TRUE;
424 }
425
426 static void remove_timeout(DBusTimeout *to, void *data)
427 {
428     intf_thread_t *intf = data;
429     intf_sys_t *sys = intf->p_sys;
430     size_t idx;
431
432     vlc_mutex_lock(&sys->lock);
433     idx = vlc_array_index_of_item(&sys->timeouts, to);
434     vlc_array_remove(&sys->timeouts, idx);
435     vlc_mutex_unlock(&sys->lock);
436 }
437
438 static void toggle_timeout(DBusTimeout *to, void *data)
439 {
440     intf_thread_t *intf = data;
441     intf_sys_t *sys = intf->p_sys;
442     vlc_tick_t *expiry = dbus_timeout_get_data(to);
443
444     vlc_mutex_lock(&sys->lock);
445     if (dbus_timeout_get_enabled(to))
446         *expiry = vlc_tick_now() + UINT64_C(1000) * dbus_timeout_get_interval(to);
447     vlc_mutex_unlock(&sys->lock);
448
449     wakeup_main_loop(intf);
450 }
451
452 /**
453  * Computes the time until the next timeout expiration.
454  * @note Interface lock must be held.
455  * @return The time in milliseconds until the next expiration,
456  *         or -1 if there are no pending timeouts.
457  */
458 static int next_timeout(intf_thread_t *intf)
459 {
460     intf_sys_t *sys = intf->p_sys;
461     vlc_tick_t next_timeout = INT64_MAX;
462     unsigned count = vlc_array_count(&sys->timeouts);
463
464     for (unsigned i = 0; i < count; i++)
465     {
466         DBusTimeout *to = vlc_array_item_at_index(&sys->timeouts, i);
467
468         if (!dbus_timeout_get_enabled(to))
469             continue;
470
471         vlc_tick_t *expiry = dbus_timeout_get_data(to);
472
473         if (next_timeout > *expiry)
474             next_timeout = *expiry;
475     }
476
477     if (next_timeout == INT64_MAX)
478         return -1;
479
480     if (MS_FROM_VLC_TICK(next_timeout) > INT_MAX)
481         return INT_MAX;
482
483     return MS_FROM_VLC_TICK(next_timeout);
484 }
485
486 /**
487  * Process pending D-Bus timeouts.
488  *
489  * @note Interface lock must be held.
490  */
491 static void process_timeouts(intf_thread_t *intf)
492 {
493     intf_sys_t *sys = intf->p_sys;
494
495     for (size_t i = 0; i < vlc_array_count(&sys->timeouts); i++)
496     {
497         DBusTimeout *to = vlc_array_item_at_index(&sys->timeouts, i);
498
499         if (!dbus_timeout_get_enabled(to))
500             continue;
501
502         vlc_tick_t *expiry = dbus_timeout_get_data(to);
503         if (*expiry > vlc_tick_now())
504             continue;
505
506         expiry += UINT64_C(1000) * dbus_timeout_get_interval(to);
507         vlc_mutex_unlock(&sys->lock);
508
509         dbus_timeout_handle(to);
510
511         vlc_mutex_lock(&sys->lock);
512         i = -1; /* lost track of state, restart from beginning */
513     }
514 }
515
516
517 static dbus_bool_t add_watch( DBusWatch *p_watch, void *p_data )
518 {
519     intf_thread_t *p_intf = (intf_thread_t*) p_data;
520     intf_sys_t    *p_sys  = (intf_sys_t*) p_intf->p_sys;
521
522     vlc_mutex_lock( &p_sys->lock );
523     vlc_array_append_or_abort( &p_sys->watches, p_watch );
524     vlc_mutex_unlock( &p_sys->lock );
525
526     return TRUE;
527 }
528
529 static void remove_watch( DBusWatch *p_watch, void *p_data )
530 {
531     intf_thread_t *p_intf = (intf_thread_t*) p_data;
532     intf_sys_t    *p_sys  = (intf_sys_t*) p_intf->p_sys;
533     size_t idx;
534
535     vlc_mutex_lock( &p_sys->lock );
536     idx = vlc_array_index_of_item( &p_sys->watches, p_watch );
537     vlc_array_remove( &p_sys->watches, idx );
538     vlc_mutex_unlock( &p_sys->lock );
539 }
540
541 static void watch_toggled( DBusWatch *p_watch, void *p_data )
542 {
543     intf_thread_t *p_intf = (intf_thread_t*) p_data;
544
545     if( dbus_watch_get_enabled( p_watch ) )
546         wakeup_main_loop( p_intf );
547 }
548
549 /**
550  * GetPollFds() fills an array of pollfd data structures with :
551  *  - the set of enabled dbus watches
552  *  - the unix pipe which we use to manually wake up the main loop
553  *
554  * This function must be called with p_sys->lock locked
555  *
556  * @return The number of file descriptors
557  *
558  * @param intf_thread_t *p_intf this interface thread's state
559  * @param struct pollfd *p_fds a pointer to a pollfd array large enough to
560  * contain all the returned data (number of enabled dbus watches + 1)
561  */
562 static int GetPollFds( intf_thread_t *p_intf, struct pollfd *p_fds )
563 {
564     intf_sys_t *p_sys = p_intf->p_sys;
565     size_t i_watches = vlc_array_count( &p_sys->watches );
566     int i_fds = 1;
567
568     p_fds[0].fd = p_sys->p_pipe_fds[PIPE_OUT];
569     p_fds[0].events = POLLIN | POLLPRI;
570
571     for( size_t i = 0; i < i_watches; i++ )
572     {
573         DBusWatch *p_watch = NULL;
574         p_watch = vlc_array_item_at_index( &p_sys->watches, i );
575         if( !dbus_watch_get_enabled( p_watch ) )
576             continue;
577
578         p_fds[i_fds].fd = dbus_watch_get_unix_fd( p_watch );
579         int i_flags = dbus_watch_get_flags( p_watch );
580
581         if( i_flags & DBUS_WATCH_READABLE )
582             p_fds[i_fds].events |= POLLIN | POLLPRI;
583
584         if( i_flags & DBUS_WATCH_WRITABLE )
585             p_fds[i_fds].events |= POLLOUT;
586
587         i_fds++;
588     }
589
590     return i_fds;
591 }
592
593 /**
594  * ProcessEvents() reacts to a list of events originating from other VLC threads
595  *
596  * This function must be called with p_sys->lock unlocked
597  *
598  * @param intf_thread_t *p_intf This interface thread state
599  * @param callback_info_t *p_events the list of events to process
600  */
601 static void ProcessEvents( intf_thread_t *p_intf,
602                            callback_info_t **p_events, int i_events )
603 {
604     bool b_can_play = p_intf->p_sys->b_can_play;
605
606     vlc_dictionary_t player_properties, tracklist_properties, root_properties;
607     vlc_dictionary_init( &player_properties,    0 );
608     vlc_dictionary_init( &tracklist_properties, 0 );
609     vlc_dictionary_init( &root_properties,      0 );
610
611     for( int i = 0; i < i_events; i++ )
612     {
613         switch( p_events[i]->signal )
614         {
615         case SIGNAL_ITEM_CURRENT:
616             TrackChange( p_intf );
617
618             // rate depends on current item
619             if( !vlc_dictionary_has_key( &player_properties, "Rate" ) )
620                 vlc_dictionary_insert( &player_properties, "Rate", NULL );
621
622             vlc_dictionary_insert( &player_properties, "Metadata", NULL );
623             break;
624         case SIGNAL_PLAYLIST_ITEM_APPEND:
625         case SIGNAL_PLAYLIST_ITEM_DELETED:
626         {
627             vlc_playlist_t *playlist = p_intf->p_sys->playlist;
628             vlc_playlist_Lock(playlist);
629             b_can_play = vlc_playlist_Count(playlist) > 0;
630             vlc_playlist_Unlock(playlist);
631
632             if( b_can_play != p_intf->p_sys->b_can_play )
633             {
634                 p_intf->p_sys->b_can_play = b_can_play;
635                 vlc_dictionary_insert( &player_properties, "CanPlay", NULL );
636             }
637
638             if( !vlc_dictionary_has_key( &tracklist_properties, "Tracks" ) )
639                 vlc_dictionary_insert( &tracklist_properties, "Tracks", NULL );
640             break;
641         }
642         case SIGNAL_VOLUME_MUTED:
643         case SIGNAL_VOLUME_CHANGE:
644             vlc_dictionary_insert( &player_properties, "Volume", NULL );
645             break;
646         case SIGNAL_RANDOM:
647             vlc_dictionary_insert( &player_properties, "Shuffle", NULL );
648             break;
649         case SIGNAL_FULLSCREEN:
650             vlc_dictionary_insert( &root_properties, "Fullscreen", NULL );
651             break;
652         case SIGNAL_REPEAT:
653         case SIGNAL_LOOP:
654             vlc_dictionary_insert( &player_properties, "LoopStatus", NULL );
655             break;
656         case SIGNAL_STATE:
657             vlc_dictionary_insert( &player_properties, "PlaybackStatus", NULL );
658             break;
659         case SIGNAL_RATE:
660             vlc_dictionary_insert( &player_properties, "Rate", NULL );
661             break;
662         case SIGNAL_INPUT_METADATA:
663         {
664             vlc_player_t *player =
665                 vlc_playlist_GetPlayer(p_intf->p_sys->playlist);
666             vlc_player_Lock(player);
667             input_item_t *p_item = vlc_player_GetCurrentMedia(player);
668             if( p_item )
669                 vlc_dictionary_insert( &player_properties,
670                                        "Metadata", NULL );
671             vlc_player_Unlock(player);
672             break;
673         }
674         case SIGNAL_CAN_SEEK:
675             vlc_dictionary_insert( &player_properties, "CanSeek", NULL );
676             break;
677         case SIGNAL_CAN_PAUSE:
678             vlc_dictionary_insert( &player_properties, "CanPause", NULL );
679             break;
680         case SIGNAL_SEEK:
681             SeekedEmit( p_intf );
682             break;
683         default:
684             vlc_assert_unreachable();
685         }
686         free( p_events[i] );
687     }
688
689     if( !vlc_dictionary_is_empty( &player_properties ) )
690         PlayerPropertiesChangedEmit( p_intf, &player_properties );
691
692     if( !vlc_dictionary_is_empty( &tracklist_properties ) )
693         TrackListPropertiesChangedEmit( p_intf, &tracklist_properties );
694
695     if( !vlc_dictionary_is_empty( &root_properties ) )
696         RootPropertiesChangedEmit( p_intf, &root_properties );
697
698     vlc_dictionary_clear( &player_properties,    NULL, NULL );
699     vlc_dictionary_clear( &tracklist_properties, NULL, NULL );
700     vlc_dictionary_clear( &root_properties,      NULL, NULL );
701 }
702
703 /**
704  * ProcessWatches() handles a list of dbus watches after poll() has returned
705  *
706  * This function must be called with p_sys->lock unlocked
707  *
708  * @param intf_thread_t *p_intf This interface thread state
709  * @param DBusWatch **p_watches The list of dbus watches to process
710  * @param int i_watches The size of the p_watches array
711  * @param struct pollfd *p_fds The result of a poll() call
712  * @param int i_fds The number of file descriptors processed by poll()
713  */
714 static void ProcessWatches( intf_thread_t *p_intf,
715                             DBusWatch **p_watches, int i_watches,
716                             struct pollfd *p_fds,  int i_fds )
717 {
718     VLC_UNUSED(p_intf);
719
720     /* Process watches */
721     for( int i = 0; i < i_watches; i++ )
722     {
723         DBusWatch *p_watch = p_watches[i];
724         if( !dbus_watch_get_enabled( p_watch ) )
725             continue;
726
727         for( int j = 0; j < i_fds; j++ )
728         {
729             if( p_fds[j].fd != dbus_watch_get_unix_fd( p_watch ) )
730                 continue;
731
732             int i_flags   = 0;
733             int i_revents = p_fds[j].revents;
734
735             if( i_revents & POLLIN )
736                 i_flags |= DBUS_WATCH_READABLE;
737
738             if( i_revents & POLLOUT )
739                 i_flags |= DBUS_WATCH_WRITABLE;
740
741             if( i_revents & POLLERR )
742                 i_flags |= DBUS_WATCH_ERROR;
743
744             if( i_revents & POLLHUP )
745                 i_flags |= DBUS_WATCH_HANGUP;
746
747             if( i_flags )
748                 dbus_watch_handle( p_watch, i_flags );
749         }
750     }
751 }
752
753 /**
754  * DispatchDBusMessages() dispatches incoming dbus messages
755  * (indirectly invoking the callbacks), then it sends outgoing
756  * messages which needs to be sent on the bus (method replies and signals)
757  *
758  * This function must be called with p_sys->lock unlocked
759  *
760  * @param intf_thread_t *p_intf This interface thread state
761  */
762 static void DispatchDBusMessages( intf_thread_t *p_intf )
763 {
764     DBusDispatchStatus status;
765     intf_sys_t *p_sys = p_intf->p_sys;
766
767     /* Dispatch incoming messages */
768     status = dbus_connection_get_dispatch_status( p_sys->p_conn );
769     while( status != DBUS_DISPATCH_COMPLETE )
770     {
771         dbus_connection_dispatch( p_sys->p_conn );
772         status = dbus_connection_get_dispatch_status( p_sys->p_conn );
773     }
774
775     /* Send outgoing data */
776     if( dbus_connection_has_messages_to_send( p_sys->p_conn ) )
777         dbus_connection_flush( p_sys->p_conn );
778 }
779
780 /**
781  * MPRISEntryPoint() routes incoming messages to their respective interface
782  * implementation.
783  *
784  * This function is called during dbus_connection_dispatch()
785  */
786 static DBusHandlerResult
787 MPRISEntryPoint ( DBusConnection *p_conn, DBusMessage *p_from, void *p_this )
788 {
789     const char *psz_target_interface;
790     const char *psz_interface = dbus_message_get_interface( p_from );
791     const char *psz_method    = dbus_message_get_member( p_from );
792
793     DBusError error;
794
795     if( psz_interface && strcmp( psz_interface, DBUS_INTERFACE_PROPERTIES ) )
796         psz_target_interface = psz_interface;
797
798     else
799     {
800         dbus_error_init( &error );
801         dbus_message_get_args( p_from, &error,
802                                DBUS_TYPE_STRING, &psz_target_interface,
803                                DBUS_TYPE_INVALID );
804
805         if( dbus_error_is_set( &error ) )
806         {
807             msg_Err( (vlc_object_t*) p_this, "D-Bus error on %s.%s: %s",
808                                              psz_interface, psz_method,
809                                              error.message );
810             dbus_error_free( &error );
811             return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
812         }
813     }
814
815     if( !strcmp( psz_target_interface, DBUS_INTERFACE_INTROSPECTABLE ) )
816         return handle_introspect( p_conn, p_from, p_this );
817
818     if( !strcmp( psz_target_interface, DBUS_MPRIS_ROOT_INTERFACE ) )
819         return handle_root( p_conn, p_from, p_this );
820
821     if( !strcmp( psz_target_interface, DBUS_MPRIS_PLAYER_INTERFACE ) )
822         return handle_player( p_conn, p_from, p_this );
823
824     if( !strcmp( psz_target_interface, DBUS_MPRIS_TRACKLIST_INTERFACE ) )
825         return handle_tracklist( p_conn, p_from, p_this );
826
827     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
828 }
829
830 /*****************************************************************************
831  * Run: main loop
832  *****************************************************************************/
833
834 static void *Run( void *data )
835 {
836     intf_thread_t *p_intf = data;
837     intf_sys_t    *p_sys = p_intf->p_sys;
838
839     int canc = vlc_savecancel();
840
841     vlc_tick_t events_last_date = VLC_TICK_INVALID;
842     int events_poll_timeout = -1;
843     for( ;; )
844     {
845         vlc_mutex_lock( &p_sys->lock );
846
847         size_t i_watches = vlc_array_count( &p_sys->watches );
848         struct pollfd fds[i_watches];
849         memset(fds, 0, sizeof fds);
850
851         int i_fds = GetPollFds( p_intf, fds );
852         int timeout = next_timeout(p_intf);
853
854         vlc_mutex_unlock( &p_sys->lock );
855
856         /* thread cancellation is allowed while the main loop sleeps */
857         vlc_restorecancel( canc );
858         if( timeout == -1 )
859             timeout = events_poll_timeout;
860
861         while (poll(fds, i_fds, timeout) == -1)
862         {
863             if (errno != EINTR)
864                 goto error;
865         }
866
867         canc = vlc_savecancel();
868
869         /* Was the main loop woken up manually ? */
870         if (fds[0].revents & POLLIN)
871         {
872             char buf;
873             (void)read( fds[0].fd, &buf, 1 );
874         }
875
876         /* We need to lock the mutex while building lists of events,
877          * timeouts and watches to process but we can't keep the lock while
878          * processing them, or else we risk a deadlock:
879          *
880          * The signal functions could lock mutex X while p_events is locked;
881          * While some other function in vlc (playlist) might lock mutex X
882          * and then set a variable which would call PlaylistCallback(), which itself
883          * needs to lock p_events to add a new event.
884          */
885         vlc_mutex_lock( &p_intf->p_sys->lock );
886
887         process_timeouts(p_intf);
888
889         /* Get the list of watches to process */
890         i_watches = vlc_array_count( &p_sys->watches );
891         DBusWatch *p_watches[i_watches ? i_watches : 1];
892         for( size_t i = 0; i < i_watches; i++ )
893         {
894             p_watches[i] = vlc_array_item_at_index( &p_sys->watches, i );
895         }
896
897         /* Get the list of events to process */
898         size_t i_events = vlc_array_count( &p_sys->events );
899         callback_info_t** pp_info = NULL;
900
901         if( i_events > 0 )
902         {
903             vlc_tick_t now = vlc_tick_now();
904             if( events_last_date == VLC_TICK_INVALID
905              || now - events_last_date > EVENTS_DELAY )
906             {
907                 /* Send events every EVENTS_DELAY */
908                 events_last_date = now;
909                 events_poll_timeout = -1;
910
911                 pp_info = vlc_alloc( i_events, sizeof(*pp_info) );
912                 if( pp_info )
913                 {
914                     for( size_t i = 0; i < i_events; i++ )
915                         pp_info[i] = vlc_array_item_at_index( &p_sys->events, i );
916                     vlc_array_clear( &p_sys->events );
917                 }
918             }
919             else if( events_poll_timeout == -1 )
920             {
921                 /* Request poll to wake up in order to send these events after
922                  * some delay */
923                 events_poll_timeout = ( EVENTS_DELAY - ( now - events_last_date ) ) / 1000;
924             }
925         }
926         else /* No events: clear timeout */
927             events_poll_timeout = -1;
928
929         /* now we can release the lock and process what's pending */
930         vlc_mutex_unlock( &p_intf->p_sys->lock );
931
932         if( pp_info )
933         {
934             ProcessEvents( p_intf, pp_info, i_events );
935             free( pp_info );
936         }
937         ProcessWatches( p_intf, p_watches, i_watches, fds, i_fds );
938
939         DispatchDBusMessages( p_intf );
940     }
941 error:
942     vlc_restorecancel(canc);
943     return NULL;
944 }
945
946 static void   wakeup_main_loop( void *p_data )
947 {
948     intf_thread_t *p_intf = (intf_thread_t*) p_data;
949
950     if( !write( p_intf->p_sys->p_pipe_fds[PIPE_IN], "\0", 1 ) )
951         msg_Err( p_intf, "Could not wake up the main loop: %s",
952                  vlc_strerror_c(errno) );
953 }
954
955 static bool add_event_locked( intf_thread_t *p_intf, const callback_info_t *p_info )
956 {
957     if( !p_info->signal )
958         return false;
959
960     for( size_t i = 0; i < vlc_array_count( &p_intf->p_sys->events ); ++ i )
961     {
962         callback_info_t *oldinfo =
963             vlc_array_item_at_index( &p_intf->p_sys->events, i );
964         if( p_info->signal == oldinfo->signal )
965             return false;
966     }
967
968     callback_info_t *p_dup = malloc( sizeof( *p_dup ) );
969     if( unlikely(p_dup == NULL) )
970         return false;
971     *p_dup = *p_info;
972
973     vlc_array_append( &p_intf->p_sys->events, p_dup );
974     return true;
975 }
976
977 static bool add_event_signal( intf_thread_t *p_intf, const callback_info_t *p_info )
978 {
979     intf_sys_t *p_sys = p_intf->p_sys;
980     vlc_mutex_lock(&p_sys->lock);
981     bool added = add_event_locked(p_intf, p_info);
982     vlc_mutex_unlock(&p_sys->lock);
983
984     if( added )
985         wakeup_main_loop( p_intf );
986     return added;
987 }
988
989 static void
990 playlist_on_items_added(vlc_playlist_t *playlist, size_t index,
991                         vlc_playlist_item_t *const items[], size_t count,
992                         void *data)
993 {
994     add_event_signal(data,
995             &(callback_info_t){ .signal = SIGNAL_PLAYLIST_ITEM_APPEND });
996     (void) playlist; (void) index; (void) items; (void) count;
997 }
998
999 static void
1000 playlist_on_items_removed(vlc_playlist_t *playlist,
1001                           size_t index, size_t count, void *data)
1002 {
1003     add_event_signal(data,
1004             &(callback_info_t){ .signal = SIGNAL_PLAYLIST_ITEM_DELETED });
1005     (void) playlist; (void) index; (void) count;
1006 }
1007
1008 static void
1009 playlist_on_playback_repeat_changed(vlc_playlist_t *playlist,
1010                                     enum vlc_playlist_playback_repeat repeat,
1011                                     void *data)
1012 {
1013     add_event_signal(data, &(callback_info_t){ .signal = SIGNAL_REPEAT });
1014     (void) playlist; (void) repeat;
1015 }
1016
1017 static void
1018 playlist_on_playback_order_changed(vlc_playlist_t *playlist,
1019                                    enum vlc_playlist_playback_order order,
1020                                    void *data)
1021 {
1022     add_event_signal(data, &(callback_info_t){ .signal = SIGNAL_RANDOM });
1023     (void) playlist; (void) order;
1024 }
1025
1026 static void
1027 playlist_on_current_index_changed(vlc_playlist_t *playlist,
1028                                   ssize_t index, void *data)
1029 {
1030     add_event_signal(data,
1031             &(callback_info_t){ .signal = SIGNAL_ITEM_CURRENT });
1032     (void) playlist; (void) index;
1033 }
1034
1035 static void
1036 player_on_state_changed(vlc_player_t *player, enum vlc_player_state state,
1037                         void *data)
1038 {
1039     intf_thread_t *intf = data;
1040     intf_sys_t *sys = intf->p_sys;
1041     dbus_int32_t playing_state;
1042
1043     switch (state)
1044     {
1045         case VLC_PLAYER_STATE_STARTED:
1046         case VLC_PLAYER_STATE_PLAYING:
1047             playing_state = PLAYBACK_STATE_PLAYING;
1048             break;
1049         case VLC_PLAYER_STATE_PAUSED:
1050             playing_state = PLAYBACK_STATE_PAUSED;
1051             break;
1052         /* STOPPING is used to detect time discontinuities that are coming
1053          * from stopping the playback in the vlc player timer callbacks. */
1054         case VLC_PLAYER_STATE_STOPPING:
1055         case VLC_PLAYER_STATE_STOPPED:
1056         default:
1057             playing_state = PLAYBACK_STATE_STOPPED;
1058             break;
1059     }
1060
1061     bool added = false;
1062     vlc_mutex_lock(&sys->lock);
1063     if (playing_state != sys->i_playing_state)
1064     {
1065         sys->i_playing_state = playing_state;
1066         added = add_event_locked(intf,
1067                 &(callback_info_t) { .signal = SIGNAL_STATE });
1068     }
1069     vlc_mutex_unlock(&sys->lock);
1070
1071     if (added)
1072         wakeup_main_loop(intf);
1073     (void) player;
1074 }
1075
1076 static void
1077 player_on_error_changed(vlc_player_t *player, enum vlc_player_error error,
1078                         void *data)
1079 {
1080     if (error == VLC_PLAYER_ERROR_GENERIC)
1081         player_on_state_changed(player, VLC_PLAYER_STATE_STOPPED, data);
1082 }
1083
1084 static void
1085 player_on_rate_changed(vlc_player_t *player, float new_rate, void *data)
1086 {
1087     add_event_signal(data, &(callback_info_t){ .signal = SIGNAL_RATE });
1088     (void) player; (void) new_rate;
1089 }
1090
1091 static void
1092 player_on_capabilities_changed(vlc_player_t *player, int old_caps, int new_caps,
1093                                void *data)
1094 {
1095     intf_thread_t *intf = data;
1096     intf_sys_t *sys = intf->p_sys;
1097
1098     vlc_mutex_lock(&sys->lock);
1099     bool ok1 = add_event_locked(intf,
1100             &(callback_info_t) { .signal =  SIGNAL_CAN_SEEK });
1101     bool ok2 = add_event_locked(intf,
1102             &(callback_info_t) { .signal =  SIGNAL_CAN_PAUSE });
1103     vlc_mutex_unlock(&sys->lock);
1104     if (ok1 || ok2)
1105         wakeup_main_loop(intf);
1106     (void) player; (void) old_caps; (void) new_caps;
1107 }
1108
1109 static void
1110 player_on_media_meta_changed(vlc_player_t *player,
1111                              input_item_t *item, void *data)
1112 {
1113     add_event_signal(data,
1114             &(callback_info_t){ .signal = SIGNAL_INPUT_METADATA });
1115     (void) player; (void) item;
1116 }
1117
1118 static void
1119 player_aout_on_volume_changed(audio_output_t *aout, float volume, void *data)
1120 {
1121     add_event_signal(data,
1122             &(callback_info_t){ .signal = SIGNAL_VOLUME_CHANGE });
1123     (void) aout; (void) volume;
1124 }
1125
1126 static void
1127 player_aout_on_mute_changed(audio_output_t *aout, bool muted, void *data)
1128 {
1129     add_event_signal(data,
1130             &(callback_info_t){ .signal = SIGNAL_VOLUME_MUTED });
1131     (void) aout; (void) muted;
1132 }
1133
1134 static void
1135 player_vout_on_fullscreen_changed(vout_thread_t *vout, bool enabled,
1136                                   void *data)
1137 {
1138     add_event_signal(data, &(callback_info_t){ .signal = SIGNAL_FULLSCREEN });
1139     (void) vout; (void) enabled;
1140 }
1141
1142 static void
1143 player_timer_on_update(const struct vlc_player_timer_point *value, void *data)
1144 {
1145     /* The callback is not used by mandatory in the vlc_player_timer API */
1146     VLC_UNUSED(value);
1147     VLC_UNUSED(data);
1148 }
1149
1150 static void
1151 player_timer_on_discontinuity(vlc_tick_t system_date, void *data)
1152 {
1153     intf_thread_t *intf = data;
1154     intf_sys_t *sys = intf->p_sys;
1155
1156     bool stopping = sys->i_playing_state == PLAYBACK_STATE_STOPPED;
1157     bool paused = system_date != VLC_TICK_INVALID;
1158
1159     if( !paused && !stopping )
1160         add_event_signal(intf, &(callback_info_t){ .signal = SIGNAL_SEEK });
1161 }
1162
1163 /*****************************************************************************
1164  * TrackChange: callback on playlist_on_current_index_changed
1165  *****************************************************************************/
1166 static int TrackChange( intf_thread_t *p_intf )
1167 {
1168     intf_sys_t          *p_sys      = p_intf->p_sys;
1169
1170     if( p_intf->p_sys->b_dead )
1171         return VLC_SUCCESS;
1172
1173     p_sys->has_input = false;
1174
1175     p_sys->b_meta_read = false;
1176
1177     vlc_player_t *player = vlc_playlist_GetPlayer(p_sys->playlist);
1178     vlc_player_Lock(player);
1179     input_item_t *item = vlc_player_GetCurrentMedia(player);
1180     vlc_player_Unlock(player);
1181     if( !item )
1182         return VLC_EGENERIC;
1183
1184     if( input_item_IsPreparsed( item ) )
1185         p_sys->b_meta_read = true;
1186
1187     p_sys->has_input = true;
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 #define ADD_META_SINGLETON_STRING_LIST( entry, item ) \
1248     { \
1249         char * psz = input_item_Get##item( p_input );\
1250         if( psz ) { \
1251             dbus_message_iter_open_container( &dict, DBUS_TYPE_DICT_ENTRY, \
1252                     NULL, &dict_entry ); \
1253             dbus_message_iter_append_basic( &dict_entry, DBUS_TYPE_STRING, \
1254                     &ppsz_meta_items[entry] ); \
1255             dbus_message_iter_open_container( &dict_entry, DBUS_TYPE_VARIANT, \
1256                     "as", &variant ); \
1257             dbus_message_iter_open_container( &variant, DBUS_TYPE_ARRAY, "s", \
1258                                               &list ); \
1259             dbus_message_iter_append_basic( &list, \
1260                     DBUS_TYPE_STRING, \
1261                     &psz ); \
1262             dbus_message_iter_close_container( &variant, &list ); \
1263             dbus_message_iter_close_container( &dict_entry, &variant ); \
1264             dbus_message_iter_close_container( &dict, &dict_entry ); \
1265         } \
1266         free( psz ); \
1267     }
1268
1269 int GetInputMeta(vlc_playlist_t *playlist, vlc_playlist_item_t *item, DBusMessageIter *args)
1270 {
1271     input_item_t *p_input = vlc_playlist_item_GetMedia(item);
1272     DBusMessageIter dict, dict_entry, variant, list;
1273     /** The duration of the track can be expressed in second, milli-seconds and
1274         µ-seconds */
1275     dbus_int64_t i_mtime = input_item_GetDuration( p_input );
1276     dbus_uint32_t i_time = i_mtime / 1000000;
1277     dbus_int64_t i_length = i_mtime / 1000;
1278     char *psz_trackid;
1279
1280     if (asprintf(&psz_trackid, MPRIS_TRACKID_FORMAT,
1281                  vlc_playlist_IndexOf(playlist, item)) == -1)
1282         return VLC_ENOMEM;
1283
1284     const char* ppsz_meta_items[] =
1285     {
1286         "mpris:trackid", "xesam:url", "xesam:title", "xesam:artist",
1287         "xesam:album", "xesam:tracknumber", "vlc:time", "mpris:length",
1288         "xesam:genre", "xesam:userRating", "xesam:contentCreated",
1289         "mpris:artUrl", "mb:trackId", "vlc:audio-bitrate",
1290         "vlc:audio-samplerate", "vlc:video-bitrate", "vlc:audio-codec",
1291         "vlc:copyright", "xesam:comment", "vlc:encodedby", "language",
1292         "vlc:length", "vlc:nowplaying", "vlc:publisher", "vlc:setting",
1293         "status", "vlc:url", "vlc:video-codec"
1294     };
1295
1296     dbus_message_iter_open_container( args, DBUS_TYPE_ARRAY, "{sv}", &dict );
1297
1298     ADD_META( 0, DBUS_TYPE_OBJECT_PATH, psz_trackid );
1299     ADD_VLC_META_STRING( 1,  URI );
1300     ADD_VLC_META_STRING( 2,  Title );
1301     ADD_META_SINGLETON_STRING_LIST( 3,  Artist );
1302     ADD_VLC_META_STRING( 4,  Album );
1303     ADD_VLC_META_STRING( 5,  TrackNum );
1304     ADD_META( 6, DBUS_TYPE_UINT32, i_time );
1305     ADD_META( 7, DBUS_TYPE_INT64,  i_mtime );
1306     ADD_META_SINGLETON_STRING_LIST( 8,  Genre );
1307     //ADD_META( 9, DBUS_TYPE_DOUBLE, rating );
1308     ADD_VLC_META_STRING( 10, Date ); // this is supposed to be in ISO 8601 extended format
1309     ADD_VLC_META_STRING( 11, ArtURL );
1310     ADD_VLC_META_STRING( 12, TrackID );
1311
1312     ADD_VLC_META_STRING( 17, Copyright );
1313     ADD_META_SINGLETON_STRING_LIST( 18, Description );
1314     ADD_VLC_META_STRING( 19, EncodedBy );
1315     ADD_VLC_META_STRING( 20, Language );
1316     ADD_META( 21, DBUS_TYPE_INT64, i_length );
1317     ADD_VLC_META_STRING( 22, NowPlaying );
1318     ADD_VLC_META_STRING( 23, Publisher );
1319     ADD_VLC_META_STRING( 24, Setting );
1320     ADD_VLC_META_STRING( 25, URL );
1321
1322     free( psz_trackid );
1323
1324     vlc_mutex_lock( &p_input->lock );
1325     if( p_input->p_meta )
1326     {
1327         int i_status = vlc_meta_GetStatus( p_input->p_meta );
1328         ADD_META( 23, DBUS_TYPE_INT32, i_status );
1329     }
1330     vlc_mutex_unlock( &p_input->lock );
1331
1332     dbus_message_iter_close_container( args, &dict );
1333     return VLC_SUCCESS;
1334 }
1335
1336 int AddProperty( intf_thread_t *p_intf,
1337                  DBusMessageIter *p_container,
1338                  const char* psz_property_name,
1339                  const char* psz_signature,
1340                  int (*pf_marshaller) (intf_thread_t*, DBusMessageIter*) )
1341 {
1342     DBusMessageIter entry, v;
1343
1344     if( !dbus_message_iter_open_container( p_container,
1345                                            DBUS_TYPE_DICT_ENTRY, NULL,
1346                                            &entry ) )
1347         return VLC_ENOMEM;
1348
1349     if( !dbus_message_iter_append_basic( &entry,
1350                                          DBUS_TYPE_STRING,
1351                                          &psz_property_name ) )
1352         return VLC_ENOMEM;
1353
1354     if( !dbus_message_iter_open_container( &entry,
1355                                            DBUS_TYPE_VARIANT, psz_signature,
1356                                            &v ) )
1357         return VLC_ENOMEM;
1358
1359     if( VLC_SUCCESS != pf_marshaller( p_intf, &v ) )
1360         return VLC_ENOMEM;
1361
1362     if( !dbus_message_iter_close_container( &entry, &v) )
1363         return VLC_ENOMEM;
1364
1365     if( !dbus_message_iter_close_container( p_container, &entry ) )
1366         return VLC_ENOMEM;
1367
1368     return VLC_SUCCESS;
1369 }
1370
1371 #undef ADD_META
1372 #undef ADD_VLC_META_STRING