Skip to content

About

先! 进! 的跨平台输入法

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Superior-IME

先!进!的跨平台输入法,目标是完全取代老旧的搜狗输入法 目标支持Android,GNU/Linux,Windows三平台 (苹果系没设备暂缓)

  • 输入引擎:内置 librime + 万象拼音方案。不依赖 fcitx5-pinyin,也不重写 语言模型,只做引擎适配、候选后处理与 UI。
  • 前端与后端分离:UI 只通过契约与后端通信。
  • 皮肤使用自有的 superior.skin/1:声明式、版本化、可校验、可确定性渲染。
  • 语音、Windows、macOS、Android 是后续里程碑,接口已预留。

详细设计:PLAN.md、docs/architecture.md、 docs/skin-format.md、docs/logging.md、 docs/fcitx5-integration.md、 docs/pinyin-engine.md、docs/ui-scaling.md。

要往别的操作系统上搬:docs/platform-onboarding.md (每个端口的确切签名、三种 TextEdit 的语义、验收清单、以及哪些端口现在不该实现)。


当前状态

里程碑 内容 状态
M0 构建系统、脚本、backend/api、边界检查 ✅
M1 superior.skin/1、loader/validator、specHash、skin-lint、默认皮肤 ✅
M2 backend/core、backend/runtime ✅
M2.5 backend/protocol:帧、Hello / Scene / Command、Unix socket ✅
M3 Fcitx5 观察者 agent、Qt 桌面壳、X11 浮层 ✅ 自绘候选窗与状态栏已实测
M3.5 统一日志、可观测性与错误路径单测 ✅
M3.6 内置 librime、万象方案、独立数据目录、按键/翻页/云端行一致 ✅
M4 XWayland 自动选择、classicui 主题导出 🚧 自动选择已完成;layer-shell 状态栏待做
P2 .ssf 导入器(可选工具,不进入运行时) 未计划
P3 本地流式语音(sherpa-onnx) 未计划

构建

要求:CMake ≥ 3.25、Ninja、C++20 编译器、nlohmann-json3-dev。

./scripts/bootstrap.sh          # 只检查依赖,不安装任何东西
./scripts/configure.sh          # 默认 linux-debug
./scripts/build.sh
./scripts/test.sh

其他 preset:

./scripts/configure.sh linux-release
./scripts/build.sh linux-release

./scripts/configure.sh linux-asan    # ASan + UBSan
./scripts/test.sh linux-asan

./scripts/configure.sh linux-ui      # Fcitx5 agent + Qt 浮层
./scripts/build.sh linux-ui

运行桌面浮层(XWayland)

状态栏与候选窗都是 override-redirect 窗口:永久置顶、不抢焦点、 不出现在任务栏。它们只是另一个进程,不参与输入路径。

superior_ui 启动时自己选择平台插件:Wayland 会话且存在 DISPLAY 时强制 QT_QPA_PLATFORM=xcb(走 XWayland),这样窗口才保持 override-redirect; SUPERIOR_UI_PLATFORM 可覆盖这个选择。判定逻辑与用例见 frontend/desktop/src/PlatformChoice.{hpp,cpp} 与 tests/qt_platform_tests.cpp。

# 1. 构建
./scripts/configure.sh linux-ui
./scripts/build.sh linux-ui

# 2. 没有安装 agent 时,用模拟器看效果
./build/linux-ui/tools/scene-sim/superior_scene_sim &
./scripts/run-ui.sh
# 默认同时绘制候选窗与状态栏;候选窗只接管 super-ime,
# 其他输入法仍由 Fcitx5 classicui 绘制:
#   ./scripts/run-ui.sh --no-candidates   # 只显示状态栏

# 3. 用户目录安装(推荐,可干净卸载,随会话自启动)
./scripts/install-user.sh
# agent 加载后会自动拉起 superior_ui;不需要时设 SUPERIOR_UI_AUTOSTART=0

# 4. 每次重新编译后,完整重启(不用注销、不用重启电脑;脚本在仓库外)
../restart-ime.sh

# 5. 添加输入法:fcitx5-configtool 里选「先!进!输入法」
#    或直接切换
fcitx5-remote -s super-ime

改了代码之后怎么让它生效

不用注销,也不用重启电脑,但必须让两个进程都退出:

进程 为什么必须重启
fcitx5 agent 是 libsuperioragent.so,以共享库形式加载进 Fcitx5,无法原地重载
superior_ui 只杀掉 Fcitx5 时旧浮层不会退出,它会重连到新 agent 并继续跑旧版本的 UI
../restart-ime.sh          # 仓库外的本机辅助脚本,见下文等价命令

