This patch adds two new commands set_module_parameter and get_module_parameter to the native API. These commands allow for dynamic reconfiguration of a module during run time. A parameter string can be passed by set_module_parameter to a callback function set_parameter_callback() within the module. Likewise get_module_parameter passes a parameter name and get_parameter_callback() within the module is expected to return a string containing the value of the named parameter. The subscription API can be used to notify clients if a parameter has changed. --- src/map-file | 4 ++ src/pulse/introspect.c | 112 +++++++++++++++++++++++++++++++++++++++ src/pulse/introspect.h | 15 ++++++ src/pulsecore/module.c | 2 + src/pulsecore/module.h | 6 +++ src/pulsecore/native-common.h | 3 ++ src/pulsecore/pdispatch.c | 3 ++ src/pulsecore/protocol-native.c | 114 ++++++++++++++++++++++++++++++++++++++++ src/utils/pactl.c | 55 +++++++++++++++++++ 9 files changed, 314 insertions(+) diff --git a/src/map-file b/src/map-file index 93a62b86..026b6df3 100644 --- a/src/map-file +++ b/src/map-file @@ -118,6 +118,10 @@ pa_context_suspend_sink_by_name; pa_context_suspend_source_by_index; pa_context_suspend_source_by_name; pa_context_unload_module; +pa_context_set_module_parameter_by_name; +pa_context_set_module_parameter_by_index; +pa_context_get_module_parameter_by_name; +pa_context_get_module_parameter_by_index; pa_context_unref; pa_cvolume_avg; pa_cvolume_avg_mask; diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c index 510d784a..3943c3fe 100644 --- a/src/pulse/introspect.c +++ b/src/pulse/introspect.c @@ -2184,3 +2184,115 @@ pa_operation* pa_context_suspend_source_by_index(pa_context *c, uint32_t idx, in return o; } + +static pa_operation* set_module_parameter_by_index_or_name(pa_context *c, uint32_t idx, const char *module_name, const char *parameter, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_MODULE_PARAMETER, &tag); + + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, module_name); + pa_tagstruct_puts(t, parameter); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_module_parameter_by_name(pa_context *c, const char *module_name, const char *parameter, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + + o = set_module_parameter_by_index_or_name(c, PA_INVALID_INDEX, module_name, parameter, cb, userdata); + return o; +} + +pa_operation* pa_context_set_module_parameter_by_index(pa_context *c, uint32_t idx, const char *parameter, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + + o = set_module_parameter_by_index_or_name(c, idx, NULL, parameter, cb, userdata); + return o; +} + +/** Module parameter string **/ + +static void context_string_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + const char *parameter_value; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + parameter_value = NULL; + } else if (pa_tagstruct_gets(t, ¶meter_value) || + !pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_context_string_cb_t cb = (pa_context_string_cb_t) o->callback; + cb(o->context, parameter_value, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +static pa_operation* get_module_parameter_by_index_or_name(pa_context *c, uint32_t idx, const char *module_name, const char *parameter, pa_context_string_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_MODULE_PARAMETER, &tag); + + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, module_name); + pa_tagstruct_puts(t, parameter); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_string_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_module_parameter_by_name(pa_context *c, const char *module_name, const char *parameter, pa_context_string_cb_t cb, void *userdata) { + pa_operation *o; + + o = get_module_parameter_by_index_or_name(c, PA_INVALID_INDEX, module_name, parameter, cb, userdata); + return o; +} + +pa_operation* pa_context_get_module_parameter_by_index(pa_context *c, uint32_t idx, const char *parameter, pa_context_string_cb_t cb, void *userdata) { + pa_operation *o; + + o = get_module_parameter_by_index_or_name(c, idx, NULL, parameter, cb, userdata); + return o; +} diff --git a/src/pulse/introspect.h b/src/pulse/introspect.h index 43389b73..764dbcf8 100644 --- a/src/pulse/introspect.h +++ b/src/pulse/introspect.h @@ -432,12 +432,27 @@ pa_operation* pa_context_get_module_info_list(pa_context *c, pa_module_info_cb_t /** Callback prototype for pa_context_load_module() */ typedef void (*pa_context_index_cb_t)(pa_context *c, uint32_t idx, void *userdata); +/** Callback prototype for pa_context_get_module_parameter() */ +typedef void (*pa_context_string_cb_t)(pa_context *c, const char *parameter, void *userdata); + /** Load a module. */ pa_operation* pa_context_load_module(pa_context *c, const char*name, const char *argument, pa_context_index_cb_t cb, void *userdata); /** Unload a module. */ pa_operation* pa_context_unload_module(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata); +/** Send parameter string to module by its index. */ +pa_operation* pa_context_set_module_parameter_by_index(pa_context *c, uint32_t idx, const char *parameter, pa_context_success_cb_t cb, void *userdata); + +/** Send parameter string to module by its name. */ +pa_operation* pa_context_set_module_parameter_by_name(pa_context *c, const char *module_name, const char *parameter, pa_context_success_cb_t cb, void *userdata); + +/** Get parameter value from module by its index. */ +pa_operation* pa_context_get_module_parameter_by_index(pa_context *c, uint32_t idx, const char *parameter, pa_context_string_cb_t cb, void *userdata); + +/** Get parameter value from module by its name. */ +pa_operation* pa_context_get_module_parameter_by_name(pa_context *c, const char *module_name, const char *parameter, pa_context_string_cb_t cb, void *userdata); + /** @} */ /** @{ \name Clients */ diff --git a/src/pulsecore/module.c b/src/pulsecore/module.c index ac158159..261c6395 100644 --- a/src/pulsecore/module.c +++ b/src/pulsecore/module.c @@ -171,6 +171,8 @@ pa_module* pa_module_load(pa_core *c, const char *name, const char *argument) { m->done = (void (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_DONE); m->get_n_used = (int (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_GET_N_USED); m->userdata = NULL; + m->set_parameter_callback = NULL; + m->get_parameter_callback = NULL; m->core = c; m->unload_requested = false; diff --git a/src/pulsecore/module.h b/src/pulsecore/module.h index 41e2189c..ff558c86 100644 --- a/src/pulsecore/module.h +++ b/src/pulsecore/module.h @@ -29,6 +29,7 @@ typedef struct pa_module pa_module; #include <pulsecore/dynarray.h> #include <pulsecore/core.h> +#include <pulsecore/tagstruct.h> struct pa_module { pa_core *core; @@ -41,6 +42,11 @@ struct pa_module { void (*done)(pa_module*m); int (*get_n_used)(pa_module *m); + /* Functions used for changing and querying parameters during run time. + * May be NULL. */ + int (*set_parameter_callback)(pa_module *m, const char *parameters); + int (*get_parameter_callback)(pa_module *m, pa_tagstruct *t, const char *parameter); + void *userdata; bool load_once:1; diff --git a/src/pulsecore/native-common.h b/src/pulsecore/native-common.h index 70338b9f..95a9cfc5 100644 --- a/src/pulsecore/native-common.h +++ b/src/pulsecore/native-common.h @@ -187,6 +187,9 @@ enum { * BOTH DIRECTIONS */ PA_COMMAND_REGISTER_MEMFD_SHMID, + PA_COMMAND_SET_MODULE_PARAMETER, + PA_COMMAND_GET_MODULE_PARAMETER, + PA_COMMAND_MAX }; diff --git a/src/pulsecore/pdispatch.c b/src/pulsecore/pdispatch.c index ab632a5a..f9423f19 100644 --- a/src/pulsecore/pdispatch.c +++ b/src/pulsecore/pdispatch.c @@ -199,6 +199,9 @@ static const char *command_names[PA_COMMAND_MAX] = { /* Supported since protocol v31 (9.0) */ /* BOTH DIRECTIONS */ [PA_COMMAND_REGISTER_MEMFD_SHMID] = "REGISTER_MEMFD_SHMID", + + [PA_COMMAND_SET_MODULE_PARAMETER] = "SET_MODULE_PARAMETER", + [PA_COMMAND_GET_MODULE_PARAMETER] = "GET_MODULE_PARAMETER", }; #endif diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index 266b676d..d13dfd1a 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -4648,6 +4648,117 @@ static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa pa_pstream_send_simple_ack(c->pstream, tag); } +/* Find a unique instance of a module. If more than one instance + * is loaded, it is considered an error. */ +static int find_module_by_name_or_index(pa_native_connection *c, pa_module **m, uint32_t idx, const char *name) { + pa_module *mod_search; + + *m = NULL; + + if (idx != PA_INVALID_INDEX) + *m = pa_idxset_get_by_index(c->protocol->core->modules, idx); + else { + pa_module *mod = NULL; + + PA_IDXSET_FOREACH(mod_search, c->protocol->core->modules, idx) { + if (pa_streq(name, mod_search->name)) { + if (!mod) + mod = mod_search; + else { + *m = NULL; + return -1; + } + } + } + *m = mod; + } + return 0; +} + +/* Send parameter string to module. */ +static void command_set_module_parameter(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx = PA_INVALID_INDEX; + const char *name = NULL; + const char *parameter_string = NULL; + pa_module *m; + int ret; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_gets(t, &name) < 0 || + pa_tagstruct_gets(t, ¶meter_string) || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, !name || pa_utf8_valid(name), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, (idx != PA_INVALID_INDEX) ^ (name != NULL), tag, PA_ERR_INVALID); + + if (find_module_by_name_or_index(c, &m, idx, name) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); + return; + } + + CHECK_VALIDITY(c->pstream, m, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, m->set_parameter_callback, tag, PA_ERR_NOTSUPPORTED); + + + if ((ret = m->set_parameter_callback(m, parameter_string)) < 0) { + pa_pstream_send_error(c->pstream, tag, -ret); + return; + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +/* Query parameter value from module. Result must be returned as string. */ +static void command_get_module_parameter(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx = PA_INVALID_INDEX; + const char *name = NULL; + const char *parameter_name = NULL; + pa_module *m; + pa_tagstruct *reply; + int ret; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_gets(t, &name) < 0 || + pa_tagstruct_gets(t, ¶meter_name) || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, !name || pa_utf8_valid(name), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, (idx != PA_INVALID_INDEX) ^ (name != NULL), tag, PA_ERR_INVALID); + + if (find_module_by_name_or_index(c, &m, idx, name) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); + return; + } + + CHECK_VALIDITY(c->pstream, m, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, m->get_parameter_callback, tag, PA_ERR_NOTSUPPORTED); + + reply = reply_new(tag); + + if ((ret = m->get_parameter_callback(m, reply, parameter_name)) < 0) { + pa_pstream_send_error(c->pstream, tag, -ret); + return; + } + + pa_pstream_send_tagstruct(c->pstream, reply); +} + static void command_extension(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); uint32_t idx = PA_INVALID_INDEX; @@ -4936,6 +5047,9 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { [PA_COMMAND_REGISTER_MEMFD_SHMID] = command_register_memfd_shmid, + [PA_COMMAND_SET_MODULE_PARAMETER] = command_set_module_parameter, + [PA_COMMAND_GET_MODULE_PARAMETER] = command_get_module_parameter, + [PA_COMMAND_EXTENSION] = command_extension }; diff --git a/src/utils/pactl.c b/src/utils/pactl.c index e9bf005b..3988374b 100644 --- a/src/utils/pactl.c +++ b/src/utils/pactl.c @@ -52,6 +52,7 @@ static char *sink_name = NULL, *source_name = NULL, *module_name = NULL, + *module_parameter = NULL, *module_args = NULL, *card_name = NULL, *profile_name = NULL, @@ -130,6 +131,8 @@ static enum { SET_SOURCE_OUTPUT_MUTE, SET_SINK_FORMATS, SET_PORT_LATENCY_OFFSET, + SET_MODULE_PARAMETER, + GET_MODULE_PARAMETER, SUBSCRIBE } action = NONE; @@ -834,6 +837,18 @@ static void index_callback(pa_context *c, uint32_t idx, void *userdata) { complete_action(); } +static void string_callback(pa_context *c, const char *parameter, void *userdata) { + if (!parameter) { + pa_log(_("Failure: %s"), pa_strerror(pa_context_errno(c))); + quit(1); + return; + } + + printf("%s\n", parameter); + + complete_action(); +} + static void volume_relative_adjust(pa_cvolume *cv) { pa_assert(volume_flags & VOL_RELATIVE); @@ -1404,6 +1419,20 @@ static void context_state_callback(pa_context *c, void *userdata) { o = pa_context_set_port_latency_offset(c, card_name, port_name, latency_offset, simple_callback, NULL); break; + case SET_MODULE_PARAMETER: + if (module_name) + o=pa_context_set_module_parameter_by_name(c, module_name, module_parameter, simple_callback, NULL); + else + o=pa_context_set_module_parameter_by_index(c, module_index, module_parameter, simple_callback, NULL); + break; + + case GET_MODULE_PARAMETER: + if (module_name) + o=pa_context_get_module_parameter_by_name(c, module_name, module_parameter, string_callback, NULL); + else + o=pa_context_get_module_parameter_by_index(c, module_index, module_parameter, string_callback, NULL); + break; + case SUBSCRIBE: pa_context_set_subscribe_callback(c, context_subscribe_callback, NULL); @@ -1580,6 +1609,8 @@ static void help(const char *argv0) { printf("%s %s %s %s\n", argv0, _("[options]"), "set-(sink-input|source-output)-mute", _("#N 1|0|toggle")); printf("%s %s %s %s\n", argv0, _("[options]"), "set-sink-formats", _("#N FORMATS")); printf("%s %s %s %s\n", argv0, _("[options]"), "set-port-latency-offset", _("CARD-NAME|CARD-#N PORT OFFSET")); + printf("%s %s %s %s\n", argv0, _("[options]"), "set-module-parameter", _("MODULE-NAME|MODULE-#N PARAMETER")); + printf("%s %s %s %s\n", argv0, _("[options]"), "get-module-parameter", _("MODULE-NAME|MODULE-#N PARAMETER")); printf("%s %s %s\n", argv0, _("[options]"), "subscribe"); printf(_("\nThe special names @DEFAULT_SINK@, @DEFAULT_SOURCE@ and @DEFAULT_MONITOR@\n" "can be used to specify the default sink, source and monitor.\n")); @@ -1799,6 +1830,30 @@ int main(int argc, char *argv[]) { if (pa_atou(argv[optind + 1], &module_index) < 0) module_name = argv[optind + 1]; + } else if (pa_streq(argv[optind], "set-module-parameter")) { + action = SET_MODULE_PARAMETER; + + if (argc != optind+3) { + pa_log(_("You have to specify a module index or name and a parameter string")); + goto quit; + } + + if (pa_atou(argv[optind + 1], &module_index) < 0) + module_name = argv[optind + 1]; + module_parameter = argv[optind + 2]; + + } else if (pa_streq(argv[optind], "get-module-parameter")) { + action = GET_MODULE_PARAMETER; + + if (argc != optind+3) { + pa_log(_("You have to specify a module index or name and a parameter name")); + goto quit; + } + + if (pa_atou(argv[optind + 1], &module_index) < 0) + module_name = argv[optind + 1]; + module_parameter = argv[optind + 2]; + } else if (pa_streq(argv[optind], "suspend-sink")) { int b; -- 2.11.0