Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
1807447
feat(mobile): Android 与鸿蒙共用 Rime 输入法后端
aeoform Oct 14, 2026
fde702a
feat(harmony): wire IME voice capture and history pipelines
aeoform Oct 14, 2026
77874bd
feat(harmony): complete marketplace and history service wiring
aeoform Oct 14, 2026
899dae4
fix(ci): package Rime JNI for emulator checks
aeoform Oct 14, 2026
c069739
test(ci): run Harmony host pipeline contracts
aeoform Oct 14, 2026
1745ac0
fix(harmony): recover voice input after cancellation races
aeoform Oct 15, 2026
d72275a
fix(harmony): release voice capture when the keyboard hides
aeoform Oct 15, 2026
abf73b9
fix(ime): preserve queued Rime input and expand native candidates
aeoform Oct 15, 2026
a928178
feat(ime): share native personal learning across mobile platforms
aeoform Oct 16, 2026
aeb98be
merge: sync beta to restore mobile learning CI
aeoform Oct 16, 2026
64abbe0
test(ui): measure chip layout independently of animation transforms
aeoform Oct 16, 2026
20c7501
feat(harmony): refine IME panel and retire legacy Android pinyin
aeoform Oct 16, 2026
c9bf5ac
fix(harmony): guard keyboard input until Chinese engine is ready
aeoform Oct 16, 2026
930bb51
fix(harmony): compact keyboard header and localize punctuation
aeoform Oct 17, 2026
71dfecb
feat(harmony): add standard split and floating IME layouts
aeoform Oct 17, 2026
1dc670a
fix(harmony): avoid recursive IME panel geometry updates
aeoform Oct 17, 2026
2f91985
fix(harmony): keep private typing available when editor attributes fail
aeoform Oct 17, 2026
dfa0330
test(harmony): normalize Windows line endings in host mocks
aeoform Oct 17, 2026
09a6229
fix(harmony): handle compatibility host commit acknowledgements safely
aeoform Oct 17, 2026
4315637
fix(harmony): recover stalled compatibility writes without blocking t…
aeoform Oct 17, 2026
28e870f
fix(harmony): bound asynchronous editor writes and confirm text notif…
aeoform Oct 17, 2026
0c3326f
fix(harmony): retain active editor baseline after manual commit recovery
aeoform Oct 17, 2026
1d14bb1
fix(harmony): verify compatibility commits with bounded editor readback
aeoform Oct 17, 2026
b04ed04
fix(harmony): dispatch compatibility deletes without queuing missing …
aeoform Oct 17, 2026
ab8ae0e
fix(harmony): keep composing after dispatching compatibility commits
aeoform Oct 17, 2026
8c13273
fix(harmony): avoid blocking compatibility input after editor actions
aeoform Oct 17, 2026
b6609ea
feat(ime): share Ice dictionaries and opt-in cloud candidates on mobile
aeoform Oct 17, 2026
24c9c4c
fix(harmony): keep cloud controls reachable in compact keyboard panels
aeoform Oct 17, 2026
08394b0
feat(harmony): add deliberate symbol gestures with input feedback
aeoform Oct 17, 2026
d8ae9e7
test(android): separate cold Rime deployment from input queue deadline
aeoform Oct 17, 2026
8dd421a
fix(harmony): prewarm Rime without blocking keys and align split rows
aeoform Oct 17, 2026
8693de9
style(harmony): balance standard keyboard width and key surfaces
aeoform Oct 17, 2026
f80b108
test(harmony): normalize Windows newlines in startup harness
aeoform Oct 17, 2026
a60a7cf
feat(harmony): use square keys with rounded icons and press motion
aeoform Oct 18, 2026
4b847e3
fix(harmony): resolve punctuation from live language and retain symbo…
aeoform Oct 18, 2026
e06ceb8
feat(ime): refresh public dictionaries while keeping learning local
aeoform Oct 18, 2026
d146ab0
fix(android): expose shared public dictionary updater through JNI
aeoform Oct 18, 2026
ed7a322
feat(harmony): fill keyboard width and group tools into horizontal tabs
aeoform Oct 18, 2026
37d4a8d
fix(harmony): keep full-width keyboard compact with wider key spacing
aeoform Oct 18, 2026
733bfcb
fix(harmony): keep standard and split keyboards at the same height
aeoform Oct 18, 2026
340f022
style(harmony): center tool tabs and move back action to top left
aeoform Oct 18, 2026
4dfff13
fix(harmony): anchor menu back button at the left edge
aeoform Oct 18, 2026
a4d0356
feat(harmony): add icon menu tiles and swipe between tool categories
aeoform Oct 18, 2026
58f8b3e
fix(harmony): animate menu paging and avoid transient commit recovery UI
aeoform Oct 18, 2026
0fa2474
style(harmony): track menu page motion and align left header controls
aeoform Oct 18, 2026
b465bec
feat(harmony): add continuous candidates and four editable toolbar slots
aeoform Oct 18, 2026
f9be114
perf(harmony): prefetch and append candidates in larger batches
aeoform Oct 18, 2026
9a697b9
fix(ime): read candidate ranges without changing the active Rime page
aeoform Oct 18, 2026
ed2b053
feat(harmony): organize two-row tools and connect keyboard preferences
aeoform Oct 18, 2026
1569a64
feat(harmony): add local clipboard history and fix live swipe controls
aeoform Oct 18, 2026
101a354
fix(harmony): read clipboard automatically with permission wiring
aeoform Oct 18, 2026
4f58831
fix(harmony): wait for clipboard history restore before collecting
aeoform Oct 18, 2026
a402a1f
fix(harmony): invalidate clipboard reads when clearing history
aeoform Oct 18, 2026
ad7a176
fix(harmony): keep normal builds installable and skip startup probes
aeoform Oct 18, 2026
6f11e63
fix(harmony): handle missing ability launch arguments
aeoform Oct 18, 2026
81b4b6e
feat(harmony): wire app dictation and hold-space voice input
aeoform Oct 18, 2026
fdc4351
fix(harmony): bind omni credentials to the active provider
aeoform Oct 18, 2026
17a28bd
chore(harmony): trace voice startup stages without credentials
aeoform Oct 18, 2026
2cc9085
fix(harmony): share voice configuration across the IME sandbox
aeoform Oct 18, 2026
ba7a8ee
feat(harmony): insert voice results when the space key is released
aeoform Oct 18, 2026
5e58894
test(harmony): normalize voice fixture source on Windows
aeoform Oct 18, 2026
e7cf6d4
fix(harmony): use the OpenLess brand icon for app and IME
aeoform Oct 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/android-apk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ jobs:
if: needs.plan.outputs.mode == 'debug'
working-directory: openless-all/app
env:
OPENLESS_CACHE_ROOT: ${{ runner.temp }}/openless-cache
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
Expand All @@ -337,6 +338,7 @@ jobs:
if: needs.plan.outputs.mode == 'release'
working-directory: openless-all/app
env:
OPENLESS_CACHE_ROOT: ${{ runner.temp }}/openless-cache
# Resolve signing credentials at execution time; compiled Gradle scripts
# are cached and must never contain secret literals.
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/check-linux-egui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ jobs:
working-directory: openless-all/app
run: |
cargo test --locked -p openless-core
cargo test --locked -p openless-harmony-host
cargo clippy --locked -p openless-core --all-targets -- -D warnings
cargo test --locked -p openless-linux-egui --all-targets
cargo clippy --locked -p openless-linux-egui --all-targets -- -D warnings
Expand Down
14 changes: 13 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,17 @@ jobs:
- name: Copy Android production and test scaffolding
run: node scripts/copy-android-scaffolding.mjs

