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 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
, *dname
, *vname
, *args
;
156 char at
[AVAHI_ADDRESS_STR_MAX
];
160 for (l
= txt
; l
; l
= l
->next
) {
162 pa_assert_se(avahi_string_list_get_pair(l
, &key
, &value
, NULL
) == 0);
164 pa_log_debug("Found key: '%s' with value: '%s'", key
, value
);
165 if (strcmp(key
, "device") == 0) {
175 dname
= pa_sprintf_malloc("raop.%s.%s", host_name
, device
);
177 dname
= pa_sprintf_malloc("raop.%s", host_name
);
179 if (!(vname
= pa_namereg_make_valid_name(dname
))) {
180 pa_log("Cannot construct valid device name from '%s'.", dname
);
188 TODO: allow this syntax of server name in things....
189 args = pa_sprintf_malloc("server=[%s]:%u "
191 avahi_address_snprint(at, sizeof(at), a), port,
193 args
= pa_sprintf_malloc("server=%s "
195 avahi_address_snprint(at
, sizeof(at
), a
),
198 pa_log_debug("Loading module-raop-sink with arguments '%s'", args
);
200 if ((m
= pa_module_load(u
->core
, "module-raop-sink", args
))) {
201 tnl
->module_index
= m
->index
;
202 pa_hashmap_put(u
->tunnels
, tnl
, tnl
);
213 avahi_service_resolver_free(r
);
219 static void browser_cb(
220 AvahiServiceBrowser
*b
,
221 AvahiIfIndex interface
, AvahiProtocol protocol
,
222 AvahiBrowserEvent event
,
223 const char *name
, const char *type
, const char *domain
,
224 AvahiLookupResultFlags flags
,
227 struct userdata
*u
= userdata
;
232 if (flags
& AVAHI_LOOKUP_RESULT_LOCAL
)
235 t
= tunnel_new(interface
, protocol
, name
, type
, domain
);
237 if (event
== AVAHI_BROWSER_NEW
) {
239 if (!pa_hashmap_get(u
->tunnels
, t
))
240 if (!(avahi_service_resolver_new(u
->client
, interface
, protocol
, name
, type
, domain
, AVAHI_PROTO_UNSPEC
, 0, resolver_cb
, u
)))
241 pa_log("avahi_service_resolver_new() failed: %s", avahi_strerror(avahi_client_errno(u
->client
)));
243 /* We ignore the returned resolver object here, since the we don't
244 * need to attach any special data to it, and we can still destory
245 * it from the callback */
247 } else if (event
== AVAHI_BROWSER_REMOVE
) {
250 if ((t2
= pa_hashmap_get(u
->tunnels
, t
))) {
251 pa_module_unload_by_index(u
->core
, t2
->module_index
, TRUE
);
252 pa_hashmap_remove(u
->tunnels
, t2
);
260 static void client_callback(AvahiClient
*c
, AvahiClientState state
, void *userdata
) {
261 struct userdata
*u
= userdata
;
269 case AVAHI_CLIENT_S_REGISTERING
:
270 case AVAHI_CLIENT_S_RUNNING
:
271 case AVAHI_CLIENT_S_COLLISION
:
273 if (!u
->sink_browser
) {
275 if (!(u
->sink_browser
= avahi_service_browser_new(
277 AVAHI_IF_UNSPEC
, AVAHI_PROTO_UNSPEC
,
283 pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c
)));
284 pa_module_unload_request(u
->module
, TRUE
);
290 case AVAHI_CLIENT_FAILURE
:
291 if (avahi_client_errno(c
) == AVAHI_ERR_DISCONNECTED
) {
294 pa_log_debug("Avahi daemon disconnected.");
296 if (!(u
->client
= avahi_client_new(u
->avahi_poll
, AVAHI_CLIENT_NO_FAIL
, client_callback
, u
, &error
))) {
297 pa_log("avahi_client_new() failed: %s", avahi_strerror(error
));
298 pa_module_unload_request(u
->module
, TRUE
);
304 case AVAHI_CLIENT_CONNECTING
:
306 if (u
->sink_browser
) {
307 avahi_service_browser_free(u
->sink_browser
);
308 u
->sink_browser
= NULL
;
317 int pa__init(pa_module
*m
) {
320 pa_modargs
*ma
= NULL
;
323 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
324 pa_log("Failed to parse module arguments.");
328 m
->userdata
= u
= pa_xnew(struct userdata
, 1);
331 u
->sink_browser
= NULL
;
333 u
->tunnels
= pa_hashmap_new(tunnel_hash
, tunnel_compare
);
335 u
->avahi_poll
= pa_avahi_poll_new(m
->core
->mainloop
);
337 if (!(u
->client
= avahi_client_new(u
->avahi_poll
, AVAHI_CLIENT_NO_FAIL
, client_callback
, u
, &error
))) {
338 pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error
));
355 void pa__done(pa_module
*m
) {
359 if (!(u
= m
->userdata
))
363 avahi_client_free(u
->client
);
366 pa_avahi_poll_free(u
->avahi_poll
);
371 while ((t
= pa_hashmap_steal_first(u
->tunnels
))) {
372 pa_module_unload_by_index(u
->core
, t
->module_index
, TRUE
);
376 pa_hashmap_free(u
->tunnels
, NULL
, NULL
);