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