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