i3-like multihead in bspwm
i3 has fantastic focus and window handling for multiple monitors out of the box in my opinion. I didn’t realize that I missed this until I was on campus and used i3 on a multihead setup there, so I set out to make my bspwm setup act the same.
1. Initial #
In the example sxhkd in the bspwm repo, the main window focus keybind is set up as such:
super + {_,shift + }{h,j,k,l} bspc node -{f,s} {west,south,north,east}
I’m going to separate this into 2 parts, a focus part and a movement part.
2. Focus #
So taking the focus part out of the above we get:
super + {h,j,k,l} bspc node -f {west,south,north,east}
So, This will focus on a window in a direction determined by keypress, which are vi-like here. However, lets say you have 2 monitors, and one is empty:
the command bspc node -f east
will fail, as there is no window to the
right, and no action occurs as a result.
2.1. Desired behavior #
In the above, on a focus right, I want the cursor to appear in the middle of the right monitor as well as focus on that monitor.
super + {h,j,k,l} dir={west,south,north,east}; \ bspc config pointer_follows_monitor true; \ bspc config pointer_follows_focus true; \ if ! bspc node -f $dir; then \ bspc monitor -f $dir; \ fi; \ bspc config pointer_follows_monitor false; \ bspc config pointer_follows_focus false
When the node right command fails in the above scenario, the monitor to the right will be focused, and the pointer will be there.
Why is half of this binding setting
pointer_follows_*
values?
So let’s say you are moving a floating window with your mouse across the 2 screens - with either of the two pointer options above enabled, when you cross the border the cursor will move, and the window with it. This window snapping out from under you can be quite annoying.
3. Movement #
Movement part of the example sxhkd:
super + shift + {h,j,k,l} bspc node -s {west,south,north,east}
Alright, so what this does is switch the currently focused window with another window by direction, although the focus remains on the original window spot, which I’ve found to be quite annoying. It feels more natural to keep focus on the same window and ’carry’ it around your workspaces/monitors.
3.1. Desired behavior #
When a window is switched, retain focus on the original window that was moved. If switching to an empty monitor, move the window to that monitor. If switching to an occupied monitor, move the window to that monitor instead of switching windows.
Under this behavior, the keypress super + shift + l
would yield the
following results:
Here is an implementation:
super + shift + {h,j,k,l} dir={west,south,north,east}; \ wid=$(bspc query -N -n); \ mon=$(bspc query -M -m); \ bspc config pointer_follows_focus true; \ if ! bspc node -f $dir; then \ bspc node -m $dir; \ bspc monitor -f $dir; \ else \ new_mon=$(bspc query -M -m); \ if [ $new_mon -eq $mon ]; then \ bspc node -s $wid; \ else \ bspc node $wid -m $new_mon; \ fi; \ bspc node -f $wid; \ fi; \ bspc config pointer_follows_focus false
You can see from this I used the same strategy as above - check if the window focus command failed and act accordingly - if it does not fail, then check if we are on a new monitor - if so, move the original window to the new monitor.
wooo.
This was initially authored in 2015, and nowaways I would recommend putting your focus/move/resize logic into scripts and then binding sxhkd keybinds to that – so that the sxkhd can be clean, and you can enjoy editing shell without having to do the ;\;\;\
dance.