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