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