From f60826c3a14ba3b49077f17e5364b7347f9b468a Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 23 Mar 2018 16:24:49 +0100 Subject: client API: add a first class hook API, and deprecate old API As it turns out, there are multiple libmpv users who saw a need to use the hook API. The API is kind of shitty and was never meant to be actually public (it was mostly a hack for the ytdl script). Introduce a proper API and deprecate the old one. The old one will probably continue to work for a few releases, but will be removed eventually. There are some slight changes to the old API, but if a user followed the manual properly, it won't break. Mostly untested. Appears to work with ytdl_hook. --- player/client.c | 23 +++++++- player/client.h | 2 +- player/command.c | 139 +++++++++++++++++++++++++++++++----------------- player/command.h | 7 ++- player/loadfile.c | 6 +-- player/lua.c | 24 +++++++++ player/lua/defaults.lua | 17 +++--- player/scripting.c | 3 +- 8 files changed, 154 insertions(+), 67 deletions(-) (limited to 'player') diff --git a/player/client.c b/player/client.c index c6803806c9..1fe38881ad 100644 --- a/player/client.c +++ b/player/client.c @@ -728,7 +728,7 @@ void mp_client_broadcast_event(struct MPContext *mpctx, int event, void *data) // If client_name == NULL, then broadcast and free the event. int mp_client_send_event(struct MPContext *mpctx, const char *client_name, - int event, void *data) + uint64_t reply_userdata, int event, void *data) { if (!client_name) { mp_client_broadcast_event(mpctx, event, data); @@ -742,6 +742,7 @@ int mp_client_send_event(struct MPContext *mpctx, const char *client_name, struct mpv_event event_data = { .event_id = event, .data = data, + .reply_userdata = reply_userdata, }; pthread_mutex_lock(&clients->lock); @@ -773,7 +774,7 @@ int mp_client_send_event_dup(struct MPContext *mpctx, const char *client_name, }; dup_event_data(&event_data); - return mp_client_send_event(mpctx, client_name, event, event_data.data); + return mp_client_send_event(mpctx, client_name, 0, event, event_data.data); } int mpv_request_event(mpv_handle *ctx, mpv_event_id event, int enable) @@ -1558,6 +1559,23 @@ static bool gen_property_change_event(struct mpv_handle *ctx) return false; } +int mpv_hook_add(mpv_handle *ctx, uint64_t reply_userdata, + const char *name, int priority) +{ + lock_core(ctx); + mp_hook_add(ctx->mpctx, ctx->name, name, reply_userdata, priority, false); + unlock_core(ctx); + return 0; +} + +int mpv_hook_continue(mpv_handle *ctx, uint64_t id) +{ + lock_core(ctx); + int r = mp_hook_continue(ctx->mpctx, ctx->name, id); + unlock_core(ctx); + return r; +} + int mpv_load_config_file(mpv_handle *ctx, const char *filename) { int flags = ctx->mpctx->initialized ? M_SETOPT_RUNTIME : 0; @@ -1708,6 +1726,7 @@ static const char *const event_table[] = { [MPV_EVENT_PROPERTY_CHANGE] = "property-change", [MPV_EVENT_CHAPTER_CHANGE] = "chapter-change", [MPV_EVENT_QUEUE_OVERFLOW] = "event-queue-overflow", + [MPV_EVENT_HOOK] = "hook", }; const char *mpv_event_name(mpv_event_id event) diff --git a/player/client.h b/player/client.h index 9ecbe2ed35..b1c2ffc500 100644 --- a/player/client.h +++ b/player/client.h @@ -25,7 +25,7 @@ bool mp_clients_all_initialized(struct MPContext *mpctx); bool mp_client_exists(struct MPContext *mpctx, const char *client_name); void mp_client_broadcast_event(struct MPContext *mpctx, int event, void *data); int mp_client_send_event(struct MPContext *mpctx, const char *client_name, - int event, void *data); + uint64_t reply_userdata, int event, void *data); int mp_client_send_event_dup(struct MPContext *mpctx, const char *client_name, int event, void *data); bool mp_client_event_is_registered(struct MPContext *mpctx, int event); diff --git a/player/command.c b/player/command.c index 572930e69f..da5a7e39f5 100644 --- a/player/command.c +++ b/player/command.c @@ -115,9 +115,10 @@ struct overlay { struct hook_handler { char *client; // client API user name char *type; // kind of hook, e.g. "on_load" - char *user_id; // numeric user-chosen ID, printed as string + uint64_t user_id; // user-chosen ID int priority; // priority for global hook order - int64_t seq; // unique ID (also age -> fixed order for equal priorities) + int64_t seq; // unique ID, != 0, also for fixed order on equal priorities + bool legacy; // old cmd based hook API bool active; // hook is currently in progress (only 1 at a time for now) }; @@ -137,12 +138,17 @@ static int set_filters(struct MPContext *mpctx, enum stream_type mediatype, static int mp_property_do_silent(const char *name, int action, void *val, struct MPContext *ctx); -static void hook_remove(struct MPContext *mpctx, int index) +static void hook_remove(struct MPContext *mpctx, struct hook_handler *h) { struct command_ctx *cmd = mpctx->command_ctx; - assert(index >= 0 && index < cmd->num_hooks); - talloc_free(cmd->hooks[index]); - MP_TARRAY_REMOVE_AT(cmd->hooks, cmd->num_hooks, index); + for (int n = 0; n < cmd->num_hooks; n++) { + if (cmd->hooks[n] == h) { + talloc_free(cmd->hooks[n]); + MP_TARRAY_REMOVE_AT(cmd->hooks, cmd->num_hooks, n); + return; + } + } + assert(0); } bool mp_hook_test_completion(struct MPContext *mpctx, char *type) @@ -152,7 +158,8 @@ bool mp_hook_test_completion(struct MPContext *mpctx, char *type) struct hook_handler *h = cmd->hooks[n]; if (h->active && strcmp(h->type, type) == 0) { if (!mp_client_exists(mpctx, h->client)) { - hook_remove(mpctx, n); + MP_WARN(mpctx, "client removed during hook handling\n"); + hook_remove(mpctx, h); break; } return false; @@ -161,49 +168,81 @@ bool mp_hook_test_completion(struct MPContext *mpctx, char *type) return true; } -static bool send_hook_msg(struct MPContext *mpctx, struct hook_handler *h, - char *cmd) -{ - mpv_event_client_message *m = talloc_ptrtype(NULL, m); - *m = (mpv_event_client_message){0}; - MP_TARRAY_APPEND(m, m->args, m->num_args, cmd); - MP_TARRAY_APPEND(m, m->args, m->num_args, talloc_strdup(m, h->user_id)); - MP_TARRAY_APPEND(m, m->args, m->num_args, talloc_strdup(m, h->type)); - bool r = - mp_client_send_event(mpctx, h->client, MPV_EVENT_CLIENT_MESSAGE, m) >= 0; - if (!r) - MP_WARN(mpctx, "Sending hook command failed.\n"); +static int invoke_hook_handler(struct MPContext *mpctx, struct hook_handler *h) +{ + MP_VERBOSE(mpctx, "Running hook: %s/%s\n", h->client, h->type); + h->active = true; + + uint64_t reply_id = 0; + void *data; + int msg; + if (h->legacy) { + mpv_event_client_message *m = talloc_ptrtype(NULL, m); + *m = (mpv_event_client_message){0}; + MP_TARRAY_APPEND(m, m->args, m->num_args, "hook_run"); + MP_TARRAY_APPEND(m, m->args, m->num_args, + talloc_asprintf(m, "%llu", (long long)h->user_id)); + MP_TARRAY_APPEND(m, m->args, m->num_args, + talloc_asprintf(m, "%llu", (long long)h->seq)); + data = m; + msg = MPV_EVENT_CLIENT_MESSAGE; + } else { + mpv_event_hook *m = talloc_ptrtype(NULL, m); + *m = (mpv_event_hook){ + .name = talloc_strdup(m, h->type), + .id = h->seq, + }, + reply_id = h->user_id; + data = m; + msg = MPV_EVENT_HOOK; + } + int r = mp_client_send_event(mpctx, h->client, reply_id, msg, data); + if (r < 0) { + MP_WARN(mpctx, "Sending hook command failed. Removing hook.\n"); + hook_remove(mpctx, h); + mp_wakeup_core(mpctx); // repeat next iteration to finish + } return r; } -// client==NULL means start the hook chain -void mp_hook_run(struct MPContext *mpctx, char *client, char *type) +static int run_next_hook_handler(struct MPContext *mpctx, char *type, int index) +{ + struct command_ctx *cmd = mpctx->command_ctx; + + for (int n = index; n < cmd->num_hooks; n++) { + struct hook_handler *h = cmd->hooks[n]; + if (strcmp(h->type, type) == 0) + return invoke_hook_handler(mpctx, h); + } + + mp_wakeup_core(mpctx); // finished hook + return 0; +} + +void mp_hook_run(struct MPContext *mpctx, char *type) +{ + while (run_next_hook_handler(mpctx, type, 0) < 0) { + // We can repeat this until all broken clients have been removed, and + // hook processing is successfully started. + } +} + +int mp_hook_continue(struct MPContext *mpctx, char *client, uint64_t id) { struct command_ctx *cmd = mpctx->command_ctx; - bool found_current = !client; - int index = -1; + for (int n = 0; n < cmd->num_hooks; n++) { struct hook_handler *h = cmd->hooks[n]; - if (!found_current) { - if (h->active && strcmp(h->type, type) == 0) { - h->active = false; - found_current = true; - mp_wakeup_core(mpctx); - } - } else if (strcmp(h->type, type) == 0) { - index = n; - break; + if (strcmp(h->client, client) == 0 && h->seq == id) { + if (!h->active) + break; + h->active = false; + return run_next_hook_handler(mpctx, h->type, n + 1); } } - if (index < 0) - return; - struct hook_handler *next = cmd->hooks[index]; - MP_VERBOSE(mpctx, "Running hook: %s/%s\n", next->client, type); - next->active = true; - if (!send_hook_msg(mpctx, next, "hook_run")) { - hook_remove(mpctx, index); - mp_wakeup_core(mpctx); // repeat next iteration to finish - } + + MP_ERR(mpctx, "invalid hook API usage\n"); + return MPV_ERROR_INVALID_PARAMETER; } static int compare_hook(const void *pa, const void *pb) @@ -215,18 +254,22 @@ static int compare_hook(const void *pa, const void *pb) return (*h1)->seq - (*h2)->seq; } -static void mp_hook_add(struct MPContext *mpctx, char *client, char *name, - int id, int pri) +void mp_hook_add(struct MPContext *mpctx, const char *client, const char *name, + uint64_t user_id, int pri, bool legacy) { + if (legacy) + MP_WARN(mpctx, "The old hook API is deprecated! Use the libmpv API.\n"); + struct command_ctx *cmd = mpctx->command_ctx; struct hook_handler *h = talloc_ptrtype(cmd, h); - int64_t seq = cmd->hook_seq++; + int64_t seq = ++cmd->hook_seq; *h = (struct hook_handler){ .client = talloc_strdup(h, client), .type = talloc_strdup(h, name), - .user_id = talloc_asprintf(h, "%d", id), + .user_id = user_id, .priority = pri, .seq = seq, + .legacy = legacy, }; MP_TARRAY_APPEND(cmd, cmd->hooks, cmd->num_hooks, h); qsort(cmd->hooks, cmd->num_hooks, sizeof(cmd->hooks[0]), compare_hook); @@ -5409,7 +5452,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re 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, + 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", @@ -5459,14 +5502,14 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re return -1; } mp_hook_add(mpctx, cmd->sender, cmd->args[0].v.s, cmd->args[1].v.i, - cmd->args[2].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; } - mp_hook_run(mpctx, cmd->sender, cmd->args[0].v.s); + mp_hook_continue(mpctx, cmd->sender, cmd->args[0].v.i); break; case MP_CMD_MOUSE: { diff --git a/player/command.h b/player/command.h index 478bc8c737..2a102ddfbe 100644 --- a/player/command.h +++ b/player/command.h @@ -50,7 +50,7 @@ uint64_t mp_get_property_event_mask(const char *name); enum { // Must start with the first unused positive value in enum mpv_event_id // MPV_EVENT_* and MP_EVENT_* must not overlap. - INTERNAL_EVENT_BASE = 25, + INTERNAL_EVENT_BASE = 26, MP_EVENT_CHANGE_ALL, MP_EVENT_CACHE_UPDATE, MP_EVENT_WIN_RESIZE, @@ -61,7 +61,10 @@ enum { }; bool mp_hook_test_completion(struct MPContext *mpctx, char *type); -void mp_hook_run(struct MPContext *mpctx, char *client, char *type); +void mp_hook_run(struct MPContext *mpctx, char *type); +int mp_hook_continue(struct MPContext *mpctx, char *client, uint64_t id); +void mp_hook_add(struct MPContext *mpctx, const char *client, const char *name, + uint64_t user_id, int pri, bool legacy); void mark_seek(struct MPContext *mpctx); diff --git a/player/loadfile.c b/player/loadfile.c index 5c741369c0..32f41213fd 100644 --- a/player/loadfile.c +++ b/player/loadfile.c @@ -753,7 +753,7 @@ static void transfer_playlist(struct MPContext *mpctx, struct playlist *pl) static int process_open_hooks(struct MPContext *mpctx, char *name) { - mp_hook_run(mpctx, NULL, name); + mp_hook_run(mpctx, name); while (!mp_hook_test_completion(mpctx, name)) { mp_idle(mpctx); @@ -770,7 +770,7 @@ static int process_open_hooks(struct MPContext *mpctx, char *name) static int process_preloaded_hooks(struct MPContext *mpctx) { - mp_hook_run(mpctx, NULL, "on_preloaded"); + mp_hook_run(mpctx, "on_preloaded"); while (!mp_hook_test_completion(mpctx, "on_preloaded")) { mp_idle(mpctx); @@ -783,7 +783,7 @@ static int process_preloaded_hooks(struct MPContext *mpctx) static void process_unload_hooks(struct MPContext *mpctx) { - mp_hook_run(mpctx, NULL, "on_unload"); + mp_hook_run(mpctx, "on_unload"); while (!mp_hook_test_completion(mpctx, "on_unload")) mp_idle(mpctx); diff --git a/player/lua.c b/player/lua.c index 778830976c..98dcfee5b2 100644 --- a/player/lua.c +++ b/player/lua.c @@ -556,6 +556,12 @@ static int script_wait_event(lua_State *L) lua_setfield(L, -2, "data"); break; } + case MPV_EVENT_HOOK: { + mpv_event_hook *hook = event->data; + lua_pushinteger(L, hook->id); + lua_setfield(L, -2, "hook_id"); + break; + } default: ; } @@ -1046,6 +1052,22 @@ static int script_get_wakeup_pipe(lua_State *L) return 1; } +static int script_raw_hook_add(lua_State *L) +{ + struct script_ctx *ctx = get_ctx(L); + uint64_t ud = luaL_checkinteger(L, 1); + const char *name = luaL_checkstring(L, 2); + int pri = luaL_checkinteger(L, 3); + return check_error(L, mpv_hook_add(ctx->client, ud, name, pri)); +} + +static int script_raw_hook_continue(lua_State *L) +{ + struct script_ctx *ctx = get_ctx(L); + lua_Integer id = luaL_checkinteger(L, 1); + return check_error(L, mpv_hook_continue(ctx->client, id)); +} + static int script_readdir(lua_State *L) { // 0 1 2 3 @@ -1335,6 +1357,8 @@ static const struct fn_entry main_fns[] = { FN_ENTRY(format_time), FN_ENTRY(enable_messages), FN_ENTRY(get_wakeup_pipe), + FN_ENTRY(raw_hook_add), + FN_ENTRY(raw_hook_continue), {0} }; diff --git a/player/lua/defaults.lua b/player/lua/defaults.lua index 32dfed9948..d5bb194c50 100644 --- a/player/lua/defaults.lua +++ b/player/lua/defaults.lua @@ -511,24 +511,21 @@ function mp.osd_message(text, duration) end local hook_table = {} -local hook_registered = false -local function hook_run(id, cont) - local fn = hook_table[tonumber(id)] +mp.register_event("hook", function(ev) + local fn = hook_table[tonumber(ev.id)] if fn then fn() end - mp.commandv("hook-ack", cont) -end + mp.raw_hook_continue(ev.hook_id) +end) function mp.add_hook(name, pri, cb) - if not hook_registered then - mp.register_script_message("hook_run", hook_run) - hook_registered = true - end local id = #hook_table + 1 hook_table[id] = cb - mp.commandv("hook-add", name, id, pri) + -- The C API suggests using 0 for a neutral priority, but lua.rst suggests + -- 50 (?), so whatever. + mp.raw_hook_add(id, name, pri - 50) end local mp_utils = package.loaded["mp.utils"] diff --git a/player/scripting.c b/player/scripting.c index 9d2cd97de5..b98588d948 100644 --- a/player/scripting.c +++ b/player/scripting.c @@ -207,7 +207,8 @@ static void load_builtin_script(struct MPContext *mpctx, bool enable, // terminated, or re-enabling the script could be racy (because it'd // recognize a still-terminating script as "loaded"). while (mp_client_exists(mpctx, name)) { - if (mp_client_send_event(mpctx, name, MPV_EVENT_SHUTDOWN, NULL) < 0) + if (mp_client_send_event(mpctx, name, 0, MPV_EVENT_SHUTDOWN, + NULL) < 0) break; mp_idle(mpctx); } -- cgit v1.2.3