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/endianmacros.h>
48 #include <pulsecore/protocol-native.h>
50 #include "module-bonjour-publish-symdef.h"
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
);
57 #define SERVICE_TYPE_SINK "_pulse-sink._tcp"
58 #define SERVICE_TYPE_SOURCE "_pulse-source._tcp"
59 #define SERVICE_TYPE_SERVER "_pulse-server._tcp"
61 static const char* const valid_modargs
[] = {
65 enum service_subtype
{
72 struct userdata
*userdata
;
73 DNSServiceRef service
;
74 DNSRecordRef rec
, rec2
;
77 enum service_subtype subtype
;
87 pa_hook_slot
*sink_new_slot
, *source_new_slot
, *sink_unlink_slot
, *source_unlink_slot
, *sink_changed_slot
, *source_changed_slot
;
89 pa_native_protocol
*native
;
90 DNSServiceRef main_service
;
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
) {
96 pa_assert(ret_proplist
);
97 pa_assert(ret_subtype
);
99 if (pa_sink_isinstance(s
->device
)) {
100 pa_sink
*sink
= PA_SINK(s
->device
);
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
;
108 } else if (pa_source_isinstance(s
->device
)) {
109 pa_source
*source
= PA_SOURCE(s
->device
);
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
);
118 pa_assert_not_reached();
121 static void txt_record_server_data(pa_core
*c
, TXTRecordRef
*txt
) {
127 TXTRecordSetValue(txt
, "server-version", strlen(PACKAGE_NAME
" "PACKAGE_VERSION
), PACKAGE_NAME
" "PACKAGE_VERSION
);
129 t
= pa_get_user_name_malloc();
130 TXTRecordSetValue(txt
, "user-name", strlen(t
), t
);
134 TXTRecordSetValue(txt
, "machine-id", strlen(t
), t
);
137 t
= pa_uname_string();
138 TXTRecordSetValue(txt
, "uname", strlen(t
), t
);
141 t
= pa_get_fqdn(s
, sizeof(s
));
142 TXTRecordSetValue(txt
, "fqdn", strlen(t
), t
);
144 snprintf(s
, sizeof(s
), "0x%08x", c
->cookie
);
145 TXTRecordSetValue(txt
, "cookie", strlen(s
), s
);
148 static void service_free(struct service
*s
);
150 static void dns_service_register_reply(DNSServiceRef sdRef
,
151 DNSServiceFlags flags
,
152 DNSServiceErrorType errorCode
,
157 struct service
*s
= context
;
162 case kDNSServiceErr_NameConflict
:
163 pa_log("DNS service reported kDNSServiceErr_NameConflict\n");
167 case kDNSServiceErr_NoError
:
173 static uint16_t compute_port(struct userdata
*u
) {
178 for (i
= pa_native_protocol_servers(u
->native
); i
; i
= pa_strlist_next(i
)) {
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
) &&
187 pa_xfree(a
.path_or_host
);
191 pa_xfree(a
.path_or_host
);
194 return PA_NATIVE_DEFAULT_PORT
;
197 static int publish_service(struct service
*s
) {
200 DNSServiceErrorType err
;
201 const char *name
= NULL
, *t
;
202 pa_proplist
*proplist
= NULL
;
205 char cm
[PA_CHANNEL_MAP_SNPRINT_MAX
], tmp
[64];
206 enum service_subtype subtype
;
208 const char * const subtype_text
[] = {
209 [SUBTYPE_HARDWARE
] = "hardware",
210 [SUBTYPE_VIRTUAL
] = "virtual",
211 [SUBTYPE_MONITOR
] = "monitor"
217 DNSServiceRefDeallocate(s
->service
);
221 TXTRecordCreate(&txt
, 0, NULL
);
223 txt_record_server_data(s
->userdata
->core
, &txt
);
225 get_service_data(s
, &ss
, &map
, &name
, &proplist
, &subtype
);
226 TXTRecordSetValue(&txt
, "device", strlen(name
), name
);
228 snprintf(tmp
, sizeof(tmp
), "%u", ss
.rate
);
229 TXTRecordSetValue(&txt
, "rate", strlen(tmp
), tmp
);
231 snprintf(tmp
, sizeof(tmp
), "%u", ss
.channels
);
232 TXTRecordSetValue(&txt
, "channels", strlen(tmp
), tmp
);
234 t
= pa_sample_format_to_string(ss
.format
);
235 TXTRecordSetValue(&txt
, "format", strlen(t
), t
);
237 t
= pa_channel_map_snprint(cm
, sizeof(cm
), &map
);
238 TXTRecordSetValue(&txt
, "channel_map", strlen(t
), t
);
240 t
= subtype_text
[subtype
];
241 TXTRecordSetValue(&txt
, "subtype", strlen(t
), t
);
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
);
256 err
= DNSServiceRegister(&s
->service
,
258 kDNSServiceInterfaceIndexAny
,
260 pa_sink_isinstance(s
->device
) ? SERVICE_TYPE_SINK
: SERVICE_TYPE_SOURCE
,
263 compute_port(s
->userdata
),
264 TXTRecordGetLength(&txt
),
265 TXTRecordGetBytesPtr(&txt
),
266 dns_service_register_reply
, s
);
268 if (err
!= kDNSServiceErr_NoError
) {
269 pa_log("DNSServiceRegister() returned err %d", err
);
273 pa_log_debug("Successfully registered Bonjour services for >%s<.", s
->service_name
);
278 /* Remove this service */
282 TXTRecordDeallocate(&txt
);
287 static struct service
*get_service(struct userdata
*u
, pa_object
*device
) {
293 pa_object_assert_ref(device
);
295 if ((s
= pa_hashmap_get(u
->services
, device
)))
298 s
= pa_xnew0(struct service
, 1);
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
;
306 if (!(n
= pa_proplist_gets(PA_SOURCE(device
)->proplist
, PA_PROP_DEVICE_DESCRIPTION
)))
307 n
= PA_SOURCE(device
)->name
;
310 hn
= pa_get_host_name_malloc();
311 un
= pa_get_user_name_malloc();
313 s
->service_name
= pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", un
, hn
, n
), kDNSServiceMaxDomainName
-1);
318 pa_hashmap_put(u
->services
, s
->device
, s
);
323 static void service_free(struct service
*s
) {
326 pa_hashmap_remove(s
->userdata
->services
, s
->device
);
329 DNSServiceRefDeallocate(s
->service
);
331 pa_xfree(s
->service_name
);
335 static pa_bool_t
shall_ignore(pa_object
*o
) {
336 pa_object_assert_ref(o
);
338 if (pa_sink_isinstance(o
))
339 return !!(PA_SINK(o
)->flags
& PA_SINK_NETWORK
);
341 if (pa_source_isinstance(o
))
342 return PA_SOURCE(o
)->monitor_of
|| (PA_SOURCE(o
)->flags
& PA_SOURCE_NETWORK
);
344 pa_assert_not_reached();
347 static pa_hook_result_t
device_new_or_changed_cb(pa_core
*c
, pa_object
*o
, struct userdata
*u
) {
349 pa_object_assert_ref(o
);
351 if (!shall_ignore(o
))
352 publish_service(get_service(u
, o
));
357 static pa_hook_result_t
device_unlink_cb(pa_core
*c
, pa_object
*o
, struct userdata
*u
) {
361 pa_object_assert_ref(o
);
363 if ((s
= pa_hashmap_get(u
->services
, o
)))
369 static int publish_main_service(struct userdata
*u
) {
370 DNSServiceErrorType err
;
375 if (u
->main_service
) {
376 DNSServiceRefDeallocate(u
->main_service
);
377 u
->main_service
= NULL
;
380 TXTRecordCreate(&txt
, 0, NULL
);
381 txt_record_server_data(u
->core
, &txt
);
383 err
= DNSServiceRegister(&u
->main_service
,
385 kDNSServiceInterfaceIndexAny
,
391 TXTRecordGetLength(&txt
),
392 TXTRecordGetBytesPtr(&txt
),
395 if (err
!= kDNSServiceErr_NoError
) {
396 pa_log("%s(): DNSServiceRegister() returned err %d", __func__
, err
);
400 TXTRecordDeallocate(&txt
);
405 static int publish_all_services(struct userdata
*u
) {
412 pa_log_debug("Publishing services in Bonjour");
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
)));
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
)));
422 return publish_main_service(u
);
425 static void unpublish_all_services(struct userdata
*u
) {
431 pa_log_debug("Unpublishing services in Bonjour");
433 while ((s
= pa_hashmap_iterate(u
->services
, &state
, NULL
)))
437 DNSServiceRefDeallocate(u
->main_service
);
440 int pa__init(pa_module
*m
) {
443 pa_modargs
*ma
= NULL
;
446 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
447 pa_log("Failed to parse module arguments.");
451 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
454 u
->native
= pa_native_protocol_get(u
->core
);
456 u
->services
= pa_hashmap_new(pa_idxset_trivial_hash_func
, pa_idxset_trivial_compare_func
);
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
);
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);
471 publish_all_services(u
);
485 void pa__done(pa_module
*m
) {
489 if (!(u
= m
->userdata
))
492 unpublish_all_services(u
);
495 pa_hashmap_free(u
->services
, NULL
, NULL
);
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
);
511 pa_native_protocol_unref(u
->native
);
513 pa_xfree(u
->service_name
);