scripting: load scripts from directories

The intention is to provide a slightly nicer way to distribute scripts.
For example, you could put multiple source files into the directory, and
then import them from the actual script file (this is still
unimplemented).

At first I wanted to require a config file (because you need to know at
least which scripting backend it should use). This wouldn't have been
too hard (could have reused/abused the mpv config file parsing
mechanism, and I already had working code that was just 2 function
calls). But probably better to do this without new config files, because
it might become a pain in the distant future.

So this just probes for "main.lua", "main.js", etc., until an existing
file is found.

Another important change is that this skips all directory entries whose
name starts with ".". This automatically excludes the "." and ".."
special directories, and is probably useful to exclude random crap that
might be lying around in the directory (such as editor temporary files,
or OSX, in its usual hrmful, annoying, and idiotic modus operandi,
sharting all over any directories opened by "Finder").

Although the changelog mentions the docs, they're added only in a later
commit.
This commit is contained in:
wm4 2020-02-01 18:09:40 +01:00
parent 66a979bd75
commit da38caff9c
7 changed files with 83 additions and 52 deletions

View File

@ -24,6 +24,10 @@ Interface changes
::
--- mpv 0.33.0 ---
- directories in ~/.mpv/scripts/ (or equivalent) now have special semantics
(see mpv Lua scripting docs)
- names starting with "." in ~/.mpv/scripts/ (or equivalent) are now ignored
--- mpv 0.32.0 ---
- change behavior when using legacy option syntax with options that start
with two dashes (``--`` instead of a ``-``). Now, using the recommended

View File

