Coverage Report

Created: 2025-10-27 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mpv/input/ipc.c
Line
Count
Source
1
/*
2
 * This file is part of mpv.
3
 *
4
 * mpv is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6
 * License as published by the Free Software Foundation; either
7
 * version 2.1 of the License, or (at your option) any later version.
8
 *
9
 * mpv is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15
 * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17
18
#include <mpv/client.h>
19
20
#include "common/msg.h"
21
#include "input/input.h"
22
#include "misc/json.h"
23
#include "misc/node.h"
24
#include "options/m_option.h"
25
#include "options/options.h"
26
#include "options/path.h"
27
#include "player/client.h"
28
29
static mpv_node *mpv_node_array_get(mpv_node *src, int index)
30
0
{
31
0
    if (src->format != MPV_FORMAT_NODE_ARRAY)
32
0
        return NULL;
33
34
0
    if (src->u.list->num < (index + 1))
35
0
        return NULL;
36
37
0
    return &src->u.list->values[index];
38
0
}
39
40
static void mpv_node_map_add(void *ta_parent, mpv_node *src, const char *key, mpv_node *val)
41
0
{
42
0
    if (src->format != MPV_FORMAT_NODE_MAP)
43
0
        return;
44
45
0
    if (!src->u.list)
46
0
        src->u.list = talloc_zero(ta_parent, mpv_node_list);
47
48
0
    MP_TARRAY_GROW(src->u.list, src->u.list->keys, src->u.list->num);
49
0
    MP_TARRAY_GROW(src->u.list, src->u.list->values, src->u.list->num);
50
51
0
    src->u.list->keys[src->u.list->num] = talloc_strdup(ta_parent, key);
52
53
0
    static const struct m_option type = { .type = CONF_TYPE_NODE };
54
0
    m_option_get_node(&type, ta_parent, &src->u.list->values[src->u.list->num], val);
55
56
0
    src->u.list->num++;
57
0
}
58
59
static void mpv_node_map_add_null(void *ta_parent, mpv_node *src, const char *key)
60
0
{
61
0
    mpv_node val_node = {.format = MPV_FORMAT_NONE};
62
0
    mpv_node_map_add(ta_parent, src, key, &val_node);
63
0
}
64
65
static void mpv_node_map_add_int64(void *ta_parent, mpv_node *src, const char *key, int64_t val)
66
0
{
67
0
    mpv_node val_node = {.format = MPV_FORMAT_INT64, .u.int64 = val};
68
0
    mpv_node_map_add(ta_parent, src, key, &val_node);
69
0
}
70
71
static void mpv_node_map_add_string(void *ta_parent, mpv_node *src, const char *key, const char *val)
72
0
{
73
0
    mpv_node val_node = {.format = MPV_FORMAT_STRING, .u.string = (char*)val};
74
0
    mpv_node_map_add(ta_parent, src, key, &val_node);
75
0
}
76
77
// This is supposed to write a reply that looks like "normal" command execution.
78
static void mpv_format_command_reply(void *ta_parent, mpv_event *event,
79
                                     mpv_node *dst)
80
0
{
81
0
    mp_assert(event->event_id == MPV_EVENT_COMMAND_REPLY);
82
0
    mpv_event_command *cmd = event->data;
83
84
0
    mpv_node_map_add_int64(ta_parent, dst, "request_id", event->reply_userdata);
85
86
0
    mpv_node_map_add_string(ta_parent, dst, "error",
87
0
                            mpv_error_string(event->error));
88
89
0
    mpv_node_map_add(ta_parent, dst, "data", &cmd->result);
90
0
}
91
92
char *mp_json_encode_event(mpv_event *event)
93
0
{
94
0
    void *ta_parent = talloc_new(NULL);
95
96
0
    struct mpv_node event_node;
97
0
    if (event->event_id == MPV_EVENT_COMMAND_REPLY) {
98
0
        event_node = (mpv_node){.format = MPV_FORMAT_NODE_MAP, .u.list = NULL};
99
0
        mpv_format_command_reply(ta_parent, event, &event_node);
100
0
    } else {
101
0
        mpv_event_to_node(&event_node, event);
102
        // Abuse mpv_event_to_node() internals.
103
0
        talloc_steal(ta_parent, node_get_alloc(&event_node));
104
0
    }
105
106
0
    char *output = talloc_strdup(NULL, "");
107
0
    json_write(&output, &event_node);
108
0
    output = ta_talloc_strdup_append(output, "\n");
109
110
0
    talloc_free(ta_parent);
111
112
0
    return output;
113
0
}
114
115
// Function is allowed to modify src[n].
116
static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
117
                                  char *src)
118
0
{
119
0
    int rc;
120
0
    const char *cmd = NULL;
121
0
    struct mp_log *log = mp_client_get_log(client);
122
123
0
    mpv_node msg_node;
124
0
    mpv_node reply_node = {.format = MPV_FORMAT_NODE_MAP, .u.list = NULL};
125
0
    mpv_node *reqid_node = NULL;
126
0
    int64_t reqid = 0;
127
0
    mpv_node *async_node = NULL;
128
0
    bool async = false;
129
0
    bool send_reply = true;
130
131
0
    rc = json_parse(ta_parent, &msg_node, &src, MAX_JSON_DEPTH);
132
0
    if (rc < 0) {
133
0
        mp_err(log, "malformed JSON received: '%s'\n", src);
134
0
        rc = MPV_ERROR_INVALID_PARAMETER;
135
0
        goto error;
136
0
    }
137
138
0
    if (msg_node.format != MPV_FORMAT_NODE_MAP) {
139
0
        rc = MPV_ERROR_INVALID_PARAMETER;
140
0
        goto error;
141
0
    }
142
143
0
    async_node = node_map_get(&msg_node, "async");
144
0
    if (async_node) {
145
0
        if (async_node->format != MPV_FORMAT_FLAG) {
146
0
            rc = MPV_ERROR_INVALID_PARAMETER;
147
0
            goto error;
148
0
        }
149
0
        async = async_node->u.flag;
150
0
    }
151
152
0
    reqid_node = node_map_get(&msg_node, "request_id");
153
0
    if (reqid_node) {
154
0
        if (reqid_node->format == MPV_FORMAT_INT64) {
155
0
            reqid = reqid_node->u.int64;
156
0
        } else if (async) {
157
0
            mp_err(log, "'request_id' must be an integer for async commands.\n");
158
0
            rc = MPV_ERROR_INVALID_PARAMETER;
159
0
            goto error;
160
0
        } else {
161
0
            mp_warn(log, "'request_id' must be an integer. Using other types is "
162
0
                    "deprecated and will trigger an error in the future!\n");
163
0
        }
164
0
    }
165
166
0
    mpv_node *cmd_node = node_map_get(&msg_node, "command");
167
0
    if (!cmd_node) {
168
0
        rc = MPV_ERROR_INVALID_PARAMETER;
169
0
        goto error;
170
0
    }
171
172
0
    if (cmd_node->format == MPV_FORMAT_NODE_ARRAY) {
173
0
        mpv_node *cmd_str_node = mpv_node_array_get(cmd_node, 0);
174
0
        if (!cmd_str_node || (cmd_str_node->format != MPV_FORMAT_STRING)) {
175
0
            rc = MPV_ERROR_INVALID_PARAMETER;
176
0
            goto error;
177
0
        }
178
179
0
        cmd = cmd_str_node->u.string;
180
0
    }
181
182
0
    if (cmd && !strcmp("client_name", cmd)) {
183
0
        const char *client_name = mpv_client_name(client);
184
0
        mpv_node_map_add_string(ta_parent, &reply_node, "data", client_name);
185
0
        rc = MPV_ERROR_SUCCESS;
186
0
    } else if (cmd && !strcmp("get_time_us", cmd)) {
187
0
        int64_t time_us = mpv_get_time_us(client);
188
0
        mpv_node_map_add_int64(ta_parent, &reply_node, "data", time_us);
189
0
        rc = MPV_ERROR_SUCCESS;
190
0
    } else if (cmd && !strcmp("get_version", cmd)) {
191
0
        int64_t ver = mpv_client_api_version();
192
0
        mpv_node_map_add_int64(ta_parent, &reply_node, "data", ver);
193
0
        rc = MPV_ERROR_SUCCESS;
194
0
    } else if (cmd && !strcmp("get_property", cmd)) {
195
0
        mpv_node result_node;
196
197
0
        if (cmd_node->u.list->num != 2) {
198
0
            rc = MPV_ERROR_INVALID_PARAMETER;
199
0
            goto error;
200
0
        }
201
202
0
        if (cmd_node->u.list->values[1].format != MPV_FORMAT_STRING) {
203
0
            rc = MPV_ERROR_INVALID_PARAMETER;
204
0
            goto error;
205
0
        }
206
207
0
        rc = mpv_get_property(client, cmd_node->u.list->values[1].u.string,
208
0
                              MPV_FORMAT_NODE, &result_node);
209
0
        if (rc >= 0) {
210
0
            mpv_node_map_add(ta_parent, &reply_node, "data", &result_node);
211
0
            mpv_free_node_contents(&result_node);
212
0
        }
213
0
    } else if (cmd && !strcmp("get_property_string", cmd)) {
214
0
        if (cmd_node->u.list->num != 2) {
215
0
            rc = MPV_ERROR_INVALID_PARAMETER;
216
0
            goto error;
217
0
        }
218
219
0
        if (cmd_node->u.list->values[1].format != MPV_FORMAT_STRING) {
220
0
            rc = MPV_ERROR_INVALID_PARAMETER;
221
0
            goto error;
222
0
        }
223
224
0
        char *result = mpv_get_property_string(client,
225
0
                                        cmd_node->u.list->values[1].u.string);
226
0
        if (result) {
227
0
            mpv_node_map_add_string(ta_parent, &reply_node, "data", result);
228
0
            mpv_free(result);
229
0
        } else {
230
0
            mpv_node_map_add_null(ta_parent, &reply_node, "data");
231
0
        }
232
0
    } else if (cmd && (!strcmp("set_property", cmd) ||
233
0
                       !strcmp("set_property_string", cmd)))
234
0
    {
235
0
        if (cmd_node->u.list->num != 3) {
236
0
            rc = MPV_ERROR_INVALID_PARAMETER;
237
0
            goto error;
238
0
        }
239
240
0
        if (cmd_node->u.list->values[1].format != MPV_FORMAT_STRING) {
241
0
            rc = MPV_ERROR_INVALID_PARAMETER;
242
0
            goto error;
243
0
        }
244
245
0
        rc = mpv_set_property(client, cmd_node->u.list->values[1].u.string,
246
0
                              MPV_FORMAT_NODE, &cmd_node->u.list->values[2]);
247
0
    } else if (cmd && !strcmp("observe_property", cmd)) {
248
0
        if (cmd_node->u.list->num != 3) {
249
0
            rc = MPV_ERROR_INVALID_PARAMETER;
250
0
            goto error;
251
0
        }
252
253
0
        if (cmd_node->u.list->values[1].format != MPV_FORMAT_INT64) {
254
0
            rc = MPV_ERROR_INVALID_PARAMETER;
255
0
            goto error;
256
0
        }
257
258
0
        if (cmd_node->u.list->values[2].format != MPV_FORMAT_STRING) {
259
0
            rc = MPV_ERROR_INVALID_PARAMETER;
260
0
            goto error;
261
0
        }
262
263
0
        rc = mpv_observe_property(client,
264
0
                                  cmd_node->u.list->values[1].u.int64,
265
0
                                  cmd_node->u.list->values[2].u.string,
266
0
                                  MPV_FORMAT_NODE);
267
0
    } else if (cmd && !strcmp("observe_property_string", cmd)) {
268
0
        if (cmd_node->u.list->num != 3) {
269
0
            rc = MPV_ERROR_INVALID_PARAMETER;
270
0
            goto error;
271
0
        }
272
273
0
        if (cmd_node->u.list->values[1].format != MPV_FORMAT_INT64) {
274
0
            rc = MPV_ERROR_INVALID_PARAMETER;
275
0
            goto error;
276
0
        }
277
278
0
        if (cmd_node->u.list->values[2].format != MPV_FORMAT_STRING) {
279
0
            rc = MPV_ERROR_INVALID_PARAMETER;
280
0
            goto error;
281
0
        }
282
283
0
        rc = mpv_observe_property(client,
284
0
                                  cmd_node->u.list->values[1].u.int64,
285
0
                                  cmd_node->u.list->values[2].u.string,
286
0
                                  MPV_FORMAT_STRING);
287
0
    } else if (cmd && !strcmp("unobserve_property", cmd)) {
288
0
        if (cmd_node->u.list->num != 2) {
289
0
            rc = MPV_ERROR_INVALID_PARAMETER;
290
0
            goto error;
291
0
        }
292
293
0
        if (cmd_node->u.list->values[1].format != MPV_FORMAT_INT64) {
294
0
            rc = MPV_ERROR_INVALID_PARAMETER;
295
0
            goto error;
296
0
        }
297
298
0
        rc = mpv_unobserve_property(client,
299
0
                                    cmd_node->u.list->values[1].u.int64);
300
0
    } else if (cmd && !strcmp("request_log_messages", cmd)) {
301
0
        if (cmd_node->u.list->num != 2) {
302
0
            rc = MPV_ERROR_INVALID_PARAMETER;
303
0
            goto error;
304
0
        }
305
306
0
        if (cmd_node->u.list->values[1].format != MPV_FORMAT_STRING) {
307
0
            rc = MPV_ERROR_INVALID_PARAMETER;
308
0
            goto error;
309
0
        }
310
311
0
        rc = mpv_request_log_messages(client,
312
0
                                      cmd_node->u.list->values[1].u.string);
313
0
    } else if (cmd && (!strcmp("enable_event", cmd) ||
314
0
                       !strcmp("disable_event", cmd)))
315
0
    {
316
0
        bool enable = !strcmp("enable_event", cmd);
317
318
0
        if (cmd_node->u.list->num != 2) {
319
0
            rc = MPV_ERROR_INVALID_PARAMETER;
320
0
            goto error;
321
0
        }
322
323
0
        if (cmd_node->u.list->values[1].format != MPV_FORMAT_STRING) {
324
0
            rc = MPV_ERROR_INVALID_PARAMETER;
325
0
            goto error;
326
0
        }
327
328
0
        char *name = cmd_node->u.list->values[1].u.string;
329
0
        if (strcmp(name, "all") == 0) {
330
0
            for (int n = 0; n < 64; n++)
331
0
                mpv_request_event(client, n, enable);
332
0
            rc = MPV_ERROR_SUCCESS;
333
0
        } else {
334
0
            int event = -1;
335
0
            for (int n = 0; n < 64; n++) {
336
0
                const char *evname = mpv_event_name(n);
337
0
                if (evname && strcmp(evname, name) == 0)
338
0
                    event = n;
339
0
            }
340
0
            if (event < 0) {
341
0
                rc = MPV_ERROR_INVALID_PARAMETER;
342
0
                goto error;
343
0
            }
344
0
            rc = mpv_request_event(client, event, enable);
345
0
        }
346
0
    } else {
347
0
        mpv_node result_node = {0};
348
349
0
        if (async) {
350
0
            rc = mpv_command_node_async(client, reqid, cmd_node);
351
0
            if (rc >= 0)
352
0
                send_reply = false;
353
0
        } else {
354
0
            rc = mpv_command_node(client, cmd_node, &result_node);
355
0
            if (rc >= 0)
356
0
                mpv_node_map_add(ta_parent, &reply_node, "data", &result_node);
357
0
        }
358
359
0
        mpv_free_node_contents(&result_node);
360
0
    }
361
362
0
error:
363
    /* If the request contains a "request_id", copy it back into the response.
364
     * This makes it easier on the requester to match up the IPC results with
365
     * the original requests.
366
     */
