diff options
-rw-r--r-- | DOCS/man/input.rst | 75 | ||||
-rw-r--r-- | DOCS/man/lua.rst | 12 | ||||
-rw-r--r-- | input/cmd.c | 185 | ||||
-rw-r--r-- | libmpv/client.h | 23 |
4 files changed, 248 insertions, 47 deletions
diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index 6aa566294f..567d2e0de8 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -78,6 +78,16 @@ that matches, and the multi-key command will never be called. Intermediate keys can be remapped to ``ignore`` in order to avoid this issue. The maximum number of (non-modifier) keys for combinations is currently 4. +Named arguments +--------------- + +Some commands support named arguments (most currently don't). Named arguments +cannot be used with the "flat" input.conf syntax shown above, but require using +e.g. ``mp.command_native()`` in Lua scripting, or e.g. ``mpv_command_node()`` +with the libmpv API. Some commands ask you to only use named arguments (because +the command order is not guaranteed), which means you can't use them as +key bindings in input.conf at all. + List of Input Commands ---------------------- @@ -283,6 +293,71 @@ List of Input Commands execute arbitrary shell commands. It is recommended to write a small shell script, and call that with ``run``. +``subprocess`` + Similar to ``run``, but gives more control about process execution to the + caller, and does does not detach the process. + + This has the following named arguments. The order of them is not guaranteed, + so you should always call them with named arguments, see `Named arguments`_. + + ``args`` (``MPV_FORMAT_NODE_ARRAY[MPV_FORMAT_STRING]``) + Array of strings with the command as first argument, and subsequent + command line arguments following. This is just like the ``run`` command + argument list. + + ``playback_only`` (``MPV_FORMAT_FLAG``) + Boolean indicating whether the process should be killed when playback + terminates (optional, default: yes). If enabled, stopping playback + will automatically kill the process, and you can't start it outside of + playback. + + ``capture_size`` (``MPV_FORMAT_INT64``) + Integer setting the maximum number of stdout plus stderr bytes that can + be captured (optional, default: 64MB). If the number of bytes exceeds + this, capturing is stopped. The limit is per captured stream. + + ``capture_stdout`` (``MPV_FORMAT_FLAG``) + Capture all data the process outputs to stdout and return it once the + process ends (optional, default: no). + + ``capture_stderr`` (``MPV_FORMAT_FLAG``) + Same as ``capture_stdout``, but for stderr. + + The command returns the following result (as ``MPV_FORMAT_NODE_MAP``): + + ``status`` (``MPV_FORMAT_INT64``) + The raw exit status of the process. It will be negative on error. The + meaning of negative values is undefined, other than meaning error (and + does not necessarily correspond to OS low level exit status values). + + On Windows, it can happen that a negative return value is returned + even if the process exits gracefully, because the win32 ``UINT`` exit + code is assigned to an ``int`` variable before being set as ``int64_t`` + field in the result map. This might be fixed later. + + ``stdout`` (``MPV_FORMAT_BYTE_ARRAY``) + Captured stdout stream, limited to ``capture_size``. + + ``stderr`` (``MPV_FORMAT_BYTE_ARRAY``) + Same as ``stdout``, but for stderr. + + ``error_string`` (``MPV_FORMAT_STRING``) + Empty string if the process exited gracefully. The string ``killed`` if + the process was terminated in an unusual way. The string ``init`` if the + process could not be started. + + On Windows, ``killed`` is only returned when the process has been + killed by mpv as a result of ``playback_only`` being set to ``yes``. + + ``killed_by_us`` (``MPV_FORMAT_FLAG``) + Set to ``yes`` if the process has been killed by mpv as a result + of ``playback_only`` being set to ``yes``. + + Note that the command itself will always return success as long as the + parameters are correct. Whether the process could be spawned or whether + it was somehow killed or returned an error status has to be queried from + the result value. + ``quit [<code>]`` Exit the player. If an argument is given, it's used as process exit code. diff --git a/DOCS/man/lua.rst b/DOCS/man/lua.rst index 0dbd7d3c0a..dab185a284 100644 --- a/DOCS/man/lua.rst +++ b/DOCS/man/lua.rst @@ -109,7 +109,17 @@ The ``mp`` module is preloaded, although it can be loaded manually with ``mp.command_native(table [,def])`` Similar to ``mp.commandv``, but pass the argument list as table. This has the advantage that in at least some cases, arguments can be passed as - native types. + native types. It also allows you to use named argument. + + If the table is an array, each array item is like an argument in + ``mp.commandv()`` (but can be a native type instead of a string). + + If the table contains string keys, it's interpreted as command with named + arguments. This requires at least an entry with the key ``name`` to be + present, which must be a string, and contains the command name. The special + entry ``_flags`` is optional, and if present, must be an array of + `Input Command Prefixes`_ to apply. All other entries are interpreted as + arguments. Returns a result table on success (usually empty), or ``def, error`` on error. ``def`` is the second parameter provided to the function, and is diff --git a/input/cmd.c b/input/cmd.c index 7405e1e5dd..c637ec22bd 100644 --- a/input/cmd.c +++ b/input/cmd.c @@ -18,6 +18,7 @@ #include <stddef.h> #include "misc/bstr.h" +#include "misc/node.h" #include "common/common.h" #include "common/msg.h" #include "options/m_option.h" @@ -30,8 +31,10 @@ static void destroy_cmd(void *ptr) { struct mp_cmd *cmd = ptr; - for (int n = 0; n < cmd->nargs; n++) - m_option_free(cmd->args[n].type, &cmd->args[n].v); + for (int n = 0; n < cmd->nargs; n++) { + if (cmd->args[n].type) + m_option_free(cmd->args[n].type, &cmd->args[n].v); + } } struct flag { @@ -111,22 +114,37 @@ static const struct m_option *get_arg_type(const struct mp_cmd_def *cmd, int i) return opt && opt->type ? opt : NULL; } -// Verify that there are missing args, fill in missing optional args. +// Return the name of the argument, possibly as stack allocated string (which is +// why this is a macro, and out of laziness). Otherwise as get_arg_type(). +#define get_arg_name(cmd, i) \ + ((i) < MP_CMD_DEF_MAX_ARGS && (cmd)->args[(i)].name && \ + (cmd)->args[(i)].name[0] \ + ? (cmd)->args[(i)].name : mp_tprintf(10, "%d", (i) + 1)) + +// Verify that there are no missing args, fill in missing optional args. static bool finish_cmd(struct mp_log *log, struct mp_cmd *cmd) { - for (int i = cmd->nargs; i < MP_CMD_DEF_MAX_ARGS; i++) { + for (int i = 0; i < MP_CMD_DEF_MAX_ARGS; i++) { + // (type==NULL is used for yet unset arguments) + if (i < cmd->nargs && cmd->args[i].type) + continue; const struct m_option *opt = get_arg_type(cmd->def, i); - if (!opt || is_vararg(cmd->def, i)) + if (i >= cmd->nargs && (!opt || is_vararg(cmd->def, i))) break; if (!opt->defval && !(opt->flags & MP_CMD_OPT_ARG)) { - mp_err(log, "Command %s: more than %d arguments required.\n", - cmd->name, cmd->nargs); + mp_err(log, "Command %s: required argument %s not set.\n", + cmd->name, get_arg_name(cmd->def, i)); return false; } struct mp_cmd_arg arg = {.type = opt}; if (opt->defval) m_option_copy(opt, &arg.v, opt->defval); - MP_TARRAY_APPEND(cmd, cmd->args, cmd->nargs, arg); + assert(i <= cmd->nargs); + if (i == cmd->nargs) { + MP_TARRAY_APPEND(cmd, cmd->args, cmd->nargs, arg); + } else { + cmd->args[i] = arg; + } } if (!(cmd->flags & (MP_ASYNC_CMD | MP_SYNC_CMD))) @@ -135,14 +153,54 @@ static bool finish_cmd(struct mp_log *log, struct mp_cmd *cmd) return true; } -struct mp_cmd *mp_input_parse_cmd_node(struct mp_log *log, mpv_node *node) +static bool set_node_arg(struct mp_log *log, struct mp_cmd *cmd, int i, + mpv_node *val) { - struct mp_cmd *cmd = talloc_ptrtype(NULL, cmd); - talloc_set_destructor(cmd, destroy_cmd); - *cmd = (struct mp_cmd) { .scale = 1, .scale_units = 1 }; + const char *name = get_arg_name(cmd->def, i); - if (node->format != MPV_FORMAT_NODE_ARRAY) - goto error; + const struct m_option *opt = get_arg_type(cmd->def, i); + if (!opt) { + mp_err(log, "Command %s: has only %d arguments.\n", cmd->name, i); + return false; + } + + if (i < cmd->nargs && cmd->args[i].type) { + mp_err(log, "Command %s: argument %s was already set.\n", cmd->name, name); + return false; + } + + struct mp_cmd_arg arg = {.type = opt}; + void *dst = &arg.v; + if (val->format == MPV_FORMAT_STRING) { + int r = m_option_parse(log, opt, bstr0(cmd->name), + bstr0(val->u.string), dst); + if (r < 0) { + mp_err(log, "Command %s: argument %s can't be parsed: %s.\n", + cmd->name, name, m_option_strerror(r)); + return false; + } + } else { + int r = m_option_set_node(opt, dst, val); + if (r < 0) { + mp_err(log, "Command %s: argument %s has incompatible type.\n", + cmd->name, name); + return false; + } + } + + // (leave unset arguments blank, to be set later or checked by finish_cmd()) + while (i >= cmd->nargs) { + struct mp_cmd_arg t = {0}; + MP_TARRAY_APPEND(cmd, cmd->args, cmd->nargs, t); + } + + cmd->args[i] = arg; + return true; +} + +static bool cmd_node_array(struct mp_log *log, struct mp_cmd *cmd, mpv_node *node) +{ + assert(node->format == MPV_FORMAT_NODE_ARRAY); mpv_node_list *args = node->u.list; int cur = 0; @@ -158,47 +216,90 @@ struct mp_cmd *mp_input_parse_cmd_node(struct mp_log *log, mpv_node *node) if (cur < args->num && args->values[cur].format == MPV_FORMAT_STRING) cmd_name = bstr0(args->values[cur++].u.string); if (!find_cmd(log, cmd, cmd_name)) - goto error; + return false; int first = cur; for (int i = 0; i < args->num - first; i++) { - const struct m_option *opt = get_arg_type(cmd->def, i); - if (!opt) { - mp_err(log, "Command %s: has only %d arguments.\n", cmd->name, i); - goto error; - } - mpv_node *val = &args->values[cur++]; - struct mp_cmd_arg arg = {.type = opt}; - void *dst = &arg.v; - if (val->format == MPV_FORMAT_STRING) { - int r = m_option_parse(log, opt, bstr0(cmd->name), - bstr0(val->u.string), dst); - if (r < 0) { - mp_err(log, "Command %s: argument %d can't be parsed: %s.\n", - cmd->name, i + 1, m_option_strerror(r)); - goto error; + if (!set_node_arg(log, cmd, cmd->nargs, &args->values[cur++])) + return false; + } + + return true; +} + +static bool cmd_node_map(struct mp_log *log, struct mp_cmd *cmd, mpv_node *node) +{ + assert(node->format == MPV_FORMAT_NODE_MAP); + mpv_node_list *args = node->u.list; + + mpv_node *name = node_map_get(node, "name"); + if (!name || name->format != MPV_FORMAT_STRING) + return false; + + if (!find_cmd(log, cmd, bstr0(name->u.string))) + return false; + + for (int n = 0; n < args->num; n++) { + const char *key = args->keys[n]; + mpv_node *val = &args->values[n]; + + if (strcmp(key, "name") == 0) { + // already handled above + } else if (strcmp(key, "_flags") == 0) { + if (val->format != MPV_FORMAT_NODE_ARRAY) + return false; + mpv_node_list *flags = val->u.list; + for (int i = 0; i < flags->num; i++) { + if (flags->values[i].format != MPV_FORMAT_STRING) + return false; + if (!apply_flag(cmd, bstr0(flags->values[i].u.string))) + return false; } } else { - int r = m_option_set_node(opt, dst, val); - if (r < 0) { - mp_err(log, "Command %s: argument %d has incompatible type.\n", - cmd->name, i + 1); - goto error; + int arg = -1; + + for (int i = 0; i < MP_CMD_DEF_MAX_ARGS; i++) { + const char *arg_name = cmd->def->args[i].name; + if (arg_name && arg_name[0] && strcmp(key, arg_name) == 0) { + arg = i; + break; + } + } + + if (arg < 0) { + mp_err(log, "Command %s: no argument %s.\n", cmd->name, key); + return false; } + + if (!set_node_arg(log, cmd, arg, val)) + return false; } - MP_TARRAY_APPEND(cmd, cmd->args, cmd->nargs, arg); } - if (!finish_cmd(log, cmd)) - goto error; + return true; +} + +struct mp_cmd *mp_input_parse_cmd_node(struct mp_log *log, mpv_node *node) +{ + struct mp_cmd *cmd = talloc_ptrtype(NULL, cmd); + talloc_set_destructor(cmd, destroy_cmd); + *cmd = (struct mp_cmd) { .scale = 1, .scale_units = 1 }; + + bool res = false; + if (node->format == MPV_FORMAT_NODE_ARRAY) { + res = cmd_node_array(log, cmd, node); + } else if (node->format == MPV_FORMAT_NODE_MAP) { + res = cmd_node_map(log, cmd, node); + } + + res = res && finish_cmd(log, cmd); + + if (!res) + TA_FREEP(&cmd); return cmd; -error: - talloc_free(cmd); - return NULL; } - static bool read_token(bstr str, bstr *out_rest, bstr *out_token) { bstr t = bstr_lstrip(str); diff --git a/libmpv/client.h b/libmpv/client.h index f9c9f3686b..f7f7fada58 100644 --- a/libmpv/client.h +++ b/libmpv/client.h @@ -941,10 +941,25 @@ int mpv_command(mpv_handle *ctx, const char **args); * * Does not use OSD and string expansion by default. * - * @param[in] args mpv_node with format set to MPV_FORMAT_NODE_ARRAY; each entry - * is an argument using an arbitrary format (the format must be - * compatible to the used command). Usually, the first item is - * the command name (as MPV_FORMAT_STRING). + * The args argument can have one of the following formats: + * + * MPV_FORMAT_NODE_ARRAY: + * Positional arguments. Each entry is an argument using an arbitrary + * format (the format must be compatible to the used command). Usually, + * the first item is the command name (as MPV_FORMAT_STRING). The order + * of arguments is as documented in each command description. + * + * MPV_FORMAT_NODE_MAP: + * Named arguments. This requires at least an entry with the key "name" + * to be present, which must be a string, and contains the command name. + * The special entry "_flags" is optional, and if present, must be an + * array of strings, each being a command prefix to apply. All other + * entries are interpreted as arguments. They must use the argument names + * as documented in each command description. Currently, most commands do + * not support named arguments at all. + * + * @param[in] args mpv_node with format set to one of the values documented + * above (see there for details) * @param[out] result Optional, pass NULL if unused. If not NULL, and if the * function succeeds, this is set to command-specific return * data. You must call mpv_free_node_contents() to free it |