26266b73a2c30e883cf2592291e5ae60578f873c
[platform/upstream/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     "  <property name=\"DLNAProfile\" type=\"s\" access=\"read\"/>"        \
138     " </interface>"                                                     \
139     " <interface name=\"org.gnome.UPnP.MediaObject2\">"                 \
140     "  <property name=\"Parent\" type=\"s\" access=\"read\"/>"          \
141     "  <property name=\"Type\" type=\"s\" access=\"read\"/>"            \
142     "  <property name=\"Path\" type=\"s\" access=\"read\"/>"            \
143     "  <property name=\"DisplayName\" type=\"s\" access=\"read\"/>"     \
144     " </interface>"                                                     \
145     " <interface name=\"org.freedesktop.DBus.Properties\">"             \
146     "  <method name=\"Get\">"                                           \
147     "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"          \
148     "   <arg name=\"property\" direction=\"in\" type=\"s\"/>"           \
149     "   <arg name=\"value\" direction=\"out\" type=\"v\"/>"             \
150     "  </method>"                                                       \
151     "  <method name=\"GetAll\">"                                        \
152     "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"          \
153     "   <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>"    \
154     "  </method>"                                                       \
155     " </interface>"                                                     \
156     " <interface name=\"org.freedesktop.DBus.Introspectable\">"         \
157     "  <method name=\"Introspect\">"                                    \
158     "   <arg name=\"data\" type=\"s\" direction=\"out\"/>"              \
159     "  </method>"                                                       \
160     " </interface>"                                                     \
161     "</node>"
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     bool 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 bool 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 bool 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 bool 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         append_property_dict_entry_string(r, &sub, "DLNAProfile", "LPCM");
591     }
592     else {
593         for (int i = 0; i < filter_len; ++i) {
594             const char *property_name = filter[i];
595             if (pa_streq(property_name, "Parent")) {
596                 append_property_dict_entry_object(r, &sub, "Parent", sink ? OBJECT_SINKS : OBJECT_SOURCES);
597             }
598             else if (pa_streq(property_name, "Type")) {
599                 append_property_dict_entry_string(r, &sub, "Type", "audio");
600             }
601             else if (pa_streq(property_name, "Path")) {
602                 append_property_dict_entry_object(r, &sub, "Path", path);
603             }
604             else if (pa_streq(property_name, "DisplayName")) {
605                 append_property_dict_entry_item_display_name(r, &sub, sink, source);
606             }
607             else if (pa_streq(property_name, "URLs")) {
608                 append_property_dict_entry_urls(r, &sub, user_data, sink, source);
609             }
610             else if (pa_streq(property_name, "MIMEType")) {
611                 append_property_dict_entry_mime_type(r, &sub, sink, source);
612             }
613             else if (pa_streq(property_name, "DLNAProfile")) {
614                 append_property_dict_entry_string(r, &sub, "DLNAProfile", "LPCM");
615             }
616         }
617     }
618
619     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
620 }
621
622 static const char *array_root_containers[] = { OBJECT_SINKS, OBJECT_SOURCES };
623 static const char *array_no_children[] = { };
624
625 static DBusHandlerResult root_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
626     struct userdata *u = userdata;
627     DBusMessage *r = NULL;
628
629     pa_assert(u);
630
631     if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ChildCount")) {
632         pa_assert_se(r = dbus_message_new_method_return(m));
633         append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_root_containers));
634
635     } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ItemCount")) {
636         pa_assert_se(r = dbus_message_new_method_return(m));
637         append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_no_children));
638
639     } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ContainerCount")) {
640         pa_assert_se(r = dbus_message_new_method_return(m));
641         append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_root_containers));
642
643     } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "Searchable")) {
644         pa_assert_se(r = dbus_message_new_method_return(m));
645         append_variant_boolean(r, NULL, false);
646
647     } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer2")) {
648         DBusMessageIter iter, sub;
649
650         pa_assert_se(r = dbus_message_new_method_return(m));
651         dbus_message_iter_init_append(r, &iter);
652
653         pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
654         append_property_dict_entry_unsigned(r, &sub, "ChildCount", PA_ELEMENTSOF(array_root_containers));
655         append_property_dict_entry_unsigned(r, &sub, "ItemCount", PA_ELEMENTSOF(array_no_children));
656         append_property_dict_entry_unsigned(r, &sub, "ContainerCount", PA_ELEMENTSOF(array_root_containers));
657         append_property_dict_entry_boolean(r, &sub, "Searchable", false);
658         pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
659
660     } else if (dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListChildren")
661         || dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListContainers")) {
662         DBusMessageIter iter, sub;
663         unsigned offset, max;
664         char ** filter;
665         int filter_len;
666
667         pa_assert_se(r = dbus_message_new_method_return(m));
668
669         dbus_message_iter_init_append(r, &iter);
670         pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "a{sv}", &sub));
671
672         if (get_mediacontainer2_list_args(m, &offset, &max, &filter, &filter_len)) {
673             unsigned end = (max != 0 && offset + max < PA_ELEMENTSOF(array_root_containers))
674                                 ? max + offset
675                                 : PA_ELEMENTSOF(array_root_containers);
676
677             for (unsigned i = offset; i < end; ++i) {
678                 const char *container_path = array_root_containers[i];
679                 append_sink_or_source_container_properties(r, &sub, container_path, u, filter, filter_len);
680             }
681
682             dbus_free_string_array(filter);
683         }
684
685         pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
686
687     } else if (dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListItems")) {
688         DBusMessageIter iter, sub;
689
690         pa_assert_se(r = dbus_message_new_method_return(m));
691
692         dbus_message_iter_init_append(r, &iter);
693         pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "a{sv}", &sub));
694         pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
695
696     } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Parent")) {
697         pa_assert_se(r = dbus_message_new_method_return(m));
698         append_variant_object(r, NULL, OBJECT_ROOT);
699
700     } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Type")) {
701         pa_assert_se(r = dbus_message_new_method_return(m));
702         append_variant_string(r, NULL, "container");
703
704     } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Path")) {
705         const char *path = dbus_message_get_path(m);
706
707         pa_assert_se(r = dbus_message_new_method_return(m));
708         append_variant_object(r, NULL, path);
709
710     } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "DisplayName")) {
711         pa_assert_se(r = dbus_message_new_method_return(m));
712         append_variant_string(r, NULL, u->display_name);
713
714     } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject2")) {
715         DBusMessageIter iter, sub;
716         const char *path = dbus_message_get_path(m);
717
718         pa_assert_se(r = dbus_message_new_method_return(m));
719         dbus_message_iter_init_append(r, &iter);
720
721         pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
722         append_property_dict_entry_object(r, &sub, "Parent", OBJECT_ROOT);
723         append_property_dict_entry_string(r, &sub, "Type", "container");
724         append_property_dict_entry_object(r, &sub, "Path", path);
725         append_property_dict_entry_string(r, &sub, "DisplayName", u->display_name);
726         pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
727
728     } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
729         const char *xml = ROOT_INTROSPECT_XML;
730
731         pa_assert_se(r = dbus_message_new_method_return(m));
732         pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
733
734     } else
735         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
736
737     if (r) {
738         pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), r, NULL));
739         dbus_message_unref(r);
740     }
741
742     return DBUS_HANDLER_RESULT_HANDLED;
743 }
744
745 static char *compute_url(const struct userdata *u, const char *name) {
746     pa_strlist *i;
747
748     pa_assert(u);
749     pa_assert(name);
750
751     for (i = pa_http_protocol_servers(u->http); i; i = pa_strlist_next(i)) {
752         pa_parsed_address a;
753
754         if (pa_parse_address(pa_strlist_data(i), &a) >= 0 &&
755             (a.type == PA_PARSED_ADDRESS_TCP4 ||
756              a.type == PA_PARSED_ADDRESS_TCP6 ||
757              a.type == PA_PARSED_ADDRESS_TCP_AUTO)) {
758
759             const char *address;
760             char *s;
761
762             if (pa_is_ip_address(a.path_or_host))
763                 address = a.path_or_host;
764             else
765                 address = "@ADDRESS@";
766
767             if (a.port <= 0)
768                 a.port = 4714;
769
770             s = pa_sprintf_malloc("http://%s:%u/listen/source/%s", address, a.port, name);
771
772             pa_xfree(a.path_or_host);
773             return s;
774         }
775
776         pa_xfree(a.path_or_host);
777     }
778
779     return pa_sprintf_malloc("http://@ADDRESS@:4714/listen/source/%s", name);
780 }
781
782 static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
783     struct userdata *u = userdata;
784     DBusMessage *r = NULL;
785     const char *path;
786
787     pa_assert(u);
788
789     path = dbus_message_get_path(m);
790
791     if (pa_streq(path, OBJECT_SINKS) || pa_streq(path, OBJECT_SOURCES)) {
792
793         /* Container nodes */
794
795         if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ChildCount")
796             || message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ItemCount")) {
797             pa_assert_se(r = dbus_message_new_method_return(m));
798             append_variant_unsigned(r, NULL, get_sinks_or_sources_count(path, u));
799
800         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ContainerCount")) {
801             pa_assert_se(r = dbus_message_new_method_return(m));
802             append_variant_unsigned(r, NULL, 0);
803
804         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "Searchable")) {
805             pa_assert_se(r = dbus_message_new_method_return(m));
806             append_variant_boolean(r, NULL, false);
807
808         } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer2")) {
809             DBusMessageIter iter, sub;
810             unsigned item_count;
811
812             pa_assert_se(r = dbus_message_new_method_return(m));
813             dbus_message_iter_init_append(r, &iter);
814
815             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
816
817             item_count = get_sinks_or_sources_count(path, u);
818
819             append_property_dict_entry_unsigned(r, &sub, "ChildCount", item_count);
820             append_property_dict_entry_unsigned(r, &sub, "ItemCount", item_count);
821             append_property_dict_entry_unsigned(r, &sub, "ContainerCount", 0);
822             append_property_dict_entry_boolean(r, &sub, "Searchable", false);
823
824             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
825
826         } else if (dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListChildren")
827             || dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListItems")) {
828             DBusMessageIter iter, sub;
829             unsigned offset, max;
830             char **filter;
831             int filter_len;
832
833             pa_assert_se(r = dbus_message_new_method_return(m));
834
835             dbus_message_iter_init_append(r, &iter);
836             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "a{sv}", &sub));
837
838             if (get_mediacontainer2_list_args(m, &offset, &max, &filter, &filter_len)) {
839                 unsigned end = (max != 0) ? max + offset : UINT_MAX;
840
841                 if (pa_streq(path, OBJECT_SINKS)) {
842                     pa_sink *sink;
843                     char sink_path[sizeof(OBJECT_SINKS) + 32];
844                     char *path_end = sink_path + sizeof(OBJECT_SINKS);
845                     unsigned item_index = 0;
846                     uint32_t idx;
847
848                     strcpy(sink_path, OBJECT_SINKS "/");
849
850                     PA_IDXSET_FOREACH(sink, u->core->sinks, idx) {
851                         if (item_index >= offset && item_index < end) {
852                             sprintf(path_end, "%u", sink->index);
853                             append_sink_or_source_item_properties(r, &sub, sink_path, u, sink, NULL, filter, filter_len);
854                         }
855                         ++item_index;
856                     }
857                 } else {
858                     pa_source *source;
859                     char source_path[sizeof(OBJECT_SOURCES) + 32];
860                     char *path_end = source_path + sizeof(OBJECT_SOURCES);
861                     unsigned item_index = 0;
862                     uint32_t idx;
863
864                     strcpy(source_path, OBJECT_SOURCES "/");
865
866                     PA_IDXSET_FOREACH(source, u->core->sources, idx)
867                         if (!source->monitor_of) {
868                             if (item_index >= offset && item_index < end) {
869                                 sprintf(path_end, "%u", source->index);
870                                 append_sink_or_source_item_properties(r, &sub, source_path, u, NULL, source, filter, filter_len);
871                             }
872                             ++item_index;
873                         }
874                 }
875
876                 dbus_free_string_array(filter);
877             }
878
879             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
880
881         } else if (dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListContainers")) {
882             DBusMessageIter iter, sub;
883
884             pa_assert_se(r = dbus_message_new_method_return(m));
885
886             dbus_message_iter_init_append(r, &iter);
887             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "a{sv}", &sub));
888             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
889
890         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Parent")) {
891             pa_assert_se(r = dbus_message_new_method_return(m));
892             append_variant_object(r, NULL, OBJECT_ROOT);
893
894         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Type")) {
895             pa_assert_se(r = dbus_message_new_method_return(m));
896             append_variant_string(r, NULL, "container");
897
898         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Path")) {
899             pa_assert_se(r = dbus_message_new_method_return(m));
900             append_variant_object(r, NULL, path);
901
902         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "DisplayName")) {
903             pa_assert_se(r = dbus_message_new_method_return(m));
904             append_variant_string(r,
905                                   NULL,
906                                   pa_streq(path, OBJECT_SINKS) ?
907                                   _("Output Devices") :
908                                   _("Input Devices"));
909
910         } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject2")) {
911             DBusMessageIter iter, sub;
912
913             pa_assert_se(r = dbus_message_new_method_return(m));
914
915             dbus_message_iter_init_append(r, &iter);
916             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
917             append_sink_or_source_container_mediaobject2_properties(r, &sub, path);
918             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
919
920         } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
921             pa_strbuf *sb;
922             char *xml;
923             uint32_t idx;
924
925             sb = pa_strbuf_new();
926             pa_strbuf_puts(sb, CONTAINER_INTROSPECT_XML_PREFIX);
927
928             if (pa_streq(path, OBJECT_SINKS)) {
929                 pa_sink *sink;
930
931                 PA_IDXSET_FOREACH(sink, u->core->sinks, idx)
932                     pa_strbuf_printf(sb, "<node name=\"%u\"/>", sink->index);
933             } else {
934                 pa_source *source;
935
936                 PA_IDXSET_FOREACH(source, u->core->sources, idx)
937                     if (!source->monitor_of)
938                         pa_strbuf_printf(sb, "<node name=\"%u\"/>", source->index);
939             }
940
941             pa_strbuf_puts(sb, CONTAINER_INTROSPECT_XML_POSTFIX);
942             xml = pa_strbuf_tostring_free(sb);
943
944             pa_assert_se(r = dbus_message_new_method_return(m));
945             pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
946
947             pa_xfree(xml);
948         } else
949             return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
950
951     } else {
952         pa_sink *sink = NULL;
953         pa_source *source = NULL;
954
955         /* Child nodes */
956
957         if (pa_startswith(path, OBJECT_SINKS "/"))
958             sink = pa_namereg_get(u->core, path + sizeof(OBJECT_SINKS), PA_NAMEREG_SINK);
959         else if (pa_startswith(path, OBJECT_SOURCES "/"))
960             source = pa_namereg_get(u->core, path + sizeof(OBJECT_SOURCES), PA_NAMEREG_SOURCE);
961
962         if (!sink && !source)
963             return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
964
965         if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Parent")) {
966             pa_assert_se(r = dbus_message_new_method_return(m));
967             append_variant_object(r, NULL, sink ? OBJECT_SINKS : OBJECT_SOURCES);
968
969         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Type")) {
970             pa_assert_se(r = dbus_message_new_method_return(m));
971             append_variant_string(r, NULL, "audio");
972
973         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Path")) {
974             pa_assert_se(r = dbus_message_new_method_return(m));
975             append_variant_object(r, NULL, path);
976
977         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "DisplayName")) {
978             pa_assert_se(r = dbus_message_new_method_return(m));
979             append_variant_item_display_name(r, NULL, sink, source);
980
981         } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject2")) {
982             DBusMessageIter iter, sub;
983
984             pa_assert_se(r = dbus_message_new_method_return(m));
985             dbus_message_iter_init_append(r, &iter);
986
987             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
988             append_sink_or_source_item_mediaobject2_properties(r, &sub, path, sink, source);
989             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
990
991         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem2", "MIMEType")) {
992             pa_assert_se(r = dbus_message_new_method_return(m));
993             append_variant_mime_type(r, NULL, sink, source);
994
995         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem2", "DLNAProfile")) {
996             pa_assert_se(r = dbus_message_new_method_return(m));
997             append_variant_string(r, NULL, "LPCM");
998
999         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem2", "URLs")) {
1000             pa_assert_se(r = dbus_message_new_method_return(m));
1001             append_variant_urls(r, NULL, u, sink, source);
1002
1003         } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaItem2")) {
1004             DBusMessageIter iter, sub;
1005
1006             pa_assert_se(r = dbus_message_new_method_return(m));
1007             dbus_message_iter_init_append(r, &iter);
1008
1009             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
1010
1011             append_property_dict_entry_mime_type(r, &sub, sink, source);
1012             append_property_dict_entry_string(r, &sub, "DLNAProfile", "LPCM");
1013             append_property_dict_entry_urls(r, &sub, u, sink, source);
1014
1015             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
1016
1017         } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
1018             const char *xml =
1019                 ITEM_INTROSPECT_XML;
1020
1021             pa_assert_se(r = dbus_message_new_method_return(m));
1022             pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
1023
1024         } else
1025             return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1026     }
1027
1028     if (r) {
1029         pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), r, NULL));
1030         dbus_message_unref(r);
1031     }
1032
1033     return DBUS_HANDLER_RESULT_HANDLED;
1034 }
1035
1036 int pa__init(pa_module *m) {
1037
1038     struct userdata *u;
1039     pa_modargs *ma = NULL;
1040     DBusError error;
1041     const char *t;
1042
1043     static const DBusObjectPathVTable vtable_root = {
1044         .message_function = root_handler,
1045     };
1046     static const DBusObjectPathVTable vtable_sinks_and_sources = {
1047         .message_function = sinks_and_sources_handler,
1048     };
1049
1050     dbus_error_init(&error);
1051
1052     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
1053         pa_log("Failed to parse module arguments.");
1054         goto fail;
1055     }
1056
1057     m->userdata = u = pa_xnew0(struct userdata, 1);
1058     u->core = m->core;
1059     u->module = m;
1060     u->http = pa_http_protocol_get(u->core);
1061
1062     if ((t = pa_modargs_get_value(ma, "display_name", NULL)))
1063         u->display_name = pa_utf8_filter(t);
1064     else
1065         u->display_name = pa_xstrdup(_("Audio on @HOSTNAME@"));
1066
1067     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);
1068     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);
1069
1070     if (!(u->bus = pa_dbus_bus_get(m->core, DBUS_BUS_SESSION, &error))) {
1071         pa_log("Failed to get session bus connection: %s", error.message);
1072         goto fail;
1073     }
1074
1075     pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(u->bus), OBJECT_ROOT, &vtable_root, u));
1076     pa_assert_se(dbus_connection_register_fallback(pa_dbus_connection_get(u->bus), OBJECT_SINKS, &vtable_sinks_and_sources, u));
1077     pa_assert_se(dbus_connection_register_fallback(pa_dbus_connection_get(u->bus), OBJECT_SOURCES, &vtable_sinks_and_sources, u));
1078
1079     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) {
1080         pa_log("Failed to request service name " SERVICE_NAME ": %s", error.message);
1081         goto fail;
1082     }
1083
1084     u->got_name = true;
1085
1086     pa_modargs_free(ma);
1087
1088     return 0;
1089
1090 fail:
1091     pa__done(m);
1092
1093     if (ma)
1094         pa_modargs_free(ma);
1095
1096     dbus_error_free(&error);
1097
1098     return -1;
1099 }
1100
1101 void pa__done(pa_module*m) {
1102     struct userdata*u;
1103     pa_assert(m);
1104
1105     if (!(u = m->userdata))
1106         return;
1107
1108     if (u->source_new_slot)
1109         pa_hook_slot_free(u->source_new_slot);
1110     if (u->source_unlink_slot)
1111         pa_hook_slot_free(u->source_unlink_slot);
1112
1113     if (u->bus) {
1114         DBusError error;
1115
1116         dbus_error_init(&error);
1117
1118         dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_ROOT);
1119         dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_SINKS);
1120         dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_SOURCES);
1121
1122         if (u->got_name) {
1123             if (dbus_bus_release_name(pa_dbus_connection_get(u->bus), SERVICE_NAME, &error) != DBUS_RELEASE_NAME_REPLY_RELEASED) {
1124                 pa_log("Failed to release service name " SERVICE_NAME ": %s", error.message);
1125                 dbus_error_free(&error);
1126             }
1127         }
1128
1129         pa_dbus_connection_unref(u->bus);
1130     }
1131
1132     pa_xfree(u->display_name);
1133
1134     if (u->http)
1135         pa_http_protocol_unref(u->http);
1136
1137     pa_xfree(u);
1138 }