From patchwork Fri Mar 4 10:25:14 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Gerd Hoffmann X-Patchwork-Id: 8501971 Return-Path: X-Original-To: patchwork-qemu-devel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id B6DA29F659 for ; Fri, 4 Mar 2016 10:26:42 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 8F33C2011D for ; Fri, 4 Mar 2016 10:26:41 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 29858200E9 for ; Fri, 4 Mar 2016 10:26:40 +0000 (UTC) Received: from localhost ([::1]:40095 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1abmwR-0001H8-FK for patchwork-qemu-devel@patchwork.kernel.org; Fri, 04 Mar 2016 05:26:39 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:50885) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1abmvN-0008UK-Bn for qemu-devel@nongnu.org; Fri, 04 Mar 2016 05:25:38 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1abmvI-0007Bm-0w for qemu-devel@nongnu.org; Fri, 04 Mar 2016 05:25:33 -0500 Received: from mx1.redhat.com ([209.132.183.28]:39564) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1abmvH-0007BS-OW for qemu-devel@nongnu.org; Fri, 04 Mar 2016 05:25:27 -0500 Received: from int-mx11.intmail.prod.int.phx2.redhat.com (int-mx11.intmail.prod.int.phx2.redhat.com [10.5.11.24]) by mx1.redhat.com (Postfix) with ESMTPS id 71D56C09FAB2 for ; Fri, 4 Mar 2016 10:25:27 +0000 (UTC) Received: from nilsson.home.kraxel.org (ovpn-116-34.ams2.redhat.com [10.36.116.34]) by int-mx11.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id u24APQeX015103; Fri, 4 Mar 2016 05:25:26 -0500 Received: by nilsson.home.kraxel.org (Postfix, from userid 500) id 213F181699; Fri, 4 Mar 2016 11:25:25 +0100 (CET) From: Gerd Hoffmann To: qemu-devel@nongnu.org Date: Fri, 4 Mar 2016 11:25:14 +0100 Message-Id: <1457087116-4379-2-git-send-email-kraxel@redhat.com> In-Reply-To: <1457087116-4379-1-git-send-email-kraxel@redhat.com> References: <1457087116-4379-1-git-send-email-kraxel@redhat.com> X-Scanned-By: MIMEDefang 2.68 on 10.5.11.24 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 209.132.183.28 Cc: Paolo Bonzini , Gerd Hoffmann Subject: [Qemu-devel] [PATCH 1/3] input: linux evdev support X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP This patch adds support for reading input events directly from linux evdev devices and forward them to the guest. Unlike virtio-input-host which simply passes on all events to the guest without looking at them this will interpret the events and feed them into the qemu input subsystem. Therefore this is limited to what the qemu input subsystem and the emulated input devices are able to handle. Also there is no support for absolute coordinates (tablet/touchscreen). So we are talking here about basic mouse and keyboard support. The advantage is that it'll work without virtio-input drivers in the guest, the events are delivered to the usual ps/2 or usb input devices (depending on what the machine happens to have). And for keyboards qemu is able to switch the keyboard between guest and host on hotkey. The hotkey is hard-coded for now (both control keys), initialy the guest owns the keyboard. Probably most useful when assigning vga devices with vfio and using a physical monitor instead of vnc/spice/gtk as guest display. Usage: Add '-input-linux /dev/input/event' to the qemu command line. Note that udev has rules which populate /dev/input/by-{id,path} with static names, which might be more convinient to use. Signed-off-by: Gerd Hoffmann --- include/ui/input.h | 2 + qemu-options.hx | 9 ++ ui/Makefile.objs | 1 + ui/input-linux.c | 357 +++++++++++++++++++++++++++++++++++++++++++++++++++++ vl.c | 11 ++ 5 files changed, 380 insertions(+) create mode 100644 ui/input-linux.c diff --git a/include/ui/input.h b/include/ui/input.h index d06a12d..102d8a3 100644 --- a/include/ui/input.h +++ b/include/ui/input.h @@ -65,4 +65,6 @@ void qemu_input_check_mode_change(void); void qemu_add_mouse_mode_change_notifier(Notifier *notify); void qemu_remove_mouse_mode_change_notifier(Notifier *notify); +int input_linux_init(void *opaque, QemuOpts *opts, Error **errp); + #endif /* INPUT_H */ diff --git a/qemu-options.hx b/qemu-options.hx index 144e6a9..bda92d2 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -1226,6 +1226,15 @@ STEXI Set the initial graphical resolution and depth (PPC, SPARC only). ETEXI +DEF("input-linux", 1, QEMU_OPTION_input_linux, + "-input-linux \n" + " Use input device.\n", QEMU_ARCH_ALL) +STEXI +@item -input-linux @var{dev} +@findex -input-linux +Use input device. +ETEXI + DEF("vnc", HAS_ARG, QEMU_OPTION_vnc , "-vnc display start a VNC server on display\n", QEMU_ARCH_ALL) STEXI diff --git a/ui/Makefile.objs b/ui/Makefile.objs index 728393c..dc936f1 100644 --- a/ui/Makefile.objs +++ b/ui/Makefile.objs @@ -9,6 +9,7 @@ vnc-obj-y += vnc-jobs.o common-obj-y += keymaps.o console.o cursor.o qemu-pixman.o common-obj-y += input.o input-keymap.o input-legacy.o +common-obj-$(CONFIG_LINUX) += input-linux.o common-obj-$(CONFIG_SPICE) += spice-core.o spice-input.o spice-display.o common-obj-$(CONFIG_SDL) += sdl.mo x_keymap.o common-obj-$(CONFIG_COCOA) += cocoa.o diff --git a/ui/input-linux.c b/ui/input-linux.c new file mode 100644 index 0000000..5374a2e --- /dev/null +++ b/ui/input-linux.c @@ -0,0 +1,357 @@ +/* + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. See the COPYING file in the + * top-level directory. + */ + +#include "qemu/osdep.h" +#include "qemu-common.h" +#include "qemu/config-file.h" +#include "qemu/sockets.h" +#include "sysemu/sysemu.h" +#include "ui/input.h" + +#include +#include "standard-headers/linux/input.h" + +static int linux_to_qcode[KEY_CNT] = { + [KEY_ESC] = Q_KEY_CODE_ESC, + [KEY_1] = Q_KEY_CODE_1, + [KEY_2] = Q_KEY_CODE_2, + [KEY_3] = Q_KEY_CODE_3, + [KEY_4] = Q_KEY_CODE_4, + [KEY_5] = Q_KEY_CODE_5, + [KEY_6] = Q_KEY_CODE_6, + [KEY_7] = Q_KEY_CODE_7, + [KEY_8] = Q_KEY_CODE_8, + [KEY_9] = Q_KEY_CODE_9, + [KEY_0] = Q_KEY_CODE_0, + [KEY_MINUS] = Q_KEY_CODE_MINUS, + [KEY_EQUAL] = Q_KEY_CODE_EQUAL, + [KEY_BACKSPACE] = Q_KEY_CODE_BACKSPACE, + [KEY_TAB] = Q_KEY_CODE_TAB, + [KEY_Q] = Q_KEY_CODE_Q, + [KEY_W] = Q_KEY_CODE_W, + [KEY_E] = Q_KEY_CODE_E, + [KEY_R] = Q_KEY_CODE_R, + [KEY_T] = Q_KEY_CODE_T, + [KEY_Y] = Q_KEY_CODE_Y, + [KEY_U] = Q_KEY_CODE_U, + [KEY_I] = Q_KEY_CODE_I, + [KEY_O] = Q_KEY_CODE_O, + [KEY_P] = Q_KEY_CODE_P, + [KEY_LEFTBRACE] = Q_KEY_CODE_BRACKET_LEFT, + [KEY_RIGHTBRACE] = Q_KEY_CODE_BRACKET_RIGHT, + [KEY_ENTER] = Q_KEY_CODE_RET, + [KEY_LEFTCTRL] = Q_KEY_CODE_CTRL, + [KEY_A] = Q_KEY_CODE_A, + [KEY_S] = Q_KEY_CODE_S, + [KEY_D] = Q_KEY_CODE_D, + [KEY_F] = Q_KEY_CODE_F, + [KEY_G] = Q_KEY_CODE_G, + [KEY_H] = Q_KEY_CODE_H, + [KEY_J] = Q_KEY_CODE_J, + [KEY_K] = Q_KEY_CODE_K, + [KEY_L] = Q_KEY_CODE_L, + [KEY_SEMICOLON] = Q_KEY_CODE_SEMICOLON, + [KEY_APOSTROPHE] = Q_KEY_CODE_APOSTROPHE, + [KEY_GRAVE] = Q_KEY_CODE_GRAVE_ACCENT, + [KEY_LEFTSHIFT] = Q_KEY_CODE_SHIFT, + [KEY_BACKSLASH] = Q_KEY_CODE_BACKSLASH, + [KEY_102ND] = Q_KEY_CODE_LESS, + [KEY_Z] = Q_KEY_CODE_Z, + [KEY_X] = Q_KEY_CODE_X, + [KEY_C] = Q_KEY_CODE_C, + [KEY_V] = Q_KEY_CODE_V, + [KEY_B] = Q_KEY_CODE_B, + [KEY_N] = Q_KEY_CODE_N, + [KEY_M] = Q_KEY_CODE_M, + [KEY_COMMA] = Q_KEY_CODE_COMMA, + [KEY_DOT] = Q_KEY_CODE_DOT, + [KEY_SLASH] = Q_KEY_CODE_SLASH, + [KEY_RIGHTSHIFT] = Q_KEY_CODE_SHIFT_R, + [KEY_LEFTALT] = Q_KEY_CODE_ALT, + [KEY_SPACE] = Q_KEY_CODE_SPC, + [KEY_CAPSLOCK] = Q_KEY_CODE_CAPS_LOCK, + [KEY_F1] = Q_KEY_CODE_F1, + [KEY_F2] = Q_KEY_CODE_F2, + [KEY_F3] = Q_KEY_CODE_F3, + [KEY_F4] = Q_KEY_CODE_F4, + [KEY_F5] = Q_KEY_CODE_F5, + [KEY_F6] = Q_KEY_CODE_F6, + [KEY_F7] = Q_KEY_CODE_F7, + [KEY_F8] = Q_KEY_CODE_F8, + [KEY_F9] = Q_KEY_CODE_F9, + [KEY_F10] = Q_KEY_CODE_F10, + [KEY_NUMLOCK] = Q_KEY_CODE_NUM_LOCK, + [KEY_SCROLLLOCK] = Q_KEY_CODE_SCROLL_LOCK, + [KEY_KP0] = Q_KEY_CODE_KP_0, + [KEY_KP1] = Q_KEY_CODE_KP_1, + [KEY_KP2] = Q_KEY_CODE_KP_2, + [KEY_KP3] = Q_KEY_CODE_KP_3, + [KEY_KP4] = Q_KEY_CODE_KP_4, + [KEY_KP5] = Q_KEY_CODE_KP_5, + [KEY_KP6] = Q_KEY_CODE_KP_6, + [KEY_KP7] = Q_KEY_CODE_KP_7, + [KEY_KP8] = Q_KEY_CODE_KP_8, + [KEY_KP9] = Q_KEY_CODE_KP_9, + [KEY_KPMINUS] = Q_KEY_CODE_KP_SUBTRACT, + [KEY_KPPLUS] = Q_KEY_CODE_KP_ADD, + [KEY_KPDOT] = Q_KEY_CODE_KP_DECIMAL, + [KEY_KPENTER] = Q_KEY_CODE_KP_ENTER, + [KEY_KPSLASH] = Q_KEY_CODE_KP_DIVIDE, + [KEY_KPASTERISK] = Q_KEY_CODE_KP_MULTIPLY, + [KEY_F11] = Q_KEY_CODE_F11, + [KEY_F12] = Q_KEY_CODE_F12, + [KEY_RIGHTCTRL] = Q_KEY_CODE_CTRL_R, + [KEY_SYSRQ] = Q_KEY_CODE_SYSRQ, + [KEY_RIGHTALT] = Q_KEY_CODE_ALT_R, + [KEY_HOME] = Q_KEY_CODE_HOME, + [KEY_UP] = Q_KEY_CODE_UP, + [KEY_PAGEUP] = Q_KEY_CODE_PGUP, + [KEY_LEFT] = Q_KEY_CODE_LEFT, + [KEY_RIGHT] = Q_KEY_CODE_RIGHT, + [KEY_END] = Q_KEY_CODE_END, + [KEY_DOWN] = Q_KEY_CODE_DOWN, + [KEY_PAGEDOWN] = Q_KEY_CODE_PGDN, + [KEY_INSERT] = Q_KEY_CODE_INSERT, + [KEY_DELETE] = Q_KEY_CODE_DELETE, + [KEY_LEFTMETA] = Q_KEY_CODE_META_L, + [KEY_RIGHTMETA] = Q_KEY_CODE_META_R, + [KEY_MENU] = Q_KEY_CODE_MENU, +}; + +static int qemu_input_linux_to_qcode(unsigned int lnx) +{ + assert(lnx < KEY_CNT); + return linux_to_qcode[lnx]; +} + +typedef struct InputLinux InputLinux; + +struct InputLinux { + const char *evdev; + int fd; + bool grab_request; + bool grab_active; + bool keydown[KEY_CNT]; + int keycount; + int wheel; +}; + +static void input_linux_toggle_grab(InputLinux *il) +{ + intptr_t request = !il->grab_active; + int rc; + + rc = ioctl(il->fd, EVIOCGRAB, request); + if (rc < 0) { + return; + } + il->grab_active = !il->grab_active; +} + +static void input_linux_event_keyboard(void *opaque) +{ + InputLinux *il = opaque; + struct input_event event; + int rc; + + for (;;) { + rc = read(il->fd, &event, sizeof(event)); + if (rc != sizeof(event)) { + if (rc < 0 && errno != EAGAIN) { + fprintf(stderr, "%s: read: %s\n", __func__, strerror(errno)); + qemu_set_fd_handler(il->fd, NULL, NULL, NULL); + close(il->fd); + } + break; + } + + switch (event.type) { + case EV_KEY: + if (event.value > 1) { + /* + * ignore autorepeat + unknown key events + * 0 == up, 1 == down, 2 == autorepeat, other == undefined + */ + continue; + } + /* keep track of key state */ + if (!il->keydown[event.code] && event.value) { + il->keydown[event.code] = true; + il->keycount++; + } + if (il->keydown[event.code] && !event.value) { + il->keydown[event.code] = false; + il->keycount--; + } + + /* send event to guest when grab is active */ + if (il->grab_active) { + int qcode = qemu_input_linux_to_qcode(event.code); + qemu_input_event_send_key_qcode(NULL, qcode, event.value); + } + + /* hotkey -> record switch request ... */ + if (il->keydown[KEY_LEFTCTRL] && + il->keydown[KEY_RIGHTCTRL]) { + il->grab_request = true; + } + + /* + * ... and do the switch when all keys are lifted, so we + * confuse neither guest nor host with keys which seem to + * be stuck due to missing key-up events. + */ + if (il->grab_request && !il->keycount) { + il->grab_request = false; + input_linux_toggle_grab(il); + } + break; + } + } +} + +static void input_linux_event_mouse_button(int button) +{ + qemu_input_queue_btn(NULL, button, true); + qemu_input_event_sync(); + qemu_input_queue_btn(NULL, button, false); + qemu_input_event_sync(); +} + +static void input_linux_event_mouse(void *opaque) +{ + InputLinux *il = opaque; + struct input_event event; + int rc; + + for (;;) { + rc = read(il->fd, &event, sizeof(event)); + if (rc != sizeof(event)) { + if (rc < 0 && errno != EAGAIN) { + fprintf(stderr, "%s: read: %s\n", __func__, strerror(errno)); + qemu_set_fd_handler(il->fd, NULL, NULL, NULL); + close(il->fd); + } + break; + } + + switch (event.type) { + case EV_KEY: + switch (event.code) { + case BTN_LEFT: + qemu_input_queue_btn(NULL, INPUT_BUTTON_LEFT, event.value); + break; + case BTN_RIGHT: + qemu_input_queue_btn(NULL, INPUT_BUTTON_RIGHT, event.value); + break; + case BTN_MIDDLE: + qemu_input_queue_btn(NULL, INPUT_BUTTON_MIDDLE, event.value); + break; + case BTN_GEAR_UP: + qemu_input_queue_btn(NULL, INPUT_BUTTON_WHEEL_UP, event.value); + break; + case BTN_GEAR_DOWN: + qemu_input_queue_btn(NULL, INPUT_BUTTON_WHEEL_DOWN, + event.value); + break; + }; + break; + case EV_REL: + switch (event.code) { + case REL_X: + qemu_input_queue_rel(NULL, INPUT_AXIS_X, event.value); + break; + case REL_Y: + qemu_input_queue_rel(NULL, INPUT_AXIS_Y, event.value); + break; + case REL_WHEEL: + il->wheel = event.value; + break; + } + break; + case EV_SYN: + qemu_input_event_sync(); + if (il->wheel != 0) { + input_linux_event_mouse_button((il->wheel > 0) + ? INPUT_BUTTON_WHEEL_UP + : INPUT_BUTTON_WHEEL_DOWN); + il->wheel = 0; + } + break; + } + } +} + +int input_linux_init(void *opaque, QemuOpts *opts, Error **errp) +{ + InputLinux *il = g_new0(InputLinux, 1); + uint32_t evtmap; + int rc, ver; + + il->evdev = qemu_opt_get(opts, "evdev"); + if (!il->evdev) { + error_setg(errp, "no input device specified"); + goto err_free; + } + + il->fd = open(il->evdev, O_RDWR); + if (il->fd < 0) { + error_setg_file_open(errp, errno, il->evdev); + goto err_free; + } + qemu_set_nonblock(il->fd); + + rc = ioctl(il->fd, EVIOCGVERSION, &ver); + if (rc < 0) { + error_setg(errp, "%s: is not an evdev device", il->evdev); + goto err_close; + } + + rc = ioctl(il->fd, EVIOCGBIT(0, sizeof(evtmap)), &evtmap); + + if (evtmap & (1 << EV_REL)) { + /* has relative axis -> assume mouse */ + qemu_set_fd_handler(il->fd, input_linux_event_mouse, NULL, il); + } else if (evtmap & (1 << EV_ABS)) { + /* has absolute axis -> not supported */ + error_setg(errp, "tablet/touchscreen not supported"); + goto err_close; + } else if (evtmap & (1 << EV_KEY)) { + /* has keys/buttons (and no axis) -> assume keyboard */ + qemu_set_fd_handler(il->fd, input_linux_event_keyboard, NULL, il); + } else { + /* Huh? What is this? */ + error_setg(errp, "unknown kind of input device"); + goto err_close; + } + input_linux_toggle_grab(il); + return 0; + +err_close: + close(il->fd); +err_free: + g_free(il); + return -1; +} + +static QemuOptsList qemu_input_linux_opts = { + .name = "input-linux", + .head = QTAILQ_HEAD_INITIALIZER(qemu_input_linux_opts.head), + .implied_opt_name = "evdev", + .desc = { + { + .name = "evdev", + .type = QEMU_OPT_STRING, + }, + { /* end of list */ } + }, +}; + +static void input_linux_register_config(void) +{ + qemu_add_opts(&qemu_input_linux_opts); +} +machine_init(input_linux_register_config); diff --git a/vl.c b/vl.c index adeddd9..7a28982 100644 --- a/vl.c +++ b/vl.c @@ -72,6 +72,7 @@ int main(int argc, char **argv) #include "net/slirp.h" #include "monitor/monitor.h" #include "ui/console.h" +#include "ui/input.h" #include "sysemu/sysemu.h" #include "sysemu/numa.h" #include "exec/gdbstub.h" @@ -3728,6 +3729,12 @@ int main(int argc, char **argv, char **envp) #endif break; } + case QEMU_OPTION_input_linux: + if (!qemu_opts_parse_noisily(qemu_find_opts("input-linux"), + optarg, true)) { + exit(1); + } + break; case QEMU_OPTION_no_acpi: acpi_enabled = 0; break; @@ -4591,6 +4598,10 @@ int main(int argc, char **argv, char **envp) qemu_spice_display_init(); } #endif +#ifdef CONFIG_LINUX + qemu_opts_foreach(qemu_find_opts("input-linux"), + input_linux_init, NULL, &error_fatal); +#endif if (foreach_device_config(DEV_GDB, gdbserver_start) < 0) { exit(1);