]> code.delx.au - pulseaudio/blob - src/pulse/browser.c
ea2706e4243711c01d96b2cba3ead968dd828386
[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 <assert.h>
29 #include <string.h>
30
31 #include <avahi-client/lookup.h>
32 #include <avahi-common/domain.h>
33 #include <avahi-common/error.h>
34
35 #include <pulse/xmalloc.h>
36
37 #include <pulsecore/log.h>
38 #include <pulsecore/core-util.h>
39
40 #include <pulsecore/avahi-wrap.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 int ref;
50 pa_mainloop_api *mainloop;
51 AvahiPoll* avahi_poll;
52
53 pa_browse_cb_t callback;
54 void *userdata;
55
56 pa_browser_error_cb_t error_callback;
57 void *error_userdata;
58
59 AvahiClient *client;
60 AvahiServiceBrowser *server_browser, *sink_browser, *source_browser;
61
62 };
63
64 static int map_to_opcode(const char *type, int new) {
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 assert(b);
101
102 memset(&i, 0, sizeof(i));
103 i.name = name;
104
105 if (event != AVAHI_RESOLVER_FOUND)
106 goto fail;
107
108 if (!b->callback)
109 goto fail;
110
111 opcode = map_to_opcode(type, 1);
112 assert(opcode >= 0);
113
114 if (aa->proto == AVAHI_PROTO_INET)
115 snprintf(a, sizeof(a), "tcp:%s:%u", avahi_address_snprint(ip, sizeof(ip), aa), port);
116 else {
117 assert(aa->proto == AVAHI_PROTO_INET6);
118 snprintf(a, sizeof(a), "tcp6:%s:%u", avahi_address_snprint(ip, sizeof(ip), aa), port);
119 }
120 i.server = a;
121
122
123 while (txt) {
124
125 if (avahi_string_list_get_pair(txt, &key, &value, NULL) < 0)
126 break;
127
128 if (!strcmp(key, "device")) {
129 device_found = 1;
130 pa_xfree((char*) i.device);
131 i.device = value;
132 value = NULL;
133 } else if (!strcmp(key, "server-version")) {
134 pa_xfree((char*) i.server_version);
135 i.server_version = value;
136 value = NULL;
137 } else if (!strcmp(key, "user-name")) {
138 pa_xfree((char*) i.user_name);
139 i.user_name = value;
140 value = NULL;
141 } else if (!strcmp(key, "fqdn")) {
142 size_t l;
143
144 pa_xfree((char*) i.fqdn);
145 i.fqdn = value;
146 value = NULL;
147
148 l = strlen(a);
149 assert(l+1 <= sizeof(a));
150 strncat(a, " ", sizeof(a)-l-1);
151 strncat(a, i.fqdn, sizeof(a)-l-2);
152 } else if (!strcmp(key, "cookie")) {
153
154 if (pa_atou(value, &cookie) < 0)
155 goto fail;
156
157 i.cookie = &cookie;
158 } else if (!strcmp(key, "description")) {
159 pa_xfree((char*) i.description);
160 i.description = value;
161 value = NULL;
162 } else if (!strcmp(key, "channels")) {
163 uint32_t ch;
164
165 if (pa_atou(value, &ch) < 0 || ch <= 0 || ch > 255)
166 goto fail;
167
168 ss.channels = (uint8_t) ch;
169 ss_valid |= 1;
170
171 } else if (!strcmp(key, "rate")) {
172 if (pa_atou(value, &ss.rate) < 0)
173 goto fail;
174 ss_valid |= 2;
175 } else if (!strcmp(key, "format")) {
176
177 if ((ss.format = pa_parse_sample_format(value)) == PA_SAMPLE_INVALID)
178 goto fail;
179
180 ss_valid |= 4;
181 }
182
183 pa_xfree(key);
184 pa_xfree(value);
185 key = value = NULL;
186
187 txt = avahi_string_list_get_next(txt);
188 }
189
190 /* No device txt record was sent for a sink or source service */
191 if (opcode != PA_BROWSE_NEW_SERVER && !device_found)
192 goto fail;
193
194 if (ss_valid == 7)
195 i.sample_spec = &ss;
196
197 b->callback(b, opcode, &i, b->userdata);
198
199 fail:
200 pa_xfree((void*) i.device);
201 pa_xfree((void*) i.fqdn);
202 pa_xfree((void*) i.server_version);
203 pa_xfree((void*) i.user_name);
204 pa_xfree((void*) i.description);
205
206 pa_xfree(key);
207 pa_xfree(value);
208
209 avahi_service_resolver_free(r);
210 }
211
212 static void handle_failure(pa_browser *b) {
213 const char *e = NULL;
214 assert(b);
215
216 if (b->sink_browser)
217 avahi_service_browser_free(b->sink_browser);
218 if (b->source_browser)
219 avahi_service_browser_free(b->source_browser);
220 if (b->server_browser)
221 avahi_service_browser_free(b->server_browser);
222
223 b->sink_browser = b->source_browser = b->server_browser = NULL;
224
225 if (b->client) {
226 e = avahi_strerror(avahi_client_errno(b->client));
227 avahi_client_free(b->client);
228 }
229
230 b->client = NULL;
231
232 if (b->error_callback)
233 b->error_callback(b, e, b->error_userdata);
234 }
235
236 static void browse_callback(
237 AvahiServiceBrowser *sb,
238 AvahiIfIndex interface,
239 AvahiProtocol protocol,
240 AvahiBrowserEvent event,
241 const char *name,
242 const char *type,
243 const char *domain,
244 AvahiLookupResultFlags flags,
245 void *userdata) {
246
247 pa_browser *b = userdata;
248 assert(b);
249
250 switch (event) {
251 case AVAHI_BROWSER_NEW: {
252
253 if (!avahi_service_resolver_new(
254 b->client,
255 interface,
256 protocol,
257 name,
258 type,
259 domain,
260 AVAHI_PROTO_UNSPEC,
261 0,
262 resolve_callback,
263 b))
264 handle_failure(b);
265
266 break;
267 }
268
269 case AVAHI_BROWSER_REMOVE: {
270
271 if (b->callback) {
272 pa_browse_info i;
273 int opcode;
274
275 memset(&i, 0, sizeof(i));
276 i.name = name;
277
278 opcode = map_to_opcode(type, 0);
279 assert(opcode >= 0);
280
281 b->callback(b, opcode, &i, b->userdata);
282 }
283 break;
284 }
285
286 case AVAHI_BROWSER_FAILURE: {
287 handle_failure(b);
288 break;
289 }
290
291 default:
292 ;
293 }
294 }
295
296 static void client_callback(AvahiClient *s, AvahiClientState state, void *userdata) {
297 pa_browser *b = userdata;
298 assert(s);
299
300 if (state == AVAHI_CLIENT_FAILURE)
301 handle_failure(b);
302 }
303
304 static void browser_free(pa_browser *b);
305
306 pa_browser *pa_browser_new(pa_mainloop_api *mainloop) {
307 return pa_browser_new_full(mainloop, PA_BROWSE_FOR_SERVERS|PA_BROWSE_FOR_SINKS|PA_BROWSE_FOR_SOURCES, NULL);
308 }
309
310 pa_browser *pa_browser_new_full(pa_mainloop_api *mainloop, pa_browse_flags_t flags, const char **error_string) {
311 pa_browser *b;
312 int error;
313
314 assert(mainloop);
315
316 if (flags & ~(PA_BROWSE_FOR_SERVERS|PA_BROWSE_FOR_SINKS|PA_BROWSE_FOR_SOURCES) || flags == 0)
317 return NULL;
318
319 b = pa_xnew(pa_browser, 1);
320 b->mainloop = mainloop;
321 b->ref = 1;
322 b->callback = NULL;
323 b->userdata = NULL;
324 b->error_callback = NULL;
325 b->error_userdata = NULL;
326 b->sink_browser = b->source_browser = b->server_browser = NULL;
327
328 b->avahi_poll = pa_avahi_poll_new(mainloop);
329
330 if (!(b->client = avahi_client_new(b->avahi_poll, 0, client_callback, b, &error))) {
331 if (error_string)
332 *error_string = avahi_strerror(error);
333 goto fail;
334 }
335
336 if ((flags & PA_BROWSE_FOR_SERVERS) &&
337 !(b->server_browser = avahi_service_browser_new(
338 b->client,
339 AVAHI_IF_UNSPEC,
340 AVAHI_PROTO_INET,
341 SERVICE_TYPE_SERVER,
342 NULL,
343 0,
344 browse_callback,
345 b))) {
346
347 if (error_string)
348 *error_string = avahi_strerror(avahi_client_errno(b->client));
349 goto fail;
350 }
351
352 if ((flags & PA_BROWSE_FOR_SINKS) &&
353 !(b->sink_browser = avahi_service_browser_new(
354 b->client,
355 AVAHI_IF_UNSPEC,
356 AVAHI_PROTO_UNSPEC,
357 SERVICE_TYPE_SINK,
358 NULL,
359 0,
360 browse_callback,
361 b))) {
362
363 if (error_string)
364 *error_string = avahi_strerror(avahi_client_errno(b->client));
365 goto fail;
366 }
367
368 if ((flags & PA_BROWSE_FOR_SOURCES) &&
369 !(b->source_browser = avahi_service_browser_new(
370 b->client,
371 AVAHI_IF_UNSPEC,
372 AVAHI_PROTO_UNSPEC,
373 SERVICE_TYPE_SOURCE,
374 NULL,
375 0,
376 browse_callback,
377 b))) {
378
379 if (error_string)
380 *error_string = avahi_strerror(avahi_client_errno(b->client));
381 goto fail;
382 }
383
384 return b;
385
386 fail:
387 if (b)
388 browser_free(b);
389
390 return NULL;
391 }
392
393 static void browser_free(pa_browser *b) {
394 assert(b && b->mainloop);
395
396 if (b->sink_browser)
397 avahi_service_browser_free(b->sink_browser);
398 if (b->source_browser)
399 avahi_service_browser_free(b->source_browser);
400 if (b->server_browser)
401 avahi_service_browser_free(b->server_browser);
402
403 if (b->client)
404 avahi_client_free(b->client);
405
406 if (b->avahi_poll)
407 pa_avahi_poll_free(b->avahi_poll);
408
409 pa_xfree(b);
410 }
411
412 pa_browser *pa_browser_ref(pa_browser *b) {
413 assert(b);
414 assert(b->ref >= 1);
415 b->ref++;
416 return b;
417 }
418
419 void pa_browser_unref(pa_browser *b) {
420 assert(b);
421 assert(b->ref >= 1);
422
423 if ((-- (b->ref)) <= 0)
424 browser_free(b);
425 }
426
427 void pa_browser_set_callback(pa_browser *b, pa_browse_cb_t cb, void *userdata) {
428 assert(b);
429
430 b->callback = cb;
431 b->userdata = userdata;
432 }
433
434 void pa_browser_set_error_callback(pa_browser *b, pa_browser_error_cb_t cb, void *userdata) {
435 assert(b);
436
437 b->error_callback = cb;
438 b->error_userdata = userdata;
439 }