2023-01-09 20:03:15 +01:00
|
|
|
/*
|
|
|
|
* HMP commands related to UI
|
|
|
|
*
|
|
|
|
* Copyright IBM, Corp. 2011
|
|
|
|
*
|
|
|
|
* Authors:
|
|
|
|
* Anthony Liguori <aliguori@us.ibm.com>
|
|
|
|
*
|
|
|
|
* This work is licensed under the terms of the GNU GPL, version 2. See
|
|
|
|
* the COPYING file in the top-level directory.
|
|
|
|
*
|
|
|
|
* Contributions after 2012-01-13 are licensed under the terms of the
|
|
|
|
* GNU GPL, version 2 or (at your option) any later version.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "qemu/osdep.h"
|
|
|
|
#ifdef CONFIG_SPICE
|
|
|
|
#include <spice/enums.h>
|
|
|
|
#endif
|
|
|
|
#include "monitor/hmp.h"
|
2023-01-09 20:03:17 +01:00
|
|
|
#include "monitor/monitor-internal.h"
|
|
|
|
#include "qapi/error.h"
|
2023-01-09 20:03:15 +01:00
|
|
|
#include "qapi/qapi-commands-ui.h"
|
|
|
|
#include "qapi/qmp/qdict.h"
|
|
|
|
#include "qemu/cutils.h"
|
|
|
|
#include "ui/console.h"
|
|
|
|
#include "ui/input.h"
|
|
|
|
|
|
|
|
static int mouse_button_state;
|
|
|
|
|
|
|
|
void hmp_mouse_move(Monitor *mon, const QDict *qdict)
|
|
|
|
{
|
|
|
|
int dx, dy, dz, button;
|
|
|
|
const char *dx_str = qdict_get_str(qdict, "dx_str");
|
|
|
|
const char *dy_str = qdict_get_str(qdict, "dy_str");
|
|
|
|
const char *dz_str = qdict_get_try_str(qdict, "dz_str");
|
|
|
|
|
|
|
|
dx = strtol(dx_str, NULL, 0);
|
|
|
|
dy = strtol(dy_str, NULL, 0);
|
|
|
|
qemu_input_queue_rel(NULL, INPUT_AXIS_X, dx);
|
|
|
|
qemu_input_queue_rel(NULL, INPUT_AXIS_Y, dy);
|
|
|
|
|
|
|
|
if (dz_str) {
|
|
|
|
dz = strtol(dz_str, NULL, 0);
|
|
|
|
if (dz != 0) {
|
|
|
|
button = (dz > 0) ? INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN;
|
|
|
|
qemu_input_queue_btn(NULL, button, true);
|
|
|
|
qemu_input_event_sync();
|
|
|
|
qemu_input_queue_btn(NULL, button, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
qemu_input_event_sync();
|
|
|
|
}
|
|
|
|
|
|
|
|
void hmp_mouse_button(Monitor *mon, const QDict *qdict)
|
|
|
|
{
|
|
|
|
static uint32_t bmap[INPUT_BUTTON__MAX] = {
|
|
|
|
[INPUT_BUTTON_LEFT] = MOUSE_EVENT_LBUTTON,
|
|
|
|
[INPUT_BUTTON_MIDDLE] = MOUSE_EVENT_MBUTTON,
|
|
|
|
[INPUT_BUTTON_RIGHT] = MOUSE_EVENT_RBUTTON,
|
|
|
|
};
|
|
|
|
int button_state = qdict_get_int(qdict, "button_state");
|
|
|
|
|
|
|
|
if (mouse_button_state == button_state) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
qemu_input_update_buttons(NULL, bmap, mouse_button_state, button_state);
|
|
|
|
qemu_input_event_sync();
|
|
|
|
mouse_button_state = button_state;
|
|
|
|
}
|
|
|
|
|
2023-01-09 20:03:20 +01:00
|
|
|
void hmp_mouse_set(Monitor *mon, const QDict *qdict)
|
|
|
|
{
|
|
|
|
Error *err = NULL;
|
|
|
|
|
|
|
|
qemu_mouse_set(qdict_get_int(qdict, "index"), &err);
|
|
|
|
hmp_handle_error(mon, err);
|
|
|
|
}
|
|
|
|
|
2023-01-09 20:03:15 +01:00
|
|
|
void hmp_info_mice(Monitor *mon, const QDict *qdict)
|
|
|
|
{
|
|
|
|
MouseInfoList *mice_list, *mouse;
|
|
|
|
|
|
|
|
mice_list = qmp_query_mice(NULL);
|
|
|
|
if (!mice_list) {
|
|
|
|
monitor_printf(mon, "No mouse devices connected\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (mouse = mice_list; mouse; mouse = mouse->next) {
|
|
|
|
monitor_printf(mon, "%c Mouse #%" PRId64 ": %s%s\n",
|
|
|
|
mouse->value->current ? '*' : ' ',
|
|
|
|
mouse->value->index, mouse->value->name,
|
|
|
|
mouse->value->absolute ? " (absolute)" : "");
|
|
|
|
}
|
|
|
|
|
|
|
|
qapi_free_MouseInfoList(mice_list);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_VNC
|
|
|
|
/* Helper for hmp_info_vnc_clients, _servers */
|
|
|
|
static void hmp_info_VncBasicInfo(Monitor *mon, VncBasicInfo *info,
|
|
|
|
const char *name)
|
|
|
|
{
|
|
|
|
monitor_printf(mon, " %s: %s:%s (%s%s)\n",
|
|
|
|
name,
|
|
|
|
info->host,
|
|
|
|
info->service,
|
|
|
|
NetworkAddressFamily_str(info->family),
|
|
|
|
info->websocket ? " (Websocket)" : "");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Helper displaying and auth and crypt info */
|
|
|
|
static void hmp_info_vnc_authcrypt(Monitor *mon, const char *indent,
|
|
|
|
VncPrimaryAuth auth,
|
|
|
|
VncVencryptSubAuth *vencrypt)
|
|
|
|
{
|
|
|
|
monitor_printf(mon, "%sAuth: %s (Sub: %s)\n", indent,
|
|
|
|
VncPrimaryAuth_str(auth),
|
|
|
|
vencrypt ? VncVencryptSubAuth_str(*vencrypt) : "none");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hmp_info_vnc_clients(Monitor *mon, VncClientInfoList *client)
|
|
|
|
{
|
|
|
|
while (client) {
|
|
|
|
VncClientInfo *cinfo = client->value;
|
|
|
|
|
|
|
|
hmp_info_VncBasicInfo(mon, qapi_VncClientInfo_base(cinfo), "Client");
|
|
|
|
monitor_printf(mon, " x509_dname: %s\n",
|
|
|
|
cinfo->x509_dname ?: "none");
|
|
|
|
monitor_printf(mon, " sasl_username: %s\n",
|
|
|
|
cinfo->sasl_username ?: "none");
|
|
|
|
|
|
|
|
client = client->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hmp_info_vnc_servers(Monitor *mon, VncServerInfo2List *server)
|
|
|
|
{
|
|
|
|
while (server) {
|
|
|
|
VncServerInfo2 *sinfo = server->value;
|
|
|
|
hmp_info_VncBasicInfo(mon, qapi_VncServerInfo2_base(sinfo), "Server");
|
|
|
|
hmp_info_vnc_authcrypt(mon, " ", sinfo->auth,
|
|
|
|
sinfo->has_vencrypt ? &sinfo->vencrypt : NULL);
|
|
|
|
server = server->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void hmp_info_vnc(Monitor *mon, const QDict *qdict)
|
|
|
|
{
|
|
|
|
VncInfo2List *info2l, *info2l_head;
|
|
|
|
Error *err = NULL;
|
|
|
|
|
|
|
|
info2l = qmp_query_vnc_servers(&err);
|
|
|
|
info2l_head = info2l;
|
|
|
|
if (hmp_handle_error(mon, err)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!info2l) {
|
|
|
|
monitor_printf(mon, "None\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (info2l) {
|
|
|
|
VncInfo2 *info = info2l->value;
|
|
|
|
monitor_printf(mon, "%s:\n", info->id);
|
|
|
|
hmp_info_vnc_servers(mon, info->server);
|
|
|
|
hmp_info_vnc_clients(mon, info->clients);
|
|
|
|
if (!info->server) {
|
|
|
|
/*
|
|
|
|
* The server entry displays its auth, we only need to
|
|
|
|
* display in the case of 'reverse' connections where
|
|
|
|
* there's no server.
|
|
|
|
*/
|
|
|
|
hmp_info_vnc_authcrypt(mon, " ", info->auth,
|
|
|
|
info->has_vencrypt ? &info->vencrypt : NULL);
|
|
|
|
}
|
|
|
|
if (info->display) {
|
|
|
|
monitor_printf(mon, " Display: %s\n", info->display);
|
|
|
|
}
|
|
|
|
info2l = info2l->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
qapi_free_VncInfo2List(info2l_head);
|
|
|
|
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef CONFIG_SPICE
|
|
|
|
void hmp_info_spice(Monitor *mon, const QDict *qdict)
|
|
|
|
{
|
|
|
|
SpiceChannelList *chan;
|
|
|
|
SpiceInfo *info;
|
|
|
|
const char *channel_name;
|
|
|
|
static const char *const channel_names[] = {
|
|
|
|
[SPICE_CHANNEL_MAIN] = "main",
|
|
|
|
[SPICE_CHANNEL_DISPLAY] = "display",
|
|
|
|
[SPICE_CHANNEL_INPUTS] = "inputs",
|
|
|
|
[SPICE_CHANNEL_CURSOR] = "cursor",
|
|
|
|
[SPICE_CHANNEL_PLAYBACK] = "playback",
|
|
|
|
[SPICE_CHANNEL_RECORD] = "record",
|
|
|
|
[SPICE_CHANNEL_TUNNEL] = "tunnel",
|
|
|
|
[SPICE_CHANNEL_SMARTCARD] = "smartcard",
|
|
|
|
[SPICE_CHANNEL_USBREDIR] = "usbredir",
|
|
|
|
[SPICE_CHANNEL_PORT] = "port",
|
|
|
|
[SPICE_CHANNEL_WEBDAV] = "webdav",
|
|
|
|
};
|
|
|
|
|
|
|
|
info = qmp_query_spice(NULL);
|
|
|
|
|
|
|
|
if (!info->enabled) {
|
|
|
|
monitor_printf(mon, "Server: disabled\n");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
monitor_printf(mon, "Server:\n");
|
|
|
|
if (info->has_port) {
|
|
|
|
monitor_printf(mon, " address: %s:%" PRId64 "\n",
|
|
|
|
info->host, info->port);
|
|
|
|
}
|
|
|
|
if (info->has_tls_port) {
|
|
|
|
monitor_printf(mon, " address: %s:%" PRId64 " [tls]\n",
|
|
|
|
info->host, info->tls_port);
|
|
|
|
}
|
|
|
|
monitor_printf(mon, " migrated: %s\n",
|
|
|
|
info->migrated ? "true" : "false");
|
|
|
|
monitor_printf(mon, " auth: %s\n", info->auth);
|
|
|
|
monitor_printf(mon, " compiled: %s\n", info->compiled_version);
|
|
|
|
monitor_printf(mon, " mouse-mode: %s\n",
|
|
|
|
SpiceQueryMouseMode_str(info->mouse_mode));
|
|
|
|
|
|
|
|
if (!info->has_channels || info->channels == NULL) {
|
|
|
|
monitor_printf(mon, "Channels: none\n");
|
|
|
|
} else {
|
|
|
|
for (chan = info->channels; chan; chan = chan->next) {
|
|
|
|
monitor_printf(mon, "Channel:\n");
|
|
|
|
monitor_printf(mon, " address: %s:%s%s\n",
|
|
|
|
chan->value->host, chan->value->port,
|
|
|
|
chan->value->tls ? " [tls]" : "");
|
|
|
|
monitor_printf(mon, " session: %" PRId64 "\n",
|
|
|
|
chan->value->connection_id);
|
|
|
|
monitor_printf(mon, " channel: %" PRId64 ":%" PRId64 "\n",
|
|
|
|
chan->value->channel_type, chan->value->channel_id);
|
|
|
|
|
|
|
|
channel_name = "unknown";
|
|
|
|
if (chan->value->channel_type > 0 &&
|
|
|
|
chan->value->channel_type < ARRAY_SIZE(channel_names) &&
|
|
|
|
channel_names[chan->value->channel_type]) {
|
|
|
|
channel_name = channel_names[chan->value->channel_type];
|
|
|
|
}
|
|
|
|
|
|
|
|
monitor_printf(mon, " channel name: %s\n", channel_name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
qapi_free_SpiceInfo(info);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void hmp_set_password(Monitor *mon, const QDict *qdict)
|
|
|
|
{
|
|
|
|
const char *protocol = qdict_get_str(qdict, "protocol");
|
|
|
|
const char *password = qdict_get_str(qdict, "password");
|
|
|
|
const char *display = qdict_get_try_str(qdict, "display");
|
|
|
|
const char *connected = qdict_get_try_str(qdict, "connected");
|
|
|
|
Error *err = NULL;
|
|
|
|
|
|
|
|
SetPasswordOptions opts = {
|
|
|
|
.password = (char *)password,
|
|
|
|
.has_connected = !!connected,
|
|
|
|
};
|
|
|
|
|
|
|
|
opts.connected = qapi_enum_parse(&SetPasswordAction_lookup, connected,
|
|
|
|
SET_PASSWORD_ACTION_KEEP, &err);
|
|
|
|
if (err) {
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
opts.protocol = qapi_enum_parse(&DisplayProtocol_lookup, protocol,
|
|
|
|
DISPLAY_PROTOCOL_VNC, &err);
|
|
|
|
if (err) {
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (opts.protocol == DISPLAY_PROTOCOL_VNC) {
|
|
|
|
opts.u.vnc.display = (char *)display;
|
|
|
|
}
|
|
|
|
|
|
|
|
qmp_set_password(&opts, &err);
|
|
|
|
|
|
|
|
out:
|
|
|
|
hmp_handle_error(mon, err);
|
|
|
|
}
|
|
|
|
|
|
|
|
void hmp_expire_password(Monitor *mon, const QDict *qdict)
|
|
|
|
{
|
|
|
|
const char *protocol = qdict_get_str(qdict, "protocol");
|
|
|
|
const char *whenstr = qdict_get_str(qdict, "time");
|
|
|
|
const char *display = qdict_get_try_str(qdict, "display");
|
|
|
|
Error *err = NULL;
|
|
|
|
|
|
|
|
ExpirePasswordOptions opts = {
|
|
|
|
.time = (char *)whenstr,
|
|
|
|
};
|
|
|
|
|
|
|
|
opts.protocol = qapi_enum_parse(&DisplayProtocol_lookup, protocol,
|
|
|
|
DISPLAY_PROTOCOL_VNC, &err);
|
|
|
|
if (err) {
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (opts.protocol == DISPLAY_PROTOCOL_VNC) {
|
|
|
|
opts.u.vnc.display = (char *)display;
|
|
|
|
}
|
|
|
|
|
|
|
|
qmp_expire_password(&opts, &err);
|
|
|
|
|
|
|
|
out:
|
|
|
|
hmp_handle_error(mon, err);
|
|
|
|
}
|
|
|
|
|
2023-01-09 20:03:17 +01:00
|
|
|
#ifdef CONFIG_VNC
|
|
|
|
static void hmp_change_read_arg(void *opaque, const char *password,
|
|
|
|
void *readline_opaque)
|
|
|
|
{
|
|
|
|
qmp_change_vnc_password(password, NULL);
|
|
|
|
monitor_read_command(opaque, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void hmp_change_vnc(Monitor *mon, const char *device, const char *target,
|
|
|
|
const char *arg, const char *read_only, bool force,
|
|
|
|
Error **errp)
|
|
|
|
{
|
|
|
|
if (read_only) {
|
|
|
|
error_setg(errp, "Parameter 'read-only-mode' is invalid for VNC");
|
|
|
|
return;
|
|
|
|
}
|
2023-01-09 20:03:18 +01:00
|
|
|
if (strcmp(target, "passwd") && strcmp(target, "password")) {
|
2023-01-09 20:03:17 +01:00
|
|
|
error_setg(errp, "Expected 'password' after 'vnc'");
|
|
|
|
return;
|
|
|
|
}
|
2023-01-09 20:03:18 +01:00
|
|
|
if (!arg) {
|
|
|
|
MonitorHMP *hmp_mon = container_of(mon, MonitorHMP, common);
|
|
|
|
monitor_read_password(hmp_mon, hmp_change_read_arg, NULL);
|
|
|
|
} else {
|
|
|
|
qmp_change_vnc_password(arg, errp);
|
|
|
|
}
|
2023-01-09 20:03:17 +01:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2023-01-09 20:03:15 +01:00
|
|
|
void hmp_sendkey(Monitor *mon, const QDict *qdict)
|
|
|
|
{
|
|
|
|
const char *keys = qdict_get_str(qdict, "keys");
|
|
|
|
KeyValue *v = NULL;
|
|
|
|
KeyValueList *head = NULL, **tail = &head;
|
|
|
|
int has_hold_time = qdict_haskey(qdict, "hold-time");
|
|
|
|
int hold_time = qdict_get_try_int(qdict, "hold-time", -1);
|
|
|
|
Error *err = NULL;
|
|
|
|
const char *separator;
|
|
|
|
int keyname_len;
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
separator = qemu_strchrnul(keys, '-');
|
|
|
|
keyname_len = separator - keys;
|
|
|
|
|
|
|
|
/* Be compatible with old interface, convert user inputted "<" */
|
|
|
|
if (keys[0] == '<' && keyname_len == 1) {
|
|
|
|
keys = "less";
|
|
|
|
keyname_len = 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
v = g_malloc0(sizeof(*v));
|
|
|
|
|
|
|
|
if (strstart(keys, "0x", NULL)) {
|
|
|
|
const char *endp;
|
|
|
|
int value;
|
|
|
|
|
|
|
|
if (qemu_strtoi(keys, &endp, 0, &value) < 0) {
|
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
assert(endp <= keys + keyname_len);
|
|
|
|
if (endp != keys + keyname_len) {
|
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
v->type = KEY_VALUE_KIND_NUMBER;
|
|
|
|
v->u.number.data = value;
|
|
|
|
} else {
|
|
|
|
int idx = index_from_key(keys, keyname_len);
|
|
|
|
if (idx == Q_KEY_CODE__MAX) {
|
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
v->type = KEY_VALUE_KIND_QCODE;
|
|
|
|
v->u.qcode.data = idx;
|
|
|
|
}
|
|
|
|
QAPI_LIST_APPEND(tail, v);
|
|
|
|
v = NULL;
|
|
|
|
|
|
|
|
if (!*separator) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
keys = separator + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
qmp_send_key(head, has_hold_time, hold_time, &err);
|
|
|
|
hmp_handle_error(mon, err);
|
|
|
|
|
|
|
|
out:
|
|
|
|
qapi_free_KeyValue(v);
|
|
|
|
qapi_free_KeyValueList(head);
|
|
|
|
return;
|
|
|
|
|
|
|
|
err_out:
|
|
|
|
monitor_printf(mon, "invalid parameter: %.*s\n", keyname_len, keys);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
void sendkey_completion(ReadLineState *rs, int nb_args, const char *str)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
char *sep;
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
if (nb_args != 2) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
sep = strrchr(str, '-');
|
|
|
|
if (sep) {
|
|
|
|
str = sep + 1;
|
|
|
|
}
|
|
|
|
len = strlen(str);
|
|
|
|
readline_set_completion_index(rs, len);
|
|
|
|
for (i = 0; i < Q_KEY_CODE__MAX; i++) {
|
|
|
|
if (!strncmp(str, QKeyCode_str(i), len)) {
|
|
|
|
readline_add_completion(rs, QKeyCode_str(i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-30 11:38:29 +02:00
|
|
|
#ifdef CONFIG_PIXMAN
|
2023-01-09 20:03:15 +01:00
|
|
|
void coroutine_fn
|
|
|
|
hmp_screendump(Monitor *mon, const QDict *qdict)
|
|
|
|
{
|
|
|
|
const char *filename = qdict_get_str(qdict, "filename");
|
|
|
|
const char *id = qdict_get_try_str(qdict, "device");
|
|
|
|
int64_t head = qdict_get_try_int(qdict, "head", 0);
|
|
|
|
const char *input_format = qdict_get_try_str(qdict, "format");
|
|
|
|
Error *err = NULL;
|
|
|
|
ImageFormat format;
|
|
|
|
|
|
|
|
format = qapi_enum_parse(&ImageFormat_lookup, input_format,
|
|
|
|
IMAGE_FORMAT_PPM, &err);
|
|
|
|
if (err) {
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
qmp_screendump(filename, id, id != NULL, head,
|
|
|
|
input_format != NULL, format, &err);
|
|
|
|
end:
|
|
|
|
hmp_handle_error(mon, err);
|
|
|
|
}
|
2023-08-30 11:38:29 +02:00
|
|
|
#endif
|
2023-03-01 19:40:14 +01:00
|
|
|
|
|
|
|
void hmp_client_migrate_info(Monitor *mon, const QDict *qdict)
|
|
|
|
{
|
|
|
|
Error *err = NULL;
|
|
|
|
const char *protocol = qdict_get_str(qdict, "protocol");
|
|
|
|
const char *hostname = qdict_get_str(qdict, "hostname");
|
|
|
|
bool has_port = qdict_haskey(qdict, "port");
|
|
|
|
int port = qdict_get_try_int(qdict, "port", -1);
|
|
|
|
bool has_tls_port = qdict_haskey(qdict, "tls-port");
|
|
|
|
int tls_port = qdict_get_try_int(qdict, "tls-port", -1);
|
|
|
|
const char *cert_subject = qdict_get_try_str(qdict, "cert-subject");
|
|
|
|
|
|
|
|
qmp_client_migrate_info(protocol, hostname,
|
|
|
|
has_port, port, has_tls_port, tls_port,
|
|
|
|
cert_subject, &err);
|
|
|
|
hmp_handle_error(mon, err);
|
|
|
|
}
|