From fb9bbf2a0d7f8dba0d0674595cc2dc0dab68ad94 Mon Sep 17 00:00:00 2001 From: wm4 Date: Tue, 1 May 2018 02:38:59 +0200 Subject: command: split big command handler switch into separate functions This gets rid of run_command() and its big switch statement, which was an idiotically big function of almost 1000 lines. The switch is replaced with a callback per command, and each command is now implemented in its own function. Command IDs are not needed anymore, so the mp_command_type enum disappears. There should be no functional changes, but since this refactors 64 commands, regressions are possible. The handler() parameter is void*, because in theory the input code is supposed to be independent of the player core code. For example, you should be able to reuse the command parser code for some other part of mpv. In practice, the variable containing command list is defined in the player core anyway, so you could say this doesn't work. But I'm still trying to hold onto this idea, so I went with void*. --- input/cmd_list.h | 92 +-- input/cmd_parse.c | 3 - player/command.c | 1770 ++++++++++++++++++++++++++++++----------------------- player/command.h | 19 + 4 files changed, 1018 insertions(+), 866 deletions(-) diff --git a/input/cmd_list.h b/input/cmd_list.h index c6d7c66100..5f44602907 100644 --- a/input/cmd_list.h +++ b/input/cmd_list.h @@ -25,10 +25,13 @@ #define MP_CMD_OPT_ARG 0x1000 +struct mp_cmd_ctx; + struct mp_cmd_def { - int id; // one of MP_CMD_... const char *name; // user-visible name (as used in input.conf) + void (*handler)(void *ctx); const struct m_option args[MP_CMD_DEF_MAX_ARGS]; + const void *priv; // for free use by handler() bool allow_auto_repeat; // react to repeated key events bool on_updown; // always emit it on both up and down key events bool vararg; // last argument can be given 0 to multiple times @@ -40,93 +43,6 @@ struct mp_cmd_def { extern const struct mp_cmd_def mp_cmds[]; -// All command IDs -enum mp_command_type { - MP_CMD_IGNORE, - MP_CMD_SEEK, - MP_CMD_REVERT_SEEK, - MP_CMD_QUIT, - MP_CMD_QUIT_WATCH_LATER, - MP_CMD_PLAYLIST_NEXT, - MP_CMD_PLAYLIST_PREV, - MP_CMD_SCREENSHOT, - MP_CMD_SCREENSHOT_TO_FILE, - MP_CMD_SCREENSHOT_RAW, - MP_CMD_LOADFILE, - MP_CMD_LOADLIST, - MP_CMD_PLAYLIST_CLEAR, - MP_CMD_PLAYLIST_REMOVE, - MP_CMD_PLAYLIST_MOVE, - MP_CMD_PLAYLIST_SHUFFLE, - MP_CMD_SUB_STEP, - MP_CMD_SUB_SEEK, - MP_CMD_TV_LAST_CHANNEL, - MP_CMD_FRAME_STEP, - MP_CMD_FRAME_BACK_STEP, - MP_CMD_RUN, - MP_CMD_SUB_ADD, - MP_CMD_SUB_REMOVE, - MP_CMD_SUB_RELOAD, - MP_CMD_SET, - MP_CMD_CHANGE_LIST, - MP_CMD_PRINT_TEXT, - MP_CMD_SHOW_TEXT, - MP_CMD_EXPAND_TEXT, - MP_CMD_SHOW_PROGRESS, - MP_CMD_ADD, - MP_CMD_CYCLE, - MP_CMD_MULTIPLY, - MP_CMD_CYCLE_VALUES, - MP_CMD_STOP, - MP_CMD_AUDIO_ADD, - MP_CMD_AUDIO_REMOVE, - MP_CMD_AUDIO_RELOAD, - - MP_CMD_ENABLE_INPUT_SECTION, - MP_CMD_DISABLE_INPUT_SECTION, - MP_CMD_DEFINE_INPUT_SECTION, - - MP_CMD_AB_LOOP, - - MP_CMD_DROP_BUFFERS, - - MP_CMD_MOUSE, - MP_CMD_KEYPRESS, - MP_CMD_KEYDOWN, - MP_CMD_KEYUP, - - /// Audio Filter commands - MP_CMD_AF, - MP_CMD_AF_COMMAND, - MP_CMD_AO_RELOAD, - - /// Video filter commands - MP_CMD_VF, - MP_CMD_VF_COMMAND, - - /// Internal for Lua scripts - MP_CMD_SCRIPT_BINDING, - MP_CMD_SCRIPT_MESSAGE, - MP_CMD_SCRIPT_MESSAGE_TO, - - MP_CMD_OVERLAY_ADD, - MP_CMD_OVERLAY_REMOVE, - - MP_CMD_WRITE_WATCH_LATER_CONFIG, - - MP_CMD_HOOK_ADD, - MP_CMD_HOOK_ACK, - - MP_CMD_RESCAN_EXTERNAL_FILES, - - MP_CMD_APPLY_PROFILE, - - MP_CMD_LOAD_SCRIPT, - - // Internal - MP_CMD_COMMAND_LIST, // list of sub-commands in args[0].v.p -}; - // Executing this command will maybe abort playback (play something else, or quit). struct mp_cmd; bool mp_input_is_maybe_abort_cmd(struct mp_cmd *cmd); diff --git a/input/cmd_parse.c b/input/cmd_parse.c index ae518fdf98..f401e5b9c7 100644 --- a/input/cmd_parse.c +++ b/input/cmd_parse.c @@ -29,7 +29,6 @@ #include "libmpv/client.h" const struct mp_cmd_def mp_cmd_list = { - .id = MP_CMD_COMMAND_LIST, .name = "list", }; @@ -87,7 +86,6 @@ static bool find_cmd(struct mp_log *log, struct mp_cmd *cmd, bstr name) if (strcmp(nname, mp_cmds[n].name) == 0) { cmd->def = &mp_cmds[n]; cmd->name = (char *)cmd->def->name; - cmd->id = cmd->def->id; return true; } } @@ -341,7 +339,6 @@ mp_cmd_t *mp_input_parse_cmd_(struct mp_log *log, bstr str, const char *loc) struct mp_cmd *list = talloc_ptrtype(NULL, list); talloc_set_destructor(list, destroy_cmd); *list = (struct mp_cmd) { - .id = mp_cmd_list.id, .name = (char *)mp_cmd_list.name, .def = &mp_cmd_list, .original = bstrdup(list, original), diff --git a/player/command.c b/player/command.c index c820a1ceb5..d5cedb235a 100644 --- a/player/command.c +++ b/player/command.c @@ -4582,11 +4582,16 @@ static void replace_overlay(struct MPContext *mpctx, int id, struct overlay *new recreate_overlays(mpctx); } -static int overlay_add(struct MPContext *mpctx, int id, int x, int y, - char *file, int offset, char *fmt, int w, int h, - int stride) +static void cmd_overlay_add(void *pcmd) { - int r = -1; + struct mp_cmd_ctx *cmd = pcmd; + struct MPContext *mpctx = cmd->mpctx; + int id = cmd->args[0].v.i, x = cmd->args[1].v.i, y = cmd->args[2].v.i; + char *file = cmd->args[3].v.s; + int offset = cmd->args[4].v.i; + char *fmt = cmd->args[5].v.s; + int w = cmd->args[6].v.i, h = cmd->args[7].v.i, stride = cmd->args[8].v.i; + if (strcmp(fmt, "bgra") != 0) { MP_ERR(mpctx, "overlay-add: unsupported OSD format '%s'\n", fmt); goto error; @@ -4644,15 +4649,18 @@ static int overlay_add(struct MPContext *mpctx, int id, int x, int y, munmap(p, map_size); replace_overlay(mpctx, id, &overlay); - r = 0; + return; error: - return r; + cmd->success = false; } -static void overlay_remove(struct MPContext *mpctx, int id) +static void cmd_overlay_remove(void *p) { - struct command_ctx *cmd = mpctx->command_ctx; - if (id >= 0 && id < cmd->num_overlays) + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + struct command_ctx *cmdctx = mpctx->command_ctx; + int id = cmd->args[0].v.i; + if (id >= 0 && id < cmdctx->num_overlays) replace_overlay(mpctx, id, &(struct overlay){0}); } @@ -4662,7 +4670,7 @@ static void overlay_uninit(struct MPContext *mpctx) if (!mpctx->osd) return; for (int id = 0; id < cmd->num_overlays; id++) - overlay_remove(mpctx, id); + replace_overlay(mpctx, id, &(struct overlay){0}); osd_set_external2(mpctx->osd, NULL); for (int n = 0; n < 2; n++) mp_image_unrefp(&cmd->overlay_osd[n].packed); @@ -4708,36 +4716,32 @@ static bool check_property_scalable(char *property, struct MPContext *mpctx) prop.type == &m_option_type_aspect; } -static int show_property_status(struct MPContext *mpctx, struct mp_cmd *cmd, - const char *name, int r) +static void show_property_status(struct mp_cmd_ctx *cmd, const char *name, int r) { + struct MPContext *mpctx = cmd->mpctx; struct MPOpts *opts = mpctx->opts; int osd_duration = opts->osd_duration; - int on_osd = cmd->flags & MP_ON_OSD_FLAGS; - bool auto_osd = on_osd == MP_ON_OSD_AUTO; - bool msg_osd = auto_osd || (on_osd & MP_ON_OSD_MSG); - int osdl = msg_osd ? 1 : OSD_LEVEL_INVISIBLE; + int osdl = cmd->msg_osd ? 1 : OSD_LEVEL_INVISIBLE; if (r == M_PROPERTY_OK || r == M_PROPERTY_UNAVAILABLE) { - show_property_osd(mpctx, name, on_osd); + show_property_osd(mpctx, name, cmd->on_osd); if (r == M_PROPERTY_UNAVAILABLE) - return -1; + cmd->success = false; } else if (r == M_PROPERTY_UNKNOWN) { set_osd_msg(mpctx, osdl, osd_duration, "Unknown property: '%s'", name); - return -1; + cmd->success = false; } else if (r <= 0) { set_osd_msg(mpctx, osdl, osd_duration, "Failed to set property '%s'", name); - return -1; + cmd->success = false; } - return 0; } -static int change_property_cmd(struct MPContext *mpctx, struct mp_cmd *cmd, - const char *name, int action, void *arg) +static void change_property_cmd(struct mp_cmd_ctx *cmd, + const char *name, int action, void *arg) { - int r = mp_property_do(name, action, arg, mpctx); - return show_property_status(mpctx, cmd, name, r); + int r = mp_property_do(name, action, arg, cmd->mpctx); + show_property_status(cmd, name, r); } static bool compare_values(struct m_option *type, void *a, void *b) @@ -4752,8 +4756,10 @@ static bool compare_values(struct m_option *type, void *a, void *b) return res; } -static int cycle_values_cmd(struct MPContext *mpctx, struct mp_cmd *cmd) +static void cmd_cycle_values(void *p) { + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; int first = 0, dir = 1; if (strcmp(cmd->args[first].v.s, "!reverse") == 0) { @@ -4764,26 +4770,31 @@ static int cycle_values_cmd(struct MPContext *mpctx, struct mp_cmd *cmd) const char *name = cmd->args[first].v.s; first += 1; - if (first >= cmd->nargs) { + if (first >= cmd->num_args) { MP_ERR(mpctx, "cycle-values command does not have any value arguments.\n"); - return -1; + cmd->success = false; + return; } struct m_option prop = {0}; int r = mp_property_do(name, M_PROPERTY_GET_TYPE, &prop, mpctx); - if (r <= 0) - return show_property_status(mpctx, cmd, name, r); + if (r <= 0) { + show_property_status(cmd, name, r); + return; + } union m_option_value curval = {0}; r = mp_property_do(name, M_PROPERTY_GET, &curval, mpctx); - if (r <= 0) - return show_property_status(mpctx, cmd, name, r); + if (r <= 0) { + show_property_status(cmd, name, r); + return; + } // Find the current value. Note that we even though compare_values() uses // strings internally, we need to convert the cycle-values arguments to // native anyway to "normalize" the value for comparison. int current = -1; - for (int n = first; n < cmd->nargs; n++) { + for (int n = first; n < cmd->num_args; n++) { union m_option_value val = {0}; if (m_option_parse(mpctx->log, &prop, bstr0(name), bstr0(cmd->args[n].v.s), &val) <= 0) @@ -4803,32 +4814,37 @@ static int cycle_values_cmd(struct MPContext *mpctx, struct mp_cmd *cmd) if (current >= 0) { current += dir; if (current < first) - current = cmd->nargs - 1; - if (current >= cmd->nargs) + current = cmd->num_args - 1; + if (current >= cmd->num_args) current = first; } else { MP_VERBOSE(mpctx, "Current value not found. Picking default.\n"); - current = dir > 0 ? first : cmd->nargs - 1; + current = dir > 0 ? first : cmd->num_args - 1; } - return change_property_cmd(mpctx, cmd, name, M_PROPERTY_SET_STRING, - cmd->args[current].v.s); + change_property_cmd(cmd, name, M_PROPERTY_SET_STRING, cmd->args[current].v.s); } int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *res) { - struct command_ctx *cmdctx = mpctx->command_ctx; - struct MPOpts *opts = mpctx->opts; - int osd_duration = opts->osd_duration; - int on_osd = cmd->flags & MP_ON_OSD_FLAGS; - bool auto_osd = on_osd == MP_ON_OSD_AUTO; - bool msg_osd = auto_osd || (on_osd & MP_ON_OSD_MSG); - bool bar_osd = auto_osd || (on_osd & MP_ON_OSD_BAR); - bool seek_msg_osd = auto_osd ? opts->osd_on_seek & 2 : msg_osd; - bool seek_bar_osd = auto_osd ? opts->osd_on_seek & 1 : bar_osd; + struct mpv_node dummy_node = {0}; + struct mp_cmd_ctx *ctx = &(struct mp_cmd_ctx){ + .mpctx = mpctx, + .cmd = cmd, + .args = cmd->args, + .num_args = cmd->nargs, + .priv = cmd->def->priv, + .success = true, + .result = res ? res : &dummy_node, + }; - int osdl = msg_osd ? 1 : OSD_LEVEL_INVISIBLE; - bool async = cmd->flags & MP_ASYNC_CMD; + struct MPOpts *opts = mpctx->opts; + ctx->on_osd = cmd->flags & MP_ON_OSD_FLAGS; + bool auto_osd = ctx->on_osd == MP_ON_OSD_AUTO; + ctx->msg_osd = auto_osd || (ctx->on_osd & MP_ON_OSD_MSG); + ctx->bar_osd = auto_osd || (ctx->on_osd & MP_ON_OSD_BAR); + ctx->seek_msg_osd = auto_osd ? opts->osd_on_seek & 2 : ctx->msg_osd; + ctx->seek_bar_osd = auto_osd ? opts->osd_on_seek & 1 : ctx->bar_osd; mp_cmd_dump(mpctx->log, cmd->def->is_ignore ? MSGL_TRACE : MSGL_DEBUG, "Run command:", cmd); @@ -4845,774 +4861,959 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re } } - switch (cmd->id) { - case MP_CMD_SEEK: { - double v = cmd->args[0].v.d * cmd->scale; - int abs = cmd->args[1].v.i & 3; - enum seek_precision precision = MPSEEK_DEFAULT; - switch (((cmd->args[2].v.i | cmd->args[1].v.i) >> 3) & 3) { - case 1: precision = MPSEEK_KEYFRAME; break; - case 2: precision = MPSEEK_EXACT; break; - } - if (!mpctx->playback_initialized) - return -1; - mark_seek(mpctx); - switch (abs) { - case 0: { // Relative seek - queue_seek(mpctx, MPSEEK_RELATIVE, v, precision, MPSEEK_FLAG_DELAY); - set_osd_function(mpctx, (v > 0) ? OSD_FFW : OSD_REW); - break; - } - case 1: { // Absolute seek by percentage - double ratio = v / 100.0; - double cur_pos = get_current_pos_ratio(mpctx, false); - queue_seek(mpctx, MPSEEK_FACTOR, ratio, precision, MPSEEK_FLAG_DELAY); - set_osd_function(mpctx, cur_pos < ratio ? OSD_FFW : OSD_REW); - break; - } - case 2: { // Absolute seek to a timestamp in seconds - if (v < 0) { - // Seek from end - double len = get_time_length(mpctx); - if (len < 0) - return -1; - v = MPMAX(0, len + v); + if (cmd->def == &mp_cmd_list) { + for (struct mp_cmd *sub = cmd->args[0].v.p; sub; sub = sub->queue_next) + run_command(mpctx, sub, NULL); + } else { + assert(cmd->def->handler); + cmd->def->handler(ctx); + } + + if (!ctx->success) + mpv_free_node_contents(ctx->result); + + mpv_free_node_contents(&dummy_node); + + return ctx->success ? 0 : -1; +} + +static void cmd_seek(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + + double v = cmd->args[0].v.d * cmd->cmd->scale; + int abs = cmd->args[1].v.i & 3; + enum seek_precision precision = MPSEEK_DEFAULT; + switch (((cmd->args[2].v.i | cmd->args[1].v.i) >> 3) & 3) { + case 1: precision = MPSEEK_KEYFRAME; break; + case 2: precision = MPSEEK_EXACT; break; + } + if (!mpctx->playback_initialized) { + cmd->success = false; + return; + } + + mark_seek(mpctx); + switch (abs) { + case 0: { // Relative seek + queue_seek(mpctx, MPSEEK_RELATIVE, v, precision, MPSEEK_FLAG_DELAY); + set_osd_function(mpctx, (v > 0) ? OSD_FFW : OSD_REW); + break; + } + case 1: { // Absolute seek by percentage + double ratio = v / 100.0; + double cur_pos = get_current_pos_ratio(mpctx, false); + queue_seek(mpctx, MPSEEK_FACTOR, ratio, precision, MPSEEK_FLAG_DELAY); + set_osd_function(mpctx, cur_pos < ratio ? OSD_FFW : OSD_REW); + break; + } + case 2: { // Absolute seek to a timestamp in seconds + if (v < 0) { + // Seek from end + double len = get_time_length(mpctx); + if (len < 0) { + cmd->success = false; + return; } - queue_seek(mpctx, MPSEEK_ABSOLUTE, v, precision, MPSEEK_FLAG_DELAY); - set_osd_function(mpctx, - v > get_current_time(mpctx) ? OSD_FFW : OSD_REW); - break; + v = MPMAX(0, len + v); } - case 3: { // Relative seek by percentage - queue_seek(mpctx, MPSEEK_FACTOR, - get_current_pos_ratio(mpctx, false) + v / 100.0, - precision, MPSEEK_FLAG_DELAY); - set_osd_function(mpctx, v > 0 ? OSD_FFW : OSD_REW); - break; - }} - if (seek_bar_osd) + queue_seek(mpctx, MPSEEK_ABSOLUTE, v, precision, MPSEEK_FLAG_DELAY); + set_osd_function(mpctx, + v > get_current_time(mpctx) ? OSD_FFW : OSD_REW); + break; + } + case 3: { // Relative seek by percentage + queue_seek(mpctx, MPSEEK_FACTOR, + get_current_pos_ratio(mpctx, false) + v / 100.0, + precision, MPSEEK_FLAG_DELAY); + set_osd_function(mpctx, v > 0 ? OSD_FFW : OSD_REW); + break; + }} + if (cmd->seek_bar_osd) + mpctx->add_osd_seek_info |= OSD_SEEK_INFO_BAR; + if (cmd->seek_msg_osd) + mpctx->add_osd_seek_info |= OSD_SEEK_INFO_TEXT; +} + +static void cmd_revert_seek(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + struct command_ctx *cmdctx = mpctx->command_ctx; + + if (!mpctx->playback_initialized) { + cmd->success = false; + return; + } + + double oldpts = cmdctx->last_seek_pts; + if (cmdctx->marked_pts != MP_NOPTS_VALUE) + oldpts = cmdctx->marked_pts; + if (cmd->args[0].v.i == 1) { + cmdctx->marked_pts = get_current_time(mpctx); + } else if (oldpts != MP_NOPTS_VALUE) { + cmdctx->last_seek_pts = get_current_time(mpctx); + cmdctx->marked_pts = MP_NOPTS_VALUE; + queue_seek(mpctx, MPSEEK_ABSOLUTE, oldpts, MPSEEK_EXACT, + MPSEEK_FLAG_DELAY); + set_osd_function(mpctx, OSD_REW); + if (cmd->seek_bar_osd) mpctx->add_osd_seek_info |= OSD_SEEK_INFO_BAR; - if (seek_msg_osd) + if (cmd->seek_msg_osd) mpctx->add_osd_seek_info |= OSD_SEEK_INFO_TEXT; - break; + } else { + cmd->success = false; } +} - case MP_CMD_REVERT_SEEK: { - if (!mpctx->playback_initialized) - return -1; - double oldpts = cmdctx->last_seek_pts; - if (cmdctx->marked_pts != MP_NOPTS_VALUE) - oldpts = cmdctx->marked_pts; - if (cmd->args[0].v.i == 1) { - cmdctx->marked_pts = get_current_time(mpctx); - } else if (oldpts != MP_NOPTS_VALUE) { - cmdctx->last_seek_pts = get_current_time(mpctx); - cmdctx->marked_pts = MP_NOPTS_VALUE; - queue_seek(mpctx, MPSEEK_ABSOLUTE, oldpts, MPSEEK_EXACT, - MPSEEK_FLAG_DELAY); - set_osd_function(mpctx, OSD_REW); - if (seek_bar_osd) - mpctx->add_osd_seek_info |= OSD_SEEK_INFO_BAR; - if (seek_msg_osd) - mpctx->add_osd_seek_info |= OSD_SEEK_INFO_TEXT; - } else { - return -1; - } - break; +static void cmd_set(void *p) +{ + struct mp_cmd_ctx *cmd = p; + + change_property_cmd(cmd, cmd->args[0].v.s, + M_PROPERTY_SET_STRING, cmd->args[1].v.s); +} + +static void cmd_change_list(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + char *name = cmd->args[0].v.s; + char *op = cmd->args[1].v.s; + char *value = cmd->args[2].v.s; + int osd_duration = mpctx->opts->osd_duration; + int osdl = cmd->msg_osd ? 1 : OSD_LEVEL_INVISIBLE; + + struct m_config_option *co = m_config_get_co(mpctx->mconfig, bstr0(name)); + if (!co) { + set_osd_msg(mpctx, osdl, osd_duration, "Unknown option: '%s'", name); + cmd->success = false; + return; } - case MP_CMD_SET: { - return change_property_cmd(mpctx, cmd, cmd->args[0].v.s, - M_PROPERTY_SET_STRING, cmd->args[1].v.s); + const struct m_option_type *type = co->opt->type; + bool found = false; + for (int i = 0; type->actions && type->actions[i].name; i++) { + const struct m_option_action *action = &type->actions[i]; + if (strcmp(action->name, op) == 0) + found = true; + } + if (!found) { + set_osd_msg(mpctx, osdl, osd_duration, "Unknown action: '%s'", op); + cmd->success = false; + return; } - case MP_CMD_CHANGE_LIST: { - char *name = cmd->args[0].v.s; - char *op = cmd->args[1].v.s; - char *value = cmd->args[2].v.s; - struct m_config_option *co = m_config_get_co(mpctx->mconfig, bstr0(name)); - if (!co) { - set_osd_msg(mpctx, osdl, osd_duration, "Unknown option: '%s'", name); - return -1; - } - const struct m_option_type *type = co->opt->type; - bool found = false; - for (int i = 0; type->actions && type->actions[i].name; i++) { - const struct m_option_action *action = &type->actions[i]; - if (strcmp(action->name, op) == 0) - found = true; - } - if (!found) { - set_osd_msg(mpctx, osdl, osd_duration, "Unknown action: '%s'", op); - return -1; - } - char *optname = mp_tprintf(80, "%s-%s", name, op); // the dirty truth - int r = m_config_set_option_cli(mpctx->mconfig, bstr0(optname), - bstr0(value), M_SETOPT_RUNTIME); - if (r < 0) { - set_osd_msg(mpctx, osdl, osd_duration, - "Failed setting option: '%s'", name); - return -1; - } - show_property_osd(mpctx, name, on_osd); - break; + char *optname = mp_tprintf(80, "%s-%s", name, op); // the dirty truth + int r = m_config_set_option_cli(mpctx->mconfig, bstr0(optname), + bstr0(value), M_SETOPT_RUNTIME); + if (r < 0) { + set_osd_msg(mpctx, osdl, osd_duration, + "Failed setting option: '%s'", name); + cmd->success = false; + return; } - case MP_CMD_ADD: - case MP_CMD_CYCLE: - { - char *property = cmd->args[0].v.s; - if (cmd->repeated && !check_property_autorepeat(property, mpctx)) { - MP_VERBOSE(mpctx, "Dropping command '%.*s' from auto-repeated key.\n", - BSTR_P(cmd->original)); - break; - } - double scale = 1; - int scale_units = cmd->scale_units; - if (check_property_scalable(property, mpctx)) { - scale = cmd->scale; - scale_units = 1; - } - for (int i = 0; i < scale_units; i++) { - struct m_property_switch_arg s = { - .inc = cmd->args[1].v.d * scale, - .wrap = cmd->id == MP_CMD_CYCLE, - }; - int r = - change_property_cmd(mpctx, cmd, property, M_PROPERTY_SWITCH, &s); - if (r < 0) - return r; - } - break; + show_property_osd(mpctx, name, cmd->on_osd); +} + +static void cmd_add_cycle(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + bool is_cycle = !!cmd->priv; + + char *property = cmd->args[0].v.s; + if (cmd->cmd->repeated && !check_property_autorepeat(property, mpctx)) { + MP_VERBOSE(mpctx, "Dropping command '%.*s' from auto-repeated key.\n", + BSTR_P(cmd->cmd->original)); + return; } - case MP_CMD_MULTIPLY: { - return change_property_cmd(mpctx, cmd, cmd->args[0].v.s, - M_PROPERTY_MULTIPLY, &cmd->args[1].v.d); + double scale = 1; + int scale_units = cmd->cmd->scale_units; + if (check_property_scalable(property, mpctx)) { + scale = cmd->cmd->scale; + scale_units = 1; } - case MP_CMD_CYCLE_VALUES: - return cycle_values_cmd(mpctx, cmd); + for (int i = 0; i < scale_units; i++) { + struct m_property_switch_arg s = { + .inc = cmd->args[1].v.d * scale, + .wrap = is_cycle, + }; + change_property_cmd(cmd, property, M_PROPERTY_SWITCH, &s); + if (!cmd->success) + return; + } +} + +static void cmd_multiply(void *p) +{ + struct mp_cmd_ctx *cmd = p; + + change_property_cmd(cmd, cmd->args[0].v.s, + M_PROPERTY_MULTIPLY, &cmd->args[1].v.d); +} + +static void cmd_frame_step(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; - case MP_CMD_FRAME_STEP: - if (!mpctx->playback_initialized) - return -1; - if (cmd->is_up_down) { - if (cmd->is_up) { - if (mpctx->step_frames < 1) - set_pause_state(mpctx, true); + if (!mpctx->playback_initialized) { + cmd->success = false; + return; + } + + if (cmd->cmd->is_up_down) { + if (cmd->cmd->is_up) { + if (mpctx->step_frames < 1) + set_pause_state(mpctx, true); + } else { + if (cmd->cmd->repeated) { + set_pause_state(mpctx, false); } else { - if (cmd->repeated) { - set_pause_state(mpctx, false); - } else { - add_step_frame(mpctx, 1); - } + add_step_frame(mpctx, 1); } - } else { - add_step_frame(mpctx, 1); } - break; + } else { + add_step_frame(mpctx, 1); + } +} - case MP_CMD_FRAME_BACK_STEP: - if (!mpctx->playback_initialized) - return -1; - add_step_frame(mpctx, -1); - break; +static void cmd_frame_back_step(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; - case MP_CMD_QUIT: - case MP_CMD_QUIT_WATCH_LATER: - if (cmd->id == MP_CMD_QUIT_WATCH_LATER || opts->position_save_on_quit) - mp_write_watch_later_conf(mpctx); - mpctx->stop_play = PT_QUIT; - mpctx->quit_custom_rc = cmd->args[0].v.i; - mpctx->has_quit_custom_rc = true; - mp_wakeup_core(mpctx); - break; + if (!mpctx->playback_initialized) { + cmd->success = false; + return; + } - case MP_CMD_PLAYLIST_NEXT: - case MP_CMD_PLAYLIST_PREV: - { - int dir = cmd->id == MP_CMD_PLAYLIST_PREV ? -1 : +1; - int force = cmd->args[0].v.i; + add_step_frame(mpctx, -1); +} - struct playlist_entry *e = mp_next_file(mpctx, dir, force, true); - if (!e && !force) - return -1; - mp_set_playlist_entry(mpctx, e); - if (on_osd & MP_ON_OSD_MSG) - mpctx->add_osd_seek_info |= OSD_SEEK_INFO_CURRENT_FILE; - break; +static void cmd_quit(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + bool write_watch_later = *(bool *)cmd->priv; + if (write_watch_later || mpctx->opts->position_save_on_quit) + mp_write_watch_later_conf(mpctx); + mpctx->stop_play = PT_QUIT; + mpctx->quit_custom_rc = cmd->args[0].v.i; + mpctx->has_quit_custom_rc = true; + mp_wakeup_core(mpctx); +} + +static void cmd_playlist_next_prev(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + int dir = *(int *)cmd->priv; + int force = cmd->args[0].v.i; + + struct playlist_entry *e = mp_next_file(mpctx, dir, force, true); + if (!e && !force) { + cmd->success = false; + return; } - case MP_CMD_SUB_STEP: - case MP_CMD_SUB_SEEK: { - if (!mpctx->playback_initialized) - return -1; - struct track *track = mpctx->current_track[0][STREAM_SUB]; - struct dec_sub *sub = track ? track->d_sub : NULL; - double refpts = get_current_time(mpctx); - if (sub && refpts != MP_NOPTS_VALUE) { - double a[2]; - a[0] = refpts; - a[1] = cmd->args[0].v.i; - if (sub_control(sub, SD_CTRL_SUB_STEP, a) > 0) { - if (cmd->id == MP_CMD_SUB_STEP) { - opts->subs_rend->sub_delay -= a[0] - refpts; - m_config_notify_change_opt_ptr(mpctx->mconfig, - &opts->subs_rend->sub_delay); - show_property_osd(mpctx, "sub-delay", on_osd); - } else { - // We can easily get stuck by failing to seek to the video - // frame which actually shows the sub first (because video - // frame PTS and sub PTS rarely match exactly). Add some - // rounding for the mess of it. - a[0] += 0.01 * (a[1] >= 0 ? 1 : -1); - mark_seek(mpctx); - queue_seek(mpctx, MPSEEK_ABSOLUTE, a[0], MPSEEK_EXACT, - MPSEEK_FLAG_DELAY); - set_osd_function(mpctx, (a[0] > refpts) ? OSD_FFW : OSD_REW); - if (seek_bar_osd) - mpctx->add_osd_seek_info |= OSD_SEEK_INFO_BAR; - if (seek_msg_osd) - mpctx->add_osd_seek_info |= OSD_SEEK_INFO_TEXT; - } + mp_set_playlist_entry(mpctx, e); + if (cmd->on_osd & MP_ON_OSD_MSG) + mpctx->add_osd_seek_info |= OSD_SEEK_INFO_CURRENT_FILE; +} + +static void cmd_sub_step_seek(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + bool step = *(bool *)cmd->priv; + + if (!mpctx->playback_initialized) { + cmd->success = false; + return; + } + + struct track *track = mpctx->current_track[0][STREAM_SUB]; + struct dec_sub *sub = track ? track->d_sub : NULL; + double refpts = get_current_time(mpctx); + if (sub && refpts != MP_NOPTS_VALUE) { + double a[2]; + a[0] = refpts; + a[1] = cmd->args[0].v.i; + if (sub_control(sub, SD_CTRL_SUB_STEP, a) > 0) { + if (step) { + mpctx->opts->subs_rend->sub_delay -= a[0] - refpts; + m_config_notify_change_opt_ptr(mpctx->mconfig, + &mpctx->opts->subs_rend->sub_delay); + show_property_osd(mpctx, "sub-delay", cmd->on_osd); + } else { + // We can easily get stuck by failing to seek to the video + // frame which actually shows the sub first (because video + // frame PTS and sub PTS rarely match exactly). Add some + // rounding for the mess of it. + a[0] += 0.01 * (a[1] >= 0 ? 1 : -1); + mark_seek(mpctx); + queue_seek(mpctx, MPSEEK_ABSOLUTE, a[0], MPSEEK_EXACT, + MPSEEK_FLAG_DELAY); + set_osd_function(mpctx, (a[0] > refpts) ? OSD_FFW : OSD_REW); + if (cmd->seek_bar_osd) + mpctx->add_osd_seek_info |= OSD_SEEK_INFO_BAR; + if (cmd->seek_msg_osd) + mpctx->add_osd_seek_info |= OSD_SEEK_INFO_TEXT; } } - break; } +} - case MP_CMD_PRINT_TEXT: { - MP_INFO(mpctx, "%s\n", cmd->args[0].v.s); - break; - } +static void cmd_print_text(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; - case MP_CMD_SHOW_TEXT: { - // if no argument supplied use default osd_duration, else ms. - set_osd_msg(mpctx, cmd->args[2].v.i, - (cmd->args[1].v.i < 0 ? osd_duration : cmd->args[1].v.i), - "%s", cmd->args[0].v.s); - break; + MP_INFO(mpctx, "%s\n", cmd->args[0].v.s); +} + +static void cmd_show_text(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + int osd_duration = mpctx->opts->osd_duration; + + // if no argument supplied use default osd_duration, else ms. + set_osd_msg(mpctx, cmd->args[2].v.i, + (cmd->args[1].v.i < 0 ? osd_duration : cmd->args[1].v.i), + "%s", cmd->args[0].v.s); +} + +static void cmd_expand_text(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + + *cmd->result = (mpv_node){ + .format = MPV_FORMAT_STRING, + .u.string = mp_property_expand_string(mpctx, cmd->args[0].v.s) + }; +} + +static void cmd_loadfile(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + char *filename = cmd->args[0].v.s; + int append = cmd->args[1].v.i; + + if (!append) + playlist_clear(mpctx->playlist); + + struct playlist_entry *entry = playlist_entry_new(filename); + if (cmd->args[2].v.str_list) { + char **pairs = cmd->args[2].v.str_list; + for (int i = 0; pairs[i] && pairs[i + 1]; i += 2) + playlist_entry_add_param(entry, bstr0(pairs[i]), bstr0(pairs[i + 1])); } + playlist_add(mpctx->playlist, entry); - case MP_CMD_EXPAND_TEXT: { - if (!res) - return -1; - *res = (mpv_node){ - .format = MPV_FORMAT_STRING, - .u.string = mp_property_expand_string(mpctx, cmd->args[0].v.s) - }; - break; + if (!append || (append == 2 && !mpctx->playlist->current)) { + if (mpctx->opts->position_save_on_quit) // requested in issue #1148 + mp_write_watch_later_conf(mpctx); + mp_set_playlist_entry(mpctx, entry); } + mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL); + mp_wakeup_core(mpctx); +} - case MP_CMD_LOADFILE: { - char *filename = cmd->args[0].v.s; - int append = cmd->args[1].v.i; +static void cmd_loadlist(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + char *filename = cmd->args[0].v.s; + bool append = cmd->args[1].v.i; + struct playlist *pl = playlist_parse_file(filename, mpctx->global); + if (pl) { + prepare_playlist(mpctx, pl); + struct playlist_entry *new = pl->current; if (!append) playlist_clear(mpctx->playlist); + playlist_append_entries(mpctx->playlist, pl); + talloc_free(pl); - struct playlist_entry *entry = playlist_entry_new(filename); - if (cmd->args[2].v.str_list) { - char **pairs = cmd->args[2].v.str_list; - for (int i = 0; pairs[i] && pairs[i + 1]; i += 2) { - playlist_entry_add_param(entry, bstr0(pairs[i]), - bstr0(pairs[i + 1])); - } - } - playlist_add(mpctx->playlist, entry); + if (!append && mpctx->playlist->first) + mp_set_playlist_entry(mpctx, new ? new : mpctx->playlist->first); - if (!append || (append == 2 && !mpctx->playlist->current)) { - if (opts->position_save_on_quit) // requested in issue #1148 - mp_write_watch_later_conf(mpctx); - mp_set_playlist_entry(mpctx, entry); - } mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL); mp_wakeup_core(mpctx); - break; + } else { + MP_ERR(mpctx, "Unable to load playlist %s.\n", filename); + cmd->success = false; } +} - case MP_CMD_LOADLIST: { - char *filename = cmd->args[0].v.s; - bool append = cmd->args[1].v.i; - struct playlist *pl = playlist_parse_file(filename, mpctx->global); - if (pl) { - prepare_playlist(mpctx, pl); - struct playlist_entry *new = pl->current; - if (!append) - playlist_clear(mpctx->playlist); - playlist_append_entries(mpctx->playlist, pl); - talloc_free(pl); - - if (!append && mpctx->playlist->first) - mp_set_playlist_entry(mpctx, new ? new : mpctx->playlist->first); +static void cmd_playlist_clear(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; - mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL); - mp_wakeup_core(mpctx); - } else { - MP_ERR(mpctx, "Unable to load playlist %s.\n", filename); - return -1; + // Supposed to clear the playlist, except the currently played item. + if (mpctx->playlist->current_was_replaced) + mpctx->playlist->current = NULL; + while (mpctx->playlist->first) { + struct playlist_entry *e = mpctx->playlist->first; + if (e == mpctx->playlist->current) { + e = e->next; + if (!e) + break; } - break; + playlist_remove(mpctx->playlist, e); } + mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL); + mp_wakeup_core(mpctx); +} - case MP_CMD_PLAYLIST_CLEAR: { - // Supposed to clear the playlist, except the currently played item. - if (mpctx->playlist->current_was_replaced) - mpctx->playlist->current = NULL; - while (mpctx->playlist->first) { - struct playlist_entry *e = mpctx->playlist->first; - if (e == mpctx->playlist->current) { - e = e->next; - if (!e) - break; - } - playlist_remove(mpctx->playlist, e); - } - mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL); - mp_wakeup_core(mpctx); - break; - } +static void cmd_playlist_remove(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; - case MP_CMD_PLAYLIST_REMOVE: { - struct playlist_entry *e = playlist_entry_from_index(mpctx->playlist, - cmd->args[0].v.i); - if (cmd->args[0].v.i < 0) - e = mpctx->playlist->current; - if (!e) - return -1; - // Can't play a removed entry - if (mpctx->playlist->current == e && !mpctx->stop_play) - mpctx->stop_play = PT_NEXT_ENTRY; - playlist_remove(mpctx->playlist, e); - mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL); - mp_wakeup_core(mpctx); - break; + struct playlist_entry *e = playlist_entry_from_index(mpctx->playlist, + cmd->args[0].v.i); + if (cmd->args[0].v.i < 0) + e = mpctx->playlist->current; + if (!e) { + cmd->success = false; + return; } - case MP_CMD_PLAYLIST_MOVE: { - struct playlist_entry *e1 = playlist_entry_from_index(mpctx->playlist, - cmd->args[0].v.i); - struct playlist_entry *e2 = playlist_entry_from_index(mpctx->playlist, - cmd->args[1].v.i); - if (!e1) - return -1; - playlist_move(mpctx->playlist, e1, e2); - mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL); - break; + // Can't play a removed entry + if (mpctx->playlist->current == e && !mpctx->stop_play) + mpctx->stop_play = PT_NEXT_ENTRY; + playlist_remove(mpctx->playlist, e); + mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL); + mp_wakeup_core(mpctx); +} + +static void cmd_playlist_move(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + + struct playlist_entry *e1 = playlist_entry_from_index(mpctx->playlist, + cmd->args[0].v.i); + struct playlist_entry *e2 = playlist_entry_from_index(mpctx->playlist, + cmd->args[1].v.i); + if (!e1) { + cmd->success = false; + return; } - case MP_CMD_PLAYLIST_SHUFFLE: { - playlist_shuffle(mpctx->playlist); - mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL); - break; + playlist_move(mpctx->playlist, e1, e2); + mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL); +} + +static void cmd_playlist_shuffle(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + + playlist_shuffle(mpctx->playlist); + mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL); +} + +static void cmd_stop(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + + playlist_clear(mpctx->playlist); + if (mpctx->stop_play != PT_QUIT) + mpctx->stop_play = PT_STOP; + mp_wakeup_core(mpctx); +} + +static void cmd_show_progress(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + + mpctx->add_osd_seek_info |= + (cmd->msg_osd ? OSD_SEEK_INFO_TEXT : 0) | + (cmd->bar_osd ? OSD_SEEK_INFO_BAR : 0); + mpctx->osd_force_update = true; + mp_wakeup_core(mpctx); +} + +static void cmd_tv_last_channel(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + + if (!mpctx->demuxer) { + cmd->success = false; + return; } - case MP_CMD_STOP: - playlist_clear(mpctx->playlist); - if (mpctx->stop_play != PT_QUIT) - mpctx->stop_play = PT_STOP; - mp_wakeup_core(mpctx); - break; + demux_stream_control(mpctx->demuxer, STREAM_CTRL_TV_LAST_CHAN, NULL); +} - case MP_CMD_SHOW_PROGRESS: - mpctx->add_osd_seek_info |= - (msg_osd ? OSD_SEEK_INFO_TEXT : 0) | - (bar_osd ? OSD_SEEK_INFO_BAR : 0); - mpctx->osd_force_update = true; - mp_wakeup_core(mpctx); - break; +static void cmd_track_add(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + int type = *(int *)cmd->priv; - case MP_CMD_TV_LAST_CHANNEL: { - if (!mpctx->demuxer) - return -1; - demux_stream_control(mpctx->demuxer, STREAM_CTRL_TV_LAST_CHAN, NULL); - break; + if (!mpctx->playing) { + cmd->success = false; + return; } - case MP_CMD_SUB_ADD: - case MP_CMD_AUDIO_ADD: { - if (!mpctx->playing) - return -1; - int type = cmd->id == MP_CMD_SUB_ADD ? STREAM_SUB : STREAM_AUDIO; - if (cmd->args[1].v.i == 2) { - struct track *t = find_track_with_url(mpctx, type, cmd->args[0].v.s); - if (t) { - if (mpctx->playback_initialized) { - mp_switch_track(mpctx, t->type, t, FLAG_MARK_SELECTION); - print_track_list(mpctx, "Track switched:"); - } else { - opts->stream_id[0][t->type] = t->user_tid; - } - return 0; + if (cmd->args[1].v.i == 2) { + struct track *t = find_track_with_url(mpctx, type, cmd->args[0].v.s); + if (t) { + if (mpctx->playback_initialized) { + mp_switch_track(mpctx, t->type, t, FLAG_MARK_SELECTION); + print_track_list(mpctx, "Track switched:"); + } else { + mpctx->opts->stream_id[0][t->type] = t->user_tid; } + return; } - int first = mp_add_external_file(mpctx, cmd->args[0].v.s, type); - if (first < 0) - return -1; - - for (int n = first; n < mpctx->num_tracks; n++) { - struct track *t = mpctx->tracks[n]; - if (cmd->args[1].v.i == 1) { - t->no_default = true; - } else if (n == first) { - if (mpctx->playback_initialized) { - mp_switch_track(mpctx, t->type, t, FLAG_MARK_SELECTION); - } else { - opts->stream_id[0][t->type] = t->user_tid; - } + } + int first = mp_add_external_file(mpctx, cmd->args[0].v.s, type); + if (first < 0) { + cmd->success = false; + return; + } + + for (int n = first; n < mpctx->num_tracks; n++) { + struct track *t = mpctx->tracks[n]; + if (cmd->args[1].v.i == 1) { + t->no_default = true; + } else if (n == first) { + if (mpctx->playback_initialized) { + mp_switch_track(mpctx, t->type, t, FLAG_MARK_SELECTION); + } else { + mpctx->opts->stream_id[0][t->type] = t->user_tid; } - char *title = cmd->args[2].v.s; - if (title && title[0]) - t->title = talloc_strdup(t, title); - char *lang = cmd->args[3].v.s; - if (lang && lang[0]) - t->lang = talloc_strdup(t, lang); } - - if (mpctx->playback_initialized) - print_track_list(mpctx, "Track added:"); - break; + char *title = cmd->args[2].v.s; + if (title && title[0]) + t->title = talloc_strdup(t, title); + char *lang = cmd->args[3].v.s; + if (lang && lang[0]) + t->lang = talloc_strdup(t, lang); } - case MP_CMD_SUB_REMOVE: - case MP_CMD_AUDIO_REMOVE: { - int type = cmd->id == MP_CMD_SUB_REMOVE ? STREAM_SUB : STREAM_AUDIO; - struct track *t = mp_track_by_tid(mpctx, type, cmd->args[0].v.i); - if (!t) - return -1; - mp_remove_track(mpctx, t); - if (mpctx->playback_initialized) - print_track_list(mpctx, "Track removed:"); - break; + if (mpctx->playback_initialized) + print_track_list(mpctx, "Track added:"); +} + +static void cmd_track_remove(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + int type = *(int *)cmd->priv; + + struct track *t = mp_track_by_tid(mpctx, type, cmd->args[0].v.i); + if (!t) { + cmd->success = false; + return; } - case MP_CMD_SUB_RELOAD: - case MP_CMD_AUDIO_RELOAD: { - if (!mpctx->playback_initialized) { - MP_ERR(mpctx, "Cannot reload while not initialized.\n"); - return -1; - } - int type = cmd->id == MP_CMD_SUB_RELOAD ? STREAM_SUB : STREAM_AUDIO; - struct track *t = mp_track_by_tid(mpctx, type, cmd->args[0].v.i); - int nt_num = -1; - if (t && t->is_external && t->external_filename) { - char *filename = talloc_strdup(NULL, t->external_filename); - mp_remove_track(mpctx, t); - nt_num = mp_add_external_file(mpctx, filename, type); - talloc_free(filename); - } - if (nt_num >= 0) { - struct track *nt = mpctx->tracks[nt_num]; - mp_switch_track(mpctx, nt->type, nt, 0); - print_track_list(mpctx, "Reloaded:"); - return 0; - } - return -1; + mp_remove_track(mpctx, t); + if (mpctx->playback_initialized) + print_track_list(mpctx, "Track removed:"); +} + +static void cmd_track_reload(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + int type = *(int *)cmd->priv; + + if (!mpctx->playback_initialized) { + MP_ERR(mpctx, "Cannot reload while not initialized.\n"); + cmd->success = false; + return; } - case MP_CMD_RESCAN_EXTERNAL_FILES: { - if (!mpctx->playing) - return -1; - autoload_external_files(mpctx); - if (cmd->args[0].v.i && mpctx->playback_initialized) { - // somewhat fuzzy and not ideal - struct track *a = select_default_track(mpctx, 0, STREAM_AUDIO); - if (a && a->is_external) - mp_switch_track(mpctx, STREAM_AUDIO, a, 0); - struct track *s = select_default_track(mpctx, 0, STREAM_SUB); - if (s && s->is_external) - mp_switch_track(mpctx, STREAM_SUB, s, 0); - - print_track_list(mpctx, "Track list:\n"); - } - break; + struct track *t = mp_track_by_tid(mpctx, type, cmd->args[0].v.i); + int nt_num = -1; + + if (t && t->is_external && t->external_filename) { + char *filename = talloc_strdup(NULL, t->external_filename); + mp_remove_track(mpctx, t); + nt_num = mp_add_external_file(mpctx, filename, type); + talloc_free(filename); } - case MP_CMD_SCREENSHOT: { - int mode = cmd->args[0].v.i & 3; - int freq = (cmd->args[0].v.i | cmd->args[1].v.i) >> 3; - screenshot_request(mpctx, mode, freq, msg_osd, async); - break; + if (nt_num < 0) { + cmd->success = false; + return; } - case MP_CMD_SCREENSHOT_TO_FILE: - screenshot_to_file(mpctx, cmd->args[0].v.s, cmd->args[1].v.i, msg_osd, - async); - break; + struct track *nt = mpctx->tracks[nt_num]; + mp_switch_track(mpctx, nt->type, nt, 0); + print_track_list(mpctx, "Reloaded:"); +} - case MP_CMD_SCREENSHOT_RAW: { - if (!res) - return -1; - struct mp_image *img = screenshot_get_rgb(mpctx, cmd->args[0].v.i); - if (!img) - return -1; - node_init(res, MPV_FORMAT_NODE_MAP, NULL); - node_map_add_int64(res, "w", img->w); - node_map_add_int64(res, "h", img->h); - node_map_add_int64(res, "stride", img->stride[0]); - node_map_add_string(res, "format", "bgr0"); - struct mpv_byte_array *ba = - node_map_add(res, "data", MPV_FORMAT_BYTE_ARRAY)->u.ba; - *ba = (struct mpv_byte_array){ - .data = img->planes[0], - .size = img->stride[0] * img->h, - }; - talloc_steal(ba, img); - break; +static void cmd_rescan_external_files(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + + if (!mpctx->playing) { + cmd->success = false; + return; } - case MP_CMD_RUN: { - char **args = talloc_zero_array(NULL, char *, cmd->nargs + 1); - for (int n = 0; n < cmd->nargs; n++) - args[n] = cmd->args[n].v.s; - mp_subprocess_detached(mpctx->log, args); - talloc_free(args); - break; + autoload_external_files(mpctx); + if (cmd->args[0].v.i && mpctx->playback_initialized) { + // somewhat fuzzy and not ideal + struct track *a = select_default_track(mpctx, 0, STREAM_AUDIO); + if (a && a->is_external) + mp_switch_track(mpctx, STREAM_AUDIO, a, 0); + struct track *s = select_default_track(mpctx, 0, STREAM_SUB); + if (s && s->is_external) + mp_switch_track(mpctx, STREAM_SUB, s, 0); + + print_track_list(mpctx, "Track list:\n"); } +} - case MP_CMD_ENABLE_INPUT_SECTION: - mp_input_enable_section(mpctx->input, cmd->args[0].v.s, cmd->args[1].v.i); - break; +static void cmd_screenshot(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + bool async = cmd->cmd->flags & MP_ASYNC_CMD; + int mode = cmd->args[0].v.i & 3; + int freq = (cmd->args[0].v.i | cmd->args[1].v.i) >> 3; + screenshot_request(mpctx, mode, freq, cmd->msg_osd, async); +} - case MP_CMD_DISABLE_INPUT_SECTION: - mp_input_disable_section(mpctx->input, cmd->args[0].v.s); - break; +static void cmd_screenshot_to_file(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + bool async = cmd->cmd->flags & MP_ASYNC_CMD; + screenshot_to_file(mpctx, cmd->args[0].v.s, cmd->args[1].v.i, cmd->msg_osd, + async); +} - case MP_CMD_DEFINE_INPUT_SECTION: - mp_input_define_section(mpctx->input, cmd->args[0].v.s, "", - cmd->args[1].v.s, !!cmd->args[2].v.i, - cmd->sender); - break; +static void cmd_screenshot_raw(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + struct mpv_node *res = cmd->result; - case MP_CMD_AB_LOOP: { - double now = get_current_time(mpctx); - if (opts->ab_loop[0] == MP_NOPTS_VALUE) { - mp_property_do("ab-loop-a", M_PROPERTY_SET, &now, mpctx); - show_property_osd(mpctx, "ab-loop-a", on_osd); - } else if (opts->ab_loop[1] == MP_NOPTS_VALUE) { - mp_property_do("ab-loop-b", M_PROPERTY_SET, &now, mpctx); - show_property_osd(mpctx, "ab-loop-b", on_osd); - } else { - now = MP_NOPTS_VALUE; - mp_property_do("ab-loop-a", M_PROPERTY_SET, &now, mpctx); - mp_property_do("ab-loop-b", M_PROPERTY_SET, &now, mpctx); - set_osd_msg(mpctx, osdl, osd_duration, "Clear A-B loop"); - } - break; + struct mp_image *img = screenshot_get_rgb(mpctx, cmd->args[0].v.i); + if (!img) { + cmd->success = false; + return; } - case MP_CMD_DROP_BUFFERS: { - reset_audio_state(mpctx); - reset_video_state(mpctx); + node_init(res, MPV_FORMAT_NODE_MAP, NULL); + node_map_add_int64(res, "w", img->w); + node_map_add_int64(res, "h", img->h); + node_map_add_int64(res, "stride", img->stride[0]); + node_map_add_string(res, "format", "bgr0"); + struct mpv_byte_array *ba = + node_map_add(res, "data", MPV_FORMAT_BYTE_ARRAY)->u.ba; + *ba = (struct mpv_byte_array){ + .data = img->planes[0], + .size = img->stride[0] * img->h, + }; + talloc_steal(ba, img); +} - if (mpctx->demuxer) - demux_flush(mpctx->demuxer); +static void cmd_run(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + char **args = talloc_zero_array(NULL, char *, cmd->num_args + 1); + for (int n = 0; n < cmd->num_args; n++) + args[n] = cmd->args[n].v.s; + mp_subprocess_detached(mpctx->log, args); + talloc_free(args); +} - break; +static void cmd_enable_input_section(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + mp_input_enable_section(mpctx->input, cmd->args[0].v.s, cmd->args[1].v.i); +} + +static void cmd_disable_input_section(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + mp_input_disable_section(mpctx->input, cmd->args[0].v.s); +} + +static void cmd_define_input_section(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + mp_input_define_section(mpctx->input, cmd->args[0].v.s, "", + cmd->args[1].v.s, !!cmd->args[2].v.i, + cmd->cmd->sender); +} + +static void cmd_ab_loop(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + int osd_duration = mpctx->opts->osd_duration; + int osdl = cmd->msg_osd ? 1 : OSD_LEVEL_INVISIBLE; + + double now = get_current_time(mpctx); + if (mpctx->opts->ab_loop[0] == MP_NOPTS_VALUE) { + mp_property_do("ab-loop-a", M_PROPERTY_SET, &now, mpctx); + show_property_osd(mpctx, "ab-loop-a", cmd->on_osd); + } else if (mpctx->opts->ab_loop[1] == MP_NOPTS_VALUE) { + mp_property_do("ab-loop-b", M_PROPERTY_SET, &now, mpctx); + show_property_osd(mpctx, "ab-loop-b", cmd->on_osd); + } else { + now = MP_NOPTS_VALUE; + mp_property_do("ab-loop-a", M_PROPERTY_SET, &now, mpctx); + mp_property_do("ab-loop-b", M_PROPERTY_SET, &now, mpctx); + set_osd_msg(mpctx, osdl, osd_duration, "Clear A-B loop"); } +} - case MP_CMD_AO_RELOAD: - reload_audio_output(mpctx); - break; +static void cmd_drop_buffers(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; - case MP_CMD_AF: - return edit_filters_osd(mpctx, STREAM_AUDIO, cmd->args[0].v.s, - cmd->args[1].v.s, msg_osd); + reset_audio_state(mpctx); + reset_video_state(mpctx); - case MP_CMD_VF: - return edit_filters_osd(mpctx, STREAM_VIDEO, cmd->args[0].v.s, - cmd->args[1].v.s, msg_osd); + if (mpctx->demuxer) + demux_flush(mpctx->demuxer); +} - case MP_CMD_AF_COMMAND: - case MP_CMD_VF_COMMAND: { - struct mp_output_chain *chain = NULL; - if (cmd->id == MP_CMD_VF_COMMAND) { - chain = mpctx->vo_chain ? mpctx->vo_chain->filter : NULL; - } else { - chain = mpctx->ao_chain ? mpctx->ao_chain->filter : NULL; - } - if (!chain) - return -1; - struct mp_filter_command filter_cmd = { - .type = MP_FILTER_COMMAND_TEXT, - .cmd = cmd->args[1].v.s, - .arg = cmd->args[2].v.s, - }; - return mp_output_chain_command(chain, cmd->args[0].v.s, &filter_cmd) - ? 0 : -1; - } - - case MP_CMD_SCRIPT_BINDING: { - mpv_event_client_message event = {0}; - char *name = cmd->args[0].v.s; - if (!name || !name[0]) - return -1; - char *sep = strchr(name, '/'); - char *target = NULL; - char space[MAX_CLIENT_NAME]; - if (sep) { - snprintf(space, sizeof(space), "%.*s", (int)(sep - name), name); - target = space; - name = sep + 1; - } - char state[3] = {'p', cmd->is_mouse_button ? 'm' : '-'}; - if (cmd->is_up_down) - state[0] = cmd->repeated ? 'r' : (cmd->is_up ? 'u' : 'd'); - event.num_args = 4; - event.args = (const char*[4]){"key-binding", name, state, - cmd->key_name ? cmd->key_name : ""}; - if (mp_client_send_event_dup(mpctx, target, - MPV_EVENT_CLIENT_MESSAGE, &event) < 0) - { - MP_VERBOSE(mpctx, "Can't find script '%s' when handling input.\n", - target ? target : "-"); - return -1; - } - break; +static void cmd_ao_reload(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + reload_audio_output(mpctx); +} + +static void cmd_filter(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + int type = *(int *)cmd->priv; + cmd->success = edit_filters_osd(mpctx, type, cmd->args[0].v.s, + cmd->args[1].v.s, cmd->msg_osd) >= 0; +} + +static void cmd_filter_command(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + int type = *(int *)cmd->priv; + + struct mp_output_chain *chain = NULL; + if (type == STREAM_VIDEO) + chain = mpctx->vo_chain ? mpctx->vo_chain->filter : NULL; + if (type == STREAM_AUDIO) + chain = mpctx->ao_chain ? mpctx->ao_chain->filter : NULL; + if (!chain) { + cmd->success = false; + return; } + struct mp_filter_command filter_cmd = { + .type = MP_FILTER_COMMAND_TEXT, + .cmd = cmd->args[1].v.s, + .arg = cmd->args[2].v.s, + }; + cmd->success = mp_output_chain_command(chain, cmd->args[0].v.s, &filter_cmd); +} - case MP_CMD_SCRIPT_MESSAGE_TO: { - mpv_event_client_message *event = talloc_ptrtype(NULL, event); - *event = (mpv_event_client_message){0}; - for (int n = 1; n < cmd->nargs; n++) { - MP_TARRAY_APPEND(event, event->args, event->num_args, - talloc_strdup(event, cmd->args[n].v.s)); - } - if (mp_client_send_event(mpctx, cmd->args[0].v.s, 0, - MPV_EVENT_CLIENT_MESSAGE, event) < 0) - { - MP_VERBOSE(mpctx, "Can't find script '%s' for %s.\n", - cmd->args[0].v.s, cmd->name); - return -1; - } - break; +static void cmd_script_binding(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct mp_cmd *incmd = cmd->cmd; + struct MPContext *mpctx = cmd->mpctx; + + mpv_event_client_message event = {0}; + char *name = cmd->args[0].v.s; + if (!name || !name[0]) { + cmd->success = false; + return; } - case MP_CMD_SCRIPT_MESSAGE: { - const char **args = talloc_array(NULL, const char *, cmd->nargs); - mpv_event_client_message event = {.args = args}; - for (int n = 0; n < cmd->nargs; n++) - event.args[event.num_args++] = cmd->args[n].v.s; - mp_client_broadcast_event(mpctx, MPV_EVENT_CLIENT_MESSAGE, &event); - talloc_free(args); - break; + + char *sep = strchr(name, '/'); + char *target = NULL; + char space[MAX_CLIENT_NAME]; + if (sep) { + snprintf(space, sizeof(space), "%.*s", (int)(sep - name), name); + target = space; + name = sep + 1; + } + char state[3] = {'p', incmd->is_mouse_button ? 'm' : '-'}; + if (incmd->is_up_down) + state[0] = incmd->repeated ? 'r' : (incmd->is_up ? 'u' : 'd'); + event.num_args = 4; + event.args = (const char*[4]){"key-binding", name, state, + incmd->key_name ? incmd->key_name : ""}; + if (mp_client_send_event_dup(mpctx, target, + MPV_EVENT_CLIENT_MESSAGE, &event) < 0) + { + MP_VERBOSE(mpctx, "Can't find script '%s' when handling input.\n", + target ? target : "-"); + cmd->success = false; } +} - case MP_CMD_OVERLAY_ADD: - overlay_add(mpctx, - cmd->args[0].v.i, cmd->args[1].v.i, cmd->args[2].v.i, - cmd->args[3].v.s, cmd->args[4].v.i, cmd->args[5].v.s, - cmd->args[6].v.i, cmd->args[7].v.i, cmd->args[8].v.i); - break; +static void cmd_script_message_to(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; - case MP_CMD_OVERLAY_REMOVE: - overlay_remove(mpctx, cmd->args[0].v.i); - break; + mpv_event_client_message *event = talloc_ptrtype(NULL, event); + *event = (mpv_event_client_message){0}; + for (int n = 1; n < cmd->num_args; n++) { + MP_TARRAY_APPEND(event, event->args, event->num_args, + talloc_strdup(event, cmd->args[n].v.s)); + } + if (mp_client_send_event(mpctx, cmd->args[0].v.s, 0, + MPV_EVENT_CLIENT_MESSAGE, event) < 0) + { + MP_VERBOSE(mpctx, "Can't find script '%s' to send message to.\n", + cmd->args[0].v.s); + cmd->success = false; + } +} - case MP_CMD_COMMAND_LIST: { - for (struct mp_cmd *sub = cmd->args[0].v.p; sub; sub = sub->queue_next) - run_command(mpctx, sub, NULL); - break; +static void cmd_script_message(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + + const char **args = talloc_array(NULL, const char *, cmd->num_args); + mpv_event_client_message event = {.args = args}; + for (int n = 0; n < cmd->num_args; n++) + event.args[event.num_args++] = cmd->args[n].v.s; + mp_client_broadcast_event(mpctx, MPV_EVENT_CLIENT_MESSAGE, &event); + talloc_free(args); +} + +static void cmd_ignore(void *p) +{ +} + +static void cmd_write_watch_later_config(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + + mp_write_watch_later_conf(mpctx); +} + +static void cmd_hook_add(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + + if (!cmd->cmd->sender) { + MP_ERR(mpctx, "Can be used from client API only.\n"); + cmd->success = false; + return; } + mp_hook_add(mpctx, cmd->cmd->sender, cmd->args[0].v.s, cmd->args[1].v.i, + cmd->args[2].v.i, true); +} - case MP_CMD_IGNORE: - break; +static void cmd_hook_ack(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; - case MP_CMD_WRITE_WATCH_LATER_CONFIG: { - mp_write_watch_later_conf(mpctx); - break; + if (!cmd->cmd->sender) { + MP_ERR(mpctx, "Can be used from client API only.\n"); + cmd->success = false; + return; } + mp_hook_continue(mpctx, cmd->cmd->sender, cmd->args[0].v.i); +} - case MP_CMD_HOOK_ADD: - if (!cmd->sender) { - MP_ERR(mpctx, "Can be used from client API only.\n"); - return -1; - } - mp_hook_add(mpctx, cmd->sender, cmd->args[0].v.s, cmd->args[1].v.i, - cmd->args[2].v.i, true); - break; - case MP_CMD_HOOK_ACK: - if (!cmd->sender) { - MP_ERR(mpctx, "Can be used from client API only.\n"); - return -1; - } -