小类随手记

在 Hyprland scrolling 布局下实现在当前窗口下方打开新终端

nixos 从0实现全集 - 目录

本文记录在 Hyprland 的 scrolling 布局下,为当前窗口的下方开启一个新终端(ghostty)的方案。核心难点在于 scrolling 布局与 dwindle 布局对"分割方向"的处理完全不同。

背景

Hyprland 支持几种平铺布局(dwindle / master / scrolling)。我日常使用 scrolling 布局,窗口按"列"排列在一条无限延伸的纸带上,每个新窗口默认在最右侧新建一列。想要一个"在当前窗口正下方打开终端"的快捷键,实现时却踩了坑。

0.55 Lua 配置模式踩坑(关键)

从 Hyprland 0.55 起配置全面转向 Lua(DVC 模式),hyprctl dispatch 的参数会被包装成 hl.dispatch(...) 求值。这导致旧的 hyprctl dispatch layoutmsg consume_or_expel prev 写法报 Lua 语法错误而失效(实测返回类似 error: hl.dispatch(layoutmsg consume_or_expel...) 的 Lua 语法错误),表现为终端直接开成新列,看起来像"直接打开 ghostty"而没有出现在窗口下方。

0.55/DVC 模式需直接传 Lua 形式:

1
hyprctl dispatch 'hl.dsp.layout("consume_or_expel prev")'

实测通过:新终端正常合并回前一列出现在下方;无相邻列时仅产生 warning(无副作用)。脚本中已采用此写法。

踩坑:split:bottom 不生效

dwindle 布局可以用 hyprctl dispatch split:bottom 预设下一个新窗口的分割方向,让它开在当前窗口下方:

1
bind = $mainMod SHIFT, E, exec, hyprctl dispatch split:bottom; ghostty

但实测在 scrolling 布局下完全无效——新开的终端仍然出现在右侧新列。

原因split: 派发器是 dwindle 布局专用的,它把"当前焦点节点"沿指定方向一分为二。而 scrolling 布局是纯列式模型,没有"节点分割"的概念,新窗口永远作为一个新列添加,不存在往下塞进当前窗口下面的原生操作。

1
2
3
4
5
6
# scrolling 布局下所有这些都会报 "Invalid dispatcher"
hyprctl dispatch split:left
hyprctl dispatch split:right
hyprctl dispatch split:top
hyprctl dispatch split:bottom
hyprctl dispatch split:toggle

思路:用 layoutmsg 的 consume 把新列合并回当前列

scrolling 布局虽然不支持原生"按方向分割",但它提供了 layoutmsg 系列命令。关键思路是:

  1. 先创建一个新窗口(默认会在右侧新开一列)
  2. layoutmsg consume_or_expel prev 把当前焦点窗口(即刚开的那个)合并进相邻的前一列

合并后,新窗口会出现在前一列的下方(因为列内支持垂直堆叠多个窗口),于是视觉上就是"当前窗口下方"。

1
hyprctl dispatch layoutmsg consume_or_expel prev

注意(0.55 Lua 配置模式):从 Hyprland 0.55 开始配置全面转向 Lua,hyprctl dispatch layoutmsg consume_or_expel prev 的参数会被包装成 hl.dispatch(...) 求值,导致报 Lua 语法错误而失效(表现为终端开成新列,看起来像"直接打开 ghostty")。0.55/DVC 模式需直接传 Lua 形式:

1
hyprctl dispatch 'hl.dsp.layout("consume_or_expel prev")'

该写法实测通过,无相邻列时仅产生 warning 无副作用。

完整脚本

由于终端需要等待新窗口成为焦点后,再由滚动布局合并,写成一个脚本更稳妥:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/env bash
# 在当前窗口下方打开一个新 ghostty 终端
# scrolling 布局专用:新窗口默认开在右侧新列,这里等窗口出现后
# 用 layoutmsg consume_or_expel prev 把它合并回当前列(出现在下方)。
set -euo pipefail

# 记录按下快捷键时的焦点窗口地址(用于识别"新开"的窗口)
FOCUSED_BEFORE="$(hyprctl -j activewindow | jq -r '.address // empty')"

# 随机取一个配色配置变体
CONFIGS=(brown graphite green magenta navy teal)
RAND_CONFIG="${CONFIGS[$((RANDOM % ${#CONFIGS[@]}))]}"
ghostty --config-file="$HOME/.config/ghostty/config-$RAND_CONFIG" &

# 等待新窗口成为焦点(最多 3 秒)
for _ in $(seq 1 30); do
    CUR="$(hyprctl -j activewindow | jq -r '.address // empty')"
    if [[ -n "$CUR" && "$CUR" != "$FOCUSED_BEFORE" ]]; then
        break
    fi
    sleep 0.1
done

# 把焦点窗口(新 ghostty)合并进前一列,出现在原窗口下方
# 0.55 Lua 配置模式下 hyprctl dispatch 参数会被包成 hl.dispatch(...) 求值,
# 旧写法 `hyprctl dispatch layoutmsg consume_or_expel prev` 会报 Lua 语法错误,
# 因此直接传 hl.dsp.layout(...) 的 Lua 形式(实测有效,无相邻列时仅 warning 无副作用)。
hyprctl dispatch 'hl.dsp.layout("consume_or_expel prev")'

几个要点:

  • 记录 FOCUSED_BEFORE 是为了准确识别"新开的窗口",避免被其它弹窗干扰
  • 用循环轮询 hyprctl -j activewindow 等待焦点切到新窗口,比固定 sleep 更可靠
  • 脚本本身放在 nix 配置仓库,通过 mkOutOfStoreSymlink 引用,修改即生效,无需 rebuild(见 mkOutOfStoreSymlink 说明

绑定快捷键

hyprland.conf 中加入:

1
2
# 在当前窗口下方新开 ghostty (Win Shift E)
bind = $mainMod SHIFT, E, exec, sh $REPO_DIR/script/hypr_open_term_below.sh

为什么能"下方"堆叠

scrolling 布局中,同一个列可以垂直堆叠多个窗口(列内也可以有多行)。consume_or_expel prev 会把窗口并入当前列的下方槽位,从而实现视觉上的"下方打开"。

相关文章

comments powered by Disqus
Theme Stack