libprotocol-simple.la \
libprotocol-http.la \
libprotocol-native.la \
+ libtunnel-manager.la \
libvolume-api.la
if HAVE_WEBRTC
libprotocol_native_la_LIBADD += $(DBUS_LIBS)
endif
+libtunnel_manager_la_SOURCES = \
+ modules/tunnel-manager/remote-device.c modules/tunnel-manager/remote-device.h \
+ modules/tunnel-manager/remote-server.c modules/tunnel-manager/remote-server.h \
+ modules/tunnel-manager/tunnel-manager.c modules/tunnel-manager/tunnel-manager.h \
+ modules/tunnel-manager/tunnel-manager-config.c modules/tunnel-manager/tunnel-manager-config.h
+libtunnel_manager_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version
+libtunnel_manager_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJORMINOR@.la libpulse.la
+
libvolume_api_la_SOURCES = \
modules/volume-api/audio-group.c modules/volume-api/audio-group.h \
modules/volume-api/bvolume.h \
module-cli-protocol-tcp.la \
module-main-volume-policy.la \
module-simple-protocol-tcp.la \
+ module-tunnel-manager.la \
module-volume-api.la \
module-null-sink.la \
module-null-source.la \
module-pipe-source-symdef.h \
module-simple-protocol-tcp-symdef.h \
module-simple-protocol-unix-symdef.h \
+ module-tunnel-manager-symdef.h \
module-volume-api-symdef.h \
module-native-protocol-tcp-symdef.h \
module-native-protocol-unix-symdef.h \
module_simple_protocol_unix_la_LDFLAGS = $(MODULE_LDFLAGS)
module_simple_protocol_unix_la_LIBADD = $(MODULE_LIBADD) libprotocol-simple.la
+# Tunnel manager
+
+module_tunnel_manager_la_SOURCES = modules/tunnel-manager/module-tunnel-manager.c
+module_tunnel_manager_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_tunnel_manager_la_LIBADD = $(MODULE_LIBADD) libtunnel-manager.la
+
# Volume API
module_volume_api_la_SOURCES = \
--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2014 Intel Corporation
+
+ 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 "module-tunnel-manager-symdef.h"
+
+#include <modules/tunnel-manager/tunnel-manager.h>
+
+#include <pulsecore/i18n.h>
+
+PA_MODULE_AUTHOR("Tanu Kaskinen");
+PA_MODULE_DESCRIPTION(_("Manage tunnels to other PulseAudio servers"));
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(true);
+
+struct userdata {
+ pa_tunnel_manager *tunnel_manager;
+};
+
+int pa__init(pa_module *module) {
+ struct userdata *u;
+
+ pa_assert(module);
+
+ u = module->userdata = pa_xnew0(struct userdata, 1);
+ u->tunnel_manager = pa_tunnel_manager_get(module->core, true);
+
+ return 0;
+}
+
+void pa__done(pa_module *module) {
+ struct userdata *u;
+
+ pa_assert(module);
+
+ u = module->userdata;
+ if (!u)
+ return;
+
+ if (u->tunnel_manager)
+ pa_tunnel_manager_unref(u->tunnel_manager);
+
+ pa_xfree(u);
+}
--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2014 Intel Corporation
+
+ 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 "remote-device.h"
+
+#include <pulse/error.h>
+#include <pulse/introspect.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/device-type.h>
+#include <pulsecore/namereg.h>
+
+void pa_tunnel_manager_remote_device_new(pa_tunnel_manager_remote_server *server, pa_device_type_t type, const void *info) {
+ const char *name = NULL;
+ uint32_t idx = PA_INVALID_INDEX;
+ pa_proplist *proplist = NULL;
+ const pa_sample_spec *sample_spec = NULL;
+ const pa_channel_map *channel_map = NULL;
+ bool is_monitor = false;
+ pa_tunnel_manager_remote_device *device;
+ unsigned i;
+ char sample_spec_str[PA_SAMPLE_SPEC_SNPRINT_MAX];
+ char channel_map_str[PA_CHANNEL_MAP_SNPRINT_MAX];
+
+ pa_assert(server);
+ pa_assert(info);
+
+ switch (type) {
+ case PA_DEVICE_TYPE_SINK: {
+ const pa_sink_info *sink_info = info;
+
+ name = sink_info->name;
+ idx = sink_info->index;
+ proplist = sink_info->proplist;
+ sample_spec = &sink_info->sample_spec;
+ channel_map = &sink_info->channel_map;
+ break;
+ }
+
+ case PA_DEVICE_TYPE_SOURCE: {
+ const pa_source_info *source_info = info;
+
+ name = source_info->name;
+ idx = source_info->index;
+ proplist = source_info->proplist;
+ sample_spec = &source_info->sample_spec;
+ channel_map = &source_info->channel_map;
+ is_monitor = !!source_info->monitor_of_sink_name;
+
+ break;
+ }
+ }
+
+ /* TODO: This check should be done in libpulse. */
+ if (!name || !pa_namereg_is_valid_name(name)) {
+ pa_log("[%s] Invalid remote device name: %s", server->name, pa_strnull(name));
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ return;
+ }
+
+ if (pa_hashmap_get(server->devices, name)) {
+ pa_log("[%s] Duplicate remote device name: %s", server->name, name);
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ return;
+ }
+
+ if (pa_hashmap_size(server->devices) + pa_hashmap_size(server->device_stubs) >= PA_TUNNEL_MANAGER_MAX_DEVICES_PER_SERVER) {
+ pa_log("[%s] Maximum number of devices exceeded.", server->name);
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ return;
+ }
+
+ /* TODO: This check should be done in libpulse. */
+ if (!pa_sample_spec_valid(sample_spec)) {
+ pa_log("[%s %s] Invalid sample spec.", server->name, name);
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ return;
+ }
+
+ /* TODO: This check should be done in libpulse. */
+ if (!pa_channel_map_valid(channel_map)) {
+ pa_log("[%s %s] Invalid channel map.", server->name, name);
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ return;
+ }
+
+ device = pa_xnew0(pa_tunnel_manager_remote_device, 1);
+ device->server = server;
+ device->type = type;
+ device->name = pa_xstrdup(name);
+ device->index = idx;
+ device->proplist = pa_proplist_copy(proplist);
+ device->sample_spec = *sample_spec;
+ device->channel_map = *channel_map;
+ device->is_monitor = is_monitor;
+
+ for (i = 0; i < PA_TUNNEL_MANAGER_REMOTE_DEVICE_HOOK_MAX; i++)
+ pa_hook_init(&device->hooks[i], device);
+
+ device->can_free = true;
+
+ pa_hashmap_put(server->devices, device->name, device);
+
+ pa_log_debug("[%s] Created remote device %s.", server->name, device->name);
+ pa_log_debug(" Type: %s", pa_device_type_to_string(type));
+ pa_log_debug(" Index: %u", idx);
+ pa_log_debug(" Sample spec: %s", pa_sample_spec_snprint(sample_spec_str, sizeof(sample_spec_str), sample_spec));
+ pa_log_debug(" Channel map: %s", pa_channel_map_snprint(channel_map_str, sizeof(channel_map_str), channel_map));
+ pa_log_debug(" Is monitor: %s", pa_boolean_to_string(device->is_monitor));
+}
+
+void pa_tunnel_manager_remote_device_free(pa_tunnel_manager_remote_device *device) {
+ unsigned i;
+
+ pa_assert(device);
+
+ if (device->dead) {
+ pa_assert(device->can_free);
+ pa_xfree(device);
+ return;
+ }
+
+ device->dead = true;
+
+ pa_log_debug("[%s] Freeing remote device %s.", device->server->name, device->name);
+
+ pa_hashmap_remove(device->server->devices, device->name);
+ pa_hook_fire(&device->hooks[PA_TUNNEL_MANAGER_REMOTE_DEVICE_HOOK_UNLINKED], NULL);
+
+ if (device->get_info_operation) {
+ pa_operation_cancel(device->get_info_operation);
+ pa_operation_unref(device->get_info_operation);
+
+ /* This member will still be accessed in get_info_cb if
+ * device->can_free is true, so we need to set it to NULL here. */
+ device->get_info_operation = NULL;
+ }
+
+ for (i = 0; i < PA_TUNNEL_MANAGER_REMOTE_DEVICE_HOOK_MAX; i++)
+ pa_hook_done(&device->hooks[i]);
+
+ if (device->proplist)
+ pa_proplist_free(device->proplist);
+
+ pa_xfree(device->name);
+
+ if (device->can_free)
+ pa_xfree(device);
+}
+
+static void set_proplist(pa_tunnel_manager_remote_device *device, pa_proplist *proplist) {
+ pa_assert(device);
+ pa_assert(proplist);
+
+ if (pa_proplist_equal(proplist, device->proplist))
+ return;
+
+ pa_proplist_update(device->proplist, PA_UPDATE_SET, proplist);
+
+ pa_log_debug("[%s %s] Proplist changed.", device->server->name, device->name);
+
+ pa_hook_fire(&device->hooks[PA_TUNNEL_MANAGER_REMOTE_DEVICE_HOOK_PROPLIST_CHANGED], NULL);
+}
+
+static void get_info_cb(pa_context *context, const void *info, int is_last, void *userdata) {
+ pa_tunnel_manager_remote_device *device = userdata;
+ pa_proplist *proplist = NULL;
+
+ pa_assert(context);
+ pa_assert(device);
+
+ if (device->get_info_operation) {
+ pa_operation_unref(device->get_info_operation);
+ device->get_info_operation = NULL;
+ }
+
+ if (is_last < 0) {
+ pa_log_debug("[%s %s] Getting info failed: %s", device->server->name, device->name,
+ pa_strerror(pa_context_errno(context)));
+ return;
+ }
+
+ if (is_last) {
+ device->can_free = true;
+
+ if (device->dead)
+ pa_tunnel_manager_remote_device_free(device);
+
+ return;
+ }
+
+ device->can_free = false;
+
+ if (device->dead)
+ return;
+
+ switch (device->type) {
+ case PA_DEVICE_TYPE_SINK:
+ proplist = ((const pa_sink_info *) info)->proplist;
+ break;
+
+ case PA_DEVICE_TYPE_SOURCE:
+ proplist = ((const pa_source_info *) info)->proplist;
+ break;
+ }
+
+ set_proplist(device, proplist);
+}
+
+void pa_tunnel_manager_remote_device_update(pa_tunnel_manager_remote_device *device) {
+ pa_assert(device);
+
+ if (device->get_info_operation)
+ return;
+
+ switch (device->type) {
+ case PA_DEVICE_TYPE_SINK:
+ device->get_info_operation = pa_context_get_sink_info_by_name(device->server->context, device->name,
+ (pa_sink_info_cb_t) get_info_cb, device);
+ break;
+
+ case PA_DEVICE_TYPE_SOURCE:
+ device->get_info_operation = pa_context_get_source_info_by_name(device->server->context, device->name,
+ (pa_source_info_cb_t) get_info_cb, device);
+ break;
+ }
+
+ if (!device->get_info_operation) {
+ pa_log("[%s %s] pa_context_get_%s_info_by_name() failed: %s", device->server->name, device->name,
+ pa_device_type_to_string(device->type), pa_strerror(pa_context_errno(device->server->context)));
+ pa_tunnel_manager_remote_server_set_failed(device->server, true);
+ }
+}
--- /dev/null
+#ifndef fooremotedevicehfoo
+#define fooremotedevicehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2014 Intel Corporation
+
+ 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.
+***/
+
+#include <modules/tunnel-manager/remote-server.h>
+
+typedef struct pa_tunnel_manager_remote_device pa_tunnel_manager_remote_device;
+
+enum {
+ PA_TUNNEL_MANAGER_REMOTE_DEVICE_HOOK_UNLINKED,
+ PA_TUNNEL_MANAGER_REMOTE_DEVICE_HOOK_PROPLIST_CHANGED,
+ PA_TUNNEL_MANAGER_REMOTE_DEVICE_HOOK_MAX,
+};
+
+struct pa_tunnel_manager_remote_device {
+ pa_tunnel_manager_remote_server *server;
+ char *name;
+ pa_device_type_t type;
+ uint32_t index;
+ pa_proplist *proplist;
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+ bool is_monitor;
+ pa_hook hooks[PA_TUNNEL_MANAGER_REMOTE_DEVICE_HOOK_MAX];
+
+ pa_operation *get_info_operation;
+
+ /* These are a workaround for the problem that the introspection API's info
+ * callbacks are called multiple times, which means that if the userdata
+ * needs to be freed during the callbacks, the freeing needs to be
+ * postponed until the last call. */
+ bool can_free;
+ bool dead;
+};
+
+void pa_tunnel_manager_remote_device_new(pa_tunnel_manager_remote_server *server, pa_device_type_t type, const void *info);
+void pa_tunnel_manager_remote_device_free(pa_tunnel_manager_remote_device *device);
+void pa_tunnel_manager_remote_device_update(pa_tunnel_manager_remote_device *device);
+
+#endif
--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2014 Intel Corporation
+
+ 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 "remote-server.h"
+
+#include <modules/tunnel-manager/remote-device.h>
+
+#include <pulse/error.h>
+#include <pulse/introspect.h>
+#include <pulse/subscribe.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/device-type.h>
+#include <pulsecore/parseaddr.h>
+
+static void set_up_connection(pa_tunnel_manager_remote_server *server);
+
+struct device_stub {
+ pa_tunnel_manager_remote_server *server;
+ pa_device_type_t type;
+ uint32_t index;
+
+ pa_operation *get_info_operation;
+
+ /* These are a workaround for the problem that the introspection API's info
+ * callbacks are called multiple times, which means that if the userdata
+ * needs to be freed during the callbacks, the freeing needs to be
+ * postponed until the last call. */
+ bool can_free;
+ bool dead;
+};
+
+static void device_stub_new(pa_tunnel_manager_remote_server *server, pa_device_type_t type, uint32_t idx);
+static void device_stub_free(struct device_stub *stub);
+
+void pa_tunnel_manager_remote_server_new(pa_tunnel_manager *manager, pa_tunnel_manager_remote_server_config *config) {
+ int r;
+ pa_parsed_address parsed_address;
+ pa_tunnel_manager_remote_server *server = NULL;
+
+ pa_assert(manager);
+ pa_assert(config);
+
+ if (!config->address) {
+ pa_log("No address configured for remote server %s.", config->name);
+ return;
+ }
+
+ r = pa_parse_address(config->address->value, &parsed_address);
+ if (r < 0) {
+ pa_log("[%s:%u] Invalid address: \"%s\"", config->address->filename, config->address->lineno, config->address->value);
+ return;
+ }
+
+ pa_xfree(parsed_address.path_or_host);
+
+ server = pa_xnew0(pa_tunnel_manager_remote_server, 1);
+ server->manager = manager;
+ server->name = pa_xstrdup(config->name);
+ server->address = pa_xstrdup(config->address->value);
+ server->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ server->device_stubs = pa_hashmap_new(NULL, NULL);
+
+ pa_assert_se(pa_hashmap_put(manager->remote_servers, server->name, server) >= 0);
+
+ pa_log_debug("Created remote server %s.", server->name);
+ pa_log_debug(" Address: %s", server->address);
+ pa_log_debug(" Failed: %s", pa_boolean_to_string(server->failed));
+
+ set_up_connection(server);
+}
+
+static void subscribe_cb(pa_context *context, pa_subscription_event_type_t event_type, uint32_t idx, void *userdata) {
+ pa_tunnel_manager_remote_server *server = userdata;
+ pa_device_type_t device_type;
+ pa_tunnel_manager_remote_device *device;
+ void *state;
+
+ pa_assert(context);
+ pa_assert(server);
+
+ if ((event_type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK)
+ device_type = PA_DEVICE_TYPE_SINK;
+ else if ((event_type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE)
+ device_type = PA_DEVICE_TYPE_SOURCE;
+ else {
+ pa_log("[%s] Unexpected event facility: %u", server->name,
+ (unsigned) (event_type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK));
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ return;
+ }
+
+ if (idx == PA_INVALID_INDEX) {
+ pa_log("[%s] Invalid %s index.", server->name, pa_device_type_to_string(device_type));
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ return;
+ }
+
+ if ((event_type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+ if ((device_type == PA_DEVICE_TYPE_SINK && server->list_sinks_operation)
+ || (device_type == PA_DEVICE_TYPE_SOURCE && server->list_sources_operation))
+ return;
+
+ device_stub_new(server, device_type, idx);
+
+ return;
+ }
+
+ if ((event_type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ struct device_stub *stub;
+
+ PA_HASHMAP_FOREACH(device, server->devices, state) {
+ if (device->type == device_type && device->index == idx) {
+ pa_tunnel_manager_remote_device_free(device);
+ return;
+ }
+ }
+
+ PA_HASHMAP_FOREACH(stub, server->device_stubs, state) {
+ if (stub->type == device_type && stub->index == idx) {
+ device_stub_free(stub);
+ return;
+ }
+ }
+
+ return;
+ }
+
+ if ((event_type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) {
+ PA_HASHMAP_FOREACH(device, server->devices, state) {
+ if (device->type == device_type && device->index == idx) {
+ pa_tunnel_manager_remote_device_update(device);
+ return;
+ }
+ }
+
+ return;
+ }
+}
+
+static void subscribe_success_cb(pa_context *context, int success, void *userdata) {
+ pa_tunnel_manager_remote_server *server = userdata;
+
+ pa_assert(context);
+ pa_assert(server);
+
+ if (!success) {
+ pa_log("[%s] Subscribing to device events failed: %s", server->name, pa_strerror(pa_context_errno(context)));
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ }
+}
+
+static void get_sink_info_list_cb(pa_context *context, const pa_sink_info *info, int is_last, void *userdata) {
+ pa_tunnel_manager_remote_server *server = userdata;
+
+ pa_assert(context);
+ pa_assert(server);
+
+ if (server->list_sinks_operation) {
+ pa_operation_unref(server->list_sinks_operation);
+ server->list_sinks_operation = NULL;
+ }
+
+ if (is_last < 0) {
+ pa_log("[%s] Listing sinks failed: %s", server->name, pa_strerror(pa_context_errno(context)));
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ return;
+ }
+
+ if (is_last)
+ return;
+
+ pa_tunnel_manager_remote_device_new(server, PA_DEVICE_TYPE_SINK, info);
+}
+
+static void get_source_info_list_cb(pa_context *context, const pa_source_info *info, int is_last, void *userdata) {
+ pa_tunnel_manager_remote_server *server = userdata;
+
+ pa_assert(context);
+ pa_assert(server);
+
+ if (server->list_sources_operation) {
+ pa_operation_unref(server->list_sources_operation);
+ server->list_sources_operation = NULL;
+ }
+
+ if (is_last < 0) {
+ pa_log("[%s] Listing sources failed: %s", server->name, pa_strerror(pa_context_errno(context)));
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ return;
+ }
+
+ if (is_last)
+ return;
+
+ pa_tunnel_manager_remote_device_new(server, PA_DEVICE_TYPE_SOURCE, info);
+}
+
+static void context_state_cb(pa_context *context, void *userdata) {
+ pa_tunnel_manager_remote_server *server = userdata;
+ pa_context_state_t state;
+
+ pa_assert(context);
+ pa_assert(server);
+ pa_assert(context == server->context);
+
+ state = pa_context_get_state(context);
+
+ switch (state) {
+ case PA_CONTEXT_READY: {
+ pa_operation *operation;
+
+ pa_context_set_subscribe_callback(context, subscribe_cb, server);
+ operation = pa_context_subscribe(context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE,
+ subscribe_success_cb, server);
+ if (operation)
+ pa_operation_unref(operation);
+ else {
+ pa_log("[%s] pa_context_subscribe() failed: %s", server->name, pa_strerror(pa_context_errno(context)));
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ return;
+ }
+
+ pa_assert(!server->list_sinks_operation);
+ pa_assert(!server->list_sources_operation);
+
+ server->list_sinks_operation = pa_context_get_sink_info_list(server->context, get_sink_info_list_cb, server);
+ if (!server->list_sinks_operation) {
+ pa_log("[%s] pa_context_get_sink_info_list() failed: %s", server->name,
+ pa_strerror(pa_context_errno(context)));
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ return;
+ }
+
+ server->list_sources_operation = pa_context_get_source_info_list(server->context, get_source_info_list_cb, server);
+ if (!server->list_sources_operation) {
+ pa_log("[%s] pa_context_get_source_info_list() failed: %s", server->name,
+ pa_strerror(pa_context_errno(context)));
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ return;
+ }
+
+ return;
+ }
+
+ case PA_CONTEXT_FAILED:
+ pa_log("[%s] Context failed: %s", server->name, pa_strerror(pa_context_errno(context)));
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ return;
+
+ default:
+ return;
+ }
+}
+
+static void set_up_connection(pa_tunnel_manager_remote_server *server) {
+ pa_assert(server);
+ pa_assert(!server->context);
+
+ server->context = pa_context_new(server->manager->core->mainloop, "PulseAudio");
+ if (server->context) {
+ int r;
+
+ r = pa_context_connect(server->context, server->address, PA_CONTEXT_NOFLAGS, NULL);
+ if (r >= 0)
+ pa_context_set_state_callback(server->context, context_state_cb, server);
+ else {
+ pa_log("[%s] pa_context_connect() failed: %s", server->name, pa_strerror(pa_context_errno(server->context)));
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ }
+ } else {
+ pa_log("[%s] pa_context_new() failed.", server->name);
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ }
+}
+
+static void tear_down_connection(pa_tunnel_manager_remote_server *server) {
+ pa_assert(server);
+
+ if (server->device_stubs) {
+ struct device_stub *stub;
+
+ while ((stub = pa_hashmap_first(server->device_stubs)))
+ device_stub_free(stub);
+ }
+
+ if (server->devices) {
+ pa_tunnel_manager_remote_device *device;
+
+ while ((device = pa_hashmap_first(server->devices)))
+ pa_tunnel_manager_remote_device_free(device);
+ }
+
+ if (server->list_sources_operation) {
+ pa_operation_cancel(server->list_sources_operation);
+ pa_operation_unref(server->list_sources_operation);
+ server->list_sources_operation = NULL;
+ }
+
+ if (server->list_sinks_operation) {
+ pa_operation_cancel(server->list_sinks_operation);
+ pa_operation_unref(server->list_sinks_operation);
+ server->list_sinks_operation = NULL;
+ }
+
+ if (server->context) {
+ pa_context_disconnect(server->context);
+ pa_context_unref(server->context);
+ server->context = NULL;
+ }
+}
+
+void pa_tunnel_manager_remote_server_free(pa_tunnel_manager_remote_server *server) {
+ pa_assert(server);
+
+ pa_log_debug("Freeing remote server %s.", server->name);
+
+ pa_hashmap_remove(server->manager->remote_servers, server->name);
+
+ tear_down_connection(server);
+
+ if (server->device_stubs) {
+ pa_assert(pa_hashmap_isempty(server->device_stubs));
+ pa_hashmap_free(server->device_stubs);
+ }
+
+ if (server->devices) {
+ pa_assert(pa_hashmap_isempty(server->devices));
+ pa_hashmap_free(server->devices);
+ }
+
+ pa_xfree(server->address);
+ pa_xfree(server->name);
+ pa_xfree(server);
+}
+
+void pa_tunnel_manager_remote_server_set_failed(pa_tunnel_manager_remote_server *server, bool failed) {
+ pa_assert(server);
+
+ if (failed == server->failed)
+ return;
+
+ server->failed = failed;
+
+ pa_log_debug("[%s] Failed changed from %s to %s.", server->name, pa_boolean_to_string(!failed),
+ pa_boolean_to_string(failed));
+
+ if (failed)
+ tear_down_connection(server);
+}
+
+static void device_stub_get_info_cb(pa_context *context, const void *info, int is_last, void *userdata) {
+ struct device_stub *stub = userdata;
+ pa_tunnel_manager_remote_server *server;
+ pa_device_type_t type;
+ uint32_t idx = PA_INVALID_INDEX;
+
+ pa_assert(context);
+ pa_assert(stub);
+
+ server = stub->server;
+ type = stub->type;
+
+ if (stub->get_info_operation) {
+ pa_operation_unref(stub->get_info_operation);
+ stub->get_info_operation = NULL;
+ }
+
+ if (is_last < 0) {
+ pa_log_debug("[%s] Getting info for %s %u failed: %s", server->name, pa_device_type_to_string(type), stub->index,
+ pa_strerror(pa_context_errno(context)));
+ device_stub_free(stub);
+ return;
+ }
+
+ if (is_last) {
+ stub->can_free = true;
+
+ /* TODO: libpulse should ensure that the get info operation doesn't
+ * return an empty result. Then this check wouldn't be needed. */
+ if (!stub->dead) {
+ pa_log("[%s] No info received for %s %u.", server->name, pa_device_type_to_string(type), stub->index);
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ return;
+ }
+
+ device_stub_free(stub);
+ return;
+ }
+
+ /* This callback will still be called at least once, so we need to keep the
+ * stub alive. */
+ stub->can_free = false;
+
+ /* TODO: libpulse should ensure that the get info operation doesn't return
+ * more than one result. Then this check wouldn't be needed. */
+ if (stub->dead) {
+ pa_log("[%s] Multiple info structs received for %s %u.", server->name, pa_device_type_to_string(type), stub->index);
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ return;
+ }
+
+ switch (type) {
+ case PA_DEVICE_TYPE_SINK:
+ idx = ((const pa_sink_info *) info)->index;
+ break;
+
+ case PA_DEVICE_TYPE_SOURCE:
+ idx = ((const pa_source_info *) info)->index;
+ break;
+ }
+
+ if (idx != stub->index) {
+ pa_log("[%s] Index mismatch for %s %u.", server->name, pa_device_type_to_string(type), stub->index);
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ return;
+ }
+
+ /* pa_tunnel_manager_remote_device_new() checks whether the maximum device
+ * limit has been reached, and device stubs count towards that limit. This
+ * stub shouldn't any more count towards the limit, so let's free the stub
+ * before calling pa_tunnel_manager_remote_device_new(). */
+ device_stub_free(stub);
+
+ pa_tunnel_manager_remote_device_new(server, type, info);
+}
+
+static void device_stub_new(pa_tunnel_manager_remote_server *server, pa_device_type_t type, uint32_t idx) {
+ pa_tunnel_manager_remote_device *device;
+ void *state;
+ struct device_stub *stub;
+
+ pa_assert(server);
+
+ PA_HASHMAP_FOREACH(device, server->devices, state) {
+ if (device->type == type && device->index == idx) {
+ pa_log("[%s] Duplicate %s index %u.", server->name, pa_device_type_to_string(type), idx);
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ return;
+ }
+ }
+
+ PA_HASHMAP_FOREACH(stub, server->device_stubs, state) {
+ if (stub->type == type && stub->index == idx) {
+ pa_log("[%s] Duplicate %s index %u.", server->name, pa_device_type_to_string(type), idx);
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ return;
+ }
+ }
+
+ if (pa_hashmap_size(server->devices) + pa_hashmap_size(server->device_stubs) >= PA_TUNNEL_MANAGER_MAX_DEVICES_PER_SERVER) {
+ pa_log("[%s] Maximum number of devices exceeded.", server->name);
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ return;
+ }
+
+ stub = pa_xnew0(struct device_stub, 1);
+ stub->server = server;
+ stub->type = type;
+ stub->index = idx;
+ stub->can_free = true;
+
+ pa_hashmap_put(server->device_stubs, stub, stub);
+
+ switch (type) {
+ case PA_DEVICE_TYPE_SINK:
+ stub->get_info_operation = pa_context_get_sink_info_by_index(server->context, idx,
+ (pa_sink_info_cb_t) device_stub_get_info_cb,
+ stub);
+ break;
+
+ case PA_DEVICE_TYPE_SOURCE:
+ stub->get_info_operation = pa_context_get_source_info_by_index(server->context, idx,
+ (pa_source_info_cb_t) device_stub_get_info_cb,
+ stub);
+ break;
+ }
+
+ if (!stub->get_info_operation) {
+ pa_log("[%s] pa_context_get_%s_info_by_index() failed: %s", server->name, pa_device_type_to_string(type),
+ pa_strerror(pa_context_errno(server->context)));
+ pa_tunnel_manager_remote_server_set_failed(server, true);
+ return;
+ }
+}
+
+static void device_stub_free(struct device_stub *stub) {
+ pa_assert(stub);
+
+ if (stub->dead) {
+ pa_assert(stub->can_free);
+ pa_xfree(stub);
+ return;
+ }
+
+ stub->dead = true;
+
+ pa_hashmap_remove(stub->server->device_stubs, stub);
+
+ if (stub->get_info_operation) {
+ pa_operation_cancel(stub->get_info_operation);
+ pa_operation_unref(stub->get_info_operation);
+
+ /* This member will still be accessed in device_stub_get_info_cb if
+ * stub->can_free is true, so we need to set it to NULL here. */
+ stub->get_info_operation = NULL;
+ }
+
+ if (stub->can_free)
+ pa_xfree(stub);
+}
--- /dev/null
+#ifndef fooremoteserverhfoo
+#define fooremoteserverhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2014 Intel Corporation
+
+ 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.
+***/
+
+#include <modules/tunnel-manager/tunnel-manager.h>
+#include <modules/tunnel-manager/tunnel-manager-config.h>
+
+#include <pulse/context.h>
+
+typedef struct pa_tunnel_manager_remote_server pa_tunnel_manager_remote_server;
+
+struct pa_tunnel_manager_remote_server {
+ pa_tunnel_manager *manager;
+ char *name;
+ char *address;
+ pa_hashmap *devices; /* name -> pa_tunnel_manager_remote_device */
+ bool failed;
+
+ pa_context *context;
+ pa_operation *list_sinks_operation;
+ pa_operation *list_sources_operation;
+ pa_hashmap *device_stubs; /* struct device_stub -> struct device_stub (hashmap-as-a-set) */
+};
+
+void pa_tunnel_manager_remote_server_new(pa_tunnel_manager *manager, pa_tunnel_manager_remote_server_config *config);
+void pa_tunnel_manager_remote_server_free(pa_tunnel_manager_remote_server *server);
+void pa_tunnel_manager_remote_server_set_failed(pa_tunnel_manager_remote_server *server, bool failed);
+
+#endif
--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2014 Intel Corporation
+
+ 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 "tunnel-manager-config.h"
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/conf-parser.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/namereg.h>
+
+#define REMOTE_SERVER_SECTION_NAME "RemoteServer"
+#define REMOTE_SERVER_SECTION_PREFIX REMOTE_SERVER_SECTION_NAME " "
+
+static int remote_server_config_new(pa_tunnel_manager_config *manager_config, const char *name,
+ pa_tunnel_manager_remote_server_config **_r);
+static void remote_server_config_free(pa_tunnel_manager_remote_server_config *config);
+
+static pa_tunnel_manager_config_value *config_value_new(const char *value, const char *filename, unsigned lineno) {
+ pa_tunnel_manager_config_value *config_value;
+
+ pa_assert(value);
+ pa_assert(filename);
+
+ config_value = pa_xnew0(pa_tunnel_manager_config_value, 1);
+ config_value->value = pa_xstrdup(value);
+ config_value->filename = pa_xstrdup(filename);
+ config_value->lineno = lineno;
+
+ return config_value;
+}
+
+static void config_value_free(pa_tunnel_manager_config_value *value) {
+ pa_assert(value);
+
+ pa_xfree(value->filename);
+ pa_xfree(value->value);
+ pa_xfree(value);
+}
+
+static int get_remote_server_config(pa_tunnel_manager_config *manager_config, const char *section,
+ pa_tunnel_manager_remote_server_config **_r) {
+ char *name = NULL;
+ pa_tunnel_manager_remote_server_config *server_config;
+ int r;
+
+ pa_assert(manager_config);
+ pa_assert(section);
+ pa_assert(_r);
+
+ name = pa_xstrdup(section + strlen(REMOTE_SERVER_SECTION_PREFIX));
+ name = pa_strip(name);
+
+ server_config = pa_hashmap_get(manager_config->remote_servers, name);
+ if (server_config)
+ goto success;
+
+ r = remote_server_config_new(manager_config, name, &server_config);
+ if (r < 0)
+ goto fail;
+
+success:
+ pa_xfree(name);
+
+ *_r = server_config;
+ return 0;
+
+fail:
+ pa_xfree(name);
+
+ return r;
+}
+
+static int parse_config_value(pa_config_parser_state *state) {
+ pa_tunnel_manager_config *manager_config;
+
+ pa_assert(state);
+
+ manager_config = state->userdata;
+
+ if (!state->section) {
+ pa_log("[%s:%u] \"%s\" is not valid in the General section.", state->filename, state->lineno, state->lvalue);
+ return 0;
+ }
+
+ if (pa_startswith(state->section, REMOTE_SERVER_SECTION_PREFIX)) {
+ int r;
+ pa_tunnel_manager_remote_server_config *server_config;
+
+ r = get_remote_server_config(manager_config, state->section, &server_config);
+ if (r < 0) {
+ pa_log("[%s:%u] Invalid section: \"%s\"", state->filename, state->lineno, state->section);
+ return 0;
+ }
+
+ if (pa_streq(state->lvalue, "address")) {
+ if (server_config->address)
+ config_value_free(server_config->address);
+
+ server_config->address = config_value_new(state->rvalue, state->filename, state->lineno);
+ } else
+ pa_log("[%s:%u] \"%s\" is not valid in the " REMOTE_SERVER_SECTION_NAME " section.", state->filename,
+ state->lineno, state->lvalue);
+ } else
+ pa_log("[%s:%u] Invalid section: \"%s\"", state->filename, state->lineno, state->section);
+
+ return 0;
+}
+
+pa_tunnel_manager_config *pa_tunnel_manager_config_new(void) {
+ pa_tunnel_manager_config *config;
+ FILE *f;
+ char *fn = NULL;
+
+ config = pa_xnew0(pa_tunnel_manager_config, 1);
+ config->remote_servers = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+ f = pa_open_config_file(PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "tunnel-manager.conf", "tunnel-manager.conf", NULL, &fn);
+ if (f) {
+ pa_config_item config_items[] = {
+ { NULL, parse_config_value, NULL, NULL },
+ { NULL, NULL, NULL, NULL },
+ };
+
+ pa_config_parse(fn, f, config_items, NULL, config);
+ pa_xfree(fn);
+ fn = NULL;
+ fclose(f);
+ f = NULL;
+ }
+
+ return config;
+}
+
+void pa_tunnel_manager_config_free(pa_tunnel_manager_config *manager_config) {
+ pa_assert(manager_config);
+
+ if (manager_config->remote_servers) {
+ pa_tunnel_manager_remote_server_config *server_config;
+
+ while ((server_config = pa_hashmap_first(manager_config->remote_servers)))
+ remote_server_config_free(server_config);
+
+ pa_hashmap_free(manager_config->remote_servers);
+ }
+
+ pa_xfree(manager_config);
+}
+
+static int remote_server_config_new(pa_tunnel_manager_config *manager_config, const char *name,
+ pa_tunnel_manager_remote_server_config **_r) {
+ pa_tunnel_manager_remote_server_config *server_config = NULL;
+
+ pa_assert(manager_config);
+ pa_assert(name);
+ pa_assert(_r);
+
+ if (!pa_namereg_is_valid_name(name))
+ return -PA_ERR_INVALID;
+
+ server_config = pa_xnew0(pa_tunnel_manager_remote_server_config, 1);
+ server_config->manager_config = manager_config;
+ server_config->name = pa_xstrdup(name);
+
+ pa_assert_se(pa_hashmap_put(manager_config->remote_servers, server_config->name, server_config) >= 0);
+
+ *_r = server_config;
+ return 0;
+}
+
+static void remote_server_config_free(pa_tunnel_manager_remote_server_config *config) {
+ pa_assert(config);
+
+ pa_hashmap_remove(config->manager_config->remote_servers, config->name);
+
+ if (config->address)
+ config_value_free(config->address);
+
+ pa_xfree(config->name);
+ pa_xfree(config);
+}
--- /dev/null
+#ifndef footunnelmanagerconfighfoo
+#define footunnelmanagerconfighfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2014 Intel Corporation
+
+ 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.
+***/
+
+#include <pulsecore/hashmap.h>
+
+typedef struct pa_tunnel_manager_config_value pa_tunnel_manager_config_value;
+typedef struct pa_tunnel_manager_config pa_tunnel_manager_config;
+typedef struct pa_tunnel_manager_remote_server_config pa_tunnel_manager_remote_server_config;
+
+struct pa_tunnel_manager_config_value {
+ char *value;
+ char *filename;
+ unsigned lineno;
+};
+
+struct pa_tunnel_manager_config {
+ pa_hashmap *remote_servers; /* name -> pa_tunnel_manager_remote_server_config */
+};
+
+pa_tunnel_manager_config *pa_tunnel_manager_config_new(void);
+void pa_tunnel_manager_config_free(pa_tunnel_manager_config *manager_config);
+
+struct pa_tunnel_manager_remote_server_config {
+ pa_tunnel_manager_config *manager_config;
+ char *name;
+ pa_tunnel_manager_config_value *address;
+};
+
+#endif
--- /dev/null
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2014 Intel Corporation
+
+ 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 "tunnel-manager.h"
+
+#include <modules/tunnel-manager/remote-server.h>
+#include <modules/tunnel-manager/tunnel-manager-config.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/shared.h>
+
+static pa_tunnel_manager *tunnel_manager_new(pa_core *core) {
+ pa_tunnel_manager *manager;
+ pa_tunnel_manager_config *manager_config;
+ pa_tunnel_manager_remote_server_config *server_config;
+ void *state;
+
+ pa_assert(core);
+
+ manager = pa_xnew0(pa_tunnel_manager, 1);
+ manager->core = core;
+ manager->remote_servers = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ manager->refcnt = 1;
+
+ manager_config = pa_tunnel_manager_config_new();
+
+ pa_log_debug("Created the tunnel manager.");
+
+ PA_HASHMAP_FOREACH(server_config, manager_config->remote_servers, state)
+ pa_tunnel_manager_remote_server_new(manager, server_config);
+
+ pa_tunnel_manager_config_free(manager_config);
+
+ pa_shared_set(core, "tunnel_manager", manager);
+
+ return manager;
+}
+
+static void tunnel_manager_free(pa_tunnel_manager *manager) {
+ pa_assert(manager);
+ pa_assert(manager->refcnt == 0);
+
+ pa_log_debug("Freeing the tunnel manager.");
+
+ pa_shared_remove(manager->core, "tunnel_manager");
+
+ if (manager->remote_servers) {
+ pa_tunnel_manager_remote_server *server;
+
+ while ((server = pa_hashmap_first(manager->remote_servers)))
+ pa_tunnel_manager_remote_server_free(server);
+
+ pa_hashmap_free(manager->remote_servers);
+ }
+
+ pa_xfree(manager);
+}
+
+pa_tunnel_manager *pa_tunnel_manager_get(pa_core *core, bool ref) {
+ pa_tunnel_manager *manager;
+
+ pa_assert(core);
+
+ manager = pa_shared_get(core, "tunnel_manager");
+ if (manager) {
+ if (ref)
+ manager->refcnt++;
+
+ return manager;
+ }
+
+ if (ref)
+ return tunnel_manager_new(core);
+
+ return NULL;
+}
+
+void pa_tunnel_manager_unref(pa_tunnel_manager *manager) {
+ pa_assert(manager);
+ pa_assert(manager->refcnt > 0);
+
+ manager->refcnt--;
+
+ if (manager->refcnt == 0)
+ tunnel_manager_free(manager);
+}
--- /dev/null
+#ifndef footunnelmanagerhfoo
+#define footunnelmanagerhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2014 Intel Corporation
+
+ 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.
+***/
+
+#include <pulsecore/core.h>
+
+#define PA_TUNNEL_MANAGER_MAX_DEVICES_PER_SERVER 50
+
+typedef struct pa_tunnel_manager pa_tunnel_manager;
+
+struct pa_tunnel_manager {
+ pa_core *core;
+ pa_hashmap *remote_servers; /* name -> pa_tunnel_manager_remote_server */
+
+ unsigned refcnt;
+};
+
+/* If ref is true, the reference count of the manager is incremented, and also
+ * the manager is created if it doesn't exist yet. If ref is false, the
+ * reference count is not incremented, and if the manager doesn't exist, the
+ * function returns NULL. */
+pa_tunnel_manager *pa_tunnel_manager_get(pa_core *core, bool ref);
+
+void pa_tunnel_manager_unref(pa_tunnel_manager *manager);
+
+#endif