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
9 * Authors: Rafaël Carré <funman at videolanorg>
10 * Mirsal Ennaime <mirsal at mirsal fr>
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.
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.
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 *****************************************************************************/
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
33 * "If you use this low-level API directly, you're signing up for some pain."
35 * MPRIS Specification version 1.0
36 * http://wiki.xmms2.xmms.se/index.php/MPRIS
39 /*****************************************************************************
41 *****************************************************************************/
47 #include <dbus/dbus.h>
49 #include "dbus_common.h"
50 #include "dbus_root.h"
51 #include "dbus_player.h"
52 #include "dbus_tracklist.h"
53 #include "dbus_introspect.h"
55 #include <vlc_common.h>
56 #include <vlc_plugin.h>
57 #include <vlc_interface.h>
58 #include <vlc_playlist.h>
60 #include <vlc_mtime.h>
71 /*****************************************************************************
73 *****************************************************************************/
85 DBusTimeout *p_timeout;
94 static int Open ( vlc_object_t * );
95 static void Close ( vlc_object_t * );
96 static void Run ( intf_thread_t * );
98 static int TrackChange( intf_thread_t * );
99 static int AllCallback( vlc_object_t*, const char*, vlc_value_t, vlc_value_t, void* );
101 static void dispatch_status_cb( DBusConnection *p_conn,
102 DBusDispatchStatus i_status,
105 static dbus_bool_t add_timeout ( DBusTimeout *p_timeout, void *p_data );
106 static dbus_bool_t add_watch ( DBusWatch *p_watch, void *p_data );
108 static void remove_timeout ( DBusTimeout *p_timeout, void *p_data );
109 static void remove_watch ( DBusWatch *p_watch, void *p_data );
111 static void timeout_toggled ( DBusTimeout *p_timeout, void *p_data );
112 static void watch_toggled ( DBusWatch *p_watch, void *p_data );
114 static void wakeup_main_loop( void *p_data );
116 static int GetPollFds( intf_thread_t *p_intf, struct pollfd *p_fds );
117 static int UpdateTimeouts( intf_thread_t *p_intf, mtime_t i_lastrun );
119 static void ProcessEvents ( intf_thread_t *p_intf,
120 callback_info_t **p_events,
123 static void ProcessWatches ( intf_thread_t *p_intf,
124 DBusWatch **p_watches,
126 struct pollfd *p_fds,
129 static void ProcessTimeouts( intf_thread_t *p_intf,
130 DBusTimeout **p_timeouts,
133 static void DispatchDBusMessages( intf_thread_t *p_intf );
135 /*****************************************************************************
137 *****************************************************************************/
138 #define DBUS_UNIQUE_TEXT N_("Unique DBUS service id (org.mpris.vlc-<pid>)")
139 #define DBUS_UNIQUE_LONGTEXT N_( \
140 "Use a unique dbus service id to identify this VLC instance on the DBUS bus. " \
141 "The process identifier (PID) is added to the service name: org.mpris.vlc-<pid>" )
144 set_shortname( N_("dbus"))
145 set_category( CAT_INTERFACE )
146 set_subcategory( SUBCAT_INTERFACE_CONTROL )
147 set_description( N_("D-Bus control interface") )
148 set_capability( "interface", 0 )
149 set_callbacks( Open, Close )
150 add_bool( "dbus-unique-service-id", false,
151 DBUS_UNIQUE_TEXT, DBUS_UNIQUE_LONGTEXT, true )
154 /*****************************************************************************
155 * Open: initialize interface
156 *****************************************************************************/
158 static int Open( vlc_object_t *p_this )
160 intf_thread_t *p_intf = (intf_thread_t*)p_this;
162 /* initialisation of the connection */
163 if( !dbus_threads_init_default() )
166 intf_sys_t *p_sys = calloc( 1, sizeof( intf_sys_t ) );
167 if( unlikely(!p_sys) )
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;
175 if( vlc_pipe( p_sys->p_pipe_fds ) )
178 msg_Err( p_intf, "Could not create pipe" );
182 char psz_service_name[sizeof(DBUS_MPRIS_BUS_NAME) + 12];
183 p_sys->b_unique = var_CreateGetBool( p_intf, "dbus-unique-service-id" );
184 if( p_sys->b_unique )
185 snprintf( psz_service_name, sizeof( psz_service_name ),
186 DBUS_MPRIS_BUS_NAME"-%d", getpid() );
188 strcpy( psz_service_name, DBUS_MPRIS_BUS_NAME );
191 dbus_error_init( &error );
193 /* connect privately to the session bus
194 * the connection will not be shared with other vlc modules which use dbus,
195 * thus avoiding a whole class of concurrency issues */
196 p_conn = dbus_bus_get_private( DBUS_BUS_SESSION, &error );
199 msg_Err( p_this, "Failed to connect to the D-Bus session daemon: %s",
201 dbus_error_free( &error );
206 dbus_connection_set_exit_on_disconnect( p_conn, FALSE );
208 /* register a well-known name on the bus */
209 dbus_bus_request_name( p_conn, psz_service_name, 0, &error );
210 if( dbus_error_is_set( &error ) )
212 msg_Err( p_this, "Error requesting service %s: %s",
213 psz_service_name, error.message );
214 dbus_error_free( &error );
218 msg_Info( p_intf, "listening on dbus as: %s", psz_service_name );
220 /* Register the entry point object path */
221 dbus_connection_register_object_path( p_conn, DBUS_MPRIS_OBJECT_PATH,
222 &dbus_mpris_vtable, p_this );
224 dbus_connection_flush( p_conn );
226 p_intf->pf_run = Run;
227 p_intf->p_sys = p_sys;
228 p_sys->p_conn = p_conn;
229 p_sys->p_events = vlc_array_new();
230 p_sys->p_timeouts = vlc_array_new();
231 p_sys->p_watches = vlc_array_new();
232 vlc_mutex_init( &p_sys->lock );
234 p_playlist = pl_Get( p_intf );
235 p_sys->p_playlist = p_playlist;
237 var_AddCallback( p_playlist, "item-current", AllCallback, p_intf );
238 var_AddCallback( p_playlist, "intf-change", AllCallback, p_intf );
239 var_AddCallback( p_playlist, "volume-change", AllCallback, p_intf );
240 var_AddCallback( p_playlist, "volume-muted", AllCallback, p_intf );
241 var_AddCallback( p_playlist, "playlist-item-append", AllCallback, p_intf );
242 var_AddCallback( p_playlist, "playlist-item-deleted", AllCallback, p_intf );
243 var_AddCallback( p_playlist, "random", AllCallback, p_intf );
244 var_AddCallback( p_playlist, "repeat", AllCallback, p_intf );
245 var_AddCallback( p_playlist, "loop", AllCallback, p_intf );
247 dbus_connection_set_dispatch_status_function( p_conn,
251 if( !dbus_connection_set_timeout_functions( p_conn,
257 dbus_connection_unref( p_conn );
262 if( !dbus_connection_set_watch_functions( p_conn,
268 dbus_connection_unref( p_conn );
273 /* dbus_connection_set_wakeup_main_function( p_conn,
280 /*****************************************************************************
281 * Close: destroy interface
282 *****************************************************************************/
284 static void Close ( vlc_object_t *p_this )
286 intf_thread_t *p_intf = (intf_thread_t*) p_this;
287 intf_sys_t *p_sys = p_intf->p_sys;
288 playlist_t *p_playlist = p_sys->p_playlist;
290 var_DelCallback( p_playlist, "item-current", AllCallback, p_intf );
291 var_DelCallback( p_playlist, "intf-change", AllCallback, p_intf );
292 var_DelCallback( p_playlist, "volume-change", AllCallback, p_intf );
293 var_DelCallback( p_playlist, "volume-muted", AllCallback, p_intf );
294 var_DelCallback( p_playlist, "playlist-item-append", AllCallback, p_intf );
295 var_DelCallback( p_playlist, "playlist-item-deleted", AllCallback, p_intf );
296 var_DelCallback( p_playlist, "random", AllCallback, p_intf );
297 var_DelCallback( p_playlist, "repeat", AllCallback, p_intf );
298 var_DelCallback( p_playlist, "loop", AllCallback, p_intf );
302 var_DelCallback( p_sys->p_input, "intf-event", AllCallback, p_intf );
303 var_DelCallback( p_sys->p_input, "can-pause", AllCallback, p_intf );
304 var_DelCallback( p_sys->p_input, "can-seek", AllCallback, p_intf );
305 vlc_object_release( p_sys->p_input );
308 /* The dbus connection is private, so we are responsible
310 dbus_connection_close( p_sys->p_conn );
311 dbus_connection_unref( p_sys->p_conn );
313 // Free the events array
314 for( int i = 0; i < vlc_array_count( p_sys->p_events ); i++ )
316 callback_info_t* info = vlc_array_item_at_index( p_sys->p_events, i );
319 vlc_mutex_destroy( &p_sys->lock );
320 vlc_array_destroy( p_sys->p_events );
321 vlc_array_destroy( p_sys->p_timeouts );
322 vlc_array_destroy( p_sys->p_watches );
326 static void dispatch_status_cb( DBusConnection *p_conn,
327 DBusDispatchStatus i_status,
331 intf_thread_t *p_intf = (intf_thread_t*) p_data;
333 static const char *p_statuses[] = { "DATA_REMAINS",
338 "DBus dispatch status changed to %s.",
339 p_statuses[i_status]);
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 msg_Dbg( p_intf, "Toggling dbus timeout" );
379 if( dbus_timeout_get_enabled( p_timeout ) )
381 msg_Dbg( p_intf, "Timeout is enabled, main loop needs to wake up" );
382 wakeup_main_loop( p_intf );
386 static dbus_bool_t add_watch( DBusWatch *p_watch, void *p_data )
388 intf_thread_t *p_intf = (intf_thread_t*) p_data;
389 intf_sys_t *p_sys = (intf_sys_t*) p_intf->p_sys;
390 int i_fd = dbus_watch_get_unix_fd( p_watch );
392 msg_Dbg( p_intf, "Adding dbus watch on fd %d", i_fd );
394 if( dbus_watch_get_flags( p_watch ) & DBUS_WATCH_READABLE )
395 msg_Dbg( p_intf, "Watching fd %d for readability", i_fd );
397 if( dbus_watch_get_flags( p_watch ) & DBUS_WATCH_WRITABLE )
398 msg_Dbg( p_intf, "Watching fd %d for writeability", i_fd );
400 vlc_mutex_lock( &p_sys->lock );
401 vlc_array_append( p_sys->p_watches, p_watch );
402 vlc_mutex_unlock( &p_sys->lock );
407 static void remove_watch( DBusWatch *p_watch, void *p_data )
409 intf_thread_t *p_intf = (intf_thread_t*) p_data;
410 intf_sys_t *p_sys = (intf_sys_t*) p_intf->p_sys;
412 msg_Dbg( p_intf, "Removing dbus watch on fd %d",
413 dbus_watch_get_unix_fd( p_watch ) );
415 vlc_mutex_lock( &p_sys->lock );
417 vlc_array_remove( p_sys->p_watches,
418 vlc_array_index_of_item( p_sys->p_watches, p_watch ) );
420 vlc_mutex_unlock( &p_sys->lock );
423 static void watch_toggled( DBusWatch *p_watch, void *p_data )
425 intf_thread_t *p_intf = (intf_thread_t*) p_data;
427 msg_Dbg( p_intf, "Toggling dbus watch on fd %d",
428 dbus_watch_get_unix_fd( p_watch ) );
430 if( dbus_watch_get_enabled( p_watch ) )
433 "Watch on fd %d has been enabled, "
434 "the main loops needs to wake up",
435 dbus_watch_get_unix_fd( p_watch ) );
437 wakeup_main_loop( p_intf );
442 * GetPollFds() fills an array of pollfd data structures with :
443 * - the set of enabled dbus watches
444 * - the unix pipe which we use to manually wake up the main loop
446 * This function must be called with p_sys->lock locked
448 * @return The number of file descriptors
450 * @param intf_thread_t *p_intf this interface thread's state
451 * @param struct pollfd *p_fds a pointer to a pollfd array large enough to
452 * contain all the returned data (number of enabled dbus watches + 1)
454 static int GetPollFds( intf_thread_t *p_intf, struct pollfd *p_fds )
456 intf_sys_t *p_sys = p_intf->p_sys;
457 int i_fds = 1, i_watches = vlc_array_count( p_sys->p_watches );
459 p_fds[0].fd = p_sys->p_pipe_fds[PIPE_OUT];
460 p_fds[0].events = POLLIN | POLLPRI;
462 for( int i = 0; i < i_watches; i++ )
464 DBusWatch *p_watch = NULL;
465 p_watch = vlc_array_item_at_index( p_sys->p_watches, i );
466 if( !dbus_watch_get_enabled( p_watch ) )
469 p_fds[i_fds].fd = dbus_watch_get_unix_fd( p_watch );
470 int i_flags = dbus_watch_get_flags( p_watch );
472 if( i_flags & DBUS_WATCH_READABLE )
473 p_fds[i_fds].events |= POLLIN | POLLPRI;
475 if( i_flags & DBUS_WATCH_WRITABLE )
476 p_fds[i_fds].events |= POLLOUT;
485 * UpdateTimeouts() updates the remaining time for each timeout and
486 * returns how much time is left until the next timeout.
488 * This function must be called with p_sys->lock locked
490 * @return int The time remaining until the next timeout, in milliseconds
491 * or -1 if there are no timeouts
493 * @param intf_thread_t *p_intf This interface thread's state
494 * @param mtime_t i_loop_interval The time which has elapsed since the last
495 * call to this function
497 static int UpdateTimeouts( intf_thread_t *p_intf, mtime_t i_loop_interval )
499 intf_sys_t *p_sys = p_intf->p_sys;
500 mtime_t i_next_timeout = LAST_MDATE;
501 unsigned int i_timeouts = vlc_array_count( p_sys->p_timeouts );
503 if( 0 == i_timeouts )
506 for( unsigned int i = 0; i < i_timeouts; i++ )
508 timeout_info_t *p_info = NULL;
509 DBusTimeout *p_timeout = NULL;
510 mtime_t i_interval = 0;
512 p_timeout = vlc_array_item_at_index( p_sys->p_timeouts, i );
513 i_interval = dbus_timeout_get_interval( p_timeout ) * 1000; /* µs */
514 p_info = (timeout_info_t*) dbus_timeout_get_data( p_timeout );
516 p_info->i_remaining -= __MAX( 0, i_loop_interval ) % i_interval;
518 if( !dbus_timeout_get_enabled( p_timeout ) )
521 /* The correct poll timeout value is the shortest one
522 * in the dbus timeouts list */
523 i_next_timeout = __MIN( i_next_timeout,
524 __MAX( 0, p_info->i_remaining ) );
527 /* next timeout in milliseconds */
528 return i_next_timeout / 1000;
532 * ProcessEvents() reacts to a list of events originating from other VLC threads
534 * This function must be called with p_sys->lock unlocked
536 * @param intf_thread_t *p_intf This interface thread state
537 * @param callback_info_t *p_events the list of events to process
539 static void ProcessEvents( intf_thread_t *p_intf,
540 callback_info_t **p_events, int i_events )
542 playlist_t *p_playlist = p_intf->p_sys->p_playlist;
543 bool b_can_play = p_intf->p_sys->b_can_play;
545 vlc_dictionary_t player_properties, tracklist_properties;
546 vlc_dictionary_init( &player_properties, 0 );
547 vlc_dictionary_init( &tracklist_properties, 0 );
549 for( int i = 0; i < i_events; i++ )
551 switch( p_events[i]->signal )
553 case SIGNAL_ITEM_CURRENT:
554 TrackChange( p_intf );
555 vlc_dictionary_insert( &player_properties, "Metadata", NULL );
557 case SIGNAL_INTF_CHANGE:
558 case SIGNAL_PLAYLIST_ITEM_APPEND:
559 case SIGNAL_PLAYLIST_ITEM_DELETED:
561 b_can_play = playlist_CurrentSize( p_playlist ) > 0;
564 if( b_can_play != p_intf->p_sys->b_can_play )
566 p_intf->p_sys->b_can_play = b_can_play;
567 vlc_dictionary_insert( &player_properties, "CanPlay", NULL );
570 vlc_dictionary_insert( &tracklist_properties, "Tracks", NULL );
572 case SIGNAL_VOLUME_MUTED:
573 case SIGNAL_VOLUME_CHANGE:
574 vlc_dictionary_insert( &player_properties, "Volume", NULL );
577 vlc_dictionary_insert( &player_properties, "Shuffle", NULL );
581 vlc_dictionary_insert( &player_properties, "LoopStatus", NULL );
584 vlc_dictionary_insert( &player_properties, "PlaybackStatus", NULL );
587 vlc_dictionary_insert( &player_properties, "Rate", NULL );
589 case SIGNAL_INPUT_METADATA:
591 input_thread_t *p_input = playlist_CurrentInput( p_playlist );
592 input_item_t *p_item;
595 p_item = input_GetItem( p_input );
596 vlc_object_release( p_input );
599 vlc_dictionary_insert( &player_properties,
604 case SIGNAL_CAN_SEEK:
605 vlc_dictionary_insert( &player_properties, "CanSeek", NULL );
607 case SIGNAL_CAN_PAUSE:
608 vlc_dictionary_insert( &player_properties, "CanPause", NULL );
612 input_thread_t *p_input;
613 input_item_t *p_item;
614 p_input = playlist_CurrentInput( p_intf->p_sys->p_playlist );
617 p_item = input_GetItem( p_input );
618 vlc_object_release( p_input );
620 if( p_item && ( p_item->i_id == p_events[i]->i_item ) )
621 SeekedEmit( p_intf );
631 if( vlc_dictionary_keys_count( &player_properties ) )
632 PlayerPropertiesChangedEmit( p_intf, &player_properties );
634 if( vlc_dictionary_keys_count( &tracklist_properties ) )
635 TrackListPropertiesChangedEmit( p_intf, &tracklist_properties );
637 vlc_dictionary_clear( &player_properties, NULL, NULL );
638 vlc_dictionary_clear( &tracklist_properties, NULL, NULL );
642 * ProcessWatches() handles a list of dbus watches after poll() has returned
644 * This function must be called with p_sys->lock unlocked
646 * @param intf_thread_t *p_intf This interface thread state
647 * @param DBusWatch **p_watches The list of dbus watches to process
648 * @param int i_watches The size of the p_watches array
649 * @param struct pollfd *p_fds The result of a poll() call
650 * @param int i_fds The number of file descriptors processed by poll()
652 static void ProcessWatches( intf_thread_t *p_intf,
653 DBusWatch **p_watches, int i_watches,
654 struct pollfd *p_fds, int i_fds )
656 /* Process watches */
657 for( int i = 0; i < i_watches; i++ )
659 DBusWatch *p_watch = p_watches[i];
660 if( !dbus_watch_get_enabled( p_watch ) )
663 for( int j = 0; j < i_fds; j++ )
665 if( p_fds[j].fd != dbus_watch_get_unix_fd( p_watch ) )
669 int i_revents = p_fds[j].revents;
670 int i_fd = p_fds[j].fd;
672 if( i_revents & POLLIN )
674 msg_Dbg( p_intf, "fd %d is ready for reading", i_fd );
675 i_flags |= DBUS_WATCH_READABLE;
678 if( i_revents & POLLOUT )
680 msg_Dbg( p_intf, "fd %d is ready for writing", i_fd );
681 i_flags |= DBUS_WATCH_WRITABLE;
684 if( i_revents & POLLERR )
686 msg_Dbg( p_intf, "error when polling fd %d", i_fd );
687 i_flags |= DBUS_WATCH_ERROR;
690 if( i_revents & POLLHUP )
692 msg_Dbg( p_intf, "Hangup signal on fd %d", i_fd );
693 i_flags |= DBUS_WATCH_HANGUP;
698 msg_Dbg( p_intf, "Handling dbus watch on fd %d", i_fd );
699 dbus_watch_handle( p_watch, i_flags );
702 msg_Dbg( p_intf, "Nothing happened on fd %d", i_fd );
708 * ProcessTimeouts() handles DBus timeouts
710 * This function must be called with p_sys->lock locked
712 * @param intf_thread_t *p_intf This interface thread state
713 * @param DBusTimeout **p_timeouts List of timeouts to process
714 * @param int i_timeouts Size of p_timeouts
716 static void ProcessTimeouts( intf_thread_t *p_intf,
717 DBusTimeout **p_timeouts, int i_timeouts )
719 VLC_UNUSED( p_intf );
721 for( int i = 0; i < i_timeouts; i++ )
723 timeout_info_t *p_info = NULL;
725 p_info = (timeout_info_t*) dbus_timeout_get_data( p_timeouts[i] );
727 if( !dbus_timeout_get_enabled( p_info->p_timeout ) )
730 if( p_info->i_remaining > 0 )
733 dbus_timeout_handle( p_info->p_timeout );
734 p_info->i_remaining = dbus_timeout_get_interval( p_info->p_timeout );
739 * DispatchDBusMessages() dispatches incoming dbus messages
740 * (indirectly invoking the callbacks), then it sends outgoing
741 * messages which needs to be sent on the bus (method replies and signals)
743 * This function must be called with p_sys->lock unlocked
745 * @param intf_thread_t *p_intf This interface thread state
747 static void DispatchDBusMessages( intf_thread_t *p_intf )
749 DBusDispatchStatus status;
750 intf_sys_t *p_sys = p_intf->p_sys;
752 /* Dispatch incoming messages */
753 status = dbus_connection_get_dispatch_status( p_sys->p_conn );
754 while( status != DBUS_DISPATCH_COMPLETE )
756 msg_Dbg( p_intf, "Dispatching incoming dbus message" );
757 dbus_connection_dispatch( p_sys->p_conn );
758 status = dbus_connection_get_dispatch_status( p_sys->p_conn );
761 /* Send outgoing data */
762 if( dbus_connection_has_messages_to_send( p_sys->p_conn ) )
764 msg_Dbg( p_intf, "Sending outgoing data" );
765 dbus_connection_flush( p_sys->p_conn );
770 * MPRISEntryPoint() routes incoming messages to their respective interface
773 * This function is called during dbus_connection_dispatch()
775 static DBusHandlerResult
776 MPRISEntryPoint ( DBusConnection *p_conn, DBusMessage *p_from, void *p_this )
778 const char *psz_target_interface;
779 const char *psz_interface = dbus_message_get_interface( p_from );
780 const char *psz_method = dbus_message_get_member( p_from );
784 if( strcmp( psz_interface, DBUS_INTERFACE_PROPERTIES ) )
785 psz_target_interface = psz_interface;
789 dbus_error_init( &error );
790 dbus_message_get_args( p_from, &error,
791 DBUS_TYPE_STRING, &psz_target_interface,
794 if( dbus_error_is_set( &error ) )
796 msg_Err( (vlc_object_t*) p_this, "D-Bus error on %s.%s: %s",
797 psz_interface, psz_method,
799 dbus_error_free( &error );
800 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
804 msg_Dbg( (vlc_object_t*) p_this, "Routing %s.%s D-Bus method call to %s",
805 psz_interface, psz_method,
806 psz_target_interface );
808 if( !strcmp( psz_target_interface, DBUS_INTERFACE_INTROSPECTABLE ) )
809 return handle_introspect( p_conn, p_from, p_this );
811 if( !strcmp( psz_target_interface, DBUS_MPRIS_ROOT_INTERFACE ) )
812 return handle_root( p_conn, p_from, p_this );
814 if( !strcmp( psz_target_interface, DBUS_MPRIS_PLAYER_INTERFACE ) )
815 return handle_player( p_conn, p_from, p_this );
817 if( !strcmp( psz_target_interface, DBUS_MPRIS_TRACKLIST_INTERFACE ) )
818 return handle_tracklist( p_conn, p_from, p_this );
820 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
823 /*****************************************************************************
825 *****************************************************************************/
827 static void Run ( intf_thread_t *p_intf )
829 intf_sys_t *p_sys = p_intf->p_sys;
830 mtime_t i_last_run = mdate();
834 int canc = vlc_savecancel();
835 vlc_mutex_lock( &p_sys->lock );
837 int i_watches = vlc_array_count( p_sys->p_watches );
838 struct pollfd *p_fds = calloc( i_watches, sizeof( struct pollfd ) );
840 int i_fds = GetPollFds( p_intf, p_fds );
842 mtime_t i_now = mdate(), i_loop_interval = i_now - i_last_run;
845 "%lld µs elapsed since last wakeup",
846 (long long) i_loop_interval );
848 int i_next_timeout = UpdateTimeouts( p_intf, i_loop_interval );
851 vlc_mutex_unlock( &p_sys->lock );
853 if( -1 != i_next_timeout )
854 msg_Dbg( p_intf, "next timeout is in %d ms", i_next_timeout );
855 msg_Dbg( p_intf, "Sleeping until something happens" );
857 /* thread cancellation is allowed while the main loop sleeps */
858 vlc_restorecancel( canc );
860 int i_pollres = poll( p_fds, i_fds, i_next_timeout );
863 canc = vlc_savecancel();
865 msg_Dbg( p_intf, "the main loop has been woken up" );
867 if( -1 == i_pollres )
868 { /* XXX: What should we do when poll() fails ? */
869 msg_Err( p_intf, "poll() failed: %m" );
870 free( p_fds ); p_fds = NULL;
871 vlc_restorecancel( canc );
875 /* Was the main loop woken up manually ? */
876 if( 0 < i_pollres && ( p_fds[0].revents & POLLIN ) )
879 msg_Dbg( p_intf, "Removing a byte from the self-pipe" );
880 (void)read( p_fds[0].fd, &buf, 1 );
883 /* We need to lock the mutex while building lists of events,
884 * timeouts and watches to process but we can't keep the lock while
885 * processing them, or else we risk a deadlock:
887 * The signal functions could lock mutex X while p_events is locked;
888 * While some other function in vlc (playlist) might lock mutex X
889 * and then set a variable which would call AllCallback(), which itself
890 * needs to lock p_events to add a new event.
892 vlc_mutex_lock( &p_intf->p_sys->lock );
894 /* Get the list of timeouts to process */
895 unsigned int i_timeouts = vlc_array_count( p_sys->p_timeouts );
896 DBusTimeout *p_timeouts[i_timeouts];
897 for( unsigned int i = 0; i < i_timeouts; i++ )
899 p_timeouts[i] = vlc_array_item_at_index( p_sys->p_timeouts, i );
902 /* Get the list of watches to process */
903 i_watches = vlc_array_count( p_sys->p_watches );
904 DBusWatch *p_watches[i_watches];
905 for( int i = 0; i < i_watches; i++ )
907 p_watches[i] = vlc_array_item_at_index( p_sys->p_watches, i );
910 /* Get the list of events to process */
911 int i_events = vlc_array_count( p_intf->p_sys->p_events );
912 callback_info_t* p_info[i_events];
913 for( int i = i_events - 1; i >= 0; i-- )
915 p_info[i] = vlc_array_item_at_index( p_intf->p_sys->p_events, i );
916 vlc_array_remove( p_intf->p_sys->p_events, i );
919 /* now we can release the lock and process what's pending */
920 vlc_mutex_unlock( &p_intf->p_sys->lock );
922 ProcessEvents( p_intf, p_info, i_events );
923 ProcessWatches( p_intf, p_watches, i_watches, p_fds, i_fds );
925 free( p_fds ); p_fds = NULL;
927 ProcessTimeouts( p_intf, p_timeouts, i_timeouts );
928 DispatchDBusMessages( p_intf );
930 vlc_restorecancel( canc );
934 static void wakeup_main_loop( void *p_data )
936 intf_thread_t *p_intf = (intf_thread_t*) p_data;
938 msg_Dbg( p_intf, "Sending wakeup signal to the main loop" );
940 if( !write( p_intf->p_sys->p_pipe_fds[PIPE_IN], "\0", 1 ) )
942 msg_Err( p_intf, "Could not wake up the main loop: %m" );
946 /* InputIntfEventCallback() fills a callback_info_t data structure in response
947 * to an "intf-event" input event.
949 * Caution: This function executes in the input thread
951 * This function must be called with p_sys->lock locked
953 * @return int VLC_SUCCESS on success, VLC_E* on error
954 * @param intf_thread_t *p_intf the interface thread
955 * @param input_thread_t *p_input This input thread
956 * @param const int i_event input event type
957 * @param callback_info_t *p_info Location of the callback info to fill
959 static int InputIntfEventCallback( intf_thread_t *p_intf,
960 input_thread_t *p_input,
962 callback_info_t *p_info )
964 dbus_int32_t i_state = PLAYBACK_STATE_INVALID;
965 assert(!p_info->signal);
966 mtime_t i_now = mdate(), i_pos, i_projected_pos, i_interval;
967 float f_current_rate;
971 case INPUT_EVENT_DEAD:
972 case INPUT_EVENT_ABORT:
973 i_state = PLAYBACK_STATE_STOPPED;
975 case INPUT_EVENT_STATE:
976 switch( var_GetInteger( p_input, "state" ) )
980 i_state = PLAYBACK_STATE_PLAYING;
983 i_state = PLAYBACK_STATE_PAUSED;
986 i_state = PLAYBACK_STATE_STOPPED;
989 case INPUT_EVENT_ITEM_META:
990 p_info->signal = SIGNAL_INPUT_METADATA;
992 case INPUT_EVENT_RATE:
993 p_info->signal = SIGNAL_RATE;
995 case INPUT_EVENT_POSITION:
997 * XXX: This is way more convoluted than it should be... */
998 if( !p_intf->p_sys->i_last_input_pos_event ||
999 !( var_GetInteger( p_input, "state" ) == PLAYING_S ) )
1001 i_pos = var_GetTime( p_input, "time" );
1002 f_current_rate = var_GetFloat( p_input, "rate" );
1003 i_interval = ( i_now - p_intf->p_sys->i_last_input_pos_event );
1004 i_projected_pos = p_intf->p_sys->i_last_input_pos + ( i_interval * f_current_rate );
1005 p_intf->p_sys->i_last_input_pos_event = i_now;
1006 p_intf->p_sys->i_last_input_pos = i_pos;
1007 if( ABS( i_pos - i_projected_pos ) < SEEK_THRESHOLD )
1009 p_info->signal = SIGNAL_SEEK;
1010 p_info->i_item = input_GetItem( p_input )->i_id;
1013 return VLC_EGENERIC;
1016 if( i_state != PLAYBACK_STATE_INVALID &&
1017 i_state != p_intf->p_sys->i_playing_state )
1019 p_intf->p_sys->i_playing_state = i_state;
1020 p_info->signal = SIGNAL_STATE;
1023 return p_info->signal ? VLC_SUCCESS : VLC_EGENERIC;
1026 // Get all the callbacks
1027 static int AllCallback( vlc_object_t *p_this, const char *psz_var,
1028 vlc_value_t oldval, vlc_value_t newval, void *p_data )
1033 intf_thread_t *p_intf = (intf_thread_t*)p_data;
1034 callback_info_t *info = calloc( 1, sizeof( callback_info_t ) );
1039 vlc_mutex_lock( &p_intf->p_sys->lock );
1041 // Wich event is it ?
1042 if( !strcmp( "item-current", psz_var ) )
1043 info->signal = SIGNAL_ITEM_CURRENT;
1045 else if( !strcmp( "volume-change", psz_var ) )
1046 info->signal = SIGNAL_VOLUME_CHANGE;
1048 else if( !strcmp( "volume-muted", psz_var ) )
1049 info->signal = SIGNAL_VOLUME_MUTED;
1051 else if( !strcmp( "intf-change", psz_var ) )
1052 info->signal = SIGNAL_INTF_CHANGE;
1054 else if( !strcmp( "playlist-item-append", psz_var ) )
1056 info->signal = SIGNAL_PLAYLIST_ITEM_APPEND;
1057 info->i_node = ((playlist_add_t*)newval.p_address)->i_node;
1060 else if( !strcmp( "playlist-item-deleted", psz_var ) )
1061 info->signal = SIGNAL_PLAYLIST_ITEM_DELETED;
1063 else if( !strcmp( "random", psz_var ) )
1064 info->signal = SIGNAL_RANDOM;
1066 else if( !strcmp( "repeat", psz_var ) )
1067 info->signal = SIGNAL_REPEAT;
1069 else if( !strcmp( "loop", psz_var ) )
1070 info->signal = SIGNAL_LOOP;
1072 else if( !strcmp( "intf-event", psz_var ) )
1074 int i_res = InputIntfEventCallback( p_intf,
1075 (input_thread_t*) p_this,
1076 newval.i_int, info );
1077 if( VLC_SUCCESS != i_res )
1079 vlc_mutex_unlock( &p_intf->p_sys->lock );
1086 else if( !strcmp( "can-seek", psz_var ) )
1087 info->signal = SIGNAL_CAN_SEEK;
1089 else if( !strcmp( "can-pause", psz_var ) )
1090 info->signal = SIGNAL_CAN_PAUSE;
1096 vlc_array_append( p_intf->p_sys->p_events, info );
1097 vlc_mutex_unlock( &p_intf->p_sys->lock );
1100 "Got a VLC event on %s. The main loop needs to wake up "
1101 "in order to process it", psz_var );
1103 wakeup_main_loop( p_intf );
1108 /*****************************************************************************
1109 * TrackChange: callback on playlist "item-current"
1110 *****************************************************************************/
1111 static int TrackChange( intf_thread_t *p_intf )
1113 intf_sys_t *p_sys = p_intf->p_sys;
1114 playlist_t *p_playlist = p_sys->p_playlist;
1115 input_thread_t *p_input = NULL;
1116 input_item_t *p_item = NULL;
1118 if( p_intf->p_sys->b_dead )
1121 if( p_sys->p_input )
1123 var_DelCallback( p_sys->p_input, "intf-event", AllCallback, p_intf );
1124 var_DelCallback( p_sys->p_input, "can-pause", AllCallback, p_intf );
1125 var_DelCallback( p_sys->p_input, "can-seek", AllCallback, p_intf );
1126 vlc_object_release( p_sys->p_input );
1127 p_sys->p_input = NULL;
1130 p_sys->b_meta_read = false;
1132 p_input = playlist_CurrentInput( p_playlist );
1138 p_item = input_GetItem( p_input );
1141 vlc_object_release( p_input );
1142 return VLC_EGENERIC;
1145 if( input_item_IsPreparsed( p_item ) )
1146 p_sys->b_meta_read = true;
1148 p_sys->p_input = p_input;
1149 var_AddCallback( p_input, "intf-event", AllCallback, p_intf );
1150 var_AddCallback( p_input, "can-pause", AllCallback, p_intf );
1151 var_AddCallback( p_input, "can-seek", AllCallback, p_intf );
1157 * DemarshalSetPropertyValue() extracts the new property value from a
1158 * org.freedesktop.DBus.Properties.Set method call message.
1160 * @return int VLC_SUCCESS on success
1161 * @param DBusMessage *p_msg a org.freedesktop.DBus.Properties.Set method call
1162 * @param void *p_arg placeholder for the demarshalled value
1164 int DemarshalSetPropertyValue( DBusMessage *p_msg, void *p_arg )
1167 bool b_valid_input = FALSE;
1168 DBusMessageIter in_args, variant;
1169 dbus_message_iter_init( p_msg, &in_args );
1173 i_type = dbus_message_iter_get_arg_type( &in_args );
1174 if( DBUS_TYPE_VARIANT == i_type )
1176 dbus_message_iter_recurse( &in_args, &variant );
1177 dbus_message_iter_get_basic( &variant, p_arg );
1178 b_valid_input = TRUE;
1180 } while( dbus_message_iter_next( &in_args ) );
1182 return b_valid_input ? VLC_SUCCESS : VLC_EGENERIC;
1185 /*****************************************************************************
1186 * GetInputMeta: Fill a DBusMessage with the given input item metadata
1187 *****************************************************************************/
1189 #define ADD_META( entry, type, data ) \
1191 dbus_message_iter_open_container( &dict, DBUS_TYPE_DICT_ENTRY, \
1192 NULL, &dict_entry ); \
1193 dbus_message_iter_append_basic( &dict_entry, DBUS_TYPE_STRING, \
1194 &ppsz_meta_items[entry] ); \
1195 dbus_message_iter_open_container( &dict_entry, DBUS_TYPE_VARIANT, \
1196 type##_AS_STRING, &variant ); \
1197 dbus_message_iter_append_basic( &variant, \
1200 dbus_message_iter_close_container( &dict_entry, &variant ); \
1201 dbus_message_iter_close_container( &dict, &dict_entry ); }
1203 #define ADD_VLC_META_STRING( entry, item ) \
1205 char * psz = input_item_Get##item( p_input );\
1206 ADD_META( entry, DBUS_TYPE_STRING, \
1211 int GetInputMeta( input_item_t* p_input,
1212 DBusMessageIter *args )
1214 DBusMessageIter dict, dict_entry, variant;
1215 /** The duration of the track can be expressed in second, milli-seconds and
1217 dbus_int64_t i_mtime = input_item_GetDuration( p_input );
1218 dbus_uint32_t i_time = i_mtime / 1000000;
1219 dbus_int64_t i_length = i_mtime / 1000;
1222 if( -1 == asprintf( &psz_trackid, MPRIS_TRACKID_FORMAT, p_input->i_id ) )
1225 const char* ppsz_meta_items[] =
1227 "mpris:trackid", "xesam:url", "xesam:title", "xesam:artist", "xesam:album",
1228 "xesam:tracknumber", "vlc:time", "mpris:length", "xesam:genre",
1229 "xesam:userRating", "xesam:contentCreated", "mpris:artUrl", "mb:trackId",
1230 "vlc:audio-bitrate", "vlc:audio-samplerate", "vlc:video-bitrate",
1231 "vlc:audio-codec", "vlc:copyright", "xesam:comment", "vlc:encodedby",
1232 "language", "vlc:length", "vlc:nowplaying", "vlc:publisher", "vlc:setting",
1233 "status", "vlc:url", "vlc:video-codec"
1236 dbus_message_iter_open_container( args, DBUS_TYPE_ARRAY, "{sv}", &dict );
1238 ADD_META( 0, DBUS_TYPE_OBJECT_PATH, psz_trackid );
1239 ADD_VLC_META_STRING( 1, URI );
1240 ADD_VLC_META_STRING( 2, Title );
1241 ADD_VLC_META_STRING( 3, Artist );
1242 ADD_VLC_META_STRING( 4, Album );
1243 ADD_VLC_META_STRING( 5, TrackNum );
1244 ADD_META( 6, DBUS_TYPE_UINT32, i_time );
1245 ADD_META( 7, DBUS_TYPE_INT64, i_mtime );
1246 ADD_VLC_META_STRING( 8, Genre );
1247 ADD_VLC_META_STRING( 9, Rating );
1248 ADD_VLC_META_STRING( 10, Date );
1249 ADD_VLC_META_STRING( 11, ArtURL );
1250 ADD_VLC_META_STRING( 12, TrackID );
1252 ADD_VLC_META_STRING( 17, Copyright );
1253 ADD_VLC_META_STRING( 18, Description );
1254 ADD_VLC_META_STRING( 19, EncodedBy );
1255 ADD_VLC_META_STRING( 20, Language );
1256 ADD_META( 21, DBUS_TYPE_INT64, i_length );
1257 ADD_VLC_META_STRING( 22, NowPlaying );
1258 ADD_VLC_META_STRING( 23, Publisher );
1259 ADD_VLC_META_STRING( 24, Setting );
1260 ADD_VLC_META_STRING( 25, URL );
1262 vlc_mutex_lock( &p_input->lock );
1263 if( p_input->p_meta )
1265 int i_status = vlc_meta_GetStatus( p_input->p_meta );
1266 ADD_META( 23, DBUS_TYPE_INT32, i_status );
1268 vlc_mutex_unlock( &p_input->lock );
1270 dbus_message_iter_close_container( args, &dict );
1275 #undef ADD_VLC_META_STRING