4 This file is part of PulseAudio.
6 Copyright 2004-2006 Lennart Poettering
8 PulseAudio is free software; you can redistribute it and/or modify
9 it under the terms of the GNU Lesser General Public License as
10 published by the Free Software Foundation; either version 2 of the
11 License, or (at your option) any later version.
13 PulseAudio is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public
19 License along with PulseAudio; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
30 #include <avahi-client/lookup.h>
31 #include <avahi-common/domain.h>
32 #include <avahi-common/error.h>
34 #include <pulse/xmalloc.h>
36 #include <pulsecore/log.h>
37 #include <pulsecore/core-util.h>
38 #include <pulsecore/avahi-wrap.h>
39 #include <pulsecore/refcnt.h>
40 #include <pulsecore/macro.h>
44 #define SERVICE_TYPE_SINK "_pulse-sink._tcp."
45 #define SERVICE_TYPE_SOURCE "_pulse-source._tcp."
46 #define SERVICE_TYPE_SERVER "_pulse-server._tcp."
51 pa_mainloop_api
*mainloop
;
52 AvahiPoll
* avahi_poll
;
54 pa_browse_cb_t callback
;
57 pa_browser_error_cb_t error_callback
;
61 AvahiServiceBrowser
*server_browser
, *sink_browser
, *source_browser
;
65 static int map_to_opcode(const char *type
, int new) {
67 if (avahi_domain_equal(type
, SERVICE_TYPE_SINK
))
68 return new ? PA_BROWSE_NEW_SINK
: PA_BROWSE_REMOVE_SINK
;
69 else if (avahi_domain_equal(type
, SERVICE_TYPE_SOURCE
))
70 return new ? PA_BROWSE_NEW_SOURCE
: PA_BROWSE_REMOVE_SOURCE
;
71 else if (avahi_domain_equal(type
, SERVICE_TYPE_SERVER
))
72 return new ? PA_BROWSE_NEW_SERVER
: PA_BROWSE_REMOVE_SERVER
;
77 static void resolve_callback(
78 AvahiServiceResolver
*r
,
79 AvahiIfIndex interface
,
80 AvahiProtocol protocol
,
81 AvahiResolverEvent event
,
85 const char *host_name
,
86 const AvahiAddress
*aa
,
89 AvahiLookupResultFlags flags
,
92 pa_browser
*b
= userdata
;
100 char *key
= NULL
, *value
= NULL
;
103 pa_assert(PA_REFCNT_VALUE(b
) >= 1);
105 memset(&i
, 0, sizeof(i
));
108 if (event
!= AVAHI_RESOLVER_FOUND
)
114 opcode
= map_to_opcode(type
, 1);
115 pa_assert(opcode
>= 0);
117 if (aa
->proto
== AVAHI_PROTO_INET
)
118 pa_snprintf(a
, sizeof(a
), "tcp:%s:%u", avahi_address_snprint(ip
, sizeof(ip
), aa
), port
);
120 pa_assert(aa
->proto
== AVAHI_PROTO_INET6
);
121 pa_snprintf(a
, sizeof(a
), "tcp6:%s:%u", avahi_address_snprint(ip
, sizeof(ip
), aa
), port
);
128 if (avahi_string_list_get_pair(txt
, &key
, &value
, NULL
) < 0)
131 if (!strcmp(key
, "device")) {
133 pa_xfree((char*) i
.device
);
136 } else if (!strcmp(key
, "server-version")) {
137 pa_xfree((char*) i
.server_version
);
138 i
.server_version
= value
;
140 } else if (!strcmp(key
, "user-name")) {
141 pa_xfree((char*) i
.user_name
);
144 } else if (!strcmp(key
, "fqdn")) {
147 pa_xfree((char*) i
.fqdn
);
152 pa_assert(l
+1 <= sizeof(a
));
153 strncat(a
, " ", sizeof(a
)-l
-1);
154 strncat(a
, i
.fqdn
, sizeof(a
)-l
-2);
155 } else if (!strcmp(key
, "cookie")) {
157 if (pa_atou(value
, &cookie
) < 0)
161 } else if (!strcmp(key
, "description")) {
162 pa_xfree((char*) i
.description
);
163 i
.description
= value
;
165 } else if (!strcmp(key
, "channels")) {
168 if (pa_atou(value
, &ch
) < 0 || ch
<= 0 || ch
> 255)
171 ss
.channels
= (uint8_t) ch
;
174 } else if (!strcmp(key
, "rate")) {
175 if (pa_atou(value
, &ss
.rate
) < 0)
178 } else if (!strcmp(key
, "format")) {
180 if ((ss
.format
= pa_parse_sample_format(value
)) == PA_SAMPLE_INVALID
)
190 txt
= avahi_string_list_get_next(txt
);
193 /* No device txt record was sent for a sink or source service */
194 if (opcode
!= PA_BROWSE_NEW_SERVER
&& !device_found
)
200 b
->callback(b
, opcode
, &i
, b
->userdata
);
203 pa_xfree((void*) i
.device
);
204 pa_xfree((void*) i
.fqdn
);
205 pa_xfree((void*) i
.server_version
);
206 pa_xfree((void*) i
.user_name
);
207 pa_xfree((void*) i
.description
);
212 avahi_service_resolver_free(r
);
215 static void handle_failure(pa_browser
*b
) {
216 const char *e
= NULL
;
219 pa_assert(PA_REFCNT_VALUE(b
) >= 1);
222 avahi_service_browser_free(b
->sink_browser
);
223 if (b
->source_browser
)
224 avahi_service_browser_free(b
->source_browser
);
225 if (b
->server_browser
)
226 avahi_service_browser_free(b
->server_browser
);
228 b
->sink_browser
= b
->source_browser
= b
->server_browser
= NULL
;
231 e
= avahi_strerror(avahi_client_errno(b
->client
));
232 avahi_client_free(b
->client
);
237 if (b
->error_callback
)
238 b
->error_callback(b
, e
, b
->error_userdata
);
241 static void browse_callback(
242 AvahiServiceBrowser
*sb
,
243 AvahiIfIndex interface
,
244 AvahiProtocol protocol
,
245 AvahiBrowserEvent event
,
249 AvahiLookupResultFlags flags
,
252 pa_browser
*b
= userdata
;
255 pa_assert(PA_REFCNT_VALUE(b
) >= 1);
258 case AVAHI_BROWSER_NEW
: {
260 if (!avahi_service_resolver_new(
276 case AVAHI_BROWSER_REMOVE
: {
282 memset(&i
, 0, sizeof(i
));
285 opcode
= map_to_opcode(type
, 0);
286 pa_assert(opcode
>= 0);
288 b
->callback(b
, opcode
, &i
, b
->userdata
);
293 case AVAHI_BROWSER_FAILURE
: {
303 static void client_callback(AvahiClient
*s
, AvahiClientState state
, void *userdata
) {
304 pa_browser
*b
= userdata
;
308 pa_assert(PA_REFCNT_VALUE(b
) >= 1);
310 if (state
== AVAHI_CLIENT_FAILURE
)
314 static void browser_free(pa_browser
*b
);
316 pa_browser
*pa_browser_new(pa_mainloop_api
*mainloop
) {
317 return pa_browser_new_full(mainloop
, PA_BROWSE_FOR_SERVERS
|PA_BROWSE_FOR_SINKS
|PA_BROWSE_FOR_SOURCES
, NULL
);
320 pa_browser
*pa_browser_new_full(pa_mainloop_api
*mainloop
, pa_browse_flags_t flags
, const char **error_string
) {
326 if (flags
& ~(PA_BROWSE_FOR_SERVERS
|PA_BROWSE_FOR_SINKS
|PA_BROWSE_FOR_SOURCES
) || flags
== 0)
329 b
= pa_xnew(pa_browser
, 1);
330 b
->mainloop
= mainloop
;
334 b
->error_callback
= NULL
;
335 b
->error_userdata
= NULL
;
336 b
->sink_browser
= b
->source_browser
= b
->server_browser
= NULL
;
338 b
->avahi_poll
= pa_avahi_poll_new(mainloop
);
340 if (!(b
->client
= avahi_client_new(b
->avahi_poll
, 0, client_callback
, b
, &error
))) {
342 *error_string
= avahi_strerror(error
);
346 if ((flags
& PA_BROWSE_FOR_SERVERS
) &&
347 !(b
->server_browser
= avahi_service_browser_new(
358 *error_string
= avahi_strerror(avahi_client_errno(b
->client
));
362 if ((flags
& PA_BROWSE_FOR_SINKS
) &&
363 !(b
->sink_browser
= avahi_service_browser_new(
374 *error_string
= avahi_strerror(avahi_client_errno(b
->client
));
378 if ((flags
& PA_BROWSE_FOR_SOURCES
) &&
379 !(b
->source_browser
= avahi_service_browser_new(
390 *error_string
= avahi_strerror(avahi_client_errno(b
->client
));
403 static void browser_free(pa_browser
*b
) {
405 pa_assert(b
->mainloop
);
408 avahi_service_browser_free(b
->sink_browser
);
409 if (b
->source_browser
)
410 avahi_service_browser_free(b
->source_browser
);
411 if (b
->server_browser
)
412 avahi_service_browser_free(b
->server_browser
);
415 avahi_client_free(b
->client
);
418 pa_avahi_poll_free(b
->avahi_poll
);
423 pa_browser
*pa_browser_ref(pa_browser
*b
) {
425 pa_assert(PA_REFCNT_VALUE(b
) >= 1);
431 void pa_browser_unref(pa_browser
*b
) {
433 pa_assert(PA_REFCNT_VALUE(b
) >= 1);
435 if (PA_REFCNT_DEC(b
) <= 0)
439 void pa_browser_set_callback(pa_browser
*b
, pa_browse_cb_t cb
, void *userdata
) {
441 pa_assert(PA_REFCNT_VALUE(b
) >= 1);
444 b
->userdata
= userdata
;
447 void pa_browser_set_error_callback(pa_browser
*b
, pa_browser_error_cb_t cb
, void *userdata
) {
449 pa_assert(PA_REFCNT_VALUE(b
) >= 1);
451 b
->error_callback
= cb
;
452 b
->error_userdata
= userdata
;