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