summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--DOCS/man/ipc.rst27
-rw-r--r--input/input.h10
-rw-r--r--input/ipc-dummy.c6
-rw-r--r--input/ipc-unix.c51
-rw-r--r--input/ipc-win.c6
-rw-r--r--osdep/subprocess.c6
-rw-r--r--player/core.h1
-rw-r--r--player/scripting.c89
8 files changed, 176 insertions, 20 deletions
diff --git a/DOCS/man/ipc.rst b/DOCS/man/ipc.rst
index 2aa406b190..42e67f6e4a 100644
--- a/DOCS/man/ipc.rst
+++ b/DOCS/man/ipc.rst
@@ -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.
diff --git a/input/input.h b/input/input.h
index df51cb7ed4..1a90a47d42 100644
--- a/input/input.h
+++ b/input/input.h
@@ -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.
diff --git a/input/ipc-dummy.c b/input/ipc-dummy.c
index d9c31c046c..f0232b2f6e 100644
--- a/input/ipc-dummy.c
+++ b/input/ipc-dummy.c
@@ -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)
{
}
diff --git a/input/ipc-unix.c b/input/ipc-unix.c
index ef478ba35e..0a7f2a5838 100644
--- a/input/ipc-unix.c
+++ b/input/ipc-unix.c
@@ -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, 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,
};
- ipc_start_client(ctx, client);
+ 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)
diff --git a/input/ipc-win.c b/input/ipc-win.c
index 727a8cca73..9672ec85fe 100644
--- a/input/ipc-win.c
+++ b/input/ipc-win.c
@@ -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
diff --git a/osdep/subprocess.c b/osdep/subprocess.c
index 8bb2acd0b7..8a930a60df 100644
--- a/osdep/subprocess.c
+++ b/osdep/subprocess.c
@@ -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)
diff --git a/player/core.h b/player/core.h
index 70fc33448e..d669d2d031 100644
--- a/player/core.h
+++ b/player/core.h
@@ -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);
diff --git a/player/scripting.c b/player/scripting.c
index d83c4d241f..c42706a0b9 100644
--- a/player/scripting.c
+++ b/player/scripting.c
@@ -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,
+};