2 This file is part of PulseAudio.
4 Copyright 2009 Daniel Mack
5 based on module-zeroconf-publish.c
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.
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.
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
33 #include <CoreFoundation/CoreFoundation.h>
35 #include <pulse/xmalloc.h>
36 #include <pulse/util.h>
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/avahi-wrap.h>
48 #include <pulsecore/endianmacros.h>
49 #include <pulsecore/protocol-native.h>
51 #include "module-bonjour-publish-symdef.h"
53 PA_MODULE_AUTHOR("Daniel Mack");
54 PA_MODULE_DESCRIPTION("Mac OS X Bonjour Service Publisher");
55 PA_MODULE_VERSION(PACKAGE_VERSION);
56 PA_MODULE_LOAD_ONCE(TRUE);
58 #define SERVICE_TYPE_SINK "_pulse-sink._tcp"
59 #define SERVICE_TYPE_SOURCE "_pulse-source._tcp"
60 #define SERVICE_TYPE_SERVER "_pulse-server._tcp"
62 static const char* const valid_modargs[] = {
66 enum service_subtype {
73 struct userdata *userdata;
74 DNSServiceRef service;
75 DNSRecordRef rec, rec2;
78 enum service_subtype subtype;
88 pa_hook_slot *sink_new_slot, *source_new_slot, *sink_unlink_slot, *source_unlink_slot, *sink_changed_slot, *source_changed_slot;
90 pa_native_protocol *native;
91 DNSServiceRef main_service;
94 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) {
97 pa_assert(ret_proplist);
98 pa_assert(ret_subtype);
100 if (pa_sink_isinstance(s->device)) {
101 pa_sink *sink = PA_SINK(s->device);
103 *ret_ss = sink->sample_spec;
104 *ret_map = sink->channel_map;
105 *ret_name = sink->name;
106 *ret_proplist = sink->proplist;
107 *ret_subtype = sink->flags & PA_SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL;
109 } else if (pa_source_isinstance(s->device)) {
110 pa_source *source = PA_SOURCE(s->device);
112 *ret_ss = source->sample_spec;
113 *ret_map = source->channel_map;
114 *ret_name = source->name;
115 *ret_proplist = source->proplist;
116 *ret_subtype = source->monitor_of ? SUBTYPE_MONITOR : (source->flags & PA_SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL);
119 pa_assert_not_reached();
122 static void txt_record_server_data(pa_core *c, TXTRecordRef *txt) {
128 TXTRecordSetValue(txt, "server-version", strlen(PACKAGE_NAME" "PACKAGE_VERSION), PACKAGE_NAME" "PACKAGE_VERSION);
130 t = pa_get_user_name_malloc();
131 TXTRecordSetValue(txt, "user-name", strlen(t), t);
135 TXTRecordSetValue(txt, "machine-id", strlen(t), t);
138 t = pa_uname_string();
139 TXTRecordSetValue(txt, "uname", strlen(t), t);
142 t = pa_get_fqdn(s, sizeof(s));
143 TXTRecordSetValue(txt, "fqdn", strlen(t), t);
145 snprintf(s, sizeof(s), "0x%08x", c->cookie);
146 TXTRecordSetValue(txt, "cookie", strlen(s), s);
149 static void service_free(struct service *s);
151 static void dns_service_register_reply(DNSServiceRef sdRef,
152 DNSServiceFlags flags,
153 DNSServiceErrorType errorCode,
158 struct service *s = context;
163 case kDNSServiceErr_NameConflict:
164 pa_log("DNS service reported kDNSServiceErr_NameConflict\n");
168 case kDNSServiceErr_NoError:
174 static uint16_t compute_port(struct userdata *u) {
179 for (i = pa_native_protocol_servers(u->native); i; i = pa_strlist_next(i)) {
182 if (pa_parse_address(pa_strlist_data(i), &a) >= 0 &&
183 (a.type == PA_PARSED_ADDRESS_TCP4 ||
184 a.type == PA_PARSED_ADDRESS_TCP6 ||
185 a.type == PA_PARSED_ADDRESS_TCP_AUTO) &&
188 pa_xfree(a.path_or_host);
192 pa_xfree(a.path_or_host);
195 return PA_NATIVE_DEFAULT_PORT;
198 static int publish_service(struct service *s) {
201 DNSServiceErrorType err;
202 const char *name = NULL, *t;
203 pa_proplist *proplist = NULL;
206 char cm[PA_CHANNEL_MAP_SNPRINT_MAX], tmp[64];
207 enum service_subtype subtype;
209 const char * const subtype_text[] = {
210 [SUBTYPE_HARDWARE] = "hardware",
211 [SUBTYPE_VIRTUAL] = "virtual",
212 [SUBTYPE_MONITOR] = "monitor"
218 DNSServiceRefDeallocate(s->service);
222 TXTRecordCreate(&txt, 0, NULL);
224 txt_record_server_data(s->userdata->core, &txt);
226 get_service_data(s, &ss, &map, &name, &proplist, &subtype);
227 TXTRecordSetValue(&txt, "device", strlen(name), name);
229 snprintf(tmp, sizeof(tmp), "%u", ss.rate);
230 TXTRecordSetValue(&txt, "rate", strlen(tmp), tmp);
232 snprintf(tmp, sizeof(tmp), "%u", ss.channels);
233 TXTRecordSetValue(&txt, "channels", strlen(tmp), tmp);
235 t = pa_sample_format_to_string(ss.format);
236 TXTRecordSetValue(&txt, "format", strlen(t), t);
238 t = pa_channel_map_snprint(cm, sizeof(cm), &map);
239 TXTRecordSetValue(&txt, "channel_map", strlen(t), t);
241 t = subtype_text[subtype];
242 TXTRecordSetValue(&txt, "subtype", strlen(t), t);
244 if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_DESCRIPTION)))
245 TXTRecordSetValue(&txt, "description", strlen(t), t);
246 if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_ICON_NAME)))
247 TXTRecordSetValue(&txt, "icon-name", strlen(t), t);
248 if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_VENDOR_NAME)))
249 TXTRecordSetValue(&txt, "vendor-name", strlen(t), t);
250 if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_PRODUCT_NAME)))
251 TXTRecordSetValue(&txt, "product-name", strlen(t), t);
252 if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_CLASS)))
253 TXTRecordSetValue(&txt, "class", strlen(t), t);
254 if ((t = pa_proplist_gets(proplist, PA_PROP_DEVICE_FORM_FACTOR)))
255 TXTRecordSetValue(&txt, "form-factor", strlen(t), t);
257 err = DNSServiceRegister(&s->service,
259 kDNSServiceInterfaceIndexAny,
261 pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE,
264 compute_port(s->userdata),
265 TXTRecordGetLength(&txt),
266 TXTRecordGetBytesPtr(&txt),
267 dns_service_register_reply, s);
269 if (err != kDNSServiceErr_NoError) {
270 pa_log("DNSServiceRegister() returned err %d", err);
274 pa_log_debug("Successfully registered Bonjour services for >%s<.", s->service_name);
279 /* Remove this service */
283 TXTRecordDeallocate(&txt);
288 static struct service *get_service(struct userdata *u, pa_object *device) {
294 pa_object_assert_ref(device);
296 if ((s = pa_hashmap_get(u->services, device)))
299 s = pa_xnew0(struct service, 1);
303 if (pa_sink_isinstance(device)) {
304 if (!(n = pa_proplist_gets(PA_SINK(device)->proplist, PA_PROP_DEVICE_DESCRIPTION)))
305 n = PA_SINK(device)->name;
307 if (!(n = pa_proplist_gets(PA_SOURCE(device)->proplist, PA_PROP_DEVICE_DESCRIPTION)))
308 n = PA_SOURCE(device)->name;
311 hn = pa_get_host_name_malloc();
312 un = pa_get_user_name_malloc();
314 s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", un, hn, n), kDNSServiceMaxDomainName-1);
319 pa_hashmap_put(u->services, s->device, s);
324 static void service_free(struct service *s) {
327 pa_hashmap_remove(s->userdata->services, s->device);
330 DNSServiceRefDeallocate(s->service);
332 pa_xfree(s->service_name);
336 static pa_bool_t shall_ignore(pa_object *o) {
337 pa_object_assert_ref(o);
339 if (pa_sink_isinstance(o))
340 return !!(PA_SINK(o)->flags & PA_SINK_NETWORK);
342 if (pa_source_isinstance(o))
343 return PA_SOURCE(o)->monitor_of || (PA_SOURCE(o)->flags & PA_SOURCE_NETWORK);
345 pa_assert_not_reached();
348 static pa_hook_result_t device_new_or_changed_cb(pa_core *c, pa_object *o, struct userdata *u) {
350 pa_object_assert_ref(o);
352 if (!shall_ignore(o))
353 publish_service(get_service(u, o));
358 static pa_hook_result_t device_unlink_cb(pa_core *c, pa_object *o, struct userdata *u) {
362 pa_object_assert_ref(o);
364 if ((s = pa_hashmap_get(u->services, o)))
370 static int publish_main_service(struct userdata *u) {
371 DNSServiceErrorType err;
376 if (u->main_service) {
377 DNSServiceRefDeallocate(u->main_service);
378 u->main_service = NULL;
381 TXTRecordCreate(&txt, 0, NULL);
382 txt_record_server_data(u->core, &txt);
384 err = DNSServiceRegister(&u->main_service,
386 kDNSServiceInterfaceIndexAny,
392 TXTRecordGetLength(&txt),
393 TXTRecordGetBytesPtr(&txt),
396 if (err != kDNSServiceErr_NoError) {
397 pa_log("%s(): DNSServiceRegister() returned err %d", __func__, err);
401 TXTRecordDeallocate(&txt);
406 static int publish_all_services(struct userdata *u) {
413 pa_log_debug("Publishing services in Bonjour");
415 for (sink = PA_SINK(pa_idxset_first(u->core->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(u->core->sinks, &idx)))
416 if (!shall_ignore(PA_OBJECT(sink)))
417 publish_service(get_service(u, PA_OBJECT(sink)));
419 for (source = PA_SOURCE(pa_idxset_first(u->core->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(u->core->sources, &idx)))
420 if (!shall_ignore(PA_OBJECT(source)))
421 publish_service(get_service(u, PA_OBJECT(source)));
423 return publish_main_service(u);
426 static void unpublish_all_services(struct userdata *u) {
432 pa_log_debug("Unpublishing services in Bonjour");
434 while ((s = pa_hashmap_iterate(u->services, &state, NULL)))
438 DNSServiceRefDeallocate(u->main_service);
441 int pa__init(pa_module*m) {
444 pa_modargs *ma = NULL;
447 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
448 pa_log("Failed to parse module arguments.");
452 m->userdata = u = pa_xnew0(struct userdata, 1);
455 u->native = pa_native_protocol_get(u->core);
457 u->services = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
459 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);
460 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);
461 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);
462 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);
463 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);
464 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);
466 un = pa_get_user_name_malloc();
467 hn = pa_get_host_name_malloc();
468 u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", un, hn), kDNSServiceMaxDomainName-1);
472 publish_all_services(u);
486 void pa__done(pa_module*m) {
490 if (!(u = m->userdata))
493 unpublish_all_services(u);
496 pa_hashmap_free(u->services, NULL, NULL);
498 if (u->sink_new_slot)
499 pa_hook_slot_free(u->sink_new_slot);
500 if (u->source_new_slot)
501 pa_hook_slot_free(u->source_new_slot);
502 if (u->sink_changed_slot)
503 pa_hook_slot_free(u->sink_changed_slot);
504 if (u->source_changed_slot)
505 pa_hook_slot_free(u->source_changed_slot);
506 if (u->sink_unlink_slot)
507 pa_hook_slot_free(u->sink_unlink_slot);
508 if (u->source_unlink_slot)
509 pa_hook_slot_free(u->source_unlink_slot);
512 pa_native_protocol_unref(u->native);
514 pa_xfree(u->service_name);