You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1494 lines
50 KiB

/*****************************************************************************
* dbus.c : D-Bus control interface
*****************************************************************************
* Copyright © 2006-2008 Rafaël Carré
* Copyright © 2007-2012 Mirsal Ennaime
* Copyright © 2009-2012 The VideoLAN team
* Copyright © 2013 Alex Merry
*
* Authors: Rafaël Carré <funman at videolanorg>
* Mirsal Ennaime <mirsal at mirsal fr>
* Alex Merry <dev at randomguy3 me uk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/*
* D-Bus Specification:
* http://dbus.freedesktop.org/doc/dbus-specification.html
* D-Bus low-level C API (libdbus)
* http://dbus.freedesktop.org/doc/dbus/api/html/index.html
* extract:
* "If you use this low-level API directly, you're signing up for some pain."
*
* MPRIS Specification version 1.0
* http://wiki.xmms2.xmms.se/index.php/MPRIS
*/
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <dbus/dbus.h>
#include "dbus_common.h"
#include "dbus_root.h"
#include "dbus_player.h"
#include "dbus_tracklist.h"
#include "dbus_introspect.h"
#define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
#include <vlc_common.h>
#include <vlc_arrays.h>
#include <vlc_threads.h>
#include <vlc_plugin.h>
#include <vlc_interface.h>
#include <vlc_meta.h>
#include <vlc_tick.h>
#include <vlc_fs.h>
#include <assert.h>
#include <limits.h>
#include <string.h>
#include <poll.h>
#include <errno.h>
#include <unistd.h>
#define DBUS_MPRIS_BUS_NAME "org.mpris.MediaPlayer2.vlc"
#define DBUS_INSTANCE_ID_PREFIX "instance"
#define SEEK_THRESHOLD 1000 /* µsec */
#define EVENTS_DELAY INT64_C(100000) /* 100 ms */
/*****************************************************************************
* Local prototypes.
*****************************************************************************/
static DBusHandlerResult
MPRISEntryPoint ( DBusConnection *p_conn, DBusMessage *p_from, void *p_this );
static const DBusObjectPathVTable dbus_mpris_vtable = {
NULL, MPRISEntryPoint, /* handler function */
NULL, NULL, NULL, NULL
};
typedef struct
{
int signal;
union {
tracklist_append_event_t *items_appended;
tracklist_remove_event_t *items_removed;
};
} callback_info_t;
enum
{
PIPE_OUT = 0,
PIPE_IN = 1
};
static int Open ( vlc_object_t * );
static void Close ( vlc_object_t * );
static void *Run ( void * );
static int TrackChange( intf_thread_t * );
static dbus_bool_t add_timeout(DBusTimeout *, void *);
static void remove_timeout(DBusTimeout *, void *);
static void toggle_timeout(DBusTimeout *, void *);
static dbus_bool_t add_watch ( DBusWatch *p_watch, void *p_data );
static void remove_watch ( DBusWatch *p_watch, void *p_data );
static void watch_toggled ( DBusWatch *p_watch, void *p_data );
static void wakeup_main_loop( void *p_data );
static void ProcessEvents ( intf_thread_t *p_intf,
callback_info_t **p_events,
int i_events );
static void ProcessWatches ( intf_thread_t *p_intf,
DBusWatch **p_watches,
int i_watches,
struct pollfd *p_fds,
int i_fds );
static void DispatchDBusMessages( intf_thread_t *p_intf );
static void playlist_on_items_added(vlc_playlist_t *,
size_t, vlc_playlist_item_t *const [],
size_t, void *);
static void playlist_on_items_removed(vlc_playlist_t *,
size_t, size_t, void *);
static void playlist_on_playback_repeat_changed(vlc_playlist_t *,
enum vlc_playlist_playback_repeat,
void *);
static void playlist_on_playback_order_changed(vlc_playlist_t *,
enum vlc_playlist_playback_order,
void *);
static void playlist_on_current_index_changed(vlc_playlist_t *,
ssize_t, void *);
static void playlist_on_has_prev_changed(vlc_playlist_t *playlist,
bool has_prev, void *data);
static void playlist_on_has_next_changed(vlc_playlist_t *playlist,
bool has_next, void *data);
static void player_on_state_changed(vlc_player_t *,
enum vlc_player_state, void *);
static void player_on_error_changed(vlc_player_t *,
enum vlc_player_error, void *);
static void player_on_rate_changed(vlc_player_t *, float, void *);
static void player_on_capabilities_changed(vlc_player_t *, int, int, void *);
static void player_on_media_meta_changed(vlc_player_t *,
input_item_t *, void *);
static void player_aout_on_volume_changed(audio_output_t *, float, void *);
static void player_aout_on_mute_changed(audio_output_t *, bool, void *);
static void player_vout_on_fullscreen_changed(vout_thread_t *, bool, void *);
static void player_timer_on_discontinuity(vlc_tick_t system_dae, void *data);
static void player_timer_on_update(const struct vlc_player_timer_point *, void *);
/*****************************************************************************
* Module descriptor
*****************************************************************************/
vlc_module_begin ()
set_shortname( N_("DBus"))
set_description( N_("D-Bus control interface") )
set_capability( "interface", 0 )
set_callbacks( Open, Close )
vlc_module_end ()
/*****************************************************************************
* Open: initialize interface
*****************************************************************************/
static int Open( vlc_object_t *p_this )
{
intf_thread_t *p_intf = (intf_thread_t*)p_this;
vlc_object_t *vlc = VLC_OBJECT(vlc_object_instance(p_this));
vlc_playlist_t *playlist = NULL;
/* initialisation of the connection */
if( !dbus_threads_init_default() )
return VLC_EGENERIC;
intf_sys_t *p_sys = calloc( 1, sizeof( intf_sys_t ) );
if( unlikely(!p_sys) )
return VLC_ENOMEM;
DBusConnection *p_conn;
p_sys->i_player_caps = PLAYER_CAPS_NONE;
p_sys->i_playing_state = PLAYBACK_STATE_INVALID;
if( vlc_pipe( p_sys->p_pipe_fds ) )
{
free( p_sys );
msg_Err( p_intf, "Could not create pipe" );
return VLC_EGENERIC;
}
DBusError error;
dbus_error_init( &error );
/* connect privately to the session bus
* the connection will not be shared with other vlc modules which use dbus,
* thus avoiding a whole class of concurrency issues */
p_conn = dbus_bus_get_private( DBUS_BUS_SESSION, &error );
if( !p_conn )
{
msg_Err( p_this, "Failed to connect to the D-Bus session daemon: %s",
error.message );
dbus_error_free( &error );
goto dbus_connection_failure;
}
dbus_connection_set_exit_on_disconnect( p_conn, FALSE );
/* Register the entry point object path */
dbus_connection_register_object_path( p_conn, DBUS_MPRIS_OBJECT_PATH,
&dbus_mpris_vtable, p_this );
/* Try to register org.mpris.MediaPlayer2.vlc */
const unsigned bus_flags = DBUS_NAME_FLAG_DO_NOT_QUEUE;
var_Create(vlc, "dbus-mpris-name", VLC_VAR_STRING);
if( dbus_bus_request_name( p_conn, DBUS_MPRIS_BUS_NAME, bus_flags, NULL )
!= DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER )
{
/* Register an instance-specific well known name of the form
* org.mpris.MediaPlayer2.vlc.instanceXXXX where XXXX is the
* current Process ID */
char unique_service[sizeof( DBUS_MPRIS_BUS_NAME ) +
sizeof( DBUS_INSTANCE_ID_PREFIX ) + 10];
snprintf( unique_service, sizeof (unique_service),
DBUS_MPRIS_BUS_NAME"."DBUS_INSTANCE_ID_PREFIX"%"PRIu32,
(uint32_t)getpid() );
if( dbus_bus_request_name( p_conn, unique_service, bus_flags, NULL )
== DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER )
{
msg_Dbg( p_intf, "listening on dbus as: %s", unique_service );
var_SetString(vlc, "dbus-mpris-name", unique_service);
}
}
else
{
msg_Dbg( p_intf, "listening on dbus as: %s", DBUS_MPRIS_BUS_NAME );
var_SetString(vlc, "dbus-mpris-name", DBUS_MPRIS_BUS_NAME);
}
dbus_connection_flush( p_conn );
p_intf->p_sys = p_sys;
p_sys->p_conn = p_conn;
vlc_array_init( &p_sys->events );
vlc_array_init( &p_sys->timeouts );
vlc_array_init( &p_sys->watches );
vlc_mutex_init( &p_sys->lock );
p_sys->playlist = playlist = vlc_intf_GetMainPlaylist(p_intf);
vlc_player_t *player = vlc_playlist_GetPlayer(playlist);
vlc_playlist_Lock(playlist);
static struct vlc_playlist_callbacks const playlist_cbs =
{
.on_items_added = playlist_on_items_added,
.on_items_removed = playlist_on_items_removed,
.on_playback_repeat_changed = playlist_on_playback_repeat_changed,
.on_playback_order_changed = playlist_on_playback_order_changed,
.on_current_index_changed = playlist_on_current_index_changed,
.on_has_prev_changed = playlist_on_has_prev_changed,
.on_has_next_changed = playlist_on_has_next_changed,
};
p_sys->playlist_listener =
vlc_playlist_AddListener(playlist, &playlist_cbs, p_intf, false);
if (!p_sys->playlist_listener)
goto playlist_listener_failure;
static struct vlc_player_cbs const player_cbs =
{
.on_state_changed = player_on_state_changed,
.on_error_changed = player_on_error_changed,
.on_rate_changed = player_on_rate_changed,
.on_capabilities_changed = player_on_capabilities_changed,
.on_media_meta_changed = player_on_media_meta_changed,
};
p_sys->player_listener =
vlc_player_AddListener(player, &player_cbs, p_intf);
if (!p_sys->player_listener)
goto player_listener_failure;
static struct vlc_player_aout_cbs const player_aout_cbs =
{
.on_volume_changed = player_aout_on_volume_changed,
.on_mute_changed = player_aout_on_mute_changed
};
p_sys->player_aout_listener =
vlc_player_aout_AddListener(player, &player_aout_cbs, p_intf);
if (!p_sys->player_aout_listener)
goto player_aout_listener_failure;
static struct vlc_player_vout_cbs const player_vout_cbs =
{
.on_fullscreen_changed = player_vout_on_fullscreen_changed,
};
p_sys->player_vout_listener =
vlc_player_vout_AddListener(player, &player_vout_cbs, p_intf);
if (!p_sys->player_vout_listener)
goto player_vout_listener_failure;
static struct vlc_player_timer_cbs const player_timer_cbs =
{
.on_update = player_timer_on_update,
.on_discontinuity = player_timer_on_discontinuity,
};
p_sys->player_timer =
vlc_player_AddTimer(player, VLC_TICK_FROM_SEC(1), &player_timer_cbs, p_intf);
if (!p_sys->player_timer)
goto player_timer_failure;
vlc_playlist_Unlock(playlist);
if( !dbus_connection_set_timeout_functions( p_conn,
add_timeout,
remove_timeout,
toggle_timeout,
p_intf, NULL ) )
goto late_failure;
if( !dbus_connection_set_watch_functions( p_conn,
add_watch,
remove_watch,
watch_toggled,
p_intf, NULL ) )
goto late_failure;
if( vlc_clone( &p_sys->thread, Run, p_intf ) )
goto late_failure;
return VLC_SUCCESS;
late_failure:
vlc_playlist_Lock(playlist);
vlc_player_RemoveTimer(player, p_sys->player_timer);
player_timer_failure:
vlc_player_vout_RemoveListener(player, p_sys->player_vout_listener);
player_vout_listener_failure:
vlc_player_aout_RemoveListener(player, p_sys->player_aout_listener);
player_aout_listener_failure:
vlc_player_RemoveListener(player, p_sys->player_listener);
player_listener_failure:
vlc_playlist_RemoveListener(playlist, p_sys->playlist_listener);
playlist_listener_failure:
vlc_playlist_Unlock(playlist);
var_Destroy(vlc, "dbus-mpris-name");
/* The dbus connection is private,
* so we are responsible for closing it
* XXX: Does this make sense when OOM ? */
dbus_connection_close( p_sys->p_conn );
dbus_connection_unref( p_conn );
dbus_connection_failure:
vlc_close( p_sys->p_pipe_fds[1] );
vlc_close( p_sys->p_pipe_fds[0] );
free( p_sys );
return VLC_EGENERIC;
}
/*****************************************************************************
* Close: destroy interface
*****************************************************************************/
static void Close ( vlc_object_t *p_this )
{
intf_thread_t *p_intf = (intf_thread_t*) p_this;
intf_sys_t *p_sys = p_intf->p_sys;
vlc_playlist_t *playlist = p_sys->playlist;
vlc_cancel( p_sys->thread );
vlc_join( p_sys->thread, NULL );
vlc_player_t *player = vlc_playlist_GetPlayer(playlist);
vlc_playlist_Lock(playlist);
vlc_player_RemoveTimer(player, p_sys->player_timer);
vlc_player_vout_RemoveListener(player, p_sys->player_vout_listener);
vlc_player_aout_RemoveListener(player, p_sys->player_aout_listener);
vlc_player_RemoveListener(player, p_sys->player_listener);
vlc_playlist_RemoveListener(playlist, p_sys->playlist_listener);
vlc_playlist_Unlock(playlist);
/* The dbus connection is private, so we are responsible
* for closing it */
dbus_connection_close( p_sys->p_conn );
dbus_connection_unref( p_sys->p_conn );
// Free the events array
for( size_t i = 0; i < vlc_array_count( &p_sys->events ); i++ )
{
callback_info_t* info = vlc_array_item_at_index( &p_sys->events, i );
switch( info->signal ) {
case SIGNAL_PLAYLIST_ITEM_APPEND:
tracklist_append_event_destroy( info->items_appended );
break;
case SIGNAL_PLAYLIST_ITEM_DELETED:
tracklist_remove_event_destroy( info->items_removed );
break;
}
free( info );
}
vlc_array_clear( &p_sys->events );
vlc_array_clear( &p_sys->timeouts );
vlc_array_clear( &p_sys->watches );
vlc_close( p_sys->p_pipe_fds[1] );
vlc_close( p_sys->p_pipe_fds[0] );
free( p_sys );
}
static dbus_bool_t add_timeout(DBusTimeout *to, void *data)
{
intf_thread_t *intf = data;
intf_sys_t *sys = intf->p_sys;
vlc_tick_t *expiry = malloc(sizeof (*expiry));
if (unlikely(expiry == NULL))
return FALSE;
dbus_timeout_set_data(to, expiry, free);
vlc_mutex_lock(&sys->lock);
vlc_array_append_or_abort(&sys->timeouts, to);
vlc_mutex_unlock(&sys->lock);
return TRUE;
}
static void remove_timeout(DBusTimeout *to, void *data)
{
intf_thread_t *intf = data;
intf_sys_t *sys = intf->p_sys;
size_t idx;
vlc_mutex_lock(&sys->lock);
idx = vlc_array_index_of_item(&sys->timeouts, to);
vlc_array_remove(&sys->timeouts, idx);
vlc_mutex_unlock(&sys->lock);
}
static void toggle_timeout(DBusTimeout *to, void *data)
{
intf_thread_t *intf = data;
intf_sys_t *sys = intf->p_sys;
vlc_tick_t *expiry = dbus_timeout_get_data(to);
vlc_mutex_lock(&sys->lock);
if (dbus_timeout_get_enabled(to))
*expiry = vlc_tick_now() + UINT64_C(1000) * dbus_timeout_get_interval(to);
vlc_mutex_unlock(&sys->lock);
wakeup_main_loop(intf);
}
/**
* Computes the time until the next timeout expiration.
* @note Interface lock must be held.
* @return The time in milliseconds until the next expiration,
* or -1 if there are no pending timeouts.
*/
static int next_timeout(intf_thread_t *intf)
{
intf_sys_t *sys = intf->p_sys;
vlc_tick_t next_timeout = VLC_TICK_MAX;
unsigned count = vlc_array_count(&sys->timeouts);
for (unsigned i = 0; i < count; i++)
{
DBusTimeout *to = vlc_array_item_at_index(&sys->timeouts, i);
if (!dbus_timeout_get_enabled(to))
continue;
vlc_tick_t *expiry = dbus_timeout_get_data(to);
if (next_timeout > *expiry)
next_timeout = *expiry;
}
if (next_timeout == VLC_TICK_MAX)
return -1;
if (MS_FROM_VLC_TICK(next_timeout) > INT_MAX)
return INT_MAX;
return MS_FROM_VLC_TICK(next_timeout);
}
/**
* Process pending D-Bus timeouts.
*
* @note Interface lock must be held.
*/
static void process_timeouts(intf_thread_t *intf)
{
intf_sys_t *sys = intf->p_sys;
for (size_t i = 0; i < vlc_array_count(&sys->timeouts); i++)
{
DBusTimeout *to = vlc_array_item_at_index(&sys->timeouts, i);
if (!dbus_timeout_get_enabled(to))
continue;
vlc_tick_t *expiry = dbus_timeout_get_data(to);
if (*expiry > vlc_tick_now())
continue;
expiry += UINT64_C(1000) * dbus_timeout_get_interval(to);
vlc_mutex_unlock(&sys->lock);
dbus_timeout_handle(to);
vlc_mutex_lock(&sys->lock);
i = -1; /* lost track of state, restart from beginning */
}
}
static dbus_bool_t add_watch( DBusWatch *p_watch, void *p_data )
{
intf_thread_t *p_intf = (intf_thread_t*) p_data;
intf_sys_t *p_sys = (intf_sys_t*) p_intf->p_sys;
vlc_mutex_lock( &p_sys->lock );
vlc_array_append_or_abort( &p_sys->watches, p_watch );
vlc_mutex_unlock( &p_sys->lock );
return TRUE;
}
static void remove_watch( DBusWatch *p_watch, void *p_data )
{
intf_thread_t *p_intf = (intf_thread_t*) p_data;
intf_sys_t *p_sys = (intf_sys_t*) p_intf->p_sys;
size_t idx;
vlc_mutex_lock( &p_sys->lock );
idx = vlc_array_index_of_item( &p_sys->watches, p_watch );
vlc_array_remove( &p_sys->watches, idx );
vlc_mutex_unlock( &p_sys->lock );
}
static void watch_toggled( DBusWatch *p_watch, void *p_data )
{
intf_thread_t *p_intf = (intf_thread_t*) p_data;
if( dbus_watch_get_enabled( p_watch ) )
wakeup_main_loop( p_intf );
}
/**
* GetPollFds() fills an array of pollfd data structures with :
* - the set of enabled dbus watches
* - the unix pipe which we use to manually wake up the main loop
*
* This function must be called with p_sys->lock locked
*
* @return The number of file descriptors
*
* @param intf_thread_t *p_intf this interface thread's state
* @param struct pollfd *p_fds a pointer to a pollfd array large enough to
* contain all the returned data (number of enabled dbus watches + 1)
*/
static int GetPollFds( intf_thread_t *p_intf, struct pollfd *p_fds )
{
intf_sys_t *p_sys = p_intf->p_sys;
size_t i_watches = vlc_array_count( &p_sys->watches );
int i_fds = 1;
p_fds[0].fd = p_sys->p_pipe_fds[PIPE_OUT];
p_fds[0].events = POLLIN | POLLPRI;
for( size_t i = 0; i < i_watches; i++ )
{
DBusWatch *p_watch = NULL;
p_watch = vlc_array_item_at_index( &p_sys->watches, i );
if( !dbus_watch_get_enabled( p_watch ) )
continue;
p_fds[i_fds].fd = dbus_watch_get_unix_fd( p_watch );
int i_flags = dbus_watch_get_flags( p_watch );
if( i_flags & DBUS_WATCH_READABLE )
p_fds[i_fds].events |= POLLIN | POLLPRI;
if( i_flags & DBUS_WATCH_WRITABLE )
p_fds[i_fds].events |= POLLOUT;
i_fds++;
}
return i_fds;
}
/**
* ProcessPlaylistChanged() reacts to tracks being either inserted or removed from the playlist
*
* This function must be called by ProcessEvents only
*
* @param intf_thread_t *p_intf This interface thread state
* @param callback_info_t *p_events the list of events to process
*/
static void ProcessPlaylistChanged( intf_thread_t *p_intf,
vlc_dictionary_t *player_properties,
vlc_dictionary_t *tracklist_properties )
{
vlc_playlist_t *playlist = p_intf->p_sys->playlist;
vlc_playlist_Lock(playlist);
bool b_can_play = vlc_playlist_Count(playlist) > 0;
vlc_playlist_Unlock(playlist);
if( b_can_play != p_intf->p_sys->b_can_play )
{
p_intf->p_sys->b_can_play = b_can_play;
vlc_dictionary_insert( player_properties, "CanPlay", NULL );
}
if( !vlc_dictionary_has_key( tracklist_properties, "Tracks" ) )
vlc_dictionary_insert( tracklist_properties, "Tracks", NULL );
}
/**
* ProcessEvents() reacts to a list of events originating from other VLC threads
*
* This function must be called with p_sys->lock unlocked
*
* @param intf_thread_t *p_intf This interface thread state
* @param callback_info_t *p_events the list of events to process
*/
static void ProcessEvents( intf_thread_t *p_intf,
callback_info_t **p_events, int i_events )
{
vlc_dictionary_t player_properties, tracklist_properties, root_properties;
vlc_dictionary_init( &player_properties, 0 );
vlc_dictionary_init( &tracklist_properties, 0 );
vlc_dictionary_init( &root_properties, 0 );
// In case multiple *_ITEM_APPEND or *_ITEM_DELETED events appear on the
// list, the elements in their respective map values will be linked in
// order.
// We keep the tail of the list in order to append the elements to the end
// of each list.
tracklist_append_event_t *last_append = NULL;
tracklist_remove_event_t *last_remove = NULL;
for( int i = 0; i < i_events; i++ )
{
switch( p_events[i]->signal )
{
case SIGNAL_ITEM_CURRENT:
TrackChange( p_intf );
// rate depends on current item
if( !vlc_dictionary_has_key( &player_properties, "Rate" ) )
vlc_dictionary_insert( &player_properties, "Rate", NULL );
vlc_dictionary_insert( &player_properties, "Metadata", NULL );
break;
case SIGNAL_PLAYLIST_ITEM_APPEND:
if ( !last_append ) {
last_append = vlc_dictionary_value_for_key( &tracklist_properties, "TrackAdded" );
}
if( !last_append ) {
vlc_dictionary_insert( &tracklist_properties, "TrackAdded", p_events[i]->items_appended );
last_append = p_events[i]->items_appended;
} else {
last_append->change_ev.next = &p_events[i]->items_appended->change_ev;
last_append = p_events[i]->items_appended;
}
ProcessPlaylistChanged( p_intf, &player_properties, &tracklist_properties );
break;
case SIGNAL_PLAYLIST_ITEM_DELETED:
if( !last_remove ) {
assert (!vlc_dictionary_has_key( &tracklist_properties, "TrackRemoved" ) );
vlc_dictionary_insert( &tracklist_properties, "TrackRemoved", p_events[i]->items_removed );
last_remove = p_events[i]->items_removed;
} else {
last_remove->change_ev.next = &p_events[i]->items_removed->change_ev;
last_remove = p_events[i]->items_removed;
}
ProcessPlaylistChanged( p_intf, &player_properties, &tracklist_properties );
break;
case SIGNAL_VOLUME_MUTED:
case SIGNAL_VOLUME_CHANGE:
vlc_dictionary_insert( &player_properties, "Volume", NULL );
break;
case SIGNAL_RANDOM:
vlc_dictionary_insert( &player_properties, "Shuffle", NULL );
break;
case SIGNAL_FULLSCREEN:
vlc_dictionary_insert( &root_properties, "Fullscreen", NULL );
break;
case SIGNAL_REPEAT:
case SIGNAL_LOOP:
vlc_dictionary_insert( &player_properties, "LoopStatus", NULL );
break;
case SIGNAL_STATE:
vlc_dictionary_insert( &player_properties, "PlaybackStatus", NULL );
break;
case SIGNAL_RATE:
vlc_dictionary_insert( &player_properties, "Rate", NULL );
break;
case SIGNAL_INPUT_METADATA:
{
vlc_player_t *player =
vlc_playlist_GetPlayer(p_intf->p_sys->playlist);
vlc_player_Lock(player);
input_item_t *p_item = vlc_player_GetCurrentMedia(player);
if( p_item )
vlc_dictionary_insert( &player_properties,
"Metadata", NULL );
vlc_player_Unlock(player);
break;
}
case SIGNAL_CAN_SEEK:
vlc_dictionary_insert( &player_properties, "CanSeek", NULL );
break;
case SIGNAL_CAN_PAUSE:
vlc_dictionary_insert( &player_properties, "CanPause", NULL );
break;
case SIGNAL_SEEK:
SeekedEmit( p_intf );
break;
case SIGNAL_CAN_GO_PREVIOUS:
vlc_dictionary_insert( &player_properties, "CanGoPrevious", NULL );
break;
case SIGNAL_CAN_GO_NEXT:
vlc_dictionary_insert( &player_properties, "CanGoNext", NULL );
break;
default:
vlc_assert_unreachable();
}
}
if( !vlc_dictionary_is_empty( &player_properties ) )
PlayerPropertiesChangedEmit( p_intf, &player_properties );
if( !vlc_dictionary_is_empty( &tracklist_properties ) )
TrackListPropertiesChangedEmit( p_intf, &tracklist_properties );
if( !vlc_dictionary_is_empty( &root_properties ) )
RootPropertiesChangedEmit( p_intf, &root_properties );
for (int i = 0; i < i_events; i++)
{
switch (p_events[i]->signal)
{
case SIGNAL_PLAYLIST_ITEM_APPEND:
tracklist_append_event_destroy(p_events[i]->items_appended);
break;
case SIGNAL_PLAYLIST_ITEM_DELETED:
tracklist_remove_event_destroy(p_events[i]->items_removed);
break;
default:
break;
}
free(p_events[i]);
}
vlc_dictionary_clear( &player_properties, NULL, NULL );
vlc_dictionary_clear( &tracklist_properties, NULL, NULL );
vlc_dictionary_clear( &root_properties, NULL, NULL );
}
/**
* ProcessWatches() handles a list of dbus watches after poll() has returned
*
* This function must be called with p_sys->lock unlocked
*
* @param intf_thread_t *p_intf This interface thread state
* @param DBusWatch **p_watches The list of dbus watches to process
* @param int i_watches The size of the p_watches array
* @param struct pollfd *p_fds The result of a poll() call
* @param int i_fds The number of file descriptors processed by poll()
*/
static void ProcessWatches( intf_thread_t *p_intf,
DBusWatch **p_watches, int i_watches,
struct pollfd *p_fds, int i_fds )
{
VLC_UNUSED(p_intf);
/* Process watches */
for( int i = 0; i < i_watches; i++ )
{
DBusWatch *p_watch = p_watches[i];
if( !dbus_watch_get_enabled( p_watch ) )
continue;
for( int j = 0; j < i_fds; j++ )
{
if( p_fds[j].fd != dbus_watch_get_unix_fd( p_watch ) )
continue;
int i_flags = 0;
int i_revents = p_fds[j].revents;
if( i_revents & POLLIN )
i_flags |= DBUS_WATCH_READABLE;
if( i_revents & POLLOUT )
i_flags |= DBUS_WATCH_WRITABLE;
if( i_revents & POLLERR )
i_flags |= DBUS_WATCH_ERROR;
if( i_revents & POLLHUP )
i_flags |= DBUS_WATCH_HANGUP;
if( i_flags )
dbus_watch_handle( p_watch, i_flags );
}
}
}
/**
* DispatchDBusMessages() dispatches incoming dbus messages
* (indirectly invoking the callbacks), then it sends outgoing
* messages which needs to be sent on the bus (method replies and signals)
*
* This function must be called with p_sys->lock unlocked
*
* @param intf_thread_t *p_intf This interface thread state
*/
static void DispatchDBusMessages( intf_thread_t *p_intf )
{
DBusDispatchStatus status;
intf_sys_t *p_sys = p_intf->p_sys;
/* Dispatch incoming messages */
status = dbus_connection_get_dispatch_status( p_sys->p_conn );
while( status != DBUS_DISPATCH_COMPLETE )
{
dbus_connection_dispatch( p_sys->p_conn );
status = dbus_connection_get_dispatch_status( p_sys->p_conn );
}
/* Send outgoing data */
if( dbus_connection_has_messages_to_send( p_sys->p_conn ) )
dbus_connection_flush( p_sys->p_conn );
}
/**
* MPRISEntryPoint() routes incoming messages to their respective interface
* implementation.
*
* This function is called during dbus_connection_dispatch()
*/
static DBusHandlerResult
MPRISEntryPoint ( DBusConnection *p_conn, DBusMessage *p_from, void *p_this )
{
const char *psz_target_interface;
const char *psz_interface = dbus_message_get_interface( p_from );
const char *psz_method = dbus_message_get_member( p_from );
DBusError error;
if( psz_interface && strcmp( psz_interface, DBUS_INTERFACE_PROPERTIES ) )
psz_target_interface = psz_interface;
else
{
dbus_error_init( &error );
dbus_message_get_args( p_from, &error,
DBUS_TYPE_STRING, &psz_target_interface,
DBUS_TYPE_INVALID );
if( dbus_error_is_set( &error ) )
{
msg_Err( (vlc_object_t*) p_this, "D-Bus error on %s.%s: %s",
psz_interface, psz_method,
error.message );
dbus_error_free( &error );
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
}
if( !strcmp( psz_target_interface, DBUS_INTERFACE_INTROSPECTABLE ) )
return handle_introspect( p_conn, p_from, p_this );
if( !strcmp( psz_target_interface, DBUS_MPRIS_ROOT_INTERFACE ) )
return handle_root( p_conn, p_from, p_this );
if( !strcmp( psz_target_interface, DBUS_MPRIS_PLAYER_INTERFACE ) )
return handle_player( p_conn, p_from, p_this );
if( !strcmp( psz_target_interface, DBUS_MPRIS_TRACKLIST_INTERFACE ) )
return handle_tracklist( p_conn, p_from, p_this );
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
/*****************************************************************************
* Run: main loop
*****************************************************************************/
static void *Run( void *data )
{
intf_thread_t *p_intf = data;
intf_sys_t *p_sys = p_intf->p_sys;
vlc_thread_set_name("vlc-ctrl-dbus");
int canc = vlc_savecancel();
vlc_tick_t events_last_date = VLC_TICK_INVALID;
int events_poll_timeout = -1;
for( ;; )
{
vlc_mutex_lock( &p_sys->lock );
size_t i_watches = vlc_array_count( &p_sys->watches );
struct pollfd fds[i_watches];
memset(fds, 0, sizeof fds);
int i_fds = GetPollFds( p_intf, fds );
int timeout = next_timeout(p_intf);
vlc_mutex_unlock( &p_sys->lock );
/* thread cancellation is allowed while the main loop sleeps */
vlc_restorecancel( canc );
if( timeout == -1 )
timeout = events_poll_timeout;
while (poll(fds, i_fds, timeout) == -1)
{
if (errno != EINTR)
goto error;
}
canc = vlc_savecancel();
/* Was the main loop woken up manually ? */
if (fds[0].revents & POLLIN)
{
while (read(fds[0].fd, &(char){' '}, 1) == -1)
{
/* Only EINTR can happen here, so ignore the error. */
}
}
/* We need to lock the mutex while building lists of events,
* timeouts and watches to process but we can't keep the lock while
* processing them, or else we risk a deadlock:
*
* The signal functions could lock mutex X while p_events is locked;
* While some other function in vlc (playlist) might lock mutex X
* and then set a variable which would call PlaylistCallback(), which itself
* needs to lock p_events to add a new event.
*/
vlc_mutex_lock( &p_intf->p_sys->lock );
process_timeouts(p_intf);
/* Get the list of watches to process */
i_watches = vlc_array_count( &p_sys->watches );
DBusWatch *p_watches[i_watches ? i_watches : 1];
for( size_t i = 0; i < i_watches; i++ )
{
p_watches[i] = vlc_array_item_at_index( &p_sys->watches, i );
}
/* Get the list of events to process */
size_t i_events = vlc_array_count( &p_sys->events );
callback_info_t** pp_info = NULL;
if( i_events > 0 )
{
vlc_tick_t now = vlc_tick_now();
if( events_last_date == VLC_TICK_INVALID
|| now - events_last_date > EVENTS_DELAY )
{
/* Send events every EVENTS_DELAY */
events_last_date = now;
events_poll_timeout = -1;
pp_info = vlc_alloc( i_events, sizeof(*pp_info) );
if( pp_info )
{
for( size_t i = 0; i < i_events; i++ )
pp_info[i] = vlc_array_item_at_index( &p_sys->events, i );
vlc_array_clear( &p_sys->events );
}
}
else if( events_poll_timeout == -1 )
{
/* Request poll to wake up in order to send these events after
* some delay */
events_poll_timeout = ( EVENTS_DELAY - ( now - events_last_date ) ) / 1000;
}
}
else /* No events: clear timeout */
events_poll_timeout = -1;
/* now we can release the lock and process what's pending */
vlc_mutex_unlock( &p_intf->p_sys->lock );
if( pp_info )
{
ProcessEvents( p_intf, pp_info, i_events );
free( pp_info );
}
ProcessWatches( p_intf, p_watches, i_watches, fds, i_fds );
DispatchDBusMessages( p_intf );
}
error:
vlc_restorecancel(canc);
return NULL;
}
static void wakeup_main_loop( void *p_data )
{
intf_thread_t *p_intf = (intf_thread_t*) p_data;
if( !write( p_intf->p_sys->p_pipe_fds[PIPE_IN], "\0", 1 ) )
msg_Err( p_intf, "Could not wake up the main loop: %s",
vlc_strerror_c(errno) );
}
static bool add_event_locked( intf_thread_t *p_intf, const callback_info_t *p_info )
{
if( !p_info->signal )
return false;
for( size_t i = 0; i < vlc_array_count( &p_intf->p_sys->events ); ++ i )
{
callback_info_t *oldinfo =
vlc_array_item_at_index( &p_intf->p_sys->events, i );
switch (p_info->signal)
{
/* Those events are squashed afterwards and must be
* released appropriately. */
case SIGNAL_PLAYLIST_ITEM_APPEND:
case SIGNAL_PLAYLIST_ITEM_DELETED:
break;
default:
if (p_info->signal == oldinfo->signal)
return false;
}
}
callback_info_t *p_dup = malloc( sizeof( *p_dup ) );
if( unlikely(p_dup == NULL) )
return false;
*p_dup = *p_info;
vlc_array_append( &p_intf->p_sys->events, p_dup );
return true;
}
static bool add_event_signal( intf_thread_t *p_intf, const callback_info_t *p_info )
{
intf_sys_t *p_sys = p_intf->p_sys;
vlc_mutex_lock(&p_sys->lock);
bool added = add_event_locked(p_intf, p_info);
vlc_mutex_unlock(&p_sys->lock);
if( added )
wakeup_main_loop( p_intf );
return added;
}
static void
playlist_on_items_added(vlc_playlist_t *playlist, size_t index,
vlc_playlist_item_t *const items[], size_t count,
void *data)
{
tracklist_append_event_t *append_event = tracklist_append_event_create(index, items, count);
bool added = add_event_signal(data,
&(callback_info_t){ .signal = SIGNAL_PLAYLIST_ITEM_APPEND,
.items_appended = append_event });
if (!added)
tracklist_append_event_destroy(append_event);
(void) playlist;
}
static void
playlist_on_items_removed(vlc_playlist_t *playlist,
size_t index, size_t count, void *data)
{
tracklist_remove_event_t *remove_event = tracklist_remove_event_create(index, count);
bool added = add_event_signal(data,
&(callback_info_t){ .signal = SIGNAL_PLAYLIST_ITEM_DELETED,
.items_removed = remove_event });
if (!added)
tracklist_remove_event_destroy(remove_event);
(void) playlist;
}
static void
playlist_on_playback_repeat_changed(vlc_playlist_t *playlist,
enum vlc_playlist_playback_repeat repeat,
void *data)
{
add_event_signal(data, &(callback_info_t){ .signal = SIGNAL_REPEAT });
(void) playlist; (void) repeat;
}
static void
playlist_on_playback_order_changed(vlc_playlist_t *playlist,
enum vlc_playlist_playback_order order,
void *data)
{
add_event_signal(data, &(callback_info_t){ .signal = SIGNAL_RANDOM });
(void) playlist; (void) order;
}
static void
playlist_on_current_index_changed(vlc_playlist_t *playlist,
ssize_t index, void *data)
{
add_event_signal(data,
&(callback_info_t){ .signal = SIGNAL_ITEM_CURRENT });
(void) playlist; (void) index;
}
static void
playlist_on_has_prev_changed(vlc_playlist_t *playlist, bool has_prev,
void *data)
{
add_event_signal(data,
&(callback_info_t){ .signal = SIGNAL_CAN_GO_PREVIOUS });
(void) playlist; (void) has_prev;
}
static void
playlist_on_has_next_changed(vlc_playlist_t *playlist, bool has_next,
void *data)
{
add_event_signal(data,
&(callback_info_t){ .signal = SIGNAL_CAN_GO_NEXT });
(void) playlist; (void) has_next;
}
static void
player_on_state_changed(vlc_player_t *player, enum vlc_player_state state,
void *data)
{
intf_thread_t *intf = data;
intf_sys_t *sys = intf->p_sys;
dbus_int32_t playing_state;
switch (state)
{
case VLC_PLAYER_STATE_STARTED:
case VLC_PLAYER_STATE_PLAYING:
playing_state = PLAYBACK_STATE_PLAYING;
break;
case VLC_PLAYER_STATE_PAUSED:
playing_state = PLAYBACK_STATE_PAUSED;
break;
/* STOPPING is used to detect time discontinuities that are coming
* from stopping the playback in the vlc player timer callbacks. */
case VLC_PLAYER_STATE_STOPPING:
case VLC_PLAYER_STATE_STOPPED:
default:
playing_state = PLAYBACK_STATE_STOPPED;
break;
}
bool added = false;
vlc_mutex_lock(&sys->lock);
if (playing_state != sys->i_playing_state)
{
sys->i_playing_state = playing_state;
added = add_event_locked(intf,
&(callback_info_t) { .signal = SIGNAL_STATE });
}
vlc_mutex_unlock(&sys->lock);
if (added)
wakeup_main_loop(intf);
(void) player;
}
static void
player_on_error_changed(vlc_player_t *player, enum vlc_player_error error,
void *data)
{
if (error == VLC_PLAYER_ERROR_GENERIC)
player_on_state_changed(player, VLC_PLAYER_STATE_STOPPED, data);
}
static void
player_on_rate_changed(vlc_player_t *player, float new_rate, void *data)
{
add_event_signal(data, &(callback_info_t){ .signal = SIGNAL_RATE });
(void) player; (void) new_rate;
}
static void
player_on_capabilities_changed(vlc_player_t *player, int old_caps, int new_caps,
void *data)
{
intf_thread_t *intf = data;
intf_sys_t *sys = intf->p_sys;
vlc_mutex_lock(&sys->lock);
bool ok1 = add_event_locked(intf,
&(callback_info_t) { .signal = SIGNAL_CAN_SEEK });
bool ok2 = add_event_locked(intf,
&(callback_info_t) { .signal = SIGNAL_CAN_PAUSE });
vlc_mutex_unlock(&sys->lock);
if (ok1 || ok2)
wakeup_main_loop(intf);
(void) player; (void) old_caps; (void) new_caps;
}
static void
player_on_media_meta_changed(vlc_player_t *player,
input_item_t *item, void *data)
{
add_event_signal(data,
&(callback_info_t){ .signal = SIGNAL_INPUT_METADATA });
(void) player; (void) item;
}
static void
player_aout_on_volume_changed(audio_output_t *aout, float volume, void *data)
{
add_event_signal(data,
&(callback_info_t){ .signal = SIGNAL_VOLUME_CHANGE });
(void) aout; (void) volume;
}
static void
player_aout_on_mute_changed(audio_output_t *aout, bool muted, void *data)
{
add_event_signal(data,
&(callback_info_t){ .signal = SIGNAL_VOLUME_MUTED });
(void) aout; (void) muted;
}
static void
player_vout_on_fullscreen_changed(vout_thread_t *vout, bool enabled,
void *data)
{
add_event_signal(data, &(callback_info_t){ .signal = SIGNAL_FULLSCREEN });
(void) vout; (void) enabled;
}
static void
player_timer_on_update(const struct vlc_player_timer_point *value, void *data)
{
/* The callback is not used by mandatory in the vlc_player_timer API */
VLC_UNUSED(value);
VLC_UNUSED(data);
}
static void
player_timer_on_discontinuity(vlc_tick_t system_date, void *data)
{
intf_thread_t *intf = data;
intf_sys_t *sys = intf->p_sys;
bool stopping = sys->i_playing_state == PLAYBACK_STATE_STOPPED;
bool paused = system_date != VLC_TICK_INVALID;
if( !paused && !stopping )
add_event_signal(intf, &(callback_info_t){ .signal = SIGNAL_SEEK });
}
/*****************************************************************************
* TrackChange: callback on playlist_on_current_index_changed
*****************************************************************************/
static int TrackChange( intf_thread_t *p_intf )
{
intf_sys_t *p_sys = p_intf->p_sys;
if( p_intf->p_sys->b_dead )
return VLC_SUCCESS;
p_sys->has_input = false;
p_sys->b_meta_read = false;
vlc_player_t *player = vlc_playlist_GetPlayer(p_sys->playlist);
vlc_player_Lock(player);
input_item_t *item = vlc_player_GetCurrentMedia(player);
vlc_player_Unlock(player);
if( !item )
return VLC_EGENERIC;
if( input_item_IsPreparsed( item ) )
p_sys->b_meta_read = true;
p_sys->has_input = true;
return VLC_SUCCESS;
}
/**
* DemarshalSetPropertyValue() extracts the new property value from a
* org.freedesktop.DBus.Properties.Set method call message.
*
* @return int VLC_SUCCESS on success
* @param DBusMessage *p_msg a org.freedesktop.DBus.Properties.Set method call
* @param void *p_arg placeholder for the demarshalled value
*/
int DemarshalSetPropertyValue( DBusMessage *p_msg, void *p_arg )
{
int i_type;
bool b_valid_input = FALSE;
DBusMessageIter in_args, variant;
dbus_message_iter_init( p_msg, &in_args );
do
{
i_type = dbus_message_iter_get_arg_type( &in_args );
if( DBUS_TYPE_VARIANT == i_type )
{
dbus_message_iter_recurse( &in_args, &variant );
dbus_message_iter_get_basic( &variant, p_arg );
b_valid_input = TRUE;
}
} while( dbus_message_iter_next( &in_args ) );
return b_valid_input ? VLC_SUCCESS : VLC_EGENERIC;
}
/*****************************************************************************
* GetInputMeta: Fill a DBusMessage with the given input item metadata
*****************************************************************************/
#define ADD_META( entry, type, data ) \
if( data ) { \
dbus_message_iter_open_container( &dict, DBUS_TYPE_DICT_ENTRY, \
NULL, &dict_entry ); \
dbus_message_iter_append_basic( &dict_entry, DBUS_TYPE_STRING, \
&ppsz_meta_items[entry] ); \
dbus_message_iter_open_container( &dict_entry, DBUS_TYPE_VARIANT, \
type##_AS_STRING, &variant ); \
dbus_message_iter_append_basic( &variant, \
type, \
& data ); \
dbus_message_iter_close_container( &dict_entry, &variant ); \
dbus_message_iter_close_container( &dict, &dict_entry ); }
#define ADD_VLC_META_STRING( entry, item ) \
{ \
char * psz = input_item_Get##item( p_input );\
ADD_META( entry, DBUS_TYPE_STRING, \
psz ); \
free( psz ); \
}
#define ADD_META_SINGLETON_STRING_LIST( entry, item ) \
{ \
char * psz = input_item_Get##item( p_input );\
if( psz ) { \
dbus_message_iter_open_container( &dict, DBUS_TYPE_DICT_ENTRY, \
NULL, &dict_entry ); \
dbus_message_iter_append_basic( &dict_entry, DBUS_TYPE_STRING, \
&ppsz_meta_items[entry] ); \
dbus_message_iter_open_container( &dict_entry, DBUS_TYPE_VARIANT, \
"as", &variant ); \
dbus_message_iter_open_container( &variant, DBUS_TYPE_ARRAY, "s", \
&list ); \
dbus_message_iter_append_basic( &list, \
DBUS_TYPE_STRING, \
&psz ); \
dbus_message_iter_close_container( &variant, &list ); \
dbus_message_iter_close_container( &dict_entry, &variant ); \
dbus_message_iter_close_container( &dict, &dict_entry ); \
} \
free( psz ); \
}
int GetInputMeta(size_t index, vlc_playlist_item_t *item, DBusMessageIter *args)
{
input_item_t *p_input = vlc_playlist_item_GetMedia(item);
DBusMessageIter dict, dict_entry, variant, list;
/** The duration of the track can be expressed in second, milli-seconds and
µ-seconds */
dbus_int64_t i_mtime = input_item_GetDuration( p_input );
dbus_uint32_t i_time = i_mtime / 1000000;
dbus_int64_t i_length = i_mtime / 1000;
char *psz_trackid;
if (asprintf(&psz_trackid, MPRIS_TRACKID_FORMAT, index) == -1)
return VLC_ENOMEM;
const char* ppsz_meta_items[] =
{
"mpris:trackid", "xesam:url", "xesam:title", "xesam:artist",
"xesam:album", "xesam:tracknumber", "vlc:time", "mpris:length",
"xesam:genre", "xesam:userRating", "xesam:contentCreated",
"mpris:artUrl", "mb:trackId", "vlc:audio-bitrate",
"vlc:audio-samplerate", "vlc:video-bitrate", "vlc:audio-codec",
"vlc:copyright", "xesam:comment", "vlc:encodedby", "language",
"vlc:length", "vlc:nowplaying", "vlc:publisher", "vlc:setting",
"status", "vlc:url", "vlc:video-codec"
};
dbus_message_iter_open_container( args, DBUS_TYPE_ARRAY, "{sv}", &dict );
ADD_META( 0, DBUS_TYPE_OBJECT_PATH, psz_trackid );
ADD_VLC_META_STRING( 1, URI );
ADD_VLC_META_STRING( 2, Title );
ADD_META_SINGLETON_STRING_LIST( 3, Artist );
ADD_VLC_META_STRING( 4, Album );
ADD_VLC_META_STRING( 5, TrackNum );
ADD_META( 6, DBUS_TYPE_UINT32, i_time );
ADD_META( 7, DBUS_TYPE_INT64, i_mtime );
ADD_META_SINGLETON_STRING_LIST( 8, Genre );
//ADD_META( 9, DBUS_TYPE_DOUBLE, rating );
ADD_VLC_META_STRING( 10, Date ); // this is supposed to be in ISO 8601 extended format
ADD_VLC_META_STRING( 11, ArtURL );
ADD_VLC_META_STRING( 12, TrackID );
ADD_VLC_META_STRING( 17, Copyright );
ADD_META_SINGLETON_STRING_LIST( 18, Description );
ADD_VLC_META_STRING( 19, EncodedBy );
ADD_VLC_META_STRING( 20, Language );
ADD_META( 21, DBUS_TYPE_INT64, i_length );
ADD_VLC_META_STRING( 22, NowPlaying );
ADD_VLC_META_STRING( 23, Publisher );
ADD_VLC_META_STRING( 24, Setting );
ADD_VLC_META_STRING( 25, URL );
free( psz_trackid );
vlc_mutex_lock( &p_input->lock );
if( p_input->p_meta )
{
int i_status = vlc_meta_GetStatus( p_input->p_meta );
ADD_META( 23, DBUS_TYPE_INT32, i_status );
}
vlc_mutex_unlock( &p_input->lock );
dbus_message_iter_close_container( args, &dict );
return VLC_SUCCESS;
}
int AddProperty( intf_thread_t *p_intf,
DBusMessageIter *p_container,
const char* psz_property_name,
const char* psz_signature,
int (*pf_marshaller) (intf_thread_t*, DBusMessageIter*) )
{
DBusMessageIter entry, v;
if( !dbus_message_iter_open_container( p_container,
DBUS_TYPE_DICT_ENTRY, NULL,
&entry ) )
return VLC_ENOMEM;
if( !dbus_message_iter_append_basic( &entry,
DBUS_TYPE_STRING,
&psz_property_name ) )
return VLC_ENOMEM;
if( !dbus_message_iter_open_container( &entry,
DBUS_TYPE_VARIANT, psz_signature,
&v ) )
return VLC_ENOMEM;
if( VLC_SUCCESS != pf_marshaller( p_intf, &v ) )
return VLC_ENOMEM;
if( !dbus_message_iter_close_container( &entry, &v) )
return VLC_ENOMEM;
if( !dbus_message_iter_close_container( p_container, &entry ) )
return VLC_ENOMEM;
return VLC_SUCCESS;
}
#undef ADD_META
#undef ADD_VLC_META_STRING