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