mirror of https://github.com/mpv-player/mpv
684 lines
19 KiB
C
684 lines
19 KiB
C
#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"
|
|
},
|
|
{"@osc",
|
|
# include "lua/osc.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(mpctx->input, 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);
|
|
int px, py;
|
|
mp_input_get_mouse_pos(mpctx->input, &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)(lua_State *L);
|
|
};
|
|
|
|
#define FN_ENTRY(name) {#name, script_ ## name}
|
|
|
|
static struct fn_entry fn_list[] = {
|
|
FN_ENTRY(log),
|
|
FN_ENTRY(find_config_file),
|
|
FN_ENTRY(send_command),
|
|
FN_ENTRY(property_list),
|
|
FN_ENTRY(set_osd_ass),
|
|
FN_ENTRY(get_osd_resolution),
|
|
FN_ENTRY(get_screen_size),
|
|
FN_ENTRY(get_mouse_pos),
|
|
FN_ENTRY(get_timer),
|
|
FN_ENTRY(get_chapter_list),
|
|
FN_ENTRY(get_track_list),
|
|
FN_ENTRY(input_define_section),
|
|
FN_ENTRY(input_enable_section),
|
|
FN_ENTRY(input_disable_section),
|
|
FN_ENTRY(input_set_section_mouse_area),
|
|
FN_ENTRY(format_time),
|
|
};
|
|
|
|
// On stack: mp table
|
|
static void add_functions(struct script_ctx *ctx)
|
|
{
|
|
lua_State *L = ctx->state;
|
|
|
|
for (int n = 0; n < MP_ARRAY_SIZE(fn_list); n++) {
|
|
lua_pushcfunction(L, fn_list[n].fn);
|
|
lua_setfield(L, -2, fn_list[n].name);
|
|
}
|
|
|
|
lua_pushinteger(L, 0);
|
|
lua_pushcclosure(L, script_property_string, 1);
|
|
lua_setfield(L, -2, "property_get");
|
|
|
|
lua_pushinteger(L, 1);
|
|
lua_pushcclosure(L, script_property_string, 1);
|
|
lua_setfield(L, -2, "property_get_string");
|
|
}
|
|
|
|
void mp_lua_init(struct MPContext *mpctx)
|
|
{
|
|
mpctx->lua_ctx = talloc_zero(NULL, struct lua_ctx);
|
|
// Load scripts from options
|
|
if (mpctx->opts->lua_load_osc)
|
|
mp_lua_load_script(mpctx, "@osc");
|
|
char **files = mpctx->opts->lua_files;
|
|
for (int n = 0; files && files[n]; n++) {
|
|
if (files[n][0])
|
|
mp_lua_load_script(mpctx, files[n]);
|
|
}
|
|
}
|
|
|
|
void mp_lua_uninit(struct MPContext *mpctx)
|
|
{
|
|
if (mpctx->lua_ctx) {
|
|
while (mpctx->lua_ctx->num_scripts)
|
|
kill_script(mpctx->lua_ctx->scripts[0]);
|
|
talloc_free(mpctx->lua_ctx);
|
|
mpctx->lua_ctx = NULL;
|
|
}
|
|
}
|