]> code.delx.au - pulseaudio/blob - src/pulse/browser.c
merge 'lennart' branch back into trunk.
[pulseaudio] / src / pulse / browser.c
1 /* $Id$ */
2
3 /***
4 This file is part of PulseAudio.
5
6 Copyright 2004-2006 Lennart Poettering
7
8 PulseAudio is free software; you can redistribute it and/or modify
9 it under the terms of the GNU Lesser General Public License as
10 published by the Free Software Foundation; either version 2 of the
11 License, or (at your option) any later version.
12
13 PulseAudio is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public
19 License along with PulseAudio; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21 USA.
22 ***/
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27
28 #include <string.h>
29
30 #include <avahi-client/lookup.h>
31 #include <avahi-common/domain.h>
32 #include <avahi-common/error.h>
33
34 #include <pulse/xmalloc.h>
35
36 #include <pulsecore/log.h>
37 #include <pulsecore/core-util.h>
38 #include <pulsecore/avahi-wrap.h>
39 #include <pulsecore/refcnt.h>
40 #include <pulsecore/macro.h>
41
42 #include "browser.h"
43
44 #define SERVICE_TYPE_SINK "_pulse-sink._tcp."
45 #define SERVICE_TYPE_SOURCE "_pulse-source._tcp."
46 #define SERVICE_TYPE_SERVER "_pulse-server._tcp."
47
48 struct pa_browser {
49 PA_REFCNT_DECLARE;
50
51 pa_mainloop_api *mainloop;
52 AvahiPoll* avahi_poll;
53
54 pa_browse_cb_t callback;
55 void *userdata;
56
57 pa_browser_error_cb_t error_callback;
58 void *error_userdata;
59
60 AvahiClient *client;
61 AvahiServiceBrowser *server_browser, *sink_browser, *source_browser;
62
63 };
64
65 static int map_to_opcode(const char *type, int new) {
66
67 if (avahi_domain_equal(type, SERVICE_TYPE_SINK))
68 return new ? PA_BROWSE_NEW_SINK : PA_BROWSE_REMOVE_SINK;
69 else if (avahi_domain_equal(type, SERVICE_TYPE_SOURCE))
70 return new ? PA_BROWSE_NEW_SOURCE : PA_BROWSE_REMOVE_SOURCE;
71 else if (avahi_domain_equal(type, SERVICE_TYPE_SERVER))
72 return new ? PA_BROWSE_NEW_SERVER : PA_BROWSE_REMOVE_SERVER;
73
74 return -1;
75 }
76
77 static void resolve_callback(
78 AvahiServiceResolver *r,
79 AvahiIfIndex interface,
80 AvahiProtocol protocol,
81 AvahiResolverEvent event,
82 const char *name,
83 const char *type,
84 const char *domain,
85 const char *host_name,
86 const AvahiAddress *aa,
87 uint16_t port,
88 AvahiStringList *txt,
89 AvahiLookupResultFlags flags,
90 void *userdata) {
91
92 pa_browser *b = userdata;
93 pa_browse_info i;
94 char ip[256], a[256];
95 int opcode;
96 int device_found = 0;
97 uint32_t cookie;
98 pa_sample_spec ss;
99 int ss_valid = 0;
100 char *key = NULL, *value = NULL;
101
102 pa_assert(b);
103 pa_assert(PA_REFCNT_VALUE(b) >= 1);
104
105 memset(&i, 0, sizeof(i));
106 i.name = name;
107
108 if (event != AVAHI_RESOLVER_FOUND)
109 goto fail;
110
111 if (!b->callback)
112 goto fail;
113
114 opcode = map_to_opcode(type, 1);
115 pa_assert(opcode >= 0);
116
117 if (aa->proto == AVAHI_PROTO_INET)
118 pa_snprintf(a, sizeof(a), "tcp:%s:%u", avahi_address_snprint(ip, sizeof(ip), aa), port);
119 else {
120 pa_assert(aa->proto == AVAHI_PROTO_INET6);
121 pa_snprintf(a, sizeof(a), "tcp6:%s:%u", avahi_address_snprint(ip, sizeof(ip), aa), port);
122 }
123 i.server = a;
124
125
126 while (txt) {
127
128 if (avahi_string_list_get_pair(txt, &key, &value, NULL) < 0)
129 break;
130
131 if (!strcmp(key, "device")) {
132 device_found = 1;
133 pa_xfree((char*) i.device);
134 i.device = value;
135 value = NULL;
136 } else if (!strcmp(key, "server-version")) {
137 pa_xfree((char*) i.server_version);
138 i.server_version = value;
139 value = NULL;
140 } else if (!strcmp(key, "user-name")) {
141 pa_xfree((char*) i.user_name);
142 i.user_name = value;
143 value = NULL;
144 } else if (!strcmp(key, "fqdn")) {
145 size_t l;
146
147 pa_xfree((char*) i.fqdn);
148 i.fqdn = value;
149 value = NULL;
150
151 l = strlen(a);
152 pa_assert(l+1 <= sizeof(a));
153 strncat(a, " ", sizeof(a)-l-1);
154 strncat(a, i.fqdn, sizeof(a)-l-2);
155 } else if (!strcmp(key, "cookie")) {
156
157 if (pa_atou(value, &cookie) < 0)
158 goto fail;
159
160 i.cookie = &cookie;
161 } else if (!strcmp(key, "description")) {
162 pa_xfree((char*) i.description);
163 i.description = value;
164 value = NULL;
165 } else if (!strcmp(key, "channels")) {
166 uint32_t ch;
167
168 if (pa_atou(value, &ch) < 0 || ch <= 0 || ch > 255)
169 goto fail;
170
171 ss.channels = (uint8_t) ch;
172 ss_valid |= 1;
173
174 } else if (!strcmp(key, "rate")) {
175 if (pa_atou(value, &ss.rate) < 0)
176 goto fail;
177 ss_valid |= 2;
178 } else if (!strcmp(key, "format")) {
179
180 if ((ss.format = pa_parse_sample_format(value)) == PA_SAMPLE_INVALID)
181 goto fail;
182
183 ss_valid |= 4;
184 }
185
186 pa_xfree(key);
187 pa_xfree(value);
188 key = value = NULL;
189
190 txt = avahi_string_list_get_next(txt);
191 }
192
193 /* No device txt record was sent for a sink or source service */
194 if (opcode != PA_BROWSE_NEW_SERVER && !device_found)
195 goto fail;
196
197 if (ss_valid == 7)
198 i.sample_spec = &ss;
199
200 b->callback(b, opcode, &i, b->userdata);
201
202 fail:
203 pa_xfree((void*) i.device);
204 pa_xfree((void*) i.fqdn);
205 pa_xfree((void*) i.server_version);
206 pa_xfree((void*) i.user_name);
207 pa_xfree((void*) i.description);
208
209 pa_xfree(key);
210 pa_xfree(value);
211
212 avahi_service_resolver_free(r);
213 }
214
215 static void handle_failure(pa_browser *b) {
216 const char *e = NULL;
217
218 pa_assert(b);
219 pa_assert(PA_REFCNT_VALUE(b) >= 1);
220
221 if (b->sink_browser)
222 avahi_service_browser_free(b->sink_browser);
223 if (b->source_browser)
224 avahi_service_browser_free(b->source_browser);
225 if (b->server_browser)
226 avahi_service_browser_free(b->server_browser);
227
228 b->sink_browser = b->source_browser = b->server_browser = NULL;
229
230 if (b->client) {
231 e = avahi_strerror(avahi_client_errno(b->client));
232 avahi_client_free(b->client);
233 }
234
235 b->client = NULL;
236
237 if (b->error_callback)
238 b->error_callback(b, e, b->error_userdata);
239 }
240
241 static void browse_callback(
242 AvahiServiceBrowser *sb,
243 AvahiIfIndex interface,
244 AvahiProtocol protocol,
245 AvahiBrowserEvent event,
246 const char *name,
247 const char *type,
248 const char *domain,
249 AvahiLookupResultFlags flags,
250 void *userdata) {
251
252 pa_browser *b = userdata;
253
254 pa_assert(b);
255 pa_assert(PA_REFCNT_VALUE(b) >= 1);
256
257 switch (event) {
258 case AVAHI_BROWSER_NEW: {
259
260 if (!avahi_service_resolver_new(
261 b->client,
262 interface,
263 protocol,
264 name,
265 type,
266 domain,
267 AVAHI_PROTO_UNSPEC,
268 0,
269 resolve_callback,
270 b))
271 handle_failure(b);
272
273 break;
274 }
275
276 case AVAHI_BROWSER_REMOVE: {
277
278 if (b->callback) {
279 pa_browse_info i;
280 int opcode;
281
282 memset(&i, 0, sizeof(i));
283 i.name = name;
284
285 opcode = map_to_opcode(type, 0);
286 pa_assert(opcode >= 0);
287
288 b->callback(b, opcode, &i, b->userdata);
289 }
290 break;
291 }
292
293 case AVAHI_BROWSER_FAILURE: {
294 handle_failure(b);
295 break;
296 }
297
298 default:
299 ;
300 }
301 }
302
303 static void client_callback(AvahiClient *s, AvahiClientState state, void *userdata) {
304 pa_browser *b = userdata;
305
306 pa_assert(s);
307 pa_assert(b);
308 pa_assert(PA_REFCNT_VALUE(b) >= 1);
309
310 if (state == AVAHI_CLIENT_FAILURE)
311 handle_failure(b);
312 }
313
314 static void browser_free(pa_browser *b);
315
316 pa_browser *pa_browser_new(pa_mainloop_api *mainloop) {
317 return pa_browser_new_full(mainloop, PA_BROWSE_FOR_SERVERS|PA_BROWSE_FOR_SINKS|PA_BROWSE_FOR_SOURCES, NULL);
318 }
319
320 pa_browser *pa_browser_new_full(pa_mainloop_api *mainloop, pa_browse_flags_t flags, const char **error_string) {
321 pa_browser *b;
322 int error;
323
324 pa_assert(mainloop);
325
326 if (flags & ~(PA_BROWSE_FOR_SERVERS|PA_BROWSE_FOR_SINKS|PA_BROWSE_FOR_SOURCES) || flags == 0)
327 return NULL;
328
329 b = pa_xnew(pa_browser, 1);
330 b->mainloop = mainloop;
331 PA_REFCNT_INIT(b);
332 b->callback = NULL;
333 b->userdata = NULL;
334 b->error_callback = NULL;
335 b->error_userdata = NULL;
336 b->sink_browser = b->source_browser = b->server_browser = NULL;
337
338 b->avahi_poll = pa_avahi_poll_new(mainloop);
339
340 if (!(b->client = avahi_client_new(b->avahi_poll, 0, client_callback, b, &error))) {
341 if (error_string)
342 *error_string = avahi_strerror(error);
343 goto fail;
344 }
345
346 if ((flags & PA_BROWSE_FOR_SERVERS) &&
347 !(b->server_browser = avahi_service_browser_new(
348 b->client,
349 AVAHI_IF_UNSPEC,
350 AVAHI_PROTO_INET,
351 SERVICE_TYPE_SERVER,
352 NULL,
353 0,
354 browse_callback,
355 b))) {
356
357 if (error_string)
358 *error_string = avahi_strerror(avahi_client_errno(b->client));
359 goto fail;
360 }
361
362 if ((flags & PA_BROWSE_FOR_SINKS) &&
363 !(b->sink_browser = avahi_service_browser_new(
364 b->client,
365 AVAHI_IF_UNSPEC,
366 AVAHI_PROTO_UNSPEC,
367 SERVICE_TYPE_SINK,
368 NULL,
369 0,
370 browse_callback,
371 b))) {
372
373 if (error_string)
374 *error_string = avahi_strerror(avahi_client_errno(b->client));
375 goto fail;
376 }
377
378 if ((flags & PA_BROWSE_FOR_SOURCES) &&
379 !(b->source_browser = avahi_service_browser_new(
380 b->client,
381 AVAHI_IF_UNSPEC,
382 AVAHI_PROTO_UNSPEC,
383 SERVICE_TYPE_SOURCE,
384 NULL,
385 0,
386 browse_callback,
387 b))) {
388
389 if (error_string)
390 *error_string = avahi_strerror(avahi_client_errno(b->client));
391 goto fail;
392 }
393
394 return b;
395
396 fail:
397 if (b)
398 browser_free(b);
399
400 return NULL;
401 }
402
403 static void browser_free(pa_browser *b) {
404 pa_assert(b);
405 pa_assert(b->mainloop);
406
407 if (b->sink_browser)
408 avahi_service_browser_free(b->sink_browser);
409 if (b->source_browser)
410 avahi_service_browser_free(b->source_browser);
411 if (b->server_browser)
412 avahi_service_browser_free(b->server_browser);
413
414 if (b->client)
415 avahi_client_free(b->client);
416
417 if (b->avahi_poll)
418 pa_avahi_poll_free(b->avahi_poll);
419
420 pa_xfree(b);
421 }
422
423 pa_browser *pa_browser_ref(pa_browser *b) {
424 pa_assert(b);
425 pa_assert(PA_REFCNT_VALUE(b) >= 1);
426
427 PA_REFCNT_INC(b);
428 return b;
429 }
430
431 void pa_browser_unref(pa_browser *b) {
432 pa_assert(b);
433 pa_assert(PA_REFCNT_VALUE(b) >= 1);
434
435 if (PA_REFCNT_DEC(b) <= 0)
436 browser_free(b);
437 }
438
439 void pa_browser_set_callback(pa_browser *b, pa_browse_cb_t cb, void *userdata) {
440 pa_assert(b);
441 pa_assert(PA_REFCNT_VALUE(b) >= 1);
442
443 b->callback = cb;
444 b->userdata = userdata;
445 }
446
447 void pa_browser_set_error_callback(pa_browser *b, pa_browser_error_cb_t cb, void *userdata) {
448 pa_assert(b);
449 pa_assert(PA_REFCNT_VALUE(b) >= 1);
450
451 b->error_callback = cb;
452 b->error_userdata = userdata;
453 }