my i3-emacs integration
tiling window managers are wonderful. ultra-flexible text editors are also<br>wonderful. for a spell, i thought i'd found the ideal solution in exwm… and i<br>think it would have been, save for the fact that i use ordinary, graphical<br>windows as much, if not more than, text buffers, and sometimes those windows are<br>from dodgy programs (e.g., steam) that have trouble with EXWM's fancy input<br>methods.
but i still like emacs a lot. hell, it switches light and dark mode on my<br>machine (still)! so, inspired by such posts as \(\sqrt{-1}\)'s, i set out to get<br>a common set of keybindings between emacs and i3, along with some sane defaults<br>around opening terminals, splitting windows, etc.
i first tried a script with xdotool and emacsclient, as in the above-referenced<br>article, and that worked… but proved to be too slow: i saw lags of up to a<br>second<br>timing the script gave a latency of 30 to 100 ms from invocation to exit,<br>which is still pretty slow but not a dealbreaker. i still don't know where the<br>rest of the latency came from.<br>between sending input to emacs and it actually registering. i don't know<br>if this is because of my emacs version, other packages, emacsclient weirdness,<br>whatever, but that wasn't going to cut it. plus, it seems wasteful to launch a<br>whole shell-plus just to register a keypress, especially for some of the most<br>commonly pressed key combinations i use. so i did the only rational thing: i<br>patched i3.
my objective was: instead of unilaterally handling commands bound via i3's<br>bindsym, add an option to check the currently focused window to see if it's<br>emacs, and if it is, pass the keypress event through to it.<br>note that this feature has been requested in the past, and the i3<br>maintainers have deemed it to be out of scope. i would make this a more<br>fully-fledged patch if that were not the case.<br>if emacs<br>decides "no, i3 should actually handle this," it can use i3-msg to route the<br>action back.
i succeeded in that, though it might not be the most elegant thing in the world.<br>if you know about xcb and want to give me advice, please! send me an email at<br>web@khz.ac.
relevant i3 code
i3 uses xcb_grab_key() with owner_events = 0 on the root x window to intercept<br>keys. the relevant code in src/bindings.c looks like<br>all unpatched code snippets refer to i3 4.25.1, if you want to follow<br>along.
172struct Binding_Keycode *binding_keycode;<br>173TAILQ_FOREACH(binding_keycode, &(bind->keycodes_head), keycodes) {<br>174 const int keycode = binding_keycode->keycode;<br>175 const int mods = (binding_keycode->modifiers & 0xFFFF);<br>176 DLOG("Binding %p Grabbing keycode %d with mods %d\n", bind, keycode, mods);<br>177 xcb_grab_key(conn, 0, root, mods, keycode, XCB_GRAB_MODE_ASYNC,<br>178 XCB_GRAB_MODE_ASYNC);<br>179}
this code isn't super relevant, except that i3 entirely steals its bindings from<br>anyone else by intercepting on the root window. if you're thinking that setting<br>owner_events = 1 to allow event passthrough so we don't have to re-emit… that<br>would be great, but that appears to instruct x to pass the event through to only<br>the root window. which is not what we want.
in i3's handle_event() in src/handlers.c, if it gets an xcb event, it sends it<br>off to a specialized handler based on its type:
1481switch (type) {<br>1482case XCB_KEY_PRESS:<br>1483case XCB_KEY_RELEASE:<br>1484 handle_key_press((xcb_key_press_event_t *)event);<br>1485 break;<br>1486 // ...<br>1487}
handle_key_press() (src/key_press.c) looks like this — it receives a keypress<br>event, looks up a binding based on that event, and, if it finds one, runs the<br>associated command:<br>yes, i do know one of the lines is too long. i opted to<br>leave it that way, as that's how it is in the i3 source. i should note, though:<br>i3 has really nice source code! i found it very readable and pleasant to work<br>inside.
12/*<br>13 * There was a KeyPress or KeyRelease (both events have the same fields). We<br>14 * compare this key code with our bindings table and pass the bound action to<br>15 * parse_command().<br>16 *<br>17 */<br>18void handle_key_press(xcb_key_press_event_t *event) {<br>19 const bool key_release = (event->response_type == XCB_KEY_RELEASE);<br>20<br>21 last_timestamp = event->time;<br>22<br>23 DLOG("%s %d, state raw = 0x%x\n", (key_release ? "KeyRelease" : "KeyPress"), event->detail, event->state);<br>24<br>25 Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event);<br>26<br>27 /* if we couldn't find a binding, we are done */<br>28 if (bind == NULL) {<br>29 return;<br>30 }<br>31<br>32 CommandResult *result = run_binding(bind, NULL);<br>33 command_result_free(result);<br>34}
notably, this function receives the original xcb_key_press_event_t from xcb,<br>which (after a bit of reading and experimentation) i realized you could just<br>re-emit diretly via xcb_send_event().<br>unfortunately, the window receiving<br>the event will still lose focus, as i3 is intercepting key events globally. i<br>haven't fixed this; let me know if you know how.
this looks like a reasonable place to make a change!
the patch
Binding struct changes
i decided to...