diff options
Diffstat (limited to 'src/modules/dbus')
-rw-r--r-- | src/modules/dbus/iface-card-profile.c | 228 | ||||
-rw-r--r-- | src/modules/dbus/iface-card-profile.h | 50 | ||||
-rw-r--r-- | src/modules/dbus/iface-card.c | 561 | ||||
-rw-r--r-- | src/modules/dbus/iface-card.h | 45 | ||||
-rw-r--r-- | src/modules/dbus/iface-client.c | 459 | ||||
-rw-r--r-- | src/modules/dbus/iface-client.h | 45 | ||||
-rw-r--r-- | src/modules/dbus/iface-core.c | 2197 | ||||
-rw-r--r-- | src/modules/dbus/iface-core.h | 52 | ||||
-rw-r--r-- | src/modules/dbus/iface-device-port.c | 190 | ||||
-rw-r--r-- | src/modules/dbus/iface-device-port.h | 50 | ||||
-rw-r--r-- | src/modules/dbus/iface-device.c | 1316 | ||||
-rw-r--r-- | src/modules/dbus/iface-device.h | 53 | ||||
-rw-r--r-- | src/modules/dbus/iface-memstats.c | 231 | ||||
-rw-r--r-- | src/modules/dbus/iface-memstats.h | 45 | ||||
-rw-r--r-- | src/modules/dbus/iface-module.c | 336 | ||||
-rw-r--r-- | src/modules/dbus/iface-module.h | 45 | ||||
-rw-r--r-- | src/modules/dbus/iface-sample.c | 519 | ||||
-rw-r--r-- | src/modules/dbus/iface-sample.h | 45 | ||||
-rw-r--r-- | src/modules/dbus/iface-stream.c | 894 | ||||
-rw-r--r-- | src/modules/dbus/iface-stream.h | 47 | ||||
-rw-r--r-- | src/modules/dbus/module-dbus-protocol.c | 607 |
21 files changed, 8015 insertions, 0 deletions
diff --git a/src/modules/dbus/iface-card-profile.c b/src/modules/dbus/iface-card-profile.c new file mode 100644 index 00000000..004e2e88 --- /dev/null +++ b/src/modules/dbus/iface-card-profile.c @@ -0,0 +1,228 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> + +#include "iface-card-profile.h" + +#define OBJECT_NAME "profile" + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_description(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_priority(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +struct pa_dbusiface_card_profile { + uint32_t index; + pa_card_profile *profile; + char *path; + pa_dbus_protocol *dbus_protocol; +}; + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_DESCRIPTION, + PROPERTY_HANDLER_SINKS, + PROPERTY_HANDLER_SOURCES, + PROPERTY_HANDLER_PRIORITY, + 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_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL }, + [PROPERTY_HANDLER_DESCRIPTION] = { .property_name = "Description", .type = "s", .get_cb = handle_get_description, .set_cb = NULL }, + [PROPERTY_HANDLER_SINKS] = { .property_name = "Sinks", .type = "u", .get_cb = handle_get_sinks, .set_cb = NULL }, + [PROPERTY_HANDLER_SOURCES] = { .property_name = "Sources", .type = "u", .get_cb = handle_get_sources, .set_cb = NULL }, + [PROPERTY_HANDLER_PRIORITY] = { .property_name = "Priority", .type = "u", .get_cb = handle_get_priority, .set_cb = NULL }, +}; + +static pa_dbus_interface_info profile_interface_info = { + .name = PA_DBUSIFACE_CARD_PROFILE_INTERFACE, + .method_handlers = NULL, + .n_method_handlers = 0, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = NULL, + .n_signals = 0 +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &p->index); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &p->profile->name); +} + +static void handle_get_description(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &p->profile->description); +} + +static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + dbus_uint32_t sinks = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + sinks = p->profile->n_sinks; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sinks); +} + +static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + dbus_uint32_t sources = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + sources = p->profile->n_sources; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sources); +} + +static void handle_get_priority(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + dbus_uint32_t priority = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + priority = p->profile->priority; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &priority); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card_profile *p = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t sinks = 0; + dbus_uint32_t sources = 0; + dbus_uint32_t priority = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + sinks = p->profile->n_sinks; + sources = p->profile->n_sources; + priority = p->profile->priority; + + 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, &p->index); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &p->profile->name); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DESCRIPTION].property_name, DBUS_TYPE_STRING, &p->profile->description); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SINKS].property_name, DBUS_TYPE_UINT32, &sinks); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SOURCES].property_name, DBUS_TYPE_UINT32, &sources); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PRIORITY].property_name, DBUS_TYPE_UINT32, &priority); + + 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); +} + +pa_dbusiface_card_profile *pa_dbusiface_card_profile_new( + pa_dbusiface_card *card, + pa_core *core, + pa_card_profile *profile, + uint32_t idx) { + pa_dbusiface_card_profile *p = NULL; + + pa_assert(card); + pa_assert(core); + pa_assert(profile); + + p = pa_xnew(pa_dbusiface_card_profile, 1); + p->index = idx; + p->profile = profile; + p->path = pa_sprintf_malloc("%s/%s%u", pa_dbusiface_card_get_path(card), OBJECT_NAME, idx); + p->dbus_protocol = pa_dbus_protocol_get(core); + + pa_assert_se(pa_dbus_protocol_add_interface(p->dbus_protocol, p->path, &profile_interface_info, p) >= 0); + + return p; +} + +void pa_dbusiface_card_profile_free(pa_dbusiface_card_profile *p) { + pa_assert(p); + + pa_assert_se(pa_dbus_protocol_remove_interface(p->dbus_protocol, p->path, profile_interface_info.name) >= 0); + + pa_dbus_protocol_unref(p->dbus_protocol); + + pa_xfree(p->path); + pa_xfree(p); +} + +const char *pa_dbusiface_card_profile_get_path(pa_dbusiface_card_profile *p) { + pa_assert(p); + + return p->path; +} + +const char *pa_dbusiface_card_profile_get_name(pa_dbusiface_card_profile *p) { + pa_assert(p); + + return p->profile->name; +} diff --git a/src/modules/dbus/iface-card-profile.h b/src/modules/dbus/iface-card-profile.h new file mode 100644 index 00000000..a09767f8 --- /dev/null +++ b/src/modules/dbus/iface-card-profile.h @@ -0,0 +1,50 @@ +#ifndef foodbusifacecardprofilehfoo +#define foodbusifacecardprofilehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.CardProfile. + * + * See http://pulseaudio.org/wiki/DBusInterface for the CardProfile interface + * documentation. + */ + +#include <pulsecore/core-scache.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-card.h" + +#define PA_DBUSIFACE_CARD_PROFILE_INTERFACE PA_DBUS_CORE_INTERFACE ".CardProfile" + +typedef struct pa_dbusiface_card_profile pa_dbusiface_card_profile; + +pa_dbusiface_card_profile *pa_dbusiface_card_profile_new( + pa_dbusiface_card *card, + pa_core *core, + pa_card_profile *profile, + uint32_t idx); +void pa_dbusiface_card_profile_free(pa_dbusiface_card_profile *p); + +const char *pa_dbusiface_card_profile_get_path(pa_dbusiface_card_profile *p); +const char *pa_dbusiface_card_profile_get_name(pa_dbusiface_card_profile *p); + +#endif diff --git a/src/modules/dbus/iface-card.c b/src/modules/dbus/iface-card.c new file mode 100644 index 00000000..1714df36 --- /dev/null +++ b/src/modules/dbus/iface-card.c @@ -0,0 +1,561 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-card-profile.h" + +#include "iface-card.h" + +#define OBJECT_NAME "card" + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(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_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_profiles(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_active_profile(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_active_profile(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, 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_get_profile_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata); + +struct pa_dbusiface_card { + pa_dbusiface_core *core; + + pa_card *card; + char *path; + pa_hashmap *profiles; + uint32_t next_profile_index; + pa_card_profile *active_profile; + pa_proplist *proplist; + + pa_dbus_protocol *dbus_protocol; + pa_subscription *subscription; +}; + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_DRIVER, + PROPERTY_HANDLER_OWNER_MODULE, + PROPERTY_HANDLER_SINKS, + PROPERTY_HANDLER_SOURCES, + PROPERTY_HANDLER_PROFILES, + PROPERTY_HANDLER_ACTIVE_PROFILE, + 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_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .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_SINKS] = { .property_name = "Sinks", .type = "ao", .get_cb = handle_get_sinks, .set_cb = NULL }, + [PROPERTY_HANDLER_SOURCES] = { .property_name = "Sources", .type = "ao", .get_cb = handle_get_sources, .set_cb = NULL }, + [PROPERTY_HANDLER_PROFILES] = { .property_name = "Profiles", .type = "ao", .get_cb = handle_get_profiles, .set_cb = NULL }, + [PROPERTY_HANDLER_ACTIVE_PROFILE] = { .property_name = "ActiveProfile", .type = "o", .get_cb = handle_get_active_profile, .set_cb = handle_set_active_profile }, + [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_GET_PROFILE_BY_NAME, + METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info get_profile_by_name_args[] = { { "name", "s", "in" }, { "profile", "o", "out" } }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_GET_PROFILE_BY_NAME] = { + .method_name = "GetProfileByName", + .arguments = get_profile_by_name_args, + .n_arguments = sizeof(get_profile_by_name_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_profile_by_name } +}; + +enum signal_index { + SIGNAL_ACTIVE_PROFILE_UPDATED, + SIGNAL_PROPERTY_LIST_UPDATED, + SIGNAL_MAX +}; + +static pa_dbus_arg_info active_profile_updated_args[] = { { "profile", "o", NULL } }; +static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_ACTIVE_PROFILE_UPDATED] = { .name = "ActiveProfileUpdated", .arguments = active_profile_updated_args, .n_arguments = 1 }, + [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 } +}; + +static pa_dbus_interface_info card_interface_info = { + .name = PA_DBUSIFACE_CARD_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_card *c = userdata; + dbus_uint32_t idx; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + idx = c->card->index; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &c->card->name); +} + +static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &c->card->driver); +} + +static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + const char *owner_module; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (!c->card->module) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Card %s doesn't have an owner module.", c->card->name); + return; + } + + owner_module = pa_dbusiface_core_get_module_path(c->core, c->card->module); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &owner_module); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_sinks(pa_dbusiface_card *c, unsigned *n) { + const char **sinks = NULL; + unsigned i = 0; + uint32_t idx = 0; + pa_sink *sink = NULL; + + pa_assert(c); + pa_assert(n); + + *n = pa_idxset_size(c->card->sinks); + + if (*n == 0) + return NULL; + + sinks = pa_xnew(const char *, *n); + + PA_IDXSET_FOREACH(sink, c->card->sinks, idx) { + sinks[i] = pa_dbusiface_core_get_sink_path(c->core, sink); + ++i; + } + + return sinks; +} + +static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + const char **sinks; + unsigned n_sinks; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + sinks = get_sinks(c, &n_sinks); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sinks, n_sinks); + + pa_xfree(sinks); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_sources(pa_dbusiface_card *c, unsigned *n) { + const char **sources = NULL; + unsigned i = 0; + uint32_t idx = 0; + pa_source *source = NULL; + + pa_assert(c); + pa_assert(n); + + *n = pa_idxset_size(c->card->sources); + + if (*n == 0) + return NULL; + + sources = pa_xnew(const char *, *n); + + PA_IDXSET_FOREACH(source, c->card->sinks, idx) { + sources[i] = pa_dbusiface_core_get_source_path(c->core, source); + ++i; + } + + return sources; +} + +static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + const char **sources; + unsigned n_sources; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + sources = get_sources(c, &n_sources); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sources, n_sources); + + pa_xfree(sources); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_profiles(pa_dbusiface_card *c, unsigned *n) { + const char **profiles; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_card_profile *profile; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->profiles); + + if (*n == 0) + return NULL; + + profiles = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(profile, c->profiles, state) + profiles[i++] = pa_dbusiface_card_profile_get_path(profile); + + return profiles; +} + +static void handle_get_profiles(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + const char **profiles; + unsigned n_profiles; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + profiles = get_profiles(c, &n_profiles); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, profiles, n_profiles); + + pa_xfree(profiles); +} + +static void handle_get_active_profile(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + const char *active_profile; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (!c->active_profile) { + pa_assert(pa_hashmap_isempty(c->profiles)); + + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "The card %s has no profiles, and therefore there's no active profile either.", c->card->name); + return; + } + + active_profile = pa_dbusiface_card_profile_get_path(pa_hashmap_get(c->profiles, c->active_profile->name)); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &active_profile); +} + +static void handle_set_active_profile(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_card *c = userdata; + const char *new_active_path; + pa_dbusiface_card_profile *new_active; + int r; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(c); + + if (!c->active_profile) { + pa_assert(pa_hashmap_isempty(c->profiles)); + + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "The card %s has no profiles, and therefore there's no active profile either.", + c->card->name); + return; + } + + dbus_message_iter_get_basic(iter, &new_active_path); + + if (!(new_active = pa_hashmap_get(c->profiles, new_active_path))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such profile.", new_active_path); + return; + } + + if ((r = pa_card_set_profile(c->card, pa_dbusiface_card_profile_get_name(new_active), TRUE)) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, + "Internal error in PulseAudio: pa_card_set_profile() failed with error code %i.", r); + return; + } + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_dbus_send_proplist_variant_reply(conn, msg, c->proplist); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t idx; + const char *owner_module = NULL; + const char **sinks = NULL; + unsigned n_sinks = 0; + const char **sources = NULL; + unsigned n_sources = 0; + const char **profiles = NULL; + unsigned n_profiles = 0; + const char *active_profile = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + idx = c->card->index; + if (c->card->module) + owner_module = pa_dbusiface_core_get_module_path(c->core, c->card->module); + sinks = get_sinks(c, &n_sinks); + sources = get_sources(c, &n_sources); + profiles = get_profiles(c, &n_profiles); + if (c->active_profile) + active_profile = pa_dbusiface_card_profile_get_path(pa_hashmap_get(c->profiles, c->active_profile->name)); + + 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); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &c->card->name); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &c->card->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); + + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SINKS].property_name, DBUS_TYPE_OBJECT_PATH, sinks, n_sinks); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SOURCES].property_name, DBUS_TYPE_OBJECT_PATH, sources, n_sources); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROFILES].property_name, DBUS_TYPE_OBJECT_PATH, profiles, n_profiles); + + if (active_profile) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACTIVE_PROFILE].property_name, DBUS_TYPE_OBJECT_PATH, &active_profile); + + pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, c->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); + + pa_xfree(sinks); + pa_xfree(sources); + pa_xfree(profiles); +} + +static void handle_get_profile_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_card *c = userdata; + const char *profile_name = NULL; + pa_dbusiface_card_profile *profile = NULL; + const char *profile_path = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &profile_name, DBUS_TYPE_INVALID)); + + if (!(profile = pa_hashmap_get(c->profiles, profile_name))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such profile on card %s.", profile_name, c->card->name); + return; + } + + profile_path = pa_dbusiface_card_profile_get_path(profile); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &profile_path); +} + +static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_card *c = userdata; + DBusMessage *signal = NULL; + + pa_assert(core); + pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_CARD); + pa_assert(c); + + /* We can't use idx != c->card->index, because the c->card pointer may + * be stale at this point. */ + if (pa_idxset_get_by_index(core->cards, idx) != c->card) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + if (c->active_profile != c->card->active_profile) { + const char *object_path; + + c->active_profile = c->card->active_profile; + object_path = pa_dbusiface_card_profile_get_path(pa_hashmap_get(c->profiles, c->active_profile->name)); + + pa_assert_se(signal = dbus_message_new_signal(c->path, + PA_DBUSIFACE_CARD_INTERFACE, + signals[SIGNAL_ACTIVE_PROFILE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + + if (!pa_proplist_equal(c->proplist, c->card->proplist)) { + DBusMessageIter msg_iter; + + pa_proplist_update(c->proplist, PA_UPDATE_SET, c->card->proplist); + + pa_assert_se(signal = dbus_message_new_signal(c->path, + PA_DBUSIFACE_CARD_INTERFACE, + signals[SIGNAL_PROPERTY_LIST_UPDATED].name)); + dbus_message_iter_init_append(signal, &msg_iter); + pa_dbus_append_proplist(&msg_iter, c->proplist); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } +} + +pa_dbusiface_card *pa_dbusiface_card_new(pa_dbusiface_core *core, pa_card *card) { + pa_dbusiface_card *c = NULL; + + pa_assert(core); + pa_assert(card); + + c = pa_xnew0(pa_dbusiface_card, 1); + c->core = core; + c->card = card; + c->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, card->index); + c->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + c->next_profile_index = 0; + c->active_profile = NULL; + c->proplist = pa_proplist_copy(card->proplist); + c->dbus_protocol = pa_dbus_protocol_get(card->core); + c->subscription = pa_subscription_new(card->core, PA_SUBSCRIPTION_MASK_CARD, subscription_cb, c); + + if (card->profiles) { + pa_card_profile *profile; + void *state = NULL; + + PA_HASHMAP_FOREACH(profile, card->profiles, state) { + pa_dbusiface_card_profile *p = pa_dbusiface_card_profile_new(c, card->core, profile, c->next_profile_index++); + pa_hashmap_put(c->profiles, pa_dbusiface_card_profile_get_name(p), p); + } + pa_assert_se(c->active_profile = card->active_profile); + } + + pa_assert_se(pa_dbus_protocol_add_interface(c->dbus_protocol, c->path, &card_interface_info, c) >= 0); + + return c; +} + +static void profile_free_cb(void *p, void *userdata) { + pa_dbusiface_card_profile *profile = p; + + pa_assert(profile); + + pa_dbusiface_card_profile_free(profile); +} + +void pa_dbusiface_card_free(pa_dbusiface_card *c) { + pa_assert(c); + + pa_assert_se(pa_dbus_protocol_remove_interface(c->dbus_protocol, c->path, card_interface_info.name) >= 0); + + pa_hashmap_free(c->profiles, profile_free_cb, NULL); + pa_proplist_free(c->proplist); + pa_dbus_protocol_unref(c->dbus_protocol); + pa_subscription_free(c->subscription); + + pa_xfree(c->path); + pa_xfree(c); +} + +const char *pa_dbusiface_card_get_path(pa_dbusiface_card *c) { + pa_assert(c); + + return c->path; +} diff --git a/src/modules/dbus/iface-card.h b/src/modules/dbus/iface-card.h new file mode 100644 index 00000000..e2c08a3b --- /dev/null +++ b/src/modules/dbus/iface-card.h @@ -0,0 +1,45 @@ +#ifndef foodbusifacecardhfoo +#define foodbusifacecardhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.Card. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Card interface + * documentation. + */ + +#include <pulsecore/card.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-core.h" + +#define PA_DBUSIFACE_CARD_INTERFACE PA_DBUS_CORE_INTERFACE ".Card" + +typedef struct pa_dbusiface_card pa_dbusiface_card; + +pa_dbusiface_card *pa_dbusiface_card_new(pa_dbusiface_core *core, pa_card *card); +void pa_dbusiface_card_free(pa_dbusiface_card *c); + +const char *pa_dbusiface_card_get_path(pa_dbusiface_card *c); + +#endif diff --git a/src/modules/dbus/iface-client.c b/src/modules/dbus/iface-client.c new file mode 100644 index 00000000..546370f9 --- /dev/null +++ b/src/modules/dbus/iface-client.c @@ -0,0 +1,459 @@ +/*** + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-client.h" + +#define OBJECT_NAME "client" + +struct pa_dbusiface_client { + pa_dbusiface_core *core; + + pa_client *client; + char *path; + pa_proplist *proplist; + + pa_dbus_protocol *dbus_protocol; + pa_subscription *subscription; +}; + +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_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_record_streams(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_kill(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_update_properties(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_remove_properties(DBusConnection *conn, DBusMessage *msg, void *userdata); + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_DRIVER, + PROPERTY_HANDLER_OWNER_MODULE, + PROPERTY_HANDLER_PLAYBACK_STREAMS, + PROPERTY_HANDLER_RECORD_STREAMS, + 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_PLAYBACK_STREAMS] = { .property_name = "PlaybackStreams", .type = "ao", .get_cb = handle_get_playback_streams, .set_cb = NULL }, + [PROPERTY_HANDLER_RECORD_STREAMS] = { .property_name = "RecordStreams", .type = "ao", .get_cb = handle_get_record_streams, .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_KILL, + METHOD_HANDLER_UPDATE_PROPERTIES, + METHOD_HANDLER_REMOVE_PROPERTIES, + METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info update_properties_args[] = { { "property_list", "a{say}", "in" }, { "update_mode", "u", "in" } }; +static pa_dbus_arg_info remove_properties_args[] = { { "keys", "as", "in" } }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_KILL] = { + .method_name = "Kill", + .arguments = NULL, + .n_arguments = 0, + .receive_cb = handle_kill }, + [METHOD_HANDLER_UPDATE_PROPERTIES] = { + .method_name = "UpdateProperties", + .arguments = update_properties_args, + .n_arguments = sizeof(update_properties_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_update_properties }, + [METHOD_HANDLER_REMOVE_PROPERTIES] = { + .method_name = "RemoveProperties", + .arguments = remove_properties_args, + .n_arguments = sizeof(remove_properties_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_remove_properties } +}; + +enum signal_index { + SIGNAL_PROPERTY_LIST_UPDATED, + SIGNAL_CLIENT_EVENT, + SIGNAL_MAX +}; + +static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } }; +static pa_dbus_arg_info client_event_args[] = { { "name", "s", NULL }, + { "property_list", "a{say}", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 }, + /* ClientEvent is sent from module-dbus-protocol.c. */ + [SIGNAL_CLIENT_EVENT] = { .name = "ClientEvent", .arguments = client_event_args, .n_arguments = 1 } +}; + +static pa_dbus_interface_info client_interface_info = { + .name = PA_DBUSIFACE_CLIENT_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_client *c = userdata; + dbus_uint32_t idx = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + idx = c->client->index; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx); +} + +static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &c->client->driver); +} + +static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + const char *owner_module = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (!c->client->module) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Client %d doesn't have an owner module.", c->client->index); + return; + } + + owner_module = pa_dbusiface_core_get_module_path(c->core, c->client->module); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &owner_module); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_playback_streams(pa_dbusiface_client *c, unsigned *n) { + const char **playback_streams = NULL; + unsigned i = 0; + uint32_t idx = 0; + pa_sink_input *sink_input = NULL; + + pa_assert(c); + pa_assert(n); + + *n = pa_idxset_size(c->client->sink_inputs); + + if (*n == 0) + return NULL; + + playback_streams = pa_xnew(const char *, *n); + + PA_IDXSET_FOREACH(sink_input, c->client->sink_inputs, idx) + playback_streams[i++] = pa_dbusiface_core_get_playback_stream_path(c->core, sink_input); + + return playback_streams; +} + +static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + const char **playback_streams = NULL; + unsigned n_playback_streams = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + playback_streams = get_playback_streams(c, &n_playback_streams); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, playback_streams, n_playback_streams); + + pa_xfree(playback_streams); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_record_streams(pa_dbusiface_client *c, unsigned *n) { + const char **record_streams = NULL; + unsigned i = 0; + uint32_t idx = 0; + pa_source_output *source_output = NULL; + + pa_assert(c); + pa_assert(n); + + *n = pa_idxset_size(c->client->source_outputs); + + if (*n == 0) + return NULL; + + record_streams = pa_xnew(const char *, *n); + + PA_IDXSET_FOREACH(source_output, c->client->source_outputs, idx) + record_streams[i++] = pa_dbusiface_core_get_record_stream_path(c->core, source_output); + + return record_streams; +} + +static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + const char **record_streams = NULL; + unsigned n_record_streams = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + record_streams = get_record_streams(c, &n_record_streams); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, record_streams, n_record_streams); + + pa_xfree(record_streams); +} + +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_dbus_send_proplist_variant_reply(conn, msg, c->client->proplist); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t idx = 0; + const char *owner_module = NULL; + const char **playback_streams = NULL; + unsigned n_playback_streams = 0; + const char **record_streams = NULL; + unsigned n_record_streams = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + idx = c->client->index; + if (c->client->module) + owner_module = pa_dbusiface_core_get_module_path(c->core, c->client->module); + playback_streams = get_playback_streams(c, &n_playback_streams); + record_streams = get_record_streams(c, &n_record_streams); + + 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); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &c->client->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); + + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PLAYBACK_STREAMS].property_name, DBUS_TYPE_OBJECT_PATH, playback_streams, n_playback_streams); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_RECORD_STREAMS].property_name, DBUS_TYPE_OBJECT_PATH, record_streams, n_record_streams); + pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, c->client->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); + + pa_xfree(playback_streams); + pa_xfree(record_streams); +} + +static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + dbus_connection_ref(conn); + + pa_client_kill(c->client); + + pa_dbus_send_empty_reply(conn, msg); + + dbus_connection_unref(conn); +} + +static void handle_update_properties(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + DBusMessageIter msg_iter; + pa_proplist *property_list = NULL; + dbus_uint32_t update_mode = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (pa_dbus_protocol_get_client(c->dbus_protocol, conn) != c->client) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "Client tried to modify the property list of another client."); + return; + } + + pa_assert_se(dbus_message_iter_init(msg, &msg_iter)); + + if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter))) + return; + + dbus_message_iter_get_basic(&msg_iter, &update_mode); + + if (!(update_mode == PA_UPDATE_SET || update_mode == PA_UPDATE_MERGE || update_mode == PA_UPDATE_REPLACE)) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid update mode: %u", update_mode); + goto finish; + } + + pa_client_update_proplist(c->client, update_mode, property_list); + + pa_dbus_send_empty_reply(conn, msg); + +finish: + if (property_list) + pa_proplist_free(property_list); +} + +static void handle_remove_properties(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_client *c = userdata; + char **keys = NULL; + int n_keys = 0; + pa_bool_t changed = FALSE; + int i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (pa_dbus_protocol_get_client(c->dbus_protocol, conn) != c->client) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "Client tried to modify the property list of another client."); + return; + } + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &keys, &n_keys, DBUS_TYPE_INVALID)); + + for (i = 0; i < n_keys; ++i) + changed |= pa_proplist_unset(c->client->proplist, keys[i]) >= 0; + + pa_dbus_send_empty_reply(conn, msg); + + if (changed) + pa_subscription_post(c->client->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE, c->client->index); + + dbus_free_string_array(keys); +} + +static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_client *c = userdata; + DBusMessage *signal = NULL; + + pa_assert(core); + pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_CLIENT); + pa_assert(c); + + /* We can't use idx != c->client->index, because the c->client pointer may + * be stale at this point. */ + if (pa_idxset_get_by_index(core->clients, idx) != c->client) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + if (!pa_proplist_equal(c->proplist, c->client->proplist)) { + DBusMessageIter msg_iter; + + pa_proplist_update(c->proplist, PA_UPDATE_SET, c->client->proplist); + + pa_assert_se(signal = dbus_message_new_signal(c->path, + PA_DBUSIFACE_CLIENT_INTERFACE, + signals[SIGNAL_PROPERTY_LIST_UPDATED].name)); + dbus_message_iter_init_append(signal, &msg_iter); + pa_dbus_append_proplist(&msg_iter, c->proplist); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } +} + +pa_dbusiface_client *pa_dbusiface_client_new(pa_dbusiface_core *core, pa_client *client) { + pa_dbusiface_client *c = NULL; + + pa_assert(core); + pa_assert(client); + + c = pa_xnew(pa_dbusiface_client, 1); + c->core = core; + c->client = client; + c->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, client->index); + c->proplist = pa_proplist_copy(client->proplist); + c->dbus_protocol = pa_dbus_protocol_get(client->core); + c->subscription = pa_subscription_new(client->core, PA_SUBSCRIPTION_MASK_CLIENT, subscription_cb, c); + + pa_assert_se(pa_dbus_protocol_add_interface(c->dbus_protocol, c->path, &client_interface_info, c) >= 0); + + return c; +} + +void pa_dbusiface_client_free(pa_dbusiface_client *c) { + pa_assert(c); + + pa_assert_se(pa_dbus_protocol_remove_interface(c->dbus_protocol, c->path, client_interface_info.name) >= 0); + + pa_dbus_protocol_unref(c->dbus_protocol); + + pa_xfree(c->path); + pa_xfree(c); +} + +const char *pa_dbusiface_client_get_path(pa_dbusiface_client *c) { + pa_assert(c); + + return c->path; +} diff --git a/src/modules/dbus/iface-client.h b/src/modules/dbus/iface-client.h new file mode 100644 index 00000000..e8f151cd --- /dev/null +++ b/src/modules/dbus/iface-client.h @@ -0,0 +1,45 @@ +#ifndef foodbusifaceclienthfoo +#define foodbusifaceclienthfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.Client. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Client interface + * documentation. + */ + +#include <pulsecore/client.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-core.h" + +#define PA_DBUSIFACE_CLIENT_INTERFACE PA_DBUS_CORE_INTERFACE ".Client" + +typedef struct pa_dbusiface_client pa_dbusiface_client; + +pa_dbusiface_client *pa_dbusiface_client_new(pa_dbusiface_core *core, pa_client *client); +void pa_dbusiface_client_free(pa_dbusiface_client *c); + +const char *pa_dbusiface_client_get_path(pa_dbusiface_client *c); + +#endif diff --git a/src/modules/dbus/iface-core.c b/src/modules/dbus/iface-core.c new file mode 100644 index 00000000..169e8e55 --- /dev/null +++ b/src/modules/dbus/iface-core.c @@ -0,0 +1,2197 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <ctype.h> + +#include <dbus/dbus.h> + +#include <pulse/utf8.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/macro.h> +#include <pulsecore/namereg.h> +#include <pulsecore/protocol-dbus.h> +#include <pulsecore/socket-util.h> +#include <pulsecore/strbuf.h> + +#include "iface-card.h" +#include "iface-client.h" +#include "iface-device.h" +#include "iface-memstats.h" +#include "iface-module.h" +#include "iface-sample.h" +#include "iface-stream.h" + +#include "iface-core.h" + +#define INTERFACE_REVISION 0 + +static void handle_get_interface_revision(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_version(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_is_local(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_username(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_hostname(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_default_channels(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_default_channels(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_default_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_default_sample_format(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_default_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_default_sample_rate(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_cards(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_fallback_sink(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_fallback_sink(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_fallback_source(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_fallback_source(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); +static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_samples(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_modules(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_clients(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_my_client(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_extensions(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_card_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sink_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_source_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sample_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_upload_sample(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_load_module(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_exit(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_listen_for_signal(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_stop_listening_for_signal(DBusConnection *conn, DBusMessage *msg, void *userdata); + +struct pa_dbusiface_core { + pa_core *core; + pa_subscription *subscription; + + pa_dbus_protocol *dbus_protocol; + + pa_hashmap *cards; + pa_hashmap *sinks_by_index; + pa_hashmap *sinks_by_path; + pa_hashmap *sources_by_index; + pa_hashmap *sources_by_path; + pa_hashmap *playback_streams; + pa_hashmap *record_streams; + pa_hashmap *samples; + pa_hashmap *modules; + pa_hashmap *clients; + + pa_sink *fallback_sink; + pa_source *fallback_source; + + pa_hook_slot *extension_registered_slot; + pa_hook_slot *extension_unregistered_slot; + + pa_dbusiface_memstats *memstats; +}; + +enum property_handler_index { + PROPERTY_HANDLER_INTERFACE_REVISION, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_VERSION, + PROPERTY_HANDLER_IS_LOCAL, + PROPERTY_HANDLER_USERNAME, + PROPERTY_HANDLER_HOSTNAME, + PROPERTY_HANDLER_DEFAULT_CHANNELS, + PROPERTY_HANDLER_DEFAULT_SAMPLE_FORMAT, + PROPERTY_HANDLER_DEFAULT_SAMPLE_RATE, + PROPERTY_HANDLER_CARDS, + PROPERTY_HANDLER_SINKS, + PROPERTY_HANDLER_FALLBACK_SINK, + PROPERTY_HANDLER_SOURCES, + PROPERTY_HANDLER_FALLBACK_SOURCE, + PROPERTY_HANDLER_PLAYBACK_STREAMS, + PROPERTY_HANDLER_RECORD_STREAMS, + PROPERTY_HANDLER_SAMPLES, + PROPERTY_HANDLER_MODULES, + PROPERTY_HANDLER_CLIENTS, + PROPERTY_HANDLER_MY_CLIENT, + PROPERTY_HANDLER_EXTENSIONS, + PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_INTERFACE_REVISION] = { .property_name = "InterfaceRevision", .type = "u", .get_cb = handle_get_interface_revision, .set_cb = NULL }, + [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL }, + [PROPERTY_HANDLER_VERSION] = { .property_name = "Version", .type = "s", .get_cb = handle_get_version, .set_cb = NULL }, + [PROPERTY_HANDLER_IS_LOCAL] = { .property_name = "IsLocal", .type = "b", .get_cb = handle_get_is_local, .set_cb = NULL }, + [PROPERTY_HANDLER_USERNAME] = { .property_name = "Username", .type = "s", .get_cb = handle_get_username, .set_cb = NULL }, + [PROPERTY_HANDLER_HOSTNAME] = { .property_name = "Hostname", .type = "s", .get_cb = handle_get_hostname, .set_cb = NULL }, + [PROPERTY_HANDLER_DEFAULT_CHANNELS] = { .property_name = "DefaultChannels", .type = "au", .get_cb = handle_get_default_channels, .set_cb = handle_set_default_channels }, + [PROPERTY_HANDLER_DEFAULT_SAMPLE_FORMAT] = { .property_name = "DefaultSampleFormat", .type = "u", .get_cb = handle_get_default_sample_format, .set_cb = handle_set_default_sample_format }, + [PROPERTY_HANDLER_DEFAULT_SAMPLE_RATE] = { .property_name = "DefaultSampleRate", .type = "u", .get_cb = handle_get_default_sample_rate, .set_cb = handle_set_default_sample_rate }, + [PROPERTY_HANDLER_CARDS] = { .property_name = "Cards", .type = "ao", .get_cb = handle_get_cards, .set_cb = NULL }, + [PROPERTY_HANDLER_SINKS] = { .property_name = "Sinks", .type = "ao", .get_cb = handle_get_sinks, .set_cb = NULL }, + [PROPERTY_HANDLER_FALLBACK_SINK] = { .property_name = "FallbackSink", .type = "o", .get_cb = handle_get_fallback_sink, .set_cb = handle_set_fallback_sink }, + [PROPERTY_HANDLER_SOURCES] = { .property_name = "Sources", .type = "ao", .get_cb = handle_get_sources, .set_cb = NULL }, + [PROPERTY_HANDLER_FALLBACK_SOURCE] = { .property_name = "FallbackSource", .type = "o", .get_cb = handle_get_fallback_source, .set_cb = handle_set_fallback_source }, + [PROPERTY_HANDLER_PLAYBACK_STREAMS] = { .property_name = "PlaybackStreams", .type = "ao", .get_cb = handle_get_playback_streams, .set_cb = NULL }, + [PROPERTY_HANDLER_RECORD_STREAMS] = { .property_name = "RecordStreams", .type = "ao", .get_cb = handle_get_record_streams, .set_cb = NULL }, + [PROPERTY_HANDLER_SAMPLES] = { .property_name = "Samples", .type = "ao", .get_cb = handle_get_samples, .set_cb = NULL }, + [PROPERTY_HANDLER_MODULES] = { .property_name = "Modules", .type = "ao", .get_cb = handle_get_modules, .set_cb = NULL }, + [PROPERTY_HANDLER_CLIENTS] = { .property_name = "Clients", .type = "ao", .get_cb = handle_get_clients, .set_cb = NULL }, + [PROPERTY_HANDLER_MY_CLIENT] = { .property_name = "MyClient", .type = "o", .get_cb = handle_get_my_client, .set_cb = NULL }, + [PROPERTY_HANDLER_EXTENSIONS] = { .property_name = "Extensions", .type = "as", .get_cb = handle_get_extensions, .set_cb = NULL } +}; + +enum method_handler_index { + METHOD_HANDLER_GET_CARD_BY_NAME, + METHOD_HANDLER_GET_SINK_BY_NAME, + METHOD_HANDLER_GET_SOURCE_BY_NAME, + METHOD_HANDLER_GET_SAMPLE_BY_NAME, + METHOD_HANDLER_UPLOAD_SAMPLE, + METHOD_HANDLER_LOAD_MODULE, + METHOD_HANDLER_EXIT, + METHOD_HANDLER_LISTEN_FOR_SIGNAL, + METHOD_HANDLER_STOP_LISTENING_FOR_SIGNAL, + METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info get_card_by_name_args[] = { { "name", "s", "in" }, { "card", "o", "out" } }; +static pa_dbus_arg_info get_sink_by_name_args[] = { { "name", "s", "in" }, { "sink", "o", "out" } }; +static pa_dbus_arg_info get_source_by_name_args[] = { { "name", "s", "in" }, { "source", "o", "out" } }; +static pa_dbus_arg_info get_sample_by_name_args[] = { { "name", "s", "in" }, { "sample", "o", "out" } }; +static pa_dbus_arg_info upload_sample_args[] = { { "name", "s", "in" }, + { "sample_format", "u", "in" }, + { "sample_rate", "u", "in" }, + { "channels", "au", "in" }, + { "default_volume", "au", "in" }, + { "property_list", "a{say}", "in" }, + { "data", "ay", "in" }, + { "sample", "o", "out" } }; +static pa_dbus_arg_info load_module_args[] = { { "name", "s", "in" }, { "arguments", "a{ss}", "in" }, { "module", "o", "out" } }; +static pa_dbus_arg_info listen_for_signal_args[] = { { "signal", "s", "in" }, { "objects", "ao", "in" } }; +static pa_dbus_arg_info stop_listening_for_signal_args[] = { { "signal", "s", "in" } }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_GET_CARD_BY_NAME] = { + .method_name = "GetCardByName", + .arguments = get_card_by_name_args, + .n_arguments = sizeof(get_card_by_name_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_card_by_name }, + [METHOD_HANDLER_GET_SINK_BY_NAME] = { + .method_name = "GetSinkByName", + .arguments = get_sink_by_name_args, + .n_arguments = sizeof(get_sink_by_name_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_sink_by_name }, + [METHOD_HANDLER_GET_SOURCE_BY_NAME] = { + .method_name = "GetSourceByName", + .arguments = get_source_by_name_args, + .n_arguments = sizeof(get_source_by_name_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_source_by_name }, + [METHOD_HANDLER_GET_SAMPLE_BY_NAME] = { + .method_name = "GetSampleByName", + .arguments = get_sample_by_name_args, + .n_arguments = sizeof(get_sample_by_name_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_sample_by_name }, + [METHOD_HANDLER_UPLOAD_SAMPLE] = { + .method_name = "UploadSample", + .arguments = upload_sample_args, + .n_arguments = sizeof(upload_sample_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_upload_sample }, + [METHOD_HANDLER_LOAD_MODULE] = { + .method_name = "LoadModule", + .arguments = load_module_args, + .n_arguments = sizeof(load_module_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_load_module }, + [METHOD_HANDLER_EXIT] = { + .method_name = "Exit", + .arguments = NULL, + .n_arguments = 0, + .receive_cb = handle_exit }, + [METHOD_HANDLER_LISTEN_FOR_SIGNAL] = { + .method_name = "ListenForSignal", + .arguments = listen_for_signal_args, + .n_arguments = sizeof(listen_for_signal_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_listen_for_signal }, + [METHOD_HANDLER_STOP_LISTENING_FOR_SIGNAL] = { + .method_name = "StopListeningForSignal", + .arguments = stop_listening_for_signal_args, + .n_arguments = sizeof(stop_listening_for_signal_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_stop_listening_for_signal } +}; + +enum signal_index { + SIGNAL_NEW_CARD, + SIGNAL_CARD_REMOVED, + SIGNAL_NEW_SINK, + SIGNAL_SINK_REMOVED, + SIGNAL_FALLBACK_SINK_UPDATED, + SIGNAL_FALLBACK_SINK_UNSET, + SIGNAL_NEW_SOURCE, + SIGNAL_SOURCE_REMOVED, + SIGNAL_FALLBACK_SOURCE_UPDATED, + SIGNAL_FALLBACK_SOURCE_UNSET, + SIGNAL_NEW_PLAYBACK_STREAM, + SIGNAL_PLAYBACK_STREAM_REMOVED, + SIGNAL_NEW_RECORD_STREAM, + SIGNAL_RECORD_STREAM_REMOVED, + SIGNAL_NEW_SAMPLE, + SIGNAL_SAMPLE_REMOVED, + SIGNAL_NEW_MODULE, + SIGNAL_MODULE_REMOVED, + SIGNAL_NEW_CLIENT, + SIGNAL_CLIENT_REMOVED, + SIGNAL_NEW_EXTENSION, + SIGNAL_EXTENSION_REMOVED, + SIGNAL_MAX +}; + +static pa_dbus_arg_info new_card_args[] = { { "card", "o", NULL } }; +static pa_dbus_arg_info card_removed_args[] = { { "card", "o", NULL } }; +static pa_dbus_arg_info new_sink_args[] = { { "sink", "o", NULL } }; +static pa_dbus_arg_info sink_removed_args[] = { { "sink", "o", NULL } }; +static pa_dbus_arg_info fallback_sink_updated_args[] = { { "sink", "o", NULL } }; +static pa_dbus_arg_info new_source_args[] = { { "source", "o", NULL } }; +static pa_dbus_arg_info source_removed_args[] = { { "source", "o", NULL } }; +static pa_dbus_arg_info fallback_source_updated_args[] = { { "source", "o", NULL } }; +static pa_dbus_arg_info new_playback_stream_args[] = { { "playback_stream", "o", NULL } }; +static pa_dbus_arg_info playback_stream_removed_args[] = { { "playback_stream", "o", NULL } }; +static pa_dbus_arg_info new_record_stream_args[] = { { "record_stream", "o", NULL } }; +static pa_dbus_arg_info record_stream_removed_args[] = { { "record_stream", "o", NULL } }; +static pa_dbus_arg_info new_sample_args[] = { { "sample", "o", NULL } }; +static pa_dbus_arg_info sample_removed_args[] = { { "sample", "o", NULL } }; +static pa_dbus_arg_info new_module_args[] = { { "module", "o", NULL } }; +static pa_dbus_arg_info module_removed_args[] = { { "module", "o", NULL } }; +static pa_dbus_arg_info new_client_args[] = { { "client", "o", NULL } }; +static pa_dbus_arg_info client_removed_args[] = { { "client", "o", NULL } }; +static pa_dbus_arg_info new_extension_args[] = { { "extension", "s", NULL } }; +static pa_dbus_arg_info extension_removed_args[] = { { "extension", "s", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_NEW_CARD] = { .name = "NewCard", .arguments = new_card_args, .n_arguments = 1 }, + [SIGNAL_CARD_REMOVED] = { .name = "CardRemoved", .arguments = card_removed_args, .n_arguments = 1 }, + [SIGNAL_NEW_SINK] = { .name = "NewSink", .arguments = new_sink_args, .n_arguments = 1 }, + [SIGNAL_SINK_REMOVED] = { .name = "SinkRemoved", .arguments = sink_removed_args, .n_arguments = 1 }, + [SIGNAL_FALLBACK_SINK_UPDATED] = { .name = "FallbackSinkUpdated", .arguments = fallback_sink_updated_args, .n_arguments = 1 }, + [SIGNAL_FALLBACK_SINK_UNSET] = { .name = "FallbackSinkUnset", .arguments = NULL, .n_arguments = 0 }, + [SIGNAL_NEW_SOURCE] = { .name = "NewSource", .arguments = new_source_args, .n_arguments = 1 }, + [SIGNAL_SOURCE_REMOVED] = { .name = "SourceRemoved", .arguments = source_removed_args, .n_arguments = 1 }, + [SIGNAL_FALLBACK_SOURCE_UPDATED] = { .name = "FallbackSourceUpdated", .arguments = fallback_source_updated_args, .n_arguments = 1 }, + [SIGNAL_FALLBACK_SOURCE_UNSET] = { .name = "FallbackSourceUnset", .arguments = NULL, .n_arguments = 0 }, + [SIGNAL_NEW_PLAYBACK_STREAM] = { .name = "NewPlaybackStream", .arguments = new_playback_stream_args, .n_arguments = 1 }, + [SIGNAL_PLAYBACK_STREAM_REMOVED] = { .name = "PlaybackStreamRemoved", .arguments = playback_stream_removed_args, .n_arguments = 1 }, + [SIGNAL_NEW_RECORD_STREAM] = { .name = "NewRecordStream", .arguments = new_record_stream_args, .n_arguments = 1 }, + [SIGNAL_RECORD_STREAM_REMOVED] = { .name = "RecordStreamRemoved", .arguments = record_stream_removed_args, .n_arguments = 1 }, + [SIGNAL_NEW_SAMPLE] = { .name = "NewSample", .arguments = new_sample_args, .n_arguments = 1 }, + [SIGNAL_SAMPLE_REMOVED] = { .name = "SampleRemoved", .arguments = sample_removed_args, .n_arguments = 1 }, + [SIGNAL_NEW_MODULE] = { .name = "NewModule", .arguments = new_module_args, .n_arguments = 1 }, + [SIGNAL_MODULE_REMOVED] = { .name = "ModuleRemoved", .arguments = module_removed_args, .n_arguments = 1 }, + [SIGNAL_NEW_CLIENT] = { .name = "NewClient", .arguments = new_client_args, .n_arguments = 1 }, + [SIGNAL_CLIENT_REMOVED] = { .name = "ClientRemoved", .arguments = client_removed_args, .n_arguments = 1 }, + [SIGNAL_NEW_EXTENSION] = { .name = "NewExtension", .arguments = new_extension_args, .n_arguments = 1 }, + [SIGNAL_EXTENSION_REMOVED] = { .name = "ExtensionRemoved", .arguments = extension_removed_args, .n_arguments = 1 } +}; + +static pa_dbus_interface_info core_interface_info = { + .name = PA_DBUS_CORE_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_interface_revision(DBusConnection *conn, DBusMessage *msg, void *userdata) { + dbus_uint32_t interface_revision = INTERFACE_REVISION; + + pa_assert(conn); + pa_assert(msg); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &interface_revision); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + const char *server_name = PACKAGE_NAME; + + pa_assert(conn); + pa_assert(msg); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &server_name); +} + +static void handle_get_version(DBusConnection *conn, DBusMessage *msg, void *userdata) { + const char *version = PACKAGE_VERSION; + + pa_assert(conn); + pa_assert(msg); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &version); +} + +static dbus_bool_t get_is_local(DBusConnection *conn) { + int conn_fd; + + pa_assert(conn); + + if (!dbus_connection_get_socket(conn, &conn_fd)) + return FALSE; + + return pa_socket_is_local(conn_fd); +} + +static void handle_get_is_local(DBusConnection *conn, DBusMessage *msg, void *userdata) { + dbus_bool_t is_local; + + pa_assert(conn); + pa_assert(msg); + + is_local = get_is_local(conn); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &is_local); +} + +static void handle_get_username(DBusConnection *conn, DBusMessage *msg, void *userdata) { + char *username = NULL; + + pa_assert(conn); + pa_assert(msg); + + username = pa_get_user_name_malloc(); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &username); + + pa_xfree(username); +} + +static void handle_get_hostname(DBusConnection *conn, DBusMessage *msg, void *userdata) { + char *hostname = NULL; + + pa_assert(conn); + pa_assert(msg); + + hostname = pa_get_host_name_malloc(); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &hostname); + + pa_xfree(hostname); +} + +/* Caller frees the returned array. */ +static dbus_uint32_t *get_default_channels(pa_dbusiface_core *c, unsigned *n) { + dbus_uint32_t *default_channels = NULL; + unsigned i; + + pa_assert(c); + pa_assert(n); + + *n = c->core->default_channel_map.channels; + default_channels = pa_xnew(dbus_uint32_t, *n); + + for (i = 0; i < *n; ++i) + default_channels[i] = c->core->default_channel_map.map[i]; + + return default_channels; +} + +static void handle_get_default_channels(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + dbus_uint32_t *default_channels = NULL; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + default_channels = get_default_channels(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, default_channels, n); + + pa_xfree(default_channels); +} + +static void handle_set_default_channels(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_core *c = userdata; + DBusMessageIter array_iter; + pa_channel_map new_channel_map; + const dbus_uint32_t *default_channels; + int n_channels; + unsigned i; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(c); + + pa_channel_map_init(&new_channel_map); + + dbus_message_iter_recurse(iter, &array_iter); + dbus_message_iter_get_fixed_array(&array_iter, &default_channels, &n_channels); + + if (n_channels <= 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Empty channel array."); + return; + } + + if (n_channels > (int) PA_CHANNELS_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "Too many channels: %i. The maximum number of channels is %u.", n_channels, PA_CHANNELS_MAX); + return; + } + + new_channel_map.channels = n_channels; + + for (i = 0; i < new_channel_map.channels; ++i) { + if (default_channels[i] >= PA_CHANNEL_POSITION_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid channel position: %u.", default_channels[i]); + return; + } + + new_channel_map.map[i] = default_channels[i]; + } + + c->core->default_channel_map = new_channel_map; + c->core->default_sample_spec.channels = n_channels; + + pa_dbus_send_empty_reply(conn, msg); +}; + +static void handle_get_default_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + dbus_uint32_t default_sample_format; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + default_sample_format = c->core->default_sample_spec.format; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &default_sample_format); +} + +static void handle_set_default_sample_format(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_core *c = userdata; + dbus_uint32_t default_sample_format; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(c); + + dbus_message_iter_get_basic(iter, &default_sample_format); + + if (default_sample_format >= PA_SAMPLE_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid sample format."); + return; + } + + c->core->default_sample_spec.format = default_sample_format; + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_get_default_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + dbus_uint32_t default_sample_rate; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + default_sample_rate = c->core->default_sample_spec.rate; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &default_sample_rate); +} + +static void handle_set_default_sample_rate(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_core *c = userdata; + dbus_uint32_t default_sample_rate; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(c); + + dbus_message_iter_get_basic(iter, &default_sample_rate); + + if (default_sample_rate <= 0 || default_sample_rate > PA_RATE_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid sample rate."); + return; + } + + c->core->default_sample_spec.rate = default_sample_rate; + + pa_dbus_send_empty_reply(conn, msg); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_cards(pa_dbusiface_core *c, unsigned *n) { + const char **cards; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_card *card; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->cards); + + if (*n == 0) + return NULL; + + cards = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(card, c->cards, state) + cards[i++] = pa_dbusiface_card_get_path(card); + + return cards; +} + +static void handle_get_cards(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **cards; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + cards = get_cards(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, cards, n); + + pa_xfree(cards); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_sinks(pa_dbusiface_core *c, unsigned *n) { + const char **sinks; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_device *sink; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->sinks_by_index); + + if (*n == 0) + return NULL; + + sinks = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(sink, c->sinks_by_index, state) + sinks[i++] = pa_dbusiface_device_get_path(sink); + + return sinks; +} + +static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **sinks; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + sinks = get_sinks(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sinks, n); + + pa_xfree(sinks); +} + +static void handle_get_fallback_sink(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + pa_dbusiface_device *fallback_sink; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (!c->fallback_sink) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "There are no sinks, and therefore no fallback sink either."); + return; + } + + pa_assert_se((fallback_sink = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(c->fallback_sink->index)))); + object_path = pa_dbusiface_device_get_path(fallback_sink); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_set_fallback_sink(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_core *c = userdata; + pa_dbusiface_device *fallback_sink; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(c); + + if (!c->fallback_sink) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "There are no sinks, and therefore no fallback sink either."); + return; + } + + dbus_message_iter_get_basic(iter, &object_path); + + if (!(fallback_sink = pa_hashmap_get(c->sinks_by_path, object_path))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such sink.", object_path); + return; + } + + pa_namereg_set_default_sink(c->core, pa_dbusiface_device_get_sink(fallback_sink)); + + pa_dbus_send_empty_reply(conn, msg); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_sources(pa_dbusiface_core *c, unsigned *n) { + const char **sources; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_device *source; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->sources_by_index); + + if (*n == 0) + return NULL; + + sources = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(source, c->sources_by_index, state) + sources[i++] = pa_dbusiface_device_get_path(source); + + return sources; +} + +static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **sources; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + sources = get_sources(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sources, n); + + pa_xfree(sources); +} + +static void handle_get_fallback_source(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + pa_dbusiface_device *fallback_source; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (!c->fallback_source) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "There are no sources, and therefore no fallback source either."); + return; + } + + pa_assert_se((fallback_source = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(c->fallback_source->index)))); + object_path = pa_dbusiface_device_get_path(fallback_source); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_set_fallback_source(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_core *c = userdata; + pa_dbusiface_device *fallback_source; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(c); + + if (!c->fallback_source) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "There are no sources, and therefore no fallback source either."); + return; + } + + dbus_message_iter_get_basic(iter, &object_path); + + if (!(fallback_source = pa_hashmap_get(c->sources_by_path, object_path))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such source.", object_path); + return; + } + + pa_namereg_set_default_source(c->core, pa_dbusiface_device_get_source(fallback_source)); + + pa_dbus_send_empty_reply(conn, msg); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_playback_streams(pa_dbusiface_core *c, unsigned *n) { + const char **streams; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_stream *stream; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->playback_streams); + + if (*n == 0) + return NULL; + + streams = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(stream, c->playback_streams, state) + streams[i++] = pa_dbusiface_stream_get_path(stream); + + return streams; +} + +static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **playback_streams; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + playback_streams = get_playback_streams(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, playback_streams, n); + + pa_xfree(playback_streams); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_record_streams(pa_dbusiface_core *c, unsigned *n) { + const char **streams; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_stream *stream; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->record_streams); + + if (*n == 0) + return NULL; + + streams = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(stream, c->record_streams, state) + streams[i++] = pa_dbusiface_stream_get_path(stream); + + return streams; +} + +static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **record_streams; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + record_streams = get_record_streams(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, record_streams, n); + + pa_xfree(record_streams); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_samples(pa_dbusiface_core *c, unsigned *n) { + const char **samples; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_sample *sample; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->samples); + + if (*n == 0) + return NULL; + + samples = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(sample, c->samples, state) + samples[i++] = pa_dbusiface_sample_get_path(sample); + + return samples; +} + +static void handle_get_samples(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **samples; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + samples = get_samples(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, samples, n); + + pa_xfree(samples); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_modules(pa_dbusiface_core *c, unsigned *n) { + const char **modules; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_module *module; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->modules); + + if (*n == 0) + return NULL; + + modules = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(module, c->modules, state) + modules[i++] = pa_dbusiface_module_get_path(module); + + return modules; +} + +static void handle_get_modules(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **modules; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + modules = get_modules(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, modules, n); + + pa_xfree(modules); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_clients(pa_dbusiface_core *c, unsigned *n) { + const char **clients; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_client *client; + + pa_assert(c); + pa_assert(n); + + *n = pa_hashmap_size(c->clients); + + if (*n == 0) + return NULL; + + clients = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(client, c->clients, state) + clients[i++] = pa_dbusiface_client_get_path(client); + + return clients; +} + +static void handle_get_clients(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **clients; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + clients = get_clients(c, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, clients, n); + + pa_xfree(clients); +} + +static const char *get_my_client(pa_dbusiface_core *c, DBusConnection *conn) { + pa_client *my_client; + + pa_assert(c); + pa_assert(conn); + + pa_assert_se((my_client = pa_dbus_protocol_get_client(c->dbus_protocol, conn))); + + return pa_dbusiface_client_get_path(pa_hashmap_get(c->clients, PA_UINT32_TO_PTR(my_client->index))); +} + +static void handle_get_my_client(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char *my_client; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + my_client = get_my_client(c, conn); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &my_client); +} + +static void handle_get_extensions(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char **extensions; + unsigned n; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + extensions = pa_dbus_protocol_get_extensions(c->dbus_protocol, &n); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_STRING, extensions, n); + + pa_xfree(extensions); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t interface_revision; + const char *server_name; + const char *version; + dbus_bool_t is_local; + char *username; + char *hostname; + dbus_uint32_t *default_channels; + unsigned n_default_channels; + dbus_uint32_t default_sample_format; + dbus_uint32_t default_sample_rate; + const char **cards; + unsigned n_cards; + const char **sinks; + unsigned n_sinks; + const char *fallback_sink; + const char **sources; + unsigned n_sources; + const char *fallback_source; + const char **playback_streams; + unsigned n_playback_streams; + const char **record_streams; + unsigned n_record_streams; + const char **samples; + unsigned n_samples; + const char **modules; + unsigned n_modules; + const char **clients; + unsigned n_clients; + const char *my_client; + const char **extensions; + unsigned n_extensions; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + interface_revision = INTERFACE_REVISION; + server_name = PACKAGE_NAME; + version = PACKAGE_VERSION; + is_local = get_is_local(conn); + username = pa_get_user_name_malloc(); + hostname = pa_get_host_name_malloc(); + default_channels = get_default_channels(c, &n_default_channels); + default_sample_format = c->core->default_sample_spec.format; + default_sample_rate = c->core->default_sample_spec.rate; + cards = get_cards(c, &n_cards); + sinks = get_sinks(c, &n_sinks); + fallback_sink = c->fallback_sink + ? pa_dbusiface_device_get_path(pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(c->fallback_sink->index))) + : NULL; + sources = get_sources(c, &n_sources); + fallback_source = c->fallback_source + ? pa_dbusiface_device_get_path(pa_hashmap_get(c->sources_by_index, + PA_UINT32_TO_PTR(c->fallback_source->index))) + : NULL; + playback_streams = get_playback_streams(c, &n_playback_streams); + record_streams = get_record_streams(c, &n_record_streams); + samples = get_samples(c, &n_samples); + modules = get_modules(c, &n_modules); + clients = get_clients(c, &n_clients); + my_client = get_my_client(c, conn); + extensions = pa_dbus_protocol_get_extensions(c->dbus_protocol, &n_extensions); + + 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_INTERFACE_REVISION].property_name, DBUS_TYPE_UINT32, &interface_revision); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &server_name); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_VERSION].property_name, DBUS_TYPE_STRING, &version); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_IS_LOCAL].property_name, DBUS_TYPE_BOOLEAN, &is_local); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_USERNAME].property_name, DBUS_TYPE_STRING, &username); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HOSTNAME].property_name, DBUS_TYPE_STRING, &hostname); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEFAULT_CHANNELS].property_name, DBUS_TYPE_UINT32, default_channels, n_default_channels); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEFAULT_SAMPLE_FORMAT].property_name, DBUS_TYPE_UINT32, &default_sample_format); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEFAULT_SAMPLE_RATE].property_name, DBUS_TYPE_UINT32, &default_sample_rate); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CARDS].property_name, DBUS_TYPE_OBJECT_PATH, cards, n_cards); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SINKS].property_name, DBUS_TYPE_OBJECT_PATH, sinks, n_sinks); + + if (fallback_sink) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_FALLBACK_SINK].property_name, DBUS_TYPE_OBJECT_PATH, &fallback_sink); + + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SOURCES].property_name, DBUS_TYPE_OBJECT_PATH, sources, n_sources); + + if (fallback_source) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_FALLBACK_SOURCE].property_name, DBUS_TYPE_OBJECT_PATH, &fallback_source); + + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PLAYBACK_STREAMS].property_name, DBUS_TYPE_OBJECT_PATH, playback_streams, n_playback_streams); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_RECORD_STREAMS].property_name, DBUS_TYPE_OBJECT_PATH, record_streams, n_record_streams); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLES].property_name, DBUS_TYPE_OBJECT_PATH, samples, n_samples); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_MODULES].property_name, DBUS_TYPE_OBJECT_PATH, modules, n_modules); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CLIENTS].property_name, DBUS_TYPE_OBJECT_PATH, clients, n_clients); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_MY_CLIENT].property_name, DBUS_TYPE_OBJECT_PATH, &my_client); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_EXTENSIONS].property_name, DBUS_TYPE_STRING, extensions, n_extensions); + + 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); + + pa_xfree(username); + pa_xfree(hostname); + pa_xfree(default_channels); + pa_xfree(cards); + pa_xfree(sinks); + pa_xfree(sources); + pa_xfree(playback_streams); + pa_xfree(record_streams); + pa_xfree(samples); + pa_xfree(modules); + pa_xfree(clients); + pa_xfree(extensions); +} + +static void handle_get_card_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + char *card_name; + pa_card *card; + pa_dbusiface_card *dbus_card; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &card_name, DBUS_TYPE_INVALID)); + + if (!(card = pa_namereg_get(c->core, card_name, PA_NAMEREG_CARD))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "No such card."); + return; + } + + pa_assert_se((dbus_card = pa_hashmap_get(c->cards, PA_UINT32_TO_PTR(card->index)))); + + object_path = pa_dbusiface_card_get_path(dbus_card); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_get_sink_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + char *sink_name; + pa_sink *sink; + pa_dbusiface_device *dbus_sink; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &sink_name, DBUS_TYPE_INVALID)); + + if (!(sink = pa_namereg_get(c->core, sink_name, PA_NAMEREG_SINK))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such sink.", sink_name); + return; + } + + pa_assert_se((dbus_sink = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(sink->index)))); + + object_path = pa_dbusiface_device_get_path(dbus_sink); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_get_source_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + char *source_name; + pa_source *source; + pa_dbusiface_device *dbus_source; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &source_name, DBUS_TYPE_INVALID)); + + if (!(source = pa_namereg_get(c->core, source_name, PA_NAMEREG_SOURCE))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such source.", source_name); + return; + } + + pa_assert_se((dbus_source = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(source->index)))); + + object_path = pa_dbusiface_device_get_path(dbus_source); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_get_sample_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + char *sample_name; + pa_scache_entry *sample; + pa_dbusiface_sample *dbus_sample; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &sample_name, DBUS_TYPE_INVALID)); + + if (!(sample = pa_namereg_get(c->core, sample_name, PA_NAMEREG_SAMPLE))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "No such sample."); + return; + } + + pa_assert_se((dbus_sample = pa_hashmap_get(c->samples, PA_UINT32_TO_PTR(sample->index)))); + + object_path = pa_dbusiface_sample_get_path(dbus_sample); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_upload_sample(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + DBusMessageIter msg_iter; + DBusMessageIter array_iter; + const char *name; + dbus_uint32_t sample_format; + dbus_uint32_t sample_rate; + const dbus_uint32_t *channels; + int n_channels; + const dbus_uint32_t *default_volume; + int n_volume_entries; + pa_proplist *property_list; + const uint8_t *data; + int data_length; + int i; + pa_sample_spec ss; + pa_channel_map map; + pa_memchunk chunk; + uint32_t idx; + pa_dbusiface_sample *dbus_sample = NULL; + pa_scache_entry *sample = NULL; + const char *object_path; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + chunk.memblock = NULL; + + pa_assert_se(dbus_message_iter_init(msg, &msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &name); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &sample_format); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &sample_rate); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_recurse(&msg_iter, &array_iter); + dbus_message_iter_get_fixed_array(&array_iter, &channels, &n_channels); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_recurse(&msg_iter, &array_iter); + dbus_message_iter_get_fixed_array(&array_iter, &default_volume, &n_volume_entries); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter))) + return; + + dbus_message_iter_recurse(&msg_iter, &array_iter); + dbus_message_iter_get_fixed_array(&array_iter, &data, &data_length); + + if (sample_format >= PA_SAMPLE_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid sample format."); + goto finish; + } + + if (sample_rate <= 0 || sample_rate > PA_RATE_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid sample rate."); + goto finish; + } + + if (n_channels <= 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Empty channel map."); + goto finish; + } + + if (n_channels > (int) PA_CHANNELS_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "Too many channels: %i. The maximum is %u.", n_channels, PA_CHANNELS_MAX); + goto finish; + } + + for (i = 0; i < n_channels; ++i) { + if (channels[i] >= PA_CHANNEL_POSITION_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid channel position."); + goto finish; + } + } + + if (n_volume_entries != 0 && n_volume_entries != n_channels) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "The channels and default_volume arguments have different number of elements (%i and %i, resp).", + n_channels, n_volume_entries); + goto finish; + } + + for (i = 0; i < n_volume_entries; ++i) { + if (default_volume[i] > PA_VOLUME_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume: %u.", default_volume[i]); + goto finish; + } + } + + if (data_length == 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Empty data."); + goto finish; + } + + if (data_length > PA_SCACHE_ENTRY_SIZE_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "Too big sample: %i bytes. The maximum sample length is %u bytes.", + data_length, PA_SCACHE_ENTRY_SIZE_MAX); + goto finish; + } + + ss.format = sample_format; + ss.rate = sample_rate; + ss.channels = n_channels; + + pa_assert(pa_sample_spec_valid(&ss)); + + if (!pa_frame_aligned(data_length, &ss)) { + char buf[PA_SAMPLE_SPEC_SNPRINT_MAX]; + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "The sample length (%i bytes) doesn't align with the sample format and channels (%s).", + data_length, pa_sample_spec_snprint(buf, sizeof(buf), &ss)); + goto finish; + } + + map.channels = n_channels; + for (i = 0; i < n_channels; ++i) + map.map[i] = channels[i]; + + chunk.memblock = pa_memblock_new(c->core->mempool, data_length); + chunk.index = 0; + chunk.length = data_length; + + memcpy(pa_memblock_acquire(chunk.memblock), data, data_length); + pa_memblock_release(chunk.memblock); + + if (pa_scache_add_item(c->core, name, &ss, &map, &chunk, property_list, &idx) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Adding the sample failed."); + goto finish; + } + + sample = pa_idxset_get_by_index(c->core->scache, idx); + + if (n_volume_entries > 0) { + sample->volume.channels = n_channels; + for (i = 0; i < n_volume_entries; ++i) + sample->volume.values[i] = default_volume[i]; + sample->volume_is_set = TRUE; + } else { + sample->volume_is_set = FALSE; + } + + dbus_sample = pa_dbusiface_sample_new(c, sample); + pa_hashmap_put(c->samples, PA_UINT32_TO_PTR(idx), dbus_sample); + + object_path = pa_dbusiface_sample_get_path(dbus_sample); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); + +finish: + if (property_list) + pa_proplist_free(property_list); + + if (chunk.memblock) + pa_memblock_unref(chunk.memblock); +} + +static pa_bool_t contains_space(const char *string) { + const char *p; + + pa_assert(string); + + for (p = string; *p; ++p) { + if (isspace(*p)) + return TRUE; + } + + return FALSE; +} + +static void handle_load_module(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + char *name = NULL; + const char *key = NULL; + const char *value = NULL; + char *escaped_value = NULL; + pa_strbuf *arg_buffer = NULL; + char *arg_string = NULL; + pa_module *module = NULL; + pa_dbusiface_module *dbus_module = NULL; + const char *object_path = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (c->core->disallow_module_loading) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "The server is configured to disallow module loading."); + return; + } + + pa_assert_se(dbus_message_iter_init(msg, &msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &name); + + arg_buffer = pa_strbuf_new(); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_recurse(&msg_iter, &dict_iter); + + while (dbus_message_iter_get_arg_type(&dict_iter) != DBUS_TYPE_INVALID) { + if (!pa_strbuf_isempty(arg_buffer)) + pa_strbuf_putc(arg_buffer, ' '); + + dbus_message_iter_recurse(&dict_iter, &dict_entry_iter); + + dbus_message_iter_get_basic(&dict_entry_iter, &key); + + if (strlen(key) <= 0 || !pa_ascii_valid(key) || contains_space(key)) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid module argument name: %s", key); + goto finish; + } + + pa_assert_se(dbus_message_iter_next(&dict_entry_iter)); + dbus_message_iter_get_basic(&dict_entry_iter, &value); + + escaped_value = pa_escape(value, "\""); + pa_strbuf_printf(arg_buffer, "%s=\"%s\"", key, escaped_value); + pa_xfree(escaped_value); + + dbus_message_iter_next(&dict_iter); + } + + arg_string = pa_strbuf_tostring(arg_buffer); + + if (!(module = pa_module_load(c->core, name, arg_string))) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Failed to load module."); + goto finish; + } + + dbus_module = pa_dbusiface_module_new(module); + pa_hashmap_put(c->modules, PA_UINT32_TO_PTR(module->index), dbus_module); + + object_path = pa_dbusiface_module_get_path(dbus_module); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); + +finish: + if (arg_buffer) + pa_strbuf_free(arg_buffer); + + pa_xfree(arg_string); +} + +static void handle_exit(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + if (c->core->disallow_exit) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "The server is configured to disallow exiting."); + return; + } + + pa_dbus_send_empty_reply(conn, msg); + + pa_core_exit(c->core, FALSE, 0); +} + +static void handle_listen_for_signal(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char *signal; + char **objects = NULL; + int n_objects; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &signal, + DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &objects, &n_objects, + DBUS_TYPE_INVALID)); + + pa_dbus_protocol_add_signal_listener(c->dbus_protocol, conn, *signal ? signal : NULL, objects, n_objects); + + pa_dbus_send_empty_reply(conn, msg); + + dbus_free_string_array(objects); +} + +static void handle_stop_listening_for_signal(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_core *c = userdata; + const char *signal; + + pa_assert(conn); + pa_assert(msg); + pa_assert(c); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &signal, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_remove_signal_listener(c->dbus_protocol, conn, *signal ? signal : NULL); + + pa_dbus_send_empty_reply(conn, msg); +} + +static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_core *c = userdata; + pa_dbusiface_card *card_iface = NULL; + pa_dbusiface_device *device_iface = NULL; + pa_dbusiface_stream *stream_iface = NULL; + pa_dbusiface_sample *sample_iface = NULL; + pa_dbusiface_module *module_iface = NULL; + pa_dbusiface_client *client_iface = NULL; + DBusMessage *signal = NULL; + const char *object_path = NULL; + pa_sink *new_fallback_sink = NULL; + pa_source *new_fallback_source = NULL; + + pa_assert(c); + + switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { + case PA_SUBSCRIPTION_EVENT_SERVER: + new_fallback_sink = pa_namereg_get_default_sink(core); + new_fallback_source = pa_namereg_get_default_source(core); + + if (c->fallback_sink != new_fallback_sink) { + if (c->fallback_sink) + pa_sink_unref(c->fallback_sink); + c->fallback_sink = new_fallback_sink ? pa_sink_ref(new_fallback_sink) : NULL; + + if (new_fallback_sink + && (device_iface = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(new_fallback_sink->index)))) { + object_path = pa_dbusiface_device_get_path(device_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_FALLBACK_SINK_UPDATED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + + } else if (!new_fallback_sink) { + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_FALLBACK_SINK_UNSET].name))); + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + } + + if (c->fallback_source != new_fallback_source) { + if (c->fallback_source) + pa_source_unref(c->fallback_source); + c->fallback_source = new_fallback_source ? pa_source_ref(new_fallback_source) : NULL; + + if (new_fallback_source + && (device_iface = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(new_fallback_source->index)))) { + object_path = pa_dbusiface_device_get_path(device_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_FALLBACK_SOURCE_UPDATED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + + } else if (!new_fallback_source) { + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_FALLBACK_SOURCE_UNSET].name))); + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + } + break; + + case PA_SUBSCRIPTION_EVENT_CARD: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + if (!(card_iface = pa_hashmap_get(c->cards, PA_UINT32_TO_PTR(idx)))) { + pa_card *card = NULL; + + if (!(card = pa_idxset_get_by_index(core->cards, idx))) + return; /* The card was removed immediately after creation. */ + + card_iface = pa_dbusiface_card_new(c, card); + pa_hashmap_put(c->cards, PA_UINT32_TO_PTR(idx), card_iface); + } + + object_path = pa_dbusiface_card_get_path(card_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_CARD].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(card_iface = pa_hashmap_remove(c->cards, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_card_get_path(card_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_CARD_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_card_free(card_iface); + } + break; + + case PA_SUBSCRIPTION_EVENT_SINK: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + pa_sink *sink = NULL; + + if (!(sink = pa_idxset_get_by_index(core->sinks, idx))) + return; /* The sink was removed immediately after creation. */ + + if (!(device_iface = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(idx)))) { + device_iface = pa_dbusiface_device_new_sink(c, sink); + pa_hashmap_put(c->sinks_by_index, PA_UINT32_TO_PTR(idx), device_iface); + pa_hashmap_put(c->sinks_by_path, pa_dbusiface_device_get_path(device_iface), device_iface); + } + + object_path = pa_dbusiface_device_get_path(device_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_SINK].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + + if (c->fallback_sink && pa_streq(c->fallback_sink->name, sink->name)) { + /* We have got default sink change event, but at that point + * the D-Bus sink object wasn't created yet. Now that the + * object is created, let's send the fallback sink change + * signal. */ + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_FALLBACK_SINK_UPDATED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(device_iface = pa_hashmap_remove(c->sinks_by_index, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_device_get_path(device_iface); + pa_assert_se(pa_hashmap_remove(c->sinks_by_path, object_path)); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_SINK_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_device_free(device_iface); + } + break; + + case PA_SUBSCRIPTION_EVENT_SOURCE: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + pa_source *source = pa_idxset_get_by_index(core->sources, idx); + + if (!(source = pa_idxset_get_by_index(core->sources, idx))) + return; /* The source was removed immediately after creation. */ + + if (!(device_iface = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(idx)))) { + device_iface = pa_dbusiface_device_new_source(c, source); + pa_hashmap_put(c->sources_by_index, PA_UINT32_TO_PTR(idx), device_iface); + pa_hashmap_put(c->sources_by_path, pa_dbusiface_device_get_path(device_iface), device_iface); + } + + object_path = pa_dbusiface_device_get_path(device_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_SOURCE].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + + if (c->fallback_source && pa_streq(c->fallback_source->name, source->name)) { + /* We have got default source change event, but at that + * point the D-Bus source object wasn't created yet. Now + * that the object is created, let's send the fallback + * source change signal. */ + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_FALLBACK_SOURCE_UPDATED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(device_iface = pa_hashmap_remove(c->sources_by_index, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_device_get_path(device_iface); + pa_assert_se(pa_hashmap_remove(c->sources_by_path, object_path)); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_SOURCE_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_device_free(device_iface); + } + break; + + case PA_SUBSCRIPTION_EVENT_SINK_INPUT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + pa_sink_input *sink_input = NULL; + + if (!(sink_input = pa_idxset_get_by_index(core->sink_inputs, idx))) + return; /* The sink input was removed immediately after creation. */ + + if (!(stream_iface = pa_hashmap_get(c->playback_streams, PA_UINT32_TO_PTR(idx)))) { + stream_iface = pa_dbusiface_stream_new_playback(c, sink_input); + pa_hashmap_put(c->playback_streams, PA_UINT32_TO_PTR(idx), stream_iface); + } + + object_path = pa_dbusiface_stream_get_path(stream_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_PLAYBACK_STREAM].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(stream_iface = pa_hashmap_remove(c->playback_streams, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_stream_get_path(stream_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_PLAYBACK_STREAM_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_stream_free(stream_iface); + } + break; + + case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + pa_source_output *source_output = NULL; + + if (!(source_output = pa_idxset_get_by_index(core->source_outputs, idx))) + return; /* The source output was removed immediately after creation. */ + + if (!(stream_iface = pa_hashmap_get(c->record_streams, PA_UINT32_TO_PTR(idx)))) { + stream_iface = pa_dbusiface_stream_new_record(c, source_output); + pa_hashmap_put(c->record_streams, PA_UINT32_TO_PTR(idx), stream_iface); + } + + object_path = pa_dbusiface_stream_get_path(stream_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_RECORD_STREAM].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(stream_iface = pa_hashmap_remove(c->record_streams, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_stream_get_path(stream_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_RECORD_STREAM_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_stream_free(stream_iface); + } + break; + + case PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + pa_scache_entry *sample = NULL; + + if (!(sample = pa_idxset_get_by_index(core->scache, idx))) + return; /* The sample was removed immediately after creation. */ + + if (!(sample_iface = pa_hashmap_get(c->samples, PA_UINT32_TO_PTR(idx)))) { + sample_iface = pa_dbusiface_sample_new(c, sample); + pa_hashmap_put(c->samples, PA_UINT32_TO_PTR(idx), sample_iface); + } + + object_path = pa_dbusiface_sample_get_path(sample_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_SAMPLE].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(sample_iface = pa_hashmap_remove(c->samples, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_sample_get_path(sample_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_SAMPLE_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_sample_free(sample_iface); + } + break; + + case PA_SUBSCRIPTION_EVENT_MODULE: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + pa_module *module = NULL; + + if (!(module = pa_idxset_get_by_index(core->modules, idx))) + return; /* The module was removed immediately after creation. */ + + if (!(module_iface = pa_hashmap_get(c->modules, PA_UINT32_TO_PTR(idx)))) { + module_iface = pa_dbusiface_module_new(module); + pa_hashmap_put(c->modules, PA_UINT32_TO_PTR(idx), module_iface); + } + + object_path = pa_dbusiface_module_get_path(module_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_MODULE].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(module_iface = pa_hashmap_remove(c->modules, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_module_get_path(module_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_MODULE_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_module_free(module_iface); + } + break; + + case PA_SUBSCRIPTION_EVENT_CLIENT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + pa_client *client = NULL; + + if (!(client = pa_idxset_get_by_index(core->clients, idx))) + return; /* The client was removed immediately after creation. */ + + if (!(client_iface = pa_hashmap_get(c->clients, PA_UINT32_TO_PTR(idx)))) { + client_iface = pa_dbusiface_client_new(c, client); + pa_hashmap_put(c->clients, PA_UINT32_TO_PTR(idx), client_iface); + } + + object_path = pa_dbusiface_client_get_path(client_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_CLIENT].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + if (!(client_iface = pa_hashmap_remove(c->clients, PA_UINT32_TO_PTR(idx)))) + return; + + object_path = pa_dbusiface_client_get_path(client_iface); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_CLIENT_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbusiface_client_free(client_iface); + } + break; + } + + if (signal) { + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + } +} + +static pa_hook_result_t extension_registered_cb(void *hook_data, void *call_data, void *slot_data) { + pa_dbusiface_core *c = slot_data; + const char *ext_name = call_data; + DBusMessage *signal = NULL; + + pa_assert(c); + pa_assert(ext_name); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_NEW_EXTENSION].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_STRING, &ext_name, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + + return PA_HOOK_OK; +} + +static pa_hook_result_t extension_unregistered_cb(void *hook_data, void *call_data, void *slot_data) { + pa_dbusiface_core *c = slot_data; + const char *ext_name = call_data; + DBusMessage *signal = NULL; + + pa_assert(c); + pa_assert(ext_name); + + pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, + PA_DBUS_CORE_INTERFACE, + signals[SIGNAL_EXTENSION_REMOVED].name))); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_STRING, &ext_name, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(c->dbus_protocol, signal); + dbus_message_unref(signal); + + return PA_HOOK_OK; +} + +pa_dbusiface_core *pa_dbusiface_core_new(pa_core *core) { + pa_dbusiface_core *c; + pa_card *card; + pa_sink *sink; + pa_source *source; + pa_dbusiface_device *device; + pa_sink_input *sink_input; + pa_source_output *source_output; + pa_scache_entry *sample; + pa_module *module; + pa_client *client; + uint32_t idx; + + pa_assert(core); + + c = pa_xnew(pa_dbusiface_core, 1); + c->core = pa_core_ref(core); + c->subscription = pa_subscription_new(core, PA_SUBSCRIPTION_MASK_ALL, subscription_cb, c); + c->dbus_protocol = pa_dbus_protocol_get(core); + c->cards = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->sinks_by_index = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->sinks_by_path = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + c->sources_by_index = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->sources_by_path = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + c->playback_streams = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->record_streams = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->samples = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->modules = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->clients = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->fallback_sink = pa_namereg_get_default_sink(core); + c->fallback_source = pa_namereg_get_default_source(core); + c->extension_registered_slot = pa_dbus_protocol_hook_connect(c->dbus_protocol, + PA_DBUS_PROTOCOL_HOOK_EXTENSION_REGISTERED, + PA_HOOK_NORMAL, + extension_registered_cb, + c); + c->extension_unregistered_slot = pa_dbus_protocol_hook_connect(c->dbus_protocol, + PA_DBUS_PROTOCOL_HOOK_EXTENSION_UNREGISTERED, + PA_HOOK_NORMAL, + extension_unregistered_cb, + c); + c->memstats = pa_dbusiface_memstats_new(c, core); + + if (c->fallback_sink) + pa_sink_ref(c->fallback_sink); + if (c->fallback_source) + pa_source_ref(c->fallback_source); + + PA_IDXSET_FOREACH(card, core->cards, idx) + pa_hashmap_put(c->cards, PA_UINT32_TO_PTR(idx), pa_dbusiface_card_new(c, card)); + + PA_IDXSET_FOREACH(sink, core->sinks, idx) { + device = pa_dbusiface_device_new_sink(c, sink); + pa_hashmap_put(c->sinks_by_index, PA_UINT32_TO_PTR(idx), device); + pa_hashmap_put(c->sinks_by_path, pa_dbusiface_device_get_path(device), device); + } + + PA_IDXSET_FOREACH(source, core->sources, idx) { + device = pa_dbusiface_device_new_source(c, source); + pa_hashmap_put(c->sources_by_index, PA_UINT32_TO_PTR(idx), device); + pa_hashmap_put(c->sources_by_path, pa_dbusiface_device_get_path(device), device); + } + + PA_IDXSET_FOREACH(sink_input, core->sink_inputs, idx) + pa_hashmap_put(c->playback_streams, PA_UINT32_TO_PTR(idx), pa_dbusiface_stream_new_playback(c, sink_input)); + + PA_IDXSET_FOREACH(source_output, core->source_outputs, idx) + pa_hashmap_put(c->record_streams, PA_UINT32_TO_PTR(idx), pa_dbusiface_stream_new_record(c, source_output)); + + PA_IDXSET_FOREACH(sample, core->scache, idx) + pa_hashmap_put(c->samples, PA_UINT32_TO_PTR(idx), pa_dbusiface_sample_new(c, sample)); + + PA_IDXSET_FOREACH(module, core->modules, idx) + pa_hashmap_put(c->modules, PA_UINT32_TO_PTR(idx), pa_dbusiface_module_new(module)); + + PA_IDXSET_FOREACH(client, core->clients, idx) + pa_hashmap_put(c->clients, PA_UINT32_TO_PTR(idx), pa_dbusiface_client_new(c, client)); + + pa_assert_se(pa_dbus_protocol_add_interface(c->dbus_protocol, PA_DBUS_CORE_OBJECT_PATH, &core_interface_info, c) >= 0); + + return c; +} + +static void free_card_cb(void *p, void *userdata) { + pa_dbusiface_card *c = p; + + pa_assert(c); + + pa_dbusiface_card_free(c); +} + +static void free_device_cb(void *p, void *userdata) { + pa_dbusiface_device *d = p; + + pa_assert(d); + + pa_dbusiface_device_free(d); +} + +static void free_stream_cb(void *p, void *userdata) { + pa_dbusiface_stream *s = p; + + pa_assert(s); + + pa_dbusiface_stream_free(s); +} + +static void free_sample_cb(void *p, void *userdata) { + pa_dbusiface_sample *s = p; + + pa_assert(s); + + pa_dbusiface_sample_free(s); +} + +static void free_module_cb(void *p, void *userdata) { + pa_dbusiface_module *m = p; + + pa_assert(m); + + pa_dbusiface_module_free(m); +} + +static void free_client_cb(void *p, void *userdata) { + pa_dbusiface_client *c = p; + + pa_assert(c); + + pa_dbusiface_client_free(c); +} + +void pa_dbusiface_core_free(pa_dbusiface_core *c) { + pa_assert(c); + + pa_assert_se(pa_dbus_protocol_remove_interface(c->dbus_protocol, PA_DBUS_CORE_OBJECT_PATH, core_interface_info.name) >= 0); + + pa_subscription_free(c->subscription); + pa_hashmap_free(c->cards, free_card_cb, NULL); + pa_hashmap_free(c->sinks_by_index, free_device_cb, NULL); + pa_hashmap_free(c->sinks_by_path, NULL, NULL); + pa_hashmap_free(c->sources_by_index, free_device_cb, NULL); + pa_hashmap_free(c->sources_by_path, NULL, NULL); + pa_hashmap_free(c->playback_streams, free_stream_cb, NULL); + pa_hashmap_free(c->record_streams, free_stream_cb, NULL); + pa_hashmap_free(c->samples, free_sample_cb, NULL); + pa_hashmap_free(c->modules, free_module_cb, NULL); + pa_hashmap_free(c->clients, free_client_cb, NULL); + pa_hook_slot_free(c->extension_registered_slot); + pa_hook_slot_free(c->extension_unregistered_slot); + pa_dbusiface_memstats_free(c->memstats); + + if (c->fallback_sink) + pa_sink_unref(c->fallback_sink); + if (c->fallback_source) + pa_source_unref(c->fallback_source); + + pa_dbus_protocol_unref(c->dbus_protocol); + pa_core_unref(c->core); + + pa_xfree(c); +} + +const char *pa_dbusiface_core_get_card_path(pa_dbusiface_core *c, const pa_card *card) { + pa_assert(c); + pa_assert(card); + + return pa_dbusiface_card_get_path(pa_hashmap_get(c->cards, PA_UINT32_TO_PTR(card->index))); +} + +const char *pa_dbusiface_core_get_sink_path(pa_dbusiface_core *c, const pa_sink *sink) { + pa_assert(c); + pa_assert(sink); + + return pa_dbusiface_device_get_path(pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(sink->index))); +} + +const char *pa_dbusiface_core_get_source_path(pa_dbusiface_core *c, const pa_source *source) { + pa_assert(c); + pa_assert(source); + + return pa_dbusiface_device_get_path(pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(source->index))); +} + +const char *pa_dbusiface_core_get_playback_stream_path(pa_dbusiface_core *c, const pa_sink_input *sink_input) { + pa_assert(c); + pa_assert(sink_input); + + return pa_dbusiface_stream_get_path(pa_hashmap_get(c->playback_streams, PA_UINT32_TO_PTR(sink_input->index))); +} + +const char *pa_dbusiface_core_get_record_stream_path(pa_dbusiface_core *c, const pa_source_output *source_output) { + pa_assert(c); + pa_assert(source_output); + + return pa_dbusiface_stream_get_path(pa_hashmap_get(c->record_streams, PA_UINT32_TO_PTR(source_output->index))); +} + +const char *pa_dbusiface_core_get_module_path(pa_dbusiface_core *c, const pa_module *module) { + pa_assert(c); + pa_assert(module); + + return pa_dbusiface_module_get_path(pa_hashmap_get(c->modules, PA_UINT32_TO_PTR(module->index))); +} + +const char *pa_dbusiface_core_get_client_path(pa_dbusiface_core *c, const pa_client *client) { + pa_assert(c); + pa_assert(client); + + return pa_dbusiface_client_get_path(pa_hashmap_get(c->clients, PA_UINT32_TO_PTR(client->index))); +} + +pa_sink *pa_dbusiface_core_get_sink(pa_dbusiface_core *c, const char *object_path) { + pa_dbusiface_device *device = NULL; + + pa_assert(c); + pa_assert(object_path); + + device = pa_hashmap_get(c->sinks_by_path, object_path); + + if (device) + return pa_dbusiface_device_get_sink(device); + else + return NULL; +} + +pa_source *pa_dbusiface_core_get_source(pa_dbusiface_core *c, const char *object_path) { + pa_dbusiface_device *device = NULL; + + pa_assert(c); + pa_assert(object_path); + + device = pa_hashmap_get(c->sources_by_path, object_path); + + if (device) + return pa_dbusiface_device_get_source(device); + else + return NULL; +} diff --git a/src/modules/dbus/iface-core.h b/src/modules/dbus/iface-core.h new file mode 100644 index 00000000..900b6d1c --- /dev/null +++ b/src/modules/dbus/iface-core.h @@ -0,0 +1,52 @@ +#ifndef foodbusifacecorehfoo +#define foodbusifacecorehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Core interface + * documentation. + */ + +#include <pulsecore/core.h> + +typedef struct pa_dbusiface_core pa_dbusiface_core; + +pa_dbusiface_core *pa_dbusiface_core_new(pa_core *core); +void pa_dbusiface_core_free(pa_dbusiface_core *c); + +const char *pa_dbusiface_core_get_card_path(pa_dbusiface_core *c, const pa_card *card); +const char *pa_dbusiface_core_get_sink_path(pa_dbusiface_core *c, const pa_sink *sink); +const char *pa_dbusiface_core_get_source_path(pa_dbusiface_core *c, const pa_source *source); +const char *pa_dbusiface_core_get_playback_stream_path(pa_dbusiface_core *c, const pa_sink_input *sink_input); +const char *pa_dbusiface_core_get_record_stream_path(pa_dbusiface_core *c, const pa_source_output *source_output); +const char *pa_dbusiface_core_get_module_path(pa_dbusiface_core *c, const pa_module *module); +const char *pa_dbusiface_core_get_client_path(pa_dbusiface_core *c, const pa_client *client); + +/* Returns NULL if there's no sink with the given path. */ +pa_sink *pa_dbusiface_core_get_sink(pa_dbusiface_core *c, const char *object_path); + +/* Returns NULL if there's no source with the given path. */ +pa_source *pa_dbusiface_core_get_source(pa_dbusiface_core *c, const char *object_path); + +#endif diff --git a/src/modules/dbus/iface-device-port.c b/src/modules/dbus/iface-device-port.c new file mode 100644 index 00000000..d403b6a2 --- /dev/null +++ b/src/modules/dbus/iface-device-port.c @@ -0,0 +1,190 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> + +#include "iface-device-port.h" + +#define OBJECT_NAME "port" + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_description(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_priority(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +struct pa_dbusiface_device_port { + uint32_t index; + pa_device_port *port; + char *path; + pa_dbus_protocol *dbus_protocol; +}; + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_DESCRIPTION, + PROPERTY_HANDLER_PRIORITY, + 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_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL }, + [PROPERTY_HANDLER_DESCRIPTION] = { .property_name = "Description", .type = "s", .get_cb = handle_get_description, .set_cb = NULL }, + [PROPERTY_HANDLER_PRIORITY] = { .property_name = "Priority", .type = "u", .get_cb = handle_get_priority, .set_cb = NULL }, +}; + +static pa_dbus_interface_info port_interface_info = { + .name = PA_DBUSIFACE_DEVICE_PORT_INTERFACE, + .method_handlers = NULL, + .n_method_handlers = 0, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = NULL, + .n_signals = 0 +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device_port *p = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &p->index); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device_port *p = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &p->port->name); +} + +static void handle_get_description(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device_port *p = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &p->port->description); +} + +static void handle_get_priority(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device_port *p = userdata; + dbus_uint32_t priority = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + priority = p->port->priority; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &priority); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device_port *p = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t priority = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(p); + + priority = p->port->priority; + + 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, &p->index); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &p->port->name); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DESCRIPTION].property_name, DBUS_TYPE_STRING, &p->port->description); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PRIORITY].property_name, DBUS_TYPE_UINT32, &priority); + + 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); +} + +pa_dbusiface_device_port *pa_dbusiface_device_port_new( + pa_dbusiface_device *device, + pa_core *core, + pa_device_port *port, + uint32_t idx) { + pa_dbusiface_device_port *p = NULL; + + pa_assert(device); + pa_assert(core); + pa_assert(port); + + p = pa_xnew(pa_dbusiface_device_port, 1); + p->index = idx; + p->port = port; + p->path = pa_sprintf_malloc("%s/%s%u", pa_dbusiface_device_get_path(device), OBJECT_NAME, idx); + p->dbus_protocol = pa_dbus_protocol_get(core); + + pa_assert_se(pa_dbus_protocol_add_interface(p->dbus_protocol, p->path, &port_interface_info, p) >= 0); + + return p; +} + +void pa_dbusiface_device_port_free(pa_dbusiface_device_port *p) { + pa_assert(p); + + pa_assert_se(pa_dbus_protocol_remove_interface(p->dbus_protocol, p->path, port_interface_info.name) >= 0); + + pa_dbus_protocol_unref(p->dbus_protocol); + + pa_xfree(p->path); + pa_xfree(p); +} + +const char *pa_dbusiface_device_port_get_path(pa_dbusiface_device_port *p) { + pa_assert(p); + + return p->path; +} + +const char *pa_dbusiface_device_port_get_name(pa_dbusiface_device_port *p) { + pa_assert(p); + + return p->port->name; +} diff --git a/src/modules/dbus/iface-device-port.h b/src/modules/dbus/iface-device-port.h new file mode 100644 index 00000000..0461e2ff --- /dev/null +++ b/src/modules/dbus/iface-device-port.h @@ -0,0 +1,50 @@ +#ifndef foodbusifacedeviceporthfoo +#define foodbusifacedeviceporthfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.DevicePort. + * + * See http://pulseaudio.org/wiki/DBusInterface for the DevicePort interface + * documentation. + */ + +#include <pulsecore/protocol-dbus.h> +#include <pulsecore/sink.h> + +#include "iface-device.h" + +#define PA_DBUSIFACE_DEVICE_PORT_INTERFACE PA_DBUS_CORE_INTERFACE ".DevicePort" + +typedef struct pa_dbusiface_device_port pa_dbusiface_device_port; + +pa_dbusiface_device_port *pa_dbusiface_device_port_new( + pa_dbusiface_device *device, + pa_core *core, + pa_device_port *port, + uint32_t idx); +void pa_dbusiface_device_port_free(pa_dbusiface_device_port *p); + +const char *pa_dbusiface_device_port_get_path(pa_dbusiface_device_port *p); +const char *pa_dbusiface_device_port_get_name(pa_dbusiface_device_port *p); + +#endif diff --git a/src/modules/dbus/iface-device.c b/src/modules/dbus/iface-device.c new file mode 100644 index 00000000..3a747a44 --- /dev/null +++ b/src/modules/dbus/iface-device.c @@ -0,0 +1,1316 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#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-device-port.h" + +#include "iface-device.h" + +#define SINK_OBJECT_NAME "sink" +#define SOURCE_OBJECT_NAME "source" + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(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_card(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_has_flat_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_has_convertible_to_decibel_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_base_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_volume_steps(DBusConnection *conn, DBusMessage *msg, 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_has_hardware_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_has_hardware_mute(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_configured_latency(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_has_dynamic_latency(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_latency(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_is_hardware_device(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_is_network_device(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_state(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_ports(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_active_port(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_active_port(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, 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_suspend(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_port_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_sink_get_monitor_source(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_sink_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_source_get_monitor_of_sink(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_source_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +enum device_type { + DEVICE_TYPE_SINK, + DEVICE_TYPE_SOURCE +}; + +struct pa_dbusiface_device { + pa_dbusiface_core *core; + + union { + pa_sink *sink; + pa_source *source; + }; + enum device_type type; + char *path; + pa_cvolume volume; + dbus_bool_t mute; + union { + pa_sink_state_t sink_state; + pa_source_state_t source_state; + }; + pa_hashmap *ports; + uint32_t next_port_index; + pa_device_port *active_port; + pa_proplist *proplist; + + pa_dbus_protocol *dbus_protocol; + pa_subscription *subscription; +}; + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_DRIVER, + PROPERTY_HANDLER_OWNER_MODULE, + PROPERTY_HANDLER_CARD, + PROPERTY_HANDLER_SAMPLE_FORMAT, + PROPERTY_HANDLER_SAMPLE_RATE, + PROPERTY_HANDLER_CHANNELS, + PROPERTY_HANDLER_VOLUME, + PROPERTY_HANDLER_HAS_FLAT_VOLUME, + PROPERTY_HANDLER_HAS_CONVERTIBLE_TO_DECIBEL_VOLUME, + PROPERTY_HANDLER_BASE_VOLUME, + PROPERTY_HANDLER_VOLUME_STEPS, + PROPERTY_HANDLER_MUTE, + PROPERTY_HANDLER_HAS_HARDWARE_VOLUME, + PROPERTY_HANDLER_HAS_HARDWARE_MUTE, + PROPERTY_HANDLER_CONFIGURED_LATENCY, + PROPERTY_HANDLER_HAS_DYNAMIC_LATENCY, + PROPERTY_HANDLER_LATENCY, + PROPERTY_HANDLER_IS_HARDWARE_DEVICE, + PROPERTY_HANDLER_IS_NETWORK_DEVICE, + PROPERTY_HANDLER_STATE, + PROPERTY_HANDLER_PORTS, + PROPERTY_HANDLER_ACTIVE_PORT, + PROPERTY_HANDLER_PROPERTY_LIST, + PROPERTY_HANDLER_MAX +}; + +enum sink_property_handler_index { + SINK_PROPERTY_HANDLER_MONITOR_SOURCE, + SINK_PROPERTY_HANDLER_MAX +}; + +enum source_property_handler_index { + SOURCE_PROPERTY_HANDLER_MONITOR_OF_SINK, + SOURCE_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_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .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_CARD] = { .property_name = "Card", .type = "o", .get_cb = handle_get_card, .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_HAS_FLAT_VOLUME] = { .property_name = "HasFlatVolume", .type = "b", .get_cb = handle_get_has_flat_volume, .set_cb = NULL }, + [PROPERTY_HANDLER_HAS_CONVERTIBLE_TO_DECIBEL_VOLUME] = { .property_name = "HasConvertibleToDecibelVolume", .type = "b", .get_cb = handle_get_has_convertible_to_decibel_volume, .set_cb = NULL }, + [PROPERTY_HANDLER_BASE_VOLUME] = { .property_name = "BaseVolume", .type = "u", .get_cb = handle_get_base_volume, .set_cb = NULL }, + [PROPERTY_HANDLER_VOLUME_STEPS] = { .property_name = "VolumeSteps", .type = "u", .get_cb = handle_get_volume_steps, .set_cb = NULL }, + [PROPERTY_HANDLER_MUTE] = { .property_name = "Mute", .type = "b", .get_cb = handle_get_mute, .set_cb = handle_set_mute }, + [PROPERTY_HANDLER_HAS_HARDWARE_VOLUME] = { .property_name = "HasHardwareVolume", .type = "b", .get_cb = handle_get_has_hardware_volume, .set_cb = NULL }, + [PROPERTY_HANDLER_HAS_HARDWARE_MUTE] = { .property_name = "HasHardwareMute", .type = "b", .get_cb = handle_get_has_hardware_mute, .set_cb = NULL }, + [PROPERTY_HANDLER_CONFIGURED_LATENCY] = { .property_name = "ConfiguredLatency", .type = "t", .get_cb = handle_get_configured_latency, .set_cb = NULL }, + [PROPERTY_HANDLER_HAS_DYNAMIC_LATENCY] = { .property_name = "HasDynamicLatency", .type = "b", .get_cb = handle_get_has_dynamic_latency, .set_cb = NULL }, + [PROPERTY_HANDLER_LATENCY] = { .property_name = "Latency", .type = "t", .get_cb = handle_get_latency, .set_cb = NULL }, + [PROPERTY_HANDLER_IS_HARDWARE_DEVICE] = { .property_name = "IsHardwareDevice", .type = "b", .get_cb = handle_get_is_hardware_device, .set_cb = NULL }, + [PROPERTY_HANDLER_IS_NETWORK_DEVICE] = { .property_name = "IsNetworkDevice", .type = "b", .get_cb = handle_get_is_network_device, .set_cb = NULL }, + [PROPERTY_HANDLER_STATE] = { .property_name = "State", .type = "u", .get_cb = handle_get_state, .set_cb = NULL }, + [PROPERTY_HANDLER_PORTS] = { .property_name = "Ports", .type = "ao", .get_cb = handle_get_ports, .set_cb = NULL }, + [PROPERTY_HANDLER_ACTIVE_PORT] = { .property_name = "ActivePort", .type = "o", .get_cb = handle_get_active_port, .set_cb = handle_set_active_port }, + [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL } +}; + +static pa_dbus_property_handler sink_property_handlers[SINK_PROPERTY_HANDLER_MAX] = { + [SINK_PROPERTY_HANDLER_MONITOR_SOURCE] = { .property_name = "MonitorSource", .type = "o", .get_cb = handle_sink_get_monitor_source, .set_cb = NULL } +}; + +static pa_dbus_property_handler source_property_handlers[SOURCE_PROPERTY_HANDLER_MAX] = { + [SOURCE_PROPERTY_HANDLER_MONITOR_OF_SINK] = { .property_name = "MonitorOfSink", .type = "o", .get_cb = handle_source_get_monitor_of_sink, .set_cb = NULL } +}; + +enum method_handler_index { + METHOD_HANDLER_SUSPEND, + METHOD_HANDLER_GET_PORT_BY_NAME, + METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info suspend_args[] = { { "suspend", "b", "in" } }; +static pa_dbus_arg_info get_port_by_name_args[] = { { "name", "s", "in" }, { "port", "o", "out" } }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_SUSPEND] = { + .method_name = "Suspend", + .arguments = suspend_args, + .n_arguments = sizeof(suspend_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_suspend }, + [METHOD_HANDLER_GET_PORT_BY_NAME] = { + .method_name = "GetPortByName", + .arguments = get_port_by_name_args, + .n_arguments = sizeof(get_port_by_name_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_get_port_by_name } +}; + +enum signal_index { + SIGNAL_VOLUME_UPDATED, + SIGNAL_MUTE_UPDATED, + SIGNAL_STATE_UPDATED, + SIGNAL_ACTIVE_PORT_UPDATED, + SIGNAL_PROPERTY_LIST_UPDATED, + SIGNAL_MAX +}; + +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 state_updated_args[] = { { "state", "u", NULL } }; +static pa_dbus_arg_info active_port_updated_args[] = { { "port", "o", NULL } }; +static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [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_STATE_UPDATED] = { .name = "StateUpdated", .arguments = state_updated_args, .n_arguments = 1 }, + [SIGNAL_ACTIVE_PORT_UPDATED] = { .name = "ActivePortUpdated", .arguments = active_port_updated_args, .n_arguments = 1 }, + [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 } +}; + +static pa_dbus_interface_info device_interface_info = { + .name = PA_DBUSIFACE_DEVICE_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 pa_dbus_interface_info sink_interface_info = { + .name = PA_DBUSIFACE_SINK_INTERFACE, + .method_handlers = NULL, + .n_method_handlers = 0, + .property_handlers = sink_property_handlers, + .n_property_handlers = SINK_PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_sink_get_all, + .signals = NULL, + .n_signals = 0 +}; + +static pa_dbus_interface_info source_interface_info = { + .name = PA_DBUSIFACE_SOURCE_INTERFACE, + .method_handlers = NULL, + .n_method_handlers = 0, + .property_handlers = source_property_handlers, + .n_property_handlers = SOURCE_PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_source_get_all, + .signals = NULL, + .n_signals = 0 +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint32_t idx = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + idx = (d->type == DEVICE_TYPE_SINK) ? d->sink->index : d->source->index; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *name = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + name = (d->type == DEVICE_TYPE_SINK) ? d->sink->name : d->source->name; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &name); +} + +static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *driver = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + driver = (d->type == DEVICE_TYPE_SINK) ? d->sink->driver : d->source->driver; + + 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_device *d = userdata; + pa_module *owner_module = NULL; + const char *object_path = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + owner_module = (d->type == DEVICE_TYPE_SINK) ? d->sink->module : d->source->module; + + if (!owner_module) { + if (d->type == DEVICE_TYPE_SINK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sink %s doesn't have an owner module.", d->sink->name); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Source %s doesn't have an owner module.", d->source->name); + return; + } + + object_path = pa_dbusiface_core_get_module_path(d->core, owner_module); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_get_card(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + pa_card *card = NULL; + const char *object_path = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + card = (d->type == DEVICE_TYPE_SINK) ? d->sink->card : d->source->card; + + if (!card) { + if (d->type == DEVICE_TYPE_SINK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sink %s doesn't belong to any card.", d->sink->name); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Source %s doesn't belong to any card.", d->source->name); + return; + } + + object_path = pa_dbusiface_core_get_card_path(d->core, card); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path); +} + +static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint32_t sample_format = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + sample_format = (d->type == DEVICE_TYPE_SINK) ? d->sink->sample_spec.format : d->source->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_device *d = userdata; + dbus_uint32_t sample_rate = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + sample_rate = (d->type == DEVICE_TYPE_SINK) ? d->sink->sample_spec.rate : d->source->sample_spec.rate; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_rate); +} + +static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = 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(d); + + channel_map = (d->type == DEVICE_TYPE_SINK) ? &d->sink->channel_map : &d->source->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_device *d = userdata; + dbus_uint32_t volume[PA_CHANNELS_MAX]; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + for (i = 0; i < d->volume.channels; ++i) + volume[i] = d->volume.values[i]; + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, volume, d->volume.channels); +} + +static void handle_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_device *d = userdata; + DBusMessageIter array_iter; + int device_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(d); + + pa_cvolume_init(&new_vol); + + device_channels = (d->type == DEVICE_TYPE_SINK) ? d->sink->channel_map.channels : d->source->channel_map.channels; + + new_vol.channels = device_channels; + + dbus_message_iter_recurse(iter, &array_iter); + dbus_message_iter_get_fixed_array(&array_iter, &volume, &n_volume_entries); + + if (n_volume_entries != device_channels) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "Expected %u volume entries, got %i.", device_channels, n_volume_entries); + return; + } + + for (i = 0; i < n_volume_entries; ++i) { + if (volume[i] > PA_VOLUME_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Too large volume value: %u", volume[i]); + return; + } + new_vol.values[i] = volume[i]; + } + + if (d->type == DEVICE_TYPE_SINK) + pa_sink_set_volume(d->sink, &new_vol, TRUE, TRUE); + else + pa_source_set_volume(d->source, &new_vol, TRUE); + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_get_has_flat_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t has_flat_volume = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + has_flat_volume = (d->type == DEVICE_TYPE_SINK) ? (d->sink->flags & PA_SINK_FLAT_VOLUME) : FALSE; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_flat_volume); +} + +static void handle_get_has_convertible_to_decibel_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t has_convertible_to_decibel_volume = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + has_convertible_to_decibel_volume = (d->type == DEVICE_TYPE_SINK) + ? (d->sink->flags & PA_SINK_DECIBEL_VOLUME) + : (d->source->flags & PA_SOURCE_DECIBEL_VOLUME); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_convertible_to_decibel_volume); +} + +static void handle_get_base_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint32_t base_volume; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + base_volume = (d->type == DEVICE_TYPE_SINK) ? d->sink->base_volume : d->source->base_volume; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &base_volume); +} + +static void handle_get_volume_steps(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint32_t volume_steps; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + volume_steps = (d->type == DEVICE_TYPE_SINK) ? d->sink->n_volume_steps : d->source->n_volume_steps; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &volume_steps); +} + +static void handle_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &d->mute); +} + +static void handle_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t mute = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(d); + + dbus_message_iter_get_basic(iter, &mute); + + if (d->type == DEVICE_TYPE_SINK) + pa_sink_set_mute(d->sink, mute, TRUE); + else + pa_source_set_mute(d->source, mute, TRUE); + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_get_has_hardware_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t has_hardware_volume = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + has_hardware_volume = (d->type == DEVICE_TYPE_SINK) + ? (d->sink->flags & PA_SINK_HW_VOLUME_CTRL) + : (d->source->flags & PA_SOURCE_HW_VOLUME_CTRL); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_hardware_volume); +} + +static void handle_get_has_hardware_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t has_hardware_mute = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + has_hardware_mute = (d->type == DEVICE_TYPE_SINK) + ? (d->sink->flags & PA_SINK_HW_MUTE_CTRL) + : (d->source->flags & PA_SOURCE_HW_MUTE_CTRL); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_hardware_mute); +} + +static void handle_get_configured_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint64_t configured_latency = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + configured_latency = (d->type == DEVICE_TYPE_SINK) + ? pa_sink_get_requested_latency(d->sink) + : pa_source_get_requested_latency(d->source); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &configured_latency); +} + +static void handle_get_has_dynamic_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t has_dynamic_latency = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + has_dynamic_latency = (d->type == DEVICE_TYPE_SINK) + ? (d->sink->flags & PA_SINK_DYNAMIC_LATENCY) + : (d->source->flags & PA_SOURCE_DYNAMIC_LATENCY); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_dynamic_latency); +} + +static void handle_get_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint64_t latency = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + if (d->type == DEVICE_TYPE_SINK && !(d->sink->flags & PA_SINK_LATENCY)) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sink %s doesn't support latency querying.", d->sink->name); + else if (d->type == DEVICE_TYPE_SOURCE && !(d->source->flags & PA_SOURCE_LATENCY)) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Source %s doesn't support latency querying.", d->source->name); + return; + + latency = (d->type == DEVICE_TYPE_SINK) ? pa_sink_get_latency(d->sink) : pa_source_get_latency(d->source); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &latency); +} + +static void handle_get_is_hardware_device(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t is_hardware_device = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + is_hardware_device = (d->type == DEVICE_TYPE_SINK) + ? (d->sink->flags & PA_SINK_HARDWARE) + : (d->source->flags & PA_SOURCE_HARDWARE); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &is_hardware_device); +} + +static void handle_get_is_network_device(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t is_network_device = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + is_network_device = (d->type == DEVICE_TYPE_SINK) + ? (d->sink->flags & PA_SINK_NETWORK) + : (d->source->flags & PA_SOURCE_NETWORK); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &is_network_device); +} + +static void handle_get_state(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_uint32_t state; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + state = (d->type == DEVICE_TYPE_SINK) ? d->sink_state : d->source_state; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &state); +} + +/* The caller frees the array, but not the strings. */ +static const char **get_ports(pa_dbusiface_device *d, unsigned *n) { + const char **ports; + unsigned i = 0; + void *state = NULL; + pa_dbusiface_device_port *port = NULL; + + pa_assert(d); + pa_assert(n); + + *n = pa_hashmap_size(d->ports); + + if (*n == 0) + return NULL; + + ports = pa_xnew(const char *, *n); + + PA_HASHMAP_FOREACH(port, d->ports, state) + ports[i++] = pa_dbusiface_device_port_get_path(port); + + return ports; +} + +static void handle_get_ports(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char **ports = NULL; + unsigned n_ports = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + ports = get_ports(d, &n_ports); + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, ports, n_ports); + + pa_xfree(ports); +} + +static void handle_get_active_port(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *active_port; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + if (!d->active_port) { + pa_assert(pa_hashmap_isempty(d->ports)); + + if (d->type == DEVICE_TYPE_SINK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "The sink %s has no ports, and therefore there's no active port either.", d->sink->name); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "The source %s has no ports, and therefore there's no active port either.", d->source->name); + return; + } + + active_port = pa_dbusiface_device_port_get_path(pa_hashmap_get(d->ports, d->active_port->name)); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &active_port); +} + +static void handle_set_active_port(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *new_active_path; + pa_dbusiface_device_port *new_active; + int r; + + pa_assert(conn); + pa_assert(msg); + pa_assert(iter); + pa_assert(d); + + if (!d->active_port) { + pa_assert(pa_hashmap_isempty(d->ports)); + + if (d->type == DEVICE_TYPE_SINK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "The sink %s has no ports, and therefore there's no active port either.", d->sink->name); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "The source %s has no ports, and therefore there's no active port either.", d->source->name); + return; + } + + dbus_message_iter_get_basic(iter, &new_active_path); + + if (!(new_active = pa_hashmap_get(d->ports, new_active_path))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "No such port: %s", new_active_path); + return; + } + + if (d->type == DEVICE_TYPE_SINK) { + if ((r = pa_sink_set_port(d->sink, pa_dbusiface_device_port_get_name(new_active), TRUE)) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, + "Internal error in PulseAudio: pa_sink_set_port() failed with error code %i.", r); + return; + } + } else { + if ((r = pa_source_set_port(d->source, pa_dbusiface_device_port_get_name(new_active), TRUE)) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, + "Internal error in PulseAudio: pa_source_set_port() failed with error code %i.", r); + return; + } + } + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + pa_dbus_send_proplist_variant_reply(conn, msg, d->proplist); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t idx = 0; + const char *name = NULL; + const char *driver = NULL; + pa_module *owner_module = NULL; + const char *owner_module_path = NULL; + pa_card *card = NULL; + const char *card_path = NULL; + dbus_uint32_t sample_format = 0; + dbus_uint32_t sample_rate = 0; + pa_channel_map *channel_map = NULL; + dbus_uint32_t channels[PA_CHANNELS_MAX]; + dbus_uint32_t volume[PA_CHANNELS_MAX]; + dbus_bool_t has_flat_volume = FALSE; + dbus_bool_t has_convertible_to_decibel_volume = FALSE; + dbus_uint32_t base_volume = 0; + dbus_uint32_t volume_steps = 0; + dbus_bool_t has_hardware_volume = FALSE; + dbus_bool_t has_hardware_mute = FALSE; + dbus_uint64_t configured_latency = 0; + dbus_bool_t has_dynamic_latency = FALSE; + dbus_uint64_t latency = 0; + dbus_bool_t is_hardware_device = FALSE; + dbus_bool_t is_network_device = FALSE; + dbus_uint32_t state = 0; + const char **ports = NULL; + unsigned n_ports = 0; + const char *active_port = NULL; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + if (d->type == DEVICE_TYPE_SINK) { + idx = d->sink->index; + name = d->sink->name; + driver = d->sink->driver; + owner_module = d->sink->module; + card = d->sink->card; + sample_format = d->sink->sample_spec.format; + sample_rate = d->sink->sample_spec.rate; + channel_map = &d->sink->channel_map; + has_flat_volume = d->sink->flags & PA_SINK_FLAT_VOLUME; + has_convertible_to_decibel_volume = d->sink->flags & PA_SINK_DECIBEL_VOLUME; + base_volume = d->sink->base_volume; + volume_steps = d->sink->n_volume_steps; + has_hardware_volume = d->sink->flags & PA_SINK_HW_VOLUME_CTRL; + has_hardware_mute = d->sink->flags & PA_SINK_HW_MUTE_CTRL; + configured_latency = pa_sink_get_requested_latency(d->sink); + has_dynamic_latency = d->sink->flags & PA_SINK_DYNAMIC_LATENCY; + latency = pa_sink_get_latency(d->sink); + is_hardware_device = d->sink->flags & PA_SINK_HARDWARE; + is_network_device = d->sink->flags & PA_SINK_NETWORK; + state = pa_sink_get_state(d->sink); + } else { + idx = d->source->index; + name = d->source->name; + driver = d->source->driver; + owner_module = d->source->module; + card = d->source->card; + sample_format = d->source->sample_spec.format; + sample_rate = d->source->sample_spec.rate; + channel_map = &d->source->channel_map; + has_flat_volume = FALSE; + has_convertible_to_decibel_volume = d->source->flags & PA_SOURCE_DECIBEL_VOLUME; + base_volume = d->source->base_volume; + volume_steps = d->source->n_volume_steps; + has_hardware_volume = d->source->flags & PA_SOURCE_HW_VOLUME_CTRL; + has_hardware_mute = d->source->flags & PA_SOURCE_HW_MUTE_CTRL; + configured_latency = pa_source_get_requested_latency(d->source); + has_dynamic_latency = d->source->flags & PA_SOURCE_DYNAMIC_LATENCY; + latency = pa_source_get_latency(d->source); + is_hardware_device = d->source->flags & PA_SOURCE_HARDWARE; + is_network_device = d->source->flags & PA_SOURCE_NETWORK; + state = pa_source_get_state(d->source); + } + if (owner_module) + owner_module_path = pa_dbusiface_core_get_module_path(d->core, owner_module); + if (card) + card_path = pa_dbusiface_core_get_card_path(d->core, card); + for (i = 0; i < channel_map->channels; ++i) + channels[i] = channel_map->map[i]; + for (i = 0; i < d->volume.channels; ++i) + volume[i] = d->volume.values[i]; + ports = get_ports(d, &n_ports); + if (d->active_port) + active_port = pa_dbusiface_device_port_get_path(pa_hashmap_get(d->ports, d->active_port->name)); + + 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); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &name); + 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 (card) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CARD].property_name, DBUS_TYPE_OBJECT_PATH, &card_path); + + 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, &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); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_VOLUME].property_name, DBUS_TYPE_UINT32, volume, d->volume.channels); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_FLAT_VOLUME].property_name, DBUS_TYPE_BOOLEAN, &has_flat_volume); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_CONVERTIBLE_TO_DECIBEL_VOLUME].property_name, DBUS_TYPE_BOOLEAN, &has_convertible_to_decibel_volume); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_BASE_VOLUME].property_name, DBUS_TYPE_UINT32, &base_volume); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_VOLUME_STEPS].property_name, DBUS_TYPE_UINT32, &volume_steps); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_MUTE].property_name, DBUS_TYPE_BOOLEAN, &d->mute); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_HARDWARE_VOLUME].property_name, DBUS_TYPE_BOOLEAN, &has_hardware_volume); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_HARDWARE_MUTE].property_name, DBUS_TYPE_BOOLEAN, &has_hardware_mute); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CONFIGURED_LATENCY].property_name, DBUS_TYPE_UINT64, &configured_latency); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_DYNAMIC_LATENCY].property_name, DBUS_TYPE_BOOLEAN, &has_dynamic_latency); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_LATENCY].property_name, DBUS_TYPE_UINT64, &latency); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_IS_HARDWARE_DEVICE].property_name, DBUS_TYPE_BOOLEAN, &is_hardware_device); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_IS_NETWORK_DEVICE].property_name, DBUS_TYPE_BOOLEAN, &is_network_device); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_STATE].property_name, DBUS_TYPE_UINT32, &state); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PORTS].property_name, DBUS_TYPE_OBJECT_PATH, ports, n_ports); + + if (active_port) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACTIVE_PORT].property_name, DBUS_TYPE_OBJECT_PATH, &active_port); + + pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, d->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); + + pa_xfree(ports); +} + +static void handle_suspend(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + dbus_bool_t suspend = FALSE; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &suspend, DBUS_TYPE_INVALID)); + + if ((d->type == DEVICE_TYPE_SINK) && (pa_sink_suspend(d->sink, suspend, PA_SUSPEND_USER) < 0)) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Internal error in PulseAudio: pa_sink_suspend() failed."); + return; + } else if ((d->type == DEVICE_TYPE_SOURCE) && (pa_source_suspend(d->source, suspend, PA_SUSPEND_USER) < 0)) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Internal error in PulseAudio: pa_source_suspend() failed."); + return; + } + + pa_dbus_send_empty_reply(conn, msg); +} + +static void handle_get_port_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *port_name = NULL; + pa_dbusiface_device_port *port = NULL; + const char *port_path = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + + pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &port_name, DBUS_TYPE_INVALID)); + + if (!(port = pa_hashmap_get(d->ports, port_name))) { + if (d->type == DEVICE_TYPE_SINK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, + "%s: No such port on sink %s.", port_name, d->sink->name); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, + "%s: No such port on source %s.", port_name, d->source->name); + return; + } + + port_path = pa_dbusiface_device_port_get_path(port); + + pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &port_path); +} + +static void handle_sink_get_monitor_source(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *monitor_source = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + pa_assert(d->type == DEVICE_TYPE_SINK); + + monitor_source = pa_dbusiface_core_get_source_path(d->core, d->sink->monitor_source); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &monitor_source); +} + +static void handle_sink_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + const char *monitor_source = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + pa_assert(d->type == DEVICE_TYPE_SINK); + + monitor_source = pa_dbusiface_core_get_source_path(d->core, d->sink->monitor_source); + + 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[SINK_PROPERTY_HANDLER_MONITOR_SOURCE].property_name, DBUS_TYPE_OBJECT_PATH, &monitor_source); + + 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_source_get_monitor_of_sink(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + const char *monitor_of_sink = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + pa_assert(d->type == DEVICE_TYPE_SOURCE); + + if (!d->source->monitor_of) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Source %s is not a monitor source.", d->source->name); + return; + } + + monitor_of_sink = pa_dbusiface_core_get_sink_path(d->core, d->source->monitor_of); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &monitor_of_sink); +} + +static void handle_source_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_device *d = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + const char *monitor_of_sink = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(d); + pa_assert(d->type == DEVICE_TYPE_SOURCE); + + if (d->source->monitor_of) + monitor_of_sink = pa_dbusiface_core_get_sink_path(d->core, d->source->monitor_of); + + 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)); + + if (monitor_of_sink) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[SOURCE_PROPERTY_HANDLER_MONITOR_OF_SINK].property_name, DBUS_TYPE_OBJECT_PATH, &monitor_of_sink); + + 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 subscription_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_device *d = userdata; + DBusMessage *signal = NULL; + const pa_cvolume *new_volume = NULL; + pa_bool_t new_mute = FALSE; + pa_sink_state_t new_sink_state = 0; + pa_source_state_t new_source_state = 0; + pa_device_port *new_active_port = NULL; + pa_proplist *new_proplist = NULL; + unsigned i = 0; + + pa_assert(c); + pa_assert(d); + + if ((d->type == DEVICE_TYPE_SINK && idx != d->sink->index) || (d->type == DEVICE_TYPE_SOURCE && idx != d->source->index)) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + pa_assert(((d->type == DEVICE_TYPE_SINK) + && ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK)) + || ((d->type == DEVICE_TYPE_SOURCE) + && ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE))); + + new_volume = (d->type == DEVICE_TYPE_SINK) + ? pa_sink_get_volume(d->sink, FALSE) + : pa_source_get_volume(d->source, FALSE); + + if (!pa_cvolume_equal(&d->volume, new_volume)) { + dbus_uint32_t volume[PA_CHANNELS_MAX]; + dbus_uint32_t *volume_ptr = volume; + + d->volume = *new_volume; + + for (i = 0; i < d->volume.channels; ++i) + volume[i] = d->volume.values[i]; + + pa_assert_se(signal = dbus_message_new_signal(d->path, + PA_DBUSIFACE_DEVICE_INTERFACE, + signals[SIGNAL_VOLUME_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, + DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &volume_ptr, d->volume.channels, + DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(d->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + + new_mute = (d->type == DEVICE_TYPE_SINK) ? pa_sink_get_mute(d->sink, FALSE) : pa_source_get_mute(d->source, FALSE); + + if (d->mute != new_mute) { + d->mute = new_mute; + + pa_assert_se(signal = dbus_message_new_signal(d->path, + PA_DBUSIFACE_DEVICE_INTERFACE, + signals[SIGNAL_MUTE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_BOOLEAN, &d->mute, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(d->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + + if (d->type == DEVICE_TYPE_SINK) + new_sink_state = pa_sink_get_state(d->sink); + else + new_source_state = pa_source_get_state(d->source); + + if ((d->type == DEVICE_TYPE_SINK && d->sink_state != new_sink_state) + || (d->type == DEVICE_TYPE_SOURCE && d->source_state != new_source_state)) { + dbus_uint32_t state = 0; + + if (d->type == DEVICE_TYPE_SINK) + d->sink_state = new_sink_state; + else + d->source_state = new_source_state; + + state = (d->type == DEVICE_TYPE_SINK) ? d->sink_state : d->source_state; + + pa_assert_se(signal = dbus_message_new_signal(d->path, + PA_DBUSIFACE_DEVICE_INTERFACE, + signals[SIGNAL_STATE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_UINT32, &state, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(d->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + + new_active_port = (d->type == DEVICE_TYPE_SINK) ? d->sink->active_port : d->source->active_port; + + if (d->active_port != new_active_port) { + const char *object_path = NULL; + + d->active_port = new_active_port; + object_path = pa_dbusiface_device_port_get_path(pa_hashmap_get(d->ports, d->active_port->name)); + + pa_assert_se(signal = dbus_message_new_signal(d->path, + PA_DBUSIFACE_DEVICE_INTERFACE, + signals[SIGNAL_ACTIVE_PORT_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(d->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + + new_proplist = (d->type == DEVICE_TYPE_SINK) ? d->sink->proplist : d->source->proplist; + + if (!pa_proplist_equal(d->proplist, new_proplist)) { + DBusMessageIter msg_iter; + + pa_proplist_update(d->proplist, PA_UPDATE_SET, new_proplist); + + pa_assert_se(signal = dbus_message_new_signal(d->path, + PA_DBUSIFACE_DEVICE_INTERFACE, + signals[SIGNAL_PROPERTY_LIST_UPDATED].name)); + dbus_message_iter_init_append(signal, &msg_iter); + pa_dbus_append_proplist(&msg_iter, d->proplist); + + pa_dbus_protocol_send_signal(d->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } +} + +pa_dbusiface_device *pa_dbusiface_device_new_sink(pa_dbusiface_core *core, pa_sink *sink) { + pa_dbusiface_device *d = NULL; + + pa_assert(core); + pa_assert(sink); + + d = pa_xnew0(pa_dbusiface_device, 1); + d->core = core; + d->sink = pa_sink_ref(sink); + d->type = DEVICE_TYPE_SINK; + d->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, SINK_OBJECT_NAME, sink->index); + d->volume = *pa_sink_get_volume(sink, FALSE); + d->mute = pa_sink_get_mute(sink, FALSE); + d->sink_state = pa_sink_get_state(sink); + d->ports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + d->next_port_index = 0; + d->active_port = NULL; + d->proplist = pa_proplist_copy(sink->proplist); + d->dbus_protocol = pa_dbus_protocol_get(sink->core); + d->subscription = pa_subscription_new(sink->core, PA_SUBSCRIPTION_MASK_SINK, subscription_cb, d); + + if (sink->ports) { + pa_device_port *port; + void *state = NULL; + + PA_HASHMAP_FOREACH(port, sink->ports, state) { + pa_dbusiface_device_port *p = pa_dbusiface_device_port_new(d, sink->core, port, d->next_port_index++); + pa_hashmap_put(d->ports, pa_dbusiface_device_port_get_name(p), p); + } + pa_assert_se(d->active_port = sink->active_port); + } + + pa_assert_se(pa_dbus_protocol_add_interface(d->dbus_protocol, d->path, &device_interface_info, d) >= 0); + pa_assert_se(pa_dbus_protocol_add_interface(d->dbus_protocol, d->path, &sink_interface_info, d) >= 0); + + return d; +} + +pa_dbusiface_device *pa_dbusiface_device_new_source(pa_dbusiface_core *core, pa_source *source) { + pa_dbusiface_device *d = NULL; + + pa_assert(core); + pa_assert(source); + + d = pa_xnew0(pa_dbusiface_device, 1); + d->core = core; + d->source = pa_source_ref(source); + d->type = DEVICE_TYPE_SOURCE; + d->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, SOURCE_OBJECT_NAME, source->index); + d->volume = *pa_source_get_volume(source, FALSE); + d->mute = pa_source_get_mute(source, FALSE); + d->source_state = pa_source_get_state(source); + d->ports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + d->next_port_index = 0; + d->active_port = NULL; + d->proplist = pa_proplist_copy(source->proplist); + d->dbus_protocol = pa_dbus_protocol_get(source->core); + d->subscription = pa_subscription_new(source->core, PA_SUBSCRIPTION_MASK_SOURCE, subscription_cb, d); + + if (source->ports) { + pa_device_port *port; + void *state = NULL; + + PA_HASHMAP_FOREACH(port, source->ports, state) { + pa_dbusiface_device_port *p = pa_dbusiface_device_port_new(d, source->core, port, d->next_port_index++); + pa_hashmap_put(d->ports, pa_dbusiface_device_port_get_name(p), p); + } + pa_assert_se(d->active_port = source->active_port); + } + + pa_assert_se(pa_dbus_protocol_add_interface(d->dbus_protocol, d->path, &device_interface_info, d) >= 0); + pa_assert_se(pa_dbus_protocol_add_interface(d->dbus_protocol, d->path, &source_interface_info, d) >= 0); + + return d; +} + +static void port_free_cb(void *p, void *userdata) { + pa_dbusiface_device_port *port = p; + + pa_assert(port); + + pa_dbusiface_device_port_free(port); +} + +void pa_dbusiface_device_free(pa_dbusiface_device *d) { + pa_assert(d); + + pa_assert_se(pa_dbus_protocol_remove_interface(d->dbus_protocol, d->path, device_interface_info.name) >= 0); + + if (d->type == DEVICE_TYPE_SINK) { + pa_assert_se(pa_dbus_protocol_remove_interface(d->dbus_protocol, d->path, sink_interface_info.name) >= 0); + pa_sink_unref(d->sink); + + } else { + pa_assert_se(pa_dbus_protocol_remove_interface(d->dbus_protocol, d->path, source_interface_info.name) >= 0); + pa_source_unref(d->source); + } + pa_hashmap_free(d->ports, port_free_cb, NULL); + pa_proplist_free(d->proplist); + pa_dbus_protocol_unref(d->dbus_protocol); + pa_subscription_free(d->subscription); + + pa_xfree(d->path); + pa_xfree(d); +} + +const char *pa_dbusiface_device_get_path(pa_dbusiface_device *d) { + pa_assert(d); + + return d->path; +} + +pa_sink *pa_dbusiface_device_get_sink(pa_dbusiface_device *d) { + pa_assert(d); + pa_assert(d->type == DEVICE_TYPE_SINK); + + return d->sink; +} + +pa_source *pa_dbusiface_device_get_source(pa_dbusiface_device *d) { + pa_assert(d); + pa_assert(d->type == DEVICE_TYPE_SOURCE); + + return d->source; +} diff --git a/src/modules/dbus/iface-device.h b/src/modules/dbus/iface-device.h new file mode 100644 index 00000000..62e05e9a --- /dev/null +++ b/src/modules/dbus/iface-device.h @@ -0,0 +1,53 @@ +#ifndef foodbusifacedevicehfoo +#define foodbusifacedevicehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interfaces org.PulseAudio.Core1.Device, + * org.PulseAudio.Core1.Sink and org.PulseAudio.Core1.Source. + * + * See http://pulseaudio.org/wiki/DBusInterface for the interface + * documentation. + */ + +#include <pulsecore/protocol-dbus.h> +#include <pulsecore/sink.h> +#include <pulsecore/source.h> + +#include "iface-core.h" + +#define PA_DBUSIFACE_DEVICE_INTERFACE PA_DBUS_CORE_INTERFACE ".Device" +#define PA_DBUSIFACE_SINK_INTERFACE PA_DBUS_CORE_INTERFACE ".Sink" +#define PA_DBUSIFACE_SOURCE_INTERFACE PA_DBUS_CORE_INTERFACE ".Source" + +typedef struct pa_dbusiface_device pa_dbusiface_device; + +pa_dbusiface_device *pa_dbusiface_device_new_sink(pa_dbusiface_core *core, pa_sink *sink); +pa_dbusiface_device *pa_dbusiface_device_new_source(pa_dbusiface_core *core, pa_source *source); +void pa_dbusiface_device_free(pa_dbusiface_device *d); + +const char *pa_dbusiface_device_get_path(pa_dbusiface_device *d); + +pa_sink *pa_dbusiface_device_get_sink(pa_dbusiface_device *d); +pa_source *pa_dbusiface_device_get_source(pa_dbusiface_device *d); + +#endif diff --git a/src/modules/dbus/iface-memstats.c b/src/modules/dbus/iface-memstats.c new file mode 100644 index 00000000..73a84be8 --- /dev/null +++ b/src/modules/dbus/iface-memstats.c @@ -0,0 +1,231 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulsecore/core-scache.h> +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-memstats.h" + +#define OBJECT_NAME "memstats" + +static void handle_get_current_memblocks(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_current_memblocks_size(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_accumulated_memblocks(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_accumulated_memblocks_size(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_sample_cache_size(DBusConnection *conn, DBusMessage *msg, void *userdata); + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata); + +struct pa_dbusiface_memstats { + pa_core *core; + char *path; + pa_dbus_protocol *dbus_protocol; +}; + +enum property_handler_index { + PROPERTY_HANDLER_CURRENT_MEMBLOCKS, + PROPERTY_HANDLER_CURRENT_MEMBLOCKS_SIZE, + PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS, + PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS_SIZE, + PROPERTY_HANDLER_SAMPLE_CACHE_SIZE, + PROPERTY_HANDLER_MAX +}; + +static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = { + [PROPERTY_HANDLER_CURRENT_MEMBLOCKS] = { .property_name = "CurrentMemblocks", .type = "u", .get_cb = handle_get_current_memblocks, .set_cb = NULL }, + [PROPERTY_HANDLER_CURRENT_MEMBLOCKS_SIZE] = { .property_name = "CurrentMemblocksSize", .type = "u", .get_cb = handle_get_current_memblocks_size, .set_cb = NULL }, + [PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS] = { .property_name = "AccumulatedMemblocks", .type = "u", .get_cb = handle_get_accumulated_memblocks, .set_cb = NULL }, + [PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS_SIZE] = { .property_name = "AccumulatedMemblocksSize", .type = "u", .get_cb = handle_get_accumulated_memblocks_size, .set_cb = NULL }, + [PROPERTY_HANDLER_SAMPLE_CACHE_SIZE] = { .property_name = "SampleCacheSize", .type = "u", .get_cb = handle_get_sample_cache_size, .set_cb = NULL } +}; + +static pa_dbus_interface_info memstats_interface_info = { + .name = PA_DBUSIFACE_MEMSTATS_INTERFACE, + .method_handlers = NULL, + .n_method_handlers = 0, + .property_handlers = property_handlers, + .n_property_handlers = PROPERTY_HANDLER_MAX, + .get_all_properties_cb = handle_get_all, + .signals = NULL, + .n_signals = 0 +}; + +static void handle_get_current_memblocks(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_memstats *m = userdata; + const pa_mempool_stat *stat; + dbus_uint32_t current_memblocks; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + stat = pa_mempool_get_stat(m->core->mempool); + + current_memblocks = pa_atomic_load(&stat->n_allocated); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, ¤t_memblocks); +} + +static void handle_get_current_memblocks_size(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_memstats *m = userdata; + const pa_mempool_stat *stat; + dbus_uint32_t current_memblocks_size; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + stat = pa_mempool_get_stat(m->core->mempool); + + current_memblocks_size = pa_atomic_load(&stat->allocated_size); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, ¤t_memblocks_size); +} + +static void handle_get_accumulated_memblocks(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_memstats *m = userdata; + const pa_mempool_stat *stat; + dbus_uint32_t accumulated_memblocks; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + stat = pa_mempool_get_stat(m->core->mempool); + + accumulated_memblocks = pa_atomic_load(&stat->n_accumulated); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &accumulated_memblocks); +} + +static void handle_get_accumulated_memblocks_size(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_memstats *m = userdata; + const pa_mempool_stat *stat; + dbus_uint32_t accumulated_memblocks_size; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + stat = pa_mempool_get_stat(m->core->mempool); + + accumulated_memblocks_size = pa_atomic_load(&stat->accumulated_size); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &accumulated_memblocks_size); +} + +static void handle_get_sample_cache_size(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_memstats *m = userdata; + dbus_uint32_t sample_cache_size; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + sample_cache_size = pa_scache_total_size(m->core); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_cache_size); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_memstats *m = userdata; + const pa_mempool_stat *stat; + dbus_uint32_t current_memblocks; + dbus_uint32_t current_memblocks_size; + dbus_uint32_t accumulated_memblocks; + dbus_uint32_t accumulated_memblocks_size; + dbus_uint32_t sample_cache_size; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + stat = pa_mempool_get_stat(m->core->mempool); + + current_memblocks = pa_atomic_load(&stat->n_allocated); + current_memblocks_size = pa_atomic_load(&stat->allocated_size); + accumulated_memblocks = pa_atomic_load(&stat->n_accumulated); + accumulated_memblocks_size = pa_atomic_load(&stat->accumulated_size); + sample_cache_size = pa_scache_total_size(m->core); + + 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_CURRENT_MEMBLOCKS].property_name, DBUS_TYPE_UINT32, ¤t_memblocks); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CURRENT_MEMBLOCKS_SIZE].property_name, DBUS_TYPE_UINT32, ¤t_memblocks_size); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS].property_name, DBUS_TYPE_UINT32, &accumulated_memblocks); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS_SIZE].property_name, DBUS_TYPE_UINT32, &accumulated_memblocks_size); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_CACHE_SIZE].property_name, DBUS_TYPE_UINT32, &sample_cache_size); + + 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); +} + +pa_dbusiface_memstats *pa_dbusiface_memstats_new(pa_dbusiface_core *dbus_core, pa_core *core) { + pa_dbusiface_memstats *m; + + pa_assert(dbus_core); + pa_assert(core); + + m = pa_xnew(pa_dbusiface_memstats, 1); + m->core = pa_core_ref(core); + m->path = pa_sprintf_malloc("%s/%s", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME); + m->dbus_protocol = pa_dbus_protocol_get(core); + + pa_assert_se(pa_dbus_protocol_add_interface(m->dbus_protocol, m->path, &memstats_interface_info, m) >= 0); + + return m; +} + +void pa_dbusiface_memstats_free(pa_dbusiface_memstats *m) { + pa_assert(m); + + pa_assert_se(pa_dbus_protocol_remove_interface(m->dbus_protocol, m->path, memstats_interface_info.name) >= 0); + + pa_xfree(m->path); + + pa_dbus_protocol_unref(m->dbus_protocol); + pa_core_unref(m->core); + + pa_xfree(m); +} + +const char *pa_dbusiface_memstats_get_path(pa_dbusiface_memstats *m) { + pa_assert(m); + + return m->path; +} diff --git a/src/modules/dbus/iface-memstats.h b/src/modules/dbus/iface-memstats.h new file mode 100644 index 00000000..0820e8fe --- /dev/null +++ b/src/modules/dbus/iface-memstats.h @@ -0,0 +1,45 @@ +#ifndef foodbusifacememstatshfoo +#define foodbusifacememstatshfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.Memstats. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Memstats interface + * documentation. + */ + +#include <pulsecore/core.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-core.h" + +#define PA_DBUSIFACE_MEMSTATS_INTERFACE PA_DBUS_CORE_INTERFACE ".Memstats" + +typedef struct pa_dbusiface_memstats pa_dbusiface_memstats; + +pa_dbusiface_memstats *pa_dbusiface_memstats_new(pa_dbusiface_core *dbus_core, pa_core *core); +void pa_dbusiface_memstats_free(pa_dbusiface_memstats *m); + +const char *pa_dbusiface_memstats_get_path(pa_dbusiface_memstats *m); + +#endif diff --git a/src/modules/dbus/iface-module.c b/src/modules/dbus/iface-module.c new file mode 100644 index 00000000..e8aea50f --- /dev/null +++ b/src/modules/dbus/iface-module.c @@ -0,0 +1,336 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-module.h" + +#define OBJECT_NAME "module" + +struct pa_dbusiface_module { + pa_module *module; + char *path; + pa_proplist *proplist; + + pa_dbus_protocol *dbus_protocol; + pa_subscription *subscription; +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_arguments(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_usage_counter(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_unload(DBusConnection *conn, DBusMessage *msg, void *userdata); + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_ARGUMENTS, + PROPERTY_HANDLER_USAGE_COUNTER, + 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_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL }, + [PROPERTY_HANDLER_ARGUMENTS] = { .property_name = "Arguments", .type = "a{ss}", .get_cb = handle_get_arguments, .set_cb = NULL }, + [PROPERTY_HANDLER_USAGE_COUNTER] = { .property_name = "UsageCounter", .type = "u", .get_cb = handle_get_usage_counter, .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_UNLOAD, + METHOD_HANDLER_MAX +}; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_UNLOAD] = { + .method_name = "Unload", + .arguments = NULL, + .n_arguments = 0, + .receive_cb = handle_unload } +}; + +enum signal_index { + SIGNAL_PROPERTY_LIST_UPDATED, + SIGNAL_MAX +}; + +static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 } +}; + +static pa_dbus_interface_info module_interface_info = { + .name = PA_DBUSIFACE_MODULE_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_module *m = userdata; + dbus_uint32_t idx = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + idx = m->module->index; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_module *m = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &m->module->name); +} + +static void append_modargs_variant(DBusMessageIter *iter, pa_dbusiface_module *m) { + pa_modargs *ma = NULL; + DBusMessageIter variant_iter; + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + void *state = NULL; + const char *key = NULL; + const char *value = NULL; + + pa_assert(iter); + pa_assert(m); + + pa_assert_se(ma = pa_modargs_new(m->module->argument, NULL)); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "a{ss}", &variant_iter)); + pa_assert_se(dbus_message_iter_open_container(&variant_iter, DBUS_TYPE_ARRAY, "{ss}", &dict_iter)); + + for (state = NULL, key = pa_modargs_iterate(ma, &state); key; key = pa_modargs_iterate(ma, &state)) { + pa_assert_se(value = pa_modargs_get_value(ma, key, NULL)); + + pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)); + + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key)); + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &value)); + + pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter)); + } + + pa_assert_se(dbus_message_iter_close_container(&variant_iter, &dict_iter)); + pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter)); + + pa_modargs_free(ma); +} + +static void handle_get_arguments(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_module *m = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + pa_assert_se(reply = dbus_message_new_method_return(msg)); + dbus_message_iter_init_append(reply, &msg_iter); + append_modargs_variant(&msg_iter, m); + pa_assert_se(dbus_connection_send(conn, reply, NULL)); + dbus_message_unref(reply); +} + +static void handle_get_usage_counter(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_module *m = userdata; + int real_counter_value = -1; + dbus_uint32_t usage_counter = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + if (!m->module->get_n_used || (real_counter_value = m->module->get_n_used(m->module)) < 0) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Module %u (%s) doesn't have a usage counter.", m->module->index, m->module->name); + return; + } + + usage_counter = real_counter_value; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &usage_counter); +} + +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_module *m = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + pa_dbus_send_proplist_variant_reply(conn, msg, m->proplist); +} + +static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_module *m = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + dbus_uint32_t idx = 0; + int real_counter_value = -1; + dbus_uint32_t usage_counter = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + idx = m->module->index; + if (m->module->get_n_used && (real_counter_value = m->module->get_n_used(m->module)) >= 0) + usage_counter = real_counter_value; + + 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); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &m->module->name); + + pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)); + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &property_handlers[PROPERTY_HANDLER_ARGUMENTS].property_name)); + append_modargs_variant(&dict_entry_iter, m); + pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter)); + + if (real_counter_value >= 0) + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ARGUMENTS].property_name, DBUS_TYPE_UINT32, &usage_counter); + + pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, m->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_unload(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_module *m = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(m); + + if (m->module->core->disallow_module_loading) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "The server is configured to disallow module unloading."); + return; + } + + pa_module_unload_request(m->module, FALSE); + + pa_dbus_send_empty_reply(conn, msg); +} + +static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_module *m = userdata; + DBusMessage *signal = NULL; + + pa_assert(core); + pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_MODULE); + pa_assert(m); + + /* We can't use idx != m->module->index, because the m->module pointer may + * be stale at this point. */ + if (pa_idxset_get_by_index(core->modules, idx) != m->module) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + if (!pa_proplist_equal(m->proplist, m->module->proplist)) { + DBusMessageIter msg_iter; + + pa_proplist_update(m->proplist, PA_UPDATE_SET, m->module->proplist); + + pa_assert_se(signal = dbus_message_new_signal(m->path, + PA_DBUSIFACE_MODULE_INTERFACE, + signals[SIGNAL_PROPERTY_LIST_UPDATED].name)); + dbus_message_iter_init_append(signal, &msg_iter); + pa_dbus_append_proplist(&msg_iter, m->proplist); + + pa_dbus_protocol_send_signal(m->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } +} + +pa_dbusiface_module *pa_dbusiface_module_new(pa_module *module) { + pa_dbusiface_module *m; + + pa_assert(module); + + m = pa_xnew0(pa_dbusiface_module, 1); + m->module = module; + m->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, module->index); + m->proplist = pa_proplist_copy(module->proplist); + m->dbus_protocol = pa_dbus_protocol_get(module->core); + m->subscription = pa_subscription_new(module->core, PA_SUBSCRIPTION_MASK_MODULE, subscription_cb, m); + + pa_assert_se(pa_dbus_protocol_add_interface(m->dbus_protocol, m->path, &module_interface_info, m) >= 0); + + return m; +} + +void pa_dbusiface_module_free(pa_dbusiface_module *m) { + pa_assert(m); + + pa_assert_se(pa_dbus_protocol_remove_interface(m->dbus_protocol, m->path, module_interface_info.name) >= 0); + + pa_proplist_free(m->proplist); + pa_dbus_protocol_unref(m->dbus_protocol); + pa_subscription_free(m->subscription); + + pa_xfree(m->path); + pa_xfree(m); +} + +const char *pa_dbusiface_module_get_path(pa_dbusiface_module *m) { + pa_assert(m); + + return m->path; +} diff --git a/src/modules/dbus/iface-module.h b/src/modules/dbus/iface-module.h new file mode 100644 index 00000000..68ca1de5 --- /dev/null +++ b/src/modules/dbus/iface-module.h @@ -0,0 +1,45 @@ +#ifndef foodbusifacemodulehfoo +#define foodbusifacemodulehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.Module. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Module interface + * documentation. + */ + +#include <pulsecore/module.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-core.h" + +#define PA_DBUSIFACE_MODULE_INTERFACE PA_DBUS_CORE_INTERFACE ".Module" + +typedef struct pa_dbusiface_module pa_dbusiface_module; + +pa_dbusiface_module *pa_dbusiface_module_new(pa_module *module); +void pa_dbusiface_module_free(pa_dbusiface_module *m); + +const char *pa_dbusiface_module_get_path(pa_dbusiface_module *m); + +#endif diff --git a/src/modules/dbus/iface-sample.c b/src/modules/dbus/iface-sample.c new file mode 100644 index 00000000..b0542a60 --- /dev/null +++ b/src/modules/dbus/iface-sample.c @@ -0,0 +1,519 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/namereg.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-sample.h" + +#define OBJECT_NAME "sample" + +struct pa_dbusiface_sample { + pa_dbusiface_core *core; + + pa_scache_entry *sample; + char *path; + pa_proplist *proplist; + + pa_dbus_protocol *dbus_protocol; + pa_subscription *subscription; +}; + +static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_name(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_default_volume(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_duration(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_get_bytes(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_play(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_play_to_sink(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_remove(DBusConnection *conn, DBusMessage *msg, void *userdata); + +enum property_handler_index { + PROPERTY_HANDLER_INDEX, + PROPERTY_HANDLER_NAME, + PROPERTY_HANDLER_SAMPLE_FORMAT, + PROPERTY_HANDLER_SAMPLE_RATE, + PROPERTY_HANDLER_CHANNELS, + PROPERTY_HANDLER_DEFAULT_VOLUME, + PROPERTY_HANDLER_DURATION, + PROPERTY_HANDLER_BYTES, + 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_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .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_DEFAULT_VOLUME] = { .property_name = "DefaultVolume", .type = "au", .get_cb = handle_get_default_volume, .set_cb = NULL }, + [PROPERTY_HANDLER_DURATION] = { .property_name = "Duration", .type = "t", .get_cb = handle_get_duration, .set_cb = NULL }, + [PROPERTY_HANDLER_BYTES] = { .property_name = "Bytes", .type = "u", .get_cb = handle_get_bytes, .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_PLAY, + METHOD_HANDLER_PLAY_TO_SINK, + METHOD_HANDLER_REMOVE, + METHOD_HANDLER_MAX +}; + +static pa_dbus_arg_info play_args[] = { { "volume", "u", "in" }, { "property_list", "a{say}", "in" } }; +static pa_dbus_arg_info play_to_sink_args[] = { { "sink", "o", "in" }, + { "volume", "u", "in" }, + { "property_list", "a{say}", "in" } }; + +static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = { + [METHOD_HANDLER_PLAY] = { + .method_name = "Play", + .arguments = play_args, + .n_arguments = sizeof(play_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_play }, + [METHOD_HANDLER_PLAY_TO_SINK] = { + .method_name = "PlayToSink", + .arguments = play_to_sink_args, + .n_arguments = sizeof(play_to_sink_args) / sizeof(pa_dbus_arg_info), + .receive_cb = handle_play_to_sink }, + [METHOD_HANDLER_REMOVE] = { + .method_name = "Remove", + .arguments = NULL, + .n_arguments = 0, + .receive_cb = handle_remove } +}; + +enum signal_index { + SIGNAL_PROPERTY_LIST_UPDATED, + SIGNAL_MAX +}; + +static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } }; + +static pa_dbus_signal_info signals[SIGNAL_MAX] = { + [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 } +}; + +static pa_dbus_interface_info sample_interface_info = { + .name = PA_DBUSIFACE_SAMPLE_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_sample *s = userdata; + dbus_uint32_t idx = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + idx = s->sample->index; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx); +} + +static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &s->sample->name); +} + +static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + dbus_uint32_t sample_format = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (!s->sample->memchunk.memblock) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sample %s isn't loaded into memory yet, so its sample format is unknown.", s->sample->name); + return; + } + + sample_format = s->sample->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_sample *s = userdata; + dbus_uint32_t sample_rate = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (!s->sample->memchunk.memblock) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sample %s isn't loaded into memory yet, so its sample rate is unknown.", s->sample->name); + return; + } + + sample_rate = s->sample->sample_spec.rate; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_rate); +} + +static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + dbus_uint32_t channels[PA_CHANNELS_MAX]; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (!s->sample->memchunk.memblock) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sample %s isn't loaded into memory yet, so its channel map is unknown.", s->sample->name); + return; + } + + for (i = 0; i < s->sample->channel_map.channels; ++i) + channels[i] = s->sample->channel_map.map[i]; + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, channels, s->sample->channel_map.channels); +} + +static void handle_get_default_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + dbus_uint32_t default_volume[PA_CHANNELS_MAX]; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (!s->sample->volume_is_set) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sample %s doesn't have default volume stored.", s->sample->name); + return; + } + + for (i = 0; i < s->sample->volume.channels; ++i) + default_volume[i] = s->sample->volume.values[i]; + + pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, default_volume, s->sample->volume.channels); +} + +static void handle_get_duration(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + dbus_uint64_t duration = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (!s->sample->memchunk.memblock) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sample %s isn't loaded into memory yet, so its duration is unknown.", s->sample->name); + return; + } + + duration = pa_bytes_to_usec(s->sample->memchunk.length, &s->sample->sample_spec); + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &duration); +} + +static void handle_get_bytes(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + dbus_uint32_t bytes = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (!s->sample->memchunk.memblock) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Sample %s isn't loaded into memory yet, so its size is unknown.", s->sample->name); + return; + } + + bytes = s->sample->memchunk.length; + + pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &bytes); +} + +static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *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_sample *s = userdata; + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + dbus_uint32_t idx = 0; + dbus_uint32_t sample_format = 0; + dbus_uint32_t sample_rate = 0; + dbus_uint32_t channels[PA_CHANNELS_MAX]; + dbus_uint32_t default_volume[PA_CHANNELS_MAX]; + dbus_uint64_t duration = 0; + dbus_uint32_t bytes = 0; + unsigned i = 0; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + idx = s->sample->index; + if (s->sample->memchunk.memblock) { + sample_format = s->sample->sample_spec.format; + sample_rate = s->sample->sample_spec.rate; + for (i = 0; i < s->sample->channel_map.channels; ++i) + channels[i] = s->sample->channel_map.map[i]; + duration = pa_bytes_to_usec(s->sample->memchunk.length, &s->sample->sample_spec); + bytes = s->sample->memchunk.length; + } + if (s->sample->volume_is_set) { + for (i = 0; i < s->sample->volume.channels; ++i) + default_volume[i] = s->sample->volume.values[i]; + } + + 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); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &s->sample->name); + + if (s->sample->memchunk.memblock) { + 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, &sample_rate); + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CHANNELS].property_name, DBUS_TYPE_UINT32, channels, s->sample->channel_map.channels); + } + + if (s->sample->volume_is_set) + pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEFAULT_VOLUME].property_name, DBUS_TYPE_UINT32, default_volume, s->sample->volume.channels); + + if (s->sample->memchunk.memblock) { + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DURATION].property_name, DBUS_TYPE_UINT64, &duration); + pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_BYTES].property_name, DBUS_TYPE_UINT32, &bytes); + } + + 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_play(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + DBusMessageIter msg_iter; + dbus_uint32_t volume = 0; + pa_proplist *property_list = NULL; + pa_sink *sink = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + pa_assert_se(dbus_message_iter_init(msg, &msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &volume); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter))) + return; + + if (volume > PA_VOLUME_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume."); + goto finish; + } + + if (!(sink = pa_namereg_get_default_sink(s->sample->core))) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, + "Can't play sample %s, because there are no sinks available.", s->sample->name); + goto finish; + } + + if (pa_scache_play_item(s->sample->core, s->sample->name, sink, volume, property_list, NULL) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Playing sample %s failed.", s->sample->name); + goto finish; + } + + pa_dbus_send_empty_reply(conn, msg); + +finish: + if (property_list) + pa_proplist_free(property_list); +} + +static void handle_play_to_sink(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + DBusMessageIter msg_iter; + const char *sink_path = NULL; + dbus_uint32_t volume = 0; + pa_proplist *property_list = NULL; + pa_sink *sink = NULL; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + pa_assert_se(dbus_message_iter_init(msg, &msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &sink_path); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &volume); + + pa_assert_se(dbus_message_iter_next(&msg_iter)); + if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter))) + return; + + if (!(sink = pa_dbusiface_core_get_sink(s->core, sink_path))) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such sink.", sink_path); + goto finish; + } + + if (volume > PA_VOLUME_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume."); + goto finish; + } + + if (pa_scache_play_item(s->sample->core, s->sample->name, sink, volume, property_list, NULL) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Playing sample %s failed.", s->sample->name); + goto finish; + } + + pa_dbus_send_empty_reply(conn, msg); + +finish: + if (property_list) + pa_proplist_free(property_list); +} + +static void handle_remove(DBusConnection *conn, DBusMessage *msg, void *userdata) { + pa_dbusiface_sample *s = userdata; + + pa_assert(conn); + pa_assert(msg); + pa_assert(s); + + if (pa_scache_remove_item(s->sample->core, s->sample->name) < 0) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Removing sample %s failed.", s->sample->name); + return; + } + + pa_dbus_send_empty_reply(conn, msg); +} + +static void subscription_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_sample *s = userdata; + DBusMessage *signal = NULL; + + pa_assert(c); + pa_assert(s); + + /* We can't use idx != s->sample->index, because the s->sample pointer may + * be stale at this point. */ + if (pa_idxset_get_by_index(c->scache, idx) != s->sample) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + if (!pa_proplist_equal(s->proplist, s->sample->proplist)) { + DBusMessageIter msg_iter; + + pa_proplist_update(s->proplist, PA_UPDATE_SET, s->sample->proplist); + + pa_assert_se(signal = dbus_message_new_signal(s->path, + PA_DBUSIFACE_SAMPLE_INTERFACE, + signals[SIGNAL_PROPERTY_LIST_UPDATED].name)); + dbus_message_iter_init_append(signal, &msg_iter); + pa_dbus_append_proplist(&msg_iter, s->proplist); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } +} + +pa_dbusiface_sample *pa_dbusiface_sample_new(pa_dbusiface_core *core, pa_scache_entry *sample) { + pa_dbusiface_sample *s = NULL; + + pa_assert(core); + pa_assert(sample); + + s = pa_xnew0(pa_dbusiface_sample, 1); + s->core = core; + s->sample = sample; + s->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, sample->index); + s->proplist = pa_proplist_copy(sample->proplist); + s->dbus_protocol = pa_dbus_protocol_get(sample->core); + s->subscription = pa_subscription_new(sample->core, PA_SUBSCRIPTION_MASK_SAMPLE_CACHE, subscription_cb, s); + + pa_assert_se(pa_dbus_protocol_add_interface(s->dbus_protocol, s->path, &sample_interface_info, s) >= 0); + + return s; +} + +void pa_dbusiface_sample_free(pa_dbusiface_sample *s) { + pa_assert(s); + + pa_assert_se(pa_dbus_protocol_remove_interface(s->dbus_protocol, s->path, sample_interface_info.name) >= 0); + + pa_proplist_free(s->proplist); + pa_dbus_protocol_unref(s->dbus_protocol); + pa_subscription_free(s->subscription); + + pa_xfree(s->path); + pa_xfree(s); +} + +const char *pa_dbusiface_sample_get_path(pa_dbusiface_sample *s) { + pa_assert(s); + + return s->path; +} diff --git a/src/modules/dbus/iface-sample.h b/src/modules/dbus/iface-sample.h new file mode 100644 index 00000000..f1947ce8 --- /dev/null +++ b/src/modules/dbus/iface-sample.h @@ -0,0 +1,45 @@ +#ifndef foodbusifacesamplehfoo +#define foodbusifacesamplehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.Sample. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Sample interface + * documentation. + */ + +#include <pulsecore/core-scache.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-core.h" + +#define PA_DBUSIFACE_SAMPLE_INTERFACE PA_DBUS_CORE_INTERFACE ".Sample" + +typedef struct pa_dbusiface_sample pa_dbusiface_sample; + +pa_dbusiface_sample *pa_dbusiface_sample_new(pa_dbusiface_core *core, pa_scache_entry *sample); +void pa_dbusiface_sample_free(pa_dbusiface_sample *c); + +const char *pa_dbusiface_sample_get_path(pa_dbusiface_sample *c); + +#endif diff --git a/src/modules/dbus/iface-stream.c b/src/modules/dbus/iface-stream.c new file mode 100644 index 00000000..04a45e6c --- /dev/null +++ b/src/modules/dbus/iface-stream.c @@ -0,0 +1,894 @@ +/*** + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#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; + + pa_dbus_protocol *dbus_protocol; + pa_subscription *subscription; + pa_hook_slot *send_event_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); +} + +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) { + if (s->type == STREAM_TYPE_PLAYBACK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Playback stream %u doesn't have a driver.", s->sink_input->index); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Record stream %u doesn't have a driver.", s->source_output->index); + 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) { + if (s->type == STREAM_TYPE_PLAYBACK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Playback stream %u doesn't have an owner module.", s->sink_input->index); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Record stream %u doesn't have an owner module.", s->source_output->index); + 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) { + if (s->type == STREAM_TYPE_PLAYBACK) + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Playback stream %u isn't associated to any client.", s->sink_input->index); + else + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, + "Record stream %u isn't associated to any client.", s->source_output->index); + 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->type == STREAM_TYPE_RECORD) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Record streams don't have volume."); + 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; + 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); + + if (s->type == STREAM_TYPE_RECORD) { + pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Record streams don't have volume."); + return; + } + + pa_cvolume_init(&new_vol); + + stream_channels = s->sink_input->channel_map.channels; + + new_vol.channels = stream_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) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, + "Expected %u volume entries, got %u.", stream_channels, n_volume_entries); + return; + } + + for (i = 0; i < n_volume_entries; ++i) { + if (volume[i] > PA_VOLUME_MAX) { + pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Too large volume value: %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); + + 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->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; + for (i = 0; i < s->volume.channels; ++i) + volume[i] = s->volume.values[i]; + 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]; + + 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_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->type == STREAM_TYPE_PLAYBACK) { + 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_STRING, &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 subscription_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + pa_dbusiface_stream *s = userdata; + DBusMessage *signal = NULL; + const char *new_device_path = NULL; + uint32_t new_sample_rate = 0; + pa_proplist *new_proplist = NULL; + unsigned i = 0; + + pa_assert(c); + pa_assert(s); + + if ((s->type == STREAM_TYPE_PLAYBACK && idx != s->sink_input->index) + || (s->type == STREAM_TYPE_RECORD && idx != s->source_output->index)) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + pa_assert(((s->type == STREAM_TYPE_PLAYBACK) + && ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT)) + || ((s->type == STREAM_TYPE_RECORD) + && ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT))); + + 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 = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_DEVICE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &new_device_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + } 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 = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_DEVICE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &new_device_path, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + } + + 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 = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_SAMPLE_RATE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_UINT32, &s->sample_rate, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + + if (s->type == STREAM_TYPE_PLAYBACK) { + pa_cvolume new_volume; + pa_bool_t new_mute = FALSE; + + 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 = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_VOLUME_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, + DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &volume_ptr, s->volume.channels, + DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + + new_mute = pa_sink_input_get_mute(s->sink_input); + + if (s->mute != new_mute) { + s->mute = new_mute; + + pa_assert_se(signal = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_MUTE_UPDATED].name)); + pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_BOOLEAN, &s->mute, DBUS_TYPE_INVALID)); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } + } + + 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 = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_PROPERTY_LIST_UPDATED].name)); + dbus_message_iter_init_append(signal, &msg_iter); + pa_dbus_append_proplist(&msg_iter, s->proplist); + + pa_dbus_protocol_send_signal(s->dbus_protocol, signal); + dbus_message_unref(signal); + signal = NULL; + } +} + +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 = 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 = dbus_message_new_signal(s->path, + PA_DBUSIFACE_STREAM_INTERFACE, + signals[SIGNAL_STREAM_EVENT].name)); + dbus_message_iter_init_append(signal, &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); + dbus_message_unref(signal); + + 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; + pa_sink_input_get_volume(sink_input, &s->volume, TRUE); + s->mute = pa_sink_input_get_mute(sink_input); + s->proplist = pa_proplist_copy(sink_input->proplist); + s->dbus_protocol = pa_dbus_protocol_get(sink_input->core); + s->subscription = pa_subscription_new(sink_input->core, PA_SUBSCRIPTION_MASK_SINK_INPUT, subscription_cb, s); + 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); + + 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->dbus_protocol = pa_dbus_protocol_get(source_output->core); + s->subscription = pa_subscription_new(source_output->core, PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscription_cb, s); + 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); + + 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_subscription_free(s->subscription); + pa_hook_slot_free(s->send_event_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; +} diff --git a/src/modules/dbus/iface-stream.h b/src/modules/dbus/iface-stream.h new file mode 100644 index 00000000..036b4e7e --- /dev/null +++ b/src/modules/dbus/iface-stream.h @@ -0,0 +1,47 @@ +#ifndef foodbusifacestreamhfoo +#define foodbusifacestreamhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* This object implements the D-Bus interface org.PulseAudio.Core1.Stream. + * + * See http://pulseaudio.org/wiki/DBusInterface for the Stream interface + * documentation. + */ + +#include <pulsecore/protocol-dbus.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> + +#include "iface-core.h" + +#define PA_DBUSIFACE_STREAM_INTERFACE PA_DBUS_CORE_INTERFACE ".Stream" + +typedef struct pa_dbusiface_stream pa_dbusiface_stream; + +pa_dbusiface_stream *pa_dbusiface_stream_new_playback(pa_dbusiface_core *core, pa_sink_input *sink_input); +pa_dbusiface_stream *pa_dbusiface_stream_new_record(pa_dbusiface_core *core, pa_source_output *source_output); +void pa_dbusiface_stream_free(pa_dbusiface_stream *s); + +const char *pa_dbusiface_stream_get_path(pa_dbusiface_stream *s); + +#endif diff --git a/src/modules/dbus/module-dbus-protocol.c b/src/modules/dbus/module-dbus-protocol.c new file mode 100644 index 00000000..11064c33 --- /dev/null +++ b/src/modules/dbus/module-dbus-protocol.c @@ -0,0 +1,607 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + Copyright 2006 Lennart Poettering + Copyright 2006 Shams E. King + + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulse/mainloop-api.h> +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/client.h> +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/idxset.h> +#include <pulsecore/macro.h> +#include <pulsecore/modargs.h> +#include <pulsecore/module.h> +#include <pulsecore/protocol-dbus.h> + +#include "iface-client.h" +#include "iface-core.h" + +#include "module-dbus-protocol-symdef.h" + +PA_MODULE_DESCRIPTION("D-Bus interface"); +PA_MODULE_USAGE( + "access=local|remote|local,remote " + "tcp_port=<port number>"); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_AUTHOR("Tanu Kaskinen"); +PA_MODULE_VERSION(PACKAGE_VERSION); + +#define CLEANUP_INTERVAL 10 /* seconds */ + +enum server_type { + SERVER_TYPE_LOCAL, + SERVER_TYPE_TCP +}; + +struct server; +struct connection; + +struct userdata { + pa_module *module; + pa_bool_t local_access; + pa_bool_t remote_access; + uint32_t tcp_port; + + struct server *local_server; + struct server *tcp_server; + + pa_idxset *connections; + + pa_time_event *cleanup_event; + + pa_dbus_protocol *dbus_protocol; + pa_dbusiface_core *core_iface; +}; + +struct server { + struct userdata *userdata; + enum server_type type; + DBusServer *dbus_server; +}; + +struct connection { + struct server *server; + pa_dbus_wrap_connection *wrap_conn; + pa_client *client; +}; + +static const char* const valid_modargs[] = { + "access", + "tcp_port", + NULL +}; + +static void connection_free(struct connection *c) { + pa_assert(c); + + pa_assert_se(pa_dbus_protocol_unregister_connection(c->server->userdata->dbus_protocol, pa_dbus_wrap_connection_get(c->wrap_conn)) >= 0); + + pa_client_free(c->client); + pa_assert_se(pa_idxset_remove_by_data(c->server->userdata->connections, c, NULL)); + pa_dbus_wrap_connection_free(c->wrap_conn); + pa_xfree(c); +} + +/* Called from pa_client_kill(). */ +static void client_kill_cb(pa_client *c) { + struct connection *conn; + + pa_assert(c); + pa_assert(c->userdata); + + conn = c->userdata; + connection_free(conn); + c->userdata = NULL; + + pa_log_info("Connection killed."); +} + +/* Called from pa_client_send_event(). */ +static void client_send_event_cb(pa_client *c, const char *name, pa_proplist *data) { + struct connection *conn = NULL; + DBusMessage *signal = NULL; + DBusMessageIter msg_iter; + + pa_assert(c); + pa_assert(name); + pa_assert(data); + pa_assert(c->userdata); + + conn = c->userdata; + + pa_assert_se(signal = dbus_message_new_signal(pa_dbusiface_core_get_client_path(conn->server->userdata->core_iface, c), + PA_DBUSIFACE_CLIENT_INTERFACE, + "ClientEvent")); + dbus_message_iter_init_append(signal, &msg_iter); + pa_assert_se(dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &name)); + pa_dbus_append_proplist(&msg_iter, data); + + pa_assert_se(dbus_connection_send(pa_dbus_wrap_connection_get(conn->wrap_conn), signal, NULL)); + dbus_message_unref(signal); +} + +/* Called by D-Bus at the authentication phase. */ +static dbus_bool_t user_check_cb(DBusConnection *connection, unsigned long uid, void *data) { + pa_log_debug("Allowing connection by user %lu.", uid); + + return TRUE; +} + +/* Called by D-Bus when a new client connection is received. */ +static void connection_new_cb(DBusServer *dbus_server, DBusConnection *new_connection, void *data) { + struct server *s = data; + struct connection *c; + pa_client_new_data new_data; + pa_client *client; + + pa_assert(new_connection); + pa_assert(s); + + pa_client_new_data_init(&new_data); + new_data.module = s->userdata->module; + new_data.driver = __FILE__; + pa_proplist_sets(new_data.proplist, PA_PROP_APPLICATION_NAME, "D-Bus client"); + client = pa_client_new(s->userdata->module->core, &new_data); + pa_client_new_data_done(&new_data); + + if (!client) { + dbus_connection_close(new_connection); + return; + } + + if (s->type == SERVER_TYPE_TCP || s->userdata->module->core->server_type == PA_SERVER_TYPE_SYSTEM) { + /* FIXME: Here we allow anyone from anywhere to access the server, + * anonymously. Access control should be configurable. */ + dbus_connection_set_unix_user_function(new_connection, user_check_cb, NULL, NULL); + dbus_connection_set_allow_anonymous(new_connection, TRUE); + } + + c = pa_xnew(struct connection, 1); + c->server = s; + c->wrap_conn = pa_dbus_wrap_connection_new_from_existing(s->userdata->module->core->mainloop, TRUE, new_connection); + c->client = client; + + c->client->kill = client_kill_cb; + c->client->send_event = client_send_event_cb; + c->client->userdata = c; + + pa_idxset_put(s->userdata->connections, c, NULL); + + pa_assert_se(pa_dbus_protocol_register_connection(s->userdata->dbus_protocol, new_connection, c->client) >= 0); +} + +/* Called by PA mainloop when a D-Bus fd watch event needs handling. */ +static void io_event_cb(pa_mainloop_api *mainloop, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { + unsigned int flags = 0; + DBusWatch *watch = userdata; + +#if HAVE_DBUS_WATCH_GET_UNIX_FD + pa_assert(fd == dbus_watch_get_unix_fd(watch)); +#else + pa_assert(fd == dbus_watch_get_fd(watch)); +#endif + + if (!dbus_watch_get_enabled(watch)) { + pa_log_warn("Asked to handle disabled watch: %p %i", (void*) watch, fd); + return; + } + + if (events & PA_IO_EVENT_INPUT) + flags |= DBUS_WATCH_READABLE; + if (events & PA_IO_EVENT_OUTPUT) + flags |= DBUS_WATCH_WRITABLE; + if (events & PA_IO_EVENT_HANGUP) + flags |= DBUS_WATCH_HANGUP; + if (events & PA_IO_EVENT_ERROR) + flags |= DBUS_WATCH_ERROR; + + dbus_watch_handle(watch, flags); +} + +/* Called by PA mainloop when a D-Bus timer event needs handling. */ +static void time_event_cb(pa_mainloop_api *mainloop, pa_time_event* e, const struct timeval *tv, void *userdata) { + DBusTimeout *timeout = userdata; + + if (dbus_timeout_get_enabled(timeout)) { + struct timeval next = *tv; + dbus_timeout_handle(timeout); + + /* restart it for the next scheduled time */ + pa_timeval_add(&next, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000); + mainloop->time_restart(e, &next); + } +} + +/* Translates D-Bus fd watch event flags to PA IO event flags. */ +static pa_io_event_flags_t get_watch_flags(DBusWatch *watch) { + unsigned int flags; + pa_io_event_flags_t events = 0; + + pa_assert(watch); + + flags = dbus_watch_get_flags(watch); + + /* no watch flags for disabled watches */ + if (!dbus_watch_get_enabled(watch)) + return PA_IO_EVENT_NULL; + + if (flags & DBUS_WATCH_READABLE) + events |= PA_IO_EVENT_INPUT; + if (flags & DBUS_WATCH_WRITABLE) + events |= PA_IO_EVENT_OUTPUT; + + return events | PA_IO_EVENT_HANGUP | PA_IO_EVENT_ERROR; +} + +/* Called by D-Bus when a D-Bus fd watch event is added. */ +static dbus_bool_t watch_add_cb(DBusWatch *watch, void *data) { + struct server *s = data; + pa_mainloop_api *mainloop; + pa_io_event *ev; + + pa_assert(watch); + pa_assert(s); + + mainloop = s->userdata->module->core->mainloop; + + ev = mainloop->io_new( + mainloop, +#if HAVE_DBUS_WATCH_GET_UNIX_FD + dbus_watch_get_unix_fd(watch), +#else + dbus_watch_get_fd(watch), +#endif + get_watch_flags(watch), io_event_cb, watch); + + dbus_watch_set_data(watch, ev, NULL); + + return TRUE; +} + +/* Called by D-Bus when a D-Bus fd watch event is removed. */ +static void watch_remove_cb(DBusWatch *watch, void *data) { + struct server *s = data; + pa_io_event *ev; + + pa_assert(watch); + pa_assert(s); + + if ((ev = dbus_watch_get_data(watch))) + s->userdata->module->core->mainloop->io_free(ev); +} + +/* Called by D-Bus when a D-Bus fd watch event is toggled. */ +static void watch_toggled_cb(DBusWatch *watch, void *data) { + struct server *s = data; + pa_io_event *ev; + + pa_assert(watch); + pa_assert(s); + + pa_assert_se(ev = dbus_watch_get_data(watch)); + + /* get_watch_flags() checks if the watch is enabled */ + s->userdata->module->core->mainloop->io_enable(ev, get_watch_flags(watch)); +} + +/* Called by D-Bus when a D-Bus timer event is added. */ +static dbus_bool_t timeout_add_cb(DBusTimeout *timeout, void *data) { + struct server *s = data; + pa_mainloop_api *mainloop; + pa_time_event *ev; + struct timeval tv; + + pa_assert(timeout); + pa_assert(s); + + if (!dbus_timeout_get_enabled(timeout)) + return FALSE; + + mainloop = s->userdata->module->core->mainloop; + + pa_gettimeofday(&tv); + pa_timeval_add(&tv, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000); + + ev = mainloop->time_new(mainloop, &tv, time_event_cb, timeout); + + dbus_timeout_set_data(timeout, ev, NULL); + + return TRUE; +} + +/* Called by D-Bus when a D-Bus timer event is removed. */ +static void timeout_remove_cb(DBusTimeout *timeout, void *data) { + struct server *s = data; + pa_time_event *ev; + + pa_assert(timeout); + pa_assert(s); + + if ((ev = dbus_timeout_get_data(timeout))) + s->userdata->module->core->mainloop->time_free(ev); +} + +/* Called by D-Bus when a D-Bus timer event is toggled. */ +static void timeout_toggled_cb(DBusTimeout *timeout, void *data) { + struct server *s = data; + pa_mainloop_api *mainloop; + pa_time_event *ev; + + pa_assert(timeout); + pa_assert(s); + + mainloop = s->userdata->module->core->mainloop; + + pa_assert_se(ev = dbus_timeout_get_data(timeout)); + + if (dbus_timeout_get_enabled(timeout)) { + struct timeval tv; + + pa_gettimeofday(&tv); + pa_timeval_add(&tv, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000); + + mainloop->time_restart(ev, &tv); + } else + mainloop->time_restart(ev, NULL); +} + +static void server_free(struct server *s) { + pa_assert(s); + + if (s->dbus_server) { + dbus_server_disconnect(s->dbus_server); + dbus_server_unref(s->dbus_server); + } + + pa_xfree(s); +} + +static struct server *start_server(struct userdata *u, const char *address, enum server_type type) { + /* XXX: We assume that when we unref the DBusServer instance at module + * shutdown, nobody else holds any references to it. If we stop assuming + * that someday, dbus_server_set_new_connection_function, + * dbus_server_set_watch_functions and dbus_server_set_timeout_functions + * calls should probably register free callbacks, instead of providing NULL + * as they do now. */ + + struct server *s = NULL; + DBusError error; + + pa_assert(u); + pa_assert(address); + + dbus_error_init(&error); + + s = pa_xnew0(struct server, 1); + s->userdata = u; + s->dbus_server = dbus_server_listen(address, &error); + + if (dbus_error_is_set(&error)) { + pa_log("dbus_server_listen() failed: %s: %s", error.name, error.message); + goto fail; + } + + dbus_server_set_new_connection_function(s->dbus_server, connection_new_cb, s, NULL); + + if (!dbus_server_set_watch_functions(s->dbus_server, watch_add_cb, watch_remove_cb, watch_toggled_cb, s, NULL)) { + pa_log("dbus_server_set_watch_functions() ran out of memory."); + goto fail; + } + + if (!dbus_server_set_timeout_functions(s->dbus_server, timeout_add_cb, timeout_remove_cb, timeout_toggled_cb, s, NULL)) { + pa_log("dbus_server_set_timeout_functions() ran out of memory."); + goto fail; + } + + return s; + +fail: + if (s) + server_free(s); + + dbus_error_free(&error); + + return NULL; +} + +static struct server *start_local_server(struct userdata *u) { + struct server *s = NULL; + char *address = NULL; + + pa_assert(u); + + address = pa_get_dbus_address_from_server_type(u->module->core->server_type); + + s = start_server(u, address, SERVER_TYPE_LOCAL); /* May return NULL */ + + pa_xfree(address); + + return s; +} + +static struct server *start_tcp_server(struct userdata *u) { + struct server *s = NULL; + char *address = NULL; + + pa_assert(u); + + address = pa_sprintf_malloc("tcp:host=127.0.0.1,port=%u", u->tcp_port); + + s = start_server(u, address, SERVER_TYPE_TCP); /* May return NULL */ + + pa_xfree(address); + + return s; +} + +static int get_access_arg(pa_modargs *ma, pa_bool_t *local_access, pa_bool_t *remote_access) { + const char *value = NULL; + + pa_assert(ma); + pa_assert(local_access); + pa_assert(remote_access); + + if (!(value = pa_modargs_get_value(ma, "access", NULL))) + return 0; + + if (!strcmp(value, "local")) { + *local_access = TRUE; + *remote_access = FALSE; + } else if (!strcmp(value, "remote")) { + *local_access = FALSE; + *remote_access = TRUE; + } else if (!strcmp(value, "local,remote")) { + *local_access = TRUE; + *remote_access = TRUE; + } else + return -1; + + return 0; +} + +/* Frees dead client connections. Called every CLEANUP_INTERVAL seconds. */ +static void cleanup_cb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *tv, void *userdata) { + struct userdata *u = userdata; + struct connection *conn = NULL; + uint32_t idx; + struct timeval cleanup_timeval; + unsigned free_count = 0; + + for (conn = pa_idxset_first(u->connections, &idx); conn; conn = pa_idxset_next(u->connections, &idx)) { + if (!dbus_connection_get_is_connected(pa_dbus_wrap_connection_get(conn->wrap_conn))) { + connection_free(conn); + ++free_count; + } + } + + if (free_count > 0) + pa_log_debug("Freed %u dead D-Bus client connections.", free_count); + + pa_gettimeofday(&cleanup_timeval); + cleanup_timeval.tv_sec += CLEANUP_INTERVAL; + u->module->core->mainloop->time_restart(e, &cleanup_timeval); +} + +int pa__init(pa_module *m) { + struct userdata *u = NULL; + pa_modargs *ma = NULL; + struct timeval cleanup_timeval; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->module = m; + u->local_access = TRUE; + u->remote_access = FALSE; + u->tcp_port = PA_DBUS_DEFAULT_PORT; + + if (get_access_arg(ma, &u->local_access, &u->remote_access) < 0) { + pa_log("Invalid access argument: '%s'", pa_modargs_get_value(ma, "access", NULL)); + goto fail; + } + + if (pa_modargs_get_value_u32(ma, "tcp_port", &u->tcp_port) < 0 || u->tcp_port < 1 || u->tcp_port > 49150) { + pa_log("Invalid tcp_port argument: '%s'", pa_modargs_get_value(ma, "tcp_port", NULL)); + goto fail; + } + + if (u->local_access && !(u->local_server = start_local_server(u))) { + pa_log("Starting the local D-Bus server failed."); + goto fail; + } + + if (u->remote_access && !(u->tcp_server = start_tcp_server(u))) { + pa_log("Starting the D-Bus server for remote connections failed."); + goto fail; + } + + u->connections = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + pa_gettimeofday(&cleanup_timeval); + cleanup_timeval.tv_sec += CLEANUP_INTERVAL; + u->cleanup_event = m->core->mainloop->time_new(m->core->mainloop, &cleanup_timeval, cleanup_cb, u); + + u->dbus_protocol = pa_dbus_protocol_get(m->core); + u->core_iface = pa_dbusiface_core_new(m->core); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +/* Called by idxset when the connection set is freed. */ +static void connection_free_cb(void *p, void *userdata) { + struct connection *conn = p; + + pa_assert(conn); + + connection_free(conn); +} + +void pa__done(pa_module *m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->core_iface) + pa_dbusiface_core_free(u->core_iface); + + if (u->cleanup_event) + m->core->mainloop->time_free(u->cleanup_event); + + if (u->connections) + pa_idxset_free(u->connections, connection_free_cb, NULL); + + if (u->tcp_server) + server_free(u->tcp_server); + + if (u->local_server) + server_free(u->local_server); + + if (u->dbus_protocol) + pa_dbus_protocol_unref(u->dbus_protocol); + + pa_xfree(u); + m->userdata = NULL; +} |