]> code.delx.au - pulseaudio/blob - src/modules/macosx/module-bonjour-publish.c
hashmap: Use pa_free_cb_t instead of pa_free2_cb_t
[pulseaudio] / src / modules / macosx / module-bonjour-publish.c
1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2009 Daniel Mack
5 based on module-zeroconf-publish.c
6
7 PulseAudio is free software; you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as
9 published by the Free Software Foundation; either version 2.1 of the
10 License, or (at your option) any later version.
11
12 PulseAudio is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with PulseAudio; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20 USA.
21 ***/
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <dns_sd.h>
32
33 #include <CoreFoundation/CoreFoundation.h>
34
35 #include <pulse/xmalloc.h>
36 #include <pulse/util.h>
37
38 #include <pulsecore/parseaddr.h>
39 #include <pulsecore/sink.h>
40 #include <pulsecore/source.h>
41 #include <pulsecore/native-common.h>
42 #include <pulsecore/core-util.h>
43 #include <pulsecore/log.h>
44 #include <pulsecore/dynarray.h>
45 #include <pulsecore/modargs.h>
46 #include <pulsecore/protocol-native.h>
47
48 #include "module-bonjour-publish-symdef.h"
49
50 PA_MODULE_AUTHOR("Daniel Mack");
51 PA_MODULE_DESCRIPTION("Mac OS X Bonjour Service Publisher");
52 PA_MODULE_VERSION(PACKAGE_VERSION);
53 PA_MODULE_LOAD_ONCE(TRUE);
54
55 #define SERVICE_TYPE_SINK "_pulse-sink._tcp"
56 #define SERVICE_TYPE_SOURCE "_pulse-source._tcp"
57 #define SERVICE_TYPE_SERVER "_pulse-server._tcp"
58
59 static const char* const valid_modargs[] = {
60 NULL
61 };
62
63 enum service_subtype {
64 SUBTYPE_HARDWARE,
65 SUBTYPE_VIRTUAL,
66 SUBTYPE_MONITOR
67 };
68
69 struct service {
70 struct userdata *userdata;
71 DNSServiceRef service;
72 DNSRecordRef rec, rec2;
73 char *service_name;
74 pa_object *device;
75 enum service_subtype subtype;
76 };
77
78 struct userdata {
79 pa_core *core;
80 pa_module *module;
81
82 pa_hashmap *services;
83 char *service_name;
84
85 pa_hook_slot *sink_new_slot, *source_new_slot, *sink_unlink_slot, *source_unlink_slot, *sink_changed_slot, *source_changed_slot;
86
87 pa_native_protocol *native;
88 DNSServiceRef main_service;
89 };
90
91 static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_channel_map *ret_map, const char **ret_name, pa_proplist **ret_proplist, enum service_subtype *ret_subtype) {
92 pa_assert(s);
93 pa_assert(ret_ss);
94 pa_assert(ret_proplist);
95 pa_assert(ret_subtype);
96
97 if (pa_sink_isinstance(s->device)) {
98 pa_sink *sink = PA_SINK(s->device);
99
100 *ret_ss = sink->sample_spec;
101 *ret_map = sink->channel_map;
102 *ret_name = sink->name;
103 *ret_proplist = sink->proplist;
104 *ret_subtype = sink->flags & PA_SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL;
105
106 } else if (pa_source_isinstance(s->device)) {
107 pa_source *source = PA_SOURCE(s->device);
108
109 *ret_ss = source->sample_spec;
110 *ret_map = source->channel_map;
111 *ret_name = source->name;
112 *ret_proplist = source->proplist;
113 *ret_subtype = source->monitor_of ? SUBTYPE_MONITOR : (source->flags & PA_SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL);
114
115 } else
116 pa_assert_not_reached();
117 }
118
119 static void txt_record_server_data(pa_core *c, TXTRecordRef *txt) {
120 char s[128];
121 char *t;
122
123 pa_assert(c);
124
125 TXTRecordSetValue(txt, "server-version", strlen(PACKAGE_NAME" "PACKAGE_VERSION), PACKAGE_NAME" "PACKAGE_VERSION);
126
127 t = pa_get_user_name_malloc();
128 TXTRecordSetValue(txt, "user-name", strlen(t), t);
129 pa_xfree(t);
130
131 t = pa_machine_id();
132 TXTRecordSetValue(txt, "machine-id", strlen(t), t);
133 pa_xfree(t);
134
135 t = pa_uname_string();
136 TXTRecordSetValue(txt, "uname", strlen(t), t);
137 pa_xfree(t);
138
139 t = pa_get_fqdn(s, sizeof(s));
140 TXTRecordSetValue(txt, "fqdn", strlen(t), t);
141
142 snprintf(s, sizeof(s), "0x%08x", c->cookie);
143 TXTRecordSetValue(txt, "cookie", strlen(s), s);
144 }
145
146 static void service_free(struct service *s);
147
148 static void dns_service_register_reply(DNSServiceRef sdRef,
149 DNSServiceFlags flags,
150 DNSServiceErrorType errorCode,
151 const char *name,
152 const char *regtype,
153 const char *domain,
154 void *context) {
155 struct service *s = context;
156
157 pa_assert(s);
158
159 switch (errorCode) {
160 case kDNSServiceErr_NameConflict:
161 pa_log("DNS service reported kDNSServiceErr_NameConflict\n");
162 service_free(s);
163 break;
164
165 case kDNSServiceErr_NoError:
166 default:
167 break;
168 }
169 }
170
171 static uint16_t compute_port(struct userdata *u) {
172 pa_strlist *i;
173
174 pa_assert(u);
175
176 for (i = pa_native_protocol_servers(u->native); i; i = pa_strlist_next(i)) {
177 pa_parsed_address a;
178
179 if (pa_parse_address(pa_strlist_data(i), &a) >= 0 &&
180 (a.type == PA_PARSED_ADDRESS_TCP4 ||
181 a.type == PA_PARSED_ADDRESS_TCP6 ||
182 a.type == PA_PARSED_ADDRESS_TCP_AUTO) &&
183 a.port > 0) {
184
185 pa_xfree(a.path_or_host);
186 return a.port;
187 }
188
189 pa_xfree(a.path_or_host);
190 }
191
192 return PA_NATIVE_DEFAULT_PORT;
193 }
194
195 static int publish_service(struct service *s) {
196 int r = -1;
197 TXTRecordRef txt;
198 DNSServiceErrorType err;
199 const char *name = NULL, *t;
200 pa_proplist *proplist = NULL;
201 pa_sample_spec ss;
202 pa_channel_map map;
203 char cm[PA_CHANNEL_MAP_SNPRINT_MAX], tmp[64];
204 enum service_subtype subtype;
205
206 const char * const subtype_text[] = {
207 [SUBTYPE_HARDWARE] = "hardware",
208 [SUBTYPE_VIRTUAL] = "virtual",
209 [SUBTYPE_MONITOR] = "monitor"
210 };
211
212 pa_assert(s);
213
214 if (s->service) {
215 DNSServiceRefDeallocate(s->service);
216 s->service = NULL;
217 }
218
219 TXTRecordCreate(&txt, 0, NULL);
220
221 txt_record_server_data(s->userdata->core, &txt);
222
223 get_service_data(s, &ss, &map, &name, &proplist, &subtype);
224 TXTRecordSetValue(&txt, "device", strlen(name), name);
225
226 snprintf(tmp, sizeof(tmp), "%u", ss.rate);
227 TXTRecordSetValue(&txt, "rate", strlen(tmp), tmp);
228
229 snprintf(tmp, sizeof(tmp), "%u", ss.channels);
230 TXTRecordSetValue(&txt, "channels", strlen(tmp), tmp);
231
232 t = pa_sample_format_to_string(ss.format);
233 TXTRecordSetValue(&txt, "format", strlen(t), t);
234
235 t = pa_channel_map_snprint(cm, sizeof(cm), &map);
236 TXTRecordSetValue(&txt, "channel_map", strlen(t), t);
237
238 t = subtype_text[subtype];
239 TXTRecordSetValue(&txt, "subtype", strlen(t), t);
240
241 if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_DESCRIPTION)))
242 TXTRecordSetValue(&txt, "description", strlen(t), t);
243 if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_ICON_NAME)))
244 TXTRecordSetValue(&txt, "icon-name", strlen(t), t);
245 if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_VENDOR_NAME)))
246 TXTRecordSetValue(&txt, "vendor-name", strlen(t), t);
247 if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_PRODUCT_NAME)))
248 TXTRecordSetValue(&txt, "product-name", strlen(t), t);
249 if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_CLASS)))
250 TXTRecordSetValue(&txt, "class", strlen(t), t);
251 if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_FORM_FACTOR)))
252 TXTRecordSetValue(&txt, "form-factor", strlen(t), t);
253
254 err = DNSServiceRegister(&s->service,
255 0, /* flags */
256 kDNSServiceInterfaceIndexAny,
257 s->service_name,
258 pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE,
259 NULL, /* domain */
260 NULL, /* host */
261 compute_port(s->userdata),
262 TXTRecordGetLength(&txt),
263 TXTRecordGetBytesPtr(&txt),
264 dns_service_register_reply, s);
265
266 if (err != kDNSServiceErr_NoError) {
267 pa_log("DNSServiceRegister() returned err %d", err);
268 goto finish;
269 }
270
271 pa_log_debug("Successfully registered Bonjour services for >%s<.", s->service_name);
272 return 0;
273
274 finish:
275
276 /* Remove this service */
277 if (r < 0)
278 service_free(s);
279
280 TXTRecordDeallocate(&txt);
281
282 return r;
283 }
284
285 static struct service *get_service(struct userdata *u, pa_object *device) {
286 struct service *s;
287 char *hn, *un;
288 const char *n;
289
290 pa_assert(u);
291 pa_object_assert_ref(device);
292
293 if ((s = pa_hashmap_get(u->services, device)))
294 return s;
295
296 s = pa_xnew0(struct service, 1);
297 s->userdata = u;
298 s->device = device;
299
300 if (pa_sink_isinstance(device)) {
301 if (!(n = pa_proplist_gets(PA_SINK(device)->proplist, PA_PROP_DEVICE_DESCRIPTION)))
302 n = PA_SINK(device)->name;
303 } else {
304 if (!(n = pa_proplist_gets(PA_SOURCE(device)->proplist, PA_PROP_DEVICE_DESCRIPTION)))
305 n = PA_SOURCE(device)->name;
306 }
307
308 hn = pa_get_host_name_malloc();
309 un = pa_get_user_name_malloc();
310
311 s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", un, hn, n), kDNSServiceMaxDomainName-1);
312
313 pa_xfree(un);
314 pa_xfree(hn);
315
316 pa_hashmap_put(u->services, s->device, s);
317
318 return s;
319 }
320
321 static void service_free(struct service *s) {
322 pa_assert(s);
323
324 pa_hashmap_remove(s->userdata->services, s->device);
325
326 if (s->service)
327 DNSServiceRefDeallocate(s->service);
328
329 pa_xfree(s->service_name);
330 pa_xfree(s);
331 }
332
333 static pa_bool_t shall_ignore(pa_object *o) {
334 pa_object_assert_ref(o);
335
336 if (pa_sink_isinstance(o))
337 return !!(PA_SINK(o)->flags & PA_SINK_NETWORK);
338
339 if (pa_source_isinstance(o))
340 return PA_SOURCE(o)->monitor_of || (PA_SOURCE(o)->flags & PA_SOURCE_NETWORK);
341
342 pa_assert_not_reached();
343 }
344
345 static pa_hook_result_t device_new_or_changed_cb(pa_core *c, pa_object *o, struct userdata *u) {
346 pa_assert(c);
347 pa_object_assert_ref(o);
348
349 if (!shall_ignore(o))
350 publish_service(get_service(u, o));
351
352 return PA_HOOK_OK;
353 }
354
355 static pa_hook_result_t device_unlink_cb(pa_core *c, pa_object *o, struct userdata *u) {
356 struct service *s;
357
358 pa_assert(c);
359 pa_object_assert_ref(o);
360
361 if ((s = pa_hashmap_get(u->services, o)))
362 service_free(s);
363
364 return PA_HOOK_OK;
365 }
366
367 static int publish_main_service(struct userdata *u) {
368 DNSServiceErrorType err;
369 TXTRecordRef txt;
370
371 pa_assert(u);
372
373 if (u->main_service) {
374 DNSServiceRefDeallocate(u->main_service);
375 u->main_service = NULL;
376 }
377
378 TXTRecordCreate(&txt, 0, NULL);
379 txt_record_server_data(u->core, &txt);
380
381 err = DNSServiceRegister(&u->main_service,
382 0, /* flags */
383 kDNSServiceInterfaceIndexAny,
384 u->service_name,
385 SERVICE_TYPE_SERVER,
386 NULL, /* domain */
387 NULL, /* host */
388 compute_port(u),
389 TXTRecordGetLength(&txt),
390 TXTRecordGetBytesPtr(&txt),
391 NULL, NULL);
392
393 if (err != kDNSServiceErr_NoError) {
394 pa_log("%s(): DNSServiceRegister() returned err %d", __func__, err);
395 return err;
396 }
397
398 TXTRecordDeallocate(&txt);
399
400 return 0;
401 }
402
403 static int publish_all_services(struct userdata *u) {
404 pa_sink *sink;
405 pa_source *source;
406 uint32_t idx;
407
408 pa_assert(u);
409
410 pa_log_debug("Publishing services in Bonjour");
411
412 for (sink = PA_SINK(pa_idxset_first(u->core->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(u->core->sinks, &idx)))
413 if (!shall_ignore(PA_OBJECT(sink)))
414 publish_service(get_service(u, PA_OBJECT(sink)));
415
416 for (source = PA_SOURCE(pa_idxset_first(u->core->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(u->core->sources, &idx)))
417 if (!shall_ignore(PA_OBJECT(source)))
418 publish_service(get_service(u, PA_OBJECT(source)));
419
420 return publish_main_service(u);
421 }
422
423 static void unpublish_all_services(struct userdata *u) {
424 void *state = NULL;
425 struct service *s;
426
427 pa_assert(u);
428
429 pa_log_debug("Unpublishing services in Bonjour");
430
431 while ((s = pa_hashmap_iterate(u->services, &state, NULL)))
432 service_free(s);
433
434 if (u->main_service)
435 DNSServiceRefDeallocate(u->main_service);
436 }
437
438 int pa__init(pa_module*m) {
439
440 struct userdata *u;
441 pa_modargs *ma = NULL;
442 char *hn, *un;
443
444 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
445 pa_log("Failed to parse module arguments.");
446 goto fail;
447 }
448
449 m->userdata = u = pa_xnew0(struct userdata, 1);
450 u->core = m->core;
451 u->module = m;
452 u->native = pa_native_protocol_get(u->core);
453
454 u->services = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
455
456 u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
457 u->sink_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
458 u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) device_unlink_cb, u);
459 u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
460 u->source_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
461 u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) device_unlink_cb, u);
462
463 un = pa_get_user_name_malloc();
464 hn = pa_get_host_name_malloc();
465 u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", un, hn), kDNSServiceMaxDomainName-1);
466 pa_xfree(un);
467 pa_xfree(hn);
468
469 publish_all_services(u);
470 pa_modargs_free(ma);
471
472 return 0;
473
474 fail:
475 pa__done(m);
476
477 if (ma)
478 pa_modargs_free(ma);
479
480 return -1;
481 }
482
483 void pa__done(pa_module*m) {
484 struct userdata*u;
485 pa_assert(m);
486
487 if (!(u = m->userdata))
488 return;
489
490 unpublish_all_services(u);
491
492 if (u->services)
493 pa_hashmap_free(u->services, NULL);
494
495 if (u->sink_new_slot)
496 pa_hook_slot_free(u->sink_new_slot);
497 if (u->source_new_slot)
498 pa_hook_slot_free(u->source_new_slot);
499 if (u->sink_changed_slot)
500 pa_hook_slot_free(u->sink_changed_slot);
501 if (u->source_changed_slot)
502 pa_hook_slot_free(u->source_changed_slot);
503 if (u->sink_unlink_slot)
504 pa_hook_slot_free(u->sink_unlink_slot);
505 if (u->source_unlink_slot)
506 pa_hook_slot_free(u->source_unlink_slot);
507
508 if (u->native)
509 pa_native_protocol_unref(u->native);
510
511 pa_xfree(u->service_name);
512 pa_xfree(u);
513 }