折腾 Niri WM:手搓一个完美的多显示器下拉终端 (Drop-down Terminal)

折腾 Niri WM:手搓一个完美的多显示器下拉终端 (Drop-down Terminal)

从 X11 或 Sway 迁移到 Niri 这个无限卷轴式 (Scrollable-tiling) 的 Wayland WM 后,我最不习惯的就是失去了原来用惯的下拉终端(类似 tdrop 或 Guake)。

Niri 确实原生支持了 scratchpad 机制,你可以把窗口扔进去隐藏,再用快捷键呼出。但它属于通用级别的窗口隐藏,没法做到"按下一个特定的快捷键,专属的终端窗口就从屏幕上方固定弹出,再按一次就消失",而且在多显示器环境下,跨屏呼出经常会遇到焦点错乱的问题。

为了解决这个问题,我决定利用 Niri 强大的 IPC 接口(niri msg --json)搭配 jq,自己手写一个极其丝滑的下拉终端管理脚本。

方案思路

  1. 专属标识 :让终端(以 Alacritty 为例)以特定的 app-id 启动(例如 alacritty-drop)。
  2. 窗口规则 :在 Niri 配置里拦截这个 app-id,让它默认就是浮动状态(Floating)。
  3. 状态机控制
    • 如果终端不存在:启动它,并自动调整为占满屏幕宽度、高度 75%。
    • 如果终端在其他工作区或显示器:把它瞬间"抓"到当前显示器和当前工作区,并夺取焦点。
    • 如果终端已经在当前工作区,但没聚焦:把它推到最前并聚焦。
    • 如果终端已经在当前工作区且正在聚焦:认为用户用完了,把它扔回 scratchpad 隐藏。

1. Niri 窗口规则与快捷键配置

首先,我们需要在 ~/.config/niri/config.kdl 中加入窗口规则,让这个终端一出生就是浮动的:

kdl 复制代码
window-rule {
    match app-id="alacritty-drop"
    open-floating true
}

然后绑定你的召唤快捷键(比如 Alt+1),让它去执行我们接下来要写的脚本:

kdl 复制代码
binds {
    Alt+1 { spawn "bash" "-c" "~/.config/waybar/tdrop-niri.sh"; }
}

2. 核心脚本解析

这是最核心的部分。新建脚本 ~/.config/waybar/tdrop-niri.sh 并赋予执行权限。

这里面踩过最大的坑是多显示器的工作区索引冲突 。在 Niri 里,每个显示器的 Workspace 索引(idx)是独立的。比如左边屏幕有个 idx=1(实际上是 Scratchpad),右边屏幕也有个正常的 idx=1。如果只用 idx 去判断终端到底在不在当前屏幕,很容易导致脚本原地抽风,以为左边屏幕隐藏的终端已经在右边屏幕显示了。

正确的做法是:获取全局唯一的 ID 进行对比,并在跨屏召唤时,先强行转移 Monitor,再转移 Workspace。

完整的脚本如下:

bash 复制代码
#!/usr/bin/env bash

CLASS="alacritty-drop"

# 1. 获取当前正在交互的屏幕和工作区信息
ACTIVE_WORKSPACE=$(niri msg --json workspaces | jq -r '.[] | select(.is_focused==true)')
ACTIVE_WORKSPACE_ID=$(echo "$ACTIVE_WORKSPACE" | jq -r '.id')
ACTIVE_WORKSPACE_IDX=$(echo "$ACTIVE_WORKSPACE" | jq -r '.idx')
ACTIVE_OUTPUT=$(echo "$ACTIVE_WORKSPACE" | jq -r '.output')

# 2. 查找我们的下拉终端窗口是否存在
WINDOW=$(niri msg --json windows | jq -r "first(.[] | select(.app_id==\"$CLASS\"))")

if [[ "$WINDOW" == "null" || -z "$WINDOW" ]]; then
  # 情况 A: 终端压根没运行,拉起它
  alacritty --class "$CLASS" &
  
  # 稍微等一下让窗口真正建出来,然后自动设置宽高 (宽100%,高75%)
  sleep 0.5
  WINDOW_ID=$(niri msg --json windows | jq -r "first(.[] | select(.app_id==\"$CLASS\") | .id)")
  if [[ "$WINDOW_ID" != "null" && -n "$WINDOW_ID" ]]; then
    niri msg action set-window-width --id "$WINDOW_ID" 100%
    niri msg action set-window-height --id "$WINDOW_ID" 75%
  fi
