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