1/*** 2 This file is part of PulseAudio. 3 4 Copyright 2009 Lennart Poettering 5 6 PulseAudio is free software; you can redistribute it and/or modify 7 it under the terms of the GNU Lesser General Public License as published 8 by the Free Software Foundation; either version 2.1 of the License, 9 or (at your option) any later version. 10 11 PulseAudio is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU Lesser General Public License 17 along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. 18***/ 19 20#ifdef HAVE_CONFIG_H 21#include <config.h> 22#endif 23 24#include <pulse/xmalloc.h> 25 26#include <pulsecore/module.h> 27#include <pulsecore/core-util.h> 28#include <pulsecore/modargs.h> 29#include <pulsecore/log.h> 30#include <pulsecore/sink-input.h> 31#include <pulsecore/source-output.h> 32#include <pulsecore/namereg.h> 33 34PA_MODULE_AUTHOR("Lennart Poettering"); 35PA_MODULE_DESCRIPTION("Automatically set device of streams based on intended roles of devices"); 36PA_MODULE_VERSION(PACKAGE_VERSION); 37PA_MODULE_LOAD_ONCE(true); 38PA_MODULE_USAGE( 39 "on_hotplug=<When new device becomes available, recheck streams?> " 40 "on_rescue=<When device becomes unavailable, recheck streams?>"); 41 42static const char* const valid_modargs[] = { 43 "on_hotplug", 44 "on_rescue", 45 NULL 46}; 47 48struct userdata { 49 pa_core *core; 50 pa_module *module; 51 52 pa_hook_slot 53 *sink_input_new_hook_slot, 54 *source_output_new_hook_slot, 55 *sink_put_hook_slot, 56 *source_put_hook_slot, 57 *sink_unlink_hook_slot, 58 *source_unlink_hook_slot; 59 60 bool on_hotplug:1; 61 bool on_rescue:1; 62}; 63 64static bool role_match(pa_proplist *proplist, const char *role) { 65 return pa_str_in_list_spaces(pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES), role); 66} 67 68static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *new_data, struct userdata *u) { 69 const char *role; 70 pa_sink *s; 71 uint32_t idx; 72 73 pa_assert(c); 74 pa_assert(new_data); 75 pa_assert(u); 76 77 if (!new_data->proplist) { 78 pa_log_debug("New stream lacks property data."); 79 return PA_HOOK_OK; 80 } 81 82 if (new_data->sink) { 83 pa_log_debug("Not setting device for stream %s, because already set.", pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME))); 84 return PA_HOOK_OK; 85 } 86 87 if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) { 88 pa_log_debug("Not setting device for stream %s, because it lacks role.", pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME))); 89 return PA_HOOK_OK; 90 } 91 92 /* Prefer the default sink over any other sink, just in case... */ 93 if (c->default_sink) 94 if (role_match(c->default_sink->proplist, role) && pa_sink_input_new_data_set_sink(new_data, c->default_sink, false, false)) 95 return PA_HOOK_OK; 96 97 /* @todo: favour the highest priority device, not the first one we find? */ 98 PA_IDXSET_FOREACH(s, c->sinks, idx) { 99 if (s == c->default_sink) 100 continue; 101 102 if (!PA_SINK_IS_LINKED(s->state)) 103 continue; 104 105 if (role_match(s->proplist, role) && pa_sink_input_new_data_set_sink(new_data, s, false, false)) 106 return PA_HOOK_OK; 107 } 108 109 return PA_HOOK_OK; 110} 111 112static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_output_new_data *new_data, struct userdata *u) { 113 const char *role; 114 pa_source *s; 115 uint32_t idx; 116 117 pa_assert(c); 118 pa_assert(new_data); 119 pa_assert(u); 120 121 if (!new_data->proplist) { 122 pa_log_debug("New stream lacks property data."); 123 return PA_HOOK_OK; 124 } 125 126 if (new_data->source) { 127 pa_log_debug("Not setting device for stream %s, because already set.", pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME))); 128 return PA_HOOK_OK; 129 } 130 131 if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) { 132 pa_log_debug("Not setting device for stream %s, because it lacks role.", pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME))); 133 return PA_HOOK_OK; 134 } 135 136 /* Prefer the default source over any other source, just in case... */ 137 if (c->default_source) 138 if (role_match(c->default_source->proplist, role)) { 139 pa_source_output_new_data_set_source(new_data, c->default_source, false, false); 140 return PA_HOOK_OK; 141 } 142 143 PA_IDXSET_FOREACH(s, c->sources, idx) { 144 if (s->monitor_of) 145 continue; 146 147 if (s == c->default_source) 148 continue; 149 150 if (!PA_SOURCE_IS_LINKED(s->state)) 151 continue; 152 153 /* @todo: favour the highest priority device, not the first one we find? */ 154 if (role_match(s->proplist, role)) { 155 pa_source_output_new_data_set_source(new_data, s, false, false); 156 return PA_HOOK_OK; 157 } 158 } 159 160 return PA_HOOK_OK; 161} 162 163static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) { 164 pa_sink_input *si; 165 uint32_t idx; 166 167 pa_assert(c); 168 pa_assert(sink); 169 pa_assert(u); 170 pa_assert(u->on_hotplug); 171 172 PA_IDXSET_FOREACH(si, c->sink_inputs, idx) { 173 const char *role; 174 175 if (si->sink == sink) 176 continue; 177 178 /* Skip this if it is already in the process of being moved 179 * anyway */ 180 if (!si->sink) 181 continue; 182 183 if (pa_safe_streq(si->sink->name, si->preferred_sink)) 184 continue; 185 186 /* It might happen that a stream and a sink are set up at the 187 same time, in which case we want to make sure we don't 188 interfere with that */ 189 if (!PA_SINK_INPUT_IS_LINKED(si->state)) 190 continue; 191 192 if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE))) 193 continue; 194 195 if (role_match(si->sink->proplist, role)) 196 continue; 197 198 if (!role_match(sink->proplist, role)) 199 continue; 200 201 pa_sink_input_move_to(si, sink, false); 202 } 203 204 return PA_HOOK_OK; 205} 206 207static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, struct userdata *u) { 208 pa_source_output *so; 209 uint32_t idx; 210 211 pa_assert(c); 212 pa_assert(source); 213 pa_assert(u); 214 pa_assert(u->on_hotplug); 215 216 if (source->monitor_of) 217 return PA_HOOK_OK; 218 219 PA_IDXSET_FOREACH(so, c->source_outputs, idx) { 220 const char *role; 221 222 if (so->source == source) 223 continue; 224 225 if (so->direct_on_input) 226 continue; 227 228 /* Skip this if it is already in the process of being moved 229 * anyway */ 230 if (!so->source) 231 continue; 232 233 if (pa_safe_streq(so->source->name, so->preferred_source)) 234 continue; 235 236 /* It might happen that a stream and a source are set up at the 237 same time, in which case we want to make sure we don't 238 interfere with that */ 239 if (!PA_SOURCE_OUTPUT_IS_LINKED(so->state)) 240 continue; 241 242 if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE))) 243 continue; 244 245 if (role_match(so->source->proplist, role)) 246 continue; 247 248 if (!role_match(source->proplist, role)) 249 continue; 250 251 pa_source_output_move_to(so, source, false); 252 } 253 254 return PA_HOOK_OK; 255} 256 257static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) { 258 pa_sink_input *si; 259 uint32_t idx; 260 261 pa_assert(c); 262 pa_assert(sink); 263 pa_assert(u); 264 pa_assert(u->on_rescue); 265 266 /* There's no point in doing anything if the core is shut down anyway */ 267 if (c->state == PA_CORE_SHUTDOWN) 268 return PA_HOOK_OK; 269 270 /* If there not default sink, then there is no sink at all */ 271 if (!c->default_sink) 272 return PA_HOOK_OK; 273 274 PA_IDXSET_FOREACH(si, sink->inputs, idx) { 275 const char *role; 276 uint32_t jdx; 277 pa_sink *d; 278 279 if (!si->sink) 280 continue; 281 282 if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE))) 283 continue; 284 285 /* Would the default sink fit? If so, let's use it */ 286 if (c->default_sink != sink && role_match(c->default_sink->proplist, role)) 287 if (pa_sink_input_move_to(si, c->default_sink, false) >= 0) 288 continue; 289 290 /* Try to find some other fitting sink */ 291 /* @todo: favour the highest priority device, not the first one we find? */ 292 PA_IDXSET_FOREACH(d, c->sinks, jdx) { 293 if (d == c->default_sink || d == sink) 294 continue; 295 296 if (!PA_SINK_IS_LINKED(d->state)) 297 continue; 298 299 if (role_match(d->proplist, role)) 300 if (pa_sink_input_move_to(si, d, false) >= 0) 301 break; 302 } 303 } 304 305 return PA_HOOK_OK; 306} 307 308static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *source, struct userdata *u) { 309 pa_source_output *so; 310 uint32_t idx; 311 312 pa_assert(c); 313 pa_assert(source); 314 pa_assert(u); 315 pa_assert(u->on_rescue); 316 317 /* There's no point in doing anything if the core is shut down anyway */ 318 if (c->state == PA_CORE_SHUTDOWN) 319 return PA_HOOK_OK; 320 321 /* If there not default source, then there is no source at all */ 322 if (!c->default_source) 323 return PA_HOOK_OK; 324 325 PA_IDXSET_FOREACH(so, source->outputs, idx) { 326 const char *role; 327 uint32_t jdx; 328 pa_source *d; 329 330 if (so->direct_on_input) 331 continue; 332 333 if (!so->source) 334 continue; 335 336 if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE))) 337 continue; 338 339 /* Would the default source fit? If so, let's use it */ 340 if (c->default_source != source && role_match(c->default_source->proplist, role) 341 && !source->monitor_of == !c->default_source->monitor_of) { 342 pa_source_output_move_to(so, c->default_source, false); 343 continue; 344 } 345 346 /* Try to find some other fitting source */ 347 /* @todo: favour the highest priority device, not the first one we find? */ 348 PA_IDXSET_FOREACH(d, c->sources, jdx) { 349 if (d == c->default_source || d == source) 350 continue; 351 352 if (!PA_SOURCE_IS_LINKED(d->state)) 353 continue; 354 355 /* If moving from a monitor, move to another monitor */ 356 if (!source->monitor_of == !d->monitor_of && role_match(d->proplist, role)) { 357 pa_source_output_move_to(so, d, false); 358 break; 359 } 360 } 361 } 362 363 return PA_HOOK_OK; 364} 365 366int pa__init(pa_module*m) { 367 pa_modargs *ma = NULL; 368 struct userdata *u; 369 bool on_hotplug = true, on_rescue = true; 370 371 pa_assert(m); 372 373 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { 374 pa_log("Failed to parse module arguments"); 375 goto fail; 376 } 377 378 if (pa_modargs_get_value_boolean(ma, "on_hotplug", &on_hotplug) < 0 || 379 pa_modargs_get_value_boolean(ma, "on_rescue", &on_rescue) < 0) { 380 pa_log("on_hotplug= and on_rescue= expect boolean arguments"); 381 goto fail; 382 } 383 384 m->userdata = u = pa_xnew0(struct userdata, 1); 385 u->core = m->core; 386 u->module = m; 387 u->on_hotplug = on_hotplug; 388 u->on_rescue = on_rescue; 389 390 /* A little bit later than module-stream-restore */ 391 u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY+10, (pa_hook_cb_t) sink_input_new_hook_callback, u); 392 u->source_output_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_EARLY+10, (pa_hook_cb_t) source_output_new_hook_callback, u); 393 394 if (on_hotplug) { 395 /* A little bit later than module-stream-restore */ 396 u->sink_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE+10, (pa_hook_cb_t) sink_put_hook_callback, u); 397 u->source_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE+10, (pa_hook_cb_t) source_put_hook_callback, u); 398 } 399 400 if (on_rescue) { 401 /* A little bit later than module-stream-restore, ... */ 402 u->sink_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE+10, (pa_hook_cb_t) sink_unlink_hook_callback, u); 403 u->source_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE+10, (pa_hook_cb_t) source_unlink_hook_callback, u); 404 } 405 406 pa_modargs_free(ma); 407 return 0; 408 409fail: 410 pa__done(m); 411 412 if (ma) 413 pa_modargs_free(ma); 414 415 return -1; 416} 417 418void pa__done(pa_module*m) { 419 struct userdata* u; 420 421 pa_assert(m); 422 423 if (!(u = m->userdata)) 424 return; 425 426 if (u->sink_input_new_hook_slot) 427 pa_hook_slot_free(u->sink_input_new_hook_slot); 428 if (u->source_output_new_hook_slot) 429 pa_hook_slot_free(u->source_output_new_hook_slot); 430 431 if (u->sink_put_hook_slot) 432 pa_hook_slot_free(u->sink_put_hook_slot); 433 if (u->source_put_hook_slot) 434 pa_hook_slot_free(u->source_put_hook_slot); 435 436 if (u->sink_unlink_hook_slot) 437 pa_hook_slot_free(u->sink_unlink_hook_slot); 438 if (u->source_unlink_hook_slot) 439 pa_hook_slot_free(u->source_unlink_hook_slot); 440 441 pa_xfree(u); 442} 443