This file is part of PulseAudio.
Written by David Henningsson <david.henningsson@canonical.com>
Copyright 2010 Canonical Ltd.
Some code taken from other parts of PulseAudio, these are
Copyright 2006-2009 Lennart Poettering
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, see <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <pulse/proplist.h>
#include <pulse/xmalloc.h>
#include <pulsecore/log.h>
#include <pulsecore/modargs.h>
#include <pulsecore/core-util.h>
#include <pulsecore/dbus-shared.h>
#include <pulsecore/strbuf.h>
PA_MODULE_AUTHOR("David Henningsson");
PA_MODULE_DESCRIPTION("Adds JACK sink/source ports when JACK is started");
PA_MODULE_LOAD_ONCE(false);
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_USAGE(
"channels=<number of channels> "
"sink_name=<name for the sink> "
"sink_properties=<properties for the card> "
"sink_client_name=<jack client name> "
"sink_channels=<number of channels> "
"sink_channel_map=<channel map> "
"source_name=<name for the source> "
"source_properties=<properties for the source> "
"source_client_name=<jack client name> "
"source_channels=<number of channels> "
"source_channel_map=<channel map> "
"connect=<connect ports?>");
#define JACK_SERVICE_NAME "org.jackaudio.service"
#define JACK_INTERFACE_NAME "org.jackaudio.JackControl"
#define JACK_INTERFACE_PATH "/org/jackaudio/Controller"
#define SERVICE_FILTER \
"type='signal'," \
"sender='" DBUS_SERVICE_DBUS "'," \
"interface='" DBUS_INTERFACE_DBUS "'," \
"member='NameOwnerChanged'," \
"arg0='" JACK_SERVICE_NAME "'"
#define RUNNING_FILTER(_a) \
"type='signal'," \
"sender='" JACK_SERVICE_NAME "'," \
"interface='" JACK_INTERFACE_NAME "'," \
"member='" _a "'"
static const char* const valid_modargs[] = {
"channels",
"sink_enabled",
"sink_name",
"sink_properties",
"sink_client_name",
"sink_channels",
"sink_channel_map",
"source_enabled",
"source_name",
"source_properties",
"source_client_name",
"source_channels",
"source_channel_map",
"connect",
NULL
};
#define JACK_SS_SINK 0
#define JACK_SS_SOURCE 1
#define JACK_SS_COUNT 2
static const char* const modnames[JACK_SS_COUNT] = {
"module-jack-sink",
"module-jack-source"
};
static const char* const modtypes[JACK_SS_COUNT] = {
"sink",
"source"
};
struct moddata {
bool enabled;
char *name;
pa_proplist *proplist;
char *client_name;
uint32_t channels;
pa_channel_map channel_map;
};
struct userdata {
pa_module *module;
pa_core *core;
pa_dbus_connection *connection;
bool filter_added, match_added;
bool is_service_started;
bool autoconnect_ports;
struct moddata mod_args[JACK_SS_COUNT];
int jack_module_index[JACK_SS_COUNT];
};
static void ensure_ports_stopped(struct userdata* u) {
unsigned i;
pa_assert(u);
for (i = 0; i < JACK_SS_COUNT; i++)
if (u->jack_module_index[i]) {
pa_module_unload_request_by_index(u->core, u->jack_module_index[i], true);
u->jack_module_index[i] = 0;
pa_log_info("Stopped %s.", modnames[i]);
}
}
static char* proplist_to_arg(pa_proplist *p) {
const char *key;
void *state = NULL;
pa_strbuf *buf;
pa_assert(p);
buf = pa_strbuf_new();
while ((key = pa_proplist_iterate(p, &state))) {
const char *v;
char *escaped;
if (!pa_strbuf_isempty(buf))
pa_strbuf_puts(buf, " ");
if ((v = pa_proplist_gets(p, key))) {
pa_strbuf_printf(buf, "%s=\"", key);
escaped = pa_escape(v, "\"'");
pa_strbuf_puts(buf, escaped);
pa_xfree(escaped);
pa_strbuf_puts(buf, "\"");
} else {
const void *value;
size_t nbytes;
char *c;
pa_assert_se(pa_proplist_get(p, key, &value, &nbytes) == 0);
c = pa_xmalloc(nbytes*2+1);
pa_hexstr((const uint8_t*) value, nbytes, c, nbytes*2+1);
pa_strbuf_printf(buf, "%s=hex:%s", key, c);
pa_xfree(c);
}
}
return pa_strbuf_to_string_free(buf);
}
static void ensure_ports_started(struct userdata* u) {
unsigned i;
char *escaped;
pa_assert(u);
for (i = 0; i < JACK_SS_COUNT; i++)
if (u->mod_args[i].enabled && !u->jack_module_index[i]) {
pa_strbuf *args_buf = pa_strbuf_new();
char *args;
pa_module *m;
pa_strbuf_printf(args_buf, "connect=%s", pa_yes_no(u->autoconnect_ports));
if (u->mod_args[i].name) {
escaped = pa_escape(u->mod_args[i].name, "'");
pa_strbuf_printf(args_buf, " %s_name='%s'", modtypes[i], escaped);
pa_xfree(escaped);
}
if (!pa_proplist_isempty(u->mod_args[i].proplist)) {
escaped = proplist_to_arg(u->mod_args[i].proplist);
pa_strbuf_printf(args_buf, " %s_properties='%s'", modtypes[i], escaped);
pa_xfree(escaped);
}
if (u->mod_args[i].client_name) {
escaped = pa_escape(u->mod_args[i].client_name, "'");
pa_strbuf_printf(args_buf, " client_name='%s'", escaped);
pa_xfree(escaped);
}
if (u->mod_args[i].channels > 0)
pa_strbuf_printf(args_buf, " channels=%" PRIu32, u->mod_args[i].channels);
if (u->mod_args[i].channel_map.channels > 0) {
char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
pa_channel_map_snprint(cm, sizeof(cm), &u->mod_args[i].channel_map);
pa_strbuf_printf(args_buf, " channel_map='%s'", cm);
}
args = pa_strbuf_to_string_free(args_buf);
pa_module_load(&m, u->core, modnames[i], args);
pa_xfree(args);
if (m) {
pa_log_info("Successfully started %s.", modnames[i]);
u->jack_module_index[i] = m->index;
}
else
pa_log_info("Failed to start %s.", modnames[i]);
}
}
static bool check_service_started(struct userdata* u) {
DBusError error;
DBusMessage *m = NULL, *reply = NULL;
bool new_status = false;
dbus_bool_t call_result;
pa_assert(u);
dbus_error_init(&error);
if (!dbus_bus_name_has_owner(pa_dbus_connection_get(u->connection),
JACK_SERVICE_NAME, &error)) {
pa_log_debug("jackdbus isn't running.");
goto finish;
}
if (!(m = dbus_message_new_method_call(JACK_SERVICE_NAME, JACK_INTERFACE_PATH, JACK_INTERFACE_NAME, "IsStarted"))) {
pa_log("Failed to allocate IsStarted() method call.");
goto finish;
}
if (!(reply = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->connection), m, -1, &error))) {
pa_log("IsStarted() call failed: %s: %s", error.name, error.message);
goto finish;
}
if (!dbus_message_get_args(reply, &error, DBUS_TYPE_BOOLEAN, &call_result, DBUS_TYPE_INVALID)) {
pa_log("IsStarted() call return failed: %s: %s", error.name, error.message);
goto finish;
}
new_status = call_result;
finish:
if (m)
dbus_message_unref(m);
if (reply)
dbus_message_unref(reply);
dbus_error_free(&error);
if (new_status)
ensure_ports_started(u);
else
ensure_ports_stopped(u);
u->is_service_started = new_status;
return new_status;
}
static DBusHandlerResult dbus_filter_handler(DBusConnection *c, DBusMessage *s, void *userdata) {
struct userdata *u = NULL;
DBusError error;
pa_assert(userdata);
u = ((pa_module*) userdata)->userdata;
pa_assert(u);
dbus_error_init(&error);
if (dbus_message_is_signal(s, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) {
const char *name, *old, *new;
if (!dbus_message_get_args(s, &error,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_STRING, &old,
DBUS_TYPE_STRING, &new,
DBUS_TYPE_INVALID))
goto finish;
if (!pa_streq(name, JACK_SERVICE_NAME))
goto finish;
ensure_ports_stopped(u);
check_service_started(u);
}
else if (dbus_message_is_signal(s, JACK_INTERFACE_NAME, "ServerStarted")) {
ensure_ports_stopped(u);
check_service_started(u);
}
else if (dbus_message_is_signal(s, JACK_INTERFACE_NAME, "ServerStopped")) {
ensure_ports_stopped(u);
}
finish:
dbus_error_free(&error);
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
int pa__init(pa_module *m) {
DBusError error;
pa_dbus_connection *connection = NULL;
struct userdata *u = NULL;
pa_modargs *ma;
uint32_t channels = 0;
unsigned i;
char argname[32];
const char *name;
pa_assert(m);
dbus_error_init(&error);
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->core = m->core;
u->module = m;
u->autoconnect_ports = true;
if (pa_modargs_get_value_boolean(ma, "connect", &u->autoconnect_ports) < 0) {
pa_log("Failed to parse connect= argument.");
goto fail;
}
if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 || (channels > 0 && !pa_channels_valid(channels))) {
pa_log("Failed to parse channels= argument.");
goto fail;
}
for (i = 0; i < JACK_SS_COUNT; i++) {
u->mod_args[i].enabled = true;
pa_snprintf(argname, sizeof(argname), "%s_enabled", modtypes[i]);
if (pa_modargs_get_value_boolean(ma, argname, &u->mod_args[i].enabled) < 0) {
pa_log("Failed to parse %s= argument.", argname);
goto fail;
}
pa_snprintf(argname, sizeof(argname), "%s_name", modtypes[i]);
name = pa_modargs_get_value(ma, argname, NULL);
u->mod_args[i].name = pa_xstrdup(name);
u->mod_args[i].proplist = pa_proplist_new();
pa_snprintf(argname, sizeof(argname), "%s_properties", modtypes[i]);
if (pa_modargs_get_proplist(ma, argname, u->mod_args[i].proplist, PA_UPDATE_REPLACE) < 0) {
pa_log("Invalid %s properties", modtypes[i]);
goto fail;
}
pa_snprintf(argname, sizeof(argname), "%s_client_name", modtypes[i]);
name = pa_modargs_get_value(ma, argname, NULL);
u->mod_args[i].client_name = pa_xstrdup(name);
u->mod_args[i].channels = channels;
pa_snprintf(argname, sizeof(argname), "%s_channels", modtypes[i]);
if (pa_modargs_get_value_u32(ma, argname, &u->mod_args[i].channels) < 0
|| (u->mod_args[i].channels > 0 && !pa_channels_valid(u->mod_args[i].channels))) {
pa_log("Failed to parse %s= argument.", argname);
goto fail;
}
pa_channel_map_init(&u->mod_args[i].channel_map);
pa_snprintf(argname, sizeof(argname), "%s_channel_map", modtypes[i]);
if (pa_modargs_get_value(ma, argname, NULL)) {
if (pa_modargs_get_channel_map(ma, argname, &u->mod_args[i].channel_map) < 0
|| (u->mod_args[i].channels > 0 && u->mod_args[i].channel_map.channels != u->mod_args[i].channels)) {
pa_log("Failed to parse %s= argument.", argname);
goto fail;
}
}
}
if (!(connection = pa_dbus_bus_get(m->core, DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) {
if (connection)
pa_dbus_connection_unref(connection);
pa_log_error("Unable to contact D-Bus session bus: %s: %s", error.name, error.message);
goto fail;
}
u->connection = connection;
if (!dbus_connection_add_filter(pa_dbus_connection_get(connection), dbus_filter_handler, m, NULL)) {
pa_log_error("Unable to add D-Bus filter");
goto fail;
}
u->filter_added = 1;
if (pa_dbus_add_matches(
pa_dbus_connection_get(connection), &error, SERVICE_FILTER,
RUNNING_FILTER("ServerStarted"), RUNNING_FILTER("ServerStopped"), NULL) < 0) {
pa_log_error("Unable to subscribe to signals: %s: %s", error.name, error.message);
goto fail;
}
u->match_added = 1;
check_service_started(u);
pa_modargs_free(ma);
return 0;
fail:
if (ma)
pa_modargs_free(ma);
dbus_error_free(&error);
pa__done(m);
return -1;
}
void pa__done(pa_module *m) {
struct userdata *u;
unsigned i;
pa_assert(m);
if (!(u = m->userdata))
return;
ensure_ports_stopped(u);
if (u->match_added) {
pa_dbus_remove_matches(
pa_dbus_connection_get(u->connection), SERVICE_FILTER,
RUNNING_FILTER("ServerStarted"), RUNNING_FILTER("ServerStopped"), NULL);
}
if (u->filter_added) {
dbus_connection_remove_filter(pa_dbus_connection_get(u->connection), dbus_filter_handler, m);
}
if (u->connection) {
pa_dbus_connection_unref(u->connection);
}
for (i = 0; i < JACK_SS_COUNT; i++) {
pa_xfree(u->mod_args[i].name);
if (u->mod_args[i].proplist)
pa_proplist_free(u->mod_args[i].proplist);
pa_xfree(u->mod_args[i].client_name);
}
pa_xfree(u);
}