mirror of
https://github.com/mpv-player/mpv
synced 2025-02-24 17:06:58 +00:00
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:
parent
7b47f12f8f
commit
ae5df9be98
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 },
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user