demux: ty: fix all warnings
[vlc.git] / modules / control / dbus / dbus.c
blob7582e8df35e45f686418f0ebaf73f6abf109c1cf
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
8 * $Id$
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
34 * extract:
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 /*****************************************************************************
42 * Preamble
43 *****************************************************************************/
45 #ifdef HAVE_CONFIG_H
46 # include "config.h"
47 #endif
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>
62 #include <vlc_meta.h>
63 #include <vlc_tick.h>
64 #include <vlc_fs.h>
66 #include <assert.h>
67 #include <limits.h>
68 #include <string.h>
70 #include <poll.h>
71 #include <errno.h>
72 #include <unistd.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 /*****************************************************************************
81 * Local prototypes.
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
92 typedef struct
94 int signal;
95 } callback_info_t;
97 enum
99 PIPE_OUT = 0,
100 PIPE_IN = 1
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,
123 int i_events );
125 static void ProcessWatches ( intf_thread_t *p_intf,
126 DBusWatch **p_watches,
127 int i_watches,
128 struct pollfd *p_fds,
129 int i_fds );
131 static void DispatchDBusMessages( intf_thread_t *p_intf );
133 /*****************************************************************************
134 * Module descriptor
135 *****************************************************************************/
136 vlc_module_begin ()
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 )
142 vlc_module_end ()
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() )
154 return VLC_EGENERIC;
156 intf_sys_t *p_sys = calloc( 1, sizeof( intf_sys_t ) );
157 if( unlikely(!p_sys) )
158 return VLC_ENOMEM;
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 ) )
167 free( p_sys );
168 msg_Err( p_intf, "Could not create pipe" );
169 return VLC_EGENERIC;
172 DBusError error;
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 );
179 if( !p_conn )
181 msg_Err( p_this, "Failed to connect to the D-Bus session daemon: %s",
182 error.message );
183 dbus_error_free( &error );
184 vlc_close( p_sys->p_pipe_fds[1] );
185 vlc_close( p_sys->p_pipe_fds[0] );
186 free( p_sys );
187 return VLC_EGENERIC;
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",
217 unique_service);
220 else
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,
249 add_timeout,
250 remove_timeout,
251 toggle_timeout,
252 p_intf, NULL ) )
253 goto error;
255 if( !dbus_connection_set_watch_functions( p_conn,
256 add_watch,
257 remove_watch,
258 watch_toggled,
259 p_intf, NULL ) )
260 goto error;
262 if( vlc_clone( &p_sys->thread, Run, p_intf, VLC_THREAD_PRIORITY_LOW ) )
263 goto error;
265 return VLC_SUCCESS;
267 error:
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] );
279 free( p_sys );
280 return VLC_ENOMEM;
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 );
306 if( p_sys->p_input )
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
315 * for closing it */
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 );
323 free( info );
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] );
331 free( p_sys );
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))
341 return FALSE;
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);
349 return TRUE;
352 static void remove_timeout(DBusTimeout *to, void *data)
354 intf_thread_t *intf = data;
355 intf_sys_t *sys = intf->p_sys;
356 size_t idx;
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))
395 continue;
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)
404 return -1;
406 next_timeout /= 1000;
408 if (next_timeout > INT_MAX)
409 return 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))
428 continue;
430 vlc_tick_t *expiry = dbus_timeout_get_data(to);
431 if (*expiry > vlc_tick_now())
432 continue;
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 );
454 return TRUE;
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;
461 size_t idx;
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 );
494 int i_fds = 1;
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 ) )
504 continue;
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;
515 i_fds++;
518 return i_fds;
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 );
551 break;
552 case SIGNAL_PLAYLIST_ITEM_APPEND:
553 case SIGNAL_PLAYLIST_ITEM_DELETED:
555 playlist_t *p_playlist = p_intf->p_sys->p_playlist;
556 PL_LOCK;
557 b_can_play = !playlist_IsEmpty( p_playlist );
558 PL_UNLOCK;
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 );
568 break;
570 case SIGNAL_VOLUME_MUTED:
571 case SIGNAL_VOLUME_CHANGE:
572 vlc_dictionary_insert( &player_properties, "Volume", NULL );
573 break;
574 case SIGNAL_RANDOM:
575 vlc_dictionary_insert( &player_properties, "Shuffle", NULL );
576 break;
577 case SIGNAL_FULLSCREEN:
578 vlc_dictionary_insert( &root_properties, "Fullscreen", NULL );
579 break;
580 case SIGNAL_REPEAT:
581 case SIGNAL_LOOP:
582 vlc_dictionary_insert( &player_properties, "LoopStatus", NULL );
583 break;
584 case SIGNAL_STATE:
585 vlc_dictionary_insert( &player_properties, "PlaybackStatus", NULL );
586 break;
587 case SIGNAL_RATE:
588 vlc_dictionary_insert( &player_properties, "Rate", NULL );
589 break;
590 case SIGNAL_INPUT_METADATA:
592 input_thread_t *p_input = pl_CurrentInput( p_intf );
593 input_item_t *p_item;
594 if( p_input )
596 p_item = input_GetItem( p_input );
597 vlc_object_release( p_input );
599 if( p_item )
600 vlc_dictionary_insert( &player_properties,
601 "Metadata", NULL );
603 break;
605 case SIGNAL_CAN_SEEK:
606 vlc_dictionary_insert( &player_properties, "CanSeek", NULL );
607 break;
608 case SIGNAL_CAN_PAUSE:
609 vlc_dictionary_insert( &player_properties, "CanPause", NULL );
610 break;
611 case SIGNAL_SEEK:
612 SeekedEmit( p_intf );
613 break;
614 default:
615 vlc_assert_unreachable();
617 free( p_events[i] );
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 )
649 VLC_UNUSED(p_intf);
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 ) )
656 continue;
658 for( int j = 0; j < i_fds; j++ )
660 if( p_fds[j].fd != dbus_watch_get_unix_fd( p_watch ) )
661 continue;
663 int i_flags = 0;
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;
678 if( i_flags )
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
713 * implementation.
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 );
724 DBusError error;
726 if( psz_interface && strcmp( psz_interface, DBUS_INTERFACE_PROPERTIES ) )
727 psz_target_interface = psz_interface;
729 else
731 dbus_error_init( &error );
732 dbus_message_get_args( p_from, &error,
733 DBUS_TYPE_STRING, &psz_target_interface,
734 DBUS_TYPE_INVALID );
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,
740 error.message );
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 /*****************************************************************************
762 * Run: main loop
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;
774 for( ;; )
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 );
789 if( timeout == -1 )
790 timeout = events_poll_timeout;
792 while (poll(fds, i_fds, timeout) == -1)
794 if (errno != EINTR)
795 goto error;
798 canc = vlc_savecancel();
800 /* Was the main loop woken up manually ? */
801 if (fds[0].revents & POLLIN)
803 char buf;
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;
832 if( i_events > 0 )
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) );
843 if( 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
853 * some delay */
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 );
863 if( pp_info )
865 ProcessEvents( p_intf, pp_info, i_events );
866 free( pp_info );
868 ProcessWatches( p_intf, p_watches, i_watches, fds, i_fds );
870 DispatchDBusMessages( p_intf );
872 error:
873 vlc_restorecancel(canc);
874 return NULL;
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 )
890 free( p_info );
891 return false;
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 )
900 free( p_info );
901 return false;
905 vlc_array_append( &p_intf->p_sys->events, p_info );
906 return true;
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) )
927 return VLC_ENOMEM;
929 switch( newval.i_int )
931 case INPUT_EVENT_DEAD:
932 i_state = PLAYBACK_STATE_STOPPED;
933 break;
934 case INPUT_EVENT_STATE:
935 switch( var_GetInteger( p_input, "state" ) )
937 case OPENING_S:
938 case PLAYING_S:
939 i_state = PLAYBACK_STATE_PLAYING;
940 break;
941 case PAUSE_S:
942 i_state = PLAYBACK_STATE_PAUSED;
943 break;
944 default:
945 i_state = PLAYBACK_STATE_STOPPED;
947 break;
948 case INPUT_EVENT_ITEM_META:
949 p_info->signal = SIGNAL_INPUT_METADATA;
950 break;
951 case INPUT_EVENT_RATE:
952 p_info->signal = SIGNAL_RATE;
953 break;
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;
959 /* Detect seeks
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;
968 break;
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 )
981 break;
983 p_info->signal = SIGNAL_SEEK;
984 break;
986 default:
987 free( p_info );
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 );
1001 if( added )
1002 wakeup_main_loop( p_intf );
1004 (void)psz_var;
1005 (void)oldval;
1006 return VLC_SUCCESS;
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;
1045 else
1046 vlc_assert_unreachable();
1048 if( info.signal == SIGNAL_NONE )
1049 return VLC_SUCCESS;
1051 callback_info_t *p_info = malloc( sizeof( *p_info ) );
1052 if( unlikely(p_info == NULL) )
1053 return VLC_ENOMEM;
1055 // Append the event
1056 *p_info = info;
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 );
1061 if( added )
1062 wakeup_main_loop( p_intf );
1064 (void) p_this;
1065 return VLC_SUCCESS;
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 )
1078 return VLC_SUCCESS;
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 );
1092 if( !p_input )
1094 return VLC_SUCCESS;
1097 p_item = input_GetItem( p_input );
1098 if( !p_item )
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 );
1112 return VLC_SUCCESS;
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 )
1125 int i_type;
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 ) \
1149 if( 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, \
1157 type, \
1158 & data ); \
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, \
1166 psz ); \
1167 free( psz ); \
1170 #define ADD_META_SINGLETON_STRING_LIST( entry, item ) \
1172 char * psz = input_item_Get##item( p_input );\
1173 if( psz ) { \
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, \
1179 "as", &variant ); \
1180 dbus_message_iter_open_container( &variant, DBUS_TYPE_ARRAY, "s", \
1181 &list ); \
1182 dbus_message_iter_append_basic( &list, \
1183 DBUS_TYPE_STRING, \
1184 &psz ); \
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 ); \
1189 free( psz ); \
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
1197 µ-seconds */
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;
1201 char *psz_trackid;
1203 if( -1 == asprintf( &psz_trackid, MPRIS_TRACKID_FORMAT, item->i_id ) )
1204 return VLC_ENOMEM;
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 );
1255 return VLC_SUCCESS;
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,
1268 &entry ) )
1269 return VLC_ENOMEM;
1271 if( !dbus_message_iter_append_basic( &entry,
1272 DBUS_TYPE_STRING,
1273 &psz_property_name ) )
1274 return VLC_ENOMEM;
1276 if( !dbus_message_iter_open_container( &entry,
1277 DBUS_TYPE_VARIANT, psz_signature,
1278 &v ) )
1279 return VLC_ENOMEM;
1281 if( VLC_SUCCESS != pf_marshaller( p_intf, &v ) )
1282 return VLC_ENOMEM;
1284 if( !dbus_message_iter_close_container( &entry, &v) )
1285 return VLC_ENOMEM;
1287 if( !dbus_message_iter_close_container( p_container, &entry ) )
1288 return VLC_ENOMEM;
1290 return VLC_SUCCESS;
1293 #undef ADD_META
1294 #undef ADD_VLC_META_STRING