Skip to content

Instantly share code, notes, and snippets.

@Diaoul
Last active March 19, 2024 22:36
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Diaoul/4a33dfec9d80763a7f74ce8086bf5237 to your computer and use it in GitHub Desktop.
Save Diaoul/4a33dfec9d80763a7f74ce8086bf5237 to your computer and use it in GitHub Desktop.
Arrange workspace on multiple monitors (Hyprland)
#!/usr/bin/env bash
set -e
declare -i last_called=0
declare -i throttle_by=4
@throttle() {
local -i now=$(date +%s)
if (($now - $last_called > $throttle_by))
then
# delay execution
sleep $throttle_by
# execute
"$@"
fi
last_called=$(date +%s)
}
function arrange_workspaces() {
# Maintain 10 workspaces across multiple monitors
# This script assumes that the monitors are in horizontal layout and already
# correctly setup
monitors_json=$(hyprctl monitors -j)
workspaces_json=$(hyprctl workspaces -j)
monitors=($(echo $monitors_json | jq -r '. | sort_by(.x) | .[].name'))
workspace_count=$(echo $workspaces_json | jq -r '. | length')
active_workspaces=($(echo $monitors_json | jq -r '. | sort_by(.x) | .[].activeWorkspace.id'))
echo "Detected ${#monitors[@]} monitors with $workspace_count workspaces and $(IFS=,; echo ${active_workspaces[*]}) active"
# create or fix workspaces
# this requires support for persistent workspaces
# see https://github.com/hyprwm/Hyprland/pull/3346
for ((i = 1; i <= 10; i++)); do
workspace=$(echo $workspaces_json | jq -r ".[] | select(.id == $i)")
if [ -z "$workspace" ]; then
echo "Workspaces $i does not exist, creating it"
hyprctl dispatch workspace $i > /dev/null
hyprctl dispatch workspaceopt persistent > /dev/null
else
persistent=$(echo $workspace | jq -r ".persistent")
if [ "$persistent" = false ]; then
echo "Workspaces $i is not persistent, persisting it"
hyprctl dispatch workspace $i > /dev/null
hyprctl dispatch workspaceopt persistent > /dev/null
fi
fi
done
# assign and move workspaces
if [[ "${#monitors[@]}" == 1 ]]; then
for ((i = 10; i >= 1; i--)); do
echo "Moving workspace $i to ${monitors[0]}"
hyprctl keyword workspace "$i, monitor:${monitors[0]}" > /dev/null
hyprctl dispatch moveworkspacetomonitor $i ${monitors[0]} > /dev/null
done
elif [[ "${#monitors[@]}" == 2 ]]; then
for ((i = 10; i >= 6; i--)); do
echo "Moving workspace $i to ${monitors[1]}"
hyprctl keyword workspace "$i, monitor:${monitors[1]}" > /dev/null
hyprctl dispatch moveworkspacetomonitor $i ${monitors[1]} > /dev/null
done
hyprctl dispatch workspace 6 > /dev/null
for ((i = 5; i >= 1; i--)); do
echo "Moving workspace $i to ${monitors[0]}"
hyprctl keyword workspace "$i, monitor:${monitors[0]}" > /dev/null
hyprctl dispatch moveworkspacetomonitor $i ${monitors[0]} > /dev/null
done
elif [[ "${#monitors[@]}" == 3 ]]; then
for ((i = 10; i >= 8; i--)); do
echo "Moving workspace $i to ${monitors[2]}"
hyprctl keyword workspace "$i, monitor:${monitors[2]}" > /dev/null
hyprctl dispatch moveworkspacetomonitor $i ${monitors[2]} > /dev/null
done
for ((i = 7; i >= 4; i--)); do
echo "Moving workspace $i to ${monitors[1]}"
hyprctl keyword workspace "$i, monitor:${monitors[1]}" > /dev/null
hyprctl dispatch moveworkspacetomonitor $i ${monitors[1]} > /dev/null
done
for ((i = 3; i >= 1; i--)); do
echo "Moving workspace $i to ${monitors[0]}"
hyprctl keyword workspace "$i, monitor:${monitors[0]}" > /dev/null
hyprctl dispatch moveworkspacetomonitor $i ${monitors[0]} > /dev/null
done
else # more than 3 monitors...
echo "Too many monitors"
fi
for id in "${active_workspaces[@]}"; do
echo "Activating workspace $id"
hyprctl dispatch workspace $id > /dev/null
done
}
function configure_monitors() {
# rely an kanshi for profile detection and output configuration
# see https://todo.sr.ht/~emersion/kanshi/54#event-235509
echo "Configuring monitors..."
kanshi > /dev/null 2>&1 &
sleep 1
killall kanshi
# arrange workspaces to match the new configuration
echo "Configuring workspaces..."
arrange_workspaces
}
function handle {
if [[ ${1:0:12} == "monitoradded" ]]; then
echo "Event detected: monitor added"
@throttle configure_monitors
elif [[ ${1:0:14} == "monitorremoved" ]]; then
echo "Event detected: monitor removed"
@throttle configure_monitors
fi
}
cli_help() {
cli_name=${0##*/}
echo "
$cli_name
Hyprland monitors control
Usage: $cli_name [command]
Commands:
configure_monitors Configure monitors using kanshi
arrange_workspaces Arrange workspaces on monitors
listen Listen for hypr monitor events and arrange workspaces automatically
* Help
"
exit 1
}
case "$1" in
configure_monitors)
configure_monitors
;;
arrange_workspaces)
arrange_workspaces
;;
listen)
echo "Listening to monitor events..."
socat - UNIX-CONNECT:/tmp/hypr/$(echo $HYPRLAND_INSTANCE_SIGNATURE)/.socket2.sock | while read line; do handle $line; done
;;
*)
cli_help
;;
esac
@ityogi
Copy link

