]> code.delx.au - pulseaudio/commitdiff
Merge branch 'master' of git://0pointer.de/pulseaudio into dbus-work
authorTanu Kaskinen <tanuk@iki.fi>
Sun, 30 Aug 2009 17:07:31 +0000 (20:07 +0300)
committerTanu Kaskinen <tanuk@iki.fi>
Sun, 30 Aug 2009 17:07:31 +0000 (20:07 +0300)
Conflicts:
src/modules/module-stream-restore.c

49 files changed:
configure.ac
src/Makefile.am
src/daemon/daemon-conf.c
src/daemon/daemon-conf.h
src/daemon/daemon.conf.in
src/daemon/default.pa.in
src/daemon/main.c
src/daemon/server-lookup.c [new file with mode: 0644]
src/daemon/server-lookup.h [new file with mode: 0644]
src/daemon/system.pa.in
src/map-file
src/modules/dbus/iface-card-profile.c [new file with mode: 0644]
src/modules/dbus/iface-card-profile.h [new file with mode: 0644]
src/modules/dbus/iface-card.c [new file with mode: 0644]
src/modules/dbus/iface-card.h [new file with mode: 0644]
src/modules/dbus/iface-client.c [new file with mode: 0644]
src/modules/dbus/iface-client.h [new file with mode: 0644]
src/modules/dbus/iface-core.c [new file with mode: 0644]
src/modules/dbus/iface-core.h [new file with mode: 0644]
src/modules/dbus/iface-device-port.c [new file with mode: 0644]
src/modules/dbus/iface-device-port.h [new file with mode: 0644]
src/modules/dbus/iface-device.c [new file with mode: 0644]
src/modules/dbus/iface-device.h [new file with mode: 0644]
src/modules/dbus/iface-memstats.c [new file with mode: 0644]
src/modules/dbus/iface-memstats.h [new file with mode: 0644]
src/modules/dbus/iface-module.c [new file with mode: 0644]
src/modules/dbus/iface-module.h [new file with mode: 0644]
src/modules/dbus/iface-sample.c [new file with mode: 0644]
src/modules/dbus/iface-sample.h [new file with mode: 0644]
src/modules/dbus/iface-stream.c [new file with mode: 0644]
src/modules/dbus/iface-stream.h [new file with mode: 0644]
src/modules/dbus/module-dbus-protocol.c [new file with mode: 0644]
src/modules/module-stream-restore.c
src/pulse/client-conf.c
src/pulse/client-conf.h
src/pulse/client.conf.in
src/pulse/proplist.c
src/pulse/proplist.h
src/pulsecore/core-util.c
src/pulsecore/core-util.h
src/pulsecore/core.h
src/pulsecore/cpu-arm.h
src/pulsecore/cpu-x86.h
src/pulsecore/dbus-util.c
src/pulsecore/dbus-util.h
src/pulsecore/modargs.c
src/pulsecore/modargs.h
src/pulsecore/protocol-dbus.c [new file with mode: 0644]
src/pulsecore/protocol-dbus.h [new file with mode: 0644]

index 40455e10c76038527911bdfba5e87eb1b0df53ca..12a61fd46e786cfa1cc083a9dc612ba8080dd80f 100644 (file)
@@ -22,8 +22,7 @@
 
 AC_PREREQ(2.63)
 
-AC_INIT([pulseaudio], m4_esyscmd([./git-version-gen .tarball-version]),
-       [mzchyfrnhqvb (at) 0pointer (dot) net])
+AC_INIT([pulseaudio],[m4_esyscmd(./git-version-gen .tarball-version)],[mzchyfrnhqvb (at) 0pointer (dot) net])
 AC_CONFIG_SRCDIR([src/daemon/main.c])
 AC_CONFIG_MACRO_DIR([m4])
 AC_CONFIG_HEADERS([config.h])
index 88f0ff56f1dfa41d6c1df24ea8c1353a8c61d5d4..623cef57a556693e441515b9dc37327859ca7ab5 100644 (file)
@@ -169,13 +169,14 @@ BUILT_SOURCES = \
 bin_PROGRAMS = pulseaudio
 
 pulseaudio_SOURCES = \
-               daemon/caps.h daemon/caps.c \
+               daemon/caps.c daemon/caps.h \
                daemon/cmdline.c daemon/cmdline.h \
                daemon/cpulimit.c daemon/cpulimit.h \
                daemon/daemon-conf.c daemon/daemon-conf.h \
                daemon/dumpmodules.c daemon/dumpmodules.h \
                daemon/ltdl-bind-now.c daemon/ltdl-bind-now.h \
-               daemon/main.c
+               daemon/main.c \
+               daemon/server-lookup.c daemon/server-lookup.h
 
 pulseaudio_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) $(LIBSAMPLERATE_CFLAGS) $(LIBSPEEX_CFLAGS) $(LIBSNDFILE_CFLAGS) $(CAP_CFLAGS) $(LIBOIL_CFLAGS) $(DBUS_CFLAGS)
 pulseaudio_LDADD = $(AM_LDADD) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la $(LIBLTDL) $(LIBSAMPLERATE_LIBS) $(LIBSPEEX_LIBS) $(LIBSNDFILE_LIBS) $(CAP_LIBS) $(LIBOIL_LIBS) $(DBUS_LIBS)
@@ -869,7 +870,9 @@ libpulsecore_@PA_MAJORMINORMICRO@_la_LDFLAGS += $(X11_LIBS)
 endif
 
 if HAVE_DBUS
-libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES += pulsecore/dbus-shared.c pulsecore/dbus-shared.h
+libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES += \
+               pulsecore/dbus-shared.c pulsecore/dbus-shared.h \
+               pulsecore/protocol-dbus.c pulsecore/protocol-dbus.h
 libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS += $(DBUS_CFLAGS)
 libpulsecore_@PA_MAJORMINORMICRO@_la_LIBADD += $(DBUS_LIBS)
 endif
@@ -1156,7 +1159,8 @@ endif
 
 if HAVE_DBUS
 modlibexec_LTLIBRARIES += \
-               module-rygel-media-server.la
+               module-rygel-media-server.la \
+               module-dbus-protocol.la
 endif
 
 if HAVE_BLUEZ
@@ -1251,6 +1255,7 @@ SYMDEF_FILES = \
                modules/module-augment-properties-symdef.h \
                modules/module-cork-music-on-phone-symdef.h \
                modules/module-console-kit-symdef.h \
+               modules/dbus/module-dbus-protocol-symdef.h \
                modules/module-loopback-symdef.h
 
 EXTRA_DIST += $(SYMDEF_FILES)
@@ -1300,6 +1305,24 @@ module_http_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_HTTP $(A
 module_http_protocol_unix_la_LDFLAGS = $(MODULE_LDFLAGS)
 module_http_protocol_unix_la_LIBADD = $(AM_LIBADD) libpulsecore-@PA_MAJORMINORMICRO@.la libprotocol-http.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la
 
+# D-Bus protocol
+
+module_dbus_protocol_la_SOURCES = \
+               modules/dbus/iface-card.c modules/dbus/iface-card.h \
+               modules/dbus/iface-card-profile.c modules/dbus/iface-card-profile.h \
+               modules/dbus/iface-client.c modules/dbus/iface-client.h \
+               modules/dbus/iface-core.c modules/dbus/iface-core.h \
+               modules/dbus/iface-device.c modules/dbus/iface-device.h \
+               modules/dbus/iface-device-port.c modules/dbus/iface-device-port.h \
+               modules/dbus/iface-memstats.c modules/dbus/iface-memstats.h \
+               modules/dbus/iface-module.c modules/dbus/iface-module.h \
+               modules/dbus/iface-sample.c modules/dbus/iface-sample.h \
+               modules/dbus/iface-stream.c modules/dbus/iface-stream.h \
+               modules/dbus/module-dbus-protocol.c
+module_dbus_protocol_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
+module_dbus_protocol_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_dbus_protocol_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la
+
 # Native protocol
 
 module_native_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c
@@ -1551,6 +1574,11 @@ module_stream_restore_la_LDFLAGS = $(MODULE_LDFLAGS)
 module_stream_restore_la_LIBADD = $(AM_LIBADD) libprotocol-native.la libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la
 module_stream_restore_la_CFLAGS = $(AM_CFLAGS)
 
+if HAVE_DBUS
+module_stream_restore_la_LIBADD += $(DBUS_LIBS)
+module_stream_restore_la_CFLAGS += $(DBUS_CFLAGS)
+endif
+
 # Card profile restore module
 module_card_restore_la_SOURCES = modules/module-card-restore.c
 module_card_restore_la_LDFLAGS = $(MODULE_LDFLAGS)
index ec1ec5ced6643ce47d48cb8050ed66e01059642e..3428f8078ced803264dd4f2a3c528eb5b66f8943 100644 (file)
@@ -83,6 +83,9 @@ static const pa_daemon_conf default_conf = {
     .config_file = NULL,
     .use_pid_file = TRUE,
     .system_instance = FALSE,
+#ifdef HAVE_DBUS
+    .local_server_type = PA_SERVER_TYPE_UNSET, /* The actual default is _USER, but we have to detect when the user doesn't specify this option. */
+#endif
     .no_cpu_limit = FALSE,
     .disable_shm = FALSE,
     .lock_memory = FALSE,
@@ -220,6 +223,22 @@ int pa_daemon_conf_set_resample_method(pa_daemon_conf *c, const char *string) {
     return 0;
 }
 
+int pa_daemon_conf_set_local_server_type(pa_daemon_conf *c, const char *string) {
+    pa_assert(c);
+    pa_assert(string);
+
+    if (!strcmp(string, "user"))
+        c->local_server_type = PA_SERVER_TYPE_USER;
+    else if (!strcmp(string, "system")) {
+        c->local_server_type = PA_SERVER_TYPE_SYSTEM;
+    } else if (!strcmp(string, "none")) {
+        c->local_server_type = PA_SERVER_TYPE_NONE;
+    } else
+        return -1;
+
+    return 0;
+}
+
 static int parse_log_target(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata) {
     pa_daemon_conf *c = data;
 
@@ -447,6 +466,22 @@ static int parse_rtprio(const char *filename, unsigned line, const char *section
     return 0;
 }
 
+static int parse_server_type(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata) {
+    pa_daemon_conf *c = data;
+
+    pa_assert(filename);
+    pa_assert(lvalue);
+    pa_assert(rvalue);
+    pa_assert(data);
+
+    if (pa_daemon_conf_set_local_server_type(c, rvalue) < 0) {
+        pa_log(_("[%s:%u] Invalid server type '%s'."), filename, line, rvalue);
+        return -1;
+    }
+
+    return 0;
+}
+
 int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
     int r = -1;
     FILE *f = NULL;
@@ -462,6 +497,9 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
         { "allow-exit",                 pa_config_parse_not_bool, &c->disallow_exit, NULL },
         { "use-pid-file",               pa_config_parse_bool,     &c->use_pid_file, NULL },
         { "system-instance",            pa_config_parse_bool,     &c->system_instance, NULL },
+#ifdef HAVE_DBUS
+        { "local-server-type",          parse_server_type,        c, NULL },
+#endif
         { "no-cpu-limit",               pa_config_parse_bool,     &c->no_cpu_limit, NULL },
         { "cpu-limit",                  pa_config_parse_not_bool, &c->no_cpu_limit, NULL },
         { "disable-shm",                pa_config_parse_bool,     &c->disable_shm, NULL },
@@ -627,6 +665,14 @@ char *pa_daemon_conf_dump(pa_daemon_conf *c) {
         [PA_LOG_WARN] = "warning",
         [PA_LOG_ERROR] = "error"
     };
+
+    static const char* const server_type_to_string[] = {
+        [PA_SERVER_TYPE_UNSET] = "!!UNSET!!",
+        [PA_SERVER_TYPE_USER] = "user",
+        [PA_SERVER_TYPE_SYSTEM] = "system",
+        [PA_SERVER_TYPE_NONE] = "none"
+    };
+
     pa_strbuf *s;
     char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
 
@@ -649,6 +695,9 @@ char *pa_daemon_conf_dump(pa_daemon_conf *c) {
     pa_strbuf_printf(s, "allow-exit = %s\n", pa_yes_no(!c->disallow_exit));
     pa_strbuf_printf(s, "use-pid-file = %s\n", pa_yes_no(c->use_pid_file));
     pa_strbuf_printf(s, "system-instance = %s\n", pa_yes_no(c->system_instance));
+#ifdef HAVE_DBUS
+    pa_strbuf_printf(s, "local-server-type = %s\n", server_type_to_string[c->local_server_type]);
+#endif
     pa_strbuf_printf(s, "cpu-limit = %s\n", pa_yes_no(!c->no_cpu_limit));
     pa_strbuf_printf(s, "enable-shm = %s\n", pa_yes_no(!c->disable_shm));
     pa_strbuf_printf(s, "flat-volumes = %s\n", pa_yes_no(c->flat_volumes));
index dd69e048f4f2de95fdda03ec754f7fbf2aec483b..41c3c4b7d047d66996ed65a3f06509770e051e72 100644 (file)
@@ -28,6 +28,7 @@
 
 #include <pulsecore/log.h>
 #include <pulsecore/macro.h>
+#include <pulsecore/core.h>
 #include <pulsecore/core-util.h>
 
 #ifdef HAVE_SYS_RESOURCE_H
@@ -75,6 +76,7 @@ typedef struct pa_daemon_conf {
         log_time,
         flat_volumes,
         lock_memory;
+    pa_server_type_t local_server_type;
     int exit_idle_time,
         scache_idle_time,
         auto_log_target,
@@ -152,6 +154,7 @@ int pa_daemon_conf_env(pa_daemon_conf *c);
 int pa_daemon_conf_set_log_target(pa_daemon_conf *c, const char *string);
 int pa_daemon_conf_set_log_level(pa_daemon_conf *c, const char *string);
 int pa_daemon_conf_set_resample_method(pa_daemon_conf *c, const char *string);
+int pa_daemon_conf_set_local_server_type(pa_daemon_conf *c, const char *string);
 
 const char *pa_daemon_conf_get_default_script_file(pa_daemon_conf *c);
 FILE *pa_daemon_conf_open_default_script_file(pa_daemon_conf *c);
index d8b58d8a0a40d729fa24ee388e82f79594322c60..a11fd06c56536243b26b2188dea471f0da25f15b 100644 (file)
@@ -25,6 +25,7 @@
 ; allow-exit = yes
 ; use-pid-file = yes
 ; system-instance = no
+; local-server-type = user
 ; enable-shm = yes
 ; shm-size-bytes = 0 # setting this 0 will use the system-default, usually 64 MiB
 ; lock-memory = no
index 00c000eb9d82104c82892134bcedf4c3a8cd923d..f9b9eadd77926cb5385e973621da95c70fee24da 100755 (executable)
@@ -66,6 +66,9 @@ load-module module-bluetooth-discover
 .ifexists module-esound-protocol-unix@PA_SOEXT@
 load-module module-esound-protocol-unix
 .endif
+.ifexists module-dbus-protocol@PA_SOEXT@
+load-module module-dbus-protocol
+.endif
 load-module module-native-protocol-unix
 
 ### Network access (may be configured with paprefs, so leave this commented
index b1d1109a50657566d2edc61c884cfcab466fbea8..409823b1def54f88212791cfe6e2bd39547aec7d 100644 (file)
 #include "dumpmodules.h"
 #include "caps.h"
 #include "ltdl-bind-now.h"
+#include "server-lookup.h"
 
 #ifdef HAVE_LIBWRAP
 /* Only one instance of these variables */
@@ -338,33 +339,31 @@ static void set_all_rlimits(const pa_daemon_conf *conf) {
 #endif
 
 #ifdef HAVE_DBUS
-static pa_dbus_connection *register_dbus(pa_core *c) {
+static pa_dbus_connection *register_dbus_name(pa_core *c, DBusBusType bus, const char* name) {
     DBusError error;
     pa_dbus_connection *conn;
 
     dbus_error_init(&error);
 
-    if (!(conn = pa_dbus_bus_get(c, pa_in_system_mode() ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) {
+    if (!(conn = pa_dbus_bus_get(c, bus, &error)) || dbus_error_is_set(&error)) {
         pa_log_warn("Unable to contact D-Bus: %s: %s", error.name, error.message);
         goto fail;
     }
 
-    if (dbus_bus_request_name(pa_dbus_connection_get(conn), "org.pulseaudio.Server", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error) == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
-        pa_log_debug("Got org.pulseaudio.Server!");
+    if (dbus_bus_request_name(pa_dbus_connection_get(conn), name, DBUS_NAME_FLAG_DO_NOT_QUEUE, &error) == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+        pa_log_debug("Got %s!", name);
         return conn;
     }
 
     if (dbus_error_is_set(&error))
-        pa_log_warn("Failed to acquire org.pulseaudio.Server: %s: %s", error.name, error.message);
+        pa_log_error("Failed to acquire %s: %s: %s", name, error.name, error.message);
     else
-        pa_log_warn("D-Bus name org.pulseaudio.Server already taken. Weird shit!");
+        pa_log_error("D-Bus name %s already taken. Weird shit!", name);
 
     /* PA cannot be started twice by the same user and hence we can
-     * ignore mostly the case that org.pulseaudio.Server is already
-     * taken. */
+     * ignore mostly the case that a name is already taken. */
 
 fail:
-
     if (conn)
         pa_dbus_connection_unref(conn);
 
@@ -394,7 +393,10 @@ int main(int argc, char *argv[]) {
     int autospawn_fd = -1;
     pa_bool_t autospawn_locked = FALSE;
 #ifdef HAVE_DBUS
-    pa_dbus_connection *dbus = NULL;
+    pa_dbusobj_server_lookup *server_lookup = NULL; /* /org/pulseaudio/server_lookup */
+    pa_dbus_connection *lookup_service_bus = NULL; /* Always the user bus. */
+    pa_dbus_connection *server_bus = NULL; /* The bus where we reserve org.pulseaudio.Server, either the user or the system bus. */
+    pa_bool_t start_server;
 #endif
 
     pa_log_set_ident("pulseaudio");
@@ -473,6 +475,32 @@ int main(int argc, char *argv[]) {
         pa_log_set_flags(PA_LOG_PRINT_TIME, PA_LOG_SET);
     pa_log_set_show_backtrace(conf->log_backtrace);
 
+#ifdef HAVE_DBUS
+    /* conf->system_instance and conf->local_server_type control almost the
+     * same thing; make them agree about what is requested. */
+    switch (conf->local_server_type) {
+        case PA_SERVER_TYPE_UNSET:
+            conf->local_server_type = conf->system_instance ? PA_SERVER_TYPE_SYSTEM : PA_SERVER_TYPE_USER;
+            break;
+        case PA_SERVER_TYPE_USER:
+        case PA_SERVER_TYPE_NONE:
+            conf->system_instance = FALSE;
+            break;
+        case PA_SERVER_TYPE_SYSTEM:
+            conf->system_instance = TRUE;
+            break;
+        default:
+            pa_assert_not_reached();
+    }
+
+    start_server = conf->local_server_type == PA_SERVER_TYPE_USER || (getuid() == 0 && conf->local_server_type == PA_SERVER_TYPE_SYSTEM);
+
+    if (!start_server && conf->local_server_type == PA_SERVER_TYPE_SYSTEM) {
+        pa_log_notice(_("System mode refused for non-root user. Only starting the D-Bus server lookup service."));
+        conf->system_instance = FALSE;
+    }
+#endif
+
     LTDL_SET_PRELOADED_SYMBOLS();
     pa_ltdl_init();
     ltdl_init = TRUE;
@@ -559,10 +587,12 @@ int main(int argc, char *argv[]) {
 
     if (getuid() == 0 && !conf->system_instance)
         pa_log_warn(_("This program is not intended to be run as root (unless --system is specified)."));
+#ifndef HAVE_DBUS /* A similar, only a notice worthy check was done earlier, if D-Bus is enabled. */
     else if (getuid() != 0 && conf->system_instance) {
         pa_log(_("Root privileges required."));
         goto finish;
     }
+#endif
 
     if (conf->cmd == PA_CMD_START && conf->system_instance) {
         pa_log(_("--start not supported for system instances."));
@@ -847,6 +877,9 @@ int main(int argc, char *argv[]) {
     c->running_as_daemon = !!conf->daemonize;
     c->disallow_exit = conf->disallow_exit;
     c->flat_volumes = conf->flat_volumes;
+#ifdef HAVE_DBUS
+    c->server_type = conf->local_server_type;
+#endif
 
     pa_assert_se(pa_signal_init(pa_mainloop_get_api(mainloop)) == 0);
     pa_signal_new(SIGINT, signal_callback, c);
@@ -869,34 +902,47 @@ int main(int argc, char *argv[]) {
         pa_assert_se(pa_cpu_limit_init(pa_mainloop_get_api(mainloop)) == 0);
 
     buf = pa_strbuf_new();
-    if (conf->load_default_script_file) {
-        FILE *f;
 
-        if ((f = pa_daemon_conf_open_default_script_file(conf))) {
-            r = pa_cli_command_execute_file_stream(c, f, buf, &conf->fail);
-            fclose(f);
+#ifdef HAVE_DBUS
+    if (start_server) {
+#endif
+        if (conf->load_default_script_file) {
+            FILE *f;
+
+            if ((f = pa_daemon_conf_open_default_script_file(conf))) {
+                r = pa_cli_command_execute_file_stream(c, f, buf, &conf->fail);
+                fclose(f);
+            }
         }
-    }
 
-    if (r >= 0)
-        r = pa_cli_command_execute(c, conf->script_commands, buf, &conf->fail);
+        if (r >= 0)
+            r = pa_cli_command_execute(c, conf->script_commands, buf, &conf->fail);
 
-    pa_log_error("%s", s = pa_strbuf_tostring_free(buf));
-    pa_xfree(s);
+        pa_log_error("%s", s = pa_strbuf_tostring_free(buf));
+        pa_xfree(s);
 
-    /* We completed the initial module loading, so let's disable it
-     * from now on, if requested */
-    c->disallow_module_loading = !!conf->disallow_module_loading;
+        if (r < 0 && conf->fail) {
+            pa_log(_("Failed to initialize daemon."));
+            goto finish;
+        }
 
-    if (r < 0 && conf->fail) {
-        pa_log(_("Failed to initialize daemon."));
-        goto finish;
+        if (!c->modules || pa_idxset_size(c->modules) == 0) {
+            pa_log(_("Daemon startup without any loaded modules, refusing to work."));
+            goto finish;
+        }
+#ifdef HAVE_DBUS
+    } else {
+        /* When we just provide the D-Bus server lookup service, we don't want
+         * any modules to be loaded. We haven't loaded any so far, so one might
+         * think there's no way to contact the server, but receiving certain
+         * signals could still cause modules to load. */
+        conf->disallow_module_loading = TRUE;
     }
+#endif
 
-    if (!c->modules || pa_idxset_size(c->modules) == 0) {
-        pa_log(_("Daemon startup without any loaded modules, refusing to work."));
-        goto finish;
-    }
+    /* We completed the initial module loading, so let's disable it
+     * from now on, if requested */
+    c->disallow_module_loading = !!conf->disallow_module_loading;
 
 #ifdef HAVE_FORK
     if (daemon_pipe[1] >= 0) {
@@ -908,7 +954,15 @@ int main(int argc, char *argv[]) {
 #endif
 
 #ifdef HAVE_DBUS
-    dbus = register_dbus(c);
+    if (!conf->system_instance) {
+        if (!(server_lookup = pa_dbusobj_server_lookup_new(c)))
+            goto finish;
+        if (!(lookup_service_bus = register_dbus_name(c, DBUS_BUS_SESSION, "org.PulseAudio1")))
+            goto finish;
+    }
+
+    if (start_server && !(server_bus = register_dbus_name(c, conf->system_instance ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, "org.pulseaudio.Server")))
+        goto finish;
 #endif
 
     pa_log_info(_("Daemon startup complete."));
@@ -921,8 +975,12 @@ int main(int argc, char *argv[]) {
 
 finish:
 #ifdef HAVE_DBUS
-    if (dbus)
-        pa_dbus_connection_unref(dbus);
+    if (server_bus)
+        pa_dbus_connection_unref(server_bus);
+    if (lookup_service_bus)
+        pa_dbus_connection_unref(lookup_service_bus);
+    if (server_lookup)
+        pa_dbusobj_server_lookup_free(server_lookup);
 #endif
 
     if (autospawn_fd >= 0) {
diff --git a/src/daemon/server-lookup.c b/src/daemon/server-lookup.c
new file mode 100644 (file)
index 0000000..45796e7
--- /dev/null
@@ -0,0 +1,522 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <dbus/dbus.h>
+
+#include <pulse/client-conf.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-shared.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "server-lookup.h"
+
+#define OBJECT_PATH "/org/pulseaudio/server_lookup1"
+#define INTERFACE "org.PulseAudio.ServerLookup1"
+
+struct pa_dbusobj_server_lookup {
+    pa_core *core;
+    pa_dbus_connection *conn;
+    pa_bool_t path_registered;
+};
+
+static const char introspection[] =
+    DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+    "<node>"
+    " <!-- If you are looking for documentation make sure to check out\n"
+    "      http://pulseaudio.org/wiki/DBusInterface -->\n"
+    " <interface name=\"" INTERFACE "\">\n"
+    "  <property name=\"Address\" type=\"s\" access=\"read\"/>\n"
+    " </interface>\n"
+    " <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n"
+    "  <method name=\"Introspect\">\n"
+    "   <arg name=\"data\" type=\"s\" direction=\"out\"/>\n"
+    "  </method>\n"
+    " </interface>\n"
+    " <interface name=\"" DBUS_INTERFACE_PROPERTIES "\">\n"
+    "  <method name=\"Get\">\n"
+    "   <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n"
+    "   <arg name=\"property_name\" type=\"s\" direction=\"in\"/>\n"
+    "   <arg name=\"value\" type=\"v\" direction=\"out\"/>\n"
+    "  </method>\n"
+    "  <method name=\"Set\">\n"
+    "   <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n"
+    "   <arg name=\"property_name\" type=\"s\" direction=\"in\"/>\n"
+    "   <arg name=\"value\" type=\"v\" direction=\"in\"/>\n"
+    "  </method>\n"
+    "  <method name=\"GetAll\">\n"
+    "   <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n"
+    "   <arg name=\"props\" type=\"a{sv}\" direction=\"out\"/>\n"
+    "  </method>\n"
+    " </interface>\n"
+    "</node>\n";
+
+static void unregister_cb(DBusConnection *conn, void *user_data) {
+    pa_dbusobj_server_lookup *sl = user_data;
+
+    pa_assert(sl);
+    pa_assert(sl->path_registered);
+
+    sl->path_registered = FALSE;
+}
+
+static DBusHandlerResult handle_introspect(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_server_lookup *sl) {
+    DBusHandlerResult r = DBUS_HANDLER_RESULT_HANDLED;
+    const char *i = introspection;
+    DBusMessage *reply = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+
+    if (!(reply = dbus_message_new_method_return(msg))) {
+        r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+        goto finish;
+    }
+    if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &i, DBUS_TYPE_INVALID)) {
+        r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+        goto finish;
+    }
+    if (!dbus_connection_send(conn, reply, NULL)) {
+        r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+        goto finish;
+    }
+
+finish:
+    if (reply)
+        dbus_message_unref(reply);
+
+    return r;
+}
+
+enum get_address_result_t {
+    SUCCESS,
+    FAILED_TO_LOAD_CLIENT_CONF,
+    SERVER_FROM_TYPE_FAILED
+};
+
+/* Caller frees the returned address. */
+static enum get_address_result_t get_address(pa_server_type_t server_type, char **address) {
+    enum get_address_result_t r = SUCCESS;
+    pa_client_conf *conf = pa_client_conf_new();
+
+    *address = NULL;
+
+    if (pa_client_conf_load(conf, NULL) < 0) {
+        r = FAILED_TO_LOAD_CLIENT_CONF;
+        goto finish;
+    }
+
+    if (conf->default_dbus_server)
+        *address = pa_xstrdup(conf->default_dbus_server);
+    else if (!(*address = pa_get_dbus_address_from_server_type(server_type))) {
+        r = SERVER_FROM_TYPE_FAILED;
+        goto finish;
+    }
+
+finish:
+    pa_client_conf_free(conf);
+    return r;
+}
+
+static DBusHandlerResult handle_get_address(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_server_lookup *sl) {
+    DBusHandlerResult r = DBUS_HANDLER_RESULT_HANDLED;
+    DBusMessage *reply = NULL;
+    char *address = NULL;
+    DBusMessageIter msg_iter;
+    DBusMessageIter variant_iter;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(sl);
+
+    switch (get_address(sl->core->server_type, &address)) {
+        case SUCCESS:
+            if (!(reply = dbus_message_new_method_return(msg))) {
+                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+                goto finish;
+            }
+            dbus_message_iter_init_append(reply, &msg_iter);
+            if (!dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_VARIANT, "s", &variant_iter)) {
+                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+                goto finish;
+            }
+            if (!dbus_message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &address)) {
+                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+                goto finish;
+            }
+            if (!dbus_message_iter_close_container(&msg_iter, &variant_iter)) {
+                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+                goto finish;
+            }
+            if (!dbus_connection_send(conn, reply, NULL)) {
+                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+                goto finish;
+            }
+            r = DBUS_HANDLER_RESULT_HANDLED;
+            goto finish;
+
+        case FAILED_TO_LOAD_CLIENT_CONF:
+            if (!(reply = dbus_message_new_error(msg, "org.pulseaudio.ClientConfLoadError", "Failed to load client.conf."))) {
+                r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+                goto finish;
+            }
+            if (!dbus_connection_send(conn, reply, NULL)) {
+                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+                goto finish;
+            }
+            r = DBUS_HANDLER_RESULT_HANDLED;
+            goto finish;
+
+        case SERVER_FROM_TYPE_FAILED:
+            if (!(reply = dbus_message_new_error(msg, DBUS_ERROR_FAILED, "PulseAudio internal error: get_dbus_server_from_type() failed."))) {
+                r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+                goto finish;
+            }
+            if (!dbus_connection_send(conn, reply, NULL)) {
+                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+                goto finish;
+            }
+            r = DBUS_HANDLER_RESULT_HANDLED;
+            goto finish;
+
+        default:
+            pa_assert_not_reached();
+    }
+
+finish:
+    pa_xfree(address);
+    if (reply)
+        dbus_message_unref(reply);
+
+    return r;
+}
+
+static DBusHandlerResult handle_get(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_server_lookup *sl) {
+    DBusHandlerResult r = DBUS_HANDLER_RESULT_HANDLED;
+    const char* interface;
+    const char* property;
+    DBusMessage *reply = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(sl);
+
+    if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID)) {
+        if (!(reply = dbus_message_new_error(msg, DBUS_ERROR_INVALID_ARGS, "Invalid arguments"))) {
+            r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+            goto finish;
+        }
+        if (!dbus_connection_send(conn, reply, NULL)) {
+            r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+            goto finish;
+        }
+        r = DBUS_HANDLER_RESULT_HANDLED;
+        goto finish;
+    }
+
+    if (*interface && !pa_streq(interface, INTERFACE)) {
+        r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+        goto finish;
+    }
+
+    if (!pa_streq(property, "Address")) {
+        if (!(reply = dbus_message_new_error_printf(msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "%s: No such property", property))) {
+            r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+            goto finish;
+        }
+        if (!dbus_connection_send(conn, reply, NULL)) {
+            r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+            goto finish;
+        }
+        r = DBUS_HANDLER_RESULT_HANDLED;
+        goto finish;
+    }
+
+    r = handle_get_address(conn, msg, sl);
+
+finish:
+    if (reply)
+        dbus_message_unref(reply);
+
+    return r;
+}
+
+static DBusHandlerResult handle_set(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_server_lookup *sl) {
+    DBusHandlerResult r = DBUS_HANDLER_RESULT_HANDLED;
+    const char* interface;
+    const char* property;
+    DBusMessage *reply = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(sl);
+
+    if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID)) {
+        if (!(reply = dbus_message_new_error(msg, DBUS_ERROR_INVALID_ARGS, "Invalid arguments"))) {
+            r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+            goto finish;
+        }
+        if (!dbus_connection_send(conn, reply, NULL)) {
+            r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+            goto finish;
+        }
+        r = DBUS_HANDLER_RESULT_HANDLED;
+        goto finish;
+    }
+
+    if (*interface && !pa_streq(interface, INTERFACE)) {
+        r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+        goto finish;
+    }
+
+    if (!pa_streq(property, "Address")) {
+        if (!(reply = dbus_message_new_error_printf(msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "%s: No such property", property))) {
+            r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+            goto finish;
+        }
+        if (!dbus_connection_send(conn, reply, NULL)) {
+            r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+            goto finish;
+        }
+        r = DBUS_HANDLER_RESULT_HANDLED;
+        goto finish;
+    }
+
+    if (!(reply = dbus_message_new_error_printf(msg, DBUS_ERROR_ACCESS_DENIED, "%s: Property not settable", property))) {
+        r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+        goto finish;
+    }
+    if (!dbus_connection_send(conn, reply, NULL)) {
+        r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+        goto finish;
+    }
+    r = DBUS_HANDLER_RESULT_HANDLED;
+
+finish:
+    if (reply)
+        dbus_message_unref(reply);
+
+    return r;
+}
+
+static DBusHandlerResult handle_get_all(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_server_lookup *sl) {
+    DBusHandlerResult r = DBUS_HANDLER_RESULT_HANDLED;
+    DBusMessage *reply = NULL;
+    const char *property = "Address";
+    char *interface = NULL;
+    char *address = NULL;
+    DBusMessageIter msg_iter;
+    DBusMessageIter dict_iter;
+    DBusMessageIter dict_entry_iter;
+    DBusMessageIter variant_iter;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(sl);
+
+    if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_INVALID)) {
+        if (!(reply = dbus_message_new_error(msg, DBUS_ERROR_INVALID_ARGS, "Invalid arguments"))) {
+            r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+            goto finish;
+        }
+        if (!dbus_connection_send(conn, reply, NULL)) {
+            r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+            goto finish;
+        }
+        r = DBUS_HANDLER_RESULT_HANDLED;
+        goto finish;
+    }
+
+    switch (get_address(sl->core->server_type, &address)) {
+        case SUCCESS:
+            if (!(reply = dbus_message_new_method_return(msg))) {
+                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+                goto finish;
+            }
+            dbus_message_iter_init_append(reply, &msg_iter);
+            if (!dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)) {
+                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+                goto finish;
+            }
+            if (!dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)) {
+                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+                goto finish;
+            }
+            if (!dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &property)) {
+                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+                goto finish;
+            }
+            if (!dbus_message_iter_open_container(&dict_entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter)) {
+                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+                goto finish;
+            }
+            if (!dbus_message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &address)) {
+                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+                goto finish;
+            }
+            if (!dbus_message_iter_close_container(&dict_entry_iter, &variant_iter)) {
+                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+                goto finish;
+            }
+            if (!dbus_message_iter_close_container(&dict_iter, &dict_entry_iter)) {
+                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+                goto finish;
+            }
+            if (!dbus_message_iter_close_container(&msg_iter, &dict_iter)) {
+                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+                goto finish;
+            }
+            if (!dbus_connection_send(conn, reply, NULL)) {
+                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+                goto finish;
+            }
+            r = DBUS_HANDLER_RESULT_HANDLED;
+            goto finish;
+
+        case FAILED_TO_LOAD_CLIENT_CONF:
+            if (!(reply = dbus_message_new_error(msg, "org.pulseaudio.ClientConfLoadError", "Failed to load client.conf."))) {
+                r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+                goto finish;
+            }
+            if (!dbus_connection_send(conn, reply, NULL)) {
+                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+                goto finish;
+            }
+            r = DBUS_HANDLER_RESULT_HANDLED;
+            goto finish;
+
+        case SERVER_FROM_TYPE_FAILED:
+            if (!(reply = dbus_message_new_error(msg, DBUS_ERROR_FAILED, "PulseAudio internal error: get_dbus_server_from_type() failed."))) {
+                r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+                goto finish;
+            }
+            if (!dbus_connection_send(conn, reply, NULL)) {
+                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
+                goto finish;
+            }
+            r = DBUS_HANDLER_RESULT_HANDLED;
+            goto finish;
+
+        default:
+            pa_assert_not_reached();
+    }
+
+finish:
+    pa_xfree(address);
+    if (reply)
+        dbus_message_unref(reply);
+
+    return r;
+}
+
+static DBusHandlerResult message_cb(DBusConnection *conn, DBusMessage *msg, void *user_data) {
+    pa_dbusobj_server_lookup *sl = user_data;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(sl);
+
+    /* pa_log("Got message! type = %s   path = %s   iface = %s   member = %s   dest = %s", dbus_message_type_to_string(dbus_message_get_type(msg)), dbus_message_get_path(msg), dbus_message_get_interface(msg), dbus_message_get_member(msg), dbus_message_get_destination(msg)); */
+
+    if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_METHOD_CALL)
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    if (dbus_message_is_method_call(msg, DBUS_INTERFACE_INTROSPECTABLE, "Introspect") ||
+        (!dbus_message_get_interface(msg) && dbus_message_has_member(msg, "Introspect")))
+        return handle_introspect(conn, msg, sl);
+
+    if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES, "Get") ||
+        (!dbus_message_get_interface(msg) && dbus_message_has_member(msg, "Get")))
+        return handle_get(conn, msg, sl);
+
+    if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES, "Set") ||
+        (!dbus_message_get_interface(msg) && dbus_message_has_member(msg, "Set")))
+        return handle_set(conn, msg, sl);
+
+    if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES, "GetAll") ||
+        (!dbus_message_get_interface(msg) && dbus_message_has_member(msg, "GetAll")))
+        return handle_get_all(conn, msg, sl);
+
+    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static DBusObjectPathVTable vtable = {
+    .unregister_function = unregister_cb,
+    .message_function = message_cb,
+    .dbus_internal_pad1 = NULL,
+    .dbus_internal_pad2 = NULL,
+    .dbus_internal_pad3 = NULL,
+    .dbus_internal_pad4 = NULL
+};
+
+pa_dbusobj_server_lookup *pa_dbusobj_server_lookup_new(pa_core *c) {
+    pa_dbusobj_server_lookup *sl;
+    DBusError error;
+
+    dbus_error_init(&error);
+
+    sl = pa_xnew(pa_dbusobj_server_lookup, 1);
+    sl->core = c;
+    sl->path_registered = FALSE;
+
+    if (!(sl->conn = pa_dbus_bus_get(c, DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) {
+        pa_log("Unable to contact D-Bus: %s: %s", error.name, error.message);
+        goto fail;
+    }
+
+    if (!dbus_connection_register_object_path(pa_dbus_connection_get(sl->conn), OBJECT_PATH, &vtable, sl)) {
+        pa_log("dbus_connection_register_object_path() failed for " OBJECT_PATH ".");
+        goto fail;
+    }
+
+    sl->path_registered = TRUE;
+
+    return sl;
+
+fail:
+    dbus_error_free(&error);
+
+    pa_dbusobj_server_lookup_free(sl);
+
+    return NULL;
+}
+
+void pa_dbusobj_server_lookup_free(pa_dbusobj_server_lookup *sl) {
+    pa_assert(sl);
+
+    if (sl->path_registered) {
+        pa_assert(sl->conn);
+        if (!dbus_connection_unregister_object_path(pa_dbus_connection_get(sl->conn), OBJECT_PATH))
+            pa_log_debug("dbus_connection_unregister_object_path() failed for " OBJECT_PATH ".");
+    }
+
+    if (sl->conn)
+        pa_dbus_connection_unref(sl->conn);
+
+    pa_xfree(sl);
+}
diff --git a/src/daemon/server-lookup.h b/src/daemon/server-lookup.h
new file mode 100644 (file)
index 0000000..c930d5b
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef fooserverlookuphfoo
+#define fooserverlookuphfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+/* This object implements the D-Bus object at path
+ * /org/pulseaudio/server_lookup. Implemented interfaces
+ * are org.pulseaudio.ServerLookup and org.freedesktop.DBus.Introspectable.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the ServerLookup interface
+ * documentation.
+ */
+
+#include <pulsecore/core.h>
+
+typedef struct pa_dbusobj_server_lookup pa_dbusobj_server_lookup;
+
+pa_dbusobj_server_lookup *pa_dbusobj_server_lookup_new(pa_core *c);
+void pa_dbusobj_server_lookup_free(pa_dbusobj_server_lookup *sl);
+
+#endif
index 27e428156abd8a52584ca25c8db21105cff72674..0ca32bd305786fea0e768eed0297d9297bc7d107 100755 (executable)
@@ -32,6 +32,10 @@ load-module module-detect
 .ifexists module-esound-protocol-unix@PA_SOEXT@
 load-module module-esound-protocol-unix
 .endif
+.ifexists module-dbus-protocol@PA_SOEXT@
+### If you want to allow TCP connections, set access to "remote" or "local,remote".
+load-module module-dbus-protocol access=local
+.endif
 load-module module-native-protocol-unix
 
 ### Automatically restore the volume of streams and devices
index 95b2803abf1c8aee6c3fb6e38324f7e30607bba1..54c518ec7da0bdf630e68743efa6bd03ded13257 100644 (file)
@@ -185,6 +185,7 @@ pa_path_get_filename;
 pa_proplist_clear;
 pa_proplist_contains;
 pa_proplist_copy;
+pa_proplist_equal;
 pa_proplist_free;
 pa_proplist_from_string;
 pa_proplist_get;
diff --git a/src/modules/dbus/iface-card-profile.c b/src/modules/dbus/iface-card-profile.c
new file mode 100644 (file)
index 0000000..004e2e8
--- /dev/null
@@ -0,0 +1,228 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <dbus/dbus.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+
+#include "iface-card-profile.h"
+
+#define OBJECT_NAME "profile"
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_description(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_priority(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+struct pa_dbusiface_card_profile {
+    uint32_t index;
+    pa_card_profile *profile;
+    char *path;
+    pa_dbus_protocol *dbus_protocol;
+};
+
+enum property_handler_index {
+    PROPERTY_HANDLER_INDEX,
+    PROPERTY_HANDLER_NAME,
+    PROPERTY_HANDLER_DESCRIPTION,
+    PROPERTY_HANDLER_SINKS,
+    PROPERTY_HANDLER_SOURCES,
+    PROPERTY_HANDLER_PRIORITY,
+    PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+    [PROPERTY_HANDLER_INDEX]       = { .property_name = "Index",       .type = "u", .get_cb = handle_get_index,       .set_cb = NULL },
+    [PROPERTY_HANDLER_NAME]        = { .property_name = "Name",        .type = "s", .get_cb = handle_get_name,        .set_cb = NULL },
+    [PROPERTY_HANDLER_DESCRIPTION] = { .property_name = "Description", .type = "s", .get_cb = handle_get_description, .set_cb = NULL },
+    [PROPERTY_HANDLER_SINKS]       = { .property_name = "Sinks",       .type = "u", .get_cb = handle_get_sinks,       .set_cb = NULL },
+    [PROPERTY_HANDLER_SOURCES]     = { .property_name = "Sources",     .type = "u", .get_cb = handle_get_sources,     .set_cb = NULL },
+    [PROPERTY_HANDLER_PRIORITY]    = { .property_name = "Priority",    .type = "u", .get_cb = handle_get_priority,    .set_cb = NULL },
+};
+
+static pa_dbus_interface_info profile_interface_info = {
+    .name = PA_DBUSIFACE_CARD_PROFILE_INTERFACE,
+    .method_handlers = NULL,
+    .n_method_handlers = 0,
+    .property_handlers = property_handlers,
+    .n_property_handlers = PROPERTY_HANDLER_MAX,
+    .get_all_properties_cb = handle_get_all,
+    .signals = NULL,
+    .n_signals = 0
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_card_profile *p = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(p);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &p->index);
+}
+
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_card_profile *p = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(p);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &p->profile->name);
+}
+
+static void handle_get_description(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_card_profile *p = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(p);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &p->profile->description);
+}
+
+static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_card_profile *p = userdata;
+    dbus_uint32_t sinks = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(p);
+
+    sinks = p->profile->n_sinks;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sinks);
+}
+
+static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_card_profile *p = userdata;
+    dbus_uint32_t sources = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(p);
+
+    sources = p->profile->n_sources;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sources);
+}
+
+static void handle_get_priority(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_card_profile *p = userdata;
+    dbus_uint32_t priority = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(p);
+
+    priority = p->profile->priority;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &priority);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_card_profile *p = userdata;
+    DBusMessage *reply = NULL;
+    DBusMessageIter msg_iter;
+    DBusMessageIter dict_iter;
+    dbus_uint32_t sinks = 0;
+    dbus_uint32_t sources = 0;
+    dbus_uint32_t priority = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(p);
+
+    sinks = p->profile->n_sinks;
+    sources = p->profile->n_sources;
+    priority = p->profile->priority;
+
+    pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+    dbus_message_iter_init_append(reply, &msg_iter);
+    pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &p->index);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &p->profile->name);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DESCRIPTION].property_name, DBUS_TYPE_STRING, &p->profile->description);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SINKS].property_name, DBUS_TYPE_UINT32, &sinks);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SOURCES].property_name, DBUS_TYPE_UINT32, &sources);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PRIORITY].property_name, DBUS_TYPE_UINT32, &priority);
+
+    pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+    pa_assert_se(dbus_connection_send(conn, reply, NULL));
+    dbus_message_unref(reply);
+}
+
+pa_dbusiface_card_profile *pa_dbusiface_card_profile_new(
+        pa_dbusiface_card *card,
+        pa_core *core,
+        pa_card_profile *profile,
+        uint32_t idx) {
+    pa_dbusiface_card_profile *p = NULL;
+
+    pa_assert(card);
+    pa_assert(core);
+    pa_assert(profile);
+
+    p = pa_xnew(pa_dbusiface_card_profile, 1);
+    p->index = idx;
+    p->profile = profile;
+    p->path = pa_sprintf_malloc("%s/%s%u", pa_dbusiface_card_get_path(card), OBJECT_NAME, idx);
+    p->dbus_protocol = pa_dbus_protocol_get(core);
+
+    pa_assert_se(pa_dbus_protocol_add_interface(p->dbus_protocol, p->path, &profile_interface_info, p) >= 0);
+
+    return p;
+}
+
+void pa_dbusiface_card_profile_free(pa_dbusiface_card_profile *p) {
+    pa_assert(p);
+
+    pa_assert_se(pa_dbus_protocol_remove_interface(p->dbus_protocol, p->path, profile_interface_info.name) >= 0);
+
+    pa_dbus_protocol_unref(p->dbus_protocol);
+
+    pa_xfree(p->path);
+    pa_xfree(p);
+}
+
+const char *pa_dbusiface_card_profile_get_path(pa_dbusiface_card_profile *p) {
+    pa_assert(p);
+
+    return p->path;
+}
+
+const char *pa_dbusiface_card_profile_get_name(pa_dbusiface_card_profile *p) {
+    pa_assert(p);
+
+    return p->profile->name;
+}
diff --git a/src/modules/dbus/iface-card-profile.h b/src/modules/dbus/iface-card-profile.h
new file mode 100644 (file)
index 0000000..a09767f
--- /dev/null
@@ -0,0 +1,50 @@
+#ifndef foodbusifacecardprofilehfoo
+#define foodbusifacecardprofilehfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+/* This object implements the D-Bus interface org.PulseAudio.Core1.CardProfile.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the CardProfile interface
+ * documentation.
+ */
+
+#include <pulsecore/core-scache.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-card.h"
+
+#define PA_DBUSIFACE_CARD_PROFILE_INTERFACE PA_DBUS_CORE_INTERFACE ".CardProfile"
+
+typedef struct pa_dbusiface_card_profile pa_dbusiface_card_profile;
+
+pa_dbusiface_card_profile *pa_dbusiface_card_profile_new(
+        pa_dbusiface_card *card,
+        pa_core *core,
+        pa_card_profile *profile,
+        uint32_t idx);
+void pa_dbusiface_card_profile_free(pa_dbusiface_card_profile *p);
+
+const char *pa_dbusiface_card_profile_get_path(pa_dbusiface_card_profile *p);
+const char *pa_dbusiface_card_profile_get_name(pa_dbusiface_card_profile *p);
+
+#endif
diff --git a/src/modules/dbus/iface-card.c b/src/modules/dbus/iface-card.c
new file mode 100644 (file)
index 0000000..1714df3
--- /dev/null
@@ -0,0 +1,561 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <dbus/dbus.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-card-profile.h"
+
+#include "iface-card.h"
+
+#define OBJECT_NAME "card"
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_profiles(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_active_profile(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_active_profile(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_profile_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+struct pa_dbusiface_card {
+    pa_dbusiface_core *core;
+
+    pa_card *card;
+    char *path;
+    pa_hashmap *profiles;
+    uint32_t next_profile_index;
+    pa_card_profile *active_profile;
+    pa_proplist *proplist;
+
+    pa_dbus_protocol *dbus_protocol;
+    pa_subscription *subscription;
+};
+
+enum property_handler_index {
+    PROPERTY_HANDLER_INDEX,
+    PROPERTY_HANDLER_NAME,
+    PROPERTY_HANDLER_DRIVER,
+    PROPERTY_HANDLER_OWNER_MODULE,
+    PROPERTY_HANDLER_SINKS,
+    PROPERTY_HANDLER_SOURCES,
+    PROPERTY_HANDLER_PROFILES,
+    PROPERTY_HANDLER_ACTIVE_PROFILE,
+    PROPERTY_HANDLER_PROPERTY_LIST,
+    PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+    [PROPERTY_HANDLER_INDEX]          = { .property_name = "Index",         .type = "u",      .get_cb = handle_get_index,          .set_cb = NULL },
+    [PROPERTY_HANDLER_NAME]           = { .property_name = "Name",          .type = "s",      .get_cb = handle_get_name,           .set_cb = NULL },
+    [PROPERTY_HANDLER_DRIVER]         = { .property_name = "Driver",        .type = "s",      .get_cb = handle_get_driver,         .set_cb = NULL },
+    [PROPERTY_HANDLER_OWNER_MODULE]   = { .property_name = "OwnerModule",   .type = "o",      .get_cb = handle_get_owner_module,   .set_cb = NULL },
+    [PROPERTY_HANDLER_SINKS]          = { .property_name = "Sinks",         .type = "ao",     .get_cb = handle_get_sinks,          .set_cb = NULL },
+    [PROPERTY_HANDLER_SOURCES]        = { .property_name = "Sources",       .type = "ao",     .get_cb = handle_get_sources,        .set_cb = NULL },
+    [PROPERTY_HANDLER_PROFILES]       = { .property_name = "Profiles",      .type = "ao",     .get_cb = handle_get_profiles,       .set_cb = NULL },
+    [PROPERTY_HANDLER_ACTIVE_PROFILE] = { .property_name = "ActiveProfile", .type = "o",      .get_cb = handle_get_active_profile, .set_cb = handle_set_active_profile },
+    [PROPERTY_HANDLER_PROPERTY_LIST]  = { .property_name = "PropertyList",  .type = "a{say}", .get_cb = handle_get_property_list,  .set_cb = NULL }
+};
+
+enum method_handler_index {
+    METHOD_HANDLER_GET_PROFILE_BY_NAME,
+    METHOD_HANDLER_MAX
+};
+
+static pa_dbus_arg_info get_profile_by_name_args[] = { { "name", "s", "in" }, { "profile", "o", "out" } };
+
+static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
+    [METHOD_HANDLER_GET_PROFILE_BY_NAME] = {
+        .method_name = "GetProfileByName",
+        .arguments = get_profile_by_name_args,
+        .n_arguments = sizeof(get_profile_by_name_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_get_profile_by_name }
+};
+
+enum signal_index {
+    SIGNAL_ACTIVE_PROFILE_UPDATED,
+    SIGNAL_PROPERTY_LIST_UPDATED,
+    SIGNAL_MAX
+};
+
+static pa_dbus_arg_info active_profile_updated_args[] = { { "profile",       "o",      NULL } };
+static pa_dbus_arg_info property_list_updated_args[] =  { { "property_list", "a{say}", NULL } };
+
+static pa_dbus_signal_info signals[SIGNAL_MAX] = {
+    [SIGNAL_ACTIVE_PROFILE_UPDATED] = { .name = "ActiveProfileUpdated", .arguments = active_profile_updated_args, .n_arguments = 1 },
+    [SIGNAL_PROPERTY_LIST_UPDATED]  = { .name = "PropertyListUpdated",  .arguments = property_list_updated_args,  .n_arguments = 1 }
+};
+
+static pa_dbus_interface_info card_interface_info = {
+    .name = PA_DBUSIFACE_CARD_INTERFACE,
+    .method_handlers = method_handlers,
+    .n_method_handlers = METHOD_HANDLER_MAX,
+    .property_handlers = property_handlers,
+    .n_property_handlers = PROPERTY_HANDLER_MAX,
+    .get_all_properties_cb = handle_get_all,
+    .signals = signals,
+    .n_signals = SIGNAL_MAX
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_card *c = userdata;
+    dbus_uint32_t idx;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    idx = c->card->index;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx);
+}
+
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_card *c = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &c->card->name);
+}
+
+static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_card *c = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &c->card->driver);
+}
+
+static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_card *c = userdata;
+    const char *owner_module;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    if (!c->card->module) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Card %s doesn't have an owner module.", c->card->name);
+        return;
+    }
+
+    owner_module = pa_dbusiface_core_get_module_path(c->core, c->card->module);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &owner_module);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_sinks(pa_dbusiface_card *c, unsigned *n) {
+    const char **sinks = NULL;
+    unsigned i = 0;
+    uint32_t idx = 0;
+    pa_sink *sink = NULL;
+
+    pa_assert(c);
+    pa_assert(n);
+
+    *n = pa_idxset_size(c->card->sinks);
+
+    if (*n == 0)
+        return NULL;
+
+    sinks = pa_xnew(const char *, *n);
+
+    PA_IDXSET_FOREACH(sink, c->card->sinks, idx) {
+        sinks[i] = pa_dbusiface_core_get_sink_path(c->core, sink);
+        ++i;
+    }
+
+    return sinks;
+}
+
+static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_card *c = userdata;
+    const char **sinks;
+    unsigned n_sinks;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    sinks = get_sinks(c, &n_sinks);
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sinks, n_sinks);
+
+    pa_xfree(sinks);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_sources(pa_dbusiface_card *c, unsigned *n) {
+    const char **sources = NULL;
+    unsigned i = 0;
+    uint32_t idx = 0;
+    pa_source *source = NULL;
+
+    pa_assert(c);
+    pa_assert(n);
+
+    *n = pa_idxset_size(c->card->sources);
+
+    if (*n == 0)
+        return NULL;
+
+    sources = pa_xnew(const char *, *n);
+
+    PA_IDXSET_FOREACH(source, c->card->sinks, idx) {
+        sources[i] = pa_dbusiface_core_get_source_path(c->core, source);
+        ++i;
+    }
+
+    return sources;
+}
+
+static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_card *c = userdata;
+    const char **sources;
+    unsigned n_sources;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    sources = get_sources(c, &n_sources);
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sources, n_sources);
+
+    pa_xfree(sources);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_profiles(pa_dbusiface_card *c, unsigned *n) {
+    const char **profiles;
+    unsigned i = 0;
+    void *state = NULL;
+    pa_dbusiface_card_profile *profile;
+
+    pa_assert(c);
+    pa_assert(n);
+
+    *n = pa_hashmap_size(c->profiles);
+
+    if (*n == 0)
+        return NULL;
+
+    profiles = pa_xnew(const char *, *n);
+
+    PA_HASHMAP_FOREACH(profile, c->profiles, state)
+        profiles[i++] = pa_dbusiface_card_profile_get_path(profile);
+
+    return profiles;
+}
+
+static void handle_get_profiles(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_card *c = userdata;
+    const char **profiles;
+    unsigned n_profiles;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    profiles = get_profiles(c, &n_profiles);
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, profiles, n_profiles);
+
+    pa_xfree(profiles);
+}
+
+static void handle_get_active_profile(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_card *c = userdata;
+    const char *active_profile;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    if (!c->active_profile) {
+        pa_assert(pa_hashmap_isempty(c->profiles));
+
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                           "The card %s has no profiles, and therefore there's no active profile either.", c->card->name);
+        return;
+    }
+
+    active_profile = pa_dbusiface_card_profile_get_path(pa_hashmap_get(c->profiles, c->active_profile->name));
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &active_profile);
+}
+
+static void handle_set_active_profile(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+    pa_dbusiface_card *c = userdata;
+    const char *new_active_path;
+    pa_dbusiface_card_profile *new_active;
+    int r;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(iter);
+    pa_assert(c);
+
+    if (!c->active_profile) {
+        pa_assert(pa_hashmap_isempty(c->profiles));
+
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                           "The card %s has no profiles, and therefore there's no active profile either.",
+                           c->card->name);
+        return;
+    }
+
+    dbus_message_iter_get_basic(iter, &new_active_path);
+
+    if (!(new_active = pa_hashmap_get(c->profiles, new_active_path))) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such profile.", new_active_path);
+        return;
+    }
+
+    if ((r = pa_card_set_profile(c->card, pa_dbusiface_card_profile_get_name(new_active), TRUE)) < 0) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED,
+                           "Internal error in PulseAudio: pa_card_set_profile() failed with error code %i.", r);
+        return;
+    }
+
+    pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_card *c = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    pa_dbus_send_proplist_variant_reply(conn, msg, c->proplist);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_card *c = userdata;
+    DBusMessage *reply = NULL;
+    DBusMessageIter msg_iter;
+    DBusMessageIter dict_iter;
+    dbus_uint32_t idx;
+    const char *owner_module = NULL;
+    const char **sinks = NULL;
+    unsigned n_sinks = 0;
+    const char **sources = NULL;
+    unsigned n_sources = 0;
+    const char **profiles = NULL;
+    unsigned n_profiles = 0;
+    const char *active_profile = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    idx = c->card->index;
+    if (c->card->module)
+        owner_module = pa_dbusiface_core_get_module_path(c->core, c->card->module);
+    sinks = get_sinks(c, &n_sinks);
+    sources = get_sources(c, &n_sources);
+    profiles = get_profiles(c, &n_profiles);
+    if (c->active_profile)
+        active_profile = pa_dbusiface_card_profile_get_path(pa_hashmap_get(c->profiles, c->active_profile->name));
+
+    pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+    dbus_message_iter_init_append(reply, &msg_iter);
+    pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &c->card->name);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &c->card->driver);
+
+    if (owner_module)
+        pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_OWNER_MODULE].property_name, DBUS_TYPE_OBJECT_PATH, &owner_module);
+
+    pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SINKS].property_name, DBUS_TYPE_OBJECT_PATH, sinks, n_sinks);
+    pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SOURCES].property_name, DBUS_TYPE_OBJECT_PATH, sources, n_sources);
+    pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROFILES].property_name, DBUS_TYPE_OBJECT_PATH, profiles, n_profiles);
+
+    if (active_profile)
+        pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACTIVE_PROFILE].property_name, DBUS_TYPE_OBJECT_PATH, &active_profile);
+
+    pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, c->proplist);
+
+    pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+    pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+    dbus_message_unref(reply);
+
+    pa_xfree(sinks);
+    pa_xfree(sources);
+    pa_xfree(profiles);
+}
+
+static void handle_get_profile_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_card *c = userdata;
+    const char *profile_name = NULL;
+    pa_dbusiface_card_profile *profile = NULL;
+    const char *profile_path = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &profile_name, DBUS_TYPE_INVALID));
+
+    if (!(profile = pa_hashmap_get(c->profiles, profile_name))) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such profile on card %s.", profile_name, c->card->name);
+        return;
+    }
+
+    profile_path = pa_dbusiface_card_profile_get_path(profile);
+
+    pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &profile_path);
+}
+
+static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+    pa_dbusiface_card *c = userdata;
+    DBusMessage *signal = NULL;
+
+    pa_assert(core);
+    pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_CARD);
+    pa_assert(c);
+
+    /* We can't use idx != c->card->index, because the c->card pointer may
+     * be stale at this point. */
+    if (pa_idxset_get_by_index(core->cards, idx) != c->card)
+        return;
+
+    if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE)
+        return;
+
+    if (c->active_profile != c->card->active_profile) {
+        const char *object_path;
+
+        c->active_profile = c->card->active_profile;
+        object_path = pa_dbusiface_card_profile_get_path(pa_hashmap_get(c->profiles, c->active_profile->name));
+
+        pa_assert_se(signal = dbus_message_new_signal(c->path,
+                                                      PA_DBUSIFACE_CARD_INTERFACE,
+                                                      signals[SIGNAL_ACTIVE_PROFILE_UPDATED].name));
+        pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+        pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+        dbus_message_unref(signal);
+        signal = NULL;
+    }
+
+    if (!pa_proplist_equal(c->proplist, c->card->proplist)) {
+        DBusMessageIter msg_iter;
+
+        pa_proplist_update(c->proplist, PA_UPDATE_SET, c->card->proplist);
+
+        pa_assert_se(signal = dbus_message_new_signal(c->path,
+                                                      PA_DBUSIFACE_CARD_INTERFACE,
+                                                      signals[SIGNAL_PROPERTY_LIST_UPDATED].name));
+        dbus_message_iter_init_append(signal, &msg_iter);
+        pa_dbus_append_proplist(&msg_iter, c->proplist);
+
+        pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+        dbus_message_unref(signal);
+        signal = NULL;
+    }
+}
+
+pa_dbusiface_card *pa_dbusiface_card_new(pa_dbusiface_core *core, pa_card *card) {
+    pa_dbusiface_card *c = NULL;
+
+    pa_assert(core);
+    pa_assert(card);
+
+    c = pa_xnew0(pa_dbusiface_card, 1);
+    c->core = core;
+    c->card = card;
+    c->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, card->index);
+    c->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+    c->next_profile_index = 0;
+    c->active_profile = NULL;
+    c->proplist = pa_proplist_copy(card->proplist);
+    c->dbus_protocol = pa_dbus_protocol_get(card->core);
+    c->subscription = pa_subscription_new(card->core, PA_SUBSCRIPTION_MASK_CARD, subscription_cb, c);
+
+    if (card->profiles) {
+        pa_card_profile *profile;
+        void *state = NULL;
+
+        PA_HASHMAP_FOREACH(profile, card->profiles, state) {
+            pa_dbusiface_card_profile *p = pa_dbusiface_card_profile_new(c, card->core, profile, c->next_profile_index++);
+            pa_hashmap_put(c->profiles, pa_dbusiface_card_profile_get_name(p), p);
+        }
+        pa_assert_se(c->active_profile = card->active_profile);
+    }
+
+    pa_assert_se(pa_dbus_protocol_add_interface(c->dbus_protocol, c->path, &card_interface_info, c) >= 0);
+
+    return c;
+}
+
+static void profile_free_cb(void *p, void *userdata) {
+    pa_dbusiface_card_profile *profile = p;
+
+    pa_assert(profile);
+
+    pa_dbusiface_card_profile_free(profile);
+}
+
+void pa_dbusiface_card_free(pa_dbusiface_card *c) {
+    pa_assert(c);
+
+    pa_assert_se(pa_dbus_protocol_remove_interface(c->dbus_protocol, c->path, card_interface_info.name) >= 0);
+
+    pa_hashmap_free(c->profiles, profile_free_cb, NULL);
+    pa_proplist_free(c->proplist);
+    pa_dbus_protocol_unref(c->dbus_protocol);
+    pa_subscription_free(c->subscription);
+
+    pa_xfree(c->path);
+    pa_xfree(c);
+}
+
+const char *pa_dbusiface_card_get_path(pa_dbusiface_card *c) {
+    pa_assert(c);
+
+    return c->path;
+}
diff --git a/src/modules/dbus/iface-card.h b/src/modules/dbus/iface-card.h
new file mode 100644 (file)
index 0000000..e2c08a3
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef foodbusifacecardhfoo
+#define foodbusifacecardhfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+/* This object implements the D-Bus interface org.PulseAudio.Core1.Card.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the Card interface
+ * documentation.
+ */
+
+#include <pulsecore/card.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-core.h"
+
+#define PA_DBUSIFACE_CARD_INTERFACE PA_DBUS_CORE_INTERFACE ".Card"
+
+typedef struct pa_dbusiface_card pa_dbusiface_card;
+
+pa_dbusiface_card *pa_dbusiface_card_new(pa_dbusiface_core *core, pa_card *card);
+void pa_dbusiface_card_free(pa_dbusiface_card *c);
+
+const char *pa_dbusiface_card_get_path(pa_dbusiface_card *c);
+
+#endif
diff --git a/src/modules/dbus/iface-client.c b/src/modules/dbus/iface-client.c
new file mode 100644 (file)
index 0000000..54550d2
--- /dev/null
@@ -0,0 +1,459 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+  Copyright 2009 Vincent Filali-Ansary <filali.v@azurdigitalnetworks.net>
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <dbus/dbus.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-client.h"
+
+#define OBJECT_NAME "client"
+
+struct pa_dbusiface_client {
+    pa_dbusiface_core *core;
+
+    pa_client *client;
+    char *path;
+    pa_proplist *proplist;
+
+    pa_dbus_protocol *dbus_protocol;
+    pa_subscription *subscription;
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_update_properties(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_remove_properties(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+enum property_handler_index {
+    PROPERTY_HANDLER_INDEX,
+    PROPERTY_HANDLER_DRIVER,
+    PROPERTY_HANDLER_OWNER_MODULE,
+    PROPERTY_HANDLER_PLAYBACK_STREAMS,
+    PROPERTY_HANDLER_RECORD_STREAMS,
+    PROPERTY_HANDLER_PROPERTY_LIST,
+    PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+    [PROPERTY_HANDLER_INDEX]            = { .property_name = "Index",           .type = "u",      .get_cb = handle_get_index,            .set_cb = NULL },
+    [PROPERTY_HANDLER_DRIVER]           = { .property_name = "Driver",          .type = "s",      .get_cb = handle_get_driver,           .set_cb = NULL },
+    [PROPERTY_HANDLER_OWNER_MODULE]     = { .property_name = "OwnerModule",     .type = "o",      .get_cb = handle_get_owner_module,     .set_cb = NULL },
+    [PROPERTY_HANDLER_PLAYBACK_STREAMS] = { .property_name = "PlaybackStreams", .type = "ao",     .get_cb = handle_get_playback_streams, .set_cb = NULL },
+    [PROPERTY_HANDLER_RECORD_STREAMS]   = { .property_name = "RecordStreams",   .type = "ao",     .get_cb = handle_get_record_streams,   .set_cb = NULL },
+    [PROPERTY_HANDLER_PROPERTY_LIST]    = { .property_name = "PropertyList",    .type = "a{say}", .get_cb = handle_get_property_list,    .set_cb = NULL }
+};
+
+enum method_handler_index {
+    METHOD_HANDLER_KILL,
+    METHOD_HANDLER_UPDATE_PROPERTIES,
+    METHOD_HANDLER_REMOVE_PROPERTIES,
+    METHOD_HANDLER_MAX
+};
+
+static pa_dbus_arg_info update_properties_args[] = { { "property_list", "a{say}", "in" }, { "update_mode", "u", "in" } };
+static pa_dbus_arg_info remove_properties_args[] = { { "keys", "as", "in" } };
+
+static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
+    [METHOD_HANDLER_KILL] = {
+        .method_name = "Kill",
+        .arguments = NULL,
+        .n_arguments = 0,
+        .receive_cb = handle_kill },
+    [METHOD_HANDLER_UPDATE_PROPERTIES] = {
+        .method_name = "UpdateProperties",
+        .arguments = update_properties_args,
+        .n_arguments = sizeof(update_properties_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_update_properties },
+    [METHOD_HANDLER_REMOVE_PROPERTIES] = {
+        .method_name = "RemoveProperties",
+        .arguments = remove_properties_args,
+        .n_arguments = sizeof(update_properties_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_remove_properties }
+};
+
+enum signal_index {
+    SIGNAL_PROPERTY_LIST_UPDATED,
+    SIGNAL_CLIENT_EVENT,
+    SIGNAL_MAX
+};
+
+static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } };
+static pa_dbus_arg_info client_event_args[]          = { { "name",          "s",      NULL },
+                                                         { "property_list", "a{say}", NULL } };
+
+static pa_dbus_signal_info signals[SIGNAL_MAX] = {
+    [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 },
+    /* ClientEvent is sent from module-dbus-protocol.c. */
+    [SIGNAL_CLIENT_EVENT]          = { .name = "ClientEvent",         .arguments = client_event_args,          .n_arguments = 1 }
+};
+
+static pa_dbus_interface_info client_interface_info = {
+    .name = PA_DBUSIFACE_CLIENT_INTERFACE,
+    .method_handlers = method_handlers,
+    .n_method_handlers = METHOD_HANDLER_MAX,
+    .property_handlers = property_handlers,
+    .n_property_handlers = PROPERTY_HANDLER_MAX,
+    .get_all_properties_cb = handle_get_all,
+    .signals = signals,
+    .n_signals = SIGNAL_MAX
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_client *c = userdata;
+    dbus_uint32_t idx = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    idx = c->client->index;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx);
+}
+
+static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_client *c = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &c->client->driver);
+}
+
+static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_client *c = userdata;
+    const char *owner_module = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    if (!c->client->module) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Client %d doesn't have an owner module.", c->client->index);
+        return;
+    }
+
+    owner_module = pa_dbusiface_core_get_module_path(c->core, c->client->module);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &owner_module);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_playback_streams(pa_dbusiface_client *c, unsigned *n) {
+    const char **playback_streams = NULL;
+    unsigned i = 0;
+    uint32_t idx = 0;
+    pa_sink_input *sink_input = NULL;
+
+    pa_assert(c);
+    pa_assert(n);
+
+    *n = pa_idxset_size(c->client->sink_inputs);
+
+    if (*n == 0)
+        return NULL;
+
+    playback_streams = pa_xnew(const char *, *n);
+
+    PA_IDXSET_FOREACH(sink_input, c->client->sink_inputs, idx)
+        playback_streams[i++] = pa_dbusiface_core_get_playback_stream_path(c->core, sink_input);
+
+    return playback_streams;
+}
+
+static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_client *c = userdata;
+    const char **playback_streams = NULL;
+    unsigned n_playback_streams = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    playback_streams = get_playback_streams(c, &n_playback_streams);
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, playback_streams, n_playback_streams);
+
+    pa_xfree(playback_streams);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_record_streams(pa_dbusiface_client *c, unsigned *n) {
+    const char **record_streams = NULL;
+    unsigned i = 0;
+    uint32_t idx = 0;
+    pa_source_output *source_output = NULL;
+
+    pa_assert(c);
+    pa_assert(n);
+
+    *n = pa_idxset_size(c->client->source_outputs);
+
+    if (*n == 0)
+        return NULL;
+
+    record_streams = pa_xnew(const char *, *n);
+
+    PA_IDXSET_FOREACH(source_output, c->client->source_outputs, idx)
+        record_streams[i++] = pa_dbusiface_core_get_record_stream_path(c->core, source_output);
+
+    return record_streams;
+}
+
+static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_client *c = userdata;
+    const char **record_streams = NULL;
+    unsigned n_record_streams = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    record_streams = get_record_streams(c, &n_record_streams);
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, record_streams, n_record_streams);
+
+    pa_xfree(record_streams);
+}
+
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_client *c = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    pa_dbus_send_proplist_variant_reply(conn, msg, c->client->proplist);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_client *c = userdata;
+    DBusMessage *reply = NULL;
+    DBusMessageIter msg_iter;
+    DBusMessageIter dict_iter;
+    dbus_uint32_t idx = 0;
+    const char *owner_module = NULL;
+    const char **playback_streams = NULL;
+    unsigned n_playback_streams = 0;
+    const char **record_streams = NULL;
+    unsigned n_record_streams = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    idx = c->client->index;
+    if (c->client->module)
+        owner_module = pa_dbusiface_core_get_module_path(c->core, c->client->module);
+    playback_streams = get_playback_streams(c, &n_playback_streams);
+    record_streams = get_record_streams(c, &n_record_streams);
+
+    pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+    dbus_message_iter_init_append(reply, &msg_iter);
+    pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &c->client->driver);
+
+    if (owner_module)
+        pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_OWNER_MODULE].property_name, DBUS_TYPE_OBJECT_PATH, &owner_module);
+
+    pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PLAYBACK_STREAMS].property_name, DBUS_TYPE_OBJECT_PATH, playback_streams, n_playback_streams);
+    pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_RECORD_STREAMS].property_name, DBUS_TYPE_OBJECT_PATH, record_streams, n_record_streams);
+    pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, c->client->proplist);
+
+    pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+    pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+    dbus_message_unref(reply);
+
+    pa_xfree(playback_streams);
+    pa_xfree(record_streams);
+}
+
+static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_client *c = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    dbus_connection_ref(conn);
+
+    pa_client_kill(c->client);
+
+    pa_dbus_send_empty_reply(conn, msg);
+
+    dbus_connection_unref(conn);
+}
+
+static void handle_update_properties(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_client *c = userdata;
+    DBusMessageIter msg_iter;
+    pa_proplist *property_list = NULL;
+    dbus_uint32_t update_mode = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    if (pa_dbus_protocol_get_client(c->dbus_protocol, conn) != c->client) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "Client tried to modify the property list of another client.");
+        return;
+    }
+
+    pa_assert_se(dbus_message_iter_init(msg, &msg_iter));
+
+    if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter)))
+        return;
+
+    dbus_message_iter_get_basic(&msg_iter, &update_mode);
+
+    if (!(update_mode == PA_UPDATE_SET || update_mode == PA_UPDATE_MERGE || update_mode == PA_UPDATE_REPLACE)) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid update mode: %u", update_mode);
+        goto finish;
+    }
+
+    pa_client_update_proplist(c->client, update_mode, property_list);
+
+    pa_dbus_send_empty_reply(conn, msg);
+
+finish:
+    if (property_list)
+        pa_proplist_free(property_list);
+}
+
+static void handle_remove_properties(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_client *c = userdata;
+    char **keys = NULL;
+    int n_keys = 0;
+    pa_bool_t changed = FALSE;
+    int i = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    if (pa_dbus_protocol_get_client(c->dbus_protocol, conn) != c->client) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "Client tried to modify the property list of another client.");
+        return;
+    }
+
+    pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &keys, &n_keys, DBUS_TYPE_INVALID));
+
+    for (i = 0; i < n_keys; ++i)
+        changed |= pa_proplist_unset(c->client->proplist, keys[i]) >= 0;
+
+    pa_dbus_send_empty_reply(conn, msg);
+
+    if (changed)
+        pa_subscription_post(c->client->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE, c->client->index);
+
+    dbus_free_string_array(keys);
+}
+
+static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+    pa_dbusiface_client *c = userdata;
+    DBusMessage *signal = NULL;
+
+    pa_assert(core);
+    pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_CLIENT);
+    pa_assert(c);
+
+    /* We can't use idx != c->client->index, because the c->client pointer may
+     * be stale at this point. */
+    if (pa_idxset_get_by_index(core->clients, idx) != c->client)
+        return;
+
+    if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE)
+        return;
+
+    if (!pa_proplist_equal(c->proplist, c->client->proplist)) {
+        DBusMessageIter msg_iter;
+
+        pa_proplist_update(c->proplist, PA_UPDATE_SET, c->client->proplist);
+
+        pa_assert_se(signal = dbus_message_new_signal(c->path,
+                                                      PA_DBUSIFACE_CLIENT_INTERFACE,
+                                                      signals[SIGNAL_PROPERTY_LIST_UPDATED].name));
+        dbus_message_iter_init_append(signal, &msg_iter);
+        pa_dbus_append_proplist(&msg_iter, c->proplist);
+
+        pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+        dbus_message_unref(signal);
+        signal = NULL;
+    }
+}
+
+pa_dbusiface_client *pa_dbusiface_client_new(pa_dbusiface_core *core, pa_client *client) {
+    pa_dbusiface_client *c = NULL;
+
+    pa_assert(core);
+    pa_assert(client);
+
+    c = pa_xnew(pa_dbusiface_client, 1);
+    c->core = core;
+    c->client = client;
+    c->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, client->index);
+    c->proplist = pa_proplist_copy(client->proplist);
+    c->dbus_protocol = pa_dbus_protocol_get(client->core);
+    c->subscription = pa_subscription_new(client->core, PA_SUBSCRIPTION_MASK_CLIENT, subscription_cb, c);
+
+    pa_assert_se(pa_dbus_protocol_add_interface(c->dbus_protocol, c->path, &client_interface_info, c) >= 0);
+
+    return c;
+}
+
+void pa_dbusiface_client_free(pa_dbusiface_client *c) {
+    pa_assert(c);
+
+    pa_assert_se(pa_dbus_protocol_remove_interface(c->dbus_protocol, c->path, client_interface_info.name) >= 0);
+
+    pa_dbus_protocol_unref(c->dbus_protocol);
+
+    pa_xfree(c->path);
+    pa_xfree(c);
+}
+
+const char *pa_dbusiface_client_get_path(pa_dbusiface_client *c) {
+    pa_assert(c);
+
+    return c->path;
+}
diff --git a/src/modules/dbus/iface-client.h b/src/modules/dbus/iface-client.h
new file mode 100644 (file)
index 0000000..e8f151c
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef foodbusifaceclienthfoo
+#define foodbusifaceclienthfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+/* This object implements the D-Bus interface org.PulseAudio.Core1.Client.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the Client interface
+ * documentation.
+ */
+
+#include <pulsecore/client.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-core.h"
+
+#define PA_DBUSIFACE_CLIENT_INTERFACE PA_DBUS_CORE_INTERFACE ".Client"
+
+typedef struct pa_dbusiface_client pa_dbusiface_client;
+
+pa_dbusiface_client *pa_dbusiface_client_new(pa_dbusiface_core *core, pa_client *client);
+void pa_dbusiface_client_free(pa_dbusiface_client *c);
+
+const char *pa_dbusiface_client_get_path(pa_dbusiface_client *c);
+
+#endif
diff --git a/src/modules/dbus/iface-core.c b/src/modules/dbus/iface-core.c
new file mode 100644 (file)
index 0000000..0507ac9
--- /dev/null
@@ -0,0 +1,2177 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+
+#include <dbus/dbus.h>
+
+#include <pulse/utf8.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/protocol-dbus.h>
+#include <pulsecore/socket-util.h>
+#include <pulsecore/strbuf.h>
+
+#include "iface-card.h"
+#include "iface-client.h"
+#include "iface-device.h"
+#include "iface-memstats.h"
+#include "iface-module.h"
+#include "iface-sample.h"
+#include "iface-stream.h"
+
+#include "iface-core.h"
+
+#define INTERFACE_REVISION 0
+
+static void handle_get_interface_revision(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_version(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_is_local(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_username(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_hostname(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_default_channels(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_default_channels(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_default_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_default_sample_format(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_default_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_default_sample_rate(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_cards(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_fallback_sink(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_fallback_sink(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_fallback_source(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_fallback_source(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_samples(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_modules(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_clients(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_my_client(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_extensions(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_card_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sink_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_source_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sample_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_upload_sample(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_load_module(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_exit(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_listen_for_signal(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_stop_listening_for_signal(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+struct pa_dbusiface_core {
+    pa_core *core;
+    pa_subscription *subscription;
+
+    pa_dbus_protocol *dbus_protocol;
+
+    pa_hashmap *cards;
+    pa_hashmap *sinks_by_index;
+    pa_hashmap *sinks_by_path;
+    pa_hashmap *sources_by_index;
+    pa_hashmap *sources_by_path;
+    pa_hashmap *playback_streams;
+    pa_hashmap *record_streams;
+    pa_hashmap *samples;
+    pa_hashmap *modules;
+    pa_hashmap *clients;
+
+    pa_sink *fallback_sink;
+    pa_source *fallback_source;
+
+    pa_hook_slot *extension_registered_slot;
+    pa_hook_slot *extension_unregistered_slot;
+
+    pa_dbusiface_memstats *memstats;
+};
+
+enum property_handler_index {
+    PROPERTY_HANDLER_INTERFACE_REVISION,
+    PROPERTY_HANDLER_NAME,
+    PROPERTY_HANDLER_VERSION,
+    PROPERTY_HANDLER_IS_LOCAL,
+    PROPERTY_HANDLER_USERNAME,
+    PROPERTY_HANDLER_HOSTNAME,
+    PROPERTY_HANDLER_DEFAULT_CHANNELS,
+    PROPERTY_HANDLER_DEFAULT_SAMPLE_FORMAT,
+    PROPERTY_HANDLER_DEFAULT_SAMPLE_RATE,
+    PROPERTY_HANDLER_CARDS,
+    PROPERTY_HANDLER_SINKS,
+    PROPERTY_HANDLER_FALLBACK_SINK,
+    PROPERTY_HANDLER_SOURCES,
+    PROPERTY_HANDLER_FALLBACK_SOURCE,
+    PROPERTY_HANDLER_PLAYBACK_STREAMS,
+    PROPERTY_HANDLER_RECORD_STREAMS,
+    PROPERTY_HANDLER_SAMPLES,
+    PROPERTY_HANDLER_MODULES,
+    PROPERTY_HANDLER_CLIENTS,
+    PROPERTY_HANDLER_MY_CLIENT,
+    PROPERTY_HANDLER_EXTENSIONS,
+    PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+    [PROPERTY_HANDLER_INTERFACE_REVISION]    = { .property_name = "InterfaceRevision",   .type = "u",  .get_cb = handle_get_interface_revision,    .set_cb = NULL },
+    [PROPERTY_HANDLER_NAME]                  = { .property_name = "Name",                .type = "s",  .get_cb = handle_get_name,                  .set_cb = NULL },
+    [PROPERTY_HANDLER_VERSION]               = { .property_name = "Version",             .type = "s",  .get_cb = handle_get_version,               .set_cb = NULL },
+    [PROPERTY_HANDLER_IS_LOCAL]              = { .property_name = "IsLocal",             .type = "b",  .get_cb = handle_get_is_local,              .set_cb = NULL },
+    [PROPERTY_HANDLER_USERNAME]              = { .property_name = "Username",            .type = "s",  .get_cb = handle_get_username,              .set_cb = NULL },
+    [PROPERTY_HANDLER_HOSTNAME]              = { .property_name = "Hostname",            .type = "s",  .get_cb = handle_get_hostname,              .set_cb = NULL },
+    [PROPERTY_HANDLER_DEFAULT_CHANNELS]      = { .property_name = "DefaultChannels",     .type = "au", .get_cb = handle_get_default_channels,      .set_cb = handle_set_default_channels },
+    [PROPERTY_HANDLER_DEFAULT_SAMPLE_FORMAT] = { .property_name = "DefaultSampleFormat", .type = "u",  .get_cb = handle_get_default_sample_format, .set_cb = handle_set_default_sample_format },
+    [PROPERTY_HANDLER_DEFAULT_SAMPLE_RATE]   = { .property_name = "DefaultSampleRate",   .type = "u",  .get_cb = handle_get_default_sample_rate,   .set_cb = handle_set_default_sample_rate },
+    [PROPERTY_HANDLER_CARDS]                 = { .property_name = "Cards",               .type = "ao", .get_cb = handle_get_cards,                 .set_cb = NULL },
+    [PROPERTY_HANDLER_SINKS]                 = { .property_name = "Sinks",               .type = "ao", .get_cb = handle_get_sinks,                 .set_cb = NULL },
+    [PROPERTY_HANDLER_FALLBACK_SINK]         = { .property_name = "FallbackSink",        .type = "o",  .get_cb = handle_get_fallback_sink,         .set_cb = handle_set_fallback_sink },
+    [PROPERTY_HANDLER_SOURCES]               = { .property_name = "Sources",             .type = "ao", .get_cb = handle_get_sources,               .set_cb = NULL },
+    [PROPERTY_HANDLER_FALLBACK_SOURCE]       = { .property_name = "FallbackSource",      .type = "o",  .get_cb = handle_get_fallback_source,       .set_cb = handle_set_fallback_source },
+    [PROPERTY_HANDLER_PLAYBACK_STREAMS]      = { .property_name = "PlaybackStreams",     .type = "ao", .get_cb = handle_get_playback_streams,      .set_cb = NULL },
+    [PROPERTY_HANDLER_RECORD_STREAMS]        = { .property_name = "RecordStreams",       .type = "ao", .get_cb = handle_get_record_streams,        .set_cb = NULL },
+    [PROPERTY_HANDLER_SAMPLES]               = { .property_name = "Samples",             .type = "ao", .get_cb = handle_get_samples,               .set_cb = NULL },
+    [PROPERTY_HANDLER_MODULES]               = { .property_name = "Modules",             .type = "ao", .get_cb = handle_get_modules,               .set_cb = NULL },
+    [PROPERTY_HANDLER_CLIENTS]               = { .property_name = "Clients",             .type = "ao", .get_cb = handle_get_clients,               .set_cb = NULL },
+    [PROPERTY_HANDLER_MY_CLIENT]             = { .property_name = "MyClient",            .type = "o",  .get_cb = handle_get_my_client,             .set_cb = NULL },
+    [PROPERTY_HANDLER_EXTENSIONS]            = { .property_name = "Extensions",          .type = "as", .get_cb = handle_get_extensions,            .set_cb = NULL }
+};
+
+enum method_handler_index {
+    METHOD_HANDLER_GET_CARD_BY_NAME,
+    METHOD_HANDLER_GET_SINK_BY_NAME,
+    METHOD_HANDLER_GET_SOURCE_BY_NAME,
+    METHOD_HANDLER_GET_SAMPLE_BY_NAME,
+    METHOD_HANDLER_UPLOAD_SAMPLE,
+    METHOD_HANDLER_LOAD_MODULE,
+    METHOD_HANDLER_EXIT,
+    METHOD_HANDLER_LISTEN_FOR_SIGNAL,
+    METHOD_HANDLER_STOP_LISTENING_FOR_SIGNAL,
+    METHOD_HANDLER_MAX
+};
+
+static pa_dbus_arg_info get_card_by_name_args[] = { { "name", "s", "in" }, { "card", "o", "out" } };
+static pa_dbus_arg_info get_sink_by_name_args[] = { { "name", "s", "in" }, { "sink", "o", "out" } };
+static pa_dbus_arg_info get_source_by_name_args[] = { { "name", "s", "in" }, { "source", "o", "out" } };
+static pa_dbus_arg_info get_sample_by_name_args[] = { { "name", "s", "in" }, { "sample", "o", "out" } };
+static pa_dbus_arg_info upload_sample_args[] = { { "name",           "s",      "in" },
+                                                 { "sample_format",  "u",      "in" },
+                                                 { "sample_rate",    "u",      "in" },
+                                                 { "channels",       "au",     "in" },
+                                                 { "default_volume", "au",     "in" },
+                                                 { "property_list",  "a{say}", "in" },
+                                                 { "data",           "ay",     "in" },
+                                                 { "sample",         "o",      "out" } };
+static pa_dbus_arg_info load_module_args[] = { { "name", "s", "in" }, { "arguments", "a{ss}", "in" }, { "module", "o", "out" } };
+static pa_dbus_arg_info listen_for_signal_args[] = { { "signal", "s", "in" }, { "objects", "ao", "in" } };
+static pa_dbus_arg_info stop_listening_for_signal_args[] = { { "signal", "s", "in" } };
+
+static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
+    [METHOD_HANDLER_GET_CARD_BY_NAME] = {
+        .method_name = "GetCardByName",
+        .arguments = get_card_by_name_args,
+        .n_arguments = sizeof(get_card_by_name_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_get_card_by_name },
+    [METHOD_HANDLER_GET_SINK_BY_NAME] = {
+        .method_name = "GetSinkByName",
+        .arguments = get_sink_by_name_args,
+        .n_arguments = sizeof(get_sink_by_name_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_get_sink_by_name },
+    [METHOD_HANDLER_GET_SOURCE_BY_NAME] = {
+        .method_name = "GetSourceByName",
+        .arguments = get_source_by_name_args,
+        .n_arguments = sizeof(get_source_by_name_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_get_source_by_name },
+    [METHOD_HANDLER_GET_SAMPLE_BY_NAME] = {
+        .method_name = "GetSampleByName",
+        .arguments = get_sample_by_name_args,
+        .n_arguments = sizeof(get_sample_by_name_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_get_sample_by_name },
+    [METHOD_HANDLER_UPLOAD_SAMPLE] = {
+        .method_name = "UploadSample",
+        .arguments = upload_sample_args,
+        .n_arguments = sizeof(upload_sample_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_upload_sample },
+    [METHOD_HANDLER_LOAD_MODULE] = {
+        .method_name = "LoadModule",
+        .arguments = load_module_args,
+        .n_arguments = sizeof(load_module_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_load_module },
+    [METHOD_HANDLER_EXIT] = {
+        .method_name = "Exit",
+        .arguments = NULL,
+        .n_arguments = 0,
+        .receive_cb = handle_exit },
+    [METHOD_HANDLER_LISTEN_FOR_SIGNAL] = {
+        .method_name = "ListenForSignal",
+        .arguments = listen_for_signal_args,
+        .n_arguments = sizeof(listen_for_signal_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_listen_for_signal },
+    [METHOD_HANDLER_STOP_LISTENING_FOR_SIGNAL] = {
+        .method_name = "StopListeningForSignal",
+        .arguments = stop_listening_for_signal_args,
+        .n_arguments = sizeof(stop_listening_for_signal_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_stop_listening_for_signal }
+};
+
+enum signal_index {
+    SIGNAL_NEW_CARD,
+    SIGNAL_CARD_REMOVED,
+    SIGNAL_NEW_SINK,
+    SIGNAL_SINK_REMOVED,
+    SIGNAL_FALLBACK_SINK_UPDATED,
+    SIGNAL_NEW_SOURCE,
+    SIGNAL_SOURCE_REMOVED,
+    SIGNAL_FALLBACK_SOURCE_UPDATED,
+    SIGNAL_NEW_PLAYBACK_STREAM,
+    SIGNAL_PLAYBACK_STREAM_REMOVED,
+    SIGNAL_NEW_RECORD_STREAM,
+    SIGNAL_RECORD_STREAM_REMOVED,
+    SIGNAL_NEW_SAMPLE,
+    SIGNAL_SAMPLE_REMOVED,
+    SIGNAL_NEW_MODULE,
+    SIGNAL_MODULE_REMOVED,
+    SIGNAL_NEW_CLIENT,
+    SIGNAL_CLIENT_REMOVED,
+    SIGNAL_NEW_EXTENSION,
+    SIGNAL_EXTENSION_REMOVED,
+    SIGNAL_MAX
+};
+
+static pa_dbus_arg_info new_card_args[] =                { { "card",            "o", NULL } };
+static pa_dbus_arg_info card_removed_args[] =            { { "card",            "o", NULL } };
+static pa_dbus_arg_info new_sink_args[] =                { { "sink",            "o", NULL } };
+static pa_dbus_arg_info sink_removed_args[] =            { { "sink",            "o", NULL } };
+static pa_dbus_arg_info fallback_sink_updated_args[] =   { { "sink",            "o", NULL } };
+static pa_dbus_arg_info new_source_args[] =              { { "source",          "o", NULL } };
+static pa_dbus_arg_info source_removed_args[] =          { { "source",          "o", NULL } };
+static pa_dbus_arg_info fallback_source_updated_args[] = { { "source",          "o", NULL } };
+static pa_dbus_arg_info new_playback_stream_args[] =     { { "playback_stream", "o", NULL } };
+static pa_dbus_arg_info playback_stream_removed_args[] = { { "playback_stream", "o", NULL } };
+static pa_dbus_arg_info new_record_stream_args[] =       { { "record_stream",   "o", NULL } };
+static pa_dbus_arg_info record_stream_removed_args[] =   { { "record_stream",   "o", NULL } };
+static pa_dbus_arg_info new_sample_args[] =              { { "sample",          "o", NULL } };
+static pa_dbus_arg_info sample_removed_args[] =          { { "sample",          "o", NULL } };
+static pa_dbus_arg_info new_module_args[] =              { { "module",          "o", NULL } };
+static pa_dbus_arg_info module_removed_args[] =          { { "module",          "o", NULL } };
+static pa_dbus_arg_info new_client_args[] =              { { "client",          "o", NULL } };
+static pa_dbus_arg_info client_removed_args[] =          { { "client",          "o", NULL } };
+static pa_dbus_arg_info new_extension_args[] =           { { "extension",       "s", NULL } };
+static pa_dbus_arg_info extension_removed_args[] =       { { "extension",       "s", NULL } };
+
+static pa_dbus_signal_info signals[SIGNAL_MAX] = {
+    [SIGNAL_NEW_CARD]                = { .name = "NewCard",               .arguments = new_card_args,                .n_arguments = 1 },
+    [SIGNAL_CARD_REMOVED]            = { .name = "CardRemoved",           .arguments = card_removed_args,            .n_arguments = 1 },
+    [SIGNAL_NEW_SINK]                = { .name = "NewSink",               .arguments = new_sink_args,                .n_arguments = 1 },
+    [SIGNAL_SINK_REMOVED]            = { .name = "SinkRemoved",           .arguments = sink_removed_args,            .n_arguments = 1 },
+    [SIGNAL_FALLBACK_SINK_UPDATED]   = { .name = "FallbackSinkUpdated",   .arguments = fallback_sink_updated_args,   .n_arguments = 1 },
+    [SIGNAL_NEW_SOURCE]              = { .name = "NewSource",             .arguments = new_source_args,              .n_arguments = 1 },
+    [SIGNAL_SOURCE_REMOVED]          = { .name = "SourceRemoved",         .arguments = source_removed_args,          .n_arguments = 1 },
+    [SIGNAL_FALLBACK_SOURCE_UPDATED] = { .name = "FallbackSourceUpdated", .arguments = fallback_source_updated_args, .n_arguments = 1 },
+    [SIGNAL_NEW_PLAYBACK_STREAM]     = { .name = "NewPlaybackStream",     .arguments = new_playback_stream_args,     .n_arguments = 1 },
+    [SIGNAL_PLAYBACK_STREAM_REMOVED] = { .name = "PlaybackStreamRemoved", .arguments = playback_stream_removed_args, .n_arguments = 1 },
+    [SIGNAL_NEW_RECORD_STREAM]       = { .name = "NewRecordStream",       .arguments = new_record_stream_args,       .n_arguments = 1 },
+    [SIGNAL_RECORD_STREAM_REMOVED]   = { .name = "RecordStreamRemoved",   .arguments = record_stream_removed_args,   .n_arguments = 1 },
+    [SIGNAL_NEW_SAMPLE]              = { .name = "NewSample",             .arguments = new_sample_args,              .n_arguments = 1 },
+    [SIGNAL_SAMPLE_REMOVED]          = { .name = "SampleRemoved",         .arguments = sample_removed_args,          .n_arguments = 1 },
+    [SIGNAL_NEW_MODULE]              = { .name = "NewModule",             .arguments = new_module_args,              .n_arguments = 1 },
+    [SIGNAL_MODULE_REMOVED]          = { .name = "ModuleRemoved",         .arguments = module_removed_args,          .n_arguments = 1 },
+    [SIGNAL_NEW_CLIENT]              = { .name = "NewClient",             .arguments = new_client_args,              .n_arguments = 1 },
+    [SIGNAL_CLIENT_REMOVED]          = { .name = "ClientRemoved",         .arguments = client_removed_args,          .n_arguments = 1 },
+    [SIGNAL_NEW_EXTENSION]           = { .name = "NewExtension",          .arguments = new_extension_args,           .n_arguments = 1 },
+    [SIGNAL_EXTENSION_REMOVED]       = { .name = "ExtensionRemoved",      .arguments = extension_removed_args,       .n_arguments = 1 }
+};
+
+static pa_dbus_interface_info core_interface_info = {
+    .name = PA_DBUS_CORE_INTERFACE,
+    .method_handlers = method_handlers,
+    .n_method_handlers = METHOD_HANDLER_MAX,
+    .property_handlers = property_handlers,
+    .n_property_handlers = PROPERTY_HANDLER_MAX,
+    .get_all_properties_cb = handle_get_all,
+    .signals = signals,
+    .n_signals = SIGNAL_MAX
+};
+
+static void handle_get_interface_revision(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    dbus_uint32_t interface_revision = INTERFACE_REVISION;
+
+    pa_assert(conn);
+    pa_assert(msg);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &interface_revision);
+}
+
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    const char *server_name = PACKAGE_NAME;
+
+    pa_assert(conn);
+    pa_assert(msg);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &server_name);
+}
+
+static void handle_get_version(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    const char *version = PACKAGE_VERSION;
+
+    pa_assert(conn);
+    pa_assert(msg);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &version);
+}
+
+static dbus_bool_t get_is_local(DBusConnection *conn) {
+    int conn_fd;
+
+    pa_assert(conn);
+
+    if (!dbus_connection_get_socket(conn, &conn_fd))
+        return FALSE;
+
+    return pa_socket_is_local(conn_fd);
+}
+
+static void handle_get_is_local(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    dbus_bool_t is_local;
+
+    pa_assert(conn);
+    pa_assert(msg);
+
+    is_local = get_is_local(conn);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &is_local);
+}
+
+static void handle_get_username(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    char *username = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+
+    username = pa_get_user_name_malloc();
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &username);
+
+    pa_xfree(username);
+}
+
+static void handle_get_hostname(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    char *hostname = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+
+    hostname = pa_get_host_name_malloc();
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &hostname);
+
+    pa_xfree(hostname);
+}
+
+/* Caller frees the returned array. */
+static dbus_uint32_t *get_default_channels(pa_dbusiface_core *c, unsigned *n) {
+    dbus_uint32_t *default_channels = NULL;
+    unsigned i;
+
+    pa_assert(c);
+    pa_assert(n);
+
+    *n = c->core->default_channel_map.channels;
+    default_channels = pa_xnew(dbus_uint32_t, *n);
+
+    for (i = 0; i < *n; ++i)
+        default_channels[i] = c->core->default_channel_map.map[i];
+
+    return default_channels;
+}
+
+static void handle_get_default_channels(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    dbus_uint32_t *default_channels = NULL;
+    unsigned n;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    default_channels = get_default_channels(c, &n);
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, default_channels, n);
+
+    pa_xfree(default_channels);
+}
+
+static void handle_set_default_channels(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    DBusMessageIter array_iter;
+    pa_channel_map new_channel_map;
+    const dbus_uint32_t *default_channels;
+    int n_channels;
+    unsigned i;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(iter);
+    pa_assert(c);
+
+    pa_channel_map_init(&new_channel_map);
+
+    dbus_message_iter_recurse(iter, &array_iter);
+    dbus_message_iter_get_fixed_array(&array_iter, &default_channels, &n_channels);
+
+    if (n_channels <= 0) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Empty channel array.");
+        return;
+    }
+
+    if (n_channels > (int) PA_CHANNELS_MAX) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS,
+                           "Too many channels: %i. The maximum number of channels is %u.", n_channels, PA_CHANNELS_MAX);
+        return;
+    }
+
+    new_channel_map.channels = n_channels;
+
+    for (i = 0; i < new_channel_map.channels; ++i) {
+        if (default_channels[i] >= PA_CHANNEL_POSITION_MAX) {
+            pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid channel position: %u.", default_channels[i]);
+            return;
+        }
+
+        new_channel_map.map[i] = default_channels[i];
+    }
+
+    c->core->default_channel_map = new_channel_map;
+    c->core->default_sample_spec.channels = n_channels;
+
+    pa_dbus_send_empty_reply(conn, msg);
+};
+
+static void handle_get_default_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    dbus_uint32_t default_sample_format;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    default_sample_format = c->core->default_sample_spec.format;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &default_sample_format);
+}
+
+static void handle_set_default_sample_format(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    dbus_uint32_t default_sample_format;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(iter);
+    pa_assert(c);
+
+    dbus_message_iter_get_basic(iter, &default_sample_format);
+
+    if (default_sample_format >= PA_SAMPLE_MAX) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid sample format.");
+        return;
+    }
+
+    c->core->default_sample_spec.format = default_sample_format;
+
+    pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void handle_get_default_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    dbus_uint32_t default_sample_rate;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    default_sample_rate = c->core->default_sample_spec.rate;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &default_sample_rate);
+}
+
+static void handle_set_default_sample_rate(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    dbus_uint32_t default_sample_rate;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(iter);
+    pa_assert(c);
+
+    dbus_message_iter_get_basic(iter, &default_sample_rate);
+
+    if (default_sample_rate <= 0 || default_sample_rate > PA_RATE_MAX) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid sample rate.");
+        return;
+    }
+
+    c->core->default_sample_spec.rate = default_sample_rate;
+
+    pa_dbus_send_empty_reply(conn, msg);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_cards(pa_dbusiface_core *c, unsigned *n) {
+    const char **cards;
+    unsigned i = 0;
+    void *state = NULL;
+    pa_dbusiface_card *card;
+
+    pa_assert(c);
+    pa_assert(n);
+
+    *n = pa_hashmap_size(c->cards);
+
+    if (*n == 0)
+        return NULL;
+
+    cards = pa_xnew(const char *, *n);
+
+    PA_HASHMAP_FOREACH(card, c->cards, state)
+        cards[i++] = pa_dbusiface_card_get_path(card);
+
+    return cards;
+}
+
+static void handle_get_cards(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    const char **cards;
+    unsigned n;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    cards = get_cards(c, &n);
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, cards, n);
+
+    pa_xfree(cards);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_sinks(pa_dbusiface_core *c, unsigned *n) {
+    const char **sinks;
+    unsigned i = 0;
+    void *state = NULL;
+    pa_dbusiface_device *sink;
+
+    pa_assert(c);
+    pa_assert(n);
+
+    *n = pa_hashmap_size(c->sinks_by_index);
+
+    if (*n == 0)
+        return NULL;
+
+    sinks = pa_xnew(const char *, *n);
+
+    PA_HASHMAP_FOREACH(sink, c->sinks_by_index, state)
+        sinks[i++] = pa_dbusiface_device_get_path(sink);
+
+    return sinks;
+}
+
+static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    const char **sinks;
+    unsigned n;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    sinks = get_sinks(c, &n);
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sinks, n);
+
+    pa_xfree(sinks);
+}
+
+static void handle_get_fallback_sink(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    pa_dbusiface_device *fallback_sink;
+    const char *object_path;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    if (!c->fallback_sink) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                           "There are no sinks, and therefore no fallback sink either.");
+        return;
+    }
+
+    pa_assert_se((fallback_sink = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(c->fallback_sink->index))));
+    object_path = pa_dbusiface_device_get_path(fallback_sink);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+}
+
+static void handle_set_fallback_sink(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    pa_dbusiface_device *fallback_sink;
+    const char *object_path;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(iter);
+    pa_assert(c);
+
+    if (!c->fallback_sink) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                           "There are no sinks, and therefore no fallback sink either.");
+        return;
+    }
+
+    dbus_message_iter_get_basic(iter, &object_path);
+
+    if (!(fallback_sink = pa_hashmap_get(c->sinks_by_path, object_path))) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such sink.", object_path);
+        return;
+    }
+
+    pa_namereg_set_default_sink(c->core, pa_dbusiface_device_get_sink(fallback_sink));
+
+    pa_dbus_send_empty_reply(conn, msg);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_sources(pa_dbusiface_core *c, unsigned *n) {
+    const char **sources;
+    unsigned i = 0;
+    void *state = NULL;
+    pa_dbusiface_device *source;
+
+    pa_assert(c);
+    pa_assert(n);
+
+    *n = pa_hashmap_size(c->sources_by_index);
+
+    if (*n == 0)
+        return NULL;
+
+    sources = pa_xnew(const char *, *n);
+
+    PA_HASHMAP_FOREACH(source, c->sources_by_index, state)
+        sources[i++] = pa_dbusiface_device_get_path(source);
+
+    return sources;
+}
+
+static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    const char **sources;
+    unsigned n;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    sources = get_sources(c, &n);
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sources, n);
+
+    pa_xfree(sources);
+}
+
+static void handle_get_fallback_source(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    pa_dbusiface_device *fallback_source;
+    const char *object_path;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    if (!c->fallback_source) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                           "There are no sources, and therefore no fallback source either.");
+        return;
+    }
+
+    pa_assert_se((fallback_source = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(c->fallback_source->index))));
+    object_path = pa_dbusiface_device_get_path(fallback_source);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+}
+
+static void handle_set_fallback_source(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    pa_dbusiface_device *fallback_source;
+    const char *object_path;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(iter);
+    pa_assert(c);
+
+    if (!c->fallback_source) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                           "There are no sources, and therefore no fallback source either.");
+        return;
+    }
+
+    dbus_message_iter_get_basic(iter, &object_path);
+
+    if (!(fallback_source = pa_hashmap_get(c->sources_by_path, object_path))) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such source.", object_path);
+        return;
+    }
+
+    pa_namereg_set_default_source(c->core, pa_dbusiface_device_get_source(fallback_source));
+
+    pa_dbus_send_empty_reply(conn, msg);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_playback_streams(pa_dbusiface_core *c, unsigned *n) {
+    const char **streams;
+    unsigned i = 0;
+    void *state = NULL;
+    pa_dbusiface_stream *stream;
+
+    pa_assert(c);
+    pa_assert(n);
+
+    *n = pa_hashmap_size(c->playback_streams);
+
+    if (*n == 0)
+        return NULL;
+
+    streams = pa_xnew(const char *, *n);
+
+    PA_HASHMAP_FOREACH(stream, c->playback_streams, state)
+        streams[i++] = pa_dbusiface_stream_get_path(stream);
+
+    return streams;
+}
+
+static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    const char **playback_streams;
+    unsigned n;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    playback_streams = get_playback_streams(c, &n);
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, playback_streams, n);
+
+    pa_xfree(playback_streams);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_record_streams(pa_dbusiface_core *c, unsigned *n) {
+    const char **streams;
+    unsigned i = 0;
+    void *state = NULL;
+    pa_dbusiface_stream *stream;
+
+    pa_assert(c);
+    pa_assert(n);
+
+    *n = pa_hashmap_size(c->record_streams);
+
+    if (*n == 0)
+        return NULL;
+
+    streams = pa_xnew(const char *, *n);
+
+    PA_HASHMAP_FOREACH(stream, c->record_streams, state)
+        streams[i++] = pa_dbusiface_stream_get_path(stream);
+
+    return streams;
+}
+
+static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    const char **record_streams;
+    unsigned n;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    record_streams = get_record_streams(c, &n);
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, record_streams, n);
+
+    pa_xfree(record_streams);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_samples(pa_dbusiface_core *c, unsigned *n) {
+    const char **samples;
+    unsigned i = 0;
+    void *state = NULL;
+    pa_dbusiface_sample *sample;
+
+    pa_assert(c);
+    pa_assert(n);
+
+    *n = pa_hashmap_size(c->samples);
+
+    if (*n == 0)
+        return NULL;
+
+    samples = pa_xnew(const char *, *n);
+
+    PA_HASHMAP_FOREACH(sample, c->samples, state)
+        samples[i++] = pa_dbusiface_sample_get_path(sample);
+
+    return samples;
+}
+
+static void handle_get_samples(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    const char **samples;
+    unsigned n;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    samples = get_samples(c, &n);
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, samples, n);
+
+    pa_xfree(samples);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_modules(pa_dbusiface_core *c, unsigned *n) {
+    const char **modules;
+    unsigned i = 0;
+    void *state = NULL;
+    pa_dbusiface_module *module;
+
+    pa_assert(c);
+    pa_assert(n);
+
+    *n = pa_hashmap_size(c->modules);
+
+    if (*n == 0)
+        return NULL;
+
+    modules = pa_xnew(const char *, *n);
+
+    PA_HASHMAP_FOREACH(module, c->modules, state)
+        modules[i++] = pa_dbusiface_module_get_path(module);
+
+    return modules;
+}
+
+static void handle_get_modules(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    const char **modules;
+    unsigned n;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    modules = get_modules(c, &n);
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, modules, n);
+
+    pa_xfree(modules);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_clients(pa_dbusiface_core *c, unsigned *n) {
+    const char **clients;
+    unsigned i = 0;
+    void *state = NULL;
+    pa_dbusiface_client *client;
+
+    pa_assert(c);
+    pa_assert(n);
+
+    *n = pa_hashmap_size(c->clients);
+
+    if (*n == 0)
+        return NULL;
+
+    clients = pa_xnew(const char *, *n);
+
+    PA_HASHMAP_FOREACH(client, c->clients, state)
+        clients[i++] = pa_dbusiface_client_get_path(client);
+
+    return clients;
+}
+
+static void handle_get_clients(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    const char **clients;
+    unsigned n;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    clients = get_clients(c, &n);
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, clients, n);
+
+    pa_xfree(clients);
+}
+
+static const char *get_my_client(pa_dbusiface_core *c, DBusConnection *conn) {
+    pa_client *my_client;
+
+    pa_assert(c);
+    pa_assert(conn);
+
+    pa_assert_se((my_client = pa_dbus_protocol_get_client(c->dbus_protocol, conn)));
+
+    return pa_dbusiface_client_get_path(pa_hashmap_get(c->clients, PA_UINT32_TO_PTR(my_client->index)));
+}
+
+static void handle_get_my_client(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    const char *my_client;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    my_client = get_my_client(c, conn);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &my_client);
+}
+
+static void handle_get_extensions(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    const char **extensions;
+    unsigned n;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    extensions = pa_dbus_protocol_get_extensions(c->dbus_protocol, &n);
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_STRING, extensions, n);
+
+    pa_xfree(extensions);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    DBusMessage *reply = NULL;
+    DBusMessageIter msg_iter;
+    DBusMessageIter dict_iter;
+    dbus_uint32_t interface_revision;
+    const char *server_name;
+    const char *version;
+    dbus_bool_t is_local;
+    char *username;
+    char *hostname;
+    dbus_uint32_t *default_channels;
+    unsigned n_default_channels;
+    dbus_uint32_t default_sample_format;
+    dbus_uint32_t default_sample_rate;
+    const char **cards;
+    unsigned n_cards;
+    const char **sinks;
+    unsigned n_sinks;
+    const char *fallback_sink;
+    const char **sources;
+    unsigned n_sources;
+    const char *fallback_source;
+    const char **playback_streams;
+    unsigned n_playback_streams;
+    const char **record_streams;
+    unsigned n_record_streams;
+    const char **samples;
+    unsigned n_samples;
+    const char **modules;
+    unsigned n_modules;
+    const char **clients;
+    unsigned n_clients;
+    const char *my_client;
+    const char **extensions;
+    unsigned n_extensions;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    interface_revision = INTERFACE_REVISION;
+    server_name = PACKAGE_NAME;
+    version = PACKAGE_VERSION;
+    is_local = get_is_local(conn);
+    username = pa_get_user_name_malloc();
+    hostname = pa_get_host_name_malloc();
+    default_channels = get_default_channels(c, &n_default_channels);
+    default_sample_format = c->core->default_sample_spec.format;
+    default_sample_rate = c->core->default_sample_spec.rate;
+    cards = get_cards(c, &n_cards);
+    sinks = get_sinks(c, &n_sinks);
+    fallback_sink = c->fallback_sink
+                    ? pa_dbusiface_device_get_path(pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(c->fallback_sink->index)))
+                    : NULL;
+    sources = get_sources(c, &n_sources);
+    fallback_source = c->fallback_source
+                      ? pa_dbusiface_device_get_path(pa_hashmap_get(c->sources_by_index,
+                                                                    PA_UINT32_TO_PTR(c->fallback_source->index)))
+                      : NULL;
+    playback_streams = get_playback_streams(c, &n_playback_streams);
+    record_streams = get_record_streams(c, &n_record_streams);
+    samples = get_samples(c, &n_samples);
+    modules = get_modules(c, &n_modules);
+    clients = get_clients(c, &n_clients);
+    my_client = get_my_client(c, conn);
+    extensions = pa_dbus_protocol_get_extensions(c->dbus_protocol, &n_extensions);
+
+    pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+    dbus_message_iter_init_append(reply, &msg_iter);
+    pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INTERFACE_REVISION].property_name, DBUS_TYPE_UINT32, &interface_revision);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &server_name);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_VERSION].property_name, DBUS_TYPE_STRING, &version);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_IS_LOCAL].property_name, DBUS_TYPE_BOOLEAN, &is_local);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_USERNAME].property_name, DBUS_TYPE_STRING, &username);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HOSTNAME].property_name, DBUS_TYPE_STRING, &hostname);
+    pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEFAULT_CHANNELS].property_name, DBUS_TYPE_UINT32, default_channels, n_default_channels);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEFAULT_SAMPLE_FORMAT].property_name, DBUS_TYPE_UINT32, &default_sample_format);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEFAULT_SAMPLE_RATE].property_name, DBUS_TYPE_UINT32, &default_sample_rate);
+    pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CARDS].property_name, DBUS_TYPE_OBJECT_PATH, cards, n_cards);
+    pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SINKS].property_name, DBUS_TYPE_OBJECT_PATH, sinks, n_sinks);
+
+    if (fallback_sink)
+        pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_FALLBACK_SINK].property_name, DBUS_TYPE_OBJECT_PATH, &fallback_sink);
+
+    pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SOURCES].property_name, DBUS_TYPE_OBJECT_PATH, sources, n_sources);
+
+    if (fallback_source)
+        pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_FALLBACK_SOURCE].property_name, DBUS_TYPE_OBJECT_PATH, &fallback_source);
+
+    pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PLAYBACK_STREAMS].property_name, DBUS_TYPE_OBJECT_PATH, playback_streams, n_playback_streams);
+    pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_RECORD_STREAMS].property_name, DBUS_TYPE_OBJECT_PATH, record_streams, n_record_streams);
+    pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLES].property_name, DBUS_TYPE_OBJECT_PATH, samples, n_samples);
+    pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_MODULES].property_name, DBUS_TYPE_OBJECT_PATH, modules, n_modules);
+    pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CLIENTS].property_name, DBUS_TYPE_OBJECT_PATH, clients, n_clients);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_MY_CLIENT].property_name, DBUS_TYPE_OBJECT_PATH, &my_client);
+    pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_EXTENSIONS].property_name, DBUS_TYPE_STRING, extensions, n_extensions);
+
+    pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+    pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+    dbus_message_unref(reply);
+
+    pa_xfree(username);
+    pa_xfree(hostname);
+    pa_xfree(default_channels);
+    pa_xfree(cards);
+    pa_xfree(sinks);
+    pa_xfree(sources);
+    pa_xfree(playback_streams);
+    pa_xfree(record_streams);
+    pa_xfree(samples);
+    pa_xfree(modules);
+    pa_xfree(clients);
+    pa_xfree(extensions);
+}
+
+static void handle_get_card_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    char *card_name;
+    pa_card *card;
+    pa_dbusiface_card *dbus_card;
+    const char *object_path;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &card_name, DBUS_TYPE_INVALID));
+
+    if (!(card = pa_namereg_get(c->core, card_name, PA_NAMEREG_CARD))) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "No such card.");
+        return;
+    }
+
+    pa_assert_se((dbus_card = pa_hashmap_get(c->cards, PA_UINT32_TO_PTR(card->index))));
+
+    object_path = pa_dbusiface_card_get_path(dbus_card);
+
+    pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+}
+
+static void handle_get_sink_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    char *sink_name;
+    pa_sink *sink;
+    pa_dbusiface_device *dbus_sink;
+    const char *object_path;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &sink_name, DBUS_TYPE_INVALID));
+
+    if (!(sink = pa_namereg_get(c->core, sink_name, PA_NAMEREG_SINK))) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such sink.", sink_name);
+        return;
+    }
+
+    pa_assert_se((dbus_sink = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(sink->index))));
+
+    object_path = pa_dbusiface_device_get_path(dbus_sink);
+
+    pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+}
+
+static void handle_get_source_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    char *source_name;
+    pa_source *source;
+    pa_dbusiface_device *dbus_source;
+    const char *object_path;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &source_name, DBUS_TYPE_INVALID));
+
+    if (!(source = pa_namereg_get(c->core, source_name, PA_NAMEREG_SOURCE))) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such source.", source_name);
+        return;
+    }
+
+    pa_assert_se((dbus_source = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(source->index))));
+
+    object_path = pa_dbusiface_device_get_path(dbus_source);
+
+    pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+}
+
+static void handle_get_sample_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    char *sample_name;
+    pa_scache_entry *sample;
+    pa_dbusiface_sample *dbus_sample;
+    const char *object_path;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &sample_name, DBUS_TYPE_INVALID));
+
+    if (!(sample = pa_namereg_get(c->core, sample_name, PA_NAMEREG_SAMPLE))) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "No such sample.");
+        return;
+    }
+
+    pa_assert_se((dbus_sample = pa_hashmap_get(c->samples, PA_UINT32_TO_PTR(sample->index))));
+
+    object_path = pa_dbusiface_sample_get_path(dbus_sample);
+
+    pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+}
+
+static void handle_upload_sample(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    DBusMessageIter msg_iter;
+    DBusMessageIter array_iter;
+    const char *name;
+    dbus_uint32_t sample_format;
+    dbus_uint32_t sample_rate;
+    const dbus_uint32_t *channels;
+    int n_channels;
+    const dbus_uint32_t *default_volume;
+    int n_volume_entries;
+    pa_proplist *property_list;
+    const uint8_t *data;
+    int data_length;
+    int i;
+    pa_sample_spec ss;
+    pa_channel_map map;
+    pa_memchunk chunk;
+    uint32_t idx;
+    pa_dbusiface_sample *dbus_sample = NULL;
+    pa_scache_entry *sample = NULL;
+    const char *object_path;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    chunk.memblock = NULL;
+
+    pa_assert_se(dbus_message_iter_init(msg, &msg_iter));
+    dbus_message_iter_get_basic(&msg_iter, &name);
+
+    pa_assert_se(dbus_message_iter_next(&msg_iter));
+    dbus_message_iter_get_basic(&msg_iter, &sample_format);
+
+    pa_assert_se(dbus_message_iter_next(&msg_iter));
+    dbus_message_iter_get_basic(&msg_iter, &sample_rate);
+
+    pa_assert_se(dbus_message_iter_next(&msg_iter));
+    dbus_message_iter_recurse(&msg_iter, &array_iter);
+    dbus_message_iter_get_fixed_array(&array_iter, &channels, &n_channels);
+
+    pa_assert_se(dbus_message_iter_next(&msg_iter));
+    dbus_message_iter_recurse(&msg_iter, &array_iter);
+    dbus_message_iter_get_fixed_array(&array_iter, &default_volume, &n_volume_entries);
+
+    pa_assert_se(dbus_message_iter_next(&msg_iter));
+    if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter)))
+        return;
+
+    dbus_message_iter_recurse(&msg_iter, &array_iter);
+    dbus_message_iter_get_fixed_array(&array_iter, &data, &data_length);
+
+    if (sample_format >= PA_SAMPLE_MAX) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid sample format.");
+        goto finish;
+    }
+
+    if (sample_rate <= 0 || sample_rate > PA_RATE_MAX) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid sample rate.");
+        goto finish;
+    }
+
+    if (n_channels <= 0) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Empty channel map.");
+        goto finish;
+    }
+
+    if (n_channels > (int) PA_CHANNELS_MAX) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS,
+                           "Too many channels: %i. The maximum is %u.", n_channels, PA_CHANNELS_MAX);
+        goto finish;
+    }
+
+    for (i = 0; i < n_channels; ++i) {
+        if (channels[i] >= PA_CHANNEL_POSITION_MAX) {
+            pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid channel position.");
+            goto finish;
+        }
+    }
+
+    if (n_volume_entries != 0 && n_volume_entries != n_channels) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS,
+                           "The channels and default_volume arguments have different number of elements (%i and %i, resp).",
+                           n_channels, n_volume_entries);
+        goto finish;
+    }
+
+    for (i = 0; i < n_volume_entries; ++i) {
+        if (default_volume[i] > PA_VOLUME_MAX) {
+            pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume: %u.", default_volume[i]);
+            goto finish;
+        }
+    }
+
+    if (data_length == 0) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Empty data.");
+        goto finish;
+    }
+
+    if (data_length > PA_SCACHE_ENTRY_SIZE_MAX) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS,
+                           "Too big sample: %i bytes. The maximum sample length is %u bytes.",
+                           data_length, PA_SCACHE_ENTRY_SIZE_MAX);
+        goto finish;
+    }
+
+    ss.format = sample_format;
+    ss.rate = sample_rate;
+    ss.channels = n_channels;
+
+    pa_assert(pa_sample_spec_valid(&ss));
+
+    if (!pa_frame_aligned(data_length, &ss)) {
+        char buf[PA_SAMPLE_SPEC_SNPRINT_MAX];
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS,
+                           "The sample length (%i bytes) doesn't align with the sample format and channels (%s).",
+                           data_length, pa_sample_spec_snprint(buf, sizeof(buf), &ss));
+        goto finish;
+    }
+
+    map.channels = n_channels;
+    for (i = 0; i < n_channels; ++i)
+        map.map[i] = channels[i];
+
+    chunk.memblock = pa_memblock_new(c->core->mempool, data_length);
+    chunk.index = 0;
+    chunk.length = data_length;
+
+    memcpy(pa_memblock_acquire(chunk.memblock), data, data_length);
+    pa_memblock_release(chunk.memblock);
+
+    if (pa_scache_add_item(c->core, name, &ss, &map, &chunk, property_list, &idx) < 0) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Adding the sample failed.");
+        goto finish;
+    }
+
+    sample = pa_idxset_get_by_index(c->core->scache, idx);
+
+    if (n_volume_entries > 0) {
+        sample->volume.channels = n_channels;
+        for (i = 0; i < n_volume_entries; ++i)
+            sample->volume.values[i] = default_volume[i];
+        sample->volume_is_set = TRUE;
+    } else {
+        sample->volume_is_set = FALSE;
+    }
+
+    dbus_sample = pa_dbusiface_sample_new(c, sample);
+    pa_hashmap_put(c->samples, PA_UINT32_TO_PTR(idx), dbus_sample);
+
+    object_path = pa_dbusiface_sample_get_path(dbus_sample);
+
+    pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+
+finish:
+    if (property_list)
+        pa_proplist_free(property_list);
+
+    if (chunk.memblock)
+        pa_memblock_unref(chunk.memblock);
+}
+
+static pa_bool_t contains_space(const char *string) {
+    const char *p;
+
+    pa_assert(string);
+
+    for (p = string; *p; ++p) {
+        if (isspace(*p))
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+static void handle_load_module(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    DBusMessageIter msg_iter;
+    DBusMessageIter dict_iter;
+    DBusMessageIter dict_entry_iter;
+    char *name = NULL;
+    const char *key = NULL;
+    const char *value = NULL;
+    char *escaped_value = NULL;
+    pa_strbuf *arg_buffer = NULL;
+    char *arg_string = NULL;
+    pa_module *module = NULL;
+    pa_dbusiface_module *dbus_module = NULL;
+    const char *object_path = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    if (c->core->disallow_module_loading) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "The server is configured to disallow module loading.");
+        return;
+    }
+
+    pa_assert_se(dbus_message_iter_init(msg, &msg_iter));
+    dbus_message_iter_get_basic(&msg_iter, &name);
+
+    arg_buffer = pa_strbuf_new();
+
+    pa_assert_se(dbus_message_iter_next(&msg_iter));
+    dbus_message_iter_recurse(&msg_iter, &dict_iter);
+
+    while (dbus_message_iter_get_arg_type(&dict_iter) != DBUS_TYPE_INVALID) {
+        if (!pa_strbuf_isempty(arg_buffer))
+            pa_strbuf_putc(arg_buffer, ' ');
+
+        dbus_message_iter_recurse(&dict_iter, &dict_entry_iter);
+
+        dbus_message_iter_get_basic(&dict_entry_iter, &key);
+
+        if (strlen(key) <= 0 || !pa_ascii_valid(key) || contains_space(key)) {
+            pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid module argument name: %s", key);
+            goto finish;
+        }
+
+        pa_assert_se(dbus_message_iter_next(&dict_entry_iter));
+        dbus_message_iter_get_basic(&dict_entry_iter, &value);
+
+        escaped_value = pa_escape(value, "\"");
+        pa_strbuf_printf(arg_buffer, "%s=\"%s\"", key, escaped_value);
+        pa_xfree(escaped_value);
+
+        dbus_message_iter_next(&dict_iter);
+    }
+
+    arg_string = pa_strbuf_tostring(arg_buffer);
+
+    if (!(module = pa_module_load(c->core, name, arg_string))) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Failed to load module.");
+        goto finish;
+    }
+
+    dbus_module = pa_dbusiface_module_new(module);
+    pa_hashmap_put(c->modules, PA_UINT32_TO_PTR(module->index), dbus_module);
+
+    object_path = pa_dbusiface_module_get_path(dbus_module);
+
+    pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+
+finish:
+    if (arg_buffer)
+        pa_strbuf_free(arg_buffer);
+
+    pa_xfree(arg_string);
+}
+
+static void handle_exit(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    if (c->core->disallow_exit) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "The server is configured to disallow exiting.");
+        return;
+    }
+
+    pa_dbus_send_empty_reply(conn, msg);
+
+    pa_core_exit(c->core, FALSE, 0);
+}
+
+static void handle_listen_for_signal(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    const char *signal;
+    char **objects = NULL;
+    int n_objects;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    pa_assert_se(dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_STRING, &signal,
+                                       DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &objects, &n_objects,
+                                       DBUS_TYPE_INVALID));
+
+    pa_dbus_protocol_add_signal_listener(c->dbus_protocol, conn, *signal ? signal : NULL, objects, n_objects);
+
+    pa_dbus_send_empty_reply(conn, msg);
+
+    dbus_free_string_array(objects);
+}
+
+static void handle_stop_listening_for_signal(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    const char *signal;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(c);
+
+    pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &signal, DBUS_TYPE_INVALID));
+
+    pa_dbus_protocol_remove_signal_listener(c->dbus_protocol, conn, *signal ? signal : NULL);
+
+    pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+    pa_dbusiface_core *c = userdata;
+    pa_dbusiface_card *card_iface = NULL;
+    pa_dbusiface_device *device_iface = NULL;
+    pa_dbusiface_stream *stream_iface = NULL;
+    pa_dbusiface_sample *sample_iface = NULL;
+    pa_dbusiface_module *module_iface = NULL;
+    pa_dbusiface_client *client_iface = NULL;
+    DBusMessage *signal = NULL;
+    const char *object_path = NULL;
+    pa_sink *new_fallback_sink = NULL;
+    pa_source *new_fallback_source = NULL;
+
+    pa_assert(c);
+
+    switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
+        case PA_SUBSCRIPTION_EVENT_SERVER:
+            new_fallback_sink = pa_namereg_get_default_sink(core);
+            new_fallback_source = pa_namereg_get_default_source(core);
+
+            if (c->fallback_sink != new_fallback_sink) {
+                if (c->fallback_sink)
+                    pa_sink_unref(c->fallback_sink);
+                c->fallback_sink = new_fallback_sink ? pa_sink_ref(new_fallback_sink) : NULL;
+
+                if (new_fallback_sink
+                    && (device_iface = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(new_fallback_sink->index)))) {
+                    object_path = pa_dbusiface_device_get_path(device_iface);
+
+                    pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                                   PA_DBUS_CORE_INTERFACE,
+                                                                   signals[SIGNAL_FALLBACK_SINK_UPDATED].name)));
+                    pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+                    pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+                    dbus_message_unref(signal);
+                    signal = NULL;
+                }
+            }
+
+            if (c->fallback_source != new_fallback_source) {
+                if (c->fallback_source)
+                    pa_source_unref(c->fallback_source);
+                c->fallback_source = new_fallback_source ? pa_source_ref(new_fallback_source) : NULL;
+
+                if (new_fallback_source
+                    && (device_iface = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(new_fallback_source->index)))) {
+                    object_path = pa_dbusiface_device_get_path(device_iface);
+
+                    pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                                   PA_DBUS_CORE_INTERFACE,
+                                                                   signals[SIGNAL_FALLBACK_SOURCE_UPDATED].name)));
+                    pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+                    pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+                    dbus_message_unref(signal);
+                    signal = NULL;
+                }
+            }
+            break;
+
+        case PA_SUBSCRIPTION_EVENT_CARD:
+            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+                if (!(card_iface = pa_hashmap_get(c->cards, PA_UINT32_TO_PTR(idx)))) {
+                    pa_card *card = NULL;
+
+                    if (!(card = pa_idxset_get_by_index(core->cards, idx)))
+                        return; /* The card was removed immediately after creation. */
+
+                    card_iface = pa_dbusiface_card_new(c, card);
+                    pa_hashmap_put(c->cards, PA_UINT32_TO_PTR(idx), card_iface);
+                }
+
+                object_path = pa_dbusiface_card_get_path(card_iface);
+
+                pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                               PA_DBUS_CORE_INTERFACE,
+                                                               signals[SIGNAL_NEW_CARD].name)));
+                pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+            } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+                if (!(card_iface = pa_hashmap_remove(c->cards, PA_UINT32_TO_PTR(idx))))
+                    return;
+
+                object_path = pa_dbusiface_card_get_path(card_iface);
+
+                pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                               PA_DBUS_CORE_INTERFACE,
+                                                               signals[SIGNAL_CARD_REMOVED].name)));
+                pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+                pa_dbusiface_card_free(card_iface);
+            }
+            break;
+
+        case PA_SUBSCRIPTION_EVENT_SINK:
+            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+                pa_sink *sink = NULL;
+
+                if (!(sink = pa_idxset_get_by_index(core->sinks, idx)))
+                    return; /* The sink was removed immediately after creation. */
+
+                if (!(device_iface = pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(idx)))) {
+                    device_iface = pa_dbusiface_device_new_sink(c, sink);
+                    pa_hashmap_put(c->sinks_by_index, PA_UINT32_TO_PTR(idx), device_iface);
+                    pa_hashmap_put(c->sinks_by_path, pa_dbusiface_device_get_path(device_iface), device_iface);
+                }
+
+                object_path = pa_dbusiface_device_get_path(device_iface);
+
+                pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                               PA_DBUS_CORE_INTERFACE,
+                                                               signals[SIGNAL_NEW_SINK].name)));
+                pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+                pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+                dbus_message_unref(signal);
+                signal = NULL;
+
+                if (c->fallback_sink && pa_streq(c->fallback_sink->name, sink->name)) {
+                    /* We have got default sink change event, but at that point
+                     * the D-Bus sink object wasn't created yet. Now that the
+                     * object is created, let's send the fallback sink change
+                     * signal. */
+                    pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                                   PA_DBUS_CORE_INTERFACE,
+                                                                   signals[SIGNAL_FALLBACK_SINK_UPDATED].name)));
+                    pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+                    pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+                    dbus_message_unref(signal);
+                    signal = NULL;
+                }
+
+            } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+                if (!(device_iface = pa_hashmap_remove(c->sinks_by_index, PA_UINT32_TO_PTR(idx))))
+                    return;
+
+                object_path = pa_dbusiface_device_get_path(device_iface);
+                pa_assert_se(pa_hashmap_remove(c->sinks_by_path, object_path));
+
+                pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                               PA_DBUS_CORE_INTERFACE,
+                                                               signals[SIGNAL_SINK_REMOVED].name)));
+                pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+                pa_dbusiface_device_free(device_iface);
+            }
+            break;
+
+        case PA_SUBSCRIPTION_EVENT_SOURCE:
+            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+                pa_source *source = pa_idxset_get_by_index(core->sources, idx);
+
+                if (!(source = pa_idxset_get_by_index(core->sources, idx)))
+                    return; /* The source was removed immediately after creation. */
+
+                if (!(device_iface = pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(idx)))) {
+                    device_iface = pa_dbusiface_device_new_source(c, source);
+                    pa_hashmap_put(c->sources_by_index, PA_UINT32_TO_PTR(idx), device_iface);
+                    pa_hashmap_put(c->sources_by_path, pa_dbusiface_device_get_path(device_iface), device_iface);
+                }
+
+                object_path = pa_dbusiface_device_get_path(device_iface);
+
+                pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                               PA_DBUS_CORE_INTERFACE,
+                                                               signals[SIGNAL_NEW_SOURCE].name)));
+                pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+                pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+                dbus_message_unref(signal);
+                signal = NULL;
+
+                if (c->fallback_source && pa_streq(c->fallback_source->name, source->name)) {
+                    /* We have got default source change event, but at that
+                     * point the D-Bus source object wasn't created yet. Now
+                     * that the object is created, let's send the fallback
+                     * source change signal. */
+                    pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                                   PA_DBUS_CORE_INTERFACE,
+                                                                   signals[SIGNAL_FALLBACK_SOURCE_UPDATED].name)));
+                    pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+                    pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+                    dbus_message_unref(signal);
+                    signal = NULL;
+                }
+
+            } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+                if (!(device_iface = pa_hashmap_remove(c->sources_by_index, PA_UINT32_TO_PTR(idx))))
+                    return;
+
+                object_path = pa_dbusiface_device_get_path(device_iface);
+                pa_assert_se(pa_hashmap_remove(c->sources_by_path, object_path));
+
+                pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                               PA_DBUS_CORE_INTERFACE,
+                                                               signals[SIGNAL_SOURCE_REMOVED].name)));
+                pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+                pa_dbusiface_device_free(device_iface);
+            }
+            break;
+
+        case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
+            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+                pa_sink_input *sink_input = NULL;
+
+                if (!(sink_input = pa_idxset_get_by_index(core->sink_inputs, idx)))
+                    return; /* The sink input was removed immediately after creation. */
+
+                if (!(stream_iface = pa_hashmap_get(c->playback_streams, PA_UINT32_TO_PTR(idx)))) {
+                    stream_iface = pa_dbusiface_stream_new_playback(c, sink_input);
+                    pa_hashmap_put(c->playback_streams, PA_UINT32_TO_PTR(idx), stream_iface);
+                }
+
+                object_path = pa_dbusiface_stream_get_path(stream_iface);
+
+                pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                               PA_DBUS_CORE_INTERFACE,
+                                                               signals[SIGNAL_NEW_PLAYBACK_STREAM].name)));
+                pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+            } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+                if (!(stream_iface = pa_hashmap_remove(c->playback_streams, PA_UINT32_TO_PTR(idx))))
+                    return;
+
+                object_path = pa_dbusiface_stream_get_path(stream_iface);
+
+                pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                               PA_DBUS_CORE_INTERFACE,
+                                                               signals[SIGNAL_PLAYBACK_STREAM_REMOVED].name)));
+                pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+                pa_dbusiface_stream_free(stream_iface);
+            }
+            break;
+
+        case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
+            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+                pa_source_output *source_output = NULL;
+
+                if (!(source_output = pa_idxset_get_by_index(core->source_outputs, idx)))
+                    return; /* The source output was removed immediately after creation. */
+
+                if (!(stream_iface = pa_hashmap_get(c->record_streams, PA_UINT32_TO_PTR(idx)))) {
+                    stream_iface = pa_dbusiface_stream_new_record(c, source_output);
+                    pa_hashmap_put(c->record_streams, PA_UINT32_TO_PTR(idx), stream_iface);
+                }
+
+                object_path = pa_dbusiface_stream_get_path(stream_iface);
+
+                pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                               PA_DBUS_CORE_INTERFACE,
+                                                               signals[SIGNAL_NEW_RECORD_STREAM].name)));
+                pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+            } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+                if (!(stream_iface = pa_hashmap_remove(c->record_streams, PA_UINT32_TO_PTR(idx))))
+                    return;
+
+                object_path = pa_dbusiface_stream_get_path(stream_iface);
+
+                pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                               PA_DBUS_CORE_INTERFACE,
+                                                               signals[SIGNAL_RECORD_STREAM_REMOVED].name)));
+                pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+                pa_dbusiface_stream_free(stream_iface);
+            }
+            break;
+
+        case PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE:
+            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+                pa_scache_entry *sample = NULL;
+
+                if (!(sample = pa_idxset_get_by_index(core->scache, idx)))
+                    return; /* The sample was removed immediately after creation. */
+
+                if (!(sample_iface = pa_hashmap_get(c->samples, PA_UINT32_TO_PTR(idx)))) {
+                    sample_iface = pa_dbusiface_sample_new(c, sample);
+                    pa_hashmap_put(c->samples, PA_UINT32_TO_PTR(idx), sample_iface);
+                }
+
+                object_path = pa_dbusiface_sample_get_path(sample_iface);
+
+                pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                               PA_DBUS_CORE_INTERFACE,
+                                                               signals[SIGNAL_NEW_SAMPLE].name)));
+                pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+            } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+                if (!(sample_iface = pa_hashmap_remove(c->samples, PA_UINT32_TO_PTR(idx))))
+                    return;
+
+                object_path = pa_dbusiface_sample_get_path(sample_iface);
+
+                pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                               PA_DBUS_CORE_INTERFACE,
+                                                               signals[SIGNAL_SAMPLE_REMOVED].name)));
+                pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+                pa_dbusiface_sample_free(sample_iface);
+            }
+            break;
+
+        case PA_SUBSCRIPTION_EVENT_MODULE:
+            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+                pa_module *module = NULL;
+
+                if (!(module = pa_idxset_get_by_index(core->modules, idx)))
+                    return; /* The module was removed immediately after creation. */
+
+                if (!(module_iface = pa_hashmap_get(c->modules, PA_UINT32_TO_PTR(idx)))) {
+                    module_iface = pa_dbusiface_module_new(module);
+                    pa_hashmap_put(c->modules, PA_UINT32_TO_PTR(idx), module_iface);
+                }
+
+                object_path = pa_dbusiface_module_get_path(module_iface);
+
+                pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                               PA_DBUS_CORE_INTERFACE,
+                                                               signals[SIGNAL_NEW_MODULE].name)));
+                pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+            } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+                if (!(module_iface = pa_hashmap_remove(c->modules, PA_UINT32_TO_PTR(idx))))
+                    return;
+
+                object_path = pa_dbusiface_module_get_path(module_iface);
+
+                pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                               PA_DBUS_CORE_INTERFACE,
+                                                               signals[SIGNAL_MODULE_REMOVED].name)));
+                pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+                pa_dbusiface_module_free(module_iface);
+            }
+            break;
+
+        case PA_SUBSCRIPTION_EVENT_CLIENT:
+            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+                pa_client *client = NULL;
+
+                if (!(client = pa_idxset_get_by_index(core->clients, idx)))
+                    return; /* The client was removed immediately after creation. */
+
+                if (!(client_iface = pa_hashmap_get(c->clients, PA_UINT32_TO_PTR(idx)))) {
+                    client_iface = pa_dbusiface_client_new(c, client);
+                    pa_hashmap_put(c->clients, PA_UINT32_TO_PTR(idx), client_iface);
+                }
+
+                object_path = pa_dbusiface_client_get_path(client_iface);
+
+                pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                               PA_DBUS_CORE_INTERFACE,
+                                                               signals[SIGNAL_NEW_CLIENT].name)));
+                pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+            } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+                if (!(client_iface = pa_hashmap_remove(c->clients, PA_UINT32_TO_PTR(idx))))
+                    return;
+
+                object_path = pa_dbusiface_client_get_path(client_iface);
+
+                pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                               PA_DBUS_CORE_INTERFACE,
+                                                               signals[SIGNAL_CLIENT_REMOVED].name)));
+                pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+                pa_dbusiface_client_free(client_iface);
+            }
+            break;
+    }
+
+    if (signal) {
+        pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+        dbus_message_unref(signal);
+    }
+}
+
+static pa_hook_result_t extension_registered_cb(void *hook_data, void *call_data, void *slot_data) {
+    pa_dbusiface_core *c = slot_data;
+    const char *ext_name = call_data;
+    DBusMessage *signal = NULL;
+
+    pa_assert(c);
+    pa_assert(ext_name);
+
+    pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                   PA_DBUS_CORE_INTERFACE,
+                                                   signals[SIGNAL_NEW_EXTENSION].name)));
+    pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_STRING, &ext_name, DBUS_TYPE_INVALID));
+
+    pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+    dbus_message_unref(signal);
+
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t extension_unregistered_cb(void *hook_data, void *call_data, void *slot_data) {
+    pa_dbusiface_core *c = slot_data;
+    const char *ext_name = call_data;
+    DBusMessage *signal = NULL;
+
+    pa_assert(c);
+    pa_assert(ext_name);
+
+    pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH,
+                                                   PA_DBUS_CORE_INTERFACE,
+                                                   signals[SIGNAL_EXTENSION_REMOVED].name)));
+    pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_STRING, &ext_name, DBUS_TYPE_INVALID));
+
+    pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
+    dbus_message_unref(signal);
+
+    return PA_HOOK_OK;
+}
+
+pa_dbusiface_core *pa_dbusiface_core_new(pa_core *core) {
+    pa_dbusiface_core *c;
+    pa_card *card;
+    pa_sink *sink;
+    pa_source *source;
+    pa_dbusiface_device *device;
+    pa_sink_input *sink_input;
+    pa_source_output *source_output;
+    pa_scache_entry *sample;
+    pa_module *module;
+    pa_client *client;
+    uint32_t idx;
+
+    pa_assert(core);
+
+    c = pa_xnew(pa_dbusiface_core, 1);
+    c->core = pa_core_ref(core);
+    c->subscription = pa_subscription_new(core, PA_SUBSCRIPTION_MASK_ALL, subscription_cb, c);
+    c->dbus_protocol = pa_dbus_protocol_get(core);
+    c->cards = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+    c->sinks_by_index = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+    c->sinks_by_path = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+    c->sources_by_index = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+    c->sources_by_path = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+    c->playback_streams = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+    c->record_streams = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+    c->samples = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+    c->modules = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+    c->clients = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+    c->fallback_sink = pa_namereg_get_default_sink(core);
+    c->fallback_source = pa_namereg_get_default_source(core);
+    c->extension_registered_slot = pa_dbus_protocol_hook_connect(c->dbus_protocol,
+                                                                 PA_DBUS_PROTOCOL_HOOK_EXTENSION_REGISTERED,
+                                                                 PA_HOOK_NORMAL,
+                                                                 extension_registered_cb,
+                                                                 c);
+    c->extension_unregistered_slot = pa_dbus_protocol_hook_connect(c->dbus_protocol,
+                                                                   PA_DBUS_PROTOCOL_HOOK_EXTENSION_UNREGISTERED,
+                                                                   PA_HOOK_NORMAL,
+                                                                   extension_unregistered_cb,
+                                                                   c);
+    c->memstats = pa_dbusiface_memstats_new(c, core);
+
+    if (c->fallback_sink)
+        pa_sink_ref(c->fallback_sink);
+    if (c->fallback_source)
+        pa_source_ref(c->fallback_source);
+
+    PA_IDXSET_FOREACH(card, core->cards, idx)
+        pa_hashmap_put(c->cards, PA_UINT32_TO_PTR(idx), pa_dbusiface_card_new(c, card));
+
+    PA_IDXSET_FOREACH(sink, core->sinks, idx) {
+        device = pa_dbusiface_device_new_sink(c, sink);
+        pa_hashmap_put(c->sinks_by_index, PA_UINT32_TO_PTR(idx), device);
+        pa_hashmap_put(c->sinks_by_path, pa_dbusiface_device_get_path(device), device);
+    }
+
+    PA_IDXSET_FOREACH(source, core->sources, idx) {
+        device = pa_dbusiface_device_new_source(c, source);
+        pa_hashmap_put(c->sources_by_index, PA_UINT32_TO_PTR(idx), device);
+        pa_hashmap_put(c->sources_by_path, pa_dbusiface_device_get_path(device), device);
+    }
+
+    PA_IDXSET_FOREACH(sink_input, core->sink_inputs, idx)
+        pa_hashmap_put(c->playback_streams, PA_UINT32_TO_PTR(idx), pa_dbusiface_stream_new_playback(c, sink_input));
+
+    PA_IDXSET_FOREACH(source_output, core->source_outputs, idx)
+        pa_hashmap_put(c->record_streams, PA_UINT32_TO_PTR(idx), pa_dbusiface_stream_new_record(c, source_output));
+
+    PA_IDXSET_FOREACH(sample, core->scache, idx)
+        pa_hashmap_put(c->samples, PA_UINT32_TO_PTR(idx), pa_dbusiface_sample_new(c, sample));
+
+    PA_IDXSET_FOREACH(module, core->modules, idx)
+        pa_hashmap_put(c->modules, PA_UINT32_TO_PTR(idx), pa_dbusiface_module_new(module));
+
+    PA_IDXSET_FOREACH(client, core->clients, idx)
+        pa_hashmap_put(c->clients, PA_UINT32_TO_PTR(idx), pa_dbusiface_client_new(c, client));
+
+    pa_assert_se(pa_dbus_protocol_add_interface(c->dbus_protocol, PA_DBUS_CORE_OBJECT_PATH, &core_interface_info, c) >= 0);
+
+    return c;
+}
+
+static void free_card_cb(void *p, void *userdata) {
+    pa_dbusiface_card *c = p;
+
+    pa_assert(c);
+
+    pa_dbusiface_card_free(c);
+}
+
+static void free_device_cb(void *p, void *userdata) {
+    pa_dbusiface_device *d = p;
+
+    pa_assert(d);
+
+    pa_dbusiface_device_free(d);
+}
+
+static void free_stream_cb(void *p, void *userdata) {
+    pa_dbusiface_stream *s = p;
+
+    pa_assert(s);
+
+    pa_dbusiface_stream_free(s);
+}
+
+static void free_sample_cb(void *p, void *userdata) {
+    pa_dbusiface_sample *s = p;
+
+    pa_assert(s);
+
+    pa_dbusiface_sample_free(s);
+}
+
+static void free_module_cb(void *p, void *userdata) {
+    pa_dbusiface_module *m = p;
+
+    pa_assert(m);
+
+    pa_dbusiface_module_free(m);
+}
+
+static void free_client_cb(void *p, void *userdata) {
+    pa_dbusiface_client *c = p;
+
+    pa_assert(c);
+
+    pa_dbusiface_client_free(c);
+}
+
+void pa_dbusiface_core_free(pa_dbusiface_core *c) {
+    pa_assert(c);
+
+    pa_assert_se(pa_dbus_protocol_remove_interface(c->dbus_protocol, PA_DBUS_CORE_OBJECT_PATH, core_interface_info.name) >= 0);
+
+    pa_subscription_free(c->subscription);
+    pa_hashmap_free(c->cards, free_card_cb, NULL);
+    pa_hashmap_free(c->sinks_by_index, free_device_cb, NULL);
+    pa_hashmap_free(c->sinks_by_path, NULL, NULL);
+    pa_hashmap_free(c->sources_by_index, free_device_cb, NULL);
+    pa_hashmap_free(c->sources_by_path, NULL, NULL);
+    pa_hashmap_free(c->playback_streams, free_stream_cb, NULL);
+    pa_hashmap_free(c->record_streams, free_stream_cb, NULL);
+    pa_hashmap_free(c->samples, free_sample_cb, NULL);
+    pa_hashmap_free(c->modules, free_module_cb, NULL);
+    pa_hashmap_free(c->clients, free_client_cb, NULL);
+    pa_hook_slot_free(c->extension_registered_slot);
+    pa_hook_slot_free(c->extension_unregistered_slot);
+    pa_dbusiface_memstats_free(c->memstats);
+
+    if (c->fallback_sink)
+        pa_sink_unref(c->fallback_sink);
+    if (c->fallback_source)
+        pa_source_unref(c->fallback_source);
+
+    pa_dbus_protocol_unref(c->dbus_protocol);
+    pa_core_unref(c->core);
+
+    pa_xfree(c);
+}
+
+const char *pa_dbusiface_core_get_card_path(pa_dbusiface_core *c, const pa_card *card) {
+    pa_assert(c);
+    pa_assert(card);
+
+    return pa_dbusiface_card_get_path(pa_hashmap_get(c->cards, PA_UINT32_TO_PTR(card->index)));
+}
+
+const char *pa_dbusiface_core_get_sink_path(pa_dbusiface_core *c, const pa_sink *sink) {
+    pa_assert(c);
+    pa_assert(sink);
+
+    return pa_dbusiface_device_get_path(pa_hashmap_get(c->sinks_by_index, PA_UINT32_TO_PTR(sink->index)));
+}
+
+const char *pa_dbusiface_core_get_source_path(pa_dbusiface_core *c, const pa_source *source) {
+    pa_assert(c);
+    pa_assert(source);
+
+    return pa_dbusiface_device_get_path(pa_hashmap_get(c->sources_by_index, PA_UINT32_TO_PTR(source->index)));
+}
+
+const char *pa_dbusiface_core_get_playback_stream_path(pa_dbusiface_core *c, const pa_sink_input *sink_input) {
+    pa_assert(c);
+    pa_assert(sink_input);
+
+    return pa_dbusiface_stream_get_path(pa_hashmap_get(c->playback_streams, PA_UINT32_TO_PTR(sink_input->index)));
+}
+
+const char *pa_dbusiface_core_get_record_stream_path(pa_dbusiface_core *c, const pa_source_output *source_output) {
+    pa_assert(c);
+    pa_assert(source_output);
+
+    return pa_dbusiface_stream_get_path(pa_hashmap_get(c->record_streams, PA_UINT32_TO_PTR(source_output->index)));
+}
+
+const char *pa_dbusiface_core_get_module_path(pa_dbusiface_core *c, const pa_module *module) {
+    pa_assert(c);
+    pa_assert(module);
+
+    return pa_dbusiface_module_get_path(pa_hashmap_get(c->modules, PA_UINT32_TO_PTR(module->index)));
+}
+
+const char *pa_dbusiface_core_get_client_path(pa_dbusiface_core *c, const pa_client *client) {
+    pa_assert(c);
+    pa_assert(client);
+
+    return pa_dbusiface_client_get_path(pa_hashmap_get(c->clients, PA_UINT32_TO_PTR(client->index)));
+}
+
+pa_sink *pa_dbusiface_core_get_sink(pa_dbusiface_core *c, const char *object_path) {
+    pa_dbusiface_device *device = NULL;
+
+    pa_assert(c);
+    pa_assert(object_path);
+
+    device = pa_hashmap_get(c->sinks_by_path, object_path);
+
+    if (device)
+        return pa_dbusiface_device_get_sink(device);
+    else
+        return NULL;
+}
+
+pa_source *pa_dbusiface_core_get_source(pa_dbusiface_core *c, const char *object_path) {
+    pa_dbusiface_device *device = NULL;
+
+    pa_assert(c);
+    pa_assert(object_path);
+
+    device = pa_hashmap_get(c->sources_by_path, object_path);
+
+    if (device)
+        return pa_dbusiface_device_get_source(device);
+    else
+        return NULL;
+}
diff --git a/src/modules/dbus/iface-core.h b/src/modules/dbus/iface-core.h
new file mode 100644 (file)
index 0000000..900b6d1
--- /dev/null
@@ -0,0 +1,52 @@
+#ifndef foodbusifacecorehfoo
+#define foodbusifacecorehfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+/* This object implements the D-Bus interface org.PulseAudio.Core1.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the Core interface
+ * documentation.
+ */
+
+#include <pulsecore/core.h>
+
+typedef struct pa_dbusiface_core pa_dbusiface_core;
+
+pa_dbusiface_core *pa_dbusiface_core_new(pa_core *core);
+void pa_dbusiface_core_free(pa_dbusiface_core *c);
+
+const char *pa_dbusiface_core_get_card_path(pa_dbusiface_core *c, const pa_card *card);
+const char *pa_dbusiface_core_get_sink_path(pa_dbusiface_core *c, const pa_sink *sink);
+const char *pa_dbusiface_core_get_source_path(pa_dbusiface_core *c, const pa_source *source);
+const char *pa_dbusiface_core_get_playback_stream_path(pa_dbusiface_core *c, const pa_sink_input *sink_input);
+const char *pa_dbusiface_core_get_record_stream_path(pa_dbusiface_core *c, const pa_source_output *source_output);
+const char *pa_dbusiface_core_get_module_path(pa_dbusiface_core *c, const pa_module *module);
+const char *pa_dbusiface_core_get_client_path(pa_dbusiface_core *c, const pa_client *client);
+
+/* Returns NULL if there's no sink with the given path. */
+pa_sink *pa_dbusiface_core_get_sink(pa_dbusiface_core *c, const char *object_path);
+
+/* Returns NULL if there's no source with the given path. */
+pa_source *pa_dbusiface_core_get_source(pa_dbusiface_core *c, const char *object_path);
+
+#endif
diff --git a/src/modules/dbus/iface-device-port.c b/src/modules/dbus/iface-device-port.c
new file mode 100644 (file)
index 0000000..d403b6a
--- /dev/null
@@ -0,0 +1,190 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <dbus/dbus.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+
+#include "iface-device-port.h"
+
+#define OBJECT_NAME "port"
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_description(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_priority(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+struct pa_dbusiface_device_port {
+    uint32_t index;
+    pa_device_port *port;
+    char *path;
+    pa_dbus_protocol *dbus_protocol;
+};
+
+enum property_handler_index {
+    PROPERTY_HANDLER_INDEX,
+    PROPERTY_HANDLER_NAME,
+    PROPERTY_HANDLER_DESCRIPTION,
+    PROPERTY_HANDLER_PRIORITY,
+    PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+    [PROPERTY_HANDLER_INDEX]       = { .property_name = "Index",       .type = "u", .get_cb = handle_get_index,       .set_cb = NULL },
+    [PROPERTY_HANDLER_NAME]        = { .property_name = "Name",        .type = "s", .get_cb = handle_get_name,        .set_cb = NULL },
+    [PROPERTY_HANDLER_DESCRIPTION] = { .property_name = "Description", .type = "s", .get_cb = handle_get_description, .set_cb = NULL },
+    [PROPERTY_HANDLER_PRIORITY]    = { .property_name = "Priority",    .type = "u", .get_cb = handle_get_priority,    .set_cb = NULL },
+};
+
+static pa_dbus_interface_info port_interface_info = {
+    .name = PA_DBUSIFACE_DEVICE_PORT_INTERFACE,
+    .method_handlers = NULL,
+    .n_method_handlers = 0,
+    .property_handlers = property_handlers,
+    .n_property_handlers = PROPERTY_HANDLER_MAX,
+    .get_all_properties_cb = handle_get_all,
+    .signals = NULL,
+    .n_signals = 0
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device_port *p = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(p);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &p->index);
+}
+
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device_port *p = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(p);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &p->port->name);
+}
+
+static void handle_get_description(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device_port *p = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(p);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &p->port->description);
+}
+
+static void handle_get_priority(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device_port *p = userdata;
+    dbus_uint32_t priority = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(p);
+
+    priority = p->port->priority;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &priority);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device_port *p = userdata;
+    DBusMessage *reply = NULL;
+    DBusMessageIter msg_iter;
+    DBusMessageIter dict_iter;
+    dbus_uint32_t priority = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(p);
+
+    priority = p->port->priority;
+
+    pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+    dbus_message_iter_init_append(reply, &msg_iter);
+    pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &p->index);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &p->port->name);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DESCRIPTION].property_name, DBUS_TYPE_STRING, &p->port->description);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PRIORITY].property_name, DBUS_TYPE_UINT32, &priority);
+
+    pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+    pa_assert_se(dbus_connection_send(conn, reply, NULL));
+    dbus_message_unref(reply);
+}
+
+pa_dbusiface_device_port *pa_dbusiface_device_port_new(
+        pa_dbusiface_device *device,
+        pa_core *core,
+        pa_device_port *port,
+        uint32_t idx) {
+    pa_dbusiface_device_port *p = NULL;
+
+    pa_assert(device);
+    pa_assert(core);
+    pa_assert(port);
+
+    p = pa_xnew(pa_dbusiface_device_port, 1);
+    p->index = idx;
+    p->port = port;
+    p->path = pa_sprintf_malloc("%s/%s%u", pa_dbusiface_device_get_path(device), OBJECT_NAME, idx);
+    p->dbus_protocol = pa_dbus_protocol_get(core);
+
+    pa_assert_se(pa_dbus_protocol_add_interface(p->dbus_protocol, p->path, &port_interface_info, p) >= 0);
+
+    return p;
+}
+
+void pa_dbusiface_device_port_free(pa_dbusiface_device_port *p) {
+    pa_assert(p);
+
+    pa_assert_se(pa_dbus_protocol_remove_interface(p->dbus_protocol, p->path, port_interface_info.name) >= 0);
+
+    pa_dbus_protocol_unref(p->dbus_protocol);
+
+    pa_xfree(p->path);
+    pa_xfree(p);
+}
+
+const char *pa_dbusiface_device_port_get_path(pa_dbusiface_device_port *p) {
+    pa_assert(p);
+
+    return p->path;
+}
+
+const char *pa_dbusiface_device_port_get_name(pa_dbusiface_device_port *p) {
+    pa_assert(p);
+
+    return p->port->name;
+}
diff --git a/src/modules/dbus/iface-device-port.h b/src/modules/dbus/iface-device-port.h
new file mode 100644 (file)
index 0000000..0461e2f
--- /dev/null
@@ -0,0 +1,50 @@
+#ifndef foodbusifacedeviceporthfoo
+#define foodbusifacedeviceporthfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+/* This object implements the D-Bus interface org.PulseAudio.Core1.DevicePort.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the DevicePort interface
+ * documentation.
+ */
+
+#include <pulsecore/protocol-dbus.h>
+#include <pulsecore/sink.h>
+
+#include "iface-device.h"
+
+#define PA_DBUSIFACE_DEVICE_PORT_INTERFACE PA_DBUS_CORE_INTERFACE ".DevicePort"
+
+typedef struct pa_dbusiface_device_port pa_dbusiface_device_port;
+
+pa_dbusiface_device_port *pa_dbusiface_device_port_new(
+        pa_dbusiface_device *device,
+        pa_core *core,
+        pa_device_port *port,
+        uint32_t idx);
+void pa_dbusiface_device_port_free(pa_dbusiface_device_port *p);
+
+const char *pa_dbusiface_device_port_get_path(pa_dbusiface_device_port *p);
+const char *pa_dbusiface_device_port_get_name(pa_dbusiface_device_port *p);
+
+#endif
diff --git a/src/modules/dbus/iface-device.c b/src/modules/dbus/iface-device.c
new file mode 100644 (file)
index 0000000..2752511
--- /dev/null
@@ -0,0 +1,1316 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-device-port.h"
+
+#include "iface-device.h"
+
+#define SINK_OBJECT_NAME "sink"
+#define SOURCE_OBJECT_NAME "source"
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_card(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_has_flat_volume(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_has_convertible_to_decibel_volume(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_base_volume(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_volume_steps(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_is_muted(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_is_muted(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_has_hardware_volume(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_has_hardware_mute(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_configured_latency(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_has_dynamic_latency(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_latency(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_is_hardware_device(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_is_network_device(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_state(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_ports(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_active_port(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_active_port(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_suspend(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_port_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_sink_get_monitor_source(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_sink_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_source_get_monitor_of_sink(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_source_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+enum device_type {
+    DEVICE_TYPE_SINK,
+    DEVICE_TYPE_SOURCE
+};
+
+struct pa_dbusiface_device {
+    pa_dbusiface_core *core;
+
+    union {
+        pa_sink *sink;
+        pa_source *source;
+    };
+    enum device_type type;
+    char *path;
+    pa_cvolume volume;
+    pa_bool_t is_muted;
+    union {
+        pa_sink_state_t sink_state;
+        pa_source_state_t source_state;
+    };
+    pa_hashmap *ports;
+    uint32_t next_port_index;
+    pa_device_port *active_port;
+    pa_proplist *proplist;
+
+    pa_dbus_protocol *dbus_protocol;
+    pa_subscription *subscription;
+};
+
+enum property_handler_index {
+    PROPERTY_HANDLER_INDEX,
+    PROPERTY_HANDLER_NAME,
+    PROPERTY_HANDLER_DRIVER,
+    PROPERTY_HANDLER_OWNER_MODULE,
+    PROPERTY_HANDLER_CARD,
+    PROPERTY_HANDLER_SAMPLE_FORMAT,
+    PROPERTY_HANDLER_SAMPLE_RATE,
+    PROPERTY_HANDLER_CHANNELS,
+    PROPERTY_HANDLER_VOLUME,
+    PROPERTY_HANDLER_HAS_FLAT_VOLUME,
+    PROPERTY_HANDLER_HAS_CONVERTIBLE_TO_DECIBEL_VOLUME,
+    PROPERTY_HANDLER_BASE_VOLUME,
+    PROPERTY_HANDLER_VOLUME_STEPS,
+    PROPERTY_HANDLER_IS_MUTED,
+    PROPERTY_HANDLER_HAS_HARDWARE_VOLUME,
+    PROPERTY_HANDLER_HAS_HARDWARE_MUTE,
+    PROPERTY_HANDLER_CONFIGURED_LATENCY,
+    PROPERTY_HANDLER_HAS_DYNAMIC_LATENCY,
+    PROPERTY_HANDLER_LATENCY,
+    PROPERTY_HANDLER_IS_HARDWARE_DEVICE,
+    PROPERTY_HANDLER_IS_NETWORK_DEVICE,
+    PROPERTY_HANDLER_STATE,
+    PROPERTY_HANDLER_PORTS,
+    PROPERTY_HANDLER_ACTIVE_PORT,
+    PROPERTY_HANDLER_PROPERTY_LIST,
+    PROPERTY_HANDLER_MAX
+};
+
+enum sink_property_handler_index {
+    SINK_PROPERTY_HANDLER_MONITOR_SOURCE,
+    SINK_PROPERTY_HANDLER_MAX
+};
+
+enum source_property_handler_index {
+    SOURCE_PROPERTY_HANDLER_MONITOR_OF_SINK,
+    SOURCE_PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+    [PROPERTY_HANDLER_INDEX]                             = { .property_name = "Index",                         .type = "u",      .get_cb = handle_get_index,                             .set_cb = NULL },
+    [PROPERTY_HANDLER_NAME]                              = { .property_name = "Name",                          .type = "s",      .get_cb = handle_get_name,                              .set_cb = NULL },
+    [PROPERTY_HANDLER_DRIVER]                            = { .property_name = "Driver",                        .type = "s",      .get_cb = handle_get_driver,                            .set_cb = NULL },
+    [PROPERTY_HANDLER_OWNER_MODULE]                      = { .property_name = "OwnerModule",                   .type = "o",      .get_cb = handle_get_owner_module,                      .set_cb = NULL },
+    [PROPERTY_HANDLER_CARD]                              = { .property_name = "Card",                          .type = "o",      .get_cb = handle_get_card,                              .set_cb = NULL },
+    [PROPERTY_HANDLER_SAMPLE_FORMAT]                     = { .property_name = "SampleFormat",                  .type = "u",      .get_cb = handle_get_sample_format,                     .set_cb = NULL },
+    [PROPERTY_HANDLER_SAMPLE_RATE]                       = { .property_name = "SampleRate",                    .type = "u",      .get_cb = handle_get_sample_rate,                       .set_cb = NULL },
+    [PROPERTY_HANDLER_CHANNELS]                          = { .property_name = "Channels",                      .type = "au",     .get_cb = handle_get_channels,                          .set_cb = NULL },
+    [PROPERTY_HANDLER_VOLUME]                            = { .property_name = "Volume",                        .type = "au",     .get_cb = handle_get_volume,                            .set_cb = handle_set_volume },
+    [PROPERTY_HANDLER_HAS_FLAT_VOLUME]                   = { .property_name = "HasFlatVolume",                 .type = "b",      .get_cb = handle_get_has_flat_volume,                   .set_cb = NULL },
+    [PROPERTY_HANDLER_HAS_CONVERTIBLE_TO_DECIBEL_VOLUME] = { .property_name = "HasConvertibleToDecibelVolume", .type = "b",      .get_cb = handle_get_has_convertible_to_decibel_volume, .set_cb = NULL },
+    [PROPERTY_HANDLER_BASE_VOLUME]                       = { .property_name = "BaseVolume",                    .type = "u",      .get_cb = handle_get_base_volume,                       .set_cb = NULL },
+    [PROPERTY_HANDLER_VOLUME_STEPS]                      = { .property_name = "VolumeSteps",                   .type = "u",      .get_cb = handle_get_volume_steps,                      .set_cb = NULL },
+    [PROPERTY_HANDLER_IS_MUTED]                          = { .property_name = "IsMuted",                       .type = "b",      .get_cb = handle_get_is_muted,                          .set_cb = handle_set_is_muted },
+    [PROPERTY_HANDLER_HAS_HARDWARE_VOLUME]               = { .property_name = "HasHardwareVolume",             .type = "b",      .get_cb = handle_get_has_hardware_volume,               .set_cb = NULL },
+    [PROPERTY_HANDLER_HAS_HARDWARE_MUTE]                 = { .property_name = "HasHardwareMute",               .type = "b",      .get_cb = handle_get_has_hardware_mute,                 .set_cb = NULL },
+    [PROPERTY_HANDLER_CONFIGURED_LATENCY]                = { .property_name = "ConfiguredLatency",             .type = "t",      .get_cb = handle_get_configured_latency,                .set_cb = NULL },
+    [PROPERTY_HANDLER_HAS_DYNAMIC_LATENCY]               = { .property_name = "HasDynamicLatency",             .type = "b",      .get_cb = handle_get_has_dynamic_latency,               .set_cb = NULL },
+    [PROPERTY_HANDLER_LATENCY]                           = { .property_name = "Latency",                       .type = "t",      .get_cb = handle_get_latency,                           .set_cb = NULL },
+    [PROPERTY_HANDLER_IS_HARDWARE_DEVICE]                = { .property_name = "IsHardwareDevice",              .type = "b",      .get_cb = handle_get_is_hardware_device,                .set_cb = NULL },
+    [PROPERTY_HANDLER_IS_NETWORK_DEVICE]                 = { .property_name = "IsNetworkDevice",               .type = "b",      .get_cb = handle_get_is_network_device,                 .set_cb = NULL },
+    [PROPERTY_HANDLER_STATE]                             = { .property_name = "State",                         .type = "u",      .get_cb = handle_get_state,                             .set_cb = NULL },
+    [PROPERTY_HANDLER_PORTS]                             = { .property_name = "Ports",                         .type = "ao",     .get_cb = handle_get_ports,                             .set_cb = NULL },
+    [PROPERTY_HANDLER_ACTIVE_PORT]                       = { .property_name = "ActivePort",                    .type = "o",      .get_cb = handle_get_active_port,                       .set_cb = handle_set_active_port },
+    [PROPERTY_HANDLER_PROPERTY_LIST]                     = { .property_name = "PropertyList",                  .type = "a{say}", .get_cb = handle_get_property_list,                     .set_cb = NULL }
+};
+
+static pa_dbus_property_handler sink_property_handlers[SINK_PROPERTY_HANDLER_MAX] = {
+    [SINK_PROPERTY_HANDLER_MONITOR_SOURCE] = { .property_name = "MonitorSource", .type = "o", .get_cb = handle_sink_get_monitor_source, .set_cb = NULL }
+};
+
+static pa_dbus_property_handler source_property_handlers[SOURCE_PROPERTY_HANDLER_MAX] = {
+    [SOURCE_PROPERTY_HANDLER_MONITOR_OF_SINK] = { .property_name = "MonitorOfSink", .type = "o", .get_cb = handle_source_get_monitor_of_sink, .set_cb = NULL }
+};
+
+enum method_handler_index {
+    METHOD_HANDLER_SUSPEND,
+    METHOD_HANDLER_GET_PORT_BY_NAME,
+    METHOD_HANDLER_MAX
+};
+
+static pa_dbus_arg_info suspend_args[] = { { "suspend", "b", "in" } };
+static pa_dbus_arg_info get_port_by_name_args[] = { { "name", "s", "in" }, { "port", "o", "out" } };
+
+static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
+    [METHOD_HANDLER_SUSPEND] = {
+        .method_name = "Suspend",
+        .arguments = suspend_args,
+        .n_arguments = sizeof(suspend_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_suspend },
+    [METHOD_HANDLER_GET_PORT_BY_NAME] = {
+        .method_name = "GetPortByName",
+        .arguments = get_port_by_name_args,
+        .n_arguments = sizeof(get_port_by_name_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_get_port_by_name }
+};
+
+enum signal_index {
+    SIGNAL_VOLUME_UPDATED,
+    SIGNAL_MUTE_UPDATED,
+    SIGNAL_STATE_UPDATED,
+    SIGNAL_ACTIVE_PORT_UPDATED,
+    SIGNAL_PROPERTY_LIST_UPDATED,
+    SIGNAL_MAX
+};
+
+static pa_dbus_arg_info volume_updated_args[]        = { { "volume",        "au",     NULL } };
+static pa_dbus_arg_info mute_updated_args[]          = { { "muted",         "b",      NULL } };
+static pa_dbus_arg_info state_updated_args[]         = { { "state",         "u",      NULL } };
+static pa_dbus_arg_info active_port_updated_args[]   = { { "port",          "o",      NULL } };
+static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } };
+
+static pa_dbus_signal_info signals[SIGNAL_MAX] = {
+    [SIGNAL_VOLUME_UPDATED]        = { .name = "VolumeUpdated",       .arguments = volume_updated_args,        .n_arguments = 1 },
+    [SIGNAL_MUTE_UPDATED]          = { .name = "MuteUpdated",         .arguments = mute_updated_args,          .n_arguments = 1 },
+    [SIGNAL_STATE_UPDATED]         = { .name = "StateUpdated",        .arguments = state_updated_args,         .n_arguments = 1 },
+    [SIGNAL_ACTIVE_PORT_UPDATED]   = { .name = "ActivePortUpdated",   .arguments = active_port_updated_args,   .n_arguments = 1 },
+    [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 }
+};
+
+static pa_dbus_interface_info device_interface_info = {
+    .name = PA_DBUSIFACE_DEVICE_INTERFACE,
+    .method_handlers = method_handlers,
+    .n_method_handlers = METHOD_HANDLER_MAX,
+    .property_handlers = property_handlers,
+    .n_property_handlers = PROPERTY_HANDLER_MAX,
+    .get_all_properties_cb = handle_get_all,
+    .signals = signals,
+    .n_signals = SIGNAL_MAX
+};
+
+static pa_dbus_interface_info sink_interface_info = {
+    .name = PA_DBUSIFACE_SINK_INTERFACE,
+    .method_handlers = NULL,
+    .n_method_handlers = 0,
+    .property_handlers = sink_property_handlers,
+    .n_property_handlers = SINK_PROPERTY_HANDLER_MAX,
+    .get_all_properties_cb = handle_sink_get_all,
+    .signals = NULL,
+    .n_signals = 0
+};
+
+static pa_dbus_interface_info source_interface_info = {
+    .name = PA_DBUSIFACE_SOURCE_INTERFACE,
+    .method_handlers = NULL,
+    .n_method_handlers = 0,
+    .property_handlers = source_property_handlers,
+    .n_property_handlers = SOURCE_PROPERTY_HANDLER_MAX,
+    .get_all_properties_cb = handle_source_get_all,
+    .signals = NULL,
+    .n_signals = 0
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    dbus_uint32_t idx = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    idx = (d->type == DEVICE_TYPE_SINK) ? d->sink->index : d->source->index;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx);
+}
+
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    const char *name = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    name = (d->type == DEVICE_TYPE_SINK) ? d->sink->name : d->source->name;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &name);
+}
+
+static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    const char *driver = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    driver = (d->type == DEVICE_TYPE_SINK) ? d->sink->driver : d->source->driver;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &driver);
+}
+
+static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    pa_module *owner_module = NULL;
+    const char *object_path = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    owner_module = (d->type == DEVICE_TYPE_SINK) ? d->sink->module : d->source->module;
+
+    if (!owner_module) {
+        if (d->type == DEVICE_TYPE_SINK)
+            pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                               "Sink %s doesn't have an owner module.", d->sink->name);
+        else
+            pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                               "Source %s doesn't have an owner module.", d->source->name);
+        return;
+    }
+
+    object_path = pa_dbusiface_core_get_module_path(d->core, owner_module);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+}
+
+static void handle_get_card(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    pa_card *card = NULL;
+    const char *object_path = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    card = (d->type == DEVICE_TYPE_SINK) ? d->sink->card : d->source->card;
+
+    if (!card) {
+        if (d->type == DEVICE_TYPE_SINK)
+            pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                               "Sink %s doesn't belong to any card.", d->sink->name);
+        else
+            pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                               "Source %s doesn't belong to any card.", d->source->name);
+        return;
+    }
+
+    object_path = pa_dbusiface_core_get_card_path(d->core, card);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+}
+
+static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    dbus_uint32_t sample_format = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    sample_format = (d->type == DEVICE_TYPE_SINK) ? d->sink->sample_spec.format : d->source->sample_spec.format;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_format);
+}
+
+static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    dbus_uint32_t sample_rate = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    sample_rate = (d->type == DEVICE_TYPE_SINK) ? d->sink->sample_spec.rate : d->source->sample_spec.rate;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_rate);
+}
+
+static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    pa_channel_map *channel_map = NULL;
+    dbus_uint32_t channels[PA_CHANNELS_MAX];
+    unsigned i = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    channel_map = (d->type == DEVICE_TYPE_SINK) ? &d->sink->channel_map : &d->source->channel_map;
+
+    for (i = 0; i < channel_map->channels; ++i)
+        channels[i] = channel_map->map[i];
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, channels, channel_map->channels);
+}
+
+static void handle_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    dbus_uint32_t volume[PA_CHANNELS_MAX];
+    unsigned i = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    for (i = 0; i < d->volume.channels; ++i)
+        volume[i] = d->volume.values[i];
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, volume, d->volume.channels);
+}
+
+static void handle_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    DBusMessageIter array_iter;
+    int device_channels = 0;
+    dbus_uint32_t *volume = NULL;
+    int n_volume_entries = 0;
+    pa_cvolume new_vol;
+    int i = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(iter);
+    pa_assert(d);
+
+    pa_cvolume_init(&new_vol);
+
+    device_channels = (d->type == DEVICE_TYPE_SINK) ? d->sink->channel_map.channels : d->source->channel_map.channels;
+
+    new_vol.channels = device_channels;
+
+    dbus_message_iter_recurse(iter, &array_iter);
+    dbus_message_iter_get_fixed_array(&array_iter, &volume, &n_volume_entries);
+
+    if (n_volume_entries != device_channels) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS,
+                           "Expected %u volume entries, got %i.", device_channels, n_volume_entries);
+        return;
+    }
+
+    for (i = 0; i < n_volume_entries; ++i) {
+        if (volume[i] > PA_VOLUME_MAX) {
+            pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Too large volume value: %u", volume[i]);
+            return;
+        }
+        new_vol.values[i] = volume[i];
+    }
+
+    if (d->type == DEVICE_TYPE_SINK)
+        pa_sink_set_volume(d->sink, &new_vol, TRUE, TRUE);
+    else
+        pa_source_set_volume(d->source, &new_vol, TRUE);
+
+    pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void handle_get_has_flat_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    dbus_bool_t has_flat_volume = FALSE;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    has_flat_volume = (d->type == DEVICE_TYPE_SINK) ? (d->sink->flags & PA_SINK_FLAT_VOLUME) : FALSE;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_flat_volume);
+}
+
+static void handle_get_has_convertible_to_decibel_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    dbus_bool_t has_convertible_to_decibel_volume = FALSE;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    has_convertible_to_decibel_volume = (d->type == DEVICE_TYPE_SINK)
+                                        ? (d->sink->flags & PA_SINK_DECIBEL_VOLUME)
+                                        : (d->source->flags & PA_SOURCE_DECIBEL_VOLUME);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_convertible_to_decibel_volume);
+}
+
+static void handle_get_base_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    dbus_uint32_t base_volume;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    base_volume = (d->type == DEVICE_TYPE_SINK) ? d->sink->base_volume : d->source->base_volume;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &base_volume);
+}
+
+static void handle_get_volume_steps(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    dbus_uint32_t volume_steps;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    volume_steps = (d->type == DEVICE_TYPE_SINK) ? d->sink->n_volume_steps : d->source->n_volume_steps;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &volume_steps);
+}
+
+static void handle_get_is_muted(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &d->is_muted);
+}
+
+static void handle_set_is_muted(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    dbus_bool_t is_muted = FALSE;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(iter);
+    pa_assert(d);
+
+    dbus_message_iter_get_basic(iter, &is_muted);
+
+    if (d->type == DEVICE_TYPE_SINK)
+        pa_sink_set_mute(d->sink, is_muted, TRUE);
+    else
+        pa_source_set_mute(d->source, is_muted, TRUE);
+
+    pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void handle_get_has_hardware_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    dbus_bool_t has_hardware_volume = FALSE;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    has_hardware_volume = (d->type == DEVICE_TYPE_SINK)
+                          ? (d->sink->flags & PA_SINK_HW_VOLUME_CTRL)
+                          : (d->source->flags & PA_SOURCE_HW_VOLUME_CTRL);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_hardware_volume);
+}
+
+static void handle_get_has_hardware_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    dbus_bool_t has_hardware_mute = FALSE;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    has_hardware_mute = (d->type == DEVICE_TYPE_SINK)
+                        ? (d->sink->flags & PA_SINK_HW_MUTE_CTRL)
+                        : (d->source->flags & PA_SOURCE_HW_MUTE_CTRL);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_hardware_mute);
+}
+
+static void handle_get_configured_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    dbus_uint64_t configured_latency = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    configured_latency = (d->type == DEVICE_TYPE_SINK)
+                         ? pa_sink_get_requested_latency(d->sink)
+                         : pa_source_get_requested_latency(d->source);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &configured_latency);
+}
+
+static void handle_get_has_dynamic_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    dbus_bool_t has_dynamic_latency = FALSE;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    has_dynamic_latency = (d->type == DEVICE_TYPE_SINK)
+                          ? (d->sink->flags & PA_SINK_DYNAMIC_LATENCY)
+                          : (d->source->flags & PA_SOURCE_DYNAMIC_LATENCY);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &has_dynamic_latency);
+}
+
+static void handle_get_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    dbus_uint64_t latency = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    if (d->type == DEVICE_TYPE_SINK && !(d->sink->flags & PA_SINK_LATENCY))
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                           "Sink %s doesn't support latency querying.", d->sink->name);
+    else if (d->type == DEVICE_TYPE_SOURCE && !(d->source->flags & PA_SOURCE_LATENCY))
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                           "Source %s doesn't support latency querying.", d->source->name);
+    return;
+
+    latency = (d->type == DEVICE_TYPE_SINK) ? pa_sink_get_latency(d->sink) : pa_source_get_latency(d->source);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &latency);
+}
+
+static void handle_get_is_hardware_device(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    dbus_bool_t is_hardware_device = FALSE;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    is_hardware_device = (d->type == DEVICE_TYPE_SINK)
+                         ? (d->sink->flags & PA_SINK_HARDWARE)
+                         : (d->source->flags & PA_SOURCE_HARDWARE);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &is_hardware_device);
+}
+
+static void handle_get_is_network_device(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    dbus_bool_t is_network_device = FALSE;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    is_network_device = (d->type == DEVICE_TYPE_SINK)
+                        ? (d->sink->flags & PA_SINK_NETWORK)
+                        : (d->source->flags & PA_SOURCE_NETWORK);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &is_network_device);
+}
+
+static void handle_get_state(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    dbus_uint32_t state;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    state = (d->type == DEVICE_TYPE_SINK) ? d->sink_state : d->source_state;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &state);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_ports(pa_dbusiface_device *d, unsigned *n) {
+    const char **ports;
+    unsigned i = 0;
+    void *state = NULL;
+    pa_dbusiface_device_port *port = NULL;
+
+    pa_assert(d);
+    pa_assert(n);
+
+    *n = pa_hashmap_size(d->ports);
+
+    if (*n == 0)
+        return NULL;
+
+    ports = pa_xnew(const char *, *n);
+
+    PA_HASHMAP_FOREACH(port, d->ports, state)
+        ports[i++] = pa_dbusiface_device_port_get_path(port);
+
+    return ports;
+}
+
+static void handle_get_ports(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    const char **ports = NULL;
+    unsigned n_ports = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    ports = get_ports(d, &n_ports);
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, ports, n_ports);
+
+    pa_xfree(ports);
+}
+
+static void handle_get_active_port(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    const char *active_port;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    if (!d->active_port) {
+        pa_assert(pa_hashmap_isempty(d->ports));
+
+        if (d->type == DEVICE_TYPE_SINK)
+            pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                               "The sink %s has no ports, and therefore there's no active port either.", d->sink->name);
+        else
+            pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                               "The source %s has no ports, and therefore there's no active port either.", d->source->name);
+        return;
+    }
+
+    active_port = pa_dbusiface_device_port_get_path(pa_hashmap_get(d->ports, d->active_port->name));
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &active_port);
+}
+
+static void handle_set_active_port(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    const char *new_active_path;
+    pa_dbusiface_device_port *new_active;
+    int r;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(iter);
+    pa_assert(d);
+
+    if (!d->active_port) {
+        pa_assert(pa_hashmap_isempty(d->ports));
+
+        if (d->type == DEVICE_TYPE_SINK)
+            pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                               "The sink %s has no ports, and therefore there's no active port either.", d->sink->name);
+        else
+            pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                               "The source %s has no ports, and therefore there's no active port either.", d->source->name);
+        return;
+    }
+
+    dbus_message_iter_get_basic(iter, &new_active_path);
+
+    if (!(new_active = pa_hashmap_get(d->ports, new_active_path))) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "No such port: %s", new_active_path);
+        return;
+    }
+
+    if (d->type == DEVICE_TYPE_SINK) {
+        if ((r = pa_sink_set_port(d->sink, pa_dbusiface_device_port_get_name(new_active), TRUE)) < 0) {
+            pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED,
+                               "Internal error in PulseAudio: pa_sink_set_port() failed with error code %i.", r);
+            return;
+        }
+    } else {
+        if ((r = pa_source_set_port(d->source, pa_dbusiface_device_port_get_name(new_active), TRUE)) < 0) {
+            pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED,
+                               "Internal error in PulseAudio: pa_source_set_port() failed with error code %i.", r);
+            return;
+        }
+    }
+
+    pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    pa_dbus_send_proplist_variant_reply(conn, msg, d->proplist);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    DBusMessage *reply = NULL;
+    DBusMessageIter msg_iter;
+    DBusMessageIter dict_iter;
+    dbus_uint32_t idx = 0;
+    const char *name = NULL;
+    const char *driver = NULL;
+    pa_module *owner_module = NULL;
+    const char *owner_module_path = NULL;
+    pa_card *card = NULL;
+    const char *card_path = NULL;
+    dbus_uint32_t sample_format = 0;
+    dbus_uint32_t sample_rate = 0;
+    pa_channel_map *channel_map = NULL;
+    dbus_uint32_t channels[PA_CHANNELS_MAX];
+    dbus_uint32_t volume[PA_CHANNELS_MAX];
+    dbus_bool_t has_flat_volume = FALSE;
+    dbus_bool_t has_convertible_to_decibel_volume = FALSE;
+    dbus_uint32_t base_volume = 0;
+    dbus_uint32_t volume_steps = 0;
+    dbus_bool_t has_hardware_volume = FALSE;
+    dbus_bool_t has_hardware_mute = FALSE;
+    dbus_uint64_t configured_latency = 0;
+    dbus_bool_t has_dynamic_latency = FALSE;
+    dbus_uint64_t latency = 0;
+    dbus_bool_t is_hardware_device = FALSE;
+    dbus_bool_t is_network_device = FALSE;
+    dbus_uint32_t state = 0;
+    const char **ports = NULL;
+    unsigned n_ports = 0;
+    const char *active_port = NULL;
+    unsigned i = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    if (d->type == DEVICE_TYPE_SINK) {
+        idx = d->sink->index;
+        name = d->sink->name;
+        driver = d->sink->driver;
+        owner_module = d->sink->module;
+        card = d->sink->card;
+        sample_format = d->sink->sample_spec.format;
+        sample_rate = d->sink->sample_spec.rate;
+        channel_map = &d->sink->channel_map;
+        has_flat_volume = d->sink->flags & PA_SINK_FLAT_VOLUME;
+        has_convertible_to_decibel_volume = d->sink->flags & PA_SINK_DECIBEL_VOLUME;
+        base_volume = d->sink->base_volume;
+        volume_steps = d->sink->n_volume_steps;
+        has_hardware_volume = d->sink->flags & PA_SINK_HW_VOLUME_CTRL;
+        has_hardware_mute = d->sink->flags & PA_SINK_HW_MUTE_CTRL;
+        configured_latency = pa_sink_get_requested_latency(d->sink);
+        has_dynamic_latency = d->sink->flags & PA_SINK_DYNAMIC_LATENCY;
+        latency = pa_sink_get_latency(d->sink);
+        is_hardware_device = d->sink->flags & PA_SINK_HARDWARE;
+        is_network_device = d->sink->flags & PA_SINK_NETWORK;
+        state = pa_sink_get_state(d->sink);
+    } else {
+        idx = d->source->index;
+        name = d->source->name;
+        driver = d->source->driver;
+        owner_module = d->source->module;
+        card = d->source->card;
+        sample_format = d->source->sample_spec.format;
+        sample_rate = d->source->sample_spec.rate;
+        channel_map = &d->source->channel_map;
+        has_flat_volume = FALSE;
+        has_convertible_to_decibel_volume = d->source->flags & PA_SOURCE_DECIBEL_VOLUME;
+        base_volume = d->source->base_volume;
+        volume_steps = d->source->n_volume_steps;
+        has_hardware_volume = d->source->flags & PA_SOURCE_HW_VOLUME_CTRL;
+        has_hardware_mute = d->source->flags & PA_SOURCE_HW_MUTE_CTRL;
+        configured_latency = pa_source_get_requested_latency(d->source);
+        has_dynamic_latency = d->source->flags & PA_SOURCE_DYNAMIC_LATENCY;
+        latency = pa_source_get_latency(d->source);
+        is_hardware_device = d->source->flags & PA_SOURCE_HARDWARE;
+        is_network_device = d->source->flags & PA_SOURCE_NETWORK;
+        state = pa_source_get_state(d->source);
+    }
+    if (owner_module)
+        owner_module_path = pa_dbusiface_core_get_module_path(d->core, owner_module);
+    if (card)
+        card_path = pa_dbusiface_core_get_card_path(d->core, card);
+    for (i = 0; i < channel_map->channels; ++i)
+        channels[i] = channel_map->map[i];
+    for (i = 0; i < d->volume.channels; ++i)
+        volume[i] = d->volume.values[i];
+    ports = get_ports(d, &n_ports);
+    if (d->active_port)
+        active_port = pa_dbusiface_device_port_get_path(pa_hashmap_get(d->ports, d->active_port->name));
+
+    pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+    dbus_message_iter_init_append(reply, &msg_iter);
+    pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &name);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &driver);
+
+    if (owner_module)
+        pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_OWNER_MODULE].property_name, DBUS_TYPE_OBJECT_PATH, &owner_module_path);
+
+    if (card)
+        pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CARD].property_name, DBUS_TYPE_OBJECT_PATH, &card_path);
+
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_FORMAT].property_name, DBUS_TYPE_UINT32, &sample_format);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_RATE].property_name, DBUS_TYPE_UINT32, &sample_rate);
+    pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CHANNELS].property_name, DBUS_TYPE_UINT32, channels, channel_map->channels);
+    pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_VOLUME].property_name, DBUS_TYPE_UINT32, volume, d->volume.channels);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_FLAT_VOLUME].property_name, DBUS_TYPE_BOOLEAN, &has_flat_volume);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_CONVERTIBLE_TO_DECIBEL_VOLUME].property_name, DBUS_TYPE_BOOLEAN, &has_convertible_to_decibel_volume);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_BASE_VOLUME].property_name, DBUS_TYPE_UINT32, &base_volume);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_VOLUME_STEPS].property_name, DBUS_TYPE_UINT32, &volume_steps);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_IS_MUTED].property_name, DBUS_TYPE_BOOLEAN, &d->is_muted);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_HARDWARE_VOLUME].property_name, DBUS_TYPE_BOOLEAN, &has_hardware_volume);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_HARDWARE_MUTE].property_name, DBUS_TYPE_BOOLEAN, &has_hardware_mute);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CONFIGURED_LATENCY].property_name, DBUS_TYPE_UINT64, &configured_latency);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_HAS_DYNAMIC_LATENCY].property_name, DBUS_TYPE_BOOLEAN, &has_dynamic_latency);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_LATENCY].property_name, DBUS_TYPE_UINT64, &latency);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_IS_HARDWARE_DEVICE].property_name, DBUS_TYPE_BOOLEAN, &is_hardware_device);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_IS_NETWORK_DEVICE].property_name, DBUS_TYPE_BOOLEAN, &is_network_device);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_STATE].property_name, DBUS_TYPE_UINT32, &state);
+    pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PORTS].property_name, DBUS_TYPE_OBJECT_PATH, ports, n_ports);
+
+    if (active_port)
+        pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACTIVE_PORT].property_name, DBUS_TYPE_OBJECT_PATH, &active_port);
+
+    pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, d->proplist);
+
+    pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+    pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+    dbus_message_unref(reply);
+
+    pa_xfree(ports);
+}
+
+static void handle_suspend(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    dbus_bool_t suspend = FALSE;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &suspend, DBUS_TYPE_INVALID));
+
+    if ((d->type == DEVICE_TYPE_SINK) && (pa_sink_suspend(d->sink, suspend, PA_SUSPEND_USER) < 0)) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Internal error in PulseAudio: pa_sink_suspend() failed.");
+        return;
+    } else if ((d->type == DEVICE_TYPE_SOURCE) && (pa_source_suspend(d->source, suspend, PA_SUSPEND_USER) < 0)) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Internal error in PulseAudio: pa_source_suspend() failed.");
+        return;
+    }
+
+    pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void handle_get_port_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    const char *port_name = NULL;
+    pa_dbusiface_device_port *port = NULL;
+    const char *port_path = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+
+    pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &port_name, DBUS_TYPE_INVALID));
+
+    if (!(port = pa_hashmap_get(d->ports, port_name))) {
+        if (d->type == DEVICE_TYPE_SINK)
+            pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND,
+                               "%s: No such port on sink %s.", port_name, d->sink->name);
+        else
+            pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND,
+                               "%s: No such port on source %s.", port_name, d->source->name);
+        return;
+    }
+
+    port_path = pa_dbusiface_device_port_get_path(port);
+
+    pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &port_path);
+}
+
+static void handle_sink_get_monitor_source(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    const char *monitor_source = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+    pa_assert(d->type == DEVICE_TYPE_SINK);
+
+    monitor_source = pa_dbusiface_core_get_source_path(d->core, d->sink->monitor_source);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &monitor_source);
+}
+
+static void handle_sink_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    DBusMessage *reply = NULL;
+    DBusMessageIter msg_iter;
+    DBusMessageIter dict_iter;
+    const char *monitor_source = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+    pa_assert(d->type == DEVICE_TYPE_SINK);
+
+    monitor_source = pa_dbusiface_core_get_source_path(d->core, d->sink->monitor_source);
+
+    pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+    dbus_message_iter_init_append(reply, &msg_iter);
+    pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[SINK_PROPERTY_HANDLER_MONITOR_SOURCE].property_name, DBUS_TYPE_OBJECT_PATH, &monitor_source);
+
+    pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+    pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+    dbus_message_unref(reply);
+}
+
+static void handle_source_get_monitor_of_sink(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    const char *monitor_of_sink = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+    pa_assert(d->type == DEVICE_TYPE_SOURCE);
+
+    if (!d->source->monitor_of) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Source %s is not a monitor source.", d->source->name);
+        return;
+    }
+
+    monitor_of_sink = pa_dbusiface_core_get_sink_path(d->core, d->source->monitor_of);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &monitor_of_sink);
+}
+
+static void handle_source_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    DBusMessage *reply = NULL;
+    DBusMessageIter msg_iter;
+    DBusMessageIter dict_iter;
+    const char *monitor_of_sink = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(d);
+    pa_assert(d->type == DEVICE_TYPE_SOURCE);
+
+    if (d->source->monitor_of)
+        monitor_of_sink = pa_dbusiface_core_get_sink_path(d->core, d->source->monitor_of);
+
+    pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+    dbus_message_iter_init_append(reply, &msg_iter);
+    pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+    if (monitor_of_sink)
+        pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[SOURCE_PROPERTY_HANDLER_MONITOR_OF_SINK].property_name, DBUS_TYPE_OBJECT_PATH, &monitor_of_sink);
+
+    pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+    pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+    dbus_message_unref(reply);
+}
+
+static void subscription_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+    pa_dbusiface_device *d = userdata;
+    DBusMessage *signal = NULL;
+    const pa_cvolume *new_volume = NULL;
+    pa_bool_t new_muted = FALSE;
+    pa_sink_state_t new_sink_state = 0;
+    pa_source_state_t new_source_state = 0;
+    pa_device_port *new_active_port = NULL;
+    pa_proplist *new_proplist = NULL;
+    unsigned i = 0;
+
+    pa_assert(c);
+    pa_assert(d);
+
+    if ((d->type == DEVICE_TYPE_SINK && idx != d->sink->index) || (d->type == DEVICE_TYPE_SOURCE && idx != d->source->index))
+        return;
+
+    if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE)
+        return;
+
+    pa_assert(((d->type == DEVICE_TYPE_SINK)
+                && ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK))
+              || ((d->type == DEVICE_TYPE_SOURCE)
+                   && ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE)));
+
+    new_volume = (d->type == DEVICE_TYPE_SINK)
+                 ? pa_sink_get_volume(d->sink, FALSE)
+                 : pa_source_get_volume(d->source, FALSE);
+
+    if (!pa_cvolume_equal(&d->volume, new_volume)) {
+        dbus_uint32_t volume[PA_CHANNELS_MAX];
+        dbus_uint32_t *volume_ptr = volume;
+
+        d->volume = *new_volume;
+
+        for (i = 0; i < d->volume.channels; ++i)
+            volume[i] = d->volume.values[i];
+
+        pa_assert_se(signal = dbus_message_new_signal(d->path,
+                                                      PA_DBUSIFACE_DEVICE_INTERFACE,
+                                                      signals[SIGNAL_VOLUME_UPDATED].name));
+        pa_assert_se(dbus_message_append_args(signal,
+                                              DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &volume_ptr, d->volume.channels,
+                                              DBUS_TYPE_INVALID));
+
+        pa_dbus_protocol_send_signal(d->dbus_protocol, signal);
+        dbus_message_unref(signal);
+        signal = NULL;
+    }
+
+    new_muted = (d->type == DEVICE_TYPE_SINK) ? pa_sink_get_mute(d->sink, FALSE) : pa_source_get_mute(d->source, FALSE);
+
+    if (d->is_muted != new_muted) {
+        d->is_muted = new_muted;
+
+        pa_assert_se(signal = dbus_message_new_signal(d->path,
+                                                      PA_DBUSIFACE_DEVICE_INTERFACE,
+                                                      signals[SIGNAL_MUTE_UPDATED].name));
+        pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_BOOLEAN, &d->is_muted, DBUS_TYPE_INVALID));
+
+        pa_dbus_protocol_send_signal(d->dbus_protocol, signal);
+        dbus_message_unref(signal);
+        signal = NULL;
+    }
+
+    if (d->type == DEVICE_TYPE_SINK)
+        new_sink_state = pa_sink_get_state(d->sink);
+    else
+        new_source_state = pa_source_get_state(d->source);
+
+    if ((d->type == DEVICE_TYPE_SINK && d->sink_state != new_sink_state)
+        || (d->type == DEVICE_TYPE_SOURCE && d->source_state != new_source_state)) {
+        dbus_uint32_t state = 0;
+
+        if (d->type == DEVICE_TYPE_SINK)
+            d->sink_state = new_sink_state;
+        else
+            d->source_state = new_source_state;
+
+        state = (d->type == DEVICE_TYPE_SINK) ? d->sink_state : d->source_state;
+
+        pa_assert_se(signal = dbus_message_new_signal(d->path,
+                                                      PA_DBUSIFACE_DEVICE_INTERFACE,
+                                                      signals[SIGNAL_STATE_UPDATED].name));
+        pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_UINT32, &state, DBUS_TYPE_INVALID));
+
+        pa_dbus_protocol_send_signal(d->dbus_protocol, signal);
+        dbus_message_unref(signal);
+        signal = NULL;
+    }
+
+    new_active_port = (d->type == DEVICE_TYPE_SINK) ? d->sink->active_port : d->source->active_port;
+
+    if (d->active_port != new_active_port) {
+        const char *object_path = NULL;
+
+        d->active_port = new_active_port;
+        object_path = pa_dbusiface_device_port_get_path(pa_hashmap_get(d->ports, d->active_port->name));
+
+        pa_assert_se(signal = dbus_message_new_signal(d->path,
+                                                      PA_DBUSIFACE_DEVICE_INTERFACE,
+                                                      signals[SIGNAL_ACTIVE_PORT_UPDATED].name));
+        pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
+
+        pa_dbus_protocol_send_signal(d->dbus_protocol, signal);
+        dbus_message_unref(signal);
+        signal = NULL;
+    }
+
+    new_proplist = (d->type == DEVICE_TYPE_SINK) ? d->sink->proplist : d->source->proplist;
+
+    if (!pa_proplist_equal(d->proplist, new_proplist)) {
+        DBusMessageIter msg_iter;
+
+        pa_proplist_update(d->proplist, PA_UPDATE_SET, new_proplist);
+
+        pa_assert_se(signal = dbus_message_new_signal(d->path,
+                                                      PA_DBUSIFACE_DEVICE_INTERFACE,
+                                                      signals[SIGNAL_PROPERTY_LIST_UPDATED].name));
+        dbus_message_iter_init_append(signal, &msg_iter);
+        pa_dbus_append_proplist(&msg_iter, d->proplist);
+
+        pa_dbus_protocol_send_signal(d->dbus_protocol, signal);
+        dbus_message_unref(signal);
+        signal = NULL;
+    }
+}
+
+pa_dbusiface_device *pa_dbusiface_device_new_sink(pa_dbusiface_core *core, pa_sink *sink) {
+    pa_dbusiface_device *d = NULL;
+
+    pa_assert(core);
+    pa_assert(sink);
+
+    d = pa_xnew0(pa_dbusiface_device, 1);
+    d->core = core;
+    d->sink = pa_sink_ref(sink);
+    d->type = DEVICE_TYPE_SINK;
+    d->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, SINK_OBJECT_NAME, sink->index);
+    d->volume = *pa_sink_get_volume(sink, FALSE);
+    d->is_muted = pa_sink_get_mute(sink, FALSE);
+    d->sink_state = pa_sink_get_state(sink);
+    d->ports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+    d->next_port_index = 0;
+    d->active_port = NULL;
+    d->proplist = pa_proplist_copy(sink->proplist);
+    d->dbus_protocol = pa_dbus_protocol_get(sink->core);
+    d->subscription = pa_subscription_new(sink->core, PA_SUBSCRIPTION_MASK_SINK, subscription_cb, d);
+
+    if (sink->ports) {
+        pa_device_port *port;
+        void *state = NULL;
+
+        PA_HASHMAP_FOREACH(port, sink->ports, state) {
+            pa_dbusiface_device_port *p = pa_dbusiface_device_port_new(d, sink->core, port, d->next_port_index++);
+            pa_hashmap_put(d->ports, pa_dbusiface_device_port_get_name(p), p);
+        }
+        pa_assert_se(d->active_port = sink->active_port);
+    }
+
+    pa_assert_se(pa_dbus_protocol_add_interface(d->dbus_protocol, d->path, &device_interface_info, d) >= 0);
+    pa_assert_se(pa_dbus_protocol_add_interface(d->dbus_protocol, d->path, &sink_interface_info, d) >= 0);
+
+    return d;
+}
+
+pa_dbusiface_device *pa_dbusiface_device_new_source(pa_dbusiface_core *core, pa_source *source) {
+    pa_dbusiface_device *d = NULL;
+
+    pa_assert(core);
+    pa_assert(source);
+
+    d = pa_xnew0(pa_dbusiface_device, 1);
+    d->core = core;
+    d->source = pa_source_ref(source);
+    d->type = DEVICE_TYPE_SOURCE;
+    d->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, SOURCE_OBJECT_NAME, source->index);
+    d->volume = *pa_source_get_volume(source, FALSE);
+    d->is_muted = pa_source_get_mute(source, FALSE);
+    d->source_state = pa_source_get_state(source);
+    d->ports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+    d->next_port_index = 0;
+    d->active_port = NULL;
+    d->proplist = pa_proplist_copy(source->proplist);
+    d->dbus_protocol = pa_dbus_protocol_get(source->core);
+    d->subscription = pa_subscription_new(source->core, PA_SUBSCRIPTION_MASK_SOURCE, subscription_cb, d);
+
+    if (source->ports) {
+        pa_device_port *port;
+        void *state = NULL;
+
+        PA_HASHMAP_FOREACH(port, source->ports, state) {
+            pa_dbusiface_device_port *p = pa_dbusiface_device_port_new(d, source->core, port, d->next_port_index++);
+            pa_hashmap_put(d->ports, pa_dbusiface_device_port_get_name(p), p);
+        }
+        pa_assert_se(d->active_port = source->active_port);
+    }
+
+    pa_assert_se(pa_dbus_protocol_add_interface(d->dbus_protocol, d->path, &device_interface_info, d) >= 0);
+    pa_assert_se(pa_dbus_protocol_add_interface(d->dbus_protocol, d->path, &source_interface_info, d) >= 0);
+
+    return d;
+}
+
+static void port_free_cb(void *p, void *userdata) {
+    pa_dbusiface_device_port *port = p;
+
+    pa_assert(port);
+
+    pa_dbusiface_device_port_free(port);
+}
+
+void pa_dbusiface_device_free(pa_dbusiface_device *d) {
+    pa_assert(d);
+
+    pa_assert_se(pa_dbus_protocol_remove_interface(d->dbus_protocol, d->path, device_interface_info.name) >= 0);
+
+    if (d->type == DEVICE_TYPE_SINK) {
+        pa_assert_se(pa_dbus_protocol_remove_interface(d->dbus_protocol, d->path, sink_interface_info.name) >= 0);
+        pa_sink_unref(d->sink);
+
+    } else {
+        pa_assert_se(pa_dbus_protocol_remove_interface(d->dbus_protocol, d->path, source_interface_info.name) >= 0);
+        pa_source_unref(d->source);
+    }
+    pa_hashmap_free(d->ports, port_free_cb, NULL);
+    pa_proplist_free(d->proplist);
+    pa_dbus_protocol_unref(d->dbus_protocol);
+    pa_subscription_free(d->subscription);
+
+    pa_xfree(d->path);
+    pa_xfree(d);
+}
+
+const char *pa_dbusiface_device_get_path(pa_dbusiface_device *d) {
+    pa_assert(d);
+
+    return d->path;
+}
+
+pa_sink *pa_dbusiface_device_get_sink(pa_dbusiface_device *d) {
+    pa_assert(d);
+    pa_assert(d->type == DEVICE_TYPE_SINK);
+
+    return d->sink;
+}
+
+pa_source *pa_dbusiface_device_get_source(pa_dbusiface_device *d) {
+    pa_assert(d);
+    pa_assert(d->type == DEVICE_TYPE_SOURCE);
+
+    return d->source;
+}
diff --git a/src/modules/dbus/iface-device.h b/src/modules/dbus/iface-device.h
new file mode 100644 (file)
index 0000000..62e05e9
--- /dev/null
@@ -0,0 +1,53 @@
+#ifndef foodbusifacedevicehfoo
+#define foodbusifacedevicehfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+/* This object implements the D-Bus interfaces org.PulseAudio.Core1.Device,
+ * org.PulseAudio.Core1.Sink and org.PulseAudio.Core1.Source.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the interface
+ * documentation.
+ */
+
+#include <pulsecore/protocol-dbus.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/source.h>
+
+#include "iface-core.h"
+
+#define PA_DBUSIFACE_DEVICE_INTERFACE PA_DBUS_CORE_INTERFACE ".Device"
+#define PA_DBUSIFACE_SINK_INTERFACE PA_DBUS_CORE_INTERFACE ".Sink"
+#define PA_DBUSIFACE_SOURCE_INTERFACE PA_DBUS_CORE_INTERFACE ".Source"
+
+typedef struct pa_dbusiface_device pa_dbusiface_device;
+
+pa_dbusiface_device *pa_dbusiface_device_new_sink(pa_dbusiface_core *core, pa_sink *sink);
+pa_dbusiface_device *pa_dbusiface_device_new_source(pa_dbusiface_core *core, pa_source *source);
+void pa_dbusiface_device_free(pa_dbusiface_device *d);
+
+const char *pa_dbusiface_device_get_path(pa_dbusiface_device *d);
+
+pa_sink *pa_dbusiface_device_get_sink(pa_dbusiface_device *d);
+pa_source *pa_dbusiface_device_get_source(pa_dbusiface_device *d);
+
+#endif
diff --git a/src/modules/dbus/iface-memstats.c b/src/modules/dbus/iface-memstats.c
new file mode 100644 (file)
index 0000000..73a84be
--- /dev/null
@@ -0,0 +1,231 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <dbus/dbus.h>
+
+#include <pulsecore/core-scache.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-memstats.h"
+
+#define OBJECT_NAME "memstats"
+
+static void handle_get_current_memblocks(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_current_memblocks_size(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_accumulated_memblocks(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_accumulated_memblocks_size(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sample_cache_size(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+struct pa_dbusiface_memstats {
+    pa_core *core;
+    char *path;
+    pa_dbus_protocol *dbus_protocol;
+};
+
+enum property_handler_index {
+    PROPERTY_HANDLER_CURRENT_MEMBLOCKS,
+    PROPERTY_HANDLER_CURRENT_MEMBLOCKS_SIZE,
+    PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS,
+    PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS_SIZE,
+    PROPERTY_HANDLER_SAMPLE_CACHE_SIZE,
+    PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+    [PROPERTY_HANDLER_CURRENT_MEMBLOCKS]          = { .property_name = "CurrentMemblocks",         .type = "u", .get_cb = handle_get_current_memblocks,          .set_cb = NULL },
+    [PROPERTY_HANDLER_CURRENT_MEMBLOCKS_SIZE]     = { .property_name = "CurrentMemblocksSize",     .type = "u", .get_cb = handle_get_current_memblocks_size,     .set_cb = NULL },
+    [PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS]      = { .property_name = "AccumulatedMemblocks",     .type = "u", .get_cb = handle_get_accumulated_memblocks,      .set_cb = NULL },
+    [PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS_SIZE] = { .property_name = "AccumulatedMemblocksSize", .type = "u", .get_cb = handle_get_accumulated_memblocks_size, .set_cb = NULL },
+    [PROPERTY_HANDLER_SAMPLE_CACHE_SIZE]          = { .property_name = "SampleCacheSize",          .type = "u", .get_cb = handle_get_sample_cache_size,          .set_cb = NULL }
+};
+
+static pa_dbus_interface_info memstats_interface_info = {
+    .name = PA_DBUSIFACE_MEMSTATS_INTERFACE,
+    .method_handlers = NULL,
+    .n_method_handlers = 0,
+    .property_handlers = property_handlers,
+    .n_property_handlers = PROPERTY_HANDLER_MAX,
+    .get_all_properties_cb = handle_get_all,
+    .signals = NULL,
+    .n_signals = 0
+};
+
+static void handle_get_current_memblocks(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_memstats *m = userdata;
+    const pa_mempool_stat *stat;
+    dbus_uint32_t current_memblocks;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    stat = pa_mempool_get_stat(m->core->mempool);
+
+    current_memblocks = pa_atomic_load(&stat->n_allocated);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &current_memblocks);
+}
+
+static void handle_get_current_memblocks_size(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_memstats *m = userdata;
+    const pa_mempool_stat *stat;
+    dbus_uint32_t current_memblocks_size;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    stat = pa_mempool_get_stat(m->core->mempool);
+
+    current_memblocks_size = pa_atomic_load(&stat->allocated_size);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &current_memblocks_size);
+}
+
+static void handle_get_accumulated_memblocks(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_memstats *m = userdata;
+    const pa_mempool_stat *stat;
+    dbus_uint32_t accumulated_memblocks;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    stat = pa_mempool_get_stat(m->core->mempool);
+
+    accumulated_memblocks = pa_atomic_load(&stat->n_accumulated);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &accumulated_memblocks);
+}
+
+static void handle_get_accumulated_memblocks_size(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_memstats *m = userdata;
+    const pa_mempool_stat *stat;
+    dbus_uint32_t accumulated_memblocks_size;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    stat = pa_mempool_get_stat(m->core->mempool);
+
+    accumulated_memblocks_size = pa_atomic_load(&stat->accumulated_size);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &accumulated_memblocks_size);
+}
+
+static void handle_get_sample_cache_size(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_memstats *m = userdata;
+    dbus_uint32_t sample_cache_size;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    sample_cache_size = pa_scache_total_size(m->core);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_cache_size);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_memstats *m = userdata;
+    const pa_mempool_stat *stat;
+    dbus_uint32_t current_memblocks;
+    dbus_uint32_t current_memblocks_size;
+    dbus_uint32_t accumulated_memblocks;
+    dbus_uint32_t accumulated_memblocks_size;
+    dbus_uint32_t sample_cache_size;
+    DBusMessage *reply = NULL;
+    DBusMessageIter msg_iter;
+    DBusMessageIter dict_iter;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    stat = pa_mempool_get_stat(m->core->mempool);
+
+    current_memblocks = pa_atomic_load(&stat->n_allocated);
+    current_memblocks_size = pa_atomic_load(&stat->allocated_size);
+    accumulated_memblocks = pa_atomic_load(&stat->n_accumulated);
+    accumulated_memblocks_size = pa_atomic_load(&stat->accumulated_size);
+    sample_cache_size = pa_scache_total_size(m->core);
+
+    pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+    dbus_message_iter_init_append(reply, &msg_iter);
+    pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CURRENT_MEMBLOCKS].property_name, DBUS_TYPE_UINT32, &current_memblocks);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CURRENT_MEMBLOCKS_SIZE].property_name, DBUS_TYPE_UINT32, &current_memblocks_size);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS].property_name, DBUS_TYPE_UINT32, &accumulated_memblocks);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACCUMULATED_MEMBLOCKS_SIZE].property_name, DBUS_TYPE_UINT32, &accumulated_memblocks_size);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_CACHE_SIZE].property_name, DBUS_TYPE_UINT32, &sample_cache_size);
+
+    pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+    pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+    dbus_message_unref(reply);
+}
+
+pa_dbusiface_memstats *pa_dbusiface_memstats_new(pa_dbusiface_core *dbus_core, pa_core *core) {
+    pa_dbusiface_memstats *m;
+
+    pa_assert(dbus_core);
+    pa_assert(core);
+
+    m = pa_xnew(pa_dbusiface_memstats, 1);
+    m->core = pa_core_ref(core);
+    m->path = pa_sprintf_malloc("%s/%s", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME);
+    m->dbus_protocol = pa_dbus_protocol_get(core);
+
+    pa_assert_se(pa_dbus_protocol_add_interface(m->dbus_protocol, m->path, &memstats_interface_info, m) >= 0);
+
+    return m;
+}
+
+void pa_dbusiface_memstats_free(pa_dbusiface_memstats *m) {
+    pa_assert(m);
+
+    pa_assert_se(pa_dbus_protocol_remove_interface(m->dbus_protocol, m->path, memstats_interface_info.name) >= 0);
+
+    pa_xfree(m->path);
+
+    pa_dbus_protocol_unref(m->dbus_protocol);
+    pa_core_unref(m->core);
+
+    pa_xfree(m);
+}
+
+const char *pa_dbusiface_memstats_get_path(pa_dbusiface_memstats *m) {
+    pa_assert(m);
+
+    return m->path;
+}
diff --git a/src/modules/dbus/iface-memstats.h b/src/modules/dbus/iface-memstats.h
new file mode 100644 (file)
index 0000000..0820e8f
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef foodbusifacememstatshfoo
+#define foodbusifacememstatshfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+/* This object implements the D-Bus interface org.PulseAudio.Core1.Memstats.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the Memstats interface
+ * documentation.
+ */
+
+#include <pulsecore/core.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-core.h"
+
+#define PA_DBUSIFACE_MEMSTATS_INTERFACE PA_DBUS_CORE_INTERFACE ".Memstats"
+
+typedef struct pa_dbusiface_memstats pa_dbusiface_memstats;
+
+pa_dbusiface_memstats *pa_dbusiface_memstats_new(pa_dbusiface_core *dbus_core, pa_core *core);
+void pa_dbusiface_memstats_free(pa_dbusiface_memstats *m);
+
+const char *pa_dbusiface_memstats_get_path(pa_dbusiface_memstats *m);
+
+#endif
diff --git a/src/modules/dbus/iface-module.c b/src/modules/dbus/iface-module.c
new file mode 100644 (file)
index 0000000..e8aea50
--- /dev/null
@@ -0,0 +1,336 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-module.h"
+
+#define OBJECT_NAME "module"
+
+struct pa_dbusiface_module {
+    pa_module *module;
+    char *path;
+    pa_proplist *proplist;
+
+    pa_dbus_protocol *dbus_protocol;
+    pa_subscription *subscription;
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_arguments(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_usage_counter(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_unload(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+enum property_handler_index {
+    PROPERTY_HANDLER_INDEX,
+    PROPERTY_HANDLER_NAME,
+    PROPERTY_HANDLER_ARGUMENTS,
+    PROPERTY_HANDLER_USAGE_COUNTER,
+    PROPERTY_HANDLER_PROPERTY_LIST,
+    PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+    [PROPERTY_HANDLER_INDEX]         = { .property_name = "Index",        .type = "u",      .get_cb = handle_get_index,         .set_cb = NULL },
+    [PROPERTY_HANDLER_NAME]          = { .property_name = "Name",         .type = "s",      .get_cb = handle_get_name,          .set_cb = NULL },
+    [PROPERTY_HANDLER_ARGUMENTS]     = { .property_name = "Arguments",    .type = "a{ss}",  .get_cb = handle_get_arguments,     .set_cb = NULL },
+    [PROPERTY_HANDLER_USAGE_COUNTER] = { .property_name = "UsageCounter", .type = "u",      .get_cb = handle_get_usage_counter, .set_cb = NULL },
+    [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL }
+};
+
+enum method_handler_index {
+    METHOD_HANDLER_UNLOAD,
+    METHOD_HANDLER_MAX
+};
+
+static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
+    [METHOD_HANDLER_UNLOAD] = {
+        .method_name = "Unload",
+        .arguments = NULL,
+        .n_arguments = 0,
+        .receive_cb = handle_unload }
+};
+
+enum signal_index {
+    SIGNAL_PROPERTY_LIST_UPDATED,
+    SIGNAL_MAX
+};
+
+static pa_dbus_arg_info property_list_updated_args[] =  { { "property_list", "a{say}", NULL } };
+
+static pa_dbus_signal_info signals[SIGNAL_MAX] = {
+    [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 }
+};
+
+static pa_dbus_interface_info module_interface_info = {
+    .name = PA_DBUSIFACE_MODULE_INTERFACE,
+    .method_handlers = method_handlers,
+    .n_method_handlers = METHOD_HANDLER_MAX,
+    .property_handlers = property_handlers,
+    .n_property_handlers = PROPERTY_HANDLER_MAX,
+    .get_all_properties_cb = handle_get_all,
+    .signals = signals,
+    .n_signals = SIGNAL_MAX
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_module *m = userdata;
+    dbus_uint32_t idx = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    idx = m->module->index;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx);
+}
+
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_module *m = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &m->module->name);
+}
+
+static void append_modargs_variant(DBusMessageIter *iter, pa_dbusiface_module *m) {
+    pa_modargs *ma = NULL;
+    DBusMessageIter variant_iter;
+    DBusMessageIter dict_iter;
+    DBusMessageIter dict_entry_iter;
+    void *state = NULL;
+    const char *key = NULL;
+    const char *value = NULL;
+
+    pa_assert(iter);
+    pa_assert(m);
+
+    pa_assert_se(ma = pa_modargs_new(m->module->argument, NULL));
+
+    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "a{ss}", &variant_iter));
+    pa_assert_se(dbus_message_iter_open_container(&variant_iter, DBUS_TYPE_ARRAY, "{ss}", &dict_iter));
+
+    for (state = NULL, key = pa_modargs_iterate(ma, &state); key; key = pa_modargs_iterate(ma, &state)) {
+        pa_assert_se(value = pa_modargs_get_value(ma, key, NULL));
+
+        pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter));
+
+        pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key));
+        pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &value));
+
+        pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter));
+    }
+
+    pa_assert_se(dbus_message_iter_close_container(&variant_iter, &dict_iter));
+    pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter));
+
+    pa_modargs_free(ma);
+}
+
+static void handle_get_arguments(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_module *m = userdata;
+    DBusMessage *reply = NULL;
+    DBusMessageIter msg_iter;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    pa_assert_se(reply = dbus_message_new_method_return(msg));
+    dbus_message_iter_init_append(reply, &msg_iter);
+    append_modargs_variant(&msg_iter, m);
+    pa_assert_se(dbus_connection_send(conn, reply, NULL));
+    dbus_message_unref(reply);
+}
+
+static void handle_get_usage_counter(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_module *m = userdata;
+    int real_counter_value = -1;
+    dbus_uint32_t usage_counter = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    if (!m->module->get_n_used || (real_counter_value = m->module->get_n_used(m->module)) < 0) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                           "Module %u (%s) doesn't have a usage counter.", m->module->index, m->module->name);
+        return;
+    }
+
+    usage_counter = real_counter_value;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &usage_counter);
+}
+
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_module *m = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    pa_dbus_send_proplist_variant_reply(conn, msg, m->proplist);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_module *m = userdata;
+    DBusMessage *reply = NULL;
+    DBusMessageIter msg_iter;
+    DBusMessageIter dict_iter;
+    DBusMessageIter dict_entry_iter;
+    dbus_uint32_t idx = 0;
+    int real_counter_value = -1;
+    dbus_uint32_t usage_counter = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    idx = m->module->index;
+    if (m->module->get_n_used && (real_counter_value = m->module->get_n_used(m->module)) >= 0)
+        usage_counter = real_counter_value;
+
+    pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+    dbus_message_iter_init_append(reply, &msg_iter);
+    pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &m->module->name);
+
+    pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter));
+    pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &property_handlers[PROPERTY_HANDLER_ARGUMENTS].property_name));
+    append_modargs_variant(&dict_entry_iter, m);
+    pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter));
+
+    if (real_counter_value >= 0)
+        pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ARGUMENTS].property_name, DBUS_TYPE_UINT32, &usage_counter);
+
+    pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, m->proplist);
+
+    pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+    pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+    dbus_message_unref(reply);
+}
+
+static void handle_unload(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_module *m = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    if (m->module->core->disallow_module_loading) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "The server is configured to disallow module unloading.");
+        return;
+    }
+
+    pa_module_unload_request(m->module, FALSE);
+
+    pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+    pa_dbusiface_module *m = userdata;
+    DBusMessage *signal = NULL;
+
+    pa_assert(core);
+    pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_MODULE);
+    pa_assert(m);
+
+    /* We can't use idx != m->module->index, because the m->module pointer may
+     * be stale at this point. */
+    if (pa_idxset_get_by_index(core->modules, idx) != m->module)
+        return;
+
+    if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE)
+        return;
+
+    if (!pa_proplist_equal(m->proplist, m->module->proplist)) {
+        DBusMessageIter msg_iter;
+
+        pa_proplist_update(m->proplist, PA_UPDATE_SET, m->module->proplist);
+
+        pa_assert_se(signal = dbus_message_new_signal(m->path,
+                                                      PA_DBUSIFACE_MODULE_INTERFACE,
+                                                      signals[SIGNAL_PROPERTY_LIST_UPDATED].name));
+        dbus_message_iter_init_append(signal, &msg_iter);
+        pa_dbus_append_proplist(&msg_iter, m->proplist);
+
+        pa_dbus_protocol_send_signal(m->dbus_protocol, signal);
+        dbus_message_unref(signal);
+        signal = NULL;
+    }
+}
+
+pa_dbusiface_module *pa_dbusiface_module_new(pa_module *module) {
+    pa_dbusiface_module *m;
+
+    pa_assert(module);
+
+    m = pa_xnew0(pa_dbusiface_module, 1);
+    m->module = module;
+    m->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, module->index);
+    m->proplist = pa_proplist_copy(module->proplist);
+    m->dbus_protocol = pa_dbus_protocol_get(module->core);
+    m->subscription = pa_subscription_new(module->core, PA_SUBSCRIPTION_MASK_MODULE, subscription_cb, m);
+
+    pa_assert_se(pa_dbus_protocol_add_interface(m->dbus_protocol, m->path, &module_interface_info, m) >= 0);
+
+    return m;
+}
+
+void pa_dbusiface_module_free(pa_dbusiface_module *m) {
+    pa_assert(m);
+
+    pa_assert_se(pa_dbus_protocol_remove_interface(m->dbus_protocol, m->path, module_interface_info.name) >= 0);
+
+    pa_proplist_free(m->proplist);
+    pa_dbus_protocol_unref(m->dbus_protocol);
+    pa_subscription_free(m->subscription);
+
+    pa_xfree(m->path);
+    pa_xfree(m);
+}
+
+const char *pa_dbusiface_module_get_path(pa_dbusiface_module *m) {
+    pa_assert(m);
+
+    return m->path;
+}
diff --git a/src/modules/dbus/iface-module.h b/src/modules/dbus/iface-module.h
new file mode 100644 (file)
index 0000000..68ca1de
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef foodbusifacemodulehfoo
+#define foodbusifacemodulehfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+/* This object implements the D-Bus interface org.PulseAudio.Core1.Module.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the Module interface
+ * documentation.
+ */
+
+#include <pulsecore/module.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-core.h"
+
+#define PA_DBUSIFACE_MODULE_INTERFACE PA_DBUS_CORE_INTERFACE ".Module"
+
+typedef struct pa_dbusiface_module pa_dbusiface_module;
+
+pa_dbusiface_module *pa_dbusiface_module_new(pa_module *module);
+void pa_dbusiface_module_free(pa_dbusiface_module *m);
+
+const char *pa_dbusiface_module_get_path(pa_dbusiface_module *m);
+
+#endif
diff --git a/src/modules/dbus/iface-sample.c b/src/modules/dbus/iface-sample.c
new file mode 100644 (file)
index 0000000..b0542a6
--- /dev/null
@@ -0,0 +1,519 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-sample.h"
+
+#define OBJECT_NAME "sample"
+
+struct pa_dbusiface_sample {
+    pa_dbusiface_core *core;
+
+    pa_scache_entry *sample;
+    char *path;
+    pa_proplist *proplist;
+
+    pa_dbus_protocol *dbus_protocol;
+    pa_subscription *subscription;
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_default_volume(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_duration(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_bytes(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_play(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_play_to_sink(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_remove(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+enum property_handler_index {
+    PROPERTY_HANDLER_INDEX,
+    PROPERTY_HANDLER_NAME,
+    PROPERTY_HANDLER_SAMPLE_FORMAT,
+    PROPERTY_HANDLER_SAMPLE_RATE,
+    PROPERTY_HANDLER_CHANNELS,
+    PROPERTY_HANDLER_DEFAULT_VOLUME,
+    PROPERTY_HANDLER_DURATION,
+    PROPERTY_HANDLER_BYTES,
+    PROPERTY_HANDLER_PROPERTY_LIST,
+    PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+    [PROPERTY_HANDLER_INDEX]          = { .property_name = "Index",         .type = "u",      .get_cb = handle_get_index,          .set_cb = NULL },
+    [PROPERTY_HANDLER_NAME]           = { .property_name = "Name",          .type = "s",      .get_cb = handle_get_name,           .set_cb = NULL },
+    [PROPERTY_HANDLER_SAMPLE_FORMAT]  = { .property_name = "SampleFormat",  .type = "u",      .get_cb = handle_get_sample_format,  .set_cb = NULL },
+    [PROPERTY_HANDLER_SAMPLE_RATE]    = { .property_name = "SampleRate",    .type = "u",      .get_cb = handle_get_sample_rate,    .set_cb = NULL },
+    [PROPERTY_HANDLER_CHANNELS]       = { .property_name = "Channels",      .type = "au",     .get_cb = handle_get_channels,       .set_cb = NULL },
+    [PROPERTY_HANDLER_DEFAULT_VOLUME] = { .property_name = "DefaultVolume", .type = "au",     .get_cb = handle_get_default_volume, .set_cb = NULL },
+    [PROPERTY_HANDLER_DURATION]       = { .property_name = "Duration",      .type = "t",      .get_cb = handle_get_duration,       .set_cb = NULL },
+    [PROPERTY_HANDLER_BYTES]          = { .property_name = "Bytes",         .type = "u",      .get_cb = handle_get_bytes,          .set_cb = NULL },
+    [PROPERTY_HANDLER_PROPERTY_LIST]  = { .property_name = "PropertyList",  .type = "a{say}", .get_cb = handle_get_property_list,  .set_cb = NULL }
+};
+
+enum method_handler_index {
+    METHOD_HANDLER_PLAY,
+    METHOD_HANDLER_PLAY_TO_SINK,
+    METHOD_HANDLER_REMOVE,
+    METHOD_HANDLER_MAX
+};
+
+static pa_dbus_arg_info play_args[] = { { "volume", "u", "in" }, { "property_list", "a{say}", "in" } };
+static pa_dbus_arg_info play_to_sink_args[] = { { "sink",          "o",      "in" },
+                                                { "volume",        "u",      "in" },
+                                                { "property_list", "a{say}", "in" } };
+
+static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
+    [METHOD_HANDLER_PLAY] = {
+        .method_name = "Play",
+        .arguments = play_args,
+        .n_arguments = sizeof(play_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_play },
+    [METHOD_HANDLER_PLAY_TO_SINK] = {
+        .method_name = "PlayToSink",
+        .arguments = play_to_sink_args,
+        .n_arguments = sizeof(play_to_sink_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_play_to_sink },
+    [METHOD_HANDLER_REMOVE] = {
+        .method_name = "Remove",
+        .arguments = NULL,
+        .n_arguments = 0,
+        .receive_cb = handle_remove }
+};
+
+enum signal_index {
+    SIGNAL_PROPERTY_LIST_UPDATED,
+    SIGNAL_MAX
+};
+
+static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } };
+
+static pa_dbus_signal_info signals[SIGNAL_MAX] = {
+    [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 }
+};
+
+static pa_dbus_interface_info sample_interface_info = {
+    .name = PA_DBUSIFACE_SAMPLE_INTERFACE,
+    .method_handlers = method_handlers,
+    .n_method_handlers = METHOD_HANDLER_MAX,
+    .property_handlers = property_handlers,
+    .n_property_handlers = PROPERTY_HANDLER_MAX,
+    .get_all_properties_cb = handle_get_all,
+    .signals = signals,
+    .n_signals = SIGNAL_MAX
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_sample *s = userdata;
+    dbus_uint32_t idx = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    idx = s->sample->index;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx);
+}
+
+static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_sample *s = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &s->sample->name);
+}
+
+static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_sample *s = userdata;
+    dbus_uint32_t sample_format = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    if (!s->sample->memchunk.memblock) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                           "Sample %s isn't loaded into memory yet, so its sample format is unknown.", s->sample->name);
+        return;
+    }
+
+    sample_format = s->sample->sample_spec.format;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_format);
+}
+
+static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_sample *s = userdata;
+    dbus_uint32_t sample_rate = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    if (!s->sample->memchunk.memblock) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                           "Sample %s isn't loaded into memory yet, so its sample rate is unknown.", s->sample->name);
+        return;
+    }
+
+    sample_rate = s->sample->sample_spec.rate;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_rate);
+}
+
+static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_sample *s = userdata;
+    dbus_uint32_t channels[PA_CHANNELS_MAX];
+    unsigned i = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    if (!s->sample->memchunk.memblock) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                           "Sample %s isn't loaded into memory yet, so its channel map is unknown.", s->sample->name);
+        return;
+    }
+
+    for (i = 0; i < s->sample->channel_map.channels; ++i)
+        channels[i] = s->sample->channel_map.map[i];
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, channels, s->sample->channel_map.channels);
+}
+
+static void handle_get_default_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_sample *s = userdata;
+    dbus_uint32_t default_volume[PA_CHANNELS_MAX];
+    unsigned i = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    if (!s->sample->volume_is_set) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                           "Sample %s doesn't have default volume stored.", s->sample->name);
+        return;
+    }
+
+    for (i = 0; i < s->sample->volume.channels; ++i)
+        default_volume[i] = s->sample->volume.values[i];
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, default_volume, s->sample->volume.channels);
+}
+
+static void handle_get_duration(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_sample *s = userdata;
+    dbus_uint64_t duration = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    if (!s->sample->memchunk.memblock) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                           "Sample %s isn't loaded into memory yet, so its duration is unknown.", s->sample->name);
+        return;
+    }
+
+    duration = pa_bytes_to_usec(s->sample->memchunk.length, &s->sample->sample_spec);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &duration);
+}
+
+static void handle_get_bytes(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_sample *s = userdata;
+    dbus_uint32_t bytes = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    if (!s->sample->memchunk.memblock) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                           "Sample %s isn't loaded into memory yet, so its size is unknown.", s->sample->name);
+        return;
+    }
+
+    bytes = s->sample->memchunk.length;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &bytes);
+}
+
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_sample *s = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    pa_dbus_send_proplist_variant_reply(conn, msg, s->proplist);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_sample *s = userdata;
+    DBusMessage *reply = NULL;
+    DBusMessageIter msg_iter;
+    DBusMessageIter dict_iter;
+    dbus_uint32_t idx = 0;
+    dbus_uint32_t sample_format = 0;
+    dbus_uint32_t sample_rate = 0;
+    dbus_uint32_t channels[PA_CHANNELS_MAX];
+    dbus_uint32_t default_volume[PA_CHANNELS_MAX];
+    dbus_uint64_t duration = 0;
+    dbus_uint32_t bytes = 0;
+    unsigned i = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    idx = s->sample->index;
+    if (s->sample->memchunk.memblock) {
+        sample_format = s->sample->sample_spec.format;
+        sample_rate = s->sample->sample_spec.rate;
+        for (i = 0; i < s->sample->channel_map.channels; ++i)
+            channels[i] = s->sample->channel_map.map[i];
+        duration = pa_bytes_to_usec(s->sample->memchunk.length, &s->sample->sample_spec);
+        bytes = s->sample->memchunk.length;
+    }
+    if (s->sample->volume_is_set) {
+        for (i = 0; i < s->sample->volume.channels; ++i)
+            default_volume[i] = s->sample->volume.values[i];
+    }
+
+    pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+    dbus_message_iter_init_append(reply, &msg_iter);
+    pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &s->sample->name);
+
+    if (s->sample->memchunk.memblock) {
+        pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_FORMAT].property_name, DBUS_TYPE_UINT32, &sample_format);
+        pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_RATE].property_name, DBUS_TYPE_UINT32, &sample_rate);
+        pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CHANNELS].property_name, DBUS_TYPE_UINT32, channels, s->sample->channel_map.channels);
+    }
+
+    if (s->sample->volume_is_set)
+        pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEFAULT_VOLUME].property_name, DBUS_TYPE_UINT32, default_volume, s->sample->volume.channels);
+
+    if (s->sample->memchunk.memblock) {
+        pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DURATION].property_name, DBUS_TYPE_UINT64, &duration);
+        pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_BYTES].property_name, DBUS_TYPE_UINT32, &bytes);
+    }
+
+    pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, s->proplist);
+
+    pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+    pa_assert_se(dbus_connection_send(conn, reply, NULL));
+    dbus_message_unref(reply);
+}
+
+static void handle_play(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_sample *s = userdata;
+    DBusMessageIter msg_iter;
+    dbus_uint32_t volume = 0;
+    pa_proplist *property_list = NULL;
+    pa_sink *sink = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    pa_assert_se(dbus_message_iter_init(msg, &msg_iter));
+    dbus_message_iter_get_basic(&msg_iter, &volume);
+
+    pa_assert_se(dbus_message_iter_next(&msg_iter));
+    if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter)))
+        return;
+
+    if (volume > PA_VOLUME_MAX) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume.");
+        goto finish;
+    }
+
+    if (!(sink = pa_namereg_get_default_sink(s->sample->core))) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED,
+                           "Can't play sample %s, because there are no sinks available.", s->sample->name);
+        goto finish;
+    }
+
+    if (pa_scache_play_item(s->sample->core, s->sample->name, sink, volume, property_list, NULL) < 0) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Playing sample %s failed.", s->sample->name);
+        goto finish;
+    }
+
+    pa_dbus_send_empty_reply(conn, msg);
+
+finish:
+    if (property_list)
+        pa_proplist_free(property_list);
+}
+
+static void handle_play_to_sink(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_sample *s = userdata;
+    DBusMessageIter msg_iter;
+    const char *sink_path = NULL;
+    dbus_uint32_t volume = 0;
+    pa_proplist *property_list = NULL;
+    pa_sink *sink = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    pa_assert_se(dbus_message_iter_init(msg, &msg_iter));
+    dbus_message_iter_get_basic(&msg_iter, &sink_path);
+
+    pa_assert_se(dbus_message_iter_next(&msg_iter));
+    dbus_message_iter_get_basic(&msg_iter, &volume);
+
+    pa_assert_se(dbus_message_iter_next(&msg_iter));
+    if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter)))
+        return;
+
+    if (!(sink = pa_dbusiface_core_get_sink(s->core, sink_path))) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such sink.", sink_path);
+        goto finish;
+    }
+
+    if (volume > PA_VOLUME_MAX) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume.");
+        goto finish;
+    }
+
+    if (pa_scache_play_item(s->sample->core, s->sample->name, sink, volume, property_list, NULL) < 0) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Playing sample %s failed.", s->sample->name);
+        goto finish;
+    }
+
+    pa_dbus_send_empty_reply(conn, msg);
+
+finish:
+    if (property_list)
+        pa_proplist_free(property_list);
+}
+
+static void handle_remove(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_sample *s = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    if (pa_scache_remove_item(s->sample->core, s->sample->name) < 0) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Removing sample %s failed.", s->sample->name);
+        return;
+    }
+
+    pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void subscription_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+    pa_dbusiface_sample *s = userdata;
+    DBusMessage *signal = NULL;
+
+    pa_assert(c);
+    pa_assert(s);
+
+    /* We can't use idx != s->sample->index, because the s->sample pointer may
+     * be stale at this point. */
+    if (pa_idxset_get_by_index(c->scache, idx) != s->sample)
+        return;
+
+    if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE)
+        return;
+
+    if (!pa_proplist_equal(s->proplist, s->sample->proplist)) {
+        DBusMessageIter msg_iter;
+
+        pa_proplist_update(s->proplist, PA_UPDATE_SET, s->sample->proplist);
+
+        pa_assert_se(signal = dbus_message_new_signal(s->path,
+                                                      PA_DBUSIFACE_SAMPLE_INTERFACE,
+                                                      signals[SIGNAL_PROPERTY_LIST_UPDATED].name));
+        dbus_message_iter_init_append(signal, &msg_iter);
+        pa_dbus_append_proplist(&msg_iter, s->proplist);
+
+        pa_dbus_protocol_send_signal(s->dbus_protocol, signal);
+        dbus_message_unref(signal);
+        signal = NULL;
+    }
+}
+
+pa_dbusiface_sample *pa_dbusiface_sample_new(pa_dbusiface_core *core, pa_scache_entry *sample) {
+    pa_dbusiface_sample *s = NULL;
+
+    pa_assert(core);
+    pa_assert(sample);
+
+    s = pa_xnew0(pa_dbusiface_sample, 1);
+    s->core = core;
+    s->sample = sample;
+    s->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, sample->index);
+    s->proplist = pa_proplist_copy(sample->proplist);
+    s->dbus_protocol = pa_dbus_protocol_get(sample->core);
+    s->subscription = pa_subscription_new(sample->core, PA_SUBSCRIPTION_MASK_SAMPLE_CACHE, subscription_cb, s);
+
+    pa_assert_se(pa_dbus_protocol_add_interface(s->dbus_protocol, s->path, &sample_interface_info, s) >= 0);
+
+    return s;
+}
+
+void pa_dbusiface_sample_free(pa_dbusiface_sample *s) {
+    pa_assert(s);
+
+    pa_assert_se(pa_dbus_protocol_remove_interface(s->dbus_protocol, s->path, sample_interface_info.name) >= 0);
+
+    pa_proplist_free(s->proplist);
+    pa_dbus_protocol_unref(s->dbus_protocol);
+    pa_subscription_free(s->subscription);
+
+    pa_xfree(s->path);
+    pa_xfree(s);
+}
+
+const char *pa_dbusiface_sample_get_path(pa_dbusiface_sample *s) {
+    pa_assert(s);
+
+    return s->path;
+}
diff --git a/src/modules/dbus/iface-sample.h b/src/modules/dbus/iface-sample.h
new file mode 100644 (file)
index 0000000..f1947ce
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef foodbusifacesamplehfoo
+#define foodbusifacesamplehfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+/* This object implements the D-Bus interface org.PulseAudio.Core1.Sample.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the Sample interface
+ * documentation.
+ */
+
+#include <pulsecore/core-scache.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-core.h"
+
+#define PA_DBUSIFACE_SAMPLE_INTERFACE PA_DBUS_CORE_INTERFACE ".Sample"
+
+typedef struct pa_dbusiface_sample pa_dbusiface_sample;
+
+pa_dbusiface_sample *pa_dbusiface_sample_new(pa_dbusiface_core *core, pa_scache_entry *sample);
+void pa_dbusiface_sample_free(pa_dbusiface_sample *c);
+
+const char *pa_dbusiface_sample_get_path(pa_dbusiface_sample *c);
+
+#endif
diff --git a/src/modules/dbus/iface-stream.c b/src/modules/dbus/iface-stream.c
new file mode 100644 (file)
index 0000000..a5f9bb5
--- /dev/null
@@ -0,0 +1,894 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+  Copyright 2009 Vincent Filali-Ansary <filali.v@azurdigitalnetworks.net>
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-stream.h"
+
+#define PLAYBACK_OBJECT_NAME "playback_stream"
+#define RECORD_OBJECT_NAME "record_stream"
+
+enum stream_type {
+    STREAM_TYPE_PLAYBACK,
+    STREAM_TYPE_RECORD
+};
+
+struct pa_dbusiface_stream {
+    pa_dbusiface_core *core;
+
+    union {
+        pa_sink_input *sink_input;
+        pa_source_output *source_output;
+    };
+    enum stream_type type;
+    char *path;
+    union {
+        pa_sink *sink;
+        pa_source *source;
+    };
+    uint32_t sample_rate;
+    pa_cvolume volume;
+    pa_bool_t is_muted;
+    pa_proplist *proplist;
+
+    pa_dbus_protocol *dbus_protocol;
+    pa_subscription *subscription;
+    pa_hook_slot *send_event_slot;
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_client(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_device(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_is_muted(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_is_muted(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_get_buffer_latency(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_device_latency(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_resample_method(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_move(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+enum property_handler_index {
+    PROPERTY_HANDLER_INDEX,
+    PROPERTY_HANDLER_DRIVER,
+    PROPERTY_HANDLER_OWNER_MODULE,
+    PROPERTY_HANDLER_CLIENT,
+    PROPERTY_HANDLER_DEVICE,
+    PROPERTY_HANDLER_SAMPLE_FORMAT,
+    PROPERTY_HANDLER_SAMPLE_RATE,
+    PROPERTY_HANDLER_CHANNELS,
+    PROPERTY_HANDLER_VOLUME,
+    PROPERTY_HANDLER_IS_MUTED,
+    PROPERTY_HANDLER_BUFFER_LATENCY,
+    PROPERTY_HANDLER_DEVICE_LATENCY,
+    PROPERTY_HANDLER_RESAMPLE_METHOD,
+    PROPERTY_HANDLER_PROPERTY_LIST,
+    PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+    [PROPERTY_HANDLER_INDEX]           = { .property_name = "Index",          .type = "u",      .get_cb = handle_get_index,           .set_cb = NULL },
+    [PROPERTY_HANDLER_DRIVER]          = { .property_name = "Driver",         .type = "s",      .get_cb = handle_get_driver,          .set_cb = NULL },
+    [PROPERTY_HANDLER_OWNER_MODULE]    = { .property_name = "OwnerModule",    .type = "o",      .get_cb = handle_get_owner_module,    .set_cb = NULL },
+    [PROPERTY_HANDLER_CLIENT]          = { .property_name = "Client",         .type = "o",      .get_cb = handle_get_client,          .set_cb = NULL },
+    [PROPERTY_HANDLER_DEVICE]          = { .property_name = "Device",         .type = "o",      .get_cb = handle_get_device,          .set_cb = NULL },
+    [PROPERTY_HANDLER_SAMPLE_FORMAT]   = { .property_name = "SampleFormat",   .type = "u",      .get_cb = handle_get_sample_format,   .set_cb = NULL },
+    [PROPERTY_HANDLER_SAMPLE_RATE]     = { .property_name = "SampleRate",     .type = "u",      .get_cb = handle_get_sample_rate,     .set_cb = NULL },
+    [PROPERTY_HANDLER_CHANNELS]        = { .property_name = "Channels",       .type = "au",     .get_cb = handle_get_channels,        .set_cb = NULL },
+    [PROPERTY_HANDLER_VOLUME]          = { .property_name = "Volume",         .type = "au",     .get_cb = handle_get_volume,          .set_cb = handle_set_volume },
+    [PROPERTY_HANDLER_IS_MUTED]        = { .property_name = "IsMuted",        .type = "b",      .get_cb = handle_get_is_muted,        .set_cb = handle_set_is_muted },
+    [PROPERTY_HANDLER_BUFFER_LATENCY]  = { .property_name = "BufferLatency",  .type = "t",      .get_cb = handle_get_buffer_latency,  .set_cb = NULL },
+    [PROPERTY_HANDLER_DEVICE_LATENCY]  = { .property_name = "DeviceLatency",  .type = "t",      .get_cb = handle_get_device_latency,  .set_cb = NULL },
+    [PROPERTY_HANDLER_RESAMPLE_METHOD] = { .property_name = "ResampleMethod", .type = "s",      .get_cb = handle_get_resample_method, .set_cb = NULL },
+    [PROPERTY_HANDLER_PROPERTY_LIST]   = { .property_name = "PropertyList",   .type = "a{say}", .get_cb = handle_get_property_list,   .set_cb = NULL }
+};
+
+enum method_handler_index {
+    METHOD_HANDLER_MOVE,
+    METHOD_HANDLER_KILL,
+    METHOD_HANDLER_MAX
+};
+
+static pa_dbus_arg_info move_args[] = { { "device", "o", "in" } };
+
+static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
+    [METHOD_HANDLER_MOVE] = {
+        .method_name = "Move",
+        .arguments = move_args,
+        .n_arguments = sizeof(move_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_move },
+    [METHOD_HANDLER_KILL] = {
+        .method_name = "Kill",
+        .arguments = NULL,
+        .n_arguments = 0,
+        .receive_cb = handle_kill }
+};
+
+enum signal_index {
+    SIGNAL_DEVICE_UPDATED,
+    SIGNAL_SAMPLE_RATE_UPDATED,
+    SIGNAL_VOLUME_UPDATED,
+    SIGNAL_MUTE_UPDATED,
+    SIGNAL_PROPERTY_LIST_UPDATED,
+    SIGNAL_STREAM_EVENT,
+    SIGNAL_MAX
+};
+
+static pa_dbus_arg_info device_updated_args[]        = { { "device",        "o",      NULL } };
+static pa_dbus_arg_info sample_rate_updated_args[]   = { { "sample_rate",   "u",      NULL } };
+static pa_dbus_arg_info volume_updated_args[]        = { { "volume",        "au",     NULL } };
+static pa_dbus_arg_info mute_updated_args[]          = { { "muted",         "b",      NULL } };
+static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } };
+static pa_dbus_arg_info stream_event_args[]          = { { "name",          "s",      NULL }, { "property_list", "a{say}", NULL } };
+
+static pa_dbus_signal_info signals[SIGNAL_MAX] = {
+    [SIGNAL_DEVICE_UPDATED]        = { .name = "DeviceUpdated",       .arguments = device_updated_args,        .n_arguments = 1 },
+    [SIGNAL_SAMPLE_RATE_UPDATED]   = { .name = "SampleRateUpdated",   .arguments = sample_rate_updated_args,   .n_arguments = 1 },
+    [SIGNAL_VOLUME_UPDATED]        = { .name = "VolumeUpdated",       .arguments = volume_updated_args,        .n_arguments = 1 },
+    [SIGNAL_MUTE_UPDATED]          = { .name = "MuteUpdated",         .arguments = mute_updated_args,          .n_arguments = 1 },
+    [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 },
+    [SIGNAL_STREAM_EVENT]          = { .name = "StreamEvent",         .arguments = stream_event_args,          .n_arguments = sizeof(stream_event_args) / sizeof(pa_dbus_arg_info) }
+};
+
+static pa_dbus_interface_info stream_interface_info = {
+    .name = PA_DBUSIFACE_STREAM_INTERFACE,
+    .method_handlers = method_handlers,
+    .n_method_handlers = METHOD_HANDLER_MAX,
+    .property_handlers = property_handlers,
+    .n_property_handlers = PROPERTY_HANDLER_MAX,
+    .get_all_properties_cb = handle_get_all,
+    .signals = signals,
+    .n_signals = SIGNAL_MAX
+};
+
+static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_stream *s = userdata;
+    dbus_uint32_t idx;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    idx = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->index : s->source_output->index;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx);
+}
+
+static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_stream *s = userdata;
+    const char *driver = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    driver = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->driver : s->source_output->driver;
+
+    if (!driver) {
+        if (s->type == STREAM_TYPE_PLAYBACK)
+            pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                               "Playback stream %u doesn't have a driver.", s->sink_input->index);
+        else
+            pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                               "Record stream %u doesn't have a driver.", s->source_output->index);
+        return;
+    }
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &driver);
+}
+
+static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_stream *s = userdata;
+    pa_module *owner_module = NULL;
+    const char *object_path = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    owner_module = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->module : s->source_output->module;
+
+    if (!owner_module) {
+        if (s->type == STREAM_TYPE_PLAYBACK)
+            pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                               "Playback stream %u doesn't have an owner module.", s->sink_input->index);
+        else
+            pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                               "Record stream %u doesn't have an owner module.", s->source_output->index);
+        return;
+    }
+
+    object_path = pa_dbusiface_core_get_module_path(s->core, owner_module);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+}
+
+static void handle_get_client(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_stream *s = userdata;
+    pa_client *client = NULL;
+    const char *object_path = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    client = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->client : s->source_output->client;
+
+    if (!client) {
+        if (s->type == STREAM_TYPE_PLAYBACK)
+            pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                               "Playback stream %u isn't associated to any client.", s->sink_input->index);
+        else
+            pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY,
+                               "Record stream %u isn't associated to any client.", s->source_output->index);
+        return;
+    }
+
+    object_path = pa_dbusiface_core_get_client_path(s->core, client);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &object_path);
+}
+
+static void handle_get_device(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_stream *s = userdata;
+    const char *device = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    if (s->type == STREAM_TYPE_PLAYBACK)
+        device = pa_dbusiface_core_get_sink_path(s->core, s->sink);
+    else
+        device = pa_dbusiface_core_get_source_path(s->core, s->source);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &device);
+}
+
+static void handle_get_sample_format(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_stream *s = userdata;
+    dbus_uint32_t sample_format = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    sample_format = (s->type == STREAM_TYPE_PLAYBACK)
+                    ? s->sink_input->sample_spec.format
+                    : s->source_output->sample_spec.format;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &sample_format);
+}
+
+static void handle_get_sample_rate(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_stream *s = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &s->sample_rate);
+}
+
+static void handle_get_channels(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_stream *s = userdata;
+    pa_channel_map *channel_map = NULL;
+    dbus_uint32_t channels[PA_CHANNELS_MAX];
+    unsigned i = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    channel_map = (s->type == STREAM_TYPE_PLAYBACK) ? &s->sink_input->channel_map : &s->source_output->channel_map;
+
+    for (i = 0; i < channel_map->channels; ++i)
+        channels[i] = channel_map->map[i];
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, channels, channel_map->channels);
+}
+
+static void handle_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_stream *s = userdata;
+    dbus_uint32_t volume[PA_CHANNELS_MAX];
+    unsigned i = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    if (s->type == STREAM_TYPE_RECORD) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Record streams don't have volume.");
+        return;
+    }
+
+    for (i = 0; i < s->volume.channels; ++i)
+        volume[i] = s->volume.values[i];
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_UINT32, volume, s->volume.channels);
+}
+
+static void handle_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+    pa_dbusiface_stream *s = userdata;
+    DBusMessageIter array_iter;
+    int stream_channels = 0;
+    dbus_uint32_t *volume = NULL;
+    int n_volume_entries = 0;
+    pa_cvolume new_vol;
+    int i = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(iter);
+    pa_assert(s);
+
+    if (s->type == STREAM_TYPE_RECORD) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Record streams don't have volume.");
+        return;
+    }
+
+    pa_cvolume_init(&new_vol);
+
+    stream_channels = s->sink_input->channel_map.channels;
+
+    new_vol.channels = stream_channels;
+
+    dbus_message_iter_recurse(iter, &array_iter);
+    dbus_message_iter_get_fixed_array(&array_iter, &volume, &n_volume_entries);
+
+    if (n_volume_entries != stream_channels) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS,
+                           "Expected %u volume entries, got %u.", stream_channels, n_volume_entries);
+        return;
+    }
+
+    for (i = 0; i < n_volume_entries; ++i) {
+        if (volume[i] > PA_VOLUME_MAX) {
+            pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Too large volume value: %u", volume[i]);
+            return;
+        }
+        new_vol.values[i] = volume[i];
+    }
+
+    pa_sink_input_set_volume(s->sink_input, &new_vol, TRUE, TRUE);
+
+    pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void handle_get_is_muted(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_stream *s = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    if (s->type == STREAM_TYPE_RECORD) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Record streams don't have mute.");
+        return;
+    }
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &s->is_muted);
+}
+
+static void handle_set_is_muted(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+    pa_dbusiface_stream *s = userdata;
+    dbus_bool_t is_muted = FALSE;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(iter);
+    pa_assert(s);
+
+    dbus_message_iter_get_basic(iter, &is_muted);
+
+    if (s->type == STREAM_TYPE_RECORD) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Record streams don't have mute.");
+        return;
+    }
+
+    pa_sink_input_set_mute(s->sink_input, is_muted, TRUE);
+
+    pa_dbus_send_empty_reply(conn, msg);
+};
+
+static void handle_get_buffer_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_stream *s = userdata;
+    dbus_uint64_t buffer_latency = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    if (s->type == STREAM_TYPE_PLAYBACK)
+        buffer_latency = pa_sink_input_get_latency(s->sink_input, NULL);
+    else
+        buffer_latency = pa_source_output_get_latency(s->source_output, NULL);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &buffer_latency);
+}
+
+static void handle_get_device_latency(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_stream *s = userdata;
+    dbus_uint64_t device_latency = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    if (s->type == STREAM_TYPE_PLAYBACK)
+        pa_sink_input_get_latency(s->sink_input, &device_latency);
+    else
+        pa_source_output_get_latency(s->source_output, &device_latency);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT64, &device_latency);
+}
+
+static void handle_get_resample_method(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_stream *s = userdata;
+    const char *resample_method = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    if (s->type == STREAM_TYPE_PLAYBACK)
+        resample_method = pa_resample_method_to_string(s->sink_input->actual_resample_method);
+    else
+        resample_method = pa_resample_method_to_string(s->source_output->actual_resample_method);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &resample_method);
+}
+
+static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_stream *s = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    pa_dbus_send_proplist_variant_reply(conn, msg, s->proplist);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_stream *s = userdata;
+    DBusMessage *reply = NULL;
+    DBusMessageIter msg_iter;
+    DBusMessageIter dict_iter;
+    dbus_uint32_t idx = 0;
+    const char *driver = NULL;
+    pa_module *owner_module = NULL;
+    const char *owner_module_path = NULL;
+    pa_client *client = NULL;
+    const char *client_path = NULL;
+    const char *device = NULL;
+    dbus_uint32_t sample_format = 0;
+    pa_channel_map *channel_map = NULL;
+    dbus_uint32_t channels[PA_CHANNELS_MAX];
+    dbus_uint32_t volume[PA_CHANNELS_MAX];
+    dbus_uint64_t buffer_latency = 0;
+    dbus_uint64_t device_latency = 0;
+    const char *resample_method = NULL;
+    unsigned i = 0;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    if (s->type == STREAM_TYPE_PLAYBACK) {
+        idx = s->sink_input->index;
+        driver = s->sink_input->driver;
+        owner_module = s->sink_input->module;
+        client = s->sink_input->client;
+        device = pa_dbusiface_core_get_sink_path(s->core, s->sink);
+        sample_format = s->sink_input->sample_spec.format;
+        channel_map = &s->sink_input->channel_map;
+        for (i = 0; i < s->volume.channels; ++i)
+            volume[i] = s->volume.values[i];
+        buffer_latency = pa_sink_input_get_latency(s->sink_input, &device_latency);
+        resample_method = pa_resample_method_to_string(s->sink_input->actual_resample_method);
+    } else {
+        idx = s->source_output->index;
+        driver = s->source_output->driver;
+        owner_module = s->source_output->module;
+        client = s->source_output->client;
+        device = pa_dbusiface_core_get_source_path(s->core, s->source);
+        sample_format = s->source_output->sample_spec.format;
+        channel_map = &s->source_output->channel_map;
+        buffer_latency = pa_source_output_get_latency(s->source_output, &device_latency);
+        resample_method = pa_resample_method_to_string(s->source_output->actual_resample_method);
+    }
+    if (owner_module)
+        owner_module_path = pa_dbusiface_core_get_module_path(s->core, owner_module);
+    if (client)
+        client_path = pa_dbusiface_core_get_client_path(s->core, client);
+    for (i = 0; i < channel_map->channels; ++i)
+        channels[i] = channel_map->map[i];
+
+    pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+    dbus_message_iter_init_append(reply, &msg_iter);
+    pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx);
+
+    if (driver)
+        pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &driver);
+
+    if (owner_module)
+        pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_OWNER_MODULE].property_name, DBUS_TYPE_OBJECT_PATH, &owner_module_path);
+
+    if (client)
+        pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CLIENT].property_name, DBUS_TYPE_OBJECT_PATH, &client_path);
+
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_FORMAT].property_name, DBUS_TYPE_UINT32, &sample_format);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SAMPLE_RATE].property_name, DBUS_TYPE_UINT32, &s->sample_rate);
+    pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CHANNELS].property_name, DBUS_TYPE_UINT32, channels, channel_map->channels);
+
+    if (s->type == STREAM_TYPE_PLAYBACK) {
+        pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_VOLUME].property_name, DBUS_TYPE_UINT32, volume, s->volume.channels);
+        pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_IS_MUTED].property_name, DBUS_TYPE_BOOLEAN, &s->is_muted);
+    }
+
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_BUFFER_LATENCY].property_name, DBUS_TYPE_UINT64, &buffer_latency);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DEVICE_LATENCY].property_name, DBUS_TYPE_UINT64, &device_latency);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_RESAMPLE_METHOD].property_name, DBUS_TYPE_STRING, &resample_method);
+    pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, s->proplist);
+
+    pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+    pa_assert_se(dbus_connection_send(conn, reply, NULL));
+    dbus_message_unref(reply);
+}
+
+static void handle_move(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_stream *s = userdata;
+    const char *device = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &device, DBUS_TYPE_INVALID));
+
+    if (s->type == STREAM_TYPE_PLAYBACK) {
+        pa_sink *sink = pa_dbusiface_core_get_sink(s->core, device);
+
+        if (!sink) {
+            pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such sink.", device);
+            return;
+        }
+
+        if (pa_sink_input_move_to(s->sink_input, sink, TRUE) < 0) {
+            pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED,
+                               "Moving playback stream %u to sink %s failed.", s->sink_input->index, sink->name);
+            return;
+        }
+    } else {
+        pa_source *source = pa_dbusiface_core_get_source(s->core, device);
+
+        if (!source) {
+            pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such source.", device);
+            return;
+        }
+
+        if (pa_source_output_move_to(s->source_output, source, TRUE) < 0) {
+            pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED,
+                               "Moving record stream %u to source %s failed.", s->source_output->index, source->name);
+            return;
+        }
+    }
+
+    pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    pa_dbusiface_stream *s = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(s);
+
+    if (s->type == STREAM_TYPE_PLAYBACK)
+        pa_sink_input_kill(s->sink_input);
+    else
+        pa_source_output_kill(s->source_output);
+
+    pa_dbus_send_empty_reply(conn, msg);
+}
+
+static void subscription_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+    pa_dbusiface_stream *s = userdata;
+    DBusMessage *signal = NULL;
+    const char *new_device_path = NULL;
+    uint32_t new_sample_rate = 0;
+    pa_proplist *new_proplist = NULL;
+    unsigned i = 0;
+
+    pa_assert(c);
+    pa_assert(s);
+
+    if ((s->type == STREAM_TYPE_PLAYBACK && idx != s->sink_input->index)
+        || (s->type == STREAM_TYPE_RECORD && idx != s->source_output->index))
+        return;
+
+    if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE)
+        return;
+
+    pa_assert(((s->type == STREAM_TYPE_PLAYBACK)
+                && ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT))
+              || ((s->type == STREAM_TYPE_RECORD)
+                   && ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT)));
+
+    if (s->type == STREAM_TYPE_PLAYBACK) {
+        pa_sink *new_sink = s->sink_input->sink;
+
+        if (s->sink != new_sink) {
+            pa_sink_unref(s->sink);
+            s->sink = pa_sink_ref(new_sink);
+
+            new_device_path = pa_dbusiface_core_get_sink_path(s->core, new_sink);
+
+            pa_assert_se(signal = dbus_message_new_signal(s->path,
+                                                          PA_DBUSIFACE_STREAM_INTERFACE,
+                                                          signals[SIGNAL_DEVICE_UPDATED].name));
+            pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &new_device_path, DBUS_TYPE_INVALID));
+
+            pa_dbus_protocol_send_signal(s->dbus_protocol, signal);
+            dbus_message_unref(signal);
+            signal = NULL;
+        }
+    } else {
+        pa_source *new_source = s->source_output->source;
+
+        if (s->source != new_source) {
+            pa_source_unref(s->source);
+            s->source = pa_source_ref(new_source);
+
+            new_device_path = pa_dbusiface_core_get_source_path(s->core, new_source);
+
+            pa_assert_se(signal = dbus_message_new_signal(s->path,
+                                                          PA_DBUSIFACE_STREAM_INTERFACE,
+                                                          signals[SIGNAL_DEVICE_UPDATED].name));
+            pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &new_device_path, DBUS_TYPE_INVALID));
+
+            pa_dbus_protocol_send_signal(s->dbus_protocol, signal);
+            dbus_message_unref(signal);
+            signal = NULL;
+        }
+    }
+
+    new_sample_rate = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->sample_spec.rate : s->source_output->sample_spec.rate;
+
+    if (s->sample_rate != new_sample_rate) {
+        s->sample_rate = new_sample_rate;
+
+        pa_assert_se(signal = dbus_message_new_signal(s->path,
+                                                      PA_DBUSIFACE_STREAM_INTERFACE,
+                                                      signals[SIGNAL_SAMPLE_RATE_UPDATED].name));
+        pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_UINT32, &s->sample_rate, DBUS_TYPE_INVALID));
+
+        pa_dbus_protocol_send_signal(s->dbus_protocol, signal);
+        dbus_message_unref(signal);
+        signal = NULL;
+    }
+
+    if (s->type == STREAM_TYPE_PLAYBACK) {
+        pa_cvolume new_volume;
+        pa_bool_t new_muted = FALSE;
+
+        pa_sink_input_get_volume(s->sink_input, &new_volume, TRUE);
+
+        if (!pa_cvolume_equal(&s->volume, &new_volume)) {
+            dbus_uint32_t volume[PA_CHANNELS_MAX];
+            dbus_uint32_t *volume_ptr = volume;
+
+            s->volume = new_volume;
+
+            for (i = 0; i < s->volume.channels; ++i)
+                volume[i] = s->volume.values[i];
+
+            pa_assert_se(signal = dbus_message_new_signal(s->path,
+                                                          PA_DBUSIFACE_STREAM_INTERFACE,
+                                                          signals[SIGNAL_VOLUME_UPDATED].name));
+            pa_assert_se(dbus_message_append_args(signal,
+                                                  DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &volume_ptr, s->volume.channels,
+                                                  DBUS_TYPE_INVALID));
+
+            pa_dbus_protocol_send_signal(s->dbus_protocol, signal);
+            dbus_message_unref(signal);
+            signal = NULL;
+        }
+
+        new_muted = pa_sink_input_get_mute(s->sink_input);
+
+        if (s->is_muted != new_muted) {
+            s->is_muted = new_muted;
+
+            pa_assert_se(signal = dbus_message_new_signal(s->path,
+                                                          PA_DBUSIFACE_STREAM_INTERFACE,
+                                                          signals[SIGNAL_MUTE_UPDATED].name));
+            pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_BOOLEAN, &s->is_muted, DBUS_TYPE_INVALID));
+
+            pa_dbus_protocol_send_signal(s->dbus_protocol, signal);
+            dbus_message_unref(signal);
+            signal = NULL;
+        }
+    }
+
+    new_proplist = (s->type == STREAM_TYPE_PLAYBACK) ? s->sink_input->proplist : s->source_output->proplist;
+
+    if (!pa_proplist_equal(s->proplist, new_proplist)) {
+        DBusMessageIter msg_iter;
+
+        pa_proplist_update(s->proplist, PA_UPDATE_SET, new_proplist);
+
+        pa_assert_se(signal = dbus_message_new_signal(s->path,
+                                                      PA_DBUSIFACE_STREAM_INTERFACE,
+                                                      signals[SIGNAL_PROPERTY_LIST_UPDATED].name));
+        dbus_message_iter_init_append(signal, &msg_iter);
+        pa_dbus_append_proplist(&msg_iter, s->proplist);
+
+        pa_dbus_protocol_send_signal(s->dbus_protocol, signal);
+        dbus_message_unref(signal);
+        signal = NULL;
+    }
+}
+
+static pa_hook_result_t send_event_cb(void *hook_data, void *call_data, void *slot_data) {
+    pa_dbusiface_stream *s = slot_data;
+    DBusMessage *signal = NULL;
+    DBusMessageIter msg_iter;
+    const char *name = NULL;
+    pa_proplist *property_list = NULL;
+
+    pa_assert(call_data);
+    pa_assert(s);
+
+    if (s->type == STREAM_TYPE_PLAYBACK) {
+        pa_sink_input_send_event_hook_data *data = call_data;
+
+        if (data->sink_input != s->sink_input)
+            return PA_HOOK_OK;
+
+        name = data->event;
+        property_list = data->data;
+    } else {
+        pa_source_output_send_event_hook_data *data = call_data;
+
+        if (data->source_output != s->source_output)
+            return PA_HOOK_OK;
+
+        name = data->event;
+        property_list = data->data;
+    }
+
+    pa_assert_se(signal = dbus_message_new_signal(s->path,
+                                                  PA_DBUSIFACE_STREAM_INTERFACE,
+                                                  signals[SIGNAL_STREAM_EVENT].name));
+    dbus_message_iter_init_append(signal, &msg_iter);
+    pa_assert_se(dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &name));
+    pa_dbus_append_proplist(&msg_iter, property_list);
+
+    pa_dbus_protocol_send_signal(s->dbus_protocol, signal);
+    dbus_message_unref(signal);
+
+    return PA_HOOK_OK;
+}
+
+pa_dbusiface_stream *pa_dbusiface_stream_new_playback(pa_dbusiface_core *core, pa_sink_input *sink_input) {
+    pa_dbusiface_stream *s;
+
+    pa_assert(core);
+    pa_assert(sink_input);
+
+    s = pa_xnew(pa_dbusiface_stream, 1);
+    s->core = core;
+    s->sink_input = pa_sink_input_ref(sink_input);
+    s->type = STREAM_TYPE_PLAYBACK;
+    s->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, PLAYBACK_OBJECT_NAME, sink_input->index);
+    s->sink = pa_sink_ref(sink_input->sink);
+    s->sample_rate = sink_input->sample_spec.rate;
+    pa_sink_input_get_volume(sink_input, &s->volume, TRUE);
+    s->is_muted = pa_sink_input_get_mute(sink_input);
+    s->proplist = pa_proplist_copy(sink_input->proplist);
+    s->dbus_protocol = pa_dbus_protocol_get(sink_input->core);
+    s->subscription = pa_subscription_new(sink_input->core, PA_SUBSCRIPTION_MASK_SINK_INPUT, subscription_cb, s);
+    s->send_event_slot = pa_hook_connect(&sink_input->core->hooks[PA_CORE_HOOK_SINK_INPUT_SEND_EVENT],
+                                         PA_HOOK_NORMAL,
+                                         send_event_cb,
+                                         s);
+
+    pa_assert_se(pa_dbus_protocol_add_interface(s->dbus_protocol, s->path, &stream_interface_info, s) >= 0);
+
+    return s;
+}
+
+pa_dbusiface_stream *pa_dbusiface_stream_new_record(pa_dbusiface_core *core, pa_source_output *source_output) {
+    pa_dbusiface_stream *s;
+
+    pa_assert(core);
+    pa_assert(source_output);
+
+    s = pa_xnew(pa_dbusiface_stream, 1);
+    s->core = core;
+    s->source_output = pa_source_output_ref(source_output);
+    s->type = STREAM_TYPE_RECORD;
+    s->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, RECORD_OBJECT_NAME, source_output->index);
+    s->source = pa_source_ref(source_output->source);
+    s->sample_rate = source_output->sample_spec.rate;
+    pa_cvolume_init(&s->volume);
+    s->is_muted = FALSE;
+    s->proplist = pa_proplist_copy(source_output->proplist);
+    s->dbus_protocol = pa_dbus_protocol_get(source_output->core);
+    s->subscription = pa_subscription_new(source_output->core, PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscription_cb, s);
+    s->send_event_slot = pa_hook_connect(&source_output->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_SEND_EVENT],
+                                         PA_HOOK_NORMAL,
+                                         send_event_cb,
+                                         s);
+
+    pa_assert_se(pa_dbus_protocol_add_interface(s->dbus_protocol, s->path, &stream_interface_info, s) >= 0);
+
+    return s;
+}
+
+void pa_dbusiface_stream_free(pa_dbusiface_stream *s) {
+    pa_assert(s);
+
+    pa_assert_se(pa_dbus_protocol_remove_interface(s->dbus_protocol, s->path, stream_interface_info.name) >= 0);
+
+    if (s->type == STREAM_TYPE_PLAYBACK) {
+        pa_sink_input_unref(s->sink_input);
+        pa_sink_unref(s->sink);
+    } else {
+        pa_source_output_unref(s->source_output);
+        pa_source_unref(s->source);
+    }
+
+    pa_proplist_free(s->proplist);
+    pa_dbus_protocol_unref(s->dbus_protocol);
+    pa_subscription_free(s->subscription);
+    pa_hook_slot_free(s->send_event_slot);
+
+    pa_xfree(s->path);
+    pa_xfree(s);
+}
+
+const char *pa_dbusiface_stream_get_path(pa_dbusiface_stream *s) {
+    pa_assert(s);
+
+    return s->path;
+}
diff --git a/src/modules/dbus/iface-stream.h b/src/modules/dbus/iface-stream.h
new file mode 100644 (file)
index 0000000..036b4e7
--- /dev/null
@@ -0,0 +1,47 @@
+#ifndef foodbusifacestreamhfoo
+#define foodbusifacestreamhfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+/* This object implements the D-Bus interface org.PulseAudio.Core1.Stream.
+ *
+ * See http://pulseaudio.org/wiki/DBusInterface for the Stream interface
+ * documentation.
+ */
+
+#include <pulsecore/protocol-dbus.h>
+#include <pulsecore/sink-input.h>
+#include <pulsecore/source-output.h>
+
+#include "iface-core.h"
+
+#define PA_DBUSIFACE_STREAM_INTERFACE PA_DBUS_CORE_INTERFACE ".Stream"
+
+typedef struct pa_dbusiface_stream pa_dbusiface_stream;
+
+pa_dbusiface_stream *pa_dbusiface_stream_new_playback(pa_dbusiface_core *core, pa_sink_input *sink_input);
+pa_dbusiface_stream *pa_dbusiface_stream_new_record(pa_dbusiface_core *core, pa_source_output *source_output);
+void pa_dbusiface_stream_free(pa_dbusiface_stream *s);
+
+const char *pa_dbusiface_stream_get_path(pa_dbusiface_stream *s);
+
+#endif
diff --git a/src/modules/dbus/module-dbus-protocol.c b/src/modules/dbus/module-dbus-protocol.c
new file mode 100644 (file)
index 0000000..11064c3
--- /dev/null
@@ -0,0 +1,607 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+  Copyright 2006 Lennart Poettering
+  Copyright 2006 Shams E. King
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <dbus/dbus.h>
+
+#include <pulse/mainloop-api.h>
+#include <pulse/timeval.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/client.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/idxset.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/module.h>
+#include <pulsecore/protocol-dbus.h>
+
+#include "iface-client.h"
+#include "iface-core.h"
+
+#include "module-dbus-protocol-symdef.h"
+
+PA_MODULE_DESCRIPTION("D-Bus interface");
+PA_MODULE_USAGE(
+        "access=local|remote|local,remote "
+        "tcp_port=<port number>");
+PA_MODULE_LOAD_ONCE(TRUE);
+PA_MODULE_AUTHOR("Tanu Kaskinen");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+
+#define CLEANUP_INTERVAL 10 /* seconds */
+
+enum server_type {
+    SERVER_TYPE_LOCAL,
+    SERVER_TYPE_TCP
+};
+
+struct server;
+struct connection;
+
+struct userdata {
+    pa_module *module;
+    pa_bool_t local_access;
+    pa_bool_t remote_access;
+    uint32_t tcp_port;
+
+    struct server *local_server;
+    struct server *tcp_server;
+
+    pa_idxset *connections;
+
+    pa_time_event *cleanup_event;
+
+    pa_dbus_protocol *dbus_protocol;
+    pa_dbusiface_core *core_iface;
+};
+
+struct server {
+    struct userdata *userdata;
+    enum server_type type;
+    DBusServer *dbus_server;
+};
+
+struct connection {
+    struct server *server;
+    pa_dbus_wrap_connection *wrap_conn;
+    pa_client *client;
+};
+
+static const char* const valid_modargs[] = {
+    "access",
+    "tcp_port",
+    NULL
+};
+
+static void connection_free(struct connection *c) {
+    pa_assert(c);
+
+    pa_assert_se(pa_dbus_protocol_unregister_connection(c->server->userdata->dbus_protocol, pa_dbus_wrap_connection_get(c->wrap_conn)) >= 0);
+
+    pa_client_free(c->client);
+    pa_assert_se(pa_idxset_remove_by_data(c->server->userdata->connections, c, NULL));
+    pa_dbus_wrap_connection_free(c->wrap_conn);
+    pa_xfree(c);
+}
+
+/* Called from pa_client_kill(). */
+static void client_kill_cb(pa_client *c) {
+    struct connection *conn;
+
+    pa_assert(c);
+    pa_assert(c->userdata);
+
+    conn = c->userdata;
+    connection_free(conn);
+    c->userdata = NULL;
+
+    pa_log_info("Connection killed.");
+}
+
+/* Called from pa_client_send_event(). */
+static void client_send_event_cb(pa_client *c, const char *name, pa_proplist *data) {
+    struct connection *conn = NULL;
+    DBusMessage *signal = NULL;
+    DBusMessageIter msg_iter;
+
+    pa_assert(c);
+    pa_assert(name);
+    pa_assert(data);
+    pa_assert(c->userdata);
+
+    conn = c->userdata;
+
+    pa_assert_se(signal = dbus_message_new_signal(pa_dbusiface_core_get_client_path(conn->server->userdata->core_iface, c),
+                                                  PA_DBUSIFACE_CLIENT_INTERFACE,
+                                                  "ClientEvent"));
+    dbus_message_iter_init_append(signal, &msg_iter);
+    pa_assert_se(dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_STRING, &name));
+    pa_dbus_append_proplist(&msg_iter, data);
+
+    pa_assert_se(dbus_connection_send(pa_dbus_wrap_connection_get(conn->wrap_conn), signal, NULL));
+    dbus_message_unref(signal);
+}
+
+/* Called by D-Bus at the authentication phase. */
+static dbus_bool_t user_check_cb(DBusConnection *connection, unsigned long uid, void *data) {
+    pa_log_debug("Allowing connection by user %lu.", uid);
+
+    return TRUE;
+}
+
+/* Called by D-Bus when a new client connection is received. */
+static void connection_new_cb(DBusServer *dbus_server, DBusConnection *new_connection, void *data) {
+    struct server *s = data;
+    struct connection *c;
+    pa_client_new_data new_data;
+    pa_client *client;
+
+    pa_assert(new_connection);
+    pa_assert(s);
+
+    pa_client_new_data_init(&new_data);
+    new_data.module = s->userdata->module;
+    new_data.driver = __FILE__;
+    pa_proplist_sets(new_data.proplist, PA_PROP_APPLICATION_NAME, "D-Bus client");
+    client = pa_client_new(s->userdata->module->core, &new_data);
+    pa_client_new_data_done(&new_data);
+
+    if (!client) {
+        dbus_connection_close(new_connection);
+        return;
+    }
+
+    if (s->type == SERVER_TYPE_TCP || s->userdata->module->core->server_type == PA_SERVER_TYPE_SYSTEM) {
+        /* FIXME: Here we allow anyone from anywhere to access the server,
+         * anonymously. Access control should be configurable. */
+        dbus_connection_set_unix_user_function(new_connection, user_check_cb, NULL, NULL);
+        dbus_connection_set_allow_anonymous(new_connection, TRUE);
+    }
+
+    c = pa_xnew(struct connection, 1);
+    c->server = s;
+    c->wrap_conn = pa_dbus_wrap_connection_new_from_existing(s->userdata->module->core->mainloop, TRUE, new_connection);
+    c->client = client;
+
+    c->client->kill = client_kill_cb;
+    c->client->send_event = client_send_event_cb;
+    c->client->userdata = c;
+
+    pa_idxset_put(s->userdata->connections, c, NULL);
+
+    pa_assert_se(pa_dbus_protocol_register_connection(s->userdata->dbus_protocol, new_connection, c->client) >= 0);
+}
+
+/* Called by PA mainloop when a D-Bus fd watch event needs handling. */
+static void io_event_cb(pa_mainloop_api *mainloop, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
+    unsigned int flags = 0;
+    DBusWatch *watch = userdata;
+
+#if HAVE_DBUS_WATCH_GET_UNIX_FD
+    pa_assert(fd == dbus_watch_get_unix_fd(watch));
+#else
+    pa_assert(fd == dbus_watch_get_fd(watch));
+#endif
+
+    if (!dbus_watch_get_enabled(watch)) {
+        pa_log_warn("Asked to handle disabled watch: %p %i", (void*) watch, fd);
+        return;
+    }
+
+    if (events & PA_IO_EVENT_INPUT)
+        flags |= DBUS_WATCH_READABLE;
+    if (events & PA_IO_EVENT_OUTPUT)
+        flags |= DBUS_WATCH_WRITABLE;
+    if (events & PA_IO_EVENT_HANGUP)
+        flags |= DBUS_WATCH_HANGUP;
+    if (events & PA_IO_EVENT_ERROR)
+        flags |= DBUS_WATCH_ERROR;
+
+    dbus_watch_handle(watch, flags);
+}
+
+/* Called by PA mainloop when a D-Bus timer event needs handling. */
+static void time_event_cb(pa_mainloop_api *mainloop, pa_time_event* e, const struct timeval *tv, void *userdata) {
+    DBusTimeout *timeout = userdata;
+
+    if (dbus_timeout_get_enabled(timeout)) {
+        struct timeval next = *tv;
+        dbus_timeout_handle(timeout);
+
+        /* restart it for the next scheduled time */
+        pa_timeval_add(&next, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000);
+        mainloop->time_restart(e, &next);
+    }
+}
+
+/* Translates D-Bus fd watch event flags to PA IO event flags. */
+static pa_io_event_flags_t get_watch_flags(DBusWatch *watch) {
+    unsigned int flags;
+    pa_io_event_flags_t events = 0;
+
+    pa_assert(watch);
+
+    flags = dbus_watch_get_flags(watch);
+
+    /* no watch flags for disabled watches */
+    if (!dbus_watch_get_enabled(watch))
+        return PA_IO_EVENT_NULL;
+
+    if (flags & DBUS_WATCH_READABLE)
+        events |= PA_IO_EVENT_INPUT;
+    if (flags & DBUS_WATCH_WRITABLE)
+        events |= PA_IO_EVENT_OUTPUT;
+
+    return events | PA_IO_EVENT_HANGUP | PA_IO_EVENT_ERROR;
+}
+
+/* Called by D-Bus when a D-Bus fd watch event is added. */
+static dbus_bool_t watch_add_cb(DBusWatch *watch, void *data) {
+    struct server *s = data;
+    pa_mainloop_api *mainloop;
+    pa_io_event *ev;
+
+    pa_assert(watch);
+    pa_assert(s);
+
+    mainloop = s->userdata->module->core->mainloop;
+
+    ev = mainloop->io_new(
+            mainloop,
+#if HAVE_DBUS_WATCH_GET_UNIX_FD
+            dbus_watch_get_unix_fd(watch),
+#else
+            dbus_watch_get_fd(watch),
+#endif
+            get_watch_flags(watch), io_event_cb, watch);
+
+    dbus_watch_set_data(watch, ev, NULL);
+
+    return TRUE;
+}
+
+/* Called by D-Bus when a D-Bus fd watch event is removed. */
+static void watch_remove_cb(DBusWatch *watch, void *data) {
+    struct server *s = data;
+    pa_io_event *ev;
+
+    pa_assert(watch);
+    pa_assert(s);
+
+    if ((ev = dbus_watch_get_data(watch)))
+        s->userdata->module->core->mainloop->io_free(ev);
+}
+
+/* Called by D-Bus when a D-Bus fd watch event is toggled. */
+static void watch_toggled_cb(DBusWatch *watch, void *data) {
+    struct server *s = data;
+    pa_io_event *ev;
+
+    pa_assert(watch);
+    pa_assert(s);
+
+    pa_assert_se(ev = dbus_watch_get_data(watch));
+
+    /* get_watch_flags() checks if the watch is enabled */
+    s->userdata->module->core->mainloop->io_enable(ev, get_watch_flags(watch));
+}
+
+/* Called by D-Bus when a D-Bus timer event is added. */
+static dbus_bool_t timeout_add_cb(DBusTimeout *timeout, void *data) {
+    struct server *s = data;
+    pa_mainloop_api *mainloop;
+    pa_time_event *ev;
+    struct timeval tv;
+
+    pa_assert(timeout);
+    pa_assert(s);
+
+    if (!dbus_timeout_get_enabled(timeout))
+        return FALSE;
+
+    mainloop = s->userdata->module->core->mainloop;
+
+    pa_gettimeofday(&tv);
+    pa_timeval_add(&tv, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000);
+
+    ev = mainloop->time_new(mainloop, &tv, time_event_cb, timeout);
+
+    dbus_timeout_set_data(timeout, ev, NULL);
+
+    return TRUE;
+}
+
+/* Called by D-Bus when a D-Bus timer event is removed. */
+static void timeout_remove_cb(DBusTimeout *timeout, void *data) {
+    struct server *s = data;
+    pa_time_event *ev;
+
+    pa_assert(timeout);
+    pa_assert(s);
+
+    if ((ev = dbus_timeout_get_data(timeout)))
+        s->userdata->module->core->mainloop->time_free(ev);
+}
+
+/* Called by D-Bus when a D-Bus timer event is toggled. */
+static void timeout_toggled_cb(DBusTimeout *timeout, void *data) {
+    struct server *s = data;
+    pa_mainloop_api *mainloop;
+    pa_time_event *ev;
+
+    pa_assert(timeout);
+    pa_assert(s);
+
+    mainloop = s->userdata->module->core->mainloop;
+
+    pa_assert_se(ev = dbus_timeout_get_data(timeout));
+
+    if (dbus_timeout_get_enabled(timeout)) {
+        struct timeval tv;
+
+        pa_gettimeofday(&tv);
+        pa_timeval_add(&tv, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000);
+
+        mainloop->time_restart(ev, &tv);
+    } else
+        mainloop->time_restart(ev, NULL);
+}
+
+static void server_free(struct server *s) {
+    pa_assert(s);
+
+    if (s->dbus_server) {
+        dbus_server_disconnect(s->dbus_server);
+        dbus_server_unref(s->dbus_server);
+    }
+
+    pa_xfree(s);
+}
+
+static struct server *start_server(struct userdata *u, const char *address, enum server_type type) {
+    /* XXX: We assume that when we unref the DBusServer instance at module
+     * shutdown, nobody else holds any references to it. If we stop assuming
+     * that someday, dbus_server_set_new_connection_function,
+     * dbus_server_set_watch_functions and dbus_server_set_timeout_functions
+     * calls should probably register free callbacks, instead of providing NULL
+     * as they do now. */
+
+    struct server *s = NULL;
+    DBusError error;
+
+    pa_assert(u);
+    pa_assert(address);
+
+    dbus_error_init(&error);
+
+    s = pa_xnew0(struct server, 1);
+    s->userdata = u;
+    s->dbus_server = dbus_server_listen(address, &error);
+
+    if (dbus_error_is_set(&error)) {
+        pa_log("dbus_server_listen() failed: %s: %s", error.name, error.message);
+        goto fail;
+    }
+
+    dbus_server_set_new_connection_function(s->dbus_server, connection_new_cb, s, NULL);
+
+    if (!dbus_server_set_watch_functions(s->dbus_server, watch_add_cb, watch_remove_cb, watch_toggled_cb, s, NULL)) {
+        pa_log("dbus_server_set_watch_functions() ran out of memory.");
+        goto fail;
+    }
+
+    if (!dbus_server_set_timeout_functions(s->dbus_server, timeout_add_cb, timeout_remove_cb, timeout_toggled_cb, s, NULL)) {
+        pa_log("dbus_server_set_timeout_functions() ran out of memory.");
+        goto fail;
+    }
+
+    return s;
+
+fail:
+    if (s)
+        server_free(s);
+
+    dbus_error_free(&error);
+
+    return NULL;
+}
+
+static struct server *start_local_server(struct userdata *u) {
+    struct server *s = NULL;
+    char *address = NULL;
+
+    pa_assert(u);
+
+    address = pa_get_dbus_address_from_server_type(u->module->core->server_type);
+
+    s = start_server(u, address, SERVER_TYPE_LOCAL); /* May return NULL */
+
+    pa_xfree(address);
+
+    return s;
+}
+
+static struct server *start_tcp_server(struct userdata *u) {
+    struct server *s = NULL;
+    char *address = NULL;
+
+    pa_assert(u);
+
+    address = pa_sprintf_malloc("tcp:host=127.0.0.1,port=%u", u->tcp_port);
+
+    s = start_server(u, address, SERVER_TYPE_TCP); /* May return NULL */
+
+    pa_xfree(address);
+
+    return s;
+}
+
+static int get_access_arg(pa_modargs *ma, pa_bool_t *local_access, pa_bool_t *remote_access) {
+    const char *value = NULL;
+
+    pa_assert(ma);
+    pa_assert(local_access);
+    pa_assert(remote_access);
+
+    if (!(value = pa_modargs_get_value(ma, "access", NULL)))
+        return 0;
+
+    if (!strcmp(value, "local")) {
+        *local_access = TRUE;
+        *remote_access = FALSE;
+    } else if (!strcmp(value, "remote")) {
+        *local_access = FALSE;
+        *remote_access = TRUE;
+    } else if (!strcmp(value, "local,remote")) {
+        *local_access = TRUE;
+        *remote_access = TRUE;
+    } else
+        return -1;
+
+    return 0;
+}
+
+/* Frees dead client connections. Called every CLEANUP_INTERVAL seconds. */
+static void cleanup_cb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *tv, void *userdata) {
+    struct userdata *u = userdata;
+    struct connection *conn = NULL;
+    uint32_t idx;
+    struct timeval cleanup_timeval;
+    unsigned free_count = 0;
+
+    for (conn = pa_idxset_first(u->connections, &idx); conn; conn = pa_idxset_next(u->connections, &idx)) {
+        if (!dbus_connection_get_is_connected(pa_dbus_wrap_connection_get(conn->wrap_conn))) {
+            connection_free(conn);
+            ++free_count;
+        }
+    }
+
+    if (free_count > 0)
+        pa_log_debug("Freed %u dead D-Bus client connections.", free_count);
+
+    pa_gettimeofday(&cleanup_timeval);
+    cleanup_timeval.tv_sec += CLEANUP_INTERVAL;
+    u->module->core->mainloop->time_restart(e, &cleanup_timeval);
+}
+
+int pa__init(pa_module *m) {
+    struct userdata *u = NULL;
+    pa_modargs *ma = NULL;
+    struct timeval cleanup_timeval;
+
+    pa_assert(m);
+
+    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+        pa_log("Failed to parse module arguments.");
+        goto fail;
+    }
+
+    m->userdata = u = pa_xnew0(struct userdata, 1);
+    u->module = m;
+    u->local_access = TRUE;
+    u->remote_access = FALSE;
+    u->tcp_port = PA_DBUS_DEFAULT_PORT;
+
+    if (get_access_arg(ma, &u->local_access, &u->remote_access) < 0) {
+        pa_log("Invalid access argument: '%s'", pa_modargs_get_value(ma, "access", NULL));
+        goto fail;
+    }
+
+    if (pa_modargs_get_value_u32(ma, "tcp_port", &u->tcp_port) < 0 || u->tcp_port < 1 || u->tcp_port > 49150) {
+        pa_log("Invalid tcp_port argument: '%s'", pa_modargs_get_value(ma, "tcp_port", NULL));
+        goto fail;
+    }
+
+    if (u->local_access && !(u->local_server = start_local_server(u))) {
+        pa_log("Starting the local D-Bus server failed.");
+        goto fail;
+    }
+
+    if (u->remote_access && !(u->tcp_server = start_tcp_server(u))) {
+        pa_log("Starting the D-Bus server for remote connections failed.");
+        goto fail;
+    }
+
+    u->connections = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+
+    pa_gettimeofday(&cleanup_timeval);
+    cleanup_timeval.tv_sec += CLEANUP_INTERVAL;
+    u->cleanup_event = m->core->mainloop->time_new(m->core->mainloop, &cleanup_timeval, cleanup_cb, u);
+
+    u->dbus_protocol = pa_dbus_protocol_get(m->core);
+    u->core_iface = pa_dbusiface_core_new(m->core);
+
+    return 0;
+
+fail:
+    if (ma)
+        pa_modargs_free(ma);
+
+    pa__done(m);
+
+    return -1;
+}
+
+/* Called by idxset when the connection set is freed. */
+static void connection_free_cb(void *p, void *userdata) {
+    struct connection *conn = p;
+
+    pa_assert(conn);
+
+    connection_free(conn);
+}
+
+void pa__done(pa_module *m) {
+    struct userdata *u;
+
+    pa_assert(m);
+
+    if (!(u = m->userdata))
+        return;
+
+    if (u->core_iface)
+        pa_dbusiface_core_free(u->core_iface);
+
+    if (u->cleanup_event)
+        m->core->mainloop->time_free(u->cleanup_event);
+
+    if (u->connections)
+        pa_idxset_free(u->connections, connection_free_cb, NULL);
+
+    if (u->tcp_server)
+        server_free(u->tcp_server);
+
+    if (u->local_server)
+        server_free(u->local_server);
+
+    if (u->dbus_protocol)
+        pa_dbus_protocol_unref(u->dbus_protocol);
+
+    pa_xfree(u);
+    m->userdata = NULL;
+}
index d6e3c1534b8936d7be4c1c75b2da10696632baac..8389be67496adb4fff823c047dd7fd9e82656aef 100644 (file)
@@ -2,6 +2,7 @@
   This file is part of PulseAudio.
 
   Copyright 2008 Lennart Poettering
+  Copyright 2009 Tanu Kaskinen
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published
 #include <pulsecore/pstream-util.h>
 #include <pulsecore/database.h>
 
+#ifdef HAVE_DBUS
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/protocol-dbus.h>
+#endif
+
 #include "module-stream-restore-symdef.h"
 
 PA_MODULE_AUTHOR("Lennart Poettering");
@@ -100,6 +106,12 @@ struct userdata {
 
     pa_native_protocol *protocol;
     pa_idxset *subscribed;
+
+#ifdef HAVE_DBUS
+    pa_dbus_protocol *dbus_protocol;
+    pa_hashmap *dbus_entries;
+    uint32_t next_index; /* For generating object paths for entries. */
+#endif
 };
 
 #define ENTRY_VERSION 3
@@ -123,6 +135,835 @@ enum {
     SUBCOMMAND_EVENT
 };
 
+static struct entry *read_entry(struct userdata *u, const char *name);
+static void apply_entry(struct userdata *u, const char *name, struct entry *e);
+static void trigger_save(struct userdata *u);
+
+#ifdef HAVE_DBUS
+
+#define OBJECT_PATH "/org/pulseaudio/stream_restore1"
+#define ENTRY_OBJECT_NAME "entry"
+#define INTERFACE_STREAM_RESTORE "org.PulseAudio.Ext.StreamRestore1"
+#define INTERFACE_ENTRY INTERFACE_STREAM_RESTORE ".RestoreEntry"
+
+#define DBUS_INTERFACE_REVISION 0
+
+struct dbus_entry {
+    struct userdata *userdata;
+
+    char *entry_name;
+    uint32_t index;
+    char *object_path;
+};
+
+static void handle_get_interface_revision(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_entries(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_add_entry(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_entry_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_entry_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_entry_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_entry_get_device(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_entry_set_device(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_entry_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_entry_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+static void handle_entry_get_is_muted(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_entry_set_is_muted(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+
+static void handle_entry_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+static void handle_entry_remove(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+enum property_handler_index {
+    PROPERTY_HANDLER_INTERFACE_REVISION,
+    PROPERTY_HANDLER_ENTRIES,
+    PROPERTY_HANDLER_MAX
+};
+
+enum entry_property_handler_index {
+    ENTRY_PROPERTY_HANDLER_INDEX,
+    ENTRY_PROPERTY_HANDLER_NAME,
+    ENTRY_PROPERTY_HANDLER_DEVICE,
+    ENTRY_PROPERTY_HANDLER_VOLUME,
+    ENTRY_PROPERTY_HANDLER_IS_MUTED,
+    ENTRY_PROPERTY_HANDLER_MAX
+};
+
+static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
+    [PROPERTY_HANDLER_INTERFACE_REVISION] = { .property_name = "InterfaceRevision", .type = "u",  .get_cb = handle_get_interface_revision, .set_cb = NULL },
+    [PROPERTY_HANDLER_ENTRIES]            = { .property_name = "Entries",           .type = "ao", .get_cb = handle_get_entries,            .set_cb = NULL }
+};
+
+static pa_dbus_property_handler entry_property_handlers[ENTRY_PROPERTY_HANDLER_MAX] = {
+    [ENTRY_PROPERTY_HANDLER_INDEX]    = { .property_name = "Index",   .type = "u",     .get_cb = handle_entry_get_index,    .set_cb = NULL },
+    [ENTRY_PROPERTY_HANDLER_NAME]     = { .property_name = "Name",    .type = "s",     .get_cb = handle_entry_get_name,     .set_cb = NULL },
+    [ENTRY_PROPERTY_HANDLER_DEVICE]   = { .property_name = "Device",  .type = "s",     .get_cb = handle_entry_get_device,   .set_cb = handle_entry_set_device },
+    [ENTRY_PROPERTY_HANDLER_VOLUME]   = { .property_name = "Volume",  .type = "a(uu)", .get_cb = handle_entry_get_volume,   .set_cb = handle_entry_set_volume },
+    [ENTRY_PROPERTY_HANDLER_IS_MUTED] = { .property_name = "IsMuted", .type = "b",     .get_cb = handle_entry_get_is_muted, .set_cb = handle_entry_set_is_muted }
+};
+
+enum method_handler_index {
+    METHOD_HANDLER_ADD_ENTRY,
+    METHOD_HANDLER_GET_ENTRY_BY_NAME,
+    METHOD_HANDLER_MAX
+};
+
+enum entry_method_handler_index {
+    ENTRY_METHOD_HANDLER_REMOVE,
+    ENTRY_METHOD_HANDLER_MAX
+};
+
+static pa_dbus_arg_info add_entry_args[] = { { "name",     "s",     "in" },
+                                             { "device",   "s",     "in" },
+                                             { "volume",   "a(uu)", "in" },
+                                             { "is_muted", "b",     "in" },
+                                             { "entry",    "o",     "out" } };
+static pa_dbus_arg_info get_entry_by_name_args[] = { { "name", "s", "in" }, { "entry", "o", "out" } };
+
+static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
+    [METHOD_HANDLER_ADD_ENTRY] = {
+        .method_name = "AddEntry",
+        .arguments = add_entry_args,
+        .n_arguments = sizeof(add_entry_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_add_entry },
+    [METHOD_HANDLER_GET_ENTRY_BY_NAME] = {
+        .method_name = "GetEntryByName",
+        .arguments = get_entry_by_name_args,
+        .n_arguments = sizeof(get_entry_by_name_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_get_entry_by_name }
+};
+
+static pa_dbus_method_handler entry_method_handlers[ENTRY_METHOD_HANDLER_MAX] = {
+    [ENTRY_METHOD_HANDLER_REMOVE] = {
+        .method_name = "Remove",
+        .arguments = NULL,
+        .n_arguments = 0,
+        .receive_cb = handle_entry_remove }
+};
+
+enum signal_index {
+    SIGNAL_NEW_ENTRY,
+    SIGNAL_ENTRY_REMOVED,
+    SIGNAL_MAX
+};
+
+enum entry_signal_index {
+    ENTRY_SIGNAL_DEVICE_UPDATED,
+    ENTRY_SIGNAL_VOLUME_UPDATED,
+    ENTRY_SIGNAL_MUTE_UPDATED,
+    ENTRY_SIGNAL_MAX
+};
+
+static pa_dbus_arg_info new_entry_args[]     = { { "entry", "o", NULL } };
+static pa_dbus_arg_info entry_removed_args[] = { { "entry", "o", NULL } };
+
+static pa_dbus_arg_info entry_device_updated_args[] = { { "device", "s",     NULL } };
+static pa_dbus_arg_info entry_volume_updated_args[] = { { "volume", "a(uu)", NULL } };
+static pa_dbus_arg_info entry_mute_updated_args[]   = { { "muted",  "b",     NULL } };
+
+static pa_dbus_signal_info signals[SIGNAL_MAX] = {
+    [SIGNAL_NEW_ENTRY]     = { .name = "NewEntry",     .arguments = new_entry_args,     .n_arguments = 1 },
+    [SIGNAL_ENTRY_REMOVED] = { .name = "EntryRemoved", .arguments = entry_removed_args, .n_arguments = 1 }
+};
+
+static pa_dbus_signal_info entry_signals[ENTRY_SIGNAL_MAX] = {
+    [ENTRY_SIGNAL_DEVICE_UPDATED] = { .name = "DeviceUpdated", .arguments = entry_device_updated_args, .n_arguments = 1 },
+    [ENTRY_SIGNAL_VOLUME_UPDATED] = { .name = "VolumeUpdated", .arguments = entry_volume_updated_args, .n_arguments = 1 },
+    [ENTRY_SIGNAL_MUTE_UPDATED]   = { .name = "MuteUpdated",   .arguments = entry_mute_updated_args,   .n_arguments = 1 }
+};
+
+static pa_dbus_interface_info stream_restore_interface_info = {
+    .name = INTERFACE_STREAM_RESTORE,
+    .method_handlers = method_handlers,
+    .n_method_handlers = METHOD_HANDLER_MAX,
+    .property_handlers = property_handlers,
+    .n_property_handlers = PROPERTY_HANDLER_MAX,
+    .get_all_properties_cb = handle_get_all,
+    .signals = signals,
+    .n_signals = SIGNAL_MAX
+};
+
+static pa_dbus_interface_info entry_interface_info = {
+    .name = INTERFACE_ENTRY,
+    .method_handlers = entry_method_handlers,
+    .n_method_handlers = ENTRY_METHOD_HANDLER_MAX,
+    .property_handlers = entry_property_handlers,
+    .n_property_handlers = ENTRY_PROPERTY_HANDLER_MAX,
+    .get_all_properties_cb = handle_entry_get_all,
+    .signals = entry_signals,
+    .n_signals = ENTRY_SIGNAL_MAX
+};
+
+static struct dbus_entry *dbus_entry_new(struct userdata *u, const char *entry_name) {
+    struct dbus_entry *de;
+
+    pa_assert(u);
+    pa_assert(entry_name);
+    pa_assert(*entry_name);
+
+    de = pa_xnew(struct dbus_entry, 1);
+    de->userdata = u;
+    de->entry_name = pa_xstrdup(entry_name);
+    de->index = u->next_index++;
+    de->object_path = pa_sprintf_malloc("%s/%s%u", OBJECT_PATH, ENTRY_OBJECT_NAME, de->index);
+
+    pa_assert_se(pa_dbus_protocol_add_interface(u->dbus_protocol, de->object_path, &entry_interface_info, u) >= 0);
+
+    return de;
+}
+
+static void dbus_entry_free(struct dbus_entry *de) {
+    pa_assert(de);
+
+    pa_assert_se(pa_dbus_protocol_remove_interface(de->userdata->dbus_protocol, de->object_path, entry_interface_info.name) >= 0);
+
+    pa_xfree(de->entry_name);
+    pa_xfree(de->object_path);
+}
+
+/* Reads an array [(UInt32, UInt32)] from the iterator. The struct items are
+ * are a channel position and a volume value, respectively. The result is
+ * stored in the map and vol arguments. The iterator must point to a "a(uu)"
+ * element. If the data is invalid, an error reply is sent and a negative
+ * number is returned. In case of a failure we make no guarantees about the
+ * state of map and vol. In case of an empty array the channels field of both
+ * map and vol are set to 0. This function calls dbus_message_iter_next(iter)
+ * before returning. */
+static int get_volume_arg(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, pa_channel_map *map, pa_cvolume *vol) {
+    DBusMessageIter array_iter;
+    DBusMessageIter struct_iter;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(iter);
+    pa_assert(pa_streq(dbus_message_iter_get_signature(iter), "a(uu)"));
+    pa_assert(map);
+    pa_assert(vol);
+
+    pa_channel_map_init(map);
+    pa_cvolume_init(vol);
+
+    map->channels = 0;
+    vol->channels = 0;
+
+    dbus_message_iter_recurse(iter, &array_iter);
+
+    while (dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_INVALID) {
+        dbus_uint32_t chan_pos;
+        dbus_uint32_t chan_vol;
+
+        dbus_message_iter_recurse(&array_iter, &struct_iter);
+
+        dbus_message_iter_get_basic(&struct_iter, &chan_pos);
+
+        if (chan_pos >= PA_CHANNEL_POSITION_MAX) {
+            pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid channel position: %u", chan_pos);
+            return -1;
+        }
+
+        pa_assert_se(dbus_message_iter_next(&struct_iter));
+        dbus_message_iter_get_basic(&struct_iter, &chan_vol);
+
+        if (chan_vol > PA_VOLUME_MAX) {
+            pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume: %u", chan_vol);
+            return -1;
+        }
+
+        if (map->channels < PA_CHANNELS_MAX) {
+            map->map[map->channels] = chan_pos;
+            vol->values[map->channels] = chan_vol;
+        }
+        ++map->channels;
+        ++vol->channels;
+
+        dbus_message_iter_next(&array_iter);
+    }
+
+    if (map->channels > PA_CHANNELS_MAX) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Too many channels: %u. The maximum is %u.", map->channels, PA_CHANNELS_MAX);
+        return -1;
+    }
+
+    dbus_message_iter_next(iter);
+
+    return 0;
+}
+
+static void append_volume(DBusMessageIter *iter, struct entry *e) {
+    DBusMessageIter array_iter;
+    DBusMessageIter struct_iter;
+    unsigned i;
+
+    pa_assert(iter);
+    pa_assert(e);
+
+    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "(uu)", &array_iter));
+
+    if (!e->volume_valid) {
+        pa_assert_se(dbus_message_iter_close_container(iter, &array_iter));
+        return;
+    }
+
+    for (i = 0; i < e->channel_map.channels; ++i) {
+        pa_assert_se(dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter));
+
+        pa_assert_se(dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &e->channel_map.map[i]));
+        pa_assert_se(dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &e->volume.values[i]));
+
+        pa_assert_se(dbus_message_iter_close_container(&array_iter, &struct_iter));
+    }
+
+    pa_assert_se(dbus_message_iter_close_container(iter, &array_iter));
+}
+
+static void append_volume_variant(DBusMessageIter *iter, struct entry *e) {
+    DBusMessageIter variant_iter;
+
+    pa_assert(iter);
+    pa_assert(e);
+
+    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "a(uu)", &variant_iter));
+
+    append_volume(&variant_iter, e);
+
+    pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter));
+}
+
+static void send_new_entry_signal(struct dbus_entry *entry) {
+    DBusMessage *signal;
+
+    pa_assert(entry);
+
+    pa_assert_se(signal = dbus_message_new_signal(OBJECT_PATH, INTERFACE_STREAM_RESTORE, signals[SIGNAL_NEW_ENTRY].name));
+    pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &entry->object_path, DBUS_TYPE_INVALID));
+    pa_dbus_protocol_send_signal(entry->userdata->dbus_protocol, signal);
+    dbus_message_unref(signal);
+}
+
+static void send_entry_removed_signal(struct dbus_entry *entry) {
+    DBusMessage *signal;
+
+    pa_assert(entry);
+
+    pa_assert_se(signal = dbus_message_new_signal(OBJECT_PATH, INTERFACE_STREAM_RESTORE, signals[SIGNAL_ENTRY_REMOVED].name));
+    pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_OBJECT_PATH, &entry->object_path, DBUS_TYPE_INVALID));
+    pa_dbus_protocol_send_signal(entry->userdata->dbus_protocol, signal);
+    dbus_message_unref(signal);
+}
+
+static void send_device_updated_signal(struct dbus_entry *de, struct entry *e) {
+    DBusMessage *signal;
+    const char *device;
+
+    pa_assert(de);
+    pa_assert(e);
+
+    device = e->device_valid ? e->device : "";
+
+    pa_assert_se(signal = dbus_message_new_signal(de->object_path, INTERFACE_ENTRY, entry_signals[ENTRY_SIGNAL_DEVICE_UPDATED].name));
+    pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_STRING, &device, DBUS_TYPE_INVALID));
+    pa_dbus_protocol_send_signal(de->userdata->dbus_protocol, signal);
+    dbus_message_unref(signal);
+}
+
+static void send_volume_updated_signal(struct dbus_entry *de, struct entry *e) {
+    DBusMessage *signal;
+    DBusMessageIter msg_iter;
+
+    pa_assert(de);
+    pa_assert(e);
+
+    pa_assert_se(signal = dbus_message_new_signal(de->object_path, INTERFACE_ENTRY, entry_signals[ENTRY_SIGNAL_VOLUME_UPDATED].name));
+    dbus_message_iter_init_append(signal, &msg_iter);
+    append_volume(&msg_iter, e);
+    pa_dbus_protocol_send_signal(de->userdata->dbus_protocol, signal);
+    dbus_message_unref(signal);
+}
+
+static void send_mute_updated_signal(struct dbus_entry *de, struct entry *e) {
+    DBusMessage *signal;
+    dbus_bool_t muted;
+
+    pa_assert(de);
+    pa_assert(e);
+
+    pa_assert(e->muted_valid);
+
+    muted = e->muted;
+
+    pa_assert_se(signal = dbus_message_new_signal(de->object_path, INTERFACE_ENTRY, entry_signals[ENTRY_SIGNAL_MUTE_UPDATED].name));
+    pa_assert_se(dbus_message_append_args(signal, DBUS_TYPE_BOOLEAN, &muted, DBUS_TYPE_INVALID));
+    pa_dbus_protocol_send_signal(de->userdata->dbus_protocol, signal);
+    dbus_message_unref(signal);
+}
+
+static void handle_get_interface_revision(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    dbus_uint32_t interface_revision = DBUS_INTERFACE_REVISION;
+
+    pa_assert(conn);
+    pa_assert(msg);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &interface_revision);
+}
+
+/* The caller frees the array, but not the strings. */
+static const char **get_entries(struct userdata *u, unsigned *n) {
+    const char **entries;
+    unsigned i = 0;
+    void *state = NULL;
+    struct dbus_entry *de;
+
+    pa_assert(u);
+    pa_assert(n);
+
+    *n = pa_hashmap_size(u->dbus_entries);
+
+    if (*n == 0)
+        return NULL;
+
+    entries = pa_xnew(const char *, *n);
+
+    PA_HASHMAP_FOREACH(de, u->dbus_entries, state)
+        entries[i++] = de->object_path;
+
+    return entries;
+}
+
+static void handle_get_entries(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    struct userdata *u = userdata;
+    const char **entries;
+    unsigned n;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(u);
+
+    entries = get_entries(u, &n);
+
+    pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, entries, n);
+
+    pa_xfree(entries);
+}
+
+static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    struct userdata *u = userdata;
+    DBusMessage *reply = NULL;
+    DBusMessageIter msg_iter;
+    DBusMessageIter dict_iter;
+    dbus_uint32_t interface_revision;
+    const char **entries;
+    unsigned n_entries;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(u);
+
+    interface_revision = DBUS_INTERFACE_REVISION;
+    entries = get_entries(u, &n_entries);
+
+    pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+    dbus_message_iter_init_append(reply, &msg_iter);
+    pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INTERFACE_REVISION].property_name, DBUS_TYPE_UINT32, &interface_revision);
+    pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ENTRIES].property_name, DBUS_TYPE_OBJECT_PATH, entries, n_entries);
+
+    pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+    pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+    dbus_message_unref(reply);
+
+    pa_xfree(entries);
+}
+
+static void handle_add_entry(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    struct userdata *u = userdata;
+    DBusMessageIter msg_iter;
+    const char *name = NULL;
+    const char *device = NULL;
+    pa_channel_map map;
+    pa_cvolume vol;
+    dbus_bool_t muted = FALSE;
+    dbus_bool_t apply_immediately = FALSE;
+    pa_datum key;
+    pa_datum value;
+    struct dbus_entry *dbus_entry = NULL;
+    struct entry *e = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(u);
+
+    pa_assert_se(dbus_message_iter_init(msg, &msg_iter));
+    dbus_message_iter_get_basic(&msg_iter, &name);
+
+    pa_assert_se(dbus_message_iter_next(&msg_iter));
+    dbus_message_iter_get_basic(&msg_iter, &device);
+
+    pa_assert_se(dbus_message_iter_next(&msg_iter));
+    if (get_volume_arg(conn, msg, &msg_iter, &map, &vol) < 0)
+        return;
+
+    dbus_message_iter_get_basic(&msg_iter, &muted);
+
+    pa_assert_se(dbus_message_iter_next(&msg_iter));
+    dbus_message_iter_get_basic(&msg_iter, &apply_immediately);
+
+    if (!*name) {
+        pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "An empty string was given as the entry name.");
+        return;
+    }
+
+    if ((dbus_entry = pa_hashmap_get(u->dbus_entries, name))) {
+        pa_bool_t mute_updated = FALSE;
+        pa_bool_t volume_updated = FALSE;
+        pa_bool_t device_updated = FALSE;
+
+        pa_assert_se(e = read_entry(u, name));
+        mute_updated = e->muted != muted;
+        e->muted = muted;
+        e->muted_valid = TRUE;
+
+        volume_updated = (e->volume_valid != !!map.channels) || !pa_cvolume_equal(&e->volume, &vol);
+        e->volume = vol;
+        e->channel_map = map;
+        e->volume_valid = !!map.channels;
+
+        device_updated = (e->device_valid != !!device[0]) || !pa_streq(e->device, device);
+        pa_strlcpy(e->device, device, sizeof(e->device));
+        e->device_valid = !!device[0];
+
+        if (mute_updated)
+            send_mute_updated_signal(dbus_entry, e);
+        if (volume_updated)
+            send_volume_updated_signal(dbus_entry, e);
+        if (device_updated)
+            send_device_updated_signal(dbus_entry, e);
+
+    } else {
+        dbus_entry = dbus_entry_new(u, name);
+        pa_assert(pa_hashmap_put(u->dbus_entries, dbus_entry->entry_name, dbus_entry) >= 0);
+
+        e->muted_valid = TRUE;
+        e->volume_valid = !!map.channels;
+        e->device_valid = !!device[0];
+        e->muted = muted;
+        e->volume = vol;
+        e->channel_map = map;
+        pa_strlcpy(e->device, device, sizeof(e->device));
+
+        send_new_entry_signal(dbus_entry);
+    }
+
+    key.data = (char *) name;
+    key.size = strlen(name);
+
+    value.data = e;
+    value.size = sizeof(struct entry);
+
+    pa_assert_se(pa_database_set(u->database, &key, &value, TRUE) == 0);
+    if (apply_immediately)
+        apply_entry(u, name, e);
+
+    trigger_save(u);
+
+    pa_dbus_send_empty_reply(conn, msg);
+
+    pa_xfree(e);
+}
+
+static void handle_get_entry_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    struct userdata *u = userdata;
+    const char *name;
+    struct dbus_entry *de;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(u);
+
+    pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID));
+
+    if (!(de = pa_hashmap_get(u->dbus_entries, name))) {
+        pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "No such stream restore entry.");
+        return;
+    }
+
+    pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &de->object_path);
+}
+
+static void handle_entry_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    struct dbus_entry *de = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(de);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &de->index);
+}
+
+static void handle_entry_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    struct dbus_entry *de = userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(de);
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &de->entry_name);
+}
+
+static void handle_entry_get_device(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    struct dbus_entry *de = userdata;
+    struct entry *e;
+    const char *device;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(de);
+
+    pa_assert_se(e = read_entry(de->userdata, de->entry_name));
+
+    device = e->device_valid ? e->device : "";
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &device);
+
+    pa_xfree(e);
+}
+
+static void handle_entry_set_device(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+    struct dbus_entry *de = userdata;
+    const char *device;
+    struct entry *e;
+    pa_bool_t updated;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(iter);
+    pa_assert(de);
+
+    dbus_message_iter_get_basic(iter, &device);
+
+    pa_assert_se(e = read_entry(de->userdata, de->entry_name));
+
+    updated = (e->device_valid != !!device[0]) || !pa_streq(e->device, device);
+
+    if (updated) {
+        pa_datum key;
+        pa_datum value;
+
+        pa_strlcpy(e->device, device, sizeof(e->device));
+        e->device_valid = !!device[0];
+
+        key.data = de->entry_name;
+        key.size = strlen(de->entry_name);
+        value.data = e;
+        value.size = sizeof(struct entry);
+        pa_assert_se(pa_database_set(de->userdata->database, &key, &value, TRUE) == 0);
+
+        send_device_updated_signal(de, e);
+        trigger_save(de->userdata);
+    }
+
+    pa_dbus_send_empty_reply(conn, msg);
+
+    pa_xfree(e);
+}
+
+static void handle_entry_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    struct dbus_entry *de = userdata;
+    DBusMessage *reply;
+    DBusMessageIter msg_iter;
+    struct entry *e;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(de);
+
+    pa_assert_se(e = read_entry(de->userdata, de->entry_name));
+
+    pa_assert_se(reply = dbus_message_new_method_return(msg));
+
+    dbus_message_iter_init_append(reply, &msg_iter);
+    append_volume_variant(&msg_iter, e);
+
+    pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+    pa_xfree(e);
+}
+
+static void handle_entry_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+    struct dbus_entry *de = userdata;
+    pa_channel_map map;
+    pa_cvolume vol;
+    struct entry *e = NULL;
+    pa_bool_t updated = FALSE;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(iter);
+    pa_assert(de);
+
+    if (get_volume_arg(conn, msg, iter, &map, &vol) < 0)
+        return;
+
+    pa_assert_se(e = read_entry(de->userdata, de->entry_name));
+
+    updated = (e->volume_valid != !!map.channels) || !pa_cvolume_equal(&e->volume, &vol);
+
+    if (updated) {
+        pa_datum key;
+        pa_datum value;
+
+        e->volume = vol;
+        e->channel_map = map;
+        e->volume_valid = !!map.channels;
+
+        key.data = de->entry_name;
+        key.size = strlen(de->entry_name);
+        value.data = e;
+        value.size = sizeof(struct entry);
+        pa_assert_se(pa_database_set(de->userdata->database, &key, &value, TRUE) == 0);
+
+        send_volume_updated_signal(de, e);
+        trigger_save(de->userdata);
+    }
+
+    pa_dbus_send_empty_reply(conn, msg);
+
+    pa_xfree(e);
+}
+
+static void handle_entry_get_is_muted(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    struct dbus_entry *de = userdata;
+    struct entry *e;
+    dbus_bool_t muted;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(de);
+
+    pa_assert_se(e = read_entry(de->userdata, de->entry_name));
+
+    muted = e->muted_valid ? e->muted : FALSE;
+
+    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &muted);
+
+    pa_xfree(e);
+}
+
+static void handle_entry_set_is_muted(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+    struct dbus_entry *de = userdata;
+    pa_bool_t muted;
+    struct entry *e;
+    pa_bool_t updated;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(iter);
+    pa_assert(de);
+
+    dbus_message_iter_get_basic(iter, &muted);
+
+    pa_assert_se(e = read_entry(de->userdata, de->entry_name));
+
+    updated = !e->muted_valid || e->muted != muted;
+
+    if (updated) {
+        pa_datum key;
+        pa_datum value;
+
+        e->muted = muted;
+        e->muted_valid = TRUE;
+
+        key.data = de->entry_name;
+        key.size = strlen(de->entry_name);
+        value.data = e;
+        value.size = sizeof(struct entry);
+        pa_assert_se(pa_database_set(de->userdata->database, &key, &value, TRUE) == 0);
+
+        send_mute_updated_signal(de, e);
+        trigger_save(de->userdata);
+    }
+
+    pa_dbus_send_empty_reply(conn, msg);
+
+    pa_xfree(e);
+}
+
+static void handle_entry_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    struct dbus_entry *de = userdata;
+    struct entry *e;
+    DBusMessage *reply = NULL;
+    DBusMessageIter msg_iter;
+    DBusMessageIter dict_iter;
+    DBusMessageIter dict_entry_iter;
+    const char *device;
+    dbus_bool_t muted;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(de);
+
+    pa_assert_se(e = read_entry(de->userdata, de->entry_name));
+
+    device = e->device_valid ? e->device : "";
+    muted = e->muted_valid ? e->muted : FALSE;
+
+    pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+    dbus_message_iter_init_append(reply, &msg_iter);
+    pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &de->index);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &de->entry_name);
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_DEVICE].property_name, DBUS_TYPE_STRING, &device);
+
+    pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter));
+
+    pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &entry_property_handlers[ENTRY_PROPERTY_HANDLER_VOLUME].property_name));
+    append_volume_variant(&dict_entry_iter, e);
+
+    pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter));
+
+    pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_IS_MUTED].property_name, DBUS_TYPE_BOOLEAN, &muted);
+
+    pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+    pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+    dbus_message_unref(reply);
+
+    pa_xfree(e);
+}
+
+static void handle_entry_remove(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    struct dbus_entry *de = userdata;
+    pa_datum key;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(de);
+
+    key.data = de->entry_name;
+    key.size = strlen(de->entry_name);
+
+    pa_assert_se(pa_database_unset(de->userdata->database, &key) == 0);
+
+    send_entry_removed_signal(de);
+    trigger_save(de->userdata);
+
+    pa_assert_se(pa_hashmap_remove(de->userdata->dbus_entries, de->entry_name));
+    dbus_entry_free(de);
+
+    pa_dbus_send_empty_reply(conn, msg);
+}
+
+#endif /* HAVE_DBUS */
+
 static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) {
     struct userdata *u = userdata;
 
@@ -163,7 +1004,7 @@ static char *get_name(pa_proplist *p, const char *prefix) {
     return t;
 }
 
-static struct entryread_entry(struct userdata *u, const char *name) {
+static struct entry *read_entry(struct userdata *u, const char *name) {
     pa_datum key, data;
     struct entry *e;
 
@@ -285,6 +1126,17 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
     char *name;
     pa_datum key, data;
 
+    /* These are only used when D-Bus is enabled, but in order to reduce ifdef
+     * clutter these are defined here unconditionally. */
+    pa_bool_t created_new_entry = TRUE;
+    pa_bool_t device_updated = FALSE;
+    pa_bool_t volume_updated = FALSE;
+    pa_bool_t mute_updated = FALSE;
+
+#ifdef HAVE_DBUS
+    struct dbus_entry *de = NULL;
+#endif
+
     pa_assert(c);
     pa_assert(u);
 
@@ -306,24 +1158,34 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
         if (!(name = get_name(sink_input->proplist, "sink-input")))
             return;
 
-        if ((old = read_entry(u, name)))
+        if ((old = read_entry(u, name))) {
             entry = *old;
+            created_new_entry = FALSE;
+        }
 
         if (sink_input->save_volume) {
             entry.channel_map = sink_input->channel_map;
             pa_sink_input_get_volume(sink_input, &entry.volume, FALSE);
             entry.volume_valid = TRUE;
+
+            volume_updated = !created_new_entry
+                             && (!old->volume_valid
+                                 || !pa_channel_map_equal(&entry.channel_map, &old->channel_map)
+                                 || !pa_cvolume_equal(&entry.volume, &old->volume));
         }
 
         if (sink_input->save_muted) {
             entry.muted = pa_sink_input_get_mute(sink_input);
             entry.muted_valid = TRUE;
+
+            mute_updated = !created_new_entry && (!old->muted_valid || entry.muted != old->muted);
         }
 
         if (sink_input->save_sink) {
             pa_strlcpy(entry.device, sink_input->sink->name, sizeof(entry.device));
             entry.device_valid = TRUE;
 
+            device_updated = !created_new_entry && (!old->device_valid || !pa_streq(entry.device, old->device));
             if (sink_input->sink->card) {
                 pa_strlcpy(entry.card, sink_input->sink->card->name, sizeof(entry.card));
                 entry.card_valid = TRUE;
@@ -341,13 +1203,17 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
         if (!(name = get_name(source_output->proplist, "source-output")))
             return;
 
-        if ((old = read_entry(u, name)))
+        if ((old = read_entry(u, name))) {
             entry = *old;
+            created_new_entry = FALSE;
+        }
 
         if (source_output->save_source) {
             pa_strlcpy(entry.device, source_output->source->name, sizeof(entry.device));
             entry.device_valid = source_output->save_source;
 
+            device_updated = !created_new_entry && (!old->device_valid || !pa_streq(entry.device, old->device));
+
             if (source_output->source->card) {
                 pa_strlcpy(entry.card, source_output->source->card->name, sizeof(entry.card));
                 entry.card_valid = TRUE;
@@ -376,6 +1242,23 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
 
     pa_database_set(u->database, &key, &data, TRUE);
 
+#ifdef HAVE_DBUS
+    if (created_new_entry) {
+        de = dbus_entry_new(u, name);
+        pa_hashmap_put(u->dbus_entries, de->entry_name, de);
+        send_new_entry_signal(de);
+    } else {
+        pa_assert((de = pa_hashmap_get(u->dbus_entries, name)));
+
+        if (device_updated)
+            send_device_updated_signal(de, &entry);
+        if (volume_updated)
+            send_volume_updated_signal(de, &entry);
+        if (mute_updated)
+            send_mute_updated_signal(de, &entry);
+    }
+#endif
+
     pa_xfree(name);
 
     trigger_save(u);
@@ -889,14 +1772,27 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
                 mode != PA_UPDATE_SET)
                 goto fail;
 
-            if (mode == PA_UPDATE_SET)
+            if (mode == PA_UPDATE_SET) {
+#ifdef HAVE_DBUS
+                struct dbus_entry *de;
+                void *state = NULL;
+
+                PA_HASHMAP_FOREACH(de, u->dbus_entries, state) {
+                    send_entry_removed_signal(de);
+                    dbus_entry_free(pa_hashmap_remove(u->dbus_entries, de->entry_name));
+                }
+#endif
                 pa_database_clear(u->database);
+            }
 
             while (!pa_tagstruct_eof(t)) {
                 const char *name, *device;
                 pa_bool_t muted;
                 struct entry entry;
                 pa_datum key, data;
+#ifdef HAVE_DBUS
+                struct entry *old;
+#endif
 
                 pa_zero(entry);
                 entry.version = ENTRY_VERSION;
@@ -928,6 +1824,10 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
                     !pa_namereg_is_valid_name(entry.device))
                     goto fail;
 
+#ifdef HAVE_DBUS
+                old = read_entry(u, name);
+#endif
+
                 key.data = (char*) name;
                 key.size = strlen(name);
 
@@ -938,9 +1838,40 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
                              pa_strnull(pa_proplist_gets(pa_native_connection_get_client(c)->proplist, PA_PROP_APPLICATION_PROCESS_BINARY)),
                              name);
 
-                if (pa_database_set(u->database, &key, &data, mode == PA_UPDATE_REPLACE) == 0)
+                if (pa_database_set(u->database, &key, &data, mode == PA_UPDATE_REPLACE) == 0) {
+#ifdef HAVE_DBUS
+                    struct dbus_entry *de;
+
+                    if (old) {
+                        pa_assert_se((de = pa_hashmap_get(u->dbus_entries, name)));
+
+                        if ((old->device_valid != entry.device_valid)
+                            || (entry.device_valid && !pa_streq(entry.device, old->device)))
+                            send_device_updated_signal(de, &entry);
+
+                        if ((old->volume_valid != entry.volume_valid)
+                            || (entry.volume_valid && (!pa_cvolume_equal(&entry.volume, &old->volume)
+                                                       || !pa_channel_map_equal(&entry.channel_map, &old->channel_map))))
+                            send_volume_updated_signal(de, &entry);
+
+                        if (!old->muted_valid || (entry.muted != old->muted))
+                            send_mute_updated_signal(de, &entry);
+
+                    } else {
+                        de = dbus_entry_new(u, name);
+                        pa_assert_se(pa_hashmap_put(u->dbus_entries, de->entry_name, de));
+                        send_new_entry_signal(de);
+                    }
+#endif
+
                     if (apply_immediately)
                         apply_entry(u, name, &entry);
+                }
+
+#ifdef HAVE_DBUS
+                if (old)
+                    pa_xfree(old);
+#endif
             }
 
             trigger_save(u);
@@ -953,10 +1884,20 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
             while (!pa_tagstruct_eof(t)) {
                 const char *name;
                 pa_datum key;
+#ifdef HAVE_DBUS
+                struct dbus_entry *de;
+#endif
 
                 if (pa_tagstruct_gets(t, &name) < 0)
                     goto fail;
 
+#ifdef HAVE_DBUS
+                if ((de = pa_hashmap_get(u->dbus_entries, name))) {
+                    send_entry_removed_signal(de);
+                    dbus_entry_free(pa_hashmap_remove(u->dbus_entries, name));
+                }
+#endif
+
                 key.data = (char*) name;
                 key.size = strlen(name);
 
@@ -1015,6 +1956,10 @@ int pa__init(pa_module*m) {
     pa_source_output *so;
     uint32_t idx;
     pa_bool_t restore_device = TRUE, restore_volume = TRUE, restore_muted = TRUE, on_hotplug = TRUE, on_rescue = TRUE;
+#ifdef HAVE_DBUS
+    pa_datum key;
+    pa_bool_t done;
+#endif
 
     pa_assert(m);
 
@@ -1085,6 +2030,34 @@ int pa__init(pa_module*m) {
     pa_log_info("Sucessfully opened database file '%s'.", fname);
     pa_xfree(fname);
 
+#ifdef HAVE_DBUS
+    u->dbus_protocol = pa_dbus_protocol_get(u->core);
+    u->dbus_entries = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+    pa_assert_se(pa_dbus_protocol_add_interface(u->dbus_protocol, OBJECT_PATH, &stream_restore_interface_info, u) >= 0);
+    pa_assert_se(pa_dbus_protocol_register_extension(u->dbus_protocol, INTERFACE_STREAM_RESTORE) >= 0);
+
+    /* Create the initial dbus entries. */
+    done = !pa_database_first(u->database, &key, NULL);
+    while (!done) {
+        pa_datum next_key;
+        char *name;
+        struct dbus_entry *de;
+
+        done = !pa_database_next(u->database, &key, &next_key, NULL);
+
+        name = pa_xstrndup(key.data, key.size);
+        pa_datum_free(&key);
+
+        de = dbus_entry_new(u, name);
+        pa_assert_se(pa_hashmap_put(u->dbus_entries, de->entry_name, de) >= 0);
+
+        pa_xfree(name);
+
+        key = next_key;
+    }
+#endif
+
     PA_IDXSET_FOREACH(si, m->core->sink_inputs, idx)
         subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW, si->index, u);
 
@@ -1103,6 +2076,16 @@ fail:
     return  -1;
 }
 
+#ifdef HAVE_DBUS
+static void free_dbus_entry_cb(void *p, void *userdata) {
+    struct dbus_entry *de = p;
+
+    pa_assert(de);
+
+    dbus_entry_free(de);
+}
+#endif
+
 void pa__done(pa_module*m) {
     struct userdata* u;
 
@@ -1111,6 +2094,19 @@ void pa__done(pa_module*m) {
     if (!(u = m->userdata))
         return;
 
+#ifdef HAVE_DBUS
+    if (u->dbus_protocol) {
+        pa_assert(u->dbus_entries);
+
+        pa_assert_se(pa_dbus_protocol_unregister_extension(u->dbus_protocol, INTERFACE_STREAM_RESTORE) >= 0);
+        pa_assert_se(pa_dbus_protocol_remove_interface(u->dbus_protocol, OBJECT_PATH, stream_restore_interface_info.name) >= 0);
+
+        pa_hashmap_free(u->dbus_entries, free_dbus_entry_cb, NULL);
+
+        pa_dbus_protocol_unref(u->dbus_protocol);
+    }
+#endif
+
     if (u->subscription)
         pa_subscription_free(u->subscription);
 
index 4aa4ba1fc82cf5e66378129cc6df9942f8c4e3db..62c06f6aa7f8530a6a491c67d4b6c7b67b12a3bf 100644 (file)
@@ -57,6 +57,7 @@ static const pa_client_conf default_conf = {
     .default_sink = NULL,
     .default_source = NULL,
     .default_server = NULL,
+    .default_dbus_server = NULL,
     .autospawn = TRUE,
     .disable_shm = FALSE,
     .cookie_file = NULL,
@@ -81,6 +82,7 @@ void pa_client_conf_free(pa_client_conf *c) {
     pa_xfree(c->default_sink);
     pa_xfree(c->default_source);
     pa_xfree(c->default_server);
+    pa_xfree(c->default_dbus_server);
     pa_xfree(c->cookie_file);
     pa_xfree(c);
 }
@@ -97,6 +99,7 @@ int pa_client_conf_load(pa_client_conf *c, const char *filename) {
         { "default-sink",           pa_config_parse_string,   &c->default_sink, NULL },
         { "default-source",         pa_config_parse_string,   &c->default_source, NULL },
         { "default-server",         pa_config_parse_string,   &c->default_server, NULL },
+        { "default-dbus-server",    pa_config_parse_string,   &c->default_dbus_server, NULL },
         { "autospawn",              pa_config_parse_bool,     &c->autospawn, NULL },
         { "cookie-file",            pa_config_parse_string,   &c->cookie_file, NULL },
         { "disable-shm",            pa_config_parse_bool,     &c->disable_shm, NULL },
index ab97dc6a43bd6f54fba9992b45e2c6e6d0964efe..618216f4ff5e622f72cf66531997d42c6691a52b 100644 (file)
   USA.
 ***/
 
+#include <pulsecore/macro.h>
 #include <pulsecore/native-common.h>
 
 /* A structure containing configuration data for PulseAudio clients. */
 
 typedef struct pa_client_conf {
-    char *daemon_binary, *extra_arguments, *default_sink, *default_source, *default_server, *cookie_file;
+    char *daemon_binary, *extra_arguments, *default_sink, *default_source, *default_server, *default_dbus_server, *cookie_file;
     pa_bool_t autospawn, disable_shm;
     uint8_t cookie[PA_NATIVE_COOKIE_LENGTH];
     pa_bool_t cookie_valid; /* non-zero, when cookie is valid */
index 6c8d371c4c9e4055ca1765ab42aac0a2a5ef97f6..e03096e09c5585615b0bac9bed0b8814ccf32859 100644 (file)
@@ -22,6 +22,7 @@
 ; default-sink =
 ; default-source =
 ; default-server =
+; default-dbus-server =
 
 ; autospawn = yes
 ; daemon-binary = @PA_BINARY@
index 048b241a5b6872b25aad0664c82c8c90bdf5e74e..faa98b79e4517aa419fc1d28742981405cb14902 100644 (file)
@@ -681,3 +681,32 @@ int pa_proplist_isempty(pa_proplist *p) {
 
     return pa_hashmap_isempty(MAKE_HASHMAP(p));
 }
+
+int pa_proplist_equal(pa_proplist *a, pa_proplist *b) {
+    const void *key = NULL;
+    struct property *a_prop = NULL;
+    struct property *b_prop = NULL;
+    void *state = NULL;
+
+    pa_assert(a);
+    pa_assert(b);
+
+    if (a == b)
+        return 1;
+
+    if (pa_proplist_size(a) != pa_proplist_size(b))
+        return 0;
+
+    while ((a_prop = pa_hashmap_iterate(MAKE_HASHMAP(a), &state, &key))) {
+        if (!(b_prop = pa_hashmap_get(MAKE_HASHMAP(b), key)))
+            return 0;
+
+        if (a_prop->nbytes != b_prop->nbytes)
+            return 0;
+
+        if (memcmp(a_prop->value, b_prop->value, a_prop->nbytes) != 0)
+            return 0;
+    }
+
+    return 1;
+}
index bc4dbd8aede14a2e7f634d27ed1e16efc165bbb7..9effc861661bcc8ab2134bbeaf2545c0c56a00d3 100644 (file)
@@ -337,7 +337,7 @@ char *pa_proplist_to_string_sep(pa_proplist *p, const char *sep);
  * readable string. \since 0.9.15 */
 pa_proplist *pa_proplist_from_string(const char *str);
 
-  /** Returns 1 if an entry for the specified key is existant in the
+/** Returns 1 if an entry for the specified key is existant in the
  * property list. \since 0.9.11 */
 int pa_proplist_contains(pa_proplist *p, const char *key);
 
@@ -354,6 +354,10 @@ unsigned pa_proplist_size(pa_proplist *t);
 /** Returns 0 when the proplist is empty, positive otherwise \since 0.9.15 */
 int pa_proplist_isempty(pa_proplist *t);
 
+/** Return non-zero when a and b have the same keys and values.
+ * \since 0.9.16 */
+int pa_proplist_equal(pa_proplist *a, pa_proplist *b);
+
 PA_C_DECL_END
 
 #endif
index 678230197860546eba10e0dc8f5a8c8e3f5a9d17..d576d01daf68334a88989adc93f713719fad2c20 100644 (file)
@@ -2648,6 +2648,28 @@ char *pa_replace(const char*s, const char*a, const char *b) {
     return pa_strbuf_tostring_free(sb);
 }
 
+char *pa_escape(const char *p, const char *chars) {
+    const char *s;
+    const char *c;
+    pa_strbuf *buf = pa_strbuf_new();
+
+    for (s = p; *s; ++s) {
+        if (*s == '\\')
+            pa_strbuf_putc(buf, '\\');
+        else if (chars) {
+            for (c = chars; *c; ++c) {
+                if (*s == *c) {
+                    pa_strbuf_putc(buf, '\\');
+                    break;
+                }
+            }
+        }
+        pa_strbuf_putc(buf, *s);
+    }
+
+    return pa_strbuf_tostring_free(buf);
+}
+
 char *pa_unescape(char *p) {
     char *s, *d;
     pa_bool_t escaped = FALSE;
index 2551f7942d2c93de80852f1a8544f1c755455cb3..8c13b5351965acbc34bf9690c03bda6d7ba9a37d 100644 (file)
@@ -222,6 +222,13 @@ unsigned pa_ncpus(void);
 
 char *pa_replace(const char*s, const char*a, const char *b);
 
+/* Escapes p by inserting backslashes in front of backslashes. chars is a
+ * regular (ie. NULL-terminated) string containing additional characters that
+ * should be escaped. chars can be NULL. The caller has to free the returned
+ * string. */
+char *pa_escape(const char *p, const char *chars);
+
+/* Does regular backslash unescaping. Returns the argument p. */
 char *pa_unescape(char *p);
 
 char *pa_realpath(const char *path);
index c1002f93e8311d085d8cb3df2b3d2c954904fa6d..bfcea4f636104c03a30a118785b36e4a9b1175eb 100644 (file)
@@ -52,6 +52,13 @@ typedef enum pa_suspend_cause {
 #include <pulsecore/sink-input.h>
 #include <pulsecore/msgobject.h>
 
+typedef enum pa_server_type {
+    PA_SERVER_TYPE_UNSET,
+    PA_SERVER_TYPE_USER,
+    PA_SERVER_TYPE_SYSTEM,
+    PA_SERVER_TYPE_NONE
+} pa_server_type_t;
+
 typedef enum pa_core_state {
     PA_CORE_STARTUP,
     PA_CORE_RUNNING,
@@ -161,6 +168,8 @@ struct pa_core {
     pa_resample_method_t resample_method;
     int realtime_priority;
 
+    pa_server_type_t server_type;
+
     /* hooks */
     pa_hook hooks[PA_CORE_HOOK_MAX];
 };
index 3ccd07089ef894fa3f4c24c2a83ad3a39e03271f..a87cb63b4c940020e3d7f6f584d5f3c4be74e632 100644 (file)
@@ -5,7 +5,7 @@
   This file is part of PulseAudio.
 
   Copyright 2004-2006 Lennart Poettering
-  Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk> 
+  Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk>
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published
index b40eb5ce18ceecfa5ed45974683bec872b11f618..f6484c592285dccd7671da9580c44db7cec94e3a 100644 (file)
@@ -5,7 +5,7 @@
   This file is part of PulseAudio.
 
   Copyright 2004-2006 Lennart Poettering
-  Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk> 
+  Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk>
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published
index 4e6148f0770e43a84ea3a0e05e04038164e78fe1..e3700ea552aa9b42022fb7f7f391573d7c073538 100644 (file)
@@ -28,6 +28,7 @@
 
 #include <pulse/rtclock.h>
 #include <pulse/timeval.h>
+#include <pulse/utf8.h>
 #include <pulse/xmalloc.h>
 
 #include <pulsecore/core-rtclock.h>
@@ -290,6 +291,31 @@ pa_dbus_wrap_connection* pa_dbus_wrap_connection_new(pa_mainloop_api *m, pa_bool
     return pconn;
 }
 
+pa_dbus_wrap_connection* pa_dbus_wrap_connection_new_from_existing(
+        pa_mainloop_api *m,
+        pa_bool_t use_rtclock,
+        DBusConnection *conn) {
+    pa_dbus_wrap_connection *pconn;
+
+    pa_assert(m);
+    pa_assert(conn);
+
+    pconn = pa_xnew(pa_dbus_wrap_connection, 1);
+    pconn->mainloop = m;
+    pconn->connection = dbus_connection_ref(conn);
+    pconn->use_rtclock = use_rtclock;
+
+    dbus_connection_set_exit_on_disconnect(conn, FALSE);
+    dbus_connection_set_dispatch_status_function(conn, dispatch_status, pconn, NULL);
+    dbus_connection_set_watch_functions(conn, add_watch, remove_watch, toggle_watch, pconn, NULL);
+    dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout, toggle_timeout, pconn, NULL);
+    dbus_connection_set_wakeup_main_function(conn, wakeup_main, pconn, NULL);
+
+    pconn->dispatch_event = pconn->mainloop->defer_new(pconn->mainloop, dispatch_cb, conn);
+
+    return pconn;
+}
+
 void pa_dbus_wrap_connection_free(pa_dbus_wrap_connection* c) {
     pa_assert(c);
 
@@ -422,3 +448,329 @@ void pa_dbus_free_pending_list(pa_dbus_pending **p) {
         pa_dbus_pending_free(i);
     }
 }
+
+void pa_dbus_send_error(DBusConnection *c, DBusMessage *in_reply_to, const char *name, const char *format, ...) {
+    va_list ap;
+    char *message;
+    DBusMessage *reply = NULL;
+
+    pa_assert(c);
+    pa_assert(in_reply_to);
+    pa_assert(name);
+    pa_assert(format);
+
+    va_start(ap, format);
+    message = pa_vsprintf_malloc(format, ap);
+    va_end(ap);
+    pa_assert_se((reply = dbus_message_new_error(in_reply_to, name, message)));
+    pa_assert_se(dbus_connection_send(c, reply, NULL));
+
+    dbus_message_unref(reply);
+
+    pa_xfree(message);
+}
+
+void pa_dbus_send_empty_reply(DBusConnection *c, DBusMessage *in_reply_to) {
+    DBusMessage *reply = NULL;
+
+    pa_assert(c);
+    pa_assert(in_reply_to);
+
+    pa_assert_se((reply = dbus_message_new_method_return(in_reply_to)));
+    pa_assert_se(dbus_connection_send(c, reply, NULL));
+    dbus_message_unref(reply);
+}
+
+void pa_dbus_send_basic_value_reply(DBusConnection *c, DBusMessage *in_reply_to, int type, void *data) {
+    DBusMessage *reply = NULL;
+
+    pa_assert(c);
+    pa_assert(in_reply_to);
+    pa_assert(dbus_type_is_basic(type));
+    pa_assert(data);
+
+    pa_assert_se((reply = dbus_message_new_method_return(in_reply_to)));
+    pa_assert_se(dbus_message_append_args(reply, type, data, DBUS_TYPE_INVALID));
+    pa_assert_se(dbus_connection_send(c, reply, NULL));
+    dbus_message_unref(reply);
+}
+
+static const char *signature_from_basic_type(int type) {
+    switch (type) {
+        case DBUS_TYPE_BOOLEAN: return DBUS_TYPE_BOOLEAN_AS_STRING;
+        case DBUS_TYPE_BYTE: return DBUS_TYPE_BYTE_AS_STRING;
+        case DBUS_TYPE_INT16: return DBUS_TYPE_INT16_AS_STRING;
+        case DBUS_TYPE_UINT16: return DBUS_TYPE_UINT16_AS_STRING;
+        case DBUS_TYPE_INT32: return DBUS_TYPE_INT32_AS_STRING;
+        case DBUS_TYPE_UINT32: return DBUS_TYPE_UINT32_AS_STRING;
+        case DBUS_TYPE_INT64: return DBUS_TYPE_INT64_AS_STRING;
+        case DBUS_TYPE_UINT64: return DBUS_TYPE_UINT64_AS_STRING;
+        case DBUS_TYPE_DOUBLE: return DBUS_TYPE_DOUBLE_AS_STRING;
+        case DBUS_TYPE_STRING: return DBUS_TYPE_STRING_AS_STRING;
+        case DBUS_TYPE_OBJECT_PATH: return DBUS_TYPE_OBJECT_PATH_AS_STRING;
+        case DBUS_TYPE_SIGNATURE: return DBUS_TYPE_SIGNATURE_AS_STRING;
+        default: pa_assert_not_reached();
+    }
+}
+
+void pa_dbus_send_basic_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, int type, void *data) {
+    DBusMessage *reply = NULL;
+    DBusMessageIter msg_iter;
+    DBusMessageIter variant_iter;
+
+    pa_assert(c);
+    pa_assert(in_reply_to);
+    pa_assert(dbus_type_is_basic(type));
+    pa_assert(data);
+
+    pa_assert_se((reply = dbus_message_new_method_return(in_reply_to)));
+    dbus_message_iter_init_append(reply, &msg_iter);
+    pa_assert_se(dbus_message_iter_open_container(&msg_iter,
+                                                  DBUS_TYPE_VARIANT,
+                                                  signature_from_basic_type(type),
+                                                  &variant_iter));
+    pa_assert_se(dbus_message_iter_append_basic(&variant_iter, type, data));
+    pa_assert_se(dbus_message_iter_close_container(&msg_iter, &variant_iter));
+    pa_assert_se(dbus_connection_send(c, reply, NULL));
+    dbus_message_unref(reply);
+}
+
+/* Note: returns sizeof(char*) for strings, object paths and signatures. */
+static unsigned basic_type_size(int type) {
+    switch (type) {
+        case DBUS_TYPE_BOOLEAN: return sizeof(dbus_bool_t);
+        case DBUS_TYPE_BYTE: return 1;
+        case DBUS_TYPE_INT16: return sizeof(dbus_int16_t);
+        case DBUS_TYPE_UINT16: return sizeof(dbus_uint16_t);
+        case DBUS_TYPE_INT32: return sizeof(dbus_int32_t);
+        case DBUS_TYPE_UINT32: return sizeof(dbus_uint32_t);
+        case DBUS_TYPE_INT64: return sizeof(dbus_int64_t);
+        case DBUS_TYPE_UINT64: return sizeof(dbus_uint64_t);
+        case DBUS_TYPE_DOUBLE: return sizeof(double);
+        case DBUS_TYPE_STRING:
+        case DBUS_TYPE_OBJECT_PATH:
+        case DBUS_TYPE_SIGNATURE: return sizeof(char*);
+        default: pa_assert_not_reached();
+    }
+}
+
+void pa_dbus_send_basic_array_variant_reply(
+        DBusConnection *c,
+        DBusMessage *in_reply_to,
+        int item_type,
+        void *array,
+        unsigned n) {
+    DBusMessage *reply = NULL;
+    DBusMessageIter msg_iter;
+
+    pa_assert(c);
+    pa_assert(in_reply_to);
+    pa_assert(dbus_type_is_basic(item_type));
+    pa_assert(array || n == 0);
+
+    pa_assert_se((reply = dbus_message_new_method_return(in_reply_to)));
+    dbus_message_iter_init_append(reply, &msg_iter);
+    pa_dbus_append_basic_array_variant(&msg_iter, item_type, array, n);
+    pa_assert_se(dbus_connection_send(c, reply, NULL));
+    dbus_message_unref(reply);
+}
+
+void pa_dbus_send_proplist_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, pa_proplist *proplist) {
+    DBusMessage *reply = NULL;
+    DBusMessageIter msg_iter;
+
+    pa_assert(c);
+    pa_assert(in_reply_to);
+    pa_assert(proplist);
+
+    pa_assert_se((reply = dbus_message_new_method_return(in_reply_to)));
+    dbus_message_iter_init_append(reply, &msg_iter);
+    pa_dbus_append_proplist_variant(&msg_iter, proplist);
+    pa_assert_se(dbus_connection_send(c, reply, NULL));
+    dbus_message_unref(reply);
+}
+
+void pa_dbus_append_basic_array(DBusMessageIter *iter, int item_type, const void *array, unsigned n) {
+    DBusMessageIter array_iter;
+    unsigned i;
+    unsigned item_size;
+
+    pa_assert(iter);
+    pa_assert(dbus_type_is_basic(item_type));
+    pa_assert(array || n == 0);
+
+    item_size = basic_type_size(item_type);
+
+    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, signature_from_basic_type(item_type), &array_iter));
+
+    for (i = 0; i < n; ++i)
+        pa_assert_se(dbus_message_iter_append_basic(&array_iter, item_type, &((uint8_t*) array)[i * item_size]));
+
+    pa_assert_se(dbus_message_iter_close_container(iter, &array_iter));
+};
+
+void pa_dbus_append_basic_variant(DBusMessageIter *iter, int type, void *data) {
+    DBusMessageIter variant_iter;
+
+    pa_assert(iter);
+    pa_assert(dbus_type_is_basic(type));
+    pa_assert(data);
+
+    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, signature_from_basic_type(type), &variant_iter));
+    pa_assert_se(dbus_message_iter_append_basic(&variant_iter, type, data));
+    pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter));
+}
+
+void pa_dbus_append_basic_array_variant(DBusMessageIter *iter, int item_type, const void *array, unsigned n) {
+    DBusMessageIter variant_iter;
+    char *array_signature;
+
+    pa_assert(iter);
+    pa_assert(dbus_type_is_basic(item_type));
+    pa_assert(array || n == 0);
+
+    array_signature = pa_sprintf_malloc("a%c", *signature_from_basic_type(item_type));
+
+    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, array_signature, &variant_iter));
+    pa_dbus_append_basic_array(&variant_iter, item_type, array, n);
+    pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter));
+
+    pa_xfree(array_signature);
+}
+
+void pa_dbus_append_basic_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, int type, void *data) {
+    DBusMessageIter dict_entry_iter;
+
+    pa_assert(dict_iter);
+    pa_assert(key);
+    pa_assert(dbus_type_is_basic(type));
+    pa_assert(data);
+
+    pa_assert_se(dbus_message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter));
+    pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key));
+    pa_dbus_append_basic_variant(&dict_entry_iter, type, data);
+    pa_assert_se(dbus_message_iter_close_container(dict_iter, &dict_entry_iter));
+}
+
+void pa_dbus_append_basic_array_variant_dict_entry(
+        DBusMessageIter *dict_iter,
+        const char *key,
+        int item_type,
+        const void *array,
+        unsigned n) {
+    DBusMessageIter dict_entry_iter;
+
+    pa_assert(dict_iter);
+    pa_assert(key);
+    pa_assert(dbus_type_is_basic(item_type));
+    pa_assert(array || n == 0);
+
+    pa_assert_se(dbus_message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter));
+    pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key));
+    pa_dbus_append_basic_array_variant(&dict_entry_iter, item_type, array, n);
+    pa_assert_se(dbus_message_iter_close_container(dict_iter, &dict_entry_iter));
+}
+
+void pa_dbus_append_proplist(DBusMessageIter *iter, pa_proplist *proplist) {
+    DBusMessageIter dict_iter;
+    DBusMessageIter dict_entry_iter;
+    DBusMessageIter array_iter;
+    void *state = NULL;
+    const char *key;
+
+    pa_assert(iter);
+    pa_assert(proplist);
+
+    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{say}", &dict_iter));
+
+    while ((key = pa_proplist_iterate(proplist, &state))) {
+        const void *value = NULL;
+        size_t nbytes;
+
+        pa_assert_se(pa_proplist_get(proplist, key, &value, &nbytes) >= 0);
+
+        pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter));
+
+        pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key));
+
+        pa_assert_se(dbus_message_iter_open_container(&dict_entry_iter, DBUS_TYPE_ARRAY, "y", &array_iter));
+        pa_assert_se(dbus_message_iter_append_fixed_array(&array_iter, DBUS_TYPE_BYTE, &value, nbytes));
+        pa_assert_se(dbus_message_iter_close_container(&dict_entry_iter, &array_iter));
+
+        pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter));
+    }
+
+    pa_assert_se(dbus_message_iter_close_container(iter, &dict_iter));
+}
+
+void pa_dbus_append_proplist_variant(DBusMessageIter *iter, pa_proplist *proplist) {
+    DBusMessageIter variant_iter;
+
+    pa_assert(iter);
+    pa_assert(proplist);
+
+    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "a{say}", &variant_iter));
+    pa_dbus_append_proplist(&variant_iter, proplist);
+    pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter));
+}
+
+void pa_dbus_append_proplist_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, pa_proplist *proplist) {
+    DBusMessageIter dict_entry_iter;
+
+    pa_assert(dict_iter);
+    pa_assert(key);
+    pa_assert(proplist);
+
+    pa_assert_se(dbus_message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter));
+    pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key));
+    pa_dbus_append_proplist_variant(&dict_entry_iter, proplist);
+    pa_assert_se(dbus_message_iter_close_container(dict_iter, &dict_entry_iter));
+}
+
+pa_proplist *pa_dbus_get_proplist_arg(DBusConnection *c, DBusMessage *msg, DBusMessageIter *iter) {
+    DBusMessageIter dict_iter;
+    DBusMessageIter dict_entry_iter;
+    pa_proplist *proplist = NULL;
+    const char *key = NULL;
+    const uint8_t *value = NULL;
+    int value_length = 0;
+
+    pa_assert(c);
+    pa_assert(msg);
+    pa_assert(iter);
+    pa_assert(pa_streq(dbus_message_iter_get_signature(iter), "a{say}"));
+
+    proplist = pa_proplist_new();
+
+    dbus_message_iter_recurse(iter, &dict_iter);
+
+    while (dbus_message_iter_get_arg_type(&dict_iter) != DBUS_TYPE_INVALID) {
+        dbus_message_iter_recurse(&dict_iter, &dict_entry_iter);
+
+        dbus_message_iter_get_basic(&dict_entry_iter, &key);
+        dbus_message_iter_next(&dict_entry_iter);
+
+        if (strlen(key) <= 0 || !pa_ascii_valid(key)) {
+            pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Invalid property list key: '%s'.", key);
+            goto fail;
+        }
+
+        dbus_message_iter_get_fixed_array(&dict_entry_iter, &value, &value_length);
+
+        pa_assert(value_length >= 0);
+
+        pa_assert_se(pa_proplist_set(proplist, key, value, value_length) >= 0);
+
+        dbus_message_iter_next(&dict_iter);
+    }
+
+    dbus_message_iter_next(iter);
+
+    return proplist;
+
+fail:
+    if (proplist)
+        pa_proplist_free(proplist);
+
+    return NULL;
+}
index 9ff298d866e340fc5763ef815386c48b81ff92dc..f35e66cb2dcee2ce3b120749ab532537478f3d1a 100644 (file)
 
 #include <pulsecore/llist.h>
 #include <pulse/mainloop-api.h>
+#include <pulse/proplist.h>
 
 /* A wrap connection is not shared or refcounted, it is available in client side */
 typedef struct pa_dbus_wrap_connection pa_dbus_wrap_connection;
 
 pa_dbus_wrap_connection* pa_dbus_wrap_connection_new(pa_mainloop_api *mainloop, pa_bool_t use_rtclock, DBusBusType type, DBusError *error);
+pa_dbus_wrap_connection* pa_dbus_wrap_connection_new_from_existing(
+        pa_mainloop_api *mainloop,
+        pa_bool_t use_rtclock,
+        DBusConnection *conn);
 void pa_dbus_wrap_connection_free(pa_dbus_wrap_connection* conn);
 
 DBusConnection* pa_dbus_wrap_connection_get(pa_dbus_wrap_connection *conn);
@@ -60,4 +65,42 @@ void pa_dbus_sync_pending_list(pa_dbus_pending **p);
 /* Free up a list of pa_dbus_pending_call objects */
 void pa_dbus_free_pending_list(pa_dbus_pending **p);
 
+/* Sends an error message as the reply to the given message. */
+void pa_dbus_send_error(
+        DBusConnection *c,
+        DBusMessage *in_reply_to,
+        const char *name,
+        const char *format, ...) PA_GCC_PRINTF_ATTR(4, 5);
+
+void pa_dbus_send_empty_reply(DBusConnection *c, DBusMessage *in_reply_to);
+void pa_dbus_send_basic_value_reply(DBusConnection *c, DBusMessage *in_reply_to, int type, void *data);
+void pa_dbus_send_basic_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, int type, void *data);
+void pa_dbus_send_basic_array_variant_reply(
+        DBusConnection *c,
+        DBusMessage *in_reply_to,
+        int item_type,
+        void *array,
+        unsigned n);
+void pa_dbus_send_proplist_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, pa_proplist *proplist);
+
+void pa_dbus_append_basic_array(DBusMessageIter *iter, int item_type, const void *array, unsigned n);
+void pa_dbus_append_basic_array_variant(DBusMessageIter *iter, int item_type, const void *array, unsigned n);
+void pa_dbus_append_basic_variant(DBusMessageIter *iter, int type, void *data);
+void pa_dbus_append_basic_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, int type, void *data);
+void pa_dbus_append_basic_array_variant_dict_entry(
+        DBusMessageIter *dict_iter,
+        const char *key,
+        int item_type,
+        const void *array,
+        unsigned n);
+void pa_dbus_append_proplist(DBusMessageIter *iter, pa_proplist *proplist);
+void pa_dbus_append_proplist_variant(DBusMessageIter *iter, pa_proplist *proplist);
+void pa_dbus_append_proplist_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, pa_proplist *proplist);
+
+/* Returns a new proplist that the caller has to free. If the proplist contains
+ * invalid keys, an error reply is sent and NULL is returned. The iterator must
+ * point to "a{say}" element. This function calls dbus_message_iter_next(iter)
+ * before returning. */
+pa_proplist *pa_dbus_get_proplist_arg(DBusConnection *c, DBusMessage *msg, DBusMessageIter *iter);
+
 #endif
index c7d734d946c5e00223d9f1a8b98d0cc1723dc765..e78cdb9a8ca425f842d62389aaae3c88d9e1a6c6 100644 (file)
@@ -415,3 +415,13 @@ int pa_modargs_get_proplist(pa_modargs *ma, const char *name, pa_proplist *p, pa
 
     return 0;
 }
+
+const char *pa_modargs_iterate(pa_modargs *ma, void **state) {
+    pa_hashmap *map = (pa_hashmap*) ma;
+    struct entry *e;
+
+    if (!(e = pa_hashmap_iterate(map, state, NULL)))
+        return NULL;
+
+    return e->key;
+}
index b3125b10b4cc153b3fd378526925e014d46cc3ad..1ed66e9a537d3388786ae99c2d9284c64c768a8c 100644 (file)
@@ -60,4 +60,13 @@ int pa_modargs_get_sample_spec_and_channel_map(pa_modargs *ma, pa_sample_spec *s
 
 int pa_modargs_get_proplist(pa_modargs *ma, const char *name, pa_proplist *p, pa_update_mode_t m);
 
+/* Iterate through the module argument list. The user should allocate a
+ * state variable of type void* and initialize it with NULL. A pointer
+ * to this variable should then be passed to pa_modargs_iterate()
+ * which should be called in a loop until it returns NULL which
+ * signifies EOL. On each invication this function will return the
+ * key string for the next entry. The keys in the argument list do not
+ * have any particular order. */
+const char *pa_modargs_iterate(pa_modargs *ma, void **state);
+
 #endif
diff --git a/src/pulsecore/protocol-dbus.c b/src/pulsecore/protocol-dbus.c
new file mode 100644 (file)
index 0000000..9102251
--- /dev/null
@@ -0,0 +1,1094 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <dbus/dbus.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/hashmap.h>
+#include <pulsecore/idxset.h>
+#include <pulsecore/shared.h>
+#include <pulsecore/strbuf.h>
+
+#include "protocol-dbus.h"
+
+struct pa_dbus_protocol {
+    PA_REFCNT_DECLARE;
+
+    pa_core *core;
+    pa_hashmap *objects; /* Object path -> struct object_entry */
+    pa_hashmap *connections; /* DBusConnection -> struct connection_entry */
+    pa_idxset *extensions; /* Strings */
+
+    pa_hook hooks[PA_DBUS_PROTOCOL_HOOK_MAX];
+};
+
+struct object_entry {
+    char *path;
+    pa_hashmap *interfaces; /* Interface name -> struct interface_entry */
+    char *introspection;
+};
+
+struct connection_entry {
+    DBusConnection *connection;
+    pa_client *client;
+
+    pa_bool_t listening_for_all_signals;
+
+    /* Contains object paths. If this is empty, then signals from all objects
+     * are accepted. Only used when listening_for_all_signals == TRUE. */
+    pa_idxset *all_signals_objects;
+
+    /* Signal name -> idxset. The idxsets contain object paths. If an idxset is
+     * empty, then that signal is accepted from all objects. Only used when
+     * listening_for_all_signals == FALSE. */
+    pa_hashmap *listening_signals;
+};
+
+struct interface_entry {
+    char *name;
+    pa_hashmap *method_handlers;
+    pa_hashmap *property_handlers;
+    pa_dbus_receive_cb_t get_all_properties_cb;
+    pa_dbus_signal_info *signals;
+    unsigned n_signals;
+    void *userdata;
+};
+
+char *pa_get_dbus_address_from_server_type(pa_server_type_t server_type) {
+    char *address = NULL;
+    char *runtime_path = NULL;
+    char *escaped_path = NULL;
+
+    switch (server_type) {
+        case PA_SERVER_TYPE_USER:
+            pa_assert_se((runtime_path = pa_runtime_path(PA_DBUS_SOCKET_NAME)));
+            pa_assert_se((escaped_path = dbus_address_escape_value(runtime_path)));
+            address = pa_sprintf_malloc("unix:path=%s", escaped_path);
+            break;
+
+        case PA_SERVER_TYPE_SYSTEM:
+            pa_assert_se((escaped_path = dbus_address_escape_value(PA_DBUS_SYSTEM_SOCKET_PATH)));
+            address = pa_sprintf_malloc("unix:path=%s", escaped_path);
+            break;
+
+        case PA_SERVER_TYPE_NONE:
+            address = pa_xnew0(char, 1);
+            break;
+
+        default:
+            pa_assert_not_reached();
+    }
+
+    pa_xfree(runtime_path);
+    pa_xfree(escaped_path);
+
+    return address;
+}
+
+static pa_dbus_protocol *dbus_protocol_new(pa_core *c) {
+    pa_dbus_protocol *p;
+    unsigned i;
+
+    pa_assert(c);
+
+    p = pa_xnew(pa_dbus_protocol, 1);
+    PA_REFCNT_INIT(p);
+    p->core = pa_core_ref(c);
+    p->objects = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+    p->connections = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+    p->extensions = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+    for (i = 0; i < PA_DBUS_PROTOCOL_HOOK_MAX; ++i)
+        pa_hook_init(&p->hooks[i], p);
+
+    pa_assert_se(pa_shared_set(c, "dbus-protocol", p) >= 0);
+
+    return p;
+}
+
+pa_dbus_protocol* pa_dbus_protocol_get(pa_core *c) {
+    pa_dbus_protocol *p;
+
+    if ((p = pa_shared_get(c, "dbus-protocol")))
+        return pa_dbus_protocol_ref(p);
+
+    return dbus_protocol_new(c);
+}
+
+pa_dbus_protocol* pa_dbus_protocol_ref(pa_dbus_protocol *p) {
+    pa_assert(p);
+    pa_assert(PA_REFCNT_VALUE(p) >= 1);
+
+    PA_REFCNT_INC(p);
+
+    return p;
+}
+
+void pa_dbus_protocol_unref(pa_dbus_protocol *p) {
+    unsigned i;
+
+    pa_assert(p);
+    pa_assert(PA_REFCNT_VALUE(p) >= 1);
+
+    if (PA_REFCNT_DEC(p) > 0)
+        return;
+
+    pa_assert(pa_hashmap_isempty(p->objects));
+    pa_assert(pa_hashmap_isempty(p->connections));
+    pa_assert(pa_idxset_isempty(p->extensions));
+
+    pa_hashmap_free(p->objects, NULL, NULL);
+    pa_hashmap_free(p->connections, NULL, NULL);
+    pa_idxset_free(p->extensions, NULL, NULL);
+
+    for (i = 0; i < PA_DBUS_PROTOCOL_HOOK_MAX; ++i)
+        pa_hook_done(&p->hooks[i]);
+
+    pa_assert_se(pa_shared_remove(p->core, "dbus-protocol") >= 0);
+
+    pa_core_unref(p->core);
+
+    pa_xfree(p);
+}
+
+static void update_introspection(struct object_entry *oe) {
+    pa_strbuf *buf;
+    void *interfaces_state = NULL;
+    struct interface_entry *iface_entry = NULL;
+
+    pa_assert(oe);
+
+    buf = pa_strbuf_new();
+    pa_strbuf_puts(buf, DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE);
+    pa_strbuf_puts(buf, "<node>\n");
+
+    PA_HASHMAP_FOREACH(iface_entry, oe->interfaces, interfaces_state) {
+        pa_dbus_method_handler *method_handler;
+        pa_dbus_property_handler *property_handler;
+        void *handlers_state = NULL;
+        unsigned i;
+        unsigned j;
+
+        pa_strbuf_printf(buf, " <interface name=\"%s\">\n", iface_entry->name);
+
+        PA_HASHMAP_FOREACH(method_handler, iface_entry->method_handlers, handlers_state) {
+            pa_strbuf_printf(buf, "  <method name=\"%s\">\n", method_handler->method_name);
+
+            for (i = 0; i < method_handler->n_arguments; ++i)
+                pa_strbuf_printf(buf, "   <arg name=\"%s\" type=\"%s\" direction=\"%s\"/>\n",
+                                 method_handler->arguments[i].name,
+                                 method_handler->arguments[i].type,
+                                 method_handler->arguments[i].direction);
+
+            pa_strbuf_puts(buf, "  </method>\n");
+        }
+
+        handlers_state = NULL;
+
+        PA_HASHMAP_FOREACH(property_handler, iface_entry->property_handlers, handlers_state)
+            pa_strbuf_printf(buf, "  <property name=\"%s\" type=\"%s\" access=\"%s\"/>\n",
+                             property_handler->property_name,
+                             property_handler->type,
+                             property_handler->get_cb ? (property_handler->set_cb ? "readwrite" : "read") : "write");
+
+        for (i = 0; i < iface_entry->n_signals; ++i) {
+            pa_strbuf_printf(buf, "  <signal name=\"%s\">\n", iface_entry->signals[i].name);
+
+            for (j = 0; j < iface_entry->signals[i].n_arguments; ++j)
+                pa_strbuf_printf(buf, "   <arg name=\"%s\" type=\"%s\"/>\n", iface_entry->signals[i].arguments[j].name,
+                                                                             iface_entry->signals[i].arguments[j].type);
+
+            pa_strbuf_puts(buf, "  </signal>\n");
+        }
+
+        pa_strbuf_puts(buf, " </interface>\n");
+    }
+
+    pa_strbuf_puts(buf, " <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n"
+                        "  <method name=\"Introspect\">\n"
+                        "   <arg name=\"data\" type=\"s\" direction=\"out\"/>\n"
+                        "  </method>\n"
+                        " </interface>\n"
+                        " <interface name=\"" DBUS_INTERFACE_PROPERTIES "\">\n"
+                        "  <method name=\"Get\">\n"
+                        "   <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n"
+                        "   <arg name=\"property_name\" type=\"s\" direction=\"in\"/>\n"
+                        "   <arg name=\"value\" type=\"v\" direction=\"out\"/>\n"
+                        "  </method>\n"
+                        "  <method name=\"Set\">\n"
+                        "   <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n"
+                        "   <arg name=\"property_name\" type=\"s\" direction=\"in\"/>\n"
+                        "   <arg name=\"value\" type=\"v\" direction=\"in\"/>\n"
+                        "  </method>\n"
+                        "  <method name=\"GetAll\">\n"
+                        "   <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n"
+                        "   <arg name=\"props\" type=\"a{sv}\" direction=\"out\"/>\n"
+                        "  </method>\n"
+                        " </interface>\n");
+
+    pa_strbuf_puts(buf, "</node>\n");
+
+    pa_xfree(oe->introspection);
+    oe->introspection = pa_strbuf_tostring_free(buf);
+}
+
+/* Return value of find_handler() and its subfunctions. */
+enum find_result_t {
+    /* The received message is a valid .Get call. */
+    FOUND_GET_PROPERTY,
+
+    /* The received message is a valid .Set call. */
+    FOUND_SET_PROPERTY,
+
+    /* The received message is a valid .GetAll call. */
+    FOUND_GET_ALL,
+
+    /* The received message is a valid method call. */
+    FOUND_METHOD,
+
+    /* The interface of the received message hasn't been registered for the
+     * destination object. */
+    NO_SUCH_INTERFACE,
+
+    /* No property handler was found for the received .Get or .Set call. */
+    NO_SUCH_PROPERTY,
+
+    /* The interface argument of a property call didn't match any registered
+     * interface. */
+    NO_SUCH_PROPERTY_INTERFACE,
+
+    /* The received message called .Get or .Set for a property whose access
+     * mode doesn't match the call. */
+    PROPERTY_ACCESS_DENIED,
+
+    /* The new value signature of a .Set call didn't match the expexted
+     * signature. */
+    INVALID_PROPERTY_SIG,
+
+    /* No method handler was found for the received message. */
+    NO_SUCH_METHOD,
+
+    /* The signature of the received message didn't match the expected
+     * signature. Despite the name, this can also be returned for a property
+     * call if its message signature is invalid. */
+    INVALID_METHOD_SIG
+};
+
+/* Data for resolving the correct reaction to a received message. */
+struct call_info {
+    DBusMessage *message; /* The received message. */
+    struct object_entry *obj_entry;
+    const char *interface; /* Destination interface name (extracted from the message). */
+    struct interface_entry *iface_entry;
+
+    const char *property; /* Property name (extracted from the message). */
+    const char *property_interface; /* The interface argument of a property call is stored here. */
+    pa_dbus_property_handler *property_handler;
+    const char *expected_property_sig; /* Property signature from the introspection data. */
+    const char *property_sig; /* The signature of the new value in the received .Set message. */
+    DBusMessageIter variant_iter; /* Iterator pointing to the beginning of the new value variant of a .Set call. */
+
+    const char *method; /* Method name (extracted from the message). */
+    pa_dbus_method_handler *method_handler;
+    const char *expected_method_sig; /* Method signature from the introspection data. */
+    const char *method_sig; /* The signature of the received message. */
+};
+
+/* Called when call_info->property has been set and the property interface has
+ * not been given. In case of a Set call, call_info->property_sig is also set,
+ * which is checked against the expected value in this function. */
+static enum find_result_t find_handler_by_property(struct call_info *call_info) {
+    void *state = NULL;
+
+    pa_assert(call_info);
+
+    PA_HASHMAP_FOREACH(call_info->iface_entry, call_info->obj_entry->interfaces, state) {
+        if ((call_info->property_handler = pa_hashmap_get(call_info->iface_entry->property_handlers, call_info->property))) {
+            if (pa_streq(call_info->method, "Get"))
+                return call_info->property_handler->get_cb ? FOUND_GET_PROPERTY : PROPERTY_ACCESS_DENIED;
+
+            else if (pa_streq(call_info->method, "Set")) {
+                call_info->expected_property_sig = call_info->property_handler->type;
+
+                if (pa_streq(call_info->property_sig, call_info->expected_property_sig))
+                    return call_info->property_handler->set_cb ? FOUND_SET_PROPERTY : PROPERTY_ACCESS_DENIED;
+                else
+                    return INVALID_PROPERTY_SIG;
+
+            } else
+                pa_assert_not_reached();
+        }
+    }
+
+    return NO_SUCH_PROPERTY;
+}
+
+static enum find_result_t find_handler_by_method(struct call_info *call_info) {
+    void *state = NULL;
+
+    pa_assert(call_info);
+
+    PA_HASHMAP_FOREACH(call_info->iface_entry, call_info->obj_entry->interfaces, state) {
+        if ((call_info->method_handler = pa_hashmap_get(call_info->iface_entry->method_handlers, call_info->method)))
+            return FOUND_METHOD;
+    }
+
+    return NO_SUCH_METHOD;
+}
+
+static enum find_result_t find_handler_from_properties_call(struct call_info *call_info) {
+    pa_assert(call_info);
+
+    if (pa_streq(call_info->method, "GetAll")) {
+        call_info->expected_method_sig = "s";
+        if (!pa_streq(call_info->method_sig, call_info->expected_method_sig))
+            return INVALID_METHOD_SIG;
+
+        pa_assert_se(dbus_message_get_args(call_info->message, NULL,
+                                           DBUS_TYPE_STRING, &call_info->property_interface,
+                                           DBUS_TYPE_INVALID));
+
+        if (*call_info->property_interface) {
+            if ((call_info->iface_entry = pa_hashmap_get(call_info->obj_entry->interfaces, call_info->property_interface)))
+                return FOUND_GET_ALL;
+            else
+                return NO_SUCH_PROPERTY_INTERFACE;
+
+        } else {
+            pa_assert_se(call_info->iface_entry = pa_hashmap_first(call_info->obj_entry->interfaces));
+            return FOUND_GET_ALL;
+        }
+
+    } else if (pa_streq(call_info->method, "Get")) {
+        call_info->expected_method_sig = "ss";
+        if (!pa_streq(call_info->method_sig, call_info->expected_method_sig))
+            return INVALID_METHOD_SIG;
+
+        pa_assert_se(dbus_message_get_args(call_info->message, NULL,
+                                           DBUS_TYPE_STRING, &call_info->property_interface,
+                                           DBUS_TYPE_STRING, &call_info->property,
+                                           DBUS_TYPE_INVALID));
+
+        if (*call_info->property_interface) {
+            if (!(call_info->iface_entry = pa_hashmap_get(call_info->obj_entry->interfaces, call_info->property_interface)))
+                return NO_SUCH_PROPERTY_INTERFACE;
+            else if ((call_info->property_handler =
+                        pa_hashmap_get(call_info->iface_entry->property_handlers, call_info->property)))
+                return FOUND_GET_PROPERTY;
+            else
+                return NO_SUCH_PROPERTY;
+
+        } else
+            return find_handler_by_property(call_info);
+
+    } else if (pa_streq(call_info->method, "Set")) {
+        DBusMessageIter msg_iter;
+
+        call_info->expected_method_sig = "ssv";
+        if (!pa_streq(call_info->method_sig, call_info->expected_method_sig))
+            return INVALID_METHOD_SIG;
+
+        pa_assert_se(dbus_message_iter_init(call_info->message, &msg_iter));
+
+        dbus_message_iter_get_basic(&msg_iter, &call_info->property_interface);
+        pa_assert_se(dbus_message_iter_next(&msg_iter));
+        dbus_message_iter_get_basic(&msg_iter, &call_info->property);
+        pa_assert_se(dbus_message_iter_next(&msg_iter));
+
+        dbus_message_iter_recurse(&msg_iter, &call_info->variant_iter);
+
+        call_info->property_sig = dbus_message_iter_get_signature(&call_info->variant_iter);
+
+        if (*call_info->property_interface) {
+            if (!(call_info->iface_entry = pa_hashmap_get(call_info->obj_entry->interfaces, call_info->property_interface)))
+                return NO_SUCH_PROPERTY_INTERFACE;
+
+            else if ((call_info->property_handler =
+                        pa_hashmap_get(call_info->iface_entry->property_handlers, call_info->property))) {
+                call_info->expected_property_sig = call_info->property_handler->type;
+
+                if (pa_streq(call_info->property_sig, call_info->expected_property_sig))
+                    return FOUND_SET_PROPERTY;
+                else
+                    return INVALID_PROPERTY_SIG;
+
+            } else
+                return NO_SUCH_PROPERTY;
+
+        } else
+            return find_handler_by_property(call_info);
+
+    } else
+        pa_assert_not_reached();
+}
+
+static enum find_result_t find_handler(struct call_info *call_info) {
+    pa_assert(call_info);
+
+    if (call_info->interface) {
+        if (pa_streq(call_info->interface, DBUS_INTERFACE_PROPERTIES))
+            return find_handler_from_properties_call(call_info);
+
+        else if (!(call_info->iface_entry = pa_hashmap_get(call_info->obj_entry->interfaces, call_info->interface)))
+            return NO_SUCH_INTERFACE;
+
+        else if ((call_info->method_handler = pa_hashmap_get(call_info->iface_entry->method_handlers, call_info->method)))
+            return FOUND_METHOD;
+
+        else
+            return NO_SUCH_METHOD;
+
+    } else { /* The method call doesn't contain an interface. */
+        if (pa_streq(call_info->method, "Get") || pa_streq(call_info->method, "Set") || pa_streq(call_info->method, "GetAll")) {
+            if (find_handler_by_method(call_info) == FOUND_METHOD)
+                /* The object has a method named Get, Set or GetAll in some other interface than .Properties. */
+                return FOUND_METHOD;
+            else
+                /* Assume this is a .Properties call. */
+                return find_handler_from_properties_call(call_info);
+
+        } else /* This is not a .Properties call. */
+            return find_handler_by_method(call_info);
+    }
+}
+
+static DBusHandlerResult handle_message_cb(DBusConnection *connection, DBusMessage *message, void *user_data) {
+    pa_dbus_protocol *p = user_data;
+    struct call_info call_info;
+
+    pa_assert(connection);
+    pa_assert(message);
+    pa_assert(p);
+    pa_assert(p->objects);
+
+    if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_METHOD_CALL)
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    pa_log_debug("Received message: destination = %s, interface = %s, member = %s",
+                 dbus_message_get_path(message),
+                 dbus_message_get_interface(message),
+                 dbus_message_get_member(message));
+
+    call_info.message = message;
+    pa_assert_se(call_info.obj_entry = pa_hashmap_get(p->objects, dbus_message_get_path(message)));
+    call_info.interface = dbus_message_get_interface(message);
+    pa_assert_se(call_info.method = dbus_message_get_member(message));
+    pa_assert_se(call_info.method_sig = dbus_message_get_signature(message));
+
+    if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect") ||
+        (!dbus_message_get_interface(message) && dbus_message_has_member(message, "Introspect"))) {
+        pa_dbus_send_basic_value_reply(connection, message, DBUS_TYPE_STRING, &call_info.obj_entry->introspection);
+        goto finish;
+    }
+
+    switch (find_handler(&call_info)) {
+        case FOUND_GET_PROPERTY:
+            call_info.property_handler->get_cb(connection, message, call_info.iface_entry->userdata);
+            break;
+
+        case FOUND_SET_PROPERTY:
+            call_info.property_handler->set_cb(connection, message, &call_info.variant_iter, call_info.iface_entry->userdata);
+            break;
+
+        case FOUND_METHOD:
+            call_info.method_handler->receive_cb(connection, message, call_info.iface_entry->userdata);
+            break;
+
+        case FOUND_GET_ALL:
+            if (call_info.iface_entry->get_all_properties_cb)
+                call_info.iface_entry->get_all_properties_cb(connection, message, call_info.iface_entry->userdata);
+            else {
+                DBusMessage *dummy_reply = NULL;
+                DBusMessageIter msg_iter;
+                DBusMessageIter dict_iter;
+
+                pa_assert_se(dummy_reply = dbus_message_new_method_return(message));
+                dbus_message_iter_init_append(dummy_reply, &msg_iter);
+                pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+                pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+                pa_assert_se(dbus_connection_send(connection, dummy_reply, NULL));
+                dbus_message_unref(dummy_reply);
+            }
+            break;
+
+        case PROPERTY_ACCESS_DENIED:
+            pa_dbus_send_error(connection, message, DBUS_ERROR_ACCESS_DENIED,
+                               "%s access denied for property %s", call_info.method, call_info.property);
+            break;
+
+        case NO_SUCH_METHOD:
+            pa_dbus_send_error(connection, message, DBUS_ERROR_UNKNOWN_METHOD, "No such method: %s", call_info.method);
+            break;
+
+        case NO_SUCH_PROPERTY:
+            pa_dbus_send_error(connection, message, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "No such property: %s", call_info.property);
+            break;
+
+        case INVALID_METHOD_SIG:
+            pa_dbus_send_error(connection, message, DBUS_ERROR_INVALID_ARGS,
+                               "Invalid signature for method %s: '%s'. Expected '%s'.",
+                               call_info.method, call_info.method_sig, call_info.expected_method_sig);
+            break;
+
+        case INVALID_PROPERTY_SIG:
+            pa_dbus_send_error(connection, message, DBUS_ERROR_INVALID_ARGS,
+                               "Invalid signature for property %s: '%s'. Expected '%s'.",
+                               call_info.property, call_info.property_sig, call_info.expected_property_sig);
+
+        default:
+            pa_assert_not_reached();
+    }
+
+finish:
+    return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusObjectPathVTable vtable = {
+    .unregister_function = NULL,
+    .message_function = handle_message_cb,
+    .dbus_internal_pad1 = NULL,
+    .dbus_internal_pad2 = NULL,
+    .dbus_internal_pad3 = NULL,
+    .dbus_internal_pad4 = NULL
+};
+
+static void register_object(pa_dbus_protocol *p, struct object_entry *obj_entry) {
+    struct connection_entry *conn_entry;
+    void *state = NULL;
+
+    pa_assert(p);
+    pa_assert(obj_entry);
+
+    PA_HASHMAP_FOREACH(conn_entry, p->connections, state)
+        pa_assert_se(dbus_connection_register_object_path(conn_entry->connection, obj_entry->path, &vtable, p));
+}
+
+static pa_dbus_arg_info *copy_args(const pa_dbus_arg_info *src, unsigned n) {
+    pa_dbus_arg_info *dst;
+    unsigned i;
+
+    if (n == 0)
+        return NULL;
+
+    pa_assert(src);
+
+    dst = pa_xnew0(pa_dbus_arg_info, n);
+
+    for (i = 0; i < n; ++i) {
+        dst[i].name = pa_xstrdup(src[i].name);
+        dst[i].type = pa_xstrdup(src[i].type);
+        dst[i].direction = pa_xstrdup(src[i].direction);
+    }
+
+    return dst;
+}
+
+static pa_hashmap *create_method_handlers(const pa_dbus_interface_info *info) {
+    pa_hashmap *handlers;
+    unsigned i;
+
+    pa_assert(info);
+    pa_assert(info->method_handlers || info->n_method_handlers == 0);
+
+    handlers = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+    for (i = 0; i < info->n_method_handlers; ++i) {
+        pa_dbus_method_handler *h = pa_xnew(pa_dbus_method_handler, 1);
+        h->method_name = pa_xstrdup(info->method_handlers[i].method_name);
+        h->arguments = copy_args(info->method_handlers[i].arguments, info->method_handlers[i].n_arguments);
+        h->n_arguments = info->method_handlers[i].n_arguments;
+        h->receive_cb = info->method_handlers[i].receive_cb;
+
+        pa_hashmap_put(handlers, h->method_name, h);
+    }
+
+    return handlers;
+}
+
+static pa_hashmap *create_property_handlers(const pa_dbus_interface_info *info) {
+    pa_hashmap *handlers;
+    unsigned i = 0;
+
+    pa_assert(info);
+    pa_assert(info->property_handlers || info->n_property_handlers == 0);
+
+    handlers = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+    for (i = 0; i < info->n_property_handlers; ++i) {
+        pa_dbus_property_handler *h = pa_xnew(pa_dbus_property_handler, 1);
+        h->property_name = pa_xstrdup(info->property_handlers[i].property_name);
+        h->type = pa_xstrdup(info->property_handlers[i].type);
+        h->get_cb = info->property_handlers[i].get_cb;
+        h->set_cb = info->property_handlers[i].set_cb;
+
+        pa_hashmap_put(handlers, h->property_name, h);
+    }
+
+    return handlers;
+}
+
+static pa_dbus_signal_info *copy_signals(const pa_dbus_interface_info *info) {
+    pa_dbus_signal_info *dst;
+    unsigned i;
+
+    pa_assert(info);
+
+    if (info->n_signals == 0)
+        return NULL;
+
+    pa_assert(info->signals);
+
+    dst = pa_xnew(pa_dbus_signal_info, info->n_signals);
+
+    for (i = 0; i < info->n_signals; ++i) {
+        dst[i].name = pa_xstrdup(info->signals[i].name);
+        dst[i].arguments = copy_args(info->signals[i].arguments, info->signals[i].n_arguments);
+        dst[i].n_arguments = info->signals[i].n_arguments;
+    }
+
+    return dst;
+}
+
+int pa_dbus_protocol_add_interface(pa_dbus_protocol *p,
+                                   const char *path,
+                                   const pa_dbus_interface_info *info,
+                                   void *userdata) {
+    struct object_entry *obj_entry;
+    struct interface_entry *iface_entry;
+    pa_bool_t obj_entry_created = FALSE;
+
+    pa_assert(p);
+    pa_assert(path);
+    pa_assert(info);
+    pa_assert(info->name);
+    pa_assert(info->method_handlers || info->n_method_handlers == 0);
+    pa_assert(info->property_handlers || info->n_property_handlers == 0);
+    pa_assert(info->get_all_properties_cb || info->n_property_handlers == 0);
+    pa_assert(info->signals || info->n_signals == 0);
+
+    if (!(obj_entry = pa_hashmap_get(p->objects, path))) {
+        obj_entry = pa_xnew(struct object_entry, 1);
+        obj_entry->path = pa_xstrdup(path);
+        obj_entry->interfaces = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+        obj_entry->introspection = NULL;
+
+        pa_hashmap_put(p->objects, path, obj_entry);
+        obj_entry_created = TRUE;
+    }
+
+    if (pa_hashmap_get(obj_entry->interfaces, info->name) != NULL)
+        goto fail; /* The interface was already registered. */
+
+    iface_entry = pa_xnew(struct interface_entry, 1);
+    iface_entry->name = pa_xstrdup(info->name);
+    iface_entry->method_handlers = create_method_handlers(info);
+    iface_entry->property_handlers = create_property_handlers(info);
+    iface_entry->get_all_properties_cb = info->get_all_properties_cb;
+    iface_entry->signals = copy_signals(info);
+    iface_entry->n_signals = info->n_signals;
+    iface_entry->userdata = userdata;
+    pa_hashmap_put(obj_entry->interfaces, iface_entry->name, iface_entry);
+
+    update_introspection(obj_entry);
+
+    if (obj_entry_created)
+        register_object(p, obj_entry);
+
+    pa_log_debug("Interface %s added for object %s", iface_entry->name, obj_entry->path);
+
+    return 0;
+
+fail:
+    if (obj_entry_created) {
+        pa_hashmap_remove(p->objects, path);
+        pa_xfree(obj_entry);
+    }
+
+    return -1;
+}
+
+static void unregister_object(pa_dbus_protocol *p, struct object_entry *obj_entry) {
+    struct connection_entry *conn_entry;
+    void *state = NULL;
+
+    pa_assert(p);
+    pa_assert(obj_entry);
+
+    PA_HASHMAP_FOREACH(conn_entry, p->connections, state)
+        pa_assert_se(dbus_connection_unregister_object_path(conn_entry->connection, obj_entry->path));
+}
+
+static void method_handler_free_cb(void *p, void *userdata) {
+    pa_dbus_method_handler *h = p;
+    unsigned i;
+
+    pa_assert(h);
+
+    pa_xfree((char *) h->method_name);
+
+    for (i = 0; i < h->n_arguments; ++i) {
+        pa_xfree((char *) h->arguments[i].name);
+        pa_xfree((char *) h->arguments[i].type);
+        pa_xfree((char *) h->arguments[i].direction);
+    }
+
+    pa_xfree((pa_dbus_arg_info *) h->arguments);
+}
+
+static void property_handler_free_cb(void *p, void *userdata) {
+    pa_dbus_property_handler *h = p;
+
+    pa_assert(h);
+
+    pa_xfree((char *) h->property_name);
+    pa_xfree((char *) h->type);
+
+    pa_xfree(h);
+}
+
+int pa_dbus_protocol_remove_interface(pa_dbus_protocol *p, const char* path, const char* interface) {
+    struct object_entry *obj_entry;
+    struct interface_entry *iface_entry;
+    unsigned i;
+
+    pa_assert(p);
+    pa_assert(path);
+    pa_assert(interface);
+
+    if (!(obj_entry = pa_hashmap_get(p->objects, path)))
+        return -1;
+
+    if (!(iface_entry = pa_hashmap_remove(obj_entry->interfaces, interface)))
+        return -1;
+
+    update_introspection(obj_entry);
+
+    pa_log_debug("Interface %s removed from object %s", iface_entry->name, obj_entry->path);
+
+    pa_xfree(iface_entry->name);
+    pa_hashmap_free(iface_entry->method_handlers, method_handler_free_cb, NULL);
+    pa_hashmap_free(iface_entry->property_handlers, property_handler_free_cb, NULL);
+
+    for (i = 0; i < iface_entry->n_signals; ++i) {
+        unsigned j;
+
+        pa_xfree((char *) iface_entry->signals[i].name);
+
+        for (j = 0; j < iface_entry->signals[i].n_arguments; ++j) {
+            pa_xfree((char *) iface_entry->signals[i].arguments[j].name);
+            pa_xfree((char *) iface_entry->signals[i].arguments[j].type);
+            pa_assert(iface_entry->signals[i].arguments[j].direction == NULL);
+        }
+
+        pa_xfree((pa_dbus_arg_info *) iface_entry->signals[i].arguments);
+    }
+
+    pa_xfree(iface_entry->signals);
+    pa_xfree(iface_entry);
+
+    if (pa_hashmap_isempty(obj_entry->interfaces)) {
+        unregister_object(p, obj_entry);
+
+        pa_hashmap_remove(p->objects, path);
+        pa_xfree(obj_entry->path);
+        pa_hashmap_free(obj_entry->interfaces, NULL, NULL);
+        pa_xfree(obj_entry->introspection);
+        pa_xfree(obj_entry);
+    }
+
+    return 0;
+}
+
+static void register_all_objects(pa_dbus_protocol *p, DBusConnection *conn) {
+    struct object_entry *obj_entry;
+    void *state = NULL;
+
+    pa_assert(p);
+    pa_assert(conn);
+
+    PA_HASHMAP_FOREACH(obj_entry, p->objects, state)
+        pa_assert_se(dbus_connection_register_object_path(conn, obj_entry->path, &vtable, p));
+}
+
+int pa_dbus_protocol_register_connection(pa_dbus_protocol *p, DBusConnection *conn, pa_client *client) {
+    struct connection_entry *conn_entry;
+
+    pa_assert(p);
+    pa_assert(conn);
+    pa_assert(client);
+
+    if (pa_hashmap_get(p->connections, conn))
+        return -1; /* The connection was already registered. */
+
+    register_all_objects(p, conn);
+
+    conn_entry = pa_xnew(struct connection_entry, 1);
+    conn_entry->connection = dbus_connection_ref(conn);
+    conn_entry->client = client;
+    conn_entry->listening_for_all_signals = FALSE;
+    conn_entry->all_signals_objects = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+    conn_entry->listening_signals = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+    pa_hashmap_put(p->connections, conn, conn_entry);
+
+    return 0;
+}
+
+static void unregister_all_objects(pa_dbus_protocol *p, DBusConnection *conn) {
+    struct object_entry *obj_entry;
+    void *state = NULL;
+
+    pa_assert(p);
+    pa_assert(conn);
+
+    PA_HASHMAP_FOREACH(obj_entry, p->objects, state)
+        pa_assert_se(dbus_connection_unregister_object_path(conn, obj_entry->path));
+}
+
+static void free_listened_object_name_cb(void *p, void *userdata) {
+    pa_assert(p);
+
+    pa_xfree(p);
+}
+
+static void free_listening_signals_idxset_cb(void *p, void *userdata) {
+    pa_idxset *set = p;
+
+    pa_assert(set);
+
+    pa_idxset_free(set, free_listened_object_name_cb, NULL);
+}
+
+int pa_dbus_protocol_unregister_connection(pa_dbus_protocol *p, DBusConnection *conn) {
+    struct connection_entry *conn_entry;
+
+    pa_assert(p);
+    pa_assert(conn);
+
+    if (!(conn_entry = pa_hashmap_remove(p->connections, conn)))
+        return -1;
+
+    unregister_all_objects(p, conn);
+
+    dbus_connection_unref(conn_entry->connection);
+    pa_idxset_free(conn_entry->all_signals_objects, free_listened_object_name_cb, NULL);
+    pa_hashmap_free(conn_entry->listening_signals, free_listening_signals_idxset_cb, NULL);
+    pa_xfree(conn_entry);
+
+    return 0;
+}
+
+pa_client *pa_dbus_protocol_get_client(pa_dbus_protocol *p, DBusConnection *conn) {
+    struct connection_entry *conn_entry;
+
+    pa_assert(p);
+    pa_assert(conn);
+
+    if (!(conn_entry = pa_hashmap_get(p->connections, conn)))
+        return NULL;
+
+    return conn_entry->client;
+}
+
+void pa_dbus_protocol_add_signal_listener(
+        pa_dbus_protocol *p,
+        DBusConnection *conn,
+        const char *signal,
+        char **objects,
+        unsigned n_objects) {
+    struct connection_entry *conn_entry;
+    pa_idxset *object_set;
+    char *object_path;
+    unsigned i;
+
+    pa_assert(p);
+    pa_assert(conn);
+    pa_assert(objects || n_objects == 0);
+
+    pa_assert_se((conn_entry = pa_hashmap_get(p->connections, conn)));
+
+    /* all_signals_objects will either be emptied or replaced with new objects,
+     * so we empty it here unconditionally. If listening_for_all_signals is
+     * currently FALSE, the idxset is empty already. */
+    while ((object_path = pa_idxset_steal_first(conn_entry->all_signals_objects, NULL)))
+        pa_xfree(object_path);
+
+    if (signal) {
+        conn_entry->listening_for_all_signals = FALSE;
+
+        /* Replace the old object list with a new one. */
+        if ((object_set = pa_hashmap_remove(conn_entry->listening_signals, signal)))
+            pa_idxset_free(object_set, free_listened_object_name_cb, NULL);
+        object_set = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+        for (i = 0; i < n_objects; ++i)
+            pa_idxset_put(object_set, pa_xstrdup(objects[i]), NULL);
+
+        pa_hashmap_put(conn_entry->listening_signals, signal, object_set);
+
+    } else {
+        conn_entry->listening_for_all_signals = TRUE;
+
+        /* We're not interested in individual signals anymore, so let's empty
+         * listening_signals. */
+        while ((object_set = pa_hashmap_steal_first(conn_entry->listening_signals)))
+            pa_idxset_free(object_set, free_listened_object_name_cb, NULL);
+
+        for (i = 0; i < n_objects; ++i)
+            pa_idxset_put(conn_entry->all_signals_objects, pa_xstrdup(objects[i]), NULL);
+    }
+}
+
+void pa_dbus_protocol_remove_signal_listener(pa_dbus_protocol *p, DBusConnection *conn, const char *signal) {
+    struct connection_entry *conn_entry;
+    pa_idxset *object_set;
+
+    pa_assert(p);
+    pa_assert(conn);
+
+    pa_assert_se((conn_entry = pa_hashmap_get(p->connections, conn)));
+
+    if (signal) {
+        if ((object_set = pa_hashmap_get(conn_entry->listening_signals, signal)))
+            pa_idxset_free(object_set, free_listened_object_name_cb, NULL);
+
+    } else {
+        char *object_path;
+
+        conn_entry->listening_for_all_signals = FALSE;
+
+        while ((object_path = pa_idxset_steal_first(conn_entry->all_signals_objects, NULL)))
+            pa_xfree(object_path);
+
+        while ((object_set = pa_hashmap_steal_first(conn_entry->listening_signals)))
+            pa_idxset_free(object_set, free_listened_object_name_cb, NULL);
+    }
+}
+
+void pa_dbus_protocol_send_signal(pa_dbus_protocol *p, DBusMessage *signal) {
+    struct connection_entry *conn_entry;
+    void *state = NULL;
+    pa_idxset *object_set;
+    DBusMessage *signal_copy;
+    char *signal_string;
+
+    pa_assert(p);
+    pa_assert(signal);
+    pa_assert(dbus_message_get_type(signal) == DBUS_MESSAGE_TYPE_SIGNAL);
+    pa_assert_se(dbus_message_get_interface(signal));
+    pa_assert_se(dbus_message_get_member(signal));
+
+    signal_string = pa_sprintf_malloc("%s.%s", dbus_message_get_interface(signal), dbus_message_get_member(signal));
+
+    PA_HASHMAP_FOREACH(conn_entry, p->connections, state) {
+        if ((conn_entry->listening_for_all_signals /* Case 1: listening for all signals */
+             && (pa_idxset_get_by_data(conn_entry->all_signals_objects, dbus_message_get_path(signal), NULL)
+                 || pa_idxset_isempty(conn_entry->all_signals_objects)))
+
+            || (!conn_entry->listening_for_all_signals /* Case 2: not listening for all signals */
+                && (object_set = pa_hashmap_get(conn_entry->listening_signals, signal_string))
+                && (pa_idxset_get_by_data(object_set, dbus_message_get_path(signal), NULL)
+                    || pa_idxset_isempty(object_set)))) {
+
+            pa_assert_se(signal_copy = dbus_message_copy(signal));
+            pa_assert_se(dbus_connection_send(conn_entry->connection, signal_copy, NULL));
+            dbus_message_unref(signal_copy);
+        }
+    }
+
+    pa_xfree(signal_string);
+}
+
+const char **pa_dbus_protocol_get_extensions(pa_dbus_protocol *p, unsigned *n) {
+    const char **extensions;
+    const char *ext_name;
+    void *state = NULL;
+    unsigned i = 0;
+
+    pa_assert(p);
+    pa_assert(n);
+
+    *n = pa_idxset_size(p->extensions);
+
+    if (*n <= 0)
+        return NULL;
+
+    extensions = pa_xnew(const char *, *n);
+
+    while ((ext_name = pa_idxset_iterate(p->extensions, &state, NULL)))
+        extensions[i++] = ext_name;
+
+    return extensions;
+}
+
+int pa_dbus_protocol_register_extension(pa_dbus_protocol *p, const char *name) {
+    char *internal_name;
+
+    pa_assert(p);
+    pa_assert(name);
+
+    internal_name = pa_xstrdup(name);
+
+    if (pa_idxset_put(p->extensions, internal_name, NULL) < 0) {
+        pa_xfree(internal_name);
+        return -1;
+    }
+
+    pa_hook_fire(&p->hooks[PA_DBUS_PROTOCOL_HOOK_EXTENSION_REGISTERED], internal_name);
+
+    return 0;
+}
+
+int pa_dbus_protocol_unregister_extension(pa_dbus_protocol *p, const char *name) {
+    char *internal_name;
+
+    pa_assert(p);
+    pa_assert(name);
+
+    if (!(internal_name = pa_idxset_remove_by_data(p->extensions, name, NULL)))
+        return -1;
+
+    pa_hook_fire(&p->hooks[PA_DBUS_PROTOCOL_HOOK_EXTENSION_UNREGISTERED], internal_name);
+
+    pa_xfree(internal_name);
+
+    return 0;
+}
+
+pa_hook_slot *pa_dbus_protocol_hook_connect(
+        pa_dbus_protocol *p,
+        pa_dbus_protocol_hook_t hook,
+        pa_hook_priority_t prio,
+        pa_hook_cb_t cb,
+        void *data) {
+    pa_assert(p);
+    pa_assert(hook < PA_DBUS_PROTOCOL_HOOK_MAX);
+    pa_assert(cb);
+
+    return pa_hook_connect(&p->hooks[hook], prio, cb, data);
+}
diff --git a/src/pulsecore/protocol-dbus.h b/src/pulsecore/protocol-dbus.h
new file mode 100644 (file)
index 0000000..6d100f7
--- /dev/null
@@ -0,0 +1,217 @@
+#ifndef fooprotocoldbushfoo
+#define fooprotocoldbushfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Tanu Kaskinen
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#include <dbus/dbus.h>
+
+#include <pulsecore/core.h>
+#include <pulsecore/macro.h>
+
+#define PA_DBUS_DEFAULT_PORT 24883
+#define PA_DBUS_SOCKET_NAME "dbus-socket"
+
+#define PA_DBUS_SYSTEM_SOCKET_PATH PA_SYSTEM_RUNTIME_PATH PA_PATH_SEP PA_DBUS_SOCKET_NAME
+
+#define PA_DBUS_CORE_INTERFACE "org.PulseAudio.Core1"
+#define PA_DBUS_CORE_OBJECT_PATH "/org/pulseaudio/core1"
+
+#define PA_DBUS_ERROR_NO_SUCH_PROPERTY PA_DBUS_CORE_INTERFACE ".NoSuchPropertyError"
+#define PA_DBUS_ERROR_NOT_FOUND PA_DBUS_CORE_INTERFACE ".NotFoundError"
+
+/* Returns the default address of the server type in the escaped form. For
+ * PA_SERVER_TYPE_NONE an empty string is returned. The caller frees the
+ * string. */
+char *pa_get_dbus_address_from_server_type(pa_server_type_t server_type);
+
+typedef struct pa_dbus_protocol pa_dbus_protocol;
+
+/* This function either creates a new pa_dbus_protocol object, or if one
+ * already exists, increases the reference count. */
+pa_dbus_protocol* pa_dbus_protocol_get(pa_core *c);
+
+pa_dbus_protocol* pa_dbus_protocol_ref(pa_dbus_protocol *p);
+void pa_dbus_protocol_unref(pa_dbus_protocol *p);
+
+/* Called when a received message needs handling. Completely ignoring the
+ * message isn't a good idea; if you can't handle the message, reply with an
+ * error.
+ *
+ * The message signature is already checked against the introspection data, so
+ * you don't have to do that yourself.
+ *
+ * All messages are method calls. */
+typedef void (*pa_dbus_receive_cb_t)(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+/* A specialized version of pa_dbus_receive_cb_t: the additional iterator
+ * argument points to the element inside the new value variant.
+ *
+ * The new value signature is checked against the introspection data, so you
+ * don't have to do that yourself. */
+typedef void (*pa_dbus_set_property_cb_t)(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+
+typedef struct pa_dbus_arg_info {
+    const char *name;
+    const char *type;
+    const char *direction; /* NULL for signal arguments. */
+} pa_dbus_arg_info;
+
+typedef struct pa_dbus_signal_info {
+    const char *name;
+    const pa_dbus_arg_info *arguments; /* NULL, if the signal has no args. */
+    unsigned n_arguments;
+} pa_dbus_signal_info;
+
+typedef struct pa_dbus_method_handler {
+    const char *method_name;
+    const pa_dbus_arg_info *arguments; /* NULL, if the method has no args. */
+    unsigned n_arguments;
+    pa_dbus_receive_cb_t receive_cb;
+} pa_dbus_method_handler;
+
+typedef struct pa_dbus_property_handler {
+    const char *property_name;
+    const char *type;
+
+    /* The access mode for the property is determined by checking whether
+     * get_cb or set_cb is NULL. */
+    pa_dbus_receive_cb_t get_cb;
+    pa_dbus_set_property_cb_t set_cb;
+} pa_dbus_property_handler;
+
+typedef struct pa_dbus_interface_info {
+    const char* name;
+    const pa_dbus_method_handler *method_handlers; /* NULL, if the interface has no methods. */
+    unsigned n_method_handlers;
+    const pa_dbus_property_handler *property_handlers; /* NULL, if the interface has no properties. */
+    unsigned n_property_handlers;
+    const pa_dbus_receive_cb_t get_all_properties_cb; /* May be NULL, in which case GetAll returns an error. */
+    const pa_dbus_signal_info *signals; /* NULL, if the interface has no signals. */
+    unsigned n_signals;
+} pa_dbus_interface_info;
+
+
+/* The following functions may only be called from the main thread. */
+
+/* Registers the given interface to the given object path. It doesn't matter
+ * whether or not the object has already been registered; if it is, then its
+ * interface set is extended.
+ *
+ * Introspection requests are handled automatically.
+ *
+ * Userdata is passed to all the callbacks.
+ *
+ * Fails and returns a negative number if the object already has the interface
+ * registered. */
+int pa_dbus_protocol_add_interface(pa_dbus_protocol *p, const char *path, const pa_dbus_interface_info *info, void *userdata);
+
+/* Returns a negative number if the given object doesn't have the given
+ * interface registered. */
+int pa_dbus_protocol_remove_interface(pa_dbus_protocol *p, const char* path, const char* interface);
+
+/* Fails and returns a negative number if the connection is already
+ * registered. */
+int pa_dbus_protocol_register_connection(pa_dbus_protocol *p, DBusConnection *conn, pa_client *client);
+
+/* Returns a negative number if the connection isn't registered. */
+int pa_dbus_protocol_unregister_connection(pa_dbus_protocol *p, DBusConnection *conn);
+
+/* Returns NULL if the connection isn't registered. */
+pa_client *pa_dbus_protocol_get_client(pa_dbus_protocol *p, DBusConnection *conn);
+
+/* Enables signal receiving for the given connection. The connection must have
+ * been registered earlier. The signal string must contain both the signal
+ * interface and the signal name, concatenated using a period as the separator.
+ *
+ * If the signal argument is NULL, all signals will be sent to the connection,
+ * otherwise calling this function only adds the given signal to the list of
+ * signals that will be delivered to the connection.
+ *
+ * The objects argument is a list of object paths. If the list is not empty,
+ * only signals from the given objects are delivered. If this function is
+ * called multiple time for the same connection and signal, the latest call
+ * always replaces the previous object list. */
+void pa_dbus_protocol_add_signal_listener(
+        pa_dbus_protocol *p,
+        DBusConnection *conn,
+        const char *signal,
+        char **objects,
+        unsigned n_objects);
+
+/* Disables the delivery of the signal for the given connection. The connection
+ * must have been registered. If signal is NULL, all signals are disabled. If
+ * signal is non-NULL and _add_signal_listener() was previously called with
+ * NULL signal (causing all signals to be enabled), this function doesn't do
+ * anything. Also, if the signal wasn't enabled before, this function doesn't
+ * do anything in that case either. */
+void pa_dbus_protocol_remove_signal_listener(pa_dbus_protocol *p, DBusConnection *conn, const char *signal);
+
+/* Sends the given signal to all interested clients. By default no signals are
+ * sent - clients have to explicitly to request signals by calling
+ * .Core1.ListenForSignal. That method's handler then calls
+ * pa_dbus_protocol_add_signal_listener(). */
+void pa_dbus_protocol_send_signal(pa_dbus_protocol *p, DBusMessage *signal);
+
+/* Returns an array of extension identifier strings. The strings pointers point
+ * to the internal copies, so don't free the strings. The caller must free the
+ * array, however. Also, do not save the returned pointer or any of the string
+ * pointers, because the contained strings may be freed at any time. If you
+ * need to save the array, copy it. */
+const char **pa_dbus_protocol_get_extensions(pa_dbus_protocol *p, unsigned *n);
+
+/* Modules that want to provide a D-Bus interface for clients should register
+ * an identifier that the clients can use to check whether the additional
+ * functionality is available.
+ *
+ * This function registers the extension with the given name. It is recommended
+ * that the name follows the D-Bus interface naming convention, so that the
+ * names remain unique in case there will be at some point in the future
+ * extensions that aren't included with the main PulseAudio source tree. For
+ * in-tree extensions the convention is to use the org.PulseAudio.Ext
+ * namespace.
+ *
+ * It is suggested that the name contains a version number, and whenever the
+ * extension interface is modified in non-backwards compatible way, the version
+ * number is incremented.
+ *
+ * Fails and returns a negative number if the extension is already registered.
+ */
+int pa_dbus_protocol_register_extension(pa_dbus_protocol *p, const char *name);
+
+/* Returns a negative number if the extension isn't registered. */
+int pa_dbus_protocol_unregister_extension(pa_dbus_protocol *p, const char *name);
+
+/* All hooks have the pa_dbus_protocol object as hook data. */
+typedef enum pa_dbus_protocol_hook {
+    PA_DBUS_PROTOCOL_HOOK_EXTENSION_REGISTERED, /* Extension name as call data. */
+    PA_DBUS_PROTOCOL_HOOK_EXTENSION_UNREGISTERED, /* Extension name as call data. */
+    PA_DBUS_PROTOCOL_HOOK_MAX
+} pa_dbus_protocol_hook_t;
+
+pa_hook_slot *pa_dbus_protocol_hook_connect(
+        pa_dbus_protocol *p,
+        pa_dbus_protocol_hook_t hook,
+        pa_hook_priority_t prio,
+        pa_hook_cb_t cb,
+        void *data);
+
+#endif