先!进!的跨平台输入法,目标是完全取代老旧的搜狗输入法 目标支持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状态栏与候选窗都是 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 在输入法内部切换中英文,与搜狗一致:
- 英文模式下字母、数字、符号直接上屏,不经过拼音;
- 只有 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 自己的面板(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。