Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * This file is part of mpv. |
3 | | * |
4 | | * mpv is free software; you can redistribute it and/or |
5 | | * modify it under the terms of the GNU Lesser General Public |
6 | | * License as published by the Free Software Foundation; either |
7 | | * version 2.1 of the License, or (at your option) any later version. |
8 | | * |
9 | | * mpv is distributed in the hope that it will be useful, |
10 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | * GNU Lesser General Public License for more details. |
13 | | * |
14 | | * You should have received a copy of the GNU Lesser General Public |
15 | | * License along with mpv. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | |
18 | | #include <stddef.h> |
19 | | |
20 | | #include "misc/bstr.h" |
21 | | #include "misc/node.h" |
22 | | #include "common/common.h" |
23 | | #include "common/msg.h" |
24 | | #include "options/m_option.h" |
25 | | |
26 | | #include "cmd.h" |
27 | | #include "input.h" |
28 | | #include "misc/json.h" |
29 | | |
30 | | #include "mpv/client.h" |
31 | | |
32 | | static void destroy_cmd(void *ptr) |
33 | 28.1M | { |
34 | 28.1M | struct mp_cmd *cmd = ptr; |
35 | 80.6M | for (int n = 0; n < cmd->nargs; n++) { |
36 | 52.4M | if (cmd->args[n].type) |
37 | 52.4M | m_option_free(cmd->args[n].type, &cmd->args[n].v); |
38 | 52.4M | } |
39 | 28.1M | } |
40 | | |
41 | | struct flag { |
42 | | const char *name; |
43 | | unsigned int remove, add; |
44 | | }; |
45 | | |
46 | | static const struct flag cmd_flags[] = { |
47 | | {"no-osd", MP_ON_OSD_FLAGS, MP_ON_OSD_NO}, |
48 | | {"osd-bar", MP_ON_OSD_FLAGS, MP_ON_OSD_BAR}, |
49 | | {"osd-msg", MP_ON_OSD_FLAGS, MP_ON_OSD_MSG}, |
50 | | {"osd-msg-bar", MP_ON_OSD_FLAGS, MP_ON_OSD_MSG | MP_ON_OSD_BAR}, |
51 | | {"osd-auto", MP_ON_OSD_FLAGS, MP_ON_OSD_AUTO}, |
52 | | {"expand-properties", 0, MP_EXPAND_PROPERTIES}, |
53 | | {"raw", MP_EXPAND_PROPERTIES, 0}, |
54 | | {"repeatable", MP_DISALLOW_REPEAT, MP_ALLOW_REPEAT}, |
55 | | {"nonrepeatable", MP_ALLOW_REPEAT, MP_DISALLOW_REPEAT}, |
56 | | {"nonscalable", 0, MP_DISALLOW_SCALE}, |
57 | | {"async", MP_SYNC_CMD, MP_ASYNC_CMD}, |
58 | | {"sync", MP_ASYNC_CMD, MP_SYNC_CMD}, |
59 | | {0} |
60 | | }; |
61 | | |
62 | | static bool apply_flag(struct mp_cmd *cmd, bstr str) |
63 | 28.2M | { |
64 | 346M | for (int n = 0; cmd_flags[n].name; n++) { |
65 | 319M | if (bstr_equals0(str, cmd_flags[n].name)) { |
66 | 1.74M | cmd->flags = (cmd->flags & ~cmd_flags[n].remove) | cmd_flags[n].add; |
67 | 1.74M | return true; |
68 | 1.74M | } |
69 | 319M | } |
70 | 26.5M | return false; |
71 | 28.2M | } |
72 | | |
73 | | static bool find_cmd(struct mp_log *log, struct mp_cmd *cmd, bstr name) |
74 | 26.5M | { |
75 | 26.5M | if (name.len == 0) { |
76 | 45.5k | mp_err(log, "Command name missing.\n"); |
77 | 45.5k | return false; |
78 | 45.5k | } |
79 | | |
80 | 26.4M | char nname[80]; |
81 | 26.4M | snprintf(nname, sizeof(nname), "%.*s", BSTR_P(name)); |
82 | 184M | for (int n = 0; nname[n]; n++) { |
83 | 158M | if (nname[n] == '_') |
84 | 1.02k | nname[n] = '-'; |
85 | 158M | } |
86 | | |
87 | 1.05G | for (int n = 0; mp_cmds[n].name; n++) { |
88 | 1.05G | if (strcmp(nname, mp_cmds[n].name) == 0) { |
89 | 26.4M | cmd->def = &mp_cmds[n]; |
90 | 26.4M | cmd->name = (char *)cmd->def->name; |
91 | 26.4M | return true; |
92 | 26.4M | } |
93 | 1.05G | } |
94 | 15.0k | mp_err(log, "Command '%.*s' not found.\n", BSTR_P(name)); |
95 | 15.0k | return false; |
96 | 26.4M | } |
97 | | |
98 | | static bool is_vararg(const struct mp_cmd_def *m, int i) |
99 | 118M | { |
100 | 118M | return m->vararg && (i + 1 >= MP_CMD_DEF_MAX_ARGS || !m->args[i + 1].type); |
101 | 118M | } |
102 | | |
103 | | static const struct m_option *get_arg_type(const struct mp_cmd_def *cmd, int i) |
104 | 105M | { |
105 | 105M | const struct m_option *opt = NULL; |
106 | 105M | if (is_vararg(cmd, i)) { |
107 | | // The last arg in a vararg command sets all vararg types. |
108 | 4.60M | for (int n = MPMIN(i, MP_CMD_DEF_MAX_ARGS - 1); n >= 0; n--) { |
109 | 4.60M | if (cmd->args[n].type) { |
110 | 2.16M | opt = &cmd->args[n]; |
111 | 2.16M | break; |
112 | 2.16M | } |
113 | 4.60M | } |
114 | 102M | } else if (i < MP_CMD_DEF_MAX_ARGS) { |
115 | 102M | opt = &cmd->args[i]; |
116 | 102M | } |
117 | 105M | return opt && opt->type ? opt : NULL; |
118 | 105M | } |
119 | | |
120 | | // Return the name of the argument, possibly as stack allocated string (which is |
121 | | // why this is a macro, and out of laziness). Otherwise as get_arg_type(). |
122 | | #define get_arg_name(cmd, i) \ |
123 | 106k | ((i) < MP_CMD_DEF_MAX_ARGS && (cmd)->args[(i)].name && \ |
124 | 106k | (cmd)->args[(i)].name[0] \ |
125 | 106k | ? (cmd)->args[(i)].name : mp_tprintf(10, "%d", (i) + 1)) |
126 | | |
127 | | // Verify that there are no missing args, fill in missing optional args. |
128 | | static bool finish_cmd(struct mp_log *log, struct mp_cmd *cmd) |
129 | 26.4M | { |
130 | 78.8M | for (int i = 0; i < MP_CMD_DEF_MAX_ARGS; i++) { |
131 | | // (type==NULL is used for yet unset arguments) |
132 | 78.8M | if (i < cmd->nargs && cmd->args[i].type) |
133 | 39.3M | continue; |
134 | 39.4M | const struct m_option *opt = get_arg_type(cmd->def, i); |
135 | 39.4M | if (i >= cmd->nargs && (!opt || is_vararg(cmd->def, i))) |
136 | 26.4M | break; |
137 | 13.0M | if (!opt->defval && !(opt->flags & MP_CMD_OPT_ARG)) { |
138 | 1.35k | mp_err(log, "Command %s: required argument %s not set.\n", |
139 | 1.35k | cmd->name, get_arg_name(cmd->def, i)); |
140 | 1.35k | return false; |
141 | 1.35k | } |
142 | 13.0M | struct mp_cmd_arg arg = {.type = opt}; |
143 | 13.0M | if (opt->defval) |
144 | 7.93M | m_option_copy(opt, &arg.v, opt->defval); |
145 | 13.0M | mp_assert(i <= cmd->nargs); |
146 | 13.0M | if (i == cmd->nargs) { |
147 | 13.0M | MP_TARRAY_APPEND(cmd, cmd->args, cmd->nargs, arg); |
148 | 13.0M | } else { |
149 | 0 | cmd->args[i] = arg; |
150 | 0 | } |
151 | 13.0M | } |
152 | | |
153 | 26.4M | if (!(cmd->flags & (MP_ASYNC_CMD | MP_SYNC_CMD))) |
154 | 26.4M | cmd->flags |= cmd->def->default_async ? MP_ASYNC_CMD : MP_SYNC_CMD; |
155 | | |
156 | 26.4M | return true; |
157 | 26.4M | } |
158 | | |
159 | | static bool set_node_arg(struct mp_log *log, struct mp_cmd *cmd, int i, |
160 | | mpv_node *val) |
161 | 106k | { |
162 | 106k | const char *name = get_arg_name(cmd->def, i); |
163 | | |
164 | 106k | const struct m_option *opt = get_arg_type(cmd->def, i); |
165 | 106k | if (!opt) { |
166 | 0 | mp_err(log, "Command %s: has only %d arguments.\n", cmd->name, i); |
167 | 0 | return false; |
168 | 0 | } |
169 | | |
170 | 106k | if (i < cmd->nargs && cmd->args[i].type) { |
171 | 0 | mp_err(log, "Command %s: argument %s was already set.\n", cmd->name, name); |
172 | 0 | return false; |
173 | 0 | } |
174 | | |
175 | 106k | struct mp_cmd_arg arg = {.type = opt}; |
176 | 106k | void *dst = &arg.v; |
177 | 106k | int r = m_option_set_node_or_string(log, opt, bstr0(cmd->name), dst, val); |
178 | 106k | if (r < 0) { |
179 | 0 | if (val->format == MPV_FORMAT_STRING) { |
180 | 0 | mp_err(log, "Command %s: argument %s can't be parsed: %s.\n", |
181 | 0 | cmd->name, name, m_option_strerror(r)); |
182 | 0 | } else { |
183 | 0 | mp_err(log, "Command %s: argument %s has incompatible type.\n", |
184 | 0 | cmd->name, name); |
185 | 0 | } |
186 | 0 | return false; |
187 | 0 | } |
188 | | |
189 | | // (leave unset arguments blank, to be set later or checked by finish_cmd()) |
190 | 213k | while (i >= cmd->nargs) { |
191 | 106k | struct mp_cmd_arg t = {0}; |
192 | 106k | MP_TARRAY_APPEND(cmd, cmd->args, cmd->nargs, t); |
193 | 106k | } |
194 | | |
195 | 106k | cmd->args[i] = arg; |
196 | 106k | return true; |
197 | 106k | } |
198 | | |
199 | | static bool cmd_node_array(struct mp_log *log, struct mp_cmd *cmd, mpv_node *node) |
200 | 247k | { |
201 | 247k | mp_assert(node->format == MPV_FORMAT_NODE_ARRAY); |
202 | 247k | mpv_node_list *args = node->u.list; |
203 | 247k | int cur = 0; |
204 | | |
205 | 247k | while (cur < args->num) { |
206 | 247k | if (args->values[cur].format != MPV_FORMAT_STRING) |
207 | 0 | break; |
208 | 247k | if (!apply_flag(cmd, bstr0(args->values[cur].u.string))) |
209 | 247k | break; |
210 | 0 | cur++; |
211 | 0 | } |
212 | | |
213 | 247k | bstr cmd_name = {0}; |
214 | 247k | if (cur < args->num && args->values[cur].format == MPV_FORMAT_STRING) |
215 | 247k | cmd_name = bstr0(args->values[cur++].u.string); |
216 | 247k | if (!find_cmd(log, cmd, cmd_name)) |
217 | 0 | return false; |
218 | | |
219 | 247k | int first = cur; |
220 | 354k | for (int i = 0; i < args->num - first; i++) { |
221 | 106k | if (!set_node_arg(log, cmd, cmd->nargs, &args->values[cur++])) |
222 | 0 | return false; |
223 | 106k | } |
224 | | |
225 | 247k | return true; |
226 | 247k | } |
227 | | |
228 | | static bool cmd_node_map(struct mp_log *log, struct mp_cmd *cmd, mpv_node *node) |
229 | 0 | { |
230 | 0 | mp_assert(node->format == MPV_FORMAT_NODE_MAP); |
231 | 0 | mpv_node_list *args = node->u.list; |
232 | |
|
233 | 0 | mpv_node *name = node_map_get(node, "name"); |
234 | 0 | if (!name || name->format != MPV_FORMAT_STRING) |
235 | 0 | return false; |
236 | | |
237 | 0 | if (!find_cmd(log, cmd, bstr0(name->u.string))) |
238 | 0 | return false; |
239 | | |
240 | 0 | if (cmd->def->vararg) { |
241 | 0 | mp_err(log, "Command %s: this command uses a variable number of " |
242 | 0 | "arguments, which does not work with named arguments.\n", |
243 | 0 | cmd->name); |
244 | 0 | return false; |
245 | 0 | } |
246 | | |
247 | 0 | for (int n = 0; n < args->num; n++) { |
248 | 0 | const char *key = args->keys[n]; |
249 | 0 | mpv_node *val = &args->values[n]; |
250 | |
|
251 | 0 | if (strcmp(key, "name") == 0) { |
252 | | // already handled above |
253 | 0 | } else if (strcmp(key, "_flags") == 0) { |
254 | 0 | if (val->format != MPV_FORMAT_NODE_ARRAY) |
255 | 0 | return false; |
256 | 0 | mpv_node_list *flags = val->u.list; |
257 | 0 | for (int i = 0; i < flags->num; i++) { |
258 | 0 | if (flags->values[i].format != MPV_FORMAT_STRING) |
259 | 0 | return false; |
260 | 0 | if (!apply_flag(cmd, bstr0(flags->values[i].u.string))) |
261 | 0 | return false; |
262 | 0 | } |
263 | 0 | } else { |
264 | 0 | int arg = -1; |
265 | |
|
266 | 0 | for (int i = 0; i < MP_CMD_DEF_MAX_ARGS; i++) { |
267 | 0 | const char *arg_name = cmd->def->args[i].name; |
268 | 0 | if (arg_name && arg_name[0] && strcmp(key, arg_name) == 0) { |
269 | 0 | arg = i; |
270 | 0 | break; |
271 | 0 | } |
272 | 0 | } |
273 | |
|
274 | 0 | if (arg < 0) { |
275 | 0 | mp_err(log, "Command %s: no argument %s.\n", cmd->name, key); |
276 | 0 | return false; |
277 | 0 | } |
278 | | |
279 | 0 | if (!set_node_arg(log, cmd, arg, val)) |
280 | 0 | return false; |
281 | 0 | } |
282 | 0 | } |
283 | | |
284 | 0 | return true; |
285 | 0 | } |
286 | | |
287 | | struct mp_cmd *mp_input_parse_cmd_node(struct mp_log *log, mpv_node *node) |
288 | 247k | { |
289 | 247k | struct mp_cmd *cmd = talloc_ptrtype(NULL, cmd); |
290 | 247k | talloc_set_destructor(cmd, destroy_cmd); |
291 | 247k | *cmd = (struct mp_cmd) { .scale = 1, .scale_units = 1 }; |
292 | | |
293 | 247k | bool res = false; |
294 | 247k | if (node->format == MPV_FORMAT_NODE_ARRAY) { |
295 | 247k | res = cmd_node_array(log, cmd, node); |
296 | 247k | } else if (node->format == MPV_FORMAT_NODE_MAP) { |
297 | 0 | res = cmd_node_map(log, cmd, node); |
298 | 0 | } |
299 | | |
300 | 247k | res = res && finish_cmd(log, cmd); |
301 | | |
302 | 247k | if (!res) |
303 | 0 | TA_FREEP(&cmd); |
304 | | |
305 | 247k | return cmd; |
306 | 247k | } |
307 | | |
308 | | static bool read_token(bstr str, bstr *out_rest, bstr *out_token) |
309 | 105M | { |
310 | 105M | bstr t = bstr_lstrip(str); |
311 | 105M | int next = bstrcspn(t, WHITESPACE "#;"); |
312 | 105M | if (!next) |
313 | 37.4M | return false; |
314 | 68.1M | *out_token = bstr_splice(t, 0, next); |
315 | 68.1M | *out_rest = bstr_cut(t, next); |
316 | 68.1M | return true; |
317 | 105M | } |
318 | | |
319 | | struct parse_ctx { |
320 | | struct mp_log *log; |
321 | | void *tmp; |
322 | | bstr start, str; |
323 | | }; |
324 | | |
325 | | static int pctx_read_token(struct parse_ctx *ctx, bstr *out) |
326 | 78.6M | { |
327 | 78.6M | *out = (bstr){0}; |
328 | 78.6M | ctx->str = bstr_lstrip(ctx->str); |
329 | 78.6M | bstr start = ctx->str; |
330 | 78.6M | if (bstr_eatstart0(&ctx->str, "\"")) { |
331 | 1.28M | if (!mp_append_escaped_string_noalloc(ctx->tmp, out, &ctx->str)) { |
332 | 2.81k | MP_ERR(ctx, "Broken string escapes: ...>%.*s<.\n", BSTR_P(start)); |
333 | 2.81k | return -1; |
334 | 2.81k | } |
335 | 1.27M | if (!bstr_eatstart0(&ctx->str, "\"")) { |
336 | 1.73k | MP_ERR(ctx, "Unterminated double quote: ...>%.*s<.\n", BSTR_P(start)); |
337 | 1.73k | return -1; |
338 | 1.73k | } |
339 | 1.27M | return 1; |
340 | 1.27M | } |
341 | 77.3M | if (bstr_eatstart0(&ctx->str, "'")) { |
342 | 164k | int next = bstrchr(ctx->str, '\''); |
343 | 164k | if (next < 0) { |
344 | 500 | MP_ERR(ctx, "Unterminated single quote: ...>%.*s<.\n", BSTR_P(start)); |
345 | 500 | return -1; |
346 | 500 | } |
347 | 164k | *out = bstr_splice(ctx->str, 0, next); |
348 | 164k | ctx->str = bstr_cut(ctx->str, next+1); |
349 | 164k | return 1; |
350 | 164k | } |
351 | 77.1M | if (ctx->start.len > 1 && bstr_eatstart0(&ctx->str, "`")) { |
352 | 3.13k | char endquote[2] = {ctx->str.start[0], '`'}; |
353 | 3.13k | ctx->str = bstr_cut(ctx->str, 1); |
354 | 3.13k | int next = bstr_find(ctx->str, (bstr){endquote, 2}); |
355 | 3.13k | if (next < 0) { |
356 | 526 | MP_ERR(ctx, "Unterminated custom quote: ...>%.*s<.\n", BSTR_P(start)); |
357 | 526 | return -1; |
358 | 526 | } |
359 | 2.60k | *out = bstr_splice(ctx->str, 0, next); |
360 | 2.60k | ctx->str = bstr_cut(ctx->str, next+2); |
361 | 2.60k | return 1; |
362 | 3.13k | } |
363 | | |
364 | 77.1M | return read_token(ctx->str, &ctx->str, out) ? 1 : 0; |
365 | 77.1M | } |
366 | | |
367 | | static struct mp_cmd *parse_cmd_str(struct mp_log *log, void *tmp, |
368 | | bstr *str, const char *loc) |
369 | 26.2M | { |
370 | 26.2M | struct parse_ctx *ctx = &(struct parse_ctx){ |
371 | 26.2M | .log = log, |
372 | 26.2M | .tmp = tmp, |
373 | 26.2M | .str = *str, |
374 | 26.2M | .start = *str, |
375 | 26.2M | }; |
376 | | |
377 | 26.2M | struct mp_cmd *cmd = talloc_ptrtype(NULL, cmd); |
378 | 26.2M | talloc_set_destructor(cmd, destroy_cmd); |
379 | 26.2M | *cmd = (struct mp_cmd) { |
380 | 26.2M | .flags = MP_ON_OSD_AUTO | MP_EXPAND_PROPERTIES, |
381 | 26.2M | .scale = 1, |
382 | 26.2M | .scale_units = 1, |
383 | 26.2M | }; |
384 | | |
385 | 26.2M | ctx->str = bstr_lstrip(ctx->str); |
386 | | |
387 | 26.2M | bstr cur_token; |
388 | 26.2M | if (pctx_read_token(ctx, &cur_token) < 0) |
389 | 4.95k | goto error; |
390 | | |
391 | 28.0M | while (1) { |
392 | 28.0M | if (!apply_flag(cmd, cur_token)) |
393 | 26.2M | break; |
394 | 1.74M | if (pctx_read_token(ctx, &cur_token) < 0) |
395 | 195 | goto error; |
396 | 1.74M | } |
397 | | |
398 | 26.2M | if (!find_cmd(ctx->log, cmd, cur_token)) |
399 | 60.5k | goto error; |
400 | | |
401 | 65.5M | for (int i = 0; i < MP_CMD_MAX_ARGS; i++) { |
402 | 65.5M | const struct m_option *opt = get_arg_type(cmd->def, i); |
403 | 65.5M | if (!opt) |
404 | 14.9M | break; |
405 | | |
406 | 50.6M | int r = pctx_read_token(ctx, &cur_token); |
407 | 50.6M | if (r < 0) { |
408 | 425 | MP_ERR(ctx, "Command %s: error in argument %d.\n", cmd->name, i + 1); |
409 | 425 | goto error; |
410 | 425 | } |
411 | 50.6M | if (r < 1) |
412 | 11.2M | break; |
413 | | |
414 | 39.3M | struct mp_cmd_arg arg = {.type = opt}; |
415 | 39.3M | r = m_option_parse(ctx->log, opt, bstr0(cmd->name), cur_token, &arg.v); |
416 | 39.3M | if (r < 0) { |
417 | 50.1k | MP_ERR(ctx, "Command %s: argument %d can't be parsed: %s.\n", |
418 | 50.1k | cmd->name, i + 1, m_option_strerror(r)); |
419 | 50.1k | goto error; |
420 | 50.1k | } |
421 | | |
422 | 39.3M | MP_TARRAY_APPEND(cmd, cmd->args, cmd->nargs, arg); |
423 | 39.3M | } |
424 | | |
425 | 26.1M | if (!finish_cmd(ctx->log, cmd)) |
426 | 1.35k | goto error; |
427 | | |
428 | 26.1M | bstr dummy; |
429 | 26.1M | if (read_token(ctx->str, &dummy, &dummy) && ctx->str.len) { |
430 | 1.31k | MP_ERR(ctx, "Command %s has trailing unused arguments: '%.*s'.\n", |
431 | 1.31k | cmd->name, BSTR_P(ctx->str)); |
432 | | // Better make it fatal to make it clear something is wrong. |
433 | 1.31k | goto error; |
434 | 1.31k | } |
435 | | |
436 | 26.1M | bstr orig = {ctx->start.start, ctx->str.start - ctx->start.start}; |
437 | 26.1M | cmd->original = bstrto0(cmd, bstr_strip(orig)); |
438 | | |
439 | 26.1M | *str = ctx->str; |
440 | 26.1M | return cmd; |
441 | | |
442 | 118k | error: |
443 | 118k | MP_ERR(ctx, "Command was defined at %s.\n", loc); |
444 | 118k | talloc_free(cmd); |
445 | 118k | *str = ctx->str; |
446 | 118k | return NULL; |
447 | 26.1M | } |
448 | | |
449 | | mp_cmd_t *mp_input_parse_cmd_str(struct mp_log *log, bstr str, const char *loc) |
450 | 24.0M | { |
451 | 24.0M | void *tmp = talloc_new(NULL); |
452 | 24.0M | bstr original = str; |
453 | 24.0M | struct mp_cmd *cmd = parse_cmd_str(log, tmp, &str, loc); |
454 | 24.0M | if (!cmd) |
455 | 117k | goto done; |
456 | | |
457 | | // Handle "multi" commands |
458 | 23.9M | struct mp_cmd **p_prev = NULL; |
459 | 26.1M | while (1) { |
460 | 26.1M | str = bstr_lstrip(str); |
461 | | // read_token just to check whether it's trailing whitespace only |
462 | 26.1M | bstr u1, u2; |
463 | 26.1M | if (!bstr_eatstart0(&str, ";") || !read_token(str, &u1, &u2)) |
464 | 23.8M | break; |
465 | | // Multi-command. Since other input.c code uses queue_next for its |
466 | | // own purposes, a pseudo-command is used to wrap the command list. |
467 | 2.25M | if (!p_prev) { |
468 | 1.61M | struct mp_cmd *list = talloc_ptrtype(NULL, list); |
469 | 1.61M | talloc_set_destructor(list, destroy_cmd); |
470 | 1.61M | *list = (struct mp_cmd) { |
471 | 1.61M | .name = (char *)mp_cmd_list.name, |
472 | 1.61M | .def = &mp_cmd_list, |
473 | 1.61M | }; |
474 | 1.61M | talloc_steal(list, cmd); |
475 | 1.61M | struct mp_cmd_arg arg = {0}; |
476 | 1.61M | arg.v.p = cmd; |
477 | 1.61M | list->args = talloc_dup(list, &arg); |
478 | 1.61M | p_prev = &cmd->queue_next; |
479 | 1.61M | cmd = list; |
480 | 1.61M | } |
481 | 2.25M | struct mp_cmd *sub = parse_cmd_str(log, tmp, &str, loc); |
482 | 2.25M | if (!sub) { |
483 | 1.78k | talloc_free(cmd); |
484 | 1.78k | cmd = NULL; |
485 | 1.78k | goto done; |
486 | 1.78k | } |
487 | 2.25M | talloc_steal(cmd, sub); |
488 | 2.25M | *p_prev = sub; |
489 | 2.25M | p_prev = &sub->queue_next; |
490 | 2.25M | } |
491 | | |
492 | 23.8M | cmd->original = bstrto0(cmd, bstr_strip( |
493 | 23.8M | bstr_splice(original, 0, str.start - original.start))); |
494 | | |
495 | 23.8M | str = bstr_strip(str); |
496 | 23.8M | if (bstr_eatstart0(&str, "#") && !bstr_startswith0(str, "#")) { |
497 | 18.2M | str = bstr_strip(str); |
498 | 18.2M | if (str.len) |
499 | 18.2M | cmd->desc = bstrto0(cmd, str); |
500 | 18.2M | } |
501 | | |
502 | 24.0M | done: |
503 | 24.0M | talloc_free(tmp); |
504 | 24.0M | return cmd; |
505 | 23.8M | } |
506 | | |
507 | | struct mp_cmd *mp_input_parse_cmd_strv(struct mp_log *log, const char **argv) |
508 | 247k | { |
509 | 247k | int count = 0; |
510 | 601k | while (argv[count]) |
511 | 354k | count++; |
512 | 247k | mpv_node *items = talloc_zero_array(NULL, mpv_node, count); |
513 | 247k | mpv_node_list list = {.values = items, .num = count}; |
514 | 247k | mpv_node node = {.format = MPV_FORMAT_NODE_ARRAY, .u = {.list = &list}}; |
515 | 601k | for (int n = 0; n < count; n++) { |
516 | 354k | items[n] = (mpv_node){.format = MPV_FORMAT_STRING, |
517 | 354k | .u = {.string = (char *)argv[n]}}; |
518 | 354k | } |
519 | 247k | struct mp_cmd *res = mp_input_parse_cmd_node(log, &node); |
520 | 247k | talloc_free(items); |
521 | 247k | return res; |
522 | 247k | } |
523 | | |
524 | | void mp_cmd_free(mp_cmd_t *cmd) |
525 | 0 | { |
526 | 0 | talloc_free(cmd); |
527 | 0 | } |
528 | | |
529 | | mp_cmd_t *mp_cmd_clone(mp_cmd_t *cmd) |
530 | 0 | { |
531 | 0 | if (!cmd) |
532 | 0 | return NULL; |
533 | | |
534 | 0 | mp_cmd_t *ret = talloc_dup(NULL, cmd); |
535 | 0 | talloc_set_destructor(ret, destroy_cmd); |
536 | 0 | ret->name = talloc_strdup(ret, cmd->name); |
537 | 0 | ret->args = talloc_zero_array(ret, struct mp_cmd_arg, ret->nargs); |
538 | 0 | for (int i = 0; i < ret->nargs; i++) { |
539 | 0 | ret->args[i].type = cmd->args[i].type; |
540 | 0 | m_option_copy(ret->args[i].type, &ret->args[i].v, &cmd->args[i].v); |
541 | 0 | } |
542 | 0 | ret->original = talloc_strdup(ret, cmd->original); |
543 | 0 | ret->desc = talloc_strdup(ret, cmd->desc); |
544 | 0 | ret->sender = NULL; |
545 | 0 | ret->key_name = talloc_strdup(ret, ret->key_name); |
546 | 0 | ret->key_text = talloc_strdup(ret, ret->key_text); |
547 | |
|
548 | 0 | if (cmd->def == &mp_cmd_list) { |
549 | 0 | struct mp_cmd *prev = NULL; |
550 | 0 | for (struct mp_cmd *sub = cmd->args[0].v.p; sub; sub = sub->queue_next) { |
551 | 0 | sub = mp_cmd_clone(sub); |
552 | 0 | talloc_steal(ret, sub); |
553 | 0 | if (prev) { |
554 | 0 | prev->queue_next = sub; |
555 | 0 | } else { |
556 | 0 | struct mp_cmd_arg arg = {0}; |
557 | 0 | arg.v.p = sub; |
558 | 0 | ret->args = talloc_dup(ret, &arg); |
559 | 0 | } |
560 | 0 | prev = sub; |
561 | 0 | } |
562 | 0 | } |
563 | |
|
564 | 0 | return ret; |
565 | 0 | } |
566 | | |
567 | | static int get_arg_count(const struct mp_cmd_def *cmd) |
568 | 230k | { |
569 | 2.27M | for (int i = MP_CMD_DEF_MAX_ARGS - 1; i >= 0; i--) { |
570 | 2.27M | if (cmd->args[i].type) |
571 | 230k | return i + 1; |
572 | 2.27M | } |
573 | 0 | return 0; |
574 | 230k | } |
575 | | |
576 | | void mp_cmd_dump(struct mp_log *log, int msgl, char *header, struct mp_cmd *cmd) |
577 | 230k | { |
578 | 230k | if (!mp_msg_test(log, msgl)) |
579 | 32 | return; |
580 | 230k | if (header) |
581 | 230k | mp_msg(log, msgl, "%s ", header); |
582 | 230k | if (!cmd) { |
583 | 0 | mp_msg(log, msgl, "(NULL)\n"); |
584 | 0 | return; |
585 | 0 | } |
586 | 230k | mp_msg(log, msgl, "%s, flags=%d, args=[", cmd->name, cmd->flags); |
587 | 230k | int argc = get_arg_count(cmd->def); |
588 | 727k | for (int n = 0; n < cmd->nargs; n++) { |
589 | 496k | const char *argname = cmd->def->args[MPMIN(n, argc - 1)].name; |
590 | 496k | char *s = m_option_print(cmd->args[n].type, &cmd->args[n].v); |
591 | 496k | if (n) |
592 | 265k | mp_msg(log, msgl, ", "); |
593 | 496k | struct mpv_node node = { |
594 | 496k | .format = MPV_FORMAT_STRING, |
595 | 496k | .u.string = s ? s : "(NULL)", |
596 | 496k | }; |
597 | 496k | char *esc = NULL; |
598 | 496k | json_write(&esc, &node); |
599 | 496k | mp_msg(log, msgl, "%s=%s", argname, esc ? esc : "<error>"); |
600 | 496k | talloc_free(esc); |
601 | 496k | talloc_free(s); |
602 | 496k | } |
603 | 230k | mp_msg(log, msgl, "]\n"); |
604 | 230k | } |
605 | | |
606 | | bool mp_input_is_repeatable_cmd(struct mp_cmd *cmd) |
607 | 0 | { |
608 | 0 | if (cmd->def == &mp_cmd_list && cmd->args[0].v.p) |
609 | 0 | cmd = cmd->args[0].v.p; // list - only 1st cmd is considered |
610 | |
|
611 | 0 | return (cmd->def->allow_auto_repeat && !(cmd->flags & MP_DISALLOW_REPEAT)) || |
612 | 0 | (cmd->flags & MP_ALLOW_REPEAT); |
613 | 0 | } |
614 | | |
615 | | bool mp_input_is_scalable_cmd(struct mp_cmd *cmd) |
616 | 0 | { |
617 | 0 | return cmd->def->scalable && !(cmd->flags & MP_DISALLOW_SCALE); |
618 | 0 | } |
619 | | |
620 | | void mp_print_cmd_list(struct mp_log *out) |
621 | 712 | { |
622 | 61.2k | for (int i = 0; mp_cmds[i].name; i++) { |
623 | 60.5k | const struct mp_cmd_def *def = &mp_cmds[i]; |
624 | 60.5k | mp_info(out, "%-25s", def->name); |
625 | 168k | for (int j = 0; j < MP_CMD_DEF_MAX_ARGS && def->args[j].type; j++) { |
626 | 108k | const struct m_option *arg = &def->args[j]; |
627 | 108k | bool is_opt = arg->defval || (arg->flags & MP_CMD_OPT_ARG); |
628 | 108k | mp_info(out, " %s%s=%s%s", is_opt ? "[" : "", arg->name, |
629 | 108k | arg->type->name, is_opt ? "]" : ""); |
630 | 108k | } |
631 | 60.5k | if (def->vararg) |
632 | 2.84k | mp_info(out, "..."); // essentially append to last argument |
633 | 60.5k | mp_info(out, "\n"); |
634 | 60.5k | } |
635 | 712 | } |