2004-12-12 22:58:53 +00:00
|
|
|
/* $Id$ */
|
|
|
|
|
|
|
|
|
|
/***
|
2006-06-19 21:53:48 +00:00
|
|
|
This file is part of PulseAudio.
|
2004-12-12 22:58:53 +00:00
|
|
|
|
2006-06-19 21:53:48 +00:00
|
|
|
PulseAudio is free software; you can redistribute it and/or modify
|
2004-12-12 22:58:53 +00:00
|
|
|
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.
|
|
|
|
|
|
2006-06-19 21:53:48 +00:00
|
|
|
PulseAudio is distributed in the hope that it will be useful, but
|
2004-12-12 22:58:53 +00:00
|
|
|
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
|
2006-06-19 21:53:48 +00:00
|
|
|
License along with PulseAudio; if not, write to the Free Software
|
2004-12-12 22:58:53 +00:00
|
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
|
|
|
|
USA.
|
|
|
|
|
***/
|
|
|
|
|
|
2006-06-21 14:05:15 +00:00
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
|
#include "config.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
2004-12-12 22:58:53 +00:00
|
|
|
#include <assert.h>
|
|
|
|
|
#include <howl.h>
|
|
|
|
|
|
2006-06-19 21:53:48 +00:00
|
|
|
#include <pulse/xmalloc.h>
|
2006-05-17 16:34:18 +00:00
|
|
|
|
2006-06-19 21:53:48 +00:00
|
|
|
#include <pulsecore/log.h>
|
|
|
|
|
#include <pulsecore/core-util.h>
|
2004-12-12 22:58:53 +00:00
|
|
|
|
2006-02-17 12:10:58 +00:00
|
|
|
#include "browser.h"
|
|
|
|
|
|
2006-06-19 23:51:58 +00:00
|
|
|
#define SERVICE_NAME_SINK "_pulse-sink._tcp."
|
|
|
|
|
#define SERVICE_NAME_SOURCE "_pulse-source._tcp."
|
|
|
|
|
#define SERVICE_NAME_SERVER "_pulse-server._tcp."
|
2004-12-12 22:58:53 +00:00
|
|
|
|
2006-02-22 20:11:56 +00:00
|
|
|
struct pa_browser {
|
2004-12-12 22:58:53 +00:00
|
|
|
int ref;
|
2006-01-11 01:17:39 +00:00
|
|
|
pa_mainloop_api *mainloop;
|
2004-12-12 22:58:53 +00:00
|
|
|
|
2006-02-22 20:11:56 +00:00
|
|
|
pa_browse_cb_t callback;
|
|
|
|
|
void *userdata;
|
2004-12-12 22:58:53 +00:00
|
|
|
|
|
|
|
|
sw_discovery discovery;
|
2006-01-11 01:17:39 +00:00
|
|
|
pa_io_event *io_event;
|
2004-12-12 22:58:53 +00:00
|
|
|
};
|
|
|
|
|
|
2006-04-18 19:31:50 +00:00
|
|
|
static void io_callback(pa_mainloop_api*a, PA_GCC_UNUSED pa_io_event*e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void *userdata) {
|
2006-01-11 01:17:39 +00:00
|
|
|
pa_browser *b = userdata;
|
2004-12-12 22:58:53 +00:00
|
|
|
assert(a && b && b->mainloop == a);
|
|
|
|
|
|
|
|
|
|
if (events != PA_IO_EVENT_INPUT || sw_discovery_read_socket(b->discovery) != SW_OKAY) {
|
2006-02-23 02:27:19 +00:00
|
|
|
pa_log(__FILE__": connection to HOWL daemon failed.");
|
2004-12-12 22:58:53 +00:00
|
|
|
b->mainloop->io_free(b->io_event);
|
|
|
|
|
b->io_event = NULL;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2006-02-22 20:11:56 +00:00
|
|
|
static int type_equal(const char *a, const char *b) {
|
|
|
|
|
size_t la, lb;
|
|
|
|
|
|
|
|
|
|
if (strcasecmp(a, b) == 0)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
la = strlen(a);
|
|
|
|
|
lb = strlen(b);
|
|
|
|
|
|
|
|
|
|
if (la > 0 && a[la-1] == '.' && la == lb+1 && strncasecmp(a, b, la-1) == 0)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
if (lb > 0 && b[lb-1] == '.' && lb == la+1 && strncasecmp(a, b, lb-1) == 0)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int map_to_opcode(const char *type, int new) {
|
|
|
|
|
if (type_equal(type, SERVICE_NAME_SINK))
|
|
|
|
|
return new ? PA_BROWSE_NEW_SINK : PA_BROWSE_REMOVE_SINK;
|
|
|
|
|
else if (type_equal(type, SERVICE_NAME_SOURCE))
|
|
|
|
|
return new ? PA_BROWSE_NEW_SOURCE : PA_BROWSE_REMOVE_SOURCE;
|
|
|
|
|
else if (type_equal(type, SERVICE_NAME_SERVER))
|
|
|
|
|
return new ? PA_BROWSE_NEW_SERVER : PA_BROWSE_REMOVE_SERVER;
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2004-12-12 22:58:53 +00:00
|
|
|
static sw_result resolve_reply(
|
2006-02-22 20:11:56 +00:00
|
|
|
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) {
|
2004-12-12 22:58:53 +00:00
|
|
|
|
2006-01-11 01:17:39 +00:00
|
|
|
pa_browser *b = extra;
|
|
|
|
|
pa_browse_info i;
|
2004-12-12 22:58:53 +00:00
|
|
|
char ip[256], a[256];
|
2006-02-22 20:11:56 +00:00
|
|
|
int opcode;
|
2004-12-12 22:58:53 +00:00
|
|
|
int device_found = 0;
|
|
|
|
|
uint32_t cookie;
|
2006-01-11 01:17:39 +00:00
|
|
|
pa_sample_spec ss;
|
2004-12-12 22:58:53 +00:00
|
|
|
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;
|
|
|
|
|
|
2006-02-22 20:11:56 +00:00
|
|
|
opcode = map_to_opcode(type, 1);
|
|
|
|
|
assert(opcode >= 0);
|
|
|
|
|
|
2004-12-12 22:58:53 +00:00
|
|
|
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) {
|
2006-02-23 02:27:19 +00:00
|
|
|
pa_log_error(__FILE__": sw_text_record_string_iterator_init() failed.");
|
2004-12-12 22:58:53 +00:00
|
|
|
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")) {
|
2005-01-11 20:47:10 +00:00
|
|
|
size_t l;
|
|
|
|
|
|
2004-12-12 22:58:53 +00:00
|
|
|
pa_xfree((char*) i.fqdn);
|
|
|
|
|
i.fqdn = c;
|
|
|
|
|
c = NULL;
|
2005-01-11 20:47:10 +00:00
|
|
|
|
|
|
|
|
l = strlen(a);
|
|
|
|
|
assert(l+1 <= sizeof(a));
|
|
|
|
|
strncat(a, " ", sizeof(a)-l-1);
|
|
|
|
|
strncat(a, i.fqdn, sizeof(a)-l-2);
|
2004-12-12 22:58:53 +00:00
|
|
|
} 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, "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;
|
|
|
|
|
|
|
|
|
|
|
2006-02-22 20:11:56 +00:00
|
|
|
b->callback(b, opcode, &i, b->userdata);
|
2004-12-12 22:58:53 +00:00
|
|
|
|
|
|
|
|
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(
|
2006-02-22 20:11:56 +00:00
|
|
|
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) {
|
2004-12-12 22:58:53 +00:00
|
|
|
|
2006-01-11 01:17:39 +00:00
|
|
|
pa_browser *b = extra;
|
2004-12-12 22:58:53 +00:00
|
|
|
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)
|
2006-02-23 02:27:19 +00:00
|
|
|
pa_log_error(__FILE__": sw_discovery_resolve() failed");
|
2004-12-12 22:58:53 +00:00
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case SW_DISCOVERY_BROWSE_REMOVE_SERVICE:
|
|
|
|
|
if (b->callback) {
|
2006-01-11 01:17:39 +00:00
|
|
|
pa_browse_info i;
|
2006-02-22 20:11:56 +00:00
|
|
|
int opcode;
|
|
|
|
|
|
2004-12-12 22:58:53 +00:00
|
|
|
memset(&i, 0, sizeof(i));
|
|
|
|
|
i.name = name;
|
2006-02-22 20:11:56 +00:00
|
|
|
|
|
|
|
|
opcode = map_to_opcode(type, 0);
|
|
|
|
|
assert(opcode >= 0);
|
|
|
|
|
|
|
|
|
|
b->callback(b, opcode, &i, b->userdata);
|
2004-12-12 22:58:53 +00:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return SW_OKAY;
|
|
|
|
|
}
|
|
|
|
|
|
2006-01-11 01:17:39 +00:00
|
|
|
pa_browser *pa_browser_new(pa_mainloop_api *mainloop) {
|
|
|
|
|
pa_browser *b;
|
2004-12-12 22:58:53 +00:00
|
|
|
sw_discovery_oid oid;
|
|
|
|
|
|
2006-02-22 20:11:56 +00:00
|
|
|
b = pa_xnew(pa_browser, 1);
|
2004-12-12 22:58:53 +00:00
|
|
|
b->mainloop = mainloop;
|
|
|
|
|
b->ref = 1;
|
|
|
|
|
b->callback = NULL;
|
2006-02-22 20:11:56 +00:00
|
|
|
b->userdata = NULL;
|
2004-12-12 22:58:53 +00:00
|
|
|
|
|
|
|
|
if (sw_discovery_init(&b->discovery) != SW_OKAY) {
|
2006-02-23 02:27:19 +00:00
|
|
|
pa_log_error(__FILE__": sw_discovery_init() failed.");
|
2004-12-12 22:58:53 +00:00
|
|
|
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) {
|
|
|
|
|
|
2006-02-23 02:27:19 +00:00
|
|
|
pa_log_error(__FILE__": sw_discovery_browse() failed.");
|
2004-12-12 22:58:53 +00:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2006-01-11 01:17:39 +00:00
|
|
|
static void browser_free(pa_browser *b) {
|
2004-12-12 22:58:53 +00:00
|
|
|
assert(b && b->mainloop);
|
|
|
|
|
|
|
|
|
|
if (b->io_event)
|
|
|
|
|
b->mainloop->io_free(b->io_event);
|
|
|
|
|
|
|
|
|
|
sw_discovery_fina(b->discovery);
|
|
|
|
|
pa_xfree(b);
|
|
|
|
|
}
|
|
|
|
|
|
2006-01-11 01:17:39 +00:00
|
|
|
pa_browser *pa_browser_ref(pa_browser *b) {
|
2004-12-12 22:58:53 +00:00
|
|
|
assert(b && b->ref >= 1);
|
|
|
|
|
b->ref++;
|
|
|
|
|
return b;
|
|
|
|
|
}
|
|
|
|
|
|
2006-01-11 01:17:39 +00:00
|
|
|
void pa_browser_unref(pa_browser *b) {
|
2004-12-12 22:58:53 +00:00
|
|
|
assert(b && b->ref >= 1);
|
|
|
|
|
|
|
|
|
|
if ((-- (b->ref)) <= 0)
|
|
|
|
|
browser_free(b);
|
|
|
|
|
}
|
|
|
|
|
|
2006-02-22 20:11:56 +00:00
|
|
|
void pa_browser_set_callback(pa_browser *b, pa_browse_cb_t cb, void *userdata) {
|
2004-12-12 22:58:53 +00:00
|
|
|
assert(b);
|
|
|
|
|
|
|
|
|
|
b->callback = cb;
|
2006-02-22 20:11:56 +00:00
|
|
|
b->userdata = userdata;
|
2004-12-12 22:58:53 +00:00
|
|
|
}
|