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..5d0a8b1 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -262,6 +262,24 @@ de en-us fi fr-be hr it lv nl-be pt sl tr The default is @code{en-us}. ETEXI +DEF("kbddelay", HAS_ARG, QEMU_OPTION_kbddelay, + "-kbddelay [duration=]msecs\n" + " ensure a minimum delay between keyboard events\n" + " duration: the minimum delay between keyboard events (default: 0)\n", + QEMU_ARCH_ALL) +STEXI address@hidden -kbddelay address@hidden address@hidden -kbddelay +Set 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 + DEF("audio-help", 0, QEMU_OPTION_audio_help, "-audio-help print list of audio drivers and their options\n", 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..490621b 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,20 @@ static QemuOptsList qemu_smp_opts = { }, }; +static QemuOptsList qemu_kbddelay_opts = { + .name = "kbddelay", + .implied_opt_name = "duration", + .head = QTAILQ_HEAD_INITIALIZER(qemu_kbddelay_opts.head), + .merge_lists = true, + .desc = { + { + .name = "duration", + .type = QEMU_OPT_NUMBER, + }, + { /* end of list */ } + }, +}; + static void smp_parse(QemuOpts *opts) { if (opts) { @@ -3021,6 +3036,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_kbddelay_opts); runstate_init(); @@ -3408,6 +3424,30 @@ int main(int argc, char **argv, char **envp) case QEMU_OPTION_k: keyboard_layout = optarg; break; + case QEMU_OPTION_kbddelay: { + uint64_t duration_value; + const char *duration_string; + + opts = qemu_opts_parse(qemu_find_opts("kbddelay"), optarg, 1); + if (!opts) { + exit(EXIT_FAILURE); + } + + duration_string = qemu_opt_get(opts, "duration"); + if (!duration_string) { + error_report("invalid -kbddelay option, missing 'duration' option"); + exit(EXIT_FAILURE); + } + + if (*duration_string) { + duration_value = qemu_opt_get_number(opts, "duration", 20); + } else { + duration_value = 20; + } + + qemu_input_event_set_keyboard_delay(duration_value); + break; + } case QEMU_OPTION_localtime: rtc_utc = 0; break;