summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2018-05-12 14:50:07 +0200
committerwm4 <wm4@nowhere>2018-05-24 19:56:34 +0200
commit1aae88b4879f40c68cebbdcd47895787ecdcdf68 (patch)
tree72de973fbbed447ee6cd7bf61ce506601fe8438f
parent1157f07c5b8b97112f9a6bde695aff8072a88fb2 (diff)
downloadmpv-1aae88b4879f40c68cebbdcd47895787ecdcdf68.tar.bz2
mpv-1aae88b4879f40c68cebbdcd47895787ecdcdf68.tar.xz
input: add glue code for named arguments
Named arguments should make it easier to have long time compatibility, even if command arguments get added or removed. They're also much nicer for commands with a large number of arguments, especially if many arguments are optional. As of this commit, this can not be used, because there is no command yet which supports them. See the following commit.
-rw-r--r--DOCS/man/input.rst75
-rw-r--r--DOCS/man/lua.rst12
-rw-r--r--input/cmd.c185
-rw-r--r--libmpv/client.h23
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