Manual Window Placement in i3 (Part 2)

Page Trail: [[ Manual Window Placement in i3 (Part 2) ]]
2013-05-06 21:39

This is the second part of a series on making the i3 window manager work the way I want. I left off last time with the goal of changing the way windows are placed as they are created, and I had a couple of pointers from the i3 hacking howto for where to start looking. This post covers how I've set up my test environment.

I started looking in src/manage.c but soon found my way into src/con.c, which does most of the grunt work surrounding containers. There are a ton of conditional branches in the i3 source code, so running i3 with debug logging on (i3 -d all) was essential in figuring out which code paths were being executed.

One particularly relevant log message,

Inserting con = x after last focused tiling con y
led me to con_attach(), the function in charge of placing a new window in i3's layout tree. I think that's one key function I'll need to change.

I started playing around with changing the layouts of things and creating extra containers in there but quickly got frustrated. The problem was that I was using my buggy version of i3 while iterating on the code and testing. Testing also became difficult, since running the tools to analyze what's going on requires opening new windows, but opening new windows affects i3's state.

A better approach is to run i3 inside a nested X server. This way you can keep your editor, browser, and other tools open outside of the test environment, and keep the test environment minimal, clean, and easy to reset.

I had used Xnest in the past, but I found that i3bar didn't display fonts for me under Xnest. I don't know what the problem was there, but I came across Xephyr, a replacement for Xnest that supports modern X extensions. Fortunately, Xephyr can run i3 and i3bar properly. Xephyr allows the nested server to grab the keyboard (toggled with Ctrl+Shift), which is quite handy for window manager development.

I'm also getting a lot of mileage from i3's contrib/ script, which uses i3's JSON-based IPC interface to show you a graphical representation of the layout tree. This script has been helpful in understanding the tree transformations that occur as I test my changes to i3. For example, it's showing me that I have a bunch of nested containers with only one child (oops). I've made a few minor improvements to the script, and Michael Stapelberg has already accepted a couple of these minor patches.

To use contrib/ with a nested X server, you need to help it find i3's IPC socket. It uses AnyEvent::I3 internally, whose default constructor finds the i3 running on your current DISPLAY. You don't want to launch the script with the DISPLAY set to the nested X server, since then it would launch also launch gv inside the nested X server. Instead, construct the AnyEvent::I3 instance as follows, for a nested X server running on DISPLAY=:1:

chomp(my $path = qx(DISPLAY=:1 i3 --get-socketpath));
my $i3 = i3($path);

Now that I can use my editor reliably and query what's going on in a controlled testing environment, I should be able to make some real progress.