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" }
|
||||
|
||||
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_client_api;
|
||||
struct mpv_handle;
|
||||
|
||||
// Platform specific implementation, provided by ipc-*.c.
|
||||
struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api,
|
||||
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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ struct client_arg {
|
|||
struct mp_log *log;
|
||||
struct mpv_handle *client;
|
||||
|
||||
char *client_name;
|
||||
const char *client_name;
|
||||
int client_fd;
|
||||
bool close_client_fd;
|
||||
|
||||
|
@ -215,9 +215,11 @@ done:
|
|||
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)
|
||||
{
|
||||
client->client = mp_new_client(ctx->client_api, client->client_name);
|
||||
if (!client->client)
|
||||
client->client = mp_new_client(ctx->client_api, client->client_name);
|
||||
if (!client->client)
|
||||
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))
|
||||
goto err;
|
||||
|
||||
return;
|
||||
return true;
|
||||
|
||||
err:
|
||||
if (client->client)
|
||||
mpv_destroy(client->client);
|
||||
if (free_on_init_fail) {
|
||||
if (client->client)
|
||||
mpv_destroy(client->client);
|
||||
|
||||
if (client->close_client_fd)
|
||||
close(client->client_fd);
|
||||
if (client->close_client_fd)
|
||||
close(client->client_fd);
|
||||
}
|
||||
|
||||
talloc_free(client);
|
||||
return false;
|
||||
}
|
||||
|
||||
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_fd = fd,
|
||||
.close_client_fd = 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)
|
||||
|
@ -292,7 +323,7 @@ static void ipc_start_client_text(struct mp_ipc_ctx *ctx, const char *path)
|
|||
.writable = writable,
|
||||
};
|
||||
|
||||
ipc_start_client(ctx, client);
|
||||
ipc_start_client(ctx, client, true);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
void mp_subprocess2(struct mp_subprocess_opts *opts,
|
||||
struct mp_subprocess_result *res)
|
||||
{
|
||||
*res = (struct mp_subprocess_result){.error = MP_SUBPROCESS_EUNSUPPORTED};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
const char *mp_subprocess_err_str(int num)
|
||||
|
|
|
@ -630,6 +630,7 @@ struct mp_script_args {
|
|||
struct mp_scripting {
|
||||
const char *name; // e.g. "lua script"
|
||||
const char *file_ext; // e.g. "lua"
|
||||
bool no_thread; // don't run load() on dedicated thread
|
||||
int (*load)(struct mp_script_args *args);
|
||||
};
|
||||
bool mp_load_scripts(struct MPContext *mpctx);
|
||||
|
|
|
@ -22,14 +22,17 @@
|
|||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "osdep/io.h"
|
||||
#include "osdep/subprocess.h"
|
||||
#include "osdep/threads.h"
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/msg.h"
|
||||
#include "input/input.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/parse_configfile.h"
|
||||
#include "options/path.h"
|
||||
|
@ -41,6 +44,7 @@
|
|||
extern const struct mp_scripting mp_scripting_lua;
|
||||
extern const struct mp_scripting mp_scripting_cplugin;
|
||||
extern const struct mp_scripting mp_scripting_js;
|
||||
extern const struct mp_scripting mp_scripting_run;
|
||||
|
||||
static const struct mp_scripting *const scripting_backends[] = {
|
||||
#if HAVE_LUA
|
||||
|
@ -52,6 +56,7 @@ static const struct mp_scripting *const scripting_backends[] = {
|
|||
#if HAVE_JAVASCRIPT
|
||||
&mp_scripting_js,
|
||||
#endif
|
||||
&mp_scripting_run,
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@ -76,12 +81,8 @@ static char *script_name_from_filename(void *talloc_ctx, const char *fname)
|
|||
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];
|
||||
snprintf(name, sizeof(name), "%s (%s)", arg->backend->name,
|
||||
mpv_client_name(arg->client));
|
||||
|
@ -92,6 +93,15 @@ static void *script_thread(void *p)
|
|||
|
||||
mpv_destroy(arg->client);
|
||||
talloc_free(arg);
|
||||
}
|
||||
|
||||
static void *script_thread(void *p)
|
||||
{
|
||||
pthread_detach(pthread_self());
|
||||
|
||||
struct mp_script_args *arg = p;
|
||||
run_script(arg);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -176,11 +186,15 @@ static int mp_load_script(struct MPContext *mpctx, const char *fname)
|
|||
|
||||
MP_DBG(arg, "Loading %s %s...\n", backend->name, fname);
|
||||
|
||||
pthread_t thread;
|
||||
if (pthread_create(&thread, NULL, script_thread, arg)) {
|
||||
mpv_destroy(arg->client);
|
||||
talloc_free(arg);
|
||||
return -1;
|
||||
if (backend->no_thread) {
|
||||
run_script(arg);
|
||||
} else {
|
||||
pthread_t thread;
|
||||
if (pthread_create(&thread, NULL, script_thread, arg)) {
|
||||
mpv_destroy(arg->client);
|
||||
talloc_free(arg);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -311,3 +325,58 @@ const struct mp_scripting mp_scripting_cplugin = {
|
|||
};
|
||||
|
||||
#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