前言

前段时间需要离开学校使用电脑工作,在学校的显示器尺寸实在太大,因此购买了一个便携屏。

在回到学校后,这个便携屏就作为了辅助显示器使用。然而,尽管 Hyprland 支持多显示器,其工作区 (Workspace) 切换的设置却并不符合我的使用习惯。工作区可以在屏幕之间移动,但这需要配置额外的快捷键。此外,当切换到另一个显示器的工作区上时,焦点和光标也会移动到另一个工作区/显示器上,这会让我一时找不到焦点和光标在哪。

如果是我,我应该会为每个显示器分配 10 个工作区。当焦点在主显示器上时,就切换到主显示器上的工作区。当焦点在副显示器上时,就切换到副显示器上的工作区。这样的逻辑不太能单纯使用 Hyprland 的配置文件实现,因此需要借助 sh 脚本的力量来实现我们的逻辑。

思路

Hyprland 的每个工作区有一个工作区 ID,每个显示器也有一个显示器 ID。那么我们可以为 ID 为 0 的显示器分配 ID 为 1-10 的工作区,为 ID 为 1 的显示器分配 ID 为 11-20 的工作区,以此类推。在按下快捷键时,检测焦点在哪个显示器上,然后切换到对应的工作区。

Hyprland Dispatcher

Hyprland 有一系列的内置操作,例如切换工作区、移动工作区、移动窗口、运行指令等。这些操作可以通过 Hyprland 配置文件中配置快捷键运行:

bind = $mainMod, 1, workspace, 1
# 当按下 mod + 1 时,切换到 1 号工作区
bind = $mainMod SHIFT 1, movetoworkspace, 1
# 当按下 mod + Shift + 1 时,将当前窗口移动到 1 号工作区,并切换到 1 号工作区
bind = $mainMod SHIFT, comma, movecurrentworkspacetomonitor, l
# 当按下 mod + Shift + "," 时,将当前工作区移动到左边的显示器上
bind = $mainMod T, exec, alacritty
# 当按下 mod + T 时,运行 alacritty 指令,启动 Alacritty 终端模拟器

也可以通过 hyprctl dispatch 指令运行:

hyprctl dispatch workspace 1
hyprctl dispatch movetoworkspace 1
hyprctl dispatch movecurrentworkspacetomonitor l
hyprctl dispatch exec alacritty

那么,可以让切换工作区的快捷键按下后运行一个 sh 脚本,在这个脚本中获取焦点所在的显示器,并将工作区切换到指定的显示器上。

sh 脚本

检测焦点所在的显示器

hyprctl 提供了子指令 hyprctl activeworkspace,可以用于获取当前活跃工作区的状态。在终端中运行这个指令可以得到类似如下的输出:

workspace ID 2 (2) on monitor DP-1:
	monitorID: 0
	windows: 4
	hasfullscreen: 0
	lastwindow: 0x627422603b10
	lastwindowtitle: hyprctl activeworksp ~

不难从第 2 行看出,当前活跃的工作区所属的显示器 ID 为 0。通过 grepawk 即可将数字部分提取出来:

hyprctl activeworkspace | grep "monitorID" | awk '{print $2}'

其中,grep 将会从 hyprctl activeworkspace 的输出中提取出包含 monitorID 的这一行。而 awk 将会提取出用空格分割的第 2 个字符串,也就是 0

在 sh 脚本中,将将输出保存为一个变量:

monitor_id=$(hyprctl activeworkspace | grep "monitorID" | awk '{print $2}')

参数设计

如你所见,这个脚本也需要一些输入参数,例如要切换到哪个工作区,以及是否需要将当前活动的窗口也移动到那个工作区。在 sh 脚本中,可以通过 $ 加上参数编号来获取运行脚本时的参数。

在这里,考虑将脚本的第 1 个参数作为操作类型,将第 2 个参数作为我们为工作区。

operation=$1
workspace=$2

operationswitch 时,则切换到目标工作区;当为 move 时,则将当前窗口移动到目标工作区,然后切换到目标工作区。

操作工作区

首先当然是计算目标工作区 ID。知道了显示器 ID 和工作区编号参数之后,在 sh 脚本中计算目标工作区 ID:

$workspace_id=$(($monitor_id * 10 + $workspace))

然后根据操作类型,通过 hyprctl 来运行对应的操作:

if [[ $operation == "switch" ]]; then
	hyprctl dispatch moveworkspacetomonitor $workspace_id $monitor_id;
	hyprctl dispatch workspace $workspace_id;
fi
if [[ $operation == "move" ]]; then
	hyprctl dispatch moveworkspacetomonitor $workspace_id $monitor_id;
	hyprctl dispatch movetoworkspace $workspace_id;
fi

您可能会注意到,在运行切换或移动工作区的操作前,还会首先将目标工作区移动到焦点所在的显示器上。这是因为目标工作区可能并不在焦点所在的显示器上,因此需要首先将其移动到焦点所在的显示器上。

最终的脚本

#!/bin/bash

operation=$1
workspace=$2

monitor_id=$(hyprctl activeworkspace | grep "monitorID" | awk '{print $2}')
workspace_id=$(($monitor_id * 10 + $workspace))
echo "Final Operation: $operation to $workspace_id"

if [[ $operation == "switch" ]]; then
	hyprctl dispatch moveworkspacetomonitor $workspace_id $monitor_id;
	hyprctl dispatch workspace $workspace_id;
fi
if [[ $operation == "move" ]]; then
	hyprctl dispatch moveworkspacetomonitor $workspace_id $monitor_id;
	hyprctl dispatch movetoworkspace $workspace_id;
fi

在接下来的操作中,我将这个脚本保存为 ~/.config/hypr/switch_workspace.sh 这一文件。

Hyprland 配置

原本默认的配置如下:

# Switch workspaces with mainMod + [0-9]
bind = $mainMod, 1, workspace, 1
bind = $mainMod, 2, workspace, 2
# ...
bind = $mainMod, 0, workspace, 10

# Move active window to a workspace with mainMod + SHIFT + [0-9]
bind = $mainMod, 1, movetoworkspace, 1
bind = $mainMod, 2, movetoworkspace, 2
# ...
bind = $mainMod, 0, movetoworkspace, 10

现在将其修改为在按下快捷键时,运行脚本:

$switch_script = ~/.config/hypr/switch_workspace.sh

# Switch workspaces with mainMod + [0-9]
bind = $mainMod, 1, exec, $switch_script switch 1
bind = $mainMod, 2, exec, $switch_script switch 2
# ...
bind = $mainMod, 0, exec, $switch_script switch 10

# Move active window to a workspace with mainMod + SHIFT + [0-9]
bind = $mainMod, 1, exec, $switch_script move 1
bind = $mainMod, 2, exec, $switch_script move 2
# ...
bind = $mainMod, 0, exec, $switch_script move 10

在 Hyprland 中设置好快捷键之后,所有工作就完成了。

结语

尽管一个程序可能并不支持复杂的操作,但通过 sh 脚本,可以将不同的程序组合起来,以达成想要做到的复杂的操作。对于 Hyprland 的多显示器设置来讲,这就是一个利用 sh 脚本来将桌面环境改造得更适合个人习惯的例子。

此外,本文所述的功能亦可用过 split-monitor-workspaces 插件实现,该插件添加了多个 Dispatcher,将工作区分割到多个显示器上,并在每个显示器上提供独立的编号。个人倾向于使用 sh 脚本实现,编写 Hyprland 插件对我来讲,可能会相对麻烦一些。

希望这篇文章可以为您提供改造桌面环境的思路和帮助。


参考资料