2 This file is part of PulseAudio.
4 Copyright 2004-2006 Lennart Poettering
5 Copyright 2008 Colin Guthrie
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.
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.
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
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>
39 #include <pulse/xmalloc.h>
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>
48 #include "module-raop-discover-symdef.h"
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);
55 #define SERVICE_TYPE_SINK "_raop._tcp"
57 static const char* const valid_modargs[] = {
62 AvahiIfIndex interface;
63 AvahiProtocol protocol;
64 char *name, *type, *domain;
65 uint32_t module_index;
71 AvahiPoll *avahi_poll;
73 AvahiServiceBrowser *sink_browser;
78 static unsigned tunnel_hash(const void *p) {
79 const struct tunnel *t = p;
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);
89 static int tunnel_compare(const void *a, const void *b) {
90 const struct tunnel *ta = a, *tb = b;
93 if (ta->interface != tb->interface)
95 if (ta->protocol != tb->protocol)
97 if ((r = strcmp(ta->name, tb->name)))
99 if ((r = strcmp(ta->type, tb->type)))
101 if ((r = strcmp(ta->domain, tb->domain)))
107 static struct tunnel *tunnel_new(
108 AvahiIfIndex interface, AvahiProtocol protocol,
109 const char *name, const char *type, const char *domain) {
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;
122 static void tunnel_free(struct tunnel *t) {
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,
140 struct userdata *u = userdata;
145 tnl = tunnel_new(interface, protocol, name, type, domain);
147 if (event != AVAHI_RESOLVER_FOUND)
148 pa_log("Resolving of '%s' failed: %s", name, avahi_strerror(avahi_client_errno(u->client)));
150 char *device = NULL, *nicename, *dname, *vname, *args;
151 char at[AVAHI_ADDRESS_STR_MAX];
155 if ((nicename = strstr(name, "@"))) {
157 if (strlen(nicename) > 0) {
158 pa_log_debug("Found RAOP: %s", nicename);
159 nicename = pa_escape(nicename, "\"'");
164 for (l = txt; l; l = l->next) {
166 pa_assert_se(avahi_string_list_get_pair(l, &key, &value, NULL) == 0);
168 pa_log_debug("Found key: '%s' with value: '%s'", key, value);
169 if (pa_streq(key, "device")) {
179 dname = pa_sprintf_malloc("raop.%s.%s", host_name, device);
181 dname = pa_sprintf_malloc("raop.%s", host_name);
183 if (!(vname = pa_namereg_make_valid_name(dname))) {
184 pa_log("Cannot construct valid device name from '%s'.", dname);
192 args = pa_sprintf_malloc("server=[%s]:%u "
194 "sink_properties='device.description=\"%s\"'",
195 avahi_address_snprint(at, sizeof(at), a), port,
200 args = pa_sprintf_malloc("server=[%s]:%u "
202 avahi_address_snprint(at, sizeof(at), a), port,
206 pa_log_debug("Loading module-raop-sink with arguments '%s'", args);
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);
221 avahi_service_resolver_free(r);
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,
235 struct userdata *u = userdata;
240 if (flags & AVAHI_LOOKUP_RESULT_LOCAL)
243 t = tunnel_new(interface, protocol, name, type, domain);
245 if (event == AVAHI_BROWSER_NEW) {
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)));
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 */
255 } else if (event == AVAHI_BROWSER_REMOVE) {
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);
268 static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
269 struct userdata *u = userdata;
277 case AVAHI_CLIENT_S_REGISTERING:
278 case AVAHI_CLIENT_S_RUNNING:
279 case AVAHI_CLIENT_S_COLLISION:
281 if (!u->sink_browser) {
283 if (!(u->sink_browser = avahi_service_browser_new(
285 AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
291 pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c)));
292 pa_module_unload_request(u->module, true);
298 case AVAHI_CLIENT_FAILURE:
299 if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) {
302 pa_log_debug("Avahi daemon disconnected.");
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);
312 case AVAHI_CLIENT_CONNECTING:
314 if (u->sink_browser) {
315 avahi_service_browser_free(u->sink_browser);
316 u->sink_browser = NULL;
325 int pa__init(pa_module*m) {
328 pa_modargs *ma = NULL;
331 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
332 pa_log("Failed to parse module arguments.");
336 m->userdata = u = pa_xnew(struct userdata, 1);
339 u->sink_browser = NULL;
341 u->tunnels = pa_hashmap_new(tunnel_hash, tunnel_compare);
343 u->avahi_poll = pa_avahi_poll_new(m->core->mainloop);
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));
363 void pa__done(pa_module*m) {
367 if (!(u = m->userdata))
371 avahi_client_free(u->client);
374 pa_avahi_poll_free(u->avahi_poll);
379 while ((t = pa_hashmap_steal_first(u->tunnels))) {
380 pa_module_unload_request_by_index(u->core, t->module_index, true);
384 pa_hashmap_free(u->tunnels, NULL);