Line | Count | Source |
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 | 30.5M | { |
34 | 30.5M | struct mp_cmd *cmd = ptr; |
35 | 87.5M | for (int n = 0; n < cmd->nargs; n++) { |
36 | 56.9M | if (cmd->args[n].type) |
37 | 56.9M | m_option_free(cmd->args[n].type, &cmd->args[n].v); |
38 | 56.9M | } |
39 | 30.5M | } |
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 | 30.7M | { |
64 | 377M | for (int n = 0; cmd_flags[n].name; n++) { |
65 | 348M | if (bstr_equals0(str, cmd_flags[n].name)) { |
66 | 2.09M | cmd->flags = (cmd->flags & ~cmd_flags[n].remove) | cmd_flags[n].add; |
67 | 2.09M | return true; |
68 | 2.09M | } |
69 | 348M | } |
70 | 28.7M | return false; |
71 | 30.7M | } |
72 | | |
73 | | static bool find_cmd(struct mp_log *log, struct mp_cmd *cmd, bstr name) |
74 | 28.7M | { |
75 | 28.7M | if (name.len == 0) { |
76 | 61.6k | mp_err(log, "Command name missing.\n"); |
77 | 61.6k | return false; |
78 | 61.6k | } |
79 | | |
80 | 28.6M | char nname[80]; |
81 | 28.6M | snprintf(nname, sizeof(nname), "%.*s", BSTR_P(name)); |
82 | 206M | for (int n = 0; nname[n]; n++) { |
83 | 177M | if (nname[n] == '_') |
84 | 2.11k | nname[n] = '-'; |
85 | 177M | } |
86 | | |
87 | 1.15G | for (int n = 0; mp_cmds[n].name; n++) { |
88 | 1.15G | if (strcmp(nname, mp_cmds[n].name) == 0) { |
89 | 28.6M | cmd->def = &mp_cmds[n]; |
90 | 28.6M | cmd->name = (char *)cmd->def->name; |
91 | 28.6M | return true; |
92 | 28.6M | } |
93 | 1.15G | } |
94 | 22.9k | mp_err(log, "Command '%.*s' not found.\n", BSTR_P(name)); |
95 | 22.9k | return false; |
96 | 28.6M | } |
97 | | |
98 | | static bool is_vararg(const struct mp_cmd_def *m, int i) |
99 | 129M | { |
100 | 129M | return m->vararg && (i + 1 >= MP_CMD_DEF_MAX_ARGS || !m->args[i + 1].type); |
101 | 129M | } |
102 | | |
103 | | static const struct m_option *get_arg_type(const struct mp_cmd_def *cmd, int i) |
104 | 113M | { |
105 | 113M | const struct m_option *opt = NULL; |
106 | 113M | if (is_vararg(cmd, i)) { |
107 | | // The last arg in a vararg command sets all vararg types. |
108 | 4.96M | for (int n = MPMIN(i, MP_CMD_DEF_MAX_ARGS - 1); n >= 0; n--) { |
109 | 4.96M | if (cmd->args[n].type) { |
110 | 2.28M | opt = &cmd->args[n]; |
111 | 2.28M | break; |
112 | 2.28M | } |
113 | 4.96M | } |
114 | 111M | } else if (i < MP_CMD_DEF_MAX_ARGS) { |
115 | 111M | opt = &cmd->args[i]; |
116 | 111M | } |
117 | 113M | return opt && opt->type ? opt : NULL; |
118 | 113M | } |
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 | 120k | ((i) < MP_CMD_DEF_MAX_ARGS && (cmd)->args[(i)].name && \ |
124 | 120k | (cmd)->args[(i)].name[0] \ |
125 | 120k | ? (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 | 28.5M | { |
130 | 85.4M | for (int i = 0; i < MP_CMD_DEF_MAX_ARGS; i++) { |
131 | | // (type==NULL is used for yet unset arguments) |
132 | 85.4M | if (i < cmd->nargs && cmd->args[i].type) |
133 | 42.4M | continue; |
134 | 43.0M | const struct m_option *opt = get_arg_type(cmd->def, i); |
135 | 43.0M | if (i >= cmd->nargs && (!opt || is_vararg(cmd->def, i))) |
136 | 28.5M | break; |
137 | 14.4M | if (!opt->defval && !(opt->flags & MP_CMD_OPT_ARG)) { |
138 | 4.30k | mp_err(log, "Command %s: required argument %s not set.\n", |
139 | 4.30k | cmd->name, get_arg_name(cmd->def, i)); |
140 | 4.30k | return false; |
141 | 4.30k | } |
142 | 14.4M | struct mp_cmd_arg arg = {.type = opt}; |
143 | 14.4M | if (opt->defval) |
144 | 8.89M | m_option_copy(opt, &arg.v, opt->defval); |
145 | 14.4M | mp_assert(i <= cmd->nargs); |
146 | 14.4M | if (i == cmd->nargs) { |
147 | 14.4M | MP_TARRAY_APPEND(cmd, cmd->args, cmd->nargs, arg); |
148 | 14.4M | } else { |
149 | 0 | cmd->args[i] = arg; |
150 | 0 | } |
151 | 14.4M | } |
152 | | |
153 | 28.5M | if (!(cmd->flags & (MP_ASYNC_CMD | MP_SYNC_CMD))) |
154 | 28.5M | cmd->flags |= cmd->def->default_async ? MP_ASYNC_CMD : MP_SYNC_CMD; |
155 | | |
156 | 28.5M | return true; |
157 | 28.5M | } |
158 | | |
159 | | static bool set_node_arg(struct mp_log *log, struct mp_cmd *cmd, int i, |
160 | | mpv_node *val) |
161 | 120k | { |
162 | 120k | const char *name = get_arg_name(cmd->def, i); |
163 | | |
164 | 120k | const struct m_option *opt = get_arg_type(cmd->def, i); |
165 | 120k | 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 | 120k | 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 | 120k | struct mp_cmd_arg arg = {.type = opt}; |
176 | 120k | void *dst = &arg.v; |
177 | 120k | int r = m_option_set_node_or_string(log, opt, bstr0(cmd->name), dst, val); |
178 | 120k | 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 | 241k | while (i >= cmd->nargs) { |
191 | 120k | struct mp_cmd_arg t = {0}; |
192 | 120k | MP_TARRAY_APPEND(cmd, cmd->args, cmd->nargs, t); |
193 | 120k | } |
194 | | |
195 | 120k | cmd->args[i] = arg; |
196 | 120k | return true; |
197 | 120k | } |
198 | | |
199 | | static bool cmd_node_array(struct mp_log *log, struct mp_cmd *cmd, mpv_node *node) |
200 | 264k | { |
201 | 264k | mp_assert(node->format == MPV_FORMAT_NODE_ARRAY); |
202 | 264k | mpv_node_list *args = node->u.list; |
203 | 264k | int cur = 0; |
204 | | |
205 | 264k | while (cur < args->num) { |
206 | 264k | if (args->values[cur].format != MPV_FORMAT_STRING) |
207 | 0 | break; |
208 | 264k | if (!apply_flag(cmd, bstr0(args->values[cur].u.string))) |
209 | 264k | break; |
210 | 0 | cur++; |
211 | 0 | } |
212 | | |
213 | 264k | bstr cmd_name = {0}; |
214 | 264k | if (cur < args->num && args->values[cur].format == MPV_FORMAT_STRING) |
215 | 264k | cmd_name = bstr0(args->values[cur++].u.string); |
216 | 264k | if (!find_cmd(log, cmd, cmd_name)) |
217 | 0 | return false; |
218 | | |
219 | 264k | int first = cur; |
220 | 385k | for (int i = 0; i < args->num - first; i++) { |
221 | 120k | if (!set_node_arg(log, cmd, cmd->nargs, &args->values[cur++])) |
222 | 0 | return false; |
223 | 120k | } |
224 | | |
225 | 264k | return true; |
226 | 264k | } |
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 | bool old_name = false; |
234 | 0 | mpv_node *name = node_map_get(node, "_name"); |
235 | 0 | if (!name || name->format != MPV_FORMAT_STRING) { |
236 | 0 | old_name = true; |
237 | 0 | name = node_map_get(node, "name"); |
238 | 0 | } |
239 | 0 | if (!name || name->format != MPV_FORMAT_STRING) |
240 | 0 | return false; |
241 | | |
242 | 0 | if (!find_cmd(log, cmd, bstr0(name->u.string))) |
243 | 0 | return false; |
244 | | |
245 | 0 | if (cmd->def->vararg) { |
246 | 0 | mp_err(log, "Command %s: this command uses a variable number of " |
247 | 0 | "arguments, which does not work with named arguments.\n", |
248 | 0 | cmd->name); |
249 | 0 | return false; |
250 | 0 | } |
251 | | |
252 | 0 | for (int n = 0; n < args->num; n++) { |
253 | 0 | const char *key = args->keys[n]; |
254 | 0 | mpv_node *val = &args->values[n]; |
255 | |
|
256 | 0 | if (strcmp(key, "_name") == 0 || (old_name && strcmp(key, "name") == 0)) { |
257 | | // already handled above |
258 | 0 | } else if (strcmp(key, "_flags") == 0) { |
259 | 0 | if (val->format != MPV_FORMAT_NODE_ARRAY) |
260 | 0 | return false; |
261 | 0 | mpv_node_list *flags = val->u.list; |
262 | 0 | for (int i = 0; i < flags->num; i++) { |
263 | 0 | if (flags->values[i].format != MPV_FORMAT_STRING) |
264 | 0 | return false; |
265 | 0 | if (!apply_flag(cmd, bstr0(flags->values[i].u.string))) |
266 | 0 | return false; |
267 | 0 | } |
268 | 0 | } else { |
269 | 0 | int arg = -1; |
270 | |
|
271 | 0 | for (int i = 0; i < MP_CMD_DEF_MAX_ARGS; i++) { |
272 | 0 | const char *arg_name = cmd->def->args[i].name; |
273 | 0 | if (arg_name && arg_name[0] && strcmp(key, arg_name) == 0) { |
274 | 0 | arg = i; |
275 | 0 | break; |
276 | 0 | } |
277 | 0 | } |
278 | |
|
279 | 0 | if (arg < 0) { |
280 | 0 | mp_err(log, "Command %s: no argument %s.\n", cmd->name, key); |
281 | 0 | return false; |
282 | 0 | } |
283 | | |
284 | 0 | if (!set_node_arg(log, cmd, arg, val)) |
285 | 0 | return false; |
286 | 0 | } |
287 | 0 | } |
288 | | |
289 | 0 | return true; |
290 | 0 | } |
291 | | |
292 | | struct mp_cmd *mp_input_parse_cmd_node(struct mp_log *log, mpv_node *node) |
293 | 264k | { |
294 | 264k | struct mp_cmd *cmd = talloc_ptrtype(NULL, cmd); |
295 | 264k | talloc_set_destructor(cmd, destroy_cmd); |
296 | 264k | *cmd = (struct mp_cmd) { .scale = 1, .scale_units = 1 }; |
297 | | |
298 | 264k | bool res = false; |
299 | 264k | if (node->format == MPV_FORMAT_NODE_ARRAY) { |
300 | 264k | res = cmd_node_array(log, cmd, node); |
301 | 264k | } else if (node->format == MPV_FORMAT_NODE_MAP) { |
302 | 0 | res = cmd_node_map(log, cmd, node); |
303 | 0 | } |
304 | | |
305 | 264k | res = res && finish_cmd(log, cmd); |
306 | | |
307 | 264k | if (!res) |
308 | 0 | TA_FREEP(&cmd); |
309 | | |
310 | 264k | return cmd; |
311 | 264k | } |
312 | | |
313 | | static bool read_token(bstr str, bstr *out_rest, bstr *out_token) |
314 | 115M | { |
315 | 115M | bstr t = bstr_lstrip(str); |
316 | 115M | int next = bstrcspn(t, WHITESPACE "#;"); |
317 | 115M | if (!next) |
318 | 40.6M | return false; |
319 | 74.6M | *out_token = bstr_splice(t, 0, next); |
320 | 74.6M | *out_rest = bstr_cut(t, next); |
321 | 74.6M | return true; |
322 | 115M | } |
323 | | |
324 | | struct parse_ctx { |
325 | | struct mp_log *log; |
326 | | void *tmp; |
327 | | bstr start, str; |
328 | | }; |
329 | | |
330 | | static int pctx_read_token(struct parse_ctx *ctx, bstr *out) |
331 | 85.3M | { |
332 | 85.3M | *out = (bstr){0}; |
333 | 85.3M | ctx->str = bstr_lstrip(ctx->str); |
334 | 85.3M | bstr start = ctx->str; |
335 | 85.3M | if (bstr_eatstart0(&ctx->str, "\"")) { |
336 | 956k | if (!mp_append_escaped_string_noalloc(ctx->tmp, out, &ctx->str)) { |
337 | 9.01k | MP_ERR(ctx, "Broken string escapes: ...>%.*s<.\n", BSTR_P(start)); |
338 | 9.01k | return -1; |
339 | 9.01k | } |
340 | 947k | if (!bstr_eatstart0(&ctx->str, "\"")) { |
341 | 1.34k | MP_ERR(ctx, "Unterminated double quote: ...>%.*s<.\n", BSTR_P(start)); |
342 | 1.34k | return -1; |
343 | 1.34k | } |
344 | 945k | return 1; |
345 | 947k | } |
346 | 84.4M | if (bstr_eatstart0(&ctx->str, "'")) { |
347 | 174k | int next = bstrchr(ctx->str, '\''); |
348 | 174k | if (next < 0) { |
349 | 1.22k | MP_ERR(ctx, "Unterminated single quote: ...>%.*s<.\n", BSTR_P(start)); |
350 | 1.22k | return -1; |
351 | 1.22k | } |
352 | 173k | *out = bstr_splice(ctx->str, 0, next); |
353 | 173k | ctx->str = bstr_cut(ctx->str, next+1); |
354 | 173k | return 1; |
355 | 174k | } |
356 | 84.2M | if (ctx->start.len > 1 && bstr_eatstart0(&ctx->str, "`")) { |
357 | 3.60k | char endquote[2] = {ctx->str.start[0], '`'}; |
358 | 3.60k | ctx->str = bstr_cut(ctx->str, 1); |
359 | 3.60k | int next = bstr_find(ctx->str, (bstr){endquote, 2}); |
360 | 3.60k | if (next < 0) { |
361 | 1.01k | MP_ERR(ctx, "Unterminated custom quote: ...>%.*s<.\n", BSTR_P(start)); |
362 | 1.01k | return -1; |
363 | 1.01k | } |
364 | 2.59k | *out = bstr_splice(ctx->str, 0, next); |
365 | 2.59k | ctx->str = bstr_cut(ctx->str, next+2); |
366 | 2.59k | return 1; |
367 | 3.60k | } |
368 | | |
369 | 84.2M | return read_token(ctx->str, &ctx->str, out) ? 1 : 0; |
370 | 84.2M | } |
371 | | |
372 | | static struct mp_cmd *parse_cmd_str(struct mp_log *log, void *tmp, |
373 | | bstr *str, const char *loc) |
374 | 28.4M | { |
375 | 28.4M | struct parse_ctx *ctx = &(struct parse_ctx){ |
376 | 28.4M | .log = log, |
377 | 28.4M | .tmp = tmp, |
378 | 28.4M | .str = *str, |
379 | 28.4M | .start = *str, |
380 | 28.4M | }; |
381 | | |
382 | 28.4M | struct mp_cmd *cmd = talloc_ptrtype(NULL, cmd); |
383 | 28.4M | talloc_set_destructor(cmd, destroy_cmd); |
384 | 28.4M | *cmd = (struct mp_cmd) { |
385 | 28.4M | .flags = MP_ON_OSD_AUTO | MP_EXPAND_PROPERTIES, |
386 | 28.4M | .scale = 1, |
387 | 28.4M | .scale_units = 1, |
388 | 28.4M | }; |
389 | | |
390 | 28.4M | ctx->str = bstr_lstrip(ctx->str); |
391 | | |
392 | 28.4M | bstr cur_token; |
393 | 28.4M | if (pctx_read_token(ctx, &cur_token) < 0) |
394 | 10.7k | goto error; |
395 | | |
396 | 30.5M | while (1) { |
397 | 30.5M | if (!apply_flag(cmd, cur_token)) |
398 | 28.4M | break; |
399 | 2.09M | if (pctx_read_token(ctx, &cur_token) < 0) |
400 | 272 | goto error; |
401 | 2.09M | } |
402 | | |
403 | 28.4M | if (!find_cmd(ctx->log, cmd, cur_token)) |
404 | 84.5k | goto error; |
405 | | |
406 | 70.7M | for (int i = 0; i < MP_CMD_MAX_ARGS; i++) { |
407 | 70.7M | const struct m_option *opt = get_arg_type(cmd->def, i); |
408 | 70.7M | if (!opt) |
409 | 15.9M | break; |
410 | | |
411 | 54.8M | int r = pctx_read_token(ctx, &cur_token); |
412 | 54.8M | if (r < 0) { |
413 | 1.57k | MP_ERR(ctx, "Command %s: error in argument %d.\n", cmd->name, i + 1); |
414 | 1.57k | goto error; |
415 | 1.57k | } |
416 | 54.8M | if (r < 1) |
417 | 12.3M | break; |
418 | | |
419 | 42.4M | struct mp_cmd_arg arg = {.type = opt}; |
420 | 42.4M | r = m_option_parse(ctx->log, opt, bstr0(cmd->name), cur_token, &arg.v); |
421 | 42.4M | if (r < 0) { |
422 | 53.7k | MP_ERR(ctx, "Command %s: argument %d can't be parsed: %s.\n", |
423 | 53.7k | cmd->name, i + 1, m_option_strerror(r)); |
424 | 53.7k | goto error; |
425 | 53.7k | } |
426 | | |
427 | 42.4M | MP_TARRAY_APPEND(cmd, cmd->args, cmd->nargs, arg); |
428 | 42.4M | } |
429 | | |
430 | 28.2M | if (!finish_cmd(ctx->log, cmd)) |
431 | 4.30k | goto error; |
432 | | |
433 | 28.2M | bstr dummy; |
434 | 28.2M | if (read_token(ctx->str, &dummy, &dummy) && ctx->str.len) { |
435 | 1.51k | MP_ERR(ctx, "Command %s has trailing unused arguments: '%.*s'.\n", |
436 | 1.51k | cmd->name, BSTR_P(ctx->str)); |
437 | | // Better make it fatal to make it clear something is wrong. |
438 | 1.51k | goto error; |
439 | 1.51k | } |
440 | | |
441 | 28.2M | bstr orig = {ctx->start.start, ctx->str.start - ctx->start.start}; |
442 | 28.2M | cmd->original = bstrto0(cmd, bstr_strip(orig)); |
443 | | |
444 | 28.2M | *str = ctx->str; |
445 | 28.2M | return cmd; |
446 | | |
447 | 156k | error: |
448 | 156k | MP_ERR(ctx, "Command was defined at %s.\n", loc); |
449 | 156k | talloc_free(cmd); |
450 | 156k | *str = ctx->str; |
451 | 156k | return NULL; |
452 | 28.2M | } |
453 | | |
454 | | mp_cmd_t *mp_input_parse_cmd_str(struct mp_log *log, bstr str, const char *loc) |
455 | 25.6M | { |
456 | 25.6M | void *tmp = talloc_new(NULL); |
457 | 25.6M | bstr original = str; |
458 | 25.6M | struct mp_cmd *cmd = parse_cmd_str(log, tmp, &str, loc); |
459 | 25.6M | if (!cmd) |
460 | 155k | goto done; |
461 | | |
462 | | // Handle "multi" commands |
463 | 25.5M | struct mp_cmd **p_prev = NULL; |
464 | 28.2M | while (1) { |
465 | 28.2M | str = bstr_lstrip(str); |
466 | | // read_token just to check whether it's trailing whitespace only |
467 | 28.2M | bstr u1, u2; |
468 | 28.2M | if (!bstr_eatstart0(&str, ";") || !read_token(str, &u1, &u2)) |
469 | 25.5M | break; |
470 | | // Multi-command. Since other input.c code uses queue_next for its |
471 | | // own purposes, a pseudo-command is used to wrap the command list. |
472 | 2.76M | if (!p_prev) { |
473 | 1.83M | struct mp_cmd *list = talloc_ptrtype(NULL, list); |
474 | 1.83M | talloc_set_destructor(list, destroy_cmd); |
475 | 1.83M | *list = (struct mp_cmd) { |
476 | 1.83M | .name = (char *)mp_cmd_list.name, |
477 | 1.83M | .def = &mp_cmd_list, |
478 | 1.83M | }; |
479 | 1.83M | talloc_steal(list, cmd); |
480 | 1.83M | struct mp_cmd_arg arg = {0}; |
481 | 1.83M | arg.v.p = cmd; |
482 | 1.83M | list->args = talloc_dup(list, &arg); |
483 | 1.83M | p_prev = &cmd->queue_next; |
484 | 1.83M | cmd = list; |
485 | 1.83M | } |
486 | 2.76M | struct mp_cmd *sub = parse_cmd_str(log, tmp, &str, loc); |
487 | 2.76M | if (!sub) { |
488 | 1.50k | talloc_free(cmd); |
489 | 1.50k | cmd = NULL; |
490 | 1.50k | goto done; |
491 | 1.50k | } |
492 | 2.75M | talloc_steal(cmd, sub); |
493 | 2.75M | *p_prev = sub; |
494 | 2.75M | p_prev = &sub->queue_next; |
495 | 2.75M | } |
496 | | |
497 | 25.5M | cmd->original = bstrto0(cmd, bstr_strip( |
498 | 25.5M | bstr_splice(original, 0, str.start - original.start))); |
499 | | |
500 | 25.5M | str = bstr_strip(str); |
501 | 25.5M | if (bstr_eatstart0(&str, "#") && !bstr_startswith0(str, "#")) { |
502 | 19.3M | str = bstr_strip(str); |
503 | 19.3M | if (str.len) |
504 | 19.3M | cmd->desc = bstrto0(cmd, str); |
505 | 19.3M | } |
506 | | |
507 | 25.6M | done: |
508 | 25.6M | talloc_free(tmp); |
509 | 25.6M | return cmd; |
510 | 25.5M | } |
511 | | |
512 | | struct mp_cmd *mp_input_parse_cmd_strv(struct mp_log *log, const char **argv) |
513 | 264k | { |
514 | 264k | int count = 0; |
515 | 649k | while (argv[count]) |
516 | 385k | count++; |
517 | 264k | mpv_node *items = talloc_zero_array(NULL, mpv_node, count); |
518 | 264k | mpv_node_list list = {.values = items, .num = count}; |
519 | 264k | mpv_node node = {.format = MPV_FORMAT_NODE_ARRAY, .u = {.list = &list}}; |
520 | 649k | for (int n = 0; n < count; n++) { |
521 | 385k | items[n] = (mpv_node){.format = MPV_FORMAT_STRING, |
522 | 385k | .u = {.string = (char *)argv[n]}}; |
523 | 385k | } |
524 | 264k | struct mp_cmd *res = mp_input_parse_cmd_node(log, &node); |
525 | 264k | talloc_free(items); |
526 | 264k | return res; |
527 | 264k | } |
528 | | |
529 | | void mp_cmd_free(mp_cmd_t *cmd) |
530 | 0 | { |
531 | 0 | talloc_free(cmd); |
532 | 0 | } |
533 | | |
534 | | mp_cmd_t *mp_cmd_clone(mp_cmd_t *cmd) |
535 | 0 | { |
536 | 0 | if (!cmd) |
537 | 0 | return NULL; |
538 | | |
539 | 0 | mp_cmd_t *ret = talloc_dup(NULL, cmd); |
540 | 0 | talloc_set_destructor(ret, destroy_cmd); |
541 | 0 | ret->name = talloc_strdup(ret, cmd->name); |
542 | 0 | ret->args = talloc_zero_array(ret, struct mp_cmd_arg, ret->nargs); |
543 | 0 | for (int i = 0; i < ret->nargs; i++) { |
544 | 0 | ret->args[i].type = cmd->args[i].type; |
545 | 0 | m_option_copy(ret->args[i].type, &ret->args[i].v, &cmd->args[i].v); |
546 | 0 | } |
547 | 0 | ret->original = talloc_strdup(ret, cmd->original); |
548 | 0 | ret->desc = talloc_strdup(ret, cmd->desc); |
549 | 0 | ret->sender = NULL; |
550 | 0 | ret->key_name = talloc_strdup(ret, ret->key_name); |
551 | 0 | ret->key_text = talloc_strdup(ret, ret->key_text); |
552 | |
|
553 | 0 | if (cmd->def == &mp_cmd_list) { |
554 | 0 | struct mp_cmd *prev = NULL; |
555 | 0 | for (struct mp_cmd *sub = cmd->args[0].v.p; sub; sub = sub->queue_next) { |
556 | 0 | sub = mp_cmd_clone(sub); |
557 | 0 | talloc_steal(ret, sub); |
558 | 0 | if (prev) { |
559 | 0 | prev->queue_next = sub; |
560 | 0 | } else { |
561 | 0 | struct mp_cmd_arg arg = {0}; |
562 | 0 | arg.v.p = sub; |
563 | 0 | ret->args = talloc_dup(ret, &arg); |
564 | 0 | } |
565 | 0 | prev = sub; |
566 | 0 | } |
567 | 0 | } |
568 | |
|
569 | 0 | return ret; |
570 | 0 | } |
571 | | |
572 | | static int get_arg_count(const struct mp_cmd_def *cmd) |
573 | 251k | { |
574 | 2.42M | for (int i = MP_CMD_DEF_MAX_ARGS - 1; i >= 0; i--) { |
575 | 2.42M | if (cmd->args[i].type) |
576 | 251k | return i + 1; |
577 | 2.42M | } |
578 | 0 | return 0; |
579 | 251k | } |
580 | | |
581 | | void mp_cmd_dump(struct mp_log *log, int msgl, char *header, struct mp_cmd *cmd) |
582 | 251k | { |
583 | 251k | if (!mp_msg_test(log, msgl)) |
584 | 104 | return; |
585 | 251k | if (header) |
586 | 251k | mp_msg(log, msgl, "%s ", header); |
587 | 251k | if (!cmd) { |
588 | 0 | mp_msg(log, msgl, "(NULL)\n"); |
589 | 0 | return; |
590 | 0 | } |
591 | 251k | mp_msg(log, msgl, "%s, flags=%d, args=[", cmd->name, cmd->flags); |
592 | 251k | int argc = get_arg_count(cmd->def); |
593 | 843k | for (int n = 0; n < cmd->nargs; n++) { |
594 | 591k | const char *argname = cmd->def->args[MPMIN(n, argc - 1)].name; |
595 | 591k | char *s = m_option_print(cmd->args[n].type, &cmd->args[n].v); |
596 | 591k | if (n) |
597 | 339k | mp_msg(log, msgl, ", "); |
598 | 591k | struct mpv_node node = { |
599 | 591k | .format = MPV_FORMAT_STRING, |
600 | 591k | .u.string = s ? s : "(NULL)", |
601 | 591k | }; |
602 | 591k | char *esc = NULL; |
603 | 591k | json_write(&esc, &node); |
604 | 591k | mp_msg(log, msgl, "%s=%s", argname, esc ? esc : "<error>"); |
605 | 591k | talloc_free(esc); |
606 | 591k | talloc_free(s); |
607 | 591k | } |
608 | 251k | mp_msg(log, msgl, "]\n"); |
609 | 251k | } |
610 | | |
611 | | bool mp_input_is_repeatable_cmd(struct mp_cmd *cmd) |
612 | 0 | { |
613 | 0 | if (cmd->def == &mp_cmd_list && cmd->args[0].v.p) |
614 | 0 | cmd = cmd->args[0].v.p; // list - only 1st cmd is considered |
615 | |
|
616 | 0 | return (cmd->def->allow_auto_repeat && !(cmd->flags & MP_DISALLOW_REPEAT)) || |
617 | 0 | (cmd->flags & MP_ALLOW_REPEAT); |
618 | 0 | } |
619 | | |
620 | | bool mp_input_is_scalable_cmd(struct mp_cmd *cmd) |
621 | 0 | { |
622 | 0 | return cmd->def->scalable && !(cmd->flags & MP_DISALLOW_SCALE); |
623 | 0 | } |
624 | | |
625 | | void mp_print_cmd_list(struct mp_log *out) |
626 | 338 | { |
627 | 29.4k | for (int i = 0; mp_cmds[i].name; i++) { |
628 | 29.0k | const struct mp_cmd_def *def = &mp_cmds[i]; |
629 | 29.0k | mp_info(out, "%-25s", def->name); |
630 | 81.4k | for (int j = 0; j < MP_CMD_DEF_MAX_ARGS && def->args[j].type; j++) { |
631 | 52.3k | const struct m_option *arg = &def->args[j]; |
632 | 52.3k | bool is_opt = arg->defval || (arg->flags & MP_CMD_OPT_ARG); |
633 | 52.3k | mp_info(out, " %s%s=%s%s", is_opt ? "[" : "", arg->name, |
634 | 52.3k | arg->type->name, is_opt ? "]" : ""); |
635 | 52.3k | } |
636 | 29.0k | if (def->vararg) |
637 | 1.35k | mp_info(out, "..."); // essentially append to last argument |
638 | 29.0k | mp_info(out, "\n"); |
639 | 29.0k | } |
640 | 338 | } |