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_legacy.h>
61 #include <vlc_input.h>
74 #define DBUS_MPRIS_BUS_NAME "org.mpris.MediaPlayer2.vlc"
75 #define DBUS_INSTANCE_ID_PREFIX "instance"
77 #define SEEK_THRESHOLD 1000 /* µsec */
78 #define EVENTS_DELAY INT64_C(100000) /* 100 ms */
80 /*****************************************************************************
82 *****************************************************************************/
84 static DBusHandlerResult
85 MPRISEntryPoint ( DBusConnection
*p_conn
, DBusMessage
*p_from
, void *p_this
);
87 static const DBusObjectPathVTable dbus_mpris_vtable
= {
88 NULL
, MPRISEntryPoint
, /* handler function */
89 NULL
, NULL
, NULL
, NULL
103 static int Open ( vlc_object_t
* );
104 static void Close ( vlc_object_t
* );
105 static void *Run ( void * );
107 static int TrackChange( intf_thread_t
* );
108 static int AllCallback( vlc_object_t
*, const char*, vlc_value_t
, vlc_value_t
, void* );
109 static int InputCallback( vlc_object_t
*, const char*, vlc_value_t
, vlc_value_t
, void* );
111 static dbus_bool_t
add_timeout(DBusTimeout
*, void *);
112 static void remove_timeout(DBusTimeout
*, void *);
113 static void toggle_timeout(DBusTimeout
*, void *);
115 static dbus_bool_t
add_watch ( DBusWatch
*p_watch
, void *p_data
);
116 static void remove_watch ( DBusWatch
*p_watch
, void *p_data
);
117 static void watch_toggled ( DBusWatch
*p_watch
, void *p_data
);
119 static void wakeup_main_loop( void *p_data
);
121 static void ProcessEvents ( intf_thread_t
*p_intf
,
122 callback_info_t
**p_events
,
125 static void ProcessWatches ( intf_thread_t
*p_intf
,
126 DBusWatch
**p_watches
,
128 struct pollfd
*p_fds
,
131 static void DispatchDBusMessages( intf_thread_t
*p_intf
);
133 /*****************************************************************************
135 *****************************************************************************/
137 set_shortname( N_("DBus"))
138 set_category( CAT_INTERFACE
)
139 set_description( N_("D-Bus control interface") )
140 set_capability( "interface", 0 )
141 set_callbacks( Open
, Close
)
144 /*****************************************************************************
145 * Open: initialize interface
146 *****************************************************************************/
148 static int Open( vlc_object_t
*p_this
)
150 intf_thread_t
*p_intf
= (intf_thread_t
*)p_this
;
152 /* initialisation of the connection */
153 if( !dbus_threads_init_default() )
156 intf_sys_t
*p_sys
= calloc( 1, sizeof( intf_sys_t
) );
157 if( unlikely(!p_sys
) )
160 playlist_t
*p_playlist
;
161 DBusConnection
*p_conn
;
162 p_sys
->i_player_caps
= PLAYER_CAPS_NONE
;
163 p_sys
->i_playing_state
= PLAYBACK_STATE_INVALID
;
165 if( vlc_pipe( p_sys
->p_pipe_fds
) )
168 msg_Err( p_intf
, "Could not create pipe" );
173 dbus_error_init( &error
);
175 /* connect privately to the session bus
176 * the connection will not be shared with other vlc modules which use dbus,
177 * thus avoiding a whole class of concurrency issues */
178 p_conn
= dbus_bus_get_private( DBUS_BUS_SESSION
, &error
);
181 msg_Err( p_this
, "Failed to connect to the D-Bus session daemon: %s",
183 dbus_error_free( &error
);
184 vlc_close( p_sys
->p_pipe_fds
[1] );
185 vlc_close( p_sys
->p_pipe_fds
[0] );
190 dbus_connection_set_exit_on_disconnect( p_conn
, FALSE
);
192 /* Register the entry point object path */
193 dbus_connection_register_object_path( p_conn
, DBUS_MPRIS_OBJECT_PATH
,
194 &dbus_mpris_vtable
, p_this
);
196 /* Try to register org.mpris.MediaPlayer2.vlc */
197 const unsigned bus_flags
= DBUS_NAME_FLAG_DO_NOT_QUEUE
;
198 var_Create(p_intf
->obj
.libvlc
, "dbus-mpris-name", VLC_VAR_STRING
);
199 if( dbus_bus_request_name( p_conn
, DBUS_MPRIS_BUS_NAME
, bus_flags
, NULL
)
200 != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER
)
202 /* Register an instance-specific well known name of the form
203 * org.mpris.MediaPlayer2.vlc.instanceXXXX where XXXX is the
204 * current Process ID */
205 char unique_service
[sizeof( DBUS_MPRIS_BUS_NAME
) +
206 sizeof( DBUS_INSTANCE_ID_PREFIX
) + 10];
208 snprintf( unique_service
, sizeof (unique_service
),
209 DBUS_MPRIS_BUS_NAME
"."DBUS_INSTANCE_ID_PREFIX
"%"PRIu32
,
210 (uint32_t)getpid() );
212 if( dbus_bus_request_name( p_conn
, unique_service
, bus_flags
, NULL
)
213 == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER
)
215 msg_Dbg( p_intf
, "listening on dbus as: %s", unique_service
);
216 var_SetString(p_intf
->obj
.libvlc
, "dbus-mpris-name",
222 msg_Dbg( p_intf
, "listening on dbus as: %s", DBUS_MPRIS_BUS_NAME
);
223 var_SetString(p_intf
->obj
.libvlc
, "dbus-mpris-name",
224 DBUS_MPRIS_BUS_NAME
);
226 dbus_connection_flush( p_conn
);
228 p_intf
->p_sys
= p_sys
;
229 p_sys
->p_conn
= p_conn
;
230 vlc_array_init( &p_sys
->events
);
231 vlc_array_init( &p_sys
->timeouts
);
232 vlc_array_init( &p_sys
->watches
);
233 vlc_mutex_init( &p_sys
->lock
);
235 p_playlist
= pl_Get( p_intf
);
236 p_sys
->p_playlist
= p_playlist
;
238 var_AddCallback( p_playlist
, "input-current", AllCallback
, p_intf
);
239 var_AddCallback( p_playlist
, "volume", AllCallback
, p_intf
);
240 var_AddCallback( p_playlist
, "mute", 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
);
246 var_AddCallback( p_playlist
, "fullscreen", AllCallback
, p_intf
);
248 if( !dbus_connection_set_timeout_functions( p_conn
,
255 if( !dbus_connection_set_watch_functions( p_conn
,
262 if( vlc_clone( &p_sys
->thread
, Run
, p_intf
, VLC_THREAD_PRIORITY_LOW
) )
268 var_Destroy(p_intf
->obj
.libvlc
, "dbus-mpris-name");
269 /* The dbus connection is private,
270 * so we are responsible for closing it
271 * XXX: Does this make sense when OOM ? */
272 dbus_connection_close( p_sys
->p_conn
);
273 dbus_connection_unref( p_conn
);
275 vlc_mutex_destroy( &p_sys
->lock
);
277 vlc_close( p_sys
->p_pipe_fds
[1] );
278 vlc_close( p_sys
->p_pipe_fds
[0] );
283 /*****************************************************************************
284 * Close: destroy interface
285 *****************************************************************************/
287 static void Close ( vlc_object_t
*p_this
)
289 intf_thread_t
*p_intf
= (intf_thread_t
*) p_this
;
290 intf_sys_t
*p_sys
= p_intf
->p_sys
;
291 playlist_t
*p_playlist
= p_sys
->p_playlist
;
293 vlc_cancel( p_sys
->thread
);
294 vlc_join( p_sys
->thread
, NULL
);
296 var_DelCallback( p_playlist
, "input-current", AllCallback
, p_intf
);
297 var_DelCallback( p_playlist
, "volume", AllCallback
, p_intf
);
298 var_DelCallback( p_playlist
, "mute", AllCallback
, p_intf
);
299 var_DelCallback( p_playlist
, "playlist-item-append", AllCallback
, p_intf
);
300 var_DelCallback( p_playlist
, "playlist-item-deleted", AllCallback
, p_intf
);
301 var_DelCallback( p_playlist
, "random", AllCallback
, p_intf
);
302 var_DelCallback( p_playlist
, "repeat", AllCallback
, p_intf
);
303 var_DelCallback( p_playlist
, "loop", AllCallback
, p_intf
);
304 var_DelCallback( p_playlist
, "fullscreen", AllCallback
, p_intf
);
308 var_DelCallback( p_sys
->p_input
, "intf-event", InputCallback
, p_intf
);
309 var_DelCallback( p_sys
->p_input
, "can-pause", AllCallback
, p_intf
);
310 var_DelCallback( p_sys
->p_input
, "can-seek", AllCallback
, p_intf
);
311 vlc_object_release( p_sys
->p_input
);
314 /* The dbus connection is private, so we are responsible
316 dbus_connection_close( p_sys
->p_conn
);
317 dbus_connection_unref( p_sys
->p_conn
);
319 // Free the events array
320 for( size_t i
= 0; i
< vlc_array_count( &p_sys
->events
); i
++ )
322 callback_info_t
* info
= vlc_array_item_at_index( &p_sys
->events
, i
);
325 vlc_mutex_destroy( &p_sys
->lock
);
326 vlc_array_clear( &p_sys
->events
);
327 vlc_array_clear( &p_sys
->timeouts
);
328 vlc_array_clear( &p_sys
->watches
);
329 vlc_close( p_sys
->p_pipe_fds
[1] );
330 vlc_close( p_sys
->p_pipe_fds
[0] );
334 static dbus_bool_t
add_timeout(DBusTimeout
*to
, void *data
)
336 intf_thread_t
*intf
= data
;
337 intf_sys_t
*sys
= intf
->p_sys
;
339 vlc_tick_t
*expiry
= malloc(sizeof (*expiry
));
340 if (unlikely(expiry
== NULL
))
343 dbus_timeout_set_data(to
, expiry
, free
);
345 vlc_mutex_lock(&sys
->lock
);
346 vlc_array_append_or_abort(&sys
->timeouts
, to
);
347 vlc_mutex_unlock(&sys
->lock
);
352 static void remove_timeout(DBusTimeout
*to
, void *data
)
354 intf_thread_t
*intf
= data
;
355 intf_sys_t
*sys
= intf
->p_sys
;
358 vlc_mutex_lock(&sys
->lock
);
359 idx
= vlc_array_index_of_item(&sys
->timeouts
, to
);
360 vlc_array_remove(&sys
->timeouts
, idx
);
361 vlc_mutex_unlock(&sys
->lock
);
364 static void toggle_timeout(DBusTimeout
*to
, void *data
)
366 intf_thread_t
*intf
= data
;
367 intf_sys_t
*sys
= intf
->p_sys
;
368 vlc_tick_t
*expiry
= dbus_timeout_get_data(to
);
370 vlc_mutex_lock(&sys
->lock
);
371 if (dbus_timeout_get_enabled(to
))
372 *expiry
= vlc_tick_now() + UINT64_C(1000) * dbus_timeout_get_interval(to
);
373 vlc_mutex_unlock(&sys
->lock
);
375 wakeup_main_loop(intf
);
379 * Computes the time until the next timeout expiration.
380 * @note Interface lock must be held.
381 * @return The time in milliseconds until the next expiration,
382 * or -1 if there are no pending timeouts.
384 static int next_timeout(intf_thread_t
*intf
)
386 intf_sys_t
*sys
= intf
->p_sys
;
387 vlc_tick_t next_timeout
= INT64_MAX
;
388 unsigned count
= vlc_array_count(&sys
->timeouts
);
390 for (unsigned i
= 0; i
< count
; i
++)
392 DBusTimeout
*to
= vlc_array_item_at_index(&sys
->timeouts
, i
);
394 if (!dbus_timeout_get_enabled(to
))
397 vlc_tick_t
*expiry
= dbus_timeout_get_data(to
);
399 if (next_timeout
> *expiry
)
400 next_timeout
= *expiry
;
403 if (next_timeout
>= INT64_MAX
)
406 next_timeout
/= 1000;
408 if (next_timeout
> INT_MAX
)
411 return (int)next_timeout
;
415 * Process pending D-Bus timeouts.
417 * @note Interface lock must be held.
419 static void process_timeouts(intf_thread_t
*intf
)
421 intf_sys_t
*sys
= intf
->p_sys
;
423 for (size_t i
= 0; i
< vlc_array_count(&sys
->timeouts
); i
++)
425 DBusTimeout
*to
= vlc_array_item_at_index(&sys
->timeouts
, i
);
427 if (!dbus_timeout_get_enabled(to
))
430 vlc_tick_t
*expiry
= dbus_timeout_get_data(to
);
431 if (*expiry
> vlc_tick_now())
434 expiry
+= UINT64_C(1000) * dbus_timeout_get_interval(to
);
435 vlc_mutex_unlock(&sys
->lock
);
437 dbus_timeout_handle(to
);
439 vlc_mutex_lock(&sys
->lock
);
440 i
= -1; /* lost track of state, restart from beginning */
445 static dbus_bool_t
add_watch( DBusWatch
*p_watch
, void *p_data
)
447 intf_thread_t
*p_intf
= (intf_thread_t
*) p_data
;
448 intf_sys_t
*p_sys
= (intf_sys_t
*) p_intf
->p_sys
;
450 vlc_mutex_lock( &p_sys
->lock
);
451 vlc_array_append_or_abort( &p_sys
->watches
, p_watch
);
452 vlc_mutex_unlock( &p_sys
->lock
);
457 static void remove_watch( DBusWatch
*p_watch
, void *p_data
)
459 intf_thread_t
*p_intf
= (intf_thread_t
*) p_data
;
460 intf_sys_t
*p_sys
= (intf_sys_t
*) p_intf
->p_sys
;
463 vlc_mutex_lock( &p_sys
->lock
);
464 idx
= vlc_array_index_of_item( &p_sys
->watches
, p_watch
);
465 vlc_array_remove( &p_sys
->watches
, idx
);
466 vlc_mutex_unlock( &p_sys
->lock
);
469 static void watch_toggled( DBusWatch
*p_watch
, void *p_data
)
471 intf_thread_t
*p_intf
= (intf_thread_t
*) p_data
;
473 if( dbus_watch_get_enabled( p_watch
) )
474 wakeup_main_loop( p_intf
);
478 * GetPollFds() fills an array of pollfd data structures with :
479 * - the set of enabled dbus watches
480 * - the unix pipe which we use to manually wake up the main loop
482 * This function must be called with p_sys->lock locked
484 * @return The number of file descriptors
486 * @param intf_thread_t *p_intf this interface thread's state
487 * @param struct pollfd *p_fds a pointer to a pollfd array large enough to
488 * contain all the returned data (number of enabled dbus watches + 1)
490 static int GetPollFds( intf_thread_t
*p_intf
, struct pollfd
*p_fds
)
492 intf_sys_t
*p_sys
= p_intf
->p_sys
;
493 size_t i_watches
= vlc_array_count( &p_sys
->watches
);
496 p_fds
[0].fd
= p_sys
->p_pipe_fds
[PIPE_OUT
];
497 p_fds
[0].events
= POLLIN
| POLLPRI
;
499 for( size_t i
= 0; i
< i_watches
; i
++ )
501 DBusWatch
*p_watch
= NULL
;
502 p_watch
= vlc_array_item_at_index( &p_sys
->watches
, i
);
503 if( !dbus_watch_get_enabled( p_watch
) )
506 p_fds
[i_fds
].fd
= dbus_watch_get_unix_fd( p_watch
);
507 int i_flags
= dbus_watch_get_flags( p_watch
);
509 if( i_flags
& DBUS_WATCH_READABLE
)
510 p_fds
[i_fds
].events
|= POLLIN
| POLLPRI
;
512 if( i_flags
& DBUS_WATCH_WRITABLE
)
513 p_fds
[i_fds
].events
|= POLLOUT
;
522 * ProcessEvents() reacts to a list of events originating from other VLC threads
524 * This function must be called with p_sys->lock unlocked
526 * @param intf_thread_t *p_intf This interface thread state
527 * @param callback_info_t *p_events the list of events to process
529 static void ProcessEvents( intf_thread_t
*p_intf
,
530 callback_info_t
**p_events
, int i_events
)
532 bool b_can_play
= p_intf
->p_sys
->b_can_play
;
534 vlc_dictionary_t player_properties
, tracklist_properties
, root_properties
;
535 vlc_dictionary_init( &player_properties
, 0 );
536 vlc_dictionary_init( &tracklist_properties
, 0 );
537 vlc_dictionary_init( &root_properties
, 0 );
539 for( int i
= 0; i
< i_events
; i
++ )
541 switch( p_events
[i
]->signal
)
543 case SIGNAL_ITEM_CURRENT
:
544 TrackChange( p_intf
);
546 // rate depends on current item
547 if( !vlc_dictionary_has_key( &player_properties
, "Rate" ) )
548 vlc_dictionary_insert( &player_properties
, "Rate", NULL
);
550 vlc_dictionary_insert( &player_properties
, "Metadata", NULL
);
552 case SIGNAL_PLAYLIST_ITEM_APPEND
:
553 case SIGNAL_PLAYLIST_ITEM_DELETED
:
555 playlist_t
*p_playlist
= p_intf
->p_sys
->p_playlist
;
557 b_can_play
= !playlist_IsEmpty( p_playlist
);
560 if( b_can_play
!= p_intf
->p_sys
->b_can_play
)
562 p_intf
->p_sys
->b_can_play
= b_can_play
;
563 vlc_dictionary_insert( &player_properties
, "CanPlay", NULL
);
566 if( !vlc_dictionary_has_key( &tracklist_properties
, "Tracks" ) )
567 vlc_dictionary_insert( &tracklist_properties
, "Tracks", NULL
);
570 case SIGNAL_VOLUME_MUTED
:
571 case SIGNAL_VOLUME_CHANGE
:
572 vlc_dictionary_insert( &player_properties
, "Volume", NULL
);
575 vlc_dictionary_insert( &player_properties
, "Shuffle", NULL
);
577 case SIGNAL_FULLSCREEN
:
578 vlc_dictionary_insert( &root_properties
, "Fullscreen", NULL
);
582 vlc_dictionary_insert( &player_properties
, "LoopStatus", NULL
);
585 vlc_dictionary_insert( &player_properties
, "PlaybackStatus", NULL
);
588 vlc_dictionary_insert( &player_properties
, "Rate", NULL
);
590 case SIGNAL_INPUT_METADATA
:
592 input_thread_t
*p_input
= pl_CurrentInput( p_intf
);
593 input_item_t
*p_item
;
596 p_item
= input_GetItem( p_input
);
597 vlc_object_release( p_input
);
600 vlc_dictionary_insert( &player_properties
,
605 case SIGNAL_CAN_SEEK
:
606 vlc_dictionary_insert( &player_properties
, "CanSeek", NULL
);
608 case SIGNAL_CAN_PAUSE
:
609 vlc_dictionary_insert( &player_properties
, "CanPause", NULL
);
612 SeekedEmit( p_intf
);
615 vlc_assert_unreachable();
620 if( !vlc_dictionary_is_empty( &player_properties
) )
621 PlayerPropertiesChangedEmit( p_intf
, &player_properties
);
623 if( !vlc_dictionary_is_empty( &tracklist_properties
) )
624 TrackListPropertiesChangedEmit( p_intf
, &tracklist_properties
);
626 if( !vlc_dictionary_is_empty( &root_properties
) )
627 RootPropertiesChangedEmit( p_intf
, &root_properties
);
629 vlc_dictionary_clear( &player_properties
, NULL
, NULL
);
630 vlc_dictionary_clear( &tracklist_properties
, NULL
, NULL
);
631 vlc_dictionary_clear( &root_properties
, NULL
, NULL
);
635 * ProcessWatches() handles a list of dbus watches after poll() has returned
637 * This function must be called with p_sys->lock unlocked
639 * @param intf_thread_t *p_intf This interface thread state
640 * @param DBusWatch **p_watches The list of dbus watches to process
641 * @param int i_watches The size of the p_watches array
642 * @param struct pollfd *p_fds The result of a poll() call
643 * @param int i_fds The number of file descriptors processed by poll()
645 static void ProcessWatches( intf_thread_t
*p_intf
,
646 DBusWatch
**p_watches
, int i_watches
,
647 struct pollfd
*p_fds
, int i_fds
)
651 /* Process watches */
652 for( int i
= 0; i
< i_watches
; i
++ )
654 DBusWatch
*p_watch
= p_watches
[i
];
655 if( !dbus_watch_get_enabled( p_watch
) )
658 for( int j
= 0; j
< i_fds
; j
++ )
660 if( p_fds
[j
].fd
!= dbus_watch_get_unix_fd( p_watch
) )
664 int i_revents
= p_fds
[j
].revents
;
666 if( i_revents
& POLLIN
)
667 i_flags
|= DBUS_WATCH_READABLE
;
669 if( i_revents
& POLLOUT
)
670 i_flags
|= DBUS_WATCH_WRITABLE
;
672 if( i_revents
& POLLERR
)
673 i_flags
|= DBUS_WATCH_ERROR
;
675 if( i_revents
& POLLHUP
)
676 i_flags
|= DBUS_WATCH_HANGUP
;
679 dbus_watch_handle( p_watch
, i_flags
);
685 * DispatchDBusMessages() dispatches incoming dbus messages
686 * (indirectly invoking the callbacks), then it sends outgoing
687 * messages which needs to be sent on the bus (method replies and signals)
689 * This function must be called with p_sys->lock unlocked
691 * @param intf_thread_t *p_intf This interface thread state
693 static void DispatchDBusMessages( intf_thread_t
*p_intf
)
695 DBusDispatchStatus status
;
696 intf_sys_t
*p_sys
= p_intf
->p_sys
;
698 /* Dispatch incoming messages */
699 status
= dbus_connection_get_dispatch_status( p_sys
->p_conn
);
700 while( status
!= DBUS_DISPATCH_COMPLETE
)
702 dbus_connection_dispatch( p_sys
->p_conn
);
703 status
= dbus_connection_get_dispatch_status( p_sys
->p_conn
);
706 /* Send outgoing data */
707 if( dbus_connection_has_messages_to_send( p_sys
->p_conn
) )
708 dbus_connection_flush( p_sys
->p_conn
);
712 * MPRISEntryPoint() routes incoming messages to their respective interface
715 * This function is called during dbus_connection_dispatch()
717 static DBusHandlerResult
718 MPRISEntryPoint ( DBusConnection
*p_conn
, DBusMessage
*p_from
, void *p_this
)
720 const char *psz_target_interface
;
721 const char *psz_interface
= dbus_message_get_interface( p_from
);
722 const char *psz_method
= dbus_message_get_member( p_from
);
726 if( psz_interface
&& strcmp( psz_interface
, DBUS_INTERFACE_PROPERTIES
) )
727 psz_target_interface
= psz_interface
;
731 dbus_error_init( &error
);
732 dbus_message_get_args( p_from
, &error
,
733 DBUS_TYPE_STRING
, &psz_target_interface
,
736 if( dbus_error_is_set( &error
) )
738 msg_Err( (vlc_object_t
*) p_this
, "D-Bus error on %s.%s: %s",
739 psz_interface
, psz_method
,
741 dbus_error_free( &error
);
742 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
746 if( !strcmp( psz_target_interface
, DBUS_INTERFACE_INTROSPECTABLE
) )
747 return handle_introspect( p_conn
, p_from
, p_this
);
749 if( !strcmp( psz_target_interface
, DBUS_MPRIS_ROOT_INTERFACE
) )
750 return handle_root( p_conn
, p_from
, p_this
);
752 if( !strcmp( psz_target_interface
, DBUS_MPRIS_PLAYER_INTERFACE
) )
753 return handle_player( p_conn
, p_from
, p_this
);
755 if( !strcmp( psz_target_interface
, DBUS_MPRIS_TRACKLIST_INTERFACE
) )
756 return handle_tracklist( p_conn
, p_from
, p_this
);
758 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
761 /*****************************************************************************
763 *****************************************************************************/
765 static void *Run( void *data
)
767 intf_thread_t
*p_intf
= data
;
768 intf_sys_t
*p_sys
= p_intf
->p_sys
;
770 int canc
= vlc_savecancel();
772 vlc_tick_t events_last_date
= VLC_TICK_INVALID
;
773 int events_poll_timeout
= -1;
776 vlc_mutex_lock( &p_sys
->lock
);
778 size_t i_watches
= vlc_array_count( &p_sys
->watches
);
779 struct pollfd fds
[i_watches
];
780 memset(fds
, 0, sizeof fds
);
782 int i_fds
= GetPollFds( p_intf
, fds
);
783 int timeout
= next_timeout(p_intf
);
785 vlc_mutex_unlock( &p_sys
->lock
);
787 /* thread cancellation is allowed while the main loop sleeps */
788 vlc_restorecancel( canc
);
790 timeout
= events_poll_timeout
;
792 while (poll(fds
, i_fds
, timeout
) == -1)
798 canc
= vlc_savecancel();
800 /* Was the main loop woken up manually ? */
801 if (fds
[0].revents
& POLLIN
)
804 (void)read( fds
[0].fd
, &buf
, 1 );
807 /* We need to lock the mutex while building lists of events,
808 * timeouts and watches to process but we can't keep the lock while
809 * processing them, or else we risk a deadlock:
811 * The signal functions could lock mutex X while p_events is locked;
812 * While some other function in vlc (playlist) might lock mutex X
813 * and then set a variable which would call AllCallback(), which itself
814 * needs to lock p_events to add a new event.
816 vlc_mutex_lock( &p_intf
->p_sys
->lock
);
818 process_timeouts(p_intf
);
820 /* Get the list of watches to process */
821 i_watches
= vlc_array_count( &p_sys
->watches
);
822 DBusWatch
*p_watches
[i_watches
? i_watches
: 1];
823 for( size_t i
= 0; i
< i_watches
; i
++ )
825 p_watches
[i
] = vlc_array_item_at_index( &p_sys
->watches
, i
);
828 /* Get the list of events to process */
829 size_t i_events
= vlc_array_count( &p_sys
->events
);
830 callback_info_t
** pp_info
= NULL
;
834 vlc_tick_t now
= vlc_tick_now();
835 if( events_last_date
== VLC_TICK_INVALID
836 || now
- events_last_date
> EVENTS_DELAY
)
838 /* Send events every EVENTS_DELAY */
839 events_last_date
= now
;
840 events_poll_timeout
= -1;
842 pp_info
= vlc_alloc( i_events
, sizeof(*pp_info
) );
845 for( size_t i
= 0; i
< i_events
; i
++ )
846 pp_info
[i
] = vlc_array_item_at_index( &p_sys
->events
, i
);
847 vlc_array_clear( &p_sys
->events
);
850 else if( events_poll_timeout
== -1 )
852 /* Request poll to wake up in order to send these events after
854 events_poll_timeout
= ( EVENTS_DELAY
- ( now
- events_last_date
) ) / 1000;
857 else /* No events: clear timeout */
858 events_poll_timeout
= -1;
860 /* now we can release the lock and process what's pending */
861 vlc_mutex_unlock( &p_intf
->p_sys
->lock
);
865 ProcessEvents( p_intf
, pp_info
, i_events
);
868 ProcessWatches( p_intf
, p_watches
, i_watches
, fds
, i_fds
);
870 DispatchDBusMessages( p_intf
);
873 vlc_restorecancel(canc
);
877 static void wakeup_main_loop( void *p_data
)
879 intf_thread_t
*p_intf
= (intf_thread_t
*) p_data
;
881 if( !write( p_intf
->p_sys
->p_pipe_fds
[PIPE_IN
], "\0", 1 ) )
882 msg_Err( p_intf
, "Could not wake up the main loop: %s",
883 vlc_strerror_c(errno
) );
886 static bool add_event_locked( intf_thread_t
*p_intf
, callback_info_t
*p_info
)
888 if( !p_info
->signal
)
894 for( size_t i
= 0; i
< vlc_array_count( &p_intf
->p_sys
->events
); ++ i
)
896 callback_info_t
*oldinfo
=
897 vlc_array_item_at_index( &p_intf
->p_sys
->events
, i
);
898 if( p_info
->signal
== oldinfo
->signal
)
905 vlc_array_append( &p_intf
->p_sys
->events
, p_info
);
909 /* Flls a callback_info_t data structure in response
910 * to an "intf-event" input event.
912 * @warning This function executes in the input thread.
914 * @return VLC_SUCCESS on success, VLC_E* on error.
916 static int InputCallback( vlc_object_t
*p_this
, const char *psz_var
,
917 vlc_value_t oldval
, vlc_value_t newval
, void *data
)
919 input_thread_t
*p_input
= (input_thread_t
*)p_this
;
920 intf_thread_t
*p_intf
= data
;
921 intf_sys_t
*p_sys
= p_intf
->p_sys
;
923 dbus_int32_t i_state
= PLAYBACK_STATE_INVALID
;
925 callback_info_t
*p_info
= calloc( 1, sizeof( callback_info_t
) );
926 if( unlikely(p_info
== NULL
) )
929 switch( newval
.i_int
)
931 case INPUT_EVENT_DEAD
:
932 i_state
= PLAYBACK_STATE_STOPPED
;
934 case INPUT_EVENT_STATE
:
935 switch( var_GetInteger( p_input
, "state" ) )
939 i_state
= PLAYBACK_STATE_PLAYING
;
942 i_state
= PLAYBACK_STATE_PAUSED
;
945 i_state
= PLAYBACK_STATE_STOPPED
;
948 case INPUT_EVENT_ITEM_META
:
949 p_info
->signal
= SIGNAL_INPUT_METADATA
;
951 case INPUT_EVENT_RATE
:
952 p_info
->signal
= SIGNAL_RATE
;
954 case INPUT_EVENT_POSITION
:
956 vlc_tick_t i_now
= vlc_tick_now(), i_pos
, i_projected_pos
, i_interval
;
957 float f_current_rate
;
960 * XXX: This is way more convoluted than it should be... */
961 i_pos
= var_GetInteger( p_input
, "time" );
963 if( !p_intf
->p_sys
->i_last_input_pos_event
||
964 !( var_GetInteger( p_input
, "state" ) == PLAYING_S
) )
966 p_intf
->p_sys
->i_last_input_pos_event
= i_now
;
967 p_intf
->p_sys
->i_last_input_pos
= i_pos
;
971 f_current_rate
= var_GetFloat( p_input
, "rate" );
972 i_interval
= ( i_now
- p_intf
->p_sys
->i_last_input_pos_event
);
974 i_projected_pos
= p_intf
->p_sys
->i_last_input_pos
+
975 ( i_interval
* f_current_rate
);
977 p_intf
->p_sys
->i_last_input_pos_event
= i_now
;
978 p_intf
->p_sys
->i_last_input_pos
= i_pos
;
980 if( llabs( i_pos
- i_projected_pos
) < SEEK_THRESHOLD
)
983 p_info
->signal
= SIGNAL_SEEK
;
988 return VLC_SUCCESS
; /* don't care */
991 vlc_mutex_lock( &p_sys
->lock
);
992 if( i_state
!= PLAYBACK_STATE_INVALID
&&
993 i_state
!= p_sys
->i_playing_state
)
995 p_sys
->i_playing_state
= i_state
;
996 p_info
->signal
= SIGNAL_STATE
;
998 bool added
= add_event_locked( p_intf
, p_info
);
999 vlc_mutex_unlock( &p_intf
->p_sys
->lock
);
1002 wakeup_main_loop( p_intf
);
1009 // Get all the callbacks
1010 static int AllCallback( vlc_object_t
*p_this
, const char *psz_var
,
1011 vlc_value_t oldval
, vlc_value_t newval
, void *p_data
)
1013 intf_thread_t
*p_intf
= p_data
;
1014 callback_info_t info
= { .signal
= SIGNAL_NONE
};
1016 // Wich event is it ?
1017 if( !strcmp( "input-current", psz_var
) )
1018 info
.signal
= SIGNAL_ITEM_CURRENT
;
1019 else if( !strcmp( "volume", psz_var
) )
1021 if( oldval
.f_float
!= newval
.f_float
)
1022 info
.signal
= SIGNAL_VOLUME_CHANGE
;
1024 else if( !strcmp( "mute", psz_var
) )
1026 if( oldval
.b_bool
!= newval
.b_bool
)
1027 info
.signal
= SIGNAL_VOLUME_MUTED
;
1029 else if( !strcmp( "playlist-item-append", psz_var
) )
1030 info
.signal
= SIGNAL_PLAYLIST_ITEM_APPEND
;
1031 else if( !strcmp( "playlist-item-deleted", psz_var
) )
1032 info
.signal
= SIGNAL_PLAYLIST_ITEM_DELETED
;
1033 else if( !strcmp( "random", psz_var
) )
1034 info
.signal
= SIGNAL_RANDOM
;
1035 else if( !strcmp( "fullscreen", psz_var
) )
1036 info
.signal
= SIGNAL_FULLSCREEN
;
1037 else if( !strcmp( "repeat", psz_var
) )
1038 info
.signal
= SIGNAL_REPEAT
;
1039 else if( !strcmp( "loop", psz_var
) )
1040 info
.signal
= SIGNAL_LOOP
;
1041 else if( !strcmp( "can-seek", psz_var
) )
1042 info
.signal
= SIGNAL_CAN_SEEK
;
1043 else if( !strcmp( "can-pause", psz_var
) )
1044 info
.signal
= SIGNAL_CAN_PAUSE
;
1046 vlc_assert_unreachable();
1048 if( info
.signal
== SIGNAL_NONE
)
1051 callback_info_t
*p_info
= malloc( sizeof( *p_info
) );
1052 if( unlikely(p_info
== NULL
) )
1057 vlc_mutex_lock( &p_intf
->p_sys
->lock
);
1058 bool added
= add_event_locked( p_intf
, p_info
);
1059 vlc_mutex_unlock( &p_intf
->p_sys
->lock
);
1062 wakeup_main_loop( p_intf
);
1068 /*****************************************************************************
1069 * TrackChange: callback on playlist "input-current"
1070 *****************************************************************************/
1071 static int TrackChange( intf_thread_t
*p_intf
)
1073 intf_sys_t
*p_sys
= p_intf
->p_sys
;
1074 input_thread_t
*p_input
= NULL
;
1075 input_item_t
*p_item
= NULL
;
1077 if( p_intf
->p_sys
->b_dead
)
1080 if( p_sys
->p_input
)
1082 var_DelCallback( p_sys
->p_input
, "intf-event", InputCallback
, p_intf
);
1083 var_DelCallback( p_sys
->p_input
, "can-pause", AllCallback
, p_intf
);
1084 var_DelCallback( p_sys
->p_input
, "can-seek", AllCallback
, p_intf
);
1085 vlc_object_release( p_sys
->p_input
);
1086 p_sys
->p_input
= NULL
;
1089 p_sys
->b_meta_read
= false;
1091 p_input
= pl_CurrentInput( p_intf
);
1097 p_item
= input_GetItem( p_input
);
1100 vlc_object_release( p_input
);
1101 return VLC_EGENERIC
;
1104 if( input_item_IsPreparsed( p_item
) )
1105 p_sys
->b_meta_read
= true;
1107 p_sys
->p_input
= p_input
;
1108 var_AddCallback( p_input
, "intf-event", InputCallback
, p_intf
);
1109 var_AddCallback( p_input
, "can-pause", AllCallback
, p_intf
);
1110 var_AddCallback( p_input
, "can-seek", AllCallback
, p_intf
);
1116 * DemarshalSetPropertyValue() extracts the new property value from a
1117 * org.freedesktop.DBus.Properties.Set method call message.
1119 * @return int VLC_SUCCESS on success
1120 * @param DBusMessage *p_msg a org.freedesktop.DBus.Properties.Set method call
1121 * @param void *p_arg placeholder for the demarshalled value
1123 int DemarshalSetPropertyValue( DBusMessage
*p_msg
, void *p_arg
)
1126 bool b_valid_input
= FALSE
;
1127 DBusMessageIter in_args
, variant
;
1128 dbus_message_iter_init( p_msg
, &in_args
);
1132 i_type
= dbus_message_iter_get_arg_type( &in_args
);
1133 if( DBUS_TYPE_VARIANT
== i_type
)
1135 dbus_message_iter_recurse( &in_args
, &variant
);
1136 dbus_message_iter_get_basic( &variant
, p_arg
);
1137 b_valid_input
= TRUE
;
1139 } while( dbus_message_iter_next( &in_args
) );
1141 return b_valid_input
? VLC_SUCCESS
: VLC_EGENERIC
;
1144 /*****************************************************************************
1145 * GetInputMeta: Fill a DBusMessage with the given input item metadata
1146 *****************************************************************************/
1148 #define ADD_META( entry, type, data ) \
1150 dbus_message_iter_open_container( &dict, DBUS_TYPE_DICT_ENTRY, \
1151 NULL, &dict_entry ); \
1152 dbus_message_iter_append_basic( &dict_entry, DBUS_TYPE_STRING, \
1153 &ppsz_meta_items[entry] ); \
1154 dbus_message_iter_open_container( &dict_entry, DBUS_TYPE_VARIANT, \
1155 type##_AS_STRING, &variant ); \
1156 dbus_message_iter_append_basic( &variant, \
1159 dbus_message_iter_close_container( &dict_entry, &variant ); \
1160 dbus_message_iter_close_container( &dict, &dict_entry ); }
1162 #define ADD_VLC_META_STRING( entry, item ) \
1164 char * psz = input_item_Get##item( p_input );\
1165 ADD_META( entry, DBUS_TYPE_STRING, \
1170 #define ADD_META_SINGLETON_STRING_LIST( entry, item ) \
1172 char * psz = input_item_Get##item( p_input );\
1174 dbus_message_iter_open_container( &dict, DBUS_TYPE_DICT_ENTRY, \
1175 NULL, &dict_entry ); \
1176 dbus_message_iter_append_basic( &dict_entry, DBUS_TYPE_STRING, \
1177 &ppsz_meta_items[entry] ); \
1178 dbus_message_iter_open_container( &dict_entry, DBUS_TYPE_VARIANT, \
1180 dbus_message_iter_open_container( &variant, DBUS_TYPE_ARRAY, "s", \
1182 dbus_message_iter_append_basic( &list, \
1185 dbus_message_iter_close_container( &variant, &list ); \
1186 dbus_message_iter_close_container( &dict_entry, &variant ); \
1187 dbus_message_iter_close_container( &dict, &dict_entry ); \
1192 int GetInputMeta( playlist_item_t
*item
, DBusMessageIter
*args
)
1194 input_item_t
*p_input
= item
->p_input
;
1195 DBusMessageIter dict
, dict_entry
, variant
, list
;
1196 /** The duration of the track can be expressed in second, milli-seconds and
1198 dbus_int64_t i_mtime
= input_item_GetDuration( p_input
);
1199 dbus_uint32_t i_time
= i_mtime
/ 1000000;
1200 dbus_int64_t i_length
= i_mtime
/ 1000;
1203 if( -1 == asprintf( &psz_trackid
, MPRIS_TRACKID_FORMAT
, item
->i_id
) )
1206 const char* ppsz_meta_items
[] =
1208 "mpris:trackid", "xesam:url", "xesam:title", "xesam:artist",
1209 "xesam:album", "xesam:tracknumber", "vlc:time", "mpris:length",
1210 "xesam:genre", "xesam:userRating", "xesam:contentCreated",
1211 "mpris:artUrl", "mb:trackId", "vlc:audio-bitrate",
1212 "vlc:audio-samplerate", "vlc:video-bitrate", "vlc:audio-codec",
1213 "vlc:copyright", "xesam:comment", "vlc:encodedby", "language",
1214 "vlc:length", "vlc:nowplaying", "vlc:publisher", "vlc:setting",
1215 "status", "vlc:url", "vlc:video-codec"
1218 dbus_message_iter_open_container( args
, DBUS_TYPE_ARRAY
, "{sv}", &dict
);
1220 ADD_META( 0, DBUS_TYPE_OBJECT_PATH
, psz_trackid
);
1221 ADD_VLC_META_STRING( 1, URI
);
1222 ADD_VLC_META_STRING( 2, Title
);
1223 ADD_META_SINGLETON_STRING_LIST( 3, Artist
);
1224 ADD_VLC_META_STRING( 4, Album
);
1225 ADD_VLC_META_STRING( 5, TrackNum
);
1226 ADD_META( 6, DBUS_TYPE_UINT32
, i_time
);
1227 ADD_META( 7, DBUS_TYPE_INT64
, i_mtime
);
1228 ADD_META_SINGLETON_STRING_LIST( 8, Genre
);
1229 //ADD_META( 9, DBUS_TYPE_DOUBLE, rating );
1230 ADD_VLC_META_STRING( 10, Date
); // this is supposed to be in ISO 8601 extended format
1231 ADD_VLC_META_STRING( 11, ArtURL
);
1232 ADD_VLC_META_STRING( 12, TrackID
);
1234 ADD_VLC_META_STRING( 17, Copyright
);
1235 ADD_META_SINGLETON_STRING_LIST( 18, Description
);
1236 ADD_VLC_META_STRING( 19, EncodedBy
);
1237 ADD_VLC_META_STRING( 20, Language
);
1238 ADD_META( 21, DBUS_TYPE_INT64
, i_length
);
1239 ADD_VLC_META_STRING( 22, NowPlaying
);
1240 ADD_VLC_META_STRING( 23, Publisher
);
1241 ADD_VLC_META_STRING( 24, Setting
);
1242 ADD_VLC_META_STRING( 25, URL
);
1244 free( psz_trackid
);
1246 vlc_mutex_lock( &p_input
->lock
);
1247 if( p_input
->p_meta
)
1249 int i_status
= vlc_meta_GetStatus( p_input
->p_meta
);
1250 ADD_META( 23, DBUS_TYPE_INT32
, i_status
);
1252 vlc_mutex_unlock( &p_input
->lock
);
1254 dbus_message_iter_close_container( args
, &dict
);
1258 int AddProperty( intf_thread_t
*p_intf
,
1259 DBusMessageIter
*p_container
,
1260 const char* psz_property_name
,
1261 const char* psz_signature
,
1262 int (*pf_marshaller
) (intf_thread_t
*, DBusMessageIter
*) )
1264 DBusMessageIter entry
, v
;
1266 if( !dbus_message_iter_open_container( p_container
,
1267 DBUS_TYPE_DICT_ENTRY
, NULL
,
1271 if( !dbus_message_iter_append_basic( &entry
,
1273 &psz_property_name
) )
1276 if( !dbus_message_iter_open_container( &entry
,
1277 DBUS_TYPE_VARIANT
, psz_signature
,
1281 if( VLC_SUCCESS
!= pf_marshaller( p_intf
, &v
) )
1284 if( !dbus_message_iter_close_container( &entry
, &v
) )
1287 if( !dbus_message_iter_close_container( p_container
, &entry
) )
1294 #undef ADD_VLC_META_STRING