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 #define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
57 #include <vlc_common.h>
58 #include <vlc_plugin.h>
59 #include <vlc_interface.h>
60 #include <vlc_playlist.h>
61 #include <vlc_input.h>
63 #include <vlc_mtime.h>
74 #define DBUS_MPRIS_BUS_NAME "org.mpris.MediaPlayer2.vlc"
75 #define DBUS_INSTANCE_ID_PREFIX "instance"
77 #define SEEK_THRESHOLD 1000 /* µsec */
79 /*****************************************************************************
81 *****************************************************************************/
83 static DBusHandlerResult
84 MPRISEntryPoint ( DBusConnection *p_conn, DBusMessage *p_from, void *p_this );
86 static const DBusObjectPathVTable dbus_mpris_vtable = {
87 NULL, MPRISEntryPoint, /* handler function */
88 NULL, NULL, NULL, NULL
104 static int Open ( vlc_object_t * );
105 static void Close ( vlc_object_t * );
106 static void *Run ( void * );
108 static int TrackChange( intf_thread_t * );
109 static int AllCallback( vlc_object_t*, const char*, vlc_value_t, vlc_value_t, void* );
110 static int InputCallback( vlc_object_t*, const char*, vlc_value_t, vlc_value_t, void* );
112 static dbus_bool_t add_timeout(DBusTimeout *, void *);
113 static void remove_timeout(DBusTimeout *, void *);
114 static void toggle_timeout(DBusTimeout *, void *);
116 static dbus_bool_t add_watch ( DBusWatch *p_watch, void *p_data );
117 static void remove_watch ( DBusWatch *p_watch, void *p_data );
118 static void watch_toggled ( DBusWatch *p_watch, void *p_data );
120 static void wakeup_main_loop( void *p_data );
122 static void ProcessEvents ( intf_thread_t *p_intf,
123 callback_info_t **p_events,
126 static void ProcessWatches ( intf_thread_t *p_intf,
127 DBusWatch **p_watches,
129 struct pollfd *p_fds,
132 static void DispatchDBusMessages( intf_thread_t *p_intf );
134 /*****************************************************************************
136 *****************************************************************************/
138 set_shortname( N_("DBus"))
139 set_category( CAT_INTERFACE )
140 set_description( N_("D-Bus control interface") )
141 set_capability( "interface", 0 )
142 set_callbacks( Open, Close )
145 /*****************************************************************************
146 * Open: initialize interface
147 *****************************************************************************/
149 static int Open( vlc_object_t *p_this )
151 intf_thread_t *p_intf = (intf_thread_t*)p_this;
153 /* initialisation of the connection */
154 if( !dbus_threads_init_default() )
157 intf_sys_t *p_sys = calloc( 1, sizeof( intf_sys_t ) );
158 if( unlikely(!p_sys) )
161 playlist_t *p_playlist;
162 DBusConnection *p_conn;
163 p_sys->i_player_caps = PLAYER_CAPS_NONE;
164 p_sys->i_playing_state = PLAYBACK_STATE_INVALID;
166 if( vlc_pipe( p_sys->p_pipe_fds ) )
169 msg_Err( p_intf, "Could not create pipe" );
174 dbus_error_init( &error );
176 /* connect privately to the session bus
177 * the connection will not be shared with other vlc modules which use dbus,
178 * thus avoiding a whole class of concurrency issues */
179 p_conn = dbus_bus_get_private( DBUS_BUS_SESSION, &error );
182 msg_Err( p_this, "Failed to connect to the D-Bus session daemon: %s",
184 dbus_error_free( &error );
185 close( p_sys->p_pipe_fds[1] );
186 close( p_sys->p_pipe_fds[0] );
191 dbus_connection_set_exit_on_disconnect( p_conn, FALSE );
193 /* Register the entry point object path */
194 dbus_connection_register_object_path( p_conn, DBUS_MPRIS_OBJECT_PATH,
195 &dbus_mpris_vtable, p_this );
197 /* Try to register org.mpris.MediaPlayer2.vlc */
198 dbus_bus_request_name( p_conn, DBUS_MPRIS_BUS_NAME, 0, &error );
199 if( dbus_error_is_set( &error ) )
201 msg_Dbg( p_this, "Failed to get service name %s: %s",
202 DBUS_MPRIS_BUS_NAME, error.message );
203 dbus_error_free( &error );
205 /* Register an instance-specific well known name of the form
206 * org.mpris.MediaPlayer2.vlc.instanceXXXX where XXXX is the
207 * current Process ID */
208 char unique_service[sizeof( DBUS_MPRIS_BUS_NAME ) +
209 sizeof( DBUS_INSTANCE_ID_PREFIX ) + 10];
211 snprintf( unique_service, sizeof (unique_service),
212 DBUS_MPRIS_BUS_NAME"."DBUS_INSTANCE_ID_PREFIX"%"PRIu32,
213 (uint32_t)getpid() );
215 dbus_bus_request_name( p_conn, unique_service, 0, &error );
216 if( dbus_error_is_set( &error ) )
218 msg_Err( p_this, "Failed to get service name %s: %s",
219 DBUS_MPRIS_BUS_NAME, error.message );
220 dbus_error_free( &error );
223 msg_Dbg( p_intf, "listening on dbus as: %s", unique_service );
226 msg_Dbg( p_intf, "listening on dbus as: %s", DBUS_MPRIS_BUS_NAME );
228 dbus_connection_flush( p_conn );
230 p_intf->p_sys = p_sys;
231 p_sys->p_conn = p_conn;
232 p_sys->p_events = vlc_array_new();
233 p_sys->p_timeouts = vlc_array_new();
234 p_sys->p_watches = vlc_array_new();
235 vlc_mutex_init( &p_sys->lock );
237 p_playlist = pl_Get( p_intf );
238 p_sys->p_playlist = p_playlist;
240 var_AddCallback( p_playlist, "input-current", AllCallback, p_intf );
241 var_AddCallback( p_playlist, "intf-change", AllCallback, p_intf );
242 var_AddCallback( p_playlist, "volume", AllCallback, p_intf );
243 var_AddCallback( p_playlist, "mute", AllCallback, p_intf );
244 var_AddCallback( p_playlist, "playlist-item-append", AllCallback, p_intf );
245 var_AddCallback( p_playlist, "playlist-item-deleted", AllCallback, p_intf );
246 var_AddCallback( p_playlist, "random", AllCallback, p_intf );
247 var_AddCallback( p_playlist, "repeat", AllCallback, p_intf );
248 var_AddCallback( p_playlist, "loop", AllCallback, p_intf );
249 var_AddCallback( p_playlist, "fullscreen", AllCallback, p_intf );
251 if( !dbus_connection_set_timeout_functions( p_conn,
258 if( !dbus_connection_set_watch_functions( p_conn,
265 if( vlc_clone( &p_sys->thread, Run, p_intf, VLC_THREAD_PRIORITY_LOW ) )
271 /* The dbus connection is private,
272 * so we are responsible for closing it
273 * XXX: Does this make sense when OOM ? */
274 dbus_connection_close( p_sys->p_conn );
275 dbus_connection_unref( p_conn );
277 vlc_array_destroy( p_sys->p_events );
278 vlc_array_destroy( p_sys->p_timeouts );
279 vlc_array_destroy( p_sys->p_watches );
281 vlc_mutex_destroy( &p_sys->lock );
283 close( p_sys->p_pipe_fds[1] );
284 close( p_sys->p_pipe_fds[0] );
289 /*****************************************************************************
290 * Close: destroy interface
291 *****************************************************************************/
293 static void Close ( vlc_object_t *p_this )
295 intf_thread_t *p_intf = (intf_thread_t*) p_this;
296 intf_sys_t *p_sys = p_intf->p_sys;
297 playlist_t *p_playlist = p_sys->p_playlist;
299 vlc_cancel( p_sys->thread );
300 vlc_join( p_sys->thread, NULL );
302 var_DelCallback( p_playlist, "input-current", AllCallback, p_intf );
303 var_DelCallback( p_playlist, "intf-change", AllCallback, p_intf );
304 var_DelCallback( p_playlist, "volume", AllCallback, p_intf );
305 var_DelCallback( p_playlist, "mute", AllCallback, p_intf );
306 var_DelCallback( p_playlist, "playlist-item-append", AllCallback, p_intf );
307 var_DelCallback( p_playlist, "playlist-item-deleted", AllCallback, p_intf );
308 var_DelCallback( p_playlist, "random", AllCallback, p_intf );
309 var_DelCallback( p_playlist, "repeat", AllCallback, p_intf );
310 var_DelCallback( p_playlist, "loop", AllCallback, p_intf );
311 var_DelCallback( p_playlist, "fullscreen", AllCallback, p_intf );
315 var_DelCallback( p_sys->p_input, "intf-event", InputCallback, p_intf );
316 var_DelCallback( p_sys->p_input, "can-pause", AllCallback, p_intf );
317 var_DelCallback( p_sys->p_input, "can-seek", AllCallback, p_intf );
318 vlc_object_release( p_sys->p_input );
321 /* The dbus connection is private, so we are responsible
323 dbus_connection_close( p_sys->p_conn );
324 dbus_connection_unref( p_sys->p_conn );
326 // Free the events array
327 for( int i = 0; i < vlc_array_count( p_sys->p_events ); i++ )
329 callback_info_t* info = vlc_array_item_at_index( p_sys->p_events, i );
332 vlc_mutex_destroy( &p_sys->lock );
333 vlc_array_destroy( p_sys->p_events );
334 vlc_array_destroy( p_sys->p_timeouts );
335 vlc_array_destroy( p_sys->p_watches );
336 close( p_sys->p_pipe_fds[1] );
337 close( p_sys->p_pipe_fds[0] );
341 static dbus_bool_t add_timeout(DBusTimeout *to, void *data)
343 intf_thread_t *intf = data;
344 intf_sys_t *sys = intf->p_sys;
346 mtime_t *expiry = malloc(sizeof (*expiry));
347 if (unlikely(expiry == NULL))
350 dbus_timeout_set_data(to, expiry, free);
352 vlc_mutex_lock(&sys->lock);
353 vlc_array_append(sys->p_timeouts, to);
354 vlc_mutex_unlock(&sys->lock);
359 static void remove_timeout(DBusTimeout *to, void *data)
361 intf_thread_t *intf = data;
362 intf_sys_t *sys = intf->p_sys;
363 unsigned idx = vlc_array_index_of_item(sys->p_timeouts, to);
365 vlc_mutex_lock(&sys->lock);
366 vlc_array_remove(sys->p_timeouts, idx);
367 vlc_mutex_unlock(&sys->lock);
370 static void toggle_timeout(DBusTimeout *to, void *data)
372 intf_thread_t *intf = data;
373 intf_sys_t *sys = intf->p_sys;
374 mtime_t *expiry = dbus_timeout_get_data(to);
376 vlc_mutex_lock(&sys->lock);
377 if (dbus_timeout_get_enabled(to))
378 *expiry = mdate() + UINT64_C(1000) * dbus_timeout_get_interval(to);
379 vlc_mutex_unlock(&sys->lock);
381 wakeup_main_loop(intf);
385 * Computes the time until the next timeout expiration.
386 * @note Interface lock must be held.
387 * @return The time in milliseconds until the next expiration,
388 * or -1 if there are no pending timeouts.
390 static int next_timeout(intf_thread_t *intf)
392 intf_sys_t *sys = intf->p_sys;
393 mtime_t next_timeout = LAST_MDATE;
394 unsigned count = vlc_array_count(sys->p_timeouts);
396 for (unsigned i = 0; i < count; i++)
398 DBusTimeout *to = vlc_array_item_at_index(sys->p_timeouts, i);
400 if (!dbus_timeout_get_enabled(to))
403 mtime_t *expiry = dbus_timeout_get_data(to);
405 if (next_timeout > *expiry)
406 next_timeout = *expiry;
409 if (next_timeout >= LAST_MDATE)
412 next_timeout /= 1000;
414 if (next_timeout > INT_MAX)
417 return (int)next_timeout;
421 * Process pending D-Bus timeouts.
423 * @note Interface lock must be held.
425 static void process_timeouts(intf_thread_t *intf)
427 intf_sys_t *sys = intf->p_sys;
429 for (int i = 0; i < vlc_array_count(sys->p_timeouts); i++)
431 DBusTimeout *to = vlc_array_item_at_index(sys->p_timeouts, i);
433 if (!dbus_timeout_get_enabled(to))
436 mtime_t *expiry = dbus_timeout_get_data(to);
437 if (*expiry > mdate())
440 expiry += UINT64_C(1000) * dbus_timeout_get_interval(to);
441 vlc_mutex_unlock(&sys->lock);
443 dbus_timeout_handle(to);
445 vlc_mutex_lock(&sys->lock);
446 i = -1; /* lost track of state, restart from beginning */
451 static dbus_bool_t add_watch( DBusWatch *p_watch, void *p_data )
453 intf_thread_t *p_intf = (intf_thread_t*) p_data;
454 intf_sys_t *p_sys = (intf_sys_t*) p_intf->p_sys;
456 vlc_mutex_lock( &p_sys->lock );
457 vlc_array_append( p_sys->p_watches, p_watch );
458 vlc_mutex_unlock( &p_sys->lock );
463 static void remove_watch( DBusWatch *p_watch, void *p_data )
465 intf_thread_t *p_intf = (intf_thread_t*) p_data;
466 intf_sys_t *p_sys = (intf_sys_t*) p_intf->p_sys;
468 vlc_mutex_lock( &p_sys->lock );
470 vlc_array_remove( p_sys->p_watches,
471 vlc_array_index_of_item( p_sys->p_watches, p_watch ) );
473 vlc_mutex_unlock( &p_sys->lock );
476 static void watch_toggled( DBusWatch *p_watch, void *p_data )
478 intf_thread_t *p_intf = (intf_thread_t*) p_data;
480 if( dbus_watch_get_enabled( p_watch ) )
481 wakeup_main_loop( p_intf );
485 * GetPollFds() fills an array of pollfd data structures with :
486 * - the set of enabled dbus watches
487 * - the unix pipe which we use to manually wake up the main loop
489 * This function must be called with p_sys->lock locked
491 * @return The number of file descriptors
493 * @param intf_thread_t *p_intf this interface thread's state
494 * @param struct pollfd *p_fds a pointer to a pollfd array large enough to
495 * contain all the returned data (number of enabled dbus watches + 1)
497 static int GetPollFds( intf_thread_t *p_intf, struct pollfd *p_fds )
499 intf_sys_t *p_sys = p_intf->p_sys;
500 int i_fds = 1, i_watches = vlc_array_count( p_sys->p_watches );
502 p_fds[0].fd = p_sys->p_pipe_fds[PIPE_OUT];
503 p_fds[0].events = POLLIN | POLLPRI;
505 for( int i = 0; i < i_watches; i++ )
507 DBusWatch *p_watch = NULL;
508 p_watch = vlc_array_item_at_index( p_sys->p_watches, i );
509 if( !dbus_watch_get_enabled( p_watch ) )
512 p_fds[i_fds].fd = dbus_watch_get_unix_fd( p_watch );
513 int i_flags = dbus_watch_get_flags( p_watch );
515 if( i_flags & DBUS_WATCH_READABLE )
516 p_fds[i_fds].events |= POLLIN | POLLPRI;
518 if( i_flags & DBUS_WATCH_WRITABLE )
519 p_fds[i_fds].events |= POLLOUT;
528 * ProcessEvents() reacts to a list of events originating from other VLC threads
530 * This function must be called with p_sys->lock unlocked
532 * @param intf_thread_t *p_intf This interface thread state
533 * @param callback_info_t *p_events the list of events to process
535 static void ProcessEvents( intf_thread_t *p_intf,
536 callback_info_t **p_events, int i_events )
538 bool b_can_play = p_intf->p_sys->b_can_play;
540 vlc_dictionary_t player_properties, tracklist_properties, root_properties;
541 vlc_dictionary_init( &player_properties, 0 );
542 vlc_dictionary_init( &tracklist_properties, 0 );
543 vlc_dictionary_init( &root_properties, 0 );
545 for( int i = 0; i < i_events; i++ )
547 switch( p_events[i]->signal )
549 case SIGNAL_ITEM_CURRENT:
550 TrackChange( p_intf );
552 // rate depends on current item
553 if( !vlc_dictionary_has_key( &player_properties, "Rate" ) )
554 vlc_dictionary_insert( &player_properties, "Rate", NULL );
556 vlc_dictionary_insert( &player_properties, "Metadata", NULL );
558 case SIGNAL_INTF_CHANGE:
559 case SIGNAL_PLAYLIST_ITEM_APPEND:
560 case SIGNAL_PLAYLIST_ITEM_DELETED:
562 playlist_t *p_playlist = p_intf->p_sys->p_playlist;
564 b_can_play = playlist_CurrentSize( p_playlist ) > 0;
567 if( b_can_play != p_intf->p_sys->b_can_play )
569 p_intf->p_sys->b_can_play = b_can_play;
570 vlc_dictionary_insert( &player_properties, "CanPlay", NULL );
573 if( !vlc_dictionary_has_key( &tracklist_properties, "Tracks" ) )
574 vlc_dictionary_insert( &tracklist_properties, "Tracks", NULL );
577 case SIGNAL_VOLUME_MUTED:
578 case SIGNAL_VOLUME_CHANGE:
579 vlc_dictionary_insert( &player_properties, "Volume", NULL );
582 vlc_dictionary_insert( &player_properties, "Shuffle", NULL );
584 case SIGNAL_FULLSCREEN:
585 vlc_dictionary_insert( &root_properties, "Fullscreen", NULL );
589 vlc_dictionary_insert( &player_properties, "LoopStatus", NULL );
592 vlc_dictionary_insert( &player_properties, "PlaybackStatus", NULL );
595 vlc_dictionary_insert( &player_properties, "Rate", NULL );
597 case SIGNAL_INPUT_METADATA:
599 input_thread_t *p_input = pl_CurrentInput( p_intf );
600 input_item_t *p_item;
603 p_item = input_GetItem( p_input );
604 vlc_object_release( p_input );
607 vlc_dictionary_insert( &player_properties,
612 case SIGNAL_CAN_SEEK:
613 vlc_dictionary_insert( &player_properties, "CanSeek", NULL );
615 case SIGNAL_CAN_PAUSE:
616 vlc_dictionary_insert( &player_properties, "CanPause", NULL );
620 input_thread_t *p_input;
621 input_item_t *p_item;
622 p_input = pl_CurrentInput( p_intf );
625 p_item = input_GetItem( p_input );
626 vlc_object_release( p_input );
628 if( p_item && ( p_item->i_id == p_events[i]->i_item ) )
629 SeekedEmit( p_intf );
634 vlc_assert_unreachable();
639 if( vlc_dictionary_keys_count( &player_properties ) )
640 PlayerPropertiesChangedEmit( p_intf, &player_properties );
642 if( vlc_dictionary_keys_count( &tracklist_properties ) )
643 TrackListPropertiesChangedEmit( p_intf, &tracklist_properties );
645 if( vlc_dictionary_keys_count( &root_properties ) )
646 RootPropertiesChangedEmit( p_intf, &root_properties );
648 vlc_dictionary_clear( &player_properties, NULL, NULL );
649 vlc_dictionary_clear( &tracklist_properties, NULL, NULL );
650 vlc_dictionary_clear( &root_properties, NULL, NULL );
654 * ProcessWatches() handles a list of dbus watches after poll() has returned
656 * This function must be called with p_sys->lock unlocked
658 * @param intf_thread_t *p_intf This interface thread state
659 * @param DBusWatch **p_watches The list of dbus watches to process
660 * @param int i_watches The size of the p_watches array
661 * @param struct pollfd *p_fds The result of a poll() call
662 * @param int i_fds The number of file descriptors processed by poll()
664 static void ProcessWatches( intf_thread_t *p_intf,
665 DBusWatch **p_watches, int i_watches,
666 struct pollfd *p_fds, int i_fds )
670 /* Process watches */
671 for( int i = 0; i < i_watches; i++ )
673 DBusWatch *p_watch = p_watches[i];
674 if( !dbus_watch_get_enabled( p_watch ) )
677 for( int j = 0; j < i_fds; j++ )
679 if( p_fds[j].fd != dbus_watch_get_unix_fd( p_watch ) )
683 int i_revents = p_fds[j].revents;
685 if( i_revents & POLLIN )
686 i_flags |= DBUS_WATCH_READABLE;
688 if( i_revents & POLLOUT )
689 i_flags |= DBUS_WATCH_WRITABLE;
691 if( i_revents & POLLERR )
692 i_flags |= DBUS_WATCH_ERROR;
694 if( i_revents & POLLHUP )
695 i_flags |= DBUS_WATCH_HANGUP;
698 dbus_watch_handle( p_watch, i_flags );
704 * DispatchDBusMessages() dispatches incoming dbus messages
705 * (indirectly invoking the callbacks), then it sends outgoing
706 * messages which needs to be sent on the bus (method replies and signals)
708 * This function must be called with p_sys->lock unlocked
710 * @param intf_thread_t *p_intf This interface thread state
712 static void DispatchDBusMessages( intf_thread_t *p_intf )
714 DBusDispatchStatus status;
715 intf_sys_t *p_sys = p_intf->p_sys;
717 /* Dispatch incoming messages */
718 status = dbus_connection_get_dispatch_status( p_sys->p_conn );
719 while( status != DBUS_DISPATCH_COMPLETE )
721 dbus_connection_dispatch( p_sys->p_conn );
722 status = dbus_connection_get_dispatch_status( p_sys->p_conn );
725 /* Send outgoing data */
726 if( dbus_connection_has_messages_to_send( p_sys->p_conn ) )
727 dbus_connection_flush( p_sys->p_conn );
731 * MPRISEntryPoint() routes incoming messages to their respective interface
734 * This function is called during dbus_connection_dispatch()
736 static DBusHandlerResult
737 MPRISEntryPoint ( DBusConnection *p_conn, DBusMessage *p_from, void *p_this )
739 const char *psz_target_interface;
740 const char *psz_interface = dbus_message_get_interface( p_from );
741 const char *psz_method = dbus_message_get_member( p_from );
745 if( psz_interface && strcmp( psz_interface, DBUS_INTERFACE_PROPERTIES ) )
746 psz_target_interface = psz_interface;
750 dbus_error_init( &error );
751 dbus_message_get_args( p_from, &error,
752 DBUS_TYPE_STRING, &psz_target_interface,
755 if( dbus_error_is_set( &error ) )
757 msg_Err( (vlc_object_t*) p_this, "D-Bus error on %s.%s: %s",
758 psz_interface, psz_method,
760 dbus_error_free( &error );
761 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
765 if( !strcmp( psz_target_interface, DBUS_INTERFACE_INTROSPECTABLE ) )
766 return handle_introspect( p_conn, p_from, p_this );
768 if( !strcmp( psz_target_interface, DBUS_MPRIS_ROOT_INTERFACE ) )
769 return handle_root( p_conn, p_from, p_this );
771 if( !strcmp( psz_target_interface, DBUS_MPRIS_PLAYER_INTERFACE ) )
772 return handle_player( p_conn, p_from, p_this );
774 if( !strcmp( psz_target_interface, DBUS_MPRIS_TRACKLIST_INTERFACE ) )
775 return handle_tracklist( p_conn, p_from, p_this );
777 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
780 /*****************************************************************************
782 *****************************************************************************/
784 static void *Run( void *data )
786 intf_thread_t *p_intf = data;
787 intf_sys_t *p_sys = p_intf->p_sys;
789 int canc = vlc_savecancel();
793 vlc_mutex_lock( &p_sys->lock );
795 int i_watches = vlc_array_count( p_sys->p_watches );
796 struct pollfd fds[i_watches];
797 memset(fds, 0, sizeof fds);
799 int i_fds = GetPollFds( p_intf, fds );
800 int timeout = next_timeout(p_intf);
802 vlc_mutex_unlock( &p_sys->lock );
804 /* thread cancellation is allowed while the main loop sleeps */
805 vlc_restorecancel( canc );
807 while (poll(fds, i_fds, timeout) == -1)
813 canc = vlc_savecancel();
815 /* Was the main loop woken up manually ? */
816 if (fds[0].revents & POLLIN)
819 (void)read( fds[0].fd, &buf, 1 );
822 /* We need to lock the mutex while building lists of events,
823 * timeouts and watches to process but we can't keep the lock while
824 * processing them, or else we risk a deadlock:
826 * The signal functions could lock mutex X while p_events is locked;
827 * While some other function in vlc (playlist) might lock mutex X
828 * and then set a variable which would call AllCallback(), which itself
829 * needs to lock p_events to add a new event.
831 vlc_mutex_lock( &p_intf->p_sys->lock );
833 process_timeouts(p_intf);
835 /* Get the list of watches to process */
836 i_watches = vlc_array_count( p_sys->p_watches );
837 DBusWatch *p_watches[i_watches ? i_watches : 1];
838 for( int i = 0; i < i_watches; i++ )
840 p_watches[i] = vlc_array_item_at_index( p_sys->p_watches, i );
843 /* Get the list of events to process */
844 int i_events = vlc_array_count( p_intf->p_sys->p_events );
845 callback_info_t* p_info[i_events ? i_events : 1];
846 for( int i = i_events - 1; i >= 0; i-- )
848 p_info[i] = vlc_array_item_at_index( p_intf->p_sys->p_events, i );
849 vlc_array_remove( p_intf->p_sys->p_events, i );
852 /* now we can release the lock and process what's pending */
853 vlc_mutex_unlock( &p_intf->p_sys->lock );
855 ProcessEvents( p_intf, p_info, i_events );
856 ProcessWatches( p_intf, p_watches, i_watches, fds, i_fds );
858 DispatchDBusMessages( p_intf );
861 vlc_restorecancel(canc);
865 static void wakeup_main_loop( void *p_data )
867 intf_thread_t *p_intf = (intf_thread_t*) p_data;
869 if( !write( p_intf->p_sys->p_pipe_fds[PIPE_IN], "\0", 1 ) )
870 msg_Err( p_intf, "Could not wake up the main loop: %s",
871 vlc_strerror_c(errno) );
874 /* Flls a callback_info_t data structure in response
875 * to an "intf-event" input event.
877 * @warning This function executes in the input thread.
879 * @return VLC_SUCCESS on success, VLC_E* on error.
881 static int InputCallback( vlc_object_t *p_this, const char *psz_var,
882 vlc_value_t oldval, vlc_value_t newval, void *data )
884 input_thread_t *p_input = (input_thread_t *)p_this;
885 intf_thread_t *p_intf = data;
886 intf_sys_t *p_sys = p_intf->p_sys;
888 dbus_int32_t i_state = PLAYBACK_STATE_INVALID;
890 callback_info_t *p_info = calloc( 1, sizeof( callback_info_t ) );
891 if( unlikely(p_info == NULL) )
894 switch( newval.i_int )
896 case INPUT_EVENT_DEAD:
897 i_state = PLAYBACK_STATE_STOPPED;
899 case INPUT_EVENT_STATE:
900 switch( var_GetInteger( p_input, "state" ) )
904 i_state = PLAYBACK_STATE_PLAYING;
907 i_state = PLAYBACK_STATE_PAUSED;
910 i_state = PLAYBACK_STATE_STOPPED;
913 case INPUT_EVENT_ITEM_META:
914 p_info->signal = SIGNAL_INPUT_METADATA;
916 case INPUT_EVENT_RATE:
917 p_info->signal = SIGNAL_RATE;
919 case INPUT_EVENT_POSITION:
921 mtime_t i_now = mdate(), i_pos, i_projected_pos, i_interval;
922 float f_current_rate;
925 * XXX: This is way more convoluted than it should be... */
926 i_pos = var_GetTime( p_input, "time" );
928 if( !p_intf->p_sys->i_last_input_pos_event ||
929 !( var_GetInteger( p_input, "state" ) == PLAYING_S ) )
931 p_intf->p_sys->i_last_input_pos_event = i_now;
932 p_intf->p_sys->i_last_input_pos = i_pos;
936 f_current_rate = var_GetFloat( p_input, "rate" );
937 i_interval = ( i_now - p_intf->p_sys->i_last_input_pos_event );
939 i_projected_pos = p_intf->p_sys->i_last_input_pos +
940 ( i_interval * f_current_rate );
942 p_intf->p_sys->i_last_input_pos_event = i_now;
943 p_intf->p_sys->i_last_input_pos = i_pos;
945 if( llabs( i_pos - i_projected_pos ) < SEEK_THRESHOLD )
948 p_info->signal = SIGNAL_SEEK;
949 p_info->i_item = input_GetItem( p_input )->i_id;
954 return VLC_SUCCESS; /* don't care */
957 vlc_mutex_lock( &p_sys->lock );
958 if( i_state != PLAYBACK_STATE_INVALID &&
959 i_state != p_sys->i_playing_state )
961 p_sys->i_playing_state = i_state;
962 p_info->signal = SIGNAL_STATE;
965 vlc_array_append( p_intf->p_sys->p_events, p_info );
968 vlc_mutex_unlock( &p_intf->p_sys->lock );
970 wakeup_main_loop( p_intf );
977 // Get all the callbacks
978 static int AllCallback( vlc_object_t *p_this, const char *psz_var,
979 vlc_value_t oldval, vlc_value_t newval, void *p_data )
981 intf_thread_t *p_intf = p_data;
982 callback_info_t info = { .signal = SIGNAL_NONE };
984 // Wich event is it ?
985 if( !strcmp( "input-current", psz_var ) )
986 info.signal = SIGNAL_ITEM_CURRENT;
987 else if( !strcmp( "volume", psz_var ) )
989 if( oldval.f_float != newval.f_float )
990 info.signal = SIGNAL_VOLUME_CHANGE;
992 else if( !strcmp( "mute", psz_var ) )
994 if( oldval.b_bool != newval.b_bool )
995 info.signal = SIGNAL_VOLUME_MUTED;
997 else if( !strcmp( "intf-change", psz_var ) )
998 info.signal = SIGNAL_INTF_CHANGE;
999 else if( !strcmp( "playlist-item-append", psz_var ) )
1001 info.signal = SIGNAL_PLAYLIST_ITEM_APPEND;
1002 info.i_node = ((playlist_add_t*)newval.p_address)->i_node;
1004 else if( !strcmp( "playlist-item-deleted", psz_var ) )
1005 info.signal = SIGNAL_PLAYLIST_ITEM_DELETED;
1006 else if( !strcmp( "random", psz_var ) )
1007 info.signal = SIGNAL_RANDOM;
1008 else if( !strcmp( "fullscreen", psz_var ) )
1009 info.signal = SIGNAL_FULLSCREEN;
1010 else if( !strcmp( "repeat", psz_var ) )
1011 info.signal = SIGNAL_REPEAT;
1012 else if( !strcmp( "loop", psz_var ) )
1013 info.signal = SIGNAL_LOOP;
1014 else if( !strcmp( "can-seek", psz_var ) )
1015 info.signal = SIGNAL_CAN_SEEK;
1016 else if( !strcmp( "can-pause", psz_var ) )
1017 info.signal = SIGNAL_CAN_PAUSE;
1019 vlc_assert_unreachable();
1021 if( info.signal == SIGNAL_NONE )
1024 callback_info_t *p_info = malloc( sizeof( *p_info ) );
1025 if( unlikely(p_info == NULL) )
1030 vlc_mutex_lock( &p_intf->p_sys->lock );
1031 vlc_array_append( p_intf->p_sys->p_events, p_info );
1032 vlc_mutex_unlock( &p_intf->p_sys->lock );
1034 wakeup_main_loop( p_intf );
1039 /*****************************************************************************
1040 * TrackChange: callback on playlist "input-current"
1041 *****************************************************************************/
1042 static int TrackChange( intf_thread_t *p_intf )
1044 intf_sys_t *p_sys = p_intf->p_sys;
1045 input_thread_t *p_input = NULL;
1046 input_item_t *p_item = NULL;
1048 if( p_intf->p_sys->b_dead )
1051 if( p_sys->p_input )
1053 var_DelCallback( p_sys->p_input, "intf-event", InputCallback, p_intf );
1054 var_DelCallback( p_sys->p_input, "can-pause", AllCallback, p_intf );
1055 var_DelCallback( p_sys->p_input, "can-seek", AllCallback, p_intf );
1056 vlc_object_release( p_sys->p_input );
1057 p_sys->p_input = NULL;
1060 p_sys->b_meta_read = false;
1062 p_input = pl_CurrentInput( p_intf );
1068 p_item = input_GetItem( p_input );
1071 vlc_object_release( p_input );
1072 return VLC_EGENERIC;
1075 if( input_item_IsPreparsed( p_item ) )
1076 p_sys->b_meta_read = true;
1078 p_sys->p_input = p_input;
1079 var_AddCallback( p_input, "intf-event", InputCallback, p_intf );
1080 var_AddCallback( p_input, "can-pause", AllCallback, p_intf );
1081 var_AddCallback( p_input, "can-seek", AllCallback, p_intf );
1087 * DemarshalSetPropertyValue() extracts the new property value from a
1088 * org.freedesktop.DBus.Properties.Set method call message.
1090 * @return int VLC_SUCCESS on success
1091 * @param DBusMessage *p_msg a org.freedesktop.DBus.Properties.Set method call
1092 * @param void *p_arg placeholder for the demarshalled value
1094 int DemarshalSetPropertyValue( DBusMessage *p_msg, void *p_arg )
1097 bool b_valid_input = FALSE;
1098 DBusMessageIter in_args, variant;
1099 dbus_message_iter_init( p_msg, &in_args );
1103 i_type = dbus_message_iter_get_arg_type( &in_args );
1104 if( DBUS_TYPE_VARIANT == i_type )
1106 dbus_message_iter_recurse( &in_args, &variant );
1107 dbus_message_iter_get_basic( &variant, p_arg );
1108 b_valid_input = TRUE;
1110 } while( dbus_message_iter_next( &in_args ) );
1112 return b_valid_input ? VLC_SUCCESS : VLC_EGENERIC;
1115 /*****************************************************************************
1116 * GetInputMeta: Fill a DBusMessage with the given input item metadata
1117 *****************************************************************************/
1119 #define ADD_META( entry, type, data ) \
1121 dbus_message_iter_open_container( &dict, DBUS_TYPE_DICT_ENTRY, \
1122 NULL, &dict_entry ); \
1123 dbus_message_iter_append_basic( &dict_entry, DBUS_TYPE_STRING, \
1124 &ppsz_meta_items[entry] ); \
1125 dbus_message_iter_open_container( &dict_entry, DBUS_TYPE_VARIANT, \
1126 type##_AS_STRING, &variant ); \
1127 dbus_message_iter_append_basic( &variant, \
1130 dbus_message_iter_close_container( &dict_entry, &variant ); \
1131 dbus_message_iter_close_container( &dict, &dict_entry ); }
1133 #define ADD_VLC_META_STRING( entry, item ) \
1135 char * psz = input_item_Get##item( p_input );\
1136 ADD_META( entry, DBUS_TYPE_STRING, \
1141 #define ADD_META_SINGLETON_STRING_LIST( entry, item ) \
1143 char * psz = input_item_Get##item( p_input );\
1145 dbus_message_iter_open_container( &dict, DBUS_TYPE_DICT_ENTRY, \
1146 NULL, &dict_entry ); \
1147 dbus_message_iter_append_basic( &dict_entry, DBUS_TYPE_STRING, \
1148 &ppsz_meta_items[entry] ); \
1149 dbus_message_iter_open_container( &dict_entry, DBUS_TYPE_VARIANT, \
1151 dbus_message_iter_open_container( &variant, DBUS_TYPE_ARRAY, "s", \
1153 dbus_message_iter_append_basic( &list, \
1156 dbus_message_iter_close_container( &variant, &list ); \
1157 dbus_message_iter_close_container( &dict_entry, &variant ); \
1158 dbus_message_iter_close_container( &dict, &dict_entry ); \
1163 int GetInputMeta( input_item_t* p_input,
1164 DBusMessageIter *args )
1166 DBusMessageIter dict, dict_entry, variant, list;
1167 /** The duration of the track can be expressed in second, milli-seconds and
1169 dbus_int64_t i_mtime = input_item_GetDuration( p_input );
1170 dbus_uint32_t i_time = i_mtime / 1000000;
1171 dbus_int64_t i_length = i_mtime / 1000;
1174 if( -1 == asprintf( &psz_trackid, MPRIS_TRACKID_FORMAT, p_input->i_id ) )
1177 const char* ppsz_meta_items[] =
1179 "mpris:trackid", "xesam:url", "xesam:title", "xesam:artist", "xesam:album",
1180 "xesam:tracknumber", "vlc:time", "mpris:length", "xesam:genre",
1181 "xesam:userRating", "xesam:contentCreated", "mpris:artUrl", "mb:trackId",
1182 "vlc:audio-bitrate", "vlc:audio-samplerate", "vlc:video-bitrate",
1183 "vlc:audio-codec", "vlc:copyright", "xesam:comment", "vlc:encodedby",
1184 "language", "vlc:length", "vlc:nowplaying", "vlc:publisher", "vlc:setting",
1185 "status", "vlc:url", "vlc:video-codec"
1188 dbus_message_iter_open_container( args, DBUS_TYPE_ARRAY, "{sv}", &dict );
1190 ADD_META( 0, DBUS_TYPE_OBJECT_PATH, psz_trackid );
1191 ADD_VLC_META_STRING( 1, URI );
1192 ADD_VLC_META_STRING( 2, Title );
1193 ADD_META_SINGLETON_STRING_LIST( 3, Artist );
1194 ADD_VLC_META_STRING( 4, Album );
1195 ADD_VLC_META_STRING( 5, TrackNum );
1196 ADD_META( 6, DBUS_TYPE_UINT32, i_time );
1197 ADD_META( 7, DBUS_TYPE_INT64, i_mtime );
1198 ADD_META_SINGLETON_STRING_LIST( 8, Genre );
1199 //ADD_META( 9, DBUS_TYPE_DOUBLE, rating );
1200 ADD_VLC_META_STRING( 10, Date ); // this is supposed to be in ISO 8601 extended format
1201 ADD_VLC_META_STRING( 11, ArtURL );
1202 ADD_VLC_META_STRING( 12, TrackID );
1204 ADD_VLC_META_STRING( 17, Copyright );
1205 ADD_META_SINGLETON_STRING_LIST( 18, Description );
1206 ADD_VLC_META_STRING( 19, EncodedBy );
1207 ADD_VLC_META_STRING( 20, Language );
1208 ADD_META( 21, DBUS_TYPE_INT64, i_length );
1209 ADD_VLC_META_STRING( 22, NowPlaying );
1210 ADD_VLC_META_STRING( 23, Publisher );
1211 ADD_VLC_META_STRING( 24, Setting );
1212 ADD_VLC_META_STRING( 25, URL );
1214 free( psz_trackid );
1216 vlc_mutex_lock( &p_input->lock );
1217 if( p_input->p_meta )
1219 int i_status = vlc_meta_GetStatus( p_input->p_meta );
1220 ADD_META( 23, DBUS_TYPE_INT32, i_status );
1222 vlc_mutex_unlock( &p_input->lock );
1224 dbus_message_iter_close_container( args, &dict );
1228 int AddProperty( intf_thread_t *p_intf,
1229 DBusMessageIter *p_container,
1230 const char* psz_property_name,
1231 const char* psz_signature,
1232 int (*pf_marshaller) (intf_thread_t*, DBusMessageIter*) )
1234 DBusMessageIter entry, v;
1236 if( !dbus_message_iter_open_container( p_container,
1237 DBUS_TYPE_DICT_ENTRY, NULL,
1241 if( !dbus_message_iter_append_basic( &entry,
1243 &psz_property_name ) )
1246 if( !dbus_message_iter_open_container( &entry,
1247 DBUS_TYPE_VARIANT, psz_signature,
1251 if( VLC_SUCCESS != pf_marshaller( p_intf, &v ) )
1254 if( !dbus_message_iter_close_container( &entry, &v) )
1257 if( !dbus_message_iter_close_container( p_container, &entry ) )
1264 #undef ADD_VLC_META_STRING