polyethene/IronAHK — Issue #42: make mappings like a::b and b::a work
Author: madewokherd
Created: 2011-11-30 15:39:52+00:00
State: open

This is mainly an excuse to get down some thoughts about the X protocol and how to use it to do these crazy things. Maybe you guys have already figured this stuff out.

As a concrete example, consider the following script:

a::b
b::a
CapsLock & B::Send, Bacon

In order to make this work properly, we need to block "a" and "b" keypresses from the user, in all cases, from arriving at their destinations. This is fairly easy to do; use XGrabKey. However, our own events will also trigger XGrabKey, and we need to make sure they go through.

So, here's what I have so far:
* First, grab all our hotkeys on the root window with XGrabKey and GrabModeSync.
* When we get a keypress from our passive grab, make a note that we now have effectively grabbed the keyboard, and make a note of all the keypresses we get without a corresponding keyrelease during that effective grab. The corresponding keyreleases will go to our client so that keyups and keyreleases always match.
* When we block an event that we did not Send (for various reasons because of a passive grab), decide whether that event should go through to other applications. If not, call XAllowEvents(SyncKeyboard). If we do want the event to go through, call XAllowEvents(ReplayKeyboard).
* To Send an event, use XTestFakeKeyEvent to simulate the appropriate event. Make a note of the sequence of events we've simulated so we can ignore our own events.
* When we get an event that we haven't simulated, clear our list of simulated events.
* In some cases, such as CapsLock & B, we'll have to grab keys temporarily in response to other passive grabs that trigger.

Problems:
* I don't know whether it's possible to grab the keyboard using XGrabKeyboard while it is frozen. If it's not possible, we won't be able to activate menus using hotkey presses.
* We still can't get any events during an active grab. AFAIK that's unsolveable.
* This may have some race conditions. For example, if we simulate an event on a key release that triggers a menu, followed by keypresses that would go to one of our passive grabs, our passive grab may never trigger (because menus actively grab the keyboard). If the user later presses that passively grabbed key, without any other input in between, we won't be able to tell that the keypress wasn't caused by us. Conversely, we can't be sure that all our simulated input will go through before user input.
* I'm unsure of whether we'll get keyup events that correspond to keydown events that we replay during a passive grab.
* Some combinations can still cause problems because a key can't be in two logical states at once. I.e. if we simulate a keyup in response to a keydown, we have no way of finding out when the real keyup happens. This also means that the remappings for "a" and "b" above cannot be used at the same time. We could try to simulate some events with XSendEvent instead, but that has its own problems.

I saw talk in another issue of using at-spi2 for keyboard events, but I don't think it will get us much in that areas. In particular, I don't think it will allow us to block keyboard events from being delivered when we need to. In any case, these ideas are not mutually exclusive; we can use X to grab keys and get events during passive keyboard grabs, and at-spi2 to get them the rest of the time. Maybe it will provide a way to simulate events without sending them through X, which could help, but I doubt it. (That said, at-spi2 allows for manipulating guis in a lot of other ways, which is awesome.)