1 /*****************************************************************************
2 * dbus.c : D-Bus control interface
3 *****************************************************************************
4 * Copyright © 2006-2008 Rafaël Carré
5 * Copyright © 2007-2012 Mirsal Ennaime
6 * Copyright © 2009-2012 The VideoLAN team
7 * Copyright © 2013 Alex Merry
10 * Authors: Rafaël Carré <funman at videolanorg>
11 * Mirsal Ennaime <mirsal at mirsal fr>
12 * Alex Merry <dev at randomguy3 me uk>
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
27 *****************************************************************************/
30 * D-Bus Specification:
31 * http://dbus.freedesktop.org/doc/dbus-specification.html
32 * D-Bus low-level C API (libdbus)
33 * http://dbus.freedesktop.org/doc/dbus/api/html/index.html
35 * "If you use this low-level API directly, you're signing up for some pain."
37 * MPRIS Specification version 1.0
38 * http://wiki.xmms2.xmms.se/index.php/MPRIS
41 /*****************************************************************************
43 *****************************************************************************/
49 #include <dbus/dbus.h>
50 #include "dbus_common.h"
51 #include "dbus_root.h"
52 #include "dbus_player.h"
53 #include "dbus_tracklist.h"
54 #include "dbus_introspect.h"
56 #include <vlc_common.h>
57 #include <vlc_plugin.h>
58 #include <vlc_interface.h>
59 #include <vlc_playlist.h>
61 #include <vlc_mtime.h>
71 #define DBUS_MPRIS_BUS_NAME "org.mpris.MediaPlayer2.vlc"
72 #define DBUS_INSTANCE_ID_PREFIX "instance"
74 #define SEEK_THRESHOLD 1000 /* µsec */
76 /*****************************************************************************
78 *****************************************************************************/
80 static DBusHandlerResult
81 MPRISEntryPoint ( DBusConnection *p_conn, DBusMessage *p_from, void *p_this );
83 static const DBusObjectPathVTable dbus_mpris_vtable = {
84 NULL, MPRISEntryPoint, /* handler function */
85 NULL, NULL, NULL, NULL
98 DBusTimeout *p_timeout;
107 static int Open ( vlc_object_t * );
108 static void Close ( vlc_object_t * );
109 static void *Run ( void * );
111 static int TrackChange( intf_thread_t * );
112 static int AllCallback( vlc_object_t*, const char*, vlc_value_t, vlc_value_t, void* );
113 static int InputCallback( vlc_object_t*, const char*, vlc_value_t, vlc_value_t, void* );
115 static dbus_bool_t add_timeout ( DBusTimeout *p_timeout, void *p_data );
116 static dbus_bool_t add_watch ( DBusWatch *p_watch, void *p_data );
118 static void remove_timeout ( DBusTimeout *p_timeout, void *p_data );
119 static void remove_watch ( DBusWatch *p_watch, void *p_data );
121 static void timeout_toggled ( DBusTimeout *p_timeout, void *p_data );
122 static void watch_toggled ( DBusWatch *p_watch, void *p_data );
124 static void wakeup_main_loop( void *p_data );
126 static int UpdateTimeouts( intf_thread_t *p_intf, mtime_t i_lastrun );
128 static void ProcessEvents ( intf_thread_t *p_intf,
129 callback_info_t **p_events,
132 static void ProcessWatches ( intf_thread_t *p_intf,
133 DBusWatch **p_watches,
135 struct pollfd *p_fds,
138 static void ProcessTimeouts( intf_thread_t *p_intf,
139 DBusTimeout **p_timeouts,
142 static void DispatchDBusMessages( intf_thread_t *p_intf );
144 /*****************************************************************************
146 *****************************************************************************/
148 set_shortname( N_("DBus"))
149 set_category( CAT_INTERFACE )
150 set_description( N_("D-Bus control interface") )
151 set_capability( "interface", 0 )
152 set_callbacks( Open, Close )
155 /*****************************************************************************
156 * Open: initialize interface
157 *****************************************************************************/
159 static int Open( vlc_object_t *p_this )
161 intf_thread_t *p_intf = (intf_thread_t*)p_this;
163 /* initialisation of the connection */
164 if( !dbus_threads_init_default() )
167 intf_sys_t *p_sys = calloc( 1, sizeof( intf_sys_t ) );
168 if( unlikely(!p_sys) )
171 playlist_t *p_playlist;
172 DBusConnection *p_conn;
173 p_sys->i_player_caps = PLAYER_CAPS_NONE;
174 p_sys->i_playing_state = PLAYBACK_STATE_INVALID;
176 if( vlc_pipe( p_sys->p_pipe_fds ) )
179 msg_Err( p_intf, "Could not create pipe" );
184 dbus_error_init( &error );
186 /* connect privately to the session bus
187 * the connection will not be shared with other vlc modules which use dbus,
188 * thus avoiding a whole class of concurrency issues */
189 p_conn = dbus_bus_get_private( DBUS_BUS_SESSION, &error );
192 msg_Err( p_this, "Failed to connect to the D-Bus session daemon: %s",
194 dbus_error_free( &error );
199 dbus_connection_set_exit_on_disconnect( p_conn, FALSE );
201 /* register an instance-specific well known name of the form
202 * org.mpris.MediaPlayer2.vlc.instanceXXXX where XXXX is the
203 * current process's pid */
204 size_t i_length = sizeof( DBUS_MPRIS_BUS_NAME ) +
205 sizeof( DBUS_INSTANCE_ID_PREFIX ) + 10;
207 char unique_service[i_length];
209 snprintf( unique_service, sizeof (unique_service),
210 DBUS_MPRIS_BUS_NAME"."DBUS_INSTANCE_ID_PREFIX"%"PRIu32,
211 (uint32_t)getpid() );
213 dbus_bus_request_name( p_conn, unique_service, 0, &error );
215 if( dbus_error_is_set( &error ) )
217 msg_Err( p_this, "Error requesting service name %s: %s",
218 unique_service, error.message );
219 dbus_error_free( &error );
223 msg_Dbg( p_intf, "listening on dbus as: %s", unique_service );
225 /* Try to register org.mpris.MediaPlayer2.vlc as well in case we are
226 * the only VLC instance currently connected to the bus */
227 dbus_bus_request_name( p_conn, DBUS_MPRIS_BUS_NAME, 0, NULL );
229 /* Register the entry point object path */
230 dbus_connection_register_object_path( p_conn, DBUS_MPRIS_OBJECT_PATH,
231 &dbus_mpris_vtable, p_this );
233 dbus_connection_flush( p_conn );
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 );
242 p_playlist = pl_Get( p_intf );
243 p_sys->p_playlist = p_playlist;
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 );
256 if( !dbus_connection_set_timeout_functions( p_conn,
263 if( !dbus_connection_set_watch_functions( p_conn,
270 if( vlc_clone( &p_sys->thread, Run, p_intf, VLC_THREAD_PRIORITY_LOW ) )
276 /* The dbus connection is private,
277 * so we are responsible for closing it
278 * XXX: Does this make sense when OOM ? */
279 dbus_connection_close( p_sys->p_conn );
280 dbus_connection_unref( p_conn );
282 vlc_array_destroy( p_sys->p_events );
283 vlc_array_destroy( p_sys->p_timeouts );
284 vlc_array_destroy( p_sys->p_watches );
286 vlc_mutex_destroy( &p_sys->lock );
292 /*****************************************************************************
293 * Close: destroy interface
294 *****************************************************************************/
296 static void Close ( vlc_object_t *p_this )
298 intf_thread_t *p_intf = (intf_thread_t*) p_this;
299 intf_sys_t *p_sys = p_intf->p_sys;
300 playlist_t *p_playlist = p_sys->p_playlist;
302 vlc_cancel( p_sys->thread );
303 vlc_join( p_sys->thread, NULL );
305 var_DelCallback( p_playlist, "activity", AllCallback, p_intf );
306 var_DelCallback( p_playlist, "intf-change", AllCallback, p_intf );
307 var_DelCallback( p_playlist, "volume", AllCallback, p_intf );
308 var_DelCallback( p_playlist, "mute", AllCallback, p_intf );
309 var_DelCallback( p_playlist, "playlist-item-append", AllCallback, p_intf );
310 var_DelCallback( p_playlist, "playlist-item-deleted", AllCallback, p_intf );
311 var_DelCallback( p_playlist, "random", AllCallback, p_intf );
312 var_DelCallback( p_playlist, "repeat", AllCallback, p_intf );
313 var_DelCallback( p_playlist, "loop", AllCallback, p_intf );
314 var_DelCallback( p_playlist, "fullscreen", AllCallback, p_intf );
318 var_DelCallback( p_sys->p_input, "intf-event", InputCallback, p_intf );
319 var_DelCallback( p_sys->p_input, "can-pause", AllCallback, p_intf );
320 var_DelCallback( p_sys->p_input, "can-seek", AllCallback, p_intf );
321 vlc_object_release( p_sys->p_input );
324 /* The dbus connection is private, so we are responsible
326 dbus_connection_close( p_sys->p_conn );
327 dbus_connection_unref( p_sys->p_conn );
329 // Free the events array
330 for( int i = 0; i < vlc_array_count( p_sys->p_events ); i++ )
332 callback_info_t* info = vlc_array_item_at_index( p_sys->p_events, i );
335 vlc_mutex_destroy( &p_sys->lock );
336 vlc_array_destroy( p_sys->p_events );
337 vlc_array_destroy( p_sys->p_timeouts );
338 vlc_array_destroy( p_sys->p_watches );
342 static dbus_bool_t add_timeout( DBusTimeout *p_timeout, void *p_data )
344 intf_thread_t *p_intf = (intf_thread_t*) p_data;
345 intf_sys_t *p_sys = (intf_sys_t*) p_intf->p_sys;
347 timeout_info_t *p_info = calloc( 1, sizeof( timeout_info_t ) );
348 p_info->i_remaining = dbus_timeout_get_interval( p_timeout ) * 1000;/* µs */
349 p_info->p_timeout = p_timeout;
351 dbus_timeout_set_data( p_timeout, p_info, free );
353 vlc_mutex_lock( &p_sys->lock );
354 vlc_array_append( p_sys->p_timeouts, p_timeout );
355 vlc_mutex_unlock( &p_sys->lock );
360 static void remove_timeout( DBusTimeout *p_timeout, void *p_data )
362 intf_thread_t *p_intf = (intf_thread_t*) p_data;
363 intf_sys_t *p_sys = (intf_sys_t*) p_intf->p_sys;
365 vlc_mutex_lock( &p_sys->lock );
367 vlc_array_remove( p_sys->p_timeouts,
368 vlc_array_index_of_item( p_sys->p_timeouts, p_timeout ) );
370 vlc_mutex_unlock( &p_sys->lock );
373 static void timeout_toggled( DBusTimeout *p_timeout, void *p_data )
375 intf_thread_t *p_intf = (intf_thread_t*) p_data;
377 if( dbus_timeout_get_enabled( p_timeout ) )
378 wakeup_main_loop( p_intf );
381 static dbus_bool_t add_watch( DBusWatch *p_watch, void *p_data )
383 intf_thread_t *p_intf = (intf_thread_t*) p_data;
384 intf_sys_t *p_sys = (intf_sys_t*) p_intf->p_sys;
386 vlc_mutex_lock( &p_sys->lock );
387 vlc_array_append( p_sys->p_watches, p_watch );
388 vlc_mutex_unlock( &p_sys->lock );
393 static void remove_watch( DBusWatch *p_watch, void *p_data )
395 intf_thread_t *p_intf = (intf_thread_t*) p_data;
396 intf_sys_t *p_sys = (intf_sys_t*) p_intf->p_sys;
398 vlc_mutex_lock( &p_sys->lock );
400 vlc_array_remove( p_sys->p_watches,
401 vlc_array_index_of_item( p_sys->p_watches, p_watch ) );
403 vlc_mutex_unlock( &p_sys->lock );
406 static void watch_toggled( DBusWatch *p_watch, void *p_data )
408 intf_thread_t *p_intf = (intf_thread_t*) p_data;
410 if( dbus_watch_get_enabled( p_watch ) )
411 wakeup_main_loop( p_intf );
415 * GetPollFds() fills an array of pollfd data structures with :
416 * - the set of enabled dbus watches
417 * - the unix pipe which we use to manually wake up the main loop
419 * This function must be called with p_sys->lock locked
421 * @return The number of file descriptors
423 * @param intf_thread_t *p_intf this interface thread's state
424 * @param struct pollfd *p_fds a pointer to a pollfd array large enough to
425 * contain all the returned data (number of enabled dbus watches + 1)
427 static int GetPollFds( intf_thread_t *p_intf, struct pollfd *p_fds )
429 intf_sys_t *p_sys = p_intf->p_sys;
430 int i_fds = 1, i_watches = vlc_array_count( p_sys->p_watches );
432 p_fds[0].fd = p_sys->p_pipe_fds[PIPE_OUT];
433 p_fds[0].events = POLLIN | POLLPRI;
435 for( int i = 0; i < i_watches; i++ )
437 DBusWatch *p_watch = NULL;
438 p_watch = vlc_array_item_at_index( p_sys->p_watches, i );
439 if( !dbus_watch_get_enabled( p_watch ) )
442 p_fds[i_fds].fd = dbus_watch_get_unix_fd( p_watch );
443 int i_flags = dbus_watch_get_flags( p_watch );
445 if( i_flags & DBUS_WATCH_READABLE )
446 p_fds[i_fds].events |= POLLIN | POLLPRI;
448 if( i_flags & DBUS_WATCH_WRITABLE )
449 p_fds[i_fds].events |= POLLOUT;
458 * UpdateTimeouts() updates the remaining time for each timeout and
459 * returns how much time is left until the next timeout.
461 * This function must be called with p_sys->lock locked
463 * @return int The time remaining until the next timeout, in milliseconds
464 * or -1 if there are no timeouts
466 * @param intf_thread_t *p_intf This interface thread's state
467 * @param mtime_t i_loop_interval The time which has elapsed since the last
468 * call to this function
470 static int UpdateTimeouts( intf_thread_t *p_intf, mtime_t i_loop_interval )
472 intf_sys_t *p_sys = p_intf->p_sys;
473 mtime_t i_next_timeout = LAST_MDATE;
474 unsigned int i_timeouts = vlc_array_count( p_sys->p_timeouts );
476 if( 0 == i_timeouts )
479 for( unsigned int i = 0; i < i_timeouts; i++ )
481 timeout_info_t *p_info = NULL;
482 DBusTimeout *p_timeout = NULL;
483 mtime_t i_interval = 0;
485 p_timeout = vlc_array_item_at_index( p_sys->p_timeouts, i );
486 i_interval = dbus_timeout_get_interval( p_timeout ) * 1000; /* µs */
487 p_info = (timeout_info_t*) dbus_timeout_get_data( p_timeout );
489 p_info->i_remaining -= __MAX( 0, i_loop_interval ) % i_interval;
491 if( !dbus_timeout_get_enabled( p_timeout ) )
494 /* The correct poll timeout value is the shortest one
495 * in the dbus timeouts list */
496 i_next_timeout = __MIN( i_next_timeout,
497 __MAX( 0, p_info->i_remaining ) );
500 /* next timeout in milliseconds */
501 return i_next_timeout / 1000;
505 * ProcessEvents() reacts to a list of events originating from other VLC threads
507 * This function must be called with p_sys->lock unlocked
509 * @param intf_thread_t *p_intf This interface thread state
510 * @param callback_info_t *p_events the list of events to process
512 static void ProcessEvents( intf_thread_t *p_intf,
513 callback_info_t **p_events, int i_events )
515 playlist_t *p_playlist = p_intf->p_sys->p_playlist;
516 bool b_can_play = p_intf->p_sys->b_can_play;
518 vlc_dictionary_t player_properties, tracklist_properties, root_properties;
519 vlc_dictionary_init( &player_properties, 0 );
520 vlc_dictionary_init( &tracklist_properties, 0 );
521 vlc_dictionary_init( &root_properties, 0 );
523 for( int i = 0; i < i_events; i++ )
525 switch( p_events[i]->signal )
527 case SIGNAL_ITEM_CURRENT:
528 TrackChange( p_intf );
530 // rate depends on current item
531 if( !vlc_dictionary_has_key( &player_properties, "Rate" ) )
532 vlc_dictionary_insert( &player_properties, "Rate", NULL );
534 vlc_dictionary_insert( &player_properties, "Metadata", NULL );
536 case SIGNAL_INTF_CHANGE:
537 case SIGNAL_PLAYLIST_ITEM_APPEND:
538 case SIGNAL_PLAYLIST_ITEM_DELETED:
540 b_can_play = playlist_CurrentSize( p_playlist ) > 0;
543 if( b_can_play != p_intf->p_sys->b_can_play )
545 p_intf->p_sys->b_can_play = b_can_play;
546 vlc_dictionary_insert( &player_properties, "CanPlay", NULL );
549 if( !vlc_dictionary_has_key( &tracklist_properties, "Tracks" ) )
550 vlc_dictionary_insert( &tracklist_properties, "Tracks", NULL );
552 case SIGNAL_VOLUME_MUTED:
553 case SIGNAL_VOLUME_CHANGE:
554 vlc_dictionary_insert( &player_properties, "Volume", NULL );
557 vlc_dictionary_insert( &player_properties, "Shuffle", NULL );
559 case SIGNAL_FULLSCREEN:
560 vlc_dictionary_insert( &root_properties, "Fullscreen", NULL );
564 vlc_dictionary_insert( &player_properties, "LoopStatus", NULL );
567 vlc_dictionary_insert( &player_properties, "PlaybackStatus", NULL );
570 vlc_dictionary_insert( &player_properties, "Rate", NULL );
572 case SIGNAL_INPUT_METADATA:
574 input_thread_t *p_input = playlist_CurrentInput( p_playlist );
575 input_item_t *p_item;
578 p_item = input_GetItem( p_input );
579 vlc_object_release( p_input );
582 vlc_dictionary_insert( &player_properties,
587 case SIGNAL_CAN_SEEK:
588 vlc_dictionary_insert( &player_properties, "CanSeek", NULL );
590 case SIGNAL_CAN_PAUSE:
591 vlc_dictionary_insert( &player_properties, "CanPause", NULL );
595 input_thread_t *p_input;
596 input_item_t *p_item;
597 p_input = playlist_CurrentInput( p_intf->p_sys->p_playlist );
600 p_item = input_GetItem( p_input );
601 vlc_object_release( p_input );
603 if( p_item && ( p_item->i_id == p_events[i]->i_item ) )
604 SeekedEmit( p_intf );
614 if( vlc_dictionary_keys_count( &player_properties ) )
615 PlayerPropertiesChangedEmit( p_intf, &player_properties );
617 if( vlc_dictionary_keys_count( &tracklist_properties ) )
618 TrackListPropertiesChangedEmit( p_intf, &tracklist_properties );
620 if( vlc_dictionary_keys_count( &root_properties ) )
621 RootPropertiesChangedEmit( p_intf, &root_properties );
623 vlc_dictionary_clear( &player_properties, NULL, NULL );
624 vlc_dictionary_clear( &tracklist_properties, NULL, NULL );
625 vlc_dictionary_clear( &root_properties, NULL, NULL );
629 * ProcessWatches() handles a list of dbus watches after poll() has returned
631 * This function must be called with p_sys->lock unlocked
633 * @param intf_thread_t *p_intf This interface thread state
634 * @param DBusWatch **p_watches The list of dbus watches to process
635 * @param int i_watches The size of the p_watches array
636 * @param struct pollfd *p_fds The result of a poll() call
637 * @param int i_fds The number of file descriptors processed by poll()
639 static void ProcessWatches( intf_thread_t *p_intf,
640 DBusWatch **p_watches, int i_watches,
641 struct pollfd *p_fds, int i_fds )
645 /* Process watches */
646 for( int i = 0; i < i_watches; i++ )
648 DBusWatch *p_watch = p_watches[i];
649 if( !dbus_watch_get_enabled( p_watch ) )
652 for( int j = 0; j < i_fds; j++ )
654 if( p_fds[j].fd != dbus_watch_get_unix_fd( p_watch ) )
658 int i_revents = p_fds[j].revents;
660 if( i_revents & POLLIN )
661 i_flags |= DBUS_WATCH_READABLE;
663 if( i_revents & POLLOUT )
664 i_flags |= DBUS_WATCH_WRITABLE;
666 if( i_revents & POLLERR )
667 i_flags |= DBUS_WATCH_ERROR;
669 if( i_revents & POLLHUP )
670 i_flags |= DBUS_WATCH_HANGUP;
673 dbus_watch_handle( p_watch, i_flags );
679 * ProcessTimeouts() handles DBus timeouts
681 * This function must be called with p_sys->lock locked
683 * @param intf_thread_t *p_intf This interface thread state
684 * @param DBusTimeout **p_timeouts List of timeouts to process
685 * @param int i_timeouts Size of p_timeouts
687 static void ProcessTimeouts( intf_thread_t *p_intf,
688 DBusTimeout **p_timeouts, int i_timeouts )
690 VLC_UNUSED( p_intf );
692 for( int i = 0; i < i_timeouts; i++ )
694 timeout_info_t *p_info = NULL;
696 p_info = (timeout_info_t*) dbus_timeout_get_data( p_timeouts[i] );
698 if( !dbus_timeout_get_enabled( p_info->p_timeout ) )
701 if( p_info->i_remaining > 0 )
704 dbus_timeout_handle( p_info->p_timeout );
705 p_info->i_remaining = dbus_timeout_get_interval( p_info->p_timeout );
710 * DispatchDBusMessages() dispatches incoming dbus messages
711 * (indirectly invoking the callbacks), then it sends outgoing
712 * messages which needs to be sent on the bus (method replies and signals)
714 * This function must be called with p_sys->lock unlocked
716 * @param intf_thread_t *p_intf This interface thread state
718 static void DispatchDBusMessages( intf_thread_t *p_intf )
720 DBusDispatchStatus status;
721 intf_sys_t *p_sys = p_intf->p_sys;
723 /* Dispatch incoming messages */
724 status = dbus_connection_get_dispatch_status( p_sys->p_conn );
725 while( status != DBUS_DISPATCH_COMPLETE )
727 dbus_connection_dispatch( p_sys->p_conn );
728 status = dbus_connection_get_dispatch_status( p_sys->p_conn );
731 /* Send outgoing data */
732 if( dbus_connection_has_messages_to_send( p_sys->p_conn ) )
733 dbus_connection_flush( p_sys->p_conn );
737 * MPRISEntryPoint() routes incoming messages to their respective interface
740 * This function is called during dbus_connection_dispatch()
742 static DBusHandlerResult
743 MPRISEntryPoint ( DBusConnection *p_conn, DBusMessage *p_from, void *p_this )
745 const char *psz_target_interface;
746 const char *psz_interface = dbus_message_get_interface( p_from );
747 const char *psz_method = dbus_message_get_member( p_from );
751 if( psz_interface && strcmp( psz_interface, DBUS_INTERFACE_PROPERTIES ) )
752 psz_target_interface = psz_interface;
756 dbus_error_init( &error );
757 dbus_message_get_args( p_from, &error,
758 DBUS_TYPE_STRING, &psz_target_interface,
761 if( dbus_error_is_set( &error ) )
763 msg_Err( (vlc_object_t*) p_this, "D-Bus error on %s.%s: %s",
764 psz_interface, psz_method,
766 dbus_error_free( &error );
767 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
771 if( !strcmp( psz_target_interface, DBUS_INTERFACE_INTROSPECTABLE ) )
772 return handle_introspect( p_conn, p_from, p_this );
774 if( !strcmp( psz_target_interface, DBUS_MPRIS_ROOT_INTERFACE ) )
775 return handle_root( p_conn, p_from, p_this );
777 if( !strcmp( psz_target_interface, DBUS_MPRIS_PLAYER_INTERFACE ) )
778 return handle_player( p_conn, p_from, p_this );
780 if( !strcmp( psz_target_interface, DBUS_MPRIS_TRACKLIST_INTERFACE ) )
781 return handle_tracklist( p_conn, p_from, p_this );
783 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
786 /*****************************************************************************
788 *****************************************************************************/
790 static void *Run( void *data )
792 intf_thread_t *p_intf = data;
793 intf_sys_t *p_sys = p_intf->p_sys;
794 mtime_t i_last_run = mdate();
798 int canc = vlc_savecancel();
799 vlc_mutex_lock( &p_sys->lock );
801 int i_watches = vlc_array_count( p_sys->p_watches );
802 struct pollfd fds[i_watches];
803 memset(fds, 0, sizeof fds);
805 int i_fds = GetPollFds( p_intf, fds );
807 mtime_t i_now = mdate(), i_loop_interval = i_now - i_last_run;
809 int i_next_timeout = UpdateTimeouts( p_intf, i_loop_interval );
812 vlc_mutex_unlock( &p_sys->lock );
814 /* thread cancellation is allowed while the main loop sleeps */
815 vlc_restorecancel( canc );
817 int i_pollres = poll( fds, i_fds, i_next_timeout );
819 canc = vlc_savecancel();
821 if( -1 == i_pollres )
822 { /* XXX: What should we do when poll() fails ? */
823 msg_Err( p_intf, "poll() failed: %m" );
824 vlc_restorecancel( canc );
828 /* Was the main loop woken up manually ? */
829 if( 0 < i_pollres && ( fds[0].revents & POLLIN ) )
832 (void)read( fds[0].fd, &buf, 1 );
835 /* We need to lock the mutex while building lists of events,
836 * timeouts and watches to process but we can't keep the lock while
837 * processing them, or else we risk a deadlock:
839 * The signal functions could lock mutex X while p_events is locked;
840 * While some other function in vlc (playlist) might lock mutex X
841 * and then set a variable which would call AllCallback(), which itself
842 * needs to lock p_events to add a new event.
844 vlc_mutex_lock( &p_intf->p_sys->lock );
846 /* Get the list of timeouts to process */
847 unsigned int i_timeouts = vlc_array_count( p_sys->p_timeouts );
848 DBusTimeout *p_timeouts[i_timeouts];
849 for( unsigned int i = 0; i < i_timeouts; i++ )
851 p_timeouts[i] = vlc_array_item_at_index( p_sys->p_timeouts, i );
854 /* Get the list of watches to process */
855 i_watches = vlc_array_count( p_sys->p_watches );
856 DBusWatch *p_watches[i_watches];
857 for( int i = 0; i < i_watches; i++ )
859 p_watches[i] = vlc_array_item_at_index( p_sys->p_watches, i );
862 /* Get the list of events to process */
863 int i_events = vlc_array_count( p_intf->p_sys->p_events );
864 callback_info_t* p_info[i_events];
865 for( int i = i_events - 1; i >= 0; i-- )
867 p_info[i] = vlc_array_item_at_index( p_intf->p_sys->p_events, i );
868 vlc_array_remove( p_intf->p_sys->p_events, i );
871 /* now we can release the lock and process what's pending */
872 vlc_mutex_unlock( &p_intf->p_sys->lock );
874 ProcessEvents( p_intf, p_info, i_events );
875 ProcessWatches( p_intf, p_watches, i_watches, fds, i_fds );
877 ProcessTimeouts( p_intf, p_timeouts, i_timeouts );
878 DispatchDBusMessages( p_intf );
880 vlc_restorecancel( canc );
885 static void wakeup_main_loop( void *p_data )
887 intf_thread_t *p_intf = (intf_thread_t*) p_data;
889 if( !write( p_intf->p_sys->p_pipe_fds[PIPE_IN], "\0", 1 ) )
890 msg_Err( p_intf, "Could not wake up the main loop: %m" );
893 /* Flls a callback_info_t data structure in response
894 * to an "intf-event" input event.
896 * @warning This function executes in the input thread.
898 * @return VLC_SUCCESS on success, VLC_E* on error.
900 static int InputCallback( vlc_object_t *p_this, const char *psz_var,
901 vlc_value_t oldval, vlc_value_t newval, void *data )
903 input_thread_t *p_input = (input_thread_t *)p_this;
904 intf_thread_t *p_intf = data;
905 intf_sys_t *p_sys = p_intf->p_sys;
907 dbus_int32_t i_state = PLAYBACK_STATE_INVALID;
909 callback_info_t *p_info = calloc( 1, sizeof( callback_info_t ) );
910 if( unlikely(p_info == NULL) )
913 switch( newval.i_int )
915 case INPUT_EVENT_DEAD:
916 case INPUT_EVENT_ABORT:
917 i_state = PLAYBACK_STATE_STOPPED;
919 case INPUT_EVENT_STATE:
920 switch( var_GetInteger( p_input, "state" ) )
924 i_state = PLAYBACK_STATE_PLAYING;
927 i_state = PLAYBACK_STATE_PAUSED;
930 i_state = PLAYBACK_STATE_STOPPED;
933 case INPUT_EVENT_ITEM_META:
934 p_info->signal = SIGNAL_INPUT_METADATA;
936 case INPUT_EVENT_RATE:
937 p_info->signal = SIGNAL_RATE;
939 case INPUT_EVENT_POSITION:
941 mtime_t i_now = mdate(), i_pos, i_projected_pos, i_interval;
942 float f_current_rate;
945 * XXX: This is way more convoluted than it should be... */
946 i_pos = var_GetTime( p_input, "time" );
948 if( !p_intf->p_sys->i_last_input_pos_event ||
949 !( var_GetInteger( p_input, "state" ) == PLAYING_S ) )
951 p_intf->p_sys->i_last_input_pos_event = i_now;
952 p_intf->p_sys->i_last_input_pos = i_pos;
956 f_current_rate = var_GetFloat( p_input, "rate" );
957 i_interval = ( i_now - p_intf->p_sys->i_last_input_pos_event );
959 i_projected_pos = p_intf->p_sys->i_last_input_pos +
960 ( i_interval * f_current_rate );
962 p_intf->p_sys->i_last_input_pos_event = i_now;
963 p_intf->p_sys->i_last_input_pos = i_pos;
965 if( llabs( i_pos - i_projected_pos ) < SEEK_THRESHOLD )
968 p_info->signal = SIGNAL_SEEK;
969 p_info->i_item = input_GetItem( p_input )->i_id;
974 return VLC_SUCCESS; /* don't care */
977 vlc_mutex_lock( &p_sys->lock );
978 if( i_state != PLAYBACK_STATE_INVALID &&
979 i_state != p_sys->i_playing_state )
981 p_sys->i_playing_state = i_state;
982 p_info->signal = SIGNAL_STATE;
985 vlc_array_append( p_intf->p_sys->p_events, p_info );
988 vlc_mutex_unlock( &p_intf->p_sys->lock );
990 wakeup_main_loop( p_intf );
997 // Get all the callbacks
998 static int AllCallback( vlc_object_t *p_this, const char *psz_var,
999 vlc_value_t oldval, vlc_value_t newval, void *p_data )
1001 intf_thread_t *p_intf = p_data;
1002 callback_info_t info = { .signal = SIGNAL_NONE };
1004 // Wich event is it ?
1005 if( !strcmp( "activity", psz_var ) )
1006 info.signal = SIGNAL_ITEM_CURRENT;
1007 else if( !strcmp( "volume", psz_var ) )
1009 if( oldval.f_float != newval.f_float )
1010 info.signal = SIGNAL_VOLUME_CHANGE;
1012 else if( !strcmp( "mute", psz_var ) )
1014 if( oldval.b_bool != newval.b_bool )
1015 info.signal = SIGNAL_VOLUME_MUTED;
1017 else if( !strcmp( "intf-change", psz_var ) )
1018 info.signal = SIGNAL_INTF_CHANGE;
1019 else if( !strcmp( "playlist-item-append", psz_var ) )
1021 info.signal = SIGNAL_PLAYLIST_ITEM_APPEND;
1022 info.i_node = ((playlist_add_t*)newval.p_address)->i_node;
1024 else if( !strcmp( "playlist-item-deleted", psz_var ) )
1025 info.signal = SIGNAL_PLAYLIST_ITEM_DELETED;
1026 else if( !strcmp( "random", psz_var ) )
1027 info.signal = SIGNAL_RANDOM;
1028 else if( !strcmp( "fullscreen", psz_var ) )
1029 info.signal = SIGNAL_FULLSCREEN;
1030 else if( !strcmp( "repeat", psz_var ) )
1031 info.signal = SIGNAL_REPEAT;
1032 else if( !strcmp( "loop", psz_var ) )
1033 info.signal = SIGNAL_LOOP;
1034 else if( !strcmp( "can-seek", psz_var ) )
1035 info.signal = SIGNAL_CAN_SEEK;
1036 else if( !strcmp( "can-pause", psz_var ) )
1037 info.signal = SIGNAL_CAN_PAUSE;
1041 if( info.signal == SIGNAL_NONE )
1044 callback_info_t *p_info = malloc( sizeof( *p_info ) );
1045 if( unlikely(p_info == NULL) )
1050 vlc_mutex_lock( &p_intf->p_sys->lock );
1051 vlc_array_append( p_intf->p_sys->p_events, p_info );
1052 vlc_mutex_unlock( &p_intf->p_sys->lock );
1054 wakeup_main_loop( p_intf );
1059 /*****************************************************************************
1060 * TrackChange: callback on playlist "activity"
1061 *****************************************************************************/
1062 static int TrackChange( intf_thread_t *p_intf )
1064 intf_sys_t *p_sys = p_intf->p_sys;
1065 playlist_t *p_playlist = p_sys->p_playlist;
1066 input_thread_t *p_input = NULL;
1067 input_item_t *p_item = NULL;
1069 if( p_intf->p_sys->b_dead )
1072 if( p_sys->p_input )
1074 var_DelCallback( p_sys->p_input, "intf-event", InputCallback, p_intf );
1075 var_DelCallback( p_sys->p_input, "can-pause", AllCallback, p_intf );
1076 var_DelCallback( p_sys->p_input, "can-seek", AllCallback, p_intf );
1077 vlc_object_release( p_sys->p_input );
1078 p_sys->p_input = NULL;
1081 p_sys->b_meta_read = false;
1083 p_input = playlist_CurrentInput( p_playlist );
1089 p_item = input_GetItem( p_input );
1092 vlc_object_release( p_input );
1093 return VLC_EGENERIC;
1096 if( input_item_IsPreparsed( p_item ) )
1097 p_sys->b_meta_read = true;
1099 p_sys->p_input = p_input;
1100 var_AddCallback( p_input, "intf-event", InputCallback, p_intf );
1101 var_AddCallback( p_input, "can-pause", AllCallback, p_intf );
1102 var_AddCallback( p_input, "can-seek", AllCallback, p_intf );
1108 * DemarshalSetPropertyValue() extracts the new property value from a
1109 * org.freedesktop.DBus.Properties.Set method call message.
1111 * @return int VLC_SUCCESS on success
1112 * @param DBusMessage *p_msg a org.freedesktop.DBus.Properties.Set method call
1113 * @param void *p_arg placeholder for the demarshalled value
1115 int DemarshalSetPropertyValue( DBusMessage *p_msg, void *p_arg )
1118 bool b_valid_input = FALSE;
1119 DBusMessageIter in_args, variant;
1120 dbus_message_iter_init( p_msg, &in_args );
1124 i_type = dbus_message_iter_get_arg_type( &in_args );
1125 if( DBUS_TYPE_VARIANT == i_type )
1127 dbus_message_iter_recurse( &in_args, &variant );
1128 dbus_message_iter_get_basic( &variant, p_arg );
1129 b_valid_input = TRUE;
1131 } while( dbus_message_iter_next( &in_args ) );
1133 return b_valid_input ? VLC_SUCCESS : VLC_EGENERIC;
1136 /*****************************************************************************
1137 * GetInputMeta: Fill a DBusMessage with the given input item metadata
1138 *****************************************************************************/
1140 #define ADD_META( entry, type, data ) \
1142 dbus_message_iter_open_container( &dict, DBUS_TYPE_DICT_ENTRY, \
1143 NULL, &dict_entry ); \
1144 dbus_message_iter_append_basic( &dict_entry, DBUS_TYPE_STRING, \
1145 &ppsz_meta_items[entry] ); \
1146 dbus_message_iter_open_container( &dict_entry, DBUS_TYPE_VARIANT, \
1147 type##_AS_STRING, &variant ); \
1148 dbus_message_iter_append_basic( &variant, \
1151 dbus_message_iter_close_container( &dict_entry, &variant ); \
1152 dbus_message_iter_close_container( &dict, &dict_entry ); }
1154 #define ADD_VLC_META_STRING( entry, item ) \
1156 char * psz = input_item_Get##item( p_input );\
1157 ADD_META( entry, DBUS_TYPE_STRING, \
1162 #define ADD_META_SINGLETON_STRING_LIST( entry, item ) \
1164 char * psz = input_item_Get##item( p_input );\
1166 dbus_message_iter_open_container( &dict, DBUS_TYPE_DICT_ENTRY, \
1167 NULL, &dict_entry ); \
1168 dbus_message_iter_append_basic( &dict_entry, DBUS_TYPE_STRING, \
1169 &ppsz_meta_items[entry] ); \
1170 dbus_message_iter_open_container( &dict_entry, DBUS_TYPE_VARIANT, \
1172 dbus_message_iter_open_container( &variant, DBUS_TYPE_ARRAY, "s", \
1174 dbus_message_iter_append_basic( &list, \
1177 dbus_message_iter_close_container( &variant, &list ); \
1178 dbus_message_iter_close_container( &dict_entry, &variant ); \
1179 dbus_message_iter_close_container( &dict, &dict_entry ); \
1184 int GetInputMeta( input_item_t* p_input,
1185 DBusMessageIter *args )
1187 DBusMessageIter dict, dict_entry, variant, list;
1188 /** The duration of the track can be expressed in second, milli-seconds and
1190 dbus_int64_t i_mtime = input_item_GetDuration( p_input );
1191 dbus_uint32_t i_time = i_mtime / 1000000;
1192 dbus_int64_t i_length = i_mtime / 1000;
1195 if( -1 == asprintf( &psz_trackid, MPRIS_TRACKID_FORMAT, p_input->i_id ) )
1198 const char* ppsz_meta_items[] =
1200 "mpris:trackid", "xesam:url", "xesam:title", "xesam:artist", "xesam:album",
1201 "xesam:tracknumber", "vlc:time", "mpris:length", "xesam:genre",
1202 "xesam:userRating", "xesam:contentCreated", "mpris:artUrl", "mb:trackId",
1203 "vlc:audio-bitrate", "vlc:audio-samplerate", "vlc:video-bitrate",
1204 "vlc:audio-codec", "vlc:copyright", "xesam:comment", "vlc:encodedby",
1205 "language", "vlc:length", "vlc:nowplaying", "vlc:publisher", "vlc:setting",
1206 "status", "vlc:url", "vlc:video-codec"
1209 dbus_message_iter_open_container( args, DBUS_TYPE_ARRAY, "{sv}", &dict );
1211 ADD_META( 0, DBUS_TYPE_OBJECT_PATH, psz_trackid );
1212 ADD_VLC_META_STRING( 1, URI );
1213 ADD_VLC_META_STRING( 2, Title );
1214 ADD_META_SINGLETON_STRING_LIST( 3, Artist );
1215 ADD_VLC_META_STRING( 4, Album );
1216 ADD_VLC_META_STRING( 5, TrackNum );
1217 ADD_META( 6, DBUS_TYPE_UINT32, i_time );
1218 ADD_META( 7, DBUS_TYPE_INT64, i_mtime );
1219 ADD_META_SINGLETON_STRING_LIST( 8, Genre );
1220 //ADD_META( 9, DBUS_TYPE_DOUBLE, rating );
1221 ADD_VLC_META_STRING( 10, Date ); // this is supposed to be in ISO 8601 extended format
1222 ADD_VLC_META_STRING( 11, ArtURL );
1223 ADD_VLC_META_STRING( 12, TrackID );
1225 ADD_VLC_META_STRING( 17, Copyright );
1226 ADD_META_SINGLETON_STRING_LIST( 18, Description );
1227 ADD_VLC_META_STRING( 19, EncodedBy );
1228 ADD_VLC_META_STRING( 20, Language );
1229 ADD_META( 21, DBUS_TYPE_INT64, i_length );
1230 ADD_VLC_META_STRING( 22, NowPlaying );
1231 ADD_VLC_META_STRING( 23, Publisher );
1232 ADD_VLC_META_STRING( 24, Setting );
1233 ADD_VLC_META_STRING( 25, URL );
1235 free( psz_trackid );
1237 vlc_mutex_lock( &p_input->lock );
1238 if( p_input->p_meta )
1240 int i_status = vlc_meta_GetStatus( p_input->p_meta );
1241 ADD_META( 23, DBUS_TYPE_INT32, i_status );
1243 vlc_mutex_unlock( &p_input->lock );
1245 dbus_message_iter_close_container( args, &dict );
1249 int AddProperty( intf_thread_t *p_intf,
1250 DBusMessageIter *p_container,
1251 const char* psz_property_name,
1252 const char* psz_signature,
1253 int (*pf_marshaller) (intf_thread_t*, DBusMessageIter*) )
1255 DBusMessageIter entry, v;
1257 if( !dbus_message_iter_open_container( p_container,
1258 DBUS_TYPE_DICT_ENTRY, NULL,
1262 if( !dbus_message_iter_append_basic( &entry,
1264 &psz_property_name ) )
1267 if( !dbus_message_iter_open_container( &entry,
1268 DBUS_TYPE_VARIANT, psz_signature,
1272 if( VLC_SUCCESS != pf_marshaller( p_intf, &v ) )
1275 if( !dbus_message_iter_close_container( &entry, &v) )
1278 if( !dbus_message_iter_close_container( p_container, &entry ) )
1285 #undef ADD_VLC_META_STRING