@ -316,16 +316,6 @@ struct mpv_global *mp_client_get_global(struct mpv_handle *ctx)
return ctx->mpctx->global;
}
struct MPContext *mp_client_get_core(struct mpv_handle *ctx)
{
return ctx->mpctx;
}
struct MPContext *mp_client_api_get_core(struct mp_client_api *api)
{
return api->mpctx;
}
static void wakeup_client(struct mpv_handle *ctx)
{
pthread_mutex_lock(&ctx->wakeup_lock);

View File

@ -36,8 +36,6 @@ struct mpv_handle *mp_new_client(struct mp_client_api *clients, const char *name
void mp_client_set_weak(struct mpv_handle *ctx);
struct mp_log *mp_client_get_log(struct mpv_handle *ctx);
struct mpv_global *mp_client_get_global(struct mpv_handle *ctx);
struct MPContext *mp_client_get_core(struct mpv_handle *ctx);
struct MPContext *mp_client_api_get_core(struct mp_client_api *api);
void mp_client_broadcast_event_external(struct mp_client_api *api, int event,
void *data);

View File

@ -619,10 +619,18 @@ void update_screensaver_state(struct MPContext *mpctx);
void update_ab_loop_clip(struct MPContext *mpctx);
// scripting.c
struct mp_script_args {
const struct mp_scripting *backend;
struct MPContext *mpctx;
struct mp_log *log;
struct mpv_handle *client;
const char *filename;
const char *path;
};
struct mp_scripting {
const char *name; // e.g. "lua script"
const char *file_ext; // e.g. "lua"
int (*load)(struct mpv_handle *client, const char *filename);
int (*load)(struct mp_script_args *args);
};
bool mp_load_scripts(struct MPContext *mpctx);
void mp_load_builtin_scripts(struct MPContext *mpctx);

View File

@ -468,15 +468,15 @@ static int s_init_js(js_State *J, struct script_ctx *ctx)
//
// Note: init functions don't need autofree. They can use ctx as a talloc
// context and free normally. If they throw - ctx is freed right afterwards.
static int s_load_javascript(struct mpv_handle *client, const char *fname)
static int s_load_javascript(struct mp_script_args *args)
{
struct script_ctx *ctx = talloc_ptrtype(NULL, ctx);
*ctx = (struct script_ctx) {
.client = client,
.mpctx = mp_client_get_core(client),
.log = mp_client_get_log(client),
.client = args->client,
.mpctx = args->mpctx,
.log = args->log,
.last_error_str = talloc_strdup(ctx, "Cannot initialize JavaScript"),
.filename = fname,
.filename = args->filename,
};
int r = -1;

View File

@ -337,18 +337,17 @@ static int run_lua(lua_State *L)
return 0;
}
static int load_lua(struct mpv_handle *client, const char *fname)
static int load_lua(struct mp_script_args *args)
{
struct MPContext *mpctx = mp_client_get_core(client);
int r = -1;
struct script_ctx *ctx = talloc_ptrtype(NULL, ctx);
*ctx = (struct script_ctx) {
.mpctx = mpctx,
.client = client,
.name = mpv_client_name(client),
.log = mp_client_get_log(client),
.filename = fname,
.mpctx = args->mpctx,
.client = args->client,
.name = mpv_client_name(args->client),
.log = args->log,
.filename = args->filename,
};
if (LUA_VERSION_NUM != 501 && LUA_VERSION_NUM != 502) {

View File

@ -30,6 +30,8 @@
#include "common/common.h"
#include "common/msg.h"
#include "options/m_config.h"
#include "options/parse_configfile.h"
#include "options/path.h"
#include "misc/bstr.h"
#include "core.h"
@ -74,26 +76,19 @@ static char *script_name_from_filename(void *talloc_ctx, const char *fname)
return talloc_asprintf(talloc_ctx, "%s", name);
}
struct thread_arg {
struct mp_log *log;
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_script_args *arg = p;
char name[90];
snprintf(name, sizeof(name), "%s (%s)", arg->backend->name,
mpv_client_name(arg->client));
mpthread_set_name(name);
if (arg->backend->load(arg->client, arg->fname) < 0)
MP_ERR(arg, "Could not load %s %s\n", arg->backend->name, arg->fname);
if (arg->backend->load(arg) < 0)
MP_ERR(arg, "Could not load %s %s\n", arg->backend->name, arg->filename);
mpv_destroy(arg->client);
talloc_free(arg);
@ -106,34 +101,70 @@ static int mp_load_script(struct MPContext *mpctx, const char *fname)
if (ext && strcasecmp(ext, "disable") == 0)
return 0;
void *tmp = talloc_new(NULL);
const char *path = 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;
struct stat s;
if (!stat(fname, &s) && S_ISDIR(s.st_mode)) {
path = fname;
fname = NULL;
for (int n = 0; scripting_backends[n]; n++) {
const struct mp_scripting *b = scripting_backends[n];
char *filename = mp_tprintf(80, "main.%s", b->file_ext);
fname = mp_path_join(tmp, path, filename);
if (!stat(fname, &s) && S_ISREG(s.st_mode)) {
backend = b;
break;
}
talloc_free((void *)fname);
fname = NULL;
}
if (!fname) {
MP_ERR(mpctx, "Cannot find main.* for any supported scripting "
"backend in: %s\n", path);
talloc_free(tmp);
return -1;
}
} else {
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_ERR(mpctx, "Can't load unknown script: %s\n", fname);
talloc_free(tmp);
return -1;
}
struct thread_arg *arg = talloc_ptrtype(NULL, arg);
struct mp_script_args *arg = talloc_ptrtype(NULL, arg);
char *name = script_name_from_filename(arg, fname);
*arg = (struct thread_arg){
.fname = talloc_strdup(arg, fname),
*arg = (struct mp_script_args){
.mpctx = mpctx,
.filename = talloc_strdup(arg, fname),
.path = talloc_strdup(arg, path),
.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),
};
talloc_free(tmp);
if (!arg->client) {
talloc_free(arg);
return -1;
}
mp_client_set_weak(arg->client);
arg->log = mp_client_get_log(arg->client);
@ -173,10 +204,12 @@ static char **list_script_files(void *talloc_ctx, char *path)
return NULL;
struct dirent *ep;
while ((ep = readdir(dp))) {
char *fname = mp_path_join(talloc_ctx, path, ep->d_name);
struct stat s;
if (!stat(fname, &s) && S_ISREG(s.st_mode))
MP_TARRAY_APPEND(talloc_ctx, files, count, fname);
if (ep->d_name[0] != '.') {
char *fname = mp_path_join(talloc_ctx, path, ep->d_name);
struct stat s;
if (!stat(fname, &s) && (S_ISREG(s.st_mode) || S_ISDIR(s.st_mode)))
MP_TARRAY_APPEND(talloc_ctx, files, count, fname);
}
}
closedir(dp);
if (files)
@ -247,10 +280,9 @@ bool mp_load_scripts(struct MPContext *mpctx)
#define MPV_DLOPEN_FN "mpv_open_cplugin"
typedef int (*mpv_open_cplugin)(mpv_handle *handle);
static int load_cplugin(struct mpv_handle *client, const char *fname)
static int load_cplugin(struct mp_script_args *args)
{
MPContext *ctx = mp_client_get_core(client);
void *lib = dlopen(fname, RTLD_NOW | RTLD_LOCAL);
void *lib = dlopen(args->filename, RTLD_NOW | RTLD_LOCAL);
if (!lib)
goto error;
// Note: once loaded, we never unload, as unloading the libraries linked to
@ -258,11 +290,11 @@ static int load_cplugin(struct mpv_handle *client, const char *fname)
mpv_open_cplugin sym = (mpv_open_cplugin)dlsym(lib, MPV_DLOPEN_FN);
if (!sym)
goto error;
return sym(client) ? -1 : 0;
return sym(args->client) ? -1 : 0;
error: ;
char *err = dlerror();
if (err)
MP_ERR(ctx, "C plugin error: '%s'\n", err);
MP_ERR(args, "C plugin error: '%s'\n", err);
return -1;
}