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
102 static int Open ( vlc_object_t * );
103 static void Close ( vlc_object_t * );
104 static void *Run ( void * );
106 static int TrackChange( intf_thread_t * );
107 static int AllCallback( vlc_object_t*, const char*, vlc_value_t, vlc_value_t, void* );
108 static int InputCallback( vlc_object_t*, const char*, vlc_value_t, vlc_value_t, void* );
110 static dbus_bool_t add_timeout(DBusTimeout *, void *);
111 static void remove_timeout(DBusTimeout *, void *);
112 static void toggle_timeout(DBusTimeout *, void *);
114 static dbus_bool_t add_watch ( DBusWatch *p_watch, void *p_data );
115 static void remove_watch ( DBusWatch *p_watch, void *p_data );
116 static void watch_toggled ( DBusWatch *p_watch, void *p_data );
118 static void wakeup_main_loop( void *p_data );
120 static void ProcessEvents ( intf_thread_t *p_intf,
121 callback_info_t **p_events,
124 static void ProcessWatches ( intf_thread_t *p_intf,
125 DBusWatch **p_watches,
127 struct pollfd *p_fds,
130 static void DispatchDBusMessages( intf_thread_t *p_intf );
132 /*****************************************************************************
134 *****************************************************************************/
136 set_shortname( N_("DBus"))
137 set_category( CAT_INTERFACE )
138 set_description( N_("D-Bus control interface") )
139 set_capability( "interface", 0 )
140 set_callbacks( Open, Close )
143 /*****************************************************************************
144 * Open: initialize interface
145 *****************************************************************************/
147 static int Open( vlc_object_t *p_this )
149 intf_thread_t *p_intf = (intf_thread_t*)p_this;
151 /* initialisation of the connection */
152 if( !dbus_threads_init_default() )
155 intf_sys_t *p_sys = calloc( 1, sizeof( intf_sys_t ) );
156 if( unlikely(!p_sys) )
159 playlist_t *p_playlist;
160 DBusConnection *p_conn;
161 p_sys->i_player_caps = PLAYER_CAPS_NONE;
162 p_sys->i_playing_state = PLAYBACK_STATE_INVALID;
164 if( vlc_pipe( p_sys->p_pipe_fds ) )
167 msg_Err( p_intf, "Could not create pipe" );
172 dbus_error_init( &error );
174 /* connect privately to the session bus
175 * the connection will not be shared with other vlc modules which use dbus,
176 * thus avoiding a whole class of concurrency issues */
177 p_conn = dbus_bus_get_private( DBUS_BUS_SESSION, &error );
180 msg_Err( p_this, "Failed to connect to the D-Bus session daemon: %s",
182 dbus_error_free( &error );
183 vlc_close( p_sys->p_pipe_fds[1] );
184 vlc_close( p_sys->p_pipe_fds[0] );
189 dbus_connection_set_exit_on_disconnect( p_conn, FALSE );
191 /* Register the entry point object path */
192 dbus_connection_register_object_path( p_conn, DBUS_MPRIS_OBJECT_PATH,
193 &dbus_mpris_vtable, p_this );
195 /* Try to register org.mpris.MediaPlayer2.vlc */
196 const unsigned bus_flags = DBUS_NAME_FLAG_DO_NOT_QUEUE;
197 var_Create(p_intf->obj.libvlc, "dbus-mpris-name", VLC_VAR_STRING);
198 if( dbus_bus_request_name( p_conn, DBUS_MPRIS_BUS_NAME, bus_flags, NULL )
199 != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER )
201 /* Register an instance-specific well known name of the form
202 * org.mpris.MediaPlayer2.vlc.instanceXXXX where XXXX is the
203 * current Process ID */
204 char unique_service[sizeof( DBUS_MPRIS_BUS_NAME ) +
205 sizeof( DBUS_INSTANCE_ID_PREFIX ) + 10];
207 snprintf( unique_service, sizeof (unique_service),
208 DBUS_MPRIS_BUS_NAME"."DBUS_INSTANCE_ID_PREFIX"%"PRIu32,
209 (uint32_t)getpid() );
211 if( dbus_bus_request_name( p_conn, unique_service, bus_flags, NULL )
212 == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER )
214 msg_Dbg( p_intf, "listening on dbus as: %s", unique_service );
215 var_SetString(p_intf->obj.libvlc, "dbus-mpris-name",
221 msg_Dbg( p_intf, "listening on dbus as: %s", DBUS_MPRIS_BUS_NAME );
222 var_SetString(p_intf->obj.libvlc, "dbus-mpris-name",
223 DBUS_MPRIS_BUS_NAME);
225 dbus_connection_flush( p_conn );
227 p_intf->p_sys = p_sys;
228 p_sys->p_conn = p_conn;
229 vlc_array_init( &p_sys->events );
230 vlc_array_init( &p_sys->timeouts );
231 vlc_array_init( &p_sys->watches );
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, "input-current", AllCallback, p_intf );
238 var_AddCallback( p_playlist, "volume", AllCallback, p_intf );
239 var_AddCallback( p_playlist, "mute", AllCallback, p_intf );
240 var_AddCallback( p_playlist, "playlist-item-append", AllCallback, p_intf );
241 var_AddCallback( p_playlist, "playlist-item-deleted", AllCallback, p_intf );
242 var_AddCallback( p_playlist, "random", AllCallback, p_intf );
243 var_AddCallback( p_playlist, "repeat", AllCallback, p_intf );
244 var_AddCallback( p_playlist, "loop", AllCallback, p_intf );
245 var_AddCallback( p_playlist, "fullscreen", AllCallback, p_intf );
247 if( !dbus_connection_set_timeout_functions( p_conn,
254 if( !dbus_connection_set_watch_functions( p_conn,
261 if( vlc_clone( &p_sys->thread, Run, p_intf, VLC_THREAD_PRIORITY_LOW ) )
267 var_Destroy(p_intf->obj.libvlc, "dbus-mpris-name");
268 /* The dbus connection is private,
269 * so we are responsible for closing it
270 * XXX: Does this make sense when OOM ? */
271 dbus_connection_close( p_sys->p_conn );
272 dbus_connection_unref( p_conn );
274 vlc_mutex_destroy( &p_sys->lock );
276 vlc_close( p_sys->p_pipe_fds[1] );
277 vlc_close( p_sys->p_pipe_fds[0] );
282 /*****************************************************************************
283 * Close: destroy interface
284 *****************************************************************************/
286 static void Close ( vlc_object_t *p_this )
288 intf_thread_t *p_intf = (intf_thread_t*) p_this;
289 intf_sys_t *p_sys = p_intf->p_sys;
290 playlist_t *p_playlist = p_sys->p_playlist;
292 vlc_cancel( p_sys->thread );
293 vlc_join( p_sys->thread, NULL );
295 var_DelCallback( p_playlist, "input-current", AllCallback, p_intf );
296 var_DelCallback( p_playlist, "volume", AllCallback, p_intf );
297 var_DelCallback( p_playlist, "mute", AllCallback, p_intf );
298 var_DelCallback( p_playlist, "playlist-item-append", AllCallback, p_intf );
299 var_DelCallback( p_playlist, "playlist-item-deleted", AllCallback, p_intf );
300 var_DelCallback( p_playlist, "random", AllCallback, p_intf );
301 var_DelCallback( p_playlist, "repeat", AllCallback, p_intf );
302 var_DelCallback( p_playlist, "loop", AllCallback, p_intf );
303 var_DelCallback( p_playlist, "fullscreen", AllCallback, p_intf );
307 var_DelCallback( p_sys->p_input, "intf-event", InputCallback, p_intf );
308 var_DelCallback( p_sys->p_input, "can-pause", AllCallback, p_intf );
309 var_DelCallback( p_sys->p_input, "can-seek", AllCallback, p_intf );
310 vlc_object_release( p_sys->p_input );
313 /* The dbus connection is private, so we are responsible
315 dbus_connection_close( p_sys->p_conn );
316 dbus_connection_unref( p_sys->p_conn );
318 // Free the events array
319 for( size_t i = 0; i < vlc_array_count( &p_sys->events ); i++ )
321 callback_info_t* info = vlc_array_item_at_index( &p_sys->events, i );
324 vlc_mutex_destroy( &p_sys->lock );
325 vlc_array_clear( &p_sys->events );
326 vlc_array_clear( &p_sys->timeouts );
327 vlc_array_clear( &p_sys->watches );
328 vlc_close( p_sys->p_pipe_fds[1] );
329 vlc_close( p_sys->p_pipe_fds[0] );
333 static dbus_bool_t add_timeout(DBusTimeout *to, void *data)
335 intf_thread_t *intf = data;
336 intf_sys_t *sys = intf->p_sys;
338 mtime_t *expiry = malloc(sizeof (*expiry));
339 if (unlikely(expiry == NULL))
342 dbus_timeout_set_data(to, expiry, free);
344 vlc_mutex_lock(&sys->lock);
345 vlc_array_append(&sys->timeouts, to);
346 vlc_mutex_unlock(&sys->lock);
351 static void remove_timeout(DBusTimeout *to, void *data)
353 intf_thread_t *intf = data;
354 intf_sys_t *sys = intf->p_sys;
357 vlc_mutex_lock(&sys->lock);
358 idx = vlc_array_index_of_item(&sys->timeouts, to);
359 vlc_array_remove(&sys->timeouts, idx);
360 vlc_mutex_unlock(&sys->lock);
363 static void toggle_timeout(DBusTimeout *to, void *data)
365 intf_thread_t *intf = data;
366 intf_sys_t *sys = intf->p_sys;
367 mtime_t *expiry = dbus_timeout_get_data(to);
369 vlc_mutex_lock(&sys->lock);
370 if (dbus_timeout_get_enabled(to))
371 *expiry = mdate() + UINT64_C(1000) * dbus_timeout_get_interval(to);
372 vlc_mutex_unlock(&sys->lock);
374 wakeup_main_loop(intf);
378 * Computes the time until the next timeout expiration.
379 * @note Interface lock must be held.
380 * @return The time in milliseconds until the next expiration,
381 * or -1 if there are no pending timeouts.
383 static int next_timeout(intf_thread_t *intf)
385 intf_sys_t *sys = intf->p_sys;
386 mtime_t next_timeout = LAST_MDATE;
387 unsigned count = vlc_array_count(&sys->timeouts);
389 for (unsigned i = 0; i < count; i++)
391 DBusTimeout *to = vlc_array_item_at_index(&sys->timeouts, i);
393 if (!dbus_timeout_get_enabled(to))
396 mtime_t *expiry = dbus_timeout_get_data(to);
398 if (next_timeout > *expiry)
399 next_timeout = *expiry;
402 if (next_timeout >= LAST_MDATE)
405 next_timeout /= 1000;
407 if (next_timeout > INT_MAX)
410 return (int)next_timeout;
414 * Process pending D-Bus timeouts.
416 * @note Interface lock must be held.
418 static void process_timeouts(intf_thread_t *intf)
420 intf_sys_t *sys = intf->p_sys;
422 for (size_t i = 0; i < vlc_array_count(&sys->timeouts); i++)
424 DBusTimeout *to = vlc_array_item_at_index(&sys->timeouts, i);
426 if (!dbus_timeout_get_enabled(to))
429 mtime_t *expiry = dbus_timeout_get_data(to);
430 if (*expiry > mdate())
433 expiry += UINT64_C(1000) * dbus_timeout_get_interval(to);
434 vlc_mutex_unlock(&sys->lock);
436 dbus_timeout_handle(to);
438 vlc_mutex_lock(&sys->lock);
439 i = -1; /* lost track of state, restart from beginning */
444 static dbus_bool_t add_watch( DBusWatch *p_watch, void *p_data )
446 intf_thread_t *p_intf = (intf_thread_t*) p_data;
447 intf_sys_t *p_sys = (intf_sys_t*) p_intf->p_sys;
449 vlc_mutex_lock( &p_sys->lock );
450 vlc_array_append( &p_sys->watches, p_watch );
451 vlc_mutex_unlock( &p_sys->lock );
456 static void remove_watch( DBusWatch *p_watch, void *p_data )
458 intf_thread_t *p_intf = (intf_thread_t*) p_data;
459 intf_sys_t *p_sys = (intf_sys_t*) p_intf->p_sys;
462 vlc_mutex_lock( &p_sys->lock );
463 idx = vlc_array_index_of_item( &p_sys->watches, p_watch );
464 vlc_array_remove( &p_sys->watches, idx );
465 vlc_mutex_unlock( &p_sys->lock );
468 static void watch_toggled( DBusWatch *p_watch, void *p_data )
470 intf_thread_t *p_intf = (intf_thread_t*) p_data;
472 if( dbus_watch_get_enabled( p_watch ) )
473 wakeup_main_loop( p_intf );
477 * GetPollFds() fills an array of pollfd data structures with :
478 * - the set of enabled dbus watches
479 * - the unix pipe which we use to manually wake up the main loop
481 * This function must be called with p_sys->lock locked
483 * @return The number of file descriptors
485 * @param intf_thread_t *p_intf this interface thread's state
486 * @param struct pollfd *p_fds a pointer to a pollfd array large enough to
487 * contain all the returned data (number of enabled dbus watches + 1)
489 static int GetPollFds( intf_thread_t *p_intf, struct pollfd *p_fds )
491 intf_sys_t *p_sys = p_intf->p_sys;
492 size_t i_watches = vlc_array_count( &p_sys->watches );
495 p_fds[0].fd = p_sys->p_pipe_fds[PIPE_OUT];
496 p_fds[0].events = POLLIN | POLLPRI;
498 for( size_t i = 0; i < i_watches; i++ )
500 DBusWatch *p_watch = NULL;
501 p_watch = vlc_array_item_at_index( &p_sys->watches, i );
502 if( !dbus_watch_get_enabled( p_watch ) )
505 p_fds[i_fds].fd = dbus_watch_get_unix_fd( p_watch );
506 int i_flags = dbus_watch_get_flags( p_watch );
508 if( i_flags & DBUS_WATCH_READABLE )
509 p_fds[i_fds].events |= POLLIN | POLLPRI;
511 if( i_flags & DBUS_WATCH_WRITABLE )
512 p_fds[i_fds].events |= POLLOUT;
521 * ProcessEvents() reacts to a list of events originating from other VLC threads
523 * This function must be called with p_sys->lock unlocked
525 * @param intf_thread_t *p_intf This interface thread state
526 * @param callback_info_t *p_events the list of events to process
528 static void ProcessEvents( intf_thread_t *p_intf,
529 callback_info_t **p_events, int i_events )
531 bool b_can_play = p_intf->p_sys->b_can_play;
533 vlc_dictionary_t player_properties, tracklist_properties, root_properties;
534 vlc_dictionary_init( &player_properties, 0 );
535 vlc_dictionary_init( &tracklist_properties, 0 );
536 vlc_dictionary_init( &root_properties, 0 );
538 for( int i = 0; i < i_events; i++ )
540 switch( p_events[i]->signal )
542 case SIGNAL_ITEM_CURRENT:
543 TrackChange( p_intf );
545 // rate depends on current item
546 if( !vlc_dictionary_has_key( &player_properties, "Rate" ) )
547 vlc_dictionary_insert( &player_properties, "Rate", NULL );
549 vlc_dictionary_insert( &player_properties, "Metadata", NULL );
551 case SIGNAL_PLAYLIST_ITEM_APPEND:
552 case SIGNAL_PLAYLIST_ITEM_DELETED:
554 playlist_t *p_playlist = p_intf->p_sys->p_playlist;
556 b_can_play = playlist_CurrentSize( p_playlist ) > 0;
559 if( b_can_play != p_intf->p_sys->b_can_play )
561 p_intf->p_sys->b_can_play = b_can_play;
562 vlc_dictionary_insert( &player_properties, "CanPlay", NULL );
565 if( !vlc_dictionary_has_key( &tracklist_properties, "Tracks" ) )
566 vlc_dictionary_insert( &tracklist_properties, "Tracks", NULL );
569 case SIGNAL_VOLUME_MUTED:
570 case SIGNAL_VOLUME_CHANGE:
571 vlc_dictionary_insert( &player_properties, "Volume", NULL );
574 vlc_dictionary_insert( &player_properties, "Shuffle", NULL );
576 case SIGNAL_FULLSCREEN:
577 vlc_dictionary_insert( &root_properties, "Fullscreen", 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 = pl_CurrentInput( p_intf );
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 );
611 SeekedEmit( p_intf );
614 vlc_assert_unreachable();
619 if( vlc_dictionary_keys_count( &player_properties ) )
620 PlayerPropertiesChangedEmit( p_intf, &player_properties );
622 if( vlc_dictionary_keys_count( &tracklist_properties ) )
623 TrackListPropertiesChangedEmit( p_intf, &tracklist_properties );
625 if( vlc_dictionary_keys_count( &root_properties ) )
626 RootPropertiesChangedEmit( p_intf, &root_properties );
628 vlc_dictionary_clear( &player_properties, NULL, NULL );
629 vlc_dictionary_clear( &tracklist_properties, NULL, NULL );
630 vlc_dictionary_clear( &root_properties, NULL, NULL );
634 * ProcessWatches() handles a list of dbus watches after poll() has returned
636 * This function must be called with p_sys->lock unlocked
638 * @param intf_thread_t *p_intf This interface thread state
639 * @param DBusWatch **p_watches The list of dbus watches to process
640 * @param int i_watches The size of the p_watches array
641 * @param struct pollfd *p_fds The result of a poll() call
642 * @param int i_fds The number of file descriptors processed by poll()
644 static void ProcessWatches( intf_thread_t *p_intf,
645 DBusWatch **p_watches, int i_watches,
646 struct pollfd *p_fds, int i_fds )
650 /* Process watches */
651 for( int i = 0; i < i_watches; i++ )
653 DBusWatch *p_watch = p_watches[i];
654 if( !dbus_watch_get_enabled( p_watch ) )
657 for( int j = 0; j < i_fds; j++ )
659 if( p_fds[j].fd != dbus_watch_get_unix_fd( p_watch ) )
663 int i_revents = p_fds[j].revents;
665 if( i_revents & POLLIN )
666 i_flags |= DBUS_WATCH_READABLE;
668 if( i_revents & POLLOUT )
669 i_flags |= DBUS_WATCH_WRITABLE;
671 if( i_revents & POLLERR )
672 i_flags |= DBUS_WATCH_ERROR;
674 if( i_revents & POLLHUP )
675 i_flags |= DBUS_WATCH_HANGUP;
678 dbus_watch_handle( p_watch, i_flags );
684 * DispatchDBusMessages() dispatches incoming dbus messages
685 * (indirectly invoking the callbacks), then it sends outgoing
686 * messages which needs to be sent on the bus (method replies and signals)
688 * This function must be called with p_sys->lock unlocked
690 * @param intf_thread_t *p_intf This interface thread state
692 static void DispatchDBusMessages( intf_thread_t *p_intf )
694 DBusDispatchStatus status;
695 intf_sys_t *p_sys = p_intf->p_sys;
697 /* Dispatch incoming messages */
698 status = dbus_connection_get_dispatch_status( p_sys->p_conn );
699 while( status != DBUS_DISPATCH_COMPLETE )
701 dbus_connection_dispatch( p_sys->p_conn );
702 status = dbus_connection_get_dispatch_status( p_sys->p_conn );
705 /* Send outgoing data */
706 if( dbus_connection_has_messages_to_send( p_sys->p_conn ) )
707 dbus_connection_flush( p_sys->p_conn );
711 * MPRISEntryPoint() routes incoming messages to their respective interface
714 * This function is called during dbus_connection_dispatch()
716 static DBusHandlerResult
717 MPRISEntryPoint ( DBusConnection *p_conn, DBusMessage *p_from, void *p_this )
719 const char *psz_target_interface;
720 const char *psz_interface = dbus_message_get_interface( p_from );
721 const char *psz_method = dbus_message_get_member( p_from );
725 if( psz_interface && strcmp( psz_interface, DBUS_INTERFACE_PROPERTIES ) )
726 psz_target_interface = psz_interface;
730 dbus_error_init( &error );
731 dbus_message_get_args( p_from, &error,
732 DBUS_TYPE_STRING, &psz_target_interface,
735 if( dbus_error_is_set( &error ) )
737 msg_Err( (vlc_object_t*) p_this, "D-Bus error on %s.%s: %s",
738 psz_interface, psz_method,
740 dbus_error_free( &error );
741 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
745 if( !strcmp( psz_target_interface, DBUS_INTERFACE_INTROSPECTABLE ) )
746 return handle_introspect( p_conn, p_from, p_this );
748 if( !strcmp( psz_target_interface, DBUS_MPRIS_ROOT_INTERFACE ) )
749 return handle_root( p_conn, p_from, p_this );
751 if( !strcmp( psz_target_interface, DBUS_MPRIS_PLAYER_INTERFACE ) )
752 return handle_player( p_conn, p_from, p_this );
754 if( !strcmp( psz_target_interface, DBUS_MPRIS_TRACKLIST_INTERFACE ) )
755 return handle_tracklist( p_conn, p_from, p_this );
757 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
760 /*****************************************************************************
762 *****************************************************************************/
764 static void *Run( void *data )
766 intf_thread_t *p_intf = data;
767 intf_sys_t *p_sys = p_intf->p_sys;
769 int canc = vlc_savecancel();
773 vlc_mutex_lock( &p_sys->lock );
775 size_t i_watches = vlc_array_count( &p_sys->watches );
776 struct pollfd fds[i_watches];
777 memset(fds, 0, sizeof fds);
779 int i_fds = GetPollFds( p_intf, fds );
780 int timeout = next_timeout(p_intf);
782 vlc_mutex_unlock( &p_sys->lock );
784 /* thread cancellation is allowed while the main loop sleeps */
785 vlc_restorecancel( canc );
787 while (poll(fds, i_fds, timeout) == -1)
793 canc = vlc_savecancel();
795 /* Was the main loop woken up manually ? */
796 if (fds[0].revents & POLLIN)
799 (void)read( fds[0].fd, &buf, 1 );
802 /* We need to lock the mutex while building lists of events,
803 * timeouts and watches to process but we can't keep the lock while
804 * processing them, or else we risk a deadlock:
806 * The signal functions could lock mutex X while p_events is locked;
807 * While some other function in vlc (playlist) might lock mutex X
808 * and then set a variable which would call AllCallback(), which itself
809 * needs to lock p_events to add a new event.
811 vlc_mutex_lock( &p_intf->p_sys->lock );
813 process_timeouts(p_intf);
815 /* Get the list of watches to process */
816 i_watches = vlc_array_count( &p_sys->watches );
817 DBusWatch *p_watches[i_watches ? i_watches : 1];
818 for( size_t i = 0; i < i_watches; i++ )
820 p_watches[i] = vlc_array_item_at_index( &p_sys->watches, i );
823 /* Get the list of events to process */
824 size_t i_events = vlc_array_count( &p_sys->events );
825 callback_info_t* p_info[i_events ? i_events : 1];
826 for( size_t i = 0; i < i_events; i++ )
828 p_info[i] = vlc_array_item_at_index( &p_sys->events, i );
831 vlc_array_clear( &p_sys->events );
833 /* now we can release the lock and process what's pending */
834 vlc_mutex_unlock( &p_intf->p_sys->lock );
836 ProcessEvents( p_intf, p_info, i_events );
837 ProcessWatches( p_intf, p_watches, i_watches, fds, i_fds );
839 DispatchDBusMessages( p_intf );
842 vlc_restorecancel(canc);
846 static void wakeup_main_loop( void *p_data )
848 intf_thread_t *p_intf = (intf_thread_t*) p_data;
850 if( !write( p_intf->p_sys->p_pipe_fds[PIPE_IN], "\0", 1 ) )
851 msg_Err( p_intf, "Could not wake up the main loop: %s",
852 vlc_strerror_c(errno) );
855 /* Flls a callback_info_t data structure in response
856 * to an "intf-event" input event.
858 * @warning This function executes in the input thread.
860 * @return VLC_SUCCESS on success, VLC_E* on error.
862 static int InputCallback( vlc_object_t *p_this, const char *psz_var,
863 vlc_value_t oldval, vlc_value_t newval, void *data )
865 input_thread_t *p_input = (input_thread_t *)p_this;
866 intf_thread_t *p_intf = data;
867 intf_sys_t *p_sys = p_intf->p_sys;
869 dbus_int32_t i_state = PLAYBACK_STATE_INVALID;
871 callback_info_t *p_info = calloc( 1, sizeof( callback_info_t ) );
872 if( unlikely(p_info == NULL) )
875 switch( newval.i_int )
877 case INPUT_EVENT_DEAD:
878 i_state = PLAYBACK_STATE_STOPPED;
880 case INPUT_EVENT_STATE:
881 switch( var_GetInteger( p_input, "state" ) )
885 i_state = PLAYBACK_STATE_PLAYING;
888 i_state = PLAYBACK_STATE_PAUSED;
891 i_state = PLAYBACK_STATE_STOPPED;
894 case INPUT_EVENT_ITEM_META:
895 p_info->signal = SIGNAL_INPUT_METADATA;
897 case INPUT_EVENT_RATE:
898 p_info->signal = SIGNAL_RATE;
900 case INPUT_EVENT_POSITION:
902 mtime_t i_now = mdate(), i_pos, i_projected_pos, i_interval;
903 float f_current_rate;
906 * XXX: This is way more convoluted than it should be... */
907 i_pos = var_GetInteger( p_input, "time" );
909 if( !p_intf->p_sys->i_last_input_pos_event ||
910 !( var_GetInteger( p_input, "state" ) == PLAYING_S ) )
912 p_intf->p_sys->i_last_input_pos_event = i_now;
913 p_intf->p_sys->i_last_input_pos = i_pos;
917 f_current_rate = var_GetFloat( p_input, "rate" );
918 i_interval = ( i_now - p_intf->p_sys->i_last_input_pos_event );
920 i_projected_pos = p_intf->p_sys->i_last_input_pos +
921 ( i_interval * f_current_rate );
923 p_intf->p_sys->i_last_input_pos_event = i_now;
924 p_intf->p_sys->i_last_input_pos = i_pos;
926 if( llabs( i_pos - i_projected_pos ) < SEEK_THRESHOLD )
929 p_info->signal = SIGNAL_SEEK;
934 return VLC_SUCCESS; /* don't care */
937 vlc_mutex_lock( &p_sys->lock );
938 if( i_state != PLAYBACK_STATE_INVALID &&
939 i_state != p_sys->i_playing_state )
941 p_sys->i_playing_state = i_state;
942 p_info->signal = SIGNAL_STATE;
945 vlc_array_append( &p_sys->events, p_info );
948 vlc_mutex_unlock( &p_intf->p_sys->lock );
950 wakeup_main_loop( p_intf );
957 // Get all the callbacks
958 static int AllCallback( vlc_object_t *p_this, const char *psz_var,
959 vlc_value_t oldval, vlc_value_t newval, void *p_data )
961 intf_thread_t *p_intf = p_data;
962 callback_info_t info = { .signal = SIGNAL_NONE };
964 // Wich event is it ?
965 if( !strcmp( "input-current", psz_var ) )
966 info.signal = SIGNAL_ITEM_CURRENT;
967 else if( !strcmp( "volume", psz_var ) )
969 if( oldval.f_float != newval.f_float )
970 info.signal = SIGNAL_VOLUME_CHANGE;
972 else if( !strcmp( "mute", psz_var ) )
974 if( oldval.b_bool != newval.b_bool )
975 info.signal = SIGNAL_VOLUME_MUTED;
977 else if( !strcmp( "playlist-item-append", psz_var ) )
978 info.signal = SIGNAL_PLAYLIST_ITEM_APPEND;
979 else if( !strcmp( "playlist-item-deleted", psz_var ) )
980 info.signal = SIGNAL_PLAYLIST_ITEM_DELETED;
981 else if( !strcmp( "random", psz_var ) )
982 info.signal = SIGNAL_RANDOM;
983 else if( !strcmp( "fullscreen", psz_var ) )
984 info.signal = SIGNAL_FULLSCREEN;
985 else if( !strcmp( "repeat", psz_var ) )
986 info.signal = SIGNAL_REPEAT;
987 else if( !strcmp( "loop", psz_var ) )
988 info.signal = SIGNAL_LOOP;
989 else if( !strcmp( "can-seek", psz_var ) )
990 info.signal = SIGNAL_CAN_SEEK;
991 else if( !strcmp( "can-pause", psz_var ) )
992 info.signal = SIGNAL_CAN_PAUSE;
994 vlc_assert_unreachable();
996 if( info.signal == SIGNAL_NONE )
999 callback_info_t *p_info = malloc( sizeof( *p_info ) );
1000 if( unlikely(p_info == NULL) )
1005 vlc_mutex_lock( &p_intf->p_sys->lock );
1006 vlc_array_append( &p_intf->p_sys->events, p_info );
1007 vlc_mutex_unlock( &p_intf->p_sys->lock );
1009 wakeup_main_loop( p_intf );
1014 /*****************************************************************************
1015 * TrackChange: callback on playlist "input-current"
1016 *****************************************************************************/
1017 static int TrackChange( intf_thread_t *p_intf )
1019 intf_sys_t *p_sys = p_intf->p_sys;
1020 input_thread_t *p_input = NULL;
1021 input_item_t *p_item = NULL;
1023 if( p_intf->p_sys->b_dead )
1026 if( p_sys->p_input )
1028 var_DelCallback( p_sys->p_input, "intf-event", InputCallback, p_intf );
1029 var_DelCallback( p_sys->p_input, "can-pause", AllCallback, p_intf );
1030 var_DelCallback( p_sys->p_input, "can-seek", AllCallback, p_intf );
1031 vlc_object_release( p_sys->p_input );
1032 p_sys->p_input = NULL;
1035 p_sys->b_meta_read = false;
1037 p_input = pl_CurrentInput( p_intf );
1043 p_item = input_GetItem( p_input );
1046 vlc_object_release( p_input );
1047 return VLC_EGENERIC;
1050 if( input_item_IsPreparsed( p_item ) )
1051 p_sys->b_meta_read = true;
1053 p_sys->p_input = p_input;
1054 var_AddCallback( p_input, "intf-event", InputCallback, p_intf );
1055 var_AddCallback( p_input, "can-pause", AllCallback, p_intf );
1056 var_AddCallback( p_input, "can-seek", AllCallback, p_intf );
1062 * DemarshalSetPropertyValue() extracts the new property value from a
1063 * org.freedesktop.DBus.Properties.Set method call message.
1065 * @return int VLC_SUCCESS on success
1066 * @param DBusMessage *p_msg a org.freedesktop.DBus.Properties.Set method call
1067 * @param void *p_arg placeholder for the demarshalled value
1069 int DemarshalSetPropertyValue( DBusMessage *p_msg, void *p_arg )
1072 bool b_valid_input = FALSE;
1073 DBusMessageIter in_args, variant;
1074 dbus_message_iter_init( p_msg, &in_args );
1078 i_type = dbus_message_iter_get_arg_type( &in_args );
1079 if( DBUS_TYPE_VARIANT == i_type )
1081 dbus_message_iter_recurse( &in_args, &variant );
1082 dbus_message_iter_get_basic( &variant, p_arg );
1083 b_valid_input = TRUE;
1085 } while( dbus_message_iter_next( &in_args ) );
1087 return b_valid_input ? VLC_SUCCESS : VLC_EGENERIC;
1090 /*****************************************************************************
1091 * GetInputMeta: Fill a DBusMessage with the given input item metadata
1092 *****************************************************************************/
1094 #define ADD_META( entry, type, data ) \
1096 dbus_message_iter_open_container( &dict, DBUS_TYPE_DICT_ENTRY, \
1097 NULL, &dict_entry ); \
1098 dbus_message_iter_append_basic( &dict_entry, DBUS_TYPE_STRING, \
1099 &ppsz_meta_items[entry] ); \
1100 dbus_message_iter_open_container( &dict_entry, DBUS_TYPE_VARIANT, \
1101 type##_AS_STRING, &variant ); \
1102 dbus_message_iter_append_basic( &variant, \
1105 dbus_message_iter_close_container( &dict_entry, &variant ); \
1106 dbus_message_iter_close_container( &dict, &dict_entry ); }
1108 #define ADD_VLC_META_STRING( entry, item ) \
1110 char * psz = input_item_Get##item( p_input );\
1111 ADD_META( entry, DBUS_TYPE_STRING, \
1116 #define ADD_META_SINGLETON_STRING_LIST( entry, item ) \
1118 char * psz = input_item_Get##item( p_input );\
1120 dbus_message_iter_open_container( &dict, DBUS_TYPE_DICT_ENTRY, \
1121 NULL, &dict_entry ); \
1122 dbus_message_iter_append_basic( &dict_entry, DBUS_TYPE_STRING, \
1123 &ppsz_meta_items[entry] ); \
1124 dbus_message_iter_open_container( &dict_entry, DBUS_TYPE_VARIANT, \
1126 dbus_message_iter_open_container( &variant, DBUS_TYPE_ARRAY, "s", \
1128 dbus_message_iter_append_basic( &list, \
1131 dbus_message_iter_close_container( &variant, &list ); \
1132 dbus_message_iter_close_container( &dict_entry, &variant ); \
1133 dbus_message_iter_close_container( &dict, &dict_entry ); \
1138 int GetInputMeta( playlist_item_t *item, DBusMessageIter *args )
1140 input_item_t *p_input = item->p_input;
1141 DBusMessageIter dict, dict_entry, variant, list;
1142 /** The duration of the track can be expressed in second, milli-seconds and
1144 dbus_int64_t i_mtime = input_item_GetDuration( p_input );
1145 dbus_uint32_t i_time = i_mtime / 1000000;
1146 dbus_int64_t i_length = i_mtime / 1000;
1149 if( -1 == asprintf( &psz_trackid, MPRIS_TRACKID_FORMAT, item->i_id ) )
1152 const char* ppsz_meta_items[] =
1154 "mpris:trackid", "xesam:url", "xesam:title", "xesam:artist",
1155 "xesam:album", "xesam:tracknumber", "vlc:time", "mpris:length",
1156 "xesam:genre", "xesam:userRating", "xesam:contentCreated",
1157 "mpris:artUrl", "mb:trackId", "vlc:audio-bitrate",
1158 "vlc:audio-samplerate", "vlc:video-bitrate", "vlc:audio-codec",
1159 "vlc:copyright", "xesam:comment", "vlc:encodedby", "language",
1160 "vlc:length", "vlc:nowplaying", "vlc:publisher", "vlc:setting",
1161 "status", "vlc:url", "vlc:video-codec"
1164 dbus_message_iter_open_container( args, DBUS_TYPE_ARRAY, "{sv}", &dict );
1166 ADD_META( 0, DBUS_TYPE_OBJECT_PATH, psz_trackid );
1167 ADD_VLC_META_STRING( 1, URI );
1168 ADD_VLC_META_STRING( 2, Title );
1169 ADD_META_SINGLETON_STRING_LIST( 3, Artist );
1170 ADD_VLC_META_STRING( 4, Album );
1171 ADD_VLC_META_STRING( 5, TrackNum );
1172 ADD_META( 6, DBUS_TYPE_UINT32, i_time );
1173 ADD_META( 7, DBUS_TYPE_INT64, i_mtime );
1174 ADD_META_SINGLETON_STRING_LIST( 8, Genre );
1175 //ADD_META( 9, DBUS_TYPE_DOUBLE, rating );
1176 ADD_VLC_META_STRING( 10, Date ); // this is supposed to be in ISO 8601 extended format
1177 ADD_VLC_META_STRING( 11, ArtURL );
1178 ADD_VLC_META_STRING( 12, TrackID );
1180 ADD_VLC_META_STRING( 17, Copyright );
1181 ADD_META_SINGLETON_STRING_LIST( 18, Description );
1182 ADD_VLC_META_STRING( 19, EncodedBy );
1183 ADD_VLC_META_STRING( 20, Language );
1184 ADD_META( 21, DBUS_TYPE_INT64, i_length );
1185 ADD_VLC_META_STRING( 22, NowPlaying );
1186 ADD_VLC_META_STRING( 23, Publisher );
1187 ADD_VLC_META_STRING( 24, Setting );
1188 ADD_VLC_META_STRING( 25, URL );
1190 free( psz_trackid );
1192 vlc_mutex_lock( &p_input->lock );
1193 if( p_input->p_meta )
1195 int i_status = vlc_meta_GetStatus( p_input->p_meta );
1196 ADD_META( 23, DBUS_TYPE_INT32, i_status );
1198 vlc_mutex_unlock( &p_input->lock );
1200 dbus_message_iter_close_container( args, &dict );
1204 int AddProperty( intf_thread_t *p_intf,
1205 DBusMessageIter *p_container,
1206 const char* psz_property_name,
1207 const char* psz_signature,
1208 int (*pf_marshaller) (intf_thread_t*, DBusMessageIter*) )
1210 DBusMessageIter entry, v;
1212 if( !dbus_message_iter_open_container( p_container,
1213 DBUS_TYPE_DICT_ENTRY, NULL,
1217 if( !dbus_message_iter_append_basic( &entry,
1219 &psz_property_name ) )
1222 if( !dbus_message_iter_open_container( &entry,
1223 DBUS_TYPE_VARIANT, psz_signature,
1227 if( VLC_SUCCESS != pf_marshaller( p_intf, &v ) )
1230 if( !dbus_message_iter_close_container( &entry, &v) )
1233 if( !dbus_message_iter_close_container( p_container, &entry ) )
1240 #undef ADD_VLC_META_STRING