Tiling Window Managers: Saving Window Positions
Some stacking window managers restore the position of windows for some applications when they are restarted. What does this mean with tiling window managers?
1 Context
I marvelled at FVWM the day I saw it open all my Firefox windows exactly where I'd left them when I last exited it. They were all restored back into their respective desktops, at the right position, to the pixel. And it's something I immediately missed when I switched to xmonad, where window position aren't (x,y) coordinates but a space in a layout. So I was wondering, how hard could it be to translate – misuse, really – (x,y) coordinates that some programs such as Firefox record to a position in a workspace with a tiling window manager using layouts?
2 An attempt with dwm
2.1 The case of a dynamic tiling window manager such as dwm
In a dynamic tiling window manager, it wouldn't make sense to try and use the (x,y) coordinates to target a given area, container, frame since this has a different meaning according to the layout currently in use. Instead, the idea would be to use the (x,y) position as a code referring to a workspace or – in dwm parlance – tag, which is arguably not as good as what a stacking window manager would achieve in positioning the window to the pixel. But it's the best we can reasonably do.
2.2 Event Flow
It doesn't seem to be possible to record the position right before a window closes because it's already too late when it gets the event notifying the window is being closed. We're therefore better off setting (x,y) each time we receive a ConfigureNotify
event to basically correct it throughout its life cycle. Setting (x,y) triggers another ConfigureNotify
, so we need to do so sparingly and check if (x,y) isn't already set to something sensible before taking any action.
2.3 Tagging Code
As a first attempt, let's try to set x
to the tag bit mask and y
to some arbitrary value that says x
is a tag (e.g. -666). If y≠-666
there's no tagging information and y
should be set to -666 regardless. If y=6
and tags≠x
, tags
should be set to x
because we're dealing with a new window which remembers how it's tagged. We don't want this to mean that the tagging has changed and x
hasn't been updated yet as we'll be updating x
in the same event whereby tags
is updated.
Once y
and/or tags
have been modified, we probably need to somehow apply the change. It seems that XConfigureWindow()
, or its little brother XMoveWindow()
, both do the business. They also physically move the window, though, whether you XSync()
or not, and that's a problem I've not solved yet.
3 An attempt with Notion
3.1 The case of a manual tiling window manager such as Notion
Unlike with a dynamic tiling window manager, where you have little control over what goes on with areas holding windows, a manual tiling window manager has static frames which you can uniquely refer to. Worse that could happen, is if the frame is destroyed, in which case we'll fall back to the dynamic tiling window manager use case whereby the window will be placed randomly in a specific workspace.
The only promising approach I can currently think of and which I'm trying to implement in Notion is to record the order in which Firefox windows are closed and use this order (or its reverse) to restore their locations. That's not perfect either when windows which are intentionally closed enter into the picture, in which case it's probably impossible to tell whether a window should be restored or whether it's a new one which should be opened in the currently-focused frame.
3.2 Basic Case: Windows Closing Because of a Firefox Shutdown
For instance, assume the following Firefox windows which have been recorded to open as follows:
Window | Frame |
---|---|
w0 | f0 |
w1 | f1 |
w2 | f2 |
Suppose, for starters, that Firefox was turned off. The windows will be recorded to have been turned off as follows:
Order | Window | Frame | How? |
---|---|---|---|
1 | w0 | f0 | Firefox termination |
2 | w1 | f1 | Firefox termination |
3 | w2 | f2 | Firefox termination |
Next time Firefox is spawned, its windows will be restored in the following frames in this order (or the reverse, doesn't matter):
Order | Window | Frame |
---|---|---|
1 | w0 | f0 |
2 | w1 | f1 |
3 | w2 | f2 |
That's exactly what we want. But it's not the only case.
3.3 Harder Case: Windows Closing Intentionally or Because of a Firefox Shutdown
How to tell the difference between a window that closes because Firefox shut down or because we deliberately closed it, in which case we don't want it restored (there's nothing to restore)? Of course, the problem could be shifted by recording all the closes and drop the extra ones upon restoring if there aren't enough windows to restore, but then how do you know when you've restored the last window? In other words, how can you make sure that you won't try to restore a window that's actually new?
Order | Window | Frame | How? |
---|---|---|---|
1 | w0 | f0 | Specific close |
2 | w1 | f1 | Firefox termination |
3 | w2 | f2 | Firefox termination |
When restoring, there should only be 2 windows restored. Assuming you'll first do w2, then w1, how do you decide that you shouldn't mistake any new window for w0 and place it in f0 instead of opening it in the currently-focused frame?
3.4 Possible Approximations
Clearly we'll have to approximate.
- We could let the user decide when windows should be restored. That's dangerous, however, because if he forgets to do so, the history could be lost. (Unless we keep a history of histories, but how to choose the history, then? Too complicated.)
- We could use some timing, but things could go a bit too random. If we overestimate the time it takes to restore, new windows will be placed where specifically closed windows used to be; if we underestimate it the last few windows which should have been restored will bluntly end up in the currently-focused frame.
- We could rely on Notion bindings and say that new windows should only be restored if you haven't spawned Firefox through its key binding in the meantime. This could work much of the time because we only open Firefox windows this way. In the few cases when we won't, new windows will be misplaced in the wrong frames, in the wrong workspaces. Again, that'll be very rare.
One might also be wondering whether we want restores to be manual because there would be cases where we simply don't want them e.g. on machines which are restarted once a day. But then that should be a host-specific setting.
Again, this assumes we're recording all closes – regardless of their types – and only sort things out upon restore. Using the above tricks would have worked well otherwise because while there are very few ways we use to spawn Firefox windows (e.g. from a terminal or a Notion key binding – even programs which open Web pages will only cause Firefox to open a new tab, not a new window), there are many ways of closing them (e.g. crashes).
4 References
- dwm – dynamic window manager
- Adrian Nye, Xlib Programming Manual
- Notion – Free Tiling Tabbed Window Manager
- Workspace Window Placement Hack: Choose which apps open in which workspaces!