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
31 #include <pulse/xmalloc.h>
32 #include <pulse/util.h>
33 #include <pulse/i18n.h>
34 #include <pulse/utf8.h>
36 #include <pulsecore/sink.h>
37 #include <pulsecore/source.h>
38 #include <pulsecore/core-util.h>
39 #include <pulsecore/log.h>
40 #include <pulsecore/modargs.h>
41 #include <pulsecore/dbus-shared.h>
42 #include <pulsecore/endianmacros.h>
43 #include <pulsecore/namereg.h>
44 #include <pulsecore/mime-type.h>
45 #include <pulsecore/strbuf.h>
46 #include <pulsecore/protocol-http.h>
47 #include <pulsecore/parseaddr.h>
49 #include "module-rygel-media-server-symdef.h"
51 PA_MODULE_AUTHOR("Lennart Poettering");
52 PA_MODULE_DESCRIPTION("UPnP MediaServer Plugin for Rygel");
53 PA_MODULE_VERSION(PACKAGE_VERSION
);
54 PA_MODULE_LOAD_ONCE(TRUE
);
56 "display_name=<UPnP Media Server name>");
58 /* This implements http://live.gnome.org/Rygel/MediaServerSpec */
60 #define SERVICE_NAME "org.gnome.UPnP.MediaServer1.PulseAudio"
62 #define OBJECT_ROOT "/org/gnome/UPnP/MediaServer1/PulseAudio"
63 #define OBJECT_SINKS "/org/gnome/UPnP/MediaServer1/PulseAudio/Sinks"
64 #define OBJECT_SOURCES "/org/gnome/UPnP/MediaServer1/PulseAudio/Sources"
66 #define CONTAINER_INTROSPECT_XML_PREFIX \
67 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
69 " <!-- If you are looking for documentation make sure to check out" \
70 " http://live.gnome.org/Rygel/MediaServerSpec -->" \
71 " <interface name=\"org.gnome.UPnP.MediaContainer1\">" \
72 " <signal name=\"Updated\">" \
73 " <arg name=\"path\" type=\"o\"/>" \
75 " <property name=\"Items\" type=\"ao\" access=\"read\"/>" \
76 " <property name=\"ItemCount\" type=\"u\" access=\"read\"/>" \
77 " <property name=\"Containers\" type=\"ao\" access=\"read\"/>" \
78 " <property name=\"ContainerCount\" type=\"u\" access=\"read\"/>" \
80 " <interface name=\"org.gnome.UPnP.MediaObject1\">" \
81 " <property name=\"Parent\" type=\"s\" access=\"read\"/>" \
82 " <property name=\"DisplayName\" type=\"s\" access=\"read\"/>" \
84 " <interface name=\"org.freedesktop.DBus.Properties\">" \
85 " <method name=\"Get\">" \
86 " <arg name=\"interface\" direction=\"in\" type=\"s\"/>" \
87 " <arg name=\"property\" direction=\"in\" type=\"s\"/>" \
88 " <arg name=\"value\" direction=\"out\" type=\"v\"/>" \
90 " <method name=\"GetAll\">" \
91 " <arg name=\"interface\" direction=\"in\" type=\"s\"/>" \
92 " <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>" \
95 " <interface name=\"org.freedesktop.DBus.Introspectable\">" \
96 " <method name=\"Introspect\">" \
97 " <arg name=\"data\" type=\"s\" direction=\"out\"/>" \
101 #define CONTAINER_INTROSPECT_XML_POSTFIX \
104 #define ROOT_INTROSPECT_XML \
105 CONTAINER_INTROSPECT_XML_PREFIX \
106 "<node name=\"Sinks\"/>" \
107 "<node name=\"Sources\"/>" \
108 CONTAINER_INTROSPECT_XML_POSTFIX
110 #define ITEM_INTROSPECT_XML \
111 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
113 " <!-- If you are looking for documentation make sure to check out" \
114 " http://live.gnome.org/Rygel/MediaProviderSpec -->" \
115 " <interface name=\"org.gnome.UPnP.MediaItem1\">" \
116 " <property name=\"URLs\" type=\"as\" access=\"read\"/>" \
117 " <property name=\"MIMEType\" type=\"s\" access=\"read\"/>" \
118 " <property name=\"Type\" type=\"s\" access=\"read\"/>" \
120 " <interface name=\"org.gnome.UPnP.MediaObject1\">" \
121 " <property name=\"Parent\" type=\"s\" access=\"read\"/>" \
122 " <property name=\"DisplayName\" type=\"s\" access=\"read\"/>" \
124 " <interface name=\"org.freedesktop.DBus.Properties\">" \
125 " <method name=\"Get\">" \
126 " <arg name=\"interface\" direction=\"in\" type=\"s\"/>" \
127 " <arg name=\"property\" direction=\"in\" type=\"s\"/>" \
128 " <arg name=\"value\" direction=\"out\" type=\"v\"/>" \
130 " <method name=\"GetAll\">" \
131 " <arg name=\"interface\" direction=\"in\" type=\"s\"/>" \
132 " <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>" \
135 " <interface name=\"org.freedesktop.DBus.Introspectable\">" \
136 " <method name=\"Introspect\">" \
137 " <arg name=\"data\" type=\"s\" direction=\"out\"/>" \
143 static const char* const valid_modargs
[] = {
152 pa_dbus_connection
*bus
;
153 pa_bool_t got_name
:1;
157 pa_hook_slot
*source_new_slot
, *source_unlink_slot
;
159 pa_http_protocol
*http
;
162 static void send_signal(struct userdata
*u
, pa_source
*s
) {
167 pa_source_assert_ref(s
);
169 if (u
->core
->state
== PA_CORE_SHUTDOWN
)
173 parent
= OBJECT_SINKS
;
175 parent
= OBJECT_SOURCES
;
177 pa_assert_se(m
= dbus_message_new_signal(parent
, "org.gnome.UPnP.MediaContainer1", "Updated"));
178 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u
->bus
), m
, NULL
));
180 dbus_message_unref(m
);
183 static pa_hook_result_t
source_new_or_unlink_cb(pa_core
*c
, pa_source
*s
, struct userdata
*u
) {
185 pa_source_assert_ref(s
);
192 static pa_bool_t
message_is_property_get(DBusMessage
*m
, const char *interface
, const char *property
) {
196 dbus_error_init(&error
);
200 if (!dbus_message_is_method_call(m
, "org.freedesktop.DBus.Properties", "Get"))
203 if (!dbus_message_get_args(m
, &error
, DBUS_TYPE_STRING
, &i
, DBUS_TYPE_STRING
, &p
, DBUS_TYPE_INVALID
) || dbus_error_is_set(&error
)) {
204 dbus_error_free(&error
);
208 return pa_streq(i
, interface
) && pa_streq(p
, property
);
211 static pa_bool_t
message_is_property_get_all(DBusMessage
*m
, const char *interface
) {
215 dbus_error_init(&error
);
219 if (!dbus_message_is_method_call(m
, "org.freedesktop.DBus.Properties", "GetAll"))
222 if (!dbus_message_get_args(m
, &error
, DBUS_TYPE_STRING
, &i
, DBUS_TYPE_INVALID
) || dbus_error_is_set(&error
)) {
223 dbus_error_free(&error
);
227 return pa_streq(i
, interface
);
230 static void append_variant_object_array(DBusMessage
*m
, DBusMessageIter
*iter
, const char *path
[], unsigned n
) {
231 DBusMessageIter _iter
, variant
, array
;
238 dbus_message_iter_init_append(m
, &_iter
);
242 pa_assert_se(dbus_message_iter_open_container(iter
, DBUS_TYPE_VARIANT
, "ao", &variant
));
243 pa_assert_se(dbus_message_iter_open_container(&variant
, DBUS_TYPE_ARRAY
, "o", &array
));
245 for (c
= 0; c
< n
; c
++)
246 pa_assert_se(dbus_message_iter_append_basic(&array
, DBUS_TYPE_OBJECT_PATH
, path
+ c
));
248 pa_assert_se(dbus_message_iter_close_container(&variant
, &array
));
249 pa_assert_se(dbus_message_iter_close_container(iter
, &variant
));
252 static void append_variant_string(DBusMessage
*m
, DBusMessageIter
*iter
, const char *s
) {
253 DBusMessageIter _iter
, sub
;
259 dbus_message_iter_init_append(m
, &_iter
);
263 pa_assert_se(dbus_message_iter_open_container(iter
, DBUS_TYPE_VARIANT
, "s", &sub
));
264 pa_assert_se(dbus_message_iter_append_basic(&sub
, DBUS_TYPE_STRING
, &s
));
265 pa_assert_se(dbus_message_iter_close_container(iter
, &sub
));
268 static void append_variant_object(DBusMessage
*m
, DBusMessageIter
*iter
, const char *s
) {
269 DBusMessageIter _iter
, sub
;
275 dbus_message_iter_init_append(m
, &_iter
);
279 pa_assert_se(dbus_message_iter_open_container(iter
, DBUS_TYPE_VARIANT
, "o", &sub
));
280 pa_assert_se(dbus_message_iter_append_basic(&sub
, DBUS_TYPE_OBJECT_PATH
, &s
));
281 pa_assert_se(dbus_message_iter_close_container(iter
, &sub
));
284 static void append_variant_unsigned(DBusMessage
*m
, DBusMessageIter
*iter
, unsigned u
) {
285 DBusMessageIter _iter
, sub
;
290 dbus_message_iter_init_append(m
, &_iter
);
294 pa_assert_se(dbus_message_iter_open_container(iter
, DBUS_TYPE_VARIANT
, "u", &sub
));
295 pa_assert_se(dbus_message_iter_append_basic(&sub
, DBUS_TYPE_UINT32
, &u
));
296 pa_assert_se(dbus_message_iter_close_container(iter
, &sub
));
299 static void append_property_dict_entry_object_array(DBusMessage
*m
, DBusMessageIter
*iter
, const char *name
, const char *path
[], unsigned n
) {
304 pa_assert_se(dbus_message_iter_open_container(iter
, DBUS_TYPE_DICT_ENTRY
, NULL
, &sub
));
305 pa_assert_se(dbus_message_iter_append_basic(&sub
, DBUS_TYPE_STRING
, &name
));
306 append_variant_object_array(m
, &sub
, path
, n
);
307 pa_assert_se(dbus_message_iter_close_container(iter
, &sub
));
310 static void append_property_dict_entry_string(DBusMessage
*m
, DBusMessageIter
*iter
, const char *name
, const char *value
) {
315 pa_assert_se(dbus_message_iter_open_container(iter
, DBUS_TYPE_DICT_ENTRY
, NULL
, &sub
));
316 pa_assert_se(dbus_message_iter_append_basic(&sub
, DBUS_TYPE_STRING
, &name
));
317 append_variant_string(m
, &sub
, value
);
318 pa_assert_se(dbus_message_iter_close_container(iter
, &sub
));
321 static void append_property_dict_entry_object(DBusMessage
*m
, DBusMessageIter
*iter
, const char *name
, const char *value
) {
326 pa_assert_se(dbus_message_iter_open_container(iter
, DBUS_TYPE_DICT_ENTRY
, NULL
, &sub
));
327 pa_assert_se(dbus_message_iter_append_basic(&sub
, DBUS_TYPE_STRING
, &name
));
328 append_variant_object(m
, &sub
, value
);
329 pa_assert_se(dbus_message_iter_close_container(iter
, &sub
));
332 static void append_property_dict_entry_unsigned(DBusMessage
*m
, DBusMessageIter
*iter
, const char *name
, unsigned u
) {
337 pa_assert_se(dbus_message_iter_open_container(iter
, DBUS_TYPE_DICT_ENTRY
, NULL
, &sub
));
338 pa_assert_se(dbus_message_iter_append_basic(&sub
, DBUS_TYPE_STRING
, &name
));
339 append_variant_unsigned(m
, &sub
, u
);
340 pa_assert_se(dbus_message_iter_close_container(iter
, &sub
));
343 static const char *array_root_containers
[] = { OBJECT_SINKS
, OBJECT_SOURCES
};
344 static const char *array_no_children
[] = { };
346 static DBusHandlerResult
root_handler(DBusConnection
*c
, DBusMessage
*m
, void *userdata
) {
347 struct userdata
*u
= userdata
;
348 DBusMessage
*r
= NULL
;
352 if (message_is_property_get(m
, "org.gnome.UPnP.MediaContainer1", "Containers")) {
353 pa_assert_se(r
= dbus_message_new_method_return(m
));
354 append_variant_object_array(r
, NULL
, (const char**) array_root_containers
, PA_ELEMENTSOF(array_root_containers
));
356 } else if (message_is_property_get(m
, "org.gnome.UPnP.MediaContainer1", "ContainerCount")) {
357 pa_assert_se(r
= dbus_message_new_method_return(m
));
358 append_variant_unsigned(r
, NULL
, PA_ELEMENTSOF(array_root_containers
));
360 } else if (message_is_property_get(m
, "org.gnome.UPnP.MediaContainer1", "Items")) {
361 pa_assert_se(r
= dbus_message_new_method_return(m
));
362 append_variant_object_array(r
, NULL
, array_no_children
, PA_ELEMENTSOF(array_no_children
));
364 } else if (message_is_property_get(m
, "org.gnome.UPnP.MediaContainer1", "ItemCount")) {
365 pa_assert_se(r
= dbus_message_new_method_return(m
));
366 append_variant_unsigned(r
, NULL
, PA_ELEMENTSOF(array_no_children
));
368 } else if (message_is_property_get_all(m
, "org.gnome.UPnP.MediaContainer1")) {
369 DBusMessageIter iter
, sub
;
371 pa_assert_se(r
= dbus_message_new_method_return(m
));
372 dbus_message_iter_init_append(r
, &iter
);
374 pa_assert_se(dbus_message_iter_open_container(&iter
, DBUS_TYPE_ARRAY
, "{sv}", &sub
));
375 append_property_dict_entry_object_array(r
, &sub
, "Containers", array_root_containers
, PA_ELEMENTSOF(array_root_containers
));
376 append_property_dict_entry_unsigned(r
, &sub
, "ContainerCount", PA_ELEMENTSOF(array_root_containers
));
377 append_property_dict_entry_object_array(r
, &sub
, "Items", array_no_children
, PA_ELEMENTSOF(array_no_children
));
378 append_property_dict_entry_unsigned(r
, &sub
, "ItemCount", PA_ELEMENTSOF(array_no_children
));
379 pa_assert_se(dbus_message_iter_close_container(&iter
, &sub
));
381 } else if (message_is_property_get(m
, "org.gnome.UPnP.MediaObject1", "Parent")) {
382 pa_assert_se(r
= dbus_message_new_method_return(m
));
383 append_variant_object(r
, NULL
, OBJECT_ROOT
);
385 } else if (message_is_property_get(m
, "org.gnome.UPnP.MediaObject1", "DisplayName")) {
386 pa_assert_se(r
= dbus_message_new_method_return(m
));
387 append_variant_string(r
, NULL
, u
->display_name
);
389 } else if (message_is_property_get_all(m
, "org.gnome.UPnP.MediaObject1")) {
390 DBusMessageIter iter
, sub
;
392 pa_assert_se(r
= dbus_message_new_method_return(m
));
393 dbus_message_iter_init_append(r
, &iter
);
395 pa_assert_se(dbus_message_iter_open_container(&iter
, DBUS_TYPE_ARRAY
, "{sv}", &sub
));
396 append_property_dict_entry_string(r
, &sub
, "DisplayName", u
->display_name
);
397 pa_assert_se(dbus_message_iter_close_container(&iter
, &sub
));
399 } else if (dbus_message_is_method_call(m
, "org.freedesktop.DBus.Introspectable", "Introspect")) {
400 const char *xml
= ROOT_INTROSPECT_XML
;
402 pa_assert_se(r
= dbus_message_new_method_return(m
));
403 pa_assert_se(dbus_message_append_args(
405 DBUS_TYPE_STRING
, &xml
,
409 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
412 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u
->bus
), r
, NULL
));
413 dbus_message_unref(r
);
416 return DBUS_HANDLER_RESULT_HANDLED
;
419 static char *compute_url(struct userdata
*u
, const char *name
) {
425 for (i
= pa_http_protocol_servers(u
->http
); i
; i
= pa_strlist_next(i
)) {
428 if (pa_parse_address(pa_strlist_data(i
), &a
) >= 0 &&
429 (a
.type
== PA_PARSED_ADDRESS_TCP4
||
430 a
.type
== PA_PARSED_ADDRESS_TCP6
||
431 a
.type
== PA_PARSED_ADDRESS_TCP_AUTO
)) {
436 if (pa_is_ip_address(a
.path_or_host
))
437 address
= a
.path_or_host
;
439 address
= "@ADDRESS@";
444 s
= pa_sprintf_malloc("http://%s:%u/listen/source/%s", address
, a
.port
, name
);
446 pa_xfree(a
.path_or_host
);
450 pa_xfree(a
.path_or_host
);
453 return pa_sprintf_malloc("http://@ADDRESS@:4714/listen/source/%s", name
);
456 static char **child_array(struct userdata
*u
, const char *path
, unsigned *n
) {
465 if (pa_streq(path
, OBJECT_SINKS
))
466 m
= pa_idxset_size(u
->core
->sinks
);
470 m
= pa_idxset_size(u
->core
->sources
);
471 k
= pa_idxset_size(u
->core
->sinks
);
475 /* Subtract the monitor sources from the numbers of
476 * sources. There is one monitor source for each sink */
480 array
= pa_xnew(char*, m
);
483 if (pa_streq(path
, OBJECT_SINKS
)) {
486 PA_IDXSET_FOREACH(sink
, u
->core
->sinks
, idx
) {
488 array
[(*n
)++] = pa_sprintf_malloc(OBJECT_SINKS
"/%u", sink
->index
);
493 PA_IDXSET_FOREACH(source
, u
->core
->sources
, idx
) {
495 if (!source
->monitor_of
) {
497 array
[(*n
)++] = pa_sprintf_malloc(OBJECT_SOURCES
"/%u", source
->index
);
502 pa_assert((*n
) <= m
);
507 static void free_child_array(char **array
, unsigned n
) {
510 pa_xfree(array
[n
-1]);
515 static DBusHandlerResult
sinks_and_sources_handler(DBusConnection
*c
, DBusMessage
*m
, void *userdata
) {
516 struct userdata
*u
= userdata
;
517 DBusMessage
*r
= NULL
;
522 path
= dbus_message_get_path(m
);
524 if (pa_streq(path
, OBJECT_SINKS
) || pa_streq(path
, OBJECT_SOURCES
)) {
526 /* Container nodes */
528 if (message_is_property_get(m
, "org.gnome.UPnP.MediaContainer1", "Containers")) {
529 pa_assert_se(r
= dbus_message_new_method_return(m
));
530 append_variant_object_array(r
, NULL
, array_no_children
, PA_ELEMENTSOF(array_no_children
));
532 } else if (message_is_property_get(m
, "org.gnome.UPnP.MediaContainer1", "ContainerCount")) {
533 pa_assert_se(r
= dbus_message_new_method_return(m
));
534 append_variant_unsigned(r
, NULL
, PA_ELEMENTSOF(array_no_children
));
536 } else if (message_is_property_get(m
, "org.gnome.UPnP.MediaContainer1", "Items")) {
540 array
= child_array(u
, path
, &n
);
542 pa_assert_se(r
= dbus_message_new_method_return(m
));
543 append_variant_object_array(r
, NULL
, (const char**) array
, n
);
545 free_child_array(array
, n
);
547 } else if (message_is_property_get(m
, "org.gnome.UPnP.MediaContainer1", "ItemCount")) {
550 n
= pa_idxset_size(u
->core
->sinks
);
551 k
= pa_idxset_size(u
->core
->sources
);
554 pa_assert_se(r
= dbus_message_new_method_return(m
));
555 append_variant_unsigned(r
, NULL
,
556 pa_streq(path
, OBJECT_SINKS
) ? n
: k
- n
);
558 } else if (message_is_property_get_all(m
, "org.gnome.UPnP.MediaContainer1")) {
559 DBusMessageIter iter
, sub
;
563 pa_assert_se(r
= dbus_message_new_method_return(m
));
564 dbus_message_iter_init_append(r
, &iter
);
566 pa_assert_se(dbus_message_iter_open_container(&iter
, DBUS_TYPE_ARRAY
, "{sv}", &sub
));
567 append_property_dict_entry_object_array(r
, &sub
, "Containers", array_no_children
, PA_ELEMENTSOF(array_no_children
));
568 append_property_dict_entry_unsigned(r
, &sub
, "ContainerCount", 0);
570 array
= child_array(u
, path
, &n
);
571 append_property_dict_entry_object_array(r
, &sub
, "Items", (const char**) array
, n
);
572 free_child_array(array
, n
);
574 n
= pa_idxset_size(u
->core
->sinks
);
575 k
= pa_idxset_size(u
->core
->sources
);
578 append_property_dict_entry_unsigned(r
, &sub
, "ItemCount",
579 pa_streq(path
, OBJECT_SINKS
) ? n
: k
- n
);
581 pa_assert_se(dbus_message_iter_close_container(&iter
, &sub
));
583 } else if (message_is_property_get(m
, "org.gnome.UPnP.MediaObject1", "Parent")) {
584 pa_assert_se(r
= dbus_message_new_method_return(m
));
585 append_variant_object(r
, NULL
, OBJECT_ROOT
);
587 } else if (message_is_property_get(m
, "org.gnome.UPnP.MediaObject1", "DisplayName")) {
588 pa_assert_se(r
= dbus_message_new_method_return(m
));
589 append_variant_string(r
,
591 pa_streq(path
, OBJECT_SINKS
) ?
592 _("Output Devices") :
595 } else if (message_is_property_get_all(m
, "org.gnome.UPnP.MediaObject1")) {
596 DBusMessageIter iter
, sub
;
598 pa_assert_se(r
= dbus_message_new_method_return(m
));
600 dbus_message_iter_init_append(r
, &iter
);
602 pa_assert_se(dbus_message_iter_open_container(&iter
, DBUS_TYPE_ARRAY
, "{sv}", &sub
));
603 append_property_dict_entry_object(m
, &sub
, "Parent", OBJECT_ROOT
);
604 append_property_dict_entry_string(m
, &sub
, "DisplayName",
605 pa_streq(path
, OBJECT_SINKS
) ?
606 _("Output Devices") :
608 pa_assert_se(dbus_message_iter_close_container(&iter
, &sub
));
610 } else if (dbus_message_is_method_call(m
, "org.freedesktop.DBus.Introspectable", "Introspect")) {
615 sb
= pa_strbuf_new();
616 pa_strbuf_puts(sb
, CONTAINER_INTROSPECT_XML_PREFIX
);
618 if (pa_streq(path
, OBJECT_SINKS
)) {
621 PA_IDXSET_FOREACH(sink
, u
->core
->sinks
, idx
)
622 pa_strbuf_printf(sb
, "<node name=\"%u\"/>", sink
->index
);
626 PA_IDXSET_FOREACH(source
, u
->core
->sources
, idx
)
627 if (!source
->monitor_of
)
628 pa_strbuf_printf(sb
, "<node name=\"%u\"/>", source
->index
);
631 pa_strbuf_puts(sb
, CONTAINER_INTROSPECT_XML_POSTFIX
);
632 xml
= pa_strbuf_tostring_free(sb
);
634 pa_assert_se(r
= dbus_message_new_method_return(m
));
635 pa_assert_se(dbus_message_append_args(
637 DBUS_TYPE_STRING
, &xml
,
642 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
645 pa_sink
*sink
= NULL
;
646 pa_source
*source
= NULL
;
650 if (pa_startswith(path
, OBJECT_SINKS
"/"))
651 sink
= pa_namereg_get(u
->core
, path
+ sizeof(OBJECT_SINKS
), PA_NAMEREG_SINK
);
652 else if (pa_startswith(path
, OBJECT_SOURCES
"/"))
653 source
= pa_namereg_get(u
->core
, path
+ sizeof(OBJECT_SOURCES
), PA_NAMEREG_SOURCE
);
655 if (!sink
&& !source
)
656 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
658 if (message_is_property_get(m
, "org.gnome.UPnP.MediaObject1", "Parent")) {
659 pa_assert_se(r
= dbus_message_new_method_return(m
));
660 append_variant_object(r
, NULL
, sink
? OBJECT_SINKS
: OBJECT_SOURCES
);
662 } else if (message_is_property_get(m
, "org.gnome.UPnP.MediaObject1", "DisplayName")) {
663 pa_assert_se(r
= dbus_message_new_method_return(m
));
664 append_variant_string(r
, NULL
, pa_strna(pa_proplist_gets(sink
? sink
->proplist
: source
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)));
666 } else if (message_is_property_get_all(m
, "org.gnome.UPnP.MediaObject1")) {
667 DBusMessageIter iter
, sub
;
669 pa_assert_se(r
= dbus_message_new_method_return(m
));
670 dbus_message_iter_init_append(r
, &iter
);
672 pa_assert_se(dbus_message_iter_open_container(&iter
, DBUS_TYPE_ARRAY
, "{sv}", &sub
));
673 append_property_dict_entry_object(r
, &sub
, "Parent", sink
? OBJECT_SINKS
: OBJECT_SOURCES
);
674 append_property_dict_entry_string(r
, &sub
, "DisplayName", pa_strna(pa_proplist_gets(sink
? sink
->proplist
: source
->proplist
, PA_PROP_DEVICE_DESCRIPTION
)));
675 pa_assert_se(dbus_message_iter_close_container(&iter
, &sub
));
677 } else if (message_is_property_get(m
, "org.gnome.UPnP.MediaItem1", "Type")) {
678 pa_assert_se(r
= dbus_message_new_method_return(m
));
679 append_variant_string(r
, NULL
, "audio");
681 } else if (message_is_property_get(m
, "org.gnome.UPnP.MediaItem1", "MIMEType")) {
685 t
= pa_sample_spec_to_mime_type_mimefy(&sink
->sample_spec
, &sink
->channel_map
);
687 t
= pa_sample_spec_to_mime_type_mimefy(&source
->sample_spec
, &source
->channel_map
);
689 pa_assert_se(r
= dbus_message_new_method_return(m
));
690 append_variant_string(r
, NULL
, t
);
693 } else if (message_is_property_get(m
, "org.gnome.UPnP.MediaItem1", "URLs")) {
694 DBusMessageIter iter
, sub
, array
;
697 pa_assert_se(r
= dbus_message_new_method_return(m
));
699 dbus_message_iter_init_append(r
, &iter
);
701 url
= compute_url(u
, sink
? sink
->monitor_source
->name
: source
->name
);
703 pa_assert_se(dbus_message_iter_open_container(&iter
, DBUS_TYPE_VARIANT
, "as", &sub
));
704 pa_assert_se(dbus_message_iter_open_container(&sub
, DBUS_TYPE_ARRAY
, "s", &array
));
705 pa_assert_se(dbus_message_iter_append_basic(&array
, DBUS_TYPE_STRING
, &url
));
706 pa_assert_se(dbus_message_iter_close_container(&sub
, &array
));
707 pa_assert_se(dbus_message_iter_close_container(&iter
, &sub
));
711 } else if (message_is_property_get_all(m
, "org.gnome.UPnP.MediaItem1")) {
712 DBusMessageIter iter
, sub
, dict
, variant
, array
;
714 const char *un
= "URLs";
716 pa_assert_se(r
= dbus_message_new_method_return(m
));
717 dbus_message_iter_init_append(r
, &iter
);
719 pa_assert_se(dbus_message_iter_open_container(&iter
, DBUS_TYPE_ARRAY
, "{sv}", &sub
));
720 append_property_dict_entry_string(r
, &sub
, "Type", "audio");
723 t
= pa_sample_spec_to_mime_type_mimefy(&sink
->sample_spec
, &sink
->channel_map
);
725 t
= pa_sample_spec_to_mime_type_mimefy(&source
->sample_spec
, &source
->channel_map
);
727 append_property_dict_entry_string(r
, &sub
, "MIMEType", t
);
730 pa_assert_se(dbus_message_iter_open_container(&sub
, DBUS_TYPE_DICT_ENTRY
, NULL
, &dict
));
731 pa_assert_se(dbus_message_iter_append_basic(&dict
, DBUS_TYPE_STRING
, &un
));
733 url
= compute_url(u
, sink
? sink
->monitor_source
->name
: source
->name
);
735 pa_assert_se(dbus_message_iter_open_container(&dict
, DBUS_TYPE_VARIANT
, "as", &variant
));
736 pa_assert_se(dbus_message_iter_open_container(&variant
, DBUS_TYPE_ARRAY
, "s", &array
));
737 pa_assert_se(dbus_message_iter_append_basic(&array
, DBUS_TYPE_STRING
, &url
));
738 pa_assert_se(dbus_message_iter_close_container(&variant
, &array
));
739 pa_assert_se(dbus_message_iter_close_container(&dict
, &variant
));
740 pa_assert_se(dbus_message_iter_close_container(&sub
, &dict
));
744 pa_assert_se(dbus_message_iter_close_container(&iter
, &sub
));
746 } else if (dbus_message_is_method_call(m
, "org.freedesktop.DBus.Introspectable", "Introspect")) {
750 pa_assert_se(r
= dbus_message_new_method_return(m
));
751 pa_assert_se(dbus_message_append_args(
753 DBUS_TYPE_STRING
, &xml
,
757 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
761 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u
->bus
), r
, NULL
));
762 dbus_message_unref(r
);
765 return DBUS_HANDLER_RESULT_HANDLED
;
768 int pa__init(pa_module
*m
) {
771 pa_modargs
*ma
= NULL
;
775 static const DBusObjectPathVTable vtable_root
= {
776 .message_function
= root_handler
,
778 static const DBusObjectPathVTable vtable_sinks_and_sources
= {
779 .message_function
= sinks_and_sources_handler
,
782 dbus_error_init(&error
);
784 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
785 pa_log("Failed to parse module arguments.");
789 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
792 u
->http
= pa_http_protocol_get(u
->core
);
794 if ((t
= pa_modargs_get_value(ma
, "display_name", NULL
)))
795 u
->display_name
= pa_utf8_filter(t
);
797 u
->display_name
= pa_xstrdup(_("Audio on @HOSTNAME@"));
799 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
);
800 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
);
802 if (!(u
->bus
= pa_dbus_bus_get(m
->core
, DBUS_BUS_SESSION
, &error
))) {
803 pa_log("Failed to get session bus connection: %s", error
.message
);
807 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(u
->bus
), OBJECT_ROOT
, &vtable_root
, u
));
808 pa_assert_se(dbus_connection_register_fallback(pa_dbus_connection_get(u
->bus
), OBJECT_SINKS
, &vtable_sinks_and_sources
, u
));
809 pa_assert_se(dbus_connection_register_fallback(pa_dbus_connection_get(u
->bus
), OBJECT_SOURCES
, &vtable_sinks_and_sources
, u
));
811 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
) {
812 pa_log("Failed to request service name " SERVICE_NAME
": %s", error
.message
);
828 dbus_error_free(&error
);
833 void pa__done(pa_module
*m
) {
837 if (!(u
= m
->userdata
))
840 if (u
->source_new_slot
)
841 pa_hook_slot_free(u
->source_new_slot
);
842 if (u
->source_unlink_slot
)
843 pa_hook_slot_free(u
->source_unlink_slot
);
848 dbus_error_init(&error
);
850 dbus_connection_unregister_object_path(pa_dbus_connection_get(u
->bus
), OBJECT_ROOT
);
851 dbus_connection_unregister_object_path(pa_dbus_connection_get(u
->bus
), OBJECT_SINKS
);
852 dbus_connection_unregister_object_path(pa_dbus_connection_get(u
->bus
), OBJECT_SOURCES
);
855 if (dbus_bus_release_name(pa_dbus_connection_get(u
->bus
), SERVICE_NAME
, &error
) != DBUS_RELEASE_NAME_REPLY_RELEASED
) {
856 pa_log("Failed to release service name " SERVICE_NAME
": %s", error
.message
);
857 dbus_error_free(&error
);
861 pa_dbus_connection_unref(u
->bus
);
864 pa_xfree(u
->display_name
);
867 pa_http_protocol_unref(u
->http
);