]> code.delx.au - pulseaudio/blob - src/modules/module-raop-discover.c
Merge commit 'origin/master-tx'
[pulseaudio] / src / modules / module-raop-discover.c
1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2004-2006 Lennart Poettering
5 Copyright 2008 Colin Guthrie
6
7 PulseAudio is free software; you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as
9 published by the Free Software Foundation; either version 2.1 of the
10 License, or (at your option) any later version.
11
12 PulseAudio is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with PulseAudio; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20 USA.
21 ***/
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31
32 #include <avahi-client/client.h>
33 #include <avahi-client/lookup.h>
34 #include <avahi-common/alternative.h>
35 #include <avahi-common/error.h>
36 #include <avahi-common/domain.h>
37 #include <avahi-common/malloc.h>
38
39 #include <pulse/xmalloc.h>
40 #include <pulse/util.h>
41
42 #include <pulsecore/sink.h>
43 #include <pulsecore/source.h>
44 #include <pulsecore/native-common.h>
45 #include <pulsecore/core-util.h>
46 #include <pulsecore/log.h>
47 #include <pulsecore/core-subscribe.h>
48 #include <pulsecore/hashmap.h>
49 #include <pulsecore/modargs.h>
50 #include <pulsecore/namereg.h>
51 #include <pulsecore/avahi-wrap.h>
52
53 #include "module-raop-discover-symdef.h"
54
55 PA_MODULE_AUTHOR("Colin Guthrie");
56 PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery of RAOP devices");
57 PA_MODULE_VERSION(PACKAGE_VERSION);
58 PA_MODULE_LOAD_ONCE(TRUE);
59
60 #define SERVICE_TYPE_SINK "_raop._tcp"
61
62 static const char* const valid_modargs[] = {
63 NULL
64 };
65
66 struct tunnel {
67 AvahiIfIndex interface;
68 AvahiProtocol protocol;
69 char *name, *type, *domain;
70 uint32_t module_index;
71 };
72
73 struct userdata {
74 pa_core *core;
75 pa_module *module;
76 AvahiPoll *avahi_poll;
77 AvahiClient *client;
78 AvahiServiceBrowser *sink_browser;
79
80 pa_hashmap *tunnels;
81 };
82
83 static unsigned tunnel_hash(const void *p) {
84 const struct tunnel *t = p;
85
86 return
87 (unsigned) t->interface +
88 (unsigned) t->protocol +
89 pa_idxset_string_hash_func(t->name) +
90 pa_idxset_string_hash_func(t->type) +
91 pa_idxset_string_hash_func(t->domain);
92 }
93
94 static int tunnel_compare(const void *a, const void *b) {
95 const struct tunnel *ta = a, *tb = b;
96 int r;
97
98 if (ta->interface != tb->interface)
99 return 1;
100 if (ta->protocol != tb->protocol)
101 return 1;
102 if ((r = strcmp(ta->name, tb->name)))
103 return r;
104 if ((r = strcmp(ta->type, tb->type)))
105 return r;
106 if ((r = strcmp(ta->domain, tb->domain)))
107 return r;
108
109 return 0;
110 }
111
112 static struct tunnel *tunnel_new(
113 AvahiIfIndex interface, AvahiProtocol protocol,
114 const char *name, const char *type, const char *domain) {
115
116 struct tunnel *t;
117 t = pa_xnew(struct tunnel, 1);
118 t->interface = interface;
119 t->protocol = protocol;
120 t->name = pa_xstrdup(name);
121 t->type = pa_xstrdup(type);
122 t->domain = pa_xstrdup(domain);
123 t->module_index = PA_IDXSET_INVALID;
124 return t;
125 }
126
127 static void tunnel_free(struct tunnel *t) {
128 pa_assert(t);
129 pa_xfree(t->name);
130 pa_xfree(t->type);
131 pa_xfree(t->domain);
132 pa_xfree(t);
133 }
134
135 static void resolver_cb(
136 AvahiServiceResolver *r,
137 AvahiIfIndex interface, AvahiProtocol protocol,
138 AvahiResolverEvent event,
139 const char *name, const char *type, const char *domain,
140 const char *host_name, const AvahiAddress *a, uint16_t port,
141 AvahiStringList *txt,
142 AvahiLookupResultFlags flags,
143 void *userdata) {
144
145 struct userdata *u = userdata;
146 struct tunnel *tnl;
147
148 pa_assert(u);
149
150 tnl = tunnel_new(interface, protocol, name, type, domain);
151
152 if (event != AVAHI_RESOLVER_FOUND)
153 pa_log("Resolving of '%s' failed: %s", name, avahi_strerror(avahi_client_errno(u->client)));
154 else {
155 char *device = NULL, *nicename, *dname, *vname, *args;
156 char at[AVAHI_ADDRESS_STR_MAX];
157 AvahiStringList *l;
158 pa_module *m;
159
160 if ((nicename = strstr(name, "@"))) {
161 ++nicename;
162 if (strlen(nicename) > 0) {
163 pa_log_debug("Found RAOP: %s", nicename);
164 }
165 }
166
167 for (l = txt; l; l = l->next) {
168 char *key, *value;
169 pa_assert_se(avahi_string_list_get_pair(l, &key, &value, NULL) == 0);
170
171 pa_log_debug("Found key: '%s' with value: '%s'", key, value);
172 if (strcmp(key, "device") == 0) {
173 pa_xfree(device);
174 device = value;
175 value = NULL;
176 }
177 avahi_free(key);
178 avahi_free(value);
179 }
180
181 if (device)
182 dname = pa_sprintf_malloc("raop.%s.%s", host_name, device);
183 else
184 dname = pa_sprintf_malloc("raop.%s", host_name);
185
186 if (!(vname = pa_namereg_make_valid_name(dname))) {
187 pa_log("Cannot construct valid device name from '%s'.", dname);
188 avahi_free(device);
189 pa_xfree(dname);
190 goto finish;
191 }
192 pa_xfree(dname);
193
194 /*
195 TODO: allow this syntax of server name in things....
196 args = pa_sprintf_malloc("server=[%s]:%u "
197 "sink_name=%s",
198 avahi_address_snprint(at, sizeof(at), a), port,
199 vname);*/
200 if (nicename) {
201 args = pa_sprintf_malloc("server=%s "
202 "sink_name=%s "
203 "description=\"%s\"",
204 avahi_address_snprint(at, sizeof(at), a),
205 vname,
206 nicename);
207
208 } else {
209 args = pa_sprintf_malloc("server=%s "
210 "sink_name=%s",
211 avahi_address_snprint(at, sizeof(at), a),
212 vname);
213 }
214
215 pa_log_debug("Loading module-raop-sink with arguments '%s'", args);
216
217 if ((m = pa_module_load(u->core, "module-raop-sink", args))) {
218 tnl->module_index = m->index;
219 pa_hashmap_put(u->tunnels, tnl, tnl);
220 tnl = NULL;
221 }
222
223 pa_xfree(vname);
224 pa_xfree(args);
225 avahi_free(device);
226 }
227
228 finish:
229
230 avahi_service_resolver_free(r);
231
232 if (tnl)
233 tunnel_free(tnl);
234 }
235
236 static void browser_cb(
237 AvahiServiceBrowser *b,
238 AvahiIfIndex interface, AvahiProtocol protocol,
239 AvahiBrowserEvent event,
240 const char *name, const char *type, const char *domain,
241 AvahiLookupResultFlags flags,
242 void *userdata) {
243
244 struct userdata *u = userdata;
245 struct tunnel *t;
246
247 pa_assert(u);
248
249 if (flags & AVAHI_LOOKUP_RESULT_LOCAL)
250 return;
251
252 t = tunnel_new(interface, protocol, name, type, domain);
253
254 if (event == AVAHI_BROWSER_NEW) {
255
256 if (!pa_hashmap_get(u->tunnels, t))
257 if (!(avahi_service_resolver_new(u->client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolver_cb, u)))
258 pa_log("avahi_service_resolver_new() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
259
260 /* We ignore the returned resolver object here, since the we don't
261 * need to attach any special data to it, and we can still destory
262 * it from the callback */
263
264 } else if (event == AVAHI_BROWSER_REMOVE) {
265 struct tunnel *t2;
266
267 if ((t2 = pa_hashmap_get(u->tunnels, t))) {
268 pa_module_unload_by_index(u->core, t2->module_index, TRUE);
269 pa_hashmap_remove(u->tunnels, t2);
270 tunnel_free(t2);
271 }
272 }
273
274 tunnel_free(t);
275 }
276
277 static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
278 struct userdata *u = userdata;
279
280 pa_assert(c);
281 pa_assert(u);
282
283 u->client = c;
284
285 switch (state) {
286 case AVAHI_CLIENT_S_REGISTERING:
287 case AVAHI_CLIENT_S_RUNNING:
288 case AVAHI_CLIENT_S_COLLISION:
289
290 if (!u->sink_browser) {
291
292 if (!(u->sink_browser = avahi_service_browser_new(
293 c,
294 AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
295 SERVICE_TYPE_SINK,
296 NULL,
297 0,
298 browser_cb, u))) {
299
300 pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c)));
301 pa_module_unload_request(u->module, TRUE);
302 }
303 }
304
305 break;
306
307 case AVAHI_CLIENT_FAILURE:
308 if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) {
309 int error;
310
311 pa_log_debug("Avahi daemon disconnected.");
312
313 if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
314 pa_log("avahi_client_new() failed: %s", avahi_strerror(error));
315 pa_module_unload_request(u->module, TRUE);
316 }
317 }
318
319 /* Fall through */
320
321 case AVAHI_CLIENT_CONNECTING:
322
323 if (u->sink_browser) {
324 avahi_service_browser_free(u->sink_browser);
325 u->sink_browser = NULL;
326 }
327
328 break;
329
330 default: ;
331 }
332 }
333
334 int pa__init(pa_module*m) {
335
336 struct userdata *u;
337 pa_modargs *ma = NULL;
338 int error;
339
340 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
341 pa_log("Failed to parse module arguments.");
342 goto fail;
343 }
344
345 m->userdata = u = pa_xnew(struct userdata, 1);
346 u->core = m->core;
347 u->module = m;
348 u->sink_browser = NULL;
349
350 u->tunnels = pa_hashmap_new(tunnel_hash, tunnel_compare);
351
352 u->avahi_poll = pa_avahi_poll_new(m->core->mainloop);
353
354 if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
355 pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error));
356 goto fail;
357 }
358
359 pa_modargs_free(ma);
360
361 return 0;
362
363 fail:
364 pa__done(m);
365
366 if (ma)
367 pa_modargs_free(ma);
368
369 return -1;
370 }
371
372 void pa__done(pa_module*m) {
373 struct userdata*u;
374 pa_assert(m);
375
376 if (!(u = m->userdata))
377 return;
378
379 if (u->client)
380 avahi_client_free(u->client);
381
382 if (u->avahi_poll)
383 pa_avahi_poll_free(u->avahi_poll);
384
385 if (u->tunnels) {
386 struct tunnel *t;
387
388 while ((t = pa_hashmap_steal_first(u->tunnels))) {
389 pa_module_unload_by_index(u->core, t->module_index, TRUE);
390 tunnel_free(t);
391 }
392
393 pa_hashmap_free(u->tunnels, NULL, NULL);
394 }
395
396 pa_xfree(u);
397 }