else
  # 终端在后台运行中,解析它的当前状态
  WINDOW_ID=$(echo "$WINDOW" | jq -r ".id")
  WIN_WORKSPACE_ID=$(echo "$WINDOW" | jq -r ".workspace_id")
  IS_FOCUSED=$(echo "$WINDOW" | jq -r ".is_focused")
  
  if [[ "$WIN_WORKSPACE_ID" == "null" || -z "$WIN_WORKSPACE_ID" ]]; then
    WIN_WORKSPACE_ID="none"
  fi
  
  # 情况 B: 终端在其他工作区,或者在隐藏的 Scratchpad 里
  if [[ "$ACTIVE_WORKSPACE_ID" != "$WIN_WORKSPACE_ID" ]]; then
    # 核心坑点:跨屏幕移动必须先 move-window-to-monitor 否则焦点和位置会乱
    niri msg action move-window-to-monitor --id "$WINDOW_ID" "$ACTIVE_OUTPUT"
    niri msg action move-window-to-workspace --window-id "$WINDOW_ID" "$ACTIVE_WORKSPACE_IDX"
    
    niri msg action focus-window --id "$WINDOW_ID"
    # 重新确保尺寸是下拉终端的尺寸
    niri msg action set-window-width --id "$WINDOW_ID" 100%
    niri msg action set-window-height --id "$WINDOW_ID" 75%
    
  # 情况 C: 终端就在眼前,但焦点在其他窗口上
  elif [[ "$IS_FOCUSED" == "false" ]]; then
    niri msg action focus-window --id "$WINDOW_ID"
    
  # 情况 D: 终端就在眼前,且当前正在使用。按快捷键就是为了收起它
  else
    # 把它丢进 scratchpad 隐藏起来,并且不要改变当前其他窗口的焦点
    niri msg action move-window-to-workspace --window-id "$WINDOW_ID" "scratchpad" --focus false
  fi
fi

代码细节拆解

  1. niri msg --json + jq:Wayland 下想要 hack 窗口行为,最爽的就是这种提供完善 JSON IPC 的 WM。我们通过一两行 jq 就能拿到精确到每个窗口的内部 ID、所处的工作区以及当前的 Focus 状态。
  2. 多显示器匹配逻辑 :你会注意到,移动工作区时,用的是相对索引 $ACTIVE_WORKSPACE_IDX(配合上一步的 monitor 转移),但判断在不在同一工作区时,用的是绝对主键 $ACTIVE_WORKSPACE_ID != $WIN_WORKSPACE_ID。这是彻底解决多显示器召唤失败的关键。
  3. 隐藏逻辑 :Niri 并没有 hide 这种命令。它的本质是存在一个名为 scratchpad 的特殊隐藏工作区。所以把下拉终端收起,等价于 move-window-to-workspace ... "scratchpad",加上 --focus false 可以保证你收起终端后,键盘输入焦点能平滑回到你刚刚在看的代码编辑器或浏览器上。

总结

在 Niri 里折腾这种小功能,一开始可能会觉得不如 X11 下用 xdotool 那样简单粗暴,但熟悉了它结构化的 JSON IPC 后,你会发现这种控制既精准又优雅,再也不用靠玄学的 sleep 和瞎猜窗口层级来写判断了。

希望这个脚本能帮到同样在使用 Niri 的朋友们,打造一个完美的下拉终端。

相关推荐
QQ12958455043 小时前
FERP50 - Excel以存储过程方式访问数据仓库
数据仓库·spark·excel
It's Q3 小时前
Hive序列函数&&排名函数
数据仓库·hive·hadoop
云策数链3 天前
ERP报表系统设计与数据仓库
数据仓库·erp·用友·云策数链
水火既济__3 天前
加快hive效率
数据仓库·hive·hadoop
真上帝的左手4 天前
19. 大数据-数据仓库简介
大数据·数据仓库
zgdlsz6 天前
羲之文化传承人王杰宝:沉厚笔墨间的守正出新
大数据·数据库·数据仓库·涛思数据
莽撞的大地瓜6 天前
舆情分析智能体:蜜度新浪舆情通以多Agent协同驱动全流程智能升级
大数据·数据仓库·数据分析
陆水A8 天前
用CASE WHEN实现横向迭代,节点数据串行推算
大数据·数据仓库·数据库开发·etl·etl工程师
承渊政道8 天前
从ROWNUM到LIMIT:KES、Oracle与PostgreSQL的执行顺序差异解析
数据库·数据仓库·sql·mysql·安全·postgresql·oracle