为什么重启必须写对:FCITX_ADDON_DIRS 若没导出,用户目录的 addon 会被静默丢掉 (systemctl --user set-environment 只对 systemd 拉起的进程生效,终端里起的 fcitx5 不继承它),症状是「输入法不见了」。等价命令:

kill $(pgrep -x superior_ui) 2>/dev/null; fcitx5-remote -e
export FCITX_ADDON_DIRS="$HOME/.local/lib/fcitx5:/usr/lib/$(gcc -print-multiarch)/fcitx5"
setsid fcitx5 -d

安装后得到这些东西:

产物 类型
libsuperioragent.so + superioragent.conf Category=Module 观察者 addon(不是输入法)
libsuperiorpinyin.so + superiorpinyin.conf Category=InputMethod 的引擎 addon(内置 librime)
super-ime.conf 唯一输入法条目,uniqueName = super-ime,绑定 Addon=superiorpinyin

方案数据(万象拼音 Pure)随仓库提供,安装脚本会解压并编译一次; 手动重跑(幂等):

scripts/fetch-wanxiang-schema.sh

没部署时条目仍然存在,但打字没有候选,日志会说明缺什么。 只有一个条目,没有需要二选一的“兼容模式”,详见 docs/pinyin-engine.md。

新增条目必须重启 Fcitx5 才会出现在“添加输入法”里;fcitx5-remote -r 不够, Controller1.ResetIMList 会重建分组并丢掉用户条目,不要用。 详见 docs/fcitx5-integration.md。

验证窗口语义:

DISPLAY=:0 xwininfo -name "Superior-IME Status" | grep "Override Redirect"
DISPLAY=:0 xprop -name "Superior-IME Status" WM_HINTS _NET_WM_STATE

预期:Override Redirect State: yes、Client accepts input or input focus: False、 _NET_WM_STATE_ABOVE。细节见 docs/xwayland-ui.md。

浮层跟随系统缩放:X11 读 X 资源的 Xft.dpi,Wayland 读 wl_output.scale, 策略层只收到一个比例。见 docs/ui-scaling.md。

Fcitx5 agent 是 Category=Module 的被动观察者:不注册输入法、不抢 UI addon、 不修改预编辑。因此 Wayland 客户端继续使用 Fcitx5 的 wayland 前端, X11 客户端继续使用 XCB/XIM 前端,应用输入框内的预编辑行为不变。

唯一例外是内联英文模式:轻按 Shift 后,字母由 agent 直接 commitString 上屏(这是搜狗式的中英文切换,不切换输入法、不加载英文键盘布局); Ctrl / Alt / Super 组合与功能键一律放行。规则见 docs/input-panel.md。


日志

全项目统一入口,无 Qt 依赖,Qt 消息也会汇入同一格式:

SUPERIOR_LOG_LEVEL=debug ./superior_ui
SUPERIOR_LOG="superior.protocol=trace,superior.ui=debug" ./superior_ui
SUPERIOR_LOG_FILE=~/.cache/superior-ime/log.txt ./superior_ui
SUPERIOR_LOG_VIA_QT=1 QT_LOGGING_RULES="superior.*=true" ./superior_ui

规则与安全约束见 docs/logging.md。核心原则:不记录用户输入内容, 预编辑/候选一律用 redacted() 只留长度。


词库

方案数据随仓库提供(data/wanxiang/,26 MB 归档),由脚本解压并编译到独立目录:

scripts/fetch-wanxiang-schema.sh
superior-config pinyin lookup nihao   # 端到端自检
  • 方案:万象拼音 Pure(版本固定在脚本里,部署前校验 SHA-256)
  • 位置:$XDG_DATA_HOME/superior-ime/pinyin/{shared,user}
  • 与 ~/.config/fcitx5/rime 完全隔离,不读取也不修改系统的 Rime 配置
  • 用户词频由 Rime 的 userdb 维护;云端选词由 learning.json 维护
  • 设计、部署、翻页与语法模型的取舍见 docs/pinyin-engine.md

Shift 中英文切换

轻按 Shift 在输入法内部切换中英文,与搜狗一致:

  • 英文模式下字母、数字、符号直接上屏,不经过拼音;
  • 只有 agent 管这个开关:Rime 引擎的 ascii_composer 被部署脚本设为 noop, 否则两边状态会错位,出现“切回中文还在打英文”;
  • 长按 Shift 打大写字母、Ctrl/Alt/Super 组合键都不会触发切换;
  • 拼音还没选词时轻按 Shift,已输入的字母直接上屏并切到英文(设置页 「字母上屏并切英文」,shiftCommitPinyin,默认开启):按错拼音不必先删, 按一下 Shift 字母就留在输入框里,接下来的字母直接上屏;
  • 关闭后回到原来的行为:只切换中英文,当前拼音被丢弃、不会上屏;
  • Esc 立刻回到中文;模式是全局的,切窗口不会改变;
  • 关闭设置页的「Shift 切换中英文」后,Shift 完全归还给应用。

