1
0
mirror of https://github.com/mpv-player/mpv synced 2025-01-11 09:29:29 +00:00

options: add some way to more or less "unapply" profiles

Make it possible to restore from profiles by backing up the option
values before profile application. This is sort of like unapplying a
profile. Since there might be multiple ways to do this, a profile needs
to explicitly provide the "profile-restore" option, which specifies how
exactly this should be done.

This is a big mess. There is not natural way to do this. Profile
application is "destructive" and simply changes the values of the
options. Maybe one could argue that the option system should have
hierarchical "overlays" of profiles instead, where unset options will
use the value of the lower profiles. Options set interactively by the
user would be the top profile. Default values would be in the lowest
profile. You could unapply a profile by simply removing it from this
overlay stack.

But uh, let's not, so here's something stupid. It reuses some code used
for file local options to reduce code size. At least the overlay idea
would still be possible in theory, and could be added as another
profile-restore mode.

This is used by the following commit.
This commit is contained in:
wm4 2020-08-07 19:39:46 +02:00
parent d5a02dd934
commit 1f132c675a
6 changed files with 216 additions and 65 deletions

View File

@ -1220,13 +1220,21 @@ Input Commands that are Possibly Subject to Change
``af-command <label> <command> <argument>``
Same as ``vf-command``, but for audio filters.
``apply-profile <name>``
``apply-profile <name> [<mode>]``
Apply the contents of a named profile. This is like using ``profile=name``
in a config file, except you can map it to a key binding to change it at
runtime.
There is no such thing as "unapplying" a profile - applying a profile
merely sets all option values listed within the profile.
The mode argument:
``default``
Apply the profile. Default if the argument is omitted.
``restore``
Restore options set by a previous ``apply-profile`` command for this
profile. Only works if the profile has ``profile-restore`` set to a
relevant mode. Prints a warning if nothing could be done. See
`Runtime profiles`_ for details.
``load-script <filename>``
Load a script, similar to the ``--script`` option. Whether this waits for

View File

@ -693,6 +693,72 @@ or at runtime with the ``apply-profile <name>`` command.
# you can also include other profiles
profile=big-cache
Runtime profiles
----------------
Profiles can be set at runtime with ``apply-profile`` command. Since this
operation is "destructive" (every item in a profile is simply set as an
option, overwriting the previous value), you can't just enable and disable
profiles again.
As a partial remedy, there is a way to make profiles save old option values
before overwriting them with the profile values, and then restoring the old
values at a later point using ``apply-profile <profile-name> restore``.
This can be enabled with the ``profile-restore`` option, which takes one of
the following options:
``default``
Does nothing, and nothing can be restored (default).
``copy``
When applying a profile, copy the old values of all profile options to a
backup before setting them from the profile. These options are reset to
their old values using the backup when restoring.
Every profile has its own list of backed up values. If the backup
already exists (e.g. if ``apply-profile name`` was called more than
once in a row), the existing backup is no changed. The restore operation
will remove the backup.
It's important to know that restoring does not "undo" setting an option,
but simply copies the old option value. Consider for example ``vf-add``,
appends an entry to ``vf``. This mechanism will simply copy the entire
``vf`` list, and does _not_ execute the inverse of ``vf-add`` (that
would be ``vf-remove``) on restoring.
Note that if a profile contains recursive profiles (via the ``profile``
option), the options in these recursive profiles are treated as if they
were part of this profile. The referenced profile's backup list is not
used when creating or using the backup. Restoring a profile does not
restore referenced profiles, only the options of referenced profiles (as
if they were part of the main profile).
``copy-equal``
Similar to ``copy``, but restore an option only if it has the same value
as the value effectively set by the profile. This tries to deal with
the situation when the user does not want the option to be reset after
interactively changing it.
.. admonition:: Example
::
[something]
profile-restore=copy-equal
vf-add=rotate=90
Then running these commands will result in behavior as commented:
::
set vf vflip
apply-profile something
vf-add=hflip
apply-profile something
# vf == vflip,rotate=90,hflip,rotate=90
apply-profile something restore
# vf == vflip
Conditional auto profiles
-------------------------

