Home > General > Detecting Xlib’s Keyboard Auto-repeat Functionality (and how to fix it)

Detecting Xlib’s Keyboard Auto-repeat Functionality (and how to fix it)

June 23rd, 2009

If you’ve ever messed around with listening for XWindows keyboard events, you may have noticed something that’s odd. XWindows has a very strange way of dealing with keyboard auto-repeating. Let’s take a scenario:

Hold down the “h” key on the keyboard for a few seconds and you’ll get something that looks like this:

hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh

Now, if you try this and watch carefully, you’ll notice a couple of things. The first thing to notice, is that after the first character, there is a slight delay before it starts repeating. That delay is about a half of a second. After that initial delay, it repeats every 30ms. These delays are configurable in X. It may be somewhere in your window manager control panel, but the xset command will tell you everything you need to know:

$ xset q
Keyboard Control:
  auto repeat:  on    key click percent:  0    LED mask:  00000000
  auto repeat delay:  500    repeat rate:  30
  auto repeating keys:  00fffffffffffbbf
                        fbdfffefffedffff
                        9fffffffffffffff
                        fff7ffffffffffff

Now, when you grab the keyboard in X using XGrabKeyboard or XGrabKey (so you’ll get the keyboard events), something strange happens with all those h’s. When holding down a key, you would at first expect to see 2 events. The KeyPress event and the KeyRelease event. But in order for auto-repeat to work, there must be more. For each auto-repeating character, you should get an additional KeyPress event. This solves the auto-repeat problem, because you can write some simple code that can easily detect auto-repeat and ignore it if you just care if the user is holding down a key.

That’s exactly how it works in Windows (or so I’m told). In Windows, you get the following events given our string of h’s:

P = KeyPressEvent
R = KeyReleaseEvent

h h h h h h h h
P P P P P P P P R

Just what we’d expect, a bunch of KeyPress Events and one KeyRelease event. But that’s not how it works in X. In XWindows, you get a KeyRelease for every KeyPress Event. In X, it looks something like this:

h h h h h h h h
PRPRPRPRPRPRPRPR

If you google around on the Internet for a bit, you’ll notice a number of people coming to this realization and a handful of ways to get around it (none of which worked for my needs). It seems like such a simple thing at first glace. Then there’s hints that lead to nowhere. For example, a fake KeyRelease event is paired with a corresponding KeyPress event. If it’s a fake, it’ll have the same serial number in the X event structure as the next KeyPress event.

That’s great, except there’s no really good way to know if the LAST KeyRelease is the actual release. There’s a hack on a Sun forum to peek into the next event in the event queue, but that doesn’t work reliably either, because it’s very possible that the next event will not be the corresponding KeyPress and/or the next event hasn’t even been queued yet. You could bypass the first problem by using XMaskEvent or XCheckMaskEvent to search the event queue for only keypress events. But if the paired KeyPress event isn’t there when your event handler runs, then what?

So, the solution I’ve come up with (I think), is the following:

When you get a KeyRelease event, use XQueryKeymap to query the X server and find out what keys are currently pressed down. If the key you’re looking for is pressed down, ignore the Release event. With this method, there’s a chance that by the time the event handler runs, the user has let up the key and pressed it down again, but for my purposes, this scenario doesn’t matter.

What a pain. When I finish up my event handler, I’ll post up a simple version that can be easily modified.

UPDATE: Here’s some code to deal with this. Put this inside your KeyRelease XEvent handler. This assume that xevent->xkey.keycode is the keycode for the key you are checking.

char pressed_keys[32];
XQueryKeymap(Xwindow, pressed_keys);
isPressed = (pressed_keys[xevent->xkey.keycode >> 3] >> (xevent->xkey.keycode & 0x07)) & 0x01; 
if (! isPressed) {
        // the key has physically been released
}

General , , ,

  1. Anon
    December 7th, 2009 at 18:28 | #1

    XQueryKeymap does an XFlush, and thus has performance consequences. It’s better simply to check to see if the next event following the release is the a press of the same key with the same timestamp. Like so:

    if (ev->type == KeyRelease && XEventsQueued(dpy, QueuedAfterReading)) {
    XEvent nev;
    XPeekEvent(xdpy, &nev);

    if (nev.type == KeyPress &&
    nev.xkey.time == ev->xkey.time &&
    nev.xkey.keycode == e->xkey.keycode) {
    /* Key wasn’t actually released */
    }
    }

  2. Anon
    December 7th, 2009 at 18:48 | #2

    Also, in response to your comment: “There’s a hack on a Sun forum to peek into the next event in the event queue, but that doesn’t work reliably either, because it’s very possible that the next event will not be the corresponding KeyPress and/or the next event hasn’t even been queued yet.”

    If it’s an auto-repeat, it will always be the next event. The X server is single threaded, and the events are placed into the queue at the same time. They may not have been read yet, but that is easily worked around.

  3. devildrey33
    September 8th, 2011 at 22:31 | #3

    I tryed the XEventsQueued solution, when i made the tests i press a key, move a mouse, and finaly release the key. This works fine for me in my ubuntu under virtual box.

    This is my console test result :

    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnMouseMove(cX = 79, cY = 38)
    OnMouseMove(cX = 79, cY = 39)
    OnMouseMove(cX = 79, cY = 40)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnMouseMove(cX = 79, cY = 43)
    OnMouseMove(cX = 79, cY = 44)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnMouseMove(cX = 78, cY = 44)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnMouseMove(cX = 77, cY = 44)
    OnMouseMove(cX = 77, cY = 45)
    OnMouseMove(cX = 76, cY = 45)
    OnMouseMove(cX = 76, cY = 46)
    OnMouseMove(cX = 75, cY = 46)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnKeyPress (KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)
    OnKeyRelease(KeyCode = 0×0026, AG = 0, C = 0, S = 0, CL = 0, NL = 1)

  1. No trackbacks yet.