Files
feresaria/docs/development/rime-poc.md
aria 993bc0f88f Integrate native Rime and stabilize input, output, and rendering
- Add optional modifier-only Super binding whose tap is cancelled by
  other input, focus changes, grabs, locks, and session teardown.
- Ship the native ferese-rime user service, Rime defaults, installer
  inventory, package dependencies, session environment, and docs.
- Vendor Smithay 8a27023 with the SHM texture-cache format fix and add
  GPU regressions for format changes and resize snapshots.
- Release reused CRTC owners before staging output modes, then restore
  changed outputs before former owners when a transaction fails.
- Wait for resize-capture GPU completion, expand filtered surface
  damage, and preserve IME popup origin under fractional scaling.
- Mark weekends like holidays in both calendar layouts.
2026-10-30 03:13:43 +09:00

13 KiB
Raw Permalink Blame History

Ferese 原生 Rime 原型

本地分支:twilight/rime-service-poc。基线为 Ferese v0.1.0,提交 f21167e1a22d527460e20a0eb3184790ee9754df。这是本设备上的架构验证,尚未作为日常桌面版本发布。

进程与协议

flowchart LR
    A[原生 Wayland 应用] <-->|text-input-v3| B[Ferese 合成器]
    B <-->|input-method-v2 / virtual-keyboard-v1| C[独立 Rust Rime 服务]
    C <--> D[librime 与独立测试词库]
    B -->|主题 IPC| C
    C -->|原生 input-popup SHM 缓冲区| B

ferese-rime 是独立进程。它直接调用系统 librime,通过原生 Wayland 协议接收按键、 提交文字和预编辑状态,并用 Rust 光栅化候选内容。候选颜色订阅现有 Ferese 主题 IPC。 Ferese 负责候选弹窗的父窗口关联、层级、坐标和屏幕边界处理。候选不是普通应用窗口。

本设备的字体默认改为 MiSans,候选服务优先加载 MiSans / MiSans TC。 候选使用紧凑的横向布局,编号和候选字按实际字形边界垂直居中, 圆角、底色、边框和选中态颜色取自 Ferese 主题。 组字时数字行 - 翻上页、= 或 + 翻下页;普通英文状态保留这些字符。

librime 仍是 C++ 引擎,通过一个由安装头文件检查 ABI 的小型 C 接口连接 Rust。 组字、词库和候选光栅化都在服务进程内执行。当前实现没有使用 Fcitx5、IBus 或其 D-Bus 兼容服务。

合成器的改动包括:

  • 在输入法开关下增加 virtual-keyboard-v1;默认不公开这两个输入法全局接口。
  • 为辅助键盘保留独立按键来源;输入法自身转发的按键绕过它自己的 grab,避免回环。
  • 拒绝锁屏期间的虚拟按键,并清理离线辅助设备的按键状态。
  • 失去嵌套窗口焦点时释放物理来源的按键,清除快捷键重复和拦截状态。
  • 区分 input-method 与 xdg popup 的几何语义;候选不再被父窗口的圆角边界裁剪。
  • 在输出底部向上展开候选,在左右边界约束位置;缩放使用逻辑坐标与原生 viewport。

构建与启动

本机日常会话安装

安装清单现包含 ferese-rime,Arch 包声明 librime 和 rime-luna-pinyin 依赖。公共方案与词库仍由系统包维护于 /usr/share/rime-data,不复制个人词库。 直接 DRM 会话默认启用原生输入法,并仅在 Ferese 会话内覆盖继承的 IBus Qt 环境。 GTK 不强制指定输入模块,由实际的原生 Wayland/X11 后端自行选择; 会话激活环境清空旧的 GTK_IM_MODULE,避免 XWayland 应用加载 GTK3 Wayland 模块时崩溃。 这些变量也导入该会话的 D-Bus/systemd 激活环境。嵌套预览保留原有的显式输入法启动方式。

会话在 $XDG_DATA_HOME/ferese/rime(默认 ~/.local/share/ferese/rime)建立独立用户目录, 仅在 default.custom.yaml 不存在时安装默认简体拼音和每页九个候选的配置。 不读取、迁移或覆盖 IBus/Fcitx 的个人词库,也保留该目录内已有的用户定制。 Rime 由会话固定版本的 systemd 用户临时服务运行,异常后重启并启用十秒输入循环 watchdog; 服务名称由合成器控制套接字稳定生成,Shell 硬崩溃恢复时仅替换该会话的旧输入法实例。 会话结束时停止,输入法失败保留桌面。可用 journalctl --user -u 'ferese-rime-session@*' 查看日志。FERESE_ENABLE_INPUT_METHOD=0 可在登录前显式停用此集成。

以下预览与硬件验证记录早于此安装集成,不能代替新安装会话的验收。

需要仓库的构建依赖,以及 librime、xkbcommon 和 Wayland 开发文件。 验证环境使用系统 librime 1.17.0、GTK4 4.24.1、RTX 4090、NVIDIA 615.78.08。 Rust 工具链由仓库固定为 1.95.0。

