diff mbox

[0/3] input: add support for kbd delays

Message ID 20140531160354.GI13640@beta.private.mielke.cc
State New
Headers show

Commit Message

Dave Mielke May 31, 2014, 4:03 p.m. UTC
[quoted lines by Gerd Hoffmann on 2014/05/28 at 14:26 +0200]

Hi:

>Little series of three input patches.  First patch adds support for
>submitting keyboard delays, which will make the input layer setup a
>timer, buffer the following keyboard events, send them to the guest
>once the delay is over.  Second patch makes the send_key monitor command
>use the new service instead of using its own timer.  Third patch inserts
>a delay between keydown and keyup events in the curses ui.
>
>please review & test,

This approach doesn't work, although the problem is different. Input is now 
recognized, but unwanted key repeats creep in. For example, I typed "exit" and 
got "exiitt". I tested this several times while typing as accurately and 
quickly as I could.

My theory is that the key releases aren't being detected by the application 
until the next key press/release delay, thus effectively leaving the keys 
pressed.

I've attached the latest verson of my patch (qemu-kbddelay-2.patch) to this 
message in case you're interested in considering it. It merges the delay option 
into the -k option, so we'd have this:

   -k [[language=]language][,delay=msecs]
diff mbox

Patch

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
-@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
 
 
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;