card: handle sticky profile flag
authorIgor V. Kovalenko <igor.v.kovalenko@gmail.com>
Tue, 15 Dec 2020 21:35:05 +0000 (00:35 +0300)
committerPulseAudio Marge Bot <pulseaudio-maintainers@lists.freedesktop.org>
Thu, 10 Jun 2021 10:30:58 +0000 (10:30 +0000)
New card database entry version 5 for card profile is sticky flag.
New messaging API handlers set-profile-sticky and get-profile-sticky.

When card profile is sticky, always restore it even if it is unavailable,
and prevent switching from it when ports become unavailable.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/568>

doc/messaging_api.txt
src/modules/module-card-restore.c
src/modules/module-switch-on-port-available.c
src/pulsecore/card.c
src/pulsecore/card.h

index d4833a4..ad0774f 100644 (file)
@@ -35,3 +35,15 @@ Object path: /card/bluez_card.XX_XX_XX_XX_XX_XX/bluez
 Message: switch-codec
 Parameters: "codec name"
 Return value: none
+
+Description: Set if card profile selection should be sticky instead of being automated
+Object path: /card/<card_name>
+Message: set-profile-sticky
+Parameters: JSON "true" or "false"
+Return value: none
+
+Description: Get if card profile selection should be sticky instead of being automated
+Object path: /card/<card_name>
+Message: get-profile-sticky
+Parameters: None
+Return value: JSON "true" or "false"
index b35cf3e..08f8333 100644 (file)
@@ -67,7 +67,7 @@ struct userdata {
     bool restore_bluetooth_profile;
 };
 
