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