Key Sequence Transformation in X11

This article is originally titled Abandoning treatment for X11, for my xkeymacs project.


I think I’ve done all that’s possible to achieve with X11 API in this project, but there still remain a bunch of issues. This document is about what they are and why they may not be resolved as an X11 application.

Before we start with the issues, we need clarify the situation first.

There are three ways to grab key events in X11: XGrabKeyboard, XGrabKey with GrabModeSync and XGrabKey with GrabModeAsync. In the meantime, there is only one way to send a key event in X11: XTestFakeKeyEvent, while XSendEvents marks events sent as synthesized and a bunch of applications (such as xterm) ignore events with this flag for some security reasons, according to various sources.

If you want to replay the key event you just grabbed, use XAllowEvents with ReplayKeyboard. This requires the key event to come from XGrabKey or a previous XAllowEvents with SyncKeyboard, but not from a XGrabKeyboard.

You cannot use XGrabKeyboard along with XTestFakeKeyEvent because the former sets an active grab and it will receive the key event faked by yourself. If you want to XUngrabKeyboard and wait for XTestFakeKeyEvent to accomplish, sadly there seems to be no way. XFlush flushes your own request to X server, while XSync flushes and make X server process all requests and generate events, but not handling them, according to this thread.

XGrabKey is called a passive grab. It is activated and then behaves like XGrabKeyboard when the registered key and modifier combination is found, and actually ungrabbed after the KeyRelease event of that combination (see this mail). So if you send a key in KeyPress event, you will intercept the key you just sent. To workaround this issue, you can do XUngrabKeyboard before you send the key. This results in garbage KeyRelease events sent to the focused window, however it is normally ignored. Another workaround would be sending the key in KeyRelease event, which works, but is not the desired behavior.

Note that if you replay a key event with XAllowEvents in KeyPress, It will also result in garbage KeyRelease events, because according to the documentation the replayed event will ignore any key grabs set by any client.

So here is the first issue.

  • Grabbed keys sent by XTestFakeKeyEvent cannot be replayed

    When testing the application, I was surprised to find that when I sent keys that are also grabbed by myself and deliberately do the replay in KeyPress, I still receive KeyRelease events for them. That is to say the XAllowEvent replay failed. I guess this is because XTestFakeKeyEvent is part of the XTest extension and is aimed for automating testing, so the internal implementation did not take this situation into consideration. So there may be no workaround for this.

    An example for this issue can be when I grabbed Control+X for C-x mode and I send Control+X inside the handling of Control+W

When sending some key combination, one have to know the status of the modifier keys and adjust them, send the key, and then restore them. This is expected to happen in a very short period of time so the user won’t notice. To accomplish this, XQueryKeymap and multiple XTestFakeKeyEvent is used.

And the second issue.

  • Sending keys containing F1~`F8causes user to be switched totty`

    When sending keys containing F3 or F4, sometimes I got switched to tty by an unexpected Control+Alt+F3/4 combination. This happens for most of the time when I send Control+F4 for (Control+X) k. You can see that there is nothing about Alt, but still I was almost always switched to tty4, however xev reported the expected key sequence for this situation. The underlying logic is unknown, it may have something to do with tty switching hot key implementation.

And some other issues.

  • Auto repeat cannot work if we intercept the KeyPress event

    X server generates continuous paired KeyPress and KeyRelease event for clients if a key is physically held for a long time. However if we intercept the first KeyPress event, and send another key event, then the auto repeat just disappears. There doesn’t seem to be a solution for this issue.

  • Efficiency

    There is a perceptible (annoying but bearable) pause when using the above stated approach to implement keyboard macros. Seems no way to resolve either.

    Note that using GrabModeAsync helps to improve the performance a little bit, but it results in various problems that I haven’t investigated yet.

So the conclusion is that, while X11 provided some API for keyboard event operations, it is vague in documentation and working mechanism, and clearly not designed, nor suitable for translating key sequences into others. This may be the reason why almost no such project like this is known.

For an alternative API, some people in a mailing list suggested Atspi, which I haven’t tried out myself.


UPDATE:

Later I found out that this can be achieved with evdev and uinput.

For a python implementation, see my pykeymacs.