mirror of
https://github.com/mpv-player/mpv
synced 2024-12-27 17:42:17 +00:00
5bf473d5ca
This implements the JSON IPC protocol with named pipes, which are probably the closest Windows equivalent to Unix domain sockets in terms of functionality. Like with Unix sockets, this will allow mpv to listen for IPC connections and handle multiple IPC clients at once. A few cross platform libraries and frameworks (Qt, node.js) use named pipes for IPC on Windows and Unix sockets on Linux and Unix, so hopefully this will ease the creation of portable JSON IPC clients. Unlike the Unix implementation, this doesn't share code with --input-file, meaning --input-file on Windows won't understand JSON commands (yet.) Sharing code and removing the separate implementation in pipe-win32.c is definitely a possible future improvement.
484 lines
15 KiB
C
484 lines
15 KiB
C
/*
|
|
* This file is part of mpv.
|
|
*
|
|
* mpv is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 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 Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "common/msg.h"
|
|
#include "input/input.h"
|
|
#include "misc/json.h"
|
|
#include "options/m_option.h"
|
|
#include "options/options.h"
|
|
#include "options/path.h"
|
|
#include "player/client.h"
|
|
|
|
static mpv_node *mpv_node_map_get(mpv_node *src, const char *key)
|
|
{
|
|
if (src->format != MPV_FORMAT_NODE_MAP)
|
|
return NULL;
|
|
|
|
for (int i = 0; i < src->u.list->num; i++)
|
|
if (!strcmp(key, src->u.list->keys[i]))
|
|
return &src->u.list->values[i];
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static mpv_node *mpv_node_array_get(mpv_node *src, int index)
|
|
{
|
|
if (src->format != MPV_FORMAT_NODE_ARRAY)
|
|
return NULL;
|
|
|
|
if (src->u.list->num < (index + 1))
|
|
return NULL;
|
|
|
|
return &src->u.list->values[index];
|
|
}
|
|
|
|
static void mpv_node_array_add(void *ta_parent, mpv_node *src, mpv_node *val)
|
|
{
|
|
if (src->format != MPV_FORMAT_NODE_ARRAY)
|
|
return;
|
|
|
|
if (!src->u.list)
|
|
src->u.list = talloc_zero(ta_parent, mpv_node_list);
|
|
|
|
MP_TARRAY_GROW(src->u.list, src->u.list->values, src->u.list->num);
|
|
|
|
static const struct m_option type = { .type = CONF_TYPE_NODE };
|
|
m_option_get_node(&type, ta_parent, &src->u.list->values[src->u.list->num], val);
|
|
|
|
src->u.list->num++;
|
|
}
|
|
|
|
static void mpv_node_array_add_string(void *ta_parent, mpv_node *src, const char *val)
|
|
{
|
|
mpv_node val_node = {.format = MPV_FORMAT_STRING, .u.string = (char *)val};
|
|
mpv_node_array_add(ta_parent, src, &val_node);
|
|
}
|
|
|
|
static void mpv_node_map_add(void *ta_parent, mpv_node *src, const char *key, mpv_node *val)
|
|
{
|
|
if (src->format != MPV_FORMAT_NODE_MAP)
|
|
return;
|
|
|
|
if (!src->u.list)
|
|
src->u.list = talloc_zero(ta_parent, mpv_node_list);
|
|
|
|
MP_TARRAY_GROW(src->u.list, src->u.list->keys, src->u.list->num);
|
|
MP_TARRAY_GROW(src->u.list, src->u.list->values, src->u.list->num);
|
|
|
|
src->u.list->keys[src->u.list->num] = talloc_strdup(ta_parent, key);
|
|
|
|
static const struct m_option type = { .type = CONF_TYPE_NODE };
|
|
m_option_get_node(&type, ta_parent, &src->u.list->values[src->u.list->num], val);
|
|
|
|
src->u.list->num++;
|
|
}
|
|
|
|
static void mpv_node_map_add_null(void *ta_parent, mpv_node *src, const char *key)
|
|
{
|
|
mpv_node val_node = {.format = MPV_FORMAT_NONE};
|
|
mpv_node_map_add(ta_parent, src, key, &val_node);
|
|
}
|
|
|
|
static void mpv_node_map_add_flag(void *ta_parent, mpv_node *src, const char *key, bool val)
|
|
{
|
|
mpv_node val_node = {.format = MPV_FORMAT_FLAG, .u.flag = val};
|
|
|
|
mpv_node_map_add(ta_parent, src, key, &val_node);
|
|
}
|
|
|
|
static void mpv_node_map_add_int64(void *ta_parent, mpv_node *src, const char *key, int64_t val)
|
|
{
|
|
mpv_node val_node = {.format = MPV_FORMAT_INT64, .u.int64 = val};
|
|
mpv_node_map_add(ta_parent, src, key, &val_node);
|
|
}
|
|
|
|
static void mpv_node_map_add_double(void *ta_parent, mpv_node *src, const char *key, double val)
|
|
{
|
|
mpv_node val_node = {.format = MPV_FORMAT_DOUBLE, .u.double_ = val};
|
|
mpv_node_map_add(ta_parent, src, key, &val_node);
|
|
}
|
|
|
|
static void mpv_node_map_add_string(void *ta_parent, mpv_node *src, const char *key, const char *val)
|
|
{
|
|
mpv_node val_node = {.format = MPV_FORMAT_STRING, .u.string = (char*)val};
|
|
mpv_node_map_add(ta_parent, src, key, &val_node);
|
|
}
|
|
|
|
static void mpv_event_to_node(void *ta_parent, mpv_event *event, mpv_node *dst)
|
|
{
|
|
mpv_node_map_add_string(ta_parent, dst, "event", mpv_event_name(event->event_id));
|
|
|
|
if (event->reply_userdata)
|
|
mpv_node_map_add_int64(ta_parent, dst, "id", event->reply_userdata);
|
|
|
|
if (event->error < 0)
|
|
mpv_node_map_add_string(ta_parent, dst, "error", mpv_error_string(event->error));
|
|
|
|
switch (event->event_id) {
|
|
case MPV_EVENT_LOG_MESSAGE: {
|
|
mpv_event_log_message *msg = event->data;
|
|
|
|
mpv_node_map_add_string(ta_parent, dst, "prefix", msg->prefix);
|
|
mpv_node_map_add_string(ta_parent, dst, "level", msg->level);
|
|
mpv_node_map_add_string(ta_parent, dst, "text", msg->text);
|
|
|
|
break;
|
|
}
|
|
|
|
case MPV_EVENT_CLIENT_MESSAGE: {
|
|
mpv_event_client_message *msg = event->data;
|
|
|
|
mpv_node args_node = {.format = MPV_FORMAT_NODE_ARRAY, .u.list = NULL};
|
|
for (int n = 0; n < msg->num_args; n++)
|
|
mpv_node_array_add_string(ta_parent, &args_node, msg->args[n]);
|
|
mpv_node_map_add(ta_parent, dst, "args", &args_node);
|
|
break;
|
|
}
|
|
|
|
case MPV_EVENT_PROPERTY_CHANGE: {
|
|
mpv_event_property *prop = event->data;
|
|
|
|
mpv_node_map_add_string(ta_parent, dst, "name", prop->name);
|
|
|
|
switch (prop->format) {
|
|
case MPV_FORMAT_NODE:
|
|
mpv_node_map_add(ta_parent, dst, "data", prop->data);
|
|
break;
|
|
case MPV_FORMAT_DOUBLE:
|
|
mpv_node_map_add_double(ta_parent, dst, "data", *(double *)prop->data);
|
|
break;
|
|
case MPV_FORMAT_FLAG:
|
|
mpv_node_map_add_flag(ta_parent, dst, "data", *(int *)prop->data);
|
|
break;
|
|
case MPV_FORMAT_STRING:
|
|
mpv_node_map_add_string(ta_parent, dst, "data", *(char **)prop->data);
|
|
break;
|
|
default:
|
|
mpv_node_map_add_null(ta_parent, dst, "data");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
char *mp_json_encode_event(mpv_event *event)
|
|
{
|
|
void *ta_parent = talloc_new(NULL);
|
|
mpv_node event_node = {.format = MPV_FORMAT_NODE_MAP, .u.list = NULL};
|
|
|
|
mpv_event_to_node(ta_parent, event, &event_node);
|
|
|
|
char *output = talloc_strdup(NULL, "");
|
|
json_write(&output, &event_node);
|
|
output = ta_talloc_strdup_append(output, "\n");
|
|
|
|
talloc_free(ta_parent);
|
|
|
|
return output;
|
|
}
|
|
|
|
// Function is allowed to modify src[n].
|
|
static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
|
|
char *src)
|
|
{
|
|
int rc;
|
|
const char *cmd = NULL;
|
|
struct mp_log *log = mp_client_get_log(client);
|
|
|
|
mpv_node msg_node;
|
|
mpv_node reply_node = {.format = MPV_FORMAT_NODE_MAP, .u.list = NULL};
|
|
mpv_node *reqid_node = NULL;
|
|
|
|
rc = json_parse(ta_parent, &msg_node, &src, 3);
|
|
if (rc < 0) {
|
|
mp_err(log, "malformed JSON received\n");
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
if (msg_node.format != MPV_FORMAT_NODE_MAP) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
reqid_node = mpv_node_map_get(&msg_node, "request_id");
|
|
|
|
mpv_node *cmd_node = mpv_node_map_get(&msg_node, "command");
|
|
if (!cmd_node ||
|
|
(cmd_node->format != MPV_FORMAT_NODE_ARRAY) ||
|
|
!cmd_node->u.list->num)
|
|
{
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
mpv_node *cmd_str_node = mpv_node_array_get(cmd_node, 0);
|
|
if (!cmd_str_node || (cmd_str_node->format != MPV_FORMAT_STRING)) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
cmd = cmd_str_node->u.string;
|
|
|
|
if (!strcmp("client_name", cmd)) {
|
|
const char *client_name = mpv_client_name(client);
|
|
mpv_node_map_add_string(ta_parent, &reply_node, "data", client_name);
|
|
rc = MPV_ERROR_SUCCESS;
|
|
} else if (!strcmp("get_time_us", cmd)) {
|
|
int64_t time_us = mpv_get_time_us(client);
|
|
mpv_node_map_add_int64(ta_parent, &reply_node, "data", time_us);
|
|
rc = MPV_ERROR_SUCCESS;
|
|
} else if (!strcmp("get_version", cmd)) {
|
|
int64_t ver = mpv_client_api_version();
|
|
mpv_node_map_add_int64(ta_parent, &reply_node, "data", ver);
|
|
rc = MPV_ERROR_SUCCESS;
|
|
} else if (!strcmp("get_property", cmd)) {
|
|
mpv_node result_node;
|
|
|
|
if (cmd_node->u.list->num != 2) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
if (cmd_node->u.list->values[1].format != MPV_FORMAT_STRING) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
rc = mpv_get_property(client, cmd_node->u.list->values[1].u.string,
|
|
MPV_FORMAT_NODE, &result_node);
|
|
if (rc >= 0) {
|
|
mpv_node_map_add(ta_parent, &reply_node, "data", &result_node);
|
|
mpv_free_node_contents(&result_node);
|
|
}
|
|
} else if (!strcmp("get_property_string", cmd)) {
|
|
if (cmd_node->u.list->num != 2) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
if (cmd_node->u.list->values[1].format != MPV_FORMAT_STRING) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
char *result = mpv_get_property_string(client,
|
|
cmd_node->u.list->values[1].u.string);
|
|
if (!result) {
|
|
mpv_node_map_add_null(ta_parent, &reply_node, "data");
|
|
} else {
|
|
mpv_node_map_add_string(ta_parent, &reply_node, "data", result);
|
|
mpv_free(result);
|
|
}
|
|
} else if (!strcmp("set_property", cmd)) {
|
|
if (cmd_node->u.list->num != 3) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
if (cmd_node->u.list->values[1].format != MPV_FORMAT_STRING) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
rc = mpv_set_property(client, cmd_node->u.list->values[1].u.string,
|
|
MPV_FORMAT_NODE, &cmd_node->u.list->values[2]);
|
|
} else if (!strcmp("set_property_string", cmd)) {
|
|
if (cmd_node->u.list->num != 3) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
if (cmd_node->u.list->values[1].format != MPV_FORMAT_STRING) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
if (cmd_node->u.list->values[2].format != MPV_FORMAT_STRING) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
rc = mpv_set_property_string(client,
|
|
cmd_node->u.list->values[1].u.string,
|
|
cmd_node->u.list->values[2].u.string);
|
|
} else if (!strcmp("observe_property", cmd)) {
|
|
if (cmd_node->u.list->num != 3) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
if (cmd_node->u.list->values[1].format != MPV_FORMAT_INT64) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
if (cmd_node->u.list->values[2].format != MPV_FORMAT_STRING) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
rc = mpv_observe_property(client,
|
|
cmd_node->u.list->values[1].u.int64,
|
|
cmd_node->u.list->values[2].u.string,
|
|
MPV_FORMAT_NODE);
|
|
} else if (!strcmp("observe_property_string", cmd)) {
|
|
if (cmd_node->u.list->num != 3) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
if (cmd_node->u.list->values[1].format != MPV_FORMAT_INT64) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
if (cmd_node->u.list->values[2].format != MPV_FORMAT_STRING) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
rc = mpv_observe_property(client,
|
|
cmd_node->u.list->values[1].u.int64,
|
|
cmd_node->u.list->values[2].u.string,
|
|
MPV_FORMAT_STRING);
|
|
} else if (!strcmp("unobserve_property", cmd)) {
|
|
if (cmd_node->u.list->num != 2) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
if (cmd_node->u.list->values[1].format != MPV_FORMAT_INT64) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
rc = mpv_unobserve_property(client,
|
|
cmd_node->u.list->values[1].u.int64);
|
|
} else if (!strcmp("request_log_messages", cmd)) {
|
|
if (cmd_node->u.list->num != 2) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
if (cmd_node->u.list->values[1].format != MPV_FORMAT_STRING) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
rc = mpv_request_log_messages(client,
|
|
cmd_node->u.list->values[1].u.string);
|
|
} else if (!strcmp("suspend", cmd)) {
|
|
mpv_suspend(client);
|
|
rc = MPV_ERROR_SUCCESS;
|
|
} else if (!strcmp("resume", cmd)) {
|
|
mpv_resume(client);
|
|
rc = MPV_ERROR_SUCCESS;
|
|
} else if (!strcmp("enable_event", cmd) ||
|
|
!strcmp("disable_event", cmd))
|
|
{
|
|
bool enable = !strcmp("enable_event", cmd);
|
|
|
|
if (cmd_node->u.list->num != 2) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
if (cmd_node->u.list->values[1].format != MPV_FORMAT_STRING) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
char *name = cmd_node->u.list->values[1].u.string;
|
|
if (strcmp(name, "all") == 0) {
|
|
for (int n = 0; n < 64; n++)
|
|
mpv_request_event(client, n, enable);
|
|
rc = MPV_ERROR_SUCCESS;
|
|
} else {
|
|
int event = -1;
|
|
for (int n = 0; n < 64; n++) {
|
|
const char *evname = mpv_event_name(n);
|
|
if (evname && strcmp(evname, name) == 0)
|
|
event = n;
|
|
}
|
|
if (event < 0) {
|
|
rc = MPV_ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
rc = mpv_request_event(client, event, enable);
|
|
}
|
|
} else {
|
|
mpv_node result_node;
|
|
|
|
rc = mpv_command_node(client, cmd_node, &result_node);
|
|
if (rc >= 0)
|
|
mpv_node_map_add(ta_parent, &reply_node, "data", &result_node);
|
|
}
|
|
|
|
error:
|
|
/* If the request contains a "request_id", copy it back into the response.
|
|
* This makes it easier on the requester to match up the IPC results with
|
|
* the original requests.
|
|
*/
|
|
if (reqid_node) {
|
|
mpv_node_map_add(ta_parent, &reply_node, "request_id", reqid_node);
|
|
}
|
|
|
|
mpv_node_map_add_string(ta_parent, &reply_node, "error", mpv_error_string(rc));
|
|
|
|
char *output = talloc_strdup(ta_parent, "");
|
|
json_write(&output, &reply_node);
|
|
output = ta_talloc_strdup_append(output, "\n");
|
|
|
|
return output;
|
|
}
|
|
|
|
static char *text_execute_command(struct mpv_handle *client, void *tmp, char *src)
|
|
{
|
|
mpv_command_string(client, src);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
char *mp_ipc_consume_next_command(struct mpv_handle *client, void *ctx, bstr *buf)
|
|
{
|
|
void *tmp = talloc_new(NULL);
|
|
|
|
bstr rest;
|
|
bstr line = bstr_getline(*buf, &rest);
|
|
char *line0 = bstrto0(tmp, line);
|
|
talloc_steal(tmp, buf->start);
|
|
*buf = bstrdup(NULL, rest);
|
|
|
|
json_skip_whitespace(&line0);
|
|
|
|
char *reply_msg = NULL;
|
|
if (line0[0] == '\0' || line0[0] == '#') {
|
|
// skip
|
|
} else if (line0[0] == '{') {
|
|
reply_msg = json_execute_command(client, tmp, line0);
|
|
} else {
|
|
reply_msg = text_execute_command(client, tmp, line0);
|
|
}
|
|
|
|
talloc_steal(ctx, reply_msg);
|
|
talloc_free(tmp);
|
|
return reply_msg;
|
|
}
|