+ pa_tagstruct_putu32(t, PA_COMMAND_GET_RECORD_LATENCY);
+#endif
+ pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_tagstruct_putu32(t, u->channel);
+
+ pa_tagstruct_put_timeval(t, pa_gettimeofday(&now));
+
+ pa_pstream_send_tagstruct(u->pstream, t);
+ pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, stream_get_latency_callback, u, NULL);
+
+ u->ignore_latency_before = tag;
+ u->counter_delta = 0;
+}
+
+/* Called from main context */
+static void timeout_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(m);
+ pa_assert(e);
+ pa_assert(u);
+
+ request_latency(u);
+
+ pa_core_rttime_restart(u->core, e, pa_rtclock_now() + LATENCY_INTERVAL);
+}
+
+/* Called from main context */
+static void update_description(struct userdata *u) {
+ char *d;
+ char un[128], hn[128];
+ pa_tagstruct *t;
+
+ pa_assert(u);
+
+ if (!u->server_fqdn || !u->user_name || !u->device_description)
+ return;
+
+ d = pa_sprintf_malloc("%s on %s@%s", u->device_description, u->user_name, u->server_fqdn);
+
+#ifdef TUNNEL_SINK
+ pa_sink_set_description(u->sink, d);
+ pa_proplist_sets(u->sink->proplist, "tunnel.remote.user", u->user_name);
+ pa_proplist_sets(u->sink->proplist, "tunnel.remote.fqdn", u->server_fqdn);
+ pa_proplist_sets(u->sink->proplist, "tunnel.remote.description", u->device_description);
+#else
+ pa_source_set_description(u->source, d);
+ pa_proplist_sets(u->source->proplist, "tunnel.remote.user", u->user_name);
+ pa_proplist_sets(u->source->proplist, "tunnel.remote.fqdn", u->server_fqdn);
+ pa_proplist_sets(u->source->proplist, "tunnel.remote.description", u->device_description);
+#endif
+
+ pa_xfree(d);
+
+ d = pa_sprintf_malloc("%s for %s@%s", u->device_description,
+ pa_get_user_name(un, sizeof(un)),
+ pa_get_host_name(hn, sizeof(hn)));
+
+ t = pa_tagstruct_new(NULL, 0);
+#ifdef TUNNEL_SINK
+ pa_tagstruct_putu32(t, PA_COMMAND_SET_PLAYBACK_STREAM_NAME);
+#else
+ pa_tagstruct_putu32(t, PA_COMMAND_SET_RECORD_STREAM_NAME);
+#endif
+ pa_tagstruct_putu32(t, u->ctag++);
+ pa_tagstruct_putu32(t, u->channel);
+ pa_tagstruct_puts(t, d);
+ pa_pstream_send_tagstruct(u->pstream, t);
+
+ pa_xfree(d);
+}
+
+/* Called from main context */
+static void server_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ pa_sample_spec ss;
+ pa_channel_map cm;
+ const char *server_name, *server_version, *user_name, *host_name, *default_sink_name, *default_source_name;
+ uint32_t cookie;
+
+ pa_assert(pd);
+ pa_assert(u);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (command == PA_COMMAND_ERROR)
+ pa_log("Failed to get info.");
+ else
+ pa_log("Protocol error.");
+ goto fail;
+ }
+
+ if (pa_tagstruct_gets(t, &server_name) < 0 ||
+ pa_tagstruct_gets(t, &server_version) < 0 ||
+ pa_tagstruct_gets(t, &user_name) < 0 ||
+ pa_tagstruct_gets(t, &host_name) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &ss) < 0 ||
+ pa_tagstruct_gets(t, &default_sink_name) < 0 ||
+ pa_tagstruct_gets(t, &default_source_name) < 0 ||
+ pa_tagstruct_getu32(t, &cookie) < 0 ||
+ (u->version >= 15 && pa_tagstruct_get_channel_map(t, &cm) < 0)) {
+
+ pa_log("Parse failure");
+ goto fail;
+ }
+
+ if (!pa_tagstruct_eof(t)) {
+ pa_log("Packet too long");
+ goto fail;
+ }
+
+ pa_xfree(u->server_fqdn);
+ u->server_fqdn = pa_xstrdup(host_name);
+
+ pa_xfree(u->user_name);
+ u->user_name = pa_xstrdup(user_name);
+
+ update_description(u);
+
+ return;
+
+fail:
+ pa_module_unload_request(u->module, true);
+}
+
+static int read_ports(struct userdata *u, pa_tagstruct *t) {
+ if (u->version >= 16) {
+ uint32_t n_ports;
+ const char *s;
+
+ if (pa_tagstruct_getu32(t, &n_ports)) {
+ pa_log("Parse failure");
+ return -PA_ERR_PROTOCOL;
+ }
+
+ for (uint32_t j = 0; j < n_ports; j++) {
+ uint32_t priority;
+
+ if (pa_tagstruct_gets(t, &s) < 0 || /* name */
+ pa_tagstruct_gets(t, &s) < 0 || /* description */
+ pa_tagstruct_getu32(t, &priority) < 0) {
+
+ pa_log("Parse failure");
+ return -PA_ERR_PROTOCOL;
+ }
+ if (u->version >= 24 && pa_tagstruct_getu32(t, &priority) < 0) { /* available */
+ pa_log("Parse failure");
+ return -PA_ERR_PROTOCOL;
+ }
+ }
+
+ if (pa_tagstruct_gets(t, &s) < 0) { /* active port */
+ pa_log("Parse failure");
+ return -PA_ERR_PROTOCOL;
+ }
+ }
+ return 0;
+}
+
+static int read_formats(struct userdata *u, pa_tagstruct *t) {
+ uint8_t n_formats;
+ pa_format_info *format;
+
+ if (pa_tagstruct_getu8(t, &n_formats) < 0) { /* no. of formats */
+ pa_log("Parse failure");
+ return -PA_ERR_PROTOCOL;
+ }
+
+ for (uint8_t j = 0; j < n_formats; j++) {
+ format = pa_format_info_new();
+ if (pa_tagstruct_get_format_info(t, format)) { /* format info */
+ pa_format_info_free(format);
+ pa_log("Parse failure");
+ return -PA_ERR_PROTOCOL;
+ }
+ pa_format_info_free(format);
+ }
+ return 0;
+}
+
+#ifdef TUNNEL_SINK
+
+/* Called from main context */
+static void sink_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ uint32_t idx, owner_module, monitor_source, flags;
+ const char *name, *description, *monitor_source_name, *driver;
+ pa_sample_spec ss;
+ pa_channel_map cm;
+ pa_cvolume volume;
+ bool mute;
+ pa_usec_t latency;
+
+ pa_assert(pd);
+ pa_assert(u);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (command == PA_COMMAND_ERROR)
+ pa_log("Failed to get info.");
+ else
+ pa_log("Protocol error.");
+ goto fail;
+ }
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_gets(t, &name) < 0 ||
+ pa_tagstruct_gets(t, &description) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &ss) < 0 ||
+ pa_tagstruct_get_channel_map(t, &cm) < 0 ||
+ pa_tagstruct_getu32(t, &owner_module) < 0 ||
+ pa_tagstruct_get_cvolume(t, &volume) < 0 ||
+ pa_tagstruct_get_boolean(t, &mute) < 0 ||
+ pa_tagstruct_getu32(t, &monitor_source) < 0 ||
+ pa_tagstruct_gets(t, &monitor_source_name) < 0 ||
+ pa_tagstruct_get_usec(t, &latency) < 0 ||
+ pa_tagstruct_gets(t, &driver) < 0 ||
+ pa_tagstruct_getu32(t, &flags) < 0) {
+
+ pa_log("Parse failure");
+ goto fail;
+ }
+
+ if (u->version >= 13) {
+ pa_usec_t configured_latency;
+
+ if (pa_tagstruct_get_proplist(t, NULL) < 0 ||
+ pa_tagstruct_get_usec(t, &configured_latency) < 0) {
+
+ pa_log("Parse failure");
+ goto fail;
+ }
+ }
+
+ if (u->version >= 15) {
+ pa_volume_t base_volume;
+ uint32_t state, n_volume_steps, card;
+
+ if (pa_tagstruct_get_volume(t, &base_volume) < 0 ||
+ pa_tagstruct_getu32(t, &state) < 0 ||
+ pa_tagstruct_getu32(t, &n_volume_steps) < 0 ||
+ pa_tagstruct_getu32(t, &card) < 0) {
+
+ pa_log("Parse failure");
+ goto fail;
+ }
+ }
+
+ if (read_ports(u, t) < 0)
+ goto fail;
+
+ if (u->version >= 21 && read_formats(u, t) < 0)
+ goto fail;
+
+ if (!pa_tagstruct_eof(t)) {
+ pa_log("Packet too long");
+ goto fail;
+ }
+
+ if (!u->sink_name || !pa_streq(name, u->sink_name))
+ return;
+
+ pa_xfree(u->device_description);
+ u->device_description = pa_xstrdup(description);
+
+ update_description(u);
+
+ return;
+
+fail:
+ pa_module_unload_request(u->module, true);
+}
+
+/* Called from main context */
+static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ uint32_t idx, owner_module, client, sink;
+ pa_usec_t buffer_usec, sink_usec;
+ const char *name, *driver, *resample_method;
+ bool mute = false;
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+ pa_cvolume volume;
+ bool b;
+
+ pa_assert(pd);
+ pa_assert(u);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (command == PA_COMMAND_ERROR)
+ pa_log("Failed to get info.");
+ else
+ pa_log("Protocol error.");
+ goto fail;
+ }
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_gets(t, &name) < 0 ||
+ pa_tagstruct_getu32(t, &owner_module) < 0 ||
+ pa_tagstruct_getu32(t, &client) < 0 ||
+ pa_tagstruct_getu32(t, &sink) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &sample_spec) < 0 ||
+ pa_tagstruct_get_channel_map(t, &channel_map) < 0 ||
+ pa_tagstruct_get_cvolume(t, &volume) < 0 ||
+ pa_tagstruct_get_usec(t, &buffer_usec) < 0 ||
+ pa_tagstruct_get_usec(t, &sink_usec) < 0 ||
+ pa_tagstruct_gets(t, &resample_method) < 0 ||
+ pa_tagstruct_gets(t, &driver) < 0) {
+
+ pa_log("Parse failure");
+ goto fail;
+ }
+
+ if (u->version >= 11) {
+ if (pa_tagstruct_get_boolean(t, &mute) < 0) {
+
+ pa_log("Parse failure");
+ goto fail;
+ }
+ }
+
+ if (u->version >= 13) {
+ if (pa_tagstruct_get_proplist(t, NULL) < 0) {
+
+ pa_log("Parse failure");
+ goto fail;
+ }
+ }
+
+ if (u->version >= 19) {
+ if (pa_tagstruct_get_boolean(t, &b) < 0) {
+
+ pa_log("Parse failure");
+ goto fail;
+ }
+ }
+
+ if (u->version >= 20) {
+ if (pa_tagstruct_get_boolean(t, &b) < 0 ||
+ pa_tagstruct_get_boolean(t, &b) < 0) {
+
+ pa_log("Parse failure");
+ goto fail;
+ }
+ }
+
+ if (u->version >= 21) {
+ pa_format_info *format = pa_format_info_new();
+
+ if (pa_tagstruct_get_format_info(t, format) < 0) {
+ pa_format_info_free(format);
+ pa_log("Parse failure");
+ goto fail;
+ }
+ pa_format_info_free(format);
+ }
+
+ if (!pa_tagstruct_eof(t)) {
+ pa_log("Packet too long");
+ goto fail;
+ }
+
+ if (idx != u->device_index)
+ return;
+
+ pa_assert(u->sink);
+
+ if ((u->version < 11 || !!mute == !!u->sink->muted) &&
+ pa_cvolume_equal(&volume, &u->sink->real_volume))
+ return;
+
+ pa_sink_volume_changed(u->sink, &volume);
+
+ if (u->version >= 11)
+ pa_sink_mute_changed(u->sink, mute);
+
+ return;
+
+fail:
+ pa_module_unload_request(u->module, true);
+}
+
+#else
+
+/* Called from main context */
+static void source_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ uint32_t idx, owner_module, monitor_of_sink, flags;
+ const char *name, *description, *monitor_of_sink_name, *driver;
+ pa_sample_spec ss;
+ pa_channel_map cm;
+ pa_cvolume volume;
+ bool mute;
+ pa_usec_t latency, configured_latency;
+
+ pa_assert(pd);
+ pa_assert(u);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (command == PA_COMMAND_ERROR)
+ pa_log("Failed to get info.");
+ else
+ pa_log("Protocol error.");
+ goto fail;
+ }
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_gets(t, &name) < 0 ||
+ pa_tagstruct_gets(t, &description) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &ss) < 0 ||
+ pa_tagstruct_get_channel_map(t, &cm) < 0 ||
+ pa_tagstruct_getu32(t, &owner_module) < 0 ||
+ pa_tagstruct_get_cvolume(t, &volume) < 0 ||
+ pa_tagstruct_get_boolean(t, &mute) < 0 ||
+ pa_tagstruct_getu32(t, &monitor_of_sink) < 0 ||
+ pa_tagstruct_gets(t, &monitor_of_sink_name) < 0 ||
+ pa_tagstruct_get_usec(t, &latency) < 0 ||
+ pa_tagstruct_gets(t, &driver) < 0 ||
+ pa_tagstruct_getu32(t, &flags) < 0) {
+
+ pa_log("Parse failure");
+ goto fail;
+ }
+
+ if (u->version >= 13) {
+ if (pa_tagstruct_get_proplist(t, NULL) < 0 ||
+ pa_tagstruct_get_usec(t, &configured_latency) < 0) {
+
+ pa_log("Parse failure");
+ goto fail;
+ }
+ }
+
+ if (u->version >= 15) {
+ pa_volume_t base_volume;
+ uint32_t state, n_volume_steps, card;
+
+ if (pa_tagstruct_get_volume(t, &base_volume) < 0 ||
+ pa_tagstruct_getu32(t, &state) < 0 ||
+ pa_tagstruct_getu32(t, &n_volume_steps) < 0 ||
+ pa_tagstruct_getu32(t, &card) < 0) {
+
+ pa_log("Parse failure");
+ goto fail;
+ }
+ }
+
+ if (read_ports(u, t) < 0)
+ goto fail;
+
+ if (u->version >= 22 && read_formats(u, t) < 0)
+ goto fail;
+
+ if (!pa_tagstruct_eof(t)) {
+ pa_log("Packet too long");
+ goto fail;
+ }
+
+ if (!u->source_name || !pa_streq(name, u->source_name))
+ return;
+
+ pa_xfree(u->device_description);
+ u->device_description = pa_xstrdup(description);
+
+ update_description(u);
+
+ return;
+
+fail:
+ pa_module_unload_request(u->module, true);
+}
+
+#endif
+
+/* Called from main context */
+static void request_info(struct userdata *u) {
+ pa_tagstruct *t;
+ uint32_t tag;
+ pa_assert(u);
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_SERVER_INFO);
+ pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_pstream_send_tagstruct(u->pstream, t);
+ pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, server_info_cb, u, NULL);
+
+#ifdef TUNNEL_SINK
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_SINK_INPUT_INFO);
+ pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_tagstruct_putu32(t, u->device_index);
+ pa_pstream_send_tagstruct(u->pstream, t);
+ pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, sink_input_info_cb, u, NULL);
+
+ if (u->sink_name) {
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_SINK_INFO);
+ pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, u->sink_name);
+ pa_pstream_send_tagstruct(u->pstream, t);
+ pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, sink_info_cb, u, NULL);
+ }
+#else
+ if (u->source_name) {
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_SOURCE_INFO);
+ pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, u->source_name);
+ pa_pstream_send_tagstruct(u->pstream, t);
+ pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, source_info_cb, u, NULL);
+ }
+#endif
+}
+
+/* Called from main context */
+static void command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ pa_subscription_event_type_t e;
+ uint32_t idx;
+
+ pa_assert(pd);
+ pa_assert(t);
+ pa_assert(u);
+ pa_assert(command == PA_COMMAND_SUBSCRIBE_EVENT);
+
+ if (pa_tagstruct_getu32(t, &e) < 0 ||
+ pa_tagstruct_getu32(t, &idx) < 0) {
+ pa_log("Invalid protocol reply");
+ pa_module_unload_request(u->module, true);
+ return;
+ }
+
+ if (e != (PA_SUBSCRIPTION_EVENT_SERVER|PA_SUBSCRIPTION_EVENT_CHANGE) &&
+#ifdef TUNNEL_SINK
+ e != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE) &&
+ e != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE)
+#else
+ e != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE)
+#endif
+ )
+ return;
+
+ request_info(u);
+}
+
+/* Called from main context */
+static void start_subscribe(struct userdata *u) {
+ pa_tagstruct *t;
+ pa_assert(u);
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_SUBSCRIBE);
+ pa_tagstruct_putu32(t, u->ctag++);
+ pa_tagstruct_putu32(t, PA_SUBSCRIPTION_MASK_SERVER|
+#ifdef TUNNEL_SINK
+ PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SINK
+#else
+ PA_SUBSCRIPTION_MASK_SOURCE
+#endif
+ );
+
+ pa_pstream_send_tagstruct(u->pstream, t);
+}
+
+/* Called from main context */
+static void create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+#ifdef TUNNEL_SINK
+ uint32_t bytes;
+#endif
+
+ pa_assert(pd);
+ pa_assert(u);
+ pa_assert(u->pdispatch == pd);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (command == PA_COMMAND_ERROR)
+ pa_log("Failed to create stream.");
+ else
+ pa_log("Protocol error.");
+ goto fail;
+ }
+
+ if (pa_tagstruct_getu32(t, &u->channel) < 0 ||
+ pa_tagstruct_getu32(t, &u->device_index) < 0
+#ifdef TUNNEL_SINK
+ || pa_tagstruct_getu32(t, &bytes) < 0
+#endif
+ )
+ goto parse_error;
+
+ if (u->version >= 9) {
+#ifdef TUNNEL_SINK
+ if (pa_tagstruct_getu32(t, &u->maxlength) < 0 ||
+ pa_tagstruct_getu32(t, &u->tlength) < 0 ||
+ pa_tagstruct_getu32(t, &u->prebuf) < 0 ||
+ pa_tagstruct_getu32(t, &u->minreq) < 0)
+ goto parse_error;
+#else
+ if (pa_tagstruct_getu32(t, &u->maxlength) < 0 ||
+ pa_tagstruct_getu32(t, &u->fragsize) < 0)
+ goto parse_error;
+#endif
+ }
+
+ if (u->version >= 12) {
+ pa_sample_spec ss;
+ pa_channel_map cm;
+ uint32_t device_index;
+ const char *dn;
+ bool suspended;
+
+ if (pa_tagstruct_get_sample_spec(t, &ss) < 0 ||
+ pa_tagstruct_get_channel_map(t, &cm) < 0 ||
+ pa_tagstruct_getu32(t, &device_index) < 0 ||
+ pa_tagstruct_gets(t, &dn) < 0 ||
+ pa_tagstruct_get_boolean(t, &suspended) < 0)
+ goto parse_error;
+
+#ifdef TUNNEL_SINK
+ pa_xfree(u->sink_name);
+ u->sink_name = pa_xstrdup(dn);
+#else
+ pa_xfree(u->source_name);
+ u->source_name = pa_xstrdup(dn);