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 | } |