diff options
| author | Stephen Moehle <stephen.moehle@gmail.com> | 2010-11-14 12:31:15 -0800 | 
|---|---|---|
| committer | Colin Guthrie <cguthrie@mandriva.org> | 2010-11-16 23:03:11 +0000 | 
| commit | ff7acb4d059e21b318b0464ee6fd1785318071cc (patch) | |
| tree | b6802c20e89481801b7a2a2df20e3a506e346fb8 | |
| parent | 914876b40a15be7316ed4610acd782c0abd2c332 (diff) | |
upnp: Implement the MediaServer2 D-Bus interface
This allows PulseAudio to work with versions of Rygel 0.7.1 and higher
which only support MediaServer2:
 http://live.gnome.org/Rygel/MediaServer2Spec
| -rw-r--r-- | src/modules/module-rygel-media-server.c | 662 | 
1 files changed, 463 insertions, 199 deletions
diff --git a/src/modules/module-rygel-media-server.c b/src/modules/module-rygel-media-server.c index 82bcd14c..76b485c3 100644 --- a/src/modules/module-rygel-media-server.c +++ b/src/modules/module-rygel-media-server.c @@ -52,33 +52,52 @@ PA_MODULE_AUTHOR("Lennart Poettering");  PA_MODULE_DESCRIPTION("UPnP MediaServer Plugin for Rygel");  PA_MODULE_VERSION(PACKAGE_VERSION);  PA_MODULE_LOAD_ONCE(TRUE); -PA_MODULE_USAGE( -        "display_name=<UPnP Media Server name>"); +PA_MODULE_USAGE("display_name=<UPnP Media Server name>"); -/* This implements http://live.gnome.org/Rygel/MediaServerSpec */ +/* This implements http://live.gnome.org/Rygel/MediaServer2Spec */ -#define SERVICE_NAME "org.gnome.UPnP.MediaServer1.PulseAudio" +#define SERVICE_NAME "org.gnome.UPnP.MediaServer2.PulseAudio" -#define OBJECT_ROOT "/org/gnome/UPnP/MediaServer1/PulseAudio" -#define OBJECT_SINKS "/org/gnome/UPnP/MediaServer1/PulseAudio/Sinks" -#define OBJECT_SOURCES "/org/gnome/UPnP/MediaServer1/PulseAudio/Sources" +#define OBJECT_ROOT "/org/gnome/UPnP/MediaServer2/PulseAudio" +#define OBJECT_SINKS "/org/gnome/UPnP/MediaServer2/PulseAudio/Sinks" +#define OBJECT_SOURCES "/org/gnome/UPnP/MediaServer2/PulseAudio/Sources"  #define CONTAINER_INTROSPECT_XML_PREFIX                                 \      DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \      "<node>"                                                            \      " <!-- If you are looking for documentation make sure to check out" \ -    "      http://live.gnome.org/Rygel/MediaServerSpec -->"             \ -    " <interface name=\"org.gnome.UPnP.MediaContainer1\">"              \ +    "      http://live.gnome.org/Rygel/MediaServer2Spec -->"            \ +    " <interface name=\"org.gnome.UPnP.MediaContainer2\">"              \ +    "  <method name='ListChildren'>"                                    \ +    "   <arg direction='in' name='offset' type='u' />"                  \ +    "   <arg direction='in' name='max' type='u' />"                     \ +    "   <arg direction='in' name='filter' type='as' />"                 \ +    "   <arg direction='out' type='aa{sv}' />"                          \ +    "  </method>"                                                       \ +    "  <method name='ListContainers'>"                                  \ +    "   <arg direction='in' name='offset' type='u' />"                  \ +    "   <arg direction='in' name='max' type='u' />"                     \ +    "   <arg direction='in' name='filter' type='as' />"                 \ +    "   <arg direction='out' type='aa{sv}' />"                          \ +    "  </method>"                                                       \ +    "  <method name='ListItems'>"                                       \ +    "   <arg direction='in' name='offset' type='u' />"                  \ +    "   <arg direction='in' name='max' type='u' />"                     \ +    "   <arg direction='in' name='filter' type='as' />"                 \ +    "   <arg direction='out' type='aa{sv}' />"                          \ +    "  </method>"                                                       \      "  <signal name=\"Updated\">"                                       \      "   <arg name=\"path\" type=\"o\"/>"                                \      "  </signal>"                                                       \ -    "  <property name=\"Items\" type=\"ao\" access=\"read\"/>"          \ +    "  <property name=\"ChildCount\" type=\"u\" access=\"read\"/>"      \      "  <property name=\"ItemCount\" type=\"u\" access=\"read\"/>"       \ -    "  <property name=\"Containers\" type=\"ao\" access=\"read\"/>"     \      "  <property name=\"ContainerCount\" type=\"u\" access=\"read\"/>"  \ +    "  <property name=\"Searchable\" type=\"b\" access=\"read\"/>"      \      " </interface>"                                                     \ -    " <interface name=\"org.gnome.UPnP.MediaObject1\">"                 \ +    " <interface name=\"org.gnome.UPnP.MediaObject2\">"                 \      "  <property name=\"Parent\" type=\"s\" access=\"read\"/>"          \ +    "  <property name=\"Type\" type=\"s\" access=\"read\"/>"            \ +    "  <property name=\"Path\" type=\"s\" access=\"read\"/>"            \      "  <property name=\"DisplayName\" type=\"s\" access=\"read\"/>"     \      " </interface>"                                                     \      " <interface name=\"org.freedesktop.DBus.Properties\">"             \ @@ -111,14 +130,14 @@ PA_MODULE_USAGE(      DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \      "<node>"                                                            \      " <!-- If you are looking for documentation make sure to check out" \ -    "      http://live.gnome.org/Rygel/MediaProviderSpec -->"           \ -    " <interface name=\"org.gnome.UPnP.MediaItem1\">"                   \ +    "      http://live.gnome.org/Rygel/MediaProvider2Spec -->"          \ +    " <interface name=\"org.gnome.UPnP.MediaItem2\">"                   \      "  <property name=\"URLs\" type=\"as\" access=\"read\"/>"           \      "  <property name=\"MIMEType\" type=\"s\" access=\"read\"/>"        \ -    "  <property name=\"Type\" type=\"s\" access=\"read\"/>"            \ -    " </interface>"                                                     \ -    " <interface name=\"org.gnome.UPnP.MediaObject1\">"                 \ +    " <interface name=\"org.gnome.UPnP.MediaObject2\">"                 \      "  <property name=\"Parent\" type=\"s\" access=\"read\"/>"          \ +    "  <property name=\"Type\" type=\"s\" access=\"read\"/>"            \ +    "  <property name=\"Path\" type=\"s\" access=\"read\"/>"            \      "  <property name=\"DisplayName\" type=\"s\" access=\"read\"/>"     \      " </interface>"                                                     \      " <interface name=\"org.freedesktop.DBus.Properties\">"             \ @@ -159,6 +178,8 @@ struct userdata {      pa_http_protocol *http;  }; +static char *compute_url(const struct userdata *u, const char *name); +  static void send_signal(struct userdata *u, pa_source *s) {      DBusMessage *m;      const char *parent; @@ -174,7 +195,7 @@ static void send_signal(struct userdata *u, pa_source *s) {      else          parent = OBJECT_SOURCES; -    pa_assert_se(m = dbus_message_new_signal(parent, "org.gnome.UPnP.MediaContainer1", "Updated")); +    pa_assert_se(m = dbus_message_new_signal(parent, "org.gnome.UPnP.MediaContainer2", "Updated"));      pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), m, NULL));      dbus_message_unref(m); @@ -296,6 +317,69 @@ static void append_variant_unsigned(DBusMessage *m, DBusMessageIter *iter, unsig      pa_assert_se(dbus_message_iter_close_container(iter, &sub));  } +static void append_variant_boolean(DBusMessage *m, DBusMessageIter *iter, dbus_bool_t b) { +    DBusMessageIter _iter, sub; + +    pa_assert(m); + +    if (!iter) { +        dbus_message_iter_init_append(m, &_iter); +        iter = &_iter; +    } + +    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "b", &sub)); +    pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_BOOLEAN, &b)); +    pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_variant_urls(DBusMessage *m, DBusMessageIter *iter, const struct userdata *u, pa_sink *sink, pa_source *source) { +    DBusMessageIter _iter, sub, array; +    char *url; + +    pa_assert(m); +    pa_assert(u); +    pa_assert(sink || source); + +    if (!iter) { +        dbus_message_iter_init_append(m, &_iter); +        iter = &_iter; +    } + +    url = compute_url(u, sink ? sink->monitor_source->name : source->name); + +    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "as", &sub)); +    pa_assert_se(dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, "s", &array)); +    pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &url)); +    pa_assert_se(dbus_message_iter_close_container(&sub, &array)); +    pa_assert_se(dbus_message_iter_close_container(iter, &sub)); + +    pa_xfree(url); +} + +static void append_variant_mime_type(DBusMessage *m, DBusMessageIter *iter, pa_sink *sink, pa_source *source) { +    char *mime_type; + +    pa_assert(sink || source); + +    if (sink) +        mime_type = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map); +    else +        mime_type = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map); + +    append_variant_string(m, iter, mime_type); + +    pa_xfree(mime_type); +} + +static void append_variant_item_display_name(DBusMessage *m, DBusMessageIter *iter, pa_sink *sink, pa_source *source) { +    const char *display_name; + +    pa_assert(sink || source); + +    display_name = pa_strna(pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_DESCRIPTION)); +    append_variant_string(m, iter, display_name); +} +  static void append_property_dict_entry_object_array(DBusMessage *m, DBusMessageIter *iter, const char *name, const char *path[], unsigned n) {      DBusMessageIter sub; @@ -340,6 +424,195 @@ static void append_property_dict_entry_unsigned(DBusMessage *m, DBusMessageIter      pa_assert_se(dbus_message_iter_close_container(iter, &sub));  } +static void append_property_dict_entry_boolean(DBusMessage *m, DBusMessageIter *iter, const char *name, dbus_bool_t b) { +    DBusMessageIter sub; + +    pa_assert(iter); + +    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub)); +    pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name)); +    append_variant_boolean(m, &sub, b); +    pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_property_dict_entry_urls(DBusMessage *m, DBusMessageIter *iter, const struct userdata *u, pa_sink *sink, pa_source *source) { +    DBusMessageIter sub; +    const char *property_name = "URLs"; + +    pa_assert(iter); + +    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub)); +    pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &property_name)); +    append_variant_urls(m, &sub, u, sink, source); +    pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_property_dict_entry_mime_type(DBusMessage *m, DBusMessageIter *iter, pa_sink *sink, pa_source *source) { +    DBusMessageIter sub; +    const char *property_name = "MIMEType"; + +    pa_assert(iter); + +    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub)); +    pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &property_name)); +    append_variant_mime_type(m, &sub, sink, source); +    pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_property_dict_entry_item_display_name(DBusMessage *m, DBusMessageIter *iter, pa_sink *sink, pa_source *source) { +    DBusMessageIter sub; +    const char *property_name = "DisplayName"; + +    pa_assert(iter); + +    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub)); +    pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &property_name)); +    append_variant_item_display_name(m, &sub, sink, source); +    pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static pa_bool_t get_mediacontainer2_list_args(DBusMessage *m, unsigned *offset, unsigned *max_entries, char ***filter, int *filter_len) { +    DBusError error; + +    dbus_error_init(&error); + +    pa_assert(m); +    pa_assert(offset); +    pa_assert(max_entries); +    pa_assert(filter); + +    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)) { +        dbus_error_free(&error); +        return FALSE; +    } + +    return TRUE; +} + +static unsigned get_sinks_or_sources_count(const char *path, const struct userdata *u) { +    unsigned n, k; + +    n = pa_idxset_size(u->core->sinks); +    k = pa_idxset_size(u->core->sources); +    pa_assert(k >= n); + +    return pa_streq(path, OBJECT_SINKS) ? n : k - n; +} + +static void append_sink_or_source_container_mediaobject2_properties(DBusMessage *r, DBusMessageIter *sub, const char *path) { +    append_property_dict_entry_object(r, sub, "Parent", OBJECT_ROOT); +    append_property_dict_entry_string(r, sub, "Type", "container"); +    append_property_dict_entry_object(r, sub, "Path", path); +    append_property_dict_entry_string(r, sub, "DisplayName", +                                      pa_streq(path, OBJECT_SINKS) ? +                                      _("Output Devices") : +                                      _("Input Devices")); +} + +static void append_sink_or_source_container_properties( +    DBusMessage *r, DBusMessageIter *iter, +    const char *path, const struct userdata *user_data, +    char * const * filter, int filter_len) { + +    DBusMessageIter sub; + +    pa_assert(r); +    pa_assert(iter); +    pa_assert(path); +    pa_assert(filter); + +    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); + +    if (filter_len == 1 && (*filter)[0] == '*' && (*filter)[1] == '\0') { +        append_sink_or_source_container_mediaobject2_properties(r, &sub, path); +        append_property_dict_entry_unsigned(r, &sub, "ChildCount", get_sinks_or_sources_count(path, user_data)); +        append_property_dict_entry_boolean(r, &sub, "Searchable", FALSE); +    } +    else { +        for (int i = 0; i < filter_len; ++i) { +            const char *property_name = filter[i]; +            if (pa_streq(property_name, "Parent")) { +                append_property_dict_entry_object(r, &sub, "Parent", OBJECT_ROOT); +            } +            else if (pa_streq(property_name, "Type")) { +                append_property_dict_entry_string(r, &sub, "Type", "container"); +            } +            else if (pa_streq(property_name, "Path")) { +                append_property_dict_entry_object(r, &sub, "Path", path); +            } +            else if (pa_streq(property_name, "DisplayName")) { +                append_property_dict_entry_string(r, &sub, "DisplayName", +                                                  pa_streq(path, OBJECT_SINKS) ? +                                                  _("Output Devices") : +                                                  _("Input Devices")); +            } +            else if (pa_streq(property_name, "ChildCount")) { +                append_property_dict_entry_unsigned(r, &sub, "ChildCount", get_sinks_or_sources_count(path, user_data)); +            } +            else if (pa_streq(property_name, "Searchable")) { +                append_property_dict_entry_boolean(r, &sub, "Searchable", FALSE); +            } +        } +    } + +    pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} + +static void append_sink_or_source_item_mediaobject2_properties(DBusMessage *r, DBusMessageIter *sub, const char *path, pa_sink *sink, pa_source *source) { +    append_property_dict_entry_object(r, sub, "Parent", sink ? OBJECT_SINKS : OBJECT_SOURCES); +    append_property_dict_entry_string(r, sub, "Type", "audio"); +    append_property_dict_entry_object(r, sub, "Path", path); +    append_property_dict_entry_item_display_name(r, sub, sink, source); +} + +static void 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) { + +    DBusMessageIter sub; + +    pa_assert(r); +    pa_assert(iter); +    pa_assert(path); +    pa_assert(filter); +    pa_assert(sink || source); + +    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); + +    if (filter_len == 1 && (*filter)[0] == '*' && (*filter)[1] == '\0') { +        append_sink_or_source_item_mediaobject2_properties(r, &sub, path, sink, source); +        append_property_dict_entry_urls(r, &sub, user_data, sink, source); +        append_property_dict_entry_mime_type(r, &sub, sink, source); +    } +    else { +        for (int i = 0; i < filter_len; ++i) { +            const char *property_name = filter[i]; +            if (pa_streq(property_name, "Parent")) { +                append_property_dict_entry_object(r, &sub, "Parent", sink ? OBJECT_SINKS : OBJECT_SOURCES); +            } +            else if (pa_streq(property_name, "Type")) { +                append_property_dict_entry_string(r, &sub, "Type", "audio"); +            } +            else if (pa_streq(property_name, "Path")) { +                append_property_dict_entry_object(r, &sub, "Path", path); +            } +            else if (pa_streq(property_name, "DisplayName")) { +                append_property_dict_entry_item_display_name(r, &sub, sink, source); +            } +            else if (pa_streq(property_name, "URLs")) { +                append_property_dict_entry_urls(r, &sub, user_data, sink, source); +            } +            else if (pa_streq(property_name, "MIMEType")) { +                append_property_dict_entry_mime_type(r, &sub, sink, source); +            } +        } +    } + +    pa_assert_se(dbus_message_iter_close_container(iter, &sub)); +} +  static const char *array_root_containers[] = { OBJECT_SINKS, OBJECT_SOURCES };  static const char *array_no_children[] = { }; @@ -349,50 +622,100 @@ static DBusHandlerResult root_handler(DBusConnection *c, DBusMessage *m, void *u      pa_assert(u); -    if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Containers")) { +    if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ChildCount")) {          pa_assert_se(r = dbus_message_new_method_return(m)); -        append_variant_object_array(r, NULL, (const char**) array_root_containers, PA_ELEMENTSOF(array_root_containers)); +        append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_root_containers)); -    } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ContainerCount")) { +    } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ItemCount")) {          pa_assert_se(r = dbus_message_new_method_return(m)); -        append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_root_containers)); +        append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_no_children)); -    } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Items")) { +    } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ContainerCount")) {          pa_assert_se(r = dbus_message_new_method_return(m)); -        append_variant_object_array(r, NULL, array_no_children, PA_ELEMENTSOF(array_no_children)); +        append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_root_containers)); -    } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ItemCount")) { +    } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "Searchable")) {          pa_assert_se(r = dbus_message_new_method_return(m)); -        append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_no_children)); +        append_variant_boolean(r, NULL, FALSE); -    } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer1")) { +    } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer2")) {          DBusMessageIter iter, sub;          pa_assert_se(r = dbus_message_new_method_return(m));          dbus_message_iter_init_append(r, &iter);          pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); -        append_property_dict_entry_object_array(r, &sub, "Containers", array_root_containers, PA_ELEMENTSOF(array_root_containers)); -        append_property_dict_entry_unsigned(r, &sub, "ContainerCount", PA_ELEMENTSOF(array_root_containers)); -        append_property_dict_entry_object_array(r, &sub, "Items", array_no_children, PA_ELEMENTSOF(array_no_children)); +        append_property_dict_entry_unsigned(r, &sub, "ChildCount", PA_ELEMENTSOF(array_root_containers));          append_property_dict_entry_unsigned(r, &sub, "ItemCount", PA_ELEMENTSOF(array_no_children)); +        append_property_dict_entry_unsigned(r, &sub, "ContainerCount", PA_ELEMENTSOF(array_root_containers)); +        append_property_dict_entry_boolean(r, &sub, "Searchable", FALSE); +        pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + +    } else if (dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListChildren") +        || dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListContainers")) { +        DBusMessageIter iter, sub; +        unsigned offset, max; +        char ** filter; +        int filter_len; + +        pa_assert_se(r = dbus_message_new_method_return(m)); + +        dbus_message_iter_init_append(r, &iter); +        pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "a{sv}", &sub)); + +        if (get_mediacontainer2_list_args(m, &offset, &max, &filter, &filter_len)) { +            unsigned end = (max != 0 && offset + max < PA_ELEMENTSOF(array_root_containers)) +                                ? max + offset +                                : PA_ELEMENTSOF(array_root_containers); + +            for (unsigned i = offset; i < end; ++i) { +                const char *container_path = array_root_containers[i]; +                append_sink_or_source_container_properties(r, &sub, container_path, u, filter, filter_len); +            } + +            dbus_free_string_array(filter); +        } + +        pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); + +    } else if (dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListItems")) { +        DBusMessageIter iter, sub; + +        pa_assert_se(r = dbus_message_new_method_return(m)); + +        dbus_message_iter_init_append(r, &iter); +        pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "a{sv}", &sub));          pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); -    } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "Parent")) { +    } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Parent")) {          pa_assert_se(r = dbus_message_new_method_return(m));          append_variant_object(r, NULL, OBJECT_ROOT); -    } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "DisplayName")) { +    } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Type")) { +        pa_assert_se(r = dbus_message_new_method_return(m)); +        append_variant_string(r, NULL, "container"); + +    } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Path")) { +        const char *path = dbus_message_get_path(m); + +        pa_assert_se(r = dbus_message_new_method_return(m)); +        append_variant_object(r, NULL, path); + +    } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "DisplayName")) {          pa_assert_se(r = dbus_message_new_method_return(m));          append_variant_string(r, NULL, u->display_name); -    } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject1")) { +    } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject2")) {          DBusMessageIter iter, sub; +        const char *path = dbus_message_get_path(m);          pa_assert_se(r = dbus_message_new_method_return(m));          dbus_message_iter_init_append(r, &iter);          pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); +        append_property_dict_entry_object(r, &sub, "Parent", OBJECT_ROOT); +        append_property_dict_entry_string(r, &sub, "Type", "container"); +        append_property_dict_entry_object(r, &sub, "Path", path);          append_property_dict_entry_string(r, &sub, "DisplayName", u->display_name);          pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); @@ -416,7 +739,7 @@ static DBusHandlerResult root_handler(DBusConnection *c, DBusMessage *m, void *u      return DBUS_HANDLER_RESULT_HANDLED;  } -static char *compute_url(struct userdata *u, const char *name) { +static char *compute_url(const struct userdata *u, const char *name) {      pa_strlist *i;      pa_assert(u); @@ -453,65 +776,6 @@ static char *compute_url(struct userdata *u, const char *name) {      return pa_sprintf_malloc("http://@ADDRESS@:4714/listen/source/%s", name);  } -static char **child_array(struct userdata *u, const char *path, unsigned *n) { -    unsigned m; -    uint32_t idx; -    char **array; - -    pa_assert(u); -    pa_assert(path); -    pa_assert(n); - -    if (pa_streq(path, OBJECT_SINKS)) -        m = pa_idxset_size(u->core->sinks); -    else { -        unsigned k; - -        m = pa_idxset_size(u->core->sources); -        k = pa_idxset_size(u->core->sinks); - -        pa_assert(m >= k); - -        /* Subtract the monitor sources from the numbers of -         * sources. There is one monitor source for each sink */ -        m -= k; -    } - -    array = pa_xnew(char*, m); -    *n = 0; - -    if (pa_streq(path, OBJECT_SINKS)) { -        pa_sink *sink; - -        PA_IDXSET_FOREACH(sink, u->core->sinks, idx) { -            pa_assert((*n) < m); -            array[(*n)++] = pa_sprintf_malloc(OBJECT_SINKS "/%u", sink->index); -        } -    } else { -        pa_source *source; - -        PA_IDXSET_FOREACH(source, u->core->sources, idx) { - -            if (!source->monitor_of) { -                pa_assert((*n) < m); -                array[(*n)++] = pa_sprintf_malloc(OBJECT_SOURCES "/%u", source->index); -            } -        } -    } - -    pa_assert((*n) <= m); - -    return array; -} - -static void free_child_array(char **array, unsigned n) { - -    for (; n >= 1; n--) -        pa_xfree(array[n-1]); - -    pa_xfree(array); -} -  static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessage *m, void *userdata) {      struct userdata *u = userdata;      DBusMessage *r = NULL; @@ -525,66 +789,114 @@ static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessag          /* Container nodes */ -        if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Containers")) { +        if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ChildCount") +            || message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ItemCount")) {              pa_assert_se(r = dbus_message_new_method_return(m)); -            append_variant_object_array(r, NULL, array_no_children, PA_ELEMENTSOF(array_no_children)); +            append_variant_unsigned(r, NULL, get_sinks_or_sources_count(path, u)); -        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ContainerCount")) { +        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "ContainerCount")) {              pa_assert_se(r = dbus_message_new_method_return(m)); -            append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_no_children)); +            append_variant_unsigned(r, NULL, 0); -        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Items")) { -            char ** array; -            unsigned n; +        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer2", "Searchable")) { +            pa_assert_se(r = dbus_message_new_method_return(m)); +            append_variant_boolean(r, NULL, FALSE); -            array = child_array(u, path, &n); +        } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer2")) { +            DBusMessageIter iter, sub; +            unsigned item_count;              pa_assert_se(r = dbus_message_new_method_return(m)); -            append_variant_object_array(r, NULL, (const char**) array, n); +            dbus_message_iter_init_append(r, &iter); -            free_child_array(array, n); +            pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); -        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ItemCount")) { -            unsigned n, k; +            item_count = get_sinks_or_sources_count(path, u); -            n = pa_idxset_size(u->core->sinks); -            k = pa_idxset_size(u->core->sources); -            pa_assert(k >= n); +            append_property_dict_entry_unsigned(r, &sub, "ChildCount", item_count); +            append_property_dict_entry_unsigned(r, &sub, "ItemCount", item_count); +            append_property_dict_entry_unsigned(r, &sub, "ContainerCount", 0); +            append_property_dict_entry_boolean(r, &sub, "Searchable", FALSE); -            pa_assert_se(r = dbus_message_new_method_return(m)); -            append_variant_unsigned(r, NULL, -                                    pa_streq(path, OBJECT_SINKS) ? n : k - n); +            pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); -        } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer1")) { +        } else if (dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListChildren") +            || dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListItems")) {              DBusMessageIter iter, sub; -            char **array; -            unsigned n, k; +            unsigned offset, max; +            char **filter; +            int filter_len;              pa_assert_se(r = dbus_message_new_method_return(m)); -            dbus_message_iter_init_append(r, &iter); -            pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); -            append_property_dict_entry_object_array(r, &sub, "Containers", array_no_children, PA_ELEMENTSOF(array_no_children)); -            append_property_dict_entry_unsigned(r, &sub, "ContainerCount", 0); +            dbus_message_iter_init_append(r, &iter); +            pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "a{sv}", &sub)); + +            if (get_mediacontainer2_list_args(m, &offset, &max, &filter, &filter_len)) { +                unsigned end = (max != 0) ? max + offset : UINT_MAX; + +                if (pa_streq(path, OBJECT_SINKS)) { +                    pa_sink *sink; +                    char sink_path[sizeof(OBJECT_SINKS) + 32]; +                    char *path_end = sink_path + sizeof(OBJECT_SINKS); +                    unsigned item_index = 0; +                    uint32_t idx; + +                    strcpy(sink_path, OBJECT_SINKS "/"); + +                    PA_IDXSET_FOREACH(sink, u->core->sinks, idx) { +                        if (item_index >= offset && item_index < end) { +                            sprintf(path_end, "%u", sink->index); +                            append_sink_or_source_item_properties(r, &sub, sink_path, u, sink, NULL, filter, filter_len); +                        } +                        ++item_index; +                    } +                } else { +                    pa_source *source; +                    char source_path[sizeof(OBJECT_SOURCES) + 32]; +                    char *path_end = source_path + sizeof(OBJECT_SOURCES); +                    unsigned item_index = 0; +                    uint32_t idx; + +                    strcpy(source_path, OBJECT_SOURCES "/"); + +                    PA_IDXSET_FOREACH(source, u->core->sources, idx) +                        if (!source->monitor_of) { +                            if (item_index >= offset && item_index < end) { +                                sprintf(path_end, "%u", source->index); +                                append_sink_or_source_item_properties(r, &sub, source_path, u, NULL, source, filter, filter_len); +                            } +                            ++item_index; +                        } +                } + +                dbus_free_string_array(filter); +            } -            array = child_array(u, path, &n); -            append_property_dict_entry_object_array(r, &sub, "Items", (const char**) array, n); -            free_child_array(array, n); +            pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); -            n = pa_idxset_size(u->core->sinks); -            k = pa_idxset_size(u->core->sources); -            pa_assert(k >= n); +        } else if (dbus_message_is_method_call(m, "org.gnome.UPnP.MediaContainer2", "ListContainers")) { +            DBusMessageIter iter, sub; -            append_property_dict_entry_unsigned(r, &sub, "ItemCount", -                                                pa_streq(path, OBJECT_SINKS) ? n : k - n); +            pa_assert_se(r = dbus_message_new_method_return(m)); +            dbus_message_iter_init_append(r, &iter); +            pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "a{sv}", &sub));              pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); -        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "Parent")) { +        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Parent")) {              pa_assert_se(r = dbus_message_new_method_return(m));              append_variant_object(r, NULL, OBJECT_ROOT); -        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "DisplayName")) { +        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Type")) { +            pa_assert_se(r = dbus_message_new_method_return(m)); +            append_variant_string(r, NULL, "container"); + +        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Path")) { +            pa_assert_se(r = dbus_message_new_method_return(m)); +            append_variant_object(r, NULL, path); + +        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "DisplayName")) {              pa_assert_se(r = dbus_message_new_method_return(m));              append_variant_string(r,                                    NULL, @@ -592,20 +904,14 @@ static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessag                                    _("Output Devices") :                                    _("Input Devices")); -        } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject1")) { +        } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject2")) {              DBusMessageIter iter, sub;              pa_assert_se(r = dbus_message_new_method_return(m));              dbus_message_iter_init_append(r, &iter); -              pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); -            append_property_dict_entry_object(m, &sub, "Parent", OBJECT_ROOT); -            append_property_dict_entry_string(m, &sub, "DisplayName", -                                              pa_streq(path, OBJECT_SINKS) ? -                                              _("Output Devices") : -                                              _("Input Devices")); -            pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); +            append_sink_or_source_container_mediaobject2_properties(r, &sub, path);          } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {              pa_strbuf *sb; @@ -655,91 +961,49 @@ static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessag          if (!sink && !source)              return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; -        if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "Parent")) { +        if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Parent")) {              pa_assert_se(r = dbus_message_new_method_return(m));              append_variant_object(r, NULL, sink ? OBJECT_SINKS : OBJECT_SOURCES); -        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "DisplayName")) { -            pa_assert_se(r = dbus_message_new_method_return(m)); -            append_variant_string(r, NULL, pa_strna(pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_DESCRIPTION))); - -        } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject1")) { -            DBusMessageIter iter, sub; - -            pa_assert_se(r = dbus_message_new_method_return(m)); -            dbus_message_iter_init_append(r, &iter); - -            pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); -            append_property_dict_entry_object(r, &sub, "Parent", sink ? OBJECT_SINKS : OBJECT_SOURCES); -            append_property_dict_entry_string(r, &sub, "DisplayName", pa_strna(pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_DESCRIPTION))); -            pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); - -        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "Type")) { +        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Type")) {              pa_assert_se(r = dbus_message_new_method_return(m));              append_variant_string(r, NULL, "audio"); -        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "MIMEType")) { -            char *t; - -            if (sink) -                t = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map); -            else -                t = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map); +        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "Path")) { +            pa_assert_se(r = dbus_message_new_method_return(m)); +            append_variant_object(r, NULL, path); +        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject2", "DisplayName")) {              pa_assert_se(r = dbus_message_new_method_return(m)); -            append_variant_string(r, NULL, t); -            pa_xfree(t); +            append_variant_item_display_name(r, NULL, sink, source); -        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "URLs")) { -            DBusMessageIter iter, sub, array; -            char *url; +        } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject2")) { +            DBusMessageIter iter, sub;              pa_assert_se(r = dbus_message_new_method_return(m)); -              dbus_message_iter_init_append(r, &iter); -            url = compute_url(u, sink ? sink->monitor_source->name : source->name); +            pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); +            append_sink_or_source_item_mediaobject2_properties(r, &sub, path, sink, source); -            pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "as", &sub)); -            pa_assert_se(dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, "s", &array)); -            pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &url)); -            pa_assert_se(dbus_message_iter_close_container(&sub, &array)); -            pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); +        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem2", "MIMEType")) { +            pa_assert_se(r = dbus_message_new_method_return(m)); +            append_variant_mime_type(r, NULL, sink, source); -            pa_xfree(url); +        } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem2", "URLs")) { +            pa_assert_se(r = dbus_message_new_method_return(m)); +            append_variant_urls(r, NULL, u, sink, source); -        } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaItem1")) { -            DBusMessageIter iter, sub, dict, variant, array; -            char *url, *t; -            const char *un = "URLs"; +        } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaItem2")) { +            DBusMessageIter iter, sub;              pa_assert_se(r = dbus_message_new_method_return(m));              dbus_message_iter_init_append(r, &iter);              pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)); -            append_property_dict_entry_string(r, &sub, "Type", "audio"); - -            if (sink) -                t = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map); -            else -                t = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map); - -            append_property_dict_entry_string(r, &sub, "MIMEType", t); -            pa_xfree(t); - -            pa_assert_se(dbus_message_iter_open_container(&sub, DBUS_TYPE_DICT_ENTRY, NULL, &dict)); -            pa_assert_se(dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &un)); - -            url = compute_url(u, sink ? sink->monitor_source->name : source->name); - -            pa_assert_se(dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, "as", &variant)); -            pa_assert_se(dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "s", &array)); -            pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &url)); -            pa_assert_se(dbus_message_iter_close_container(&variant, &array)); -            pa_assert_se(dbus_message_iter_close_container(&dict, &variant)); -            pa_assert_se(dbus_message_iter_close_container(&sub, &dict)); -            pa_xfree(url); +            append_property_dict_entry_mime_type(r, &sub, sink, source); +            append_property_dict_entry_urls(r, &sub, u, sink, source);              pa_assert_se(dbus_message_iter_close_container(&iter, &sub));  | 
