Linux 窗口捕获
仅限 Wayland。每次捕获都在 LinuxSnapShot.ts 中选定一个后端(Hyprland 辅助程序、Niri IPC、KDE 辅助程序、以活动窗口为目标的 Screenshot portal、GNOME 扩展,然后是 Electron 选择器),失败、取消或被拒绝时,绝不回退到另一个后端。过期的 NIRI_SOCKET,或一个可达的 GNOME 扩展,都不得据此改选其他桌面的后端;会话检测来自 XDG_CURRENT_DESKTOP,位于 linuxCaptureSession.ts。
模块边界
dbus-next 只能经由动态导入到达。linuxCaptureSession.ts 存放纯辅助函数(会话检测、Niri 绑定文本、portal 按键映射、PNG 读取),这样主进程就能回答「这是哪个桌面」,而不必在 macOS 或 Windows 上加载 D-Bus 客户端。
patches/dbus-next@0.10.2.patch 会移除 usocket(其可选的原生 Unix-FD 传输),并连接到 unix:path= 与 unix:abstract= 两条总线,连接经由 Node 的 net。我们从不传递 Unix 描述符,而上游对抽象套接字没有 net 回退;这类套接字由 dbus-launch 在 systemd 之外产生。升级 dbus-next 时请保留这份补丁。
无障碍身份
Screenshot portal 返回的是 PNG URI,没有窗口身份,因此 portal 捕获和选择器捕获都不携带无障碍数据。不要根据随后获得焦点的窗口,或猜测出来的标题来推断这些数据。
原生后端会提供 PID、标题和帧。AT-SPI 按 PID,再加上唯一的标题与尺寸来匹配。在 Wayland 上,即使合成器知道真实位置,AT-SPI 对窗口屏幕位置报告的仍是 (0, 0),因此只比较宽度和高度;只有当 AT-SPI 根节点与合成器帧一致,且至少一个后代报告了不同位置时,后代坐标才可信。否则边界取 null,而不是原点为零的矩形。
标题匹配会忽略开头单独的一帧盲文 CLI 转轮,因为终端会在捕获与查找之间改掉它。KDE 同时提供 frameGeometry 和 clientGeometry;两者中任一已经校验过的尺寸都可以接受,绝不要使用装饰高度容差。
在 GNOME 上,浏览器的无障碍桥接必须在浏览器启动之前具备 org.gnome.desktop.interface toolkit-accessibility。不要在捕获过程中切换它。
KDE
KWin 授权 org.kde.KWin.ScreenShot2 的方式,是把调用方 PID 的可执行文件解析到它的应用程序注册表,因此辅助程序要安装在 XDG data home 下的稳定路径,并带有隐藏的 desktop 条目,而不是从 AppImage 挂载点运行。有两个陷阱:
X-KDE-DBUS-Restricted-Interfaces使用 KConfig 的逗号分隔列表语法。末尾的分号会变成接口名的一部分,KWin 会拒绝它。- KService 的缓存键包含搜索路径和区域设置。请直接使用
kbuildsycoca6刷新,并通过systemd-run --user再刷新一次;只在 AppImage 环境里刷新,可能让 KWin 读到过期的权限。
就绪与否的检查方法是:用一个无效 ID 调用 CaptureWindow,并期望得到 InvalidWindow。这次调用发生在 KWin 的授权检查之后。磁盘上有文件,绝不表示已经就绪。
Plasma 会消耗 Shift 来处理上档数字,并产生一个标点 keysym,因此绑定成功的 Ctrl+Shift+2 在某些布局上可能永远不会触发。可以用字母组合键绕过。通用的修复需要能感知布局的编码。
Hyprland
辅助程序先用 hyprland-toplevel-mapping-v1 把 foreign-toplevel 句柄映射为完整的 64 位窗口地址,再通过 hyprland-toplevel-export-v1 导出。绝不要截断该地址,也不要按标题挑选窗口。
同意被拒绝,或适用 no_screen_share 规则时,Hyprland 0.56.2 会渲染一张拒绝访问的纹理,而不是让导出帧失败。ready 并不是一次权限授予,也不得靠检查像素来猜测是否已经授予。参见 ScreenshareFrame。
Hyprland 的 GlobalShortcuts portal 注册的是动作,不是按键组合。shortcutActionRegistered 与 shortcutRegistered 是两回事;界面中任何地方都不得声称这些按键已被预留。
捆绑的协议 XML 随辅助程序一并提供,因为其 BSD 许可证要求附上该声明。
Niri
Niri 没有实现 global-shortcut portal。捕获启用期间,应用在会话总线上拥有 <app-id>.SnapShot,并导出 com.t3tools.SnapShot.Capture;配置中的绑定会启动 gdbus 来调用它。开发用的应用 ID 与打包后的应用 ID 使用不同的名称,这样开发构建就不会抢走用户的绑定。
Niri 不提供全局截图原点,因此 AT-SPI 匹配只使用逻辑尺寸和标题。
配置编辑
CaptureShortcutConfig 会编辑用户的点文件。不变量如下:打开设置时绝不读取该文件;审阅更改才表示同意读取;渲染进程只会经由受信任的桌面 IPC 看到修改前与修改后的文本;保存发送的是提案 ID,桌面端在写入之前,会对照预览时的那份快照,重新校验字节、inode、文件模式以及 include。写入经过暂存临时文件和原子重命名,并在原文件旁边留下备份。Niri 会先校验暂存文件。
修饰键的序列化会显式写出 Linux 的 Ctrl,绝不会使用跨平台的 mod 别名。
GNOME 扩展
源码位于 apps/desktop/gnome-extension,UUID 为 snap-shot@t3.codes。GNOME 只在登录时才会发现新安装的扩展,因此设置流程会把「已安装,需要注销」和「已发现,但已禁用」区分开,并比较已加载版本与已安装版本。
扩展信任同一连接上拥有 com.t3tools.T3Code.SnapShot(或 .Development 变体)的调用方。这是 GNOME 的受信任会话客户端模式,并不是针对用户总线上的恶意进程做身份验证。
Electron 不会在 Wayland 上为覆盖窗口定位,因此闪光和飞入作为扩展内部的 Shell actor 运行,坐标相对于 T3 的内容区域。Electron 44 的会话恢复路径可能跳过重新绑定,并在注销时遗留回调,所以 PortalCaptureShortcut 自己持有 portal 会话,而不是使用 Electron 的 global-shortcut API。
GNOME 50 移除了 Meta.is_wayland_compositor。Shell 的内部实现会随主版本变化;在把某个版本加入 metadata.json 之前,请先逐一验证。