diff options
Diffstat (limited to 'mpvcore')
-rw-r--r-- | mpvcore/command.c | 72 | ||||
-rw-r--r-- | mpvcore/command.h | 18 | ||||
-rw-r--r-- | mpvcore/input/input.c | 5 | ||||
-rw-r--r-- | mpvcore/input/input.h | 3 | ||||
-rw-r--r-- | mpvcore/lua/assdraw.lua | 98 | ||||
-rw-r--r-- | mpvcore/lua/defaults.lua | 82 | ||||
-rw-r--r-- | mpvcore/mp_core.h | 7 | ||||
-rw-r--r-- | mpvcore/mp_lua.c | 678 | ||||
-rw-r--r-- | mpvcore/mp_lua.h | 14 | ||||
-rw-r--r-- | mpvcore/mplayer.c | 38 | ||||
-rw-r--r-- | mpvcore/options.c | 4 | ||||
-rw-r--r-- | mpvcore/options.h | 1 |
12 files changed, 1017 insertions, 3 deletions
diff --git a/mpvcore/command.c b/mpvcore/command.c index b30a5299e0..35e72cb3ad 100644 --- a/mpvcore/command.c +++ b/mpvcore/command.c @@ -68,6 +68,8 @@ #include "mpvcore/mp_core.h" +#include "mp_lua.h" + static int edit_filters(struct MPContext *mpctx, enum stream_type mediatype, const char *cmd, const char *arg); static int set_filters(struct MPContext *mpctx, enum stream_type mediatype, @@ -1913,6 +1915,11 @@ static const m_option_t mp_properties[] = { {0}, }; +const struct m_option *mp_get_property_list(void) +{ + return mp_properties; +} + int mp_property_do(const char *name, int action, void *val, struct MPContext *ctx) { @@ -2729,6 +2736,15 @@ void run_command(MPContext *mpctx, mp_cmd_t *cmd) cmd->args[1].v.s, msg_osd); break; + case MP_CMD_SCRIPT_DISPATCH: + if (mpctx->lua_ctx) { +#ifdef CONFIG_LUA + mp_lua_script_dispatch(mpctx, cmd->args[0].v.s, cmd->args[1].v.i, + cmd->key_up_follows ? "keyup_follows" : "press"); +#endif + } + break; + case MP_CMD_COMMAND_LIST: { for (struct mp_cmd *sub = cmd->args[0].v.p; sub; sub = sub->queue_next) run_command(mpctx, sub); @@ -2755,3 +2771,59 @@ void run_command(MPContext *mpctx, mp_cmd_t *cmd) break; } } + +struct command_ctx { + int events; +}; + +void command_init(struct MPContext *mpctx) +{ + mpctx->command_ctx = talloc_zero(mpctx, struct command_ctx); +} + +// Notify that a property might have changed. +void mp_notify_property(struct MPContext *mpctx, const char *property) +{ + mp_notify(mpctx, MP_EVENT_PROPERTY, (void *)property); +} + +void mp_notify(struct MPContext *mpctx, enum mp_event event, void *arg) +{ + struct command_ctx *ctx = mpctx->command_ctx; + ctx->events |= 1u << event; +} + +static void handle_script_event(struct MPContext *mpctx, const char *name, + const char *arg) +{ +#ifdef CONFIG_LUA + mp_lua_event(mpctx, name, arg); +#endif +} + +void mp_flush_events(struct MPContext *mpctx) +{ + struct command_ctx *ctx = mpctx->command_ctx; + + ctx->events |= (1u << MP_EVENT_TICK); + + for (int n = 0; n < 16; n++) { + enum mp_event event = n; + unsigned mask = 1 << event; + if (ctx->events & mask) { + // The event handler could set event flags again; in this case let + // the next mp_flush_events() call handle it to avoid infinite loops. + ctx->events &= ~mask; + const char *name = NULL; + switch (event) { + case MP_EVENT_TICK: name = "tick"; break; + case MP_EVENT_TRACKS_CHANGED: name = "track-layout"; break; + case MP_EVENT_START_FILE: name = "start"; break; + case MP_EVENT_END_FILE: name = "end"; break; + default: ; + } + if (name) + handle_script_event(mpctx, name, ""); + } + } +} diff --git a/mpvcore/command.h b/mpvcore/command.h index 5ac8c3a8f0..1ac5b6f8f0 100644 --- a/mpvcore/command.h +++ b/mpvcore/command.h @@ -22,6 +22,8 @@ struct MPContext; struct mp_cmd; +void command_init(struct MPContext *mpctx); + void mp_get_osd_mouse_pos(struct MPContext *mpctx, float *x, float *y); void run_command(struct MPContext *mpctx, struct mp_cmd *cmd); @@ -30,4 +32,20 @@ void property_print_help(void); int mp_property_do(const char* name, int action, void* val, struct MPContext *mpctx); +const struct m_option *mp_get_property_list(void); + +enum mp_event { + MP_EVENT_NONE, + MP_EVENT_TICK, + MP_EVENT_PROPERTY, // char*, property that is changed + MP_EVENT_TRACKS_CHANGED, + MP_EVENT_START_FILE, + MP_EVENT_END_FILE, +}; + +void mp_notify(struct MPContext *mpctx, enum mp_event event, void *arg); +void mp_notify_property(struct MPContext *mpctx, const char *property); + +void mp_flush_events(struct MPContext *mpctx); + #endif /* MPLAYER_COMMAND_H */ diff --git a/mpvcore/input/input.c b/mpvcore/input/input.c index 902aa52b47..f624b8f5dd 100644 --- a/mpvcore/input/input.c +++ b/mpvcore/input/input.c @@ -229,6 +229,8 @@ static const mp_cmd_t mp_cmds[] = { { MP_CMD_VO_CMDLINE, "vo_cmdline", { ARG_STRING } }, + { MP_CMD_SCRIPT_DISPATCH, "script_dispatch", { ARG_STRING, ARG_INT } }, + {0} }; @@ -528,6 +530,7 @@ struct input_ctx { // Autorepeat stuff short ar_state; int64_t last_ar; + // Autorepeat config unsigned int ar_delay; unsigned int ar_rate; @@ -1455,6 +1458,8 @@ static void remove_key_down(struct input_ctx *ictx, int code) static bool key_updown_ok(enum mp_command_type cmd) { switch (cmd) { + case MP_CMD_SCRIPT_DISPATCH: + return true; default: return false; } diff --git a/mpvcore/input/input.h b/mpvcore/input/input.h index f3ea895395..639f069a9c 100644 --- a/mpvcore/input/input.h +++ b/mpvcore/input/input.h @@ -85,6 +85,9 @@ enum mp_command_type { /// Video output commands MP_CMD_VO_CMDLINE, + /// Internal for Lua scripts + MP_CMD_SCRIPT_DISPATCH, + // Internal MP_CMD_COMMAND_LIST, // list of sub-commands in args[0].v.p }; diff --git a/mpvcore/lua/assdraw.lua b/mpvcore/lua/assdraw.lua new file mode 100644 index 0000000000..fc3b727f57 --- /dev/null +++ b/mpvcore/lua/assdraw.lua @@ -0,0 +1,98 @@ +local ass_mt = {} +ass_mt.__index = ass_mt + +local function ass_new() + return setmetatable({ scale = 4, text = "" }, ass_mt) +end + +function ass_mt.new_event(ass) + -- osd_libass.c adds an event per line + if #ass.text > 0 then + ass.text = ass.text .. "\n" + end +end + +function ass_mt.draw_start(ass) + ass.text = string.format("%s{\\p%d}", ass.text, ass.scale) +end + +function ass_mt.draw_stop(ass) + ass.text = ass.text .. "{\\p0}" +end + +function ass_mt.coord(ass, x, y) + local scale = math.pow(2, ass.scale - 1) + local ix = math.ceil(x * scale) + local iy = math.ceil(y * scale) + ass.text = string.format("%s %d %d", ass.text, ix, iy) +end + +function ass_mt.append(ass, s) + ass.text = ass.text .. s +end + +function ass_mt.merge(ass1, ass2) + ass1.text = ass1.text .. ass2.text +end + +function ass_mt.pos(ass, x, y) + ass:append(string.format("{\\pos(%f,%f)}", x, y)) +end + +function ass_mt.an(ass, an) + ass:append(string.format("{\\an%d}", an)) +end + +function ass_mt.move_to(ass, x, y) + ass:append(" m") + ass:coord(x, y) +end + +function ass_mt.line_to(ass, x, y) + ass:append(" l") + ass:coord(x, y) +end + +function ass_mt.bezier_curve(ass, x1, y1, x2, y2, x3, y3) + ass:append(" b") + ass:coord(x1, y1) + ass:coord(x2, y2) + ass:coord(x3, y3) +end + + +function ass_mt.rect_ccw(ass, x0, y0, x1, y1) + ass:move_to(x0, y0) + ass:line_to(x0, y1) + ass:line_to(x1, y1) + ass:line_to(x1, y0) +end + +function ass_mt.rect_cw(ass, x0, y0, x1, y1) + ass:move_to(x0, y0) + ass:line_to(x1, y0) + ass:line_to(x1, y1) + ass:line_to(x0, y1) +end + +function ass_mt.round_rect_cw(ass, x0, y0, x1, y1, r) + ass:move_to(x0 + r, y0) + ass:line_to(x1 - r, y0) -- top line + if r > 0 then + ass:bezier_curve(x1, y0, x1, y0, x1, y0 + r) -- top right corner + end + ass:line_to(x1, y1 - r) -- right line + if r > 0 then + ass:bezier_curve(x1, y1, x1, y1, x1 - r, y1) -- bottom right corner + end + ass:line_to(x0 + r, y1) -- bottom line + if r > 0 then + ass:bezier_curve(x0, y1, x0, y1, x0, y1 - r) -- bottom left corner + end + ass:line_to(x0, y0 + r) -- left line + if r > 0 then + ass:bezier_curve(x0, y0, x0, y0, x0 + r, y0) -- top left corner + end +end + +return {ass_new = ass_new} diff --git a/mpvcore/lua/defaults.lua b/mpvcore/lua/defaults.lua new file mode 100644 index 0000000000..d24cda9cbe --- /dev/null +++ b/mpvcore/lua/defaults.lua @@ -0,0 +1,82 @@ + +local callbacks = {} +-- each script has its own section, so that they don't conflict +local default_section = "input_" .. mp.script_name + +-- Set the list of key bindings. These will override the user's bindings, so +-- you should use this sparingly. +-- A call to this function will remove all bindings previously set with this +-- function. For example, set_key_bindings({}) would remove all script defined +-- 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: +-- {key, callback} +-- {key, callback, callback_down} +-- 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 +-- (And callback_down is ignored.) +function mp.set_key_bindings(list, section) + local cfg = "" + for i = 1, #list do + local entry = list[i] + 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" + else + cfg = cfg .. key .. " " .. cb .. "\n" + end + end + mp.input_define_section(section or default_section, cfg) +end + +function mp.enable_key_bindings(section, flags) + mp.input_enable_section(section or default_section, flags) +end + +function mp.disable_key_bindings(section) + mp.input_disable_section(section or default_section) +end + +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 + +-- called by C on script_dispatch input command +function mp_script_dispatch(id, event) + local cb = callbacks[id] + if cb then + if event == "press" and cb.press then + cb.press() + elseif event == "keyup_follows" and cb.before_press then + cb.before_press() + end + end +end + +mp.msg = { + log = mp.log, + fatal = function(...) return mp.log("fatal", ...) end, + error = function(...) return mp.log("error", ...) end, + warn = function(...) return mp.log("warn", ...) end, + info = function(...) return mp.log("info", ...) end, + verbose = function(...) return mp.log("verbose", ...) end, + debug = function(...) return mp.log("debug", ...) end, +} + +_G.print = mp.msg.info + +package.loaded["mp"] = mp +package.loaded["mp.msg"] = mp.msg + +return {} diff --git a/mpvcore/mp_core.h b/mpvcore/mp_core.h index 4b8a5fcef9..e15ec0355e 100644 --- a/mpvcore/mp_core.h +++ b/mpvcore/mp_core.h @@ -166,6 +166,8 @@ typedef struct MPContext { struct track **tracks; int num_tracks; + char *track_layout_hash; + // Selected tracks. NULL if no track selected. struct track *current_track[STREAM_TYPE_COUNT]; @@ -291,10 +293,9 @@ typedef struct MPContext { bool drop_message_shown; struct screenshot_ctx *screenshot_ctx; - - char *track_layout_hash; - + struct command_ctx *command_ctx; struct encode_lavc_context *encode_lavc_ctx; + struct lua_ctx *lua_ctx; } MPContext; diff --git a/mpvcore/mp_lua.c b/mpvcore/mp_lua.c new file mode 100644 index 0000000000..ec63134673 --- /dev/null +++ b/mpvcore/mp_lua.c @@ -0,0 +1,678 @@ +#include <assert.h> +#include <string.h> + +#include <lua.h> +#include <lualib.h> +#include <lauxlib.h> + +#include "talloc.h" + +#include "mp_common.h" +#include "mp_lua.h" +#include "mp_core.h" +#include "mp_msg.h" +#include "m_property.h" +#include "m_option.h" +#include "command.h" +#include "input/input.h" +#include "sub/sub.h" +#include "osdep/timer.h" +#include "path.h" +#include "bstr.h" + +// List of builtin modules and their contents as strings. +// All these are generated from mpvcore/lua/*.lua +static const char *builtin_lua_scripts[][2] = { + {"mp.defaults", +# include "lua/defaults.inc" + }, + {"mp.assdraw", +# include "lua/assdraw.inc" + }, + {0} +}; + +// Represents a loaded script. Each has its own Lua state. +struct script_ctx { + const char *name; + lua_State *state; + struct mp_log *log; + struct MPContext *mpctx; +}; + +struct lua_ctx { + struct script_ctx **scripts; + int num_scripts; +}; + +static struct script_ctx *find_script(struct lua_ctx *lctx, const char *name) +{ + for (int n = 0; n < lctx->num_scripts; n++) { + if (strcmp(lctx->scripts[n]->name, name) == 0) + return lctx->scripts[n]; + } + return NULL; +} + +static struct script_ctx *get_ctx(lua_State *L) +{ + lua_getfield(L, LUA_REGISTRYINDEX, "ctx"); + struct script_ctx *ctx = lua_touserdata(L, -1); + lua_pop(L, 1); + assert(ctx); + return ctx; +} + +static struct MPContext *get_mpctx(lua_State *L) +{ + return get_ctx(L)->mpctx; +} + +static int wrap_cpcall(lua_State *L) +{ + lua_CFunction fn = lua_touserdata(L, -1); + lua_pop(L, 1); + return fn(L); +} + +// Call the given function fn under a Lua error handler (similar to lua_cpcall). +// Pass the given number of args from the Lua stack to fn. +// Returns 0 (and empty stack) on success. +// Returns LUA_ERR[RUN|MEM|ERR] otherwise, with the error value on the stack. +static int mp_cpcall(lua_State *L, lua_CFunction fn, int args) +{ + // Don't use lua_pushcfunction() - it allocates memory on Lua 5.1. + // Instead, emulate C closures by making wrap_cpcall call fn. + lua_pushlightuserdata(L, fn); // args... fn + // Will always succeed if mp_lua_init() set it up correctly. + lua_getfield(L, LUA_REGISTRYINDEX, "wrap_cpcall"); // args... fn wrap_cpcall + lua_insert(L, -(args + 2)); // wrap_cpcall args... fn + return lua_pcall(L, args + 1, 0, 0); +} + +static void report_error(lua_State *L) +{ + const char *err = lua_tostring(L, -1); + mp_msg(MSGT_CPLAYER, MSGL_WARN, "[lua] Error: %s\n", + err ? err : "[unknown]"); + lua_pop(L, 1); +} + +static void add_functions(struct script_ctx *ctx); + +static char *script_name_from_filename(void *talloc_ctx, struct lua_ctx *lctx, + const char *fname) +{ + fname = mp_basename(fname); + if (fname[0] == '@') + fname += 1; + char *name = talloc_strdup(talloc_ctx, fname); + // Drop .lua extension + char *dot = strrchr(name, '.'); + if (dot) + *dot = '\0'; + // Turn it into a safe identifier - this is used with e.g. dispatching + // input via: "send scriptname ..." + for (int n = 0; name[n]; n++) { + char c = name[n]; + if (!(c >= 'A' && c <= 'Z') && !(c >= 'a' && c <= 'z') && + !(c >= '0' && c <= '9')) + name[n] = '_'; + } + // Make unique (stupid but simple) + while (find_script(lctx, name)) + name = talloc_strdup_append(name, "_"); + return name; +} + +static int load_file(struct script_ctx *ctx, const char *fname) +{ + int r = 0; + lua_State *L = ctx->state; + if (luaL_loadfile(L, fname) || lua_pcall(L, 0, 0, 0)) { + report_error(L); + r = -1; + } + assert(lua_gettop(L) == 0); + return r; +} + +static int load_builtin(lua_State *L) +{ + const char *name = luaL_checkstring(L, 1); + for (int n = 0; builtin_lua_scripts[n][0]; n++) { + if (strcmp(name, builtin_lua_scripts[n][0]) == 0) { + if (luaL_loadstring(L, builtin_lua_scripts[n][1])) + lua_error(L); + lua_call(L, 0, 1); + return 1; + } + } + return 0; +} + +// Execute "require " .. name +static bool require(lua_State *L, const char *name) +{ + char buf[80]; + // Lazy, but better than calling the "require" function manually + snprintf(buf, sizeof(buf), "require '%s'", name); + if (luaL_loadstring(L, buf) || lua_pcall(L, 0, 0, 0)) { + report_error(L); + return false; + } + return true; +} + +static void mp_lua_load_script(struct MPContext *mpctx, const char *fname) +{ + struct lua_ctx *lctx = mpctx->lua_ctx; + struct script_ctx *ctx = talloc_ptrtype(NULL, ctx); + *ctx = (struct script_ctx) { + .mpctx = mpctx, + .name = script_name_from_filename(ctx, lctx, fname), + }; + char *log_name = talloc_asprintf(ctx, "lua/%s", ctx->name); + ctx->log = mp_log_new(ctx, mpctx->log, log_name); + + lua_State *L = ctx->state = luaL_newstate(); + if (!L) + goto error_out; + + // used by get_ctx() + lua_pushlightuserdata(L, ctx); // ctx + lua_setfield(L, LUA_REGISTRYINDEX, "ctx"); // - + + lua_pushcfunction(L, wrap_cpcall); // closure + lua_setfield(L, LUA_REGISTRYINDEX, "wrap_cpcall"); // - + + luaL_openlibs(L); + + lua_newtable(L); // mp + lua_pushvalue(L, -1); // mp mp + lua_setglobal(L, "mp"); // mp + + add_functions(ctx); // mp + + lua_pushstring(L, ctx->name); // mp name + lua_setfield(L, -2, "script_name"); // mp + + lua_pop(L, 1); // - + + // Add a preloader for each builtin Lua module + lua_getglobal(L, "package"); // package + assert(lua_type(L, -1) == LUA_TTABLE); + lua_getfield(L, -1, "preload"); // package preload + assert(lua_type(L, -1) == LUA_TTABLE); + for (int n = 0; builtin_lua_scripts[n][0]; n++) { + lua_pushcfunction(L, load_builtin); // package preload load_builtin + lua_setfield(L, -2, builtin_lua_scripts[n][0]); + } + lua_pop(L, 2); // - + + assert(lua_gettop(L) == 0); + + if (!require(L, "mp.defaults")) { + report_error(L); + goto error_out; + } + + assert(lua_gettop(L) == 0); + + if (fname[0] == '@') { + if (!require(L, fname)) + goto error_out; + } else { + if (load_file(ctx, fname) < 0) + goto error_out; + } + + MP_TARRAY_APPEND(lctx, lctx->scripts, lctx->num_scripts, ctx); + return; + +error_out: + if (ctx->state) + lua_close(ctx->state); + talloc_free(ctx); +} + +static void kill_script(struct script_ctx *ctx) +{ + if (!ctx) + return; + struct lua_ctx *lctx = ctx->mpctx->lua_ctx; + lua_close(ctx->state); + for (int n = 0; n < lctx->num_scripts; n++) { + if (lctx->scripts[n] == ctx) { + MP_TARRAY_REMOVE_AT(lctx->scripts, lctx->num_scripts, n); + break; + } + } + talloc_free(ctx); +} + +static const char *log_level[] = { + [MSGL_FATAL] = "fatal", + [MSGL_ERR] = "error", + [MSGL_WARN] = "warn", + [MSGL_INFO] = "info", + [MSGL_V] = "verbose", + [MSGL_DBG2] = "debug", +}; + +static int script_log(lua_State *L) +{ + struct script_ctx *ctx = get_ctx(L); + + const char *level = luaL_checkstring(L, 1); + int msgl = -1; + for (int n = 0; n < MP_ARRAY_SIZE(log_level); n++) { + if (log_level[n] && strcasecmp(log_level[n], level) == 0) { + msgl = n; + break; + } + } + if (msgl < 0) + luaL_error(L, "Invalid log level '%s'", level); + + int last = lua_gettop(L); + lua_getglobal(L, "tostring"); // args... tostring + for (int i = 2; i <= last; i++) { + lua_pushvalue(L, -1); // args... tostring tostring + lua_pushvalue(L, i); // args... tostring tostring args[i] + lua_call(L, 1, 1); // args... tostring str + const char *s = lua_tostring(L, -1); + if (s == NULL) + return luaL_error(L, "Invalid argument"); + mp_msg_log(ctx->log, msgl, "%s%s", s, i > 0 ? " " : ""); + lua_pop(L, 1); // args... tostring + } + mp_msg_log(ctx->log, msgl, "\n"); + + return 0; +} + +static int script_find_config_file(lua_State *L) +{ + const char *s = luaL_checkstring(L, 1); + char *path = mp_find_user_config_file(s); + if (path) { + lua_pushstring(L, path); + } else { + lua_pushnil(L); + } + talloc_free(path); + return 1; +} + +static int run_event(lua_State *L) +{ + lua_getglobal(L, "mp_event"); // name arg mp_event + if (lua_isnil(L, -1)) + return 0; + lua_insert(L, -3); // mp_event name arg + lua_call(L, 2, 0); + return 0; +} + +void mp_lua_event(struct MPContext *mpctx, const char *name, const char *arg) +{ + // There is no proper subscription mechanism yet, so all scripts get it. + struct lua_ctx *lctx = mpctx->lua_ctx; + for (int n = 0; n < lctx->num_scripts; n++) { + struct script_ctx *ctx = lctx->scripts[n]; + lua_State *L = ctx->state; + lua_pushstring(L, name); + if (arg) { + lua_pushstring(L, arg); + } else { + lua_pushnil(L); + } + if (mp_cpcall(L, run_event, 2) != 0) + report_error(L); + } +} + +static int run_script_dispatch(lua_State *L) +{ + int id = lua_tointeger(L, 1); + const char *event = lua_tostring(L, 2); + lua_getglobal(L, "mp_script_dispatch"); + if (lua_isnil(L, -1)) + return 0; + lua_pushinteger(L, id); + lua_pushstring(L, event); + lua_call(L, 2, 0); + return 0; +} + +void mp_lua_script_dispatch(struct MPContext *mpctx, char *script_name, + int id, char *event) +{ + struct script_ctx *ctx = find_script(mpctx->lua_ctx, script_name); + if (!ctx) { + mp_msg(MSGT_CPLAYER, MSGL_V, + "Can't find script '%s' when handling input.\n", script_name); + return; + } + lua_State *L = ctx->state; + lua_pushinteger(L, id); + lua_pushstring(L, event); + if (mp_cpcall(L, run_script_dispatch, 2) != 0) + report_error(L); +} + +static int script_send_command(lua_State *L) +{ + struct MPContext *mpctx = get_mpctx(L); + const char *s = luaL_checkstring(L, 1); + + mp_cmd_t *cmd = mp_input_parse_cmd(bstr0((char*)s), "<lua>"); + if (!cmd) + luaL_error(L, "error parsing command"); + mp_input_queue_cmd(mpctx->input, cmd); + + return 0; +} + +static int script_property_list(lua_State *L) +{ + const struct m_option *props = mp_get_property_list(); + lua_newtable(L); + for (int i = 0; props[i].name; i++) { + lua_pushinteger(L, i + 1); + lua_pushstring(L, props[i].name); + lua_settable(L, -3); + } + return 1; +} + +static int script_property_string(lua_State *L) +{ + const struct m_option *props = mp_get_property_list(); + struct MPContext *mpctx = get_mpctx(L); + const char *name = luaL_checkstring(L, 1); + int type = lua_tointeger(L, lua_upvalueindex(1)) + ? M_PROPERTY_PRINT : M_PROPERTY_GET_STRING; + + char *result = NULL; + if (m_property_do(props, name, type, &result, mpctx) >= 0 && result) { + lua_pushstring(L, result); + talloc_free(result); + return 1; + } + if (type == M_PROPERTY_PRINT) { + lua_pushstring(L, ""); + return 1; + } + return 0; +} + +static int script_set_osd_ass(lua_State *L) +{ + struct MPContext *mpctx = get_mpctx(L); + int res_x = luaL_checkinteger(L, 1); + int res_y = luaL_checkinteger(L, 2); + const char *text = luaL_checkstring(L, 3); + if (!mpctx->osd->external || + strcmp(mpctx->osd->external, text) != 0 || + mpctx->osd->external_res_x != res_x || + mpctx->osd->external_res_y != res_y) + { + talloc_free(mpctx->osd->external); + mpctx->osd->external = talloc_strdup(mpctx->osd, text); + mpctx->osd->external_res_x = res_x; + mpctx->osd->external_res_y = res_y; + osd_changed(mpctx->osd, OSDTYPE_EXTERNAL); + } + return 0; +} + +static int script_get_osd_resolution(lua_State *L) +{ + struct MPContext *mpctx = get_mpctx(L); + int w, h; + osd_object_get_resolution(mpctx->osd, mpctx->osd->objs[OSDTYPE_EXTERNAL], + &w, &h); + lua_pushnumber(L, w); + lua_pushnumber(L, h); + return 2; +} + +static int script_get_screen_size(lua_State *L) +{ + struct MPContext *mpctx = get_mpctx(L); + struct osd_object *obj = mpctx->osd->objs[OSDTYPE_EXTERNAL]; + double aspect = 1.0 * obj->vo_res.w / MPMAX(obj->vo_res.h, 1) / + obj->vo_res.display_par; + lua_pushnumber(L, obj->vo_res.w); + lua_pushnumber(L, obj->vo_res.h); + lua_pushnumber(L, aspect); + return 3; +} + +static int script_get_mouse_pos(lua_State *L) +{ + struct MPContext *mpctx = get_mpctx(L); + float px, py; + mp_get_osd_mouse_pos(mpctx, &px, &py); + double sw, sh; + osd_object_get_scale_factor(mpctx->osd, mpctx->osd->objs[OSDTYPE_EXTERNAL], + &sw, &sh); + lua_pushnumber(L, px * sw); + lua_pushnumber(L, py * sh); + return 2; +} + +static int script_get_timer(lua_State *L) +{ + lua_pushnumber(L, mp_time_sec()); + return 1; +} + +static int script_get_chapter_list(lua_State *L) +{ + struct MPContext *mpctx = get_mpctx(L); + lua_newtable(L); // list + int num = get_chapter_count(mpctx); + for (int n = 0; n < num; n++) { + double time = chapter_start_time(mpctx, n); + char *name = chapter_display_name(mpctx, n); + lua_newtable(L); // list ch + lua_pushnumber(L, time); // list ch time + lua_setfield(L, -2, "time"); // list ch + lua_pushstring(L, name); // list ch name + lua_setfield(L, -2, "name"); // list ch + lua_pushinteger(L, n + 1); // list ch n1 + lua_insert(L, -2); // list n1 ch + lua_settable(L, -3); // list + talloc_free(name); + } + return 1; +} + +static const char *stream_type(enum stream_type t) +{ + switch (t) { + case STREAM_VIDEO: return "video"; + case STREAM_AUDIO: return "audio"; + case STREAM_SUB: return "sub"; + default: return "unknown"; + } +} + +static int script_get_track_list(lua_State *L) +{ + struct MPContext *mpctx = get_mpctx(L); + lua_newtable(L); // list + for (int n = 0; n < mpctx->num_tracks; n++) { + struct track *track = mpctx->tracks[n]; + lua_newtable(L); // list track + + lua_pushstring(L, stream_type(track->type)); + lua_setfield(L, -2, "type"); + lua_pushinteger(L, track->user_tid); + lua_setfield(L, -2, "id"); + lua_pushboolean(L, track->default_track); + lua_setfield(L, -2, "default"); + lua_pushboolean(L, track->attached_picture); + lua_setfield(L, -2, "attached_picture"); + if (track->lang) { + lua_pushstring(L, track->lang); + lua_setfield(L, -2, "language"); + } + if (track->title) { + lua_pushstring(L, track->title); + lua_setfield(L, -2, "title"); + } + lua_pushboolean(L, track->is_external); + lua_setfield(L, -2, "external"); + if (track->external_filename) { + lua_pushstring(L, track->external_filename); + lua_setfield(L, -2, "external_filename"); + } + lua_pushboolean(L, track->auto_loaded); + lua_setfield(L, -2, "auto_loaded"); + + lua_pushinteger(L, n + 1); // list track n1 + lua_insert(L, -2); // list n1 track + lua_settable(L, -3); // list + } + return 1; +} + +static int script_input_define_section(lua_State *L) +{ + struct MPContext *mpctx = get_mpctx(L); + char *section = (char *)luaL_checkstring(L, 1); + char *contents = (char *)luaL_checkstring(L, 2); + mp_input_define_section(mpctx->input, section, "<script>", contents, true); + return 0; +} + +static int script_input_enable_section(lua_State *L) +{ + struct MPContext *mpctx = get_mpctx(L); + char *section = (char *)luaL_checkstring(L, 1); + char *sflags = (char *)luaL_optstring(L, 2, ""); + bstr bflags = bstr0(sflags); + int flags = 0; + while (bflags.len) { + bstr val; + bstr_split_tok(bflags, "|", &val, &bflags); + if (bstr_equals0(val, "allow-hide-cursor")) { + flags |= MP_INPUT_ALLOW_HIDE_CURSOR; + } else if (bstr_equals0(val, "allow-vo-dragging")) { + flags |= MP_INPUT_ALLOW_VO_DRAGGING; + } else if (bstr_equals0(val, "exclusive")) { + flags |= MP_INPUT_EXCLUSIVE; + } else { + luaL_error(L, "invalid flag: '%.*s'", BSTR_P(val)); + } + } + mp_input_enable_section(mpctx->input, section, flags); + return 0; +} + +static int script_input_disable_section(lua_State *L) +{ + struct MPContext *mpctx = get_mpctx(L); + char *section = (char *)luaL_checkstring(L, 1); + mp_input_disable_section(mpctx->input, section); + return 0; +} + +static int script_input_set_section_mouse_area(lua_State *L) +{ + struct MPContext *mpctx = get_mpctx(L); + + double sw, sh; + struct osd_object *obj = mpctx->osd->objs[OSDTYPE_EXTERNAL]; + osd_object_get_scale_factor(mpctx->osd, obj, &sw, &sh); + + char *section = (char *)luaL_checkstring(L, 1); + int x0 = luaL_checkinteger(L, 2) / sw; + int y0 = luaL_checkinteger(L, 3) / sh; + int x1 = luaL_checkinteger(L, 4) / sw; + int y1 = luaL_checkinteger(L, 5) / sh; + mp_input_set_section_mouse_area(mpctx->input, section, x0, y0, x1, y1); + return 0; +} + +static int script_format_time(lua_State *L) +{ + double t = luaL_checknumber(L, 1); + const char *fmt = luaL_optstring(L, 2, "%H:%M:%S"); + char *r = mp_format_time_fmt(fmt, t); + if (!r) + luaL_error(L, "Invalid time format string '%s'", fmt); + lua_pushstring(L, r); + talloc_free(r); + return 1; +} + +struct fn_entry { + const char *name; + int (*fn |