4 This file is part of PulseAudio.
6 PulseAudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as
8 published by the Free Software Foundation; either version 2 of the
9 License, or (at your option) any later version.
11 PulseAudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with PulseAudio; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
29 #include <avahi-client/lookup.h>
30 #include <avahi-common/domain.h>
31 #include <avahi-common/error.h>
33 #include <pulse/xmalloc.h>
35 #include <pulsecore/log.h>
36 #include <pulsecore/core-util.h>
38 #include <pulsecore/avahi-wrap.h>
42 #define SERVICE_TYPE_SINK "_pulse-sink._tcp."
43 #define SERVICE_TYPE_SOURCE "_pulse-source._tcp."
44 #define SERVICE_TYPE_SERVER "_pulse-server._tcp."
48 pa_mainloop_api *mainloop;
49 AvahiPoll* avahi_poll;
51 pa_browse_cb_t callback;
54 pa_browser_error_cb_t error_callback;
58 AvahiServiceBrowser *server_browser, *sink_browser, *source_browser;
62 static int map_to_opcode(const char *type, int new) {
63 if (avahi_domain_equal(type, SERVICE_TYPE_SINK))
64 return new ? PA_BROWSE_NEW_SINK : PA_BROWSE_REMOVE_SINK;
65 else if (avahi_domain_equal(type, SERVICE_TYPE_SOURCE))
66 return new ? PA_BROWSE_NEW_SOURCE : PA_BROWSE_REMOVE_SOURCE;
67 else if (avahi_domain_equal(type, SERVICE_TYPE_SERVER))
68 return new ? PA_BROWSE_NEW_SERVER : PA_BROWSE_REMOVE_SERVER;
73 static void resolve_callback(
74 AvahiServiceResolver *r,
75 AvahiIfIndex interface,
76 AvahiProtocol protocol,
77 AvahiResolverEvent event,
81 const char *host_name,
82 const AvahiAddress *aa,
85 AvahiLookupResultFlags flags,
88 pa_browser *b = userdata;
96 char *key = NULL, *value = NULL;
100 memset(&i, 0, sizeof(i));
103 if (event != AVAHI_RESOLVER_FOUND)
109 opcode = map_to_opcode(type, 1);
112 if (aa->proto == AVAHI_PROTO_INET)
113 snprintf(a, sizeof(a), "tcp:%s:%u", avahi_address_snprint(ip, sizeof(ip), aa), port);
115 assert(aa->proto == AVAHI_PROTO_INET6);
116 snprintf(a, sizeof(a), "tcp6:%s:%u", avahi_address_snprint(ip, sizeof(ip), aa), port);
123 if (avahi_string_list_get_pair(txt, &key, &value, NULL) < 0)
126 if (!strcmp(key, "device")) {
128 pa_xfree((char*) i.device);
131 } else if (!strcmp(key, "server-version")) {
132 pa_xfree((char*) i.server_version);
133 i.server_version = value;
135 } else if (!strcmp(key, "user-name")) {
136 pa_xfree((char*) i.user_name);
139 } else if (!strcmp(key, "fqdn")) {
142 pa_xfree((char*) i.fqdn);
147 assert(l+1 <= sizeof(a));
148 strncat(a, " ", sizeof(a)-l-1);
149 strncat(a, i.fqdn, sizeof(a)-l-2);
150 } else if (!strcmp(key, "cookie")) {
152 if (pa_atou(value, &cookie) < 0)
156 } else if (!strcmp(key, "description")) {
157 pa_xfree((char*) i.description);
158 i.description = value;
160 } else if (!strcmp(key, "channels")) {
163 if (pa_atou(value, &ch) < 0 || ch <= 0 || ch > 255)
166 ss.channels = (uint8_t) ch;
169 } else if (!strcmp(key, "rate")) {
170 if (pa_atou(value, &ss.rate) < 0)
173 } else if (!strcmp(key, "format")) {
175 if ((ss.format = pa_parse_sample_format(value)) == PA_SAMPLE_INVALID)
185 txt = avahi_string_list_get_next(txt);
188 /* No device txt record was sent for a sink or source service */
189 if (opcode != PA_BROWSE_NEW_SERVER && !device_found)
195 b->callback(b, opcode, &i, b->userdata);
198 pa_xfree((void*) i.device);
199 pa_xfree((void*) i.fqdn);
200 pa_xfree((void*) i.server_version);
201 pa_xfree((void*) i.user_name);
202 pa_xfree((void*) i.description);
207 avahi_service_resolver_free(r);
210 static void handle_failure(pa_browser *b) {
211 const char *e = NULL;
215 avahi_service_browser_free(b->sink_browser);
216 if (b->source_browser)
217 avahi_service_browser_free(b->source_browser);
218 if (b->server_browser)
219 avahi_service_browser_free(b->server_browser);
221 b->sink_browser = b->source_browser = b->server_browser = NULL;
224 e = avahi_strerror(avahi_client_errno(b->client));
225 avahi_client_free(b->client);
230 if (b->error_callback)
231 b->error_callback(b, e, b->error_userdata);
234 static void browse_callback(
235 AvahiServiceBrowser *sb,
236 AvahiIfIndex interface,
237 AvahiProtocol protocol,
238 AvahiBrowserEvent event,
242 AvahiLookupResultFlags flags,
245 pa_browser *b = userdata;
249 case AVAHI_BROWSER_NEW: {
251 if (!avahi_service_resolver_new(
267 case AVAHI_BROWSER_REMOVE: {
273 memset(&i, 0, sizeof(i));
276 opcode = map_to_opcode(type, 0);
279 b->callback(b, opcode, &i, b->userdata);
284 case AVAHI_BROWSER_FAILURE: {
294 static void client_callback(AvahiClient *s, AvahiClientState state, void *userdata) {
295 pa_browser *b = userdata;
298 if (state == AVAHI_CLIENT_FAILURE)
302 static void browser_free(pa_browser *b);
304 pa_browser *pa_browser_new(pa_mainloop_api *mainloop) {
305 return pa_browser_new_full(mainloop, PA_BROWSE_FOR_SERVERS|PA_BROWSE_FOR_SINKS|PA_BROWSE_FOR_SOURCES, NULL);
308 pa_browser *pa_browser_new_full(pa_mainloop_api *mainloop, pa_browse_flags_t flags, const char **error_string) {
314 if (flags & ~(PA_BROWSE_FOR_SERVERS|PA_BROWSE_FOR_SINKS|PA_BROWSE_FOR_SOURCES) || flags == 0)
317 b = pa_xnew(pa_browser, 1);
318 b->mainloop = mainloop;
322 b->error_callback = NULL;
323 b->error_userdata = NULL;
324 b->sink_browser = b->source_browser = b->server_browser = NULL;
326 b->avahi_poll = pa_avahi_poll_new(mainloop);
328 if (!(b->client = avahi_client_new(b->avahi_poll, 0, client_callback, b, &error))) {
330 *error_string = avahi_strerror(error);
334 if ((flags & PA_BROWSE_FOR_SERVERS) &&
335 !(b->server_browser = avahi_service_browser_new(
346 *error_string = avahi_strerror(avahi_client_errno(b->client));
350 if ((flags & PA_BROWSE_FOR_SINKS) &&
351 !(b->sink_browser = avahi_service_browser_new(
362 *error_string = avahi_strerror(avahi_client_errno(b->client));
366 if ((flags & PA_BROWSE_FOR_SOURCES) &&
367 !(b->source_browser = avahi_service_browser_new(
378 *error_string = avahi_strerror(avahi_client_errno(b->client));
391 static void browser_free(pa_browser *b) {
392 assert(b && b->mainloop);
395 avahi_service_browser_free(b->sink_browser);
396 if (b->source_browser)
397 avahi_service_browser_free(b->source_browser);
398 if (b->server_browser)
399 avahi_service_browser_free(b->server_browser);
402 avahi_client_free(b->client);
405 pa_avahi_poll_free(b->avahi_poll);
410 pa_browser *pa_browser_ref(pa_browser *b) {
417 void pa_browser_unref(pa_browser *b) {
421 if ((-- (b->ref)) <= 0)
425 void pa_browser_set_callback(pa_browser *b, pa_browse_cb_t cb, void *userdata) {
429 b->userdata = userdata;
432 void pa_browser_set_error_callback(pa_browser *b, pa_browser_error_cb_t cb, void *userdata) {
435 b->error_callback = cb;
436 b->error_userdata = userdata;