niri 录屏(Screencasting)实战指南:Portal/PipeWire 采集、窗口遮挡、动态采集目标与镜像
【免费下载链接】niriA scrollable-tiling Wayland compositor.项目地址: https://gitcode.com/GitHub_Trending/ni/niri
本指南围绕 niri(一款可滚动平铺的 Wayland 合成器)的录屏功能展开,系统讲解如何通过 xdg-desktop-portal 与 PipeWire 对显示器或单个窗口进行采集,并深入介绍block-out-from窗口/图层遮挡规则、25.05 引入的动态采集目标(Dynamic Cast Target)、is-window-cast-target采集指示、窗口化全屏(windowed fullscreen)以及输出镜像等专为录屏场景设计的特性。读完本文,你将能够配置一套"既能录屏、又能保护敏感窗口"的完整 niri 录屏方案,并理解其底层(ScreenCast D-Bus 协议 + PipeWire 流)是如何工作的。
录屏的两种主流方式
niri 主要的录屏接口走Portal + PipeWire这条标准链路,并且已经得到 OBS、Firefox、Chromium、Electron、Telegram 等大量应用的开箱支持。你可以采集整个显示器,也可以只采集某个单独的窗口。
要使用它,需要满足以下运行条件:
- 一个可用的 D-Bus 会话;
- 已安装并运行 PipeWire;
- 已安装
xdg-desktop-portal-gnome; - niri 以会话方式运行(即通过
niri-session或显示管理器启动,参见 Getting-Started)。
在主流发行版上,这些依赖通常"开箱即用"。除了 Portal 方式,niri 也支持依赖wlr-screencopy协议的第三方工具(例如许多截图/录屏小工具),因此选择面很广。
从源码结构看,这条链路的实现集中在 src/screencasting/mod.rs:Screencasting结构体维护casts(正在进行的采集)、pending_dynamic_casts(等待首个目标的动态采集)、mapped_cast_output(每个已映射窗口对应的输出)以及pipewire实例。Portal 端的请求通过 mutter 的 ScreenCast D-Bus 协议(src/dbus/mutter_screen_cast.rs)以ScreenCastToNiri::StartCast/StopCast消息进入on_screen_cast_msg,随后 niri 初始化 GBM 设备与渲染格式、启动 PipeWire 流,并在每个帧周期通过render_for_screen_cast(整屏采集)或render_windows_for_screen_cast(单窗口采集)把渲染元素送往采集缓冲区。
从录屏中遮挡窗口(block-out-from)
录屏演示时,你可能不希望密码管理器、聊天窗口等内容出现在画面里。niri 提供block-out-from窗口规则,把被匹配到的窗口在录屏中替换为纯黑色矩形。
// 将密码管理器从录屏中遮挡掉。 window-rule { match app-id=r#"^org\.keepassxc\.KeePassXC$"# match app-id=r#"^org\.gnome\.World\.Secrets$"# block-out-from "screencast" }同样的思路也适用于 layer-shell 表面(比如通知弹窗),只需改用图层规则:
// 将 mako 的通知从录屏中遮挡掉。 layer-rule { match namespace="^notifications$" block-out-from "screencast" }在配置解析层面,block-out-from对应的值由 niri-config/src/appearance.rs 中的BlockOutFrom枚举定义,目前支持两个取值:
"screencast":仅从 xdg-desktop-portal 录屏中遮挡;"screen-capture":从所有屏幕捕获中遮挡,包括第三方截图工具。
Configuration:-Window-Rules.md 对这两种取值做了更详细的对比:
"screencast"不会影响第三方截图工具——如果你在录屏过程中打开了带预览的截图工具,被遮挡的窗口仍会出现在录屏里。niri 内置的交互式截图 UI 不受此问题影响:录屏时打开截图 UI,你仍能看到所有窗口并正常框选区域,而录屏画面上截图选区 UI 会以"窗口被遮挡"的状态呈现。"screen-capture"会额外把窗口从第三方面截图中也遮挡掉,避免"录屏时误开截图预览导致敏感内容泄露"的尴尬;它仍然允许使用内置的交互式截图 UI,但会自动屏蔽screenshot-screen、screenshot-window这类全自动截图动作——因为交互式框选时你可以自行避开敏感内容。
调试这类规则时,可以利用配置中 debug 段的preview-render选项来预览遮挡效果。
[!CAUTION] 请小心基于动态变化的窗口标题来做遮挡。例如下面这个"遮挡 Firefox 的 Gmail 标签页"的写法:
window-rule { // 并不能完美工作!尝试遮挡 Gmail 标签页。 match app-id="firefox$" title="- Gmail " block-out-from "screencast" }它虽然能生效,但从敏感标签页切回普通标签页的瞬间,敏感标签页的内容仍会在录屏上闪现一瞬。原因在于 Wayland 协议中窗口标题(以及 app-id)不是双缓冲的,无法与特定窗口内容严格绑定,Firefox 也没有可靠手段让"可见标签页切换"与"标题变更"完全同步。
动态采集目标(Dynamic Cast Target)
自 25.05 起提供
niri 提供一个可以动态切换内容的特殊录屏流,它在录屏窗口选择对话框中显示为 "niri Dynamic Cast Target"。选择它之后,再用下面的绑定键来切换它展示的内容:
set-dynamic-cast-window:采集当前聚焦的窗口;set-dynamic-cast-monitor:采集当前聚焦的显示器;clear-dynamic-cast-target:重置为空白视频流。
注意:在你做出第一次目标选择之前,该视频流不会启动(保持空流)。
也可以从命令行触发这些动作,例如配合pick-window交互式挑选要采集的窗口:
$ niri msg action set-dynamic-cast-window --id $(niri msg --json pick-window | jq .id)从实现上看(src/screencasting/mod.rs),niri 会为 Portal 端合成一个特殊的窗口 IDdynamic_cast_id_for_portal,当 ScreenCast 请求落在该 ID 上时,请求不会立即启动,而是进入pending_dynamic_casts队列等待首个目标;set_dynamic_cast_target会把目标(CastTarget::Window/CastTarget::Output/CastTarget::Nothing)应用到所有动态采集流上,并据目标输出刷新率同步调整流的 FPS。行为细节如下:
- 如果采集目标消失(例如目标窗口被关闭),视频流会自动回到空白;
- 所有动态采集共享同一个目标,但新建的动态采集会从空白开始,直到你下一次显式切换目标(这是为了避免新会话一上来就意外共享敏感内容);
- 从源码可以推断,动态采集在目标缺失/输出断开时不会像普通采集那样直接停止,而是切回
Nothing状态等待下次切换(见stop_casts_for_target中 "We don't stop dynamic casts, instead we switch them to Nothing" 的注释)。
标记正在被采集的窗口
自 25.02 起提供
is-window-cast-target=true窗口规则(详见 Configuration:-Window-Rules.md)用于匹配正处于窗口采集目标的窗口。典型用法是给它配上醒目的边框/阴影颜色,让观众一眼看出哪些窗口正在被录屏,例如:
// 用红色系标记正在被采集的窗口。 window-rule { match is-window-cast-target=true focus-ring { active-color "#f38ba8" inactive-color "#7d0d2d" } border { inactive-color "#7d0d2d" } shadow { color "#7d0d2d70" } tab-indicator { active-color "#f38ba8" inactive-color "#7d0d2d" } }需要注意它的适用范围:
- 对动态采集(Dynamic Cast Target)锁定的窗口同样生效;
- 对仅仅"恰好显示在整屏采集画面里"的窗口不生效——只有被显式指定为窗口采集目标时才会被标记。
底层支撑在Niri::refresh_mapped_cast_window_rules(src/screencasting/mod.rs):每当采集列表变化时,niri 会遍历所有映射窗口,检查是否存在CastTarget::Window { id }与之匹配,并把结果写入mapped.set_is_window_cast_target(...),供渲染与规则匹配阶段使用。
窗口化(伪/分离)全屏
自 25.05 起提供
录屏基于浏览器的演示(如 Google Slides)时,通常希望隐藏浏览器 UI,这往往要求浏览器进入全屏。但全屏并不总是方便:比如你用的是超宽屏显示器,或者只想让浏览器保持一个小窗口、不想占满整块屏幕。
toggle-windowed-fullscreen绑定键就是为此设计的:它告诉应用"你已经全屏了",但实际上窗口仍是普通窗口,你可以随意调整大小、放到任意位置。
binds { Mod+Ctrl+Shift+F { toggle-windowed-fullscreen; } }需要注意:并非所有应用都会响应"全屏"请求,所以有时按下绑定键看起来什么都没发生——这属于应用侧的行为差异,而非 niri 的问题。
屏幕镜像(Screen Mirroring)
做演示时,把一块输出镜像到另一块输出往往很有用。niri 目前没有内置输出镜像,但可以借助第三方工具wl-mirror把某块输出镜像到一个窗口里。下面这条绑定还依赖jq命令:
binds { Mod+P repeat=false { spawn-sh "wl-mirror $(niri msg --json focused-output | jq -r .name)"; } }操作流程:
- 聚焦想要镜像的那块输出;
- 按下ModP;
- 把弹出的
wl-mirror窗口移动到目标输出; - 将该窗口全屏(默认是ModShiftF)。
小结与建议配置
把上述特性组合起来,你可以得到一套完整的 niri 录屏配置:
// 遮挡敏感窗口与通知。 window-rule { match app-id=r#"^org\.keepassxc\.KeePassXC$"# block-out-from "screencast" } layer-rule { match namespace="^notifications$" block-out-from "screencast" } // 标记正在被采集的窗口。 window-rule { match is-window-cast-target=true border { inactive-color "#7d0d2d" } shadow { color "#7d0d2d70" } } binds { // 演示浏览器内容时隐藏浏览器 UI。 Mod+Ctrl+Shift+F { toggle-windowed-fullscreen; } // 动态采集目标。 Mod+Ctrl+C { set-dynamic-cast-window; } Mod+Ctrl+M { set-dynamic-cast-monitor; } Mod+Ctrl+X { clear-dynamic-cast-target; } // 输出镜像。 Mod+P repeat=false { spawn-sh "wl-mirror $(niri msg --json focused-output | jq -r .name)"; } }更完整的规则说明与示例可继续参阅 Configuration:-Window-Rules.md 与 Configuration:-Layer-Rules.md;关于录屏实现细节(流管理、尺寸/刷新率同步、光标合成等)可深入阅读 src/screencasting/mod.rs。
【免费下载链接】niriA scrollable-tiling Wayland compositor.项目地址: https://gitcode.com/GitHub_Trending/ni/niri
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考