-#define ENTRY_VERSION 4
+#define ENTRY_VERSION 5
 
 struct port_info {
     char *name;
@@ -80,6 +80,7 @@ struct entry {
     pa_hashmap *ports; /* Port name -> struct port_info */
     char *preferred_input_port;
     char *preferred_output_port;
+    bool profile_is_sticky; /* since version 5; must be restored together with profile name */
 };
 
 static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) {
@@ -153,7 +154,8 @@ static struct entry *entry_from_card(pa_card *card) {
     pa_assert(card);
 
     entry = entry_new();
-    if (card->save_profile)
+    entry->profile_is_sticky = card->profile_is_sticky;
+    if (card->save_profile || entry->profile_is_sticky)
         entry->profile = pa_xstrdup(card->active_profile->name);
 
     PA_HASHMAP_FOREACH(port, card->ports, state) {
@@ -189,6 +191,9 @@ static bool entrys_equal(struct entry *a, struct entry *b) {
     if (!pa_safe_streq(a->preferred_output_port, b->preferred_output_port))
         return false;
 
+    if (a->profile_is_sticky != b->profile_is_sticky)
+        return false;
+
     return true;
 }
 
@@ -217,6 +222,8 @@ static bool entry_write(struct userdata *u, const char *name, const struct entry
     pa_tagstruct_puts(t, e->preferred_input_port);
     pa_tagstruct_puts(t, e->preferred_output_port);
 
+    pa_tagstruct_put_boolean(t, e->profile_is_sticky);
+
     key.data = (char *) name;
     key.size = strlen(name);
 
@@ -342,6 +349,14 @@ static struct entry* entry_read(struct userdata *u, const char *name) {
         e->preferred_output_port = pa_xstrdup(preferred_output_port);
     }
 
+    if (version >= 5) {
+        bool profile_is_sticky;
+        if (pa_tagstruct_get_boolean(t, &profile_is_sticky) < 0)
+            goto fail;
+
+        e->profile_is_sticky = profile_is_sticky;
+    }
+
     if (!pa_tagstruct_eof(t))
         goto fail;
 
@@ -437,11 +452,12 @@ static pa_hook_result_t card_profile_changed_callback(pa_core *c, pa_card *card,
 
     pa_assert(card);
 
-    if (!card->save_profile)
+    if (!card->save_profile && !card->profile_is_sticky)
         return PA_HOOK_OK;
 
     if ((entry = entry_read(u, card->name))) {
         pa_xfree(entry->profile);
+        entry->profile_is_sticky = card->profile_is_sticky;
         entry->profile = pa_xstrdup(card->active_profile->name);
         pa_log_info("Storing card profile for card %s.", card->name);
     } else {
@@ -565,12 +581,18 @@ static pa_hook_result_t card_choose_initial_profile_callback(pa_core *core, pa_c
             goto finish;
     }
 
+    card->profile_is_sticky = e->profile_is_sticky;
+    pa_log_info("Profile '%s' was previously %s for card %s.",
+            e->profile,
+            card->profile_is_sticky ? "sticky" : "automatically selected",
+            card->name);
+
     if (e->profile[0]) {
         pa_card_profile *profile;
 
         profile = pa_hashmap_get(card->profiles, e->profile);
         if (profile) {
-            if (profile->available != PA_AVAILABLE_NO) {
+            if (profile->available != PA_AVAILABLE_NO || card->profile_is_sticky) {
                 pa_log_info("Restoring profile '%s' for card %s.", profile->name, card->name);
                 pa_card_set_profile(card, profile, true);
             } else
index e60d785..de9c870 100644 (file)
@@ -137,6 +137,11 @@ static int try_to_switch_profile(pa_device_port *port) {
     void *state;
     unsigned best_prio = 0;
 
+    if (port->card->profile_is_sticky) {
+        pa_log_info("Keeping sticky card profile '%s'", port->card->active_profile->name);
+        return -1;
+    }
+
     pa_log_debug("Finding best profile for port %s, preferred = %s",
                  port->name, pa_strnull(port->preferred_profile));
 
@@ -391,6 +396,11 @@ static pa_hook_result_t card_profile_available_hook_callback(pa_core *c, pa_card
     if (!pa_streq(profile->name, card->active_profile->name))
         return PA_HOOK_OK;
 
+    if (card->profile_is_sticky) {
+        pa_log_info("Keeping sticky 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);
 
index 6989596..23b347b 100644 (file)
 #include <pulse/xmalloc.h>
 #include <pulse/util.h>
 
+#include <pulsecore/json.h>
 #include <pulsecore/log.h>
 #include <pulsecore/macro.h>
 #include <pulsecore/core-util.h>
 #include <pulsecore/namereg.h>
+#include <pulsecore/message-handler.h>
 #include <pulsecore/device-port.h>
 
 #include "card.h"
 
+static int card_message_handler(const char *object_path, const char *message, const pa_json_object *parameters, char **response, void *userdata);
+
+static char* make_message_handler_path(const char *name) {
+    return pa_sprintf_malloc("/card/%s", name);
+}
+
 const char *pa_available_to_string(pa_available_t available) {
     switch (available) {
         case PA_AVAILABLE_UNKNOWN:
@@ -136,7 +144,8 @@ void pa_card_new_data_done(pa_card_new_data *data) {
 
 pa_card *pa_card_new(pa_core *core, pa_card_new_data *data) {
     pa_card *c;
-    const char *name;
+    const char *name, *tmp;
+    char *object_path, *description;
     void *state;
     pa_card_profile *profile;
     pa_device_port *port;
@@ -186,6 +195,14 @@ pa_card *pa_card_new(pa_core *core, pa_card_new_data *data) {
     pa_device_init_icon(c->proplist, true);
     pa_device_init_intended_roles(c->proplist);
 
+    object_path = make_message_handler_path(c->name);
+    if (!(tmp = pa_proplist_gets(c->proplist, PA_PROP_DEVICE_DESCRIPTION)))
+        tmp = c->name;
+    description = pa_sprintf_malloc("Message handler for card \"%s\"", tmp);
+    pa_message_handler_register(c->core, object_path, description, card_message_handler, (void *) c);
+    pa_xfree(object_path);
+    pa_xfree(description);
+
     return c;
 }
 
@@ -220,6 +237,7 @@ void pa_card_choose_initial_profile(pa_card *card) {
 
     card->active_profile = best;
     card->save_profile = false;
+    card->profile_is_sticky = false;
     pa_log_info("%s: active_profile: %s", card->name, card->active_profile->name);
 
     /* Let policy modules override the default. */
@@ -239,6 +257,7 @@ void pa_card_put(pa_card *card) {
 
 void pa_card_free(pa_card *c) {
     pa_core *core;
+    char *object_path;
 
     pa_assert(c);
     pa_assert(c->core);
@@ -253,6 +272,10 @@ void pa_card_free(pa_card *c) {
         pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_REMOVE, c->index);
     }
 
+    object_path = make_message_handler_path(c->name);
+    pa_message_handler_unregister(core, object_path);
+    pa_xfree(object_path);
+
     pa_namereg_unregister(core, c->name);
 
     pa_assert(pa_idxset_isempty(c->sinks));
@@ -305,6 +328,25 @@ static void update_port_preferred_profile(pa_card *c) {
             pa_device_port_set_preferred_profile(source->active_port, profile_name_for_dir(c->active_profile, PA_DIRECTION_INPUT));
 }
 
+static int card_set_profile_is_sticky(pa_card *c, bool profile_is_sticky) {
+    pa_assert(c);
+
+    if (c->profile_is_sticky == profile_is_sticky)
+        return 0;
+
+    pa_log_debug("%s: profile_is_sticky: %s -> %s",
+            c->name, pa_yes_no(c->profile_is_sticky), pa_yes_no(profile_is_sticky));
+
+    c->profile_is_sticky = profile_is_sticky;
+
+    if (c->linked) {
+        pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CARD_PROFILE_CHANGED], c);
+        pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, c->index);
+    }
+
+    return 0;
+}
+
 int pa_card_set_profile(pa_card *c, pa_card_profile *profile, bool save) {
     int r;
 
@@ -423,3 +465,44 @@ int pa_card_suspend(pa_card *c, bool suspend, pa_suspend_cause_t cause) {
 
     return ret;
 }
+
+static int card_message_handler(const char *object_path, const char *message, const pa_json_object *parameters, char **response, void *userdata) {
+    pa_card *c;
+    char *message_handler_path;
+
+    pa_assert(c = (pa_card *) userdata);
+    pa_assert(message);
+    pa_assert(response);
+
+    message_handler_path = make_message_handler_path(c->name);
+
+    if (!object_path || !pa_streq(object_path, message_handler_path)) {
+        pa_xfree(message_handler_path);
+        return -PA_ERR_NOENTITY;
+    }
+
+    pa_xfree(message_handler_path);
+
+    if (pa_streq(message, "get-profile-sticky")) {
+        pa_json_encoder *encoder;
+        encoder = pa_json_encoder_new();
+
+        pa_json_encoder_add_element_bool(encoder, c->profile_is_sticky);
+
+        *response = pa_json_encoder_to_string_free(encoder);
+
+        return PA_OK;
+    } else if (pa_streq(message, "set-profile-sticky")) {
+
+        if (!parameters || pa_json_object_get_type(parameters) != PA_JSON_TYPE_BOOL) {
+            pa_log_info("Card operation set-profile-sticky requires argument: \"true\" or \"false\"");
+            return -PA_ERR_INVALID;
+        }
+
+        card_set_profile_is_sticky(c, pa_json_object_get_bool(parameters));
+
+        return PA_OK;
+    }
+
+    return -PA_ERR_NOTIMPLEMENTED;
+}
index a11e33d..20f66aa 100644 (file)
@@ -83,6 +83,7 @@ struct pa_card {
     pa_device_port *preferred_output_port;
 
     bool save_profile:1;
+    bool profile_is_sticky:1;
 
     pa_suspend_cause_t suspend_cause;