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