Sway multiscreen workspace switching

2021-11-21

I recently switched jobs and now have a permanent dual screen setup for Sway. Arranging monitors is neatly documented.

However, each screen is its own workspace. You can naturally assign workspaces to certain monitors, but you have to switch to them separately.

I often have tasks where I use both screens:

With the default config, to switch from coding to communication, I would need to switch the workspace for the left monitor and the right monitor separately.

I want to just do that with a single button press.

(Others have requested such a feature from Sway and even submitted different solutions in this issue, but since it is not a default i3 feature, there seems to be little interest in the suggestions. They even submitted similar issues to i3, but it was closed due to the possibility of external implementation.)

Version 1

Sway allows you to just chain workspace commands behind a bindsym.

# switching workspace
bindsym $mod+1 workspace  1-1; workspace  1-2
bindsym $mod+2 workspace  2-1; workspace  2-2

# moving windows to workspaces
bindsym $mod+Shift+1 move container to workspace  1-1
bindsym $mod+ctrl+1 move container to workspace  1-2

bindsym $mod+Shift+2 move container to workspace  2-1
bindsym $mod+ctrl+2 move container to workspace  2-2

# assigning workspace to outputs 
set $o-left HDMI-A-1
set $o-right DP-1

workspace 1-1 output $o-left
workspace 2-1 output $o-left

workspace 1-2 output $o-right
workspace 2-2 output $o-right

You can get the output names with swaymsg -t get_outputs | jq '.[] | .name,.rect', assuming you have jq.

This does the basics: both screen sides switch upon a single button press, and you can send windows precisely where you want them.

Some caveats remain: It breaks workspace_auto_back_and_forth, since wherever you go, you last moved to workspace ws-name-1 and then ws-name-2. You also always go to the second output.

Version 2

The nice thing with Sway (and many Linux programs in general) is that they are so easy to extend via your own scripts. The issue linked above had some scripted solutions in Python and Rust. I decided to add give this a try in bash instead. Here is what I came up with instead.

Basically, I decided to add a suffix based on the output to the workspace name, e.g. 1-1 or 1-2. (I first went for 1l and 1r, but for laptops I often place the second screen above the first, so I wanted the name to be independent of that.)

Every time my script changes to an output, it saves the workspace to a cache file, so it can read it the next time it is run. It reads in the current workspace, and compares it to the requested one. If they match, it changes to the last workspace (or to the currently unfocused output if AUTO_BACK_AND_FORTH is false). Otherwise, it changes to the requested workspace.

It also finds the currently focused workspace side, and remembers it when switching workspaces.

#!/bin/bash

# script to switch dual screen setup
# * remembers which side you are currently on
# * can handle auto-back-and-forth or go to unfocused side on repress
# 
# requires jq 

SEPARATOR="-"
PRIMARY="-1"
SECONDARY="-2"
TMPDIR=~/.cache/sway
TMPFILE=$TMPDIR/last_workspace

AUTO_BACK_AND_FORTH=true

WS_CURRENT=$(swaymsg -t get_workspaces | jq --raw-output '.[] | try select(.focused == true) | .name')
WS_CURRENT_SIDE_FOCUSED=$SEPARATOR${WS_CURRENT##*$SEPARATOR}
WS_CURRENT="${WS_CURRENT%$SEPARATOR*}"

mkdir -p $TMPDIR
if [ -f $TMPFILE ]; then
    WS_LAST=$(cat $TMPFILE)
fi

WS_CURRENT_SIDE_UNFOCUSED=$SECONDARY
if [ $WS_CURRENT_SIDE_FOCUSED == $WS_CURRENT_SIDE_UNFOCUSED ]; then
    WS_CURRENT_SIDE_UNFOCUSED=$PRIMARY
fi


if [ $1 == $WS_CURRENT ]; then
    if [ $AUTO_BACK_AND_FORTH == true ];then
        swaymsg "workspace $WS_LAST$WS_CURRENT_SIDE_UNFOCUSED"
        swaymsg "workspace $WS_LAST$WS_CURRENT_SIDE_FOCUSED"
    else
        swaymsg "workspace $1$WS_CURRENT_SIDE_UNFOCUSED"
    fi
else
    swaymsg "workspace $1$WS_CURRENT_SIDE_UNFOCUSED; workspace $1$WS_CURRENT_SIDE_FOCUSED"
fi

echo $WS_CURRENT>$TMPFILE

In the sway config, this requires the following changes:

set $cmd_switch_multihead /bin/bash ~/auto-setup/sway/switch_multihead.sh

bindsym $mod+1 exec $cmd_switch_multihead 1
bindsym $mod+2 exec $cmd_switch_multihead 2

bindsym $mod+Shift+1 move container to workspace  1-1
bindsym $mod+Shift+2 move container to workspace  2-1

bindsym $mod+ctrl+1 move container to workspace  1-2
bindsym $mod+ctrl+2 move container to workspace  2-2

workspace 1-1 output $o-left
workspace 2-1 output $o-left

workspace 1-2 output $o-right
workspace 2-2 output $o-right

# disable since it now leads to some weird stuff with window class assignments
workspace_auto_back_and_forth no

It has been working nicely the last week, and also supports workspace names other than 1-10. I might add a similar script for moving windows between workspaces in the future, but it hardly bothers me so far.

Update: I've since decided I also want this feature for i3 and added a switch which decides between i3-msg and swaymsg depending on the running window manager. Check it out in the repo.

You can find the rest of my config here.


◅ Sewing a bouldering chalk bag
Curses, foiled again! - Getting started with a Silhouette Cameo 4 Cutting Plotter ▻