- name: Cache Android Rime instrumentation dependencies
uses: actions/cache@v4
with:
path: ${{ runner.temp }}/openless-cache/mobile-rime
key: mobile-rime-ndk26-x86_64-${{ hashFiles('openless-all/app/scripts/build-mobile-rime.py', 'openless-all/app/mobile-ime/**') }}

- name: Build and stage Rime for Android instrumentation tests
env:
OPENLESS_CACHE_ROOT: ${{ runner.temp }}/openless-cache
run: python3 scripts/build-mobile-rime.py android --abi x86_64 --stage

- name: Merge Shizuku manifest
run: node scripts/merge-android-shizuku-manifest.mjs

Expand Down Expand Up @@ -219,7 +230,8 @@ jobs:
api-level: 35
target: google_apis
arch: x86_64
# The instrumentation suite exercises AndroidKeyStore without launching Tauri.
# The suite exercises AndroidKeyStore and the real packaged Rime JNI
# without launching Tauri. Rime is staged for this emulator ABI above.
script: >-
cd openless-all/app &&
src-tauri/gen/android/gradlew
Expand Down
53 changes: 53 additions & 0 deletions docs/mobile-ime-engine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Android / 鸿蒙输入法后端整理与引擎选型

核对日期:2026-10-17。移动端已确定统一采用 librime,不引入 Fcitx5。Linux 保留现有 Fcitx5 系统宿主。Android 用 JNI、鸿蒙用 Node-API 接到同一套 `mobile-ime/rime_engine.cpp`;两端保留原生键盘和系统输入接口。

鸿蒙已完成 Rime 1.17.0 arm64 构建与签名安装,真机自检通过 `nihao` → “你好”候选、编码退格、候选确认、取消。用户已确认中文候选出现。Android 已改接共用引擎,NDK arm64 原生库及 JNI 编译通过,尚未完成 APK 真机验收和旧学习数据导入。

## 当前问题

鸿蒙早期首版只有英文直输;当前已接 Rime 拼音编码、候选、翻页、空格确认及中英文切换。已接通输入法录音、PCM 输送、ASR/润色管线与结果确认写入,并通过隔离的离线管线检查;真实联网识别尚需配置渠道后验收。

鸿蒙退格使用 IME Kit `InputClient.deleteForward` 删除光标前的内容;`deleteBackward` 删除光标后内容。方向和按键交互已修正,用户确认可删除。诊断仅记录动作类型、返回值和错误码,不记录输入内容;删除键支持长按重复。

移动端现已接入共享 librime,旧 LitePinyin 算法及 TSV 已移除。2026-10-17 核对本机配置发现,桌面默认实际为 Fcitx5 libime 拼音并启用云拼音,而非 Rime。移动端继续使用 Rime,升级固定修订的雾凇中文词库与拼写规则,并接入默认关闭的 Google 云候选;不迁移桌面个人词典,也不引入 Fcitx 到移动端。完整能力及隐私边界见 `openless-all/app/mobile-ime/README.md`。

## 开源候选

