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
- add MPV_END_FILE_REASON_ERROR and the mpv_event_end_file.error field
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,
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 "<name>"``
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

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
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

View File

@ -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 },

View File

@ -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,

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);
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) {

View File

@ -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

View File

@ -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

View File

@ -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)

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);
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);

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,
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;

View File

@ -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
attrs.repeatable = rp == "repeatable"
attrs.key = key
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.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,