367
0
    if (reqid_node) {
368
0
        mpv_node_map_add(ta_parent, &reply_node, "request_id", reqid_node);
369
0
    } else {
370
0
        mpv_node_map_add_int64(ta_parent, &reply_node, "request_id", 0);
371
0
    }
372
373
0
    mpv_node_map_add_string(ta_parent, &reply_node, "error", mpv_error_string(rc));
374
375
0
    char *output = talloc_strdup(ta_parent, "");
376
377
0
    if (send_reply) {
378
0
        json_write(&output, &reply_node);
379
0
        output = ta_talloc_strdup_append(output, "\n");
380
0
    }
381
382
0
    return output;
383
0
}
384
385
static char *text_execute_command(struct mpv_handle *client, void *tmp, char *src)
386
324
{
387
324
    mpv_command_string(client, src);
388
389
324
    return NULL;
390
324
}
391
392
char *mp_ipc_consume_next_command(struct mpv_handle *client, void *ctx, bstr *buf)
393
324
{
394
324
    void *tmp = talloc_new(NULL);
395
396
324
    bstr rest;
397
324
    bstr line = bstr_getline(*buf, &rest);
398
324
    char *line0 = bstrto0(tmp, line);
399
324
    talloc_steal(tmp, buf->start);
400
324
    *buf = bstrdup(NULL, rest);
401
402
324
    json_skip_whitespace(&line0);
403
404
324
    char *reply_msg = NULL;
405
324
    if (line0[0] == '\0' || line0[0] == '#') {
406
        // skip
407
324
    } else if (line0[0] == '{') {
408
0
        reply_msg = json_execute_command(client, tmp, line0);
409
324
    } else {
410
324
        reply_msg = text_execute_command(client, tmp, line0);
411
324
    }
412
413
324
    talloc_steal(ctx, reply_msg);
414
324
    talloc_free(tmp);
415
324
    return reply_msg;
416
324
}