判定逻辑(ShiftTapDetector)在 backend/platforms/fcitx5/src/ShiftTap.cpp, 不依赖 Fcitx5 或 Qt,单测见 tests/shift_tap_tests.cpp。


设置

~/.local/bin/superior_settings      # 或从应用菜单打开「先!进!输入法设置」
# 真正的可执行文件在 ~/.local/share/superior-ime/bin/,~/.local/bin 里只是 200 字节的包装脚本

设置写在 ~/.config/superior-ime/settings.json,两个进程都轮询这个文件, 所以改完 500 ms 内生效,不需要重启 Fcitx5,也不需要重启浮层:

键 作用
skinId 浮层重新加载皮肤
uiScale 候选窗与状态栏的字号、内边距、命中区一起缩放(0.75–3.0),与桌面缩放相乘,见 docs/ui-scaling.md
showStatusBar 状态栏显隐(是设置项,不会被下一帧场景覆盖)
statusPlacement bottomRight / bottomLeft / topRight / topLeft
shiftToggle 内联中英文切换总开关
shiftCommitPinyin 拼音组合中轻按 Shift:字母直接上屏并切到英文(默认开)
cloudPinyinEnabled / cloudPinyinBackend 云拼音开关与服务商,见 docs/cloud-pinyin.md

状态栏是可点击的。点击只发语义命令 ToggleStatusItem{id},agent 找到 Fcitx5 里同名的 action 并调用它——引擎状态仍由引擎决定,浮层永远不持输入状态,也不抢焦点。

元素 含义 点击
圆点 拖动手柄,见下节 拖动
中 / 英 内联中英文 切换中英文
🌙 月牙 半角标点 切到全角
● 实心圆 全角标点 切到半角

标点项画成图形而不是「半角标点」四个字,原因有两个:字宽会随着切换变化, 整条工具栏会跟着跳;而且这个状态本就适合用图形表达。形状由 StatusGlyph.cpp 决定,active(全角)画实心圆,否则画月牙;月牙是「圆减去 偏移的圆」,缺口用工具栏底色绘制而不是抠透明,否则会露出后面的窗口。缺口方向 从正右(0°)逆时针旋转 45°(kMoonRotationDegrees),所以月牙开口朝右上。

状态来源:内置 Rime 引擎注册的 punctuation action 用 isChecked() 表示全角, 同时把图标设成 fcitx-punc-active / fcitx-punc-inactive。旧的 Fcitx 标点模块 只翻图标、不暴露 checked,所以 statusIconIsOn() 仍然先匹配 -inactive 再匹配 -active——「inactive」本身就包含「active」,只做子串搜索会把半角当成全角。

点击这个按钮会把 Rime 的 ascii_punct 开关(中标/英标)同步到所有打开的会话, 所以下一个获得焦点的窗口也会用同一种标点。状态在 Fcitx5 重启后回到方案默认的 中标/全角(目前没有单独持久化)。

引擎的 pinyin-prediction(预测开关)被过滤掉,不出现在工具栏:它是配置项, 不是打字时要按的按钮。

组合文本(输入框)也是自己画的

搜狗 Linux 版的输入框是它自己绘制的,所以拼音、下划线、光标都长在皮肤里。 先!进!在 super-ime 上下文里做同样的事:

环节 做法
让引擎把组合文本交给我们 取走 CapabilityFlag::Preedit,引擎于是写 InputPanel::preedit 而不是 clientPreedit
绘制 候选窗顶行:拼音 + 下划线 + 光标(PreeditLayout.cpp)
归还 切到别的输入法 / 浮层断开时把 capability 加回去,客户端恢复内联预编辑
兜底 引擎仍只写 clientPreedit 时也照样显示(PreeditSource.cpp)

这就是「在 xterm 里皮肤正常、在 VSCode 里皮肤不一样」的根因:VSCode 自己渲染 client preedit(用它的字体和颜色),我们的皮肤只能覆盖候选行。取走 capability 后 两处完全一致。

只移除 Preedit 一个 flag,归还时也只加回这一个 —— 不会覆盖别人在此期间改过的 Password / Disable。

候选窗

每个候选带序号(1. 你好)。序号优先用引擎自己的 label(因此 ①②③ 之类的 编号会保留),label 为空时用「位置 + 1」兜底;Fcitx5 只给了 label 没给文本时不会 渲染成 1. 1。格式化逻辑是纯函数:frontend/desktop/src/CandidateLabel.cpp, 单测 tests/candidate_label_tests.cpp。

  • 点击候选上屏:UI 发 ActivateCandidate{session, candidateId},agent 用 candidateIndexFor 把 id 换算成索引并重新校验(超出当前页就丢弃,不猜)。 过期的帧点了也不会选中别的词。
  • 滚轮翻页:一格一页(PageCandidates{delta}),hasNext/hasPrev 为假时 不动,不循环。触摸板的小 delta 被忽略,否则一次手势会翻过好几页。
  • 悬停高亮来自点击热区的实测矩形,换了新场景就重置,不会高亮到别的词上。

