incise.org: xlib key passing

Update: This page is wrong. First off, using this method is slow when using X11 over a network, since every single key/button even requires a round-trip to determine what to do with it. Second and more importantly, there seems to be a problem with programs which are too slow to keep up with repeated key presses—events get dropped or repeated in strange ways and key repeat breaks. I can't say I understand it fully, because I haven't gone into Xlib or the X server code to figure out exactly how this stuff is implemented. All I can say is that it's extremely convoluted and undocumented and I reached my personal code insanity limit in trying to figure it out. So it's back to asynchronous XGrabKey and XGrabButton... bummer.

The following is the old content of this page, which should not serve as a definitive answer as I once though it was.


There is a problem with doing key combos with Xlib, and that is, how do can you dynamically decide to not handle a key combo? Generally if you're writing a window manager or keygrabber or whatever, you read a config file and grab some known key combinations, and it's that simple. But what if your key combo handling "rules" aren't quite so static? What if you want to match against some sort of logic that can't be predetermined at the time of your XGrabKey calls?

Well the answer is pretty simple, but seemed elusive for quite a while. Basically, to swallow a keycombo, you use XAllowEvents with ReplayKeyboard on both the KeyPress and KeyRelease events that match your keybinding. To pass a keycombo on to the client windows, you use XAllowEvents with AsyncKeyboard and do an XFlush. If you don't do an XFlush, then quickly repeated keycombos (like when you're holding the keys down and they auto-repeat) will get "lost."

Example:

/* Written by Nick Welch <mack@incise.org>, 2005.
 *
 * This software is in the public domain
 * and is provided AS IS, with NO WARRANTY.
 */

#include <stdio.h>
#include <X11/Xlib.h>

void swallow_keystroke(Display * dpy, XEvent * ev)
{
    XAllowEvents(dpy, AsyncKeyboard, ev->xkey.time);
    /* burp */
}

void passthru_keystroke(Display * dpy, XEvent * ev)
{
    /* pass it through to the app, as if we never intercepted it */
    XAllowEvents(dpy, ReplayKeyboard, ev->xkey.time);
    XFlush(dpy); /* don't forget! */
}

int main(void)
{
    Display * dpy = XOpenDisplay(0x0);
    KeyCode F1;
    XEvent ev;

    if(!dpy) return 1;

    F1 = XKeysymToKeycode(dpy, XStringToKeysym("F1"));

    /* grab our key combo -- we use AnyModifier because of caps lock/num lock
     * complexity.  just grab every F1 press.
     */
    XGrabKey(dpy, F1, AnyModifier, DefaultRootWindow(dpy), 1, GrabModeSync,
            GrabModeSync);

    for(;;)
    {
        XNextEvent(dpy, &ev);

        if(ev.type == KeyPress  ev.type == KeyRelease)
        {
            /* again the lock key issues -- we just fire if alt is being
             * pressed, regardless if other modifiers are being pressed or not.
             */
            if(ev.xkey.keycode == F1 && ev.xkey.state & Mod1Mask)
            {
                if(ev.type == KeyPress)
                    fprintf(stderr, "got Alt+F1\n");

                swallow_keystroke(dpy, &ev);
            }
            else
            {
                fprintf(stderr, "got (something else)+F1, passing through\n");
                passthru_keystroke(dpy, &ev);
            }
        }
    }
}

/* a simpler pseudo-code version showing only the logic:
 *
 * handle_key_event()
 * {
 *     if key event is alt+f1 keypress:
 *         print "got alt+f1!"
 *         swallow key event
 *
 *     elif key event is alt+f1 keyrelease:
 *         swallow key event
 *
 *     else:
 *         pass through key event
 *         xflush
 * }
 */

comments


Nick Welch <nick@incise.org> · github · twitter