| 项目 | 可以复用的部分 | 适配成本及边界 |
| --- | --- | --- |
| [Fcitx5 Android](https://github.com/fcitx5-android/fcitx5-android) / [Fcitx5 Harmony](https://github.com/fcitx-contrib/fcitx5-harmony) | 两个平台的 IME 适配参考 | 已排除,不是当前移动端依赖。 |
| [librime](https://github.com/rime/librime) / [Trime](https://github.com/osfans/trime) | 跨平台 C++ 中文引擎、输入方案、候选和用户词典;Trime 提供 Android JNI 与前端参考 | librime 为 BSD-3-Clause,Trime 为 GPL-3.0。需要编译 Boost、LevelDB、marisa、OpenCC、yaml-cpp 等依赖并适配鸿蒙;目前核对的上游文档未提供可直接装入本项目的 OHOS 构建产物。 |
| [libpinyin](https://github.com/libpinyin/libpinyin) | 拼音算法库 | GPL-3.0;不是完整移动输入法。平台接线、UI 和词库打包仍需自行完成,当前不优先。 |

以上是选型时的比较,最终按用户要求采用 Rime。Fcitx5 / libime 不作为移动端依赖;移动端只复用 librime 及其构建依赖、输入方案和词库。

来源:[鸿蒙子模块清单](https://github.com/fcitx-contrib/fcitx5-harmony/blob/main/.gitmodules)、[鸿蒙按键与提交适配](https://github.com/fcitx-contrib/fcitx5-harmony/blob/main/entry/src/main/ets/InputMethodExtensionAbility/model/KeyboardController.ets)、[librime 构建依赖](https://github.com/rime/librime#build-dependencies)。当前 OpenLess 仓库使用 AGPLv3;是否复用第三方源码或只链接引擎,需按实际文件许可记录,本文未引入第三方源码。

## 共用边界

两端保留原生键盘宿主与渲染,共用引擎接口和行为规范。输入框权限、原生客户端对象和面板生命周期不能抽进跨平台引擎。

1. **引擎适配层(C/C++ + 稳定 C ABI)**:创建/销毁会话、按键、选候选、翻页、清空、方案切换、用户词典。输出编码、预编辑、候选及待提交文字,不接触目标应用。
2. **OpenLess 共用业务层(现有 Rust core)**:语音会话、ASR、润色、风格、渠道、历史。语音最终文字走与候选相同的提交路径;不重写现有提供商逻辑。
3. **Android 平台层(Kotlin / JNI)**:InputMethodService、InputConnection、composing、提交、删除、编辑器 action、权限、原生 UI。
4. **鸿蒙平台层(ArkTS / Node-API)**:InputMethodExtensionAbility、InputClient、Panel、预编辑、提交、删除、编辑器 action、权限、原生 UI。只使用输入法,不接无障碍。

统一请求携带 `sessionId`、`generation` 和递增 `requestId`;异步候选/语音结果必须匹配当前会话才可显示或提交。候选项用稳定 ID,不能只依赖旧列表下标。状态包括语言模式、编码、预编辑、候选及页码、录音状态、错误状态。平台返回提交成功/失败,成功后才清理待提交内容和更新学习记录。

## 行为规范

- 中文字母先进入编码,选择候选或空格确认首选候选;无编码时空格直接插入空格。
- 退格优先删编码;无编码时删选中内容;无选中内容时删光标前一个用户感知字符。平台需处理 emoji、组合字符、目标应用的删除语义,不能统一按一个 UTF-16 单元处理。
- 回车有编码时先确认编码;无编码时按目标编辑器的发送/搜索/换行语义执行。
- 切换输入框、语言、密码框、关闭键盘时明确取消编码与未提交异步结果;不要把旧输入框文本带进新会话。
- 密码框不采集词库学习,不把上下文送给语音/润色服务。普通按键、候选及个人词库默认本地处理。
- 候选区支持翻页;布局区分语言、数字和符号;按键有按下反馈、足够触摸范围、明确删除标签和长按删除。

## 验证与迁移顺序

先验证引擎能在 OHOS arm64 交叉编译,加载真实中文词库,完成 `nihao` → 候选 → `你好` 提交,再验证 Android 使用同一引擎和同一套输入案例。记录离线首启耗时、候选延迟、库与词典体积及生命周期行为。未通过这一步前不整体替换安卓版。

随后接入编码/候选 UI、退格与选区、模式切换、编辑器 action,再接语音结果确认提交。最后逐段迁移安卓版大 Service,保留当前可用功能及用户词库数据,先有稳定新路径再删除旧实现。

共同验收案例包括全拼短句、简拼、重码翻页、连续空格、编码退格、选区删除、中间光标删除、emoji 删除、快速切换输入框、密码框、录音取消、识别结束时切换应用、输入失败后重新确认。两端均使用相同案例,不要求两端 UI 代码相同。
11 changes: 11 additions & 0 deletions openless-all/app/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions openless-all/app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ resolver = "2"
members = [
"crates/openless-core",
"linux-egui",
"harmony/rust",
]
exclude = [
"src-tauri",
Expand Down
8 changes: 6 additions & 2 deletions openless-all/app/android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,13 @@ Manifest 合并脚本:
- [`scripts/merge-android-shizuku-manifest.mjs`](../scripts/merge-android-shizuku-manifest.mjs) — Shizuku Provider / 授权 Activity
- [`scripts/patch-android-shizuku-deps.mjs`](../scripts/patch-android-shizuku-deps.mjs) — Shizuku Gradle 依赖

### 轻量拼音输入(Pinyin,可选模式)
### Rime 拼音输入(Pinyin,可选模式)

英文键盘长按空格可在英文 / 拼音之间切换。拼音模式为离线精确匹配查找(单字全拼 + 高频简拼词语),不含 NDK / JNI / librime / 语言模型,不新增联网权限。核心类:`LitePinyinController`(编码缓冲 + 查询)、`LitePinyinRepository`(词库索引 + 排序)、`LitePinyinUserFrequency`(本地选择频率)。词库资源 `android/assets/pinyin_chars.tsv`(约 8105 字)与 `pinyin_phrases.tsv`(约 2000 词),由 [`scripts/generate-pinyin-characters.mjs`](../scripts/generate-pinyin-characters.mjs) / [`scripts/generate-pinyin-phrases.mjs`](../scripts/generate-pinyin-phrases.mjs) 从三方开源数据生成,来源、版本与许可证见同目录下的 `pinyin_chars.LICENSE.txt` / `pinyin_phrases.LICENSE.txt`。
英文键盘长按空格可在英文 / 拼音之间切换。拼音路径已改为 `RimePinyinController` → `OpenLessRime` JNI → 与鸿蒙共用的 `mobile-ime/rime_engine.cpp` / librime 1.17.0,不引入 Fcitx5。编码和候选由 Rime 生成,空格确认候选,确认后由 InputConnection 提交。密码框不进入拼音,禁止学习时使用禁用用户词典的独立方案。

`npm run tauri:android:build` 会在 Tauri 构建之前按目标 ABI 编译 Rime、生成 JNI 库并复制离线词库到生成的 Android 工程。需要 `ANDROID_NDK_HOME`;缓存默认 `/data-win/openless-cache/mobile-rime`。单独验证 arm64 引擎:`python3 scripts/build-mobile-rime.py android --ndk /path/to/ndk`。直接 Gradle 构建前需带 `--stage` 执行该脚本。

旧 `LitePinyin*` 查询、排序、学习算法和旧内置拼音 TSV 已移除;构建脚本会清除生成工程中的同名遗留文件。`LegacyPinyinBackup` 仅保留旧导入/导出格式兼容,不参与候选或训练。当前学习与个人词典由 Rime 原生实现,沿用持久目录中的 `pinyin_simp.userdb`;普通输入框启用学习,私密字段使用不读取个人词典的独立方案。

## 前端(`android/frontend/`,别名 `@android`)

Expand Down
38 changes: 0 additions & 38 deletions openless-all/app/android/assets/pinyin_chars.LICENSE.txt

This file was deleted.

Loading
Loading