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.
This commit is contained in:
wm4 2014-11-23 15:08:49 +01:00
parent 7b47f12f8f
commit ae5df9be98
12 changed files with 211 additions and 79 deletions

View File

@ -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 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 - add MPV_END_FILE_REASON_ERROR and the mpv_event_end_file.error field
for slightly better error reporting on playback failure for slightly better error reporting on playback failure

View File

@ -541,8 +541,26 @@ Input Commands that are Possibly Subject to Change
``<target>``. Each client (scripts etc.) has a unique name. For example, ``<target>``. Each client (scripts etc.) has a unique name. For example,
Lua scripts can get their name via ``mp.get_script_name()``. Lua scripts can get their name via ``mp.get_script_name()``.
(Scripts use this internally to dispatch key bindings, and this can also ``script_binding "<name>"``
be used in input.conf to reassign such bindings.) 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`` ``ab_loop``
Cycle through A-B loop states. The first command will set the ``A`` point Cycle through A-B loop states. The first command will set the ``A`` point

View File

@ -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 overwritten. You can omit the name, in which case a random name is generated
internally. internally.
The last argument is used for additional flags. Currently, this includes The last argument is used for optional flags. This is a table, which can
the string ``repeatable``, which enables key repeat for this specific have the following entries:
binding.
Internally, key bindings are dispatched via the ``script_message_to`` input ``repeatable``
command and ``mp.register_script_message``. 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 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 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 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(...)`` ``mp.add_forced_key_binding(...)``
This works almost the same as ``mp.add_key_binding``, but registers the This works almost the same as ``mp.add_key_binding``, but registers the

View File

