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 /// \file
19 /// \ingroup Options
20 
21 #include "config.h"
22 
23 #include <stdlib.h>
24 #include <string.h>
25 #include <limits.h>
26 #include <math.h>
27 #include <stdio.h>
28 #include <stdarg.h>
29 #include <limits.h>
30 #include <inttypes.h>
31 #include <unistd.h>
32 #include <assert.h>
33 
34 #include <libavutil/common.h>
35 
36 #include "libmpv/client.h"
37 #include "player/client.h"
38 
39 #include "mpv_talloc.h"
40 #include "common/common.h"
41 #include "common/msg.h"
42 #include "common/msg_control.h"
43 #include "misc/json.h"
44 #include "misc/node.h"
45 #include "m_option.h"
46 #include "m_config_frontend.h"
47 
48 #if HAVE_DOS_PATHS
49 #define OPTION_PATH_SEPARATOR ';'
50 #else
51 #define OPTION_PATH_SEPARATOR ':'
52 #endif
53 
54 const char m_option_path_separator = OPTION_PATH_SEPARATOR;
55 
56 // For integer types: since min/max are floats and may not be able to represent
57 // the real min/max, and since opt.min/.max may use +/-INFINITY, some care has
58 // to be taken. (Also tricky rounding.)
59 #define OPT_INT_MIN(opt, T, Tm) ((opt)->min < (opt)->max \
60     ? ((opt)->min <= (double)(Tm) ? (Tm) : (T)((opt)->min)) : (Tm))
61 #define OPT_INT_MAX(opt, T, Tm) ((opt)->min < (opt)->max \
62     ? ((opt)->max >= (double)(Tm) ? (Tm) : (T)((opt)->max)) : (Tm))
63 
m_option_parse(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)64 int m_option_parse(struct mp_log *log, const m_option_t *opt,
65                    struct bstr name, struct bstr param, void *dst)
66 {
67     int r = M_OPT_INVALID;
68     if (bstr_equals0(param, "help") && opt->help) {
69         r = opt->help(log, opt, name);
70         if (r < 0)
71             return r;
72     }
73 
74     r = opt->type->parse(log, opt, name, param, dst);
75     if (r < 0)
76         return r;
77 
78     if (opt->validate) {
79         r = opt->validate(log, opt, name, dst);
80         if (r < 0) {
81             if (opt->type->free)
82                 opt->type->free(dst);
83             return r;
84         }
85     }
86     return 1;
87 }
88 
m_option_strerror(int code)89 char *m_option_strerror(int code)
90 {
91     switch (code) {
92     case M_OPT_UNKNOWN:
93         return "option not found";
94     case M_OPT_MISSING_PARAM:
95         return "option requires parameter";
96     case M_OPT_INVALID:
97         return "option parameter could not be parsed";
98     case M_OPT_OUT_OF_RANGE:
99         return "parameter is outside values allowed for option";
100     case M_OPT_DISALLOW_PARAM:
101         return "option doesn't take a parameter";
102     default:
103         return "parser error";
104     }
105 }
106 
m_option_required_params(const m_option_t * opt)107 int m_option_required_params(const m_option_t *opt)
108 {
109     if (opt->type->flags & M_OPT_TYPE_OPTIONAL_PARAM)
110         return 0;
111     if (opt->flags & M_OPT_OPTIONAL_PARAM)
112         return 0;
113     if (opt->type == &m_option_type_choice) {
114         struct m_opt_choice_alternatives *alt;
115         for (alt = opt->priv; alt->name; alt++) {
116             if (strcmp(alt->name, "yes") == 0)
117                 return 0;
118         }
119     }
120     return 1;
121 }
122 
m_option_set_node_or_string(struct mp_log * log,const m_option_t * opt,const char * name,void * dst,struct mpv_node * src)123 int m_option_set_node_or_string(struct mp_log *log, const m_option_t *opt,
124                                 const char *name, void *dst, struct mpv_node *src)
125 {
126     if (src->format == MPV_FORMAT_STRING) {
127         // The af and vf option unfortunately require this, because the
128         // option name includes the "action".
129         bstr optname = bstr0(name), a, b;
130         if (bstr_split_tok(optname, "/", &a, &b))
131             optname = b;
132         return m_option_parse(log, opt, optname, bstr0(src->u.string), dst);
133     } else {
134         return m_option_set_node(opt, dst, src);
135     }
136 }
137 
138 // Default function that just does a memcpy
139 
copy_opt(const m_option_t * opt,void * dst,const void * src)140 static void copy_opt(const m_option_t *opt, void *dst, const void *src)
141 {
142     if (dst && src)
143         memcpy(dst, src, opt->type->size);
144 }
145 
146 // Bool
147 
148 #define VAL(x) (*(bool *)(x))
149 
parse_bool(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)150 static int parse_bool(struct mp_log *log, const m_option_t *opt,
151                       struct bstr name, struct bstr param, void *dst)
152 {
153     if (bstr_equals0(param, "yes") || !param.len) {
154         if (dst)
155             VAL(dst) = 1;
156         return 1;
157     }
158     if (bstr_equals0(param, "no")) {
159         if (dst)
160             VAL(dst) = 0;
161         return 1;
162     }
163     bool is_help = bstr_equals0(param, "help");
164     if (is_help) {
165         mp_info(log, "Valid values for %.*s flag are:\n", BSTR_P(name));
166     } else {
167         mp_fatal(log, "Invalid parameter for %.*s flag: %.*s\n",
168                  BSTR_P(name), BSTR_P(param));
169         mp_info(log, "Valid values are:\n");
170     }
171     mp_info(log, "    yes\n");
172     mp_info(log, "    no\n");
173     mp_info(log, "    (passing nothing)\n");
174     return is_help ? M_OPT_EXIT : M_OPT_INVALID;
175 }
176 
print_bool(const m_option_t * opt,const void * val)177 static char *print_bool(const m_option_t *opt, const void *val)
178 {
179     return talloc_strdup(NULL, VAL(val) ? "yes" : "no");
180 }
181 
add_bool(const m_option_t * opt,void * val,double add,bool wrap)182 static void add_bool(const m_option_t *opt, void *val, double add, bool wrap)
183 {
184     if (fabs(add) < 0.5)
185         return;
186     bool state = !!VAL(val);
187     state = wrap ? !state : add > 0;
188     VAL(val) = state ? 1 : 0;
189 }
190 
bool_set(const m_option_t * opt,void * dst,struct mpv_node * src)191 static int bool_set(const m_option_t *opt, void *dst, struct mpv_node *src)
192 {
193     if (src->format != MPV_FORMAT_FLAG)
194         return M_OPT_UNKNOWN;
195     VAL(dst) = !!src->u.flag;
196     return 1;
197 }
198 
bool_get(const m_option_t * opt,void * ta_parent,struct mpv_node * dst,void * src)199 static int bool_get(const m_option_t *opt, void *ta_parent,
200                     struct mpv_node *dst, void *src)
201 {
202     dst->format = MPV_FORMAT_FLAG;
203     dst->u.flag = !!VAL(src);
204     return 1;
205 }
206 
bool_equal(const m_option_t * opt,void * a,void * b)207 static bool bool_equal(const m_option_t *opt, void *a, void *b)
208 {
209     return VAL(a) == VAL(b);
210 }
211 
212 const m_option_type_t m_option_type_bool = {
213     .name  = "Flag", // same as m_option_type_flag; transparent to user
214     .size  = sizeof(bool),
215     .flags = M_OPT_TYPE_OPTIONAL_PARAM | M_OPT_TYPE_CHOICE,
216     .parse = parse_bool,
217     .print = print_bool,
218     .copy  = copy_opt,
219     .add   = add_bool,
220     .set   = bool_set,
221     .get   = bool_get,
222     .equal = bool_equal,
223 };
224 
225 #undef VAL
226 
227 // Flag
228 
229 #define VAL(x) (*(int *)(x))
230 
parse_flag(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)231 static int parse_flag(struct mp_log *log, const m_option_t *opt,
232                       struct bstr name, struct bstr param, void *dst)
233 {
234     bool bdst = false;
235     int r = parse_bool(log, opt, name, param, &bdst);
236     if (dst)
237         VAL(dst) = bdst;
238     return r;
239 }
240 
print_flag(const m_option_t * opt,const void * val)241 static char *print_flag(const m_option_t *opt, const void *val)
242 {
243     return print_bool(opt, &(bool){VAL(val)});
244 }
245 
add_flag(const m_option_t * opt,void * val,double add,bool wrap)246 static void add_flag(const m_option_t *opt, void *val, double add, bool wrap)
247 {
248     bool bval = VAL(val);
249     add_bool(opt, &bval, add, wrap);
250     VAL(val) = bval;
251 }
252 
flag_set(const m_option_t * opt,void * dst,struct mpv_node * src)253 static int flag_set(const m_option_t *opt, void *dst, struct mpv_node *src)
254 {
255     bool bdst = false;
256     int r = bool_set(opt, &bdst, src);
257     if (r >= 0)
258         VAL(dst) = bdst;
259     return r;
260 }
261 
flag_get(const m_option_t * opt,void * ta_parent,struct mpv_node * dst,void * src)262 static int flag_get(const m_option_t *opt, void *ta_parent,
263                     struct mpv_node *dst, void *src)
264 {
265     return bool_get(opt, ta_parent, dst, &(bool){VAL(src)});
266 }
267 
flag_equal(const m_option_t * opt,void * a,void * b)268 static bool flag_equal(const m_option_t *opt, void *a, void *b)
269 {
270     return VAL(a) == VAL(b);
271 }
272 
273 const m_option_type_t m_option_type_flag = {
274     // need yes or no in config files
275     .name  = "Flag",
276     .size  = sizeof(int),
277     .flags = M_OPT_TYPE_OPTIONAL_PARAM | M_OPT_TYPE_CHOICE,
278     .parse = parse_flag,
279     .print = print_flag,
280     .copy  = copy_opt,
281     .add   = add_flag,
282     .set   = flag_set,
283     .get   = flag_get,
284     .equal = flag_equal,
285 };
286 
287 // Integer
288 
289 #undef VAL
290 
clamp_longlong(const m_option_t * opt,long long i_min,long long i_max,void * val)291 static int clamp_longlong(const m_option_t *opt, long long i_min, long long i_max,
292                           void *val)
293 {
294     long long v = *(long long *)val;
295     int r = 0;
296     long long min = OPT_INT_MIN(opt, long long, i_min);
297     long long max = OPT_INT_MAX(opt, long long, i_max);
298     if (v > max) {
299         v = max;
300         r = M_OPT_OUT_OF_RANGE;
301     }
302     if (v < min) {
303         v = min;
304         r = M_OPT_OUT_OF_RANGE;
305     }
306     *(long long *)val = v;
307     return r;
308 }
309 
parse_longlong(struct mp_log * log,const m_option_t * opt,long long i_min,long long i_max,struct bstr name,struct bstr param,void * dst)310 static int parse_longlong(struct mp_log *log, const m_option_t *opt,
311                           long long i_min, long long i_max,
312                           struct bstr name, struct bstr param, void *dst)
313 {
314     if (param.len == 0)
315         return M_OPT_MISSING_PARAM;
316 
317     struct bstr rest;
318     long long tmp_int = bstrtoll(param, &rest, 10);
319     if (rest.len)
320         tmp_int = bstrtoll(param, &rest, 0);
321     if (rest.len) {
322         mp_err(log, "The %.*s option must be an integer: %.*s\n",
323                BSTR_P(name), BSTR_P(param));
324         return M_OPT_INVALID;
325     }
326 
327     long long min = OPT_INT_MIN(opt, long long, i_min);
328     if (tmp_int < min) {
329         mp_err(log, "The %.*s option must be >= %lld: %.*s\n",
330                BSTR_P(name), min, BSTR_P(param));
331         return M_OPT_OUT_OF_RANGE;
332     }
333 
334     long long max = OPT_INT_MAX(opt, long long, i_max);
335     if (tmp_int > max) {
336         mp_err(log, "The %.*s option must be <= %lld: %.*s\n",
337                BSTR_P(name), max, BSTR_P(param));
338         return M_OPT_OUT_OF_RANGE;
339     }
340 
341     if (dst)
342         *(long long *)dst = tmp_int;
343 
344     return 1;
345 }
346 
clamp_int64(const m_option_t * opt,void * val)347 static int clamp_int64(const m_option_t *opt, void *val)
348 {
349     long long tmp = *(int64_t *)val;
350     int r = clamp_longlong(opt, INT64_MIN, INT64_MAX, &tmp);
351     *(int64_t *)val = tmp;
352     return r;
353 }
354 
parse_int(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)355 static int parse_int(struct mp_log *log, const m_option_t *opt,
356                      struct bstr name, struct bstr param, void *dst)
357 {
358     long long tmp;
359     int r = parse_longlong(log, opt, INT_MIN, INT_MAX, name, param, &tmp);
360     if (r >= 0 && dst)
361         *(int *)dst = tmp;
362     return r;
363 }
364 
parse_int64(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)365 static int parse_int64(struct mp_log *log, const m_option_t *opt,
366                        struct bstr name, struct bstr param, void *dst)
367 {
368     long long tmp;
369     int r = parse_longlong(log, opt, INT64_MIN, INT64_MAX, name, param, &tmp);
370     if (r >= 0 && dst)
371         *(int64_t *)dst = tmp;
372     return r;
373 }
374 
print_int(const m_option_t * opt,const void * val)375 static char *print_int(const m_option_t *opt, const void *val)
376 {
377     if (opt->type->size == sizeof(int64_t))
378         return talloc_asprintf(NULL, "%"PRId64, *(const int64_t *)val);
379     return talloc_asprintf(NULL, "%d", *(const int *)val);
380 }
381 
add_int64(const m_option_t * opt,void * val,double add,bool wrap)382 static void add_int64(const m_option_t *opt, void *val, double add, bool wrap)
383 {
384     int64_t v = *(int64_t *)val;
385 
386     clamp_int64(opt, &v);
387 
388     v = v + add;
389 
390     bool is64 = opt->type->size == sizeof(int64_t);
391     int64_t nmin = is64 ? INT64_MIN : INT_MIN;
392     int64_t nmax = is64 ? INT64_MAX : INT_MAX;
393 
394     int64_t min = OPT_INT_MIN(opt, int64_t, nmin);
395     int64_t max = OPT_INT_MAX(opt, int64_t, nmax);
396 
397     if (v < min)
398         v = wrap ? max : min;
399     if (v > max)
400         v = wrap ? min : max;
401 
402     *(int64_t *)val = v;
403 }
404 
add_int(const m_option_t * opt,void * val,double add,bool wrap)405 static void add_int(const m_option_t *opt, void *val, double add, bool wrap)
406 {
407     int64_t tmp = *(int *)val;
408     add_int64(opt, &tmp, add, wrap);
409     *(int *)val = tmp;
410 }
411 
multiply_int64(const m_option_t * opt,void * val,double f)412 static void multiply_int64(const m_option_t *opt, void *val, double f)
413 {
414     double v = *(int64_t *)val * f;
415     int64_t iv = v;
416     if (v < INT64_MIN)
417         iv = INT64_MIN;
418     if (v >= (double)INT64_MAX)
419         iv = INT64_MAX;
420     *(int64_t *)val = iv;
421     clamp_int64(opt, val);
422 }
423 
multiply_int(const m_option_t * opt,void * val,double f)424 static void multiply_int(const m_option_t *opt, void *val, double f)
425 {
426     int64_t tmp = *(int *)val;
427     multiply_int64(opt, &tmp, f);
428     *(int *)val = MPCLAMP(tmp, INT_MIN, INT_MAX);
429 }
430 
int64_set(const m_option_t * opt,void * dst,struct mpv_node * src)431 static int int64_set(const m_option_t *opt, void *dst, struct mpv_node *src)
432 {
433     if (src->format != MPV_FORMAT_INT64)
434         return M_OPT_UNKNOWN;
435     int64_t val = src->u.int64;
436     if (val < OPT_INT_MIN(opt, int64_t, INT64_MIN))
437         return M_OPT_OUT_OF_RANGE;
438     if (val > OPT_INT_MAX(opt, int64_t, INT64_MAX))
439         return M_OPT_OUT_OF_RANGE;
440     *(int64_t *)dst = val;
441     return 1;
442 }
443 
int_set(const m_option_t * opt,void * dst,struct mpv_node * src)444 static int int_set(const m_option_t *opt, void *dst, struct mpv_node *src)
445 {
446     int64_t val;
447     int r = int64_set(opt, &val, src);
448     if (r >= 0) {
449         if (val < INT_MIN || val > INT_MAX)
450             return M_OPT_OUT_OF_RANGE;
451         *(int *)dst = val;
452     }
453     return r;
454 }
455 
int64_get(const m_option_t * opt,void * ta_parent,struct mpv_node * dst,void * src)456 static int int64_get(const m_option_t *opt, void *ta_parent,
457                      struct mpv_node *dst, void *src)
458 {
459     dst->format = MPV_FORMAT_INT64;
460     dst->u.int64 = *(int64_t *)src;
461     return 1;
462 }
463 
int_get(const m_option_t * opt,void * ta_parent,struct mpv_node * dst,void * src)464 static int int_get(const m_option_t *opt, void *ta_parent,
465                    struct mpv_node *dst, void *src)
466 {
467     dst->format = MPV_FORMAT_INT64;
468     dst->u.int64 = *(int *)src;
469     return 1;
470 }
471 
int_equal(const m_option_t * opt,void * a,void * b)472 static bool int_equal(const m_option_t *opt, void *a, void *b)
473 {
474     return *(int *)a == *(int *)b;
475 }
476 
int64_equal(const m_option_t * opt,void * a,void * b)477 static bool int64_equal(const m_option_t *opt, void *a, void *b)
478 {
479     return *(int64_t *)a == *(int64_t *)b;
480 }
481 
482 const m_option_type_t m_option_type_int = {
483     .name  = "Integer",
484     .flags = M_OPT_TYPE_USES_RANGE,
485     .size  = sizeof(int),
486     .parse = parse_int,
487     .print = print_int,
488     .copy  = copy_opt,
489     .add = add_int,
490     .multiply = multiply_int,
491     .set   = int_set,
492     .get   = int_get,
493     .equal = int_equal,
494 };
495 
496 const m_option_type_t m_option_type_int64 = {
497     .name  = "Integer64",
498     .flags = M_OPT_TYPE_USES_RANGE,
499     .size  = sizeof(int64_t),
500     .parse = parse_int64,
501     .print = print_int,
502     .copy  = copy_opt,
503     .add = add_int64,
504     .multiply = multiply_int64,
505     .set   = int64_set,
506     .get   = int64_get,
507     .equal = int64_equal,
508 };
509 
parse_byte_size(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)510 static int parse_byte_size(struct mp_log *log, const m_option_t *opt,
511                            struct bstr name, struct bstr param, void *dst)
512 {
513     if (param.len == 0)
514         return M_OPT_MISSING_PARAM;
515 
516     struct bstr r;
517     long long tmp_int = bstrtoll(param, &r, 0);
518     int64_t unit = 1;
519     if (r.len) {
520         if (bstrcasecmp0(r, "b") == 0) {
521             unit = 1;
522         } else if (bstrcasecmp0(r, "kib") == 0 || bstrcasecmp0(r, "k") == 0) {
523             unit = 1024;
524         } else if (bstrcasecmp0(r, "mib") == 0 || bstrcasecmp0(r, "m") == 0) {
525             unit = 1024 * 1024;
526         } else if (bstrcasecmp0(r, "gib") == 0 || bstrcasecmp0(r, "g") == 0) {
527             unit = 1024 * 1024 * 1024;
528         } else if (bstrcasecmp0(r, "tib") == 0 || bstrcasecmp0(r, "t") == 0) {
529             unit = 1024 * 1024 * 1024 * 1024LL;
530         } else {
531             mp_err(log, "The %.*s option must be an integer: %.*s\n",
532                    BSTR_P(name), BSTR_P(param));
533             mp_err(log, "The following suffixes are also allowed: "
534                    "KiB, MiB, GiB, TiB, B, K, M, G, T.\n");
535             return M_OPT_INVALID;
536         }
537     }
538 
539     if (tmp_int < 0) {
540         mp_err(log, "The %.*s option does not support negative numbers: %.*s\n",
541                BSTR_P(name), BSTR_P(param));
542         return M_OPT_OUT_OF_RANGE;
543     }
544 
545     if (INT64_MAX / unit < tmp_int) {
546         mp_err(log, "The %.*s option overflows: %.*s\n",
547                BSTR_P(name), BSTR_P(param));
548         return M_OPT_OUT_OF_RANGE;
549     }
550 
551     tmp_int *= unit;
552 
553     int64_t min = OPT_INT_MIN(opt, int64_t, INT64_MIN);
554     if (tmp_int < min) {
555         mp_err(log, "The %.*s option must be >= %"PRId64": %.*s\n",
556                BSTR_P(name), min, BSTR_P(param));
557         return M_OPT_OUT_OF_RANGE;
558     }
559 
560     int64_t max = OPT_INT_MAX(opt, int64_t, INT64_MAX);
561     if (tmp_int > max) {
562         mp_err(log, "The %.*s option must be <= %"PRId64": %.*s\n",
563                BSTR_P(name), max, BSTR_P(param));
564         return M_OPT_OUT_OF_RANGE;
565     }
566 
567     if (dst)
568         *(int64_t *)dst = tmp_int;
569 
570     return 1;
571 }
572 
format_file_size(int64_t size)573 char *format_file_size(int64_t size)
574 {
575     double s = size;
576     if (size < 1024)
577         return talloc_asprintf(NULL, "%.0f B", s);
578 
579     if (size < (1024 * 1024))
580         return talloc_asprintf(NULL, "%.3f KiB", s / (1024.0));
581 
582     if (size < (1024 * 1024 * 1024))
583         return talloc_asprintf(NULL, "%.3f MiB", s / (1024.0 * 1024.0));
584 
585     if (size < (1024LL * 1024LL * 1024LL * 1024LL))
586         return talloc_asprintf(NULL, "%.3f GiB", s / (1024.0 * 1024.0 * 1024.0));
587 
588     return talloc_asprintf(NULL, "%.3f TiB", s / (1024.0 * 1024.0 * 1024.0 * 1024.0));
589 }
590 
pretty_print_byte_size(const m_option_t * opt,const void * val)591 static char *pretty_print_byte_size(const m_option_t *opt, const void *val)
592 {
593     return format_file_size(*(int64_t *)val);
594 }
595 
596 const m_option_type_t m_option_type_byte_size = {
597     .name  = "ByteSize",
598     .flags = M_OPT_TYPE_USES_RANGE,
599     .size  = sizeof(int64_t),
600     .parse = parse_byte_size,
601     .print = print_int,
602     .pretty_print = pretty_print_byte_size,
603     .copy  = copy_opt,
604     .add = add_int64,
605     .multiply = multiply_int64,
606     .set   = int64_set,
607     .get   = int64_get,
608     .equal = int64_equal,
609 };
610 
m_opt_choice_str(const struct m_opt_choice_alternatives * choices,int value)611 const char *m_opt_choice_str(const struct m_opt_choice_alternatives *choices,
612                              int value)
613 {
614     for (const struct m_opt_choice_alternatives *c = choices; c->name; c++) {
615         if (c->value == value)
616             return c->name;
617     }
618     return NULL;
619 }
620 
print_choice_values(struct mp_log * log,const struct m_option * opt)621 static void print_choice_values(struct mp_log *log, const struct m_option *opt)
622 {
623     struct m_opt_choice_alternatives *alt = opt->priv;
624     for ( ; alt->name; alt++)
625         mp_info(log, "    %s\n", alt->name[0] ? alt->name : "(passing nothing)");
626     if (opt->min < opt->max)
627         mp_info(log, "    %g-%g (integer range)\n", opt->min, opt->max);
628 }
629 
parse_choice(struct mp_log * log,const struct m_option * opt,struct bstr name,struct bstr param,void * dst)630 static int parse_choice(struct mp_log *log, const struct m_option *opt,
631                         struct bstr name, struct bstr param, void *dst)
632 {
633     struct m_opt_choice_alternatives *alt = opt->priv;
634     for ( ; alt->name; alt++) {
635         if (!bstrcmp0(param, alt->name))
636             break;
637     }
638     if (!alt->name && param.len == 0) {
639         // allow flag-style options, e.g. "--mute" implies "--mute=yes"
640         for (alt = opt->priv; alt->name; alt++) {
641             if (!strcmp("yes", alt->name))
642                 break;
643         }
644     }
645     if (!alt->name) {
646         if (!bstrcmp0(param, "help")) {
647             mp_info(log, "Valid values for option %.*s are:\n", BSTR_P(name));
648             print_choice_values(log, opt);
649             return M_OPT_EXIT;
650         }
651         if (param.len == 0)
652             return M_OPT_MISSING_PARAM;
653         if (opt->min < opt->max) {
654             long long val;
655             if (parse_longlong(mp_null_log, opt, INT_MIN, INT_MAX, name, param,
656                                &val) == 1)
657             {
658                 if (dst)
659                     *(int *)dst = val;
660                 return 1;
661             }
662         }
663         mp_fatal(log, "Invalid value for option %.*s: %.*s\n",
664                  BSTR_P(name), BSTR_P(param));
665         mp_info(log, "Valid values are:\n");
666         print_choice_values(log, opt);
667         return M_OPT_INVALID;
668     }
669     if (dst)
670         *(int *)dst = alt->value;
671 
672     return 1;
673 }
674 
choice_get_min_max(const struct m_option * opt,int * min,int * max)675 static void choice_get_min_max(const struct m_option *opt, int *min, int *max)
676 {
677     assert(opt->type == &m_option_type_choice);
678     *min = INT_MAX;
679     *max = INT_MIN;
680     for (struct m_opt_choice_alternatives *alt = opt->priv; alt->name; alt++) {
681         *min = MPMIN(*min, alt->value);
682         *max = MPMAX(*max, alt->value);
683     }
684     if (opt->min < opt->max) {
685         *min = MPMIN(*min, opt->min);
686         *max = MPMAX(*max, opt->max);
687     }
688 }
689 
check_choice(int dir,int val,bool * found,int * best,int choice)690 static void check_choice(int dir, int val, bool *found, int *best, int choice)
691 {
692     if ((dir == -1 && (!(*found) || choice > (*best)) && choice < val) ||
693         (dir == +1 && (!(*found) || choice < (*best)) && choice > val))
694     {
695         *found = true;
696         *best = choice;
697     }
698 }
699 
add_choice(const m_option_t * opt,void * val,double add,bool wrap)700 static void add_choice(const m_option_t *opt, void *val, double add, bool wrap)
701 {
702     assert(opt->type == &m_option_type_choice);
703     int dir = add > 0 ? +1 : -1;
704     bool found = false;
705     int ival = *(int *)val;
706     int best = 0; // init. value unused
707 
708     if (fabs(add) < 0.5)
709         return;
710 
711     if (opt->min < opt->max) {
712         int newval = ival + add;
713         if (ival >= opt->min && ival <= opt->max &&
714             newval >= opt->min && newval <= opt->max)
715         {
716             found = true;
717             best = newval;
718         } else {
719             check_choice(dir, ival, &found, &best, opt->min);
720             check_choice(dir, ival, &found, &best, opt->max);
721         }
722     }
723 
724     for (struct m_opt_choice_alternatives *alt = opt->priv; alt->name; alt++)
725         check_choice(dir, ival, &found, &best, alt->value);
726 
727     if (!found) {
728         int min, max;
729         choice_get_min_max(opt, &min, &max);
730         best = (dir == -1) ^ wrap ? min : max;
731     }
732 
733     *(int *)val = best;
734 }
735 
choice_set(const m_option_t * opt,void * dst,struct mpv_node * src)736 static int choice_set(const m_option_t *opt, void *dst, struct mpv_node *src)
737 {
738     char buf[80];
739     char *src_str = NULL;
740     if (src->format == MPV_FORMAT_INT64) {
741         snprintf(buf, sizeof(buf), "%" PRId64, src->u.int64);
742         src_str = buf;
743     } else if (src->format == MPV_FORMAT_STRING) {
744         src_str = src->u.string;
745     } else if (src->format == MPV_FORMAT_FLAG) {
746         src_str = src->u.flag ? "yes" : "no";
747     }
748     if (!src_str)
749         return M_OPT_UNKNOWN;
750     int val = 0;
751     int r = parse_choice(mp_null_log, opt, (bstr){0}, bstr0(src_str), &val);
752     if (r >= 0)
753         *(int *)dst = val;
754     return r;
755 }
756 
get_choice(const m_option_t * opt,const void * val,int * out_val)757 static struct m_opt_choice_alternatives *get_choice(const m_option_t *opt,
758                                                     const void *val, int *out_val)
759 {
760     int v = *(int *)val;
761     struct m_opt_choice_alternatives *alt;
762     for (alt = opt->priv; alt->name; alt++) {
763         if (alt->value == v)
764             return alt;
765     }
766     if (opt->min < opt->max) {
767         if (v >= opt->min && v <= opt->max) {
768             *out_val = v;
769             return NULL;
770         }
771     }
772     abort();
773 }
774 
choice_get(const m_option_t * opt,void * ta_parent,struct mpv_node * dst,void * src)775 static int choice_get(const m_option_t *opt, void *ta_parent,
776                       struct mpv_node *dst, void *src)
777 {
778     int ival = 0;
779     struct m_opt_choice_alternatives *alt = get_choice(opt, src, &ival);
780     // If a choice string looks like a number, return it as number
781     if (alt) {
782         char *end = NULL;
783         ival = strtol(alt->name, &end, 10);
784         if (end && !end[0])
785             alt = NULL;
786     }
787     if (alt) {
788         int b = -1;
789         if (strcmp(alt->name, "yes") == 0) {
790             b = 1;
791         } else if (strcmp(alt->name, "no") == 0) {
792             b = 0;
793         }
794         if (b >= 0) {
795             dst->format = MPV_FORMAT_FLAG;
796             dst->u.flag = b;
797         } else {
798             dst->format = MPV_FORMAT_STRING;
799             dst->u.string = talloc_strdup(ta_parent, alt->name);
800         }
801     } else {
802         dst->format = MPV_FORMAT_INT64;
803         dst->u.int64 = ival;
804     }
805     return 1;
806 }
807 
print_choice(const m_option_t * opt,const void * val)808 static char *print_choice(const m_option_t *opt, const void *val)
809 {
810     int ival = 0;
811     struct m_opt_choice_alternatives *alt = get_choice(opt, val, &ival);
812     return alt ? talloc_strdup(NULL, alt->name)
813                : talloc_asprintf(NULL, "%d", ival);
814 }
815 
816 const struct m_option_type m_option_type_choice = {
817     .name  = "Choice",
818     .size  = sizeof(int),
819     .flags = M_OPT_TYPE_CHOICE | M_OPT_TYPE_USES_RANGE,
820     .parse = parse_choice,
821     .print = print_choice,
822     .copy  = copy_opt,
823     .add   = add_choice,
824     .set   = choice_set,
825     .get   = choice_get,
826     .equal = int_equal,
827 };
828 
apply_flag(const struct m_option * opt,int * val,bstr flag)829 static int apply_flag(const struct m_option *opt, int *val, bstr flag)
830 {
831     struct m_opt_choice_alternatives *alt;
832     for (alt = opt->priv; alt->name; alt++) {
833         if (bstr_equals0(flag, alt->name)) {
834             if (*val & alt->value)
835                 return M_OPT_INVALID;
836             *val |= alt->value;
837             return 0;
838         }
839     }
840     return M_OPT_UNKNOWN;
841 }
842 
find_next_flag(const struct m_option * opt,int * val)843 static const char *find_next_flag(const struct m_option *opt, int *val)
844 {
845     struct m_opt_choice_alternatives *best = NULL;
846     struct m_opt_choice_alternatives *alt;
847     for (alt = opt->priv; alt->name; alt++) {
848         if (alt->value && (alt->value & (*val)) == alt->value) {
849             if (!best || av_popcount64(alt->value) > av_popcount64(best->value))
850                 best = alt;
851         }
852     }
853     if (best) {
854         *val = *val & ~(unsigned)best->value;
855         return best->name;
856     }
857     *val = 0; // if there are still flags left, there's not much we can do
858     return NULL;
859 }
860 
parse_flags(struct mp_log * log,const struct m_option * opt,struct bstr name,struct bstr param,void * dst)861 static int parse_flags(struct mp_log *log, const struct m_option *opt,
862                        struct bstr name, struct bstr param, void *dst)
863 {
864     int value = 0;
865     while (param.len) {
866         bstr flag;
867         bstr_split_tok(param, "+", &flag, &param);
868         int r = apply_flag(opt, &value, flag);
869         if (r == M_OPT_UNKNOWN) {
870             mp_fatal(log, "Invalid flag for option %.*s: %.*s\n",
871                      BSTR_P(name), BSTR_P(flag));
872             mp_info(log, "Valid flags are:\n");
873             struct m_opt_choice_alternatives *alt;
874             for (alt = opt->priv; alt->name; alt++)
875                 mp_info(log, "    %s\n", alt->name);
876             mp_info(log, "Flags can usually be combined with '+'.\n");
877             return M_OPT_INVALID;
878         } else if (r < 0) {
879             mp_fatal(log, "Option %.*s: flag '%.*s' conflicts with a previous "
880                      "flag value.\n", BSTR_P(name), BSTR_P(flag));
881             return M_OPT_INVALID;
882         }
883     }
884     if (dst)
885         *(int *)dst = value;
886     return 1;
887 }
888 
flags_set(const m_option_t * opt,void * dst,struct mpv_node * src)889 static int flags_set(const m_option_t *opt, void *dst, struct mpv_node *src)
890 {
891     int value = 0;
892     if (src->format != MPV_FORMAT_NODE_ARRAY)
893         return M_OPT_UNKNOWN;
894     struct mpv_node_list *srclist = src->u.list;
895     for (int n = 0; n < srclist->num; n++) {
896         if (srclist->values[n].format != MPV_FORMAT_STRING)
897             return M_OPT_INVALID;
898         if (apply_flag(opt, &value, bstr0(srclist->values[n].u.string)) < 0)
899             return M_OPT_INVALID;
900     }
901     *(int *)dst = value;
902     return 0;
903 }
904 
flags_get(const m_option_t * opt,void * ta_parent,struct mpv_node * dst,void * src)905 static int flags_get(const m_option_t *opt, void *ta_parent,
906                      struct mpv_node *dst, void *src)
907 {
908     int value = *(int *)src;
909 
910     dst->format = MPV_FORMAT_NODE_ARRAY;
911     dst->u.list = talloc_zero(ta_parent, struct mpv_node_list);
912     struct mpv_node_list *list = dst->u.list;
913     while (1) {
914         const char *flag = find_next_flag(opt, &value);
915         if (!flag)
916             break;
917 
918         struct mpv_node node;
919         node.format = MPV_FORMAT_STRING;
920         node.u.string = (char *)flag;
921         MP_TARRAY_APPEND(list, list->values, list->num, node);
922     }
923 
924     return 1;
925 }
926 
print_flags(const m_option_t * opt,const void * val)927 static char *print_flags(const m_option_t *opt, const void *val)
928 {
929     int value = *(int *)val;
930     char *res = talloc_strdup(NULL, "");
931     while (1) {
932         const char *flag = find_next_flag(opt, &value);
933         if (!flag)
934             break;
935 
936         res = talloc_asprintf_append_buffer(res, "%s%s", res[0] ? "+" : "", flag);
937     }
938     return res;
939 }
940 
941 const struct m_option_type m_option_type_flags = {
942     .name  = "Flags",
943     .size  = sizeof(int),
944     .parse = parse_flags,
945     .print = print_flags,
946     .copy  = copy_opt,
947     .set   = flags_set,
948     .get   = flags_get,
949     .equal = int_equal,
950 };
951 
952 // Float
953 
954 #undef VAL
955 #define VAL(x) (*(double *)(x))
956 
clamp_double(const m_option_t * opt,void * val)957 static int clamp_double(const m_option_t *opt, void *val)
958 {
959     double v = VAL(val);
960     int r = 0;
961     if (opt->min < opt->max) {
962         if (v > opt->max) {
963             v = opt->max;
964             r = M_OPT_OUT_OF_RANGE;
965         }
966         if (v < opt->min) {
967             v = opt->min;
968             r = M_OPT_OUT_OF_RANGE;
969         }
970     }
971     // (setting max/min to INFINITY/-INFINITY is allowed)
972     if (!isfinite(v) && v != opt->max && v != opt->min) {
973         v = opt->min;
974         r = M_OPT_OUT_OF_RANGE;
975     }
976     VAL(val) = v;
977     return r;
978 }
979 
parse_double(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)980 static int parse_double(struct mp_log *log, const m_option_t *opt,
981                         struct bstr name, struct bstr param, void *dst)
982 {
983     if (param.len == 0)
984         return M_OPT_MISSING_PARAM;
985 
986     struct bstr rest;
987     double tmp_float = bstrtod(param, &rest);
988 
989     if (bstr_eatstart0(&rest, ":") || bstr_eatstart0(&rest, "/"))
990         tmp_float /= bstrtod(rest, &rest);
991 
992     if ((opt->flags & M_OPT_DEFAULT_NAN) && bstr_equals0(param, "default")) {
993         tmp_float = NAN;
994         goto done;
995     }
996 
997     if (rest.len) {
998         mp_err(log, "The %.*s option must be a floating point number or a "
999                "ratio (numerator[:/]denominator): %.*s\n",
1000                BSTR_P(name), BSTR_P(param));
1001         return M_OPT_INVALID;
1002     }
1003 
1004     if (clamp_double(opt, &tmp_float) < 0) {
1005         mp_err(log, "The %.*s option is out of range: %.*s\n",
1006                BSTR_P(name), BSTR_P(param));
1007         return M_OPT_OUT_OF_RANGE;
1008     }
1009 
1010 done:
1011     if (dst)
1012         VAL(dst) = tmp_float;
1013     return 1;
1014 }
1015 
print_double(const m_option_t * opt,const void * val)1016 static char *print_double(const m_option_t *opt, const void *val)
1017 {
1018     double f = VAL(val);
1019     if (isnan(f) && (opt->flags & M_OPT_DEFAULT_NAN))
1020         return talloc_strdup(NULL, "default");
1021     return talloc_asprintf(NULL, "%f", f);
1022 }
1023 
print_double_f3(const m_option_t * opt,const void * val)1024 static char *print_double_f3(const m_option_t *opt, const void *val)
1025 {
1026     double f = VAL(val);
1027     if (isnan(f))
1028         return print_double(opt, val);
1029     return talloc_asprintf(NULL, "%.3f", f);
1030 }
1031 
add_double(const m_option_t * opt,void * val,double add,bool wrap)1032 static void add_double(const m_option_t *opt, void *val, double add, bool wrap)
1033 {
1034     double v = VAL(val);
1035 
1036     v = v + add;
1037 
1038     double min = opt->min < opt->max ? opt->min : -INFINITY;
1039     double max = opt->min < opt->max ? opt->max : +INFINITY;
1040 
1041     if (v < min)
1042         v = wrap ? max : min;
1043     if (v > max)
1044         v = wrap ? min : max;
1045 
1046     VAL(val) = v;
1047 }
1048 
multiply_double(const m_option_t * opt,void * val,double f)1049 static void multiply_double(const m_option_t *opt, void *val, double f)
1050 {
1051     *(double *)val *= f;
1052     clamp_double(opt, val);
1053 }
1054 
double_set(const m_option_t * opt,void * dst,struct mpv_node * src)1055 static int double_set(const m_option_t *opt, void *dst, struct mpv_node *src)
1056 {
1057     double val;
1058     if (src->format == MPV_FORMAT_INT64) {
1059         // Can't always be represented exactly, but don't care.
1060         val = src->u.int64;
1061     } else if (src->format == MPV_FORMAT_DOUBLE) {
1062         val = src->u.double_;
1063     } else {
1064         return M_OPT_UNKNOWN;
1065     }
1066     if (clamp_double(opt, &val) < 0)
1067         return M_OPT_OUT_OF_RANGE;
1068     *(double *)dst = val;
1069     return 1;
1070 }
1071 
double_get(const m_option_t * opt,void * ta_parent,struct mpv_node * dst,void * src)1072 static int double_get(const m_option_t *opt, void *ta_parent,
1073                       struct mpv_node *dst, void *src)
1074 {
1075     double f = *(double *)src;
1076     if (isnan(f) && (opt->flags & M_OPT_DEFAULT_NAN)) {
1077         dst->format = MPV_FORMAT_STRING;
1078         dst->u.string = talloc_strdup(ta_parent, "default");
1079     } else {
1080         dst->format = MPV_FORMAT_DOUBLE;
1081         dst->u.double_ = f;
1082     }
1083     return 1;
1084 }
1085 
double_equal(const m_option_t * opt,void * a,void * b)1086 static bool double_equal(const m_option_t *opt, void *a, void *b)
1087 {
1088     double fa = VAL(a), fb = VAL(b);
1089     if (isnan(fa) || isnan(fb))
1090         return isnan(fa) == isnan(fb);
1091     return fa == fb;
1092 }
1093 
1094 const m_option_type_t m_option_type_double = {
1095     // double precision float or ratio (numerator[:/]denominator)
1096     .name  = "Double",
1097     .flags = M_OPT_TYPE_USES_RANGE,
1098     .size  = sizeof(double),
1099     .parse = parse_double,
1100     .print = print_double,
1101     .pretty_print = print_double_f3,
1102     .copy  = copy_opt,
1103     .add = add_double,
1104     .multiply = multiply_double,
1105     .set   = double_set,
1106     .get   = double_get,
1107     .equal = double_equal,
1108 };
1109 
1110 #undef VAL
1111 #define VAL(x) (*(float *)(x))
1112 
parse_float(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)1113 static int parse_float(struct mp_log *log, const m_option_t *opt,
1114                        struct bstr name, struct bstr param, void *dst)
1115 {
1116     double tmp;
1117     int r = parse_double(log, opt, name, param, &tmp);
1118     if (r == 1 && dst)
1119         VAL(dst) = tmp;
1120     return r;
1121 }
1122 
print_float(const m_option_t * opt,const void * val)1123 static char *print_float(const m_option_t *opt, const void *val)
1124 {
1125     double tmp = VAL(val);
1126     return print_double(opt, &tmp);
1127 }
1128 
print_float_f3(const m_option_t * opt,const void * val)1129 static char *print_float_f3(const m_option_t *opt, const void *val)
1130 {
1131     double tmp = VAL(val);
1132     return print_double_f3(opt, &tmp);
1133 }
1134 
add_float(const m_option_t * opt,void * val,double add,bool wrap)1135 static void add_float(const m_option_t *opt, void *val, double add, bool wrap)
1136 {
1137     double tmp = VAL(val);
1138     add_double(opt, &tmp, add, wrap);
1139     VAL(val) = tmp;
1140 }
1141 
multiply_float(const m_option_t * opt,void * val,double f)1142 static void multiply_float(const m_option_t *opt, void *val, double f)
1143 {
1144     double tmp = VAL(val);
1145     multiply_double(opt, &tmp, f);
1146     VAL(val) = tmp;
1147 }
1148 
float_set(const m_option_t * opt,void * dst,struct mpv_node * src)1149 static int float_set(const m_option_t *opt, void *dst, struct mpv_node *src)
1150 {
1151     double tmp;
1152     int r = double_set(opt, &tmp, src);
1153     if (r >= 0)
1154         VAL(dst) = tmp;
1155     return r;
1156 }
1157 
float_get(const m_option_t * opt,void * ta_parent,struct mpv_node * dst,void * src)1158 static int float_get(const m_option_t *opt, void *ta_parent,
1159                      struct mpv_node *dst, void *src)
1160 {
1161     double tmp = VAL(src);
1162     return double_get(opt, ta_parent, dst, &tmp);
1163 }
1164 
float_equal(const m_option_t * opt,void * a,void * b)1165 static bool float_equal(const m_option_t *opt, void *a, void *b)
1166 {
1167     return double_equal(opt, &(double){VAL(a)}, &(double){VAL(b)});
1168 }
1169 
1170 const m_option_type_t m_option_type_float = {
1171     // floating point number or ratio (numerator[:/]denominator)
1172     .name  = "Float",
1173     .flags = M_OPT_TYPE_USES_RANGE,
1174     .size  = sizeof(float),
1175     .parse = parse_float,
1176     .print = print_float,
1177     .pretty_print = print_float_f3,
1178     .copy  = copy_opt,
1179     .add = add_float,
1180     .multiply = multiply_float,
1181     .set   = float_set,
1182     .get   = float_get,
1183     .equal = float_equal,
1184 };
1185 
parse_float_aspect(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)1186 static int parse_float_aspect(struct mp_log *log, const m_option_t *opt,
1187                               struct bstr name, struct bstr param, void *dst)
1188 {
1189     if (bstr_equals0(param, "no")) {
1190         if (dst)
1191             VAL(dst) = 0.0f;
1192         return 1;
1193     }
1194     return parse_float(log, opt, name, param, dst);
1195 }
1196 
1197 const m_option_type_t m_option_type_aspect = {
1198     .name  = "Aspect",
1199     .size  = sizeof(float),
1200     .flags = M_OPT_TYPE_CHOICE | M_OPT_TYPE_USES_RANGE,
1201     .parse = parse_float_aspect,
1202     .print = print_float,
1203     .pretty_print = print_float_f3,
1204     .copy  = copy_opt,
1205     .add = add_float,
1206     .multiply = multiply_float,
1207     .set   = float_set,
1208     .get   = float_get,
1209     .equal = float_equal,
1210 };
1211 
1212 ///////////// String
1213 
1214 #undef VAL
1215 #define VAL(x) (*(char **)(x))
1216 
parse_str(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)1217 static int parse_str(struct mp_log *log, const m_option_t *opt,
1218                      struct bstr name, struct bstr param, void *dst)
1219 {
1220     if (dst) {
1221         talloc_free(VAL(dst));
1222         VAL(dst) = bstrdup0(NULL, param);
1223     }
1224 
1225     return 0;
1226 }
1227 
print_str(const m_option_t * opt,const void * val)1228 static char *print_str(const m_option_t *opt, const void *val)
1229 {
1230     return talloc_strdup(NULL, VAL(val) ? VAL(val) : "");
1231 }
1232 
copy_str(const m_option_t * opt,void * dst,const void * src)1233 static void copy_str(const m_option_t *opt, void *dst, const void *src)
1234 {
1235     if (dst && src) {
1236         talloc_free(VAL(dst));
1237         VAL(dst) = talloc_strdup(NULL, VAL(src));
1238     }
1239 }
1240 
str_set(const m_option_t * opt,void * dst,struct mpv_node * src)1241 static int str_set(const m_option_t *opt, void *dst, struct mpv_node *src)
1242 {
1243     if (src->format != MPV_FORMAT_STRING)
1244         return M_OPT_UNKNOWN;
1245     char *s = src->u.string;
1246     int r = s ? 0 : M_OPT_INVALID;
1247     if (r >= 0)
1248         copy_str(opt, dst, &s);
1249     return r;
1250 }
1251 
str_get(const m_option_t * opt,void * ta_parent,struct mpv_node * dst,void * src)1252 static int str_get(const m_option_t *opt, void *ta_parent,
1253                    struct mpv_node *dst, void *src)
1254 {
1255     dst->format = MPV_FORMAT_STRING;
1256     dst->u.string = talloc_strdup(ta_parent, VAL(src) ? VAL(src) : "");
1257     return 1;
1258 }
1259 
str_equal(const m_option_t * opt,void * a,void * b)1260 static bool str_equal(const m_option_t *opt, void *a, void *b)
1261 {
1262     return bstr_equals(bstr0(VAL(a)), bstr0(VAL(b)));
1263 }
1264 
free_str(void * src)1265 static void free_str(void *src)
1266 {
1267     if (src && VAL(src)) {
1268         talloc_free(VAL(src));
1269         VAL(src) = NULL;
1270     }
1271 }
1272 
1273 const m_option_type_t m_option_type_string = {
1274     .name  = "String",
1275     .size  = sizeof(char *),
1276     .parse = parse_str,
1277     .print = print_str,
1278     .copy  = copy_str,
1279     .free  = free_str,
1280     .set   = str_set,
1281     .get   = str_get,
1282     .equal = str_equal,
1283 };
1284 
1285 //////////// String list
1286 
1287 #undef VAL
1288 #define VAL(x) (*(char ***)(x))
1289 
1290 #define OP_NONE 0
1291 #define OP_ADD 1
1292 #define OP_PRE 2
1293 #define OP_DEL 3
1294 #define OP_CLR 4
1295 #define OP_TOGGLE 5
1296 #define OP_APPEND 6
1297 #define OP_REMOVE 7
1298 
free_str_list(void * dst)1299 static void free_str_list(void *dst)
1300 {
1301     char **d;
1302     int i;
1303 
1304     if (!dst || !VAL(dst))
1305         return;
1306     d = VAL(dst);
1307 
1308     for (i = 0; d[i] != NULL; i++)
1309         talloc_free(d[i]);
1310     talloc_free(d);
1311     VAL(dst) = NULL;
1312 }
1313 
str_list_add(char ** add,int n,void * dst,int pre)1314 static int str_list_add(char **add, int n, void *dst, int pre)
1315 {
1316     char **lst = VAL(dst);
1317 
1318     int ln;
1319     for (ln = 0; lst && lst[ln]; ln++)
1320         /**/;
1321 
1322     lst = talloc_realloc(NULL, lst, char *, n + ln + 1);
1323 
1324     if (pre) {
1325         memmove(&lst[n], lst, ln * sizeof(char *));
1326         memcpy(lst, add, n * sizeof(char *));
1327     } else
1328         memcpy(&lst[ln], add, n * sizeof(char *));
1329     // (re-)add NULL-termination
1330     lst[ln + n] = NULL;
1331 
1332     talloc_free(add);
1333 
1334     VAL(dst) = lst;
1335 
1336     return 1;
1337 }
1338 
str_list_del(struct mp_log * log,char ** del,int n,void * dst)1339 static int str_list_del(struct mp_log *log, char **del, int n, void *dst)
1340 {
1341     char **lst, *ep;
1342     int i, ln, s;
1343     long idx;
1344 
1345     lst = VAL(dst);
1346 
1347     for (ln = 0; lst && lst[ln]; ln++)
1348         /**/;
1349     s = ln;
1350 
1351     for (i = 0; del[i] != NULL; i++) {
1352         idx = strtol(del[i], &ep, 0);
1353         if (*ep) {
1354             mp_err(log, "Invalid index: %s\n", del[i]);
1355             talloc_free(del[i]);
1356             continue;
1357         }
1358         talloc_free(del[i]);
1359         if (idx < 0 || idx >= ln) {
1360             mp_err(log, "Index %ld is out of range.\n", idx);
1361             continue;
1362         } else if (!lst[idx])
1363             continue;
1364         talloc_free(lst[idx]);
1365         lst[idx] = NULL;
1366         s--;
1367     }
1368     talloc_free(del);
1369 
1370     if (s == 0) {
1371         talloc_free(lst);
1372         VAL(dst) = NULL;
1373         return 1;
1374     }
1375 
1376     // Don't bother shrinking the list allocation
1377     for (i = 0, n = 0; i < ln; i++) {
1378         if (!lst[i])
1379             continue;
1380         lst[n] = lst[i];
1381         n++;
1382     }
1383     lst[s] = NULL;
1384 
1385     return 1;
1386 }
1387 
get_nextsep(struct bstr * ptr,char sep,bool modify)1388 static struct bstr get_nextsep(struct bstr *ptr, char sep, bool modify)
1389 {
1390     struct bstr str = *ptr;
1391     struct bstr orig = str;
1392     for (;;) {
1393         int idx = sep ? bstrchr(str, sep) : -1;
1394         if (idx > 0 && str.start[idx - 1] == '\\') {
1395             if (modify) {
1396                 memmove(str.start + idx - 1, str.start + idx, str.len - idx);
1397                 str.len--;
1398                 str = bstr_cut(str, idx);
1399             } else
1400                 str = bstr_cut(str, idx + 1);
1401         } else {
1402             str = bstr_cut(str, idx < 0 ? str.len : idx);
1403             break;
1404         }
1405     }
1406     *ptr = str;
1407     return bstr_splice(orig, 0, str.start - orig.start);
1408 }
1409 
find_list_bstr(char ** list,bstr item)1410 static int find_list_bstr(char **list, bstr item)
1411 {
1412     for (int n = 0; list && list[n]; n++) {
1413         if (bstr_equals0(item, list[n]))
1414             return n;
1415     }
1416     return -1;
1417 }
1418 
parse_str_list_impl(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst,int default_op)1419 static int parse_str_list_impl(struct mp_log *log, const m_option_t *opt,
1420                                struct bstr name, struct bstr param, void *dst,
1421                                int default_op)
1422 {
1423     char **res;
1424     int op = default_op;
1425     bool multi = true;
1426 
1427     if (bstr_endswith0(name, "-add")) {
1428         op = OP_ADD;
1429     } else if (bstr_endswith0(name, "-append")) {
1430         op = OP_ADD;
1431         multi = false;
1432     } else if (bstr_endswith0(name, "-pre")) {
1433         op = OP_PRE;
1434     } else if (bstr_endswith0(name, "-del")) {
1435         op = OP_DEL;
1436         mp_warn(log, "Option %.*s: -del is deprecated! "
1437                 "Use -remove (removes by content instead of by index).\n",
1438                 BSTR_P(name));
1439     } else if (bstr_endswith0(name, "-clr")) {
1440         op = OP_CLR;
1441     } else if (bstr_endswith0(name, "-set")) {
1442         op = OP_NONE;
1443     } else if (bstr_endswith0(name, "-toggle")) {
1444         op = OP_TOGGLE;
1445     } else if (bstr_endswith0(name, "-remove")) {
1446         op = OP_REMOVE;
1447     }
1448 
1449     if (op == OP_TOGGLE || op == OP_REMOVE) {
1450         if (dst) {
1451             char **list = VAL(dst);
1452             int index = find_list_bstr(list, param);
1453             if (index >= 0) {
1454                 char *old = list[index];
1455                 for (int n = index; list[n]; n++)
1456                     list[n] = list[n + 1];
1457                 talloc_free(old);
1458                 return 1;
1459             }
1460         }
1461         if (op == OP_REMOVE)
1462             return 1; // ignore if not found
1463         op = OP_ADD;
1464         multi = false;
1465     }
1466 
1467     // Clear the list ??
1468     if (op == OP_CLR) {
1469         if (dst)
1470             free_str_list(dst);
1471         return 0;
1472     }
1473 
1474     // All other ops need a param
1475     if (param.len == 0 && op != OP_NONE)
1476         return M_OPT_MISSING_PARAM;
1477 
1478     char separator = opt->priv ? *(char *)opt->priv : OPTION_LIST_SEPARATOR;
1479     if (!multi)
1480         separator = 0; // specially handled
1481     int n = 0;
1482     struct bstr str = param;
1483     while (str.len) {
1484         get_nextsep(&str, separator, 0);
1485         str = bstr_cut(str, 1);
1486         n++;
1487     }
1488     if (n == 0 && op != OP_NONE)
1489         return M_OPT_INVALID;
1490 
1491     if (!dst)
1492         return 1;
1493 
1494     res = talloc_array(NULL, char *, n + 2);
1495     str = bstrdup(NULL, param);
1496     char *ptr = str.start;
1497     n = 0;
1498 
1499     while (1) {
1500         struct bstr el = get_nextsep(&str, separator, 1);
1501         res[n] = bstrdup0(NULL, el);
1502         n++;
1503         if (!str.len)
1504             break;
1505         str = bstr_cut(str, 1);
1506     }
1507     res[n] = NULL;
1508     talloc_free(ptr);
1509 
1510     if (op != OP_NONE && n > 1) {
1511         mp_warn(log, "Passing multiple arguments to %.*s is deprecated!\n",
1512                 BSTR_P(name));
1513     }
1514 
1515     switch (op) {
1516     case OP_ADD:
1517         return str_list_add(res, n, dst, 0);
1518     case OP_PRE:
1519         return str_list_add(res, n, dst, 1);
1520     case OP_DEL:
1521         return str_list_del(log, res, n, dst);
1522     }
1523 
1524     if (VAL(dst))
1525         free_str_list(dst);
1526     VAL(dst) = res;
1527 
1528     if (!res[0])
1529         free_str_list(dst);
1530 
1531     return 1;
1532 }
1533 
copy_str_list(const m_option_t * opt,void * dst,const void * src)1534 static void copy_str_list(const m_option_t *opt, void *dst, const void *src)
1535 {
1536     int n;
1537     char **d, **s;
1538 
1539     if (!(dst && src))
1540         return;
1541     s = VAL(src);
1542 
1543     if (VAL(dst))
1544         free_str_list(dst);
1545 
1546     if (!s) {
1547         VAL(dst) = NULL;
1548         return;
1549     }
1550 
1551     for (n = 0; s[n] != NULL; n++)
1552         /* NOTHING */;
1553     d = talloc_array(NULL, char *, n + 1);
1554     for (; n >= 0; n--)
1555         d[n] = talloc_strdup(NULL, s[n]);
1556 
1557     VAL(dst) = d;
1558 }
1559 
print_str_list(const m_option_t * opt,const void * src)1560 static char *print_str_list(const m_option_t *opt, const void *src)
1561 {
1562     char **lst = NULL;
1563     char *ret = NULL;
1564 
1565     if (!(src && VAL(src)))
1566         return talloc_strdup(NULL, "");
1567     lst = VAL(src);
1568 
1569     for (int i = 0; lst[i]; i++) {
1570         if (ret)
1571             ret = talloc_strdup_append_buffer(ret, ",");
1572         ret = talloc_strdup_append_buffer(ret, lst[i]);
1573     }
1574     return ret;
1575 }
1576 
str_list_set(const m_option_t * opt,void * dst,struct mpv_node * src)1577 static int str_list_set(const m_option_t *opt, void *dst, struct mpv_node *src)
1578 {
1579     if (src->format != MPV_FORMAT_NODE_ARRAY)
1580         return M_OPT_UNKNOWN;
1581     struct mpv_node_list *srclist = src->u.list;
1582     for (int n = 0; n < srclist->num; n++) {
1583         if (srclist->values[n].format != MPV_FORMAT_STRING)
1584             return M_OPT_INVALID;
1585     }
1586     free_str_list(dst);
1587     if (srclist->num > 0) {
1588         VAL(dst) = talloc_array(NULL, char*, srclist->num + 1);
1589         for (int n = 0; n < srclist->num; n++)
1590             VAL(dst)[n] = talloc_strdup(NULL, srclist->values[n].u.string);
1591         VAL(dst)[srclist->num] = NULL;
1592     }
1593     return 1;
1594 }
1595 
str_list_get(const m_option_t * opt,void * ta_parent,struct mpv_node * dst,void * src)1596 static int str_list_get(const m_option_t *opt, void *ta_parent,
1597                         struct mpv_node *dst, void *src)
1598 {
1599     dst->format = MPV_FORMAT_NODE_ARRAY;
1600     dst->u.list = talloc_zero(ta_parent, struct mpv_node_list);
1601     struct mpv_node_list *list = dst->u.list;
1602     for (int n = 0; VAL(src) && VAL(src)[n]; n++) {
1603         struct mpv_node node;
1604         node.format = MPV_FORMAT_STRING;
1605         node.u.string = talloc_strdup(list, VAL(src)[n]);
1606         MP_TARRAY_APPEND(list, list->values, list->num, node);
1607     }
1608     return 1;
1609 }
1610 
parse_str_list(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)1611 static int parse_str_list(struct mp_log *log, const m_option_t *opt,
1612                           struct bstr name, struct bstr param, void *dst)
1613 {
1614     return parse_str_list_impl(log, opt, name, param, dst, OP_NONE);
1615 }
1616 
str_list_equal(const m_option_t * opt,void * a,void * b)1617 static bool str_list_equal(const m_option_t *opt, void *a, void *b)
1618 {
1619     char **la = VAL(a);
1620     char **lb = VAL(b);
1621 
1622     bool a_empty = !la || !la[0];
1623     bool b_empty = !lb || !lb[0];
1624     if (a_empty || b_empty)
1625         return a_empty == b_empty;
1626 
1627     for (int n = 0; la[n] || lb[n]; n++) {
1628         if (!la[n] || !lb[n])
1629             return false;
1630         if (strcmp(la[n], lb[n]) != 0)
1631             return false;
1632     }
1633 
1634     return true;
1635 }
1636 
1637 const m_option_type_t m_option_type_string_list = {
1638     .name  = "String list",
1639     .size  = sizeof(char **),
1640     .parse = parse_str_list,
1641     .print = print_str_list,
1642     .copy  = copy_str_list,
1643     .free  = free_str_list,
1644     .get   = str_list_get,
1645     .set   = str_list_set,
1646     .equal = str_list_equal,
1647     .actions = (const struct m_option_action[]){
1648         {"add"},
1649         {"append"},
1650         {"clr",         M_OPT_TYPE_OPTIONAL_PARAM},
1651         {"del"},
1652         {"pre"},
1653         {"set"},
1654         {"toggle"},
1655         {"remove"},
1656         {0}
1657     },
1658 };
1659 
1660 static int read_subparam(struct mp_log *log, bstr optname, char *termset,
1661                          bstr *str, bstr *out_subparam);
1662 
keyvalue_list_find_key(char ** lst,bstr str)1663 static int keyvalue_list_find_key(char **lst, bstr str)
1664 {
1665     for (int n = 0; lst && lst[n] && lst[n + 1]; n += 2) {
1666         if (bstr_equals0(str, lst[n]))
1667             return n / 2;
1668     }
1669     return -1;
1670 }
1671 
keyvalue_list_del_key(char ** lst,int index)1672 static void keyvalue_list_del_key(char **lst, int index)
1673 {
1674     int count = 0;
1675     for (int n = 0; lst && lst[n]; n++)
1676         count++;
1677     assert(index * 2 + 1 < count);
1678     count += 1; // terminating item
1679     talloc_free(lst[index * 2 + 0]);
1680     talloc_free(lst[index * 2 + 1]);
1681     MP_TARRAY_REMOVE_AT(lst, count, index * 2 + 1);
1682     MP_TARRAY_REMOVE_AT(lst, count, index * 2 + 0);
1683 }
1684 
parse_keyvalue_list(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)1685 static int parse_keyvalue_list(struct mp_log *log, const m_option_t *opt,
1686                                struct bstr name, struct bstr param, void *dst)
1687 {
1688     char **lst = NULL;
1689     int num = 0;
1690     int r = 0;
1691     bool append = false;
1692     bool full_value = false;
1693 
1694     if ((opt->flags & M_OPT_HAVE_HELP) && bstr_equals0(param, "help"))
1695         param = bstr0("help=");
1696 
1697     if (bstr_endswith0(name, "-add")) {
1698         append = true;
1699     } else if (bstr_endswith0(name, "-append")) {
1700         append = full_value = true;
1701     } else if (bstr_endswith0(name, "-remove")) {
1702         lst = dst ? VAL(dst) : NULL;
1703         int index = dst ? keyvalue_list_find_key(lst, param) : -1;
1704         if (index >= 0) {
1705             keyvalue_list_del_key(lst, index);
1706             VAL(dst) = lst;
1707         }
1708         return 1;
1709     }
1710 
1711     if (append && dst) {
1712         lst = VAL(dst);
1713         for (int n = 0; lst && lst[n]; n++)
1714             num++;
1715     }
1716 
1717     while (param.len) {
1718         bstr key, val;
1719         r = read_subparam(log, name, "=", &param, &key);
1720         if (r < 0)
1721             break;
1722         if (!bstr_eatstart0(&param, "=")) {
1723             mp_err(log, "Expected '=' and a value.\n");
1724             r = M_OPT_INVALID;
1725             break;
1726         }
1727         if (full_value) {
1728             val = param;
1729             param.len = 0;
1730         } else {
1731             r = read_subparam(log, name, ",", &param, &val);
1732             if (r < 0)
1733                 break;
1734         }
1735         if (dst) {
1736             int index = keyvalue_list_find_key(lst, key);
1737             if (index >= 0) {
1738                 keyvalue_list_del_key(lst, index);
1739                 num -= 2;
1740             }
1741             MP_TARRAY_APPEND(NULL, lst, num, bstrto0(NULL, key));
1742             MP_TARRAY_APPEND(NULL, lst, num, bstrto0(NULL, val));
1743             MP_TARRAY_APPEND(NULL, lst, num, NULL);
1744             num -= 1;
1745         }
1746 
1747         if (!bstr_eatstart0(&param, ",") && !bstr_eatstart0(&param, ":"))
1748             break;
1749 
1750         if (append) {
1751             mp_warn(log, "Passing more than 1 argument to %.*s is deprecated!\n",
1752                     BSTR_P(name));
1753         }
1754     }
1755 
1756     if (param.len) {
1757         mp_err(log, "Unparseable garbage at end of option value: '%.*s'\n",
1758                BSTR_P(param));
1759         r = M_OPT_INVALID;
1760     }
1761 
1762     if (dst) {
1763         if (!append)
1764             free_str_list(dst);
1765         VAL(dst) = lst;
1766         if (r < 0)
1767             free_str_list(dst);
1768     } else {
1769         free_str_list(&lst);
1770     }
1771     return r;
1772 }
1773 
print_keyvalue_list(const m_option_t * opt,const void * src)1774 static char *print_keyvalue_list(const m_option_t *opt, const void *src)
1775 {
1776     char **lst = VAL(src);
1777     char *ret = talloc_strdup(NULL, "");
1778     for (int n = 0; lst && lst[n] && lst[n + 1]; n += 2) {
1779         if (ret[0])
1780             ret = talloc_strdup_append(ret, ",");
1781         ret = talloc_asprintf_append(ret, "%s=%s", lst[n], lst[n + 1]);
1782     }
1783     return ret;
1784 }
1785 
keyvalue_list_set(const m_option_t * opt,void * dst,struct mpv_node * src)1786 static int keyvalue_list_set(const m_option_t *opt, void *dst,
1787                              struct mpv_node *src)
1788 {
1789     if (src->format != MPV_FORMAT_NODE_MAP)
1790         return M_OPT_UNKNOWN;
1791     struct mpv_node_list *srclist = src->u.list;
1792     for (int n = 0; n < srclist->num; n++) {
1793         if (srclist->values[n].format != MPV_FORMAT_STRING)
1794             return M_OPT_INVALID;
1795     }
1796     free_str_list(dst);
1797     if (srclist->num > 0) {
1798         VAL(dst) = talloc_array(NULL, char*, (srclist->num + 1) * 2);
1799         for (int n = 0; n < srclist->num; n++) {
1800             VAL(dst)[n * 2 + 0] = talloc_strdup(NULL, srclist->keys[n]);
1801             VAL(dst)[n * 2 + 1] = talloc_strdup(NULL, srclist->values[n].u.string);
1802         }
1803         VAL(dst)[srclist->num * 2 + 0] = NULL;
1804         VAL(dst)[srclist->num * 2 + 1] = NULL;
1805     }
1806     return 1;
1807 }
1808 
keyvalue_list_get(const m_option_t * opt,void * ta_parent,struct mpv_node * dst,void * src)1809 static int keyvalue_list_get(const m_option_t *opt, void *ta_parent,
1810                              struct mpv_node *dst, void *src)
1811 {
1812     dst->format = MPV_FORMAT_NODE_MAP;
1813     dst->u.list = talloc_zero(ta_parent, struct mpv_node_list);
1814     struct mpv_node_list *list = dst->u.list;
1815     for (int n = 0; VAL(src) && VAL(src)[n * 2 + 0]; n++) {
1816         MP_TARRAY_GROW(list, list->values, list->num);
1817         MP_TARRAY_GROW(list, list->keys, list->num);
1818         list->keys[list->num] = talloc_strdup(list, VAL(src)[n * 2 + 0]);
1819         list->values[list->num] = (struct mpv_node){
1820             .format = MPV_FORMAT_STRING,
1821             .u.string = talloc_strdup(list, VAL(src)[n * 2 + 1]),
1822         };
1823         list->num++;
1824     }
1825     return 1;
1826 }
1827 
1828 const m_option_type_t m_option_type_keyvalue_list = {
1829     .name  = "Key/value list",
1830     .size  = sizeof(char **),
1831     .parse = parse_keyvalue_list,
1832     .print = print_keyvalue_list,
1833     .copy  = copy_str_list,
1834     .free  = free_str_list,
1835     .get   = keyvalue_list_get,
1836     .set   = keyvalue_list_set,
1837     .equal = str_list_equal,
1838     .actions = (const struct m_option_action[]){
1839         {"add"},
1840         {"append"},
1841         {"set"},
1842         {"remove"},
1843         {0}
1844     },
1845 };
1846 
1847 
1848 #undef VAL
1849 #define VAL(x) (*(char **)(x))
1850 
check_msg_levels(struct mp_log * log,char ** list)1851 static int check_msg_levels(struct mp_log *log, char **list)
1852 {
1853     for (int n = 0; list && list[n * 2 + 0]; n++) {
1854         char *level = list[n * 2 + 1];
1855         if (mp_msg_find_level(level) < 0 && strcmp(level, "no") != 0) {
1856             mp_err(log, "Invalid message level '%s'\n", level);
1857             return M_OPT_INVALID;
1858         }
1859     }
1860     return 1;
1861 }
1862 
parse_msglevels(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)1863 static int parse_msglevels(struct mp_log *log, const m_option_t *opt,
1864                            struct bstr name, struct bstr param, void *dst)
1865 {
1866     if (bstr_equals0(param, "help")) {
1867         mp_info(log, "Syntax:\n\n   --msg-level=module1=level,module2=level,...\n\n"
1868                      "'module' is output prefix as shown with -v, or a prefix\n"
1869                      "of it. level is one of:\n\n"
1870                      "  fatal error warn info status v debug trace\n\n"
1871                      "The level specifies the minimum log level a message\n"
1872                      "must have to be printed.\n"
1873                      "The special module name 'all' affects all modules.\n");
1874         return M_OPT_EXIT;
1875     }
1876 
1877     char **dst_copy = NULL;
1878     int r = m_option_type_keyvalue_list.parse(log, opt, name, param, &dst_copy);
1879     if (r >= 0)
1880         r = check_msg_levels(log, dst_copy);
1881 
1882     if (r >= 0)
1883         m_option_type_keyvalue_list.copy(opt, dst, &dst_copy);
1884     m_option_type_keyvalue_list.free(&dst_copy);
1885     return r;
1886 }
1887 
set_msglevels(const m_option_t * opt,void * dst,struct mpv_node * src)1888 static int set_msglevels(const m_option_t *opt, void *dst,
1889                              struct mpv_node *src)
1890 {
1891     char **dst_copy = NULL;
1892     int r = m_option_type_keyvalue_list.set(opt, &dst_copy, src);
1893     if (r >= 0)
1894         r = check_msg_levels(mp_null_log, dst_copy);
1895 
1896     if (r >= 0)
1897         m_option_type_keyvalue_list.copy(opt, dst, &dst_copy);
1898     m_option_type_keyvalue_list.free(&dst_copy);
1899     return r;
1900 }
1901 
1902 const m_option_type_t m_option_type_msglevels = {
1903     .name = "Output verbosity levels",
1904     .size  = sizeof(char **),
1905     .parse = parse_msglevels,
1906     .print = print_keyvalue_list,
1907     .copy  = copy_str_list,
1908     .free  = free_str_list,
1909     .get   = keyvalue_list_get,
1910     .set   = set_msglevels,
1911     .equal = str_list_equal,
1912 };
1913 
parse_print(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)1914 static int parse_print(struct mp_log *log, const m_option_t *opt,
1915                        struct bstr name, struct bstr param, void *dst)
1916 {
1917     ((m_opt_print_fn) opt->priv)(log);
1918     return M_OPT_EXIT;
1919 }
1920 
1921 const m_option_type_t m_option_type_print_fn = {
1922     .name  = "Print",
1923     .flags = M_OPT_TYPE_OPTIONAL_PARAM,
1924     .parse = parse_print,
1925 };
1926 
parse_dummy_flag(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)1927 static int parse_dummy_flag(struct mp_log *log, const m_option_t *opt,
1928                             struct bstr name, struct bstr param, void *dst)
1929 {
1930     if (param.len) {
1931         mp_err(log, "Invalid parameter for %.*s flag: %.*s\n",
1932                BSTR_P(name), BSTR_P(param));
1933         return M_OPT_DISALLOW_PARAM;
1934     }
1935     return 0;
1936 }
1937 
1938 const m_option_type_t m_option_type_dummy_flag = {
1939     // can only be activated
1940     .name  = "Flag",
1941     .flags = M_OPT_TYPE_OPTIONAL_PARAM,
1942     .parse = parse_dummy_flag,
1943 };
1944 
1945 #undef VAL
1946 
1947 // Read s sub-option name, or a positional sub-opt value.
1948 // termset is a string containing the set of chars that terminate an option.
1949 // Return 0 on succes, M_OPT_ error code otherwise.
1950 // optname is for error reporting.
read_subparam(struct mp_log * log,bstr optname,char * termset,bstr * str,bstr * out_subparam)1951 static int read_subparam(struct mp_log *log, bstr optname, char *termset,
1952                          bstr *str, bstr *out_subparam)
1953 {
1954     bstr p = *str;
1955     bstr subparam = {0};
1956 
1957     if (bstr_eatstart0(&p, "\"")) {
1958         int optlen = bstrcspn(p, "\"");
1959         subparam = bstr_splice(p, 0, optlen);
1960         p = bstr_cut(p, optlen);
1961         if (!bstr_startswith0(p, "\"")) {
1962             mp_err(log, "Terminating '\"' missing for '%.*s'\n",
1963                    BSTR_P(optname));
1964             return M_OPT_INVALID;
1965         }
1966         p = bstr_cut(p, 1);
1967     } else if (bstr_eatstart0(&p, "[")) {
1968         bstr s = p;
1969         int balance = 1;
1970         while (p.len && balance > 0) {
1971             if (p.start[0] == '[') {
1972                 balance++;
1973             } else if (p.start[0] == ']') {
1974                 balance--;
1975             }
1976             p = bstr_cut(p, 1);
1977         }
1978         if (balance != 0) {
1979             mp_err(log, "Terminating ']' missing for '%.*s'\n",
1980                    BSTR_P(optname));
1981             return M_OPT_INVALID;
1982         }
1983         subparam = bstr_splice(s, 0, s.len - p.len - 1);
1984     } else if (bstr_eatstart0(&p, "%")) {
1985         int optlen = bstrtoll(p, &p, 0);
1986         if (!bstr_startswith0(p, "%") || (optlen > p.len - 1)) {
1987             mp_err(log, "Invalid length %d for '%.*s'\n",
1988                    optlen, BSTR_P(optname));
1989             return M_OPT_INVALID;
1990         }
1991         subparam = bstr_splice(p, 1, optlen + 1);
1992         p = bstr_cut(p, optlen + 1);
1993     } else {
1994         // Skip until the next character that could possibly be a meta
1995         // character in option parsing.
1996         int optlen = bstrcspn(p, termset);
1997         subparam = bstr_splice(p, 0, optlen);
1998         p = bstr_cut(p, optlen);
1999     }
2000 
2001     *str = p;
2002     *out_subparam = subparam;
2003     return 0;
2004 }
2005 
2006 // Return 0 on success, otherwise error code
2007 // On success, set *out_name and *out_val, and advance *str
2008 // out_val.start is NULL if there was no parameter.
2009 // optname is for error reporting.
split_subconf(struct mp_log * log,bstr optname,bstr * str,bstr * out_name,bstr * out_val)2010 static int split_subconf(struct mp_log *log, bstr optname, bstr *str,
2011                          bstr *out_name, bstr *out_val)
2012 {
2013     bstr p = *str;
2014     bstr subparam = {0};
2015     bstr subopt;
2016     int r = read_subparam(log, optname, ":=,\\%\"'[]", &p, &subopt);
2017     if (r < 0)
2018         return r;
2019     if (bstr_eatstart0(&p, "=")) {
2020         r = read_subparam(log, subopt, ":=,\\%\"'[]", &p, &subparam);
2021         if (r < 0)
2022             return r;
2023     }
2024     *str = p;
2025     *out_name = subopt;
2026     *out_val = subparam;
2027     return 0;
2028 }
2029 
2030 #undef VAL
2031 
2032 // Split the string on the given split character.
2033 // out_arr is at least max entries long.
2034 // Return number of out_arr entries filled.
split_char(bstr str,unsigned char split,int max,bstr * out_arr)2035 static int split_char(bstr str, unsigned char split, int max, bstr *out_arr)
2036 {
2037     if (max < 1)
2038         return 0;
2039 
2040     int count = 0;
2041     while (1) {
2042         int next = bstrchr(str, split);
2043         if (next >= 0 && max - count > 1) {
2044             out_arr[count++] = bstr_splice(str, 0, next);
2045             str = bstr_cut(str, next + 1);
2046         } else {
2047             out_arr[count++] = str;
2048             break;
2049         }
2050     }
2051     return count;
2052 }
2053 
parse_color(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)2054 static int parse_color(struct mp_log *log, const m_option_t *opt,
2055                        struct bstr name, struct bstr param, void *dst)
2056 {
2057     if (param.len == 0)
2058         return M_OPT_MISSING_PARAM;
2059 
2060     bool is_help = bstr_equals0(param, "help");
2061     if (is_help)
2062         goto exit;
2063 
2064     bstr val = param;
2065     struct m_color color = {0};
2066 
2067     if (bstr_eatstart0(&val, "#")) {
2068         // #[AA]RRGGBB
2069         if (val.len != 6 && val.len != 8)
2070             goto exit;
2071         bool has_alpha = val.len == 8;
2072         uint32_t c = bstrtoll(val, &val, 16);
2073         if (val.len)
2074             goto exit;
2075         color = (struct m_color) {
2076             (c >> 16) & 0xFF,
2077             (c >> 8) & 0xFF,
2078             c & 0xFF,
2079             has_alpha ? (c >> 24) & 0xFF : 0xFF,
2080         };
2081     } else {
2082         bstr comp_str[5];
2083         int num = split_char(param, '/', 5, comp_str);
2084         if (num < 1 || num > 4)
2085             goto exit;
2086         double comp[4] = {0, 0, 0, 1};
2087         for (int n = 0; n < num; n++) {
2088             bstr rest;
2089             double d = bstrtod(comp_str[n], &rest);
2090             if (rest.len || !comp_str[n].len || d < 0 || d > 1 || !isfinite(d))
2091                 goto exit;
2092             comp[n] = d;
2093         }
2094         if (num == 2)
2095             comp[3] = comp[1];
2096         if (num < 3)
2097             comp[2] = comp[1] = comp[0];
2098         color = (struct m_color) { comp[0] * 0xFF, comp[1] * 0xFF,
2099                                    comp[2] * 0xFF, comp[3] * 0xFF };
2100     }
2101 
2102     if (dst)
2103         *((struct m_color *)dst) = color;
2104 
2105     return 1;
2106 
2107 exit:
2108     if (!is_help) {
2109         mp_err(log, "Option %.*s: invalid color: '%.*s'\n",
2110                BSTR_P(name), BSTR_P(param));
2111     }
2112     mp_info(log, "Valid colors must be in the form #RRGGBB or #AARRGGBB (in hex)\n"
2113             "or in the form 'r/g/b/a', where each component is a value in the\n"
2114             "range 0.0-1.0. (Also allowed: 'gray', 'gray/a', 'r/g/b').\n");
2115     return is_help ? M_OPT_EXIT : M_OPT_INVALID;
2116 }
2117 
print_color(const m_option_t * opt,const void * val)2118 static char *print_color(const m_option_t *opt, const void *val)
2119 {
2120     const struct m_color *c = val;
2121     return talloc_asprintf(NULL, "#%02X%02X%02X%02X", c->a, c->r, c->g, c->b);
2122 }
2123 
color_equal(const m_option_t * opt,void * a,void * b)2124 static bool color_equal(const m_option_t *opt, void *a, void *b)
2125 {
2126     struct m_color *ca = a;
2127     struct m_color *cb = b;
2128     return ca->a == cb->a && ca->r == cb->r && ca->g == cb->g && ca->b == cb->b;
2129 }
2130 
2131 const m_option_type_t m_option_type_color = {
2132     .name  = "Color",
2133     .size  = sizeof(struct m_color),
2134     .parse = parse_color,
2135     .print = print_color,
2136     .copy  = copy_opt,
2137     .equal = color_equal,
2138 };
2139 
2140 
2141 // Parse a >=0 number starting at s. Set s to the string following the number.
2142 // If the number ends with '%', eat that and set *out_per to true, but only
2143 // if the number is between 0-100; if not, don't eat anything, even the number.
eat_num_per(bstr * s,int * out_num,bool * out_per)2144 static bool eat_num_per(bstr *s, int *out_num, bool *out_per)
2145 {
2146     bstr rest;
2147     long long v = bstrtoll(*s, &rest, 10);
2148     if (s->len == rest.len || v < INT_MIN || v > INT_MAX)
2149         return false;
2150     *out_num = v;
2151     *out_per = false;
2152     *s = rest;
2153     if (bstr_eatstart0(&rest, "%") && v >= 0 && v <= 100) {
2154         *out_per = true;
2155         *s = rest;
2156     }
2157     return true;
2158 }
2159 
parse_geometry_str(struct m_geometry * gm,bstr s)2160 static bool parse_geometry_str(struct m_geometry *gm, bstr s)
2161 {
2162     *gm = (struct m_geometry) { .x = INT_MIN, .y = INT_MIN };
2163     if (s.len == 0)
2164         return true;
2165     // Approximate grammar:
2166     // [[W][xH]][{+-}X{+-}Y][/WS] | [X:Y]
2167     // (meaning: [optional] {one character of} one|alternative)
2168     // Every number can be followed by '%'
2169     int num;
2170     bool per;
2171 
2172 #define READ_NUM(F, F_PER) do {         \
2173     if (!eat_num_per(&s, &num, &per))   \
2174         goto error;                     \
2175     gm->F = num;                        \
2176     gm->F_PER = per;                    \
2177 } while(0)
2178 
2179 #define READ_SIGN(F) do {               \
2180     if (bstr_eatstart0(&s, "+")) {      \
2181         gm->F = false;                  \
2182     } else if (bstr_eatstart0(&s, "-")) {\
2183         gm->F = true;                   \
2184     } else goto error;                  \
2185 } while(0)
2186 
2187     if (bstrchr(s, ':') < 0) {
2188         gm->wh_valid = true;
2189         if (!bstr_startswith0(s, "+") && !bstr_startswith0(s, "-")) {
2190             if (!bstr_startswith0(s, "x"))
2191                 READ_NUM(w, w_per);
2192             if (bstr_eatstart0(&s, "x"))
2193                 READ_NUM(h, h_per);
2194         }
2195         if (s.len > 0) {
2196             gm->xy_valid = true;
2197             READ_SIGN(x_sign);
2198             READ_NUM(x, x_per);
2199             READ_SIGN(y_sign);
2200             READ_NUM(y, y_per);
2201         }
2202         if (bstr_eatstart0(&s, "/")) {
2203             bstr rest;
2204             long long v = bstrtoll(s, &rest, 10);
2205             if (s.len == rest.len || v < 1 || v > INT_MAX)
2206                 goto error;
2207             s = rest;
2208             gm->ws = v;
2209         }
2210     } else {
2211         gm->xy_valid = true;
2212         READ_NUM(x, x_per);
2213         if (!bstr_eatstart0(&s, ":"))
2214             goto error;
2215         READ_NUM(y, y_per);
2216     }
2217 
2218     return s.len == 0;
2219 
2220 error:
2221     return false;
2222 }
2223 
2224 #undef READ_NUM
2225 #undef READ_SIGN
2226 
2227 #define APPEND_PER(F, F_PER) \
2228     res = talloc_asprintf_append(res, "%d%s", gm->F, gm->F_PER ? "%" : "")
2229 
print_geometry(const m_option_t * opt,const void * val)2230 static char *print_geometry(const m_option_t *opt, const void *val)
2231 {
2232     const struct m_geometry *gm = val;
2233     char *res = talloc_strdup(NULL, "");
2234     if (gm->wh_valid || gm->xy_valid) {
2235         if (gm->wh_valid) {
2236             APPEND_PER(w, w_per);
2237             res = talloc_asprintf_append(res, "x");
2238             APPEND_PER(h, h_per);
2239         }
2240         if (gm->xy_valid) {
2241             res = talloc_asprintf_append(res, gm->x_sign ? "-" : "+");
2242             APPEND_PER(x, x_per);
2243             res = talloc_asprintf_append(res, gm->y_sign ? "-" : "+");
2244             APPEND_PER(y, y_per);
2245         }
2246         if (gm->ws > 0)
2247             res = talloc_asprintf_append(res, "/%d", gm->ws);
2248     }
2249     return res;
2250 }
2251 
2252 #undef APPEND_PER
2253 
2254 // xpos,ypos: position of the left upper corner
2255 // widw,widh: width and height of the window
2256 // scrw,scrh: width and height of the current screen
2257 // The input parameters should be set to a centered window (default fallbacks).
m_geometry_apply(int * xpos,int * ypos,int * widw,int * widh,int scrw,int scrh,struct m_geometry * gm)2258 void m_geometry_apply(int *xpos, int *ypos, int *widw, int *widh,
2259                       int scrw, int scrh, struct m_geometry *gm)
2260 {
2261     if (gm->wh_valid) {
2262         int prew = *widw, preh = *widh;
2263         if (gm->w > 0)
2264             *widw = gm->w_per ? scrw * (gm->w / 100.0) : gm->w;
2265         if (gm->h > 0)
2266             *widh = gm->h_per ? scrh * (gm->h / 100.0) : gm->h;
2267         // keep aspect if the other value is not set
2268         double asp = (double)prew / preh;
2269         if (gm->w > 0 && !(gm->h > 0)) {
2270             *widh = *widw / asp;
2271         } else if (!(gm->w > 0) && gm->h > 0) {
2272             *widw = *widh * asp;
2273         }
2274         // Center window after resize. If valid x:y values are passed to
2275         // geometry, then those values will be overriden.
2276         *xpos += prew / 2 - *widw / 2;
2277         *ypos += preh / 2 - *widh / 2;
2278     }
2279 
2280     if (gm->xy_valid) {
2281         if (gm->x != INT_MIN) {
2282             *xpos = gm->x;
2283             if (gm->x_per)
2284                 *xpos = (scrw - *widw) * (*xpos / 100.0);
2285             if (gm->x_sign)
2286                 *xpos = scrw - *widw - *xpos;
2287         }
2288         if (gm->y != INT_MIN) {
2289             *ypos = gm->y;
2290             if (gm->y_per)
2291                 *ypos = (scrh - *widh) * (*ypos / 100.0);
2292             if (gm->y_sign)
2293                 *ypos = scrh - *widh - *ypos;
2294         }
2295     }
2296 }
2297 
parse_geometry(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)2298 static int parse_geometry(struct mp_log *log, const m_option_t *opt,
2299                           struct bstr name, struct bstr param, void *dst)
2300 {
2301     bool is_help = bstr_equals0(param, "help");
2302     if (is_help)
2303         goto exit;
2304 
2305     struct m_geometry gm;
2306     if (!parse_geometry_str(&gm, param))
2307         goto exit;
2308 
2309     if (dst)
2310         *((struct m_geometry *)dst) = gm;
2311 
2312     return 1;
2313 
2314 exit:
2315     if (!is_help) {
2316         mp_err(log, "Option %.*s: invalid geometry: '%.*s'\n",
2317                BSTR_P(name), BSTR_P(param));
2318     }
2319     mp_info(log,
2320          "Valid format: [W[%%][xH[%%]]][{+-}X[%%]{+-}Y[%%]] | [X[%%]:Y[%%]]\n");
2321     return is_help ? M_OPT_EXIT : M_OPT_INVALID;
2322 }
2323 
geometry_equal(const m_option_t * opt,void * a,void * b)2324 static bool geometry_equal(const m_option_t *opt, void *a, void *b)
2325 {
2326     struct m_geometry *ga = a;
2327     struct m_geometry *gb = b;
2328     return ga->x == gb->x && ga->y == gb->y && ga->w == gb->w && ga->h == gb->h &&
2329            ga->xy_valid == gb->xy_valid && ga->wh_valid == gb->wh_valid &&
2330            ga->w_per == gb->w_per && ga->h_per == gb->h_per &&
2331            ga->x_per == gb->x_per && ga->y_per == gb->y_per &&
2332            ga->x_sign == gb->x_sign && ga->y_sign == gb->y_sign &&
2333            ga->ws == gb->ws;
2334 }
2335 
2336 const m_option_type_t m_option_type_geometry = {
2337     .name  = "Window geometry",
2338     .size  = sizeof(struct m_geometry),
2339     .parse = parse_geometry,
2340     .print = print_geometry,
2341     .copy  = copy_opt,
2342     .equal = geometry_equal,
2343 };
2344 
parse_size_box(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)2345 static int parse_size_box(struct mp_log *log, const m_option_t *opt,
2346                           struct bstr name, struct bstr param, void *dst)
2347 {
2348     bool is_help = bstr_equals0(param, "help");
2349     if (is_help)
2350         goto exit;
2351 
2352     struct m_geometry gm;
2353     if (!parse_geometry_str(&gm, param))
2354         goto exit;
2355 
2356     if (gm.xy_valid)
2357         goto exit;
2358 
2359     if (dst)
2360         *((struct m_geometry *)dst) = gm;
2361 
2362     return 1;
2363 
2364 exit:
2365     if (!is_help) {
2366         mp_err(log, "Option %.*s: invalid size: '%.*s'\n",
2367                BSTR_P(name), BSTR_P(param));
2368     }
2369     mp_info(log, "Valid format: W[%%][xH[%%]] or empty string\n");
2370     return is_help ? M_OPT_EXIT : M_OPT_INVALID;
2371 }
2372 
2373 const m_option_type_t m_option_type_size_box = {
2374     .name  = "Window size",
2375     .size  = sizeof(struct m_geometry),
2376     .parse = parse_size_box,
2377     .print = print_geometry,
2378     .copy  = copy_opt,
2379     .equal = geometry_equal,
2380 };
2381 
2382 
2383 #include "video/img_format.h"
2384 
parse_imgfmt(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)2385 static int parse_imgfmt(struct mp_log *log, const m_option_t *opt,
2386                         struct bstr name, struct bstr param, void *dst)
2387 {
2388     if (param.len == 0)
2389         return M_OPT_MISSING_PARAM;
2390 
2391     if (!bstrcmp0(param, "help")) {
2392         mp_info(log, "Available formats:");
2393         char **list = mp_imgfmt_name_list();
2394         for (int i = 0; list[i]; i++)
2395             mp_info(log, " %s", list[i]);
2396         mp_info(log, " no");
2397         mp_info(log, "\n");
2398         talloc_free(list);
2399         return M_OPT_EXIT;
2400     }
2401 
2402     unsigned int fmt = mp_imgfmt_from_name(param);
2403     if (!fmt && !bstr_equals0(param, "no")) {
2404         mp_err(log, "Option %.*s: unknown format name: '%.*s'\n",
2405                BSTR_P(name), BSTR_P(param));
2406         return M_OPT_INVALID;
2407     }
2408 
2409     if (dst)
2410         *((int *)dst) = fmt;
2411 
2412     return 1;
2413 }
2414 
print_imgfmt(const m_option_t * opt,const void * val)2415 static char *print_imgfmt(const m_option_t *opt, const void *val)
2416 {
2417     int fmt = *(int *)val;
2418     return talloc_strdup(NULL, fmt ? mp_imgfmt_to_name(fmt) : "no");
2419 }
2420 
2421 const m_option_type_t m_option_type_imgfmt = {
2422     .name  = "Image format",
2423     .size  = sizeof(int),
2424     .parse = parse_imgfmt,
2425     .print = print_imgfmt,
2426     .copy  = copy_opt,
2427     .equal = int_equal,
2428 };
2429 
parse_fourcc(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)2430 static int parse_fourcc(struct mp_log *log, const m_option_t *opt,
2431                         struct bstr name, struct bstr param, void *dst)
2432 {
2433     if (param.len == 0)
2434         return M_OPT_MISSING_PARAM;
2435 
2436     unsigned int value;
2437 
2438     if (param.len == 4) {
2439         uint8_t *s = param.start;
2440         value = s[0] | (s[1] << 8) | (s[2] << 16) | (s[3] << 24);
2441     } else {
2442         bstr rest;
2443         value = bstrtoll(param, &rest, 16);
2444         if (rest.len != 0) {
2445             mp_err(log, "Option %.*s: invalid FourCC: '%.*s'\n",
2446                    BSTR_P(name), BSTR_P(param));
2447             return M_OPT_INVALID;
2448         }
2449     }
2450 
2451     if (dst)
2452         *((unsigned int *)dst) = value;
2453 
2454     return 1;
2455 }
2456 
print_fourcc(const m_option_t * opt,const void * val)2457 static char *print_fourcc(const m_option_t *opt, const void *val)
2458 {
2459     unsigned int fourcc = *(unsigned int *)val;
2460     return talloc_asprintf(NULL, "%08x", fourcc);
2461 }
2462 
2463 const m_option_type_t m_option_type_fourcc = {
2464     .name  = "FourCC",
2465     .size  = sizeof(unsigned int),
2466     .parse = parse_fourcc,
2467     .print = print_fourcc,
2468     .copy  = copy_opt,
2469     .equal = int_equal,
2470 };
2471 
2472 #include "audio/format.h"
2473 
parse_afmt(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)2474 static int parse_afmt(struct mp_log *log, const m_option_t *opt,
2475                       struct bstr name, struct bstr param, void *dst)
2476 {
2477     if (param.len == 0)
2478         return M_OPT_MISSING_PARAM;
2479 
2480     if (!bstrcmp0(param, "help")) {
2481         mp_info(log, "Available formats:");
2482         for (int i = 1; i < AF_FORMAT_COUNT; i++)
2483             mp_info(log, " %s", af_fmt_to_str(i));
2484         mp_info(log, "\n");
2485         return M_OPT_EXIT;
2486     }
2487 
2488     int fmt = 0;
2489     for (int i = 1; i < AF_FORMAT_COUNT; i++) {
2490         if (bstr_equals0(param, af_fmt_to_str(i)))
2491             fmt = i;
2492     }
2493     if (!fmt) {
2494         mp_err(log, "Option %.*s: unknown format name: '%.*s'\n",
2495                BSTR_P(name), BSTR_P(param));
2496         return M_OPT_INVALID;
2497     }
2498 
2499     if (dst)
2500         *((int *)dst) = fmt;
2501 
2502     return 1;
2503 }
2504 
print_afmt(const m_option_t * opt,const void * val)2505 static char *print_afmt(const m_option_t *opt, const void *val)
2506 {
2507     int fmt = *(int *)val;
2508     return talloc_strdup(NULL, fmt ? af_fmt_to_str(fmt) : "no");
2509 }
2510 
2511 const m_option_type_t m_option_type_afmt = {
2512     .name  = "Audio format",
2513     .size  = sizeof(int),
2514     .parse = parse_afmt,
2515     .print = print_afmt,
2516     .copy  = copy_opt,
2517     .equal = int_equal,
2518 };
2519 
2520 #include "audio/chmap.h"
2521 
parse_channels(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)2522 static int parse_channels(struct mp_log *log, const m_option_t *opt,
2523                           struct bstr name, struct bstr param, void *dst)
2524 {
2525     bool limited = opt->flags & M_OPT_CHANNELS_LIMITED;
2526 
2527     struct m_channels res = {0};
2528 
2529     if (bstr_equals0(param, "help")) {
2530         mp_chmap_print_help(log);
2531         if (!limited) {
2532             mp_info(log, "\nOther values:\n"
2533                          "    auto-safe\n");
2534         }
2535         return M_OPT_EXIT;
2536     }
2537 
2538     bool auto_safe = bstr_equals0(param, "auto-safe");
2539     if (bstr_equals0(param, "auto") || bstr_equals0(param, "empty") || auto_safe) {
2540         if (limited) {
2541             mp_err(log, "Disallowed parameter.\n");
2542             return M_OPT_INVALID;
2543         }
2544         param.len = 0;
2545         res.set = true;
2546         res.auto_safe = auto_safe;
2547     }
2548 
2549     while (param.len) {
2550         bstr item;
2551         if (limited) {
2552             item = param;
2553             param.len = 0;
2554         } else {
2555             bstr_split_tok(param, ",", &item, &param);
2556         }
2557 
2558         struct mp_chmap map = {0};
2559         if (!mp_chmap_from_str(&map, item) || !mp_chmap_is_valid(&map)) {
2560             mp_err(log, "Invalid channel layout: %.*s\n", BSTR_P(item));
2561             talloc_free(res.chmaps);
2562             return M_OPT_INVALID;
2563         }
2564 
2565         MP_TARRAY_APPEND(NULL, res.chmaps, res.num_chmaps, map);
2566         res.set = true;
2567     }
2568 
2569     if (dst) {
2570         *(struct m_channels *)dst = res;
2571     } else {
2572         talloc_free(res.chmaps);
2573     }
2574 
2575     return 1;
2576 }
2577 
print_channels(const m_option_t * opt,const void * val)2578 static char *print_channels(const m_option_t *opt, const void *val)
2579 {
2580     const struct m_channels *ch = val;
2581     if (!ch->set)
2582         return talloc_strdup(NULL, "");
2583     if (ch->auto_safe)
2584         return talloc_strdup(NULL, "auto-safe");
2585     if (ch->num_chmaps > 0) {
2586         char *res = talloc_strdup(NULL, "");
2587         for (int n = 0; n < ch->num_chmaps; n++) {
2588             if (n > 0)
2589                 res = talloc_strdup_append(res, ",");
2590             res = talloc_strdup_append(res, mp_chmap_to_str(&ch->chmaps[n]));
2591         }
2592         return res;
2593     }
2594     return talloc_strdup(NULL, "auto");
2595 }
2596 
free_channels(void * src)2597 static void free_channels(void *src)
2598 {
2599     if (!src)
2600         return;
2601 
2602     struct m_channels *ch = src;
2603     talloc_free(ch->chmaps);
2604     *ch = (struct m_channels){0};
2605 }
2606 
copy_channels(const m_option_t * opt,void * dst,const void * src)2607 static void copy_channels(const m_option_t *opt, void *dst, const void *src)
2608 {
2609     if (!(dst && src))
2610         return;
2611 
2612     struct m_channels *ch = dst;
2613     free_channels(dst);
2614     *ch = *(struct m_channels *)src;
2615     ch->chmaps =
2616         talloc_memdup(NULL, ch->chmaps, sizeof(ch->chmaps[0]) * ch->num_chmaps);
2617 }
2618 
channels_equal(const m_option_t * opt,void * a,void * b)2619 static bool channels_equal(const m_option_t *opt, void *a, void *b)
2620 {
2621     struct m_channels *ca = a;
2622     struct m_channels *cb = b;
2623 
2624     if (ca->set         != cb->set ||
2625         ca->auto_safe   != cb->auto_safe ||
2626         ca->num_chmaps  != cb->num_chmaps)
2627         return false;
2628 
2629     for (int n = 0; n < ca->num_chmaps; n++) {
2630         if (!mp_chmap_equals(&ca->chmaps[n], &cb->chmaps[n]))
2631             return false;
2632     }
2633 
2634     return true;
2635 }
2636 
2637 const m_option_type_t m_option_type_channels = {
2638     .name  = "Audio channels or channel map",
2639     .size  = sizeof(struct m_channels),
2640     .parse = parse_channels,
2641     .print = print_channels,
2642     .copy  = copy_channels,
2643     .free  = free_channels,
2644     .equal = channels_equal,
2645 };
2646 
parse_timestring(struct bstr str,double * time,char endchar)2647 static int parse_timestring(struct bstr str, double *time, char endchar)
2648 {
2649     int a, b, len;
2650     double d;
2651     *time = 0; /* ensure initialization for error cases */
2652     if (bstr_sscanf(str, "%d:%d:%lf%n", &a, &b, &d, &len) >= 3)
2653         *time = 3600 * a + 60 * b + d;
2654     else if (bstr_sscanf(str, "%d:%lf%n", &a, &d, &len) >= 2)
2655         *time = 60 * a + d;
2656     else if (bstr_sscanf(str, "%lf%n", &d, &len) >= 1)
2657         *time = d;
2658     else
2659         return 0;  /* unsupported time format */
2660     if (len < str.len && str.start[len] != endchar)
2661         return 0;  /* invalid extra characters at the end */
2662     if (!isfinite(*time))
2663         return 0;
2664     return len;
2665 }
2666 
2667 #define HAS_NOPTS(opt) ((opt)->flags & M_OPT_ALLOW_NO)
2668 
parse_time(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)2669 static int parse_time(struct mp_log *log, const m_option_t *opt,
2670                       struct bstr name, struct bstr param, void *dst)
2671 {
2672     if (param.len == 0)
2673         return M_OPT_MISSING_PARAM;
2674 
2675     double time = MP_NOPTS_VALUE;
2676     if (HAS_NOPTS(opt) && bstr_equals0(param, "no")) {
2677         // nothing
2678     } else if (!parse_timestring(param, &time, 0)) {
2679         mp_err(log, "Option %.*s: invalid time: '%.*s'\n",
2680                BSTR_P(name), BSTR_P(param));
2681         return M_OPT_INVALID;
2682     }
2683 
2684     if (dst)
2685         *(double *)dst = time;
2686     return 1;
2687 }
2688 
print_time(const m_option_t * opt,const void * val)2689 static char *print_time(const m_option_t *opt, const void *val)
2690 {
2691     double pts = *(double *)val;
2692     if (pts == MP_NOPTS_VALUE && HAS_NOPTS(opt))
2693         return talloc_strdup(NULL, "no"); // symmetry with parsing
2694     return talloc_asprintf(NULL, "%f", pts);
2695 }
2696 
pretty_print_time(const m_option_t * opt,const void * val)2697 static char *pretty_print_time(const m_option_t *opt, const void *val)
2698 {
2699     double pts = *(double *)val;
2700     if (pts == MP_NOPTS_VALUE && HAS_NOPTS(opt))
2701         return talloc_strdup(NULL, "no"); // symmetry with parsing
2702     return mp_format_time(pts, false);
2703 }
2704 
time_set(const m_option_t * opt,void * dst,struct mpv_node * src)2705 static int time_set(const m_option_t *opt, void *dst, struct mpv_node *src)
2706 {
2707     if (HAS_NOPTS(opt) && src->format == MPV_FORMAT_STRING) {
2708         if (strcmp(src->u.string, "no") == 0) {
2709             *(double *)dst = MP_NOPTS_VALUE;
2710             return 1;
2711         }
2712     }
2713     return double_set(opt, dst, src);
2714 }
2715 
time_get(const m_option_t * opt,void * ta_parent,struct mpv_node * dst,void * src)2716 static int time_get(const m_option_t *opt, void *ta_parent,
2717                       struct mpv_node *dst, void *src)
2718 {
2719     if (HAS_NOPTS(opt) && *(double *)src == MP_NOPTS_VALUE) {
2720         dst->format = MPV_FORMAT_STRING;
2721         dst->u.string = talloc_strdup(ta_parent, "no");
2722         return 1;
2723     }
2724     return double_get(opt, ta_parent, dst, src);
2725 }
2726 
2727 const m_option_type_t m_option_type_time = {
2728     .name  = "Time",
2729     .size  = sizeof(double),
2730     .parse = parse_time,
2731     .print = print_time,
2732     .pretty_print = pretty_print_time,
2733     .copy  = copy_opt,
2734     .add   = add_double,
2735     .set   = time_set,
2736     .get   = time_get,
2737     .equal = double_equal,
2738 };
2739 
2740 
2741 // Relative time
2742 
parse_rel_time(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)2743 static int parse_rel_time(struct mp_log *log, const m_option_t *opt,
2744                           struct bstr name, struct bstr param, void *dst)
2745 {
2746     struct m_rel_time t = {0};
2747 
2748     if (param.len == 0)
2749         return M_OPT_MISSING_PARAM;
2750 
2751     if (bstr_equals0(param, "none")) {
2752         t.type = REL_TIME_NONE;
2753         goto out;
2754     }
2755 
2756     // Percent pos
2757     if (bstr_endswith0(param, "%")) {
2758         double percent = bstrtod(bstr_splice(param, 0, -1), &param);
2759         if (param.len == 0 && percent >= 0 && percent <= 100) {
2760             t.type = REL_TIME_PERCENT;
2761             t.pos = percent;
2762             goto out;
2763         }
2764     }
2765 
2766     // Chapter pos
2767     if (bstr_startswith0(param, "#")) {
2768         int chapter = bstrtoll(bstr_cut(param, 1), &param, 10);
2769         if (param.len == 0 && chapter >= 1) {
2770             t.type = REL_TIME_CHAPTER;
2771             t.pos = chapter - 1;
2772             goto out;
2773         }
2774     }
2775 
2776     double time;
2777     if (parse_timestring(param, &time, 0)) {
2778         if (bstr_startswith0(param, "+") || bstr_startswith0(param, "-")) {
2779             t.type = REL_TIME_RELATIVE;
2780         } else {
2781             t.type = REL_TIME_ABSOLUTE;
2782         }
2783         t.pos = time;
2784         goto out;
2785     }
2786 
2787     mp_err(log, "Option %.*s: invalid time or position: '%.*s'\n",
2788            BSTR_P(name), BSTR_P(param));
2789     return M_OPT_INVALID;
2790 
2791 out:
2792     if (dst)
2793         *(struct m_rel_time *)dst = t;
2794     return 1;
2795 }
2796 
print_rel_time(const m_option_t * opt,const void * val)2797 static char *print_rel_time(const m_option_t *opt, const void *val)
2798 {
2799     const struct m_rel_time *t = val;
2800     switch(t->type) {
2801     case REL_TIME_ABSOLUTE:
2802         return talloc_asprintf(NULL, "%g", t->pos);
2803     case REL_TIME_RELATIVE:
2804         return talloc_asprintf(NULL, "%s%g",
2805             (t->pos >= 0) ? "+" : "-", fabs(t->pos));
2806     case REL_TIME_CHAPTER:
2807         return talloc_asprintf(NULL, "#%g", t->pos);
2808     case REL_TIME_PERCENT:
2809         return talloc_asprintf(NULL, "%g%%", t->pos);
2810     }
2811     return talloc_strdup(NULL, "none");
2812 }
2813 
rel_time_equal(const m_option_t * opt,void * a,void * b)2814 static bool rel_time_equal(const m_option_t *opt, void *a, void *b)
2815 {
2816     struct m_rel_time *ta = a;
2817     struct m_rel_time *tb = b;
2818     return ta->type == tb->type && ta->pos == tb->pos;
2819 }
2820 
2821 const m_option_type_t m_option_type_rel_time = {
2822     .name  = "Relative time or percent position",
2823     .size  = sizeof(struct m_rel_time),
2824     .parse = parse_rel_time,
2825     .print = print_rel_time,
2826     .copy  = copy_opt,
2827     .equal = rel_time_equal,
2828 };
2829 
2830 
2831 //// Objects (i.e. filters, etc) settings
2832 
2833 #undef VAL
2834 #define VAL(x) (*(m_obj_settings_t **)(x))
2835 
m_obj_list_find(struct m_obj_desc * dst,const struct m_obj_list * l,bstr name)2836 bool m_obj_list_find(struct m_obj_desc *dst, const struct m_obj_list *l,
2837                      bstr name)
2838 {
2839     for (int i = 0; ; i++) {
2840         if (!l->get_desc(dst, i))
2841             break;
2842         if (bstr_equals0(name, dst->name))
2843             return true;
2844     }
2845     for (int i = 0; l->aliases[i][0]; i++) {
2846         const char *aname = l->aliases[i][0];
2847         const char *alias = l->aliases[i][1];
2848         if (bstr_equals0(name, aname) && m_obj_list_find(dst, l, bstr0(alias)))
2849         {
2850             dst->replaced_name = aname;
2851             return true;
2852         }
2853     }
2854     return false;
2855 }
2856 
obj_setting_free(m_obj_settings_t * item)2857 static void obj_setting_free(m_obj_settings_t *item)
2858 {
2859     talloc_free(item->name);
2860     talloc_free(item->label);
2861     free_str_list(&(item->attribs));
2862 }
2863 
2864 // If at least one item has a label, compare labels only - otherwise ignore them.
obj_setting_match(m_obj_settings_t * a,m_obj_settings_t * b)2865 static bool obj_setting_match(m_obj_settings_t *a, m_obj_settings_t *b)
2866 {
2867     bstr la = bstr0(a->label), lb = bstr0(b->label);
2868     if (la.len || lb.len)
2869         return bstr_equals(la, lb);
2870 
2871     return m_obj_settings_equal(a, b);
2872 }
2873 
obj_settings_list_num_items(m_obj_settings_t * obj_list)2874 static int obj_settings_list_num_items(m_obj_settings_t *obj_list)
2875 {
2876     int num = 0;
2877     while (obj_list && obj_list[num].name)
2878         num++;
2879     return num;
2880 }
2881 
obj_settings_list_del_at(m_obj_settings_t ** p_obj_list,int idx)2882 static void obj_settings_list_del_at(m_obj_settings_t **p_obj_list, int idx)
2883 {
2884     m_obj_settings_t *obj_list = *p_obj_list;
2885     int num = obj_settings_list_num_items(obj_list);
2886 
2887     assert(idx >= 0 && idx < num);
2888 
2889     obj_setting_free(&obj_list[idx]);
2890 
2891     // Note: the NULL-terminating element is moved down as part of this
2892     memmove(&obj_list[idx], &obj_list[idx + 1],
2893             sizeof(m_obj_settings_t) * (num - idx));
2894 
2895     *p_obj_list = talloc_realloc(NULL, obj_list, struct m_obj_settings, num);
2896 }
2897 
2898 // Insert such that *p_obj_list[idx] is set to item.
2899 // If idx < 0, set idx = count + idx + 1 (i.e. -1 inserts it as last element).
2900 // Memory referenced by *item is not copied.
obj_settings_list_insert_at(m_obj_settings_t ** p_obj_list,int idx,m_obj_settings_t * item)2901 static void obj_settings_list_insert_at(m_obj_settings_t **p_obj_list, int idx,
2902                                         m_obj_settings_t *item)
2903 {
2904     int num = obj_settings_list_num_items(*p_obj_list);
2905     if (idx < 0)
2906         idx = num + idx + 1;
2907     assert(idx >= 0 && idx <= num);
2908     *p_obj_list = talloc_realloc(NULL, *p_obj_list, struct m_obj_settings,
2909                                  num + 2);
2910     memmove(*p_obj_list + idx + 1, *p_obj_list + idx,
2911             (num - idx) * sizeof(m_obj_settings_t));
2912     (*p_obj_list)[idx] = *item;
2913     (*p_obj_list)[num + 1] = (m_obj_settings_t){0};
2914 }
2915 
obj_settings_list_find_by_label(m_obj_settings_t * obj_list,bstr label)2916 static int obj_settings_list_find_by_label(m_obj_settings_t *obj_list,
2917                                            bstr label)
2918 {
2919     for (int n = 0; obj_list && obj_list[n].name; n++) {
2920         if (label.len && bstr_equals0(label, obj_list[n].label))
2921             return n;
2922     }
2923     return -1;
2924 }
2925 
obj_settings_list_find_by_label0(m_obj_settings_t * obj_list,const char * label)2926 static int obj_settings_list_find_by_label0(m_obj_settings_t *obj_list,
2927                                             const char *label)
2928 {
2929     return obj_settings_list_find_by_label(obj_list, bstr0(label));
2930 }
2931 
obj_settings_find_by_content(m_obj_settings_t * obj_list,m_obj_settings_t * item)2932 static int obj_settings_find_by_content(m_obj_settings_t *obj_list,
2933                                         m_obj_settings_t *item)
2934 {
2935     for (int n = 0; obj_list && obj_list[n].name; n++) {
2936         if (obj_setting_match(&obj_list[n], item))
2937             return n;
2938     }
2939     return -1;
2940 }
2941 
free_obj_settings_list(void * dst)2942 static void free_obj_settings_list(void *dst)
2943 {
2944     int n;
2945     m_obj_settings_t *d;
2946 
2947     if (!dst || !VAL(dst))
2948         return;
2949 
2950     d = VAL(dst);
2951     for (n = 0; d[n].name; n++)
2952         obj_setting_free(&d[n]);
2953     talloc_free(d);
2954     VAL(dst) = NULL;
2955 }
2956 
copy_obj_settings_list(const m_option_t * opt,void * dst,const void * src)2957 static void copy_obj_settings_list(const m_option_t *opt, void *dst,
2958                                    const void *src)
2959 {
2960     m_obj_settings_t *d, *s;
2961     int n;
2962 
2963     if (!(dst && src))
2964         return;
2965 
2966     s = VAL(src);
2967 
2968     if (VAL(dst))
2969         free_obj_settings_list(dst);
2970     if (!s)
2971         return;
2972 
2973     for (n = 0; s[n].name; n++)
2974         /* NOP */;
2975     d = talloc_array(NULL, struct m_obj_settings, n + 1);
2976     for (n = 0; s[n].name; n++) {
2977         d[n].name = talloc_strdup(NULL, s[n].name);
2978         d[n].label = talloc_strdup(NULL, s[n].label);
2979         d[n].enabled = s[n].enabled;
2980         d[n].attribs = NULL;
2981         copy_str_list(NULL, &(d[n].attribs), &(s[n].attribs));
2982     }
2983     d[n].name = NULL;
2984     d[n].label = NULL;
2985     d[n].attribs = NULL;
2986     VAL(dst) = d;
2987 }
2988 
2989 // Consider -vf a=b=c:d=e. This verifies "b"="c" and "d"="e" and that the
2990 // option names/values are correct. Try to determine whether an option
2991 // without '=' sets a flag, or whether it's a positional argument.
get_obj_param(struct mp_log * log,bstr opt_name,bstr obj_name,struct m_config * config,bstr name,bstr val,int flags,bool nopos,int * nold,bstr * out_name,bstr * out_val,char * tmp,size_t tmp_size)2992 static int get_obj_param(struct mp_log *log, bstr opt_name, bstr obj_name,
2993                          struct m_config *config, bstr name, bstr val,
2994                          int flags, bool nopos,
2995                          int *nold, bstr *out_name, bstr *out_val,
2996                          char *tmp, size_t tmp_size)
2997 {
2998     int r;
2999 
3000     if (!config) {
3001         // Duplicates the logic below, but with unknown parameter types/names.
3002         if (val.start || nopos) {
3003             *out_name = name;
3004             *out_val = val;
3005         } else {
3006             val = name;
3007             // positional fields
3008             if (val.len == 0) { // Empty field, count it and go on
3009                 (*nold)++;
3010                 return 0;
3011             }
3012             // Positional naming convention for/followed by mp_set_avopts().
3013             snprintf(tmp, tmp_size, "@%d", *nold);
3014             *out_name = bstr0(tmp);
3015             *out_val = val;
3016             (*nold)++;
3017         }
3018         return 1;
3019     }
3020 
3021     // val.start != NULL => of the form name=val (not positional)
3022     // If it's just "name", and the associated option exists and is a flag,
3023     // don't accept it as positional argument.
3024     if (val.start || m_config_option_requires_param(config, name) == 0 || nopos) {
3025         r = m_config_set_option_cli(config, name, val, flags);
3026         if (r < 0) {
3027             if (r == M_OPT_UNKNOWN) {
3028                 mp_err(log, "Option %.*s: %.*s doesn't have a %.*s parameter.\n",
3029                        BSTR_P(opt_name), BSTR_P(obj_name), BSTR_P(name));
3030                 return M_OPT_UNKNOWN;
3031             }
3032             if (r != M_OPT_EXIT)
3033                 mp_err(log, "Option %.*s: "
3034                        "Error while parsing %.*s parameter %.*s (%.*s)\n",
3035                        BSTR_P(opt_name), BSTR_P(obj_name), BSTR_P(name),
3036                        BSTR_P(val));
3037             return r;
3038         }
3039         *out_name = name;
3040         *out_val = val;
3041         return 1;
3042     } else {
3043         val = name;
3044         // positional fields
3045         if (val.len == 0) { // Empty field, count it and go on
3046             (*nold)++;
3047             return 0;
3048         }
3049         const char *opt = m_config_get_positional_option(config, *nold);
3050         if (!opt) {
3051             mp_err(log, "Option %.*s: %.*s has only %d "
3052                    "params, so you can't give more than %d unnamed params.\n",
3053                    BSTR_P(opt_name), BSTR_P(obj_name), *nold, *nold);
3054             return M_OPT_OUT_OF_RANGE;
3055         }
3056         r = m_config_set_option_cli(config, bstr0(opt), val, flags);
3057         if (r < 0) {
3058             if (r != M_OPT_EXIT)
3059                 mp_err(log, "Option %.*s: "
3060                        "Error while parsing %.*s parameter %s (%.*s)\n",
3061                        BSTR_P(opt_name), BSTR_P(obj_name), opt, BSTR_P(val));
3062             return r;
3063         }
3064         *out_name = bstr0(opt);
3065         *out_val = val;
3066         (*nold)++;
3067         return 1;
3068     }
3069 }
3070 
3071 // Consider -vf a=b:c:d. This parses "b:c:d" into name/value pairs, stored as
3072 // linear array in *_ret. In particular, config contains what options a the
3073 // object takes, and verifies the option values as well.
3074 // If config is NULL, all parameters are accepted without checking.
3075 // _ret set to NULL can be used for checking-only.
3076 // flags can contain any M_SETOPT_* flag.
3077 // desc is optional.
m_obj_parse_sub_config(struct mp_log * log,struct bstr opt_name,struct bstr name,struct bstr * pstr,struct m_config * config,int flags,bool nopos,struct m_obj_desc * desc,const struct m_obj_list * list,char *** ret)3078 static int m_obj_parse_sub_config(struct mp_log *log, struct bstr opt_name,
3079                                   struct bstr name, struct bstr *pstr,
3080                                   struct m_config *config, int flags, bool nopos,
3081                                   struct m_obj_desc *desc,
3082                                   const struct m_obj_list *list, char ***ret)
3083 {
3084     int nold = 0;
3085     char **args = NULL;
3086     int num_args = 0;
3087     int r = 1;
3088     char tmp[80];
3089 
3090     if (ret) {
3091         args = *ret;
3092         while (args && args[num_args])
3093             num_args++;
3094     }
3095 
3096     while (pstr->len > 0) {
3097         bstr fname, fval;
3098         r = split_subconf(log, opt_name, pstr, &fname, &fval);
3099         if (r < 0)
3100             goto exit;
3101 
3102         if (list->use_global_options) {
3103             mp_err(log, "Option %.*s: this option does not accept sub-options.\n",
3104                    BSTR_P(opt_name));
3105             mp_err(log, "Sub-options for --vo and --ao were removed from mpv in "
3106                    "release 0.23.0.\nSee https://0x0.st/uM for details.\n");
3107             r = M_OPT_INVALID;
3108             goto exit;
3109         }
3110 
3111         if (bstr_equals0(fname, "help"))
3112             goto print_help;
3113         r = get_obj_param(log, opt_name, name, config, fname, fval, flags,
3114                           nopos, &nold, &fname, &fval, tmp, sizeof(tmp));
3115         if (r < 0)
3116             goto exit;
3117 
3118         if (r > 0 && ret) {
3119             MP_TARRAY_APPEND(NULL, args, num_args, bstrto0(NULL, fname));
3120             MP_TARRAY_APPEND(NULL, args, num_args, bstrto0(NULL, fval));
3121             MP_TARRAY_APPEND(NULL, args, num_args, NULL);
3122             MP_TARRAY_APPEND(NULL, args, num_args, NULL);
3123             num_args -= 2;
3124         }
3125 
3126         if (!bstr_eatstart0(pstr, ":"))
3127             break;
3128     }
3129 
3130     if (ret) {
3131         if (num_args > 0) {
3132             *ret = args;
3133             args = NULL;
3134         } else {
3135             *ret = NULL;
3136         }
3137     }
3138 
3139     goto exit;
3140 
3141 print_help: ;
3142     if (config) {
3143         if (desc->print_help)
3144             desc->print_help(log);
3145         m_config_print_option_list(config, "*");
3146     } else if (list->print_unknown_entry_help) {
3147         list->print_unknown_entry_help(log, mp_tprintf(80, "%.*s", BSTR_P(name)));
3148     } else {
3149         mp_warn(log, "Option %.*s: item %.*s doesn't exist.\n",
3150                BSTR_P(opt_name), BSTR_P(name));
3151     }
3152     r = M_OPT_EXIT;
3153 
3154 exit:
3155     free_str_list(&args);
3156     return r;
3157 }
3158 
3159 // Characters which may appear in a filter name
3160 #define NAMECH "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"
3161 
3162 // Parse one item, e.g. -vf a=b:c:d,e=f:g => parse a=b:c:d into "a" and "b:c:d"
parse_obj_settings(struct mp_log * log,struct bstr opt,int op,struct bstr * pstr,const struct m_obj_list * list,m_obj_settings_t ** _ret)3163 static int parse_obj_settings(struct mp_log *log, struct bstr opt, int op,
3164                               struct bstr *pstr, const struct m_obj_list *list,
3165                               m_obj_settings_t **_ret)
3166 {
3167     int r;
3168     char **plist = NULL;
3169     struct m_obj_desc desc;
3170     bstr str = {0};
3171     bstr label = {0};
3172     bool nopos = list->disallow_positional_parameters;
3173     bool enabled = true;
3174 
3175     if (bstr_eatstart0(pstr, "@")) {
3176         bstr rest;
3177         if (!bstr_split_tok(*pstr, ":", &label, &rest)) {
3178             // "@labelname" is the special enable/disable toggle syntax
3179             if (op == OP_TOGGLE) {
3180                 int idx = bstrspn(*pstr, NAMECH);
3181                 label = bstr_splice(*pstr, 0, idx);
3182                 if (label.len) {
3183                     *pstr = bstr_cut(*pstr, idx);
3184                     goto done;
3185                 }
3186             }
3187             mp_err(log, "Option %.*s: ':' expected after label.\n", BSTR_P(opt));
3188             return M_OPT_INVALID;
3189         }
3190         *pstr = rest;
3191         if (label.len == 0) {
3192             mp_err(log, "Option %.*s: label name expected.\n", BSTR_P(opt));
3193             return M_OPT_INVALID;
3194         }
3195     }
3196 
3197     if (list->allow_disable_entries && bstr_eatstart0(pstr, "!"))
3198         enabled = false;
3199 
3200     bool has_param = false;
3201     int idx = bstrspn(*pstr, NAMECH);
3202     str = bstr_splice(*pstr, 0, idx);
3203     if (!str.len) {
3204         mp_err(log, "Option %.*s: filter name expected.\n", BSTR_P(opt));
3205         return M_OPT_INVALID;
3206     }
3207     *pstr = bstr_cut(*pstr, idx);
3208     // video filters use "=", VOs use ":"
3209     if (bstr_eatstart0(pstr, "=") || bstr_eatstart0(pstr, ":"))
3210         has_param = true;
3211 
3212     bool skip = false;
3213     if (m_obj_list_find(&desc, list, str)) {
3214         if (desc.replaced_name)
3215             mp_warn(log, "Driver '%s' has been replaced with '%s'!\n",
3216                    desc.replaced_name, desc.name);
3217     } else {
3218         char name[80];
3219         snprintf(name, sizeof(name), "%.*s", BSTR_P(str));
3220         if (!list->allow_unknown_entries ||
3221             (list->check_unknown_entry && !list->check_unknown_entry(name)))
3222         {
3223             mp_err(log, "Option %.*s: %.*s doesn't exist.\n",
3224                    BSTR_P(opt), BSTR_P(str));
3225             return M_OPT_INVALID;
3226         }
3227         desc = (struct m_obj_desc){0};
3228         skip = true;
3229     }
3230 
3231     if (has_param) {
3232         struct m_config *config = NULL;
3233         if (!skip)
3234             config = m_config_from_obj_desc_noalloc(NULL, log, &desc);
3235         r = m_obj_parse_sub_config(log, opt, str, pstr, config,
3236                                    M_SETOPT_CHECK_ONLY, nopos, &desc, list,
3237                                    _ret ? &plist : NULL);
3238         talloc_free(config);
3239         if (r < 0)
3240             return r;
3241     }
3242     if (!_ret)
3243         return 1;
3244 
3245 done: ;
3246     m_obj_settings_t item = {
3247         .name = bstrto0(NULL, str),
3248         .label = bstrdup0(NULL, label),
3249         .enabled = enabled,
3250         .attribs = plist,
3251     };
3252     obj_settings_list_insert_at(_ret, -1, &item);
3253     return 1;
3254 }
3255 
3256 // Parse a single entry for -vf-del (return 0 if not applicable)
3257 // mark_del is bounded by the number of items in dst
parse_obj_settings_del(struct mp_log * log,struct bstr opt_name,struct bstr * param,int op,void * dst,bool * mark_del)3258 static int parse_obj_settings_del(struct mp_log *log, struct bstr opt_name,
3259                                   struct bstr *param, int op,
3260                                   void *dst, bool *mark_del)
3261 {
3262     bstr s = *param;
3263     if (bstr_eatstart0(&s, "@")) {
3264         // '@name:' -> parse as normal filter entry
3265         // '@name,' or '@name<end>' -> parse here
3266         int idx = bstrspn(s, NAMECH);
3267         bstr label = bstr_splice(s, 0, idx);
3268         s = bstr_cut(s, idx);
3269         if (bstr_startswith0(s, ":"))
3270             return 0;
3271         if (dst) {
3272             int label_index = obj_settings_list_find_by_label(VAL(dst), label);
3273             if (label_index >= 0) {
3274                 mark_del[label_index] = true;
3275             } else {
3276                 mp_warn(log, "Option %.*s: item label @%.*s not found.\n",
3277                         BSTR_P(opt_name), BSTR_P(label));
3278             }
3279         }
3280         *param = s;
3281         return 1;
3282     }
3283 
3284     if (op == OP_REMOVE)
3285         return 0;
3286 
3287     bstr rest;
3288     long long id = bstrtoll(s, &rest, 0);
3289     if (rest.len == s.len)
3290         return 0;
3291 
3292     if (dst) {
3293         int num = obj_settings_list_num_items(VAL(dst));
3294         if (id < 0)
3295             id = num + id;
3296 
3297         if (id >= 0 && id < num) {
3298             mark_del[id] = true;
3299         } else {
3300             mp_warn(log, "Option %.*s: Index %lld is out of range.\n",
3301                     BSTR_P(opt_name), id);
3302         }
3303     }
3304 
3305     *param = rest;
3306     return 1;
3307 }
3308 
parse_obj_settings_list(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)3309 static int parse_obj_settings_list(struct mp_log *log, const m_option_t *opt,
3310                                    struct bstr name, struct bstr param, void *dst)
3311 {
3312     m_obj_settings_t *res = NULL;
3313     int op = OP_NONE;
3314     bool *mark_del = NULL;
3315     int num_items = obj_settings_list_num_items(dst ? VAL(dst) : 0);
3316     struct m_obj_list *ol = opt->priv;
3317 
3318     assert(opt->priv);
3319 
3320     if (bstr_endswith0(name, "-add")) {
3321         op = OP_ADD;
3322     } else if (bstr_endswith0(name, "-append")) {
3323         op = OP_APPEND;
3324     } else if (bstr_endswith0(name, "-set")) {
3325         op = OP_NONE;
3326     } else if (bstr_endswith0(name, "-pre")) {
3327         op = OP_PRE;
3328     } else if (bstr_endswith0(name, "-del")) {
3329         op = OP_DEL;
3330         mp_warn(log, "Option %.*s: -del is deprecated! "
3331                 "Use -remove (removes by content instead of by index).\n",
3332                 BSTR_P(name));
3333     } else if (bstr_endswith0(name, "-remove")) {
3334         op = OP_REMOVE;
3335     } else if (bstr_endswith0(name, "-clr")) {
3336         op = OP_CLR;
3337     } else if (bstr_endswith0(name, "-toggle")) {
3338         op = OP_TOGGLE;
3339     } else if (bstr_endswith0(name, "-help")) {
3340         mp_err(log, "Option %s:\n"
3341                 "Supported operations are:\n"
3342                 "  %s-set\n"
3343                 " Overwrite the old list with the given list\n\n"
3344                 "  %s-append\n"
3345                 " Append the given filter to the current list\n\n"
3346                 "  %s-add\n"
3347                 " Append the given list to the current list\n\n"
3348                 "  %s-pre\n"
3349                 " Prepend the given list to the current list\n\n"
3350                 "  %s-remove\n"
3351                 " Remove the given filter from the current list\n\n"
3352                 "  %s-del x,y,...\n"
3353                 " Remove the given elements. Take the list element index (starting from 0).\n"
3354                 " Negative index can be used (i.e. -1 is the last element).\n"
3355                 " Filter names work as well.\n\n"
3356                 "  %s-toggle\n"
3357                 " Add the filter to the list, or remove it if it's already added.\n\n"
3358                 "  %s-clr\n"
3359                 " Clear the current list.\n\n",
3360                 opt->name, opt->name, opt->name, opt->name, opt->name,
3361                 opt->name, opt->name, opt->name, opt->name);
3362 
3363         return M_OPT_EXIT;
3364     }
3365 
3366     if (!bstrcmp0(param, "help")) {
3367         mp_info(log, "Available %s:\n", ol->description);
3368         for (int n = 0; ; n++) {
3369             struct m_obj_desc desc;
3370             if (!ol->get_desc(&desc, n))
3371                 break;
3372             if (!desc.hidden) {
3373                 mp_info(log, "  %-16s %s\n",
3374                        desc.name, desc.description);
3375             }
3376         }
3377         mp_info(log, "\n");
3378         if (ol->print_help_list)
3379             ol->print_help_list(log);
3380         if (!ol->use_global_options) {
3381             mp_info(log, "Get help on individual entries via: --%s=entry=help\n",
3382                     opt->name);
3383         }
3384         return M_OPT_EXIT;
3385     }
3386 
3387     if (op == OP_CLR) {
3388         if (param.len) {
3389             mp_err(log, "Option %.*s: -clr does not take an argument.\n",
3390                    BSTR_P(name));
3391             return M_OPT_INVALID;
3392         }
3393         if (dst)
3394             free_obj_settings_list(dst);
3395         return 0;
3396     } else if (op == OP_DEL || op == OP_REMOVE) {
3397         mark_del = talloc_zero_array(NULL, bool, num_items + 1);
3398     }
3399 
3400     if (op != OP_NONE && param.len == 0)
3401         return M_OPT_MISSING_PARAM;
3402 
3403     while (param.len > 0) {
3404         int r = 0;
3405         if (op == OP_DEL || op == OP_REMOVE)
3406             r = parse_obj_settings_del(log, name, &param, op, dst, mark_del);
3407         if (r == 0) {
3408             r = parse_obj_settings(log, name, op, &param, ol, dst ? &res : NULL);
3409         }
3410         if (r < 0)
3411             return r;
3412         if (param.len > 0) {
3413             const char sep[2] = {OPTION_LIST_SEPARATOR, 0};
3414             if (!bstr_eatstart0(&param, sep))
3415                 return M_OPT_INVALID;
3416             if (param.len == 0) {
3417                 if (!ol->allow_trailer)
3418                     return M_OPT_INVALID;
3419                 if (dst) {
3420                     m_obj_settings_t item = {
3421                         .name = talloc_strdup(NULL, ""),
3422                     };
3423                     obj_settings_list_insert_at(&res, -1, &item);
3424                 }
3425             }
3426         }
3427     }
3428 
3429     if (op != OP_NONE && res && res[0].name && res[1].name) {
3430         if (op == OP_APPEND) {
3431             mp_err(log, "Option %.*s: -append takes only 1 filter (no ',').\n",
3432                    BSTR_P(name));
3433             return M_OPT_INVALID;
3434         }
3435         mp_warn(log, "Passing more than 1 argument to %.*s is deprecated!\n",
3436                 BSTR_P(name));
3437     }
3438 
3439     if (dst) {
3440         m_obj_settings_t *list = VAL(dst);
3441         if (op == OP_PRE) {
3442             int prepend_counter = 0;
3443             for (int n = 0; res && res[n].name; n++) {
3444                 int label = obj_settings_list_find_by_label0(list, res[n].label);
3445                 if (label < 0) {
3446                     obj_settings_list_insert_at(&list, prepend_counter, &res[n]);
3447                     prepend_counter++;
3448                 } else {
3449                     // Prefer replacement semantics, instead of actually
3450                     // prepending.
3451                     obj_setting_free(&list[label]);
3452                     list[label] = res[n];
3453                 }
3454             }
3455             talloc_free(res);
3456         } else if (op == OP_ADD || op == OP_APPEND) {
3457             for (int n = 0; res && res[n].name; n++) {
3458                 int label = obj_settings_list_find_by_label0(list, res[n].label);
3459                 if (label < 0) {
3460                     obj_settings_list_insert_at(&list, -1, &res[n]);
3461                 } else {
3462                     // Prefer replacement semantics, instead of actually
3463                     // appending.
3464                     obj_setting_free(&list[label]);
3465                     list[label] = res[n];
3466                 }
3467             }
3468             talloc_free(res);
3469         } else if (op == OP_TOGGLE) {
3470             for (int n = 0; res && res[n].name; n++) {
3471                 if (res[n].label && !res[n].name[0]) {
3472                     // Toggle enable/disable special case.
3473                     int found =
3474                         obj_settings_list_find_by_label0(list, res[n].label);
3475                     if (found < 0) {
3476                         mp_warn(log, "Option %.*s: Label %s not found\n",
3477                                 BSTR_P(name), res[n].label);
3478                     } else {
3479                         list[found].enabled = !list[found].enabled;
3480                     }
3481                     obj_setting_free(&res[n]);
3482                 } else {
3483                     int found = obj_settings_find_by_content(list, &res[n]);
3484                     if (found < 0) {
3485                         obj_settings_list_insert_at(&list, -1, &res[n]);
3486                     } else {
3487                         obj_settings_list_del_at(&list, found);
3488                         obj_setting_free(&res[n]);
3489                     }
3490                 }
3491             }
3492             talloc_free(res);
3493         } else if (op == OP_DEL || op == OP_REMOVE) {
3494             for (int n = num_items - 1; n >= 0; n--) {
3495                 if (mark_del[n])
3496                     obj_settings_list_del_at(&list, n);
3497             }
3498             for (int n = 0; res && res[n].name; n++) {
3499                 int found = obj_settings_find_by_content(list, &res[n]);
3500                 if (found < 0) {
3501                     if (op == OP_DEL)
3502                         mp_warn(log, "Option %.*s: Item not found\n", BSTR_P(name));
3503                 } else {
3504                     obj_settings_list_del_at(&list, found);
3505                 }
3506             }
3507             free_obj_settings_list(&res);
3508         } else {
3509             assert(op == OP_NONE);
3510             free_obj_settings_list(&list);
3511             list = res;
3512         }
3513         VAL(dst) = list;
3514     }
3515 
3516     talloc_free(mark_del);
3517     return 1;
3518 }
3519 
append_param(char ** res,char * param)3520 static void append_param(char **res, char *param)
3521 {
3522     if (strspn(param, NAMECH) == strlen(param)) {
3523         *res = talloc_strdup_append(*res, param);
3524     } else {
3525         // Simple escaping: %BYTECOUNT%STRING
3526         *res = talloc_asprintf_append(*res, "%%%zd%%%s", strlen(param), param);
3527     }
3528 }
3529 
print_obj_settings_list(const m_option_t * opt,const void * val)3530 static char *print_obj_settings_list(const m_option_t *opt, const void *val)
3531 {
3532     m_obj_settings_t *list = VAL(val);
3533     char *res = talloc_strdup(NULL, "");
3534     for (int n = 0; list && list[n].name; n++) {
3535         m_obj_settings_t *entry = &list[n];
3536         if (n > 0)
3537             res = talloc_strdup_append(res, ",");
3538         // Assume labels and names don't need escaping
3539         if (entry->label && entry->label[0])
3540             res = talloc_asprintf_append(res, "@%s:", entry->label);
3541         if (!entry->enabled)
3542             res = talloc_strdup_append(res, "!");
3543         res = talloc_strdup_append(res, entry->name);
3544         if (entry->attribs && entry->attribs[0]) {
3545             res = talloc_strdup_append(res, "=");
3546             for (int i = 0; entry->attribs[i * 2 + 0]; i++) {
3547                 if (i > 0)
3548                     res = talloc_strdup_append(res, ":");
3549                 append_param(&res, entry->attribs[i * 2 + 0]);
3550                 res = talloc_strdup_append(res, "=");
3551                 append_param(&res, entry->attribs[i * 2 + 1]);
3552             }
3553         }
3554     }
3555     return res;
3556 }
3557 
set_obj_settings_list(const m_option_t * opt,void * dst,struct mpv_node * src)3558 static int set_obj_settings_list(const m_option_t *opt, void *dst,
3559                                  struct mpv_node *src)
3560 {
3561     if (src->format != MPV_FORMAT_NODE_ARRAY)
3562         return M_OPT_INVALID;
3563     m_obj_settings_t *entries =
3564         talloc_zero_array(NULL, m_obj_settings_t, src->u.list->num + 1);
3565     for (int n = 0; n < src->u.list->num; n++) {
3566         m_obj_settings_t *entry = &entries[n];
3567         entry->enabled = true;
3568         if (src->u.list->values[n].format != MPV_FORMAT_NODE_MAP)
3569             goto error;
3570         struct mpv_node_list *src_entry = src->u.list->values[n].u.list;
3571         for (int i = 0; i < src_entry->num; i++) {
3572             const char *key = src_entry->keys[i];
3573             struct mpv_node *val = &src_entry->values[i];
3574             if (strcmp(key, "name") == 0) {
3575                 if (val->format != MPV_FORMAT_STRING)
3576                     goto error;
3577                 entry->name = talloc_strdup(NULL, val->u.string);
3578             } else if (strcmp(key, "label") == 0) {
3579                 if (val->format != MPV_FORMAT_STRING)
3580                     goto error;
3581                 entry->label = talloc_strdup(NULL, val->u.string);
3582             } else if (strcmp(key, "enabled") == 0) {
3583                 if (val->format != MPV_FORMAT_FLAG)
3584                     goto error;
3585                 entry->enabled = val->u.flag;
3586             } else if (strcmp(key, "params") == 0) {
3587                 if (val->format != MPV_FORMAT_NODE_MAP)
3588                     goto error;
3589                 struct mpv_node_list *src_params = val->u.list;
3590                 entry->attribs =
3591                     talloc_zero_array(NULL, char*, (src_params->num + 1) * 2);
3592                 for (int x = 0; x < src_params->num; x++) {
3593                     if (src_params->values[x].format != MPV_FORMAT_STRING)
3594                         goto error;
3595                     entry->attribs[x * 2 + 0] =
3596                         talloc_strdup(NULL, src_params->keys[x]);
3597                     entry->attribs[x * 2 + 1] =
3598                         talloc_strdup(NULL, src_params->values[x].u.string);
3599                 }
3600             }
3601         }
3602     }
3603     free_obj_settings_list(dst);
3604     VAL(dst) = entries;
3605     return 0;
3606 error:
3607     free_obj_settings_list(&entries);
3608     return M_OPT_INVALID;
3609 }
3610 
add_array_entry(struct mpv_node * dst)3611 static struct mpv_node *add_array_entry(struct mpv_node *dst)
3612 {
3613     struct mpv_node_list *list = dst->u.list;
3614     assert(dst->format == MPV_FORMAT_NODE_ARRAY&& dst->u.list);
3615     MP_TARRAY_GROW(list, list->values, list->num);
3616     return &list->values[list->num++];
3617 }
3618 
add_map_entry(struct mpv_node * dst,const char * key)3619 static struct mpv_node *add_map_entry(struct mpv_node *dst, const char *key)
3620 {
3621     struct mpv_node_list *list = dst->u.list;
3622     assert(dst->format == MPV_FORMAT_NODE_MAP && dst->u.list);
3623     MP_TARRAY_GROW(list, list->values, list->num);
3624     MP_TARRAY_GROW(list, list->keys, list->num);
3625     list->keys[list->num] = talloc_strdup(list, key);
3626     return &list->values[list->num++];
3627 }
3628 
add_map_string(struct mpv_node * dst,const char * key,const char * val)3629 static void add_map_string(struct mpv_node *dst, const char *key, const char *val)
3630 {
3631     struct mpv_node *entry = add_map_entry(dst, key);
3632     entry->format = MPV_FORMAT_STRING;
3633     entry->u.string = talloc_strdup(dst->u.list, val);
3634 }
3635 
get_obj_settings_list(const m_option_t * opt,void * ta_parent,struct mpv_node * dst,void * val)3636 static int get_obj_settings_list(const m_option_t *opt, void *ta_parent,
3637                                  struct mpv_node *dst, void *val)
3638 {
3639     m_obj_settings_t *list = VAL(val);
3640     dst->format = MPV_FORMAT_NODE_ARRAY;
3641     dst->u.list = talloc_zero(ta_parent, struct mpv_node_list);
3642     ta_parent = dst->u.list;
3643     for (int n = 0; list && list[n].name; n++) {
3644         m_obj_settings_t *entry = &list[n];
3645         struct mpv_node *nentry = add_array_entry(dst);
3646         nentry->format = MPV_FORMAT_NODE_MAP;
3647         nentry->u.list = talloc_zero(ta_parent, struct mpv_node_list);
3648         add_map_string(nentry, "name", entry->name);
3649         if (entry->label && entry->label[0])
3650             add_map_string(nentry, "label", entry->label);
3651         struct mpv_node *enabled = add_map_entry(nentry, "enabled");
3652         enabled->format = MPV_FORMAT_FLAG;
3653         enabled->u.flag = entry->enabled;
3654         struct mpv_node *params = add_map_entry(nentry, "params");
3655         params->format = MPV_FORMAT_NODE_MAP;
3656         params->u.list = talloc_zero(ta_parent, struct mpv_node_list);
3657         for (int i = 0; entry->attribs && entry->attribs[i * 2 + 0]; i++) {
3658             add_map_string(params, entry->attribs[i * 2 + 0],
3659                                    entry->attribs[i * 2 + 1]);
3660         }
3661     }
3662     return 1;
3663 }
3664 
obj_settings_list_equal(const m_option_t * opt,void * pa,void * pb)3665 static bool obj_settings_list_equal(const m_option_t *opt, void *pa, void *pb)
3666 {
3667     struct m_obj_settings *a = VAL(pa);
3668     struct m_obj_settings *b = VAL(pb);
3669 
3670     if (a == b || !a || !b)
3671         return a == b || (!a && !b[0].name) || (!b && !a[0].name);
3672 
3673     for (int n = 0; a[n].name || b[n].name; n++) {
3674         if (!a[n].name || !b[n].name)
3675             return false;
3676         if (!m_obj_settings_equal(&a[n], &b[n]))
3677             return false;
3678     }
3679 
3680     return true;
3681 }
3682 
m_obj_settings_equal(struct m_obj_settings * a,struct m_obj_settings * b)3683 bool m_obj_settings_equal(struct m_obj_settings *a, struct m_obj_settings *b)
3684 {
3685     if (!str_equal(NULL, &a->name, &b->name))
3686         return false;
3687 
3688     if (!str_equal(NULL, &a->label, &b->label))
3689         return false;
3690 
3691     if (a->enabled != b->enabled)
3692         return false;
3693 
3694     return str_list_equal(NULL, &a->attribs, &b->attribs);
3695 }
3696 
3697 const m_option_type_t m_option_type_obj_settings_list = {
3698     .name  = "Object settings list",
3699     .size  = sizeof(m_obj_settings_t *),
3700     .parse = parse_obj_settings_list,
3701     .print = print_obj_settings_list,
3702     .copy  = copy_obj_settings_list,
3703     .free  = free_obj_settings_list,
3704     .set   = set_obj_settings_list,
3705     .get   = get_obj_settings_list,
3706     .equal = obj_settings_list_equal,
3707     .actions = (const struct m_option_action[]){
3708         {"add"},
3709         {"append"},
3710         {"clr",     M_OPT_TYPE_OPTIONAL_PARAM},
3711         {"del"},
3712         {"help",    M_OPT_TYPE_OPTIONAL_PARAM},
3713         {"pre"},
3714         {"set"},
3715         {"toggle"},
3716         {"remove"},
3717         {0}
3718     },
3719 };
3720 
3721 #undef VAL
3722 #define VAL(x) (*(struct mpv_node *)(x))
3723 
parse_node(struct mp_log * log,const m_option_t * opt,struct bstr name,struct bstr param,void * dst)3724 static int parse_node(struct mp_log *log, const m_option_t *opt,
3725                       struct bstr name, struct bstr param, void *dst)
3726 {
3727     // Maybe use JSON?
3728     mp_err(log, "option type doesn't accept strings");
3729     return M_OPT_INVALID;
3730 }
3731 
print_node(const m_option_t * opt,const void * val)3732 static char *print_node(const m_option_t *opt, const void *val)
3733 {
3734     char *t = talloc_strdup(NULL, "");
3735     if (json_write(&t, &VAL(val)) < 0) {
3736         talloc_free(t);
3737         t = NULL;
3738     }
3739     return t;
3740 }
3741 
pretty_print_node(const m_option_t * opt,const void * val)3742 static char *pretty_print_node(const m_option_t *opt, const void *val)
3743 {
3744     char *t = talloc_strdup(NULL, "");
3745     if (json_write_pretty(&t, &VAL(val)) < 0) {
3746         talloc_free(t);
3747         t = NULL;
3748     }
3749     return t;
3750 }
3751 
dup_node(void * ta_parent,struct mpv_node * node)3752 static void dup_node(void *ta_parent, struct mpv_node *node)
3753 {
3754     switch (node->format) {
3755     case MPV_FORMAT_STRING:
3756         node->u.string = talloc_strdup(ta_parent, node->u.string);
3757         break;
3758     case MPV_FORMAT_NODE_ARRAY:
3759     case MPV_FORMAT_NODE_MAP: {
3760         struct mpv_node_list *oldlist = node->u.list;
3761         struct mpv_node_list *new = talloc_zero(ta_parent, struct mpv_node_list);
3762         node->u.list = new;
3763         if (oldlist->num > 0) {
3764             *new = *oldlist;
3765             new->values = talloc_array(new, struct mpv_node, new->num);
3766             for (int n = 0; n < new->num; n++) {
3767                 new->values[n] = oldlist->values[n];
3768                 dup_node(new, &new->values[n]);
3769             }
3770             if (node->format == MPV_FORMAT_NODE_MAP) {
3771                 new->keys = talloc_array(new, char*, new->num);
3772                 for (int n = 0; n < new->num; n++)
3773                     new->keys[n] = talloc_strdup(new, oldlist->keys[n]);
3774             }
3775         }
3776         break;
3777     }
3778     case MPV_FORMAT_NONE:
3779     case MPV_FORMAT_FLAG:
3780     case MPV_FORMAT_INT64:
3781     case MPV_FORMAT_DOUBLE:
3782         break;
3783     default:
3784         // unknown entry - mark as invalid
3785         node->format = (mpv_format)-1;
3786     }
3787 }
3788 
copy_node(const m_option_t * opt,void * dst,const void * src)3789 static void copy_node(const m_option_t *opt, void *dst, const void *src)
3790 {
3791     assert(sizeof(struct mpv_node) <= sizeof(union m_option_value));
3792 
3793     if (!(dst && src))
3794         return;
3795 
3796     opt->type->free(dst);
3797     VAL(dst) = VAL(src);
3798     dup_node(NULL, &VAL(dst));
3799 }
3800 
node_get_alloc(struct mpv_node * node)3801 void *node_get_alloc(struct mpv_node *node)
3802 {
3803     // Assume it was allocated with copy_node(), which allocates all
3804     // sub-nodes with the parent node as talloc parent.
3805     switch (node->format) {
3806     case MPV_FORMAT_STRING:
3807         return node->u.string;
3808     case MPV_FORMAT_NODE_ARRAY:
3809     case MPV_FORMAT_NODE_MAP:
3810         return node->u.list;
3811     default:
3812         return NULL;
3813     }
3814 }
3815 
free_node(void * src)3816 static void free_node(void *src)
3817 {
3818     if (src) {
3819         struct mpv_node *node = &VAL(src);
3820         talloc_free(node_get_alloc(node));
3821         *node = (struct mpv_node){{0}};
3822     }
3823 }
3824 
3825 // idempotent functions for convenience
node_set(const m_option_t * opt,void * dst,struct mpv_node * src)3826 static int node_set(const m_option_t *opt, void *dst, struct mpv_node *src)
3827 {
3828     copy_node(opt, dst, src);
3829     return 1;
3830 }
3831 
node_get(const m_option_t * opt,void * ta_parent,struct mpv_node * dst,void * src)3832 static int node_get(const m_option_t *opt, void *ta_parent,
3833                     struct mpv_node *dst, void *src)
3834 {
3835     *dst = VAL(src);
3836     dup_node(ta_parent, dst);
3837     return 1;
3838 }
3839 
node_equal(const m_option_t * opt,void * a,void * b)3840 static bool node_equal(const m_option_t *opt, void *a, void *b)
3841 {
3842     return equal_mpv_node(&VAL(a), &VAL(b));
3843 }
3844 
3845 const m_option_type_t m_option_type_node = {
3846     .name  = "Complex",
3847     .size  = sizeof(struct mpv_node),
3848     .parse = parse_node,
3849     .print = print_node,
3850     .pretty_print = pretty_print_node,
3851     .copy  = copy_node,
3852     .free  = free_node,
3853     .set   = node_set,
3854     .get   = node_get,
3855     .equal = node_equal,
3856 };
3857 
3858 // Special-cased by m_config.c.
3859 const m_option_type_t m_option_type_alias = {
3860     .name  = "alias",
3861 };
3862 const m_option_type_t m_option_type_cli_alias = {
3863     .name  = "alias",
3864 };
3865 const m_option_type_t m_option_type_removed = {
3866     .name  = "removed",
3867 };
3868 const m_option_type_t m_option_type_subconfig = {
3869     .name = "Subconfig",
3870 };
3871