@@ -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);
@@ -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
-@item -k @var{language}
+@item -k [[language=]@var{language}][,delay=@var{msecs}]
@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
@@ -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);
@@ -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;