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