cargo build -p ferese -p feresectl -p ferese-rime --locked -j 12
python3 scripts/rime-preview.py start
python3 scripts/rime-preview.py status

预览使用开发版合成器、开发版输入法和已安装的 0.1.0 Shell。它会打开原生 GTK 测试框, 并在本机存在 Strata 时打开单独的测试目录。GTK 使用 GTK_IM_MODULE=wayland。

scripts/rime-preview.py restart 重开开发预览;stop 停止它;ime 只启动输入法服务。 输入法由独立 systemd 用户临时服务管理。异常退出后 1 秒自动重启;输入循环停止响应时, 10 秒 watchdog 会终止旧进程并重启。心跳来自实际输入循环,librime 卡住时不会继续报健康。 每分钟最多尝试 5 次,避免配置错误造成无限快速重启;主动 stop 不会触发重启。 服务不可用期间桌面保留,英文继续由合成器直接处理。

本机应用菜单中已有“Ferese Rime 开发预览”入口,右键操作可重开或停止。 入口仅调用本仓库脚本;移动仓库后应更新 ~/.local/share/applications/ferese-rime-preview.desktop 中的路径。

预览的配置、缓存、词库和证据位于 .artifacts/rime-poc-20261028/。 运行时套接字位于当前 UID 的 /run/user/<uid>/ferese-rime-poc/,目录权限为 0700。 它使用独立 D-Bus 和独立 systemd 用户临时服务,不导入宿主桌面的激活环境。 测试词库由系统方案建立,不导入原有 IBus/Fcitx 个人词库。

验证

按窗口保留状态

运行时按合成器会话 ID 和窗口 ID 隔离中英文模式、Rime schema;新窗口按应用 ID 恢复上次选择。 同一应用的两个现存窗口可保留不同状态;关闭后重开时采用该应用最后显式选择的状态。 输入法进程重启后也会恢复当前窗口偏好,不恢复未提交的组字。

input-profiles.json 位于独立 Rime 用户目录,通过 0600 临时文件、fsync 和原子重命名保存。 只保存应用标识和输入偏好,不保存窗口标题、聊天内容或组字文本。 Ctrl+Space 切换当前窗口的英文/Rime;原有 Shift 切换也会被保存。

# 在对应会话环境中查询或更改当前窗口;敏感输入框拒绝更改。
target/debug/ferese-rime --status
target/debug/ferese-rime --mode ascii
target/debug/ferese-rime --mode rime
target/debug/ferese-rime --select-schema luna_pinyin

控制套接字由 FERESE_RIME_CONTROL 指定,权限 0600。服务不运行时,查询命令不会初始化词库。

cargo test -p ferese input::binding_mode_tests -- --nocapture
cargo test -p ferese handlers::input_method::tests -- --nocapture
cargo test -p ferese-rime --lib --locked
cargo clippy -p ferese-rime -p ferese --all-targets --locked -- -D warnings
./target/debug/ferese-rime --user-data .artifacts/rime-poc-20261028/rime-self-test --self-test
FERESE_TEST_RIME=1 python3 scripts/tests/test_rime_native_isolated.py
FERESE_TEST_RIME=1 python3 scripts/tests/test_rime_resilience_isolated.py

最后一个测试实际启动构建树预览,操作专用原生 GTK 测试框,验证:

  • 组字未提前提交、原生候选不是普通顶层窗口、空格和数字选字。
  • 退格、取消、Shift 中英文切换、英文透传和 Ctrl+A 的按下/释放配对。
  • 数字行减号、等号及 Shift+等号翻页,翻页后数字选字;候选保持单行横向排列。
  • 敏感输入框直接收到测试英文,Rime 未收到这些组字事件。
  • 底边候选组字和提交,以及候选主题的实时切换;截图单独检查位置和外观。
  • 在组字期间 SIGKILL 输入法:合成器进程保持不变,服务自动重启并恢复当前窗口方案。
  • SIGSTOP 停止输入循环,确认 watchdog 真正回收并重启进程;关闭重开应用仍保留模式和 schema。
  • 输入法进程实际映射 librime,未映射 IBus/Fcitx 库。
  • 测试输入工具连接真实宿主时拒绝输入。

ferese-rime-probe 只供集成测试使用,需要显式 FERESE_TEST_INPUT=1,且在创建虚拟键盘前 检查目标确为只有 ferese-winit 输出的 Ferese 嵌套实例。 独立 DRM 测试额外要求专用 runtime 目录和与连接端 SO_PEERCRED 一致的合成器 PID。 普通宿主显示连接仍被拒绝,测试不向宿主注入键盘事件。 测试框只输入可丢弃样本,其普通字段保存在 entry-state.json 便于独立读回;敏感字段只记录长度。 自动结果和截图保存在同一证据目录,不能用单纯的编译通过代替桌面输入验收。

