This patch adds cli commands to pacmd and pactl to make use of the new SEND_MODULE_{COMMAND,QUERY} commands in protocol-native. Now module parameters can be changed and queried from the command line if the module implements the feature. There is no default syntax for the strings passed to a module, so each module is free to implement its own set of commands. --- man/pactl.1.xml.in | 13 ++++++ man/pulse-cli-syntax.5.xml.in | 13 ++++++ shell-completion/bash/pulseaudio | 25 ++++++++++- shell-completion/zsh/_pulseaudio | 8 ++++ src/pulsecore/cli-command.c | 95 ++++++++++++++++++++++++++++++++++++++++ src/utils/pacmd.c | 2 + src/utils/pactl.c | 55 +++++++++++++++++++++++ 7 files changed, 209 insertions(+), 2 deletions(-) diff --git a/man/pactl.1.xml.in b/man/pactl.1.xml.in index c2064cae..4cfe7bce 100644 --- a/man/pactl.1.xml.in +++ b/man/pactl.1.xml.in @@ -119,6 +119,19 @@ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. </option> <option> + <p><opt>send-module-command</opt> <arg>ID|NAME</arg> <arg>COMMAND-STRING</arg></p> + <optdesc><p>Send a command string to the specified module. If the module is specified by name, the name must be unambiguous. + The ability to process client commands at run time is an optional feature of a module. Implemented commands and syntax are + module specific.</p></optdesc> + </option> + + <option> + <p><opt>send-module-query</opt> <arg>ID|NAME</arg> <arg>COMMAND-STRING</arg></p> + <optdesc><p>Send a query command string to the specified module. If the module is specified by name, the name must be unambiguous. + The command is similar to send-module-command with the difference that the module returns a string as a response to the query.</p></optdesc> + </option> + + <option> <p><opt>move-sink-input</opt> <arg>ID</arg> <arg>SINK</arg></p> <optdesc><p>Move the specified playback stream (identified by its numerical index) to the specified sink (identified by its symbolic name or numerical index).</p></optdesc> </option> diff --git a/man/pulse-cli-syntax.5.xml.in b/man/pulse-cli-syntax.5.xml.in index 0a0fabaf..b4e962e1 100644 --- a/man/pulse-cli-syntax.5.xml.in +++ b/man/pulse-cli-syntax.5.xml.in @@ -103,6 +103,19 @@ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. </option> <option> + <p><opt>send-module-command</opt> <arg>index|name</arg> <arg>command-string</arg></p> + <optdesc><p>Send a command string to the specified module. If the module is specified by name, the name must be unambiguous. + The ability to process client commands at run time is an optional feature of a module. Implemented commands and syntax are + module specific.</p></optdesc> + </option> + + <option> + <p><opt>send-module-query</opt> <arg>index|name</arg> <arg>command-string</arg></p> + <optdesc><p>Send a query command string to the specified module. If the module is specified by name, the name must be unambiguous. + The command is similar to send-module-command with the difference that the module returns a string as a response to the query.</p></optdesc> + </option> + + <option> <p><opt>describe-module</opt> <arg>name</arg></p> <optdesc><p>Give information about a module specified by its name.</p></optdesc> </option> diff --git a/shell-completion/bash/pulseaudio b/shell-completion/bash/pulseaudio index e473b9c2..5303fe32 100644 --- a/shell-completion/bash/pulseaudio +++ b/shell-completion/bash/pulseaudio @@ -120,7 +120,8 @@ _pactl() { set-source-port set-sink-volume set-source-volume set-sink-input-volume set-source-output-volume set-sink-mute set-source-mute set-sink-input-mute set-source-output-mute - set-sink-formats set-port-latency-offset subscribe help) + set-sink-formats set-port-latency-offset send-module-command + send-module-query subscribe help) _init_completion -n = || return preprev=${words[$cword-2]} @@ -195,6 +196,16 @@ _pactl() { COMPREPLY=($(compgen -W '${comps[*]}' -- "$cur")) ;; + send-module-command) + comps=$(__loaded_modules) + COMPREPLY=($(compgen -W '${comps[*]}' -- "$cur")) + ;; + + send-module-query) + comps=$(__loaded_modules) + COMPREPLY=($(compgen -W '${comps[*]}' -- "$cur")) + ;; + set-card*) comps=$(__cards) COMPREPLY=($(compgen -W '${comps[*]}' -- "$cur")) @@ -270,7 +281,7 @@ _pacmd() { move-sink-input move-source-output suspend-sink suspend-source suspend set-card-profile set-sink-port set-source-port set-port-latency-offset set-log-target set-log-level set-log-meta - set-log-time set-log-backtrace) + set-log-time set-log-backtrace send-module-command send-module-query) _init_completion -n = || return preprev=${words[$cword-2]} @@ -327,6 +338,16 @@ _pacmd() { COMPREPLY=($(compgen -W '${comps[*]}' -- "$cur")) ;; + send-module-command) + comps=$(__loaded_modules) + COMPREPLY=($(compgen -W '${comps[*]}' -- "$cur")) + ;; + + send-module-query) + comps=$(__loaded_modules) + COMPREPLY=($(compgen -W '${comps[*]}' -- "$cur")) + ;; + load-sample-dir-lazy) _filedir -d ;; play-file) _filedir ;; diff --git a/shell-completion/zsh/_pulseaudio b/shell-completion/zsh/_pulseaudio index 0e9e89bd..09c53aa6 100644 --- a/shell-completion/zsh/_pulseaudio +++ b/shell-completion/zsh/_pulseaudio @@ -244,6 +244,8 @@ _pactl_completion() { 'remove-sample: remove the specified sample from the sample cache' 'load-module: load a module' 'unload-module: unload a module' + 'send-module-command: send command string to module' + 'send-module-query: send command string to module with reply' 'move-sink-input: move a stream to a sink' 'move-source-output: move a recording stream to a source' 'suspend-sink: suspend or resume a sink' @@ -473,6 +475,8 @@ _pactl_completion() { remove-sample) ;; # TODO: Implement sample name completion. load-module) _load_module_parameter;; unload-module) if ((CURRENT == 2)); then _loaded_modules; fi;; + send-module-command) if ((CURRENT == 2)); then _loaded_modules; fi;; + send-module-query) if ((CURRENT == 2)); then _loaded_modules; fi;; move-sink-input) _move_sink_input_parameter;; move-source-output) _move_source_output_parameter;; suspend-sink) _suspend_sink_parameter;; @@ -519,6 +523,8 @@ _pacmd_completion() { 'info: dump info about the PulseAudio daemon' 'load-module: load a module' 'unload-module: unload a module' + 'send-module-command: send command string to module' + 'send-module-query: send command string to module with reply' 'describe-module: print info for a module' 'set-sink-volume: set the volume of a sink' 'set-source-volume: set the volume of a source' @@ -578,6 +584,8 @@ _pacmd_completion() { load-module) _all_modules;; describe-module) _all_modules;; unload-module) _loaded_modules;; + send-module-command) _loaded_modules;; + send-module-query) _loaded_modules;; suspend-*) _devices;; move-*) _devices;; set-port-latency-offset) _cards;; diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c index 5a632be4..14e307b4 100644 --- a/src/pulsecore/cli-command.c +++ b/src/pulsecore/cli-command.c @@ -135,6 +135,8 @@ static int pa_cli_command_sink_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, static int pa_cli_command_source_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); static int pa_cli_command_port_offset(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); static int pa_cli_command_dump_volumes(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_module_command(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_module_query(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); /* A method table for all available commands */ @@ -154,6 +156,8 @@ static const struct command commands[] = { { "load-module", pa_cli_command_load, "Load a module (args: name, arguments)", 3}, { "unload-module", pa_cli_command_unload, "Unload a module (args: index|name)", 2}, { "describe-module", pa_cli_command_describe, "Describe a module (arg: name)", 2}, + { "send-module-command", pa_cli_command_module_command, "Send command to a module (args: index|name, command)", 3}, + { "send-module-query", pa_cli_command_module_query, "Send command to a module with reply (args: index|name, command)", 3}, { "set-sink-volume", pa_cli_command_sink_volume, "Set the volume of a sink (args: index|name, volume)", 3}, { "set-source-volume", pa_cli_command_source_volume, "Set the volume of a source (args: index|name, volume)", 3}, { "set-sink-mute", pa_cli_command_sink_mute, "Set the mute switch of a sink (args: index|name, bool)", 3}, @@ -1773,6 +1777,97 @@ static int pa_cli_command_port_offset(pa_core *c, pa_tokenizer *t, pa_strbuf *bu return 0; } +static int pa_cli_command_module_command(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *mod_name_or_index, *command; + const char *module_name = NULL; + uint32_t module_index = PA_INVALID_INDEX; + pa_module *m; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(mod_name_or_index = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a module either by its name or its index.\n"); + return -1; + } + + if (!(command = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a command string.\n"); + return -1; + } + + if (pa_atou(mod_name_or_index, &module_index) < 0) + module_name = mod_name_or_index; + + if (pa_module_find_unique_instance(c, &m, module_index, module_name) < 0 || !m) { + pa_strbuf_puts(buf, "Invalid module name or index.\n"); + return -1; + } + + if (!m->set_command_callback) { + pa_strbuf_puts(buf, "Not implemented by module.\n"); + return -1; + } + + if (m->set_command_callback(m, command) < 0) { + pa_strbuf_puts(buf, "Command failed.\n"); + return -1; + } + + return 0; +} + +static int pa_cli_command_module_query(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *mod_name_or_index, *command; + const char *module_name = NULL; + uint32_t module_index = PA_INVALID_INDEX; + char *response = NULL; + pa_module *m; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(mod_name_or_index = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a module either by its name or its index.\n"); + return -1; + } + + if (!(command = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a command string.\n"); + return -1; + } + + if (pa_atou(mod_name_or_index, &module_index) < 0) + module_name = mod_name_or_index; + + if (pa_module_find_unique_instance(c, &m, module_index, module_name) < 0 || !m) { + pa_strbuf_puts(buf, "Invalid module name or index.\n"); + return -1; + } + + if (!m->get_command_callback) { + pa_strbuf_puts(buf, "Not implemented by module.\n"); + return -1; + } + + if (m->get_command_callback(m, &response, command) < 0) { + pa_strbuf_puts(buf, "Command failed.\n"); + pa_xfree(response); + return -1; + } + + pa_strbuf_puts(buf, (const char *)response); + pa_strbuf_puts(buf, "\n"); + + pa_xfree(response); + return 0; +} + + static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { pa_module *m; pa_sink *sink; diff --git a/src/utils/pacmd.c b/src/utils/pacmd.c index 616573cc..69fe7bff 100644 --- a/src/utils/pacmd.c +++ b/src/utils/pacmd.c @@ -77,6 +77,8 @@ static void help(const char *argv0) { printf("%s %s %s\n", argv0, "set-log-meta", _("1|0")); printf("%s %s %s\n", argv0, "set-log-time", _("1|0")); printf("%s %s %s\n", argv0, "set-log-backtrace", _("FRAMES")); + printf("%s %s %s\n", argv0, "send-module-command", _("MODULE-NAME|MODULE-#N COMMANDSTRING")); + printf("%s %s %s\n", argv0, "send-module-query", _("MODULE-NAME|MODULE-#N COMMANDSTRING")); printf(_("\n" " -h, --help Show this help\n" diff --git a/src/utils/pactl.c b/src/utils/pactl.c index e9bf005b..f5aa6b95 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_command = 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, + SEND_MODULE_COMMAND, + SEND_MODULE_QUERY, 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 *response, void *userdata) { + if (!response) { + pa_log(_("Failure: %s"), pa_strerror(pa_context_errno(c))); + quit(1); + return; + } + + printf("%s\n", response); + + 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 SEND_MODULE_COMMAND: + if (module_name) + o=pa_context_send_module_command_by_name(c, module_name, module_command, simple_callback, NULL); + else + o=pa_context_send_module_command_by_index(c, module_index, module_command, simple_callback, NULL); + break; + + case SEND_MODULE_QUERY: + if (module_name) + o=pa_context_send_module_query_by_name(c, module_name, module_command, string_callback, NULL); + else + o=pa_context_send_module_query_by_index(c, module_index, module_command, 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]"), "send-module-command", _("MODULE-NAME|MODULE-#N COMMANDSTRING")); + printf("%s %s %s %s\n", argv0, _("[options]"), "send-module-query", _("MODULE-NAME|MODULE-#N QUERYSTRING")); 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], "send-module-command")) { + action = SEND_MODULE_COMMAND; + + if (argc != optind+3) { + pa_log(_("You have to specify a module index or name and a command string")); + goto quit; + } + + if (pa_atou(argv[optind + 1], &module_index) < 0) + module_name = argv[optind + 1]; + module_command = argv[optind + 2]; + + } else if (pa_streq(argv[optind], "send-module-query")) { + action = SEND_MODULE_QUERY; + + if (argc != optind+3) { + pa_log(_("You have to specify a module index or name and a command string")); + goto quit; + } + + if (pa_atou(argv[optind + 1], &module_index) < 0) + module_name = argv[optind + 1]; + module_command = argv[optind + 2]; + } else if (pa_streq(argv[optind], "suspend-sink")) { int b; -- 2.11.0