The author details a custom patch to i3 window manager that enables selective keypress passthrough to Emacs, solving latency issues with previous approaches like xdotool/emacsclient while maintaining compatibility with graphical applications. The solution modifies i3's key binding handling to conditionally forward events to Emacs when focused, paired with Emacs Lisp to delegate window management back to i3 when needed.
Tiling window managers and extensible text editors like Emacs represent two pillars of efficient keyboard-driven workflows. Yet combining them often involves trade-offs: EXWM offers deep integration but struggles with graphical applications that expect standard input handling, while traditional setups force context-switching between WM and editor keybindings. The author’s journey—from experimenting with EXWM to trying xdotool-based scripts—highlights a common pain point: latency. Their initial script approach using xdotool and emacsclient introduced 30-100ms of overhead just to invoke the script, plus unpredictable delays within Emacs itself, making it feel sluggish for frequent commands like window navigation.
The core insight driving their solution was recognizing that i3’s architecture intercepts all key events at the root window via xcb_grab_key() with owner_events = 0, effectively blocking passthrough to client applications. Simply flipping owner_events to 1 doesn’t solve the problem, as it only forwards events to the root window—not the focused client. Instead, they patched i3’s key handling logic to conditionally passthrough events based on the focused window’s class. The modification involves three key changes:
- Data structure update: Added a
passthroughfield to theBindingstruct ininclude/data.hto store a target window class (hardcoded to "Emacs" for their use case). - Key press handler enhancement: In
src/key_press.c,handle_key_press()now checks if the current binding has a passthrough class set. If so, it retrieves the focused window viaxcb_get_input_focus(), verifies its class matches the target, and if true, resends the original key event directly to that window usingxcb_send_event()—bypassing i3’s command execution. - Parser extension: Modified i3’s DSL parser (
parser-specs/config.spec) to accept a--passthroughflag inbindsym/bindcodedirectives, passing the flag’s value to the binding configuration function.
The elegance lies in the reciprocity: when Emacs receives a keypress it doesn’t handle (e.g., a movement command beyond its frame boundaries), it uses i3-msg to delegate the action back to i3. This avoids the focus-loss issue inherent in naive passthrough—since i3 only steps in when explicitly asked by Emacs, window focus remains stable. The author’s Emacs Lisp implements this bidirectional flow: functions like nausicaa/emacs-i3-windmove first attempt to find an Emacs window in the target direction; if none exists (or it’s the minibuffer), they fall back to i3-msg focus.
Terminal launching showcases the practical payoff. By binding $super+Return to mistty-create and $super+Control+Return to alacritty-create with the --passthrough flag, these keys flow straight to Emacs when focused. Emacs then either launches a Mistty frame via emacsclient or spawns Alacritty in the current project directory—all without leaving the keyboard. The Nix shell configuration they share ensures reproducible builds of their patched i3, dependencies like libxcb and xcb-util, and tooling for hacking on the WM.
Critically, this approach addresses the latency that doomed the xdotool method. By handling passthrough entirely within i3’s event loop (avoiding shell spawns and round-trips to Emacs for every keypress), it achieves near-native responsiveness. The author notes they still don’t know the exact source of residual latency in their earlier script attempts—whether from Emacs’ input processing, package interference, or emacsclient quirks—but the kernel-level patch eliminates the avoidable overhead.
While the i3 maintainers have deemed similar features out of scope (prioritizing WM purity over application-specific hacks), this patch demonstrates how targeted modifications can bridge workflow gaps without compromising the WM’s core principles. For users who live in Emacs but need reliable GUI app support—think Steam, IDEs, or design tools—it offers a path to unified muscle memory: the same Super+h/j/k/l moves panes in Emacs and i3, with focus managed intelligently at the boundary. The patch isn’t just about keybindings; it’s about redefining where the editor ends and the window manager begins.
Comments
Please log in or register to join the discussion