/*** This file is part of PulseAudio. Written by David Henningsson 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 . ***/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include 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= " "sink_name= " "sink_properties= " "sink_client_name= " "sink_channels= " "sink_channel_map= " "source_name= " "source_properties= " "source_client_name= " "source_channels= " "source_channel_map= " "connect="); #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]; /* Using index here protects us from module unloading without us knowing */ 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); /* Just a safety check; it isn't such a big deal if the name disappears just after the call. */ 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); }