153a5a1b3Sopenharmony_ci/*** 253a5a1b3Sopenharmony_ci This file is part of PulseAudio. 353a5a1b3Sopenharmony_ci 453a5a1b3Sopenharmony_ci Copyright 2006 Lennart Poettering 553a5a1b3Sopenharmony_ci Copyright 2011 Canonical Ltd 653a5a1b3Sopenharmony_ci 753a5a1b3Sopenharmony_ci PulseAudio is free software; you can redistribute it and/or modify 853a5a1b3Sopenharmony_ci it under the terms of the GNU Lesser General Public License as published 953a5a1b3Sopenharmony_ci by the Free Software Foundation; either version 2.1 of the License, 1053a5a1b3Sopenharmony_ci or (at your option) any later version. 1153a5a1b3Sopenharmony_ci 1253a5a1b3Sopenharmony_ci PulseAudio is distributed in the hope that it will be useful, but 1353a5a1b3Sopenharmony_ci WITHOUT ANY WARRANTY; without even the implied warranty of 1453a5a1b3Sopenharmony_ci MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 1553a5a1b3Sopenharmony_ci General Public License for more details. 1653a5a1b3Sopenharmony_ci 1753a5a1b3Sopenharmony_ci You should have received a copy of the GNU Lesser General Public License 1853a5a1b3Sopenharmony_ci along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. 1953a5a1b3Sopenharmony_ci***/ 2053a5a1b3Sopenharmony_ci 2153a5a1b3Sopenharmony_ci#ifdef HAVE_CONFIG_H 2253a5a1b3Sopenharmony_ci#include <config.h> 2353a5a1b3Sopenharmony_ci#endif 2453a5a1b3Sopenharmony_ci 2553a5a1b3Sopenharmony_ci#include <pulsecore/core.h> 2653a5a1b3Sopenharmony_ci#include <pulsecore/core-util.h> 2753a5a1b3Sopenharmony_ci#include <pulsecore/device-port.h> 2853a5a1b3Sopenharmony_ci#include <pulsecore/hashmap.h> 2953a5a1b3Sopenharmony_ci 3053a5a1b3Sopenharmony_ciPA_MODULE_AUTHOR("David Henningsson"); 3153a5a1b3Sopenharmony_ciPA_MODULE_DESCRIPTION("Switches ports and profiles when devices are plugged/unplugged"); 3253a5a1b3Sopenharmony_ciPA_MODULE_LOAD_ONCE(true); 3353a5a1b3Sopenharmony_ciPA_MODULE_VERSION(PACKAGE_VERSION); 3453a5a1b3Sopenharmony_ci 3553a5a1b3Sopenharmony_cistruct card_info { 3653a5a1b3Sopenharmony_ci struct userdata *userdata; 3753a5a1b3Sopenharmony_ci pa_card *card; 3853a5a1b3Sopenharmony_ci 3953a5a1b3Sopenharmony_ci /* We need to cache the active profile, because we want to compare the old 4053a5a1b3Sopenharmony_ci * and new profiles in the PROFILE_CHANGED hook. Without this we'd only 4153a5a1b3Sopenharmony_ci * have access to the new profile. */ 4253a5a1b3Sopenharmony_ci pa_card_profile *active_profile; 4353a5a1b3Sopenharmony_ci}; 4453a5a1b3Sopenharmony_ci 4553a5a1b3Sopenharmony_cistruct userdata { 4653a5a1b3Sopenharmony_ci pa_hashmap *card_infos; /* pa_card -> struct card_info */ 4753a5a1b3Sopenharmony_ci}; 4853a5a1b3Sopenharmony_ci 4953a5a1b3Sopenharmony_cistatic void card_info_new(struct userdata *u, pa_card *card) { 5053a5a1b3Sopenharmony_ci struct card_info *info; 5153a5a1b3Sopenharmony_ci 5253a5a1b3Sopenharmony_ci info = pa_xnew0(struct card_info, 1); 5353a5a1b3Sopenharmony_ci info->userdata = u; 5453a5a1b3Sopenharmony_ci info->card = card; 5553a5a1b3Sopenharmony_ci info->active_profile = card->active_profile; 5653a5a1b3Sopenharmony_ci 5753a5a1b3Sopenharmony_ci pa_hashmap_put(u->card_infos, card, info); 5853a5a1b3Sopenharmony_ci} 5953a5a1b3Sopenharmony_ci 6053a5a1b3Sopenharmony_cistatic void card_info_free(struct card_info *info) { 6153a5a1b3Sopenharmony_ci pa_hashmap_remove(info->userdata->card_infos, info->card); 6253a5a1b3Sopenharmony_ci pa_xfree(info); 6353a5a1b3Sopenharmony_ci} 6453a5a1b3Sopenharmony_ci 6553a5a1b3Sopenharmony_cistatic bool profile_good_for_output(pa_card_profile *profile, pa_device_port *port) { 6653a5a1b3Sopenharmony_ci pa_card *card; 6753a5a1b3Sopenharmony_ci pa_sink *sink; 6853a5a1b3Sopenharmony_ci uint32_t idx; 6953a5a1b3Sopenharmony_ci 7053a5a1b3Sopenharmony_ci pa_assert(profile); 7153a5a1b3Sopenharmony_ci 7253a5a1b3Sopenharmony_ci card = profile->card; 7353a5a1b3Sopenharmony_ci 7453a5a1b3Sopenharmony_ci if (pa_safe_streq(card->active_profile->name, "off")) 7553a5a1b3Sopenharmony_ci return true; 7653a5a1b3Sopenharmony_ci 7753a5a1b3Sopenharmony_ci if (!pa_safe_streq(card->active_profile->input_name, profile->input_name)) 7853a5a1b3Sopenharmony_ci return false; 7953a5a1b3Sopenharmony_ci 8053a5a1b3Sopenharmony_ci if (card->active_profile->n_sources != profile->n_sources) 8153a5a1b3Sopenharmony_ci return false; 8253a5a1b3Sopenharmony_ci 8353a5a1b3Sopenharmony_ci if (card->active_profile->max_source_channels != profile->max_source_channels) 8453a5a1b3Sopenharmony_ci return false; 8553a5a1b3Sopenharmony_ci 8653a5a1b3Sopenharmony_ci if (port == card->preferred_output_port) 8753a5a1b3Sopenharmony_ci return true; 8853a5a1b3Sopenharmony_ci 8953a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(sink, card->sinks, idx) { 9053a5a1b3Sopenharmony_ci if (!sink->active_port) 9153a5a1b3Sopenharmony_ci continue; 9253a5a1b3Sopenharmony_ci 9353a5a1b3Sopenharmony_ci if ((sink->active_port->available != PA_AVAILABLE_NO) && (sink->active_port->priority >= port->priority)) 9453a5a1b3Sopenharmony_ci return false; 9553a5a1b3Sopenharmony_ci } 9653a5a1b3Sopenharmony_ci 9753a5a1b3Sopenharmony_ci return true; 9853a5a1b3Sopenharmony_ci} 9953a5a1b3Sopenharmony_ci 10053a5a1b3Sopenharmony_cistatic bool profile_good_for_input(pa_card_profile *profile, pa_device_port *port) { 10153a5a1b3Sopenharmony_ci pa_card *card; 10253a5a1b3Sopenharmony_ci pa_source *source; 10353a5a1b3Sopenharmony_ci uint32_t idx; 10453a5a1b3Sopenharmony_ci 10553a5a1b3Sopenharmony_ci pa_assert(profile); 10653a5a1b3Sopenharmony_ci 10753a5a1b3Sopenharmony_ci card = profile->card; 10853a5a1b3Sopenharmony_ci 10953a5a1b3Sopenharmony_ci if (pa_safe_streq(card->active_profile->name, "off")) 11053a5a1b3Sopenharmony_ci return true; 11153a5a1b3Sopenharmony_ci 11253a5a1b3Sopenharmony_ci if (!pa_safe_streq(card->active_profile->output_name, profile->output_name)) 11353a5a1b3Sopenharmony_ci return false; 11453a5a1b3Sopenharmony_ci 11553a5a1b3Sopenharmony_ci if (card->active_profile->n_sinks != profile->n_sinks) 11653a5a1b3Sopenharmony_ci return false; 11753a5a1b3Sopenharmony_ci 11853a5a1b3Sopenharmony_ci if (card->active_profile->max_sink_channels != profile->max_sink_channels) 11953a5a1b3Sopenharmony_ci return false; 12053a5a1b3Sopenharmony_ci 12153a5a1b3Sopenharmony_ci if (port == card->preferred_input_port) 12253a5a1b3Sopenharmony_ci return true; 12353a5a1b3Sopenharmony_ci 12453a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(source, card->sources, idx) { 12553a5a1b3Sopenharmony_ci if (!source->active_port) 12653a5a1b3Sopenharmony_ci continue; 12753a5a1b3Sopenharmony_ci 12853a5a1b3Sopenharmony_ci if ((source->active_port->available != PA_AVAILABLE_NO) && (source->active_port->priority >= port->priority)) 12953a5a1b3Sopenharmony_ci return false; 13053a5a1b3Sopenharmony_ci } 13153a5a1b3Sopenharmony_ci 13253a5a1b3Sopenharmony_ci return true; 13353a5a1b3Sopenharmony_ci} 13453a5a1b3Sopenharmony_ci 13553a5a1b3Sopenharmony_cistatic int try_to_switch_profile(pa_device_port *port) { 13653a5a1b3Sopenharmony_ci pa_card_profile *best_profile = NULL, *profile; 13753a5a1b3Sopenharmony_ci void *state; 13853a5a1b3Sopenharmony_ci unsigned best_prio = 0; 13953a5a1b3Sopenharmony_ci 14053a5a1b3Sopenharmony_ci if (port->card->profile_is_sticky) { 14153a5a1b3Sopenharmony_ci pa_log_info("Keeping sticky card profile '%s'", port->card->active_profile->name); 14253a5a1b3Sopenharmony_ci return -1; 14353a5a1b3Sopenharmony_ci } 14453a5a1b3Sopenharmony_ci 14553a5a1b3Sopenharmony_ci pa_log_debug("Finding best profile for port %s, preferred = %s", 14653a5a1b3Sopenharmony_ci port->name, pa_strnull(port->preferred_profile)); 14753a5a1b3Sopenharmony_ci 14853a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(profile, port->profiles, state) { 14953a5a1b3Sopenharmony_ci bool good = false; 15053a5a1b3Sopenharmony_ci const char *name; 15153a5a1b3Sopenharmony_ci unsigned prio = profile->priority; 15253a5a1b3Sopenharmony_ci 15353a5a1b3Sopenharmony_ci /* We make a best effort to keep other direction unchanged */ 15453a5a1b3Sopenharmony_ci switch (port->direction) { 15553a5a1b3Sopenharmony_ci case PA_DIRECTION_OUTPUT: 15653a5a1b3Sopenharmony_ci name = profile->output_name; 15753a5a1b3Sopenharmony_ci good = profile_good_for_output(profile, port); 15853a5a1b3Sopenharmony_ci break; 15953a5a1b3Sopenharmony_ci 16053a5a1b3Sopenharmony_ci case PA_DIRECTION_INPUT: 16153a5a1b3Sopenharmony_ci name = profile->input_name; 16253a5a1b3Sopenharmony_ci good = profile_good_for_input(profile, port); 16353a5a1b3Sopenharmony_ci break; 16453a5a1b3Sopenharmony_ci } 16553a5a1b3Sopenharmony_ci 16653a5a1b3Sopenharmony_ci if (!good) 16753a5a1b3Sopenharmony_ci continue; 16853a5a1b3Sopenharmony_ci 16953a5a1b3Sopenharmony_ci /* Give a high bonus in case this is the preferred profile */ 17053a5a1b3Sopenharmony_ci if (pa_safe_streq(name ? name : profile->name, port->preferred_profile)) 17153a5a1b3Sopenharmony_ci prio += 1000000; 17253a5a1b3Sopenharmony_ci 17353a5a1b3Sopenharmony_ci if (best_profile && best_prio >= prio) 17453a5a1b3Sopenharmony_ci continue; 17553a5a1b3Sopenharmony_ci 17653a5a1b3Sopenharmony_ci best_profile = profile; 17753a5a1b3Sopenharmony_ci best_prio = prio; 17853a5a1b3Sopenharmony_ci } 17953a5a1b3Sopenharmony_ci 18053a5a1b3Sopenharmony_ci if (!best_profile) { 18153a5a1b3Sopenharmony_ci pa_log_debug("No suitable profile found"); 18253a5a1b3Sopenharmony_ci return -1; 18353a5a1b3Sopenharmony_ci } 18453a5a1b3Sopenharmony_ci 18553a5a1b3Sopenharmony_ci if (pa_card_set_profile(port->card, best_profile, false) != 0) { 18653a5a1b3Sopenharmony_ci pa_log_debug("Could not set profile %s", best_profile->name); 18753a5a1b3Sopenharmony_ci return -1; 18853a5a1b3Sopenharmony_ci } 18953a5a1b3Sopenharmony_ci 19053a5a1b3Sopenharmony_ci return 0; 19153a5a1b3Sopenharmony_ci} 19253a5a1b3Sopenharmony_ci 19353a5a1b3Sopenharmony_cistruct port_pointers { 19453a5a1b3Sopenharmony_ci pa_device_port *port; 19553a5a1b3Sopenharmony_ci pa_sink *sink; 19653a5a1b3Sopenharmony_ci pa_source *source; 19753a5a1b3Sopenharmony_ci bool is_possible_profile_active; 19853a5a1b3Sopenharmony_ci bool is_preferred_profile_active; 19953a5a1b3Sopenharmony_ci bool is_port_active; 20053a5a1b3Sopenharmony_ci}; 20153a5a1b3Sopenharmony_ci 20253a5a1b3Sopenharmony_cistatic const char* profile_name_for_dir(pa_card_profile *cp, pa_direction_t dir) { 20353a5a1b3Sopenharmony_ci if (dir == PA_DIRECTION_OUTPUT && cp->output_name) 20453a5a1b3Sopenharmony_ci return cp->output_name; 20553a5a1b3Sopenharmony_ci if (dir == PA_DIRECTION_INPUT && cp->input_name) 20653a5a1b3Sopenharmony_ci return cp->input_name; 20753a5a1b3Sopenharmony_ci return cp->name; 20853a5a1b3Sopenharmony_ci} 20953a5a1b3Sopenharmony_ci 21053a5a1b3Sopenharmony_cistatic struct port_pointers find_port_pointers(pa_device_port *port) { 21153a5a1b3Sopenharmony_ci struct port_pointers pp = { .port = port }; 21253a5a1b3Sopenharmony_ci uint32_t state; 21353a5a1b3Sopenharmony_ci pa_card *card; 21453a5a1b3Sopenharmony_ci 21553a5a1b3Sopenharmony_ci pa_assert(port); 21653a5a1b3Sopenharmony_ci pa_assert_se(card = port->card); 21753a5a1b3Sopenharmony_ci 21853a5a1b3Sopenharmony_ci switch (port->direction) { 21953a5a1b3Sopenharmony_ci case PA_DIRECTION_OUTPUT: 22053a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(pp.sink, card->sinks, state) 22153a5a1b3Sopenharmony_ci if (port == pa_hashmap_get(pp.sink->ports, port->name)) 22253a5a1b3Sopenharmony_ci break; 22353a5a1b3Sopenharmony_ci break; 22453a5a1b3Sopenharmony_ci 22553a5a1b3Sopenharmony_ci case PA_DIRECTION_INPUT: 22653a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(pp.source, card->sources, state) 22753a5a1b3Sopenharmony_ci if (port == pa_hashmap_get(pp.source->ports, port->name)) 22853a5a1b3Sopenharmony_ci break; 22953a5a1b3Sopenharmony_ci break; 23053a5a1b3Sopenharmony_ci } 23153a5a1b3Sopenharmony_ci 23253a5a1b3Sopenharmony_ci pp.is_possible_profile_active = 23353a5a1b3Sopenharmony_ci card->active_profile == pa_hashmap_get(port->profiles, card->active_profile->name); 23453a5a1b3Sopenharmony_ci pp.is_preferred_profile_active = pp.is_possible_profile_active && (!port->preferred_profile || 23553a5a1b3Sopenharmony_ci pa_safe_streq(port->preferred_profile, profile_name_for_dir(card->active_profile, port->direction))); 23653a5a1b3Sopenharmony_ci pp.is_port_active = (pp.sink && pp.sink->active_port == port) || (pp.source && pp.source->active_port == port); 23753a5a1b3Sopenharmony_ci 23853a5a1b3Sopenharmony_ci return pp; 23953a5a1b3Sopenharmony_ci} 24053a5a1b3Sopenharmony_ci 24153a5a1b3Sopenharmony_ci/* Switches to a port, switching profiles if necessary or preferred */ 24253a5a1b3Sopenharmony_cistatic void switch_to_port(pa_device_port *port, struct port_pointers pp) { 24353a5a1b3Sopenharmony_ci if (pp.is_port_active) 24453a5a1b3Sopenharmony_ci return; /* Already selected */ 24553a5a1b3Sopenharmony_ci 24653a5a1b3Sopenharmony_ci pa_log_debug("Trying to switch to port %s", port->name); 24753a5a1b3Sopenharmony_ci if (!pp.is_preferred_profile_active) { 24853a5a1b3Sopenharmony_ci if (try_to_switch_profile(port) < 0) { 24953a5a1b3Sopenharmony_ci if (!pp.is_possible_profile_active) 25053a5a1b3Sopenharmony_ci return; 25153a5a1b3Sopenharmony_ci } 25253a5a1b3Sopenharmony_ci else 25353a5a1b3Sopenharmony_ci /* Now that profile has changed, our sink and source pointers must be updated */ 25453a5a1b3Sopenharmony_ci pp = find_port_pointers(port); 25553a5a1b3Sopenharmony_ci } 25653a5a1b3Sopenharmony_ci 25753a5a1b3Sopenharmony_ci if (pp.source) 25853a5a1b3Sopenharmony_ci pa_source_set_port(pp.source, port->name, false); 25953a5a1b3Sopenharmony_ci if (pp.sink) 26053a5a1b3Sopenharmony_ci pa_sink_set_port(pp.sink, port->name, false); 26153a5a1b3Sopenharmony_ci} 26253a5a1b3Sopenharmony_ci 26353a5a1b3Sopenharmony_ci/* Switches away from a port, switching profiles if necessary or preferred */ 26453a5a1b3Sopenharmony_cistatic void switch_from_port(pa_device_port *port, struct port_pointers pp) { 26553a5a1b3Sopenharmony_ci pa_device_port *p, *best_port = NULL; 26653a5a1b3Sopenharmony_ci void *state; 26753a5a1b3Sopenharmony_ci 26853a5a1b3Sopenharmony_ci if (!pp.is_port_active) 26953a5a1b3Sopenharmony_ci return; /* Already deselected */ 27053a5a1b3Sopenharmony_ci 27153a5a1b3Sopenharmony_ci /* Try to find a good enough port to switch to */ 27253a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(p, port->card->ports, state) { 27353a5a1b3Sopenharmony_ci if (p == port) 27453a5a1b3Sopenharmony_ci continue; 27553a5a1b3Sopenharmony_ci 27653a5a1b3Sopenharmony_ci if (p->available == PA_AVAILABLE_NO) 27753a5a1b3Sopenharmony_ci continue; 27853a5a1b3Sopenharmony_ci 27953a5a1b3Sopenharmony_ci if (p->direction != port->direction) 28053a5a1b3Sopenharmony_ci continue; 28153a5a1b3Sopenharmony_ci 28253a5a1b3Sopenharmony_ci if (!best_port || best_port->priority < p->priority) 28353a5a1b3Sopenharmony_ci best_port = p; 28453a5a1b3Sopenharmony_ci } 28553a5a1b3Sopenharmony_ci 28653a5a1b3Sopenharmony_ci pa_log_debug("Trying to switch away from port %s, found %s", port->name, best_port ? best_port->name : "no better option"); 28753a5a1b3Sopenharmony_ci 28853a5a1b3Sopenharmony_ci /* If there is no available port to switch to we need check if the active 28953a5a1b3Sopenharmony_ci * profile is still available in the 29053a5a1b3Sopenharmony_ci * PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_CHANGED callback, as at this point 29153a5a1b3Sopenharmony_ci * the profile availability hasn't been updated yet. */ 29253a5a1b3Sopenharmony_ci if (best_port) { 29353a5a1b3Sopenharmony_ci struct port_pointers best_pp = find_port_pointers(best_port); 29453a5a1b3Sopenharmony_ci switch_to_port(best_port, best_pp); 29553a5a1b3Sopenharmony_ci } 29653a5a1b3Sopenharmony_ci} 29753a5a1b3Sopenharmony_ci 29853a5a1b3Sopenharmony_ci 29953a5a1b3Sopenharmony_cistatic pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port *port, void* userdata) { 30053a5a1b3Sopenharmony_ci struct port_pointers pp = find_port_pointers(port); 30153a5a1b3Sopenharmony_ci 30253a5a1b3Sopenharmony_ci if (!port->card) { 30353a5a1b3Sopenharmony_ci pa_log_warn("Port %s does not have a card", port->name); 30453a5a1b3Sopenharmony_ci return PA_HOOK_OK; 30553a5a1b3Sopenharmony_ci } 30653a5a1b3Sopenharmony_ci 30753a5a1b3Sopenharmony_ci /* Our profile switching logic caused trouble with bluetooth headsets (see 30853a5a1b3Sopenharmony_ci * https://bugs.freedesktop.org/show_bug.cgi?id=107044) and 30953a5a1b3Sopenharmony_ci * module-bluetooth-policy takes care of automatic profile switching 31053a5a1b3Sopenharmony_ci * anyway, so we ignore bluetooth cards in 31153a5a1b3Sopenharmony_ci * module-switch-on-port-available. */ 31253a5a1b3Sopenharmony_ci if (pa_safe_streq(pa_proplist_gets(port->card->proplist, PA_PROP_DEVICE_BUS), "bluetooth")) 31353a5a1b3Sopenharmony_ci return PA_HOOK_OK; 31453a5a1b3Sopenharmony_ci 31553a5a1b3Sopenharmony_ci switch (port->available) { 31653a5a1b3Sopenharmony_ci case PA_AVAILABLE_UNKNOWN: 31753a5a1b3Sopenharmony_ci /* If a port availability became unknown, let's see if it's part of 31853a5a1b3Sopenharmony_ci * some availability group. If it is, it is likely to be a headphone 31953a5a1b3Sopenharmony_ci * jack that does not have impedance sensing to detect whether what was 32053a5a1b3Sopenharmony_ci * plugged in was a headphone, headset or microphone. In desktop 32153a5a1b3Sopenharmony_ci * environments that support it, this will trigger a user choice to 32253a5a1b3Sopenharmony_ci * select what kind of device was plugged in. However, let's switch to 32353a5a1b3Sopenharmony_ci * the headphone port at least, so that we have don't break 32453a5a1b3Sopenharmony_ci * functionality for setups that can't trigger this kind of 32553a5a1b3Sopenharmony_ci * interaction. 32653a5a1b3Sopenharmony_ci * 32753a5a1b3Sopenharmony_ci * For headset or microphone, if they are part of some availability group 32853a5a1b3Sopenharmony_ci * and they become unknown from off, it needs to check if their source is 32953a5a1b3Sopenharmony_ci * unlinked or not, if their source is unlinked, let switch_to_port() 33053a5a1b3Sopenharmony_ci * process them, then with the running of pa_card_set_profile(), their 33153a5a1b3Sopenharmony_ci * source will be created, otherwise the headset or microphone can't be used 33253a5a1b3Sopenharmony_ci * to record sound since there is no source for these 2 ports. This issue 33353a5a1b3Sopenharmony_ci * is observed on Dell machines which have multi-function audio jack but no 33453a5a1b3Sopenharmony_ci * internal mic. 33553a5a1b3Sopenharmony_ci * 33653a5a1b3Sopenharmony_ci * We should make this configurable so that users can optionally 33753a5a1b3Sopenharmony_ci * override the default to a headset or mic. */ 33853a5a1b3Sopenharmony_ci 33953a5a1b3Sopenharmony_ci /* Not part of a group of ports, so likely not a combination port */ 34053a5a1b3Sopenharmony_ci if (!port->availability_group) { 34153a5a1b3Sopenharmony_ci pa_log_debug("Not switching to port %s, its availability is unknown and it's not in any availability group.", port->name); 34253a5a1b3Sopenharmony_ci break; 34353a5a1b3Sopenharmony_ci } 34453a5a1b3Sopenharmony_ci 34553a5a1b3Sopenharmony_ci /* Switch the headphone port, the input ports without source and the 34653a5a1b3Sopenharmony_ci * input ports their source->active_port is part of a group of ports. 34753a5a1b3Sopenharmony_ci */ 34853a5a1b3Sopenharmony_ci if (port->direction == PA_DIRECTION_INPUT && pp.source && !pp.source->active_port->availability_group) { 34953a5a1b3Sopenharmony_ci pa_log_debug("Not switching to input port %s, its availability is unknown.", port->name); 35053a5a1b3Sopenharmony_ci break; 35153a5a1b3Sopenharmony_ci } 35253a5a1b3Sopenharmony_ci 35353a5a1b3Sopenharmony_ci switch_to_port(port, pp); 35453a5a1b3Sopenharmony_ci break; 35553a5a1b3Sopenharmony_ci 35653a5a1b3Sopenharmony_ci case PA_AVAILABLE_YES: 35753a5a1b3Sopenharmony_ci switch_to_port(port, pp); 35853a5a1b3Sopenharmony_ci break; 35953a5a1b3Sopenharmony_ci case PA_AVAILABLE_NO: 36053a5a1b3Sopenharmony_ci switch_from_port(port, pp); 36153a5a1b3Sopenharmony_ci break; 36253a5a1b3Sopenharmony_ci default: 36353a5a1b3Sopenharmony_ci break; 36453a5a1b3Sopenharmony_ci } 36553a5a1b3Sopenharmony_ci 36653a5a1b3Sopenharmony_ci return PA_HOOK_OK; 36753a5a1b3Sopenharmony_ci} 36853a5a1b3Sopenharmony_ci 36953a5a1b3Sopenharmony_cistatic pa_card_profile *find_best_profile(pa_card *card) { 37053a5a1b3Sopenharmony_ci pa_card_profile *profile, *best_profile; 37153a5a1b3Sopenharmony_ci void *state; 37253a5a1b3Sopenharmony_ci 37353a5a1b3Sopenharmony_ci pa_assert(card); 37453a5a1b3Sopenharmony_ci best_profile = pa_hashmap_get(card->profiles, "off"); 37553a5a1b3Sopenharmony_ci 37653a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(profile, card->profiles, state) { 37753a5a1b3Sopenharmony_ci if (profile->available == PA_AVAILABLE_NO) 37853a5a1b3Sopenharmony_ci continue; 37953a5a1b3Sopenharmony_ci 38053a5a1b3Sopenharmony_ci if (profile->priority > best_profile->priority) 38153a5a1b3Sopenharmony_ci best_profile = profile; 38253a5a1b3Sopenharmony_ci } 38353a5a1b3Sopenharmony_ci 38453a5a1b3Sopenharmony_ci return best_profile; 38553a5a1b3Sopenharmony_ci} 38653a5a1b3Sopenharmony_ci 38753a5a1b3Sopenharmony_cistatic pa_hook_result_t card_profile_available_hook_callback(pa_core *c, pa_card_profile *profile, struct userdata *u) { 38853a5a1b3Sopenharmony_ci pa_card *card; 38953a5a1b3Sopenharmony_ci 39053a5a1b3Sopenharmony_ci pa_assert(profile); 39153a5a1b3Sopenharmony_ci pa_assert_se(card = profile->card); 39253a5a1b3Sopenharmony_ci 39353a5a1b3Sopenharmony_ci if (profile->available != PA_AVAILABLE_NO) 39453a5a1b3Sopenharmony_ci return PA_HOOK_OK; 39553a5a1b3Sopenharmony_ci 39653a5a1b3Sopenharmony_ci if (!pa_streq(profile->name, card->active_profile->name)) 39753a5a1b3Sopenharmony_ci return PA_HOOK_OK; 39853a5a1b3Sopenharmony_ci 39953a5a1b3Sopenharmony_ci if (card->profile_is_sticky) { 40053a5a1b3Sopenharmony_ci pa_log_info("Keeping sticky card profile '%s'", profile->name); 40153a5a1b3Sopenharmony_ci return PA_HOOK_OK; 40253a5a1b3Sopenharmony_ci } 40353a5a1b3Sopenharmony_ci 40453a5a1b3Sopenharmony_ci pa_log_debug("Active profile %s on card %s became unavailable, switching to another profile", profile->name, card->name); 40553a5a1b3Sopenharmony_ci pa_card_set_profile(card, find_best_profile(card), false); 40653a5a1b3Sopenharmony_ci 40753a5a1b3Sopenharmony_ci return PA_HOOK_OK; 40853a5a1b3Sopenharmony_ci 40953a5a1b3Sopenharmony_ci} 41053a5a1b3Sopenharmony_ci 41153a5a1b3Sopenharmony_cistatic void handle_all_unavailable(pa_core *core) { 41253a5a1b3Sopenharmony_ci pa_card *card; 41353a5a1b3Sopenharmony_ci uint32_t state; 41453a5a1b3Sopenharmony_ci 41553a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(card, core->cards, state) { 41653a5a1b3Sopenharmony_ci pa_device_port *port; 41753a5a1b3Sopenharmony_ci void *state2; 41853a5a1b3Sopenharmony_ci 41953a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(port, card->ports, state2) { 42053a5a1b3Sopenharmony_ci if (port->available == PA_AVAILABLE_NO) 42153a5a1b3Sopenharmony_ci port_available_hook_callback(core, port, NULL); 42253a5a1b3Sopenharmony_ci } 42353a5a1b3Sopenharmony_ci } 42453a5a1b3Sopenharmony_ci} 42553a5a1b3Sopenharmony_ci 42653a5a1b3Sopenharmony_cistatic pa_device_port *new_sink_source(pa_hashmap *ports, const char *name) { 42753a5a1b3Sopenharmony_ci 42853a5a1b3Sopenharmony_ci void *state; 42953a5a1b3Sopenharmony_ci pa_device_port *i, *p = NULL; 43053a5a1b3Sopenharmony_ci 43153a5a1b3Sopenharmony_ci if (!ports) 43253a5a1b3Sopenharmony_ci return NULL; 43353a5a1b3Sopenharmony_ci if (name) 43453a5a1b3Sopenharmony_ci p = pa_hashmap_get(ports, name); 43553a5a1b3Sopenharmony_ci if (!p) 43653a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(i, ports, state) 43753a5a1b3Sopenharmony_ci if (!p || i->priority > p->priority) 43853a5a1b3Sopenharmony_ci p = i; 43953a5a1b3Sopenharmony_ci if (!p) 44053a5a1b3Sopenharmony_ci return NULL; 44153a5a1b3Sopenharmony_ci if (p->available != PA_AVAILABLE_NO) 44253a5a1b3Sopenharmony_ci return NULL; 44353a5a1b3Sopenharmony_ci 44453a5a1b3Sopenharmony_ci pa_assert_se(p = pa_device_port_find_best(ports)); 44553a5a1b3Sopenharmony_ci return p; 44653a5a1b3Sopenharmony_ci} 44753a5a1b3Sopenharmony_ci 44853a5a1b3Sopenharmony_cistatic pa_hook_result_t sink_new_hook_callback(pa_core *c, pa_sink_new_data *new_data, void *u) { 44953a5a1b3Sopenharmony_ci 45053a5a1b3Sopenharmony_ci pa_device_port *p = new_sink_source(new_data->ports, new_data->active_port); 45153a5a1b3Sopenharmony_ci 45253a5a1b3Sopenharmony_ci if (p) { 45353a5a1b3Sopenharmony_ci pa_log_debug("Switching initial port for sink '%s' to '%s'", new_data->name, p->name); 45453a5a1b3Sopenharmony_ci pa_sink_new_data_set_port(new_data, p->name); 45553a5a1b3Sopenharmony_ci } 45653a5a1b3Sopenharmony_ci return PA_HOOK_OK; 45753a5a1b3Sopenharmony_ci} 45853a5a1b3Sopenharmony_ci 45953a5a1b3Sopenharmony_cistatic pa_hook_result_t source_new_hook_callback(pa_core *c, pa_source_new_data *new_data, void *u) { 46053a5a1b3Sopenharmony_ci 46153a5a1b3Sopenharmony_ci pa_device_port *p = new_sink_source(new_data->ports, new_data->active_port); 46253a5a1b3Sopenharmony_ci 46353a5a1b3Sopenharmony_ci if (p) { 46453a5a1b3Sopenharmony_ci pa_log_debug("Switching initial port for source '%s' to '%s'", new_data->name, p->name); 46553a5a1b3Sopenharmony_ci pa_source_new_data_set_port(new_data, p->name); 46653a5a1b3Sopenharmony_ci } 46753a5a1b3Sopenharmony_ci return PA_HOOK_OK; 46853a5a1b3Sopenharmony_ci} 46953a5a1b3Sopenharmony_ci 47053a5a1b3Sopenharmony_cistatic pa_hook_result_t card_put_hook_callback(pa_core *core, pa_card *card, struct userdata *u) { 47153a5a1b3Sopenharmony_ci card_info_new(u, card); 47253a5a1b3Sopenharmony_ci 47353a5a1b3Sopenharmony_ci return PA_HOOK_OK; 47453a5a1b3Sopenharmony_ci} 47553a5a1b3Sopenharmony_ci 47653a5a1b3Sopenharmony_cistatic pa_hook_result_t card_unlink_hook_callback(pa_core *core, pa_card *card, struct userdata *u) { 47753a5a1b3Sopenharmony_ci card_info_free(pa_hashmap_get(u->card_infos, card)); 47853a5a1b3Sopenharmony_ci 47953a5a1b3Sopenharmony_ci return PA_HOOK_OK; 48053a5a1b3Sopenharmony_ci} 48153a5a1b3Sopenharmony_ci 48253a5a1b3Sopenharmony_cistatic void update_preferred_input_port(pa_card *card, pa_card_profile *old_profile, pa_card_profile *new_profile) { 48353a5a1b3Sopenharmony_ci pa_source *source; 48453a5a1b3Sopenharmony_ci 48553a5a1b3Sopenharmony_ci /* If the profile change didn't affect input, it doesn't indicate change in 48653a5a1b3Sopenharmony_ci * the user's input port preference. */ 48753a5a1b3Sopenharmony_ci if (pa_safe_streq(old_profile->input_name, new_profile->input_name)) 48853a5a1b3Sopenharmony_ci return; 48953a5a1b3Sopenharmony_ci 49053a5a1b3Sopenharmony_ci /* If there are more than one source, we don't know which of those the user 49153a5a1b3Sopenharmony_ci * prefers. If there are no sources, then the user doesn't seem to care 49253a5a1b3Sopenharmony_ci * about input at all. */ 49353a5a1b3Sopenharmony_ci if (pa_idxset_size(card->sources) != 1) { 49453a5a1b3Sopenharmony_ci pa_card_set_preferred_port(card, PA_DIRECTION_INPUT, NULL); 49553a5a1b3Sopenharmony_ci return; 49653a5a1b3Sopenharmony_ci } 49753a5a1b3Sopenharmony_ci 49853a5a1b3Sopenharmony_ci /* If the profile change modified the set of sinks, then it's unclear 49953a5a1b3Sopenharmony_ci * whether the user wanted to activate some specific input port, or was the 50053a5a1b3Sopenharmony_ci * input change only a side effect of activating some output. If the new 50153a5a1b3Sopenharmony_ci * profile contains no sinks, though, then we know the user only cares 50253a5a1b3Sopenharmony_ci * about input. */ 50353a5a1b3Sopenharmony_ci if (pa_idxset_size(card->sinks) > 0 && !pa_safe_streq(old_profile->output_name, new_profile->output_name)) { 50453a5a1b3Sopenharmony_ci pa_card_set_preferred_port(card, PA_DIRECTION_INPUT, NULL); 50553a5a1b3Sopenharmony_ci return; 50653a5a1b3Sopenharmony_ci } 50753a5a1b3Sopenharmony_ci 50853a5a1b3Sopenharmony_ci source = pa_idxset_first(card->sources, NULL); 50953a5a1b3Sopenharmony_ci 51053a5a1b3Sopenharmony_ci /* We know the user wanted to activate this source. The user might not have 51153a5a1b3Sopenharmony_ci * wanted to activate the port that was selected by default, but if that's 51253a5a1b3Sopenharmony_ci * the case, the user will change the port manually, and we'll update the 51353a5a1b3Sopenharmony_ci * port preference at that time. If no port change occurs, we can assume 51453a5a1b3Sopenharmony_ci * that the user likes the port that is now active. */ 51553a5a1b3Sopenharmony_ci pa_card_set_preferred_port(card, PA_DIRECTION_INPUT, source->active_port); 51653a5a1b3Sopenharmony_ci} 51753a5a1b3Sopenharmony_ci 51853a5a1b3Sopenharmony_cistatic void update_preferred_output_port(pa_card *card, pa_card_profile *old_profile, pa_card_profile *new_profile) { 51953a5a1b3Sopenharmony_ci pa_sink *sink; 52053a5a1b3Sopenharmony_ci 52153a5a1b3Sopenharmony_ci /* If the profile change didn't affect output, it doesn't indicate change in 52253a5a1b3Sopenharmony_ci * the user's output port preference. */ 52353a5a1b3Sopenharmony_ci if (pa_safe_streq(old_profile->output_name, new_profile->output_name)) 52453a5a1b3Sopenharmony_ci return; 52553a5a1b3Sopenharmony_ci 52653a5a1b3Sopenharmony_ci /* If there are more than one sink, we don't know which of those the user 52753a5a1b3Sopenharmony_ci * prefers. If there are no sinks, then the user doesn't seem to care about 52853a5a1b3Sopenharmony_ci * output at all. */ 52953a5a1b3Sopenharmony_ci if (pa_idxset_size(card->sinks) != 1) { 53053a5a1b3Sopenharmony_ci pa_card_set_preferred_port(card, PA_DIRECTION_OUTPUT, NULL); 53153a5a1b3Sopenharmony_ci return; 53253a5a1b3Sopenharmony_ci } 53353a5a1b3Sopenharmony_ci 53453a5a1b3Sopenharmony_ci /* If the profile change modified the set of sources, then it's unclear 53553a5a1b3Sopenharmony_ci * whether the user wanted to activate some specific output port, or was 53653a5a1b3Sopenharmony_ci * the output change only a side effect of activating some input. If the 53753a5a1b3Sopenharmony_ci * new profile contains no sources, though, then we know the user only 53853a5a1b3Sopenharmony_ci * cares about output. */ 53953a5a1b3Sopenharmony_ci if (pa_idxset_size(card->sources) > 0 && !pa_safe_streq(old_profile->input_name, new_profile->input_name)) { 54053a5a1b3Sopenharmony_ci pa_card_set_preferred_port(card, PA_DIRECTION_OUTPUT, NULL); 54153a5a1b3Sopenharmony_ci return; 54253a5a1b3Sopenharmony_ci } 54353a5a1b3Sopenharmony_ci 54453a5a1b3Sopenharmony_ci sink = pa_idxset_first(card->sinks, NULL); 54553a5a1b3Sopenharmony_ci 54653a5a1b3Sopenharmony_ci /* We know the user wanted to activate this sink. The user might not have 54753a5a1b3Sopenharmony_ci * wanted to activate the port that was selected by default, but if that's 54853a5a1b3Sopenharmony_ci * the case, the user will change the port manually, and we'll update the 54953a5a1b3Sopenharmony_ci * port preference at that time. If no port change occurs, we can assume 55053a5a1b3Sopenharmony_ci * that the user likes the port that is now active. */ 55153a5a1b3Sopenharmony_ci pa_card_set_preferred_port(card, PA_DIRECTION_OUTPUT, sink->active_port); 55253a5a1b3Sopenharmony_ci} 55353a5a1b3Sopenharmony_ci 55453a5a1b3Sopenharmony_cistatic pa_hook_result_t card_profile_changed_callback(pa_core *core, pa_card *card, struct userdata *u) { 55553a5a1b3Sopenharmony_ci struct card_info *info; 55653a5a1b3Sopenharmony_ci pa_card_profile *old_profile; 55753a5a1b3Sopenharmony_ci pa_card_profile *new_profile; 55853a5a1b3Sopenharmony_ci 55953a5a1b3Sopenharmony_ci info = pa_hashmap_get(u->card_infos, card); 56053a5a1b3Sopenharmony_ci old_profile = info->active_profile; 56153a5a1b3Sopenharmony_ci new_profile = card->active_profile; 56253a5a1b3Sopenharmony_ci info->active_profile = new_profile; 56353a5a1b3Sopenharmony_ci 56453a5a1b3Sopenharmony_ci /* This profile change wasn't initiated by the user, so it doesn't signal 56553a5a1b3Sopenharmony_ci * a change in the user's port preferences. */ 56653a5a1b3Sopenharmony_ci if (!card->save_profile) 56753a5a1b3Sopenharmony_ci return PA_HOOK_OK; 56853a5a1b3Sopenharmony_ci 56953a5a1b3Sopenharmony_ci update_preferred_input_port(card, old_profile, new_profile); 57053a5a1b3Sopenharmony_ci update_preferred_output_port(card, old_profile, new_profile); 57153a5a1b3Sopenharmony_ci 57253a5a1b3Sopenharmony_ci return PA_HOOK_OK; 57353a5a1b3Sopenharmony_ci} 57453a5a1b3Sopenharmony_ci 57553a5a1b3Sopenharmony_cistatic pa_hook_result_t source_port_changed_callback(pa_core *core, pa_source *source, void *userdata) { 57653a5a1b3Sopenharmony_ci if (!source->save_port) 57753a5a1b3Sopenharmony_ci return PA_HOOK_OK; 57853a5a1b3Sopenharmony_ci 57953a5a1b3Sopenharmony_ci pa_card_set_preferred_port(source->card, PA_DIRECTION_INPUT, source->active_port); 58053a5a1b3Sopenharmony_ci 58153a5a1b3Sopenharmony_ci return PA_HOOK_OK; 58253a5a1b3Sopenharmony_ci} 58353a5a1b3Sopenharmony_ci 58453a5a1b3Sopenharmony_cistatic pa_hook_result_t sink_port_changed_callback(pa_core *core, pa_sink *sink, void *userdata) { 58553a5a1b3Sopenharmony_ci if (!sink->save_port) 58653a5a1b3Sopenharmony_ci return PA_HOOK_OK; 58753a5a1b3Sopenharmony_ci 58853a5a1b3Sopenharmony_ci pa_card_set_preferred_port(sink->card, PA_DIRECTION_OUTPUT, sink->active_port); 58953a5a1b3Sopenharmony_ci 59053a5a1b3Sopenharmony_ci return PA_HOOK_OK; 59153a5a1b3Sopenharmony_ci} 59253a5a1b3Sopenharmony_ci 59353a5a1b3Sopenharmony_ciint pa__init(pa_module*m) { 59453a5a1b3Sopenharmony_ci struct userdata *u; 59553a5a1b3Sopenharmony_ci pa_card *card; 59653a5a1b3Sopenharmony_ci uint32_t idx; 59753a5a1b3Sopenharmony_ci 59853a5a1b3Sopenharmony_ci pa_assert(m); 59953a5a1b3Sopenharmony_ci 60053a5a1b3Sopenharmony_ci u = m->userdata = pa_xnew0(struct userdata, 1); 60153a5a1b3Sopenharmony_ci u->card_infos = pa_hashmap_new(NULL, NULL); 60253a5a1b3Sopenharmony_ci 60353a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(card, m->core->cards, idx) 60453a5a1b3Sopenharmony_ci card_info_new(u, card); 60553a5a1b3Sopenharmony_ci 60653a5a1b3Sopenharmony_ci /* Make sure we are after module-device-restore, so we can overwrite that suggestion if necessary */ 60753a5a1b3Sopenharmony_ci pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_NEW], 60853a5a1b3Sopenharmony_ci PA_HOOK_NORMAL, (pa_hook_cb_t) sink_new_hook_callback, NULL); 60953a5a1b3Sopenharmony_ci pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_NEW], 61053a5a1b3Sopenharmony_ci PA_HOOK_NORMAL, (pa_hook_cb_t) source_new_hook_callback, NULL); 61153a5a1b3Sopenharmony_ci pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_PORT_AVAILABLE_CHANGED], 61253a5a1b3Sopenharmony_ci PA_HOOK_LATE, (pa_hook_cb_t) port_available_hook_callback, NULL); 61353a5a1b3Sopenharmony_ci pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_CHANGED], 61453a5a1b3Sopenharmony_ci PA_HOOK_LATE, (pa_hook_cb_t) card_profile_available_hook_callback, NULL); 61553a5a1b3Sopenharmony_ci pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PUT], 61653a5a1b3Sopenharmony_ci PA_HOOK_NORMAL, (pa_hook_cb_t) card_put_hook_callback, u); 61753a5a1b3Sopenharmony_ci pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_UNLINK], 61853a5a1b3Sopenharmony_ci PA_HOOK_NORMAL, (pa_hook_cb_t) card_unlink_hook_callback, u); 61953a5a1b3Sopenharmony_ci pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_CHANGED], 62053a5a1b3Sopenharmony_ci PA_HOOK_NORMAL, (pa_hook_cb_t) card_profile_changed_callback, u); 62153a5a1b3Sopenharmony_ci pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_PORT_CHANGED], 62253a5a1b3Sopenharmony_ci PA_HOOK_NORMAL, (pa_hook_cb_t) source_port_changed_callback, NULL); 62353a5a1b3Sopenharmony_ci pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_PORT_CHANGED], 62453a5a1b3Sopenharmony_ci PA_HOOK_NORMAL, (pa_hook_cb_t) sink_port_changed_callback, NULL); 62553a5a1b3Sopenharmony_ci 62653a5a1b3Sopenharmony_ci handle_all_unavailable(m->core); 62753a5a1b3Sopenharmony_ci 62853a5a1b3Sopenharmony_ci return 0; 62953a5a1b3Sopenharmony_ci} 63053a5a1b3Sopenharmony_ci 63153a5a1b3Sopenharmony_civoid pa__done(pa_module *module) { 63253a5a1b3Sopenharmony_ci struct userdata *u; 63353a5a1b3Sopenharmony_ci struct card_info *info; 63453a5a1b3Sopenharmony_ci 63553a5a1b3Sopenharmony_ci pa_assert(module); 63653a5a1b3Sopenharmony_ci 63753a5a1b3Sopenharmony_ci if (!(u = module->userdata)) 63853a5a1b3Sopenharmony_ci return; 63953a5a1b3Sopenharmony_ci 64053a5a1b3Sopenharmony_ci while ((info = pa_hashmap_last(u->card_infos))) 64153a5a1b3Sopenharmony_ci card_info_free(info); 64253a5a1b3Sopenharmony_ci 64353a5a1b3Sopenharmony_ci pa_hashmap_free(u->card_infos); 64453a5a1b3Sopenharmony_ci 64553a5a1b3Sopenharmony_ci pa_xfree(u); 64653a5a1b3Sopenharmony_ci} 647