From ae5df9be98e4193342321f30285655fcf88e7e63 Mon Sep 17 00:00:00 2001 From: wm4 Date: Sun, 23 Nov 2014 15:08:49 +0100 Subject: input, lua: redo input handling Much of it is the same, but now there's the possibility to distinguish key down/up events in the Lua API. --- DOCS/client-api-changes.rst | 2 + DOCS/man/input.rst | 22 +++++++- DOCS/man/lua.rst | 25 ++++++--- input/cmd_list.c | 2 +- input/cmd_list.h | 2 +- input/input.c | 14 +++-- input/input.h | 9 ++- libmpv/client.h | 26 +++------ player/client.c | 24 ++++++++ player/client.h | 2 + player/command.c | 30 +++++++--- player/lua/defaults.lua | 132 ++++++++++++++++++++++++++++++++------------ 12 files changed, 211 insertions(+), 79 deletions(-) diff --git a/DOCS/client-api-changes.rst b/DOCS/client-api-changes.rst index 9490fa4b7a..177c70cb16 100644 --- a/DOCS/client-api-changes.rst +++ b/DOCS/client-api-changes.rst @@ -25,6 +25,8 @@ API changes :: + 1.10 - deprecate/disable everything directly related to script_dispatch + (most likely affects nobody) 1.9 - add enum mpv_end_file_reason for mpv_event_end_file.reason - add MPV_END_FILE_REASON_ERROR and the mpv_event_end_file.error field for slightly better error reporting on playback failure diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index 3e4fb300f8..a1cca52191 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -541,8 +541,26 @@ Input Commands that are Possibly Subject to Change ````. Each client (scripts etc.) has a unique name. For example, Lua scripts can get their name via ``mp.get_script_name()``. - (Scripts use this internally to dispatch key bindings, and this can also - be used in input.conf to reassign such bindings.) +``script_binding ""`` + Invoke a script-provided key binding. This can be used to remap key + bindings provided by external Lua scripts. + + The argument is the name of the binding. + + It can optionally be prefixed with the name of the script, using ``/`` as + separator, e.g. ``script_binding scriptname/bindingname``. + + For completeness, here is how this command works internally. The details + could change any time. On any matching key event, ``script_message_to`` + or ``script_message`` is called (depending on whether the script name is + included), where the first argument is the string ``key-binding``, the + second argument is the name of the binding, and the third argument is the + key state as string. The key state consists of a number of letters. The + first letter is one of ``d`` (key was pressed down), ``u`` (was released), + ``r`` (key is still down, and was repeated; only if key repeat is enabled + for this binding), ``p`` (key was pressed; happens if up/down can't be + tracked). The second letter whether the event originates from the mouse, + either ``m`` (mouse button) or ``-`` (something else). ``ab_loop`` Cycle through A-B loop states. The first command will set the ``A`` point diff --git a/DOCS/man/lua.rst b/DOCS/man/lua.rst index e09792a277..3f9e90e0b0 100644 --- a/DOCS/man/lua.rst +++ b/DOCS/man/lua.rst @@ -198,12 +198,23 @@ The ``mp`` module is preloaded, although it can be loaded manually with overwritten. You can omit the name, in which case a random name is generated internally. - The last argument is used for additional flags. Currently, this includes - the string ``repeatable``, which enables key repeat for this specific - binding. + The last argument is used for optional flags. This is a table, which can + have the following entries: - Internally, key bindings are dispatched via the ``script_message_to`` input - command and ``mp.register_script_message``. + ``repeatable`` + If set to ``true``, enables key repeat for this specific binding. + + ``complex`` + If set to ``true``, then ``fn`` is called on both key up and down + events (as well as key repeat, if enabled), with the first + argument being a table. This table has an ``event`` entry, which + is set to one of the strings ``down``, ``repeat``, ``up`` or + ``press`` (the latter if key up/down can't be tracked). It further + has an ``is_mouse`` entry, which tells whether the event was caused + by a mouse button. + + Internally, key bindings are dispatched via the ``script_message_to`` or + ``script_binding`` input commands and ``mp.register_script_message``. Trying to map multiple commands to a key will essentially prefer a random binding, while the other bindings are not called. It is guaranteed that @@ -226,7 +237,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with :: - y script_message something + y script_binding something This will print the message when the key ``y`` is pressed. (``x`` will @@ -237,7 +248,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with :: - y script_message_to fooscript something + y script_binding fooscript.something ``mp.add_forced_key_binding(...)`` This works almost the same as ``mp.add_key_binding``, but registers the diff --git a/input/cmd_list.c b/input/cmd_list.c index e0e307b4c4..1edc28d521 100644 --- a/input/cmd_list.c +++ b/input/cmd_list.c @@ -166,7 +166,7 @@ const struct mp_cmd_def mp_cmds[] = { { MP_CMD_VO_CMDLINE, "vo_cmdline", { ARG_STRING } }, - { MP_CMD_SCRIPT_DISPATCH, "script_dispatch", { ARG_STRING, ARG_INT } }, + { MP_CMD_SCRIPT_BINDING, "script_binding", { ARG_STRING } }, { MP_CMD_SCRIPT_MESSAGE, "script_message", { ARG_STRING }, .vararg = true }, { MP_CMD_SCRIPT_MESSAGE_TO, "script_message_to", { ARG_STRING, ARG_STRING }, .vararg = true }, diff --git a/input/cmd_list.h b/input/cmd_list.h index 638fc5bb68..6705d3f803 100644 --- a/input/cmd_list.h +++ b/input/cmd_list.h @@ -93,7 +93,7 @@ enum mp_command_type { MP_CMD_VO_CMDLINE, /// Internal for Lua scripts - MP_CMD_SCRIPT_DISPATCH, + MP_CMD_SCRIPT_BINDING, MP_CMD_SCRIPT_MESSAGE, MP_CMD_SCRIPT_MESSAGE_TO, diff --git a/input/input.c b/input/input.c index 4133396399..7c647adef3 100644 --- a/input/input.c +++ b/input/input.c @@ -476,6 +476,7 @@ static mp_cmd_t *get_cmd_from_keys(struct input_ctx *ictx, char *force_section, keyname, cmd->cmd, ret->input_section); talloc_free(keyname); } + ret->is_mouse_button = code & MP_KEY_EMIT_ON_UP; } else { char *key_buf = mp_input_get_key_combo_name(&code, 1); MP_ERR(ictx, "Invalid command for bound key '%s': '%s'\n", @@ -518,6 +519,7 @@ static void release_down_cmd(struct input_ctx *ictx, bool drop_current) { memset(ictx->key_history, 0, sizeof(ictx->key_history)); ictx->current_down_cmd->key_up_follows = false; + ictx->current_down_cmd->is_up = true; mp_input_queue_cmd(ictx, ictx->current_down_cmd); } else { talloc_free(ictx->current_down_cmd); @@ -534,7 +536,7 @@ static void release_down_cmd(struct input_ctx *ictx, bool drop_current) static bool key_updown_ok(enum mp_command_type cmd) { switch (cmd) { - case MP_CMD_SCRIPT_DISPATCH: + case MP_CMD_SCRIPT_BINDING: return true; default: return false; @@ -589,8 +591,11 @@ static void interpret_key(struct input_ctx *ictx, int code, double scale) // Cancel current down-event (there can be only one) release_down_cmd(ictx, true); cmd = resolve_key(ictx, code); - if (cmd && (code & MP_KEY_EMIT_ON_UP)) - cmd->key_up_follows = true; + if (cmd) { + cmd->is_up_down = true; + cmd->key_up_follows = (code & MP_KEY_EMIT_ON_UP) | + key_updown_ok(cmd->id); + } ictx->last_key_down = code; ictx->last_key_down_time = mp_time_us(); ictx->ar_state = 0; @@ -885,8 +890,7 @@ mp_cmd_t *mp_input_read_cmd(struct input_ctx *ictx) struct mp_cmd *ret = queue_remove_head(&ictx->cmd_queue); if (!ret) { ret = check_autorepeat(ictx); - // (if explicitly repeated, don't let command.c ignore it) - if (ret && !(ret->flags & MP_ALLOW_REPEAT)) + if (ret) ret->repeated = true; } if (ret && ret->mouse_move) { diff --git a/input/input.h b/input/input.h index 6da1f719c2..02e9d2f86b 100644 --- a/input/input.h +++ b/input/input.h @@ -76,9 +76,12 @@ typedef struct mp_cmd { int flags; // mp_cmd_flags bitfield bstr original; char *input_section; - bool key_up_follows; - bool repeated; - bool mouse_move; + bool is_up_down : 1; + bool is_up : 1; + bool is_mouse_button : 1; + bool key_up_follows : 1; + bool repeated : 1; + bool mouse_move : 1; int mouse_x, mouse_y; struct mp_cmd *queue_next; double scale; // for scaling numeric arguments diff --git a/libmpv/client.h b/libmpv/client.h index 4ad0a5e65a..11b27b26df 100644 --- a/libmpv/client.h +++ b/libmpv/client.h @@ -164,7 +164,7 @@ extern "C" { * relational operators (<, >, <=, >=). */ #define MPV_MAKE_VERSION(major, minor) (((major) << 16) | (minor) | 0UL) -#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 9) +#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 10) /** * Return the MPV_CLIENT_API_VERSION the mpv source has been compiled with. @@ -1010,12 +1010,12 @@ typedef enum mpv_event_id { */ MPV_EVENT_TICK = 14, /** - * Triggered by the script_dispatch input command. The command uses the - * client name (see mpv_client_name()) to dispatch keyboard or mouse input - * to a client. - * (This is pretty obscure and largely replaced by MPV_EVENT_CLIENT_MESSAGE, - * but still the only way to distinguish key down/up events when binding - * script_dispatch via input.conf.) + * @deprecated This was used internally with the internal "script_dispatch" + * command to dispatch keyboard and mouse input for the OSC. + * It was never useful in general and has been completely + * replaced with "script_binding". + * This event never happens anymore, and is included in this + * header only for compatibility. */ MPV_EVENT_SCRIPT_INPUT_DISPATCH = 15, /** @@ -1211,17 +1211,10 @@ typedef struct mpv_event_end_file { int error; } mpv_event_end_file; +/** @deprecated see MPV_EVENT_SCRIPT_INPUT_DISPATCH for remarks + */ typedef struct mpv_event_script_input_dispatch { - /** - * Arbitrary integer value that was provided as argument to the - * script_dispatch input command. - */ int arg0; - /** - * Type of the input. Currently either "keyup_follows" (basically a key - * down event), or "press" (either a single key event, or a key up event - * following a "keyup_follows" event). - */ const char *type; } mpv_event_script_input_dispatch; @@ -1269,7 +1262,6 @@ typedef struct mpv_event { * MPV_EVENT_GET_PROPERTY_REPLY: mpv_event_property* * MPV_EVENT_PROPERTY_CHANGE: mpv_event_property* * MPV_EVENT_LOG_MESSAGE: mpv_event_log_message* - * MPV_EVENT_SCRIPT_INPUT_DISPATCH: mpv_event_script_input_dispatch* * MPV_EVENT_CLIENT_MESSAGE: mpv_event_client_message* * MPV_EVENT_END_FILE: mpv_event_end_file* * other: NULL diff --git a/player/client.c b/player/client.c index 97bb215464..d01c611a3d 100644 --- a/player/client.c +++ b/player/client.c @@ -589,9 +589,16 @@ void mp_client_broadcast_event(struct MPContext *mpctx, int event, void *data) pthread_mutex_unlock(&clients->lock); } +// 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) { + if (!client_name) { + mp_client_broadcast_event(mpctx, event, data); + talloc_free(data); + return 0; + } + struct mp_client_api *clients = mpctx->clients; int r = 0; @@ -615,6 +622,23 @@ int mp_client_send_event(struct MPContext *mpctx, const char *client_name, return r; } +int mp_client_send_event_dup(struct MPContext *mpctx, const char *client_name, + int event, void *data) +{ + if (!client_name) { + mp_client_broadcast_event(mpctx, event, data); + return 0; + } + + struct mpv_event event_data = { + .event_id = event, + .data = data, + }; + + dup_event_data(&event_data); + return mp_client_send_event(mpctx, client_name, event, event_data.data); +} + int mpv_request_event(mpv_handle *ctx, mpv_event_id event, int enable) { if (!mpv_event_name(event) || enable < 0 || enable > 1) diff --git a/player/client.h b/player/client.h index bdecae20ae..a275bb9728 100644 --- a/player/client.h +++ b/player/client.h @@ -23,6 +23,8 @@ 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); +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); void mp_client_property_change(struct MPContext *mpctx, const char *name); diff --git a/player/command.c b/player/command.c index 30768e9b71..0042d8dee7 100644 --- a/player/command.c +++ b/player/command.c @@ -4471,17 +4471,29 @@ int run_command(MPContext *mpctx, mp_cmd_t *cmd) return edit_filters_osd(mpctx, STREAM_VIDEO, cmd->args[0].v.s, cmd->args[1].v.s, msg_osd); - case MP_CMD_SCRIPT_DISPATCH: { - mpv_event_script_input_dispatch *event = talloc_ptrtype(NULL, event); - *event = (mpv_event_script_input_dispatch){ - .arg0 = cmd->args[1].v.i, - .type = cmd->key_up_follows ? "keyup_follows" : "press", - }; - if (mp_client_send_event(mpctx, cmd->args[0].v.s, - MPV_EVENT_SCRIPT_INPUT_DISPATCH, event) < 0) + 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 (name) { + 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 = 3; + event.args = (const char*[3]){"key-binding", name, state}; + 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", - cmd->args[0].v.s); + target ? target : "-"); return -1; } break; diff --git a/player/lua/defaults.lua b/player/lua/defaults.lua index 4b3be12ac9..812bb71afe 100644 --- a/player/lua/defaults.lua +++ b/player/lua/defaults.lua @@ -22,7 +22,27 @@ function mp.get_opt(key, def) return val end -local callbacks = {} +-- For dispatching script_binding. This is sent as: +-- script_message_to $script_name $binding_name $keystate +-- The array is indexed by $binding_name, and has functions like this as value: +-- fn($binding_name, $keystate) +local dispatch_key_bindings = {} + +local message_id = 0 +local function reserve_binding() + message_id = message_id + 1 + return "__keybinding" .. tostring(message_id) +end + +local function dispatch_key_binding(name, state) + local fn = dispatch_key_bindings[name] + if fn then + fn(name, state) + end +end + +-- "Old", deprecated API + -- each script has its own section, so that they don't conflict local default_section = "input_dispatch_" .. mp.script_name @@ -34,13 +54,8 @@ local default_section = "input_dispatch_" .. mp.script_name -- Note: the bindings are not active by default. Use enable_key_bindings(). -- -- list is an array of key bindings, where each entry is an array as follow: --- {key, callback} --- {key, callback, callback_down} +-- {key, callback_press, callback_down, callback_up} -- key is the key string as used in input.conf, like "ctrl+a" --- callback is a Lua function that is called when the key binding is used. --- callback_down can be given too, and is called when a mouse button is pressed --- if the key is a mouse button. (The normal callback will be for mouse button --- down.) -- -- callback can be a string too, in which case the following will be added like -- an input.conf line: key .. " " .. callback @@ -52,10 +67,29 @@ function mp.set_key_bindings(list, section, flags) local key = entry[1] local cb = entry[2] local cb_down = entry[3] - if type(cb) == "function" then - callbacks[#callbacks + 1] = {press=cb, before_press=cb_down} - cfg = cfg .. key .. " script_dispatch " .. mp.script_name - .. " " .. #callbacks .. "\n" + local cb_up = entry[4] + if type(cb) ~= "string" then + local mangle = reserve_binding() + dispatch_key_bindings[mangle] = function(name, state) + local event = state:sub(1, 1) + local is_mouse = state:sub(2, 2) == "m" + local def = (is_mouse and "u") or "d" + if event == "r" then + event = "d" + end + if event == "p" and cb then + cb() + elseif event == "d" and cb_down then + cb_down() + elseif event == "u" and cb_up then + cb_up() + elseif event == def and cb then + print("whooo") + cb() + end + end + cfg = cfg .. key .. " script_binding " .. + mp.script_name .. "/" .. mangle .. "\n" else cfg = cfg .. key .. " " .. cb .. "\n" end @@ -75,21 +109,9 @@ function mp.set_mouse_area(x0, y0, x1, y1, section) mp.input_set_section_mouse_area(section or default_section, x0, y0, x1, y1) end -local function script_dispatch(event) - local cb = callbacks[event.arg0] - if cb then - if event.type == "press" and cb.press then - cb.press() - elseif event.type == "keyup_follows" and cb.before_press then - cb.before_press() - end - end -end - -- "Newer" and more convenient API local key_bindings = {} -local message_id = 1 local function update_key_bindings() for i = 1, 2 do @@ -105,9 +127,7 @@ local function update_key_bindings() local cfg = "" for k, v in pairs(key_bindings) do if v.forced ~= def then - local flags = (v.repeatable and " repeatable") or "" - cfg = cfg .. v.key .. " " .. flags .. " script_message_to " - .. mp.script_name .. " " .. v.name .. "\n" + cfg = cfg .. v.bind .. "\n" end end mp.input_define_section(section, cfg, flags) @@ -117,19 +137,61 @@ local function update_key_bindings() end local function add_binding(attrs, key, name, fn, rp) + rp = rp or "" if (type(name) ~= "string") and (not fn) then fn = name - name = "message" .. tostring(message_id) - message_id = message_id + 1 + name = reserve_binding() + end + local bind = key + if rp == "repeatable" or rp["repeatable"] then + bind = bind .. " repeatable" + end + if rp["forced"] then + attrs.forced = true + end + local key_cb, msg_cb + if not fn then + fn = function() end + end + if rp["complex"] then + local key_states = { + ["u"] = "up", + ["d"] = "down", + ["r"] = "repeat", + ["p"] = "press", + } + key_cb = function(name, state) + fn({ + event = key_states[state:sub(1, 1)] or "unknown", + is_mouse = state:sub(2, 2) == "m" + }) + end + msg_cb = function() + fn({event = "press", is_mouse = false}) + end + else + key_cb = function(name, state) + -- Emulate the same semantics as input.c uses for most bindings: + -- For keyboard, "down" runs the command, "up" does nothing; + -- for mouse, "down" does nothing, "up" runs the command. + -- Also, key repeat triggers the binding again. + local event = state:sub(1, 1) + local is_mouse = state:sub(2, 2) == "m" + if is_mouse and event == "u" then + fn() + elseif (not is_mouse) and (event == "d" or event == "r") then + fn() + end + end + msg_cb = fn end - attrs.repeatable = rp == "repeatable" - attrs.key = key + attrs.bind = bind .. " script_binding " .. mp.script_name .. "/" .. name attrs.name = name key_bindings[name] = attrs update_key_bindings() - if fn then - mp.register_script_message(name, fn) - end + dispatch_key_bindings[name] = key_cb + mp.unregister_script_message(name) + mp.register_script_message(name, msg_cb) end function mp.add_key_binding(...) @@ -322,10 +384,12 @@ end -- default handlers mp.register_event("shutdown", function() mp.keep_running = false end) -mp.register_event("script-input-dispatch", script_dispatch) mp.register_event("client-message", message_dispatch) mp.register_event("property-change", property_change) +-- sent by "script_binding" +mp.register_script_message("key-binding", dispatch_key_binding) + mp.msg = { log = mp.log, fatal = function(...) return mp.log("fatal", ...) end, -- cgit v1.2.3