diff options
| author | Lennart Poettering <lennart@poettering.net> | 2009-10-07 03:39:30 +0200 | 
|---|---|---|
| committer | Lennart Poettering <lennart@poettering.net> | 2009-10-07 03:39:30 +0200 | 
| commit | 692ce73899285c6ed07e93084a5f830a9a8effcc (patch) | |
| tree | fb57aacd95086eb35787f902353857daf3f8fe95 /src | |
| parent | b3592a160f0d2a28605048a81c0261bf7c45acbb (diff) | |
| parent | 019331d25b6af107fb8cacc3ada552e7567a64bf (diff) | |
Merge remote branch 'tanuk/dbus-work'
Diffstat (limited to 'src')
48 files changed, 11607 insertions, 46 deletions
| diff --git a/src/Makefile.am b/src/Makefile.am index 6544e2aa..69711ac1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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) $(LIBSAMPLERATE_CFLAGS) $(LIBSPEEX_CFLAGS) $(LIBSNDFILE_CFLAGS) $(CAP_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) $(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) diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c index 6e7926f8..571faae4 100644 --- a/src/daemon/daemon-conf.c +++ b/src/daemon/daemon-conf.c @@ -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 = TRUE,      .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)); diff --git a/src/daemon/daemon-conf.h b/src/daemon/daemon-conf.h index dd69e048..41c3c4b7 100644 --- a/src/daemon/daemon-conf.h +++ b/src/daemon/daemon-conf.h @@ -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); diff --git a/src/daemon/daemon.conf.in b/src/daemon/daemon.conf.in index 3af68b0a..7c1a7b8c 100644 --- a/src/daemon/daemon.conf.in +++ b/src/daemon/daemon.conf.in @@ -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 diff --git a/src/daemon/default.pa.in b/src/daemon/default.pa.in index 00c000eb..f9b9eadd 100755 --- a/src/daemon/default.pa.in +++ b/src/daemon/default.pa.in @@ -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 diff --git a/src/daemon/main.c b/src/daemon/main.c index 2e16c187..9e5647a8 100644 --- a/src/daemon/main.c +++ b/src/daemon/main.c @@ -102,6 +102,7 @@  #include "dumpmodules.h"  #include "caps.h"  #include "ltdl-bind-now.h" +#include "server-lookup.h"  #ifdef HAVE_LIBWRAP  /* Only one instance of these variables */ @@ -343,33 +344,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); @@ -399,7 +398,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"); @@ -483,6 +485,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; @@ -569,10 +597,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.")); @@ -859,6 +889,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); @@ -881,34 +914,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) { @@ -920,7 +966,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.")); @@ -933,8 +987,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 index 00000000..45796e72 --- /dev/null +++ b/src/daemon/server-lookup.c @@ -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 index 00000000..c930d5b7 --- /dev/null +++ b/src/daemon/server-lookup.h @@ -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 diff --git a/src/daemon/system.pa.in b/src/daemon/system.pa.in index 27e42815..0ca32bd3 100755 --- a/src/daemon/system.pa.in +++ b/src/daemon/system.pa.in @@ -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 diff --git a/src/map-file b/src/map-file index 95b2803a..54c518ec 100644 --- a/src/map-file +++ b/src/map-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 index 00000000..004e2e88 --- /dev/null +++ b/src/modules/dbus/iface-card-profile.c @@ -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 index 00000000..a09767f8 --- /dev/null +++ b/src/modules/dbus/iface-card-profile.h @@ -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 index 00000000..1714df36 --- /dev/null +++ b/src/modules/dbus/iface-card.c @@ -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 index 00000000..e2c08a3b --- /dev/null +++ b/src/modules/dbus/iface-card.h @@ -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 index 00000000..546370f9 --- /dev/null +++ b/src/modules/dbus/iface-client.c @@ -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(remove_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 index 00000000..e8f151cd --- /dev/null +++ b/src/modules/dbus/iface-client.h @@ -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 index 00000000..169e8e55 --- /dev/null +++ b/src/modules/dbus/iface-core.c @@ -0,0 +1,2197 @@ +/*** +  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_FALLBACK_SINK_UNSET, +    SIGNAL_NEW_SOURCE, +    SIGNAL_SOURCE_REMOVED, +    SIGNAL_FALLBACK_SOURCE_UPDATED, +    SIGNAL_FALLBACK_SOURCE_UNSET, +    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_FALLBACK_SINK_UNSET]     = { .name = "FallbackSinkUnset",     .arguments = NULL,                         .n_arguments = 0 }, +    [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_FALLBACK_SOURCE_UNSET]   = { .name = "FallbackSourceUnset",   .arguments = NULL,                         .n_arguments = 0 }, +    [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; + +                } else if (!new_fallback_sink) { +                    pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, +                                                                   PA_DBUS_CORE_INTERFACE, +                                                                   signals[SIGNAL_FALLBACK_SINK_UNSET].name))); +                    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; + +                } else if (!new_fallback_source) { +                    pa_assert_se((signal = dbus_message_new_signal(PA_DBUS_CORE_OBJECT_PATH, +                                                                   PA_DBUS_CORE_INTERFACE, +                                                                   signals[SIGNAL_FALLBACK_SOURCE_UNSET].name))); +                    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 index 00000000..900b6d1c --- /dev/null +++ b/src/modules/dbus/iface-core.h @@ -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 index 00000000..d403b6a2 --- /dev/null +++ b/src/modules/dbus/iface-device-port.c @@ -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 index 00000000..0461e2ff --- /dev/null +++ b/src/modules/dbus/iface-device-port.h @@ -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 index 00000000..3a747a44 --- /dev/null +++ b/src/modules/dbus/iface-device.c @@ -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_mute(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_mute(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; +    dbus_bool_t mute; +    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_MUTE, +    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_MUTE]                              = { .property_name = "Mute",                          .type = "b",      .get_cb = handle_get_mute,                              .set_cb = handle_set_mute }, +    [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_mute(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->mute); +} + +static void handle_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { +    pa_dbusiface_device *d = userdata; +    dbus_bool_t mute = FALSE; + +    pa_assert(conn); +    pa_assert(msg); +    pa_assert(iter); +    pa_assert(d); + +    dbus_message_iter_get_basic(iter, &mute); + +    if (d->type == DEVICE_TYPE_SINK) +        pa_sink_set_mute(d->sink, mute, TRUE); +    else +        pa_source_set_mute(d->source, mute, 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_MUTE].property_name, DBUS_TYPE_BOOLEAN, &d->mute); +    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_mute = 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_mute = (d->type == DEVICE_TYPE_SINK) ? pa_sink_get_mute(d->sink, FALSE) : pa_source_get_mute(d->source, FALSE); + +    if (d->mute != new_mute) { +        d->mute = new_mute; + +        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->mute, 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->mute = 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->mute = 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 index 00000000..62e05e9a --- /dev/null +++ b/src/modules/dbus/iface-device.h @@ -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 index 00000000..73a84be8 --- /dev/null +++ b/src/modules/dbus/iface-memstats.c @@ -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, ¤t_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, ¤t_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, ¤t_memblocks); +    pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_CURRENT_MEMBLOCKS_SIZE].property_name, DBUS_TYPE_UINT32, ¤t_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 index 00000000..0820e8fe --- /dev/null +++ b/src/modules/dbus/iface-memstats.h @@ -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 index 00000000..e8aea50f --- /dev/null +++ b/src/modules/dbus/iface-module.c @@ -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 index 00000000..68ca1de5 --- /dev/null +++ b/src/modules/dbus/iface-module.h @@ -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 index 00000000..b0542a60 --- /dev/null +++ b/src/modules/dbus/iface-sample.c @@ -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 index 00000000..f1947ce8 --- /dev/null +++ b/src/modules/dbus/iface-sample.h @@ -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 index 00000000..04a45e6c --- /dev/null +++ b/src/modules/dbus/iface-stream.c @@ -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; +    dbus_bool_t mute; +    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_mute(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_set_mute(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_MUTE, +    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_MUTE]            = { .property_name = "Mute",           .type = "b",      .get_cb = handle_get_mute,            .set_cb = handle_set_mute }, +    [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_mute(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->mute); +} + +static void handle_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { +    pa_dbusiface_stream *s = userdata; +    dbus_bool_t mute = FALSE; + +    pa_assert(conn); +    pa_assert(msg); +    pa_assert(iter); +    pa_assert(s); + +    dbus_message_iter_get_basic(iter, &mute); + +    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, mute, 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_MUTE].property_name, DBUS_TYPE_BOOLEAN, &s->mute); +    } + +    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_mute = 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_mute = pa_sink_input_get_mute(s->sink_input); + +        if (s->mute != new_mute) { +            s->mute = new_mute; + +            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->mute, 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->mute = 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->mute = 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 index 00000000..036b4e7e --- /dev/null +++ b/src/modules/dbus/iface-stream.h @@ -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 index 00000000..11064c33 --- /dev/null +++ b/src/modules/dbus/module-dbus-protocol.c @@ -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; +} diff --git a/src/modules/module-stream-restore.c b/src/modules/module-stream-restore.c index 9b6f9143..1e2dc4df 100644 --- a/src/modules/module-stream-restore.c +++ b/src/modules/module-stream-restore.c @@ -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 @@ -51,6 +52,11 @@  #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_mute(DBusConnection *conn, DBusMessage *msg, void *userdata); +static void handle_entry_set_mute(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_MUTE, +    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_MUTE]     = { .property_name = "Mute",    .type = "b",     .get_cb = handle_entry_get_mute,     .set_cb = handle_entry_set_mute } +}; + +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" }, +                                             { "mute",   "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_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) { +    struct dbus_entry *de = userdata; +    struct entry *e; +    dbus_bool_t mute; + +    pa_assert(conn); +    pa_assert(msg); +    pa_assert(de); + +    pa_assert_se(e = read_entry(de->userdata, de->entry_name)); + +    mute = e->muted_valid ? e->muted : FALSE; + +    pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &mute); + +    pa_xfree(e); +} + +static void handle_entry_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) { +    struct dbus_entry *de = userdata; +    dbus_bool_t mute; +    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, &mute); + +    pa_assert_se(e = read_entry(de->userdata, de->entry_name)); + +    updated = !e->muted_valid || e->muted != mute; + +    if (updated) { +        pa_datum key; +        pa_datum value; + +        e->muted = mute; +        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 mute; + +    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 : ""; +    mute = 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_MUTE].property_name, DBUS_TYPE_BOOLEAN, &mute); + +    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 entry* read_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); diff --git a/src/pulse/client-conf.c b/src/pulse/client-conf.c index 4aa4ba1f..62c06f6a 100644 --- a/src/pulse/client-conf.c +++ b/src/pulse/client-conf.c @@ -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 }, diff --git a/src/pulse/client-conf.h b/src/pulse/client-conf.h index ab97dc6a..618216f4 100644 --- a/src/pulse/client-conf.h +++ b/src/pulse/client-conf.h @@ -22,12 +22,13 @@    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 */ diff --git a/src/pulse/client.conf.in b/src/pulse/client.conf.in index 6c8d371c..e03096e0 100644 --- a/src/pulse/client.conf.in +++ b/src/pulse/client.conf.in @@ -22,6 +22,7 @@  ; default-sink =  ; default-source =  ; default-server = +; default-dbus-server =  ; autospawn = yes  ; daemon-binary = @PA_BINARY@ diff --git a/src/pulse/proplist.c b/src/pulse/proplist.c index 048b241a..faa98b79 100644 --- a/src/pulse/proplist.c +++ b/src/pulse/proplist.c @@ -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; +} diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h index 8dff8df4..aae05346 100644 --- a/src/pulse/proplist.h +++ b/src/pulse/proplist.h @@ -358,7 +358,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); @@ -375,6 +375,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 diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c index 8e98e857..27e09cbc 100644 --- a/src/pulsecore/core-util.c +++ b/src/pulsecore/core-util.c @@ -2678,6 +2678,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; diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h index 84752d4d..9986b14a 100644 --- a/src/pulsecore/core-util.h +++ b/src/pulsecore/core-util.h @@ -224,6 +224,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); diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h index c1002f93..bfcea4f6 100644 --- a/src/pulsecore/core.h +++ b/src/pulsecore/core.h @@ -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];  }; diff --git a/src/pulsecore/cpu-arm.h b/src/pulsecore/cpu-arm.h index 3ccd0708..a87cb63b 100644 --- a/src/pulsecore/cpu-arm.h +++ b/src/pulsecore/cpu-arm.h @@ -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 diff --git a/src/pulsecore/cpu-x86.h b/src/pulsecore/cpu-x86.h index b40eb5ce..f6484c59 100644 --- a/src/pulsecore/cpu-x86.h +++ b/src/pulsecore/cpu-x86.h @@ -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 diff --git a/src/pulsecore/dbus-util.c b/src/pulsecore/dbus-util.c index 4e6148f0..e3700ea5 100644 --- a/src/pulsecore/dbus-util.c +++ b/src/pulsecore/dbus-util.c @@ -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; +} diff --git a/src/pulsecore/dbus-util.h b/src/pulsecore/dbus-util.h index 9ff298d8..f35e66cb 100644 --- a/src/pulsecore/dbus-util.h +++ b/src/pulsecore/dbus-util.h @@ -26,11 +26,16 @@  #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 diff --git a/src/pulsecore/modargs.c b/src/pulsecore/modargs.c index c7d734d9..e78cdb9a 100644 --- a/src/pulsecore/modargs.c +++ b/src/pulsecore/modargs.c @@ -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; +} diff --git a/src/pulsecore/modargs.h b/src/pulsecore/modargs.h index b3125b10..1ed66e9a 100644 --- a/src/pulsecore/modargs.h +++ b/src/pulsecore/modargs.h @@ -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 index 00000000..5c1127be --- /dev/null +++ b/src/pulsecore/protocol-dbus.c @@ -0,0 +1,1134 @@ +/*** +  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 *method_signatures; /* Derived from method_handlers. Contains only "in" arguments. */ +    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))) { +            call_info->expected_method_sig = pa_hashmap_get(call_info->iface_entry->method_signatures, call_info->method); + +            if (pa_streq(call_info->method_sig, call_info->expected_method_sig)) +                return FOUND_METHOD; +            else +                return INVALID_METHOD_SIG; +        } +    } + +    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 *extract_method_signatures(pa_hashmap *method_handlers) { +    pa_hashmap *signatures = NULL; +    pa_dbus_method_handler *handler = NULL; +    void *state = NULL; +    pa_strbuf *sig_buf = NULL; +    unsigned i = 0; + +    pa_assert(method_handlers); + +    signatures = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + +    PA_HASHMAP_FOREACH(handler, method_handlers, state) { +        sig_buf = pa_strbuf_new(); + +        for (i = 0; i < handler->n_arguments; ++i) { +            if (pa_streq(handler->arguments[i].direction, "in")) +                pa_strbuf_puts(sig_buf, handler->arguments[i].type); +        } + +        pa_hashmap_put(signatures, handler->method_name, pa_strbuf_tostring_free(sig_buf)); +    } + +    return signatures; +} + +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->method_signatures = extract_method_signatures(iface_entry->method_handlers); +    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 method_signature_free_cb(void *p, void *userdata) { +    pa_assert(p); + +    pa_xfree(p); +} + +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->method_signatures, method_signature_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 index 00000000..6d100f7c --- /dev/null +++ b/src/pulsecore/protocol-dbus.h @@ -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 | 
