153a5a1b3Sopenharmony_ci/*** 253a5a1b3Sopenharmony_ci This file is part of PulseAudio. 353a5a1b3Sopenharmony_ci 453a5a1b3Sopenharmony_ci Copyright 2006 Lennart Poettering 553a5a1b3Sopenharmony_ci Copyright 2009 Canonical Ltd 653a5a1b3Sopenharmony_ci Copyright (C) 2012 Intel Corporation 753a5a1b3Sopenharmony_ci 853a5a1b3Sopenharmony_ci PulseAudio is free software; you can redistribute it and/or modify 953a5a1b3Sopenharmony_ci it under the terms of the GNU Lesser General Public License as published 1053a5a1b3Sopenharmony_ci by the Free Software Foundation; either version 2.1 of the License, 1153a5a1b3Sopenharmony_ci or (at your option) any later version. 1253a5a1b3Sopenharmony_ci 1353a5a1b3Sopenharmony_ci PulseAudio is distributed in the hope that it will be useful, but 1453a5a1b3Sopenharmony_ci WITHOUT ANY WARRANTY; without even the implied warranty of 1553a5a1b3Sopenharmony_ci MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 1653a5a1b3Sopenharmony_ci General Public License for more details. 1753a5a1b3Sopenharmony_ci 1853a5a1b3Sopenharmony_ci You should have received a copy of the GNU Lesser General Public License 1953a5a1b3Sopenharmony_ci along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. 2053a5a1b3Sopenharmony_ci***/ 2153a5a1b3Sopenharmony_ci 2253a5a1b3Sopenharmony_ci#ifdef HAVE_CONFIG_H 2353a5a1b3Sopenharmony_ci#include <config.h> 2453a5a1b3Sopenharmony_ci#endif 2553a5a1b3Sopenharmony_ci 2653a5a1b3Sopenharmony_ci#include <pulse/xmalloc.h> 2753a5a1b3Sopenharmony_ci 2853a5a1b3Sopenharmony_ci#include <pulsecore/core.h> 2953a5a1b3Sopenharmony_ci#include <pulsecore/modargs.h> 3053a5a1b3Sopenharmony_ci#include <pulsecore/source-output.h> 3153a5a1b3Sopenharmony_ci#include <pulsecore/source.h> 3253a5a1b3Sopenharmony_ci#include <pulsecore/core-util.h> 3353a5a1b3Sopenharmony_ci 3453a5a1b3Sopenharmony_ciPA_MODULE_AUTHOR("Frédéric Dalleau, Pali Rohár"); 3553a5a1b3Sopenharmony_ciPA_MODULE_DESCRIPTION("Policy module to make using bluetooth devices out-of-the-box easier"); 3653a5a1b3Sopenharmony_ciPA_MODULE_VERSION(PACKAGE_VERSION); 3753a5a1b3Sopenharmony_ciPA_MODULE_LOAD_ONCE(true); 3853a5a1b3Sopenharmony_ciPA_MODULE_USAGE( 3953a5a1b3Sopenharmony_ci "auto_switch=<Switch between hsp and a2dp profile? (0 - never, 1 - media.role=phone, 2 - heuristic> " 4053a5a1b3Sopenharmony_ci "a2dp_source=<Handle a2dp_source card profile (sink role)?> " 4153a5a1b3Sopenharmony_ci "ag=<Handle headset_audio_gateway or handsfree_audio_gateway card profile (headset role)?> "); 4253a5a1b3Sopenharmony_ci 4353a5a1b3Sopenharmony_cistatic const char* const valid_modargs[] = { 4453a5a1b3Sopenharmony_ci "auto_switch", 4553a5a1b3Sopenharmony_ci "a2dp_source", 4653a5a1b3Sopenharmony_ci "ag", 4753a5a1b3Sopenharmony_ci NULL 4853a5a1b3Sopenharmony_ci}; 4953a5a1b3Sopenharmony_ci 5053a5a1b3Sopenharmony_cistruct userdata { 5153a5a1b3Sopenharmony_ci uint32_t auto_switch; 5253a5a1b3Sopenharmony_ci bool enable_a2dp_source; 5353a5a1b3Sopenharmony_ci bool enable_ag; 5453a5a1b3Sopenharmony_ci pa_hook_slot *source_put_slot; 5553a5a1b3Sopenharmony_ci pa_hook_slot *sink_put_slot; 5653a5a1b3Sopenharmony_ci pa_hook_slot *source_output_put_slot; 5753a5a1b3Sopenharmony_ci pa_hook_slot *source_output_unlink_slot; 5853a5a1b3Sopenharmony_ci pa_hook_slot *card_init_profile_slot; 5953a5a1b3Sopenharmony_ci pa_hook_slot *card_unlink_slot; 6053a5a1b3Sopenharmony_ci pa_hook_slot *profile_available_changed_slot; 6153a5a1b3Sopenharmony_ci pa_hashmap *will_need_revert_card_map; 6253a5a1b3Sopenharmony_ci}; 6353a5a1b3Sopenharmony_ci 6453a5a1b3Sopenharmony_ci/* When a source is created, loopback it to default sink */ 6553a5a1b3Sopenharmony_cistatic pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, void *userdata) { 6653a5a1b3Sopenharmony_ci struct userdata *u = userdata; 6753a5a1b3Sopenharmony_ci const char *s; 6853a5a1b3Sopenharmony_ci const char *role; 6953a5a1b3Sopenharmony_ci char *args; 7053a5a1b3Sopenharmony_ci pa_module *m = NULL; 7153a5a1b3Sopenharmony_ci 7253a5a1b3Sopenharmony_ci pa_assert(c); 7353a5a1b3Sopenharmony_ci pa_assert(source); 7453a5a1b3Sopenharmony_ci 7553a5a1b3Sopenharmony_ci /* Only consider bluetooth sinks and sources */ 7653a5a1b3Sopenharmony_ci s = pa_proplist_gets(source->proplist, PA_PROP_DEVICE_BUS); 7753a5a1b3Sopenharmony_ci if (!s) 7853a5a1b3Sopenharmony_ci return PA_HOOK_OK; 7953a5a1b3Sopenharmony_ci 8053a5a1b3Sopenharmony_ci if (!pa_streq(s, "bluetooth")) 8153a5a1b3Sopenharmony_ci return PA_HOOK_OK; 8253a5a1b3Sopenharmony_ci 8353a5a1b3Sopenharmony_ci s = pa_proplist_gets(source->proplist, "bluetooth.protocol"); 8453a5a1b3Sopenharmony_ci if (!s) 8553a5a1b3Sopenharmony_ci return PA_HOOK_OK; 8653a5a1b3Sopenharmony_ci 8753a5a1b3Sopenharmony_ci if (u->enable_a2dp_source && pa_streq(s, "a2dp_source")) 8853a5a1b3Sopenharmony_ci role = "music"; 8953a5a1b3Sopenharmony_ci else if (u->enable_ag && (pa_streq(s, "headset_audio_gateway") || pa_streq(s, "handsfree_audio_gateway"))) 9053a5a1b3Sopenharmony_ci role = "phone"; 9153a5a1b3Sopenharmony_ci else { 9253a5a1b3Sopenharmony_ci pa_log_debug("Profile %s cannot be selected for loopback", s); 9353a5a1b3Sopenharmony_ci return PA_HOOK_OK; 9453a5a1b3Sopenharmony_ci } 9553a5a1b3Sopenharmony_ci 9653a5a1b3Sopenharmony_ci /* Load module-loopback */ 9753a5a1b3Sopenharmony_ci args = pa_sprintf_malloc("source=\"%s\" source_dont_move=\"true\" sink_input_properties=\"media.role=%s\"", source->name, 9853a5a1b3Sopenharmony_ci role); 9953a5a1b3Sopenharmony_ci (void) pa_module_load(&m, c, "module-loopback", args); 10053a5a1b3Sopenharmony_ci pa_xfree(args); 10153a5a1b3Sopenharmony_ci 10253a5a1b3Sopenharmony_ci return PA_HOOK_OK; 10353a5a1b3Sopenharmony_ci} 10453a5a1b3Sopenharmony_ci 10553a5a1b3Sopenharmony_ci/* When a sink is created, loopback it to default source */ 10653a5a1b3Sopenharmony_cistatic pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, void *userdata) { 10753a5a1b3Sopenharmony_ci struct userdata *u = userdata; 10853a5a1b3Sopenharmony_ci const char *s; 10953a5a1b3Sopenharmony_ci const char *role; 11053a5a1b3Sopenharmony_ci char *args; 11153a5a1b3Sopenharmony_ci pa_module *m = NULL; 11253a5a1b3Sopenharmony_ci 11353a5a1b3Sopenharmony_ci pa_assert(c); 11453a5a1b3Sopenharmony_ci pa_assert(sink); 11553a5a1b3Sopenharmony_ci 11653a5a1b3Sopenharmony_ci /* Only consider bluetooth sinks and sources */ 11753a5a1b3Sopenharmony_ci s = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_BUS); 11853a5a1b3Sopenharmony_ci if (!s) 11953a5a1b3Sopenharmony_ci return PA_HOOK_OK; 12053a5a1b3Sopenharmony_ci 12153a5a1b3Sopenharmony_ci if (!pa_streq(s, "bluetooth")) 12253a5a1b3Sopenharmony_ci return PA_HOOK_OK; 12353a5a1b3Sopenharmony_ci 12453a5a1b3Sopenharmony_ci s = pa_proplist_gets(sink->proplist, "bluetooth.protocol"); 12553a5a1b3Sopenharmony_ci if (!s) 12653a5a1b3Sopenharmony_ci return PA_HOOK_OK; 12753a5a1b3Sopenharmony_ci 12853a5a1b3Sopenharmony_ci if (u->enable_ag && (pa_streq(s, "headset_audio_gateway") || pa_streq(s, "handsfree_audio_gateway"))) 12953a5a1b3Sopenharmony_ci role = "phone"; 13053a5a1b3Sopenharmony_ci else { 13153a5a1b3Sopenharmony_ci pa_log_debug("Profile %s cannot be selected for loopback", s); 13253a5a1b3Sopenharmony_ci return PA_HOOK_OK; 13353a5a1b3Sopenharmony_ci } 13453a5a1b3Sopenharmony_ci 13553a5a1b3Sopenharmony_ci /* Load module-loopback */ 13653a5a1b3Sopenharmony_ci args = pa_sprintf_malloc("sink=\"%s\" sink_dont_move=\"true\" source_output_properties=\"media.role=%s\"", sink->name, 13753a5a1b3Sopenharmony_ci role); 13853a5a1b3Sopenharmony_ci (void) pa_module_load(&m, c, "module-loopback", args); 13953a5a1b3Sopenharmony_ci pa_xfree(args); 14053a5a1b3Sopenharmony_ci 14153a5a1b3Sopenharmony_ci return PA_HOOK_OK; 14253a5a1b3Sopenharmony_ci} 14353a5a1b3Sopenharmony_ci 14453a5a1b3Sopenharmony_cistatic void card_set_profile(struct userdata *u, pa_card *card, bool revert_to_a2dp) 14553a5a1b3Sopenharmony_ci{ 14653a5a1b3Sopenharmony_ci pa_card_profile *profile; 14753a5a1b3Sopenharmony_ci void *state; 14853a5a1b3Sopenharmony_ci 14953a5a1b3Sopenharmony_ci /* Find available profile and activate it */ 15053a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(profile, card->profiles, state) { 15153a5a1b3Sopenharmony_ci if (profile->available == PA_AVAILABLE_NO) 15253a5a1b3Sopenharmony_ci continue; 15353a5a1b3Sopenharmony_ci 15453a5a1b3Sopenharmony_ci /* Check for correct profile based on revert_to_a2dp */ 15553a5a1b3Sopenharmony_ci if (revert_to_a2dp) { 15653a5a1b3Sopenharmony_ci if (!pa_streq(profile->name, "a2dp_sink")) 15753a5a1b3Sopenharmony_ci continue; 15853a5a1b3Sopenharmony_ci } else { 15953a5a1b3Sopenharmony_ci if (!pa_streq(profile->name, "headset_head_unit") && !pa_streq(profile->name, "handsfree_head_unit")) 16053a5a1b3Sopenharmony_ci continue; 16153a5a1b3Sopenharmony_ci } 16253a5a1b3Sopenharmony_ci 16353a5a1b3Sopenharmony_ci pa_log_debug("Setting card '%s' to profile '%s'", card->name, profile->name); 16453a5a1b3Sopenharmony_ci 16553a5a1b3Sopenharmony_ci if (pa_card_set_profile(card, profile, false) != 0) { 16653a5a1b3Sopenharmony_ci pa_log_warn("Could not set profile '%s'", profile->name); 16753a5a1b3Sopenharmony_ci continue; 16853a5a1b3Sopenharmony_ci } 16953a5a1b3Sopenharmony_ci 17053a5a1b3Sopenharmony_ci /* When we are not in revert_to_a2dp phase flag this card for will_need_revert */ 17153a5a1b3Sopenharmony_ci if (!revert_to_a2dp) 17253a5a1b3Sopenharmony_ci pa_hashmap_put(u->will_need_revert_card_map, card, PA_INT_TO_PTR(1)); 17353a5a1b3Sopenharmony_ci 17453a5a1b3Sopenharmony_ci break; 17553a5a1b3Sopenharmony_ci } 17653a5a1b3Sopenharmony_ci} 17753a5a1b3Sopenharmony_ci 17853a5a1b3Sopenharmony_ci/* Switch profile for one card */ 17953a5a1b3Sopenharmony_cistatic void switch_profile(pa_card *card, bool revert_to_a2dp, void *userdata) { 18053a5a1b3Sopenharmony_ci struct userdata *u = userdata; 18153a5a1b3Sopenharmony_ci const char *s; 18253a5a1b3Sopenharmony_ci 18353a5a1b3Sopenharmony_ci /* Only consider bluetooth cards */ 18453a5a1b3Sopenharmony_ci s = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_BUS); 18553a5a1b3Sopenharmony_ci if (!s || !pa_streq(s, "bluetooth")) 18653a5a1b3Sopenharmony_ci return; 18753a5a1b3Sopenharmony_ci 18853a5a1b3Sopenharmony_ci if (revert_to_a2dp) { 18953a5a1b3Sopenharmony_ci /* In revert_to_a2dp phase only consider cards with will_need_revert flag and remove it */ 19053a5a1b3Sopenharmony_ci if (!pa_hashmap_remove(u->will_need_revert_card_map, card)) 19153a5a1b3Sopenharmony_ci return; 19253a5a1b3Sopenharmony_ci 19353a5a1b3Sopenharmony_ci /* Skip card if does not have active headset profile */ 19453a5a1b3Sopenharmony_ci if (!pa_streq(card->active_profile->name, "headset_head_unit") && !pa_streq(card->active_profile->name, "handsfree_head_unit")) 19553a5a1b3Sopenharmony_ci return; 19653a5a1b3Sopenharmony_ci 19753a5a1b3Sopenharmony_ci /* Skip card if already has active a2dp profile */ 19853a5a1b3Sopenharmony_ci if (pa_streq(card->active_profile->name, "a2dp_sink")) 19953a5a1b3Sopenharmony_ci return; 20053a5a1b3Sopenharmony_ci } else { 20153a5a1b3Sopenharmony_ci /* Skip card if does not have active a2dp profile */ 20253a5a1b3Sopenharmony_ci if (!pa_streq(card->active_profile->name, "a2dp_sink")) 20353a5a1b3Sopenharmony_ci return; 20453a5a1b3Sopenharmony_ci 20553a5a1b3Sopenharmony_ci /* Skip card if already has active headset profile */ 20653a5a1b3Sopenharmony_ci if (pa_streq(card->active_profile->name, "headset_head_unit") || pa_streq(card->active_profile->name, "handsfree_head_unit")) 20753a5a1b3Sopenharmony_ci return; 20853a5a1b3Sopenharmony_ci } 20953a5a1b3Sopenharmony_ci 21053a5a1b3Sopenharmony_ci card_set_profile(u, card, revert_to_a2dp); 21153a5a1b3Sopenharmony_ci} 21253a5a1b3Sopenharmony_ci 21353a5a1b3Sopenharmony_ci/* Return true if we should ignore this source output */ 21453a5a1b3Sopenharmony_cistatic bool ignore_output(pa_source_output *source_output, void *userdata) { 21553a5a1b3Sopenharmony_ci struct userdata *u = userdata; 21653a5a1b3Sopenharmony_ci const char *s; 21753a5a1b3Sopenharmony_ci 21853a5a1b3Sopenharmony_ci /* New applications could set media.role for identifying streams */ 21953a5a1b3Sopenharmony_ci /* We are interested only in media.role=phone */ 22053a5a1b3Sopenharmony_ci s = pa_proplist_gets(source_output->proplist, PA_PROP_MEDIA_ROLE); 22153a5a1b3Sopenharmony_ci if (s) 22253a5a1b3Sopenharmony_ci return !pa_streq(s, "phone"); 22353a5a1b3Sopenharmony_ci 22453a5a1b3Sopenharmony_ci /* If media.role is not set use some heuristic (if enabled) */ 22553a5a1b3Sopenharmony_ci if (u->auto_switch != 2) 22653a5a1b3Sopenharmony_ci return true; 22753a5a1b3Sopenharmony_ci 22853a5a1b3Sopenharmony_ci /* Ignore if resample method is peaks (used by desktop volume programs) */ 22953a5a1b3Sopenharmony_ci if (pa_source_output_get_resample_method(source_output) == PA_RESAMPLER_PEAKS) 23053a5a1b3Sopenharmony_ci return true; 23153a5a1b3Sopenharmony_ci 23253a5a1b3Sopenharmony_ci /* Ignore if there is no client/application assigned (used by virtual stream) */ 23353a5a1b3Sopenharmony_ci if (!source_output->client) 23453a5a1b3Sopenharmony_ci return true; 23553a5a1b3Sopenharmony_ci 23653a5a1b3Sopenharmony_ci /* Ignore if recording from monitor of sink */ 23753a5a1b3Sopenharmony_ci if (source_output->direct_on_input) 23853a5a1b3Sopenharmony_ci return true; 23953a5a1b3Sopenharmony_ci 24053a5a1b3Sopenharmony_ci return false; 24153a5a1b3Sopenharmony_ci} 24253a5a1b3Sopenharmony_ci 24353a5a1b3Sopenharmony_cistatic unsigned source_output_count(pa_core *c, void *userdata) { 24453a5a1b3Sopenharmony_ci pa_source_output *source_output; 24553a5a1b3Sopenharmony_ci uint32_t idx; 24653a5a1b3Sopenharmony_ci unsigned count = 0; 24753a5a1b3Sopenharmony_ci 24853a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(source_output, c->source_outputs, idx) 24953a5a1b3Sopenharmony_ci if (!ignore_output(source_output, userdata)) 25053a5a1b3Sopenharmony_ci ++count; 25153a5a1b3Sopenharmony_ci 25253a5a1b3Sopenharmony_ci return count; 25353a5a1b3Sopenharmony_ci} 25453a5a1b3Sopenharmony_ci 25553a5a1b3Sopenharmony_ci/* Switch profile for all cards */ 25653a5a1b3Sopenharmony_cistatic void switch_profile_all(pa_idxset *cards, bool revert_to_a2dp, void *userdata) { 25753a5a1b3Sopenharmony_ci pa_card *card; 25853a5a1b3Sopenharmony_ci uint32_t idx; 25953a5a1b3Sopenharmony_ci 26053a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(card, cards, idx) 26153a5a1b3Sopenharmony_ci switch_profile(card, revert_to_a2dp, userdata); 26253a5a1b3Sopenharmony_ci} 26353a5a1b3Sopenharmony_ci 26453a5a1b3Sopenharmony_ci/* When a source output is created, switch profile a2dp to profile hsp */ 26553a5a1b3Sopenharmony_cistatic pa_hook_result_t source_output_put_hook_callback(pa_core *c, pa_source_output *source_output, void *userdata) { 26653a5a1b3Sopenharmony_ci pa_assert(c); 26753a5a1b3Sopenharmony_ci pa_assert(source_output); 26853a5a1b3Sopenharmony_ci 26953a5a1b3Sopenharmony_ci if (ignore_output(source_output, userdata)) 27053a5a1b3Sopenharmony_ci return PA_HOOK_OK; 27153a5a1b3Sopenharmony_ci 27253a5a1b3Sopenharmony_ci switch_profile_all(c->cards, false, userdata); 27353a5a1b3Sopenharmony_ci return PA_HOOK_OK; 27453a5a1b3Sopenharmony_ci} 27553a5a1b3Sopenharmony_ci 27653a5a1b3Sopenharmony_ci/* When all source outputs are unlinked, switch profile hsp back back to profile a2dp */ 27753a5a1b3Sopenharmony_cistatic pa_hook_result_t source_output_unlink_hook_callback(pa_core *c, pa_source_output *source_output, void *userdata) { 27853a5a1b3Sopenharmony_ci pa_assert(c); 27953a5a1b3Sopenharmony_ci pa_assert(source_output); 28053a5a1b3Sopenharmony_ci 28153a5a1b3Sopenharmony_ci if (ignore_output(source_output, userdata)) 28253a5a1b3Sopenharmony_ci return PA_HOOK_OK; 28353a5a1b3Sopenharmony_ci 28453a5a1b3Sopenharmony_ci /* If there are still some source outputs do nothing. */ 28553a5a1b3Sopenharmony_ci if (source_output_count(c, userdata) > 0) 28653a5a1b3Sopenharmony_ci return PA_HOOK_OK; 28753a5a1b3Sopenharmony_ci 28853a5a1b3Sopenharmony_ci switch_profile_all(c->cards, true, userdata); 28953a5a1b3Sopenharmony_ci return PA_HOOK_OK; 29053a5a1b3Sopenharmony_ci} 29153a5a1b3Sopenharmony_ci 29253a5a1b3Sopenharmony_cistatic pa_hook_result_t card_init_profile_hook_callback(pa_core *c, pa_card *card, void *userdata) { 29353a5a1b3Sopenharmony_ci struct userdata *u = userdata; 29453a5a1b3Sopenharmony_ci const char *s; 29553a5a1b3Sopenharmony_ci 29653a5a1b3Sopenharmony_ci pa_assert(c); 29753a5a1b3Sopenharmony_ci pa_assert(card); 29853a5a1b3Sopenharmony_ci 29953a5a1b3Sopenharmony_ci if (source_output_count(c, userdata) == 0) 30053a5a1b3Sopenharmony_ci return PA_HOOK_OK; 30153a5a1b3Sopenharmony_ci 30253a5a1b3Sopenharmony_ci /* Only consider bluetooth cards */ 30353a5a1b3Sopenharmony_ci s = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_BUS); 30453a5a1b3Sopenharmony_ci if (!s || !pa_streq(s, "bluetooth")) 30553a5a1b3Sopenharmony_ci return PA_HOOK_OK; 30653a5a1b3Sopenharmony_ci 30753a5a1b3Sopenharmony_ci /* Ignore card if has already set other initial profile than a2dp */ 30853a5a1b3Sopenharmony_ci if (card->active_profile && 30953a5a1b3Sopenharmony_ci !pa_streq(card->active_profile->name, "a2dp_sink")) 31053a5a1b3Sopenharmony_ci return PA_HOOK_OK; 31153a5a1b3Sopenharmony_ci 31253a5a1b3Sopenharmony_ci /* Set initial profile to hsp */ 31353a5a1b3Sopenharmony_ci card_set_profile(u, card, false); 31453a5a1b3Sopenharmony_ci 31553a5a1b3Sopenharmony_ci /* Flag this card for will_need_revert */ 31653a5a1b3Sopenharmony_ci pa_hashmap_put(u->will_need_revert_card_map, card, PA_INT_TO_PTR(1)); 31753a5a1b3Sopenharmony_ci return PA_HOOK_OK; 31853a5a1b3Sopenharmony_ci} 31953a5a1b3Sopenharmony_ci 32053a5a1b3Sopenharmony_cistatic pa_hook_result_t card_unlink_hook_callback(pa_core *c, pa_card *card, void *userdata) { 32153a5a1b3Sopenharmony_ci pa_assert(c); 32253a5a1b3Sopenharmony_ci pa_assert(card); 32353a5a1b3Sopenharmony_ci switch_profile(card, true, userdata); 32453a5a1b3Sopenharmony_ci return PA_HOOK_OK; 32553a5a1b3Sopenharmony_ci} 32653a5a1b3Sopenharmony_ci 32753a5a1b3Sopenharmony_cistatic pa_card_profile *find_best_profile(pa_card *card) { 32853a5a1b3Sopenharmony_ci void *state; 32953a5a1b3Sopenharmony_ci pa_card_profile *profile; 33053a5a1b3Sopenharmony_ci pa_card_profile *result = card->active_profile; 33153a5a1b3Sopenharmony_ci 33253a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(profile, card->profiles, state) { 33353a5a1b3Sopenharmony_ci if (profile->available == PA_AVAILABLE_NO) 33453a5a1b3Sopenharmony_ci continue; 33553a5a1b3Sopenharmony_ci 33653a5a1b3Sopenharmony_ci if (result == NULL || 33753a5a1b3Sopenharmony_ci (profile->available == PA_AVAILABLE_YES && result->available == PA_AVAILABLE_UNKNOWN) || 33853a5a1b3Sopenharmony_ci (profile->available == result->available && profile->priority > result->priority)) 33953a5a1b3Sopenharmony_ci result = profile; 34053a5a1b3Sopenharmony_ci } 34153a5a1b3Sopenharmony_ci 34253a5a1b3Sopenharmony_ci return result; 34353a5a1b3Sopenharmony_ci} 34453a5a1b3Sopenharmony_ci 34553a5a1b3Sopenharmony_cistatic pa_hook_result_t profile_available_hook_callback(pa_core *c, pa_card_profile *profile, void *userdata) { 34653a5a1b3Sopenharmony_ci pa_card *card; 34753a5a1b3Sopenharmony_ci const char *s; 34853a5a1b3Sopenharmony_ci bool is_active_profile; 34953a5a1b3Sopenharmony_ci pa_card_profile *selected_profile; 35053a5a1b3Sopenharmony_ci 35153a5a1b3Sopenharmony_ci pa_assert(c); 35253a5a1b3Sopenharmony_ci pa_assert(profile); 35353a5a1b3Sopenharmony_ci pa_assert_se((card = profile->card)); 35453a5a1b3Sopenharmony_ci 35553a5a1b3Sopenharmony_ci /* Only consider bluetooth cards */ 35653a5a1b3Sopenharmony_ci s = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_BUS); 35753a5a1b3Sopenharmony_ci if (!s || !pa_streq(s, "bluetooth")) 35853a5a1b3Sopenharmony_ci return PA_HOOK_OK; 35953a5a1b3Sopenharmony_ci 36053a5a1b3Sopenharmony_ci /* Do not automatically switch profiles for headsets, just in case */ 36153a5a1b3Sopenharmony_ci if (pa_streq(profile->name, "a2dp_sink") || 36253a5a1b3Sopenharmony_ci pa_streq(profile->name, "headset_head_unit") || 36353a5a1b3Sopenharmony_ci pa_streq(profile->name, "handsfree_head_unit")) 36453a5a1b3Sopenharmony_ci return PA_HOOK_OK; 36553a5a1b3Sopenharmony_ci 36653a5a1b3Sopenharmony_ci is_active_profile = card->active_profile == profile; 36753a5a1b3Sopenharmony_ci 36853a5a1b3Sopenharmony_ci if (profile->available == PA_AVAILABLE_YES) { 36953a5a1b3Sopenharmony_ci if (is_active_profile) 37053a5a1b3Sopenharmony_ci return PA_HOOK_OK; 37153a5a1b3Sopenharmony_ci 37253a5a1b3Sopenharmony_ci if (card->active_profile->available == PA_AVAILABLE_YES && card->active_profile->priority >= profile->priority) 37353a5a1b3Sopenharmony_ci return PA_HOOK_OK; 37453a5a1b3Sopenharmony_ci 37553a5a1b3Sopenharmony_ci selected_profile = profile; 37653a5a1b3Sopenharmony_ci } else { 37753a5a1b3Sopenharmony_ci if (!is_active_profile) 37853a5a1b3Sopenharmony_ci return PA_HOOK_OK; 37953a5a1b3Sopenharmony_ci 38053a5a1b3Sopenharmony_ci pa_assert_se((selected_profile = find_best_profile(card))); 38153a5a1b3Sopenharmony_ci 38253a5a1b3Sopenharmony_ci if (selected_profile == card->active_profile) 38353a5a1b3Sopenharmony_ci return PA_HOOK_OK; 38453a5a1b3Sopenharmony_ci } 38553a5a1b3Sopenharmony_ci 38653a5a1b3Sopenharmony_ci pa_log_debug("Setting card '%s' to profile '%s'", card->name, selected_profile->name); 38753a5a1b3Sopenharmony_ci 38853a5a1b3Sopenharmony_ci if (pa_card_set_profile(card, selected_profile, false) != 0) 38953a5a1b3Sopenharmony_ci pa_log_warn("Could not set profile '%s'", selected_profile->name); 39053a5a1b3Sopenharmony_ci 39153a5a1b3Sopenharmony_ci return PA_HOOK_OK; 39253a5a1b3Sopenharmony_ci} 39353a5a1b3Sopenharmony_ci 39453a5a1b3Sopenharmony_cistatic void handle_all_profiles(pa_core *core) { 39553a5a1b3Sopenharmony_ci pa_card *card; 39653a5a1b3Sopenharmony_ci uint32_t state; 39753a5a1b3Sopenharmony_ci 39853a5a1b3Sopenharmony_ci PA_IDXSET_FOREACH(card, core->cards, state) { 39953a5a1b3Sopenharmony_ci pa_card_profile *profile; 40053a5a1b3Sopenharmony_ci void *state2; 40153a5a1b3Sopenharmony_ci 40253a5a1b3Sopenharmony_ci PA_HASHMAP_FOREACH(profile, card->profiles, state2) 40353a5a1b3Sopenharmony_ci profile_available_hook_callback(core, profile, NULL); 40453a5a1b3Sopenharmony_ci } 40553a5a1b3Sopenharmony_ci} 40653a5a1b3Sopenharmony_ci 40753a5a1b3Sopenharmony_ciint pa__init(pa_module *m) { 40853a5a1b3Sopenharmony_ci pa_modargs *ma; 40953a5a1b3Sopenharmony_ci struct userdata *u; 41053a5a1b3Sopenharmony_ci 41153a5a1b3Sopenharmony_ci pa_assert(m); 41253a5a1b3Sopenharmony_ci 41353a5a1b3Sopenharmony_ci if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { 41453a5a1b3Sopenharmony_ci pa_log_error("Failed to parse module arguments"); 41553a5a1b3Sopenharmony_ci goto fail; 41653a5a1b3Sopenharmony_ci } 41753a5a1b3Sopenharmony_ci 41853a5a1b3Sopenharmony_ci m->userdata = u = pa_xnew0(struct userdata, 1); 41953a5a1b3Sopenharmony_ci 42053a5a1b3Sopenharmony_ci u->auto_switch = 1; 42153a5a1b3Sopenharmony_ci 42253a5a1b3Sopenharmony_ci if (pa_modargs_get_value(ma, "auto_switch", NULL)) { 42353a5a1b3Sopenharmony_ci bool auto_switch_bool; 42453a5a1b3Sopenharmony_ci 42553a5a1b3Sopenharmony_ci /* auto_switch originally took a boolean value, let's keep 42653a5a1b3Sopenharmony_ci * compatibility with configuration files that still pass a boolean. */ 42753a5a1b3Sopenharmony_ci if (pa_modargs_get_value_boolean(ma, "auto_switch", &auto_switch_bool) >= 0) { 42853a5a1b3Sopenharmony_ci if (auto_switch_bool) 42953a5a1b3Sopenharmony_ci u->auto_switch = 1; 43053a5a1b3Sopenharmony_ci else 43153a5a1b3Sopenharmony_ci u->auto_switch = 0; 43253a5a1b3Sopenharmony_ci 43353a5a1b3Sopenharmony_ci } else if (pa_modargs_get_value_u32(ma, "auto_switch", &u->auto_switch) < 0) { 43453a5a1b3Sopenharmony_ci pa_log("Failed to parse auto_switch argument."); 43553a5a1b3Sopenharmony_ci goto fail; 43653a5a1b3Sopenharmony_ci } 43753a5a1b3Sopenharmony_ci } 43853a5a1b3Sopenharmony_ci 43953a5a1b3Sopenharmony_ci u->enable_a2dp_source = true; 44053a5a1b3Sopenharmony_ci if (pa_modargs_get_value_boolean(ma, "a2dp_source", &u->enable_a2dp_source) < 0) { 44153a5a1b3Sopenharmony_ci pa_log("Failed to parse a2dp_source argument."); 44253a5a1b3Sopenharmony_ci goto fail; 44353a5a1b3Sopenharmony_ci } 44453a5a1b3Sopenharmony_ci 44553a5a1b3Sopenharmony_ci u->enable_ag = true; 44653a5a1b3Sopenharmony_ci if (pa_modargs_get_value_boolean(ma, "ag", &u->enable_ag) < 0) { 44753a5a1b3Sopenharmony_ci pa_log("Failed to parse ag argument."); 44853a5a1b3Sopenharmony_ci goto fail; 44953a5a1b3Sopenharmony_ci } 45053a5a1b3Sopenharmony_ci 45153a5a1b3Sopenharmony_ci u->will_need_revert_card_map = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); 45253a5a1b3Sopenharmony_ci 45353a5a1b3Sopenharmony_ci u->source_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_NORMAL, 45453a5a1b3Sopenharmony_ci (pa_hook_cb_t) source_put_hook_callback, u); 45553a5a1b3Sopenharmony_ci 45653a5a1b3Sopenharmony_ci u->sink_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_NORMAL, 45753a5a1b3Sopenharmony_ci (pa_hook_cb_t) sink_put_hook_callback, u); 45853a5a1b3Sopenharmony_ci 45953a5a1b3Sopenharmony_ci if (u->auto_switch) { 46053a5a1b3Sopenharmony_ci u->source_output_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_NORMAL, 46153a5a1b3Sopenharmony_ci (pa_hook_cb_t) source_output_put_hook_callback, u); 46253a5a1b3Sopenharmony_ci 46353a5a1b3Sopenharmony_ci u->source_output_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], PA_HOOK_NORMAL, 46453a5a1b3Sopenharmony_ci (pa_hook_cb_t) source_output_unlink_hook_callback, u); 46553a5a1b3Sopenharmony_ci 46653a5a1b3Sopenharmony_ci u->card_init_profile_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_CHOOSE_INITIAL_PROFILE], PA_HOOK_NORMAL, 46753a5a1b3Sopenharmony_ci (pa_hook_cb_t) card_init_profile_hook_callback, u); 46853a5a1b3Sopenharmony_ci 46953a5a1b3Sopenharmony_ci u->card_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_UNLINK], PA_HOOK_NORMAL, 47053a5a1b3Sopenharmony_ci (pa_hook_cb_t) card_unlink_hook_callback, u); 47153a5a1b3Sopenharmony_ci } 47253a5a1b3Sopenharmony_ci 47353a5a1b3Sopenharmony_ci u->profile_available_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_CHANGED], 47453a5a1b3Sopenharmony_ci PA_HOOK_NORMAL, (pa_hook_cb_t) profile_available_hook_callback, u); 47553a5a1b3Sopenharmony_ci 47653a5a1b3Sopenharmony_ci handle_all_profiles(m->core); 47753a5a1b3Sopenharmony_ci 47853a5a1b3Sopenharmony_ci pa_modargs_free(ma); 47953a5a1b3Sopenharmony_ci return 0; 48053a5a1b3Sopenharmony_ci 48153a5a1b3Sopenharmony_cifail: 48253a5a1b3Sopenharmony_ci if (ma) 48353a5a1b3Sopenharmony_ci pa_modargs_free(ma); 48453a5a1b3Sopenharmony_ci return -1; 48553a5a1b3Sopenharmony_ci} 48653a5a1b3Sopenharmony_ci 48753a5a1b3Sopenharmony_civoid pa__done(pa_module *m) { 48853a5a1b3Sopenharmony_ci struct userdata *u; 48953a5a1b3Sopenharmony_ci 49053a5a1b3Sopenharmony_ci pa_assert(m); 49153a5a1b3Sopenharmony_ci 49253a5a1b3Sopenharmony_ci if (!(u = m->userdata)) 49353a5a1b3Sopenharmony_ci return; 49453a5a1b3Sopenharmony_ci 49553a5a1b3Sopenharmony_ci if (u->source_put_slot) 49653a5a1b3Sopenharmony_ci pa_hook_slot_free(u->source_put_slot); 49753a5a1b3Sopenharmony_ci 49853a5a1b3Sopenharmony_ci if (u->sink_put_slot) 49953a5a1b3Sopenharmony_ci pa_hook_slot_free(u->sink_put_slot); 50053a5a1b3Sopenharmony_ci 50153a5a1b3Sopenharmony_ci if (u->source_output_put_slot) 50253a5a1b3Sopenharmony_ci pa_hook_slot_free(u->source_output_put_slot); 50353a5a1b3Sopenharmony_ci 50453a5a1b3Sopenharmony_ci if (u->source_output_unlink_slot) 50553a5a1b3Sopenharmony_ci pa_hook_slot_free(u->source_output_unlink_slot); 50653a5a1b3Sopenharmony_ci 50753a5a1b3Sopenharmony_ci if (u->card_init_profile_slot) 50853a5a1b3Sopenharmony_ci pa_hook_slot_free(u->card_init_profile_slot); 50953a5a1b3Sopenharmony_ci 51053a5a1b3Sopenharmony_ci if (u->card_unlink_slot) 51153a5a1b3Sopenharmony_ci pa_hook_slot_free(u->card_unlink_slot); 51253a5a1b3Sopenharmony_ci 51353a5a1b3Sopenharmony_ci if (u->profile_available_changed_slot) 51453a5a1b3Sopenharmony_ci pa_hook_slot_free(u->profile_available_changed_slot); 51553a5a1b3Sopenharmony_ci 51653a5a1b3Sopenharmony_ci pa_hashmap_free(u->will_need_revert_card_map); 51753a5a1b3Sopenharmony_ci 51853a5a1b3Sopenharmony_ci pa_xfree(u); 51953a5a1b3Sopenharmony_ci} 520