命令只在 session 属于当前聚焦的输入上下文时才执行,所以窗口失焦后到达的 点击不会打到新窗口里。

拖动与位置记忆

状态栏只有左侧的圆点(拖动手柄)能拖:从「中」「全角标点」上按下永远只是点击, 不会误拖动。手柄在悬停时会高亮,光标变成手形。

松开鼠标时位置写入 ~/.config/superior-ime/statusbar.json,下次启动直接还原:

{ "schema": "superior.statusbar/1", "x": 600, "y": 395, "placement": "bottomRight" }
  • 位置会被夹在屏幕可用区域内,多屏时按落点所在的屏幕计算;
  • 在设置页重新选「位置」会丢弃拖动坐标(文件同时删除),否则那次选择看起来没有效果;
  • 单独一个文件而不是塞进 settings.json:设置页写设置、浮层写坐标,互不覆盖。

窗口位置与缩放的纯计算在 frontend/desktop/src/LayoutRules.{hpp,cpp}, 单测见 tests/layout_rules_tests.cpp。


云拼音

默认关闭。开启后输入两个字母以上、停顿 200 ms,就会把纯拼音发给服务商, 用返回的词补一个候选;密码框与英文模式永不发送。

# 先确认当前网络能不能用(不改任何配置)
./build/linux-ui/tools/superior-config/superior_config_cli cloud probe --backend googlecn nihao
服务商 状态
googlecn(默认) 可用,直连约 0.2 s
baidu 接口失效(固定返回 errno 19)
sogou 无公开端点,暂不可用

实现与取舍(为什么不用线程、为什么状态以拼音为身份、为什么去重)见 docs/cloud-pinyin.md。


可选:把皮肤导出成 Fcitx5 主题

自绘候选窗不依赖 Fcitx5 主题。只有当你想让 Fcitx5 自己的面板(Rime 等) 也用同一套配色时,才需要导出:

./build/linux-debug/tools/skin-lint/skin_lint skins/default --strict \
    --export-theme ~/.local/share/fcitx5/themes/org.superior.default

然后在 classicui 配置里选 org.superior.default。这是全局设置,会影响所有 输入法;默认不写。详见 docs/fcitx5-theme.md。


皮肤工具

# 校验(warning 也视为失败)
./build/linux-debug/tools/skin-lint/skin_lint skins/default --strict

# 机器可读
./build/linux-debug/tools/skin-lint/skin_lint skins/default --json

# 打印规范化后的 spec(用于 diff / golden)
./build/linux-debug/tools/skin-lint/skin_lint skins/default --print-spec

退出码:0 干净,1 有诊断,2 用法或 I/O 错误。


目录

backend/
├─ api/        契约(事件、场景、命令、端口)
├─ skin/       皮肤模型 + 校验 + 哈希
├─ core/       纯逻辑
├─ runtime/    串行泵 + 票据
├─ protocol/   帧编解码 + Unix socket
├─ rime/       内置 librime 引擎(无 Qt / 无 Fcitx5,可单测)
└─ platforms/  fcitx5(观察者 agent + Rime 引擎适配)

frontend/
├─ app/        (后续)应用装配
├─ render/     (后续)离屏渲染
├─ desktop/    Qt 桌面壳:状态栏 + X11 候选窗
├─ mobile/     (预留)
└─ widgets/    (后续)共享控件

tools/skin-lint
skins/default
tests/
docs/

边界规则由 scripts/check-boundaries.sh 强制执行,并作为 ctest 用例运行:

  • frontend/ 不得依赖 backend/core、backend/runtime、fcitx*;
  • backend/core 不得依赖 Qt / Fcitx5 / 墙钟;
  • backend/pinyin 不得依赖 Qt / Fcitx5,公共头不得出现 rime_api.h / dlfcn.h;
  • backend/skin 不得依赖 Qt 或做像素解码;
  • backend/api 不得依赖任何实现。

许可

GPL-3.0-or-later,见 LICENSE。

内置皮肤为本项目自绘,不含任何第三方输入法的素材、字体或图标。

随仓库分发的万象拼音方案/词库(data/wanxiang/)许可为 CC-BY-4.0 (作者 amzxyz),署名、许可链接与改动说明见 data/wanxiang/README.md,来源与版本详见 docs/pinyin-engine.md。

About

先! 进! 的跨平台输入法

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages