153a5a1b3Sopenharmony_ci/*** 253a5a1b3Sopenharmony_ci This file is part of PulseAudio. 353a5a1b3Sopenharmony_ci 453a5a1b3Sopenharmony_ci Copyright 2009 Lennart Poettering 553a5a1b3Sopenharmony_ci 653a5a1b3Sopenharmony_ci PulseAudio is free software; you can redistribute it and/or modify 753a5a1b3Sopenharmony_ci it under the terms of the GNU Lesser General Public License as published 853a5a1b3Sopenharmony_ci by the Free Software Foundation; either version 2.1 of the License, 953a5a1b3Sopenharmony_ci or (at your option) any later version. 1053a5a1b3Sopenharmony_ci 1153a5a1b3Sopenharmony_ci PulseAudio is distributed in the hope that it will be useful, but 1253a5a1b3Sopenharmony_ci WITHOUT ANY WARRANTY; without even the implied warranty of 1353a5a1b3Sopenharmony_ci MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 1453a5a1b3Sopenharmony_ci General Public License for more details. 1553a5a1b3Sopenharmony_ci 1653a5a1b3Sopenharmony_ci You should have received a copy of the GNU Lesser General Public License 1753a5a1b3Sopenharmony_ci along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. 1853a5a1b3Sopenharmony_ci***/ 1953a5a1b3Sopenharmony_ci 2053a5a1b3Sopenharmony_ci#ifdef HAVE_CONFIG_H 2153a5a1b3Sopenharmony_ci#include <config.h> 2253a5a1b3Sopenharmony_ci#endif 2353a5a1b3Sopenharmony_ci 2453a5a1b3Sopenharmony_ci#include <pulse/xmalloc.h> 2553a5a1b3Sopenharmony_ci 2653a5a1b3Sopenharmony_ci#include <pulsecore/module.h> 2753a5a1b3Sopenharmony_ci#include <pulsecore/core-util.h> 2853a5a1b3Sopenharmony_ci#include <pulsecore/modargs.h> 2953a5a1b3Sopenharmony_ci#include <pulsecore/log.h> 3053a5a1b3Sopenharmony_ci#include <pulsecore/sink-input.h> 3153a5a1b3Sopenharmony_ci#include <pulsecore/source-output.h> 3253a5a1b3Sopenharmony_ci#include <pulsecore/namereg.h> 3353a5a1b3Sopenharmony_ci 3453a5a1b3Sopenharmony_ciPA_MODULE_AUTHOR("Lennart Poettering"); 3553a5a1b3Sopenharmony_ciPA_MODULE_DESCRIPTION("Automatically set device of streams based on intended roles of devices"); 3653a5a1b3Sopenharmony_ciPA_MODULE_VERSION(PACKAGE_VERSION); 3753a5a1b3Sopenharmony_ciPA_MODULE_LOAD_ONCE(true); 3853a5a1b3Sopenharmony_ciPA_MODULE_USAGE( 3953a5a1b3Sopenharmony_ci "on_hotplug=<When new device becomes available, recheck streams?> " 4053a5a1b3Sopenharmony_ci "on_rescue=<When device becomes unavailable, recheck streams?>"); 4153a5a1b3Sopenharmony_ci 4253a5a1b3Sopenharmony_cistatic const char* const valid_modargs[] = { 4353a5a1b3Sopenharmony_ci "on_hotplug", 4453a5a1b3Sopenharmony_ci "on_rescue", 4553a5a1b3Sopenharmony_ci NULL 4653a5a1b3Sopenharmony_ci}; 4753a5a1b3Sopenharmony_ci 4853a5a1b3Sopenharmony_cistruct userdata { 4953a5a1b3Sopenharmony_ci pa_core *core; 5053a5a1b3Sopenharmony_ci pa_module *module; 5153a5a1b3Sopenharmony_ci 5253a5a1b3Sopenharmony_ci pa_hook_slot 5353a5a1b3Sopenharmony_ci *sink_input_new_hook_slot, 5453a5a1b3Sopenharmony_ci *source_output_new_hook_slot, 5553a5a1b3Sopenharmony_ci *sink_put_hook_slot, 5653a5a1b3Sopenharmony_ci *source_put_hook_slot, 5753a5a1b3Sopenharmony_ci *sink_unlink_hook_slot, 5853a5a1b3Sopenharmony_ci *source_unlink_hook_slot; 5953a5a1b3Sopenharmony_ci 6053a5a1b3Sopenharmony_ci bool on_hotplug:1; 6153a5a1b3Sopenharmony_ci bool on_rescue:1; 6253a5a1b3Sopenharmony_ci}; 6353a5a1b3Sopenharmony_ci 6453a5a1b3Sopenharmony_cistatic bool role_match(pa_proplist *proplist, const char *role) { 6553a5a1b3Sopenharmony_ci return pa_str_in_list_spaces(pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES), role); 6653a5a1b3Sopenharmony_ci} 6753a5a1b3Sopenharmony_ci 6853a5a1b3Sopenharmony_cistatic pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *new_data, struct userdata *u) { 6953a5a1b3Sopenharmony_ci const char *role; 7053a5a1b3Sopenharmony_ci pa_sink *s; 7153a5a1b3Sopenharmony_ci uint32_t idx; 7253a5a1b3Sopenharmony_ci 7353a5a1b3Sopenharmony_ci pa_assert(c); 7453a5a1b3Sopenharmony_ci pa_assert(new_data); 7553a5a1b3Sopenharmony_ci pa_assert(u); 7653a5a1b3Sopenharmony_ci 7753a5a1b3Sopenharmony_ci if (!new_data->proplist) { 7853a5a1b3Sopenharmony_ci pa_log_debug("New stream lacks property data."); 7953a5a1b3Sopenharmony_ci return PA_HOOK_OK; 8053a5a1b3Sopenharmony_ci } 8153a5a1b3Sopenharmony_ci 8253a5a1b3Sopenharmony_ci if (new_data->sink) { 8353a5a1b3Sopenharmony_ci pa_log_debug("Not setting device for stream %s, because already set.", pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME))); 8453a5a1b3Sopenharmony_ci return PA_HOOK_OK; 8553a5a1b3Sopenharmony_ci } 8653a5a1b3Sopenharmony_ci 8753a5a1b3Sopenharmony_ci if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) { 8853a5a1b3Sopenharmony_ci 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))); 8953a5a1b3Sopenharmony_ci return PA_HOOK_OK; 9053a5a1b3Sopenharmony_ci } 9153a5a1b3Sopenharmony_ci 9253a5a1b3Sopenharmony_ci /* Prefer the default sink over any other sink, just in case... */ 9353a5a1b3Sopenharmony_ci if (c->default_sink) 9453a5a1b3Sopenharmony_ci if (role_match(c->default_sink->proplist, role) && pa_sink_input_new_data_set_sink(new_data, c->default_sink, false, false)) 9553a5a1b3Sopenharmony_ci return PA_HOOK_OK; 9653a5a1b3Sopenharmony_ci 9753a5a1b3Sopenharmony_ci /* @todo: favour the highest priority device, not the first one we find? */ 9853a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(s, c->sinks, idx) { 9953a5a1b3Sopenharmony_ci if (s == c->default_sink) 10053a5a1b3Sopenharmony_ci continue; 10153a5a1b3Sopenharmony_ci 10253a5a1b3Sopenharmony_ci if (!PA_SINK_IS_LINKED(s->state)) 10353a5a1b3Sopenharmony_ci continue; 10453a5a1b3Sopenharmony_ci 10553a5a1b3Sopenharmony_ci if (role_match(s->proplist, role) && pa_sink_input_new_data_set_sink(new_data, s, false, false)) 10653a5a1b3Sopenharmony_ci return PA_HOOK_OK; 10753a5a1b3Sopenharmony_ci } 10853a5a1b3Sopenharmony_ci 10953a5a1b3Sopenharmony_ci return PA_HOOK_OK; 11053a5a1b3Sopenharmony_ci} 11153a5a1b3Sopenharmony_ci 11253a5a1b3Sopenharmony_cistatic pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_output_new_data *new_data, struct userdata *u) { 11353a5a1b3Sopenharmony_ci const char *role; 11453a5a1b3Sopenharmony_ci pa_source *s; 11553a5a1b3Sopenharmony_ci uint32_t idx; 11653a5a1b3Sopenharmony_ci 11753a5a1b3Sopenharmony_ci pa_assert(c); 11853a5a1b3Sopenharmony_ci pa_assert(new_data); 11953a5a1b3Sopenharmony_ci pa_assert(u); 12053a5a1b3Sopenharmony_ci 12153a5a1b3Sopenharmony_ci if (!new_data->proplist) { 12253a5a1b3Sopenharmony_ci pa_log_debug("New stream lacks property data."); 12353a5a1b3Sopenharmony_ci return PA_HOOK_OK; 12453a5a1b3Sopenharmony_ci } 12553a5a1b3Sopenharmony_ci 12653a5a1b3Sopenharmony_ci if (new_data->source) { 12753a5a1b3Sopenharmony_ci pa_log_debug("Not setting device for stream %s, because already set.", pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME))); 12853a5a1b3Sopenharmony_ci return PA_HOOK_OK; 12953a5a1b3Sopenharmony_ci } 13053a5a1b3Sopenharmony_ci 13153a5a1b3Sopenharmony_ci if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE))) { 13253a5a1b3Sopenharmony_ci 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))); 13353a5a1b3Sopenharmony_ci return PA_HOOK_OK; 13453a5a1b3Sopenharmony_ci } 13553a5a1b3Sopenharmony_ci 13653a5a1b3Sopenharmony_ci /* Prefer the default source over any other source, just in case... */ 13753a5a1b3Sopenharmony_ci if (c->default_source) 13853a5a1b3Sopenharmony_ci if (role_match(c->default_source->proplist, role)) { 13953a5a1b3Sopenharmony_ci pa_source_output_new_data_set_source(new_data, c->default_source, false, false); 14053a5a1b3Sopenharmony_ci return PA_HOOK_OK; 14153a5a1b3Sopenharmony_ci } 14253a5a1b3Sopenharmony_ci 14353a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(s, c->sources, idx) { 14453a5a1b3Sopenharmony_ci if (s->monitor_of) 14553a5a1b3Sopenharmony_ci continue; 14653a5a1b3Sopenharmony_ci 14753a5a1b3Sopenharmony_ci if (s == c->default_source) 14853a5a1b3Sopenharmony_ci continue; 14953a5a1b3Sopenharmony_ci 15053a5a1b3Sopenharmony_ci if (!PA_SOURCE_IS_LINKED(s->state)) 15153a5a1b3Sopenharmony_ci continue; 15253a5a1b3Sopenharmony_ci 15353a5a1b3Sopenharmony_ci /* @todo: favour the highest priority device, not the first one we find? */ 15453a5a1b3Sopenharmony_ci if (role_match(s->proplist, role)) { 15553a5a1b3Sopenharmony_ci pa_source_output_new_data_set_source(new_data, s, false, false); 15653a5a1b3Sopenharmony_ci return PA_HOOK_OK; 15753a5a1b3Sopenharmony_ci } 15853a5a1b3Sopenharmony_ci } 15953a5a1b3Sopenharmony_ci 16053a5a1b3Sopenharmony_ci return PA_HOOK_OK; 16153a5a1b3Sopenharmony_ci} 16253a5a1b3Sopenharmony_ci 16353a5a1b3Sopenharmony_cistatic pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) { 16453a5a1b3Sopenharmony_ci pa_sink_input *si; 16553a5a1b3Sopenharmony_ci uint32_t idx; 16653a5a1b3Sopenharmony_ci 16753a5a1b3Sopenharmony_ci pa_assert(c); 16853a5a1b3Sopenharmony_ci pa_assert(sink); 16953a5a1b3Sopenharmony_ci pa_assert(u); 17053a5a1b3Sopenharmony_ci pa_assert(u->on_hotplug); 17153a5a1b3Sopenharmony_ci 17253a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(si, c->sink_inputs, idx) { 17353a5a1b3Sopenharmony_ci const char *role; 17453a5a1b3Sopenharmony_ci 17553a5a1b3Sopenharmony_ci if (si->sink == sink) 17653a5a1b3Sopenharmony_ci continue; 17753a5a1b3Sopenharmony_ci 17853a5a1b3Sopenharmony_ci /* Skip this if it is already in the process of being moved 17953a5a1b3Sopenharmony_ci * anyway */ 18053a5a1b3Sopenharmony_ci if (!si->sink) 18153a5a1b3Sopenharmony_ci continue; 18253a5a1b3Sopenharmony_ci 18353a5a1b3Sopenharmony_ci if (pa_safe_streq(si->sink->name, si->preferred_sink)) 18453a5a1b3Sopenharmony_ci continue; 18553a5a1b3Sopenharmony_ci 18653a5a1b3Sopenharmony_ci /* It might happen that a stream and a sink are set up at the 18753a5a1b3Sopenharmony_ci same time, in which case we want to make sure we don't 18853a5a1b3Sopenharmony_ci interfere with that */ 18953a5a1b3Sopenharmony_ci if (!PA_SINK_INPUT_IS_LINKED(si->state)) 19053a5a1b3Sopenharmony_ci continue; 19153a5a1b3Sopenharmony_ci 19253a5a1b3Sopenharmony_ci if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE))) 19353a5a1b3Sopenharmony_ci continue; 19453a5a1b3Sopenharmony_ci 19553a5a1b3Sopenharmony_ci if (role_match(si->sink->proplist, role)) 19653a5a1b3Sopenharmony_ci continue; 19753a5a1b3Sopenharmony_ci 19853a5a1b3Sopenharmony_ci if (!role_match(sink->proplist, role)) 19953a5a1b3Sopenharmony_ci continue; 20053a5a1b3Sopenharmony_ci 20153a5a1b3Sopenharmony_ci pa_sink_input_move_to(si, sink, false); 20253a5a1b3Sopenharmony_ci } 20353a5a1b3Sopenharmony_ci 20453a5a1b3Sopenharmony_ci return PA_HOOK_OK; 20553a5a1b3Sopenharmony_ci} 20653a5a1b3Sopenharmony_ci 20753a5a1b3Sopenharmony_cistatic pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, struct userdata *u) { 20853a5a1b3Sopenharmony_ci pa_source_output *so; 20953a5a1b3Sopenharmony_ci uint32_t idx; 21053a5a1b3Sopenharmony_ci 21153a5a1b3Sopenharmony_ci pa_assert(c); 21253a5a1b3Sopenharmony_ci pa_assert(source); 21353a5a1b3Sopenharmony_ci pa_assert(u); 21453a5a1b3Sopenharmony_ci pa_assert(u->on_hotplug); 21553a5a1b3Sopenharmony_ci 21653a5a1b3Sopenharmony_ci if (source->monitor_of) 21753a5a1b3Sopenharmony_ci return PA_HOOK_OK; 21853a5a1b3Sopenharmony_ci 21953a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(so, c->source_outputs, idx) { 22053a5a1b3Sopenharmony_ci const char *role; 22153a5a1b3Sopenharmony_ci 22253a5a1b3Sopenharmony_ci if (so->source == source) 22353a5a1b3Sopenharmony_ci continue; 22453a5a1b3Sopenharmony_ci 22553a5a1b3Sopenharmony_ci if (so->direct_on_input) 22653a5a1b3Sopenharmony_ci continue; 22753a5a1b3Sopenharmony_ci 22853a5a1b3Sopenharmony_ci /* Skip this if it is already in the process of being moved 22953a5a1b3Sopenharmony_ci * anyway */ 23053a5a1b3Sopenharmony_ci if (!so->source) 23153a5a1b3Sopenharmony_ci continue; 23253a5a1b3Sopenharmony_ci 23353a5a1b3Sopenharmony_ci if (pa_safe_streq(so->source->name, so->preferred_source)) 23453a5a1b3Sopenharmony_ci continue; 23553a5a1b3Sopenharmony_ci 23653a5a1b3Sopenharmony_ci /* It might happen that a stream and a source are set up at the 23753a5a1b3Sopenharmony_ci same time, in which case we want to make sure we don't 23853a5a1b3Sopenharmony_ci interfere with that */ 23953a5a1b3Sopenharmony_ci if (!PA_SOURCE_OUTPUT_IS_LINKED(so->state)) 24053a5a1b3Sopenharmony_ci continue; 24153a5a1b3Sopenharmony_ci 24253a5a1b3Sopenharmony_ci if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE))) 24353a5a1b3Sopenharmony_ci continue; 24453a5a1b3Sopenharmony_ci 24553a5a1b3Sopenharmony_ci if (role_match(so->source->proplist, role)) 24653a5a1b3Sopenharmony_ci continue; 24753a5a1b3Sopenharmony_ci 24853a5a1b3Sopenharmony_ci if (!role_match(source->proplist, role)) 24953a5a1b3Sopenharmony_ci continue; 25053a5a1b3Sopenharmony_ci 25153a5a1b3Sopenharmony_ci pa_source_output_move_to(so, source, false); 25253a5a1b3Sopenharmony_ci } 25353a5a1b3Sopenharmony_ci 25453a5a1b3Sopenharmony_ci return PA_HOOK_OK; 25553a5a1b3Sopenharmony_ci} 25653a5a1b3Sopenharmony_ci 25753a5a1b3Sopenharmony_cistatic pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) { 25853a5a1b3Sopenharmony_ci pa_sink_input *si; 25953a5a1b3Sopenharmony_ci uint32_t idx; 26053a5a1b3Sopenharmony_ci 26153a5a1b3Sopenharmony_ci pa_assert(c); 26253a5a1b3Sopenharmony_ci pa_assert(sink); 26353a5a1b3Sopenharmony_ci pa_assert(u); 26453a5a1b3Sopenharmony_ci pa_assert(u->on_rescue); 26553a5a1b3Sopenharmony_ci 26653a5a1b3Sopenharmony_ci /* There's no point in doing anything if the core is shut down anyway */ 26753a5a1b3Sopenharmony_ci if (c->state == PA_CORE_SHUTDOWN) 26853a5a1b3Sopenharmony_ci return PA_HOOK_OK; 26953a5a1b3Sopenharmony_ci 27053a5a1b3Sopenharmony_ci /* If there not default sink, then there is no sink at all */ 27153a5a1b3Sopenharmony_ci if (!c->default_sink) 27253a5a1b3Sopenharmony_ci return PA_HOOK_OK; 27353a5a1b3Sopenharmony_ci 27453a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(si, sink->inputs, idx) { 27553a5a1b3Sopenharmony_ci const char *role; 27653a5a1b3Sopenharmony_ci uint32_t jdx; 27753a5a1b3Sopenharmony_ci pa_sink *d; 27853a5a1b3Sopenharmony_ci 27953a5a1b3Sopenharmony_ci if (!si->sink) 28053a5a1b3Sopenharmony_ci continue; 28153a5a1b3Sopenharmony_ci 28253a5a1b3Sopenharmony_ci if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE))) 28353a5a1b3Sopenharmony_ci continue; 28453a5a1b3Sopenharmony_ci 28553a5a1b3Sopenharmony_ci /* Would the default sink fit? If so, let's use it */ 28653a5a1b3Sopenharmony_ci if (c->default_sink != sink && role_match(c->default_sink->proplist, role)) 28753a5a1b3Sopenharmony_ci if (pa_sink_input_move_to(si, c->default_sink, false) >= 0) 28853a5a1b3Sopenharmony_ci continue; 28953a5a1b3Sopenharmony_ci 29053a5a1b3Sopenharmony_ci /* Try to find some other fitting sink */ 29153a5a1b3Sopenharmony_ci /* @todo: favour the highest priority device, not the first one we find? */ 29253a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(d, c->sinks, jdx) { 29353a5a1b3Sopenharmony_ci if (d == c->default_sink || d == sink) 29453a5a1b3Sopenharmony_ci continue; 29553a5a1b3Sopenharmony_ci 29653a5a1b3Sopenharmony_ci if (!PA_SINK_IS_LINKED(d->state)) 29753a5a1b3Sopenharmony_ci continue; 29853a5a1b3Sopenharmony_ci 29953a5a1b3Sopenharmony_ci if (role_match(d->proplist, role)) 30053a5a1b3Sopenharmony_ci if (pa_sink_input_move_to(si, d, false) >= 0) 30153a5a1b3Sopenharmony_ci break; 30253a5a1b3Sopenharmony_ci } 30353a5a1b3Sopenharmony_ci } 30453a5a1b3Sopenharmony_ci 30553a5a1b3Sopenharmony_ci return PA_HOOK_OK; 30653a5a1b3Sopenharmony_ci} 30753a5a1b3Sopenharmony_ci 30853a5a1b3Sopenharmony_cistatic pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *source, struct userdata *u) { 30953a5a1b3Sopenharmony_ci pa_source_output *so; 31053a5a1b3Sopenharmony_ci uint32_t idx; 31153a5a1b3Sopenharmony_ci 31253a5a1b3Sopenharmony_ci pa_assert(c); 31353a5a1b3Sopenharmony_ci pa_assert(source); 31453a5a1b3Sopenharmony_ci pa_assert(u); 31553a5a1b3Sopenharmony_ci pa_assert(u->on_rescue); 31653a5a1b3Sopenharmony_ci 31753a5a1b3Sopenharmony_ci /* There's no point in doing anything if the core is shut down anyway */ 31853a5a1b3Sopenharmony_ci if (c->state == PA_CORE_SHUTDOWN) 31953a5a1b3Sopenharmony_ci return PA_HOOK_OK; 32053a5a1b3Sopenharmony_ci 32153a5a1b3Sopenharmony_ci /* If there not default source, then there is no source at all */ 32253a5a1b3Sopenharmony_ci if (!c->default_source) 32353a5a1b3Sopenharmony_ci return PA_HOOK_OK; 32453a5a1b3Sopenharmony_ci 32553a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(so, source->outputs, idx) { 32653a5a1b3Sopenharmony_ci const char *role; 32753a5a1b3Sopenharmony_ci uint32_t jdx; 32853a5a1b3Sopenharmony_ci pa_source *d; 32953a5a1b3Sopenharmony_ci 33053a5a1b3Sopenharmony_ci if (so->direct_on_input) 33153a5a1b3Sopenharmony_ci continue; 33253a5a1b3Sopenharmony_ci 33353a5a1b3Sopenharmony_ci if (!so->source) 33453a5a1b3Sopenharmony_ci continue; 33553a5a1b3Sopenharmony_ci 33653a5a1b3Sopenharmony_ci if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE))) 33753a5a1b3Sopenharmony_ci continue; 33853a5a1b3Sopenharmony_ci 33953a5a1b3Sopenharmony_ci /* Would the default source fit? If so, let's use it */ 34053a5a1b3Sopenharmony_ci if (c->default_source != source && role_match(c->default_source->proplist, role) 34153a5a1b3Sopenharmony_ci && !source->monitor_of == !c->default_source->monitor_of) { 34253a5a1b3Sopenharmony_ci pa_source_output_move_to(so, c->default_source, false); 34353a5a1b3Sopenharmony_ci continue; 34453a5a1b3Sopenharmony_ci } 34553a5a1b3Sopenharmony_ci 34653a5a1b3Sopenharmony_ci /* Try to find some other fitting source */ 34753a5a1b3Sopenharmony_ci /* @todo: favour the highest priority device, not the first one we find? */ 34853a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(d, c->sources, jdx) { 34953a5a1b3Sopenharmony_ci if (d == c->default_source || d == source) 35053a5a1b3Sopenharmony_ci continue; 35153a5a1b3Sopenharmony_ci 35253a5a1b3Sopenharmony_ci if (!PA_SOURCE_IS_LINKED(d->state)) 35353a5a1b3Sopenharmony_ci continue; 35453a5a1b3Sopenharmony_ci 35553a5a1b3Sopenharmony_ci /* If moving from a monitor, move to another monitor */ 35653a5a1b3Sopenharmony_ci if (!source->monitor_of == !d->monitor_of && role_match(d->proplist, role)) { 35753a5a1b3Sopenharmony_ci pa_source_output_move_to(so, d, false); 35853a5a1b3Sopenharmony_ci break; 35953a5a1b3Sopenharmony_ci } 36053a5a1b3Sopenharmony_ci } 36153a5a1b3Sopenharmony_ci } 36253a5a1b3Sopenharmony_ci 36353a5a1b3Sopenharmony_ci return PA_HOOK_OK; 36453a5a1b3Sopenharmony_ci} 36553a5a1b3Sopenharmony_ci 36653a5a1b3Sopenharmony_ciint pa__init(pa_module*m) { 36753a5a1b3Sopenharmony_ci pa_modargs *ma = NULL; 36853a5a1b3Sopenharmony_ci struct userdata *u; 36953a5a1b3Sopenharmony_ci bool on_hotplug = true, on_rescue = true; 37053a5a1b3Sopenharmony_ci 37153a5a1b3Sopenharmony_ci pa_assert(m); 37253a5a1b3Sopenharmony_ci 37353a5a1b3Sopenharmony_ci if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { 37453a5a1b3Sopenharmony_ci pa_log("Failed to parse module arguments"); 37553a5a1b3Sopenharmony_ci goto fail; 37653a5a1b3Sopenharmony_ci } 37753a5a1b3Sopenharmony_ci 37853a5a1b3Sopenharmony_ci if (pa_modargs_get_value_boolean(ma, "on_hotplug", &on_hotplug) < 0 || 37953a5a1b3Sopenharmony_ci pa_modargs_get_value_boolean(ma, "on_rescue", &on_rescue) < 0) { 38053a5a1b3Sopenharmony_ci pa_log("on_hotplug= and on_rescue= expect boolean arguments"); 38153a5a1b3Sopenharmony_ci goto fail; 38253a5a1b3Sopenharmony_ci } 38353a5a1b3Sopenharmony_ci 38453a5a1b3Sopenharmony_ci m->userdata = u = pa_xnew0(struct userdata, 1); 38553a5a1b3Sopenharmony_ci u->core = m->core; 38653a5a1b3Sopenharmony_ci u->module = m; 38753a5a1b3Sopenharmony_ci u->on_hotplug = on_hotplug; 38853a5a1b3Sopenharmony_ci u->on_rescue = on_rescue; 38953a5a1b3Sopenharmony_ci 39053a5a1b3Sopenharmony_ci /* A little bit later than module-stream-restore */ 39153a5a1b3Sopenharmony_ci 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); 39253a5a1b3Sopenharmony_ci 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); 39353a5a1b3Sopenharmony_ci 39453a5a1b3Sopenharmony_ci if (on_hotplug) { 39553a5a1b3Sopenharmony_ci /* A little bit later than module-stream-restore */ 39653a5a1b3Sopenharmony_ci 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); 39753a5a1b3Sopenharmony_ci 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); 39853a5a1b3Sopenharmony_ci } 39953a5a1b3Sopenharmony_ci 40053a5a1b3Sopenharmony_ci if (on_rescue) { 40153a5a1b3Sopenharmony_ci /* A little bit later than module-stream-restore, ... */ 40253a5a1b3Sopenharmony_ci 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); 40353a5a1b3Sopenharmony_ci 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); 40453a5a1b3Sopenharmony_ci } 40553a5a1b3Sopenharmony_ci 40653a5a1b3Sopenharmony_ci pa_modargs_free(ma); 40753a5a1b3Sopenharmony_ci return 0; 40853a5a1b3Sopenharmony_ci 40953a5a1b3Sopenharmony_cifail: 41053a5a1b3Sopenharmony_ci pa__done(m); 41153a5a1b3Sopenharmony_ci 41253a5a1b3Sopenharmony_ci if (ma) 41353a5a1b3Sopenharmony_ci pa_modargs_free(ma); 41453a5a1b3Sopenharmony_ci 41553a5a1b3Sopenharmony_ci return -1; 41653a5a1b3Sopenharmony_ci} 41753a5a1b3Sopenharmony_ci 41853a5a1b3Sopenharmony_civoid pa__done(pa_module*m) { 41953a5a1b3Sopenharmony_ci struct userdata* u; 42053a5a1b3Sopenharmony_ci 42153a5a1b3Sopenharmony_ci pa_assert(m); 42253a5a1b3Sopenharmony_ci 42353a5a1b3Sopenharmony_ci if (!(u = m->userdata)) 42453a5a1b3Sopenharmony_ci return; 42553a5a1b3Sopenharmony_ci 42653a5a1b3Sopenharmony_ci if (u->sink_input_new_hook_slot) 42753a5a1b3Sopenharmony_ci pa_hook_slot_free(u->sink_input_new_hook_slot); 42853a5a1b3Sopenharmony_ci if (u->source_output_new_hook_slot) 42953a5a1b3Sopenharmony_ci pa_hook_slot_free(u->source_output_new_hook_slot); 43053a5a1b3Sopenharmony_ci 43153a5a1b3Sopenharmony_ci if (u->sink_put_hook_slot) 43253a5a1b3Sopenharmony_ci pa_hook_slot_free(u->sink_put_hook_slot); 43353a5a1b3Sopenharmony_ci if (u->source_put_hook_slot) 43453a5a1b3Sopenharmony_ci pa_hook_slot_free(u->source_put_hook_slot); 43553a5a1b3Sopenharmony_ci 43653a5a1b3Sopenharmony_ci if (u->sink_unlink_hook_slot) 43753a5a1b3Sopenharmony_ci pa_hook_slot_free(u->sink_unlink_hook_slot); 43853a5a1b3Sopenharmony_ci if (u->source_unlink_hook_slot) 43953a5a1b3Sopenharmony_ci pa_hook_slot_free(u->source_unlink_hook_slot); 44053a5a1b3Sopenharmony_ci 44153a5a1b3Sopenharmony_ci pa_xfree(u); 44253a5a1b3Sopenharmony_ci} 443