4 This file is part of polypaudio.
6 polypaudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as
8 published by the Free Software Foundation; either version 2 of the
9 License, or (at your option) any later version.
11 polypaudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with polypaudio; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
32 #include "module-zeroconf-publish-symdef.h"
33 #include "howl-wrap.h"
38 #include "native-common.h"
41 #include "subscribe.h"
43 #include "endianmacros.h"
46 PA_MODULE_AUTHOR("Lennart Poettering")
47 PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher")
48 PA_MODULE_VERSION(PACKAGE_VERSION
)
49 PA_MODULE_USAGE("port=<IP port number>")
51 #define SERVICE_NAME_SINK "_polypaudio-sink._tcp"
52 #define SERVICE_NAME_SOURCE "_polypaudio-source._tcp"
53 #define SERVICE_NAME_SERVER "_polypaudio-server._tcp"
55 static const char* const valid_modargs
[] = {
63 int published
; /* 0 -> not yet registered, 1 -> registered with data from real device, 2 -> registered with data from autoload device */
80 pa_howl_wrapper
*howl_wrapper
;
82 pa_dynarray
*sink_dynarray
, *source_dynarray
, *autoload_dynarray
;
83 pa_subscription
*subscription
;
86 sw_discovery_oid server_oid
;
89 static sw_result
publish_reply(sw_discovery discovery
, sw_discovery_publish_status status
, sw_discovery_oid oid
, sw_opaque extra
) {
93 static void get_service_data(struct userdata
*u
, struct service
*s
, pa_sample_spec
*ret_ss
, char **ret_description
, pa_typeid_t
*ret_typeid
) {
94 assert(u
&& s
&& s
->loaded
.valid
&& ret_ss
&& ret_description
&& ret_typeid
);
96 if (s
->loaded
.type
== PA_NAMEREG_SINK
) {
97 pa_sink
*sink
= pa_idxset_get_by_index(u
->core
->sinks
, s
->loaded
.index
);
99 *ret_ss
= sink
->sample_spec
;
100 *ret_description
= sink
->description
;
101 *ret_typeid
= sink
->typeid;
102 } else if (s
->loaded
.type
== PA_NAMEREG_SOURCE
) {
103 pa_source
*source
= pa_idxset_get_by_index(u
->core
->sources
, s
->loaded
.index
);
105 *ret_ss
= source
->sample_spec
;
106 *ret_description
= source
->description
;
107 *ret_typeid
= source
->typeid;
112 static void txt_record_server_data(pa_core
*c
, sw_text_record t
) {
116 sw_text_record_add_key_and_string_value(t
, "server-version", PACKAGE_NAME
" "PACKAGE_VERSION
);
117 sw_text_record_add_key_and_string_value(t
, "user-name", pa_get_user_name(s
, sizeof(s
)));
118 sw_text_record_add_key_and_string_value(t
, "fqdn", pa_get_fqdn(s
, sizeof(s
)));
119 snprintf(s
, sizeof(s
), "0x%08x", c
->cookie
);
120 sw_text_record_add_key_and_string_value(t
, "cookie", s
);
123 static int publish_service(struct userdata
*u
, struct service
*s
) {
131 if ((s
->published
== 1 && s
->loaded
.valid
) ||
132 (s
->published
== 2 && s
->autoload
.valid
&& !s
->loaded
.valid
))
136 sw_discovery_cancel(pa_howl_wrapper_get_discovery(u
->howl_wrapper
), s
->oid
);
140 snprintf(t
, sizeof(t
), "Networked Audio Device %s on %s", s
->name
, pa_get_host_name(hn
, sizeof(hn
)));
142 if (sw_text_record_init(&txt
) != SW_OKAY
) {
143 pa_log(__FILE__
": sw_text_record_init() failed\n");
148 sw_text_record_add_key_and_string_value(txt
, "device", s
->name
);
150 txt_record_server_data(u
->core
, txt
);
152 if (s
->loaded
.valid
) {
153 char z
[64], *description
;
157 get_service_data(u
, s
, &ss
, &description
, &typeid);
159 snprintf(z
, sizeof(z
), "%u", ss
.rate
);
160 sw_text_record_add_key_and_string_value(txt
, "rate", z
);
161 snprintf(z
, sizeof(z
), "%u", ss
.channels
);
162 sw_text_record_add_key_and_string_value(txt
, "channels", z
);
163 sw_text_record_add_key_and_string_value(txt
, "format", pa_sample_format_to_string(ss
.format
));
165 sw_text_record_add_key_and_string_value(txt
, "description", description
);
167 snprintf(z
, sizeof(z
), "0x%8x", typeid);
168 sw_text_record_add_key_and_string_value(txt
, "typeid", z
);
171 if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u
->howl_wrapper
), 0, t
,
172 s
->loaded
.type
== PA_NAMEREG_SINK
? SERVICE_NAME_SINK
: SERVICE_NAME_SOURCE
,
173 NULL
, NULL
, u
->port
, sw_text_record_bytes(txt
), sw_text_record_len(txt
),
174 publish_reply
, s
, &s
->oid
) != SW_OKAY
) {
175 pa_log(__FILE__
": failed to register sink on zeroconf.\n");
180 } else if (s
->autoload
.valid
) {
182 if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u
->howl_wrapper
), 0, t
,
183 s
->autoload
.type
== PA_NAMEREG_SINK
? SERVICE_NAME_SINK
: SERVICE_NAME_SOURCE
,
184 NULL
, NULL
, u
->port
, sw_text_record_bytes(txt
), sw_text_record_len(txt
),
185 publish_reply
, s
, &s
->oid
) != SW_OKAY
) {
186 pa_log(__FILE__
": failed to register sink on zeroconf.\n");
198 /* Remove this service */
199 pa_hashmap_remove(u
->services
, s
->name
);
205 sw_text_record_fina(txt
);
210 struct service
*get_service(struct userdata
*u
, const char *name
) {
213 if ((s
= pa_hashmap_get(u
->services
, name
)))
216 s
= pa_xmalloc(sizeof(struct service
));
218 s
->name
= pa_xstrdup(name
);
219 s
->loaded
.valid
= s
->autoload
.valid
= 0;
221 pa_hashmap_put(u
->services
, s
->name
, s
);
226 static int publish_sink(struct userdata
*u
, pa_sink
*s
) {
230 svc
= get_service(u
, s
->name
);
231 if (svc
->loaded
.valid
)
234 svc
->loaded
.valid
= 1;
235 svc
->loaded
.type
= PA_NAMEREG_SINK
;
236 svc
->loaded
.index
= s
->index
;
238 pa_dynarray_put(u
->sink_dynarray
, s
->index
, svc
);
240 return publish_service(u
, svc
);
243 static int publish_source(struct userdata
*u
, pa_source
*s
) {
247 svc
= get_service(u
, s
->name
);
248 if (svc
->loaded
.valid
)
251 svc
->loaded
.valid
= 1;
252 svc
->loaded
.type
= PA_NAMEREG_SOURCE
;
253 svc
->loaded
.index
= s
->index
;
255 pa_dynarray_put(u
->source_dynarray
, s
->index
, svc
);
257 return publish_service(u
, svc
);
260 static int publish_autoload(struct userdata
*u
, pa_autoload_entry
*s
) {
264 svc
= get_service(u
, s
->name
);
265 if (svc
->autoload
.valid
)
268 svc
->autoload
.valid
= 1;
269 svc
->autoload
.type
= s
->type
;
270 svc
->autoload
.index
= s
->index
;
272 pa_dynarray_put(u
->autoload_dynarray
, s
->index
, svc
);
274 return publish_service(u
, svc
);
277 static int remove_sink(struct userdata
*u
, uint32_t index
) {
279 assert(u
&& index
!= PA_INVALID_INDEX
);
281 if (!(svc
= pa_dynarray_get(u
->sink_dynarray
, index
)))
284 if (!svc
->loaded
.valid
|| svc
->loaded
.type
!= PA_NAMEREG_SINK
)
287 svc
->loaded
.valid
= 0;
288 pa_dynarray_put(u
->sink_dynarray
, index
, NULL
);
290 return publish_service(u
, svc
);
293 static int remove_source(struct userdata
*u
, uint32_t index
) {
295 assert(u
&& index
!= PA_INVALID_INDEX
);
297 if (!(svc
= pa_dynarray_get(u
->source_dynarray
, index
)))
300 if (!svc
->loaded
.valid
|| svc
->loaded
.type
!= PA_NAMEREG_SOURCE
)
303 svc
->loaded
.valid
= 0;
304 pa_dynarray_put(u
->source_dynarray
, index
, NULL
);
306 return publish_service(u
, svc
);
309 static int remove_autoload(struct userdata
*u
, uint32_t index
) {
311 assert(u
&& index
!= PA_INVALID_INDEX
);
313 if (!(svc
= pa_dynarray_get(u
->autoload_dynarray
, index
)))
316 if (!svc
->autoload
.valid
)
319 svc
->autoload
.valid
= 0;
320 pa_dynarray_put(u
->autoload_dynarray
, index
, NULL
);
322 return publish_service(u
, svc
);
325 static void subscribe_callback(pa_core
*c
, pa_subscription_event_type t
, uint32_t index
, void *userdata
) {
326 struct userdata
*u
= userdata
;
329 switch (t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
)
330 case PA_SUBSCRIPTION_EVENT_SINK
: {
331 if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_NEW
) {
334 if ((sink
= pa_idxset_get_by_index(c
->sinks
, index
))) {
335 if (publish_sink(u
, sink
) < 0)
338 } else if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_REMOVE
) {
339 if (remove_sink(u
, index
) < 0)
345 case PA_SUBSCRIPTION_EVENT_SOURCE
:
347 if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_NEW
) {
350 if ((source
= pa_idxset_get_by_index(c
->sources
, index
))) {
351 if (publish_source(u
, source
) < 0)
354 } else if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_REMOVE
) {
355 if (remove_source(u
, index
) < 0)
361 case PA_SUBSCRIPTION_EVENT_AUTOLOAD
:
362 if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_NEW
) {
363 pa_autoload_entry
*autoload
;
365 if ((autoload
= pa_idxset_get_by_index(c
->autoload_idxset
, index
))) {
366 if (publish_autoload(u
, autoload
) < 0)
369 } else if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) == PA_SUBSCRIPTION_EVENT_REMOVE
) {
370 if (remove_autoload(u
, index
) < 0)
380 if (u
->subscription
) {
381 pa_subscription_free(u
->subscription
);
382 u
->subscription
= NULL
;
386 int pa__init(pa_core
*c
, pa_module
*m
) {
388 uint32_t index
, port
= PA_NATIVE_DEFAULT_PORT
;
391 pa_autoload_entry
*autoload
;
392 pa_modargs
*ma
= NULL
;
393 char t
[256], hn
[256];
397 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
398 pa_log(__FILE__
": failed to parse module arguments.\n");
402 if (pa_modargs_get_value_u32(ma
, "port", &port
) < 0 || port
== 0 || port
>= 0xFFFF) {
403 pa_log(__FILE__
": invalid port specified.\n");
407 m
->userdata
= u
= pa_xmalloc(sizeof(struct userdata
));
409 u
->port
= (uint16_t) port
;
411 if (!(u
->howl_wrapper
= pa_howl_wrapper_get(c
)))
414 u
->services
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
415 u
->sink_dynarray
= pa_dynarray_new();
416 u
->source_dynarray
= pa_dynarray_new();
417 u
->autoload_dynarray
= pa_dynarray_new();
419 u
->subscription
= pa_subscription_new(c
,
420 PA_SUBSCRIPTION_MASK_SINK
|
421 PA_SUBSCRIPTION_MASK_SOURCE
|
422 PA_SUBSCRIPTION_MASK_AUTOLOAD
, subscribe_callback
, u
);
424 for (sink
= pa_idxset_first(c
->sinks
, &index
); sink
; sink
= pa_idxset_next(c
->sinks
, &index
))
425 if (publish_sink(u
, sink
) < 0)
428 for (source
= pa_idxset_first(c
->sources
, &index
); source
; source
= pa_idxset_next(c
->sources
, &index
))
429 if (publish_source(u
, source
) < 0)
432 if (c
->autoload_idxset
)
433 for (autoload
= pa_idxset_first(c
->autoload_idxset
, &index
); autoload
; autoload
= pa_idxset_next(c
->autoload_idxset
, &index
))
434 if (publish_autoload(u
, autoload
) < 0)
437 snprintf(t
, sizeof(t
), "Networked Audio Server on %s", pa_get_host_name(hn
, sizeof(hn
)));
439 if (sw_text_record_init(&txt
) != SW_OKAY
) {
440 pa_log(__FILE__
": sw_text_record_init() failed\n");
445 txt_record_server_data(u
->core
, txt
);
447 if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u
->howl_wrapper
), 0, t
,
449 NULL
, NULL
, u
->port
, sw_text_record_bytes(txt
), sw_text_record_len(txt
),
450 publish_reply
, u
, &u
->server_oid
) != SW_OKAY
) {
451 pa_log(__FILE__
": failed to register server on zeroconf.\n");
455 sw_text_record_fina(txt
);
467 sw_text_record_fina(txt
);
472 static void service_free(void *p
, void *userdata
) {
473 struct service
*s
= p
;
474 struct userdata
*u
= userdata
;
476 sw_discovery_cancel(pa_howl_wrapper_get_discovery(u
->howl_wrapper
), s
->oid
);
481 void pa__done(pa_core
*c
, pa_module
*m
) {
485 if (!(u
= m
->userdata
))
489 pa_hashmap_free(u
->services
, service_free
, u
);
491 if (u
->sink_dynarray
)
492 pa_dynarray_free(u
->sink_dynarray
, NULL
, NULL
);
493 if (u
->source_dynarray
)
494 pa_dynarray_free(u
->source_dynarray
, NULL
, NULL
);
495 if (u
->autoload_dynarray
)
496 pa_dynarray_free(u
->autoload_dynarray
, NULL
, NULL
);
499 pa_subscription_free(u
->subscription
);
502 pa_howl_wrapper_unref(u
->howl_wrapper
);