sticky tiling windows in bspwm

bspwm has a useful sticky flag, which keeps the window on whatever the focused desktop is, basically like using node -d whenever you change desktops. for floating windows this works very well, but the situation is more complicated if you set the sticky flag for a tiled window.

It works, of course, but since the sticky window is moved away from a desktop when it stops being focused, it loses its location in the desktop's layout, and switching back to that desktop places the sticky tiled window in the location any window would be. This is not ideal if you don't want to constantly reconfigure your layout after switching to a browser or something similar.

There's actually a draft pull request from 2019 on bspwm's Github that addresses this, #1032, but patching bspwm to solve this problem is quite bothersome if you use a binary package. Plus, bspwm's great advantage is how extensible and scriptable it is; there must be a way to solve this problem that way!

There absolutely is, but it took a while for me to figure it out. I spent a few hours on this, getting it to almost work, but be flawed in a way that made it not worth using. But I finally got it working after emanuele6 helped me on IRC (thanks!), and now I use it. So, here it is, bsp_movedesk:

 #!/bin/sh
 # deal with sticky windows when moving workspaces.
 test -z "$1" && exit
 # path format: desktop/stickywindow: parent
 bspc query -N -n '.local.leaf.!hidden.sticky' | while read -r wid; do
 	[ "$(bsp_countwindows)" = 1 ] && break
 	dir="$HOME/run/bspsticky/$(bspc query -D -d focused)"
 	mkdir -p "$dir"
 	bspc node "$wid" -i
 	bspc query -N "$wid" -n '@brother.leaf.!window' > "${dir}/${wid}"
 done
 bspc desktop -f "$1"
 bspc query -N -n '.local.leaf.!hidden.sticky' | while read -r wid; do
 	dir="$HOME/run/bspsticky/$(bspc query -D -d focused)"
 	[ "$(bsp_countwindows)" = 1 ] && break
 	stdir="${dir}/${wid}"
 	if [ -s "$stdir" ]; then
 		rece="$(cat "$stdir")"
 		bspc node "$wid" -n "$rece"
 		rm "$stdir"
 	fi
 done
 # remove hidden window's receptacles. sucks but better than the alternative
 while bspc node 'any.local.leaf.!window' -k; do :; done

The script is a wrapper around desktop -f, and can be used by replacing instances of bspc desktop -f with bsp_movedesk in your sxhkd config.

It works by reading and writing to a special directory (on my machine $HOME/run/bspsticky, kept inside a ramdisk), which contains directories named with a desktop's ID. These directories contain files named for a sticky node's ID, and contain the ID of a receptacle which the window should be moved to if that desktop is focused. These receptacles are placed before the focused desktop changes if there are multiple windows open (because doing this when the sticky window is the only open window is unnecessary.)

This is the bsp_countwindows script:

 #!/bin/sh
 bspc query -N -n '.window.!hidden' -d | wc -l

There are definitely improvements that could be made here, but It Works. the end.

---

here's a completely unrelated script, which swaps a window's parent to its sibling, in case you want that:

 #!/bin/sh
 # move focused window to sibling
 set -e
 # possibly causes a race condition if focused changes during script?
 focused=$(bspc query -N -n)
 if [ "$1" = "2" ]; then
     bspc node -f @parent/parent/brother
 else
     bspc node -f @parent/brother
 fi
 bspc node -i
 bspc node $focused -n $(bspc query -N -n .local.leaf.!window)
 bspc node $focused -f