summaryrefslogtreecommitdiffstats
path: root/src/polyp/browser.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/polyp/browser.c')
-rw-r--r--src/polyp/browser.c312
1 files changed, 312 insertions, 0 deletions
diff --git a/src/polyp/browser.c b/src/polyp/browser.c
new file mode 100644
index 00000000..80051d54
--- /dev/null
+++ b/src/polyp/browser.c
@@ -0,0 +1,312 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio 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 of the
+ License, or (at your option) any later version.
+
+ polypaudio 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 polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <assert.h>
+#include <howl.h>
+
+#include "browser.h"
+#include <polypcore/xmalloc.h>
+#include <polypcore/log.h>
+#include <polypcore/util.h>
+
+#define SERVICE_NAME_SINK "_polypaudio-sink._tcp."
+#define SERVICE_NAME_SOURCE "_polypaudio-source._tcp."
+#define SERVICE_NAME_SERVER "_polypaudio-server._tcp."
+
+pa_browser {
+ int ref;
+ pa_mainloop_api *mainloop;
+
+ void (*callback)(pa_browser *z, pa_browse_opcode c, const pa_browse_info *i, void *userdata);
+ void *callback_userdata;
+
+ sw_discovery discovery;
+ pa_io_event *io_event;
+};
+
+
+static void io_callback(pa_mainloop_api*a, pa_io_event*e, int fd, pa_io_event_flags events, void *userdata) {
+ pa_browser *b = userdata;
+ assert(a && b && b->mainloop == a);
+
+ if (events != PA_IO_EVENT_INPUT || sw_discovery_read_socket(b->discovery) != SW_OKAY) {
+ pa_log(__FILE__": connection to HOWL daemon failed.\n");
+ b->mainloop->io_free(b->io_event);
+ b->io_event = NULL;
+ return;
+ }
+}
+
+static sw_result resolve_reply(
+ sw_discovery discovery,
+ sw_discovery_oid oid,
+ sw_uint32 interface_index,
+ sw_const_string name,
+ sw_const_string type,
+ sw_const_string domain,
+ sw_ipv4_address address,
+ sw_port port,
+ sw_octets text_record,
+ sw_ulong text_record_len,
+ sw_opaque extra) {
+
+ pa_browser *b = extra;
+ pa_browse_info i;
+ char ip[256], a[256];
+ pa_browse_opcode opcode;
+ int device_found = 0;
+ uint32_t cookie;
+ pa_typeid_t typeid;
+ pa_sample_spec ss;
+ int ss_valid = 0;
+ sw_text_record_iterator iterator;
+ int free_iterator = 0;
+ char *c = NULL;
+
+ assert(b);
+
+ sw_discovery_cancel(discovery, oid);
+
+ memset(&i, 0, sizeof(i));
+ i.name = name;
+
+ if (!b->callback)
+ goto fail;
+
+ if (!strcmp(type, SERVICE_NAME_SINK))
+ opcode = PA_BROWSE_NEW_SINK;
+ else if (!strcmp(type, SERVICE_NAME_SOURCE))
+ opcode = PA_BROWSE_NEW_SOURCE;
+ else if (!strcmp(type, SERVICE_NAME_SERVER))
+ opcode = PA_BROWSE_NEW_SERVER;
+ else
+ goto fail;
+
+
+ snprintf(a, sizeof(a), "tcp:%s:%u", sw_ipv4_address_name(address, ip, sizeof(ip)), port);
+ i.server = a;
+
+ if (text_record && text_record_len) {
+ char key[SW_TEXT_RECORD_MAX_LEN];
+ uint8_t val[SW_TEXT_RECORD_MAX_LEN];
+ uint32_t val_len;
+
+ if (sw_text_record_iterator_init(&iterator, text_record, text_record_len) != SW_OKAY) {
+ pa_log("sw_text_record_string_iterator_init() failed.\n");
+ goto fail;
+ }
+
+ free_iterator = 1;
+
+ while (sw_text_record_iterator_next(iterator, key, val, &val_len) == SW_OKAY) {
+ c = pa_xstrndup((char*) val, val_len);
+
+ if (!strcmp(key, "device")) {
+ device_found = 1;
+ pa_xfree((char*) i.device);
+ i.device = c;
+ c = NULL;
+ } else if (!strcmp(key, "server-version")) {
+ pa_xfree((char*) i.server_version);
+ i.server_version = c;
+ c = NULL;
+ } else if (!strcmp(key, "user-name")) {
+ pa_xfree((char*) i.user_name);
+ i.user_name = c;
+ c = NULL;
+ } else if (!strcmp(key, "fqdn")) {
+ size_t l;
+
+ pa_xfree((char*) i.fqdn);
+ i.fqdn = c;
+ c = NULL;
+
+ l = strlen(a);
+ assert(l+1 <= sizeof(a));
+ strncat(a, " ", sizeof(a)-l-1);
+ strncat(a, i.fqdn, sizeof(a)-l-2);
+ } else if (!strcmp(key, "cookie")) {
+
+ if (pa_atou(c, &cookie) < 0)
+ goto fail;
+
+ i.cookie = &cookie;
+ } else if (!strcmp(key, "description")) {
+ pa_xfree((char*) i.description);
+ i.description = c;
+ c = NULL;
+ } else if (!strcmp(key, "typeid")) {
+
+ if (pa_atou(c, &typeid) < 0)
+ goto fail;
+
+ i.typeid = &typeid;
+ } else if (!strcmp(key, "channels")) {
+ uint32_t ch;
+
+ if (pa_atou(c, &ch) < 0 || ch <= 0 || ch > 255)
+ goto fail;
+
+ ss.channels = (uint8_t) ch;
+ ss_valid |= 1;
+
+ } else if (!strcmp(key, "rate")) {
+ if (pa_atou(c, &ss.rate) < 0)
+ goto fail;
+ ss_valid |= 2;
+ } else if (!strcmp(key, "format")) {
+
+ if ((ss.format = pa_parse_sample_format(c)) == PA_SAMPLE_INVALID)
+ goto fail;
+
+ ss_valid |= 4;
+ }
+
+ pa_xfree(c);
+ c = NULL;
+ }
+
+ }
+
+ /* No device txt record was sent for a sink or source service */
+ if (opcode != PA_BROWSE_NEW_SERVER && !device_found)
+ goto fail;
+
+ if (ss_valid == 7)
+ i.sample_spec = &ss;
+
+
+ b->callback(b, opcode, &i, b->callback_userdata);
+
+fail:
+ pa_xfree((void*) i.device);
+ pa_xfree((void*) i.fqdn);
+ pa_xfree((void*) i.server_version);
+ pa_xfree((void*) i.user_name);
+ pa_xfree((void*) i.description);
+ pa_xfree(c);
+
+ if (free_iterator)
+ sw_text_record_iterator_fina(iterator);
+
+
+ return SW_OKAY;
+}
+
+static sw_result browse_reply(
+ sw_discovery discovery,
+ sw_discovery_oid id,
+ sw_discovery_browse_status status,
+ sw_uint32 interface_index,
+ sw_const_string name,
+ sw_const_string type,
+ sw_const_string domain,
+ sw_opaque extra) {
+
+ pa_browser *b = extra;
+ assert(b);
+
+ switch (status) {
+ case SW_DISCOVERY_BROWSE_ADD_SERVICE: {
+ sw_discovery_oid oid;
+
+ if (sw_discovery_resolve(b->discovery, 0, name, type, domain, resolve_reply, b, &oid) != SW_OKAY)
+ pa_log("sw_discovery_resolve() failed\n");
+
+ break;
+ }
+
+ case SW_DISCOVERY_BROWSE_REMOVE_SERVICE:
+ if (b->callback) {
+ pa_browse_info i;
+ memset(&i, 0, sizeof(i));
+ i.name = name;
+ b->callback(b, PA_BROWSE_REMOVE, &i, b->callback_userdata);
+ }
+ break;
+
+ default:
+ ;
+ }
+
+ return SW_OKAY;
+}
+
+pa_browser *pa_browser_new(pa_mainloop_api *mainloop) {
+ pa_browser *b;
+ sw_discovery_oid oid;
+
+ b = pa_xmalloc(sizeof(pa_browser));
+ b->mainloop = mainloop;
+ b->ref = 1;
+ b->callback = NULL;
+ b->callback_userdata = NULL;
+
+ if (sw_discovery_init(&b->discovery) != SW_OKAY) {
+ pa_log("sw_discovery_init() failed.\n");
+ pa_xfree(b);
+ return NULL;
+ }
+
+ if (sw_discovery_browse(b->discovery, 0, SERVICE_NAME_SERVER, NULL, browse_reply, b, &oid) != SW_OKAY ||
+ sw_discovery_browse(b->discovery, 0, SERVICE_NAME_SINK, NULL, browse_reply, b, &oid) != SW_OKAY ||
+ sw_discovery_browse(b->discovery, 0, SERVICE_NAME_SOURCE, NULL, browse_reply, b, &oid) != SW_OKAY) {
+
+ pa_log("sw_discovery_browse() failed.\n");
+
+ sw_discovery_fina(b->discovery);
+ pa_xfree(b);
+ return NULL;
+ }
+
+ b->io_event = mainloop->io_new(mainloop, sw_discovery_socket(b->discovery), PA_IO_EVENT_INPUT, io_callback, b);
+ return b;
+}
+
+static void browser_free(pa_browser *b) {
+ assert(b && b->mainloop);
+
+ if (b->io_event)
+ b->mainloop->io_free(b->io_event);
+
+ sw_discovery_fina(b->discovery);
+ pa_xfree(b);
+}
+
+pa_browser *pa_browser_ref(pa_browser *b) {
+ assert(b && b->ref >= 1);
+ b->ref++;
+ return b;
+}
+
+void pa_browser_unref(pa_browser *b) {
+ assert(b && b->ref >= 1);
+
+ if ((-- (b->ref)) <= 0)
+ browser_free(b);
+}
+
+void pa_browser_set_callback(pa_browser *b, void (*cb)(pa_browser *z, pa_browse_opcode c, const pa_browse_info *i, void* userdata), void *userdata) {
+ assert(b);
+
+ b->callback = cb;
+ b->callback_userdata = userdata;
+}