]> git.sesse.net Git - vlc/blob - modules/control/dbus/dbus.c
dbus: Use vlc_pipe
[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-2010 Mirsal Ennaime
6  * Copyright © 2009-2010 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.h"
49 #include "dbus_common.h"
50 #include "dbus_root.h"
51 #include "dbus_player.h"
52 #include "dbus_tracklist.h"
53
54 #include <vlc_common.h>
55 #include <vlc_fixups.h>
56 #include <vlc_plugin.h>
57 #include <vlc_interface.h>
58 #include <vlc_playlist.h>
59 #include <vlc_meta.h>
60 #include <vlc_mtime.h>
61 #include <vlc_fs.h>
62
63 #include <assert.h>
64 #include <string.h>
65
66 #include <poll.h>
67 #include <errno.h>
68 #include <unistd.h>
69
70 /*****************************************************************************
71  * Local prototypes.
72  *****************************************************************************/
73
74 typedef struct
75 {
76     int signal;
77     int i_node;
78 } callback_info_t;
79
80 typedef struct
81 {
82     mtime_t      i_remaining;
83     DBusTimeout *p_timeout;
84 } timeout_info_t;
85
86 enum
87 {
88     PIPE_OUT = 0,
89     PIPE_IN  = 1
90 };
91
92 static int  Open    ( vlc_object_t * );
93 static void Close   ( vlc_object_t * );
94 static void Run     ( intf_thread_t * );
95
96 static int StateChange( intf_thread_t * );
97 static int TrackChange( intf_thread_t * );
98 static int AllCallback( vlc_object_t*, const char*, vlc_value_t, vlc_value_t, void* );
99
100 static void dispatch_status_cb( DBusConnection *p_conn,
101                                 DBusDispatchStatus i_status,
102                                 void *p_data);
103
104 static dbus_bool_t add_timeout ( DBusTimeout *p_timeout, void *p_data );
105 static dbus_bool_t add_watch   ( DBusWatch *p_watch, void *p_data );
106
107 static void remove_timeout  ( DBusTimeout *p_timeout, void *p_data );
108 static void remove_watch    ( DBusWatch *p_watch, void *p_data );
109
110 static void timeout_toggled ( DBusTimeout *p_timeout, void *p_data );
111 static void watch_toggled   ( DBusWatch *p_watch, void *p_data );
112
113 static void wakeup_main_loop( void *p_data );
114
115 static int GetPollFds( intf_thread_t *p_intf, struct pollfd *p_fds );
116 static int UpdateTimeouts( intf_thread_t *p_intf, mtime_t i_lastrun );
117
118 static void ProcessEvents  ( intf_thread_t    *p_intf,
119                              callback_info_t **p_events,
120                              int               i_events );
121
122 static void ProcessWatches ( intf_thread_t    *p_intf,
123                              DBusWatch       **p_watches,
124                              int               i_watches,
125                              struct pollfd    *p_fds,
126                              int               i_fds );
127
128 static void ProcessTimeouts( intf_thread_t    *p_intf,
129                              DBusTimeout     **p_timeouts,
130                              int               i_timeouts );
131
132 static void DispatchDBusMessages( intf_thread_t *p_intf );
133
134 /*****************************************************************************
135  * Module descriptor
136  *****************************************************************************/
137 #define DBUS_UNIQUE_TEXT N_("Unique DBUS service id (org.mpris.vlc-<pid>)")
138 #define DBUS_UNIQUE_LONGTEXT N_( \
139     "Use a unique dbus service id to identify this VLC instance on the DBUS bus. " \
140     "The process identifier (PID) is added to the service name: org.mpris.vlc-<pid>" )
141
142 vlc_module_begin ()
143     set_shortname( N_("dbus"))
144     set_category( CAT_INTERFACE )
145     set_subcategory( SUBCAT_INTERFACE_CONTROL )
146     set_description( N_("D-Bus control interface") )
147     set_capability( "interface", 0 )
148     set_callbacks( Open, Close )
149     add_bool( "dbus-unique-service-id", false,
150               DBUS_UNIQUE_TEXT, DBUS_UNIQUE_LONGTEXT, true )
151 vlc_module_end ()
152
153 /*****************************************************************************
154  * Open: initialize interface
155  *****************************************************************************/
156
157 static int Open( vlc_object_t *p_this )
158 { /* initialisation of the connection */
159     intf_thread_t   *p_intf = (intf_thread_t*)p_this;
160     intf_sys_t      *p_sys  = malloc( sizeof( intf_sys_t ) );
161     playlist_t      *p_playlist;
162     DBusConnection  *p_conn;
163     DBusError       error;
164     char            *psz_service_name = NULL;
165
166     if( !p_sys || !dbus_threads_init_default())
167         return VLC_ENOMEM;
168
169     p_sys->b_meta_read = false;
170     p_sys->i_caps = CAPS_NONE;
171     p_sys->b_dead = false;
172     p_sys->p_input = NULL;
173     p_sys->i_playing_state = -1;
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     p_sys->b_unique = var_CreateGetBool( p_intf, "dbus-unique-service-id" );
183     if( p_sys->b_unique )
184     {
185         if( asprintf( &psz_service_name, "%s-%d",
186             DBUS_MPRIS_BUS_NAME, getpid() ) < 0 )
187         {
188             free( p_sys );
189             return VLC_ENOMEM;
190         }
191     }
192     else
193     {
194         psz_service_name = strdup(DBUS_MPRIS_BUS_NAME);
195     }
196
197     dbus_error_init( &error );
198
199     /* connect privately to the session bus
200      * the connection will not be shared with other vlc modules which use dbus,
201      * thus avoiding a whole class of concurrency issues */
202     p_conn = dbus_bus_get_private( DBUS_BUS_SESSION, &error );
203     if( !p_conn )
204     {
205         msg_Err( p_this, "Failed to connect to the D-Bus session daemon: %s",
206                 error.message );
207         dbus_error_free( &error );
208         free( psz_service_name );
209         free( p_sys );
210         return VLC_EGENERIC;
211     }
212
213     dbus_connection_set_exit_on_disconnect( p_conn, FALSE );
214
215     /* register a well-known name on the bus */
216     dbus_bus_request_name( p_conn, psz_service_name, 0, &error );
217     if( dbus_error_is_set( &error ) )
218     {
219         msg_Err( p_this, "Error requesting service %s: %s",
220                  psz_service_name, error.message );
221         dbus_error_free( &error );
222         free( psz_service_name );
223         free( p_sys );
224         return VLC_EGENERIC;
225     }
226     msg_Info( p_intf, "listening on dbus as: %s", psz_service_name );
227     free( psz_service_name );
228
229     /* we register the objects */
230     dbus_connection_register_object_path( p_conn, DBUS_MPRIS_ROOT_PATH,
231             &dbus_mpris_root_vtable, p_this );
232     dbus_connection_register_object_path( p_conn, DBUS_MPRIS_PLAYER_PATH,
233             &dbus_mpris_player_vtable, p_this );
234     dbus_connection_register_object_path( p_conn, DBUS_MPRIS_TRACKLIST_PATH,
235             &dbus_mpris_tracklist_vtable, p_this );
236
237     dbus_connection_flush( p_conn );
238
239     p_intf->pf_run = Run;
240     p_intf->p_sys = p_sys;
241     p_sys->p_conn = p_conn;
242     p_sys->p_events = vlc_array_new();
243     p_sys->p_timeouts = vlc_array_new();
244     p_sys->p_watches = vlc_array_new();
245     vlc_mutex_init( &p_sys->lock );
246
247     p_playlist = pl_Get( p_intf );
248     p_sys->p_playlist = p_playlist;
249
250     var_AddCallback( p_playlist, "item-current", AllCallback, p_intf );
251     var_AddCallback( p_playlist, "intf-change", AllCallback, p_intf );
252     var_AddCallback( p_playlist, "playlist-item-append", AllCallback, p_intf );
253     var_AddCallback( p_playlist, "playlist-item-deleted", AllCallback, p_intf );
254     var_AddCallback( p_playlist, "random", AllCallback, p_intf );
255     var_AddCallback( p_playlist, "repeat", AllCallback, p_intf );
256     var_AddCallback( p_playlist, "loop", AllCallback, p_intf );
257
258     dbus_connection_set_dispatch_status_function( p_conn,
259                                                   dispatch_status_cb,
260                                                   p_intf, NULL );
261
262     if( !dbus_connection_set_timeout_functions( p_conn,
263                                                 add_timeout,
264                                                 remove_timeout,
265                                                 timeout_toggled,
266                                                 p_intf, NULL ) )
267     {
268         dbus_connection_unref( p_conn );
269         free( psz_service_name );
270         free( p_sys );
271         return VLC_ENOMEM;
272     }
273
274     if( !dbus_connection_set_watch_functions( p_conn,
275                                               add_watch,
276                                               remove_watch,
277                                               watch_toggled,
278                                               p_intf, NULL ) )
279     {
280         dbus_connection_unref( p_conn );
281         free( psz_service_name );
282         free( p_sys );
283         return VLC_ENOMEM;
284     }
285
286 /*     dbus_connection_set_wakeup_main_function( p_conn,
287                                               wakeup_main_loop,
288                                               p_intf, NULL); */
289
290     UpdateCaps( p_intf );
291
292     return VLC_SUCCESS;
293 }
294
295 /*****************************************************************************
296  * Close: destroy interface
297  *****************************************************************************/
298
299 static void Close   ( vlc_object_t *p_this )
300 {
301     intf_thread_t   *p_intf     = (intf_thread_t*) p_this;
302     intf_sys_t      *p_sys      = p_intf->p_sys;
303     playlist_t      *p_playlist = p_sys->p_playlist;
304
305     var_DelCallback( p_playlist, "item-current", AllCallback, p_intf );
306     var_DelCallback( p_playlist, "intf-change", AllCallback, p_intf );
307     var_DelCallback( p_playlist, "playlist-item-append", AllCallback, p_intf );
308     var_DelCallback( p_playlist, "playlist-item-deleted", AllCallback, p_intf );
309     var_DelCallback( p_playlist, "random", AllCallback, p_intf );
310     var_DelCallback( p_playlist, "repeat", AllCallback, p_intf );
311     var_DelCallback( p_playlist, "loop", AllCallback, p_intf );
312
313     if( p_sys->p_input )
314     {
315         var_DelCallback( p_sys->p_input, "intf-event", AllCallback, p_intf );
316         vlc_object_release( p_sys->p_input );
317     }
318
319     /* The dbus connection is private, so we are responsible
320      * for closing it */
321     dbus_connection_close( p_sys->p_conn );
322     dbus_connection_unref( p_sys->p_conn );
323
324     // Free the events array
325     for( int i = 0; i < vlc_array_count( p_sys->p_events ); i++ )
326     {
327         callback_info_t* info = vlc_array_item_at_index( p_sys->p_events, i );
328         free( info );
329     }
330     vlc_mutex_destroy( &p_sys->lock );
331     vlc_array_destroy( p_sys->p_events );
332     vlc_array_destroy( p_sys->p_timeouts );
333     vlc_array_destroy( p_sys->p_watches );
334     free( p_sys );
335 }
336
337 static void dispatch_status_cb( DBusConnection *p_conn,
338                                 DBusDispatchStatus i_status,
339                                 void *p_data)
340 {
341     (void) p_conn;
342     intf_thread_t *p_intf = (intf_thread_t*) p_data;
343
344     static const char *p_statuses[] = { "DATA_REMAINS",
345                                         "COMPLETE",
346                                         "NEED_MEMORY" };
347
348     msg_Dbg( p_intf,
349              "DBus dispatch status changed to %s.",
350              p_statuses[i_status]);
351 }
352
353 static dbus_bool_t add_timeout( DBusTimeout *p_timeout, void *p_data )
354 {
355     intf_thread_t *p_intf = (intf_thread_t*) p_data;
356     intf_sys_t    *p_sys  = (intf_sys_t*) p_intf->p_sys;
357
358     timeout_info_t *p_info = calloc( sizeof( timeout_info_t ), 1 );
359     p_info->i_remaining = dbus_timeout_get_interval( p_timeout ) * 1000;/* µs */
360     p_info->p_timeout = p_timeout;
361
362     dbus_timeout_set_data( p_timeout, p_info, free );
363
364     vlc_mutex_lock( &p_sys->lock );
365     vlc_array_append( p_sys->p_timeouts, p_timeout );
366     vlc_mutex_unlock( &p_sys->lock );
367
368     return TRUE;
369 }
370
371 static void remove_timeout( DBusTimeout *p_timeout, 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
378     vlc_array_remove( p_sys->p_timeouts,
379                       vlc_array_index_of_item( p_sys->p_timeouts, p_timeout ) );
380
381     vlc_mutex_unlock( &p_sys->lock );
382 }
383
384 static void timeout_toggled( DBusTimeout *p_timeout, void *p_data )
385 {
386     intf_thread_t *p_intf = (intf_thread_t*) p_data;
387
388     msg_Dbg( p_intf, "Toggling dbus timeout" );
389
390     if( dbus_timeout_get_enabled( p_timeout ) )
391     {
392         msg_Dbg( p_intf, "Timeout is enabled, main loop needs to wake up" );
393         wakeup_main_loop( p_intf );
394     }
395 }
396
397 static dbus_bool_t add_watch( DBusWatch *p_watch, void *p_data )
398 {
399     intf_thread_t *p_intf = (intf_thread_t*) p_data;
400     intf_sys_t    *p_sys  = (intf_sys_t*) p_intf->p_sys;
401     int            i_fd   = dbus_watch_get_unix_fd( p_watch );
402
403     msg_Dbg( p_intf, "Adding dbus watch on fd %d", i_fd );
404
405     if( dbus_watch_get_flags( p_watch ) & DBUS_WATCH_READABLE )
406         msg_Dbg( p_intf, "Watching fd %d for readability", i_fd );
407
408     if( dbus_watch_get_flags( p_watch ) & DBUS_WATCH_WRITABLE )
409         msg_Dbg( p_intf, "Watching fd %d for writeability", i_fd );
410
411     vlc_mutex_lock( &p_sys->lock );
412     vlc_array_append( p_sys->p_watches, p_watch );
413     vlc_mutex_unlock( &p_sys->lock );
414
415     return TRUE;
416 }
417
418 static void remove_watch( DBusWatch *p_watch, void *p_data )
419 {
420     intf_thread_t *p_intf = (intf_thread_t*) p_data;
421     intf_sys_t    *p_sys  = (intf_sys_t*) p_intf->p_sys;
422
423     msg_Dbg( p_intf, "Removing dbus watch on fd %d",
424               dbus_watch_get_unix_fd( p_watch ) );
425
426     vlc_mutex_lock( &p_sys->lock );
427
428     vlc_array_remove( p_sys->p_watches,
429                       vlc_array_index_of_item( p_sys->p_watches, p_watch ) );
430
431     vlc_mutex_unlock( &p_sys->lock );
432 }
433
434 static void watch_toggled( DBusWatch *p_watch, void *p_data )
435 {
436     intf_thread_t *p_intf = (intf_thread_t*) p_data;
437
438     msg_Dbg( p_intf, "Toggling dbus watch on fd %d",
439              dbus_watch_get_unix_fd( p_watch ) );
440
441     if( dbus_watch_get_enabled( p_watch ) )
442     {
443         msg_Dbg( p_intf,
444                   "Watch on fd %d has been enabled, "
445                   "the main loops needs to wake up",
446                   dbus_watch_get_unix_fd( p_watch ) );
447
448         wakeup_main_loop( p_intf );
449     }
450 }
451
452 /**
453  * GetPollFds() fills an array of pollfd data structures with :
454  *  - the set of enabled dbus watches
455  *  - the unix pipe which we use to manually wake up the main loop
456  *
457  * This function must be called with p_sys->lock locked
458  *
459  * @return The number of file descriptors
460  *
461  * @param intf_thread_t *p_intf this interface thread's state
462  * @param struct pollfd *p_fds a pointer to a pollfd array large enough to
463  * contain all the returned data (number of enabled dbus watches + 1)
464  */
465 static int GetPollFds( intf_thread_t *p_intf, struct pollfd *p_fds )
466 {
467     intf_sys_t *p_sys = p_intf->p_sys;
468     int i_fds = 1, i_watches = vlc_array_count( p_sys->p_watches );
469
470     p_fds[0].fd = p_sys->p_pipe_fds[PIPE_OUT];
471     p_fds[0].events = POLLIN | POLLPRI;
472
473     for( int i = 0; i < i_watches; i++ )
474     {
475         DBusWatch *p_watch = NULL;
476         p_watch = vlc_array_item_at_index( p_sys->p_watches, i );
477         if( !dbus_watch_get_enabled( p_watch ) )
478             continue;
479
480         p_fds[i_fds].fd = dbus_watch_get_unix_fd( p_watch );
481         int i_flags = dbus_watch_get_flags( p_watch );
482
483         if( i_flags & DBUS_WATCH_READABLE )
484             p_fds[i_fds].events |= POLLIN | POLLPRI;
485
486         if( i_flags & DBUS_WATCH_WRITABLE )
487             p_fds[i_fds].events |= POLLOUT;
488
489         i_fds++;
490     }
491
492     return i_fds;
493 }
494
495 /**
496  * UpdateTimeouts() updates the remaining time for each timeout and
497  * returns how much time is left until the next timeout.
498  *
499  * This function must be called with p_sys->lock locked
500  *
501  * @return int The time remaining until the next timeout, in milliseconds
502  * or -1 if there are no timeouts
503  *
504  * @param intf_thread_t *p_intf This interface thread's state
505  * @param mtime_t i_loop_interval The time which has elapsed since the last
506  * call to this function
507  */
508 static int UpdateTimeouts( intf_thread_t *p_intf, mtime_t i_loop_interval )
509 {
510     intf_sys_t *p_sys = p_intf->p_sys;
511     mtime_t i_next_timeout = LAST_MDATE;
512     unsigned int i_timeouts = vlc_array_count( p_sys->p_timeouts );
513
514     if( 0 == i_timeouts )
515         return -1;
516
517     for( unsigned int i = 0; i < i_timeouts; i++ )
518     {
519         timeout_info_t *p_info = NULL;
520         DBusTimeout    *p_timeout = NULL;
521         mtime_t         i_interval = 0;
522
523         p_timeout = vlc_array_item_at_index( p_sys->p_timeouts, i );
524         i_interval = dbus_timeout_get_interval( p_timeout ) * 1000; /* µs */
525         p_info = (timeout_info_t*) dbus_timeout_get_data( p_timeout );
526
527         p_info->i_remaining -= __MAX( 0, i_loop_interval ) % i_interval;
528
529         if( !dbus_timeout_get_enabled( p_timeout ) )
530             continue;
531
532         /* The correct poll timeout value is the shortest one
533          * in the dbus timeouts list */
534         i_next_timeout = __MIN( i_next_timeout,
535                                 __MAX( 0, p_info->i_remaining ) );
536     }
537
538     /* next timeout in milliseconds */
539     return i_next_timeout / 1000;
540 }
541
542 /**
543  * ProcessEvents() reacts to a list of events originating from other VLC threads
544  *
545  * This function must be called with p_sys->lock unlocked
546  *
547  * @param intf_thread_t *p_intf This interface thread state
548  * @param callback_info_t *p_events the list of events to process
549  */
550 static void ProcessEvents( intf_thread_t *p_intf,
551                            callback_info_t **p_events, int i_events )
552 {
553     for( int i = 0; i < i_events; i++ )
554     {
555         switch( p_events[i]->signal )
556         {
557         case SIGNAL_ITEM_CURRENT:
558             TrackChange( p_intf );
559             break;
560         case SIGNAL_INTF_CHANGE:
561         case SIGNAL_PLAYLIST_ITEM_APPEND:
562         case SIGNAL_PLAYLIST_ITEM_DELETED:
563             TrackListChangeEmit( p_intf,
564                                  p_events[i]->signal,
565                                  p_events[i]->i_node );
566             break;
567         case SIGNAL_RANDOM:
568         case SIGNAL_REPEAT:
569         case SIGNAL_LOOP:
570             StatusChangeEmit( p_intf );
571             break;
572         case SIGNAL_STATE:
573             StateChange( p_intf );
574             break;
575         case SIGNAL_INPUT_METADATA:
576             break;
577         default:
578             assert(0);
579         }
580         free( p_events[i] );
581     }
582 }
583
584 /**
585  * ProcessWatches() handles a list of dbus watches after poll() has returned
586  *
587  * This function must be called with p_sys->lock unlocked
588  *
589  * @param intf_thread_t *p_intf This interface thread state
590  * @param DBusWatch **p_watches The list of dbus watches to process
591  * @param int i_watches The size of the p_watches array
592  * @param struct pollfd *p_fds The result of a poll() call
593  * @param int i_fds The number of file descriptors processed by poll()
594  */
595 static void ProcessWatches( intf_thread_t *p_intf,
596                             DBusWatch **p_watches, int i_watches,
597                             struct pollfd *p_fds,  int i_fds )
598 {
599     /* Process watches */
600     for( int i = 0; i < i_watches; i++ )
601     {
602         DBusWatch *p_watch = p_watches[i];
603         if( !dbus_watch_get_enabled( p_watch ) )
604             continue;
605
606         for( int j = 0; j < i_fds; j++ )
607         {
608             if( p_fds[j].fd != dbus_watch_get_unix_fd( p_watch ) )
609                 continue;
610
611             int i_flags   = 0;
612             int i_revents = p_fds[j].revents;
613             int i_fd      = p_fds[j].fd;
614
615             if( i_revents & POLLIN )
616             {
617                 msg_Dbg( p_intf, "fd %d is ready for reading", i_fd );
618                 i_flags |= DBUS_WATCH_READABLE;
619             }
620
621             if( i_revents & POLLOUT )
622             {
623                 msg_Dbg( p_intf, "fd %d is ready for writing", i_fd );
624                 i_flags |= DBUS_WATCH_WRITABLE;
625             }
626
627             if( i_revents & POLLERR )
628             {
629                 msg_Dbg( p_intf, "error when polling fd %d", i_fd );
630                 i_flags |= DBUS_WATCH_ERROR;
631             }
632
633             if( i_revents & POLLHUP )
634             {
635                 msg_Dbg( p_intf, "Hangup signal on fd %d", i_fd );
636                 i_flags |= DBUS_WATCH_HANGUP;
637             }
638
639             if( i_flags )
640             {
641                 msg_Dbg( p_intf, "Handling dbus watch on fd %d", i_fd );
642                 dbus_watch_handle( p_watch, i_flags );
643             }
644             else
645                 msg_Dbg( p_intf, "Nothing happened on fd %d", i_fd );
646         }
647     }
648 }
649
650 /**
651  * ProcessTimeouts() handles DBus timeouts
652  *
653  * This function must be called with p_sys->lock locked
654  *
655  * @param intf_thread_t *p_intf This interface thread state
656  * @param DBusTimeout **p_timeouts List of timeouts to process
657  * @param int i_timeouts Size of p_timeouts
658  */
659 static void ProcessTimeouts( intf_thread_t *p_intf,
660                              DBusTimeout  **p_timeouts, int i_timeouts )
661 {
662     VLC_UNUSED( p_intf );
663
664     for( int i = 0; i < i_timeouts; i++ )
665     {
666         timeout_info_t *p_info = NULL;
667
668         p_info = (timeout_info_t*) dbus_timeout_get_data( p_timeouts[i] );
669
670         if( !dbus_timeout_get_enabled( p_info->p_timeout ) )
671             continue;
672
673         if( p_info->i_remaining > 0 )
674             continue;
675
676         dbus_timeout_handle( p_info->p_timeout );
677         p_info->i_remaining = dbus_timeout_get_interval( p_info->p_timeout );
678     }
679 }
680
681 /**
682  * DispatchDBusMessages() dispatches incoming dbus messages
683  * (indirectly invoking the callbacks), then it sends outgoing
684  * messages which needs to be sent on the bus (method replies and signals)
685  *
686  * This function must be called with p_sys->lock unlocked
687  *
688  * @param intf_thread_t *p_intf This interface thread state
689  */
690 static void DispatchDBusMessages( intf_thread_t *p_intf )
691 {
692     DBusDispatchStatus status;
693     intf_sys_t *p_sys = p_intf->p_sys;
694
695     /* Dispatch incoming messages */
696     status = dbus_connection_get_dispatch_status( p_sys->p_conn );
697     while( status != DBUS_DISPATCH_COMPLETE )
698     {
699         msg_Dbg( p_intf, "Dispatching incoming dbus message" );
700         dbus_connection_dispatch( p_sys->p_conn );
701         status = dbus_connection_get_dispatch_status( p_sys->p_conn );
702     }
703
704     /* Send outgoing data */
705     if( dbus_connection_has_messages_to_send( p_sys->p_conn ) )
706     {
707         msg_Dbg( p_intf, "Sending outgoing data" );
708         dbus_connection_flush( p_sys->p_conn );
709     }
710 }
711
712 /*****************************************************************************
713  * Run: main loop
714  *****************************************************************************/
715
716 static void Run          ( intf_thread_t *p_intf )
717 {
718     intf_sys_t    *p_sys = p_intf->p_sys;
719     mtime_t        i_last_run = mdate();
720
721     for( ;; )
722     {
723         int canc = vlc_savecancel();
724         vlc_mutex_lock( &p_sys->lock );
725
726         int i_watches = vlc_array_count( p_sys->p_watches );
727         struct pollfd *p_fds = calloc( sizeof( struct pollfd ), i_watches );
728
729         int i_fds = GetPollFds( p_intf, p_fds );
730
731         mtime_t i_now = mdate(), i_loop_interval = i_now - i_last_run;
732
733         msg_Dbg( p_intf,
734                  "%lld µs elapsed since last wakeup",
735                  (long long) i_loop_interval );
736
737         int i_next_timeout = UpdateTimeouts( p_intf, i_loop_interval );
738         i_last_run = i_now;
739
740         vlc_mutex_unlock( &p_sys->lock );
741
742         if( -1 != i_next_timeout )
743             msg_Dbg( p_intf, "next timeout is in %d ms", i_next_timeout );
744         msg_Dbg( p_intf, "Sleeping until something happens" );
745
746         /* thread cancellation is allowed while the main loop sleeps */
747         vlc_restorecancel( canc );
748
749         int i_pollres = poll( p_fds, i_fds, i_next_timeout );
750         int i_errsv   = errno;
751
752         canc = vlc_savecancel();
753
754         msg_Dbg( p_intf, "the main loop has been woken up" );
755
756         if( -1 == i_pollres )
757         { /* XXX: What should we do when poll() fails ? */
758             char buf[64];
759             msg_Err( p_intf, "poll() failed: %s", strerror_r( i_errsv, buf, 64 ) );
760             free( p_fds ); p_fds = NULL;
761             vlc_restorecancel( canc );
762             continue;
763         }
764
765         /* Was the main loop woken up manually ? */
766         if( 0 < i_pollres && ( p_fds[0].revents & POLLIN ) )
767         {
768             char buf;
769             msg_Dbg( p_intf, "Removing a byte from the self-pipe" );
770             (void)read( p_fds[0].fd, &buf, 1 );
771         }
772
773         /* We need to lock the mutex while building lists of events,
774          * timeouts and watches to process but we can't keep the lock while
775          * processing them, or else we risk a deadlock:
776          *
777          * The signal functions could lock mutex X while p_events is locked;
778          * While some other function in vlc (playlist) might lock mutex X
779          * and then set a variable which would call AllCallback(), which itself
780          * needs to lock p_events to add a new event.
781          */
782         vlc_mutex_lock( &p_intf->p_sys->lock );
783
784         /* Get the list of timeouts to process */
785         unsigned int i_timeouts = vlc_array_count( p_sys->p_timeouts );
786         DBusTimeout *p_timeouts[i_timeouts];
787         for( unsigned int i = 0; i < i_timeouts; i++ )
788         {
789             p_timeouts[i] = vlc_array_item_at_index( p_sys->p_timeouts, i );
790         }
791
792         /* Get the list of watches to process */
793         i_watches = vlc_array_count( p_sys->p_watches );
794         DBusWatch *p_watches[i_watches];
795         for( int i = 0; i < i_watches; i++ )
796         {
797             p_watches[i] = vlc_array_item_at_index( p_sys->p_watches, i );
798         }
799
800         /* Get the list of events to process */
801         int i_events = vlc_array_count( p_intf->p_sys->p_events );
802         callback_info_t* p_info[i_events];
803         for( int i = i_events - 1; i >= 0; i-- )
804         {
805             p_info[i] = vlc_array_item_at_index( p_intf->p_sys->p_events, i );
806             vlc_array_remove( p_intf->p_sys->p_events, i );
807         }
808
809         /* now we can release the lock and process what's pending */
810         vlc_mutex_unlock( &p_intf->p_sys->lock );
811
812         ProcessEvents( p_intf, p_info, i_events );
813         ProcessWatches( p_intf, p_watches, i_watches, p_fds, i_fds );
814
815         free( p_fds ); p_fds = NULL;
816
817         ProcessTimeouts( p_intf, p_timeouts, i_timeouts );
818         DispatchDBusMessages( p_intf );
819
820         vlc_restorecancel( canc );
821     }
822 }
823
824 static void   wakeup_main_loop( void *p_data )
825 {
826     intf_thread_t *p_intf = (intf_thread_t*) p_data;
827
828     msg_Dbg( p_intf, "Sending wakeup signal to the main loop" );
829
830     if( !write( p_intf->p_sys->p_pipe_fds[PIPE_IN], "\0", 1 ) )
831     {
832         msg_Err( p_intf,
833             "Could not wake up the main loop: %s", strerror( errno ) );
834     }
835 }
836
837 /*****************************************************************************
838  * UpdateCaps: update p_sys->i_caps
839  * This function have to be called with the playlist unlocked
840  ****************************************************************************/
841 int UpdateCaps( intf_thread_t* p_intf )
842 {
843     intf_sys_t* p_sys = p_intf->p_sys;
844     dbus_int32_t i_caps = CAPS_CAN_HAS_TRACKLIST;
845     playlist_t* p_playlist = p_sys->p_playlist;
846
847     PL_LOCK;
848     if( p_playlist->current.i_size > 0 )
849         i_caps |= CAPS_CAN_PLAY | CAPS_CAN_GO_PREV | CAPS_CAN_GO_NEXT;
850     PL_UNLOCK;
851
852     input_thread_t* p_input = playlist_CurrentInput( p_playlist );
853     if( p_input )
854     {
855         /* XXX: if UpdateCaps() is called too early, these are
856          * unconditionnaly true */
857         if( var_GetBool( p_input, "can-pause" ) )
858             i_caps |= CAPS_CAN_PAUSE;
859         if( var_GetBool( p_input, "can-seek" ) )
860             i_caps |= CAPS_CAN_SEEK;
861         vlc_object_release( p_input );
862     }
863
864     if( p_sys->b_meta_read )
865         i_caps |= CAPS_CAN_PROVIDE_METADATA;
866
867     if( i_caps != p_intf->p_sys->i_caps )
868     {
869         p_sys->i_caps = i_caps;
870         CapsChangeEmit( p_intf );
871     }
872
873     return VLC_SUCCESS;
874 }
875
876 /* InputIntfEventCallback() fills a callback_info_t data structure in response
877  * to an "intf-event" input event.
878  *
879  * Caution: This function executes in the input thread
880  *
881  * This function must be called with p_sys->lock locked
882  *
883  * @return int VLC_SUCCESS on success, VLC_E* on error
884  * @param intf_thread_t *p_intf the interface thread
885  * @param input_thread_t *p_input This input thread
886  * @param const int i_event input event type
887  * @param callback_info_t *p_info Location of the callback info to fill
888  */
889 static int InputIntfEventCallback( intf_thread_t   *p_intf,
890                                    input_thread_t  *p_input,
891                                    const int        i_event,
892                                    callback_info_t *p_info )
893 {
894     dbus_int32_t i_state = PLAYBACK_STATE_INVALID;
895     assert(!p_info->signal);
896
897     switch( i_event )
898     {
899         case INPUT_EVENT_DEAD:
900         case INPUT_EVENT_ABORT:
901             i_state = PLAYBACK_STATE_STOPPED;
902             break;
903         case INPUT_EVENT_STATE:
904             i_state = ( var_GetInteger( p_input, "state" ) == PAUSE_S ) ?
905                 PLAYBACK_STATE_PAUSED : PLAYBACK_STATE_PLAYING;
906             break;
907         case INPUT_EVENT_ITEM_META:
908             p_info->signal = SIGNAL_INPUT_METADATA;
909             return VLC_SUCCESS;
910         default:
911             return VLC_EGENERIC;
912     }
913
914     if( i_state != p_intf->p_sys->i_playing_state )
915     {
916         p_intf->p_sys->i_playing_state = i_state;
917         p_info->signal = SIGNAL_STATE;
918     }
919
920     return p_info->signal ? VLC_SUCCESS : VLC_EGENERIC;
921 }
922
923 // Get all the callbacks
924 static int AllCallback( vlc_object_t *p_this, const char *psz_var,
925                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
926 {
927     (void)p_this;
928     (void)oldval;
929
930     intf_thread_t *p_intf = (intf_thread_t*)p_data;
931     callback_info_t *info = calloc( 1, sizeof( callback_info_t ) );
932
933     if( !info )
934         return VLC_ENOMEM;
935
936     vlc_mutex_lock( &p_intf->p_sys->lock );
937
938     // Wich event is it ?
939     if( !strcmp( "item-current", psz_var ) )
940         info->signal = SIGNAL_ITEM_CURRENT;
941
942     else if( !strcmp( "intf-change", psz_var ) )
943         info->signal = SIGNAL_INTF_CHANGE;
944
945     else if( !strcmp( "playlist-item-append", psz_var ) )
946     {
947         info->signal = SIGNAL_PLAYLIST_ITEM_APPEND;
948         info->i_node = ((playlist_add_t*)newval.p_address)->i_node;
949     }
950
951     else if( !strcmp( "playlist-item-deleted", psz_var ) )
952         info->signal = SIGNAL_PLAYLIST_ITEM_DELETED;
953
954     else if( !strcmp( "random", psz_var ) )
955         info->signal = SIGNAL_RANDOM;
956
957     else if( !strcmp( "repeat", psz_var ) )
958         info->signal = SIGNAL_REPEAT;
959
960     else if( !strcmp( "loop", psz_var ) )
961         info->signal = SIGNAL_LOOP;
962
963     else if( !strcmp( "intf-event", psz_var ) )
964     {
965         int i_res;
966         i_res = InputIntfEventCallback( p_intf, p_this, newval.i_int, info );
967
968         if( VLC_SUCCESS != i_res )
969         {
970             vlc_mutex_unlock( &p_intf->p_sys->lock );
971             free( info );
972
973             return i_res;
974         }
975     }
976
977     else
978         assert(0);
979
980     // Append the event
981     vlc_array_append( p_intf->p_sys->p_events, info );
982     vlc_mutex_unlock( &p_intf->p_sys->lock );
983
984     msg_Dbg( p_intf,
985              "Got a VLC event on %s. The main loop needs to wake up "
986              "in order to process it", psz_var );
987
988     wakeup_main_loop( p_intf );
989
990     return VLC_SUCCESS;
991 }
992
993 /*****************************************************************************
994  * StateChange: callback on input "state"
995  *****************************************************************************/
996 static int StateChange( intf_thread_t *p_intf )
997 {
998     intf_sys_t          *p_sys      = p_intf->p_sys;
999     playlist_t          *p_playlist = p_sys->p_playlist;
1000     input_thread_t      *p_input;
1001     input_item_t        *p_item;
1002
1003     if( p_intf->p_sys->b_dead )
1004         return VLC_SUCCESS;
1005
1006     UpdateCaps( p_intf );
1007
1008     if( !p_sys->b_meta_read && p_sys->i_playing_state == 0)
1009     {
1010         p_input = playlist_CurrentInput( p_playlist );
1011         if( p_input )
1012         {
1013             p_item = input_GetItem( p_input );
1014             if( p_item )
1015             {
1016                 p_sys->b_meta_read = true;
1017                 TrackChangeEmit( p_intf, p_item );
1018             }
1019             vlc_object_release( p_input );
1020         }
1021     }
1022
1023     StatusChangeEmit( p_intf );
1024
1025     return VLC_SUCCESS;
1026 }
1027
1028 /*****************************************************************************
1029  * TrackChange: callback on playlist "item-current"
1030  *****************************************************************************/
1031 static int TrackChange( intf_thread_t *p_intf )
1032 {
1033     intf_sys_t          *p_sys      = p_intf->p_sys;
1034     playlist_t          *p_playlist = p_sys->p_playlist;
1035     input_thread_t      *p_input    = NULL;
1036     input_item_t        *p_item     = NULL;
1037
1038     if( p_intf->p_sys->b_dead )
1039         return VLC_SUCCESS;
1040
1041     if( p_sys->p_input )
1042     {
1043         var_DelCallback( p_sys->p_input, "intf-event", AllCallback, p_intf );
1044         vlc_object_release( p_sys->p_input );
1045         p_sys->p_input = NULL;
1046     }
1047
1048     p_sys->b_meta_read = false;
1049
1050     p_input = playlist_CurrentInput( p_playlist );
1051     if( !p_input )
1052     {
1053         return VLC_SUCCESS;
1054     }
1055
1056     p_item = input_GetItem( p_input );
1057     if( !p_item )
1058     {
1059         vlc_object_release( p_input );
1060         return VLC_EGENERIC;
1061     }
1062
1063     if( input_item_IsPreparsed( p_item ) )
1064     {
1065         p_sys->b_meta_read = true;
1066         TrackChangeEmit( p_intf, p_item );
1067     }
1068
1069     p_sys->p_input = p_input;
1070     var_AddCallback( p_input, "intf-event", AllCallback, p_intf );
1071
1072     return VLC_SUCCESS;
1073 }
1074
1075 /*****************************************************************************
1076  * GetInputMeta: Fill a DBusMessage with the given input item metadata
1077  *****************************************************************************/
1078
1079 #define ADD_META( entry, type, data ) \
1080     if( data ) { \
1081         dbus_message_iter_open_container( &dict, DBUS_TYPE_DICT_ENTRY, \
1082                 NULL, &dict_entry ); \
1083         dbus_message_iter_append_basic( &dict_entry, DBUS_TYPE_STRING, \
1084                 &ppsz_meta_items[entry] ); \
1085         dbus_message_iter_open_container( &dict_entry, DBUS_TYPE_VARIANT, \
1086                 type##_AS_STRING, &variant ); \
1087         dbus_message_iter_append_basic( &variant, \
1088                 type, \
1089                 & data ); \
1090         dbus_message_iter_close_container( &dict_entry, &variant ); \
1091         dbus_message_iter_close_container( &dict, &dict_entry ); }
1092
1093 #define ADD_VLC_META_STRING( entry, item ) \
1094     { \
1095         char * psz = input_item_Get##item( p_input );\
1096         ADD_META( entry, DBUS_TYPE_STRING, \
1097                   psz ); \
1098         free( psz ); \
1099     }
1100
1101 int GetInputMeta( input_item_t* p_input,
1102                         DBusMessageIter *args )
1103 {
1104     DBusMessageIter dict, dict_entry, variant;
1105     /** The duration of the track can be expressed in second, milli-seconds and
1106         µ-seconds */
1107     dbus_int64_t i_mtime = input_item_GetDuration( p_input );
1108     dbus_uint32_t i_time = i_mtime / 1000000;
1109     dbus_int64_t i_length = i_mtime / 1000;
1110
1111     const char* ppsz_meta_items[] =
1112     {
1113     /* Official MPRIS metas */
1114     "location", "title", "artist", "album", "tracknumber", "time", "mtime",
1115     "genre", "rating", "date", "arturl",
1116     "audio-bitrate", "audio-samplerate", "video-bitrate",
1117     /* VLC specifics metas */
1118     "audio-codec", "copyright", "description", "encodedby", "language", "length",
1119     "nowplaying", "publisher", "setting", "status", "trackid", "url",
1120     "video-codec"
1121     };
1122
1123     dbus_message_iter_open_container( args, DBUS_TYPE_ARRAY, "{sv}", &dict );
1124
1125     ADD_VLC_META_STRING( 0,  URI );
1126     ADD_VLC_META_STRING( 1,  Title );
1127     ADD_VLC_META_STRING( 2,  Artist );
1128     ADD_VLC_META_STRING( 3,  Album );
1129     ADD_VLC_META_STRING( 4,  TrackNum );
1130     ADD_META( 5, DBUS_TYPE_UINT32, i_time );
1131     ADD_META( 6, DBUS_TYPE_UINT32, i_mtime );
1132     ADD_VLC_META_STRING( 7,  Genre );
1133     ADD_VLC_META_STRING( 8,  Rating );
1134     ADD_VLC_META_STRING( 9,  Date );
1135     ADD_VLC_META_STRING( 10, ArtURL );
1136
1137     ADD_VLC_META_STRING( 15, Copyright );
1138     ADD_VLC_META_STRING( 16, Description );
1139     ADD_VLC_META_STRING( 17, EncodedBy );
1140     ADD_VLC_META_STRING( 18, Language );
1141     ADD_META( 19, DBUS_TYPE_INT64, i_length );
1142     ADD_VLC_META_STRING( 20, NowPlaying );
1143     ADD_VLC_META_STRING( 21, Publisher );
1144     ADD_VLC_META_STRING( 22, Setting );
1145     ADD_VLC_META_STRING( 24, TrackID );
1146     ADD_VLC_META_STRING( 25, URL );
1147
1148     vlc_mutex_lock( &p_input->lock );
1149     if( p_input->p_meta )
1150     {
1151         int i_status = vlc_meta_GetStatus( p_input->p_meta );
1152         ADD_META( 23, DBUS_TYPE_INT32, i_status );
1153     }
1154     vlc_mutex_unlock( &p_input->lock );
1155
1156     dbus_message_iter_close_container( args, &dict );
1157     return VLC_SUCCESS;
1158 }
1159
1160 #undef ADD_META
1161 #undef ADD_VLC_META_STRING
1162
1163