#include <stdio.h>
#include <stdlib.h>
+#ifdef HAVE_X11
+#include <xcb/xcb.h>
+#endif
+
#include <pulse/rtclock.h>
#include <pulse/timeval.h>
#include <pulse/util.h>
#include <pulsecore/proplist-util.h>
#include <pulsecore/auth-cookie.h>
#include <pulsecore/mcalign.h>
+#include <pulsecore/strlist.h>
+
+#ifdef HAVE_X11
+#include <pulsecore/x11prop.h>
+#endif
#ifdef TUNNEL_SINK
#include "module-tunnel-sink-symdef.h"
#include "module-tunnel-source-symdef.h"
#endif
+#define ENV_DEFAULT_SINK "PULSE_SINK"
+#define ENV_DEFAULT_SOURCE "PULSE_SOURCE"
+#define ENV_DEFAULT_SERVER "PULSE_SERVER"
+#define ENV_COOKIE_FILE "PULSE_COOKIE"
+
#ifdef TUNNEL_SINK
PA_MODULE_DESCRIPTION("Tunnel module for sinks");
PA_MODULE_USAGE(
"sink_name=<name for the local sink> "
"sink_properties=<properties for the local sink> "
+ "auto=<determine server/sink/cookie automatically> "
"server=<address> "
"sink=<remote sink name> "
"cookie=<filename> "
PA_MODULE_USAGE(
"source_name=<name for the local source> "
"source_properties=<properties for the local source> "
+ "auto=<determine server/source/cookie automatically> "
"server=<address> "
"source=<remote source name> "
"cookie=<filename> "
PA_MODULE_LOAD_ONCE(false);
static const char* const valid_modargs[] = {
+ "auto",
"server",
"cookie",
"format",
/* First, change the state, because otherwise pa_sink_render() would fail */
if ((r = pa_sink_process_msg(o, code, data, offset, chunk)) >= 0) {
- stream_cork_within_thread(u, u->sink->state == PA_SINK_SUSPENDED);
+ stream_cork_within_thread(u, u->sink->thread_info.state == PA_SINK_SUSPENDED);
- if (PA_SINK_IS_OPENED(u->sink->state))
+ if (PA_SINK_IS_OPENED(u->sink->thread_info.state))
send_data(u);
}
int r;
if ((r = pa_source_process_msg(o, code, data, offset, chunk)) >= 0)
- stream_cork_within_thread(u, u->source->state == PA_SOURCE_SUSPENDED);
+ stream_cork_within_thread(u, u->source->thread_info.state == PA_SOURCE_SUSPENDED);
return r;
}
int pa__init(pa_module*m) {
pa_modargs *ma = NULL;
struct userdata *u = NULL;
+ char *server = NULL;
+ pa_strlist *server_list = NULL;
pa_sample_spec ss;
pa_channel_map map;
char *dn = NULL;
#else
pa_source_new_data data;
#endif
+ bool automatic;
+#ifdef HAVE_X11
+ xcb_connection_t *xcb = NULL;
+#endif
+ const char *cookie_path;
pa_assert(m);
u->rtpoll = pa_rtpoll_new();
pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
- if (!(u->auth_cookie = pa_auth_cookie_get(u->core, pa_modargs_get_value(ma, "cookie", PA_NATIVE_COOKIE_FILE), true, PA_NATIVE_COOKIE_LENGTH)))
+ if (pa_modargs_get_value_boolean(ma, "auto", &automatic) < 0) {
+ pa_log("Failed to parse argument \"auto\".");
goto fail;
+ }
- if (!(u->server_name = pa_xstrdup(pa_modargs_get_value(ma, "server", NULL)))) {
- pa_log("No server specified.");
- goto fail;
+ cookie_path = pa_modargs_get_value(ma, "cookie", NULL);
+ server = pa_xstrdup(pa_modargs_get_value(ma, "server", NULL));
+
+ if (automatic) {
+#ifdef HAVE_X11
+ /* Need an X11 connection to get root properties */
+ if (getenv("DISPLAY") != NULL) {
+ if (!(xcb = xcb_connect(getenv("DISPLAY"), NULL)))
+ pa_log("xcb_connect() failed");
+ else {
+ if (xcb_connection_has_error(xcb)) {
+ pa_log("xcb_connection_has_error() returned true");
+ xcb_disconnect(xcb);
+ xcb = NULL;
+ }
+ }
+ }
+#endif
+
+ /* Figure out the cookie the same way a normal client would */
+ if (!cookie_path)
+ cookie_path = getenv(ENV_COOKIE_FILE);
+
+#ifdef HAVE_X11
+ if (!cookie_path && xcb) {
+ char t[1024];
+ if (pa_x11_get_prop(xcb, 0, "PULSE_COOKIE", t, sizeof(t))) {
+ uint8_t cookie[PA_NATIVE_COOKIE_LENGTH];
+
+ if (pa_parsehex(t, cookie, sizeof(cookie)) != sizeof(cookie))
+ pa_log("Failed to parse cookie data");
+ else {
+ if (!(u->auth_cookie = pa_auth_cookie_create(u->core, cookie, sizeof(cookie))))
+ goto fail;
+ }
+ }
+ }
+#endif
+
+ /* Same thing for the server name */
+ if (!server)
+ server = pa_xstrdup(getenv(ENV_DEFAULT_SERVER));
+
+#ifdef HAVE_X11
+ if (!server && xcb) {
+ char t[1024];
+ if (pa_x11_get_prop(xcb, 0, "PULSE_SERVER", t, sizeof(t)))
+ server = pa_xstrdup(t);
+ }
+#endif
+
+ /* Also determine the default sink/source on the other server */
+#ifdef TUNNEL_SINK
+ if (!u->sink_name)
+ u->sink_name = pa_xstrdup(getenv(ENV_DEFAULT_SINK));
+
+#ifdef HAVE_X11
+ if (!u->sink_name && xcb) {
+ char t[1024];
+ if (pa_x11_get_prop(xcb, 0, "PULSE_SINK", t, sizeof(t)))
+ u->sink_name = pa_xstrdup(t);
+ }
+#endif
+#else
+ if (!u->source_name)
+ u->source_name = pa_xstrdup(getenv(ENV_DEFAULT_SOURCE));
+
+#ifdef HAVE_X11
+ if (!u->source_name && xcb) {
+ char t[1024];
+ if (pa_x11_get_prop(xcb, 0, "PULSE_SOURCE", t, sizeof(t)))
+ u->source_name = pa_xstrdup(t);
+ }
+#endif
+#endif
+ }
+
+ if (!cookie_path && !u->auth_cookie)
+ cookie_path = PA_NATIVE_COOKIE_FILE;
+
+ if (cookie_path) {
+ if (!(u->auth_cookie = pa_auth_cookie_get(u->core, cookie_path, true, PA_NATIVE_COOKIE_LENGTH)))
+ goto fail;
+ }
+
+ if (server) {
+ if (!(server_list = pa_strlist_parse(server))) {
+ pa_log("Invalid server specified.");
+ goto fail;
+ }
+ } else {
+ char *ufn;
+
+ if (!automatic) {
+ pa_log("No server specified.");
+ goto fail;
+ }
+
+ pa_log("No server address found. Attempting default local sockets.");
+
+ /* The system wide instance via PF_LOCAL */
+ server_list = pa_strlist_prepend(server_list, PA_SYSTEM_RUNTIME_PATH PA_PATH_SEP PA_NATIVE_DEFAULT_UNIX_SOCKET);
+
+ /* The user instance via PF_LOCAL */
+ if ((ufn = pa_runtime_path(PA_NATIVE_DEFAULT_UNIX_SOCKET))) {
+ server_list = pa_strlist_prepend(server_list, ufn);
+ pa_xfree(ufn);
+ }
}
ss = m->core->default_sample_spec;
goto fail;
}
- if (!(u->client = pa_socket_client_new_string(m->core->mainloop, true, u->server_name, PA_NATIVE_DEFAULT_PORT))) {
- pa_log("Failed to connect to server '%s'", u->server_name);
- goto fail;
- }
+ for (;;) {
+ server_list = pa_strlist_pop(server_list, &u->server_name);
+
+ if (!u->server_name) {
+ pa_log("Failed to connect to server '%s'", server);
+ goto fail;
+ }
+
+ pa_log_debug("Trying to connect to %s...", u->server_name);
+
+ if (!(u->client = pa_socket_client_new_string(m->core->mainloop, true, u->server_name, PA_NATIVE_DEFAULT_PORT))) {
+ pa_xfree(u->server_name);
+ u->server_name = NULL;
+ continue;
+ }
+
+ break;
+ }
pa_socket_client_set_callback(u->client, on_connection, u);
pa_sink_new_data_init(&data);
data.driver = __FILE__;
data.module = m;
- data.namereg_fail = true;
+ data.namereg_fail = false;
pa_sink_new_data_set_name(&data, dn);
pa_sink_new_data_set_sample_spec(&data, &ss);
pa_sink_new_data_set_channel_map(&data, &map);
pa_source_new_data_init(&data);
data.driver = __FILE__;
data.module = m;
- data.namereg_fail = true;
+ data.namereg_fail = false;
pa_source_new_data_set_name(&data, dn);
pa_source_new_data_set_sample_spec(&data, &ss);
pa_source_new_data_set_channel_map(&data, &map);
pa_source_put(u->source);
#endif
+ if (server)
+ pa_xfree(server);
+
+ if (server_list)
+ pa_strlist_free(server_list);
+
+#ifdef HAVE_X11
+ if (xcb)
+ xcb_disconnect(xcb);
+#endif
+
pa_modargs_free(ma);
return 0;
fail:
pa__done(m);
+ if (server)
+ pa_xfree(server);
+
+ if (server_list)
+ pa_strlist_free(server_list);
+
+#ifdef HAVE_X11
+ if (xcb)
+ xcb_disconnect(xcb);
+#endif
+
if (ma)
pa_modargs_free(ma);