Skip to content

Instantly share code, notes, and snippets.

@mildred
Last active March 15, 2023 08:57
Show Gist options
  • Save mildred/8c6983295fe5ff8522028e7a2c7c7a0b to your computer and use it in GitHub Desktop.
Save mildred/8c6983295fe5ff8522028e7a2c7c7a0b to your computer and use it in GitHub Desktop.
SwayWM synchronize workspaces on outputs

This will monitor workspace changes and update the current workspace on other monitors. The idea is that workspace number 1 is paired with workspace 11 on second monitor, workspace 21 on 3rd monitor...

Usage:

run it in background along your sway session

Problem: Sway does not allow switching workspaces without focusing it, and focusing a workspaces moves the mouse cursor. So it's impossible to change the current workspace on the secondary output without disrupting the user.

As a consequence, the implementation - for now - will only update the workspace when the moves moves to the output, but this is far from ideal.

swaywm/sway#7507

#!/bin/bash
# <https://gist.github.com/mildred/8c6983295fe5ff8522028e7a2c7c7a0b>
subscribe_workspaces(){
swaymsg -t subscribe -m '["workspace"]' \
| jq --unbuffered -r '[ .old.num, .old.output, .current.num, .current.output, .current.focused ] | join(";")'
}
index_of(){
local i=0
local resval="$1"
local needle="$2"
shift 2
for item in "$@"; do
if [[ $item == $needle ]]; then
eval "${resval}=\$i"
return 0
fi
let i++
done
eval "${resval}="
return 1
}
log(){
( set -x
"$@"
)
}
update_other_workspaces(){
local num="$1"
local current_output="$2"
local current_output_index="$3"
local group_index
let group_index=num%10
local json_workspaces="$(swaymsg -r -t get_workspaces)"
local need_move_back=false
local i=-1
for target_output in "${outputs[@]}"; do
let i++
local target_num
let target_num=i*10+group_index
if [[ $target_output = $current_output ]]; then
continue
fi
#echo "Workspace $num on $current_output_index:$current_output maps to $target_num on $i:$target_output"
target_workspace_output="$(<<<"$json_workspaces" \
jq -r --arg num "$target_num" \
'.[] | select(.num == ($num|tonumber)) | .output')"
target_workspace_visible="$(<<<"$json_workspaces" \
jq -r --arg num "$target_num" \
'.[] | select(.num == ($num|tonumber)) | .visible')"
if [[ $target_workspace_visible = true ]]; then
# Target workspace already visible, do nothing
true
elif [[ -z "$target_workspace_output" ]]; then
echo "$num on $current_output_index:$current_output -> (new) $target_num on $i:$target_output"
# The workspace does not exists, create it and switch to it
log swaymsg workspace $target_num output $target_output
log swaymsg workspace $target_num
ignore+=($target_num)
need_move_back=true
elif [[ $target_workspace_output = $target_output ]]; then
echo "$num on $current_output_index:$current_output -> (existing) $target_num on $i:$target_output"
# The workspace exists and is already on correct output, switch to it
log swaymsg workspace $target_num
ignore+=($target_num)
need_move_back=true
else
echo "Workspace $target_num is already on output $target_workspace_output, not moving to $target_output"
fi
done
if $need_move_back; then
# Switch back to current workspace if needed
echo "$num on $current_output_index:$current_output (move back)"
log swaymsg workspace $num
ignore+=($num)
need_move_back=false
fi
}
update_workspace_on_output_change(){
local ws="$1"
local target_num
let target_num=current_output_index*10+ws
target_workspace_output="$(<<<"$json_workspaces" \
jq -r --arg num "$target_num" \
'.[] | select(.num == ($num|tonumber)) | .output')"
target_workspace_visible="$(<<<"$json_workspaces" \
jq -r --arg num "$target_num" \
'.[] | select(.num == ($num|tonumber)) | .visible')"
if [[ $target_workspace_visible = true ]]; then
# Target workspace already visible, do nothing
echo "$target_num visible on $target_workspace_output"
true
elif [[ $num != $target_num ]] && [[ -z "$target_workspace_output" ]]; then
echo "=> (new) $target_num on $current_output_index:$current_output"
# The workspace does not exists, create it and switch to it
log swaymsg workspace $target_num output $current_output
log swaymsg workspace $target_num
# ignore+=($target_num)
elif [[ $num != $target_num ]] && [[ $target_workspace_output = $current_output ]]; then
# The workspace exists and is already on correct output, switch to it
echo "=> (existing) $target_num on $current_output_index:$current_output"
log swaymsg workspace $target_num
# ignore+=($target_num)
else
echo "$target_num already on $current_output_index:$current_output"
fi
}
ignore=()
subscribe_workspaces | while IFS=';' read last_num last_output num current_output focused; do
if [[ ${ignore[0]} == $num ]]; then
echo "ignore event"
ignore=("${ignore[@]:1}")
continue
#elif [[ -n "${ignore[0]}" ]]; then
# echo "Ignore event: $num $current_output (not in list, expect ${ignore[0]})"
# continue
fi
outputs=($(swaymsg -r -t get_outputs | jq -r '.[] | .name'))
if ! index_of current_output_index "$current_output" "${outputs[@]}"; then
echo -n "[$last_num on $last_output_index:$last_output -> $num on $current_output_index:$current_output] "
>&2 echo "Failed to recognize current workspace $num output $current_output in (${outputs[@]})"
continue
fi
if ! index_of last_output_index "$last_output" "${outputs[@]}"; then
echo -n "[$last_num on $last_output_index:$last_output -> $num on $current_output_index:$current_output] "
>&2 echo "Failed to recognize last workspace $num output $last_output in (${outputs[@]})"
continue
fi
# echo "detect workspace change $num on $current_output_index:$current_output (focused: $focused)"
echo -n "[$last_num on $last_output_index:$last_output -> $num on $current_output_index:$current_output] "
let expected_output_index=num/10
if [[ $expected_output_index != $current_output_index ]]; then
echo "Workspace $num should be on output ${outputs[$expected_output_index]} and not ${outputs[$current_output_index]}"
last_num="$num"
last_output="$current_output"
last_output_index=
continue
fi
if [[ -z "$ws" ]]; then
let ws=last_num%10
fi
if [[ $last_output = $current_output ]]; then
let ws=num%10
echo "ws=$ws"
else
update_workspace_on_output_change "$ws"
fi
# update_other_workspaces "$num" "$current_output" "$current_output_index"
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment