merge glitch-free branch back into trunk
[profile/ivi/pulseaudio.git] / src / pulse / browser.c
1 /* $Id$ */
2
3 /***
4   This file is part of PulseAudio.
5
6   Copyright 2004-2006 Lennart Poettering
7
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.
12
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.
17
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
21   USA.
22 ***/
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27
28 #include <string.h>
29
30 #include <avahi-client/lookup.h>
31 #include <avahi-common/domain.h>
32 #include <avahi-common/error.h>
33
34 #include <pulse/xmalloc.h>
35
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>
41
42 #include "browser.h"
43
44 #define SERVICE_TYPE_SINK "_pulse-sink._tcp."
45 #define SERVICE_TYPE_SOURCE "_pulse-source._tcp."
46 #define SERVICE_TYPE_SERVER "_pulse-server._tcp."
47
48 struct pa_browser {
49     PA_REFCNT_DECLARE;
50
51     pa_mainloop_api *mainloop;
52     AvahiPoll* avahi_poll;
53
54     pa_browse_cb_t callback;
55     void *userdata;
56
57     pa_browser_error_cb_t error_callback;
58     void *error_userdata;
59
60     AvahiClient *client;
61     AvahiServiceBrowser *server_browser, *sink_browser, *source_browser;
62
63 };
64
65 static int map_to_opcode(const char *type, int new) {
66
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;
73
74     return -1;
75 }
76
77 static void resolve_callback(
78         AvahiServiceResolver *r,
79         AvahiIfIndex interface,
80         AvahiProtocol protocol,
81         AvahiResolverEvent event,
82         const char *name,
83         const char *type,
84         const char *domain,
85         const char *host_name,
86         const AvahiAddress *aa,
87         uint16_t port,
88         AvahiStringList *txt,
89         AvahiLookupResultFlags flags,
90         void *userdata) {
91
92     pa_browser *b = userdata;
93     pa_browse_info i;
94     char ip[256], a[256];
95     int opcode;
96     int device_found = 0;
97     uint32_t cookie;
98     pa_sample_spec ss;
99     int ss_valid = 0;
100     char *key = NULL, *value = NULL;
101
102     pa_assert(b);
103     pa_assert(PA_REFCNT_VALUE(b) >= 1);
104
105     memset(&i, 0, sizeof(i));
106     i.name = name;
107
108     if (event != AVAHI_RESOLVER_FOUND)
109         goto fail;
110
111     if (!b->callback)
112         goto fail;
113
114     opcode = map_to_opcode(type, 1);
115     pa_assert(opcode >= 0);
116
117     if (aa->proto == AVAHI_PROTO_INET)
118         pa_snprintf(a, sizeof(a), "tcp:%s:%u", avahi_address_snprint(ip, sizeof(ip), aa), port);
119     else {
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);
122     }
123     i.server = a;
124
125
126     while (txt) {
127
128         if (avahi_string_list_get_pair(txt, &key, &value, NULL) < 0)
129             break;
130
131         if (!strcmp(key, "device")) {
132             device_found = 1;
133             pa_xfree((char*) i.device);
134             i.device = value;
135             value = NULL;
136         } else if (!strcmp(key, "server-version")) {
137             pa_xfree((char*) i.server_version);
138             i.server_version = value;
139             value = NULL;
140         } else if (!strcmp(key, "user-name")) {
141             pa_xfree((char*) i.user_name);
142             i.user_name = value;
143             value = NULL;
144         } else if (!strcmp(key, "fqdn")) {
145             size_t l;
146
147             pa_xfree((char*) i.fqdn);
148             i.fqdn = value;
149             value = NULL;
150
151             l = strlen(a);
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")) {
156
157             if (pa_atou(value, &cookie) < 0)
158                 goto fail;
159
160             i.cookie = &cookie;
161         } else if (!strcmp(key, "description")) {
162             pa_xfree((char*) i.description);
163             i.description = value;
164             value = NULL;
165         } else if (!strcmp(key, "channels")) {
166             uint32_t ch;
167
168             if (pa_atou(value, &ch) < 0 || ch <= 0 || ch > 255)
169                 goto fail;
170
171             ss.channels = (uint8_t) ch;
172             ss_valid |= 1;
173
174         } else if (!strcmp(key, "rate")) {
175             if (pa_atou(value, &ss.rate) < 0)
176                 goto fail;
177             ss_valid |= 2;
178         } else if (!strcmp(key, "format")) {
179
180             if ((ss.format = pa_parse_sample_format(value)) == PA_SAMPLE_INVALID)
181                 goto fail;
182
183             ss_valid |= 4;
184         }
185
186         pa_xfree(key);
187         pa_xfree(value);
188         key = value = NULL;
189
190         txt = avahi_string_list_get_next(txt);
191     }
192
193     /* No device txt record was sent for a sink or source service */
194     if (opcode != PA_BROWSE_NEW_SERVER && !device_found)
195         goto fail;
196
197     if (ss_valid == 7)
198         i.sample_spec = &ss;
199
200     b->callback(b, opcode, &i, b->userdata);
201
202 fail:
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);
208
209     pa_xfree(key);
210     pa_xfree(value);
211
212     avahi_service_resolver_free(r);
213 }
214
215 static void handle_failure(pa_browser *b) {
216     const char *e = NULL;
217
218     pa_assert(b);
219     pa_assert(PA_REFCNT_VALUE(b) >= 1);
220
221     if (b->sink_browser)
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);
227
228     b->sink_browser = b->source_browser = b->server_browser = NULL;
229
230     if (b->client) {
231         e = avahi_strerror(avahi_client_errno(b->client));
232         avahi_client_free(b->client);
233     }
234
235     b->client = NULL;
236
237     if (b->error_callback)
238         b->error_callback(b, e, b->error_userdata);
239 }
240
241 static void browse_callback(
242         AvahiServiceBrowser *sb,
243         AvahiIfIndex interface,
244         AvahiProtocol protocol,
245         AvahiBrowserEvent event,
246         const char *name,
247         const char *type,
248         const char *domain,
249         AvahiLookupResultFlags flags,
250         void *userdata) {
251
252     pa_browser *b = userdata;
253
254     pa_assert(b);
255     pa_assert(PA_REFCNT_VALUE(b) >= 1);
256
257     switch (event) {
258         case AVAHI_BROWSER_NEW: {
259
260             if (!avahi_service_resolver_new(
261                           b->client,
262                           interface,
263                           protocol,
264                           name,
265                           type,
266                           domain,
267                           AVAHI_PROTO_UNSPEC,
268                           0,
269                           resolve_callback,
270                           b))
271                 handle_failure(b);
272
273             break;
274         }
275
276         case AVAHI_BROWSER_REMOVE: {
277
278             if (b->callback) {
279                 pa_browse_info i;
280                 int opcode;
281
282                 memset(&i, 0, sizeof(i));
283                 i.name = name;
284
285                 opcode = map_to_opcode(type, 0);
286                 pa_assert(opcode >= 0);
287
288                 b->callback(b, opcode, &i, b->userdata);
289             }
290             break;
291         }
292
293         case AVAHI_BROWSER_FAILURE: {
294             handle_failure(b);
295             break;
296         }
297
298         default:
299             ;
300     }
301 }
302
303 static void client_callback(AvahiClient *s, AvahiClientState state, void *userdata) {
304     pa_browser *b = userdata;
305
306     pa_assert(s);
307     pa_assert(b);
308     pa_assert(PA_REFCNT_VALUE(b) >= 1);
309
310     if (state == AVAHI_CLIENT_FAILURE)
311         handle_failure(b);
312 }
313
314 static void browser_free(pa_browser *b);
315
316
317 PA_WARN_REFERENCE(pa_browser_new, "libpulse-browse is being phased out.");
318
319 pa_browser *pa_browser_new(pa_mainloop_api *mainloop) {
320     return pa_browser_new_full(mainloop, PA_BROWSE_FOR_SERVERS|PA_BROWSE_FOR_SINKS|PA_BROWSE_FOR_SOURCES, NULL);
321 }
322
323 PA_WARN_REFERENCE(pa_browser_new_full, "libpulse-browse is being phased out.");
324
325 pa_browser *pa_browser_new_full(pa_mainloop_api *mainloop, pa_browse_flags_t flags, const char **error_string) {
326     pa_browser *b;
327     int error;
328
329     pa_assert(mainloop);
330
331     if (flags & ~(PA_BROWSE_FOR_SERVERS|PA_BROWSE_FOR_SINKS|PA_BROWSE_FOR_SOURCES) || flags == 0)
332         return NULL;
333
334     b = pa_xnew(pa_browser, 1);
335     b->mainloop = mainloop;
336     PA_REFCNT_INIT(b);
337     b->callback = NULL;
338     b->userdata = NULL;
339     b->error_callback = NULL;
340     b->error_userdata = NULL;
341     b->sink_browser = b->source_browser = b->server_browser = NULL;
342
343     b->avahi_poll = pa_avahi_poll_new(mainloop);
344
345     if (!(b->client = avahi_client_new(b->avahi_poll, 0, client_callback, b, &error))) {
346         if (error_string)
347             *error_string = avahi_strerror(error);
348         goto fail;
349     }
350
351     if ((flags & PA_BROWSE_FOR_SERVERS) &&
352         !(b->server_browser = avahi_service_browser_new(
353                   b->client,
354                   AVAHI_IF_UNSPEC,
355                   AVAHI_PROTO_INET,
356                   SERVICE_TYPE_SERVER,
357                   NULL,
358                   0,
359                   browse_callback,
360                   b))) {
361
362         if (error_string)
363             *error_string = avahi_strerror(avahi_client_errno(b->client));
364         goto fail;
365     }
366
367     if ((flags & PA_BROWSE_FOR_SINKS) &&
368         !(b->sink_browser = avahi_service_browser_new(
369                   b->client,
370                   AVAHI_IF_UNSPEC,
371                   AVAHI_PROTO_UNSPEC,
372                   SERVICE_TYPE_SINK,
373                   NULL,
374                   0,
375                   browse_callback,
376                   b))) {
377
378         if (error_string)
379             *error_string = avahi_strerror(avahi_client_errno(b->client));
380         goto fail;
381     }
382
383     if ((flags & PA_BROWSE_FOR_SOURCES) &&
384         !(b->source_browser = avahi_service_browser_new(
385                   b->client,
386                   AVAHI_IF_UNSPEC,
387                   AVAHI_PROTO_UNSPEC,
388                   SERVICE_TYPE_SOURCE,
389                   NULL,
390                   0,
391                   browse_callback,
392                   b))) {
393
394         if (error_string)
395             *error_string = avahi_strerror(avahi_client_errno(b->client));
396         goto fail;
397     }
398
399     return b;
400
401 fail:
402     if (b)
403         browser_free(b);
404
405     return NULL;
406 }
407
408 static void browser_free(pa_browser *b) {
409     pa_assert(b);
410     pa_assert(b->mainloop);
411
412     if (b->sink_browser)
413         avahi_service_browser_free(b->sink_browser);
414     if (b->source_browser)
415         avahi_service_browser_free(b->source_browser);
416     if (b->server_browser)
417         avahi_service_browser_free(b->server_browser);
418
419     if (b->client)
420         avahi_client_free(b->client);
421
422     if (b->avahi_poll)
423         pa_avahi_poll_free(b->avahi_poll);
424
425     pa_xfree(b);
426 }
427
428 PA_WARN_REFERENCE(pa_browser_ref, "libpulse-browse is being phased out.");
429
430 pa_browser *pa_browser_ref(pa_browser *b) {
431     pa_assert(b);
432     pa_assert(PA_REFCNT_VALUE(b) >= 1);
433
434     PA_REFCNT_INC(b);
435     return b;
436 }
437
438 PA_WARN_REFERENCE(pa_browser_unref, "libpulse-browse is being phased out.");
439
440 void pa_browser_unref(pa_browser *b) {
441     pa_assert(b);
442     pa_assert(PA_REFCNT_VALUE(b) >= 1);
443
444     if (PA_REFCNT_DEC(b) <= 0)
445         browser_free(b);
446 }
447
448 PA_WARN_REFERENCE(pa_browser_set_callback, "libpulse-browse is being phased out.");
449
450 void pa_browser_set_callback(pa_browser *b, pa_browse_cb_t cb, void *userdata) {
451     pa_assert(b);
452     pa_assert(PA_REFCNT_VALUE(b) >= 1);
453
454     b->callback = cb;
455     b->userdata = userdata;
456 }
457
458 PA_WARN_REFERENCE(pa_browser_set_error_callback, "libpulse-browse is being phased out.");
459
460 void pa_browser_set_error_callback(pa_browser *b, pa_browser_error_cb_t cb, void *userdata) {
461     pa_assert(b);
462     pa_assert(PA_REFCNT_VALUE(b) >= 1);
463
464     b->error_callback = cb;
465     b->error_userdata = userdata;
466 }