4 This file is part of PulseAudio.
6 PulseAudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as
8 published by the Free Software Foundation; either version 2 of the
9 License, or (at your option) any later version.
11 PulseAudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with PulseAudio; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
29 #include <pulse/xmalloc.h>
31 #include <pulsecore/log.h>
32 #include <pulsecore/core-util.h>
36 #define SERVICE_NAME_SINK "_pulse-sink._tcp."
37 #define SERVICE_NAME_SOURCE "_pulse-source._tcp."
38 #define SERVICE_NAME_SERVER "_pulse-server._tcp."
42 pa_mainloop_api *mainloop;
44 pa_browse_cb_t callback;
47 sw_discovery discovery;
48 pa_io_event *io_event;
51 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) {
52 pa_browser *b = userdata;
53 assert(a && b && b->mainloop == a);
55 if (events != PA_IO_EVENT_INPUT || sw_discovery_read_socket(b->discovery) != SW_OKAY) {
56 pa_log(__FILE__": connection to HOWL daemon failed.");
57 b->mainloop->io_free(b->io_event);
63 static int type_equal(const char *a, const char *b) {
66 if (strcasecmp(a, b) == 0)
72 if (la > 0 && a[la-1] == '.' && la == lb+1 && strncasecmp(a, b, la-1) == 0)
75 if (lb > 0 && b[lb-1] == '.' && lb == la+1 && strncasecmp(a, b, lb-1) == 0)
81 static int map_to_opcode(const char *type, int new) {
82 if (type_equal(type, SERVICE_NAME_SINK))
83 return new ? PA_BROWSE_NEW_SINK : PA_BROWSE_REMOVE_SINK;
84 else if (type_equal(type, SERVICE_NAME_SOURCE))
85 return new ? PA_BROWSE_NEW_SOURCE : PA_BROWSE_REMOVE_SOURCE;
86 else if (type_equal(type, SERVICE_NAME_SERVER))
87 return new ? PA_BROWSE_NEW_SERVER : PA_BROWSE_REMOVE_SERVER;
92 static sw_result resolve_reply(
93 sw_discovery discovery,
95 sw_uint32 interface_index,
98 sw_const_string domain,
99 sw_ipv4_address address,
101 sw_octets text_record,
102 sw_ulong text_record_len,
105 pa_browser *b = extra;
107 char ip[256], a[256];
109 int device_found = 0;
113 sw_text_record_iterator iterator;
114 int free_iterator = 0;
119 sw_discovery_cancel(discovery, oid);
121 memset(&i, 0, sizeof(i));
127 opcode = map_to_opcode(type, 1);
130 snprintf(a, sizeof(a), "tcp:%s:%u", sw_ipv4_address_name(address, ip, sizeof(ip)), port);
133 if (text_record && text_record_len) {
134 char key[SW_TEXT_RECORD_MAX_LEN];
135 uint8_t val[SW_TEXT_RECORD_MAX_LEN];
138 if (sw_text_record_iterator_init(&iterator, text_record, text_record_len) != SW_OKAY) {
139 pa_log_error(__FILE__": sw_text_record_string_iterator_init() failed.");
145 while (sw_text_record_iterator_next(iterator, key, val, &val_len) == SW_OKAY) {
146 c = pa_xstrndup((char*) val, val_len);
148 if (!strcmp(key, "device")) {
150 pa_xfree((char*) i.device);
153 } else if (!strcmp(key, "server-version")) {
154 pa_xfree((char*) i.server_version);
155 i.server_version = c;
157 } else if (!strcmp(key, "user-name")) {
158 pa_xfree((char*) i.user_name);
161 } else if (!strcmp(key, "fqdn")) {
164 pa_xfree((char*) i.fqdn);
169 assert(l+1 <= sizeof(a));
170 strncat(a, " ", sizeof(a)-l-1);
171 strncat(a, i.fqdn, sizeof(a)-l-2);
172 } else if (!strcmp(key, "cookie")) {
174 if (pa_atou(c, &cookie) < 0)
178 } else if (!strcmp(key, "description")) {
179 pa_xfree((char*) i.description);
182 } else if (!strcmp(key, "channels")) {
185 if (pa_atou(c, &ch) < 0 || ch <= 0 || ch > 255)
188 ss.channels = (uint8_t) ch;
191 } else if (!strcmp(key, "rate")) {
192 if (pa_atou(c, &ss.rate) < 0)
195 } else if (!strcmp(key, "format")) {
197 if ((ss.format = pa_parse_sample_format(c)) == PA_SAMPLE_INVALID)
209 /* No device txt record was sent for a sink or source service */
210 if (opcode != PA_BROWSE_NEW_SERVER && !device_found)
217 b->callback(b, opcode, &i, b->userdata);
220 pa_xfree((void*) i.device);
221 pa_xfree((void*) i.fqdn);
222 pa_xfree((void*) i.server_version);
223 pa_xfree((void*) i.user_name);
224 pa_xfree((void*) i.description);
228 sw_text_record_iterator_fina(iterator);
234 static sw_result browse_reply(
235 sw_discovery discovery,
237 sw_discovery_browse_status status,
238 sw_uint32 interface_index,
239 sw_const_string name,
240 sw_const_string type,
241 sw_const_string domain,
244 pa_browser *b = extra;
248 case SW_DISCOVERY_BROWSE_ADD_SERVICE: {
249 sw_discovery_oid oid;
251 if (sw_discovery_resolve(b->discovery, 0, name, type, domain, resolve_reply, b, &oid) != SW_OKAY)
252 pa_log_error(__FILE__": sw_discovery_resolve() failed");
257 case SW_DISCOVERY_BROWSE_REMOVE_SERVICE:
262 memset(&i, 0, sizeof(i));
265 opcode = map_to_opcode(type, 0);
268 b->callback(b, opcode, &i, b->userdata);
279 pa_browser *pa_browser_new(pa_mainloop_api *mainloop) {
281 sw_discovery_oid oid;
283 b = pa_xnew(pa_browser, 1);
284 b->mainloop = mainloop;
289 if (sw_discovery_init(&b->discovery) != SW_OKAY) {
290 pa_log_error(__FILE__": sw_discovery_init() failed.");
295 if (sw_discovery_browse(b->discovery, 0, SERVICE_NAME_SERVER, NULL, browse_reply, b, &oid) != SW_OKAY ||
296 sw_discovery_browse(b->discovery, 0, SERVICE_NAME_SINK, NULL, browse_reply, b, &oid) != SW_OKAY ||
297 sw_discovery_browse(b->discovery, 0, SERVICE_NAME_SOURCE, NULL, browse_reply, b, &oid) != SW_OKAY) {
299 pa_log_error(__FILE__": sw_discovery_browse() failed.");
301 sw_discovery_fina(b->discovery);
306 b->io_event = mainloop->io_new(mainloop, sw_discovery_socket(b->discovery), PA_IO_EVENT_INPUT, io_callback, b);
310 static void browser_free(pa_browser *b) {
311 assert(b && b->mainloop);
314 b->mainloop->io_free(b->io_event);
316 sw_discovery_fina(b->discovery);
320 pa_browser *pa_browser_ref(pa_browser *b) {
321 assert(b && b->ref >= 1);
326 void pa_browser_unref(pa_browser *b) {
327 assert(b && b->ref >= 1);
329 if ((-- (b->ref)) <= 0)
333 void pa_browser_set_callback(pa_browser *b, pa_browse_cb_t cb, void *userdata) {
337 b->userdata = userdata;