2026-10-28 的本机自动检查全部通过;用户随后使用实际键盘确认 “显示、翻页和选字正常”。底边候选向上展开、浅深色主题已逐张检查截图。 后续故障注入中,SIGKILL 后约 2.4 秒恢复,SIGSTOP 后约 11 秒被 watchdog 回收并恢复。

本设备的图形路径

这台设备由 NVIDIA 负责三个物理显示器,AMD 核显没有连接显示器。 嵌套后端原先只公开 v3 格式列表,现增加实际 EGL 渲染设备的 v4 DMA-BUF 反馈。 DRM 后端在可识别主 GPU 时只向客户端公开其分配入口,保留已有的按表面分配反馈。 修复后 QQ 图形子进程使用 NVIDIA 渲染节点;GTK 原先的空白内容恢复显示。 独立会话已分别用 GTK Vulkan 和 OpenGL 路径完成三屏输入及锁定/解锁协议测试。

Super+左键拖动沿用上游默认绑定,已用实际输入处理路径验证窗口位置改变。 FERESE_ENABLE_TEST_POINTER=1 仅在本机测试启动器启用虚拟指针;默认不公开该全局接口, 锁定时也拒绝虚拟指针事件。此接口用于重现 QQ 对话切换和拖动,不用于候选点选。

独立 logind/DRM 会话

python3 scripts/rime-drm-validation.py start
FERESE_TEST_DRM=1 FERESE_TEST_GSK=vulkan python3 scripts/tests/test_rime_drm_isolated.py
FERESE_TEST_DRM=1 FERESE_TEST_GSK=gl python3 scripts/tests/test_rime_drm_isolated.py
python3 scripts/rime-drm-validation.py return
python3 scripts/rime-drm-validation.py stop

启动器在空闲 tty3 建立独立的临时 PAM/logind 会话,保留原 GNOME 会话。 启动前先安排独立的返回定时器;默认 60 秒返回,人工解锁测试使用 120 秒。 配置、D-Bus、运行时目录均独立,私有 runtime 在启动 D-Bus 前设置,避免影响宿主激活环境。 停止时清理本次输入法、测试应用、临时服务和本次 logind scope;不注销原会话。

已验证 DP-4、DP-5、DP-6 同时以 3840×2160、150% 缩放工作,刷新率约 60/160/144 Hz。 各屏组字、候选和提交正常。协议测试确认三个输出都完成保护帧后才报告锁定, 锁定时普通应用不收到测试键,截屏被拒绝;锁定期间输入法自动恢复后,解锁可继续中文输入。 协议测试夹具不校验 PAM 密码;人工密码解锁另用原版 /usr/bin/ferese-lock --foreground 验证。 真实锁屏已由用户输入本人密码完成,状态读回包含锁定到解锁的完整转换,锁屏程序正常退出。 用户确认“三屏显示和解锁正常”。该测试不读取或记录密码。

QQ 与 Teams

python3 scripts/rime-app-preview.py qq
python3 scripts/rime-app-preview.py teams
python3 scripts/rime-app-preview.py stop

直接使用原生 Wayland Electron 输入参数,在各自独立测试资料目录中启动。 原 GNOME 下的 IBus/Fcitx 启动器、登录资料和 desktop 文件不修改。 QQ 已由用户登录,搜索输入和实际消息编辑框中的候选及“你好”提交通过, 测试文字随后删除,窗口关闭重开后仍保持 Rime。 已自动反复切换群助手和其他对话,对比屏幕帧及独立窗口重绘;首次加载图片导致的差异单独检查。 用户复测确认 QQ 切换对话“不再花屏”。未发送测试消息。 Teams 使用额外空白资料目录验证实际登录页文本框:原生 Rime 提交“你好”, 通过页面字段值独立读回,然后清空,未提交登录。 用户账户的 Teams 登录停在人脸/指纹/安全密钥验证页,用户确认 GNOME 下也有同样表现; 该登录条件尚未解除,不能把 Teams 的消息编辑器输入标为已验收。

当前边界

  • 已覆盖原生 GTK4 输入路径和 Strata 的候选显示,当前默认方案为 luna_pinyin_simp。
  • 候选只做键盘操作,按本次需求不实现鼠标选词;配置界面仍待实现。
  • 使用 US 键盘布局和嵌套输出验证;多布局切换、其他方案、个人词库迁移尚未验收。
  • 原生 QQ 搜索和消息编辑框、Teams 登录页输入已验证;Teams 消息编辑器、Qt、Xwayland 和 Shell 的 layer-surface 输入仍未验收。
  • 独立 DRM 三屏及锁定协议已覆盖;物理热插拔、跨不同 GPU 的显示器组合和长期使用仍需验证。
  • 当前为调试构建;字体加载、词库部署和重复绘制尚未进行性能优化。
  • FERESE_ENABLE_INPUT_METHOD=1 仍沿用上游“受信任会话客户端”的许可边界, 专用私有连接授权属于后续工作。

后续可以继续复用这套独立进程边界,逐步补齐候选交互和会话管理。