bluetooth: Support port availability flag
[profile/ivi/pulseaudio.git] / src / modules / module-rygel-media-server.c
1 /***
2   This file is part of PulseAudio.
3
4   Copyright 2005-2009 Lennart Poettering
5
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.
10
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.
15
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
19   USA.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30
31 #include <pulse/gccmacro.h>
32 #include <pulse/xmalloc.h>
33 #include <pulse/utf8.h>
34
35 #include <pulsecore/i18n.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/macro.h>
41 #include <pulsecore/modargs.h>
42 #include <pulsecore/dbus-shared.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>
48
49 #include "module-rygel-media-server-symdef.h"
50
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);
55 PA_MODULE_USAGE("display_name=<UPnP Media Server name>");
56
57 /* This implements http://live.gnome.org/Rygel/MediaServer2Spec */
58
59 #define SERVICE_NAME "org.gnome.UPnP.MediaServer2.PulseAudio"
60
61 #define OBJECT_ROOT "/org/gnome/UPnP/MediaServer2/PulseAudio"
62 #define OBJECT_SINKS "/org/gnome/UPnP/MediaServer2/PulseAudio/Sinks"
63 #define OBJECT_SOURCES "/org/gnome/UPnP/MediaServer2/PulseAudio/Sources"
64
65 #define CONTAINER_INTROSPECT_XML_PREFIX                                 \
66     DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \
67     "<node>"                                                            \
68     " <!-- If you are looking for documentation make sure to check out" \
69     "      http://live.gnome.org/Rygel/MediaServer2Spec -->"            \
70     " <interface name=\"org.gnome.UPnP.MediaContainer2\">"              \
71     "  <method name='ListChildren'>"                                    \
72     "   <arg direction='in' name='offset' type='u' />"                  \
73     "   <arg direction='in' name='max' type='u' />"                     \
74     "   <arg direction='in' name='filter' type='as' />"                 \
75     "   <arg direction='out' type='aa{sv}' />"                          \
76     "  </method>"                                                       \
77     "  <method name='ListContainers'>"                                  \
78     "   <arg direction='in' name='offset' type='u' />"                  \
79     "   <arg direction='in' name='max' type='u' />"                     \
80     "   <arg direction='in' name='filter' type='as' />"                 \
81     "   <arg direction='out' type='aa{sv}' />"                          \
82     "  </method>"                                                       \
83     "  <method name='ListItems'>"                                       \
84     "   <arg direction='in' name='offset' type='u' />"                  \
85     "   <arg direction='in' name='max' type='u' />"                     \
86     "   <arg direction='in' name='filter' type='as' />"                 \
87     "   <arg direction='out' type='aa{sv}' />"                          \
88     "  </method>"                                                       \
89     "  <signal name=\"Updated\">"                                       \
90     "   <arg name=\"path\" type=\"o\"/>"                                \
91     "  </signal>"                                                       \
92     "  <property name=\"ChildCount\" type=\"u\" access=\"read\"/>"      \
93     "  <property name=\"ItemCount\" type=\"u\" access=\"read\"/>"       \
94     "  <property name=\"ContainerCount\" type=\"u\" access=\"read\"/>"  \
95     "  <property name=\"Searchable\" type=\"b\" access=\"read\"/>"      \
96     " </interface>"                                                     \
97     " <interface name=\"org.gnome.UPnP.MediaObject2\">"                 \
98     "  <property name=\"Parent\" type=\"s\" access=\"read\"/>"          \
99     "  <property name=\"Type\" type=\"s\" access=\"read\"/>"            \
100     "  <property name=\"Path\" type=\"s\" access=\"read\"/>"            \
101     "  <property name=\"DisplayName\" type=\"s\" access=\"read\"/>"     \
102     " </interface>"                                                     \
103     " <interface name=\"org.freedesktop.DBus.Properties\">"             \
104     "  <method name=\"Get\">"                                           \
105     "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"          \
106     "   <arg name=\"property\" direction=\"in\" type=\"s\"/>"           \
107     "   <arg name=\"value\" direction=\"out\" type=\"v\"/>"             \
108     "  </method>"                                                       \
109     "  <method name=\"GetAll\">"                                        \
110     "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"          \
111     "   <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>"    \
112     "  </method>"                                                       \
113     " </interface>"                                                     \
114     " <interface name=\"org.freedesktop.DBus.Introspectable\">"         \
115     "  <method name=\"Introspect\">"                                    \
116     "   <arg name=\"data\" type=\"s\" direction=\"out\"/>"              \
117     "  </method>"                                                       \
118     " </interface>"
119
120 #define CONTAINER_INTROSPECT_XML_POSTFIX                                \
121     "</node>"
122
123 #define ROOT_INTROSPECT_XML                                             \
124     CONTAINER_INTROSPECT_XML_PREFIX                                     \
125     "<node name=\"Sinks\"/>"                                            \
126     "<node name=\"Sources\"/>"                                          \
127     CONTAINER_INTROSPECT_XML_POSTFIX
128
129 #define ITEM_INTROSPECT_XML                                             \
130     DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \
131     "<node>"                                                            \
132     " <!-- If you are looking for documentation make sure to check out" \
133     "      http://live.gnome.org/Rygel/MediaProvider2Spec -->"          \
134     " <interface name=\"org.gnome.UPnP.MediaItem2\">"                   \
135     "  <property name=\"URLs\" type=\"as\" access=\"read\"/>"           \
136     "  <property name=\"MIMEType\" type=\"s\" access=\"read\"/>"        \
137     " </interface>"                                                     \
138     " <interface name=\"org.gnome.UPnP.MediaObject2\">"                 \
139     "  <property name=\"Parent\" type=\"s\" access=\"read\"/>"          \
140     "  <property name=\"Type\" type=\"s\" access=\"read\"/>"            \
141     "  <property name=\"Path\" type=\"s\" access=\"read\"/>"            \
142     "  <property name=\"DisplayName\" type=\"s\" access=\"read\"/>"     \
143     " </interface>"                                                     \
144     " <interface name=\"org.freedesktop.DBus.Properties\">"             \
145     "  <method name=\"Get\">"                                           \
146     "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"          \
147     "   <arg name=\"property\" direction=\"in\" type=\"s\"/>"           \
148     "   <arg name=\"value\" direction=\"out\" type=\"v\"/>"             \
149     "  </method>"                                                       \
150     "  <method name=\"GetAll\">"                                        \
151     "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"          \
152     "   <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>"    \
153     "  </method>"                                                       \
154     " </interface>"                                                     \
155     " <interface name=\"org.freedesktop.DBus.Introspectable\">"         \
156     "  <method name=\"Introspect\">"                                    \
157     "   <arg name=\"data\" type=\"s\" direction=\"out\"/>"              \
158     "  </method>"                                                       \
159     " </interface>"                                                     \
160     "</node>"
161
162
163 static const char* const valid_modargs[] = {
164     "display_name",
165     NULL
166 };
167
168 struct userdata {
169     pa_core *core;
170     pa_module *module;
171
172     pa_dbus_connection *bus;
173     pa_bool_t got_name:1;
174
175     char *display_name;
176
177     pa_hook_slot *source_new_slot, *source_unlink_slot;
178
179     pa_http_protocol *http;
180 };
181
182 static char *compute_url(const struct userdata *u, const char *name);
183
184 static void send_signal(struct userdata *u, pa_source *s) {
185     DBusMessage *m;
186     const char *parent;
187
188     pa_assert(u);
189     pa_source_assert_ref(s);
190
191     if (u->core->state == PA_CORE_SHUTDOWN)
192         return;
193
194     if (s->monitor_of)
195         parent = OBJECT_SINKS;
196     else
197         parent = OBJECT_SOURCES;
198
199     pa_assert_se(m = dbus_message_new_signal(parent, "org.gnome.UPnP.MediaContainer2", "Updated"));
200     pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), m, NULL));
201
202     dbus_message_unref(m);
203 }
204
205 static pa_hook_result_t source_new_or_unlink_cb(pa_core *c, pa_source *s, struct userdata *u) {
206     pa_assert(c);
207     pa_source_assert_ref(s);
208
209     send_signal(u, s);
210
211     return PA_HOOK_OK;
212 }
213
214 static pa_bool_t message_is_property_get(DBusMessage *m, const char *interface, const char *property) {
215     const char *i, *p;
216     DBusError error;
217
218     dbus_error_init(&error);
219
220     pa_assert(m);
221
222     if (!dbus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Get"))
223         return FALSE;
224
225     if (!dbus_message_get_args(m, &error, DBUS_TYPE_STRING, &i, DBUS_TYPE_STRING, &p, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) {
226         dbus_error_free(&error);
227         return FALSE;
228     }
229
230     return pa_streq(i, interface) && pa_streq(p, property);
231 }
232
233 static pa_bool_t message_is_property_get_all(DBusMessage *m, const char *interface) {
234     const char *i;
235     DBusError error;
236
237     dbus_error_init(&error);
238
239     pa_assert(m);
240
241     if (!dbus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "GetAll"))
242         return FALSE;
243
244     if (!dbus_message_get_args(m, &error, DBUS_TYPE_STRING, &i, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) {
245         dbus_error_free(&error);
246         return FALSE;
247     }
248
249     return pa_streq(i, interface);
250 }
251
252 static void append_variant_object_array(DBusMessage *m, DBusMessageIter *iter, const char *path[], unsigned n) {
253     DBusMessageIter _iter, variant, array;
254     unsigned c;
255
256     pa_assert(m);
257     pa_assert(path);
258
259     if (!iter) {
260         dbus_message_iter_init_append(m, &_iter);
261         iter = &_iter;
262     }
263
264     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "ao", &variant));
265     pa_assert_se(dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "o", &array));
266
267     for (c = 0; c < n; c++)
268         pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_OBJECT_PATH, path + c));
269
270     pa_assert_se(dbus_message_iter_close_container(&variant, &array));
271     pa_assert_se(dbus_message_iter_close_container(iter, &variant));
272 }
273
274 static void append_variant_string(DBusMessage *m, DBusMessageIter *iter, const char *s) {
275     DBusMessageIter _iter, sub;
276
277     pa_assert(m);
278     pa_assert(s);
279
280     if (!iter) {
281         dbus_message_iter_init_append(m, &_iter);
282         iter = &_iter;
283     }
284
285     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "s", &sub));
286     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &s));
287     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
288 }
289
290 static void append_variant_object(DBusMessage *m, DBusMessageIter *iter, const char *s) {
291     DBusMessageIter _iter, sub;
292
293     pa_assert(m);
294     pa_assert(s);
295
296     if (!iter) {
297         dbus_message_iter_init_append(m, &_iter);
298         iter = &_iter;
299     }
300
301     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "o", &sub));
302     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &s));
303     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
304 }
305
306 static void append_variant_unsigned(DBusMessage *m, DBusMessageIter *iter, unsigned u) {
307     DBusMessageIter _iter, sub;
308
309     pa_assert(m);
310
311     if (!iter) {
312         dbus_message_iter_init_append(m, &_iter);
313         iter = &_iter;
314     }
315
316     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "u", &sub));
317     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &u));
318     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
319 }
320
321 static void append_variant_boolean(DBusMessage *m, DBusMessageIter *iter, dbus_bool_t b) {
322     DBusMessageIter _iter, sub;
323
324     pa_assert(m);
325
326     if (!iter) {
327         dbus_message_iter_init_append(m, &_iter);
328         iter = &_iter;
329     }
330
331     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "b", &sub));
332     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_BOOLEAN, &b));
333     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
334 }
335
336 static void append_variant_urls(DBusMessage *m, DBusMessageIter *iter, const struct userdata *u, pa_sink *sink, pa_source *source) {
337     DBusMessageIter _iter, sub, array;
338     char *url;
339
340     pa_assert(m);
341     pa_assert(u);
342     pa_assert(sink || source);
343
344     if (!iter) {
345         dbus_message_iter_init_append(m, &_iter);
346         iter = &_iter;
347     }
348
349     url = compute_url(u, sink ? sink->monitor_source->name : source->name);
350
351     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "as", &sub));
352     pa_assert_se(dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, "s", &array));
353     pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &url));
354     pa_assert_se(dbus_message_iter_close_container(&sub, &array));
355     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
356
357     pa_xfree(url);
358 }
359
360 static void append_variant_mime_type(DBusMessage *m, DBusMessageIter *iter, pa_sink *sink, pa_source *source) {
361     char *mime_type;
362
363     pa_assert(sink || source);
364
365     if (sink)
366         mime_type = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map);
367     else
368         mime_type = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map);
369
370     append_variant_string(m, iter, mime_type);
371
372     pa_xfree(mime_type);
373 }
374
375 static void append_variant_item_display_name(DBusMessage *m, DBusMessageIter *iter, pa_sink *sink, pa_source *source) {
376     const char *display_name;
377
378     pa_assert(sink || source);
379
380     display_name = pa_strna(pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_DESCRIPTION));
381     append_variant_string(m, iter, display_name);
382 }
383
384 PA_GCC_UNUSED
385 static void append_property_dict_entry_object_array(DBusMessage *m, DBusMessageIter *iter, const char *name, const char *path[], unsigned n) {
386     DBusMessageIter sub;
387
388     pa_assert(iter);
389
390     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub));
391     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name));
392     append_variant_object_array(m, &sub, path, n);
393     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
394 }
395
396 static void append_property_dict_entry_string(DBusMessage *m, DBusMessageIter *iter, const char *name, const char *value) {
397     DBusMessageIter sub;
398
399     pa_assert(iter);
400
401     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub));
402     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name));
403     append_variant_string(m, &sub, value);
404     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
405 }
406
407 static void append_property_dict_entry_object(DBusMessage *m, DBusMessageIter *iter, const char *name, const char *value) {
408     DBusMessageIter sub;
409
410     pa_assert(iter);
411
412     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub));
413     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name));
414     append_variant_object(m, &sub, value);
415     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
416 }
417
418 static void append_property_dict_entry_unsigned(DBusMessage *m, DBusMessageIter *iter, const char *name, unsigned u) {
419     DBusMessageIter sub;
420
421     pa_assert(iter);
422
423     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub));
424     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name));
425     append_variant_unsigned(m, &sub, u);
426     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
427 }
428
429 static void append_property_dict_entry_boolean(DBusMessage *m, DBusMessageIter *iter, const char *name, dbus_bool_t b) {
430     DBusMessageIter sub;
431
432     pa_assert(iter);
433
434     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub));
435     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name));
436     append_variant_boolean(m, &sub, b);
437     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
438 }
439
440 static void append_property_dict_entry_urls(DBusMessage *m, DBusMessageIter *iter, const struct userdata *u, pa_sink *sink, pa_source *source) {
441     DBusMessageIter sub;
442     const char *property_name = "URLs";
443
444     pa_assert(iter);
445
446     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub));
447     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &property_name));
448     append_variant_urls(m, &sub, u, sink, source);
449     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
450 }
451
452 static void append_property_dict_entry_mime_type(DBusMessage *m, DBusMessageIter *iter, pa_sink *sink, pa_source *source) {
453     DBusMessageIter sub;
454     const char *property_name = "MIMEType";
455
456     pa_assert(iter);
457
458     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub));
459     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &property_name));
460     append_variant_mime_type(m, &sub, sink, source);
461     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
462 }
463
464 static void append_property_dict_entry_item_display_name(DBusMessage *m, DBusMessageIter *iter, pa_sink *sink, pa_source *source) {
465     DBusMessageIter sub;
466     const char *property_name = "DisplayName";
467
468     pa_assert(iter);
469
470     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub));
471     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &property_name));
472     append_variant_item_display_name(m, &sub, sink, source);
473     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
474 }
475
476 static pa_bool_t get_mediacontainer2_list_args(DBusMessage *m, unsigned *offset, unsigned *max_entries, char ***filter, int *filter_len) {
477     DBusError error;
478
479     dbus_error_init(&error);
480
481     pa_assert(m);
482     pa_assert(offset);
483     pa_assert(max_entries);
484     pa_assert(filter);
485
486     if (!dbus_message_get_args(m, &error, DBUS_TYPE_UINT32, offset, DBUS_TYPE_UINT32, max_entries, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, filter, filter_len, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) {
487         dbus_error_free(&error);
488         return FALSE;
489     }
490
491     return TRUE;
492 }
493
494 static unsigned get_sinks_or_sources_count(const char *path, const struct userdata *u) {
495     unsigned n, k;
496
497     n = pa_idxset_size(u->core->sinks);
498     k = pa_idxset_size(u->core->sources);
499     pa_assert(k >= n);
500
501     return pa_streq(path, OBJECT_SINKS) ? n : k - n;
502 }
503
504 static void append_sink_or_source_container_mediaobject2_properties(DBusMessage *r, DBusMessageIter *sub, const char *path) {
505     append_property_dict_entry_object(r, sub, "Parent", OBJECT_ROOT);
506     append_property_dict_entry_string(r, sub, "Type", "container");
507     append_property_dict_entry_object(r, sub, "Path", path);
508     append_property_dict_entry_string(r, sub, "DisplayName",
509                                       pa_streq(path, OBJECT_SINKS) ?
510                                       _("Output Devices") :
511                                       _("Input Devices"));
512 }
513
514 static void append_sink_or_source_container_properties(
515     DBusMessage *r, DBusMessageIter *iter,
516     const char *path, const struct userdata *user_data,
517     char * const * filter, int filter_len) {
518
519     DBusMessageIter sub;
520
521     pa_assert(r);
522     pa_assert(iter);
523     pa_assert(path);
524     pa_assert(filter);
525
526     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
527
528     if (filter_len == 1 && (*filter)[0] == '*' && (*filter)[1] == '\0') {
529         append_sink_or_source_container_mediaobject2_properties(r, &sub, path);
530         append_property_dict_entry_unsigned(r, &sub, "ChildCount", get_sinks_or_sources_count(path, user_data));
531         append_property_dict_entry_boolean(r, &sub, "Searchable", FALSE);
532     }
533     else {
534         for (int i = 0; i < filter_len; ++i) {
535             const char *property_name = filter[i];
536             if (pa_streq(property_name, "Parent")) {
537                 append_property_dict_entry_object(r, &sub, "Parent", OBJECT_ROOT);
538             }
539             else if (pa_streq(property_name, "Type")) {
540                 append_property_dict_entry_string(r, &sub, "Type", "container");
541             }
542             else if (pa_streq(property_name, "Path")) {
543                 append_property_dict_entry_object(r, &sub, "Path", path);
544             }
545             else if (pa_streq(property_name, "DisplayName")) {
546                 append_property_dict_entry_string(r, &sub, "DisplayName",
547                                                   pa_streq(path, OBJECT_SINKS) ?
548                                                   _("Output Devices") :
549                                                   _("Input Devices"));
550             }
551             else if (pa_streq(property_name, "ChildCount")) {
552                 append_property_dict_entry_unsigned(r, &sub, "ChildCount", get_sinks_or_sources_count(path, user_data));
553             }
554             else if (pa_streq(property_name, "Searchable")) {
555                 append_property_dict_entry_boolean(r, &sub, "Searchable", FALSE);
556             }
557         }
558     }
559
560     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
561 }
562
563 static void append_sink_or_source_item_mediaobject2_properties(DBusMessage *r, DBusMessageIter *sub, const char *path, pa_sink *sink, pa_source *source) {
564     append_property_dict_entry_object(r, sub, "Parent", sink ? OBJECT_SINKS : OBJECT_SOURCES);
565     append_property_dict_entry_string(r, sub, "Type", "audio");
566     append_property_dict_entry_object(r, sub, "Path", path);
567     append_property_dict_entry_item_display_name(r, sub, sink, source);
568 }
569
570 static void append_sink_or_source_item_properties(
571     DBusMessage *r, DBusMessageIter *iter,
572     const char *path, const struct userdata *user_data,
573     pa_sink *sink, pa_source *source,
574     char * const * filter, int filter_len) {
575
576     DBusMessageIter sub;
577
578     pa_assert(r);
579     pa_assert(iter);
580     pa_assert(path);
581     pa_assert(filter);
582     pa_assert(sink || source);
583
584     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
585
586     if (filter_len == 1 && (*filter)[0] == '*' && (*filter)[1] == '\0') {
587         append_sink_or_source_item_mediaobject2_properties(r, &sub, path, sink, source);
588         append_property_dict_entry_urls(r, &sub, user_data, sink, source);
589         append_property_dict_entry_mime_type(r, &sub, sink, source);
590     }
591     else {
592         for (int i = 0; i < filter_len; ++i) {
593             const char *property_name = filter[i];
594             if (pa_streq(property_name, "Parent")) {
595                 append_property_dict_entry_object(r, &sub, "Parent", sink ? OBJECT_SINKS : OBJECT_SOURCES);
596             }
597             else if (pa_streq(property_name, "Type")) {
598                 append_property_dict_entry_string(r, &sub, "Type", "audio");
599             }
600             else if (pa_streq(property_name, "Path")) {
601                 append_property_dict_entry_object(r, &sub, "Path", path);
602             }
603             else if (pa_streq(property_name, "DisplayName")) {
604                 append_property_dict_entry_item_display_name(r, &sub, sink, source);
605             }
606             else if (pa_streq(property_name, "URLs")) {
607                 append_property_dict_entry_urls(r, &sub, user_data, sink, source);
608             }
609             else if (pa_streq(property_name, "MIMEType")) {
610                 append_property_dict_entry_mime_type(r, &sub, sink, source);
611             }
612         }
613     }
614
615     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
616 }
617
618 static const char *array_root_containers[] = { OBJECT_SINKS, OBJECT_SOURCES };
619 static const char *array_no_children[] = { };
620
621 static DBusHandlerResult root_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
622     struct userdata *u = userdata;
623     DBusMessage *r = NULL;
624
625     pa_assert(u);
626
627     if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ChildCount")) {
628         pa_assert_se(r = dbus_message_new_method_return(m));
629         append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_root_containers));
630
631     } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ItemCount")) {
632         pa_assert_se(r = dbus_message_new_method_return(m));
633         append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_no_children));
634
635     } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ContainerCount")) {
636         pa_assert_se(r = dbus_message_new_method_return(m));
637         append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_root_containers));
638
639     } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "Searchable")) {
640         pa_assert_se(r = dbus_message_new_method_return(m));
641         append_variant_boolean(r, NULL, FALSE);
642
643     } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer2")) {
644         DBusMessageIter iter, sub;
645
646         pa_assert_se(r = dbus_message_new_method_return(m));
647         dbus_message_iter_init_append(r, &iter);
648
649         pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
650         append_property_dict_entry_unsigned(r, &sub, "ChildCount", PA_ELEMENTSOF(array_root_containers));
651         append_property_dict_entry_unsigned(r, &sub, "ItemCount", PA_ELEMENTSOF(array_no_children));
652         append_property_dict_entry_unsigned(r, &sub, "ContainerCount", PA_ELEMENTSOF(array_root_containers));
653         append_property_dict_entry_boolean(r, &sub, "Searchable", FALSE);
654         pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
655
656     } else if (dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListChildren")
657         || dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListContainers")) {
658         DBusMessageIter iter, sub;
659         unsigned offset, max;
660         char ** filter;
661         int filter_len;
662
663         pa_assert_se(r = dbus_message_new_method_return(m));
664
665         dbus_message_iter_init_append(r, &iter);
666         pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "a{sv}", &sub));
667
668         if (get_mediacontainer2_list_args(m, &offset, &max, &filter, &filter_len)) {
669             unsigned end = (max != 0 && offset + max < PA_ELEMENTSOF(array_root_containers))
670                                 ? max + offset
671                                 : PA_ELEMENTSOF(array_root_containers);
672
673             for (unsigned i = offset; i < end; ++i) {
674                 const char *container_path = array_root_containers[i];
675                 append_sink_or_source_container_properties(r, &sub, container_path, u, filter, filter_len);
676             }
677
678             dbus_free_string_array(filter);
679         }
680
681         pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
682
683     } else if (dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListItems")) {
684         DBusMessageIter iter, sub;
685
686         pa_assert_se(r = dbus_message_new_method_return(m));
687
688         dbus_message_iter_init_append(r, &iter);
689         pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "a{sv}", &sub));
690         pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
691
692     } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Parent")) {
693         pa_assert_se(r = dbus_message_new_method_return(m));
694         append_variant_object(r, NULL, OBJECT_ROOT);
695
696     } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Type")) {
697         pa_assert_se(r = dbus_message_new_method_return(m));
698         append_variant_string(r, NULL, "container");
699
700     } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Path")) {
701         const char *path = dbus_message_get_path(m);
702
703         pa_assert_se(r = dbus_message_new_method_return(m));
704         append_variant_object(r, NULL, path);
705
706     } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "DisplayName")) {
707         pa_assert_se(r = dbus_message_new_method_return(m));
708         append_variant_string(r, NULL, u->display_name);
709
710     } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject2")) {
711         DBusMessageIter iter, sub;
712         const char *path = dbus_message_get_path(m);
713
714         pa_assert_se(r = dbus_message_new_method_return(m));
715         dbus_message_iter_init_append(r, &iter);
716
717         pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
718         append_property_dict_entry_object(r, &sub, "Parent", OBJECT_ROOT);
719         append_property_dict_entry_string(r, &sub, "Type", "container");
720         append_property_dict_entry_object(r, &sub, "Path", path);
721         append_property_dict_entry_string(r, &sub, "DisplayName", u->display_name);
722         pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
723
724     } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
725         const char *xml = ROOT_INTROSPECT_XML;
726
727         pa_assert_se(r = dbus_message_new_method_return(m));
728         pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
729
730     } else
731         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
732
733     if (r) {
734         pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), r, NULL));
735         dbus_message_unref(r);
736     }
737
738     return DBUS_HANDLER_RESULT_HANDLED;
739 }
740
741 static char *compute_url(const struct userdata *u, const char *name) {
742     pa_strlist *i;
743
744     pa_assert(u);
745     pa_assert(name);
746
747     for (i = pa_http_protocol_servers(u->http); i; i = pa_strlist_next(i)) {
748         pa_parsed_address a;
749
750         if (pa_parse_address(pa_strlist_data(i), &a) >= 0 &&
751             (a.type == PA_PARSED_ADDRESS_TCP4 ||
752              a.type == PA_PARSED_ADDRESS_TCP6 ||
753              a.type == PA_PARSED_ADDRESS_TCP_AUTO)) {
754
755             const char *address;
756             char *s;
757
758             if (pa_is_ip_address(a.path_or_host))
759                 address = a.path_or_host;
760             else
761                 address = "@ADDRESS@";
762
763             if (a.port <= 0)
764                 a.port = 4714;
765
766             s = pa_sprintf_malloc("http://%s:%u/listen/source/%s", address, a.port, name);
767
768             pa_xfree(a.path_or_host);
769             return s;
770         }
771
772         pa_xfree(a.path_or_host);
773     }
774
775     return pa_sprintf_malloc("http://@ADDRESS@:4714/listen/source/%s", name);
776 }
777
778 static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
779     struct userdata *u = userdata;
780     DBusMessage *r = NULL;
781     const char *path;
782
783     pa_assert(u);
784
785     path = dbus_message_get_path(m);
786
787     if (pa_streq(path, OBJECT_SINKS) || pa_streq(path, OBJECT_SOURCES)) {
788
789         /* Container nodes */
790
791         if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ChildCount")
792             || message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ItemCount")) {
793             pa_assert_se(r = dbus_message_new_method_return(m));
794             append_variant_unsigned(r, NULL, get_sinks_or_sources_count(path, u));
795
796         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ContainerCount")) {
797             pa_assert_se(r = dbus_message_new_method_return(m));
798             append_variant_unsigned(r, NULL, 0);
799
800         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "Searchable")) {
801             pa_assert_se(r = dbus_message_new_method_return(m));
802             append_variant_boolean(r, NULL, FALSE);
803
804         } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer2")) {
805             DBusMessageIter iter, sub;
806             unsigned item_count;
807
808             pa_assert_se(r = dbus_message_new_method_return(m));
809             dbus_message_iter_init_append(r, &iter);
810
811             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
812
813             item_count = get_sinks_or_sources_count(path, u);
814
815             append_property_dict_entry_unsigned(r, &sub, "ChildCount", item_count);
816             append_property_dict_entry_unsigned(r, &sub, "ItemCount", item_count);
817             append_property_dict_entry_unsigned(r, &sub, "ContainerCount", 0);
818             append_property_dict_entry_boolean(r, &sub, "Searchable", FALSE);
819
820             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
821
822         } else if (dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListChildren")
823             || dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListItems")) {
824             DBusMessageIter iter, sub;
825             unsigned offset, max;
826             char **filter;
827             int filter_len;
828
829             pa_assert_se(r = dbus_message_new_method_return(m));
830
831             dbus_message_iter_init_append(r, &iter);
832             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "a{sv}", &sub));
833
834             if (get_mediacontainer2_list_args(m, &offset, &max, &filter, &filter_len)) {
835                 unsigned end = (max != 0) ? max + offset : UINT_MAX;
836
837                 if (pa_streq(path, OBJECT_SINKS)) {
838                     pa_sink *sink;
839                     char sink_path[sizeof(OBJECT_SINKS) + 32];
840                     char *path_end = sink_path + sizeof(OBJECT_SINKS);
841                     unsigned item_index = 0;
842                     uint32_t idx;
843
844                     strcpy(sink_path, OBJECT_SINKS "/");
845
846                     PA_IDXSET_FOREACH(sink, u->core->sinks, idx) {
847                         if (item_index >= offset && item_index < end) {
848                             sprintf(path_end, "%u", sink->index);
849                             append_sink_or_source_item_properties(r, &sub, sink_path, u, sink, NULL, filter, filter_len);
850                         }
851                         ++item_index;
852                     }
853                 } else {
854                     pa_source *source;
855                     char source_path[sizeof(OBJECT_SOURCES) + 32];
856                     char *path_end = source_path + sizeof(OBJECT_SOURCES);
857                     unsigned item_index = 0;
858                     uint32_t idx;
859
860                     strcpy(source_path, OBJECT_SOURCES "/");
861
862                     PA_IDXSET_FOREACH(source, u->core->sources, idx)
863                         if (!source->monitor_of) {
864                             if (item_index >= offset && item_index < end) {
865                                 sprintf(path_end, "%u", source->index);
866                                 append_sink_or_source_item_properties(r, &sub, source_path, u, NULL, source, filter, filter_len);
867                             }
868                             ++item_index;
869                         }
870                 }
871
872                 dbus_free_string_array(filter);
873             }
874
875             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
876
877         } else if (dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListContainers")) {
878             DBusMessageIter iter, sub;
879
880             pa_assert_se(r = dbus_message_new_method_return(m));
881
882             dbus_message_iter_init_append(r, &iter);
883             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "a{sv}", &sub));
884             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
885
886         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Parent")) {
887             pa_assert_se(r = dbus_message_new_method_return(m));
888             append_variant_object(r, NULL, OBJECT_ROOT);
889
890         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Type")) {
891             pa_assert_se(r = dbus_message_new_method_return(m));
892             append_variant_string(r, NULL, "container");
893
894         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Path")) {
895             pa_assert_se(r = dbus_message_new_method_return(m));
896             append_variant_object(r, NULL, path);
897
898         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "DisplayName")) {
899             pa_assert_se(r = dbus_message_new_method_return(m));
900             append_variant_string(r,
901                                   NULL,
902                                   pa_streq(path, OBJECT_SINKS) ?
903                                   _("Output Devices") :
904                                   _("Input Devices"));
905
906         } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject2")) {
907             DBusMessageIter iter, sub;
908
909             pa_assert_se(r = dbus_message_new_method_return(m));
910
911             dbus_message_iter_init_append(r, &iter);
912             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
913             append_sink_or_source_container_mediaobject2_properties(r, &sub, path);
914             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
915
916         } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
917             pa_strbuf *sb;
918             char *xml;
919             uint32_t idx;
920
921             sb = pa_strbuf_new();
922             pa_strbuf_puts(sb, CONTAINER_INTROSPECT_XML_PREFIX);
923
924             if (pa_streq(path, OBJECT_SINKS)) {
925                 pa_sink *sink;
926
927                 PA_IDXSET_FOREACH(sink, u->core->sinks, idx)
928                     pa_strbuf_printf(sb, "<node name=\"%u\"/>", sink->index);
929             } else {
930                 pa_source *source;
931
932                 PA_IDXSET_FOREACH(source, u->core->sources, idx)
933                     if (!source->monitor_of)
934                         pa_strbuf_printf(sb, "<node name=\"%u\"/>", source->index);
935             }
936
937             pa_strbuf_puts(sb, CONTAINER_INTROSPECT_XML_POSTFIX);
938             xml = pa_strbuf_tostring_free(sb);
939
940             pa_assert_se(r = dbus_message_new_method_return(m));
941             pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
942
943             pa_xfree(xml);
944         } else
945             return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
946
947     } else {
948         pa_sink *sink = NULL;
949         pa_source *source = NULL;
950
951         /* Child nodes */
952
953         if (pa_startswith(path, OBJECT_SINKS "/"))
954             sink = pa_namereg_get(u->core, path + sizeof(OBJECT_SINKS), PA_NAMEREG_SINK);
955         else if (pa_startswith(path, OBJECT_SOURCES "/"))
956             source = pa_namereg_get(u->core, path + sizeof(OBJECT_SOURCES), PA_NAMEREG_SOURCE);
957
958         if (!sink && !source)
959             return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
960
961         if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Parent")) {
962             pa_assert_se(r = dbus_message_new_method_return(m));
963             append_variant_object(r, NULL, sink ? OBJECT_SINKS : OBJECT_SOURCES);
964
965         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Type")) {
966             pa_assert_se(r = dbus_message_new_method_return(m));
967             append_variant_string(r, NULL, "audio");
968
969         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Path")) {
970             pa_assert_se(r = dbus_message_new_method_return(m));
971             append_variant_object(r, NULL, path);
972
973         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "DisplayName")) {
974             pa_assert_se(r = dbus_message_new_method_return(m));
975             append_variant_item_display_name(r, NULL, sink, source);
976
977         } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject2")) {
978             DBusMessageIter iter, sub;
979
980             pa_assert_se(r = dbus_message_new_method_return(m));
981             dbus_message_iter_init_append(r, &iter);
982
983             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
984             append_sink_or_source_item_mediaobject2_properties(r, &sub, path, sink, source);
985             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
986
987         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem2", "MIMEType")) {
988             pa_assert_se(r = dbus_message_new_method_return(m));
989             append_variant_mime_type(r, NULL, sink, source);
990
991         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem2", "URLs")) {
992             pa_assert_se(r = dbus_message_new_method_return(m));
993             append_variant_urls(r, NULL, u, sink, source);
994
995         } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaItem2")) {
996             DBusMessageIter iter, sub;
997
998             pa_assert_se(r = dbus_message_new_method_return(m));
999             dbus_message_iter_init_append(r, &iter);
1000
1001             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
1002
1003             append_property_dict_entry_mime_type(r, &sub, sink, source);
1004             append_property_dict_entry_urls(r, &sub, u, sink, source);
1005
1006             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
1007
1008         } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
1009             const char *xml =
1010                 ITEM_INTROSPECT_XML;
1011
1012             pa_assert_se(r = dbus_message_new_method_return(m));
1013             pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
1014
1015         } else
1016             return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1017     }
1018
1019     if (r) {
1020         pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), r, NULL));
1021         dbus_message_unref(r);
1022     }
1023
1024     return DBUS_HANDLER_RESULT_HANDLED;
1025 }
1026
1027 int pa__init(pa_module *m) {
1028
1029     struct userdata *u;
1030     pa_modargs *ma = NULL;
1031     DBusError error;
1032     const char *t;
1033
1034     static const DBusObjectPathVTable vtable_root = {
1035         .message_function = root_handler,
1036     };
1037     static const DBusObjectPathVTable vtable_sinks_and_sources = {
1038         .message_function = sinks_and_sources_handler,
1039     };
1040
1041     dbus_error_init(&error);
1042
1043     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
1044         pa_log("Failed to parse module arguments.");
1045         goto fail;
1046     }
1047
1048     m->userdata = u = pa_xnew0(struct userdata, 1);
1049     u->core = m->core;
1050     u->module = m;
1051     u->http = pa_http_protocol_get(u->core);
1052
1053     if ((t = pa_modargs_get_value(ma, "display_name", NULL)))
1054         u->display_name = pa_utf8_filter(t);
1055     else
1056         u->display_name = pa_xstrdup(_("Audio on @HOSTNAME@"));
1057
1058     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);
1059     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);
1060
1061     if (!(u->bus = pa_dbus_bus_get(m->core, DBUS_BUS_SESSION, &error))) {
1062         pa_log("Failed to get session bus connection: %s", error.message);
1063         goto fail;
1064     }
1065
1066     pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(u->bus), OBJECT_ROOT, &vtable_root, u));
1067     pa_assert_se(dbus_connection_register_fallback(pa_dbus_connection_get(u->bus), OBJECT_SINKS, &vtable_sinks_and_sources, u));
1068     pa_assert_se(dbus_connection_register_fallback(pa_dbus_connection_get(u->bus), OBJECT_SOURCES, &vtable_sinks_and_sources, u));
1069
1070     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) {
1071         pa_log("Failed to request service name " SERVICE_NAME ": %s", error.message);
1072         goto fail;
1073     }
1074
1075     u->got_name = TRUE;
1076
1077     pa_modargs_free(ma);
1078
1079     return 0;
1080
1081 fail:
1082     pa__done(m);
1083
1084     if (ma)
1085         pa_modargs_free(ma);
1086
1087     dbus_error_free(&error);
1088
1089     return -1;
1090 }
1091
1092 void pa__done(pa_module*m) {
1093     struct userdata*u;
1094     pa_assert(m);
1095
1096     if (!(u = m->userdata))
1097         return;
1098
1099     if (u->source_new_slot)
1100         pa_hook_slot_free(u->source_new_slot);
1101     if (u->source_unlink_slot)
1102         pa_hook_slot_free(u->source_unlink_slot);
1103
1104     if (u->bus) {
1105         DBusError error;
1106
1107         dbus_error_init(&error);
1108
1109         dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_ROOT);
1110         dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_SINKS);
1111         dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_SOURCES);
1112
1113         if (u->got_name) {
1114             if (dbus_bus_release_name(pa_dbus_connection_get(u->bus), SERVICE_NAME, &error) != DBUS_RELEASE_NAME_REPLY_RELEASED) {
1115                 pa_log("Failed to release service name " SERVICE_NAME ": %s", error.message);
1116                 dbus_error_free(&error);
1117             }
1118         }
1119
1120         pa_dbus_connection_unref(u->bus);
1121     }
1122
1123     pa_xfree(u->display_name);
1124
1125     if (u->http)
1126         pa_http_protocol_unref(u->http);
1127
1128     pa_xfree(u);
1129 }