pulseaudio/src/modules/dbus/iface-stream.c

1000 lines
39 KiB
C

/***
This file is part of PulseAudio.
Copyright 2009 Tanu Kaskinen
Copyright 2009 Vincent Filali-Ansary <filali.v@azurdigitalnetworks.net>
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2.1 of the License,
or (at your option) any later version.
PulseAudio 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 Lesser General Public License
along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <pulsecore/core-util.h>
#include <pulsecore/dbus-util.h>
#include <pulsecore/protocol-dbus.h>
#include "iface-stream.h"
#define PLAYBACK_OBJECT_NAME "playback_stream"
#define RECORD_OBJECT_NAME "record_stream"
enum stream_type {
STREAM_TYPE_PLAYBACK,
STREAM_TYPE_RECORD
};
struct pa_dbusiface_stream {
pa_dbusiface_core *core;
union {
pa_sink_input *sink_input;
pa_source_output *source_output;
};
enum stream_type type;
char *path;
union {
pa_sink *sink;
pa_source *source;
};
uint32_t sample_rate;
pa_cvolume volume;
dbus_bool_t mute;
pa_proplist *proplist;
bool has_volume;
pa_dbus_protocol *dbus_protocol;
pa_hook_slot *send_event_slot;
pa_hook_slot *move_finish_slot;
pa_hook_slot *volume_changed_slot;
pa_hook_slot *mute_changed_slot;
pa_hook_slot *proplist_changed_slot;
pa_hook_slot *state_changed_slot;
};
static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata);
static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata);
static void handle_get_client(DBusConnection *conn, DBusMessage *msg, void *userdata);
static void handle_get_device(DBusConnection *conn, DBusMessage *msg, void *userdata);
static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata);
static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata);
static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata);
static void handle_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata);
static void handle_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
static void handle_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata);
static void handle_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
static void handle_get_buffer_latency(DBusConnection *conn, DBusMessage *msg, void *userdata);
static void handle_get_device_latency(DBusConnection *conn, DBusMessage *msg, void *userdata);
static void handle_get_resample_method(DBusConnection *conn, DBusMessage *msg, void *userdata);
static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata);
static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
static void handle_move(DBusConnection *conn, DBusMessage *msg, void *userdata);
static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata);
enum property_handler_index {
PROPERTY_HANDLER_INDEX,
PROPERTY_HANDLER_DRIVER,
PROPERTY_HANDLER_OWNER_MODULE,
PROPERTY_HANDLER_CLIENT,
PROPERTY_HANDLER_DEVICE,
PROPERTY_HANDLER_SAMPLE_FORMAT,
PROPERTY_HANDLER_SAMPLE_RATE,
PROPERTY_HANDLER_CHANNELS,
PROPERTY_HANDLER_VOLUME,
PROPERTY_HANDLER_MUTE,
PROPERTY_HANDLER_BUFFER_LATENCY,
PROPERTY_HANDLER_DEVICE_LATENCY,
PROPERTY_HANDLER_RESAMPLE_METHOD,
PROPERTY_HANDLER_PROPERTY_LIST,
PROPERTY_HANDLER_MAX
};
static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
[PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL },
[PROPERTY_HANDLER_DRIVER] = { .property_name = "Driver", .type = "s", .get_cb = handle_get_driver, .set_cb = NULL },
[PROPERTY_HANDLER_OWNER_MODULE] = { .property_name = "OwnerModule", .type = "o", .get_cb = handle_get_owner_module, .set_cb = NULL },
[PROPERTY_HANDLER_CLIENT] = { .property_name = "Client", .type = "o", .get_cb = handle_get_client, .set_cb = NULL },
[PROPERTY_HANDLER_DEVICE] = { .property_name = "Device", .type = "o", .get_cb = handle_get_device, .set_cb = NULL },
[PROPERTY_HANDLER_SAMPLE_FORMAT] = { .property_name = "SampleFormat", .type = "u", .get_cb = handle_get_sample_format, .set_cb = NULL },
[PROPERTY_HANDLER_SAMPLE_RATE] = { .property_name = "SampleRate", .type = "u", .get_cb = handle_get_sample_rate, .set_cb = NULL },
[PROPERTY_HANDLER_CHANNELS] = { .property_name = "Channels", .type = "au", .get_cb = handle_get_channels, .set_cb = NULL },
[PROPERTY_HANDLER_VOLUME] = { .property_name = "Volume", .type = "au", .get_cb = handle_get_volume, .set_cb = handle_set_volume },
[PROPERTY_HANDLER_MUTE] = { .property_name = "Mute", .type = "b", .get_cb = handle_get_mute, .set_cb = handle_set_mute },
[PROPERTY_HANDLER_BUFFER_LATENCY] = { .property_name = "BufferLatency", .type = "t", .get_cb = handle_get_buffer_latency, .set_cb = NULL },
[PROPERTY_HANDLER_DEVICE_LATENCY] = { .property_name = "DeviceLatency", .type = "t", .get_cb = handle_get_device_latency, .set_cb = NULL },
[PROPERTY_HANDLER_RESAMPLE_METHOD] = { .property_name = "ResampleMethod", .type = "s", .get_cb = handle_get_resample_method, .set_cb = NULL },
[PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL }
};
enum method_handler_index {
METHOD_HANDLER_MOVE,
METHOD_HANDLER_KILL,
METHOD_HANDLER_MAX
};
static pa_dbus_arg_info move_args[] = { { "device", "o", "in" } };
static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
[METHOD_HANDLER_MOVE] = {
.method_name = "Move",
.arguments = move_args,
.n_arguments = sizeof(move_args) / sizeof(pa_dbus_arg_info),
.receive_cb = handle_move },
[METHOD_HANDLER_KILL] = {
.method_name = "Kill",
.arguments = NULL,
.n_arguments = 0,
.receive_cb = handle_kill }
};
enum signal_index {
SIGNAL_DEVICE_UPDATED,
SIGNAL_SAMPLE_RATE_UPDATED,
SIGNAL_VOLUME_UPDATED,
SIGNAL_MUTE_UPDATED,
SIGNAL_PROPERTY_LIST_UPDATED,
SIGNAL_STREAM_EVENT,
SIGNAL_MAX
};
static pa_dbus_arg_info device_updated_args[] = { { "device", "o", NULL } };
static pa_dbus_arg_info sample_rate_updated_args[] = { { "sample_rate", "u", NULL } };
static pa_dbus_arg_info volume_updated_args[] = { { "volume", "au", NULL } };
static pa_dbus_arg_info mute_updated_args[] = { { "muted", "b", NULL } };
static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } };
static pa_dbus_arg_info stream_event_args[] = { { "name", "s", NULL }, { "property_list", "a{say}", NULL } };
static pa_dbus_signal_info signals[SIGNAL_MAX] = {
[SIGNAL_DEVICE_UPDATED] = { .name = "DeviceUpdated", .arguments = device_updated_args, .n_arguments = 1 },
[SIGNAL_SAMPLE_RATE_UPDATED] = { .name = "SampleRateUpdated", .arguments = sample_rate_updated_args, .n_arguments = 1 },
[SIGNAL_VOLUME_UPDATED] = { .name = "VolumeUpdated", .arguments = volume_updated_args, .n_arguments = 1 },
[SIGNAL_MUTE_UPDATED] = { .name = "MuteUpdated", .arguments = mute_updated_args, .n_arguments = 1 },
[SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 },
[SIGNAL_STREAM_EVENT] = { .name = "StreamEvent", .arguments = stream_event_args, .n_arguments = sizeof(stream_event_args) / sizeof(pa_dbus_arg_info) }
};
static pa_dbus_interface_info stream_interface_info = {
.name = PA_DBUSIFACE_STREAM_INTERFACE,
.method_handlers = method_handlers,
.n_method_handlers = METHOD_HANDLER_MAX,
.property_handlers = property_handlers,
.n_property_handlers = PROPERTY_HANDLER_MAX,
.get_all_properties_cb = handle_get_all,
.signals = signals,
.n_signals = SIGNAL_MAX
};
static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
pa_dbusiface_stream *s = userdata;
dbus_uint32_t idx;
pa_assert(conn);
pa_assert(msg);
pa_assert(s);
idx = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->index : s->source_output->index;
pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx);
}
/* The returned string has to be freed with pa_xfree() by the caller. */
static char *stream_to_string(pa_dbusiface_stream *s) {
if (s->type == STREAM_TYPE_PLAYBACK)
return pa_sprintf_malloc("Playback stream %u", (unsigned) s->sink_input->index);
else
return pa_sprintf_malloc("Record stream %u", (unsigned) s->source_output->index);
}
static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) {
pa_dbusiface_stream *s = userdata;
const char *driver = NULL;
pa_assert(conn);
pa_assert(msg);
pa_assert(s);
driver = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->driver : s->source_output->driver;
if (!driver) {
char *str = stream_to_string(s);
pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "%s doesn't have a driver.", str);
pa_xfree(str);
return;
}
pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &driver);
}
static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) {
pa_dbusiface_stream *s = userdata;
pa_module *owner_module = NULL;
const char *object_path = NULL;
pa_assert(conn);
pa_assert(msg);
pa_assert(s);
owner_module = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->module : s->source_output->module;
if (!owner_module) {
char *str = stream_to_string(s);
pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "%s doesn't have an owner module.", str);
pa_xfree(str);
return;
}
object_path = pa_dbusiface_core_get_module_path(s->core, owner_module);
pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
}
static void handle_get_client(DBusConnection *conn, DBusMessage *msg, void *userdata) {
pa_dbusiface_stream *s = userdata;
pa_client *client = NULL;
const char *object_path = NULL;
pa_assert(conn);
pa_assert(msg);
pa_assert(s);
client = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->client : s->source_output->client;
if (!client) {
char *str = stream_to_string(s);
pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "%s isn't associated to any client.", str);
pa_xfree(str);
return;
}
object_path = pa_dbusiface_core_get_client_path(s->core, client);
pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
}
static void handle_get_device(DBusConnection *conn, DBusMessage *msg, void *userdata) {
pa_dbusiface_stream *s = userdata;
const char *device = NULL;
pa_assert(conn);
pa_assert(msg);
pa_assert(s);
if (s->type == STREAM_TYPE_PLAYBACK)
device = pa_dbusiface_core_get_sink_path(s->core, s->sink);
else
device = pa_dbusiface_core_get_source_path(s->core, s->source);
pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &device);
}
static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata) {
pa_dbusiface_stream *s = userdata;
dbus_uint32_t sample_format = 0;
pa_assert(conn);
pa_assert(msg);
pa_assert(s);
sample_format = (s->type == STREAM_TYPE_PLAYBACK)
? s->sink_input->sample_spec.format
: s->source_output->sample_spec.format;
pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_format);
}
static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata) {
pa_dbusiface_stream *s = userdata;
pa_assert(conn);
pa_assert(msg);
pa_assert(s);
pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &s->sample_rate);
}
static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata) {
pa_dbusiface_stream *s = userdata;
pa_channel_map *channel_map = NULL;
dbus_uint32_t channels[PA_CHANNELS_MAX];
unsigned i = 0;
pa_assert(conn);
pa_assert(msg);
pa_assert(s);
channel_map = (s->type == STREAM_TYPE_PLAYBACK) ? &s->sink_input->channel_map : &s->source_output->channel_map;
for (i = 0; i < channel_map->channels; ++i)
channels[i] = channel_map->map[i];
pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, channels, channel_map->channels);
}
static void handle_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) {
pa_dbusiface_stream *s = userdata;
dbus_uint32_t volume[PA_CHANNELS_MAX];
unsigned i = 0;
pa_assert(conn);
pa_assert(msg);
pa_assert(s);
if (!s->has_volume) {
char *str = stream_to_string(s);
pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "%s doesn't have volume.", str);
pa_xfree(str);
return;
}
for (i = 0; i < s->volume.channels; ++i)
volume[i] = s->volume.values[i];
pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, volume, s->volume.channels);
}
static void handle_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
pa_dbusiface_stream *s = userdata;
bool volume_writable = true;
DBusMessageIter array_iter;
int stream_channels = 0;
dbus_uint32_t *volume = NULL;
int n_volume_entries = 0;
pa_cvolume new_vol;
int i = 0;
pa_assert(conn);
pa_assert(msg);
pa_assert(iter);
pa_assert(s);
volume_writable = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->volume_writable : false;
if (!s->has_volume || !volume_writable) {
char *str = stream_to_string(s);
if (!s->has_volume)
pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "%s doesn't have volume.", str);
else if (!volume_writable)
pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "%s has read-only volume.", str);
pa_xfree(str);
return;
}
stream_channels = s->sink_input->channel_map.channels;
dbus_message_iter_recurse(iter, &array_iter);
dbus_message_iter_get_fixed_array(&array_iter, &volume, &n_volume_entries);
if (n_volume_entries != stream_channels && n_volume_entries != 1) {
pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS,
"Expected %u volume entries, got %u.", stream_channels, n_volume_entries);
return;
}
pa_cvolume_init(&new_vol);
new_vol.channels = n_volume_entries;
for (i = 0; i < n_volume_entries; ++i) {
if (!PA_VOLUME_IS_VALID(volume[i])) {
pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume: %u", volume[i]);
return;
}
new_vol.values[i] = volume[i];
}
pa_sink_input_set_volume(s->sink_input, &new_vol, true, true);
pa_dbus_send_empty_reply(conn, msg);
}
static void handle_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) {
pa_dbusiface_stream *s = userdata;
pa_assert(conn);
pa_assert(msg);
pa_assert(s);
if (s->type == STREAM_TYPE_RECORD) {
pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Record streams don't have mute.");
return;
}
pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &s->mute);
}
static void handle_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
pa_dbusiface_stream *s = userdata;
dbus_bool_t mute = FALSE;
pa_assert(conn);
pa_assert(msg);
pa_assert(iter);
pa_assert(s);
dbus_message_iter_get_basic(iter, &mute);
if (s->type == STREAM_TYPE_RECORD) {
pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Record streams don't have mute.");
return;
}
pa_sink_input_set_mute(s->sink_input, mute, true);
pa_dbus_send_empty_reply(conn, msg);
}
static void handle_get_buffer_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) {
pa_dbusiface_stream *s = userdata;
dbus_uint64_t buffer_latency = 0;
pa_assert(conn);
pa_assert(msg);
pa_assert(s);
if (s->type == STREAM_TYPE_PLAYBACK)
buffer_latency = pa_sink_input_get_latency(s->sink_input, NULL);
else
buffer_latency = pa_source_output_get_latency(s->source_output, NULL);
pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &buffer_latency);
}
static void handle_get_device_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) {
pa_dbusiface_stream *s = userdata;
dbus_uint64_t device_latency = 0;
pa_assert(conn);
pa_assert(msg);
pa_assert(s);
if (s->type == STREAM_TYPE_PLAYBACK)
pa_sink_input_get_latency(s->sink_input, &device_latency);
else
pa_source_output_get_latency(s->source_output, &device_latency);
pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &device_latency);
}
static void handle_get_resample_method(DBusConnection *conn, DBusMessage *msg, void *userdata) {
pa_dbusiface_stream *s = userdata;
const char *resample_method = NULL;
pa_assert(conn);
pa_assert(msg);
pa_assert(s);
if (s->type == STREAM_TYPE_PLAYBACK)
resample_method = pa_resample_method_to_string(s->sink_input->actual_resample_method);
else
resample_method = pa_resample_method_to_string(s->source_output->actual_resample_method);
if (!resample_method)
resample_method = "";
pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &resample_method);
}
static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) {
pa_dbusiface_stream *s = userdata;
pa_assert(conn);
pa_assert(msg);
pa_assert(s);
pa_dbus_send_proplist_variant_reply(conn, msg, s->proplist);
}
static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
pa_dbusiface_stream *s = userdata;
DBusMessage *reply = NULL;
DBusMessageIter msg_iter;
DBusMessageIter dict_iter;
dbus_uint32_t idx = 0;
const char *driver = NULL;
pa_module *owner_module = NULL;
const char *owner_module_path = NULL;
pa_client *client = NULL;
const char *client_path = NULL;
const char *device = NULL;
dbus_uint32_t sample_format = 0;
pa_channel_map *channel_map = NULL;
dbus_uint32_t channels[PA_CHANNELS_MAX];
dbus_uint32_t volume[PA_CHANNELS_MAX];
dbus_uint64_t buffer_latency = 0;
dbus_uint64_t device_latency = 0;
const char *resample_method = NULL;
unsigned i = 0;
pa_assert(conn);
pa_assert(msg);
pa_assert(s);
if (s->has_volume) {
for (i = 0; i < s->volume.channels; ++i)
volume[i] = s->volume.values[i];
}
if (s->type == STREAM_TYPE_PLAYBACK) {
idx = s->sink_input->index;
driver = s->sink_input->driver;
owner_module = s->sink_input->module;
client = s->sink_input->client;
device = pa_dbusiface_core_get_sink_path(s->core, s->sink);
sample_format = s->sink_input->sample_spec.format;
channel_map = &s->sink_input->channel_map;
buffer_latency = pa_sink_input_get_latency(s->sink_input, &device_latency);
resample_method = pa_resample_method_to_string(s->sink_input->actual_resample_method);
} else {
idx = s->source_output->index;
driver = s->source_output->driver;
owner_module = s->source_output->module;
client = s->source_output->client;
device = pa_dbusiface_core_get_source_path(s->core, s->source);
sample_format = s->source_output->sample_spec.format;
channel_map = &s->source_output->channel_map;
buffer_latency = pa_source_output_get_latency(s->source_output, &device_latency);
resample_method = pa_resample_method_to_string(s->source_output->actual_resample_method);
}
if (owner_module)
owner_module_path = pa_dbusiface_core_get_module_path(s->core, owner_module);
if (client)
client_path = pa_dbusiface_core_get_client_path(s->core, client);
for (i = 0; i < channel_map->channels; ++i)
channels[i] = channel_map->map[i];
if (!resample_method)
resample_method = "";
pa_assert_se((reply = dbus_message_new_method_return(msg)));
dbus_message_iter_init_append(reply, &msg_iter);
pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx);
if (driver)
pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &driver);
if (owner_module)
pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_OWNER_MODULE].property_name, DBUS_TYPE_OBJECT_PATH, &owner_module_path);
if (client)
pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CLIENT].property_name, DBUS_TYPE_OBJECT_PATH, &client_path);
pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEVICE].property_name, DBUS_TYPE_OBJECT_PATH, &device);
pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_FORMAT].property_name, DBUS_TYPE_UINT32, &sample_format);
pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_RATE].property_name, DBUS_TYPE_UINT32, &s->sample_rate);
pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CHANNELS].property_name, DBUS_TYPE_UINT32, channels, channel_map->channels);
if (s->has_volume) {
pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_VOLUME].property_name, DBUS_TYPE_UINT32, volume, s->volume.channels);
pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_MUTE].property_name, DBUS_TYPE_BOOLEAN, &s->mute);
}
pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_BUFFER_LATENCY].property_name, DBUS_TYPE_UINT64, &buffer_latency);
pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEVICE_LATENCY].property_name, DBUS_TYPE_UINT64, &device_latency);
pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_RESAMPLE_METHOD].property_name, DBUS_TYPE_STRING, &resample_method);
pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, s->proplist);
pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
pa_assert_se(dbus_connection_send(conn, reply, NULL));
dbus_message_unref(reply);
}
static void handle_move(DBusConnection *conn, DBusMessage *msg, void *userdata) {
pa_dbusiface_stream *s = userdata;
const char *device = NULL;
pa_assert(conn);
pa_assert(msg);
pa_assert(s);
pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &device, DBUS_TYPE_INVALID));
if (s->type == STREAM_TYPE_PLAYBACK) {
pa_sink *sink = pa_dbusiface_core_get_sink(s->core, device);
if (!sink) {
pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such sink.", device);
return;
}
if (pa_sink_input_move_to(s->sink_input, sink, true) < 0) {
pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED,
"Moving playback stream %u to sink %s failed.", s->sink_input->index, sink->name);
return;
}
} else {
pa_source *source = pa_dbusiface_core_get_source(s->core, device);
if (!source) {
pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such source.", device);
return;
}
if (pa_source_output_move_to(s->source_output, source, true) < 0) {
pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED,
"Moving record stream %u to source %s failed.", s->source_output->index, source->name);
return;
}
}
pa_dbus_send_empty_reply(conn, msg);
}
static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata) {
pa_dbusiface_stream *s = userdata;
pa_assert(conn);
pa_assert(msg);
pa_assert(s);
if (s->type == STREAM_TYPE_PLAYBACK)
pa_sink_input_kill(s->sink_input);
else
pa_source_output_kill(s->source_output);
pa_dbus_send_empty_reply(conn, msg);
}
static void check_and_signal_rate(pa_dbusiface_stream *s) {
DBusMessage *signal_msg = NULL;
uint32_t new_sample_rate = 0;
pa_assert(s);
new_sample_rate = (s->type == STREAM_TYPE_PLAYBACK)
? s->sink_input->sample_spec.rate
: s->source_output->sample_spec.rate;
if (s->sample_rate != new_sample_rate) {
s->sample_rate = new_sample_rate;
pa_assert_se(signal_msg = dbus_message_new_signal(s->path,
PA_DBUSIFACE_STREAM_INTERFACE,
signals[SIGNAL_SAMPLE_RATE_UPDATED].name));
pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_UINT32, &s->sample_rate, DBUS_TYPE_INVALID));
pa_dbus_protocol_send_signal(s->dbus_protocol, signal_msg);
dbus_message_unref(signal_msg);
}
}
static pa_hook_result_t move_finish_cb(void *hook_data, void *call_data, void *slot_data) {
pa_dbusiface_stream *s = slot_data;
const char *new_device_path = NULL;
DBusMessage *signal_msg = NULL;
if ((s->type == STREAM_TYPE_PLAYBACK && s->sink_input != call_data) ||
(s->type == STREAM_TYPE_RECORD && s->source_output != call_data))
return PA_HOOK_OK;
if (s->type == STREAM_TYPE_PLAYBACK) {
pa_sink *new_sink = s->sink_input->sink;
if (s->sink != new_sink) {
pa_sink_unref(s->sink);
s->sink = pa_sink_ref(new_sink);
new_device_path = pa_dbusiface_core_get_sink_path(s->core, new_sink);
pa_assert_se(signal_msg = dbus_message_new_signal(s->path,
PA_DBUSIFACE_STREAM_INTERFACE,
signals[SIGNAL_DEVICE_UPDATED].name));
pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &new_device_path, DBUS_TYPE_INVALID));
pa_dbus_protocol_send_signal(s->dbus_protocol, signal_msg);
dbus_message_unref(signal_msg);
}
} else {
pa_source *new_source = s->source_output->source;
if (s->source != new_source) {
pa_source_unref(s->source);
s->source = pa_source_ref(new_source);
new_device_path = pa_dbusiface_core_get_source_path(s->core, new_source);
pa_assert_se(signal_msg = dbus_message_new_signal(s->path,
PA_DBUSIFACE_STREAM_INTERFACE,
signals[SIGNAL_DEVICE_UPDATED].name));
pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &new_device_path, DBUS_TYPE_INVALID));
pa_dbus_protocol_send_signal(s->dbus_protocol, signal_msg);
dbus_message_unref(signal_msg);
}
}
check_and_signal_rate(s);
return PA_HOOK_OK;
}
static pa_hook_result_t volume_changed_cb(void *hook_data, void *call_data, void *slot_data) {
pa_dbusiface_stream *s = slot_data;
DBusMessage *signal_msg = NULL;
unsigned i = 0;
if ((s->type == STREAM_TYPE_PLAYBACK && s->sink_input != call_data) ||
(s->type == STREAM_TYPE_RECORD && s->source_output != call_data))
return PA_HOOK_OK;
if (s->type == STREAM_TYPE_PLAYBACK && s->has_volume) {
pa_cvolume new_volume;
pa_sink_input_get_volume(s->sink_input, &new_volume, true);
if (!pa_cvolume_equal(&s->volume, &new_volume)) {
dbus_uint32_t volume[PA_CHANNELS_MAX];
dbus_uint32_t *volume_ptr = volume;
s->volume = new_volume;
for (i = 0; i < s->volume.channels; ++i)
volume[i] = s->volume.values[i];
pa_assert_se(signal_msg = dbus_message_new_signal(s->path,
PA_DBUSIFACE_STREAM_INTERFACE,
signals[SIGNAL_VOLUME_UPDATED].name));
pa_assert_se(dbus_message_append_args(signal_msg,
DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &volume_ptr, s->volume.channels,
DBUS_TYPE_INVALID));
pa_dbus_protocol_send_signal(s->dbus_protocol, signal_msg);
dbus_message_unref(signal_msg);
}
}
return PA_HOOK_OK;
}
static pa_hook_result_t mute_changed_cb(void *hook_data, void *call_data, void *slot_data) {
pa_dbusiface_stream *s = slot_data;
DBusMessage *signal_msg = NULL;
if ((s->type == STREAM_TYPE_PLAYBACK && s->sink_input != call_data) ||
(s->type == STREAM_TYPE_RECORD && s->source_output != call_data))
return PA_HOOK_OK;
if (s->type == STREAM_TYPE_PLAYBACK) {
bool new_mute = false;
new_mute = s->sink_input->muted;
if (s->mute != new_mute) {
s->mute = new_mute;
pa_assert_se(signal_msg = dbus_message_new_signal(s->path,
PA_DBUSIFACE_STREAM_INTERFACE,
signals[SIGNAL_MUTE_UPDATED].name));
pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_BOOLEAN, &s->mute, DBUS_TYPE_INVALID));
pa_dbus_protocol_send_signal(s->dbus_protocol, signal_msg);
dbus_message_unref(signal_msg);
signal_msg = NULL;
}
}
return PA_HOOK_OK;
}
static pa_hook_result_t proplist_changed_cb(void *hook_data, void *call_data, void *slot_data) {
pa_dbusiface_stream *s = slot_data;
DBusMessage *signal_msg = NULL;
pa_proplist *new_proplist = NULL;
if ((s->type == STREAM_TYPE_PLAYBACK && s->sink_input != call_data) ||
(s->type == STREAM_TYPE_RECORD && s->source_output != call_data))
return PA_HOOK_OK;
new_proplist = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->proplist : s->source_output->proplist;
if (!pa_proplist_equal(s->proplist, new_proplist)) {
DBusMessageIter msg_iter;
pa_proplist_update(s->proplist, PA_UPDATE_SET, new_proplist);
pa_assert_se(signal_msg = dbus_message_new_signal(s->path,
PA_DBUSIFACE_STREAM_INTERFACE,
signals[SIGNAL_PROPERTY_LIST_UPDATED].name));
dbus_message_iter_init_append(signal_msg, &msg_iter);
pa_dbus_append_proplist(&msg_iter, s->proplist);
pa_dbus_protocol_send_signal(s->dbus_protocol, signal_msg);
dbus_message_unref(signal_msg);
}
return PA_HOOK_OK;
}
static pa_hook_result_t state_changed_cb(void *hook_data, void *call_data, void *slot_data) {
pa_dbusiface_stream *s = slot_data;
pa_assert(s);
if ((s->type == STREAM_TYPE_PLAYBACK && s->sink_input != call_data) ||
(s->type == STREAM_TYPE_RECORD && s->source_output != call_data))
return PA_HOOK_OK;
check_and_signal_rate(s);
return PA_HOOK_OK;
}
static pa_hook_result_t send_event_cb(void *hook_data, void *call_data, void *slot_data) {
pa_dbusiface_stream *s = slot_data;
DBusMessage *signal_msg = NULL;
DBusMessageIter msg_iter;
const char *name = NULL;
pa_proplist *property_list = NULL;
pa_assert(call_data);
pa_assert(s);
if (s->type == STREAM_TYPE_PLAYBACK) {
pa_sink_input_send_event_hook_data *data = call_data;
if (data->sink_input != s->sink_input)
return PA_HOOK_OK;
name = data->event;
property_list = data->data;
} else {
pa_source_output_send_event_hook_data *data = call_data;
if (data->source_output != s->source_output)
return PA_HOOK_OK;
name = data->event;
property_list = data->data;
}
pa_assert_se(signal_msg = dbus_message_new_signal(s->path,
PA_DBUSIFACE_STREAM_INTERFACE,
signals[SIGNAL_STREAM_EVENT].name));
dbus_message_iter_init_append(signal_msg, &msg_iter);
pa_assert_se(dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &name));
pa_dbus_append_proplist(&msg_iter, property_list);
pa_dbus_protocol_send_signal(s->dbus_protocol, signal_msg);
dbus_message_unref(signal_msg);
return PA_HOOK_OK;
}
pa_dbusiface_stream *pa_dbusiface_stream_new_playback(pa_dbusiface_core *core, pa_sink_input *sink_input) {
pa_dbusiface_stream *s;
pa_assert(core);
pa_assert(sink_input);
s = pa_xnew(pa_dbusiface_stream, 1);
s->core = core;
s->sink_input = pa_sink_input_ref(sink_input);
s->type = STREAM_TYPE_PLAYBACK;
s->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, PLAYBACK_OBJECT_NAME, sink_input->index);
s->sink = pa_sink_ref(sink_input->sink);
s->sample_rate = sink_input->sample_spec.rate;
s->has_volume = pa_sink_input_is_volume_readable(sink_input);
if (s->has_volume)
pa_sink_input_get_volume(sink_input, &s->volume, true);
else
pa_cvolume_init(&s->volume);
s->mute = sink_input->muted;
s->proplist = pa_proplist_copy(sink_input->proplist);
s->dbus_protocol = pa_dbus_protocol_get(sink_input->core);
s->send_event_slot = pa_hook_connect(&sink_input->core->hooks[PA_CORE_HOOK_SINK_INPUT_SEND_EVENT],
PA_HOOK_NORMAL,
send_event_cb,
s);
s->move_finish_slot = pa_hook_connect(&sink_input->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH],
PA_HOOK_NORMAL, move_finish_cb, s);
s->volume_changed_slot = pa_hook_connect(&sink_input->core->hooks[PA_CORE_HOOK_SINK_INPUT_VOLUME_CHANGED],
PA_HOOK_NORMAL, volume_changed_cb, s);
s->mute_changed_slot = pa_hook_connect(&sink_input->core->hooks[PA_CORE_HOOK_SINK_INPUT_MUTE_CHANGED],
PA_HOOK_NORMAL, mute_changed_cb, s);
s->proplist_changed_slot = pa_hook_connect(&sink_input->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED],
PA_HOOK_NORMAL, proplist_changed_cb, s);
s->state_changed_slot = pa_hook_connect(&sink_input->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED],
PA_HOOK_NORMAL, state_changed_cb, s);
pa_assert_se(pa_dbus_protocol_add_interface(s->dbus_protocol, s->path, &stream_interface_info, s) >= 0);
return s;
}
pa_dbusiface_stream *pa_dbusiface_stream_new_record(pa_dbusiface_core *core, pa_source_output *source_output) {
pa_dbusiface_stream *s;
pa_assert(core);
pa_assert(source_output);
s = pa_xnew(pa_dbusiface_stream, 1);
s->core = core;
s->source_output = pa_source_output_ref(source_output);
s->type = STREAM_TYPE_RECORD;
s->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, RECORD_OBJECT_NAME, source_output->index);
s->source = pa_source_ref(source_output->source);
s->sample_rate = source_output->sample_spec.rate;
pa_cvolume_init(&s->volume);
s->mute = false;
s->proplist = pa_proplist_copy(source_output->proplist);
s->has_volume = false;
s->dbus_protocol = pa_dbus_protocol_get(source_output->core);
s->send_event_slot = pa_hook_connect(&source_output->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_SEND_EVENT],
PA_HOOK_NORMAL,
send_event_cb,
s);
s->move_finish_slot = pa_hook_connect(&source_output->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FINISH],
PA_HOOK_NORMAL, move_finish_cb, s);
s->volume_changed_slot = pa_hook_connect(&source_output->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_VOLUME_CHANGED],
PA_HOOK_NORMAL, volume_changed_cb, s);
s->mute_changed_slot = pa_hook_connect(&source_output->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MUTE_CHANGED],
PA_HOOK_NORMAL, mute_changed_cb, s);
s->proplist_changed_slot = pa_hook_connect(&source_output->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED],
PA_HOOK_NORMAL, proplist_changed_cb, s);
s->state_changed_slot = pa_hook_connect(&source_output->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED],
PA_HOOK_NORMAL, state_changed_cb, s);
pa_assert_se(pa_dbus_protocol_add_interface(s->dbus_protocol, s->path, &stream_interface_info, s) >= 0);
return s;
}
void pa_dbusiface_stream_free(pa_dbusiface_stream *s) {
pa_assert(s);
pa_assert_se(pa_dbus_protocol_remove_interface(s->dbus_protocol, s->path, stream_interface_info.name) >= 0);
if (s->type == STREAM_TYPE_PLAYBACK) {
pa_sink_input_unref(s->sink_input);
pa_sink_unref(s->sink);
} else {
pa_source_output_unref(s->source_output);
pa_source_unref(s->source);
}
pa_proplist_free(s->proplist);
pa_dbus_protocol_unref(s->dbus_protocol);
pa_hook_slot_free(s->send_event_slot);
pa_hook_slot_free(s->move_finish_slot);
pa_hook_slot_free(s->volume_changed_slot);
pa_hook_slot_free(s->mute_changed_slot);
pa_hook_slot_free(s->proplist_changed_slot);
pa_hook_slot_free(s->state_changed_slot);
pa_xfree(s->path);
pa_xfree(s);
}
const char *pa_dbusiface_stream_get_path(pa_dbusiface_stream *s) {
pa_assert(s);
return s->path;
}