1/*** 2 This file is part of PulseAudio. 3 4 Written by David Henningsson <david.henningsson@canonical.com> 5 Copyright 2010 Canonical Ltd. 6 7 Some code taken from other parts of PulseAudio, these are 8 Copyright 2006-2009 Lennart Poettering 9 10 PulseAudio is free software; you can redistribute it and/or modify 11 it under the terms of the GNU Lesser General Public License as published 12 by the Free Software Foundation; either version 2.1 of the License, 13 or (at your option) any later version. 14 15 PulseAudio is distributed in the hope that it will be useful, but 16 WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 General Public License for more details. 19 20 You should have received a copy of the GNU Lesser General Public License 21 along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. 22***/ 23 24#ifdef HAVE_CONFIG_H 25#include <config.h> 26#endif 27 28#include <pulse/proplist.h> 29#include <pulse/xmalloc.h> 30 31#include <pulsecore/log.h> 32#include <pulsecore/modargs.h> 33#include <pulsecore/core-util.h> 34#include <pulsecore/dbus-shared.h> 35#include <pulsecore/strbuf.h> 36 37PA_MODULE_AUTHOR("David Henningsson"); 38PA_MODULE_DESCRIPTION("Adds JACK sink/source ports when JACK is started"); 39PA_MODULE_LOAD_ONCE(false); 40PA_MODULE_VERSION(PACKAGE_VERSION); 41PA_MODULE_USAGE( 42 "channels=<number of channels> " 43 "sink_name=<name for the sink> " 44 "sink_properties=<properties for the card> " 45 "sink_client_name=<jack client name> " 46 "sink_channels=<number of channels> " 47 "sink_channel_map=<channel map> " 48 "source_name=<name for the source> " 49 "source_properties=<properties for the source> " 50 "source_client_name=<jack client name> " 51 "source_channels=<number of channels> " 52 "source_channel_map=<channel map> " 53 "connect=<connect ports?>"); 54 55#define JACK_SERVICE_NAME "org.jackaudio.service" 56#define JACK_INTERFACE_NAME "org.jackaudio.JackControl" 57#define JACK_INTERFACE_PATH "/org/jackaudio/Controller" 58 59#define SERVICE_FILTER \ 60 "type='signal'," \ 61 "sender='" DBUS_SERVICE_DBUS "'," \ 62 "interface='" DBUS_INTERFACE_DBUS "'," \ 63 "member='NameOwnerChanged'," \ 64 "arg0='" JACK_SERVICE_NAME "'" 65 66#define RUNNING_FILTER(_a) \ 67 "type='signal'," \ 68 "sender='" JACK_SERVICE_NAME "'," \ 69 "interface='" JACK_INTERFACE_NAME "'," \ 70 "member='" _a "'" 71 72static const char* const valid_modargs[] = { 73 "channels", 74 "sink_enabled", 75 "sink_name", 76 "sink_properties", 77 "sink_client_name", 78 "sink_channels", 79 "sink_channel_map", 80 "source_enabled", 81 "source_name", 82 "source_properties", 83 "source_client_name", 84 "source_channels", 85 "source_channel_map", 86 "connect", 87 NULL 88}; 89 90#define JACK_SS_SINK 0 91#define JACK_SS_SOURCE 1 92#define JACK_SS_COUNT 2 93 94static const char* const modnames[JACK_SS_COUNT] = { 95 "module-jack-sink", 96 "module-jack-source" 97}; 98 99static const char* const modtypes[JACK_SS_COUNT] = { 100 "sink", 101 "source" 102}; 103 104struct moddata { 105 bool enabled; 106 char *name; 107 pa_proplist *proplist; 108 char *client_name; 109 uint32_t channels; 110 pa_channel_map channel_map; 111}; 112 113struct userdata { 114 pa_module *module; 115 pa_core *core; 116 pa_dbus_connection *connection; 117 bool filter_added, match_added; 118 bool is_service_started; 119 bool autoconnect_ports; 120 struct moddata mod_args[JACK_SS_COUNT]; 121 /* Using index here protects us from module unloading without us knowing */ 122 int jack_module_index[JACK_SS_COUNT]; 123}; 124 125static void ensure_ports_stopped(struct userdata* u) { 126 unsigned i; 127 pa_assert(u); 128 129 for (i = 0; i < JACK_SS_COUNT; i++) 130 if (u->jack_module_index[i]) { 131 pa_module_unload_request_by_index(u->core, u->jack_module_index[i], true); 132 u->jack_module_index[i] = 0; 133 pa_log_info("Stopped %s.", modnames[i]); 134 } 135} 136 137static char* proplist_to_arg(pa_proplist *p) { 138 const char *key; 139 void *state = NULL; 140 pa_strbuf *buf; 141 142 pa_assert(p); 143 144 buf = pa_strbuf_new(); 145 146 while ((key = pa_proplist_iterate(p, &state))) { 147 const char *v; 148 char *escaped; 149 150 if (!pa_strbuf_isempty(buf)) 151 pa_strbuf_puts(buf, " "); 152 153 if ((v = pa_proplist_gets(p, key))) { 154 pa_strbuf_printf(buf, "%s=\"", key); 155 156 escaped = pa_escape(v, "\"'"); 157 pa_strbuf_puts(buf, escaped); 158 pa_xfree(escaped); 159 160 pa_strbuf_puts(buf, "\""); 161 } else { 162 const void *value; 163 size_t nbytes; 164 char *c; 165 166 pa_assert_se(pa_proplist_get(p, key, &value, &nbytes) == 0); 167 c = pa_xmalloc(nbytes*2+1); 168 pa_hexstr((const uint8_t*) value, nbytes, c, nbytes*2+1); 169 170 pa_strbuf_printf(buf, "%s=hex:%s", key, c); 171 pa_xfree(c); 172 } 173 } 174 175 return pa_strbuf_to_string_free(buf); 176} 177 178static void ensure_ports_started(struct userdata* u) { 179 unsigned i; 180 char *escaped; 181 pa_assert(u); 182 183 for (i = 0; i < JACK_SS_COUNT; i++) 184 if (u->mod_args[i].enabled && !u->jack_module_index[i]) { 185 pa_strbuf *args_buf = pa_strbuf_new(); 186 char *args; 187 pa_module *m; 188 pa_strbuf_printf(args_buf, "connect=%s", pa_yes_no(u->autoconnect_ports)); 189 if (u->mod_args[i].name) { 190 escaped = pa_escape(u->mod_args[i].name, "'"); 191 pa_strbuf_printf(args_buf, " %s_name='%s'", modtypes[i], escaped); 192 pa_xfree(escaped); 193 } 194 if (!pa_proplist_isempty(u->mod_args[i].proplist)) { 195 escaped = proplist_to_arg(u->mod_args[i].proplist); 196 pa_strbuf_printf(args_buf, " %s_properties='%s'", modtypes[i], escaped); 197 pa_xfree(escaped); 198 } 199 if (u->mod_args[i].client_name) { 200 escaped = pa_escape(u->mod_args[i].client_name, "'"); 201 pa_strbuf_printf(args_buf, " client_name='%s'", escaped); 202 pa_xfree(escaped); 203 } 204 if (u->mod_args[i].channels > 0) 205 pa_strbuf_printf(args_buf, " channels=%" PRIu32, u->mod_args[i].channels); 206 if (u->mod_args[i].channel_map.channels > 0) { 207 char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; 208 pa_channel_map_snprint(cm, sizeof(cm), &u->mod_args[i].channel_map); 209 pa_strbuf_printf(args_buf, " channel_map='%s'", cm); 210 } 211 args = pa_strbuf_to_string_free(args_buf); 212 pa_module_load(&m, u->core, modnames[i], args); 213 pa_xfree(args); 214 215 if (m) { 216 pa_log_info("Successfully started %s.", modnames[i]); 217 u->jack_module_index[i] = m->index; 218 } 219 else 220 pa_log_info("Failed to start %s.", modnames[i]); 221 } 222} 223 224static bool check_service_started(struct userdata* u) { 225 DBusError error; 226 DBusMessage *m = NULL, *reply = NULL; 227 bool new_status = false; 228 dbus_bool_t call_result; 229 pa_assert(u); 230 231 dbus_error_init(&error); 232 233 /* Just a safety check; it isn't such a big deal if the name disappears just after the call. */ 234 if (!dbus_bus_name_has_owner(pa_dbus_connection_get(u->connection), 235 JACK_SERVICE_NAME, &error)) { 236 pa_log_debug("jackdbus isn't running."); 237 goto finish; 238 } 239 240 if (!(m = dbus_message_new_method_call(JACK_SERVICE_NAME, JACK_INTERFACE_PATH, JACK_INTERFACE_NAME, "IsStarted"))) { 241 pa_log("Failed to allocate IsStarted() method call."); 242 goto finish; 243 } 244 245 if (!(reply = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->connection), m, -1, &error))) { 246 pa_log("IsStarted() call failed: %s: %s", error.name, error.message); 247 goto finish; 248 } 249 250 if (!dbus_message_get_args(reply, &error, DBUS_TYPE_BOOLEAN, &call_result, DBUS_TYPE_INVALID)) { 251 pa_log("IsStarted() call return failed: %s: %s", error.name, error.message); 252 goto finish; 253 } 254 255 new_status = call_result; 256 257finish: 258 if (m) 259 dbus_message_unref(m); 260 if (reply) 261 dbus_message_unref(reply); 262 263 dbus_error_free(&error); 264 if (new_status) 265 ensure_ports_started(u); 266 else 267 ensure_ports_stopped(u); 268 u->is_service_started = new_status; 269 return new_status; 270} 271 272static DBusHandlerResult dbus_filter_handler(DBusConnection *c, DBusMessage *s, void *userdata) { 273 struct userdata *u = NULL; 274 DBusError error; 275 276 pa_assert(userdata); 277 u = ((pa_module*) userdata)->userdata; 278 pa_assert(u); 279 280 dbus_error_init(&error); 281 282 if (dbus_message_is_signal(s, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) { 283 const char *name, *old, *new; 284 if (!dbus_message_get_args(s, &error, 285 DBUS_TYPE_STRING, &name, 286 DBUS_TYPE_STRING, &old, 287 DBUS_TYPE_STRING, &new, 288 DBUS_TYPE_INVALID)) 289 goto finish; 290 if (!pa_streq(name, JACK_SERVICE_NAME)) 291 goto finish; 292 293 ensure_ports_stopped(u); 294 check_service_started(u); 295 } 296 297 else if (dbus_message_is_signal(s, JACK_INTERFACE_NAME, "ServerStarted")) { 298 ensure_ports_stopped(u); 299 check_service_started(u); 300 } 301 302 else if (dbus_message_is_signal(s, JACK_INTERFACE_NAME, "ServerStopped")) { 303 ensure_ports_stopped(u); 304 } 305 306finish: 307 dbus_error_free(&error); 308 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 309} 310 311int pa__init(pa_module *m) { 312 DBusError error; 313 pa_dbus_connection *connection = NULL; 314 struct userdata *u = NULL; 315 pa_modargs *ma; 316 uint32_t channels = 0; 317 unsigned i; 318 char argname[32]; 319 const char *name; 320 321 pa_assert(m); 322 323 dbus_error_init(&error); 324 325 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { 326 pa_log("Failed to parse module arguments"); 327 goto fail; 328 } 329 330 m->userdata = u = pa_xnew0(struct userdata, 1); 331 u->core = m->core; 332 u->module = m; 333 u->autoconnect_ports = true; 334 335 if (pa_modargs_get_value_boolean(ma, "connect", &u->autoconnect_ports) < 0) { 336 pa_log("Failed to parse connect= argument."); 337 goto fail; 338 } 339 340 if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 || (channels > 0 && !pa_channels_valid(channels))) { 341 pa_log("Failed to parse channels= argument."); 342 goto fail; 343 } 344 345 for (i = 0; i < JACK_SS_COUNT; i++) { 346 u->mod_args[i].enabled = true; 347 pa_snprintf(argname, sizeof(argname), "%s_enabled", modtypes[i]); 348 if (pa_modargs_get_value_boolean(ma, argname, &u->mod_args[i].enabled) < 0) { 349 pa_log("Failed to parse %s= argument.", argname); 350 goto fail; 351 } 352 353 pa_snprintf(argname, sizeof(argname), "%s_name", modtypes[i]); 354 name = pa_modargs_get_value(ma, argname, NULL); 355 u->mod_args[i].name = pa_xstrdup(name); 356 357 u->mod_args[i].proplist = pa_proplist_new(); 358 pa_snprintf(argname, sizeof(argname), "%s_properties", modtypes[i]); 359 if (pa_modargs_get_proplist(ma, argname, u->mod_args[i].proplist, PA_UPDATE_REPLACE) < 0) { 360 pa_log("Invalid %s properties", modtypes[i]); 361 goto fail; 362 } 363 364 pa_snprintf(argname, sizeof(argname), "%s_client_name", modtypes[i]); 365 name = pa_modargs_get_value(ma, argname, NULL); 366 u->mod_args[i].client_name = pa_xstrdup(name); 367 368 u->mod_args[i].channels = channels; 369 pa_snprintf(argname, sizeof(argname), "%s_channels", modtypes[i]); 370 if (pa_modargs_get_value_u32(ma, argname, &u->mod_args[i].channels) < 0 371 || (u->mod_args[i].channels > 0 && !pa_channels_valid(u->mod_args[i].channels))) { 372 pa_log("Failed to parse %s= argument.", argname); 373 goto fail; 374 } 375 376 pa_channel_map_init(&u->mod_args[i].channel_map); 377 pa_snprintf(argname, sizeof(argname), "%s_channel_map", modtypes[i]); 378 if (pa_modargs_get_value(ma, argname, NULL)) { 379 if (pa_modargs_get_channel_map(ma, argname, &u->mod_args[i].channel_map) < 0 380 || (u->mod_args[i].channels > 0 && u->mod_args[i].channel_map.channels != u->mod_args[i].channels)) { 381 pa_log("Failed to parse %s= argument.", argname); 382 goto fail; 383 } 384 } 385 } 386 387 if (!(connection = pa_dbus_bus_get(m->core, DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) { 388 389 if (connection) 390 pa_dbus_connection_unref(connection); 391 392 pa_log_error("Unable to contact D-Bus session bus: %s: %s", error.name, error.message); 393 goto fail; 394 } 395 u->connection = connection; 396 397 if (!dbus_connection_add_filter(pa_dbus_connection_get(connection), dbus_filter_handler, m, NULL)) { 398 pa_log_error("Unable to add D-Bus filter"); 399 goto fail; 400 } 401 u->filter_added = 1; 402 403 if (pa_dbus_add_matches( 404 pa_dbus_connection_get(connection), &error, SERVICE_FILTER, 405 RUNNING_FILTER("ServerStarted"), RUNNING_FILTER("ServerStopped"), NULL) < 0) { 406 pa_log_error("Unable to subscribe to signals: %s: %s", error.name, error.message); 407 goto fail; 408 } 409 u->match_added = 1; 410 411 check_service_started(u); 412 413 pa_modargs_free(ma); 414 return 0; 415 416fail: 417 if (ma) 418 pa_modargs_free(ma); 419 420 dbus_error_free(&error); 421 pa__done(m); 422 423 return -1; 424} 425 426void pa__done(pa_module *m) { 427 struct userdata *u; 428 unsigned i; 429 430 pa_assert(m); 431 432 if (!(u = m->userdata)) 433 return; 434 435 ensure_ports_stopped(u); 436 437 if (u->match_added) { 438 pa_dbus_remove_matches( 439 pa_dbus_connection_get(u->connection), SERVICE_FILTER, 440 RUNNING_FILTER("ServerStarted"), RUNNING_FILTER("ServerStopped"), NULL); 441 } 442 443 if (u->filter_added) { 444 dbus_connection_remove_filter(pa_dbus_connection_get(u->connection), dbus_filter_handler, m); 445 } 446 447 if (u->connection) { 448 pa_dbus_connection_unref(u->connection); 449 } 450 451 for (i = 0; i < JACK_SS_COUNT; i++) { 452 pa_xfree(u->mod_args[i].name); 453 454 if (u->mod_args[i].proplist) 455 pa_proplist_free(u->mod_args[i].proplist); 456 457 pa_xfree(u->mod_args[i].client_name); 458 } 459 460 pa_xfree(u); 461} 462