640 lines · 19.4 KB · C
RawDownload
/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv.  If not, see <http://www.gnu.org/licenses/>. */ #include <stddef.h> #include "misc/bstr.h"#include "misc/node.h"#include "common/common.h"#include "common/msg.h"#include "options/m_option.h" #include "cmd.h"#include "input.h"#include "misc/json.h" #include "libmpv/client.h" static void destroy_cmd(void *ptr){    struct mp_cmd *cmd = ptr;    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 {    const char *name;    unsigned int remove, add;}; static const struct flag cmd_flags[] = {    {"no-osd",              MP_ON_OSD_FLAGS, MP_ON_OSD_NO},    {"osd-bar",             MP_ON_OSD_FLAGS, MP_ON_OSD_BAR},    {"osd-msg",             MP_ON_OSD_FLAGS, MP_ON_OSD_MSG},    {"osd-msg-bar",         MP_ON_OSD_FLAGS, MP_ON_OSD_MSG | MP_ON_OSD_BAR},    {"osd-auto",            MP_ON_OSD_FLAGS, MP_ON_OSD_AUTO},    {"expand-properties",   0,               MP_EXPAND_PROPERTIES},    {"raw",                 MP_EXPAND_PROPERTIES, 0},    {"repeatable",          MP_DISALLOW_REPEAT, MP_ALLOW_REPEAT},    {"nonrepeatable",       MP_ALLOW_REPEAT,    MP_DISALLOW_REPEAT},    {"nonscalable",         0,               MP_DISALLOW_SCALE},    {"async",               MP_SYNC_CMD,     MP_ASYNC_CMD},    {"sync",                MP_ASYNC_CMD,    MP_SYNC_CMD},    {0}}; static bool apply_flag(struct mp_cmd *cmd, bstr str){    for (int n = 0; cmd_flags[n].name; n++) {        if (bstr_equals0(str, cmd_flags[n].name)) {            cmd->flags = (cmd->flags & ~cmd_flags[n].remove) | cmd_flags[n].add;            return true;        }    }    return false;} static bool find_cmd(struct mp_log *log, struct mp_cmd *cmd, bstr name){    if (name.len == 0) {        mp_err(log, "Command name missing.\n");        return false;    }     char nname[80];    snprintf(nname, sizeof(nname), "%.*s", BSTR_P(name));    for (int n = 0; nname[n]; n++) {        if (nname[n] == '_')            nname[n] = '-';    }     for (int n = 0; mp_cmds[n].name; n++) {        if (strcmp(nname, mp_cmds[n].name) == 0) {            cmd->def = &mp_cmds[n];            cmd->name = (char *)cmd->def->name;            return true;        }    }    mp_err(log, "Command '%.*s' not found.\n", BSTR_P(name));    return false;} static bool is_vararg(const struct mp_cmd_def *m, int i){    return m->vararg && (i + 1 >= MP_CMD_DEF_MAX_ARGS || !m->args[i + 1].type);} static const struct m_option *get_arg_type(const struct mp_cmd_def *cmd, int i){    const struct m_option *opt = NULL;    if (is_vararg(cmd, i)) {        // The last arg in a vararg command sets all vararg types.        for (int n = MPMIN(i, MP_CMD_DEF_MAX_ARGS - 1); n >= 0; n--) {            if (cmd->args[n].type) {                opt = &cmd->args[n];                break;            }        }    } else if (i < MP_CMD_DEF_MAX_ARGS) {        opt = &cmd->args[i];    }    return opt && opt->type ? opt : NULL;} // 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 = 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 (i >= cmd->nargs && (!opt || is_vararg(cmd->def, i)))            break;        if (!opt->defval && !(opt->flags & MP_CMD_OPT_ARG)) {            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);        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;} static bool set_node_arg(struct mp_log *log, struct mp_cmd *cmd, int i,                         mpv_node *val){    const char *name = get_arg_name(cmd->def, 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);        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;     while (cur < args->num) {        if (args->values[cur].format != MPV_FORMAT_STRING)            break;        if (!apply_flag(cmd, bstr0(args->values[cur].u.string)))            break;        cur++;    }     bstr cmd_name = {0};    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))        return false;     int first = cur;    for (int i = 0; i < args->num - first; i++) {        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 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;        }    }     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;} static bool read_token(bstr str, bstr *out_rest, bstr *out_token){    bstr t = bstr_lstrip(str);    int next = bstrcspn(t, WHITESPACE "#;");    if (!next)        return false;    *out_token = bstr_splice(t, 0, next);    *out_rest = bstr_cut(t, next);    return true;} struct parse_ctx {    struct mp_log *log;    void *tmp;    bstr start, str;}; static int pctx_read_token(struct parse_ctx *ctx, bstr *out){    *out = (bstr){0};    ctx->str = bstr_lstrip(ctx->str);    bstr start = ctx->str;    if (bstr_eatstart0(&ctx->str, "\"")) {        if (!mp_append_escaped_string_noalloc(ctx->tmp, out, &ctx->str)) {            MP_ERR(ctx, "Broken string escapes: ...>%.*s<.\n", BSTR_P(start));            return -1;        }        if (!bstr_eatstart0(&ctx->str, "\"")) {            MP_ERR(ctx, "Unterminated double quote: ...>%.*s<.\n", BSTR_P(start));            return -1;        }        return 1;    }    if (bstr_eatstart0(&ctx->str, "'")) {        int next = bstrchr(ctx->str, '\'');        if (next < 0) {            MP_ERR(ctx, "Unterminated single quote: ...>%.*s<.\n", BSTR_P(start));            return -1;        }        *out = bstr_splice(ctx->str, 0, next);        ctx->str = bstr_cut(ctx->str, next+1);        return 1;    }    if (ctx->start.len > 1 && bstr_eatstart0(&ctx->str, "`")) {        char endquote[2] = {ctx->str.start[0], '`'};        ctx->str = bstr_cut(ctx->str, 1);        int next = bstr_find(ctx->str, (bstr){endquote, 2});        if (next < 0) {            MP_ERR(ctx, "Unterminated custom quote: ...>%.*s<.\n", BSTR_P(start));            return -1;        }        *out = bstr_splice(ctx->str, 0, next);        ctx->str = bstr_cut(ctx->str, next+2);        return 1;    }     return read_token(ctx->str, &ctx->str, out) ? 1 : 0;} static struct mp_cmd *parse_cmd_str(struct mp_log *log, void *tmp,                                    bstr *str, const char *loc){    struct parse_ctx *ctx = &(struct parse_ctx){        .log = log,        .tmp = tmp,        .str = *str,        .start = *str,    };     struct mp_cmd *cmd = talloc_ptrtype(NULL, cmd);    talloc_set_destructor(cmd, destroy_cmd);    *cmd = (struct mp_cmd) {        .flags = MP_ON_OSD_AUTO | MP_EXPAND_PROPERTIES,        .scale = 1,        .scale_units = 1,    };     ctx->str = bstr_lstrip(ctx->str);     bstr cur_token;    if (pctx_read_token(ctx, &cur_token) < 0)        goto error;     while (1) {        if (!apply_flag(cmd, cur_token))            break;        if (pctx_read_token(ctx, &cur_token) < 0)            goto error;    }     if (!find_cmd(ctx->log, cmd, cur_token))        goto error;     for (int i = 0; i < MP_CMD_MAX_ARGS; i++) {        const struct m_option *opt = get_arg_type(cmd->def, i);        if (!opt)            break;         int r = pctx_read_token(ctx, &cur_token);        if (r < 0) {            MP_ERR(ctx, "Command %s: error in argument %d.\n", cmd->name, i + 1);            goto error;        }        if (r < 1)            break;         struct mp_cmd_arg arg = {.type = opt};        r = m_option_parse(ctx->log, opt, bstr0(cmd->name), cur_token, &arg.v);        if (r < 0) {            MP_ERR(ctx, "Command %s: argument %d can't be parsed: %s.\n",                   cmd->name, i + 1, m_option_strerror(r));            goto error;        }         MP_TARRAY_APPEND(cmd, cmd->args, cmd->nargs, arg);    }     if (!finish_cmd(ctx->log, cmd))        goto error;     bstr dummy;    if (read_token(ctx->str, &dummy, &dummy) && ctx->str.len) {        MP_ERR(ctx, "Command %s has trailing unused arguments: '%.*s'.\n",               cmd->name, BSTR_P(ctx->str));        // Better make it fatal to make it clear something is wrong.        goto error;    }     bstr orig = {ctx->start.start, ctx->str.start - ctx->start.start};    cmd->original = bstrto0(cmd, bstr_strip(orig));     *str = ctx->str;    return cmd; error:    MP_ERR(ctx, "Command was defined at %s.\n", loc);    talloc_free(cmd);    *str = ctx->str;    return NULL;} mp_cmd_t *mp_input_parse_cmd_str(struct mp_log *log, bstr str, const char *loc){    void *tmp = talloc_new(NULL);    bstr original = str;    struct mp_cmd *cmd = parse_cmd_str(log, tmp, &str, loc);    if (!cmd)        goto done;     // Handle "multi" commands    struct mp_cmd **p_prev = NULL;    while (1) {        str = bstr_lstrip(str);        // read_token just to check whether it's trailing whitespace only        bstr u1, u2;        if (!bstr_eatstart0(&str, ";") || !read_token(str, &u1, &u2))            break;        // Multi-command. Since other input.c code uses queue_next for its        // own purposes, a pseudo-command is used to wrap the command list.        if (!p_prev) {            struct mp_cmd *list = talloc_ptrtype(NULL, list);            talloc_set_destructor(list, destroy_cmd);            *list = (struct mp_cmd) {                .name = (char *)mp_cmd_list.name,                .def = &mp_cmd_list,            };            talloc_steal(list, cmd);            struct mp_cmd_arg arg = {0};            arg.v.p = cmd;            list->args = talloc_dup(list, &arg);            p_prev = &cmd->queue_next;            cmd = list;        }        struct mp_cmd *sub = parse_cmd_str(log, tmp, &str, loc);        if (!sub) {            talloc_free(cmd);            cmd = NULL;            goto done;        }        talloc_steal(cmd, sub);        *p_prev = sub;        p_prev = &sub->queue_next;    }     cmd->original = bstrto0(cmd, bstr_strip(                        bstr_splice(original, 0, str.start - original.start)));     str = bstr_strip(str);

Lines 1–500 of 640.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t