From 385161b12a8bbeb58909f815eae8073e9799cdb3 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Thu, 18 Dec 2025 16:35:35 -0800 Subject: [PATCH] pulse-server: Add a message to enable/disable mono mixdown WirePlumber recently added a mechanism to force mono mixdown on audio outputs, which is a useful feature for accessibility. Let's also expose that setting via libpulse for existing audio settings UIs to be able to use. --- src/modules/module-protocol-pulse/client.h | 5 ++ src/modules/module-protocol-pulse/defs.h | 1 + .../module-protocol-pulse/message-handler.c | 58 ++++++++++++++++--- .../module-protocol-pulse/pulse-server.c | 16 +++++ 4 files changed, 73 insertions(+), 7 deletions(-) diff --git a/src/modules/module-protocol-pulse/client.h b/src/modules/module-protocol-pulse/client.h index 18c3efb76..2c413e51c 100644 --- a/src/modules/module-protocol-pulse/client.h +++ b/src/modules/module-protocol-pulse/client.h @@ -60,6 +60,11 @@ struct client { struct pw_manager_object *metadata_routes; struct pw_properties *routes; + struct pw_manager_object *metadata_schema_sm_settings; + bool have_force_mono_audio; + struct pw_manager_object *metadata_sm_settings; + bool force_mono_audio; + uint32_t connect_tag; uint32_t in_index; diff --git a/src/modules/module-protocol-pulse/defs.h b/src/modules/module-protocol-pulse/defs.h index 378644bda..51e2453ff 100644 --- a/src/modules/module-protocol-pulse/defs.h +++ b/src/modules/module-protocol-pulse/defs.h @@ -323,5 +323,6 @@ static inline uint32_t port_type_value(const char *port_type) #define METADATA_CONFIG_DEFAULT_SOURCE "default.configured.audio.source" #define METADATA_TARGET_NODE "target.node" #define METADATA_TARGET_OBJECT "target.object" +#define METADATA_FEATURES_AUDIO_MONO "node.features.audio.mono" #endif /* PULSE_SERVER_DEFS_H */ diff --git a/src/modules/module-protocol-pulse/message-handler.c b/src/modules/module-protocol-pulse/message-handler.c index fdbe3ac5d..48c7c7be3 100644 --- a/src/modules/module-protocol-pulse/message-handler.c +++ b/src/modules/module-protocol-pulse/message-handler.c @@ -19,6 +19,7 @@ #include "client.h" #include "collect.h" +#include "defs.h" #include "log.h" #include "manager.h" #include "module.h" @@ -89,6 +90,46 @@ static int bluez_card_object_message_handler(struct client *client, struct pw_ma return 0; } + +static int core_object_force_mono_output(struct client *client, const char *params, FILE *response) +{ + if (!client->have_force_mono_audio) { + /* Not supported, return a null value to indicate that */ + fprintf(response, "null"); + return 0; + } + + if (!params || params[0] == '\0') { + /* No parameter => query the current value */ + fprintf(response, "%s", client->force_mono_audio ? "true" : "false"); + return 0; + } else { + /* The caller is trying to set a value or clear with a null */ + int ret; + + if (spa_streq(params, "true")) { + ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE, + METADATA_FEATURES_AUDIO_MONO, "Spa:String:JSON", "true"); + } else if (spa_streq(params, "false")) { + ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE, + METADATA_FEATURES_AUDIO_MONO, "Spa:String:JSON", "false"); + } else if (spa_streq(params, "null")) { + ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE, + METADATA_FEATURES_AUDIO_MONO, NULL, NULL); + } else { + fprintf(response, "Value must be true, false, or clear"); + return -EINVAL; + } + + if (ret < 0) + fprintf(response, "Could not set metadata: %s", spa_strerror(ret)); + else + fprintf(response, "%s", params); + + return ret; + } +} + static int core_object_message_handler(struct client *client, struct pw_manager_object *o, const char *message, const char *params, FILE *response) { pw_log_debug(": core %p object message:'%s' params:'%s'", o, message, params); @@ -97,13 +138,14 @@ static int core_object_message_handler(struct client *client, struct pw_manager_ fprintf(response, "/core []\n" "available commands:\n" - " help this help\n" - " list-handlers show available object handlers\n" - " pipewire-pulse:malloc-info show malloc_info\n" - " pipewire-pulse:malloc-trim run malloc_trim\n" - " pipewire-pulse:log-level update log level with \n" - " pipewire-pulse:list-modules list all module names\n" - " pipewire-pulse:describe-module describe module info for " + " help this help\n" + " list-handlers show available object handlers\n" + " pipewire-pulse:malloc-info show malloc_info\n" + " pipewire-pulse:malloc-trim run malloc_trim\n" + " pipewire-pulse:log-level update log level with \n" + " pipewire-pulse:list-modules list all module names\n" + " pipewire-pulse:describe-module describe module info for \n" + " pipewire-pulse:force-mono-output force mono mixdown on all hardware outputs" ); } else if (spa_streq(message, "list-handlers")) { bool first = true; @@ -164,6 +206,8 @@ static int core_object_message_handler(struct client *client, struct pw_manager_ } else { fprintf(response, "Failed to open module.\n"); } + } else if (spa_streq(message, "pipewire-pulse:force-mono-output")) { + return core_object_force_mono_output(client, params, response); } else { return -ENOSYS; } diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index b637dbf0c..c22499718 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -404,6 +404,14 @@ static void handle_metadata(struct client *client, struct pw_manager_object *old if (client->metadata_routes == old) client->metadata_routes = new; } + else if (spa_streq(name, "sm-settings")) { + if (client->metadata_sm_settings == old) + client->metadata_sm_settings = new; + } + else if (spa_streq(name, "schema-sm-settings")) { + if (client->metadata_schema_sm_settings == old) + client->metadata_schema_sm_settings = new; + } } static uint32_t frac_to_bytes_round_up(struct spa_fraction val, const struct sample_spec *ss) @@ -964,6 +972,14 @@ static void manager_metadata(void *data, struct pw_manager_object *o, } if (subject == PW_ID_CORE && o == client->metadata_routes) client_update_routes(client, key, value); + if (subject == PW_ID_CORE && o == client->metadata_schema_sm_settings) { + if (spa_streq(key, METADATA_FEATURES_AUDIO_MONO)) + client->have_force_mono_audio = true; + } + if (subject == PW_ID_CORE && o == client->metadata_sm_settings) { + if (spa_streq(key, METADATA_FEATURES_AUDIO_MONO)) + client->force_mono_audio = spa_streq(value, "true"); + } }