/* * SPDX-License-Identifier: GPL-2.0-or-later * * This work is licensed under the terms of the GNU GPL, version 2 or later. * See the COPYING file in the top-level directory. */ #include "qemu/osdep.h" #include "sysemu/sysemu.h" #include "qemu/main-loop.h" #include "qemu/sockets.h" #include "qapi/error.h" #include "qom/object_interfaces.h" #include "io/channel-socket.h" #include "ui/input.h" #include "qom/object.h" #include "ui/vnc_keysym.h" /* use name2keysym from VNC as we use X11 values */ #include "qemu/cutils.h" #include "qapi/qmp/qerror.h" #include "input-barrier.h" #define TYPE_INPUT_BARRIER "input-barrier" OBJECT_DECLARE_SIMPLE_TYPE(InputBarrier, INPUT_BARRIER) #define MAX_HELLO_LENGTH 1024 struct InputBarrier { Object parent; QIOChannelSocket *sioc; guint ioc_tag; /* display properties */ gchar *name; int16_t x_origin, y_origin; int16_t width, height; /* keyboard/mouse server */ SocketAddress saddr; char buffer[MAX_HELLO_LENGTH]; }; static const char *cmd_names[] = { [barrierCmdCNoop] = "CNOP", [barrierCmdCClose] = "CBYE", [barrierCmdCEnter] = "CINN", [barrierCmdCLeave] = "COUT", [barrierCmdCClipboard] = "CCLP", [barrierCmdCScreenSaver] = "CSEC", [barrierCmdCResetOptions] = "CROP", [barrierCmdCInfoAck] = "CIAK", [barrierCmdCKeepAlive] = "CALV", [barrierCmdDKeyDown] = "DKDN", [barrierCmdDKeyRepeat] = "DKRP", [barrierCmdDKeyUp] = "DKUP", [barrierCmdDMouseDown] = "DMDN", [barrierCmdDMouseUp] = "DMUP", [barrierCmdDMouseMove] = "DMMV", [barrierCmdDMouseRelMove] = "DMRM", [barrierCmdDMouseWheel] = "DMWM", [barrierCmdDClipboard] = "DCLP", [barrierCmdDInfo] = "DINF", [barrierCmdDSetOptions] = "DSOP", [barrierCmdDFileTransfer] = "DFTR", [barrierCmdDDragInfo] = "DDRG", [barrierCmdQInfo] = "QINF", [barrierCmdEIncompatible] = "EICV", [barrierCmdEBusy] = "EBSY", [barrierCmdEUnknown] = "EUNK", [barrierCmdEBad] = "EBAD", [barrierCmdHello] = "Barrier", [barrierCmdHelloBack] = "Barrier", }; static kbd_layout_t *kbd_layout; static int input_barrier_to_qcode(uint16_t keyid, uint16_t keycode) { /* keycode is optional, if it is not provided use keyid */ if (keycode && keycode <= qemu_input_map_xorgkbd_to_qcode_len) { return qemu_input_map_xorgkbd_to_qcode[keycode]; } if (keyid >= 0xE000 && keyid <= 0xEFFF) { keyid += 0x1000; } /* keyid is the X11 key id */ if (kbd_layout) { keycode = keysym2scancode(kbd_layout, keyid, NULL, false); return qemu_input_key_number_to_qcode(keycode); } return qemu_input_map_x11_to_qcode[keyid]; } static int input_barrier_to_mouse(uint8_t buttonid) { switch (buttonid) { case barrierButtonLeft: return INPUT_BUTTON_LEFT; case barrierButtonMiddle: return INPUT_BUTTON_MIDDLE; case barrierButtonRight: return INPUT_BUTTON_RIGHT; case barrierButtonExtra0: return INPUT_BUTTON_SIDE; } return buttonid; } #define read_char(x, p, l) \ do { \ int size = sizeof(char); \ if (l < size) { \ return G_SOURCE_REMOVE; \ } \ x = *(char *)p; \ p += size; \ l -= size; \ } while (0) #define read_short(x, p, l) \ do { \ int size = sizeof(short); \ if (l < size) { \ return G_SOURCE_REMOVE; \ } \ x = ntohs(*(short *)p); \ p += size; \ l -= size; \ } while (0) #define write_short(p, x, l) \ do { \ int size = sizeof(short); \ if (l < size) { \ return G_SOURCE_REMOVE; \ } \ *(short *)p = htons(x); \ p += size; \ l -= size; \ } while (0) #define read_int(x, p, l) \ do { \ int size = sizeof(int); \ if (l < size) { \ return G_SOURCE_REMOVE; \ } \ x = ntohl(*(int *)p); \ p += size; \ l -= size; \ } while (0) #define write_int(p, x, l) \ do { \ int size = sizeof(int); \ if (l < size) { \ return G_SOURCE_REMOVE; \ } \ *(int *)p = htonl(x); \ p += size; \ l -= size; \ } while (0) #define write_cmd(p, c, l) \ do { \ int size = strlen(cmd_names[c]); \ if (l < size) { \ return G_SOURCE_REMOVE; \ } \ memcpy(p, cmd_names[c], size); \ p += size; \ l -= size; \ } while (0) #define write_string(p, s, l) \ do { \ int size = strlen(s); \ if (l < size + sizeof(int)) { \ return G_SOURCE_REMOVE; \ } \ *(int *)p = htonl(size); \ p += sizeof(size); \ l -= sizeof(size); \ memcpy(p, s, size); \ p += size; \ l -= size; \ } while (0) static gboolean readcmd(InputBarrier *ib, struct barrierMsg *msg) { int ret, len, i; enum barrierCmd cmd; char *p; ret = qio_channel_read(QIO_CHANNEL(ib->sioc), (char *)&len, sizeof(len), NULL); if (ret < 0) { return G_SOURCE_REMOVE; } len = ntohl(len); if (len > MAX_HELLO_LENGTH) { return G_SOURCE_REMOVE; } ret = qio_channel_read(QIO_CHANNEL(ib->sioc), ib->buffer, len, NULL); if (ret < 0) { return G_SOURCE_REMOVE; } p = ib->buffer; if (len >= strlen(cmd_names[barrierCmdHello]) && memcmp(p, cmd_names[barrierCmdHello], strlen(cmd_names[barrierCmdHello])) == 0) { cmd = barrierCmdHello; p += strlen(cmd_names[barrierCmdHello]); len -= strlen(cmd_names[barrierCmdHello]); } else { for (cmd = 0; cmd < barrierCmdHello; cmd++) { if (memcmp(ib->buffer, cmd_names[cmd], 4) == 0) { break; } } if (cmd == barrierCmdHello) { return G_SOURCE_REMOVE; } p += 4; len -= 4; } msg->cmd = cmd; switch (cmd) { /* connection */ case barrierCmdHello: read_short(msg->version.major, p, len); read_short(msg->version.minor, p, len); break; case barrierCmdDSetOptions: read_int(msg->set.nb, p, len); msg->set.nb /= 2; if (msg->set.nb > BARRIER_MAX_OPTIONS) { msg->set.nb = BARRIER_MAX_OPTIONS; } i = 0; while (len && i < msg->set.nb) { read_int(msg->set.option[i].id, p, len); /* it's a string, restore endianness */ msg->set.option[i].id = htonl(msg->set.option[i].id); msg->set.option[i].nul = 0; read_int(msg->set.option[i].value, p, len); i++; } break; case barrierCmdQInfo: break; /* mouse */ case barrierCmdDMouseMove: case barrierCmdDMouseRelMove: read_short(msg->mousepos.x, p, len); read_short(msg->mousepos.y, p, len); break; case barrierCmdDMouseDown: case barrierCmdDMouseUp: read_char(msg->mousebutton.buttonid, p, len); break; case barrierCmdDMouseWheel: read_short(msg->mousepos.y, p, len); msg->mousepos.x = 0; if (len) { msg->mousepos.x = msg->mousepos.y; read_short(msg->mousepos.y, p, len); } break; /* keyboard */ case barrierCmdDKeyDown: case barrierCmdDKeyUp: read_short(msg->key.keyid, p, len); read_short(msg->key.modifier, p, len); msg->key.button = 0; if (len) { read_short(msg->key.button, p, len); } break; case barrierCmdDKeyRepeat: read_short(msg->repeat.keyid, p, len); read_short(msg->repeat.modifier, p, len); read_short(msg->repeat.repeat, p, len); msg->repeat.button = 0; if (len) { read_short(msg->repeat.button, p, len); } break; case barrierCmdCInfoAck: case barrierCmdCResetOptions: case barrierCmdCEnter: case barrierCmdDClipboard: case barrierCmdCKeepAlive: case barrierCmdCLeave: case barrierCmdCClose: break; /* Invalid from the server */ case barrierCmdHelloBack: case barrierCmdCNoop: case barrierCmdDInfo: break; /* Error codes */ case barrierCmdEIncompatible: read_short(msg->version.major, p, len); read_short(msg->version.minor, p, len); break; case barrierCmdEBusy: case barrierCmdEUnknown: case barrierCmdEBad: break; default: return G_SOURCE_REMOVE; } return G_SOURCE_CONTINUE; } static gboolean writecmd(InputBarrier *ib, struct barrierMsg *msg) { char *p; int ret, i; int avail, len; p = ib->buffer; avail = MAX_HELLO_LENGTH; /* reserve space to store the length */ p += sizeof(int); avail -= sizeof(int); switch (msg->cmd) { case barrierCmdHello: if (msg->version.major < BARRIER_VERSION_MAJOR || (msg->version.major == BARRIER_VERSION_MAJOR && msg->version.minor < BARRIER_VERSION_MINOR)) { ib->ioc_tag = 0; return G_SOURCE_REMOVE; } write_cmd(p, barrierCmdHelloBack, avail); write_short(p, BARRIER_VERSION_MAJOR, avail); write_short(p, BARRIER_VERSION_MINOR, avail); write_string(p, ib->name, avail); break; case barrierCmdCClose: ib->ioc_tag = 0; return G_SOURCE_REMOVE; case barrierCmdQInfo: write_cmd(p, barrierCmdDInfo, avail); write_short(p, ib->x_origin, avail); write_short(p, ib->y_origin, avail); write_short(p, ib->width, avail); write_short(p, ib->height, avail); write_short(p, 0, avail); /* warpsize (obsolete) */ write_short(p, 0, avail); /* mouse x */ write_short(p, 0, avail); /* mouse y */ break; case barrierCmdCInfoAck: break; case barrierCmdCResetOptions: /* TODO: reset options */ break; case barrierCmdDSetOptions: /* TODO: set options */ break; case barrierCmdCEnter: break; case barrierCmdDClipboard: break; case barrierCmdCKeepAlive: write_cmd(p, barrierCmdCKeepAlive, avail); break; case barrierCmdCLeave: break; /* mouse */ case barrierCmdDMouseMove: qemu_input_queue_abs(NULL, INPUT_AXIS_X, msg->mousepos.x, ib->x_origin, ib->width); qemu_input_queue_abs(NULL, INPUT_AXIS_Y, msg->mousepos.y, ib->y_origin, ib->height); qemu_input_event_sync(); break; case barrierCmdDMouseRelMove: qemu_input_queue_rel(NULL, INPUT_AXIS_X, msg->mousepos.x); qemu_input_queue_rel(NULL, INPUT_AXIS_Y, msg->mousepos.y); qemu_input_event_sync(); break; case barrierCmdDMouseDown: qemu_input_queue_btn(NULL, input_barrier_to_mouse(msg->mousebutton.buttonid), true); qemu_input_event_sync(); break; case barrierCmdDMouseUp: qemu_input_queue_btn(NULL, input_barrier_to_mouse(msg->mousebutton.buttonid), false); qemu_input_event_sync(); break; case barrierCmdDMouseWheel: qemu_input_queue_btn(NULL, (msg->mousepos.y > 0) ? INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN, true); qemu_input_event_sync(); qemu_input_queue_btn(NULL, (msg->mousepos.y > 0) ? INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN, false); qemu_input_event_sync(); break; /* keyboard */ case barrierCmdDKeyDown: qemu_input_event_send_key_qcode(NULL, input_barrier_to_qcode(msg->key.keyid, msg->key.button), true); break; case barrierCmdDKeyRepeat: for (i = 0; i < msg->repeat.repeat; i++) { qemu_input_event_send_key_qcode(NULL, input_barrier_to_qcode(msg->repeat.keyid, msg->repeat.button), false); qemu_input_event_send_key_qcode(NULL, input_barrier_to_qcode(msg->repeat.keyid, msg->repeat.button), true); } break; case barrierCmdDKeyUp: qemu_input_event_send_key_qcode(NULL, input_barrier_to_qcode(msg->key.keyid, msg->key.button), false); break; default: write_cmd(p, barrierCmdEUnknown, avail); break; } len = MAX_HELLO_LENGTH - avail - sizeof(int); if (len) { p = ib->buffer; avail = sizeof(len); write_int(p, len, avail); ret = qio_channel_write(QIO_CHANNEL(ib->sioc), ib->buffer, len + sizeof(len), NULL); if (ret < 0) { ib->ioc_tag = 0; return G_SOURCE_REMOVE; } } return G_SOURCE_CONTINUE; } static gboolean input_barrier_event(QIOChannel *ioc G_GNUC_UNUSED, GIOCondition condition, void *opaque) { InputBarrier *ib = opaque; int ret; struct barrierMsg msg; ret = readcmd(ib, &msg); if (ret == G_SOURCE_REMOVE) { ib->ioc_tag = 0; return G_SOURCE_REMOVE; } return writecmd(ib, &msg); } static void input_barrier_complete(UserCreatable *uc, Error **errp) { InputBarrier *ib = INPUT_BARRIER(uc); Error *local_err = NULL; if (!ib->name) { error_setg(errp, QERR_MISSING_PARAMETER, "name"); return; } /* * Connect to the primary * Primary is the server where the keyboard and the mouse * are connected and forwarded to the secondary (the client) */ ib->sioc = qio_channel_socket_new(); qio_channel_set_name(QIO_CHANNEL(ib->sioc), "barrier-client"); qio_channel_socket_connect_sync(ib->sioc, &ib->saddr, &local_err); if (local_err) { error_propagate(errp, local_err); return; } qio_channel_set_delay(QIO_CHANNEL(ib->sioc), false); ib->ioc_tag = qio_channel_add_watch(QIO_CHANNEL(ib->sioc), G_IO_IN, input_barrier_event, ib, NULL); } static void input_barrier_instance_finalize(Object *obj) { InputBarrier *ib = INPUT_BARRIER(obj); if (ib->ioc_tag) { g_source_remove(ib->ioc_tag); ib->ioc_tag = 0; } if (ib->sioc) { qio_channel_close(QIO_CHANNEL(ib->sioc), NULL); object_unref(OBJECT(ib->sioc)); } g_free(ib->name); g_free(ib->saddr.u.inet.host); g_free(ib->saddr.u.inet.port); } static char *input_barrier_get_name(Object *obj, Error **errp) { InputBarrier *ib = INPUT_BARRIER(obj); return g_strdup(ib->name); } static void input_barrier_set_name(Object *obj, const char *value, Error **errp) { InputBarrier *ib = INPUT_BARRIER(obj); if (ib->name) { error_setg(errp, "name property already set"); return; } ib->name = g_strdup(value); } static char *input_barrier_get_server(Object *obj, Error **errp) { InputBarrier *ib = INPUT_BARRIER(obj); return g_strdup(ib->saddr.u.inet.host); } static void input_barrier_set_server(Object *obj, const char *value, Error **errp) { InputBarrier *ib = INPUT_BARRIER(obj); g_free(ib->saddr.u.inet.host); ib->saddr.u.inet.host = g_strdup(value); } static char *input_barrier_get_port(Object *obj, Error **errp) { InputBarrier *ib = INPUT_BARRIER(obj); return g_strdup(ib->saddr.u.inet.port); } static void input_barrier_set_port(Object *obj, const char *value, Error **errp) { InputBarrier *ib = INPUT_BARRIER(obj); g_free(ib->saddr.u.inet.port); ib->saddr.u.inet.port = g_strdup(value); } static void input_barrier_set_x_origin(Object *obj, const char *value, Error **errp) { InputBarrier *ib = INPUT_BARRIER(obj); int result, err; err = qemu_strtoi(value, NULL, 0, &result); if (err < 0 || result < 0 || result > SHRT_MAX) { error_setg(errp, "x-origin property must be in the range [0..%d]", SHRT_MAX); return; } ib->x_origin = result; } static char *input_barrier_get_x_origin(Object *obj, Error **errp) { InputBarrier *ib = INPUT_BARRIER(obj); return g_strdup_printf("%d", ib->x_origin); } static void input_barrier_set_y_origin(Object *obj, const char *value, Error **errp) { InputBarrier *ib = INPUT_BARRIER(obj); int result, err; err = qemu_strtoi(value, NULL, 0, &result); if (err < 0 || result < 0 || result > SHRT_MAX) { error_setg(errp, "y-origin property must be in the range [0..%d]", SHRT_MAX); return; } ib->y_origin = result; } static char *input_barrier_get_y_origin(Object *obj, Error **errp) { InputBarrier *ib = INPUT_BARRIER(obj); return g_strdup_printf("%d", ib->y_origin); } static void input_barrier_set_width(Object *obj, const char *value, Error **errp) { InputBarrier *ib = INPUT_BARRIER(obj); int result, err; err = qemu_strtoi(value, NULL, 0, &result); if (err < 0 || result < 0 || result > SHRT_MAX) { error_setg(errp, "width property must be in the range [0..%d]", SHRT_MAX); return; } ib->width = result; } static char *input_barrier_get_width(Object *obj, Error **errp) { InputBarrier *ib = INPUT_BARRIER(obj); return g_strdup_printf("%d", ib->width); } static void input_barrier_set_height(Object *obj, const char *value, Error **errp) { InputBarrier *ib = INPUT_BARRIER(obj); int result, err; err = qemu_strtoi(value, NULL, 0, &result); if (err < 0 || result < 0 || result > SHRT_MAX) { error_setg(errp, "height property must be in the range [0..%d]", SHRT_MAX); return; } ib->height = result; } static char *input_barrier_get_height(Object *obj, Error **errp) { InputBarrier *ib = INPUT_BARRIER(obj); return g_strdup_printf("%d", ib->height); } static void input_barrier_instance_init(Object *obj) { InputBarrier *ib = INPUT_BARRIER(obj); /* always use generic keymaps */ if (keyboard_layout && !kbd_layout) { /* We use X11 key id, so use VNC name2keysym */ kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout, &error_fatal); } ib->saddr.type = SOCKET_ADDRESS_TYPE_INET; ib->saddr.u.inet.host = g_strdup("localhost"); ib->saddr.u.inet.port = g_strdup("24800"); ib->x_origin = 0; ib->y_origin = 0; ib->width = 1920; ib->height = 1080; } static void input_barrier_class_init(ObjectClass *oc, void *data) { UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc); ucc->complete = input_barrier_complete; object_class_property_add_str(oc, "name", input_barrier_get_name, input_barrier_set_name); object_class_property_add_str(oc, "server", input_barrier_get_server, input_barrier_set_server); object_class_property_add_str(oc, "port", input_barrier_get_port, input_barrier_set_port); object_class_property_add_str(oc, "x-origin", input_barrier_get_x_origin, input_barrier_set_x_origin); object_class_property_add_str(oc, "y-origin", input_barrier_get_y_origin, input_barrier_set_y_origin); object_class_property_add_str(oc, "width", input_barrier_get_width, input_barrier_set_width); object_class_property_add_str(oc, "height", input_barrier_get_height, input_barrier_set_height); } static const TypeInfo input_barrier_info = { .name = TYPE_INPUT_BARRIER, .parent = TYPE_OBJECT, .class_init = input_barrier_class_init, .instance_size = sizeof(InputBarrier), .instance_init = input_barrier_instance_init, .instance_finalize = input_barrier_instance_finalize, .interfaces = (InterfaceInfo[]) { { TYPE_USER_CREATABLE }, { } } }; static void register_types(void) { type_register_static(&input_barrier_info); } type_init(register_types);