diff --git a/options/m_config.c b/options/m_config.c index 632f27b64f..271b2bde8b 100644 --- a/options/m_config.c +++ b/options/m_config.c @@ -471,6 +471,80 @@ const char *m_config_get_positional_option(const struct m_config *config, int p) return NULL; } +// return: <0: M_OPT_ error, 0: skip, 1: check, 2: set +static int handle_set_opt_flags(struct m_config *config, + struct m_config_option *co, int flags) +{ + int optflags = co->opt->flags; + bool set = !(flags & M_SETOPT_CHECK_ONLY); + + if ((flags & M_SETOPT_PRE_PARSE_ONLY) && !(optflags & M_OPT_PRE_PARSE)) + return 0; + + if ((flags & M_SETOPT_PRESERVE_CMDLINE) && co->is_set_from_cmdline) + set = false; + + if ((flags & M_SETOPT_NO_FIXED) && (optflags & M_OPT_FIXED)) + return M_OPT_INVALID; + + if ((flags & M_SETOPT_NO_PRE_PARSE) && (optflags & M_OPT_PRE_PARSE)) + return M_OPT_INVALID; + + // Check if this option isn't forbidden in the current mode + if ((flags & M_SETOPT_FROM_CONFIG_FILE) && (optflags & M_OPT_NOCFG)) { + MP_ERR(config, "The %s option can't be used in a config file.\n", + co->name); + return M_OPT_INVALID; + } + if (flags & M_SETOPT_BACKUP) { + if (optflags & M_OPT_GLOBAL) { + MP_ERR(config, "The %s option is global and can't be set per-file.\n", + co->name); + return M_OPT_INVALID; + } + if (set) + ensure_backup(config, co); + } + + return set ? 2 : 1; +} + +static void handle_set_from_cmdline(struct m_config *config, + struct m_config_option *co) +{ + co->is_set_from_cmdline = true; + // Mark aliases too + if (co->data) { + for (int n = 0; n < config->num_opts; n++) { + struct m_config_option *co2 = &config->opts[n]; + if (co2->data == co->data) + co2->is_set_from_cmdline = true; + } + } +} + +// The type data points to is as in: m_config_get_co(config, name)->opt +int m_config_set_option_raw(struct m_config *config, struct m_config_option *co, + void *data, int flags) +{ + if (!co) + return M_OPT_UNKNOWN; + + // This affects some special options like "include", "profile". Maybe these + // should work, or maybe not. For now they would require special code. + if (!co->data) + return M_OPT_UNKNOWN; + + int r = handle_set_opt_flags(config, co, flags); + if (r <= 1) + return r; + + m_option_copy(co->opt, co->data, data); + if (flags & M_SETOPT_FROM_CMDLINE) + handle_set_from_cmdline(config, co); + return 0; +} + static int parse_subopts(struct m_config *config, char *name, char *prefix, struct bstr param, int flags); @@ -479,7 +553,6 @@ static int m_config_parse_option(struct m_config *config, struct bstr name, { assert(config != NULL); assert(name.len != 0); - bool set = !(flags & M_SETOPT_CHECK_ONLY); struct m_config_option *co = m_config_get_co(config, name); if (!co) { @@ -498,33 +571,16 @@ static int m_config_parse_option(struct m_config *config, struct bstr name, // This is the only mandatory function assert(co->opt->type->parse); - if ((flags & M_SETOPT_PRE_PARSE_ONLY) && !(co->opt->flags & M_OPT_PRE_PARSE)) - return 0; - - if ((flags & M_SETOPT_PRESERVE_CMDLINE) && co->is_set_from_cmdline) - set = false; + int r = handle_set_opt_flags(config, co, flags); + if (r <= 0) + return r; + bool set = r == 2; if (set) { MP_VERBOSE(config, "Setting option '%.*s' = '%.*s' (flags = %d)\n", BSTR_P(name), BSTR_P(param), flags); } - // Check if this option isn't forbidden in the current mode - if ((flags & M_SETOPT_FROM_CONFIG_FILE) && (co->opt->flags & M_OPT_NOCFG)) { - MP_ERR(config, "The %.*s option can't be used in a config file.\n", - BSTR_P(name)); - return M_OPT_INVALID; - } - if (flags & M_SETOPT_BACKUP) { - if (co->opt->flags & M_OPT_GLOBAL) { - MP_ERR(config, "The %.*s option is global and can't be set per-file.\n", - BSTR_P(name)); - return M_OPT_INVALID; - } - if (set) - ensure_backup(config, co); - } - if (config->includefunc && bstr_equals0(name, "include")) return parse_include(config, param, set, flags); if (config->use_profiles && bstr_equals0(name, "profile")) @@ -542,20 +598,10 @@ static int m_config_parse_option(struct m_config *config, struct bstr name, return parse_subopts(config, (char *)co->name, prefix, param, flags); } - int r = m_option_parse(config->log, co->opt, name, param, - set ? co->data : NULL); + r = m_option_parse(config->log, co->opt, name, param, set ? co->data : NULL); - if (r >= 0 && set && (flags & M_SETOPT_FROM_CMDLINE)) { - co->is_set_from_cmdline = true; - // Mark aliases too - if (co->data) { - for (int n = 0; n < config->num_opts; n++) { - struct m_config_option *co2 = &config->opts[n]; - if (co2->data == co->data) - co2->is_set_from_cmdline = true; - } - } - } + if (r >= 0 && set && (flags & M_SETOPT_FROM_CMDLINE)) + handle_set_from_cmdline(config, co); return r; } @@ -621,17 +667,12 @@ int m_config_set_option(struct m_config *config, struct bstr name, } int m_config_set_option_node(struct m_config *config, bstr name, - struct mpv_node *data) + struct mpv_node *data, int flags) { struct m_config_option *co = m_config_get_co(config, name); if (!co) return M_OPT_UNKNOWN; - // This affects some special options like "include", "profile". Maybe these - // should work, or maybe not. For now they would require special code. - if (!co->data) - return M_OPT_UNKNOWN; - int r; // Do this on an "empty" type to make setting the option strictly overwrite @@ -646,7 +687,7 @@ int m_config_set_option_node(struct m_config *config, bstr name, } if (r >= 0) - m_option_copy(co->opt, co->data, &val); + r = m_config_set_option_raw(config, co, &val, flags); m_option_free(co->opt, &val); return r; diff --git a/options/m_config.h b/options/m_config.h index 57a271f1c4..5a6274b945 100644 --- a/options/m_config.h +++ b/options/m_config.h @@ -116,8 +116,13 @@ enum { M_SETOPT_FROM_CMDLINE = 8, // Mark as set by command line M_SETOPT_BACKUP = 16, // Call m_config_backup_opt() before M_SETOPT_PRESERVE_CMDLINE = 32, // Don't set if already marked as FROM_CMDLINE + M_SETOPT_NO_FIXED = 64, // Reject M_OPT_FIXED options + M_SETOPT_NO_PRE_PARSE = 128, // Reject M_OPT_PREPARSE options }; +// Flags for safe option setting during runtime. +#define M_SETOPT_RUNTIME (M_SETOPT_NO_FIXED | M_SETOPT_NO_PRE_PARSE) + // Set the named option to the given string. // flags: combination of M_SETOPT_* flags (0 for normal operation) // Returns >= 0 on success, otherwise see OptionParserReturn. @@ -139,10 +144,15 @@ static inline int m_config_set_option0(struct m_config *config, return m_config_set_option(config, bstr0(name), bstr0(param)); } +// Similar to m_config_set_option_ext(), but set as data in its native format. +// The type data points to is as in co->opt +int m_config_set_option_raw(struct m_config *config, struct m_config_option *co, + void *data, int flags); + // Similar to m_config_set_option_ext(), but set as data using mpv_node. struct mpv_node; int m_config_set_option_node(struct m_config *config, bstr name, - struct mpv_node *data); + struct mpv_node *data, int flags); int m_config_parse_suboptions(struct m_config *config, char *name, diff --git a/player/client.c b/player/client.c index 88ebabafaa..580eebaddb 100644 --- a/player/client.c +++ b/player/client.c @@ -237,6 +237,18 @@ void mpv_resume(mpv_handle *ctx) mp_dispatch_resume(ctx->mpctx->dispatch); } +static void lock_core(mpv_handle *ctx) +{ + if (ctx->mpctx->initialized) + mp_dispatch_lock(ctx->mpctx->dispatch); +} + +static void unlock_core(mpv_handle *ctx) +{ + if (ctx->mpctx->initialized) + mp_dispatch_unlock(ctx->mpctx->dispatch); +} + void mpv_destroy(mpv_handle *ctx) { if (!ctx) @@ -631,47 +643,33 @@ void mpv_free_node_contents(mpv_node *node) int mpv_set_option(mpv_handle *ctx, const char *name, mpv_format format, void *data) { - if (ctx->mpctx->initialized) { - char prop[100]; - snprintf(prop, sizeof(prop), "options/%s", name); - int err = mpv_set_property(ctx, prop, format, data); - switch (err) { - case MPV_ERROR_PROPERTY_UNAVAILABLE: - case MPV_ERROR_PROPERTY_ERROR: - return MPV_ERROR_OPTION_ERROR; - case MPV_ERROR_PROPERTY_FORMAT: - return MPV_ERROR_OPTION_FORMAT; - case MPV_ERROR_PROPERTY_NOT_FOUND: - return MPV_ERROR_OPTION_NOT_FOUND; - default: - return err; - } - } else { - const struct m_option *type = get_mp_type(format); - if (!type) - return MPV_ERROR_OPTION_FORMAT; - struct mpv_node tmp; - if (format != MPV_FORMAT_NODE) { - tmp.format = format; - memcpy(&tmp.u, data, type->type->size); - format = MPV_FORMAT_NODE; - data = &tmp; - } - int err = m_config_set_option_node(ctx->mpctx->mconfig, bstr0(name), - data); - switch (err) { - case M_OPT_MISSING_PARAM: - case M_OPT_INVALID: - return MPV_ERROR_OPTION_ERROR; - case M_OPT_OUT_OF_RANGE: - return MPV_ERROR_OPTION_FORMAT; - case M_OPT_UNKNOWN: - return MPV_ERROR_OPTION_NOT_FOUND; - default: - if (err >= 0) - return 0; - return MPV_ERROR_OPTION_ERROR; - } + int flags = ctx->mpctx->initialized ? M_SETOPT_RUNTIME : 0; + const struct m_option *type = get_mp_type(format); + if (!type) + return MPV_ERROR_OPTION_FORMAT; + struct mpv_node tmp; + if (format != MPV_FORMAT_NODE) { + tmp.format = format; + memcpy(&tmp.u, data, type->type->size); + format = MPV_FORMAT_NODE; + data = &tmp; + } + lock_core(ctx); + int err = m_config_set_option_node(ctx->mpctx->mconfig, bstr0(name), + data, flags); + unlock_core(ctx); + switch (err) { + case M_OPT_MISSING_PARAM: + case M_OPT_INVALID: + return MPV_ERROR_OPTION_ERROR; + case M_OPT_OUT_OF_RANGE: + return MPV_ERROR_OPTION_FORMAT; + case M_OPT_UNKNOWN: + return MPV_ERROR_OPTION_NOT_FOUND; + default: + if (err >= 0) + return 0; + return MPV_ERROR_OPTION_ERROR; } } diff --git a/player/command.c b/player/command.c index fd0f589053..5e09079866 100644 --- a/player/command.c +++ b/player/command.c @@ -2273,12 +2273,11 @@ static int access_options(struct m_property_action_arg *ka, MPContext *mpctx) case M_PROPERTY_GET: m_option_copy(opt->opt, ka->arg, opt->data); return M_PROPERTY_OK; - case M_PROPERTY_SET: - if (!(opt->opt->flags & (M_OPT_PRE_PARSE | M_OPT_FIXED))) { - m_option_copy(opt->opt, opt->data, ka->arg); - return M_PROPERTY_OK; - } - return M_PROPERTY_ERROR; + case M_PROPERTY_SET: { + int r = m_config_set_option_raw(mpctx->mconfig, opt, ka->arg, + M_SETOPT_RUNTIME); + return r < 0 ? M_PROPERTY_ERROR : M_PROPERTY_OK; + } case M_PROPERTY_GET_TYPE: *(struct m_option *)ka->arg = *opt->opt; return M_PROPERTY_OK;