mirror of https://github.com/mpv-player/mpv
scripting: add a way to run sub processes as "scripts"
This is just a more convenient way to start IPC client scripts per mpv instance. Does not work on Windows, although it could if the subprocess and IPC parts are implemented (and I guess .exe/.bat suffixes are required). Also untested whether it builds on Windows. A lot of other things are untested too, so don't complain.
This commit is contained in:
parent
d06ebe2251
commit
e2ab6b7f35
|
@ -297,3 +297,30 @@ Is equivalent to:
|
||||||
::
|
::
|
||||||
|
|
||||||
{ "objkey": "value\n" }
|
{ "objkey": "value\n" }
|
||||||
|
|
||||||
|
Alternative ways of starting clients
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
You can create an anonymous IPC connection without having to set
|
||||||
|
``--input-ipc-server``. This is achieved through a mpv pseudo scripting backend
|
||||||
|
that starts processes.
|
||||||
|
|
||||||
|
You can put ``.run`` file extension in the mpv scripts directory in its config
|
||||||
|
directory (see the `FILES`_ section for details), or load them through other
|
||||||
|
means (see `Script location`_). These scripts are simply executed with the OS
|
||||||
|
native mechanism (as if you ran them in the shell). They must have a proper
|
||||||
|
shebang and have the executable bit set.
|
||||||
|
|
||||||
|
When executed, a socket (the IPC connection) is passed to them through file
|
||||||
|
descriptor inheritance. The file descriptor is indicated as the special command
|
||||||
|
line argument ``--mpv-ipc-fd=N``, where ``N`` is the numeric file descriptor.
|
||||||
|
Currently, this is hardcoded as ``--mpv-ipc-fd=3``, and the intention is that
|
||||||
|
it will always be ``3``. (This was a promise between keeping it as simple as
|
||||||
|
possible, and not doing too much implicitly. Also, since there is a chance that
|
||||||
|
this will change anyway, you should at least validate that you got the expected
|
||||||
|
argument.)
|
||||||
|
|
||||||
|
The rest is the same as with a normal ``--input-ipc-server`` IPC connection. mpv
|
||||||
|
does not attempt to observe or other interact with the started script process.
|
||||||
|
|
||||||
|
This does not work in Windows yet.
|
||||||
|
|
|
@ -213,10 +213,20 @@ void mp_input_sdl_gamepad_add(struct input_ctx *ictx);
|
||||||
|
|
||||||
struct mp_ipc_ctx;
|
struct mp_ipc_ctx;
|
||||||
struct mp_client_api;
|
struct mp_client_api;
|
||||||
|
struct mpv_handle;
|
||||||
|
|
||||||
// Platform specific implementation, provided by ipc-*.c.
|
// Platform specific implementation, provided by ipc-*.c.
|
||||||
struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api,
|
struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api,
|
||||||
struct mpv_global *global);
|
struct mpv_global *global);
|
||||||
|
// Start a thread for the given handle and return a socket in out_fd[0] that
|
||||||
|
// is served by this thread. If the FD is not full-duplex, then out_fd[0] is
|
||||||
|
// the user's read-end, and out_fd[1] the write-end, otherwise out_fd[1] is set
|
||||||
|
// to -1.
|
||||||
|
// returns:
|
||||||
|
// true: out_fd[0] and out_fd[1] are set, ownership of h is transferred
|
||||||
|
// false: out_fd are not touched, caller retains ownership of h
|
||||||
|
bool mp_ipc_start_anon_client(struct mp_ipc_ctx *ctx, struct mpv_handle *h,
|
||||||
|
int out_fd[2]);
|
||||||
void mp_uninit_ipc(struct mp_ipc_ctx *ctx);
|
void mp_uninit_ipc(struct mp_ipc_ctx *ctx);
|
||||||
|
|
||||||
// Serialize the given mpv_event structure to JSON. Returns an allocated string.
|
// Serialize the given mpv_event structure to JSON. Returns an allocated string.
|
||||||
|
|
|
@ -8,6 +8,12 @@ struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool mp_ipc_start_anon_client(struct mp_ipc_ctx *ctx, struct mpv_handle *h,
|
||||||
|
int out_fd[2])
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void mp_uninit_ipc(struct mp_ipc_ctx *ctx)
|
void mp_uninit_ipc(struct mp_ipc_ctx *ctx)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ struct client_arg {
|
||||||
struct mp_log *log;
|
struct mp_log *log;
|
||||||
struct mpv_handle *client;
|
struct mpv_handle *client;
|
||||||
|
|
||||||
char *client_name;
|
const char *client_name;
|
||||||
int client_fd;
|
int client_fd;
|
||||||
bool close_client_fd;
|
bool close_client_fd;
|
||||||
|
|
||||||
|
@ -215,8 +215,10 @@ done:
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ipc_start_client(struct mp_ipc_ctx *ctx, struct client_arg *client)
|
static bool ipc_start_client(struct mp_ipc_ctx *ctx, struct client_arg *client,
|
||||||
|
bool free_on_init_fail)
|
||||||
{
|
{
|
||||||
|
if (!client->client)
|
||||||
client->client = mp_new_client(ctx->client_api, client->client_name);
|
client->client = mp_new_client(ctx->client_api, client->client_name);
|
||||||
if (!client->client)
|
if (!client->client)
|
||||||
goto err;
|
goto err;
|
||||||
|
@ -227,16 +229,19 @@ static void ipc_start_client(struct mp_ipc_ctx *ctx, struct client_arg *client)
|
||||||
if (pthread_create(&client_thr, NULL, client_thread, client))
|
if (pthread_create(&client_thr, NULL, client_thread, client))
|
||||||
goto err;
|
goto err;
|
||||||
|
|
||||||
return;
|
return true;
|
||||||
|
|
||||||
err:
|
err:
|
||||||
|
if (free_on_init_fail) {
|
||||||
if (client->client)
|
if (client->client)
|
||||||
mpv_destroy(client->client);
|
mpv_destroy(client->client);
|
||||||
|
|
||||||
if (client->close_client_fd)
|
if (client->close_client_fd)
|
||||||
close(client->client_fd);
|
close(client->client_fd);
|
||||||
|
}
|
||||||
|
|
||||||
talloc_free(client);
|
talloc_free(client);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ipc_start_client_json(struct mp_ipc_ctx *ctx, int id, int fd)
|
static void ipc_start_client_json(struct mp_ipc_ctx *ctx, int id, int fd)
|
||||||
|
@ -246,11 +251,37 @@ static void ipc_start_client_json(struct mp_ipc_ctx *ctx, int id, int fd)
|
||||||
.client_name = talloc_asprintf(client, "ipc-%d", id),
|
.client_name = talloc_asprintf(client, "ipc-%d", id),
|
||||||
.client_fd = fd,
|
.client_fd = fd,
|
||||||
.close_client_fd = true,
|
.close_client_fd = true,
|
||||||
|
|
||||||
.writable = true,
|
.writable = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
ipc_start_client(ctx, client);
|
ipc_start_client(ctx, client, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mp_ipc_start_anon_client(struct mp_ipc_ctx *ctx, struct mpv_handle *h,
|
||||||
|
int out_fd[2])
|
||||||
|
{
|
||||||
|
int pair[2];
|
||||||
|
if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
struct client_arg *client = talloc_ptrtype(NULL, client);
|
||||||
|
*client = (struct client_arg){
|
||||||
|
.client = h,
|
||||||
|
.client_name = mpv_client_name(h),
|
||||||
|
.client_fd = pair[1],
|
||||||
|
.close_client_fd = true,
|
||||||
|
.writable = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!ipc_start_client(ctx, client, false)) {
|
||||||
|
close(pair[0]);
|
||||||
|
close(pair[1]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_fd[0] = pair[0];
|
||||||
|
out_fd[1] = -1;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ipc_start_client_text(struct mp_ipc_ctx *ctx, const char *path)
|
static void ipc_start_client_text(struct mp_ipc_ctx *ctx, const char *path)
|
||||||
|
@ -292,7 +323,7 @@ static void ipc_start_client_text(struct mp_ipc_ctx *ctx, const char *path)
|
||||||
.writable = writable,
|
.writable = writable,
|
||||||
};
|
};
|
||||||
|
|
||||||
ipc_start_client(ctx, client);
|
ipc_start_client(ctx, client, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *ipc_thread(void *p)
|
static void *ipc_thread(void *p)
|
||||||
|
|
|
@ -335,6 +335,12 @@ static void ipc_start_client_json(struct mp_ipc_ctx *ctx, int id, HANDLE h)
|
||||||
ipc_start_client(ctx, client);
|
ipc_start_client(ctx, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool mp_ipc_start_anon_client(struct mp_ipc_ctx *ctx, struct mpv_handle *h,
|
||||||
|
int out_fd[2])
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static void *ipc_thread(void *p)
|
static void *ipc_thread(void *p)
|
||||||
{
|
{
|
||||||
// Use PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE so message framing is
|
// Use PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE so message framing is
|
||||||
|
|
|
@ -123,6 +123,12 @@ void mp_subprocess_detached(struct mp_log *log, char **args)
|
||||||
talloc_free(p);
|
talloc_free(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void mp_subprocess2(struct mp_subprocess_opts *opts,
|
||||||
|
struct mp_subprocess_result *res)
|
||||||
|
{
|
||||||
|
*res = (struct mp_subprocess_result){.error = MP_SUBPROCESS_EUNSUPPORTED};
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const char *mp_subprocess_err_str(int num)
|
const char *mp_subprocess_err_str(int num)
|
||||||
|
|
|
@ -630,6 +630,7 @@ struct mp_script_args {
|
||||||
struct mp_scripting {
|
struct mp_scripting {
|
||||||
const char *name; // e.g. "lua script"
|
const char *name; // e.g. "lua script"
|
||||||
const char *file_ext; // e.g. "lua"
|
const char *file_ext; // e.g. "lua"
|
||||||
|
bool no_thread; // don't run load() on dedicated thread
|
||||||
int (*load)(struct mp_script_args *args);
|
int (*load)(struct mp_script_args *args);
|
||||||
};
|
};
|
||||||
bool mp_load_scripts(struct MPContext *mpctx);
|
bool mp_load_scripts(struct MPContext *mpctx);
|
||||||
|
|
|
@ -22,14 +22,17 @@
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include "osdep/io.h"
|
#include "osdep/io.h"
|
||||||
|
#include "osdep/subprocess.h"
|
||||||
#include "osdep/threads.h"
|
#include "osdep/threads.h"
|
||||||
|
|
||||||
#include "common/common.h"
|
#include "common/common.h"
|
||||||
#include "common/msg.h"
|
#include "common/msg.h"
|
||||||
|
#include "input/input.h"
|
||||||
#include "options/m_config.h"
|
#include "options/m_config.h"
|
||||||
#include "options/parse_configfile.h"
|
#include "options/parse_configfile.h"
|
||||||
#include "options/path.h"
|
#include "options/path.h"
|
||||||
|
@ -41,6 +44,7 @@
|
||||||
extern const struct mp_scripting mp_scripting_lua;
|
extern const struct mp_scripting mp_scripting_lua;
|
||||||
extern const struct mp_scripting mp_scripting_cplugin;
|
extern const struct mp_scripting mp_scripting_cplugin;
|
||||||
extern const struct mp_scripting mp_scripting_js;
|
extern const struct mp_scripting mp_scripting_js;
|
||||||
|
extern const struct mp_scripting mp_scripting_run;
|
||||||
|
|
||||||
static const struct mp_scripting *const scripting_backends[] = {
|
static const struct mp_scripting *const scripting_backends[] = {
|
||||||
#if HAVE_LUA
|
#if HAVE_LUA
|
||||||
|
@ -52,6 +56,7 @@ static const struct mp_scripting *const scripting_backends[] = {
|
||||||
#if HAVE_JAVASCRIPT
|
#if HAVE_JAVASCRIPT
|
||||||
&mp_scripting_js,
|
&mp_scripting_js,
|
||||||
#endif
|
#endif
|
||||||
|
&mp_scripting_run,
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -76,12 +81,8 @@ static char *script_name_from_filename(void *talloc_ctx, const char *fname)
|
||||||
return talloc_asprintf(talloc_ctx, "%s", name);
|
return talloc_asprintf(talloc_ctx, "%s", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *script_thread(void *p)
|
static void run_script(struct mp_script_args *arg)
|
||||||
{
|
{
|
||||||
pthread_detach(pthread_self());
|
|
||||||
|
|
||||||
struct mp_script_args *arg = p;
|
|
||||||
|
|
||||||
char name[90];
|
char name[90];
|
||||||
snprintf(name, sizeof(name), "%s (%s)", arg->backend->name,
|
snprintf(name, sizeof(name), "%s (%s)", arg->backend->name,
|
||||||
mpv_client_name(arg->client));
|
mpv_client_name(arg->client));
|
||||||
|
@ -92,6 +93,15 @@ static void *script_thread(void *p)
|
||||||
|
|
||||||
mpv_destroy(arg->client);
|
mpv_destroy(arg->client);
|
||||||
talloc_free(arg);
|
talloc_free(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *script_thread(void *p)
|
||||||
|
{
|
||||||
|
pthread_detach(pthread_self());
|
||||||
|
|
||||||
|
struct mp_script_args *arg = p;
|
||||||
|
run_script(arg);
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,12 +186,16 @@ static int mp_load_script(struct MPContext *mpctx, const char *fname)
|
||||||
|
|
||||||
MP_DBG(arg, "Loading %s %s...\n", backend->name, fname);
|
MP_DBG(arg, "Loading %s %s...\n", backend->name, fname);
|
||||||
|
|
||||||
|
if (backend->no_thread) {
|
||||||
|
run_script(arg);
|
||||||
|
} else {
|
||||||
pthread_t thread;
|
pthread_t thread;
|
||||||
if (pthread_create(&thread, NULL, script_thread, arg)) {
|
if (pthread_create(&thread, NULL, script_thread, arg)) {
|
||||||
mpv_destroy(arg->client);
|
mpv_destroy(arg->client);
|
||||||
talloc_free(arg);
|
talloc_free(arg);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -311,3 +325,58 @@ const struct mp_scripting mp_scripting_cplugin = {
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static int load_run(struct mp_script_args *args)
|
||||||
|
{
|
||||||
|
int fds[2];
|
||||||
|
if (!mp_ipc_start_anon_client(args->mpctx->ipc_ctx, args->client, fds))
|
||||||
|
return -1;
|
||||||
|
args->client = NULL; // ownership lost
|
||||||
|
|
||||||
|
// Hardcode them (according to opts.fds[]), because we want to allow clients
|
||||||
|
// to hardcode them if they want. Sue me.
|
||||||
|
char *fdopt = fds[1] >= 0 ? "--mpv-ipc-fd=3:4"
|
||||||
|
: "--mpv-ipc-fd=3";
|
||||||
|
|
||||||
|
struct mp_subprocess_opts opts = {
|
||||||
|
.exe = (char *)args->filename,
|
||||||
|
.args = (char *[]){(char *)args->filename, fdopt, NULL},
|
||||||
|
.fds = {
|
||||||
|
// Keep terminal stuff
|
||||||
|
{.fd = 0, .src_fd = 0,},
|
||||||
|
{.fd = 1, .src_fd = 1,},
|
||||||
|
{.fd = 2, .src_fd = 2,},
|
||||||
|
// Just hope these don't step over each other (e.g. fds[1] is not
|
||||||
|
// below 4, if the std FDs are missing).
|
||||||
|
{.fd = 3, .src_fd = fds[0], },
|
||||||
|
{.fd = 4, .src_fd = fds[1], },
|
||||||
|
},
|
||||||
|
.num_fds = fds[1] >= 0 ? 4 : 5,
|
||||||
|
.detach = true,
|
||||||
|
};
|
||||||
|
struct mp_subprocess_result res;
|
||||||
|
mp_subprocess2(&opts, &res);
|
||||||
|
|
||||||
|
// Closing these will (probably) make the client exit, if it really died.
|
||||||
|
// They _should_ be CLOEXEC, but are not, because
|
||||||
|
// posix_spawn_file_actions_adddup2() may not clear the CLOEXEC flag
|
||||||
|
// properly if by coincidence fd==src_fd.
|
||||||
|
close(fds[0]);
|
||||||
|
if (fds[1] >= 0)
|
||||||
|
close(fds[1]);
|
||||||
|
|
||||||
|
if (res.error < 0) {
|
||||||
|
MP_ERR(args, "Starting '%s' failed: %s\n", args->filename,
|
||||||
|
mp_subprocess_err_str(res.error));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct mp_scripting mp_scripting_run = {
|
||||||
|
.name = "spawned IPC process",
|
||||||
|
.file_ext = "run",
|
||||||
|
.no_thread = true,
|
||||||
|
.load = load_run,
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue