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