diff options
Diffstat (limited to 'input')
-rw-r--r-- | input/cmd.c | 242 | ||||
-rw-r--r-- | input/cmd.h | 34 | ||||
-rw-r--r-- | input/input.c | 40 | ||||
-rw-r--r-- | input/input.h | 4 | ||||
-rw-r--r-- | input/ipc-unix.c | 7 | ||||
-rw-r--r-- | input/ipc-win.c | 5 | ||||
-rw-r--r-- | input/ipc.c | 23 |
7 files changed, 212 insertions, 143 deletions
diff --git a/input/cmd.c b/input/cmd.c index 8c15c0109b..f0bb53e040 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" @@ -27,15 +28,13 @@ #include "libmpv/client.h" -const struct mp_cmd_def mp_cmd_list = { - .name = "list", -}; - 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 { @@ -52,7 +51,8 @@ static const struct flag cmd_flags[] = { {"expand-properties", 0, MP_EXPAND_PROPERTIES}, {"raw", MP_EXPAND_PROPERTIES, 0}, {"repeatable", 0, MP_ALLOW_REPEAT}, - {"async", 0, MP_ASYNC_CMD}, + {"async", MP_SYNC_CMD, MP_ASYNC_CMD}, + {"sync", MP_ASYNC_CMD, MP_SYNC_CMD}, {0} }; @@ -114,34 +114,93 @@ 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))) + cmd->flags |= cmd->def->default_async ? MP_ASYNC_CMD : MP_SYNC_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; @@ -157,47 +216,97 @@ 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; + + if (cmd->def->vararg) { + mp_err(log, "Command %s: this command uses a variable number of " + "arguments, which does not work with named arguments.\n", + cmd->name); + 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); @@ -443,34 +552,6 @@ void mp_cmd_dump(struct mp_log *log, int msgl, char *header, struct mp_cmd *cmd) mp_msg(log, msgl, "]\n"); } -// 0: no, 1: maybe, 2: sure -static int is_abort_cmd(struct mp_cmd *cmd) -{ - if (cmd->def->is_abort) - return 2; - if (cmd->def->is_soft_abort) - return 1; - if (cmd->def == &mp_cmd_list) { - int r = 0; - for (struct mp_cmd *sub = cmd->args[0].v.p; sub; sub = sub->queue_next) { - int x = is_abort_cmd(sub); - r = MPMAX(r, x); - } - return r; - } - return 0; -} - -bool mp_input_is_maybe_abort_cmd(struct mp_cmd *cmd) -{ - return is_abort_cmd(cmd) >= 1; -} - -bool mp_input_is_abort_cmd(struct mp_cmd *cmd) -{ - return is_abort_cmd(cmd) >= 2; -} - bool mp_input_is_repeatable_cmd(struct mp_cmd *cmd) { return (cmd->def->allow_auto_repeat) || cmd->def == &mp_cmd_list || @@ -488,12 +569,13 @@ void mp_print_cmd_list(struct mp_log *out) const struct mp_cmd_def *def = &mp_cmds[i]; mp_info(out, "%-20.20s", def->name); for (int j = 0; j < MP_CMD_DEF_MAX_ARGS && def->args[j].type; j++) { - const char *type = def->args[j].type->name; - if (def->args[j].defval) - mp_info(out, " [%s]", type); - else - mp_info(out, " %s", type); + const struct m_option *arg = &def->args[j]; + bool is_opt = arg->defval || (arg->flags & MP_CMD_OPT_ARG); + mp_info(out, " %s%s=%s%s", is_opt ? "[" : "", arg->name, + arg->type->name, is_opt ? "]" : ""); } + if (def->vararg) + mp_info(out, "..."); // essentially append to last argument mp_info(out, "\n"); } } diff --git a/input/cmd.h b/input/cmd.h index e09bcaa1c4..2d9c922230 100644 --- a/input/cmd.h +++ b/input/cmd.h @@ -39,9 +39,26 @@ struct mp_cmd_def { bool on_updown; // always emit it on both up and down key events bool vararg; // last argument can be given 0 to multiple times bool scalable; - bool is_abort; - bool is_soft_abort; bool is_ignore; + bool default_async; // default to MP_ASYNC flag if none set by user + // If you set this, handler() must ensure mp_cmd_ctx_complete() is called + // at some point (can be after handler() returns). If you don't set it, the + // common code will call mp_cmd_ctx_complete() when handler() returns. + // You must make sure that the core cannot disappear while you do work. The + // common code keeps the core referenced only until handler() returns. + bool exec_async; + // If set, handler() is run on a separate worker thread. This means you can + // use mp_core_[un]lock() to temporarily unlock and re-lock the core (while + // unlocked, you have no synchronized access to mpctx, but you can do long + // running operations without blocking playback or input handling). + bool spawn_thread; + // If this is set, mp_cmd_ctx.abort is set. Set this if handler() can do + // asynchronous abort of the command, and explicitly uses mp_cmd_ctx.abort. + // (Not setting it when it's not needed can save resources.) + bool can_abort; + // If playback ends, and the command is still running, an abort is + // automatically triggered. + bool abort_on_playback_end; }; enum mp_cmd_flags { @@ -51,7 +68,11 @@ enum mp_cmd_flags { MP_ON_OSD_MSG = 4, // force a message, if applicable MP_EXPAND_PROPERTIES = 8, // expand strings as properties MP_ALLOW_REPEAT = 16, // if used as keybinding, allow key repeat - MP_ASYNC_CMD = 32, + + // Exactly one of the following 2 bits is set. Which one is used depends on + // the command parser (prefixes and mp_cmd_def.default_async). + MP_ASYNC_CMD = 32, // do not wait for command to complete + MP_SYNC_CMD = 64, // block on command completion MP_ON_OSD_FLAGS = MP_ON_OSD_NO | MP_ON_OSD_AUTO | MP_ON_OSD_BAR | MP_ON_OSD_MSG, @@ -64,6 +85,7 @@ struct mp_cmd_arg { const struct m_option *type; union { int i; + int64_t i64; float f; double d; char *s; @@ -73,7 +95,6 @@ struct mp_cmd_arg { }; typedef struct mp_cmd { - int id; char *name; struct mp_cmd_arg *args; int nargs; @@ -98,11 +119,6 @@ typedef struct mp_cmd { extern const struct mp_cmd_def mp_cmds[]; extern const struct mp_cmd_def mp_cmd_list; -// Executing this command will maybe abort playback (play something else, or quit). -bool mp_input_is_maybe_abort_cmd(struct mp_cmd *cmd); -// This command will definitely abort playback. -bool mp_input_is_abort_cmd(struct mp_cmd *cmd); - bool mp_input_is_repeatable_cmd(struct mp_cmd *cmd); bool mp_input_is_scalable_cmd(struct mp_cmd *cmd); diff --git a/input/input.c b/input/input.c index 20c39dd4ac..9e96da267d 100644 --- a/input/input.c +++ b/input/input.c @@ -153,9 +153,6 @@ struct input_ctx { struct cmd_queue cmd_queue; - void (*cancel)(void *cancel_ctx); - void *cancel_ctx; - void (*wakeup_cb)(void *ctx); void *wakeup_ctx; }; @@ -531,13 +528,11 @@ static void release_down_cmd(struct input_ctx *ictx, bool drop_current) } // We don't want the append to the command queue indefinitely, because that -// could lead to situations where recovery would take too long. On the other -// hand, don't drop commands that will abort playback. +// could lead to situations where recovery would take too long. static bool should_drop_cmd(struct input_ctx *ictx, struct mp_cmd *cmd) { struct cmd_queue *queue = &ictx->cmd_queue; - return queue_count_cmds(queue) >= ictx->opts->key_fifo_size && - !mp_input_is_abort_cmd(cmd); + return queue_count_cmds(queue) >= ictx->opts->key_fifo_size; } static struct mp_cmd *resolve_key(struct input_ctx *ictx, int code) @@ -883,26 +878,10 @@ static void adjust_max_wait_time(struct input_ctx *ictx, double *time) } } -static bool test_abort_cmd(struct input_ctx *ictx, struct mp_cmd *new) -{ - if (!mp_input_is_maybe_abort_cmd(new)) - return false; - if (mp_input_is_abort_cmd(new)) - return true; - // Abort only if there are going to be at least 2 commands in the queue. - for (struct mp_cmd *cmd = ictx->cmd_queue.first; cmd; cmd = cmd->queue_next) { - if (mp_input_is_maybe_abort_cmd(cmd)) - return true; - } - return false; -} - int mp_input_queue_cmd(struct input_ctx *ictx, mp_cmd_t *cmd) { input_lock(ictx); if (cmd) { - if (ictx->cancel && test_abort_cmd(ictx, cmd)) - ictx->cancel(ictx->cancel_ctx); queue_add_tail(&ictx->cmd_queue, cmd); mp_input_wakeup(ictx); } @@ -1391,8 +1370,11 @@ void mp_input_load_config(struct input_ctx *ictx) } #if HAVE_WIN32_PIPES - if (ictx->global->opts->input_file && *ictx->global->opts->input_file) - mp_input_pipe_add(ictx, ictx->global->opts->input_file); + char *ifile; + mp_read_option_raw(ictx->global, "input-file", &m_option_type_string, &ifile); + if (ifile && ifile[0]) + mp_input_pipe_add(ictx, ifile); + talloc_free(ifile); #endif input_unlock(ictx); @@ -1423,14 +1405,6 @@ void mp_input_uninit(struct input_ctx *ictx) talloc_free(ictx); } -void mp_input_set_cancel(struct input_ctx *ictx, void (*cb)(void *c), void *c) -{ - input_lock(ictx); - ictx->cancel = cb; - ictx->cancel_ctx = c; - input_unlock(ictx); -} - bool mp_input_use_alt_gr(struct input_ctx *ictx) { input_lock(ictx); diff --git a/input/input.h b/input/input.h index 1641f9fad6..ff194785c9 100644 --- a/input/input.h +++ b/input/input.h @@ -193,10 +193,6 @@ double mp_input_get_delay(struct input_ctx *ictx); // Wake up sleeping input loop from another thread. void mp_input_wakeup(struct input_ctx *ictx); -// Used to asynchronously abort playback. Needed because the core still can -// block on network in some situations. -void mp_input_set_cancel(struct input_ctx *ictx, void (*cb)(void *c), void *c); - // If this returns true, use Right Alt key as Alt Gr to produce special // characters. If false, count Right Alt as the modifier Alt key. bool mp_input_use_alt_gr(struct input_ctx *ictx); diff --git a/input/ipc-unix.c b/input/ipc-unix.c index a9cb66e2c6..94a0b4700b 100644 --- a/input/ipc-unix.c +++ b/input/ipc-unix.c @@ -36,6 +36,7 @@ #include "common/msg.h" #include "input/input.h" #include "libmpv/client.h" +#include "options/m_config.h" #include "options/options.h" #include "options/path.h" #include "player/client.h" @@ -386,7 +387,7 @@ done: struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api, struct mpv_global *global) { - struct MPOpts *opts = global->opts; + struct MPOpts *opts = mp_get_config_group(NULL, global, GLOBAL_CONFIG); struct mp_ipc_ctx *arg = talloc_ptrtype(NULL, arg); *arg = (struct mp_ipc_ctx){ @@ -397,10 +398,12 @@ struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api, }; char *input_file = mp_get_user_path(arg, global, opts->input_file); + talloc_free(opts); + if (input_file && *input_file) ipc_start_client_text(arg, input_file); - if (!opts->ipc_path || !*opts->ipc_path) + if (!arg->path || !arg->path[0]) goto out; if (mp_make_wakeup_pipe(arg->death_pipe) < 0) diff --git a/input/ipc-win.c b/input/ipc-win.c index 3cbdad3749..8bfbaf409b 100644 --- a/input/ipc-win.c +++ b/input/ipc-win.c @@ -29,6 +29,7 @@ #include "common/msg.h" #include "input/input.h" #include "libmpv/client.h" +#include "options/m_config.h" #include "options/options.h" #include "player/client.h" @@ -449,7 +450,7 @@ done: struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api, struct mpv_global *global) { - struct MPOpts *opts = global->opts; + struct MPOpts *opts = mp_get_config_group(NULL, global, GLOBAL_CONFIG); struct mp_ipc_ctx *arg = talloc_ptrtype(NULL, arg); *arg = (struct mp_ipc_ctx){ @@ -478,12 +479,14 @@ struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api, if (pthread_create(&arg->thread, NULL, ipc_thread, arg)) goto out; + talloc_free(opts); return arg; out: if (arg->death_event) CloseHandle(arg->death_event); talloc_free(arg); + talloc_free(opts); return NULL; } diff --git a/input/ipc.c b/input/ipc.c index 3a735dab14..6568feacfa 100644 --- a/input/ipc.c +++ b/input/ipc.c @@ -20,23 +20,12 @@ #include "common/msg.h" #include "input/input.h" #include "misc/json.h" +#include "misc/node.h" #include "options/m_option.h" #include "options/options.h" #include "options/path.h" #include "player/client.h" -static mpv_node *mpv_node_map_get(mpv_node *src, const char *key) -{ - if (src->format != MPV_FORMAT_NODE_MAP) - return NULL; - - for (int i = 0; i < src->u.list->num; i++) - if (!strcmp(key, src->u.list->keys[i])) - return &src->u.list->values[i]; - - return NULL; -} - static mpv_node *mpv_node_array_get(mpv_node *src, int index) { if (src->format != MPV_FORMAT_NODE_ARRAY) @@ -217,9 +206,13 @@ static char *json_execute_command(struct mpv_handle *client, void *ta_parent, goto error; } - reqid_node = mpv_node_map_get(&msg_node, "request_id"); + reqid_node = node_map_get(&msg_node, "request_id"); + if (reqid_node && reqid_node->format != MPV_FORMAT_INT64) { + mp_warn(log, "'request_id' must be an integer. Using other types is " + "deprecated and will trigger an error in the future!\n"); + } - mpv_node *cmd_node = mpv_node_map_get(&msg_node, "command"); + mpv_node *cmd_node = node_map_get(&msg_node, "command"); if (!cmd_node || (cmd_node->format != MPV_FORMAT_NODE_ARRAY) || !cmd_node->u.list->num) @@ -415,6 +408,8 @@ error: */ if (reqid_node) { mpv_node_map_add(ta_parent, &reply_node, "request_id", reqid_node); + } else { + mpv_node_map_add_int64(ta_parent, &reply_node, "request_id", 0); } mpv_node_map_add_string(ta_parent, &reply_node, "error", mpv_error_string(rc)); |