From 30cf3cd6ecd75efb027fded9152134e6deae38f6 Mon Sep 17 00:00:00 2001 From: "Igor V. Kovalenko" Date: Mon, 23 Nov 2020 23:35:37 +0300 Subject: [PATCH] module-switch-on-port-available: Also blacklist HDMI devices by default HDMI jacks are also plugged and unplugged when display power is operated. In this case automatic selection of card profile matching plugged in port is most likely undesireable as it may route audio to display just turned on. Add blacklists for port events and card profiles, by default add HDMI to both. This change prevents switching active HDMI card profile when display power is operated. --- src/modules/module-switch-on-port-available.c | 99 +++++++++++++++++-- 1 file changed, 93 insertions(+), 6 deletions(-) diff --git a/src/modules/module-switch-on-port-available.c b/src/modules/module-switch-on-port-available.c index de9c87092..546b39cb1 100644 --- a/src/modules/module-switch-on-port-available.c +++ b/src/modules/module-switch-on-port-available.c @@ -26,11 +26,29 @@ #include #include #include +#include + +/* Ignore HDMI devices by default. HDMI monitors don't necessarily have audio + * output on them, and even if they do, waking up from sleep or changing + * monitor resolution may appear as a plugin event, which causes trouble if the + * user doesn't want to use the monitor for audio. */ +#define DEFAULT_BLACKLIST_PORTS "hdmi" +#define DEFAULT_BLACKLIST_PROFILES "hdmi" PA_MODULE_AUTHOR("David Henningsson"); PA_MODULE_DESCRIPTION("Switches ports and profiles when devices are plugged/unplugged"); PA_MODULE_LOAD_ONCE(true); PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_USAGE( + "blacklist_ports= " + "blacklist_profiles= " +); + +static const char* const valid_modargs[] = { + "blacklist_ports", + "blacklist_profiles", + NULL, +}; struct card_info { struct userdata *userdata; @@ -44,6 +62,8 @@ struct card_info { struct userdata { pa_hashmap *card_infos; /* pa_card -> struct card_info */ + char *blacklist_ports; + char *blacklist_profiles; }; static void card_info_new(struct userdata *u, pa_card *card) { @@ -296,9 +316,13 @@ static void switch_from_port(pa_device_port *port, struct port_pointers pp) { } -static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port *port, void* userdata) { +static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port *port, struct userdata *u) { struct port_pointers pp = find_port_pointers(port); + pa_assert(c); + pa_assert(port); + pa_assert(u); + if (!port->card) { pa_log_warn("Port %s does not have a card", port->name); return PA_HOOK_OK; @@ -312,6 +336,12 @@ static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port if (pa_safe_streq(pa_proplist_gets(port->card->proplist, PA_PROP_DEVICE_BUS), "bluetooth")) return PA_HOOK_OK; + /* Ignore ports matching the blacklist regex */ + if (u->blacklist_ports && (pa_match(u->blacklist_ports, port->name) > 0)) { + pa_log_info("Ignoring blacklisted port %s", port->name); + return PA_HOOK_OK; + } + switch (port->available) { case PA_AVAILABLE_UNKNOWN: /* If a port availability became unknown, let's see if it's part of @@ -387,6 +417,7 @@ static pa_card_profile *find_best_profile(pa_card *card) { static pa_hook_result_t card_profile_available_hook_callback(pa_core *c, pa_card_profile *profile, struct userdata *u) { pa_card *card; + pa_assert(u); pa_assert(profile); pa_assert_se(card = profile->card); @@ -401,6 +432,12 @@ static pa_hook_result_t card_profile_available_hook_callback(pa_core *c, pa_card return PA_HOOK_OK; } + /* Ignore p matching the blacklist regex */ + if (u->blacklist_profiles && (pa_match(u->blacklist_profiles, profile->name) > 0)) { + pa_log_info("Ignoring blacklisted card profile %s", profile->name); + return PA_HOOK_OK; + } + pa_log_debug("Active profile %s on card %s became unavailable, switching to another profile", profile->name, card->name); pa_card_set_profile(card, find_best_profile(card), false); @@ -408,7 +445,7 @@ static pa_hook_result_t card_profile_available_hook_callback(pa_core *c, pa_card } -static void handle_all_unavailable(pa_core *core) { +static void handle_all_unavailable(pa_core *core, struct userdata *u) { pa_card *card; uint32_t state; @@ -418,7 +455,7 @@ static void handle_all_unavailable(pa_core *core) { PA_HASHMAP_FOREACH(port, card->ports, state2) { if (port->available == PA_AVAILABLE_NO) - port_available_hook_callback(core, port, NULL); + port_available_hook_callback(core, port, u); } } } @@ -591,15 +628,49 @@ static pa_hook_result_t sink_port_changed_callback(pa_core *core, pa_sink *sink, } int pa__init(pa_module*m) { + pa_modargs *ma; struct userdata *u; pa_card *card; uint32_t idx; pa_assert(m); + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + return -1; + } + u = m->userdata = pa_xnew0(struct userdata, 1); u->card_infos = pa_hashmap_new(NULL, NULL); + u->blacklist_ports = pa_xstrdup(pa_modargs_get_value(ma, "blacklist_ports", DEFAULT_BLACKLIST_PORTS)); + + /* An empty string disables all blacklisting. */ + if (!*u->blacklist_ports) { + pa_xfree(u->blacklist_ports); + u->blacklist_ports = NULL; + } + + if (u->blacklist_ports != NULL && !pa_is_regex_valid(u->blacklist_ports)) { + pa_log_error("A port blacklist pattern was provided but is not a valid regex"); + pa_xfree(u->blacklist_ports); + goto fail; + } + + u->blacklist_profiles = pa_xstrdup(pa_modargs_get_value(ma, "blacklist_profiles", DEFAULT_BLACKLIST_PROFILES)); + + /* An empty string disables all blacklisting. */ + if (!*u->blacklist_profiles) { + pa_xfree(u->blacklist_profiles); + u->blacklist_profiles = NULL; + } + + if (u->blacklist_profiles != NULL && !pa_is_regex_valid(u->blacklist_profiles)) { + pa_log_error("A card profile blacklist pattern was provided but is not a valid regex"); + pa_xfree(u->blacklist_profiles); + goto fail; + } + PA_IDXSET_FOREACH(card, m->core->cards, idx) card_info_new(u, card); @@ -609,9 +680,9 @@ int pa__init(pa_module*m) { pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_NEW], PA_HOOK_NORMAL, (pa_hook_cb_t) source_new_hook_callback, NULL); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_PORT_AVAILABLE_CHANGED], - PA_HOOK_LATE, (pa_hook_cb_t) port_available_hook_callback, NULL); + PA_HOOK_LATE, (pa_hook_cb_t) port_available_hook_callback, u); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_CHANGED], - PA_HOOK_LATE, (pa_hook_cb_t) card_profile_available_hook_callback, NULL); + PA_HOOK_LATE, (pa_hook_cb_t) card_profile_available_hook_callback, u); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PUT], PA_HOOK_NORMAL, (pa_hook_cb_t) card_put_hook_callback, u); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_UNLINK], @@ -623,9 +694,19 @@ int pa__init(pa_module*m) { pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_PORT_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) sink_port_changed_callback, NULL); - handle_all_unavailable(m->core); + handle_all_unavailable(m->core, u); + + pa_modargs_free(ma); return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; } void pa__done(pa_module *module) { @@ -642,5 +723,11 @@ void pa__done(pa_module *module) { pa_hashmap_free(u->card_infos); + if (u->blacklist_ports) + pa_xfree(u->blacklist_ports); + + if (u->blacklist_profiles) + pa_xfree(u->blacklist_profiles); + pa_xfree(u); }