View File

@ -51,17 +51,28 @@ struct m_profile {
char *name;
char *desc;
char *cond;
int restore_mode;
int num_opts;
// Option/value pair array.
// name,value = opts[n*2+0],opts[n*2+1]
char **opts;
// For profile restoring.
struct m_opt_backup *backups;
};
// In the file local case, this contains the old global value.
// It's also used for profile restoring.
struct m_opt_backup {
struct m_opt_backup *next;
struct m_config_option *co;
void *backup;
int flags;
void *backup, *nval;
};
static const struct m_option profile_restore_mode_opt = {
.name = "profile-restore",
.type = &m_option_type_choice,
M_CHOICES({"default", 0}, {"copy", 1}, {"copy-equal", 2}),
};
static void list_profiles(struct m_config *config)
@ -178,44 +189,66 @@ error:
return NULL;
}
static void ensure_backup(struct m_config *config, struct m_config_option *co)
static void backup_dtor(void *p)
{
struct m_opt_backup *bc = p;
m_option_free(bc->co->opt, bc->backup);
if (bc->nval)
m_option_free(bc->co->opt, bc->nval);
}
#define BACKUP_LOCAL 1
#define BACKUP_NVAL 2
static void ensure_backup(struct m_opt_backup **list, int flags,
struct m_config_option *co)
{
if (!co->data)
return;
for (struct m_opt_backup *cur = config->backup_opts; cur; cur = cur->next) {
for (struct m_opt_backup *cur = *list; cur; cur = cur->next) {
if (cur->co->data == co->data) // comparing data ptr catches aliases
return;
}
struct m_opt_backup *bc = talloc_ptrtype(NULL, bc);
talloc_set_destructor(bc, backup_dtor);
*bc = (struct m_opt_backup) {
.co = co,
.backup = talloc_zero_size(bc, co->opt->type->size),
.nval = flags & BACKUP_NVAL
? talloc_zero_size(bc, co->opt->type->size) : NULL,
.flags = flags,
};
m_option_copy(co->opt, bc->backup, co->data);
bc->next = config->backup_opts;
config->backup_opts = bc;
co->is_set_locally = true;
bc->next = *list;
*list = bc;
if (bc->flags & BACKUP_LOCAL)
co->is_set_locally = true;
}
static void restore_backups(struct m_opt_backup **list, struct m_config *config)
{
while (*list) {
struct m_opt_backup *bc = *list;
*list = bc->next;
if (!bc->nval || m_option_equal(bc->co->opt, bc->co->data, bc->nval))
m_config_set_option_raw(config, bc->co, bc->backup, 0);
if (bc->flags & BACKUP_LOCAL)
bc->co->is_set_locally = false;
talloc_free(bc);
}
}
void m_config_restore_backups(struct m_config *config)
{
while (config->backup_opts) {
struct m_opt_backup *bc = config->backup_opts;
config->backup_opts = bc->next;
m_config_set_option_raw(config, bc->co, bc->backup, 0);
m_option_free(bc->co->opt, bc->backup);
bc->co->is_set_locally = false;
talloc_free(bc);
}
restore_backups(&config->backup_opts, config);
}
void m_config_backup_opt(struct m_config *config, const char *opt)
{
struct m_config_option *co = m_config_get_co(config, bstr0(opt));
if (co) {
ensure_backup(config, co);
ensure_backup(&config->backup_opts, BACKUP_LOCAL, co);
} else {
MP_ERR(config, "Option %s not found.\n", opt);
}
@ -224,7 +257,7 @@ void m_config_backup_opt(struct m_config *config, const char *opt)
void m_config_backup_all_opts(struct m_config *config)
{
for (int n = 0; n < config->num_opts; n++)
ensure_backup(config, &config->opts[n]);
ensure_backup(&config->backup_opts, BACKUP_LOCAL, &config->opts[n]);
}
@ -360,7 +393,7 @@ static int handle_set_opt_flags(struct m_config *config,
return M_OPT_INVALID;
}
if ((flags & M_SETOPT_BACKUP) && set)
ensure_backup(config, co);
ensure_backup(&config->backup_opts, BACKUP_LOCAL, co);
return set ? 2 : 1;
}
@ -577,6 +610,9 @@ int m_config_set_option_raw(struct m_config *config,
if (!co->data)
return flags & M_SETOPT_FROM_CMDLINE ? 0 : M_OPT_UNKNOWN;
if (config->profile_backup_tmp)
ensure_backup(config->profile_backup_tmp, config->profile_backup_flags, co);
m_config_mark_co_flags(co, flags);
m_option_copy(co->opt, co->data, data);
@ -884,23 +920,26 @@ struct m_profile *m_config_add_profile(struct m_config *config, char *name)
return p;
}
void m_profile_set_desc(struct m_profile *p, bstr desc)
{
talloc_free(p->desc);
p->desc = bstrto0(p, desc);
}
void m_profile_set_cond(struct m_profile *p, bstr cond)
{
TA_FREEP(&p->cond);
cond = bstr_strip(cond);
if (cond.len)
p->cond = bstrto0(p, cond);
}
int m_config_set_profile_option(struct m_config *config, struct m_profile *p,
bstr name, bstr val)
{
if (bstr_equals0(name, "profile-desc")) {
talloc_free(p->desc);
p->desc = bstrto0(p, val);
return 0;
}
if (bstr_equals0(name, "profile-cond")) {
TA_FREEP(&p->cond);
val = bstr_strip(val);
if (val.len)
p->cond = bstrto0(p, val);
return 0;
}
if (bstr_equals0(name, profile_restore_mode_opt.name)) {
return m_option_parse(config->log, &profile_restore_mode_opt, name, val,
&p->restore_mode);
}
int i = m_config_set_option_cli(config, name, val,
M_SETOPT_CHECK_ONLY |
M_SETOPT_FROM_CONFIG_FILE);
@ -914,19 +953,32 @@ int m_config_set_profile_option(struct m_config *config, struct m_profile *p,
return 1;
}
int m_config_set_profile(struct m_config *config, char *name, int flags)
static struct m_profile *find_check_profile(struct m_config *config, char *name)
{
struct m_profile *p = m_config_get_profile0(config, name);
if (!p) {
MP_WARN(config, "Unknown profile '%s'.\n", name);
return M_OPT_INVALID;
return NULL;
}
MP_VERBOSE(config, "Applying profile '%s'...\n", name);
if (config->profile_depth > MAX_PROFILE_DEPTH) {
MP_WARN(config, "WARNING: Profile inclusion too deep.\n");
return M_OPT_INVALID;
return NULL;
}
return p;
}
int m_config_set_profile(struct m_config *config, char *name, int flags)
{
MP_VERBOSE(config, "Applying profile '%s'...\n", name);
struct m_profile *p = find_check_profile(config, name);
if (!p)
return M_OPT_INVALID;
if (!config->profile_backup_tmp && p->restore_mode) {
config->profile_backup_tmp = &p->backups;
config->profile_backup_flags = p->restore_mode == 2 ? BACKUP_NVAL : 0;
}
config->profile_depth++;
for (int i = 0; i < p->num_opts; i++) {
m_config_set_option_cli(config,
@ -936,6 +988,31 @@ int m_config_set_profile(struct m_config *config, char *name, int flags)
}
config->profile_depth--;
if (config->profile_backup_tmp == &p->backups) {
config->profile_backup_tmp = NULL;
for (struct m_opt_backup *bc = p->backups; bc; bc = bc->next) {
if (bc && bc->nval)
m_option_copy(bc->co->opt, bc->nval, bc->co->data);
talloc_steal(p, bc);
}
}
return 0;
}
int m_config_restore_profile(struct m_config *config, char *name)
{
MP_VERBOSE(config, "Restoring from profile '%s'...\n", name);
struct m_profile *p = find_check_profile(config, name);
if (!p)
return M_OPT_INVALID;
if (!p->backups)
MP_WARN(config, "Profile contains no restore data.\n");
restore_backups(&p->backups, config);
return 0;
}
@ -960,6 +1037,12 @@ struct mpv_node m_config_get_profiles(struct m_config *config)
node_map_add_string(entry, "profile-desc", profile->desc);
if (profile->cond)
node_map_add_string(entry, "profile-cond", profile->cond);
if (profile->restore_mode) {
char *s =
m_option_print(&profile_restore_mode_opt, &profile->restore_mode);
node_map_add_string(entry, profile_restore_mode_opt.name, s);
talloc_free(s);
}
struct mpv_node *opts =
node_map_add(entry, "options", MPV_FORMAT_NODE_ARRAY);

View File

@ -70,6 +70,9 @@ typedef struct m_config {
struct m_profile *profiles;
// Depth when recursively including profiles.
int profile_depth;
// Temporary during profile application.
struct m_opt_backup **profile_backup_tmp;
int profile_backup_flags;
struct m_opt_backup *backup_opts;
@ -224,17 +227,6 @@ void m_config_finish_default_profile(struct m_config *config, int flags);
*/
struct m_profile *m_config_add_profile(struct m_config *config, char *name);
/* Set the description of a profile.
* Used by the config file parser when defining a profile.
*
* \param p The profile object.
* \param arg The profile's name.
*/
void m_profile_set_desc(struct m_profile *p, bstr desc);
// Set auto profile condition of a profile.
void m_profile_set_cond(struct m_profile *p, bstr cond);
/* Add an option to a profile.
* Used by the config file parser when defining a profile.
*
@ -256,6 +248,9 @@ int m_config_set_profile_option(struct m_config *config, struct m_profile *p,
*/
int m_config_set_profile(struct m_config *config, char *name, int flags);
// Attempt to "unset" a profile if possible.
int m_config_restore_profile(struct m_config *config, char *name);
struct mpv_node m_config_get_profiles(struct m_config *config);
// Run async option updates here. This will call option_change_callback() on it.

View File

@ -127,16 +127,7 @@ int m_config_parse(m_config_t *config, const char *location, bstr data,
goto error;
}
int res;
if (bstr_equals0(option, "profile-desc")) {
m_profile_set_desc(profile, value);
res = 0;
} else if (bstr_equals0(option, "profile-cond")) {
m_profile_set_cond(profile, value);
res = 0;
} else {
res = m_config_set_profile_option(config, profile, option, value);
}
int res = m_config_set_profile_option(config, profile, option, value);
if (res < 0) {
MP_ERR(config, "%s setting option %.*s='%.*s' failed.\n",
loc, BSTR_P(option), BSTR_P(value));

View File

@ -5647,8 +5647,12 @@ static void cmd_apply_profile(void *p)
struct MPContext *mpctx = cmd->mpctx;
char *profile = cmd->args[0].v.s;
if (m_config_set_profile(mpctx->mconfig, profile, 0) < 0)
cmd->success = false;
int mode = cmd->args[1].v.i;
if (mode == 0) {
cmd->success = m_config_set_profile(mpctx->mconfig, profile, 0) >= 0;
} else {
cmd->success = m_config_restore_profile(mpctx->mconfig, profile) >= 0;
}
}
static void cmd_load_script(void *p)
@ -6138,7 +6142,11 @@ const struct mp_cmd_def mp_cmds[] = {
{ "keyup", cmd_key, { {"name", OPT_STRING(v.s), .flags = MP_CMD_OPT_ARG} },
.priv = &(const int){MP_KEY_STATE_UP}},
{ "apply-profile", cmd_apply_profile, {{"name", OPT_STRING(v.s)}} },
{ "apply-profile", cmd_apply_profile, {
{"name", OPT_STRING(v.s)},
{"mode", OPT_CHOICE(v.i, {"apply", 0}, {"restore", 1}),
.flags = MP_CMD_OPT_ARG}, }
},
{ "load-script", cmd_load_script, {{"filename", OPT_STRING(v.s)}} },