@ -166,7 +166,7 @@ const struct mp_cmd_def mp_cmds[] = {
{ MP_CMD_VO_CMDLINE, "vo_cmdline", { ARG_STRING } }, { 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, "script_message", { ARG_STRING }, .vararg = true },
{ MP_CMD_SCRIPT_MESSAGE_TO, "script_message_to", { ARG_STRING, ARG_STRING }, { MP_CMD_SCRIPT_MESSAGE_TO, "script_message_to", { ARG_STRING, ARG_STRING },
.vararg = true }, .vararg = true },

View File

@ -93,7 +93,7 @@ enum mp_command_type {
MP_CMD_VO_CMDLINE, MP_CMD_VO_CMDLINE,
/// Internal for Lua scripts /// Internal for Lua scripts
MP_CMD_SCRIPT_DISPATCH, MP_CMD_SCRIPT_BINDING,
MP_CMD_SCRIPT_MESSAGE, MP_CMD_SCRIPT_MESSAGE,
MP_CMD_SCRIPT_MESSAGE_TO, MP_CMD_SCRIPT_MESSAGE_TO,

View File

@ -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); keyname, cmd->cmd, ret->input_section);
talloc_free(keyname); talloc_free(keyname);
} }
ret->is_mouse_button = code & MP_KEY_EMIT_ON_UP;
} else { } else {
char *key_buf = mp_input_get_key_combo_name(&code, 1); char *key_buf = mp_input_get_key_combo_name(&code, 1);
MP_ERR(ictx, "Invalid command for bound key '%s': '%s'\n", 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)); memset(ictx->key_history, 0, sizeof(ictx->key_history));
ictx->current_down_cmd->key_up_follows = false; ictx->current_down_cmd->key_up_follows = false;
ictx->current_down_cmd->is_up = true;
mp_input_queue_cmd(ictx, ictx->current_down_cmd); mp_input_queue_cmd(ictx, ictx->current_down_cmd);
} else { } else {
talloc_free(ictx->current_down_cmd); 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) static bool key_updown_ok(enum mp_command_type cmd)
{ {
switch (cmd) { switch (cmd) {
case MP_CMD_SCRIPT_DISPATCH: case MP_CMD_SCRIPT_BINDING:
return true; return true;
default: default:
return false; 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) // Cancel current down-event (there can be only one)
release_down_cmd(ictx, true); release_down_cmd(ictx, true);
cmd = resolve_key(ictx, code); cmd = resolve_key(ictx, code);
if (cmd && (code & MP_KEY_EMIT_ON_UP)) if (cmd) {
cmd->key_up_follows = true; 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 = code;
ictx->last_key_down_time = mp_time_us(); ictx->last_key_down_time = mp_time_us();
ictx->ar_state = 0; 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); struct mp_cmd *ret = queue_remove_head(&ictx->cmd_queue);
if (!ret) { if (!ret) {
ret = check_autorepeat(ictx); ret = check_autorepeat(ictx);
// (if explicitly repeated, don't let command.c ignore it) if (ret)
if (ret && !(ret->flags & MP_ALLOW_REPEAT))
ret->repeated = true; ret->repeated = true;
} }
if (ret && ret->mouse_move) { if (ret && ret->mouse_move) {

View File

@ -76,9 +76,12 @@ typedef struct mp_cmd {
int flags; // mp_cmd_flags bitfield int flags; // mp_cmd_flags bitfield
bstr original; bstr original;
char *input_section; char *input_section;
bool key_up_follows; bool is_up_down : 1;
bool repeated; bool is_up : 1;
bool mouse_move; bool is_mouse_button : 1;
bool key_up_follows : 1;
bool repeated : 1;
bool mouse_move : 1;
int mouse_x, mouse_y; int mouse_x, mouse_y;
struct mp_cmd *queue_next; struct mp_cmd *queue_next;
double scale; // for scaling numeric arguments double scale; // for scaling numeric arguments

View File

@ -164,7 +164,7 @@ extern "C" {
* relational operators (<, >, <=, >=). * relational operators (<, >, <=, >=).
*/ */
#define MPV_MAKE_VERSION(major, minor) (((major) << 16) | (minor) | 0UL) #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. * 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, MPV_EVENT_TICK = 14,
/** /**
* Triggered by the script_dispatch input command. The command uses the * @deprecated This was used internally with the internal "script_dispatch"
* client name (see mpv_client_name()) to dispatch keyboard or mouse input * command to dispatch keyboard and mouse input for the OSC.
* to a client. * It was never useful in general and has been completely
* (This is pretty obscure and largely replaced by MPV_EVENT_CLIENT_MESSAGE, * replaced with "script_binding".
* but still the only way to distinguish key down/up events when binding * This event never happens anymore, and is included in this
* script_dispatch via input.conf.) * header only for compatibility.
*/ */
MPV_EVENT_SCRIPT_INPUT_DISPATCH = 15, MPV_EVENT_SCRIPT_INPUT_DISPATCH = 15,
/** /**
@ -1211,17 +1211,10 @@ typedef struct mpv_event_end_file {
int error; int error;
} mpv_event_end_file; } mpv_event_end_file;
/** @deprecated see MPV_EVENT_SCRIPT_INPUT_DISPATCH for remarks
*/
typedef struct mpv_event_script_input_dispatch { typedef struct mpv_event_script_input_dispatch {
/**
* Arbitrary integer value that was provided as argument to the
* script_dispatch input command.
*/
int arg0; 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; const char *type;
} mpv_event_script_input_dispatch; } mpv_event_script_input_dispatch;
@ -1269,7 +1262,6 @@ typedef struct mpv_event {
* MPV_EVENT_GET_PROPERTY_REPLY: mpv_event_property* * MPV_EVENT_GET_PROPERTY_REPLY: mpv_event_property*
* MPV_EVENT_PROPERTY_CHANGE: mpv_event_property* * MPV_EVENT_PROPERTY_CHANGE: mpv_event_property*
* MPV_EVENT_LOG_MESSAGE: mpv_event_log_message* * 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_CLIENT_MESSAGE: mpv_event_client_message*
* MPV_EVENT_END_FILE: mpv_event_end_file* * MPV_EVENT_END_FILE: mpv_event_end_file*
* other: NULL * other: NULL

View File

@ -589,9 +589,16 @@ void mp_client_broadcast_event(struct MPContext *mpctx, int event, void *data)
pthread_mutex_unlock(&clients->lock); 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 mp_client_send_event(struct MPContext *mpctx, const char *client_name,
int event, void *data) 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; struct mp_client_api *clients = mpctx->clients;
int r = 0; int r = 0;
@ -615,6 +622,23 @@ int mp_client_send_event(struct MPContext *mpctx, const char *client_name,
return r; 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) int mpv_request_event(mpv_handle *ctx, mpv_event_id event, int enable)
{ {
if (!mpv_event_name(event) || enable < 0 || enable > 1) if (!mpv_event_name(event) || enable < 0 || enable > 1)

View File

@ -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); 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 mp_client_send_event(struct MPContext *mpctx, const char *client_name,
int event, void *data); 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); bool mp_client_event_is_registered(struct MPContext *mpctx, int event);
void mp_client_property_change(struct MPContext *mpctx, const char *name); void mp_client_property_change(struct MPContext *mpctx, const char *name);

View File

@ -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, return edit_filters_osd(mpctx, STREAM_VIDEO, cmd->args[0].v.s,
cmd->args[1].v.s, msg_osd); cmd->args[1].v.s, msg_osd);
case MP_CMD_SCRIPT_DISPATCH: { case MP_CMD_SCRIPT_BINDING: {
mpv_event_script_input_dispatch *event = talloc_ptrtype(NULL, event); mpv_event_client_message event = {0};
*event = (mpv_event_script_input_dispatch){ char *name = cmd->args[0].v.s;
.arg0 = cmd->args[1].v.i, if (!name || !name[0])
.type = cmd->key_up_follows ? "keyup_follows" : "press", return -1;
}; char *sep = strchr(name, '/');
if (mp_client_send_event(mpctx, cmd->args[0].v.s, char *target = NULL;
MPV_EVENT_SCRIPT_INPUT_DISPATCH, event) < 0) 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", MP_VERBOSE(mpctx, "Can't find script '%s' when handling input.\n",
cmd->args[0].v.s); target ? target : "-");
return -1; return -1;
} }
break; break;

View File

@ -22,7 +22,27 @@ function mp.get_opt(key, def)
return val return val
end 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 -- each script has its own section, so that they don't conflict
local default_section = "input_dispatch_" .. mp.script_name 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(). -- 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: -- list is an array of key bindings, where each entry is an array as follow:
-- {key, callback} -- {key, callback_press, callback_down, callback_up}
-- {key, callback, callback_down}
-- key is the key string as used in input.conf, like "ctrl+a" -- 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 -- callback can be a string too, in which case the following will be added like
-- an input.conf line: key .. " " .. callback -- an input.conf line: key .. " " .. callback
@ -52,10 +67,29 @@ function mp.set_key_bindings(list, section, flags)
local key = entry[1] local key = entry[1]
local cb = entry[2] local cb = entry[2]
local cb_down = entry[3] local cb_down = entry[3]
if type(cb) == "function" then local cb_up = entry[4]
callbacks[#callbacks + 1] = {press=cb, before_press=cb_down} if type(cb) ~= "string" then
cfg = cfg .. key .. " script_dispatch " .. mp.script_name local mangle = reserve_binding()
.. " " .. #callbacks .. "\n" 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 else
cfg = cfg .. key .. " " .. cb .. "\n" cfg = cfg .. key .. " " .. cb .. "\n"
end 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) mp.input_set_section_mouse_area(section or default_section, x0, y0, x1, y1)
end 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 -- "Newer" and more convenient API
local key_bindings = {} local key_bindings = {}
local message_id = 1
local function update_key_bindings() local function update_key_bindings()
for i = 1, 2 do for i = 1, 2 do
@ -105,9 +127,7 @@ local function update_key_bindings()
local cfg = "" local cfg = ""
for k, v in pairs(key_bindings) do for k, v in pairs(key_bindings) do
if v.forced ~= def then if v.forced ~= def then
local flags = (v.repeatable and " repeatable") or "" cfg = cfg .. v.bind .. "\n"
cfg = cfg .. v.key .. " " .. flags .. " script_message_to "
.. mp.script_name .. " " .. v.name .. "\n"
end end
end end
mp.input_define_section(section, cfg, flags) mp.input_define_section(section, cfg, flags)
@ -117,19 +137,61 @@ local function update_key_bindings()
end end
local function add_binding(attrs, key, name, fn, rp) local function add_binding(attrs, key, name, fn, rp)
rp = rp or ""
if (type(name) ~= "string") and (not fn) then if (type(name) ~= "string") and (not fn) then
fn = name fn = name
name = "message" .. tostring(message_id) name = reserve_binding()
message_id = message_id + 1
end end
attrs.repeatable = rp == "repeatable" local bind = key
attrs.key = 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.bind = bind .. " script_binding " .. mp.script_name .. "/" .. name
attrs.name = name attrs.name = name
key_bindings[name] = attrs key_bindings[name] = attrs
update_key_bindings() update_key_bindings()
if fn then dispatch_key_bindings[name] = key_cb
mp.register_script_message(name, fn) mp.unregister_script_message(name)
end mp.register_script_message(name, msg_cb)
end end
function mp.add_key_binding(...) function mp.add_key_binding(...)
@ -322,10 +384,12 @@ end
-- default handlers -- default handlers
mp.register_event("shutdown", function() mp.keep_running = false end) 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("client-message", message_dispatch)
mp.register_event("property-change", property_change) mp.register_event("property-change", property_change)
-- sent by "script_binding"
mp.register_script_message("key-binding", dispatch_key_binding)
mp.msg = { mp.msg = {
log = mp.log, log = mp.log,
fatal = function(...) return mp.log("fatal", ...) end, fatal = function(...) return mp.log("fatal", ...) end,