Why Does XQueryKeymap() Return an Empty Bit Field in C? (And How to Fix It)
Understanding Why XQueryKeymap() Returns All Zeros
When working with low-level keyboard input in Linux using the Xlib library, developers often turn to XQueryKeymap() to read keyboard states. However, a common frustration is that the returned 32-byte bit array contains only zeros, even while typing keys in the console.
If your call to XQueryKeymap() is returning an empty bitfield (all zeros), it is almost always caused by one of three primary reasons: polling timing issues, opening/closing the X display inside a loop, or running on a Wayland session.
1. Instantaneous State vs. Buffered Input
The most common misconception about XQueryKeymap() is treating it like an input buffer. XQueryKeymap() does not read keystrokes from an event queue or buffer; it captures an instantaneous snapshot of the physical keys currently held down at the exact microsecond the function executes.
In the provided code snippet:
XQueryKeymap(display, keys_return);
// ...
usleep(500000); // 500ms delayWith a 500ms delay, the CPU spends 99.9% of its time sleeping. If you tap the D key, you will likely press and release it while the program is sleeping. By the time XQueryKeymap() queries the X server, the physical key has already been released, resulting in an array of all zeros. To see a value change, you must physically hold the key down through the moment XQueryKeymap() executes.
2. Opening and Closing Display in Every Iteration
In the example code, XOpenDisplay(0) and XCloseDisplay(display) are invoked inside the while(1) loop:
while(1) {
Display *display = XOpenDisplay(0); // Inefficient and introduces latency
// ...
XCloseDisplay(display);
usleep(500000);
}Constantly opening and closing the socket connection to the X server adds substantial overhead, introduces socket exhaustion risks, and desynchronizes timing. The display handle should be opened once before the loop and closed once after the loop finishes.
3. Wayland vs. X11 Security Restrictions
If you are testing this on a modern Linux distribution (such as Ubuntu 21.04+, Fedora, or Debian with GNOME/KDE), your desktop environment likely defaults to Wayland rather than native X11.
For security reasons (specifically keylogger mitigation), Wayland and XWayland do not allow arbitrary clients to snoop on global keyboard states across windows. Under XWayland, XQueryKeymap() will frequently return zeros unless your specific application window currently has active keyboard focus.
To test if this is the issue, check your display server by running:
echo $XDG_SESSION_TYPEIf it returns wayland, XQueryKeymap() will not work globally. You will need to switch to an X11 session or use desktop-native tools (such as Wayland input protocols or reading /dev/input/event* with root/input group privileges).
A Correct Implementation of XQueryKeymap()
To inspect active keys properly, move the display initialization outside the loop, reduce the polling delay (e.g., 20–50ms), and inspect the bits using bitwise operations rather than directly printing raw bytes.
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <X11/Xlib.h>
// Helper to check if a specific keycode is currently pressed
bool is_key_pressed(char keys[32], KeyCode keycode) {
return (keys[keycode / 8] & (1 << (keycode % 8))) != 0;
}
int main() {
Display *display = XOpenDisplay(NULL);
if (!display) {
fprintf(stderr, "Unable to open X display\n");
return 1;
}
char keys[32];
printf("Polling keyboard state. Hold down keys to see them registered...\n");
while (1) {
// Query instantaneous keyboard state
XQueryKeymap(display, keys);
bool any_pressed = false;
for (int keycode = 0; keycode < 256; keycode++) {
if (is_key_pressed(keys, keycode)) {
printf("Keycode %d is down | ", keycode);
any_pressed = true;
}
}
if (any_pressed) {
printf("\n");
}
// Poll every 30 milliseconds (around ~33 Hz)
usleep(30000);
}
XCloseDisplay(display);
return 0;
}Alternative: Use X11 Events Instead of Polling
Polling with XQueryKeymap() is CPU-intensive and prone to missing quick keystrokes. If your goal is to handle keyboard input reliably within an application, use event-driven programming with XSelectInput() and XNextEvent():
#include <stdio.h>
#include <X11/Xlib.h>
int main() {
Display *display = XOpenDisplay(NULL);
if (!display) return 1;
Window root = DefaultRootWindow(display);
// Listen for KeyPress and KeyRelease on the root window
XSelectInput(display, root, KeyPressMask | KeyReleaseMask);
XEvent ev;
while (1) {
XNextEvent(display, &ev);
if (ev.type == KeyPress) {
printf("Key pressed: %u\n", ev.xkey.keycode);
} else if (ev.type == KeyRelease) {
printf("Key released: %u\n", ev.xkey.keycode);
}
}
XCloseDisplay(display);
return 0;
}Summary
- Hold keys down:
XQueryKeymap()checks instantaneous state, not past key events. - Optimize the loop: Keep
XOpenDisplay()outside the loop and decrease the sleep duration. - Check your session: Ensure you are running a native X11 session if you need global input tracking, as Wayland intentionally prevents background key snooping.