/*** This file is part of PulseAudio. Copyright 2009 Tanu Kaskinen Copyright 2009 Vincent Filali-Ansary 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 . ***/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #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; }