diff --git a/include/ui/input.h b/include/ui/input.h index 3d3d487..17c77c8 100644 --- a/include/ui/input.h +++ b/include/ui/input.h @@ -35,6 +35,7 @@ void qemu_input_event_sync(void); InputEvent *qemu_input_event_new_key(KeyValue *key, bool down); void qemu_input_event_send_key(QemuConsole *src, KeyValue *key, bool down); void qemu_input_event_send_key_number(QemuConsole *src, int num, bool down); +void qemu_input_event_set_keyboard_delay(uint64_t duration); void qemu_input_event_send_key_qcode(QemuConsole *src, QKeyCode q, bool down); int qemu_input_key_value_to_number(const KeyValue *value); int qemu_input_key_value_to_qcode(const KeyValue *value); diff --git a/qemu-options.hx b/qemu-options.hx index c2c0823..e906f82 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -241,16 +241,20 @@ Preallocate memory when using -mem-path. ETEXI DEF("k", HAS_ARG, QEMU_OPTION_k, - "-k language use keyboard layout (for example 'fr' for French)\n", + "-k [[language=]language][,delay=msecs]\n" + " set keyboard options\n" + " language: use keyboard layout (for example 'fr' for French)\n" + " delay: ensure minimum delay between keyboard events\n", QEMU_ARCH_ALL) STEXI address@hidden -k @var{language} address@hidden -k address@hidden,address@hidden @findex -k -Use keyboard layout @var{language} (for example @code{fr} for -French). This option is only needed where it is not easy to get raw PC -keycodes (e.g. on Macs, with some X11 servers or with a VNC -display). You don't normally need to use it on PC/Linux or PC/Windows -hosts. + +The @option{language} option specifies the keyboard layout +(for example @code{fr} for French). This option is only needed +where it is not easy to get raw PC keycodes +(e.g. on Macs, with some X11 servers, or with a VNC display). +You don't normally need to use it on PC/Linux or PC/Windows hosts. The available layouts are: @example @@ -260,6 +264,18 @@ de en-us fi fr-be hr it lv nl-be pt sl tr @end example The default is @code{en-us}. + +The @option{delay} option sets the minimum delay +between keyboard events to @var{msecs} milliseconds. +The default is 0 (no minimum delay). +This option exists in order to deal with problematic applications +which run into trouble when keys are typed too quickly. +Certain UIs, like curses, aggravate the problem +because they must turn a single user keyboard action +into several keyboard events being delivered to the virtual machine. +Only try this option if you encounter an unexpected keyboard input issue, +for example the input not being recognized at all. +A duration of 20 seems to be reasonable. ETEXI diff --git a/ui/input.c b/ui/input.c index fc91fba..eb6c1dd 100644 --- a/ui/input.c +++ b/ui/input.c @@ -188,7 +188,9 @@ InputEvent *qemu_input_event_new_key(KeyValue *key, bool down) return evt; } -void qemu_input_event_send_key(QemuConsole *src, KeyValue *key, bool down) +static void qemu_input_event_send_key_immediate(QemuConsole *src, + KeyValue *key, + bool down) { InputEvent *evt; evt = qemu_input_event_new_key(key, down); @@ -197,6 +199,82 @@ void qemu_input_event_send_key(QemuConsole *src, KeyValue *key, bool down) qapi_free_InputEvent(evt); } +struct PendingKeyEvent { + struct PendingKeyEvent *next; + QemuConsole *src; + KeyValue *key; + bool down; +}; + +static struct PendingKeyEvent *firstPendingKeyEvent = NULL; +static struct PendingKeyEvent *lastPendingKeyEvent = NULL; + +static unsigned long long pendingKeyEventDelay = 0; +static QEMUTimer *pendingKeyEventTimer = NULL; + +static void qemu_input_begin_pending_key_event(void) +{ + struct PendingKeyEvent *event = firstPendingKeyEvent; + + qemu_input_event_send_key_immediate(event->src, event->key, event->down); + timer_mod(pendingKeyEventTimer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + pendingKeyEventDelay); +} + +static void qemu_input_end_pending_key_event(void *opaque) +{ + struct PendingKeyEvent *event = firstPendingKeyEvent; + + if ((firstPendingKeyEvent = event->next)) + { + qemu_input_begin_pending_key_event(); + } + else + { + lastPendingKeyEvent = NULL; + } + + g_free(event); +} + +void qemu_input_event_send_key(QemuConsole *src, KeyValue *key, bool down) +{ + if (pendingKeyEventDelay) + { + struct PendingKeyEvent *event; + + event = g_malloc(sizeof(*event)); + event->next = NULL; + event->src = src; + event->key = key; + event->down = down; + + if (lastPendingKeyEvent) + { + lastPendingKeyEvent->next = event; + lastPendingKeyEvent = event; + } + else + { + if (!pendingKeyEventTimer) + { + pendingKeyEventTimer = timer_new_ms(QEMU_CLOCK_VIRTUAL, qemu_input_end_pending_key_event, NULL); + } + + firstPendingKeyEvent = lastPendingKeyEvent = event; + qemu_input_begin_pending_key_event(); + } + } + else + { + qemu_input_event_send_key_immediate(src, key, down); + } +} + +void qemu_input_event_set_keyboard_delay(uint64_t duration) +{ + pendingKeyEventDelay = duration; +} + void qemu_input_event_send_key_number(QemuConsole *src, int num, bool down) { KeyValue *key = g_new0(KeyValue, 1); diff --git a/vl.c b/vl.c index 709d8cd..7ab8ce0 100644 --- a/vl.c +++ b/vl.c @@ -77,6 +77,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 "exec/gdbstub.h" #include "qemu/timer.h" @@ -1387,6 +1388,27 @@ static QemuOptsList qemu_smp_opts = { }, }; +static QemuOptsList qemu_keyboard_opts = { + .name = "keyboard", + .implied_opt_name = "language", + .head = QTAILQ_HEAD_INITIALIZER(qemu_keyboard_opts.head), + .merge_lists = true, + + .desc = { + { + .name = "language", + .type = QEMU_OPT_STRING, + }, + + { + .name = "delay", + .type = QEMU_OPT_NUMBER, + }, + + { /* end of list */ } + }, +}; + static void smp_parse(QemuOpts *opts) { if (opts) { @@ -3021,6 +3043,7 @@ int main(int argc, char **argv, char **envp) qemu_add_opts(&qemu_realtime_opts); qemu_add_opts(&qemu_msg_opts); qemu_add_opts(&qemu_name_opts); + qemu_add_opts(&qemu_keyboard_opts); runstate_init(); @@ -3405,9 +3428,48 @@ int main(int argc, char **argv, char **envp) case QEMU_OPTION_S: autostart = 0; break; + case QEMU_OPTION_k: - keyboard_layout = optarg; - break; + { + opts = qemu_opts_parse(qemu_find_opts("keyboard"), optarg, 1); + + if (!opts) { + exit(EXIT_FAILURE); + } + + { + const char *language_string; + + language_string = qemu_opt_get(opts, "language"); + if (language_string) { + if (!*language_string) { + error_report("missing 'language' option value"); + exit(EXIT_FAILURE); + } + + keyboard_layout = language_string; + } + } + + { + const char *delay_string; + uint64_t delay_value; + + delay_string = qemu_opt_get(opts, "delay"); + if (delay_string) { + if (*delay_string) { + delay_value = qemu_opt_get_number(opts, "delay", 20); + } else { + delay_value = 20; + } + + qemu_input_event_set_keyboard_delay(delay_value); + } + } + + break; + } + case QEMU_OPTION_localtime: rtc_utc = 0; break;