]> code.delx.au - pulseaudio/blob - src/modules/raop/module-raop-discover.c
Remove pa_bool_t and replace it with bool.
[pulseaudio] / src / modules / raop / 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
41 #include <pulsecore/core-util.h>
42 #include <pulsecore/log.h>
43 #include <pulsecore/hashmap.h>
44 #include <pulsecore/modargs.h>
45 #include <pulsecore/namereg.h>
46 #include <pulsecore/avahi-wrap.h>
47
48 #include "module-raop-discover-symdef.h"
49
50 PA_MODULE_AUTHOR("Colin Guthrie");
51 PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery of RAOP devices");
52 PA_MODULE_VERSION(PACKAGE_VERSION);
53 PA_MODULE_LOAD_ONCE(true);
54
55 #define SERVICE_TYPE_SINK "_raop._tcp"
56
57 static const char* const valid_modargs[] = {
58 NULL
59 };
60
61 struct tunnel {
62 AvahiIfIndex interface;
63 AvahiProtocol protocol;
64 char *name, *type, *domain;
65 uint32_t module_index;
66 };
67
68 struct userdata {
69 pa_core *core;
70 pa_module *module;
71 AvahiPoll *avahi_poll;
72 AvahiClient *client;
73 AvahiServiceBrowser *sink_browser;
74
75 pa_hashmap *tunnels;
76 };
77
78 static unsigned tunnel_hash(const void *p) {
79 const struct tunnel *t = p;
80
81 return
82 (unsigned) t->interface +
83 (unsigned) t->protocol +
84 pa_idxset_string_hash_func(t->name) +
85 pa_idxset_string_hash_func(t->type) +
86 pa_idxset_string_hash_func(t->domain);
87 }
88
89 static int tunnel_compare(const void *a, const void *b) {
90 const struct tunnel *ta = a, *tb = b;
91 int r;
92
93 if (ta->interface != tb->interface)
94 return 1;
95 if (ta->protocol != tb->protocol)
96 return 1;
97 if ((r = strcmp(ta->name, tb->name)))
98 return r;
99 if ((r = strcmp(ta->type, tb->type)))
100 return r;
101 if ((r = strcmp(ta->domain, tb->domain)))
102 return r;
103
104 return 0;
105 }
106
107 static struct tunnel *tunnel_new(
108 AvahiIfIndex interface, AvahiProtocol protocol,
109 const char *name, const char *type, const char *domain) {
110
111 struct tunnel *t;
112 t = pa_xnew(struct tunnel, 1);
113 t->interface = interface;
114 t->protocol = protocol;
115 t->name = pa_xstrdup(name);
116 t->type = pa_xstrdup(type);
117 t->domain = pa_xstrdup(domain);
118 t->module_index = PA_IDXSET_INVALID;
119 return t;
120 }
121
122 static void tunnel_free(struct tunnel *t) {
123 pa_assert(t);
124 pa_xfree(t->name);
125 pa_xfree(t->type);
126 pa_xfree(t->domain);
127 pa_xfree(t);
128 }
129
130 static void resolver_cb(
131 AvahiServiceResolver *r,
132 AvahiIfIndex interface, AvahiProtocol protocol,
133 AvahiResolverEvent event,
134 const char *name, const char *type, const char *domain,
135 const char *host_name, const AvahiAddress *a, uint16_t port,
136 AvahiStringList *txt,
137 AvahiLookupResultFlags flags,
138 void *userdata) {
139
140 struct userdata *u = userdata;
141 struct tunnel *tnl;
142
143 pa_assert(u);
144
145 tnl = tunnel_new(interface, protocol, name, type, domain);
146
147 if (event != AVAHI_RESOLVER_FOUND)
148 pa_log("Resolving of '%s' failed: %s", name, avahi_strerror(avahi_client_errno(u->client)));
149 else {
150 char *device = NULL, *nicename, *dname, *vname, *args;
151 char at[AVAHI_ADDRESS_STR_MAX];
152 AvahiStringList *l;
153 pa_module *m;
154
155 if ((nicename = strstr(name, "@"))) {
156 ++nicename;
157 if (strlen(nicename) > 0) {
158 pa_log_debug("Found RAOP: %s", nicename);
159 nicename = pa_escape(nicename, "\"'");
160 } else
161 nicename = NULL;
162 }
163
164 for (l = txt; l; l = l->next) {
165 char *key, *value;
166 pa_assert_se(avahi_string_list_get_pair(l, &key, &value, NULL) == 0);
167
168 pa_log_debug("Found key: '%s' with value: '%s'", key, value);
169 if (pa_streq(key, "device")) {
170 pa_xfree(device);
171 device = value;
172 value = NULL;
173 }
174 avahi_free(key);
175 avahi_free(value);
176 }
177
178 if (device)
179 dname = pa_sprintf_malloc("raop.%s.%s", host_name, device);
180 else
181 dname = pa_sprintf_malloc("raop.%s", host_name);
182
183 if (!(vname = pa_namereg_make_valid_name(dname))) {
184 pa_log("Cannot construct valid device name from '%s'.", dname);
185 avahi_free(device);
186 pa_xfree(dname);
187 goto finish;
188 }
189 pa_xfree(dname);
190
191 if (nicename) {
192 args = pa_sprintf_malloc("server=[%s]:%u "
193 "sink_name=%s "
194 "sink_properties='device.description=\"%s\"'",
195 avahi_address_snprint(at, sizeof(at), a), port,
196 vname,
197 nicename);
198 pa_xfree(nicename);
199 } else {
200 args = pa_sprintf_malloc("server=[%s]:%u "
201 "sink_name=%s",
202 avahi_address_snprint(at, sizeof(at), a), port,
203 vname);
204 }
205
206 pa_log_debug("Loading module-raop-sink with arguments '%s'", args);
207
208 if ((m = pa_module_load(u->core, "module-raop-sink", args))) {
209 tnl->module_index = m->index;
210 pa_hashmap_put(u->tunnels, tnl, tnl);
211 tnl = NULL;
212 }
213
214 pa_xfree(vname);
215 pa_xfree(args);
216 avahi_free(device);
217 }
218
219 finish:
220
221 avahi_service_resolver_free(r);
222
223 if (tnl)
224 tunnel_free(tnl);
225 }
226
227 static void browser_cb(
228 AvahiServiceBrowser *b,
229 AvahiIfIndex interface, AvahiProtocol protocol,
230 AvahiBrowserEvent event,
231 const char *name, const char *type, const char *domain,
232 AvahiLookupResultFlags flags,
233 void *userdata) {
234
235 struct userdata *u = userdata;
236 struct tunnel *t;
237
238 pa_assert(u);
239
240 if (flags & AVAHI_LOOKUP_RESULT_LOCAL)
241 return;
242
243 t = tunnel_new(interface, protocol, name, type, domain);
244
245 if (event == AVAHI_BROWSER_NEW) {
246
247 if (!pa_hashmap_get(u->tunnels, t))
248 if (!(avahi_service_resolver_new(u->client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolver_cb, u)))
249 pa_log("avahi_service_resolver_new() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
250
251 /* We ignore the returned resolver object here, since the we don't
252 * need to attach any special data to it, and we can still destroy
253 * it from the callback */
254
255 } else if (event == AVAHI_BROWSER_REMOVE) {
256 struct tunnel *t2;
257
258 if ((t2 = pa_hashmap_get(u->tunnels, t))) {
259 pa_module_unload_request_by_index(u->core, t2->module_index, true);
260 pa_hashmap_remove(u->tunnels, t2);
261 tunnel_free(t2);
262 }
263 }
264
265 tunnel_free(t);
266 }
267
268 static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
269 struct userdata *u = userdata;
270
271 pa_assert(c);
272 pa_assert(u);
273
274 u->client = c;
275
276 switch (state) {
277 case AVAHI_CLIENT_S_REGISTERING:
278 case AVAHI_CLIENT_S_RUNNING:
279 case AVAHI_CLIENT_S_COLLISION:
280
281 if (!u->sink_browser) {
282
283 if (!(u->sink_browser = avahi_service_browser_new(
284 c,
285 AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
286 SERVICE_TYPE_SINK,
287 NULL,
288 0,
289 browser_cb, u))) {
290
291 pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c)));
292 pa_module_unload_request(u->module, true);
293 }
294 }
295
296 break;
297
298 case AVAHI_CLIENT_FAILURE:
299 if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) {
300 int error;
301
302 pa_log_debug("Avahi daemon disconnected.");
303
304 if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
305 pa_log("avahi_client_new() failed: %s", avahi_strerror(error));
306 pa_module_unload_request(u->module, true);
307 }
308 }
309
310 /* Fall through */
311
312 case AVAHI_CLIENT_CONNECTING:
313
314 if (u->sink_browser) {
315 avahi_service_browser_free(u->sink_browser);
316 u->sink_browser = NULL;
317 }
318
319 break;
320
321 default: ;
322 }
323 }
324
325 int pa__init(pa_module*m) {
326
327 struct userdata *u;
328 pa_modargs *ma = NULL;
329 int error;
330
331 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
332 pa_log("Failed to parse module arguments.");
333 goto fail;
334 }
335
336 m->userdata = u = pa_xnew(struct userdata, 1);
337 u->core = m->core;
338 u->module = m;
339 u->sink_browser = NULL;
340
341 u->tunnels = pa_hashmap_new(tunnel_hash, tunnel_compare);
342
343 u->avahi_poll = pa_avahi_poll_new(m->core->mainloop);
344
345 if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
346 pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error));
347 goto fail;
348 }
349
350 pa_modargs_free(ma);
351
352 return 0;
353
354 fail:
355 pa__done(m);
356
357 if (ma)
358 pa_modargs_free(ma);
359
360 return -1;
361 }
362
363 void pa__done(pa_module*m) {
364 struct userdata*u;
365 pa_assert(m);
366
367 if (!(u = m->userdata))
368 return;
369
370 if (u->client)
371 avahi_client_free(u->client);
372
373 if (u->avahi_poll)
374 pa_avahi_poll_free(u->avahi_poll);
375
376 if (u->tunnels) {
377 struct tunnel *t;
378
379 while ((t = pa_hashmap_steal_first(u->tunnels))) {
380 pa_module_unload_request_by_index(u->core, t->module_index, true);
381 tunnel_free(t);
382 }
383
384 pa_hashmap_free(u->tunnels, NULL);
385 }
386
387 pa_xfree(u);
388 }