ityogi commented Apr 25, 2023

Wow. Thanks for sharing this!

Is there a way to automatically trigger this script on plugging in an external monitor?

@Diaoul
Copy link
Author

Diaoul commented Apr 25, 2023

Yes, I have a script that listens to Hyprland's events monitoradded and monitorremoved, throttles that a bit (for when I plug multiple monitors at the same time with the dock) and executes:

  1. Configuring the monitors using kanshi
  2. Run this script to arrange the workspaces

Works pretty well, I'll share my config in my dotfiles soon ™️

@JasonLandbridge
Copy link

@Diaoul Hey this is great! Could you write a short tutorial with you config for noobs (not me :P) to set this up? Cheers!

@yanboyang713
Copy link

Hello @Diaoul,

Good day to you,

Could you share your kanshi.service file and ~/.config/kanshi/config

Thanks for your help,
Boyang Yan

@Diaoul
Copy link
Author

Diaoul commented Oct 7, 2023

Updated my gist to a newer version that uses a fork with persistent workspaces (PR)

Notable changes:

  • CLI format with different functions
    • configure_monitors uses kanshi to apply monitor configuration on resolution and scaling
    • arrange_workspace arranges workspaces on monitors (like previous version)
    • listen will apply changes on monitor change detected on hyprland socket
  • Replace usage of deprecated wsbind by a workspace rule
  • Try to re-activate the previously active workspace
  • Create workspaces and flag them as persistent

@Diaoul
Copy link
Author

Diaoul commented Oct 7, 2023

@yanboyang713 this is just regular kanshi stuff here: my monitor setup. Nothing really special to share.

@Diaoul
Copy link
Author

Diaoul commented Oct 7, 2023

Associated Hyprland config

exec-once = ~/.config/hypr/scripts/monitors.sh listen
exec= ~/.config/hypr/scripts/monitors.sh configure_monitors

@yanboyang713
Copy link

Thanks @Diaoul

Thanks for share

@PolarisPyra
Copy link

First of all, thank you so much for this script it's really helped! Second of all, I have noticed a odd b

2023-11-21.16-29-18.mp4

ug where if i press super 6 to go to the 6th workspace but my mouse is on the first monitor itll bring the 6th workspace to the first monitors screen. it will also do the same if im on the second monitor and go to say the 4th tab. I have also noticed that it snaps my cursor on the 4th workspace to the second monitor rather than the 6th (super 6) in my case
here is a video recording

and here is my config

// Workspaces
    "hyprland/workspaces" : {
        "on-click": "activate",
        "active-only": false,

        "all-outputs": false,
        "format": "{}",
        "format-icons": {
			"urgent": "",
			"active": "",
			"default": ""
        },
        "persistent_workspaces": {
    
        "1": ["DP-1"], 
        "2": ["DP-1"],
        "3": ["DP-1"],
        "4": ["DP-1"],
        "5": ["DP-1"],
        "6": ["DP-2"],
        "7": ["DP-2"],
        "8": ["DP-2"],
        "9": ["DP-2"],
        "10": ["DP-2"]
          
    }       
    ```
    and here are my shortcuts 
    ```
    bind = $mainMod, 1, workspace, 1
bind = $mainMod, 2, workspace, 2
bind = $mainMod, 3, workspace, 3
bind = $mainMod, 4, workspace, 4
bind = $mainMod, 5, workspace, 5
bind = $mainMod, 6, workspace, 6
bind = $mainMod, 7, workspace, 7
bind = $mainMod, 8, workspace, 8
bind = $mainMod, 9, workspace, 9
bind = $mainMod, 0, workspace, 10

bind = $mainMod SHIFT, 1, movetoworkspace, 1
bind = $mainMod SHIFT, 2, movetoworkspace, 2
bind = $mainMod SHIFT, 3, movetoworkspace, 3
bind = $mainMod SHIFT, 4, movetoworkspace, 4
bind = $mainMod SHIFT, 5, movetoworkspace, 5
bind = $mainMod SHIFT, 6, movetoworkspace, 6
bind = $mainMod SHIFT, 7, movetoworkspace, 7
bind = $mainMod SHIFT, 8, movetoworkspace, 8
bind = $mainMod SHIFT, 9, movetoworkspace, 9
bind = $mainMod SHIFT, 0, movetoworkspace, 10

again thank you so much for this script @Diaoul

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment