• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, see <http://www.gnu.org/licenses/>.
18 ***/
19 
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 
29 #include <pulse/gccmacro.h>
30 #include <pulse/xmalloc.h>
31 #include <pulse/utf8.h>
32 
33 #include <pulsecore/i18n.h>
34 #include <pulsecore/sink.h>
35 #include <pulsecore/source.h>
36 #include <pulsecore/core-util.h>
37 #include <pulsecore/log.h>
38 #include <pulsecore/macro.h>
39 #include <pulsecore/modargs.h>
40 #include <pulsecore/dbus-shared.h>
41 #include <pulsecore/namereg.h>
42 #include <pulsecore/mime-type.h>
43 #include <pulsecore/strbuf.h>
44 #include <pulsecore/protocol-http.h>
45 #include <pulsecore/parseaddr.h>
46 
47 PA_MODULE_AUTHOR("Lennart Poettering");
48 PA_MODULE_DESCRIPTION("UPnP MediaServer Plugin for Rygel");
49 PA_MODULE_VERSION(PACKAGE_VERSION);
50 PA_MODULE_LOAD_ONCE(true);
51 PA_MODULE_USAGE("display_name=<UPnP Media Server name>");
52 
53 /* This implements http://live.gnome.org/Rygel/MediaServer2Spec */
54 
55 #define SERVICE_NAME "org.gnome.UPnP.MediaServer2.PulseAudio"
56 
57 #define OBJECT_ROOT "/org/gnome/UPnP/MediaServer2/PulseAudio"
58 #define OBJECT_SINKS "/org/gnome/UPnP/MediaServer2/PulseAudio/Sinks"
59 #define OBJECT_SOURCES "/org/gnome/UPnP/MediaServer2/PulseAudio/Sources"
60 
61 #define CONTAINER_INTROSPECT_XML_PREFIX                                 \
62     DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \
63     "<node>"                                                            \
64     " <!-- If you are looking for documentation make sure to check out" \
65     "      http://live.gnome.org/Rygel/MediaServer2Spec -->"            \
66     " <interface name=\"org.gnome.UPnP.MediaContainer2\">"              \
67     "  <method name='ListChildren'>"                                    \
68     "   <arg direction='in' name='offset' type='u' />"                  \
69     "   <arg direction='in' name='max' type='u' />"                     \
70     "   <arg direction='in' name='filter' type='as' />"                 \
71     "   <arg direction='out' type='aa{sv}' />"                          \
72     "  </method>"                                                       \
73     "  <method name='ListContainers'>"                                  \
74     "   <arg direction='in' name='offset' type='u' />"                  \
75     "   <arg direction='in' name='max' type='u' />"                     \
76     "   <arg direction='in' name='filter' type='as' />"                 \
77     "   <arg direction='out' type='aa{sv}' />"                          \
78     "  </method>"                                                       \
79     "  <method name='ListItems'>"                                       \
80     "   <arg direction='in' name='offset' type='u' />"                  \
81     "   <arg direction='in' name='max' type='u' />"                     \
82     "   <arg direction='in' name='filter' type='as' />"                 \
83     "   <arg direction='out' type='aa{sv}' />"                          \
84     "  </method>"                                                       \
85     "  <signal name=\"Updated\">"                                       \
86     "   <arg name=\"path\" type=\"o\"/>"                                \
87     "  </signal>"                                                       \
88     "  <property name=\"ChildCount\" type=\"u\" access=\"read\"/>"      \
89     "  <property name=\"ItemCount\" type=\"u\" access=\"read\"/>"       \
90     "  <property name=\"ContainerCount\" type=\"u\" access=\"read\"/>"  \
91     "  <property name=\"Searchable\" type=\"b\" access=\"read\"/>"      \
92     " </interface>"                                                     \
93     " <interface name=\"org.gnome.UPnP.MediaObject2\">"                 \
94     "  <property name=\"Parent\" type=\"s\" access=\"read\"/>"          \
95     "  <property name=\"Type\" type=\"s\" access=\"read\"/>"            \
96     "  <property name=\"Path\" type=\"s\" access=\"read\"/>"            \
97     "  <property name=\"DisplayName\" type=\"s\" access=\"read\"/>"     \
98     " </interface>"                                                     \
99     " <interface name=\"org.freedesktop.DBus.Properties\">"             \
100     "  <method name=\"Get\">"                                           \
101     "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"          \
102     "   <arg name=\"property\" direction=\"in\" type=\"s\"/>"           \
103     "   <arg name=\"value\" direction=\"out\" type=\"v\"/>"             \
104     "  </method>"                                                       \
105     "  <method name=\"GetAll\">"                                        \
106     "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"          \
107     "   <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>"    \
108     "  </method>"                                                       \
109     " </interface>"                                                     \
110     " <interface name=\"org.freedesktop.DBus.Introspectable\">"         \
111     "  <method name=\"Introspect\">"                                    \
112     "   <arg name=\"data\" type=\"s\" direction=\"out\"/>"              \
113     "  </method>"                                                       \
114     " </interface>"
115 
116 #define CONTAINER_INTROSPECT_XML_POSTFIX                                \
117     "</node>"
118 
119 #define ROOT_INTROSPECT_XML                                             \
120     CONTAINER_INTROSPECT_XML_PREFIX                                     \
121     "<node name=\"Sinks\"/>"                                            \
122     "<node name=\"Sources\"/>"                                          \
123     CONTAINER_INTROSPECT_XML_POSTFIX
124 
125 #define ITEM_INTROSPECT_XML                                             \
126     DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \
127     "<node>"                                                            \
128     " <!-- If you are looking for documentation make sure to check out" \
129     "      http://live.gnome.org/Rygel/MediaProvider2Spec -->"          \
130     " <interface name=\"org.gnome.UPnP.MediaItem2\">"                   \
131     "  <property name=\"URLs\" type=\"as\" access=\"read\"/>"           \
132     "  <property name=\"MIMEType\" type=\"s\" access=\"read\"/>"        \
133     "  <property name=\"DLNAProfile\" type=\"s\" access=\"read\"/>"        \
134     " </interface>"                                                     \
135     " <interface name=\"org.gnome.UPnP.MediaObject2\">"                 \
136     "  <property name=\"Parent\" type=\"s\" access=\"read\"/>"          \
137     "  <property name=\"Type\" type=\"s\" access=\"read\"/>"            \
138     "  <property name=\"Path\" type=\"s\" access=\"read\"/>"            \
139     "  <property name=\"DisplayName\" type=\"s\" access=\"read\"/>"     \
140     " </interface>"                                                     \
141     " <interface name=\"org.freedesktop.DBus.Properties\">"             \
142     "  <method name=\"Get\">"                                           \
143     "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"          \
144     "   <arg name=\"property\" direction=\"in\" type=\"s\"/>"           \
145     "   <arg name=\"value\" direction=\"out\" type=\"v\"/>"             \
146     "  </method>"                                                       \
147     "  <method name=\"GetAll\">"                                        \
148     "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"          \
149     "   <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>"    \
150     "  </method>"                                                       \
151     " </interface>"                                                     \
152     " <interface name=\"org.freedesktop.DBus.Introspectable\">"         \
153     "  <method name=\"Introspect\">"                                    \
154     "   <arg name=\"data\" type=\"s\" direction=\"out\"/>"              \
155     "  </method>"                                                       \
156     " </interface>"                                                     \
157     "</node>"
158 
159 static const char* const valid_modargs[] = {
160     "display_name",
161     NULL
162 };
163 
164 struct userdata {
165     pa_core *core;
166     pa_module *module;
167 
168     pa_dbus_connection *bus;
169     bool got_name:1;
170 
171     char *display_name;
172 
173     pa_hook_slot *source_new_slot, *source_unlink_slot;
174 
175     pa_http_protocol *http;
176 };
177 
178 static char *compute_url(const struct userdata *u, const char *name);
179 
send_signal(struct userdata * u,pa_source * s)180 static void send_signal(struct userdata *u, pa_source *s) {
181     DBusMessage *m;
182     const char *parent;
183 
184     pa_assert(u);
185     pa_source_assert_ref(s);
186 
187     if (u->core->state == PA_CORE_SHUTDOWN)
188         return;
189 
190     if (s->monitor_of)
191         parent = OBJECT_SINKS;
192     else
193         parent = OBJECT_SOURCES;
194 
195     pa_assert_se(m = dbus_message_new_signal(parent, "org.gnome.UPnP.MediaContainer2", "Updated"));
196     pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), m, NULL));
197 
198     dbus_message_unref(m);
199 }
200 
source_new_or_unlink_cb(pa_core * c,pa_source * s,struct userdata * u)201 static pa_hook_result_t source_new_or_unlink_cb(pa_core *c, pa_source *s, struct userdata *u) {
202     pa_assert(c);
203     pa_source_assert_ref(s);
204 
205     send_signal(u, s);
206 
207     return PA_HOOK_OK;
208 }
209 
message_is_property_get(DBusMessage * m,const char * interface,const char * property)210 static bool message_is_property_get(DBusMessage *m, const char *interface, const char *property) {
211     const char *i, *p;
212     DBusError error;
213 
214     dbus_error_init(&error);
215 
216     pa_assert(m);
217 
218     if (!dbus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Get"))
219         return false;
220 
221     if (!dbus_message_get_args(m, &error, DBUS_TYPE_STRING, &i, DBUS_TYPE_STRING, &p, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) {
222         dbus_error_free(&error);
223         return false;
224     }
225 
226     return pa_streq(i, interface) && pa_streq(p, property);
227 }
228 
message_is_property_get_all(DBusMessage * m,const char * interface)229 static bool message_is_property_get_all(DBusMessage *m, const char *interface) {
230     const char *i;
231     DBusError error;
232 
233     dbus_error_init(&error);
234 
235     pa_assert(m);
236 
237     if (!dbus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "GetAll"))
238         return false;
239 
240     if (!dbus_message_get_args(m, &error, DBUS_TYPE_STRING, &i, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) {
241         dbus_error_free(&error);
242         return false;
243     }
244 
245     return pa_streq(i, interface);
246 }
247 
append_variant_object_array(DBusMessage * m,DBusMessageIter * iter,const char * path[],unsigned n)248 static void append_variant_object_array(DBusMessage *m, DBusMessageIter *iter, const char *path[], unsigned n) {
249     DBusMessageIter _iter, variant, array;
250     unsigned c;
251 
252     pa_assert(m);
253     pa_assert(path);
254 
255     if (!iter) {
256         dbus_message_iter_init_append(m, &_iter);
257         iter = &_iter;
258     }
259 
260     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "ao", &variant));
261     pa_assert_se(dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "o", &array));
262 
263     for (c = 0; c < n; c++)
264         pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_OBJECT_PATH, path + c));
265 
266     pa_assert_se(dbus_message_iter_close_container(&variant, &array));
267     pa_assert_se(dbus_message_iter_close_container(iter, &variant));
268 }
269 
append_variant_string(DBusMessage * m,DBusMessageIter * iter,const char * s)270 static void append_variant_string(DBusMessage *m, DBusMessageIter *iter, const char *s) {
271     DBusMessageIter _iter, sub;
272 
273     pa_assert(m);
274     pa_assert(s);
275 
276     if (!iter) {
277         dbus_message_iter_init_append(m, &_iter);
278         iter = &_iter;
279     }
280 
281     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "s", &sub));
282     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &s));
283     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
284 }
285 
append_variant_object(DBusMessage * m,DBusMessageIter * iter,const char * s)286 static void append_variant_object(DBusMessage *m, DBusMessageIter *iter, const char *s) {
287     DBusMessageIter _iter, sub;
288 
289     pa_assert(m);
290     pa_assert(s);
291 
292     if (!iter) {
293         dbus_message_iter_init_append(m, &_iter);
294         iter = &_iter;
295     }
296 
297     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "o", &sub));
298     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &s));
299     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
300 }
301 
append_variant_unsigned(DBusMessage * m,DBusMessageIter * iter,unsigned u)302 static void append_variant_unsigned(DBusMessage *m, DBusMessageIter *iter, unsigned u) {
303     DBusMessageIter _iter, sub;
304 
305     pa_assert(m);
306 
307     if (!iter) {
308         dbus_message_iter_init_append(m, &_iter);
309         iter = &_iter;
310     }
311 
312     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "u", &sub));
313     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &u));
314     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
315 }
316 
append_variant_boolean(DBusMessage * m,DBusMessageIter * iter,dbus_bool_t b)317 static void append_variant_boolean(DBusMessage *m, DBusMessageIter *iter, dbus_bool_t b) {
318     DBusMessageIter _iter, sub;
319 
320     pa_assert(m);
321 
322     if (!iter) {
323         dbus_message_iter_init_append(m, &_iter);
324         iter = &_iter;
325     }
326 
327     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "b", &sub));
328     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_BOOLEAN, &b));
329     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
330 }
331 
append_variant_urls(DBusMessage * m,DBusMessageIter * iter,const struct userdata * u,pa_sink * sink,pa_source * source)332 static void append_variant_urls(DBusMessage *m, DBusMessageIter *iter, const struct userdata *u, pa_sink *sink, pa_source *source) {
333     DBusMessageIter _iter, sub, array;
334     char *url;
335 
336     pa_assert(m);
337     pa_assert(u);
338     pa_assert(sink || source);
339 
340     if (!iter) {
341         dbus_message_iter_init_append(m, &_iter);
342         iter = &_iter;
343     }
344 
345     url = compute_url(u, sink ? sink->monitor_source->name : source->name);
346 
347     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "as", &sub));
348     pa_assert_se(dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, "s", &array));
349     pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &url));
350     pa_assert_se(dbus_message_iter_close_container(&sub, &array));
351     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
352 
353     pa_xfree(url);
354 }
355 
append_variant_mime_type(DBusMessage * m,DBusMessageIter * iter,pa_sink * sink,pa_source * source)356 static void append_variant_mime_type(DBusMessage *m, DBusMessageIter *iter, pa_sink *sink, pa_source *source) {
357     char *mime_type;
358 
359     pa_assert(sink || source);
360 
361     if (sink)
362         mime_type = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map);
363     else
364         mime_type = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map);
365 
366     append_variant_string(m, iter, mime_type);
367 
368     pa_xfree(mime_type);
369 }
370 
append_variant_item_display_name(DBusMessage * m,DBusMessageIter * iter,pa_sink * sink,pa_source * source)371 static void append_variant_item_display_name(DBusMessage *m, DBusMessageIter *iter, pa_sink *sink, pa_source *source) {
372     const char *display_name;
373 
374     pa_assert(sink || source);
375 
376     display_name = pa_strna(pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_DESCRIPTION));
377     append_variant_string(m, iter, display_name);
378 }
379 
380 PA_GCC_UNUSED
append_property_dict_entry_object_array(DBusMessage * m,DBusMessageIter * iter,const char * name,const char * path[],unsigned n)381 static void append_property_dict_entry_object_array(DBusMessage *m, DBusMessageIter *iter, const char *name, const char *path[], unsigned n) {
382     DBusMessageIter sub;
383 
384     pa_assert(iter);
385 
386     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub));
387     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name));
388     append_variant_object_array(m, &sub, path, n);
389     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
390 }
391 
append_property_dict_entry_string(DBusMessage * m,DBusMessageIter * iter,const char * name,const char * value)392 static void append_property_dict_entry_string(DBusMessage *m, DBusMessageIter *iter, const char *name, const char *value) {
393     DBusMessageIter sub;
394 
395     pa_assert(iter);
396 
397     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub));
398     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name));
399     append_variant_string(m, &sub, value);
400     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
401 }
402 
append_property_dict_entry_object(DBusMessage * m,DBusMessageIter * iter,const char * name,const char * value)403 static void append_property_dict_entry_object(DBusMessage *m, DBusMessageIter *iter, const char *name, const char *value) {
404     DBusMessageIter sub;
405 
406     pa_assert(iter);
407 
408     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub));
409     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name));
410     append_variant_object(m, &sub, value);
411     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
412 }
413 
append_property_dict_entry_unsigned(DBusMessage * m,DBusMessageIter * iter,const char * name,unsigned u)414 static void append_property_dict_entry_unsigned(DBusMessage *m, DBusMessageIter *iter, const char *name, unsigned u) {
415     DBusMessageIter sub;
416 
417     pa_assert(iter);
418 
419     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub));
420     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name));
421     append_variant_unsigned(m, &sub, u);
422     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
423 }
424 
append_property_dict_entry_boolean(DBusMessage * m,DBusMessageIter * iter,const char * name,dbus_bool_t b)425 static void append_property_dict_entry_boolean(DBusMessage *m, DBusMessageIter *iter, const char *name, dbus_bool_t b) {
426     DBusMessageIter sub;
427 
428     pa_assert(iter);
429 
430     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub));
431     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name));
432     append_variant_boolean(m, &sub, b);
433     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
434 }
435 
append_property_dict_entry_urls(DBusMessage * m,DBusMessageIter * iter,const struct userdata * u,pa_sink * sink,pa_source * source)436 static void append_property_dict_entry_urls(DBusMessage *m, DBusMessageIter *iter, const struct userdata *u, pa_sink *sink, pa_source *source) {
437     DBusMessageIter sub;
438     const char *property_name = "URLs";
439 
440     pa_assert(iter);
441 
442     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub));
443     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &property_name));
444     append_variant_urls(m, &sub, u, sink, source);
445     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
446 }
447 
append_property_dict_entry_mime_type(DBusMessage * m,DBusMessageIter * iter,pa_sink * sink,pa_source * source)448 static void append_property_dict_entry_mime_type(DBusMessage *m, DBusMessageIter *iter, pa_sink *sink, pa_source *source) {
449     DBusMessageIter sub;
450     const char *property_name = "MIMEType";
451 
452     pa_assert(iter);
453 
454     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub));
455     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &property_name));
456     append_variant_mime_type(m, &sub, sink, source);
457     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
458 }
459 
append_property_dict_entry_item_display_name(DBusMessage * m,DBusMessageIter * iter,pa_sink * sink,pa_source * source)460 static void append_property_dict_entry_item_display_name(DBusMessage *m, DBusMessageIter *iter, pa_sink *sink, pa_source *source) {
461     DBusMessageIter sub;
462     const char *property_name = "DisplayName";
463 
464     pa_assert(iter);
465 
466     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub));
467     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &property_name));
468     append_variant_item_display_name(m, &sub, sink, source);
469     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
470 }
471 
get_mediacontainer2_list_args(DBusMessage * m,unsigned * offset,unsigned * max_entries,char *** filter,int * filter_len)472 static bool get_mediacontainer2_list_args(DBusMessage *m, unsigned *offset, unsigned *max_entries, char ***filter, int *filter_len) {
473     DBusError error;
474 
475     dbus_error_init(&error);
476 
477     pa_assert(m);
478     pa_assert(offset);
479     pa_assert(max_entries);
480     pa_assert(filter);
481 
482     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)) {
483         dbus_error_free(&error);
484         return false;
485     }
486 
487     return true;
488 }
489 
get_sinks_or_sources_count(const char * path,const struct userdata * u)490 static unsigned get_sinks_or_sources_count(const char *path, const struct userdata *u) {
491     unsigned n, k;
492 
493     n = pa_idxset_size(u->core->sinks);
494     k = pa_idxset_size(u->core->sources);
495     pa_assert(k >= n);
496 
497     return pa_streq(path, OBJECT_SINKS) ? n : k - n;
498 }
499 
append_sink_or_source_container_mediaobject2_properties(DBusMessage * r,DBusMessageIter * sub,const char * path)500 static void append_sink_or_source_container_mediaobject2_properties(DBusMessage *r, DBusMessageIter *sub, const char *path) {
501     append_property_dict_entry_object(r, sub, "Parent", OBJECT_ROOT);
502     append_property_dict_entry_string(r, sub, "Type", "container");
503     append_property_dict_entry_object(r, sub, "Path", path);
504     append_property_dict_entry_string(r, sub, "DisplayName",
505                                       pa_streq(path, OBJECT_SINKS) ?
506                                       _("Output Devices") :
507                                       _("Input Devices"));
508 }
509 
append_sink_or_source_container_properties(DBusMessage * r,DBusMessageIter * iter,const char * path,const struct userdata * user_data,char * const * filter,int filter_len)510 static void append_sink_or_source_container_properties(
511     DBusMessage *r, DBusMessageIter *iter,
512     const char *path, const struct userdata *user_data,
513     char * const * filter, int filter_len) {
514 
515     DBusMessageIter sub;
516 
517     pa_assert(r);
518     pa_assert(iter);
519     pa_assert(path);
520     pa_assert(filter);
521 
522     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
523 
524     if (filter_len == 1 && (*filter)[0] == '*' && (*filter)[1] == '\0') {
525         append_sink_or_source_container_mediaobject2_properties(r, &sub, path);
526         append_property_dict_entry_unsigned(r, &sub, "ChildCount", get_sinks_or_sources_count(path, user_data));
527         append_property_dict_entry_boolean(r, &sub, "Searchable", FALSE);
528     }
529     else {
530         for (int i = 0; i < filter_len; ++i) {
531             const char *property_name = filter[i];
532             if (pa_streq(property_name, "Parent")) {
533                 append_property_dict_entry_object(r, &sub, "Parent", OBJECT_ROOT);
534             }
535             else if (pa_streq(property_name, "Type")) {
536                 append_property_dict_entry_string(r, &sub, "Type", "container");
537             }
538             else if (pa_streq(property_name, "Path")) {
539                 append_property_dict_entry_object(r, &sub, "Path", path);
540             }
541             else if (pa_streq(property_name, "DisplayName")) {
542                 append_property_dict_entry_string(r, &sub, "DisplayName",
543                                                   pa_streq(path, OBJECT_SINKS) ?
544                                                   _("Output Devices") :
545                                                   _("Input Devices"));
546             }
547             else if (pa_streq(property_name, "ChildCount")) {
548                 append_property_dict_entry_unsigned(r, &sub, "ChildCount", get_sinks_or_sources_count(path, user_data));
549             }
550             else if (pa_streq(property_name, "Searchable")) {
551                 append_property_dict_entry_boolean(r, &sub, "Searchable", FALSE);
552             }
553         }
554     }
555 
556     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
557 }
558 
append_sink_or_source_item_mediaobject2_properties(DBusMessage * r,DBusMessageIter * sub,const char * path,pa_sink * sink,pa_source * source)559 static void append_sink_or_source_item_mediaobject2_properties(DBusMessage *r, DBusMessageIter *sub, const char *path, pa_sink *sink, pa_source *source) {
560     append_property_dict_entry_object(r, sub, "Parent", sink ? OBJECT_SINKS : OBJECT_SOURCES);
561     append_property_dict_entry_string(r, sub, "Type", "audio");
562     append_property_dict_entry_object(r, sub, "Path", path);
563     append_property_dict_entry_item_display_name(r, sub, sink, source);
564 }
565 
append_sink_or_source_item_properties(DBusMessage * r,DBusMessageIter * iter,const char * path,const struct userdata * user_data,pa_sink * sink,pa_source * source,char * const * filter,int filter_len)566 static void append_sink_or_source_item_properties(
567     DBusMessage *r, DBusMessageIter *iter,
568     const char *path, const struct userdata *user_data,
569     pa_sink *sink, pa_source *source,
570     char * const * filter, int filter_len) {
571 
572     DBusMessageIter sub;
573 
574     pa_assert(r);
575     pa_assert(iter);
576     pa_assert(path);
577     pa_assert(filter);
578     pa_assert(sink || source);
579 
580     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
581 
582     if (filter_len == 1 && (*filter)[0] == '*' && (*filter)[1] == '\0') {
583         append_sink_or_source_item_mediaobject2_properties(r, &sub, path, sink, source);
584         append_property_dict_entry_urls(r, &sub, user_data, sink, source);
585         append_property_dict_entry_mime_type(r, &sub, sink, source);
586         append_property_dict_entry_string(r, &sub, "DLNAProfile", "LPCM");
587     }
588     else {
589         for (int i = 0; i < filter_len; ++i) {
590             const char *property_name = filter[i];
591             if (pa_streq(property_name, "Parent")) {
592                 append_property_dict_entry_object(r, &sub, "Parent", sink ? OBJECT_SINKS : OBJECT_SOURCES);
593             }
594             else if (pa_streq(property_name, "Type")) {
595                 append_property_dict_entry_string(r, &sub, "Type", "audio");
596             }
597             else if (pa_streq(property_name, "Path")) {
598                 append_property_dict_entry_object(r, &sub, "Path", path);
599             }
600             else if (pa_streq(property_name, "DisplayName")) {
601                 append_property_dict_entry_item_display_name(r, &sub, sink, source);
602             }
603             else if (pa_streq(property_name, "URLs")) {
604                 append_property_dict_entry_urls(r, &sub, user_data, sink, source);
605             }
606             else if (pa_streq(property_name, "MIMEType")) {
607                 append_property_dict_entry_mime_type(r, &sub, sink, source);
608             }
609             else if (pa_streq(property_name, "DLNAProfile")) {
610                 append_property_dict_entry_string(r, &sub, "DLNAProfile", "LPCM");
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 
root_handler(DBusConnection * c,DBusMessage * m,void * userdata)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 
compute_url(const struct userdata * u,const char * name)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 
sinks_and_sources_handler(DBusConnection * c,DBusMessage * m,void * userdata)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_to_string_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", "DLNAProfile")) {
992             pa_assert_se(r = dbus_message_new_method_return(m));
993             append_variant_string(r, NULL, "LPCM");
994 
995         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem2", "URLs")) {
996             pa_assert_se(r = dbus_message_new_method_return(m));
997             append_variant_urls(r, NULL, u, sink, source);
998 
999         } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaItem2")) {
1000             DBusMessageIter iter, sub;
1001 
1002             pa_assert_se(r = dbus_message_new_method_return(m));
1003             dbus_message_iter_init_append(r, &iter);
1004 
1005             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
1006 
1007             append_property_dict_entry_mime_type(r, &sub, sink, source);
1008             append_property_dict_entry_string(r, &sub, "DLNAProfile", "LPCM");
1009             append_property_dict_entry_urls(r, &sub, u, sink, source);
1010 
1011             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
1012 
1013         } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
1014             const char *xml =
1015                 ITEM_INTROSPECT_XML;
1016 
1017             pa_assert_se(r = dbus_message_new_method_return(m));
1018             pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID));
1019 
1020         } else
1021             return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1022     }
1023 
1024     if (r) {
1025         pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), r, NULL));
1026         dbus_message_unref(r);
1027     }
1028 
1029     return DBUS_HANDLER_RESULT_HANDLED;
1030 }
1031 
pa__init(pa_module * m)1032 int pa__init(pa_module *m) {
1033 
1034     struct userdata *u;
1035     pa_modargs *ma = NULL;
1036     DBusError error;
1037     const char *t;
1038 
1039     static const DBusObjectPathVTable vtable_root = {
1040         .message_function = root_handler,
1041     };
1042     static const DBusObjectPathVTable vtable_sinks_and_sources = {
1043         .message_function = sinks_and_sources_handler,
1044     };
1045 
1046     dbus_error_init(&error);
1047 
1048     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
1049         pa_log("Failed to parse module arguments.");
1050         goto fail;
1051     }
1052 
1053     m->userdata = u = pa_xnew0(struct userdata, 1);
1054     u->core = m->core;
1055     u->module = m;
1056     u->http = pa_http_protocol_get(u->core);
1057 
1058     if ((t = pa_modargs_get_value(ma, "display_name", NULL)))
1059         u->display_name = pa_utf8_filter(t);
1060     else
1061         u->display_name = pa_xstrdup(_("Audio on @HOSTNAME@"));
1062 
1063     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);
1064     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);
1065 
1066     if (!(u->bus = pa_dbus_bus_get(m->core, DBUS_BUS_SESSION, &error))) {
1067         pa_log("Failed to get session bus connection: %s", error.message);
1068         goto fail;
1069     }
1070 
1071     pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(u->bus), OBJECT_ROOT, &vtable_root, u));
1072     pa_assert_se(dbus_connection_register_fallback(pa_dbus_connection_get(u->bus), OBJECT_SINKS, &vtable_sinks_and_sources, u));
1073     pa_assert_se(dbus_connection_register_fallback(pa_dbus_connection_get(u->bus), OBJECT_SOURCES, &vtable_sinks_and_sources, u));
1074 
1075     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) {
1076         pa_log("Failed to request service name " SERVICE_NAME ": %s", error.message);
1077         goto fail;
1078     }
1079 
1080     u->got_name = true;
1081 
1082     pa_modargs_free(ma);
1083 
1084     return 0;
1085 
1086 fail:
1087     pa__done(m);
1088 
1089     if (ma)
1090         pa_modargs_free(ma);
1091 
1092     dbus_error_free(&error);
1093 
1094     return -1;
1095 }
1096 
pa__done(pa_module * m)1097 void pa__done(pa_module*m) {
1098     struct userdata*u;
1099     pa_assert(m);
1100 
1101     if (!(u = m->userdata))
1102         return;
1103 
1104     if (u->source_new_slot)
1105         pa_hook_slot_free(u->source_new_slot);
1106     if (u->source_unlink_slot)
1107         pa_hook_slot_free(u->source_unlink_slot);
1108 
1109     if (u->bus) {
1110         DBusError error;
1111 
1112         dbus_error_init(&error);
1113 
1114         dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_ROOT);
1115         dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_SINKS);
1116         dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_SOURCES);
1117 
1118         if (u->got_name) {
1119             if (dbus_bus_release_name(pa_dbus_connection_get(u->bus), SERVICE_NAME, &error) != DBUS_RELEASE_NAME_REPLY_RELEASED) {
1120                 pa_log("Failed to release service name " SERVICE_NAME ": %s", error.message);
1121                 dbus_error_free(&error);
1122             }
1123         }
1124 
1125         pa_dbus_connection_unref(u->bus);
1126     }
1127 
1128     pa_xfree(u->display_name);
1129 
1130     if (u->http)
1131         pa_http_protocol_unref(u->http);
1132 
1133     pa_xfree(u);
1134 }
1135