2 This file is part of PulseAudio.
4 Copyright 2005-2009 Lennart Poettering
6 PulseAudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published
8 by the Free Software Foundation; either version 2.1 of the License,
9 or (at your option) any later version.
11 PulseAudio 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 License
17 along with PulseAudio; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
35 #include <pulse/xmalloc.h>
36 #include <pulse/util.h>
37 #include <pulse/i18n.h>
38 #include <pulse/utf8.h>
40 #include <pulsecore/sink.h>
41 #include <pulsecore/source.h>
42 #include <pulsecore/core-util.h>
43 #include <pulsecore/log.h>
44 #include <pulsecore/modargs.h>
45 #include <pulsecore/dbus-shared.h>
46 #include <pulsecore/endianmacros.h>
47 #include <pulsecore/namereg.h>
48 #include <pulsecore/mime-type.h>
49 #include <pulsecore/strbuf.h>
50 #include <pulsecore/protocol-http.h>
51 #include <pulsecore/parseaddr.h>
53 #include "module-rygel-media-server-symdef.h"
55 PA_MODULE_AUTHOR("Lennart Poettering");
56 PA_MODULE_DESCRIPTION("UPnP MediaServer Plugin for Rygel");
57 PA_MODULE_VERSION(PACKAGE_VERSION
);
58 PA_MODULE_LOAD_ONCE(TRUE
);
60 "display_name=<UPnP Media Server name>");
62 /* This implements http://live.gnome.org/action/edit/Rygel/MediaServerSpec */
64 #define SERVICE_NAME "org.Rygel.MediaServer1.PulseAudio"
66 #define OBJECT_ROOT "/org/Rygel/MediaServer1/PulseAudio"
67 #define OBJECT_SINKS "/org/Rygel/MediaServer1/PulseAudio/Sinks"
68 #define OBJECT_SOURCES "/org/Rygel/MediaServer1/PulseAudio/Sources"
70 #define CONTAINER_INTROSPECT_XML_PREFIX \
71 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
73 " <!-- If you are looking for documentation make sure to check out" \
74 " http://live.gnome.org/Rygel/MediaServerSpec -->" \
75 " <interface name=\"org.Rygel.MediaContainer1\">" \
76 " <method name=\"GetContainers\">" \
77 " <arg name=\"children\" type=\"ao\" direction=\"out\"/>" \
79 " <method name=\"GetItems\">" \
80 " <arg name=\"children\" type=\"ao\" direction=\"out\"/>" \
82 " <signal name=\"Updated\">" \
83 " <arg name=\"path\" type=\"o\"/>" \
85 " <property name=\"item-count\" type=\"u\" access=\"read\"/>" \
86 " <property name=\"container-count\" type=\"u\" access=\"read\"/>" \
88 " <interface name=\"org.Rygel.MediaObject1\">" \
89 " <property name=\"icon-name\" type=\"s\" access=\"read\"/>" \
90 " <property name=\"display-name\" type=\"s\" access=\"read\"/>" \
92 " <interface name=\"org.freedesktop.DBus.Properties\">" \
93 " <method name=\"Get\">" \
94 " <arg name=\"interface\" direction=\"in\" type=\"s\"/>" \
95 " <arg name=\"property\" direction=\"in\" type=\"s\"/>" \
96 " <arg name=\"value\" direction=\"out\" type=\"v\"/>" \
98 " <method name=\"GetAll\">" \
99 " <arg name=\"interface\" direction=\"in\" type=\"s\"/>" \
100 " <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>" \
103 " <interface name=\"org.freedesktop.DBus.Introspectable\">" \
104 " <method name=\"Introspect\">" \
105 " <arg name=\"data\" type=\"s\" direction=\"out\"/>" \
109 #define CONTAINER_INTROSPECT_XML_POSTFIX \
112 #define ROOT_INTROSPECT_XML \
113 CONTAINER_INTROSPECT_XML_PREFIX \
114 "<node name=\"Sinks\"/>" \
115 "<node name=\"Sources\"/>" \
116 CONTAINER_INTROSPECT_XML_POSTFIX
118 #define ITEM_INTROSPECT_XML \
119 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
121 " <!-- If you are looking for documentation make sure to check out" \
122 " http://live.gnome.org/Rygel/MediaProviderSpec -->" \
123 " <interface name=\"org.Rygel.MediaItem1\">" \
124 " <property name=\"urls\" type=\"as\" access=\"read\"/>" \
125 " <property name=\"mime-type\" type=\"s\" access=\"read\"/>" \
126 " <property name=\"type\" type=\"s\" access=\"read\"/>" \
128 " <interface name=\"org.Rygel.MediaObject1\">" \
129 " <property name=\"display-name\" type=\"s\" access=\"read\"/>" \
131 " <interface name=\"org.freedesktop.DBus.Properties\">" \
132 " <method name=\"Get\">" \
133 " <arg name=\"interface\" direction=\"in\" type=\"s\"/>" \
134 " <arg name=\"property\" direction=\"in\" type=\"s\"/>" \
135 " <arg name=\"value\" direction=\"out\" type=\"v\"/>" \
137 " <method name=\"GetAll\">" \
138 " <arg name=\"interface\" direction=\"in\" type=\"s\"/>" \
139 " <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>" \
142 " <interface name=\"org.freedesktop.DBus.Introspectable\">" \
143 " <method name=\"Introspect\">" \
144 " <arg name=\"data\" type=\"s\" direction=\"out\"/>" \
150 static const char* const valid_modargs
[] = {
159 pa_dbus_connection
*bus
;
160 pa_bool_t got_name
:1;
164 pa_hook_slot
*source_new_slot
, *source_unlink_slot
;
166 pa_http_protocol
*http
;
169 static void send_signal(struct userdata
*u
, pa_source
*s
) {
174 pa_source_assert_ref(s
);
176 if (u
->core
->state
== PA_CORE_SHUTDOWN
)
180 parent
= OBJECT_SINKS
;
182 parent
= OBJECT_SOURCES
;
184 pa_assert_se(m
= dbus_message_new_signal(parent
, "org.Rygel.MediaContainer1", "Updated"));
185 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u
->bus
), m
, NULL
));
187 dbus_message_unref(m
);
190 static pa_hook_result_t
source_new_or_unlink_cb(pa_core
*c
, pa_source
*s
, struct userdata
*u
) {
192 pa_source_assert_ref(s
);
199 static pa_bool_t
message_is_property_get(DBusMessage
*m
, const char *interface
, const char *property
) {
203 dbus_error_init(&error
);
207 if (!dbus_message_is_method_call(m
, "org.freedesktop.DBus.Properties", "Get"))
210 if (!dbus_message_get_args(m
, &error
, DBUS_TYPE_STRING
, &i
, DBUS_TYPE_STRING
, &p
, DBUS_TYPE_INVALID
) || dbus_error_is_set(&error
)) {
211 dbus_error_free(&error
);
215 return pa_streq(i
, interface
) && pa_streq(p
, property
);
218 static pa_bool_t
message_is_property_get_all(DBusMessage
*m
, const char *interface
) {
222 dbus_error_init(&error
);
226 if (!dbus_message_is_method_call(m
, "org.freedesktop.DBus.Properties", "GetAll"))
229 if (!dbus_message_get_args(m
, &error
, DBUS_TYPE_STRING
, &i
, DBUS_TYPE_INVALID
) || dbus_error_is_set(&error
)) {
230 dbus_error_free(&error
);
234 return pa_streq(i
, interface
);
237 static void append_variant_string(DBusMessage
*m
, DBusMessageIter
*iter
, const char *s
) {
238 DBusMessageIter _iter
, sub
;
244 dbus_message_iter_init_append(m
, &_iter
);
248 pa_assert_se(dbus_message_iter_open_container(iter
, DBUS_TYPE_VARIANT
, "s", &sub
));
249 pa_assert_se(dbus_message_iter_append_basic(&sub
, DBUS_TYPE_STRING
, &s
));
250 pa_assert_se(dbus_message_iter_close_container(iter
, &sub
));
253 static void append_variant_unsigned(DBusMessage
*m
, DBusMessageIter
*iter
, unsigned u
) {
254 DBusMessageIter _iter
, sub
;
259 dbus_message_iter_init_append(m
, &_iter
);
263 pa_assert_se(dbus_message_iter_open_container(iter
, DBUS_TYPE_VARIANT
, "u", &sub
));
264 pa_assert_se(dbus_message_iter_append_basic(&sub
, DBUS_TYPE_UINT32
, &u
));
265 pa_assert_se(dbus_message_iter_close_container(iter
, &sub
));
268 static void append_property_dict_entry_string(DBusMessage
*m
, DBusMessageIter
*iter
, const char *name
, const char *value
) {
273 pa_assert_se(dbus_message_iter_open_container(iter
, DBUS_TYPE_DICT_ENTRY
, NULL
, &sub
));
274 pa_assert_se(dbus_message_iter_append_basic(&sub
, DBUS_TYPE_STRING
, &name
));
275 append_variant_string(m
, &sub
, value
);
276 pa_assert_se(dbus_message_iter_close_container(iter
, &sub
));
279 static void append_property_dict_entry_unsigned(DBusMessage
*m
, DBusMessageIter
*iter
, const char *name
, unsigned u
) {
284 pa_assert_se(dbus_message_iter_open_container(iter
, DBUS_TYPE_DICT_ENTRY
, NULL
, &sub
));
285 pa_assert_se(dbus_message_iter_append_basic(&sub
, DBUS_TYPE_STRING
, &name
));
286 append_variant_unsigned(m
, &sub
, u
);
287 pa_assert_se(dbus_message_iter_close_container(iter
, &sub
));
290 static DBusHandlerResult
root_handler(DBusConnection
*c
, DBusMessage
*m
, void *userdata
) {
291 struct userdata
*u
= userdata
;
292 DBusMessage
*r
= NULL
;
296 if (dbus_message_is_method_call(m
, "org.Rygel.MediaContainer1", "GetContainers")) {
297 const char * array
[] = { OBJECT_SINKS
, OBJECT_SOURCES
};
298 const char ** parray
= array
;
300 pa_assert_se(r
= dbus_message_new_method_return(m
));
301 pa_assert_se(dbus_message_append_args(
303 DBUS_TYPE_ARRAY
, DBUS_TYPE_OBJECT_PATH
, &parray
, 2,
306 } else if (dbus_message_is_method_call(m
, "org.Rygel.MediaContainer1", "GetItems")) {
307 const char * array
[] = { };
308 const char ** parray
= array
;
310 pa_assert_se(r
= dbus_message_new_method_return(m
));
311 pa_assert_se(dbus_message_append_args(
313 DBUS_TYPE_ARRAY
, DBUS_TYPE_OBJECT_PATH
, &parray
, 0,
316 } else if (message_is_property_get(m
, "org.Rygel.MediaObject1", "display-name")) {
317 pa_assert_se(r
= dbus_message_new_method_return(m
));
318 append_variant_string(r
, NULL
, u
->display_name
);
320 } else if (message_is_property_get(m
, "org.Rygel.MediaObject1", "icon-name")) {
321 pa_assert_se(r
= dbus_message_new_method_return(m
));
322 append_variant_string(r
, NULL
, "audio-card");
324 } else if (message_is_property_get_all(m
, "org.Rygel.MediaObject1")) {
325 DBusMessageIter iter
, sub
;
327 pa_assert_se(r
= dbus_message_new_method_return(m
));
328 dbus_message_iter_init_append(r
, &iter
);
330 pa_assert_se(dbus_message_iter_open_container(&iter
, DBUS_TYPE_ARRAY
, "{sv}", &sub
));
331 append_property_dict_entry_string(r
, &sub
, "display-name", u
->display_name
);
332 append_property_dict_entry_string(r
, &sub
, "icon-name", "audio-card");
333 pa_assert_se(dbus_message_iter_close_container(&iter
, &sub
));
335 } else if (message_is_property_get(m
, "org.Rygel.MediaContainer1", "item-count")) {
336 pa_assert_se(r
= dbus_message_new_method_return(m
));
337 append_variant_unsigned(r
, NULL
, 0);
339 } else if (message_is_property_get(m
, "org.Rygel.MediaContainer1", "container-count")) {
340 pa_assert_se(r
= dbus_message_new_method_return(m
));
341 append_variant_unsigned(r
, NULL
, 2);
343 } else if (message_is_property_get_all(m
, "org.Rygel.MediaContainer1")) {
344 DBusMessageIter iter
, sub
;
346 pa_assert_se(r
= dbus_message_new_method_return(m
));
347 dbus_message_iter_init_append(r
, &iter
);
349 pa_assert_se(dbus_message_iter_open_container(&iter
, DBUS_TYPE_ARRAY
, "{sv}", &sub
));
350 append_property_dict_entry_unsigned(r
, &sub
, "item-count", 0);
351 append_property_dict_entry_unsigned(r
, &sub
, "container-count", 2);
352 pa_assert_se(dbus_message_iter_close_container(&iter
, &sub
));
354 } else if (dbus_message_is_method_call(m
, "org.freedesktop.DBus.Introspectable", "Introspect")) {
355 const char *xml
= ROOT_INTROSPECT_XML
;
357 pa_assert_se(r
= dbus_message_new_method_return(m
));
358 pa_assert_se(dbus_message_append_args(
360 DBUS_TYPE_STRING
, &xml
,
364 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
367 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u
->bus
), r
, NULL
));
368 dbus_message_unref(r
);
371 return DBUS_HANDLER_RESULT_HANDLED
;
374 static char *compute_url(struct userdata
*u
, const char *name
) {
380 for (i
= pa_http_protocol_servers(u
->http
); i
; i
= pa_strlist_next(i
)) {
383 if (pa_parse_address(pa_strlist_data(i
), &a
) >= 0 &&
384 (a
.type
== PA_PARSED_ADDRESS_TCP4
||
385 a
.type
== PA_PARSED_ADDRESS_TCP6
||
386 a
.type
== PA_PARSED_ADDRESS_TCP_AUTO
)) {
391 if (pa_is_ip_address(a
.path_or_host
))
392 address
= a
.path_or_host
;
394 address
= "@ADDRESS@";
399 s
= pa_sprintf_malloc("http://%s:%u/listen/source/%s", address
, a
.port
, name
);
401 pa_xfree(a
.path_or_host
);
405 pa_xfree(a
.path_or_host
);
408 return pa_sprintf_malloc("http://@ADDRESS@:4714/listen/source/%s", name
);
411 static DBusHandlerResult
sinks_and_sources_handler(DBusConnection
*c
, DBusMessage
*m
, void *userdata
) {
412 struct userdata
*u
= userdata
;
413 DBusMessage
*r
= NULL
;
418 path
= dbus_message_get_path(m
);
420 if (pa_streq(path
, OBJECT_SINKS
) || pa_streq(path
, OBJECT_SOURCES
)) {
422 /* Container nodes */
424 if (dbus_message_is_method_call(m
, "org.Rygel.MediaContainer1", "GetContainers")) {
426 const char * array
[] = { };
427 const char ** parray
= array
;
429 pa_assert_se(r
= dbus_message_new_method_return(m
));
430 pa_assert_se(dbus_message_append_args(
432 DBUS_TYPE_ARRAY
, DBUS_TYPE_OBJECT_PATH
, &parray
, 0,
435 } else if (dbus_message_is_method_call(m
, "org.Rygel.MediaContainer1", "GetItems")) {
440 if (pa_streq(path
, OBJECT_SINKS
))
441 n
= pa_idxset_size(u
->core
->sinks
);
443 n
= pa_idxset_size(u
->core
->sources
);
445 array
= pa_xnew(char*, n
);
447 if (pa_streq(path
, OBJECT_SINKS
)) {
450 PA_IDXSET_FOREACH(sink
, u
->core
->sinks
, idx
)
451 array
[i
++] = pa_sprintf_malloc(OBJECT_SINKS
"/%u", sink
->index
);
455 PA_IDXSET_FOREACH(source
, u
->core
->sources
, idx
)
456 if (!source
->monitor_of
)
457 array
[i
++] = pa_sprintf_malloc(OBJECT_SOURCES
"/%u", source
->index
);
462 pa_assert_se(r
= dbus_message_new_method_return(m
));
463 pa_assert_se(dbus_message_append_args(
465 DBUS_TYPE_ARRAY
, DBUS_TYPE_OBJECT_PATH
, &array
, i
,
469 pa_xfree(array
[i
-1]);
473 } else if (message_is_property_get(m
, "org.Rygel.MediaObject1", "display-name")) {
475 pa_assert_se(r
= dbus_message_new_method_return(m
));
477 append_variant_string(r
,
479 pa_streq(path
, OBJECT_SINKS
) ?
480 _("Output Devices") :
483 } else if (message_is_property_get_all(m
, "org.Rygel.MediaObject1")) {
484 DBusMessageIter iter
, sub
;
486 pa_assert_se(r
= dbus_message_new_method_return(m
));
488 dbus_message_iter_init_append(r
, &iter
);
490 pa_assert_se(dbus_message_iter_open_container(&iter
, DBUS_TYPE_ARRAY
, "{sv}", &sub
));
491 append_property_dict_entry_string(m
, &sub
, "display-name",
492 pa_streq(path
, OBJECT_SINKS
) ?
493 _("Output Devices") :
495 pa_assert_se(dbus_message_iter_close_container(&iter
, &sub
));
497 } else if (message_is_property_get(m
, "org.Rygel.MediaContainer1", "item-count")) {
498 pa_assert_se(r
= dbus_message_new_method_return(m
));
500 append_variant_unsigned(r
, NULL
,
501 pa_streq(path
, OBJECT_SINKS
) ?
502 pa_idxset_size(u
->core
->sinks
) :
503 pa_idxset_size(u
->core
->sources
));
505 } else if (message_is_property_get(m
, "org.Rygel.MediaContainer1", "container-count")) {
506 pa_assert_se(r
= dbus_message_new_method_return(m
));
507 append_variant_unsigned(r
, NULL
, 0);
509 } else if (message_is_property_get_all(m
, "org.Rygel.MediaContainer1")) {
510 DBusMessageIter iter
, sub
;
512 pa_assert_se(r
= dbus_message_new_method_return(m
));
513 dbus_message_iter_init_append(r
, &iter
);
515 pa_assert_se(dbus_message_iter_open_container(&iter
, DBUS_TYPE_ARRAY
, "{sv}", &sub
));
516 append_property_dict_entry_unsigned(r
, &sub
, "item-count",
517 pa_streq(path
, OBJECT_SINKS
) ?
518 pa_idxset_size(u
->core
->sinks
) :
519 pa_idxset_size(u
->core
->sources
));
520 append_property_dict_entry_unsigned(r
, &sub
, "container-count", 0);
521 pa_assert_se(dbus_message_iter_close_container(&iter
, &sub
));
522 } else if (dbus_message_is_method_call(m
, "org.freedesktop.DBus.Introspectable", "Introspect")) {
527 sb
= pa_strbuf_new();
528 pa_strbuf_puts(sb
, CONTAINER_INTROSPECT_XML_PREFIX
);
530 if (pa_streq(path
, OBJECT_SINKS
)) {
533 PA_IDXSET_FOREACH(sink
, u
->core
->sinks
, idx
)
534 pa_strbuf_printf(sb
, "<node name=\"%u\"/>", sink
->index
);
538 PA_IDXSET_FOREACH(source
, u
->core
->sources
, idx
)
539 if (!source
->monitor_of
)
540 pa_strbuf_printf(sb
, "<node name=\"%u\"/>", source
->index
);
543 pa_strbuf_puts(sb
, CONTAINER_INTROSPECT_XML_POSTFIX
);
544 xml
= pa_strbuf_tostring_free(sb
);
546 pa_assert_se(r
= dbus_message_new_method_return(m
));
547 pa_assert_se(dbus_message_append_args(
549 DBUS_TYPE_STRING
, &xml
,
554 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
557 pa_sink
*sink
= NULL
;
558 pa_source
*source
= NULL
;
562 if (pa_startswith(path
, OBJECT_SINKS
"/"))
563 sink
= pa_namereg_get(u
->core
, path
+ sizeof(OBJECT_SINKS
), PA_NAMEREG_SINK
);
564 else if (pa_startswith(path
, OBJECT_SOURCES
"/"))
565 source
= pa_namereg_get(u
->core
, path
+ sizeof(OBJECT_SOURCES
), PA_NAMEREG_SOURCE
);
567 if (!sink
&& !source
)
568 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
570 if (message_is_property_get(m
, "org.Rygel.MediaObject1", "display-name")) {
571 pa_assert_se(r
= dbus_message_new_method_return(m
));
572 append_variant_string(r
, NULL
, pa_strna(pa_proplist_gets(sink
? sink
->proplist
: source
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)));
574 } else if (message_is_property_get(m
, "org.Rygel.MediaItem1", "type")) {
575 pa_assert_se(r
= dbus_message_new_method_return(m
));
576 append_variant_string(r
, NULL
, "audio");
578 } else if (message_is_property_get(m
, "org.Rygel.MediaItem1", "mime-type")) {
582 t
= pa_sample_spec_to_mime_type_mimefy(&sink
->sample_spec
, &sink
->channel_map
);
584 t
= pa_sample_spec_to_mime_type_mimefy(&source
->sample_spec
, &source
->channel_map
);
586 pa_assert_se(r
= dbus_message_new_method_return(m
));
587 append_variant_string(r
, NULL
, t
);
590 } else if (message_is_property_get(m
, "org.Rygel.MediaItem1", "urls")) {
591 DBusMessageIter iter
, sub
, array
;
594 pa_assert_se(r
= dbus_message_new_method_return(m
));
596 dbus_message_iter_init_append(r
, &iter
);
598 url
= compute_url(u
, sink
? sink
->monitor_source
->name
: source
->name
);
600 pa_assert_se(dbus_message_iter_open_container(&iter
, DBUS_TYPE_VARIANT
, "as", &sub
));
601 pa_assert_se(dbus_message_iter_open_container(&sub
, DBUS_TYPE_ARRAY
, "s", &array
));
602 pa_assert_se(dbus_message_iter_append_basic(&array
, DBUS_TYPE_STRING
, &url
));
603 pa_assert_se(dbus_message_iter_close_container(&sub
, &array
));
604 pa_assert_se(dbus_message_iter_close_container(&iter
, &sub
));
608 } else if (message_is_property_get_all(m
, "org.Rygel.MediaObject1")) {
609 DBusMessageIter iter
, sub
;
611 pa_assert_se(r
= dbus_message_new_method_return(m
));
612 dbus_message_iter_init_append(r
, &iter
);
614 pa_assert_se(dbus_message_iter_open_container(&iter
, DBUS_TYPE_ARRAY
, "{sv}", &sub
));
615 append_property_dict_entry_string(r
, &sub
, "display-name", pa_strna(pa_proplist_gets(sink
? sink
->proplist
: source
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)));
616 pa_assert_se(dbus_message_iter_close_container(&iter
, &sub
));
618 } else if (message_is_property_get_all(m
, "org.Rygel.MediaItem1")) {
619 DBusMessageIter iter
, sub
, dict
, variant
, array
;
621 const char *un
= "urls";
623 pa_assert_se(r
= dbus_message_new_method_return(m
));
624 dbus_message_iter_init_append(r
, &iter
);
626 pa_assert_se(dbus_message_iter_open_container(&iter
, DBUS_TYPE_ARRAY
, "{sv}", &sub
));
627 append_property_dict_entry_string(r
, &sub
, "type", "audio");
630 t
= pa_sample_spec_to_mime_type_mimefy(&sink
->sample_spec
, &sink
->channel_map
);
632 t
= pa_sample_spec_to_mime_type_mimefy(&source
->sample_spec
, &source
->channel_map
);
634 append_property_dict_entry_string(r
, &sub
, "mime-type", t
);
637 pa_assert_se(dbus_message_iter_open_container(&sub
, DBUS_TYPE_DICT_ENTRY
, NULL
, &dict
));
638 pa_assert_se(dbus_message_iter_append_basic(&dict
, DBUS_TYPE_STRING
, &un
));
640 url
= compute_url(u
, sink
? sink
->monitor_source
->name
: source
->name
);
642 pa_assert_se(dbus_message_iter_open_container(&dict
, DBUS_TYPE_VARIANT
, "as", &variant
));
643 pa_assert_se(dbus_message_iter_open_container(&variant
, DBUS_TYPE_ARRAY
, "s", &array
));
644 pa_assert_se(dbus_message_iter_append_basic(&array
, DBUS_TYPE_STRING
, &url
));
645 pa_assert_se(dbus_message_iter_close_container(&variant
, &array
));
646 pa_assert_se(dbus_message_iter_close_container(&dict
, &variant
));
647 pa_assert_se(dbus_message_iter_close_container(&sub
, &dict
));
651 pa_assert_se(dbus_message_iter_close_container(&iter
, &sub
));
653 } else if (dbus_message_is_method_call(m
, "org.freedesktop.DBus.Introspectable", "Introspect")) {
657 pa_assert_se(r
= dbus_message_new_method_return(m
));
658 pa_assert_se(dbus_message_append_args(
660 DBUS_TYPE_STRING
, &xml
,
664 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
668 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u
->bus
), r
, NULL
));
669 dbus_message_unref(r
);
672 return DBUS_HANDLER_RESULT_HANDLED
;
675 int pa__init(pa_module
*m
) {
678 pa_modargs
*ma
= NULL
;
682 static const DBusObjectPathVTable vtable_root
= {
683 .message_function
= root_handler
,
685 static const DBusObjectPathVTable vtable_sinks_and_sources
= {
686 .message_function
= sinks_and_sources_handler
,
689 dbus_error_init(&error
);
691 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
692 pa_log("Failed to parse module arguments.");
696 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
699 u
->http
= pa_http_protocol_get(u
->core
);
701 if ((t
= pa_modargs_get_value(ma
, "display_name", NULL
)))
702 u
->display_name
= pa_utf8_filter(t
);
704 u
->display_name
= pa_xstrdup(_("Audio on @HOSTNAME@"));
706 u
->source_new_slot
= pa_hook_connect(&m
->core
->hooks
[PA_CORE_HOOK_SOURCE_PUT
], PA_HOOK_LATE
, (pa_hook_cb_t
) source_new_or_unlink_cb
, u
);
707 u
->source_unlink_slot
= pa_hook_connect(&m
->core
->hooks
[PA_CORE_HOOK_SOURCE_UNLINK
], PA_HOOK_LATE
, (pa_hook_cb_t
) source_new_or_unlink_cb
, u
);
709 if (!(u
->bus
= pa_dbus_bus_get(m
->core
, DBUS_BUS_SESSION
, &error
))) {
710 pa_log("Failed to get session bus connection: %s", error
.message
);
714 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(u
->bus
), OBJECT_ROOT
, &vtable_root
, u
));
715 pa_assert_se(dbus_connection_register_fallback(pa_dbus_connection_get(u
->bus
), OBJECT_SINKS
, &vtable_sinks_and_sources
, u
));
716 pa_assert_se(dbus_connection_register_fallback(pa_dbus_connection_get(u
->bus
), OBJECT_SOURCES
, &vtable_sinks_and_sources
, u
));
718 if (dbus_bus_request_name(pa_dbus_connection_get(u
->bus
), SERVICE_NAME
, DBUS_NAME_FLAG_DO_NOT_QUEUE
, &error
) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER
) {
719 pa_log("Failed to request service name " SERVICE_NAME
": %s", error
.message
);
735 dbus_error_free(&error
);
740 void pa__done(pa_module
*m
) {
744 if (!(u
= m
->userdata
))
747 if (u
->source_new_slot
)
748 pa_hook_slot_free(u
->source_new_slot
);
749 if (u
->source_unlink_slot
)
750 pa_hook_slot_free(u
->source_unlink_slot
);
755 dbus_error_init(&error
);
757 dbus_connection_unregister_object_path(pa_dbus_connection_get(u
->bus
), OBJECT_ROOT
);
758 dbus_connection_unregister_object_path(pa_dbus_connection_get(u
->bus
), OBJECT_SINKS
);
759 dbus_connection_unregister_object_path(pa_dbus_connection_get(u
->bus
), OBJECT_SOURCES
);
762 if (dbus_bus_release_name(pa_dbus_connection_get(u
->bus
), SERVICE_NAME
, &error
) != DBUS_RELEASE_NAME_REPLY_RELEASED
) {
763 pa_log("Failed to release service name " SERVICE_NAME
": %s", error
.message
);
764 dbus_error_free(&error
);
768 pa_dbus_connection_unref(u
->bus
);
771 pa_xfree(u
->display_name
);
774 pa_http_protocol_unref(u
->http
);