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
31 #include <avahi-client/lookup.h>
32 #include <avahi-common/domain.h>
33 #include <avahi-common/error.h>
35 #include <pulse/xmalloc.h>
37 #include <pulsecore/log.h>
38 #include <pulsecore/core-util.h>
40 #include <pulsecore/avahi-wrap.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."
50 pa_mainloop_api
*mainloop
;
51 AvahiPoll
* avahi_poll
;
53 pa_browse_cb_t callback
;
56 pa_browser_error_cb_t error_callback
;
60 AvahiServiceBrowser
*server_browser
, *sink_browser
, *source_browser
;
64 static int map_to_opcode(const char *type
, int new) {
65 if (avahi_domain_equal(type
, SERVICE_TYPE_SINK
))
66 return new ? PA_BROWSE_NEW_SINK
: PA_BROWSE_REMOVE_SINK
;
67 else if (avahi_domain_equal(type
, SERVICE_TYPE_SOURCE
))
68 return new ? PA_BROWSE_NEW_SOURCE
: PA_BROWSE_REMOVE_SOURCE
;
69 else if (avahi_domain_equal(type
, SERVICE_TYPE_SERVER
))
70 return new ? PA_BROWSE_NEW_SERVER
: PA_BROWSE_REMOVE_SERVER
;
75 static void resolve_callback(
76 AvahiServiceResolver
*r
,
77 AvahiIfIndex interface
,
78 AvahiProtocol protocol
,
79 AvahiResolverEvent event
,
83 const char *host_name
,
84 const AvahiAddress
*aa
,
87 AvahiLookupResultFlags flags
,
90 pa_browser
*b
= userdata
;
98 char *key
= NULL
, *value
= NULL
;
102 memset(&i
, 0, sizeof(i
));
105 if (event
!= AVAHI_RESOLVER_FOUND
)
111 opcode
= map_to_opcode(type
, 1);
114 if (aa
->proto
== AVAHI_PROTO_INET
)
115 snprintf(a
, sizeof(a
), "tcp:%s:%u", avahi_address_snprint(ip
, sizeof(ip
), aa
), port
);
117 assert(aa
->proto
== AVAHI_PROTO_INET6
);
118 snprintf(a
, sizeof(a
), "tcp6:%s:%u", avahi_address_snprint(ip
, sizeof(ip
), aa
), port
);
125 if (avahi_string_list_get_pair(txt
, &key
, &value
, NULL
) < 0)
128 if (!strcmp(key
, "device")) {
130 pa_xfree((char*) i
.device
);
133 } else if (!strcmp(key
, "server-version")) {
134 pa_xfree((char*) i
.server_version
);
135 i
.server_version
= value
;
137 } else if (!strcmp(key
, "user-name")) {
138 pa_xfree((char*) i
.user_name
);
141 } else if (!strcmp(key
, "fqdn")) {
144 pa_xfree((char*) i
.fqdn
);
149 assert(l
+1 <= sizeof(a
));
150 strncat(a
, " ", sizeof(a
)-l
-1);
151 strncat(a
, i
.fqdn
, sizeof(a
)-l
-2);
152 } else if (!strcmp(key
, "cookie")) {
154 if (pa_atou(value
, &cookie
) < 0)
158 } else if (!strcmp(key
, "description")) {
159 pa_xfree((char*) i
.description
);
160 i
.description
= value
;
162 } else if (!strcmp(key
, "channels")) {
165 if (pa_atou(value
, &ch
) < 0 || ch
<= 0 || ch
> 255)
168 ss
.channels
= (uint8_t) ch
;
171 } else if (!strcmp(key
, "rate")) {
172 if (pa_atou(value
, &ss
.rate
) < 0)
175 } else if (!strcmp(key
, "format")) {
177 if ((ss
.format
= pa_parse_sample_format(value
)) == PA_SAMPLE_INVALID
)
187 txt
= avahi_string_list_get_next(txt
);
190 /* No device txt record was sent for a sink or source service */
191 if (opcode
!= PA_BROWSE_NEW_SERVER
&& !device_found
)
197 b
->callback(b
, opcode
, &i
, b
->userdata
);
200 pa_xfree((void*) i
.device
);
201 pa_xfree((void*) i
.fqdn
);
202 pa_xfree((void*) i
.server_version
);
203 pa_xfree((void*) i
.user_name
);
204 pa_xfree((void*) i
.description
);
209 avahi_service_resolver_free(r
);
212 static void handle_failure(pa_browser
*b
) {
213 const char *e
= NULL
;
217 avahi_service_browser_free(b
->sink_browser
);
218 if (b
->source_browser
)
219 avahi_service_browser_free(b
->source_browser
);
220 if (b
->server_browser
)
221 avahi_service_browser_free(b
->server_browser
);
223 b
->sink_browser
= b
->source_browser
= b
->server_browser
= NULL
;
226 e
= avahi_strerror(avahi_client_errno(b
->client
));
227 avahi_client_free(b
->client
);
232 if (b
->error_callback
)
233 b
->error_callback(b
, e
, b
->error_userdata
);
236 static void browse_callback(
237 AvahiServiceBrowser
*sb
,
238 AvahiIfIndex interface
,
239 AvahiProtocol protocol
,
240 AvahiBrowserEvent event
,
244 AvahiLookupResultFlags flags
,
247 pa_browser
*b
= userdata
;
251 case AVAHI_BROWSER_NEW
: {
253 if (!avahi_service_resolver_new(
269 case AVAHI_BROWSER_REMOVE
: {
275 memset(&i
, 0, sizeof(i
));
278 opcode
= map_to_opcode(type
, 0);
281 b
->callback(b
, opcode
, &i
, b
->userdata
);
286 case AVAHI_BROWSER_FAILURE
: {
296 static void client_callback(AvahiClient
*s
, AvahiClientState state
, void *userdata
) {
297 pa_browser
*b
= userdata
;
300 if (state
== AVAHI_CLIENT_FAILURE
)
304 static void browser_free(pa_browser
*b
);
306 pa_browser
*pa_browser_new(pa_mainloop_api
*mainloop
) {
307 return pa_browser_new_full(mainloop
, PA_BROWSE_FOR_SERVERS
|PA_BROWSE_FOR_SINKS
|PA_BROWSE_FOR_SOURCES
, NULL
);
310 pa_browser
*pa_browser_new_full(pa_mainloop_api
*mainloop
, pa_browse_flags_t flags
, const char **error_string
) {
316 if (flags
& ~(PA_BROWSE_FOR_SERVERS
|PA_BROWSE_FOR_SINKS
|PA_BROWSE_FOR_SOURCES
) || flags
== 0)
319 b
= pa_xnew(pa_browser
, 1);
320 b
->mainloop
= mainloop
;
324 b
->error_callback
= NULL
;
325 b
->error_userdata
= NULL
;
326 b
->sink_browser
= b
->source_browser
= b
->server_browser
= NULL
;
328 b
->avahi_poll
= pa_avahi_poll_new(mainloop
);
330 if (!(b
->client
= avahi_client_new(b
->avahi_poll
, 0, client_callback
, b
, &error
))) {
332 *error_string
= avahi_strerror(error
);
336 if ((flags
& PA_BROWSE_FOR_SERVERS
) &&
337 !(b
->server_browser
= avahi_service_browser_new(
348 *error_string
= avahi_strerror(avahi_client_errno(b
->client
));
352 if ((flags
& PA_BROWSE_FOR_SINKS
) &&
353 !(b
->sink_browser
= avahi_service_browser_new(
364 *error_string
= avahi_strerror(avahi_client_errno(b
->client
));
368 if ((flags
& PA_BROWSE_FOR_SOURCES
) &&
369 !(b
->source_browser
= avahi_service_browser_new(
380 *error_string
= avahi_strerror(avahi_client_errno(b
->client
));
393 static void browser_free(pa_browser
*b
) {
394 assert(b
&& b
->mainloop
);
397 avahi_service_browser_free(b
->sink_browser
);
398 if (b
->source_browser
)
399 avahi_service_browser_free(b
->source_browser
);
400 if (b
->server_browser
)
401 avahi_service_browser_free(b
->server_browser
);
404 avahi_client_free(b
->client
);
407 pa_avahi_poll_free(b
->avahi_poll
);
412 pa_browser
*pa_browser_ref(pa_browser
*b
) {
419 void pa_browser_unref(pa_browser
*b
) {
423 if ((-- (b
->ref
)) <= 0)
427 void pa_browser_set_callback(pa_browser
*b
, pa_browse_cb_t cb
, void *userdata
) {
431 b
->userdata
= userdata
;
434 void pa_browser_set_error_callback(pa_browser
*b
, pa_browser_error_cb_t cb
, void *userdata
) {
437 b
->error_callback
= cb
;
438 b
->error_userdata
= userdata
;