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>
40 #include <pulse/util.h>
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>
53 #include "module-raop-discover-symdef.h"
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);
60 #define SERVICE_TYPE_SINK "_raop._tcp"
62 static const char* const valid_modargs[] = {
67 AvahiIfIndex interface;
68 AvahiProtocol protocol;
69 char *name, *type, *domain;
70 uint32_t module_index;
76 AvahiPoll *avahi_poll;
78 AvahiServiceBrowser *sink_browser;
83 static unsigned tunnel_hash(const void *p) {
84 const struct tunnel *t = p;
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);
94 static int tunnel_compare(const void *a, const void *b) {
95 const struct tunnel *ta = a, *tb = b;
98 if (ta->interface != tb->interface)
100 if (ta->protocol != tb->protocol)
102 if ((r = strcmp(ta->name, tb->name)))
104 if ((r = strcmp(ta->type, tb->type)))
106 if ((r = strcmp(ta->domain, tb->domain)))
112 static struct tunnel *tunnel_new(
113 AvahiIfIndex interface, AvahiProtocol protocol,
114 const char *name, const char *type, const char *domain) {
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;
127 static void tunnel_free(struct tunnel *t) {
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,
145 struct userdata *u = userdata;
150 tnl = tunnel_new(interface, protocol, name, type, domain);
152 if (event != AVAHI_RESOLVER_FOUND)
153 pa_log("Resolving of '%s' failed: %s", name, avahi_strerror(avahi_client_errno(u->client)));
155 char *device = NULL, *nicename, *dname, *vname, *args;
156 char at[AVAHI_ADDRESS_STR_MAX];
160 if ((nicename = strstr(name, "@"))) {
162 if (strlen(nicename) > 0) {
163 pa_log_debug("Found RAOP: %s", nicename);
167 for (l = txt; l; l = l->next) {
169 pa_assert_se(avahi_string_list_get_pair(l, &key, &value, NULL) == 0);
171 pa_log_debug("Found key: '%s' with value: '%s'", key, value);
172 if (strcmp(key, "device") == 0) {
182 dname = pa_sprintf_malloc("raop.%s.%s", host_name, device);
184 dname = pa_sprintf_malloc("raop.%s", host_name);
186 if (!(vname = pa_namereg_make_valid_name(dname))) {
187 pa_log("Cannot construct valid device name from '%s'.", dname);
195 TODO: allow this syntax of server name in things....
196 args = pa_sprintf_malloc("server=[%s]:%u "
198 avahi_address_snprint(at, sizeof(at), a), port,
201 args = pa_sprintf_malloc("server=%s "
203 "description=\"%s\"",
204 avahi_address_snprint(at, sizeof(at), a),
209 args = pa_sprintf_malloc("server=%s "
211 avahi_address_snprint(at, sizeof(at), a),
215 pa_log_debug("Loading module-raop-sink with arguments '%s'", args);
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);
230 avahi_service_resolver_free(r);
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,
244 struct userdata *u = userdata;
249 if (flags & AVAHI_LOOKUP_RESULT_LOCAL)
252 t = tunnel_new(interface, protocol, name, type, domain);
254 if (event == AVAHI_BROWSER_NEW) {
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)));
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 destroy
262 * it from the callback */
264 } else if (event == AVAHI_BROWSER_REMOVE) {
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);
277 static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
278 struct userdata *u = userdata;
286 case AVAHI_CLIENT_S_REGISTERING:
287 case AVAHI_CLIENT_S_RUNNING:
288 case AVAHI_CLIENT_S_COLLISION:
290 if (!u->sink_browser) {
292 if (!(u->sink_browser = avahi_service_browser_new(
294 AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
300 pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c)));
301 pa_module_unload_request(u->module, TRUE);
307 case AVAHI_CLIENT_FAILURE:
308 if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) {
311 pa_log_debug("Avahi daemon disconnected.");
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);
321 case AVAHI_CLIENT_CONNECTING:
323 if (u->sink_browser) {
324 avahi_service_browser_free(u->sink_browser);
325 u->sink_browser = NULL;
334 int pa__init(pa_module*m) {
337 pa_modargs *ma = NULL;
340 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
341 pa_log("Failed to parse module arguments.");
345 m->userdata = u = pa_xnew(struct userdata, 1);
348 u->sink_browser = NULL;
350 u->tunnels = pa_hashmap_new(tunnel_hash, tunnel_compare);
352 u->avahi_poll = pa_avahi_poll_new(m->core->mainloop);
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));
372 void pa__done(pa_module*m) {
376 if (!(u = m->userdata))
380 avahi_client_free(u->client);
383 pa_avahi_poll_free(u->avahi_poll);
388 while ((t = pa_hashmap_steal_first(u->tunnels))) {
389 pa_module_unload_by_index(u->core, t->module_index, TRUE);
393 pa_hashmap_free(u->tunnels, NULL, NULL);