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/dynarray.h>
45 #include <pulsecore/modargs.h>
46 #include <pulsecore/protocol-native.h>
48 #include "module-bonjour-publish-symdef.h"
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);
55 #define SERVICE_TYPE_SINK "_pulse-sink._tcp"
56 #define SERVICE_TYPE_SOURCE "_pulse-source._tcp"
57 #define SERVICE_TYPE_SERVER "_pulse-server._tcp"
59 static const char* const valid_modargs[] = {
63 enum service_subtype {
70 struct userdata *userdata;
71 DNSServiceRef service;
72 DNSRecordRef rec, rec2;
75 enum service_subtype subtype;
85 pa_hook_slot *sink_new_slot, *source_new_slot, *sink_unlink_slot, *source_unlink_slot, *sink_changed_slot, *source_changed_slot;
87 pa_native_protocol *native;
88 DNSServiceRef main_service;
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) {
94 pa_assert(ret_proplist);
95 pa_assert(ret_subtype);
97 if (pa_sink_isinstance(s->device)) {
98 pa_sink *sink = PA_SINK(s->device);
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;
106 } else if (pa_source_isinstance(s->device)) {
107 pa_source *source = PA_SOURCE(s->device);
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);
116 pa_assert_not_reached();
119 static void txt_record_server_data(pa_core *c, TXTRecordRef *txt) {
125 TXTRecordSetValue(txt, "server-version", strlen(PACKAGE_NAME" "PACKAGE_VERSION), PACKAGE_NAME" "PACKAGE_VERSION);
127 t = pa_get_user_name_malloc();
128 TXTRecordSetValue(txt, "user-name", strlen(t), t);
132 TXTRecordSetValue(txt, "machine-id", strlen(t), t);
135 t = pa_uname_string();
136 TXTRecordSetValue(txt, "uname", strlen(t), t);
139 t = pa_get_fqdn(s, sizeof(s));
140 TXTRecordSetValue(txt, "fqdn", strlen(t), t);
142 snprintf(s, sizeof(s), "0x%08x", c->cookie);
143 TXTRecordSetValue(txt, "cookie", strlen(s), s);
146 static void service_free(struct service *s);
148 static void dns_service_register_reply(DNSServiceRef sdRef,
149 DNSServiceFlags flags,
150 DNSServiceErrorType errorCode,
155 struct service *s = context;
160 case kDNSServiceErr_NameConflict:
161 pa_log("DNS service reported kDNSServiceErr_NameConflict\n");
165 case kDNSServiceErr_NoError:
171 static uint16_t compute_port(struct userdata *u) {
176 for (i = pa_native_protocol_servers(u->native); i; i = pa_strlist_next(i)) {
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) &&
185 pa_xfree(a.path_or_host);
189 pa_xfree(a.path_or_host);
192 return PA_NATIVE_DEFAULT_PORT;
195 static int publish_service(struct service *s) {
198 DNSServiceErrorType err;
199 const char *name = NULL, *t;
200 pa_proplist *proplist = NULL;
203 char cm[PA_CHANNEL_MAP_SNPRINT_MAX], tmp[64];
204 enum service_subtype subtype;
206 const char * const subtype_text[] = {
207 [SUBTYPE_HARDWARE] = "hardware",
208 [SUBTYPE_VIRTUAL] = "virtual",
209 [SUBTYPE_MONITOR] = "monitor"
215 DNSServiceRefDeallocate(s->service);
219 TXTRecordCreate(&txt, 0, NULL);
221 txt_record_server_data(s->userdata->core, &txt);
223 get_service_data(s, &ss, &map, &name, &proplist, &subtype);
224 TXTRecordSetValue(&txt, "device", strlen(name), name);
226 snprintf(tmp, sizeof(tmp), "%u", ss.rate);
227 TXTRecordSetValue(&txt, "rate", strlen(tmp), tmp);
229 snprintf(tmp, sizeof(tmp), "%u", ss.channels);
230 TXTRecordSetValue(&txt, "channels", strlen(tmp), tmp);
232 t = pa_sample_format_to_string(ss.format);
233 TXTRecordSetValue(&txt, "format", strlen(t), t);
235 t = pa_channel_map_snprint(cm, sizeof(cm), &map);
236 TXTRecordSetValue(&txt, "channel_map", strlen(t), t);
238 t = subtype_text[subtype];
239 TXTRecordSetValue(&txt, "subtype", strlen(t), t);
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);
254 err = DNSServiceRegister(&s->service,
256 kDNSServiceInterfaceIndexAny,
258 pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE,
261 compute_port(s->userdata),
262 TXTRecordGetLength(&txt),
263 TXTRecordGetBytesPtr(&txt),
264 dns_service_register_reply, s);
266 if (err != kDNSServiceErr_NoError) {
267 pa_log("DNSServiceRegister() returned err %d", err);
271 pa_log_debug("Successfully registered Bonjour services for >%s<.", s->service_name);
276 /* Remove this service */
280 TXTRecordDeallocate(&txt);
285 static struct service *get_service(struct userdata *u, pa_object *device) {
291 pa_object_assert_ref(device);
293 if ((s = pa_hashmap_get(u->services, device)))
296 s = pa_xnew0(struct service, 1);
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;
304 if (!(n = pa_proplist_gets(PA_SOURCE(device)->proplist, PA_PROP_DEVICE_DESCRIPTION)))
305 n = PA_SOURCE(device)->name;
308 hn = pa_get_host_name_malloc();
309 un = pa_get_user_name_malloc();
311 s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", un, hn, n), kDNSServiceMaxDomainName-1);
316 pa_hashmap_put(u->services, s->device, s);
321 static void service_free(struct service *s) {
324 pa_hashmap_remove(s->userdata->services, s->device);
327 DNSServiceRefDeallocate(s->service);
329 pa_xfree(s->service_name);
333 static bool shall_ignore(pa_object *o) {
334 pa_object_assert_ref(o);
336 if (pa_sink_isinstance(o))
337 return !!(PA_SINK(o)->flags & PA_SINK_NETWORK);
339 if (pa_source_isinstance(o))
340 return PA_SOURCE(o)->monitor_of || (PA_SOURCE(o)->flags & PA_SOURCE_NETWORK);
342 pa_assert_not_reached();
345 static pa_hook_result_t device_new_or_changed_cb(pa_core *c, pa_object *o, struct userdata *u) {
347 pa_object_assert_ref(o);
349 if (!shall_ignore(o))
350 publish_service(get_service(u, o));
355 static pa_hook_result_t device_unlink_cb(pa_core *c, pa_object *o, struct userdata *u) {
359 pa_object_assert_ref(o);
361 if ((s = pa_hashmap_get(u->services, o)))
367 static int publish_main_service(struct userdata *u) {
368 DNSServiceErrorType err;
373 if (u->main_service) {
374 DNSServiceRefDeallocate(u->main_service);
375 u->main_service = NULL;
378 TXTRecordCreate(&txt, 0, NULL);
379 txt_record_server_data(u->core, &txt);
381 err = DNSServiceRegister(&u->main_service,
383 kDNSServiceInterfaceIndexAny,
389 TXTRecordGetLength(&txt),
390 TXTRecordGetBytesPtr(&txt),
393 if (err != kDNSServiceErr_NoError) {
394 pa_log("%s(): DNSServiceRegister() returned err %d", __func__, err);
398 TXTRecordDeallocate(&txt);
403 static int publish_all_services(struct userdata *u) {
410 pa_log_debug("Publishing services in Bonjour");
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)));
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)));
420 return publish_main_service(u);
423 static void unpublish_all_services(struct userdata *u) {
429 pa_log_debug("Unpublishing services in Bonjour");
431 while ((s = pa_hashmap_iterate(u->services, &state, NULL)))
435 DNSServiceRefDeallocate(u->main_service);
438 int pa__init(pa_module*m) {
441 pa_modargs *ma = NULL;
444 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
445 pa_log("Failed to parse module arguments.");
449 m->userdata = u = pa_xnew0(struct userdata, 1);
452 u->native = pa_native_protocol_get(u->core);
454 u->services = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
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);
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);
469 publish_all_services(u);
483 void pa__done(pa_module*m) {
487 if (!(u = m->userdata))
490 unpublish_all_services(u);
493 pa_hashmap_free(u->services);
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);
509 pa_native_protocol_unref(u->native);
511 pa_xfree(u->service_name);