diff --git a/old-makefile b/old-makefile index 1d3b45a8cc..b54c147eb6 100644 --- a/old-makefile +++ b/old-makefile @@ -227,6 +227,7 @@ SOURCES = audio/audio.c \ player/osd.c \ player/playloop.c \ player/screenshot.c \ + player/scripting.c \ player/sub.c \ player/video.c \ player/timeline/tl_matroska.c \ diff --git a/options/options.c b/options/options.c index 5674c7dc0d..d3a10457a8 100644 --- a/options/options.c +++ b/options/options.c @@ -680,7 +680,9 @@ const struct MPOpts mp_default_opts = { .osd_bar_h = 3.125, .osd_scale = 1, .osd_scale_by_window = 1, +#if HAVE_LUA .lua_load_osc = 1, +#endif .auto_load_scripts = 1, .loop_times = -1, .ordered_chapters = 1, diff --git a/player/client.c b/player/client.c index 45ba680e84..31a32b5616 100644 --- a/player/client.c +++ b/player/client.c @@ -205,6 +205,11 @@ struct mp_log *mp_client_get_log(struct mpv_handle *ctx) return ctx->log; } +struct MPContext *mp_client_get_core(struct mpv_handle *ctx) +{ + return ctx->mpctx; +} + static void wakeup_client(struct mpv_handle *ctx) { pthread_cond_signal(&ctx->wakeup); diff --git a/player/client.h b/player/client.h index 6e078e9d7b..e9d41d8c09 100644 --- a/player/client.h +++ b/player/client.h @@ -21,5 +21,6 @@ void mp_client_property_change(struct MPContext *mpctx, const char **list); struct mpv_handle *mp_new_client(struct mp_client_api *clients, const char *name); struct mp_log *mp_client_get_log(struct mpv_handle *ctx); +struct MPContext *mp_client_get_core(struct mpv_handle *ctx); #endif diff --git a/player/command.c b/player/command.c index ec05e8cc40..512f2b478a 100644 --- a/player/command.c +++ b/player/command.c @@ -75,7 +75,6 @@ #include "osdep/io.h" #include "core.h" -#include "lua.h" struct command_ctx { double last_seek_time; diff --git a/player/core.h b/player/core.h index 18a749376b..891c14792d 100644 --- a/player/core.h +++ b/player/core.h @@ -449,6 +449,13 @@ void idle_loop(struct MPContext *mpctx); void handle_force_window(struct MPContext *mpctx, bool reconfig); void add_frame_pts(struct MPContext *mpctx, double pts); +// scripting.c +struct mp_scripting { + const char *file_ext; // e.g. "lua" + int (*load)(struct mpv_handle *client, const char *filename); +}; +void mp_load_scripts(struct MPContext *mpctx); + // sub.c void reset_subtitles(struct MPContext *mpctx, int order); void uninit_subs(struct demuxer *demuxer); diff --git a/player/lua.c b/player/lua.c index 0c8c415630..1c8a5c6cb5 100644 --- a/player/lua.c +++ b/player/lua.c @@ -44,7 +44,6 @@ #include "command.h" #include "client.h" #include "libmpv/client.h" -#include "lua.h" // List of builtin modules and their contents as strings. // All these are generated from player/lua/*.lua @@ -55,7 +54,7 @@ static const char *builtin_lua_scripts[][2] = { {"mp.assdraw", # include "player/lua/assdraw.inc" }, - {"@osc", + {"@osc.lua", # include "player/lua/osc.inc" }, {0} @@ -140,27 +139,6 @@ static int run_event_loop(lua_State *L) static void add_functions(struct script_ctx *ctx); -static char *script_name_from_filename(void *talloc_ctx, 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] = '_'; - } - return talloc_asprintf(talloc_ctx, "%s", name); -} - static int load_file(struct script_ctx *ctx, const char *fname) { int r = 0; @@ -206,20 +184,10 @@ static bool require(lua_State *L, const char *name) return true; } -struct thread_arg { - struct MPContext *mpctx; - mpv_handle *client; - const char *fname; -}; - -static void *lua_thread(void *p) +static int load_lua(struct mpv_handle *client, const char *fname) { - pthread_detach(pthread_self()); - - struct thread_arg *arg = p; - struct MPContext *mpctx = arg->mpctx; - const char *fname = arg->fname; - mpv_handle *client = arg->client; + struct MPContext *mpctx = mp_client_get_core(client); + int r = -1; struct script_ctx *ctx = talloc_ptrtype(NULL, ctx); *ctx = (struct script_ctx) { @@ -301,40 +269,15 @@ static void *lua_thread(void *p) if (mp_cpcall(L, run_event_loop, 0) != 0) report_error(L); + r = 0; + error_out: - MP_VERBOSE(ctx, "exiting.\n"); if (ctx->suspended) mpv_resume(ctx->client); if (ctx->state) lua_close(ctx->state); - mpv_destroy(ctx->client); talloc_free(ctx); - talloc_free(arg); - return NULL; -} - -static void mp_lua_load_script(struct MPContext *mpctx, const char *fname) -{ - struct thread_arg *arg = talloc_ptrtype(NULL, arg); - char *name = script_name_from_filename(arg, fname); - *arg = (struct thread_arg){ - .mpctx = mpctx, - .fname = talloc_strdup(arg, fname), - // Create the client before creating the thread; otherwise a race - // condition could happen, where MPContext is destroyed while the - // thread tries to create the client. - .client = mp_new_client(mpctx->clients, name), - }; - if (!arg->client) { - talloc_free(arg); - return; - } - - pthread_t thread; - if (pthread_create(&thread, NULL, lua_thread, arg)) - talloc_free(arg); - - return; + return r; } static int check_loglevel(lua_State *L, int arg) @@ -1099,53 +1042,7 @@ static void add_functions(struct script_ctx *ctx) lua_setfield(L, -2, "get_property_osd"); } -static int compare_filename(const void *pa, const void *pb) -{ - char *a = (char *)pa; - char *b = (char *)pb; - return strcmp(a, b); -} - -static char **list_lua_files(void *talloc_ctx, char *path) -{ - char **files = NULL; - int count = 0; - DIR *dp = opendir(path); - if (!dp) - return NULL; - struct dirent *ep; - while ((ep = readdir(dp))) { - char *ext = mp_splitext(ep->d_name, NULL); - if (!ext || strcasecmp(ext, "lua") != 0) - continue; - char *fname = mp_path_join(talloc_ctx, bstr0(path), bstr0(ep->d_name)); - MP_TARRAY_APPEND(talloc_ctx, files, count, fname); - } - closedir(dp); - qsort(files, count, sizeof(char *), compare_filename); - MP_TARRAY_APPEND(talloc_ctx, files, count, NULL); - return files; -} - -void mp_lua_init(struct MPContext *mpctx) -{ - // 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]); - } - if (!mpctx->opts->auto_load_scripts) - return; - // Load ~/.mpv/lua/* - void *tmp = talloc_new(NULL); - char *lua_path = mp_find_user_config_file(tmp, mpctx->global, "lua"); - if (lua_path) { - files = list_lua_files(tmp, lua_path); - for (int n = 0; files && files[n]; n++) - mp_lua_load_script(mpctx, files[n]); - } - talloc_free(tmp); -} +const struct mp_scripting mp_scripting_lua = { + .file_ext = "lua", + .load = load_lua, +}; diff --git a/player/lua.h b/player/lua.h deleted file mode 100644 index cb4a7f95ec..0000000000 --- a/player/lua.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef MP_LUA_H -#define MP_LUA_H - -#include - -struct MPContext; - -void mp_lua_init(struct MPContext *mpctx); - -#endif diff --git a/player/main.c b/player/main.c index 097e51e11c..8c5e90a0d7 100644 --- a/player/main.c +++ b/player/main.c @@ -61,7 +61,6 @@ #include "core.h" #include "client.h" -#include "lua.h" #include "command.h" #include "screenshot.h" @@ -450,11 +449,9 @@ int mp_initialize(struct MPContext *mpctx) mpctx->initialized_flags |= INITIALIZED_VO; } -#if HAVE_LUA - // Lua user scripts can call arbitrary functions. Load them at a point + // Lua user scripts (etc.) can call arbitrary functions. Load them at a point // where this is safe. - mp_lua_init(mpctx); -#endif + mp_load_scripts(mpctx); if (opts->shuffle) playlist_shuffle(mpctx->playlist); diff --git a/player/scripting.c b/player/scripting.c new file mode 100644 index 0000000000..ed7a1c2d3b --- /dev/null +++ b/player/scripting.c @@ -0,0 +1,179 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mpv. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include "osdep/io.h" + +#include "common/common.h" +#include "common/msg.h" +#include "options/path.h" +#include "bstr/bstr.h" +#include "core.h" +#include "client.h" +#include "libmpv/client.h" + +extern const struct mp_scripting mp_scripting_lua; + +static const struct mp_scripting *scripting_backends[] = { +#if HAVE_LUA + &mp_scripting_lua, +#endif + NULL +}; + +static char *script_name_from_filename(void *talloc_ctx, const char *fname) +{ + fname = mp_basename(fname); + if (fname[0] == '@') + fname += 1; + char *name = talloc_strdup(talloc_ctx, fname); + // Drop file 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] = '_'; + } + return talloc_asprintf(talloc_ctx, "%s", name); +} + +struct thread_arg { + const struct mp_scripting *backend; + mpv_handle *client; + const char *fname; +}; + +static void *script_thread(void *p) +{ + pthread_detach(pthread_self()); + + struct thread_arg *arg = p; + struct mp_log *log = mp_client_get_log(arg->client); + + mp_verbose(log, "Loading script...\n"); + + if (arg->backend->load(arg->client, arg->fname) < 0) + mp_err(log, "Could not load script %s\n", arg->fname); + + mp_verbose(log, "Exiting...\n"); + + mpv_destroy(arg->client); + talloc_free(arg); + return NULL; +} + +static void mp_load_script(struct MPContext *mpctx, const char *fname) +{ + char *ext = mp_splitext(fname, NULL); + const struct mp_scripting *backend = NULL; + for (int n = 0; scripting_backends[n]; n++) { + const struct mp_scripting *b = scripting_backends[n]; + if (ext && strcasecmp(ext, b->file_ext) == 0) { + backend = b; + break; + } + } + + if (!backend) { + MP_WARN(mpctx, "Can't load unknown script: %s\n", fname); + return; + } + + struct thread_arg *arg = talloc_ptrtype(NULL, arg); + char *name = script_name_from_filename(arg, fname); + *arg = (struct thread_arg){ + .fname = talloc_strdup(arg, fname), + .backend = backend, + // Create the client before creating the thread; otherwise a race + // condition could happen, where MPContext is destroyed while the + // thread tries to create the client. + .client = mp_new_client(mpctx->clients, name), + }; + if (!arg->client) { + talloc_free(arg); + return; + } + + pthread_t thread; + if (pthread_create(&thread, NULL, script_thread, arg)) + talloc_free(arg); + + return; +} + +static int compare_filename(const void *pa, const void *pb) +{ + char *a = (char *)pa; + char *b = (char *)pb; + return strcmp(a, b); +} + +static char **list_script_files(void *talloc_ctx, char *path) +{ + char **files = NULL; + int count = 0; + DIR *dp = opendir(path); + if (!dp) + return NULL; + struct dirent *ep; + while ((ep = readdir(dp))) { + char *fname = mp_path_join(talloc_ctx, bstr0(path), bstr0(ep->d_name)); + struct stat s; + if (!mp_stat(fname, &s) && S_ISREG(s.st_mode)) + MP_TARRAY_APPEND(talloc_ctx, files, count, fname); + } + closedir(dp); + qsort(files, count, sizeof(char *), compare_filename); + MP_TARRAY_APPEND(talloc_ctx, files, count, NULL); + return files; +} + +void mp_load_scripts(struct MPContext *mpctx) +{ + // Load scripts from options + if (mpctx->opts->lua_load_osc) + mp_load_script(mpctx, "@osc.lua"); + char **files = mpctx->opts->lua_files; + for (int n = 0; files && files[n]; n++) { + if (files[n][0]) + mp_load_script(mpctx, files[n]); + } + if (!mpctx->opts->auto_load_scripts) + return; + // Load ~/.mpv/lua/* + void *tmp = talloc_new(NULL); + char *script_path = mp_find_user_config_file(tmp, mpctx->global, "lua"); + if (script_path) { + files = list_script_files(tmp, script_path); + for (int n = 0; files && files[n]; n++) + mp_load_script(mpctx, files[n]); + } + talloc_free(tmp); +} diff --git a/wscript_build.py b/wscript_build.py index c8058b6fe6..fe711b554f 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -216,6 +216,7 @@ def build(ctx): ( "player/osd.c" ), ( "player/playloop.c" ), ( "player/screenshot.c" ), + ( "player/scripting.c" ), ( "player/sub.c" ), ( "player/timeline/tl_cue.c" ), ( "player/timeline/tl_mpv_edl.c" ),