commit 29374caffd419277c51a4955a7296d2b40e0ea49 Author: herry Date: Sat Nov 14 12:10:10 2026 +0800 feat: 开源蟹蟹输入RTOS适配改版并采用MIT商用许可 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b744327 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +**/.glyphix-work/ +**/__pycache__/ +**/src/assets/ime-dict/ +temp/ +work/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5a0b817 --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +MIT License + +Copyright (c) 2026 herry +Copyright (c) 2026 kqakqakqa +Copyright (c) 2026 crabKun + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NOTICE.md b/NOTICE.md new file mode 100644 index 0000000..d269ef4 --- /dev/null +++ b/NOTICE.md @@ -0,0 +1,17 @@ +# 蟹蟹输入 RTOS适配改版 + +本项目为基于[CrabKeyboard / 蟹蟹输入](https://github.com/crabPangXie/crabKeyboard)的独立RTOS适配改版,由herry进行Glyphix页面、离线词库索引、九宫/14键消歧、标准QWERTY、候选分页及生命周期适配。不是上游原版,也不表示上游作者、华为、OPPO或Glyphix对本改版作出背书。 + +上游固定提交:`93d36b5002dee80a3d253b1ec7ec9e29800a104c`。保留原作者kqakqakqa、crabKun的版权声明、MIT许可证及原始词库/读取器。RTOS自研适配部分以根目录[MIT许可证](./LICENSE)发布,**允许商用、修改、再分发和闭源集成**,须保留适用版权及许可全文。 + +第三方内容继续按各自许可证使用: + +| 内容 | 作者/项目 | 许可与位置 | +| --- | --- | --- | +| CrabKeyboard V5词库和原始读取器 | kqakqakqa、crabKun | MIT,`third_party/crabKeyboard/LICENSE` | +| 本地候选排序词频 | jieba 0.42.1 / Sun Junyi | MIT,`third_party/jieba/LICENSE` | +| 操作图标 | Remix Icon v4.6.0,对应iconfont公开矢量 | Apache-2.0,`third_party/remix-icon/LICENSE`;矢量改色并转换PNG,来源/哈希见同目录`icons.json` | + +14键参考公开演示中的相邻字母分组,自行实现离线消歧;不包含华为内部模型或固件代码。Glyphix/厂商SDK与运行时不包含在本项目MIT授权中,也不随源码分发,使用者需自行取得适用的厂商授权。 + +公开仓库:。匿名问题与建议:。 diff --git a/PUBLIC_SOURCE_MANIFEST.json b/PUBLIC_SOURCE_MANIFEST.json new file mode 100644 index 0000000..c3e4a96 --- /dev/null +++ b/PUBLIC_SOURCE_MANIFEST.json @@ -0,0 +1,124 @@ +{ + "project": "蟹蟹输入 RTOS适配改版", + "version": "1.1.0", + "license": "MIT", + "independent_history": true, + "files": { + ".gitignore": "5fc1922c8c233ebab1a7a6d3f15f83a727039ad0b5be612bcb274706080c37fc", + "LICENSE": "fbe0034e81020ac52847583ed5a8dab0e212266623b475381c0a468a800f67df", + "NOTICE.md": "a16fb81a4ac41e6ebd8730c76194def41ba1abf61221d5c8f658907287c6ac03", + "README.md": "027180cde6817890733b364dfe9f3bde016d7c1c39b13a87ea258d9e00f49db8", + "build-rtos-game.ps1": "207ff9875af96221559f314364c932fe37bfc22c698ad1afabb9aeec40a8679e", + "com.sydxky.rtos.imefixture/build.ps1": "36b41ff37b153c1030756e641d2bb8d8a5404425905897e1f99e728c96493dad", + "com.sydxky.rtos.imefixture/glyphix.config.js": "c402178dc7457c56d9762b464a868b0c9ade304597d81ed46f0af04971fcddcf", + "com.sydxky.rtos.imefixture/src/app.js": "fc00d19bc94e10c24f07bd9be1c2bb24d4978856963c5a68745338ecbf131dfc", + "com.sydxky.rtos.imefixture/src/assets/icon.png": "f712ac5f8c500a33dd72cc1f15f9ad467e6043c284fb6ece1f1683e54d40e683", + "com.sydxky.rtos.imefixture/src/main/index.ux": "d71f1cb5e0342cbb9e9559760631046887c942ecb75436a450acef7c493ec9de", + "com.sydxky.rtos.imefixture/src/manifest.json": "40d7d1464409121b5c06ee77e3ff78d38c3f5271701cd754028cae5b5ef488c0", + "com.sydxky.rtos.inputmethod/.gitignore": "0f139de1ca71ff6aa1e6f02974b5e38d219d7781d4184d524845dce12d947454", + "com.sydxky.rtos.inputmethod/DESIGN.md": "5ba2d52755e9852be46f883507376fade0fbb006b44afc0cccf12447b8ae5a79", + "com.sydxky.rtos.inputmethod/KEYBOARD_14.md": "27ae1fa73dd8fa8fce70743fa1e418b4a4ff71ba610e735f009344896adfa754", + "com.sydxky.rtos.inputmethod/LICENSE": "fbe0034e81020ac52847583ed5a8dab0e212266623b475381c0a468a800f67df", + "com.sydxky.rtos.inputmethod/NOTICE.md": "a16fb81a4ac41e6ebd8730c76194def41ba1abf61221d5c8f658907287c6ac03", + "com.sydxky.rtos.inputmethod/PRIVACY.md": "c3062d8434db2fad3b6824cf2c41561a0bf6ae05e62f05d7c6316efd64d74af9", + "com.sydxky.rtos.inputmethod/README.md": "5b652f6892960eb6d9fe00d1c64207fc9872c7d6f5e5ddaef8ec3590d4a390b6", + "com.sydxky.rtos.inputmethod/VALIDATION_1.1.0.md": "891ea8683d0d1cc56d548ba9a7dc66fe18db5d68a94b5f6a7f11f1ad48c0c849", + "com.sydxky.rtos.inputmethod/build.ps1": "32168e753699f48274fe2492e53b7285e4b62c15dc928f3aeb366a11150ebf5a", + "com.sydxky.rtos.inputmethod/glyphix.config.js": "43beae1f2a61eed010149ae6ad77223ba084117ea5aee3cb3fcd48bfc0a4a32b", + "com.sydxky.rtos.inputmethod/scripts/build-icons.py": "51f859a9517e7dd2e5e4bf9f688ce41dc79d4bc491fc66066dfe2db4aae4d704", + "com.sydxky.rtos.inputmethod/scripts/convert-dictionary.py": "83eea8c7adae0254eb69170dec905c285c09c43ffcf10e8e553beb108acf43b3", + "com.sydxky.rtos.inputmethod/scripts/install.mjs": "46c7d95b25c312734c0ca8387f4a782becd4723f21d01492d074da22ca05432d", + "com.sydxky.rtos.inputmethod/src/app.js": "e51c7e0a67563bd64b6bc04aedde22842295824432942bc8f1ab0d4731cc3ed3", + "com.sydxky.rtos.inputmethod/src/assets/crab-keyboard-license.txt": "b140337f94481310f35abfd2210fde308ba30411899095fd961a77b94e022775", + "com.sydxky.rtos.inputmethod/src/assets/icon.png": "f712ac5f8c500a33dd72cc1f15f9ad467e6043c284fb6ece1f1683e54d40e683", + "com.sydxky.rtos.inputmethod/src/assets/ime-icons/LICENSE.txt": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4", + "com.sydxky.rtos.inputmethod/src/assets/ime-icons/arrow-down-s-line.png": "69bb846769721ed4ad056ed0136da59d4aa6873edccd1e38a7ebd783cb94ed1e", + "com.sydxky.rtos.inputmethod/src/assets/ime-icons/arrow-left-s-line.png": "68d8920081fb883f4c59af47549d6b41a9b0a3748383b814e4480bfab0e711ed", + "com.sydxky.rtos.inputmethod/src/assets/ime-icons/arrow-right-s-line.png": "c6bffa0a51254c60c92e276e4d21ae8a1595d82c77d9d6a1064afbf534ea3562", + "com.sydxky.rtos.inputmethod/src/assets/ime-icons/check-line.png": "dc3c240ba0db808212dae8a29dadbda9b0428d699a42f9e52e789e74ed56967c", + "com.sydxky.rtos.inputmethod/src/assets/ime-icons/close-line.png": "d3cfb57fa03b03c512286187681caaf4026a4dc60c18b91cbdc0bcc9e2586621", + "com.sydxky.rtos.inputmethod/src/assets/ime-icons/delete-back-2-line.png": "a83002ddc7ba1f1a86b361c0c672ec63205393cae2df275a9ff2d49501fd6b9d", + "com.sydxky.rtos.inputmethod/src/assets/ime-icons/edit-2-line.png": "6161624226e2b62b24da7244d86564d6d0856533c2149d3b347e17013c562497", + "com.sydxky.rtos.inputmethod/src/assets/ime-icons/keyboard-box-line.png": "bf4b45e2e54b4fd7e901068df1de865334f7e4616583dc5358d35d14890948af", + "com.sydxky.rtos.inputmethod/src/assets/jieba-license.txt": "18ba0984839f85853b29fadaf992f7dba8fd0ca0fbeae34de2b8735222dc7a37", + "com.sydxky.rtos.inputmethod/src/common/ime-engine.js": "fbfc3fa9a09914ce69da1ec0d03bed92505389c9b8bdf254d236f379bb0ecd16", + "com.sydxky.rtos.inputmethod/src/common/ime-lifecycle.js": "93525dffabcca0620acc36defd077ab6677b1032ef67faf695f3fbf3116b8a64", + "com.sydxky.rtos.inputmethod/src/keyboard/index.ux": "8022e5cff0075975377e0c3390ee9d9a541379768dc4732a77ff0ef865306dd3", + "com.sydxky.rtos.inputmethod/src/main/index.ux": "f763f0dc80b9fd46e00830d6ec0de8683063f98c1edee94ee522c2605b695fca", + "com.sydxky.rtos.inputmethod/src/manifest.json": "1adcd2d7610ce92c1c01f9c06f597e64569ae31de2422f1022e49bc04bb804ff", + "com.sydxky.rtos.inputmethod/tests/client.test.mjs": "8633a789b0c5e19ea63881ccf084e4442b0ab0fbd97a9fba4317e34e5570ff81", + "com.sydxky.rtos.inputmethod/tests/engine.test.mjs": "4967791409f7aa418b5d2b60a61aca48bbedfe5f8fad4a51b556a4f8e7c3e5a4", + "com.sydxky.rtos.inputmethod/tests/layout.py": "b632411cd593d369180abaeedbf036002e986e3e5e2c2f15b4e5ff157588aa78", + "com.sydxky.rtos.inputmethod/tests/native.py": "4b92ee16edb92ca919f6bce52974fa114dca7b6b23c83b3300f6e370a3247379", + "com.sydxky.rtos.inputmethod/tests/visual.py": "2874b3446028f7b7e54c4b1ecf594f8801c7249d67a0d17b8e7b0fdf97c8ff0f", + "com.sydxky.rtos.inputmethod/third_party/UPSTREAM.md": "e8311679981a318fef7142f7a2ecb2b1a7860f5fee32282729cab6af2a41d640", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/LICENSE": "b140337f94481310f35abfd2210fde308ba30411899095fd961a77b94e022775", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/README.md": "88c2f96be16dc5d80677745072940c8a1cbc7aab40e554de50ec11bc22339b75", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/lookupDictV5.original.js": "915605439ebc404d808ccce6cb18abfff3981f1cdf53a78e9a743c78c4a6ddc9", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key0.dat": "795a5316089b67a7bffabf87080bd61f3720f9e0fd2f2866a092901f99b794e8", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key1.dat": "a4bcae2b6eb30084d3cf2ac64e5d9ca71eb94e8ac1f7c8fc8b38a5d478982be2", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key10.dat": "5731d9f3a3cad0304ecc650d74c723185c9d43f74d66d7b5622454d1fc31f99c", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key11.dat": "fa2a4bfa64cb236a051ceaf6b9b227159dde7aaba21ae997baafac6df4332345", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key12.dat": "7685ad03e3b5eeac404e8d6f0fc9e8524262b2c4a42ef0301d9e85c64d3db63b", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key13.dat": "b06f6fc0e2769a8f6e9491a0b97148f8c8d211ba8fe8ed122e1dbffca0eb7399", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key14.dat": "5d21973e2c1d833e93bfe51c42064b26743f6012e35c73ffcd2b5046cca3eeff", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key15.dat": "62c3324ccc143cd4e9ee533b0117ee06fc893d58c5d2ed0353da4dde33f5eab1", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key16.dat": "8c2c0fc7568cf6b674e8630449eb989c7ebdf34f34d0b2041a59748bd20c9638", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key17.dat": "f62c9c886ca4ee387a780a8c1e24116d73dedfda228de3614978c22f994ef7ec", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key18.dat": "fa7fda7b3b452908623383a6285aa15b265a41d48de58524fe0f0879b8aad259", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key19.dat": "00674fb37b56b5973cf0a7ee38b8258652949e4c02782ff6651d2aa63b9c62fc", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key2.dat": "45e860c89d1362de6007e46a6aa84a0d2fcd9682924de6e5f111cd536e18bfd1", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key20.dat": "1715a74e0bd00bf89406806682d865a53782d4cd9c4aee5050e547e8a84ffad6", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key3.dat": "00b464b6ee7ecd7e4406150eecf6c512e22611c6505a5045834cd5c3b7d9068d", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key4.dat": "a5c5719d09f931efc44b629fe92fd3faea0cc8c5a6ec28d0a9c9b9cdb0eb5ad9", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key5.dat": "3d5e43374441150af704b9dae9a6d62ac66406306b37709f12119fe44153e353", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key6.dat": "586c47ee8542f8bf59d7b4bf71fb1715a1bf3e7452d91068edcaf8b566e81e52", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key7.dat": "4525656f4d9fcd85bff07b876af75eb84f4f711a55a63c258c491d22dfeedff5", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key8.dat": "8e88d27fbc09fc3b133cbafd3b8e2f1b2269950cfcae12c23c1e152013f63f47", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/key9.dat": "c741aefb551b686a9ade67d3a16a5230d74134af6c3d0faf08557e19698d5825", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/value1.dat": "81c1798a0dda463cacae6ac5285ed9ad0cd0730dde167c18ee3500f1e1d8d8f2", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/value10.dat": "9c4fded85bb7467304df917d510d987a824785058a9f8bb9c9095571b49fbf0d", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/value11.dat": "3be277f87112c7bbc7242882b4ca92ff6e9d38d7be1b51b7899545a5c13d1a19", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/value12.dat": "789c47185b5aaa93293239b356a6a4928d3d29daaf9a4d18f3c94f392f861613", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/value13.dat": "b7366771afebe2ddc466eb9dc8dd840b1e7a8a8214354e2930d4fa4346f27dc6", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/value14.dat": "9c638ef5d554738087505b158482e5b0caba59a36df65b9d0f3e5eee78f91588", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/value15.dat": "846ea5776c9e11f44762cc032d690435464a312e82813530b01cb6001bd0a2f6", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/value16.dat": "b86c42aaf4a818b4eaccd2663b6fb01f3ef3b1dc8482f20636cbde1a95ca52da", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/value17.dat": "2f809c59a318d7d8d8605985092517902b6c627a40936af856785ed596c1133e", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/value18.dat": "d64613d9bc90f503b964984939de0dc881c3a0feda2684bade0c572555c2099d", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/value19.dat": "ecfb7ba56450c671fc941d56556a42d508569e341c5dd2581b18a3d7d27ab55c", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/value2.dat": "d66703e7ac7ab6c2cd3b3c8109e656e1f78ace3e2bb49123ce3a2b3da4ca04d0", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/value20.dat": "896757081c224c72586dfa5babb4300c93639a27ba9a70eb34b9dfd5c23735f2", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/value3.dat": "19c47c2f8b8483946db81572368b3f3b2aa61c5a03953351e327f3f83cb31275", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/value4.dat": "1be64f23183fca36920ae3f002d541febaa1b26b5ca3018ee53ffdd05546d56d", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/value5.dat": "7b8a109e705537784a0a8075cc4fe3d1de8288a6b6b0240627685c81ed9695a9", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/value6.dat": "5b4028a017b228357446f799cd29b28dd5a24d63340f8b48ce858c75c64029f1", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/value7.dat": "b9f6e109ba5f1b5bca758613cb5cccfecf358915ce6415525c333b19515d6428", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/value8.dat": "3d1622d4717680b31a6e037c88e7f046fecfe2339e521272e1d1eae0a7094606", + "com.sydxky.rtos.inputmethod/third_party/crabKeyboard/pinyinDictV5_20260416/value9.dat": "1c0caab370f1f14048ab6dd2da0011bedcddfed0ed0378b5c98171b6b22caf35", + "com.sydxky.rtos.inputmethod/third_party/jieba/LICENSE": "18ba0984839f85853b29fadaf992f7dba8fd0ca0fbeae34de2b8735222dc7a37", + "com.sydxky.rtos.inputmethod/third_party/jieba/dict.txt": "7197c3211ddd98962b036cdf40324d1ea2bfaa12bd028e68faa70111a88e12a8", + "com.sydxky.rtos.inputmethod/third_party/remix-icon/LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4", + "com.sydxky.rtos.inputmethod/third_party/remix-icon/README.md": "6a7bee86ad1942d01be10b556dd3036e43a59d708fc94fd2f5f3e3fcfcf84b0b", + "com.sydxky.rtos.inputmethod/third_party/remix-icon/arrow-down-s-line.iconfont.svg": "1efa1c713b710c0e9ef5eb47bec762a4b5ce21ffb454d44a3bbb118a8bec3e83", + "com.sydxky.rtos.inputmethod/third_party/remix-icon/arrow-down-s-line.upstream.svg": "4b0bf8a45d554ba97d8115ef2713010f196ad49d57eeecb8a42d4839a5ba8f9e", + "com.sydxky.rtos.inputmethod/third_party/remix-icon/arrow-left-s-line.iconfont.svg": "ccf02486d37383bbdc3319d9b348c5daef43693526d41aff6ad409d04e0664ef", + "com.sydxky.rtos.inputmethod/third_party/remix-icon/arrow-left-s-line.upstream.svg": "7d0407d47b59e7d69446ef81b79e95c5d5a4085e7fa7ba30770556b67197407e", + "com.sydxky.rtos.inputmethod/third_party/remix-icon/arrow-right-s-line.iconfont.svg": "b6678427ec45e7b00257b6c2d8eeb533e6b92c6d09864ce7844178e38fade8af", + "com.sydxky.rtos.inputmethod/third_party/remix-icon/arrow-right-s-line.upstream.svg": "2c579a4dbaf25e19a128d71e04f4cf43b19ad2487a0076ff7e7e5a94c9b3e013", + "com.sydxky.rtos.inputmethod/third_party/remix-icon/check-line.iconfont.svg": "1b194f3b6ac39e6199fb003ac3140cc2ed2d1213201c0bdbf69abffaba67f6ce", + "com.sydxky.rtos.inputmethod/third_party/remix-icon/check-line.upstream.svg": "b83a64bf881169d5edeaa6134a7ba37192f0ddc80c2e2a890578ae0f2f8f9169", + "com.sydxky.rtos.inputmethod/third_party/remix-icon/close-line.iconfont.svg": "748ea3e8a4fe22671ff3347e998d51679eaf3ff3950f6348f286f6dd36f19a92", + "com.sydxky.rtos.inputmethod/third_party/remix-icon/close-line.upstream.svg": "db3b4dcebb2d962a0231a191538bf6939620798b18f0ba52d4b91c5a4649ce54", + "com.sydxky.rtos.inputmethod/third_party/remix-icon/delete-back-2-line.iconfont.svg": "a6279d35f9fd1578f813bcfa2077fac98ec8e3a4d7bc8f90a5b3f3f2744c3a59", + "com.sydxky.rtos.inputmethod/third_party/remix-icon/delete-back-2-line.upstream.svg": "d6fc7f41c0b84fe48a06685dc081780b6cd103ac9b3db96dc44e624aa0a113e8", + "com.sydxky.rtos.inputmethod/third_party/remix-icon/edit-2-line.iconfont.svg": "74d6590a22053c253c0d00d9ac5e35fb1f8c02e2243d0e3643a959eef233476f", + "com.sydxky.rtos.inputmethod/third_party/remix-icon/edit-2-line.upstream.svg": "d03a3a7b874377a18488b7b24b596b8078afbd9bced93ad791b6fb2f4866613d", + "com.sydxky.rtos.inputmethod/third_party/remix-icon/icons.json": "e80556881b6db1a87e5308679d55e3d2b6bed130a4421407451eae9b0e41ff5a", + "com.sydxky.rtos.inputmethod/third_party/remix-icon/keyboard-box-line.iconfont.svg": "813d79347c357a919317edf84252a505dbdc441c1144076b4e8389a8ce217de0", + "com.sydxky.rtos.inputmethod/third_party/remix-icon/keyboard-box-line.upstream.svg": "e39f28cbf418269405f517126a64cbf4b3332b3295cc8a3af670f77344a669c4", + "docs/UI_PREVIEW.png": "7853297cccbc52122e17605db677a1be723cacdd82dae36efb8fc7ec300ad1ab", + "tests/emulator-validate.py": "dfc550b5873f1d03254e8bc6f819139992a1648b9a10b168575cda8e35224f50" + }, + "sensitive_findings": [] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..61695c4 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# 蟹蟹输入 · RTOS适配改版 + +基于[CrabKeyboard / 蟹蟹输入](https://github.com/crabPangXie/crabKeyboard)的独立Glyphix RTOS改版,由herry适配。不是上游原版,不表示上游或设备厂商背书。 + +**MIT开源,允许商用、修改、再分发和闭源集成。** 请保留版权及许可全文;[LICENSE](./LICENSE) / [署名与第三方许可](./NOTICE.md)。Remix Icon图标另按Apache-2.0使用,厂商SDK/运行时不包含在本项目MIT授权中。 + +![实际模拟器界面](./docs/UI_PREVIEW.png) + +## 功能 + +- 浅色满屏键盘,适配圆屏466×466及方屏390×450。 +- 26键全拼/英文均为标准QWERTY,10/9/7三排;九宫拼音与14键双字母模式。 +- 14键采用`QW ER TY UI OP / AS DF GH JK L / ZX CV BN M`,参考公开布局,自研离线消歧,保留完整候选及连续输入。 +- 104,837组拼音/缩写索引、163,238候选项,四项分页,不作100项截断。 +- 英文大小写、数字、符号、Unicode光标与删除、可选本地学习、私密输入和本机草稿。 +- 原生`.ux`页面,可通过`router.push`接入其他Glyphix应用;系统级全局输入法注册需厂商支持。 + +## 下载与反馈 + +- [1.1.0发行版与两屏QuickApp完整包](https://git2.sydxky.cn/herry/crab-input-rtos/releases) +- [已有问题与建议](https://git2.sydxky.cn/herry/crab-input-rtos/issues) +- [免注册匿名提交问题与建议](https://git2.sydxky.cn/feedback/herry/crab-input-rtos) + +Git2免登录浏览、克隆和下载,当前关闭注册,代码与版本由所有者维护。 + +```powershell +git clone https://git2.sydxky.cn/herry/crab-input-rtos.git +cd crab-input-rtos +``` + +## 构建 + +准备Windows PowerShell 7、Node.js和Python 3。厂商Glyphix SDK不随仓库分发;自行取得适用授权后,将含`bin/gx.exe`与`share/`的完整SDK放在`temp/glyphix-v0.7.2-windows-x64/`。本版验证使用`gx v0.10.1 / 3ed38835`,需要`generic-watch-466x466-quickapp`和`generic-watch-390x450-quickapp`配置。 + +```powershell +& .\com.sydxky.rtos.inputmethod\build.ps1 +``` + +脚本无损生成词库分片,执行40项引擎及29项UX脚本断言,然后构建普通/QuickApp四配置。QuickApp输出位于应用的`.glyphix-work/dist/`,每屏必须保留完整`bundle.pkg`、`icon.png`、`manifest.json`,不是APK/HAP。 + +## 原生模拟器复现 + +`com.sydxky.rtos.imefixture/`是本仓库附带的最小离线验收页,只用于实际前后台切换,不包含其他内部应用。先构建并启动一次圆屏参考页,获得厂商launcher/global资源,然后退出该模拟器: + +```powershell +& .\com.sydxky.rtos.imefixture\build.ps1 +Push-Location .\com.sydxky.rtos.imefixture +& ..\temp\glyphix-v0.7.2-windows-x64\bin\gx.exe emulate -d generic-watch-466x466 +Pop-Location +python .\com.sydxky.rtos.inputmethod\tests\native.py --work work/native --output work/evidence +python .\com.sydxky.rtos.inputmethod\tests\visual.py --work work/visual --output work/evidence/visual +``` + +视觉脚本需要Pillow。运行时保持8192 KiB内存,不接管其他占用Inspector 14201的进程。1.1.0原产品四配置84项原生操作、84项操作后布局、44个视觉状态均通过,见[验收记录](./com.sydxky.rtos.inputmethod/VALIDATION_1.1.0.md)。公开仓库用最小参考页替换内部参考应用,发布前另行验证公开树构建及原生测试。 + +真机安装、目标固件性能/续航和商店审核尚未验收。详细接入、词库与14键说明见[应用文档](./com.sydxky.rtos.inputmethod/README.md)和[14键算法](./com.sydxky.rtos.inputmethod/KEYBOARD_14.md)。 diff --git a/build-rtos-game.ps1 b/build-rtos-game.ps1 new file mode 100644 index 0000000..5c614ac --- /dev/null +++ b/build-rtos-game.ps1 @@ -0,0 +1,81 @@ +param( + [Parameter(Mandatory = $true)][string]$ProjectRoot, + [switch]$QuickAppOnly, + [switch]$Clean +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +$ProjectRoot = (Resolve-Path -LiteralPath $ProjectRoot).Path +$toolBin = Join-Path $PSScriptRoot 'temp\glyphix-v0.7.2-windows-x64\bin' +$gx = Join-Path $toolBin 'gx.exe' +if (-not (Test-Path -LiteralPath $gx)) { throw "Glyphix toolchain not found: $gx" } +$manifestPath = Join-Path $ProjectRoot 'src\manifest.json' +$manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json +if ($manifest.package -cnotmatch '^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$') { + throw 'Invalid Glyphix package name' +} +$quickAppTargets = @('generic-watch-466x466-quickapp', 'generic-watch-390x450-quickapp') + +$targets = if ($QuickAppOnly) { + $quickAppTargets +} else { + @('generic-watch-466x466', 'generic-watch-390x450', 'generic-watch-466x466-quickapp', 'generic-watch-390x450-quickapp') +} + +$originalPath = $env:Path +Push-Location $ProjectRoot +try { + $env:Path = "$toolBin;$originalPath" + $availableDevices = @(& $gx list device) + if ($LASTEXITCODE -ne 0) { throw 'Unable to read Glyphix device profiles' } + foreach ($device in $targets) { + if ($availableDevices -cnotcontains $device) { throw "Missing Glyphix device profile: $device" } + } + if ($Clean) { + & $gx clean + if ($LASTEXITCODE -ne 0) { throw 'Glyphix clean failed' } + } + foreach ($device in $targets) { + $arguments = @('build', '-d', $device) + if (-not $device.Contains('quickapp')) { $arguments += '-e' } + $output = @(& $gx @arguments 2>&1 | ForEach-Object { $_.ToString() }) + $output | ForEach-Object { Write-Host $_ } + if ($LASTEXITCODE -ne 0 -or ($output -join "`n") -match '(?im)^\s*(?:error|fatal):\s') { + throw "Glyphix build failed for $device" + } + } + + foreach ($device in $quickAppTargets) { + $deliveryRoot = Join-Path $ProjectRoot ".glyphix-work\dist\$device\$($manifest.package)" + foreach ($fileName in @('bundle.pkg', 'manifest.json', 'icon.png')) { + $artifact = Join-Path $deliveryRoot $fileName + if (-not (Test-Path -LiteralPath $artifact -PathType Leaf) -or (Get-Item -LiteralPath $artifact).Length -eq 0) { + throw "Missing or empty QuickApp artifact: $artifact" + } + } + $packedManifest = Get-Content -LiteralPath (Join-Path $deliveryRoot 'manifest.json') -Raw | ConvertFrom-Json + if ($packedManifest.package -cne $manifest.package -or + $packedManifest.versionName -cne $manifest.versionName -or + $packedManifest.versionCode -ne $manifest.versionCode) { + throw "QuickApp package/version mismatch: $device" + } + $icon = [IO.File]::ReadAllBytes((Join-Path $deliveryRoot 'icon.png')) + if ($icon.Length -lt 8 -or [Convert]::ToHexString($icon[0..7]) -cne '89504E470D0A1A0A') { + throw "Invalid QuickApp PNG icon: $device" + } + foreach ($fileName in @('bundle.pkg', 'manifest.json', 'icon.png')) { + $artifact = Join-Path $deliveryRoot $fileName + [pscustomobject]@{ + Package = $manifest.package + Device = $device + File = $fileName + Bytes = (Get-Item -LiteralPath $artifact).Length + SHA256 = (Get-FileHash -LiteralPath $artifact -Algorithm SHA256).Hash + } + } + } +} finally { + Pop-Location + $env:Path = $originalPath +} diff --git a/com.sydxky.rtos.imefixture/build.ps1 b/com.sydxky.rtos.imefixture/build.ps1 new file mode 100644 index 0000000..e0d49c9 --- /dev/null +++ b/com.sydxky.rtos.imefixture/build.ps1 @@ -0,0 +1,2 @@ +param([switch]$QuickAppOnly) +& (Join-Path $PSScriptRoot '..\build-rtos-game.ps1') -ProjectRoot $PSScriptRoot -QuickAppOnly:$QuickAppOnly diff --git a/com.sydxky.rtos.imefixture/glyphix.config.js b/com.sydxky.rtos.imefixture/glyphix.config.js new file mode 100644 index 0000000..cb088cc --- /dev/null +++ b/com.sydxky.rtos.imefixture/glyphix.config.js @@ -0,0 +1 @@ +module.exports = {minify:false} diff --git a/com.sydxky.rtos.imefixture/src/app.js b/com.sydxky.rtos.imefixture/src/app.js new file mode 100644 index 0000000..b1c6ea4 --- /dev/null +++ b/com.sydxky.rtos.imefixture/src/app.js @@ -0,0 +1 @@ +export default {} diff --git a/com.sydxky.rtos.imefixture/src/assets/icon.png b/com.sydxky.rtos.imefixture/src/assets/icon.png new file mode 100644 index 0000000..94a7ba0 Binary files /dev/null and b/com.sydxky.rtos.imefixture/src/assets/icon.png differ diff --git a/com.sydxky.rtos.imefixture/src/main/index.ux b/com.sydxky.rtos.imefixture/src/main/index.ux new file mode 100644 index 0000000..40543e1 --- /dev/null +++ b/com.sydxky.rtos.imefixture/src/main/index.ux @@ -0,0 +1,6 @@ + + + diff --git a/com.sydxky.rtos.imefixture/src/manifest.json b/com.sydxky.rtos.imefixture/src/manifest.json new file mode 100644 index 0000000..33b83e0 --- /dev/null +++ b/com.sydxky.rtos.imefixture/src/manifest.json @@ -0,0 +1,19 @@ +{ + "package": "com.sydxky.rtos.imefixture", + "name": "输入验收参考页", + "icon": "/assets/icon.png", + "versionName": "1.0.0", + "versionCode": 10000, + "features": [], + "router": { + "entry": "main", + "pages": { + "main": { + "component": "index" + } + } + }, + "config": { + "designWidth": 410 + } +} diff --git a/com.sydxky.rtos.inputmethod/.gitignore b/com.sydxky.rtos.inputmethod/.gitignore new file mode 100644 index 0000000..c9ceeeb --- /dev/null +++ b/com.sydxky.rtos.inputmethod/.gitignore @@ -0,0 +1,5 @@ +.glyphix-work/ +src/assets/ime-dict/ +tests/__pycache__/ +!third_party/ +!third_party/** diff --git a/com.sydxky.rtos.inputmethod/DESIGN.md b/com.sydxky.rtos.inputmethod/DESIGN.md new file mode 100644 index 0000000..65247be --- /dev/null +++ b/com.sydxky.rtos.inputmethod/DESIGN.md @@ -0,0 +1,22 @@ +# 浅色输入界面 + +用户选择:浅色简洁,接近手机输入法。自 1.0.1 起使用以下界面规范。 + +- 浅灰键盘底 `#ECEEF2`、白色主按键、深色主文字 `#20232B`。 +- 蓝色 `#2867D9` 用于完成、取消、拼音编码和候选翻页;候选文字保留深色,不使用多种彩色按键。 +- 候选栏使用连续浅色区域,三个候选与展开箭头同排。全候选页每页四个大选项,字形和拼音分别对齐,不截断剩余候选。 +- 九宫字母居中,数字作为较小的辅助标记。全拼和英文使用 QWERTY 10/9/7 三排;14键使用相邻字母配对的5/5/4三排。英语及数字模式隐藏无用的候选栏。 +- 模式切换采用无底色文字操作,完成保留蓝色主按钮,空格和删除分别使用白色及灰色。 +- 首页只突出一个“开始输入”按钮,草稿、数字、私密输入放在同一行;操作说明通过独立入口展开。 +- 圆屏与方屏保留各自的安全边距,不将矩形按键硬贴圆形边缘。界面只使用厂商支持的原生布局、颜色和圆角,不依赖阴影、CSS 渐变或 DOM。图标作为原生按钮内部的本地图片,不用图片替代交互布局。 + +1.1.0 增加以下约束: + +- 最外层无 padding 的背景容器铺满视口,内层单独处理安全边距,避免黑色留边。 +- 九宫、26键和14键保留 49px 实际行高,键盘容器禁止收缩;26键采用22px字号、14键采用24px字号。QWERTY第二/三排收为94%/84%,其他模式末排收为91%,使用显式行布局。 +- 圆屏完整候选每项高37px、上下间距各2px,四项总高度不得超过列表容器;页码不允许收缩。不能仅凭按钮落在圆屏内就认定没有内部裁切。 +- 圆屏顶部、预览、候选、模式和底部操作按垂直预算分配空间;底部操作栏宽 200 设计像素,编辑行宽 300,按钮四角离圆边至少 3px。 +- 取消、退格、完成、编辑、返回键盘、翻页使用 iconfont 上经固定版本上游核对的 Remix Icon 线性图标。运行时使用 72px 透明 PNG,以 20/24px 显示;保留原始 SVG 与许可,不在运行时联网加载。 +- 使用实际 Inspector 树检查按钮和文字是否超出任一父容器;另检查屏幕边界、边缘黑色缺口、四候选与翻页/底栏的整体显示。几何与截图同时通过才可用于最终预览。 + +浏览器1.1.0同步使用浅色主界面和同一个键盘维护源。14键使用独立编码索引,候选仍来自同一完整词库;账号、私密输入和草稿持久化规则保持有效。 diff --git a/com.sydxky.rtos.inputmethod/KEYBOARD_14.md b/com.sydxky.rtos.inputmethod/KEYBOARD_14.md new file mode 100644 index 0000000..13c062a --- /dev/null +++ b/com.sydxky.rtos.inputmethod/KEYBOARD_14.md @@ -0,0 +1,40 @@ +# 14键拼音 + +1.1.0新增离线14键模式。布局参考华为手表输入法的公开演示;当前没有公开可核对的华为内部词库、消歧模型或实现源码。本项目自行实现编码、词库索引、候选排序和前缀消费,不声称复现其内部算法。 + +## 公开参考与布局 + +- [公开演示一](https://www.bilibili.com/video/BV1YFCEB7EkW/):已核对约34~35秒出现的14键页面。 +- [公开演示二](https://www.bilibili.com/video/BV18t1nB9EwD/):作为交互参考。 + +```text +QW ER TY UI OP +AS DF GH JK L +ZX CV BN M +``` + +14个原生按钮覆盖全部26个字母,没有重排QWERTY字母顺序。每次点击表示该组中的一个字母,`L`、`M`为单字母键;`V`可用于拼音中的`ü`。模式栏“14键”直接进入,选中时显示蓝色。26键全拼和英文使用独立的标准10/9/7三排。 + +## 编码与候选 + +`ime-engine.js`中`KEY14_GROUPS`定义分组,`0123456789abcd`为内部token;每次点击只追加一个token,一段最多32位。原始词库每个拼音/缩写映射为相同分组编码,按输入前缀筛选所有真实候选。三位前缀分片减少单次解析和资源文件数量;一位/两位编码另建短索引。 + +例如`nihao`和`buhao`都映射为`c3754`,按键显示`BN UI GH AS OP`,因此“你好”和“不好”都应出现。候选先按本地已选偏好(默认关闭),再按匹配消费长度和jieba词频排序,不硬编码“你好”第一。保留原始候选,按四项分页访问全部结果。 + +连续输入`nihaoshijie`时,选择“你好”只消费对应的五次点击,剩余`shijie`编码继续查词。存在未确认编码时阻止模式切换与完成;内部token不能作为正文提交。退格先删除组合编码,再删除正文。 + +组合栏最多展示最近六个字母组,更长时加省略标记;原始编码与候选不因显示省略而丢失。只将前三个推荐候选及展开页的四个候选放入响应式状态。 + +## 词库、缓存和学习 + +原始41个CrabKeyboard V5文件不变,104,837组索引、163,238个候选项。生成器为14键建立2585个分片,加上4194个九宫/全拼分片,共6779个;最大116,686字节。生成后分别回读两套索引,与全部原始条目逐项比较,生成文件不提交Git。 + +解码缓存最多四项、JSON文本合计不超过90,000字符,超过上限的分片不驻留。模拟器沿用8192 KiB内存配置;没有通过增加内存通过验收。 + +14键学习使用`k14:`独立键空间,与全拼/九宫隔离;合计仍最多100组、每组三个真实已选候选。私密输入不学习、不持久化,隐藏时清除编辑状态。 + +## 验收范围 + +逻辑回归验证分组覆盖、编码、歧义、词库完整性、连续前缀消费和学习隔离。四配置原生回归实际点击14个按钮,检查“你好/不好”共存、未确认组合保护、连续输入和候选前后翻页;视觉回归覆盖14键与14键完整候选页。具体结果见[1.1.0验收](./VALIDATION_1.1.0.md)。 + +公开演示仅用于布局参考;未测得华为算法内部参数,也没有真机延迟、帧率或功耗结论。 diff --git a/com.sydxky.rtos.inputmethod/LICENSE b/com.sydxky.rtos.inputmethod/LICENSE new file mode 100644 index 0000000..5a0b817 --- /dev/null +++ b/com.sydxky.rtos.inputmethod/LICENSE @@ -0,0 +1,23 @@ +MIT License + +Copyright (c) 2026 herry +Copyright (c) 2026 kqakqakqa +Copyright (c) 2026 crabKun + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/com.sydxky.rtos.inputmethod/NOTICE.md b/com.sydxky.rtos.inputmethod/NOTICE.md new file mode 100644 index 0000000..d269ef4 --- /dev/null +++ b/com.sydxky.rtos.inputmethod/NOTICE.md @@ -0,0 +1,17 @@ +# 蟹蟹输入 RTOS适配改版 + +本项目为基于[CrabKeyboard / 蟹蟹输入](https://github.com/crabPangXie/crabKeyboard)的独立RTOS适配改版,由herry进行Glyphix页面、离线词库索引、九宫/14键消歧、标准QWERTY、候选分页及生命周期适配。不是上游原版,也不表示上游作者、华为、OPPO或Glyphix对本改版作出背书。 + +上游固定提交:`93d36b5002dee80a3d253b1ec7ec9e29800a104c`。保留原作者kqakqakqa、crabKun的版权声明、MIT许可证及原始词库/读取器。RTOS自研适配部分以根目录[MIT许可证](./LICENSE)发布,**允许商用、修改、再分发和闭源集成**,须保留适用版权及许可全文。 + +第三方内容继续按各自许可证使用: + +| 内容 | 作者/项目 | 许可与位置 | +| --- | --- | --- | +| CrabKeyboard V5词库和原始读取器 | kqakqakqa、crabKun | MIT,`third_party/crabKeyboard/LICENSE` | +| 本地候选排序词频 | jieba 0.42.1 / Sun Junyi | MIT,`third_party/jieba/LICENSE` | +| 操作图标 | Remix Icon v4.6.0,对应iconfont公开矢量 | Apache-2.0,`third_party/remix-icon/LICENSE`;矢量改色并转换PNG,来源/哈希见同目录`icons.json` | + +14键参考公开演示中的相邻字母分组,自行实现离线消歧;不包含华为内部模型或固件代码。Glyphix/厂商SDK与运行时不包含在本项目MIT授权中,也不随源码分发,使用者需自行取得适用的厂商授权。 + +公开仓库:。匿名问题与建议:。 diff --git a/com.sydxky.rtos.inputmethod/PRIVACY.md b/com.sydxky.rtos.inputmethod/PRIVACY.md new file mode 100644 index 0000000..146c238 --- /dev/null +++ b/com.sydxky.rtos.inputmethod/PRIVACY.md @@ -0,0 +1,11 @@ +# 蟹蟹输入数据说明 + +本独立应用全程离线,不请求网络、账号、位置、麦克风或通讯录,不上传输入内容。 + +正在编辑的文本仅在本次页面的内存中。主动“保存草稿”后,普通文本保存于本应用本地存储,供下次启动恢复;可清空全文后重新保存以删除旧草稿,卸载应用也会清除该存储。私密输入不提供保存、不参与学习,切换其他应用或销毁页面时清空编辑状态。 + +候选学习默认关闭。开启后仅在本应用保存最多 100 组编码、每组三个主动选择的真实候选;不会保存全文。在编辑页可关闭学习或“清除学习”。关闭学习不会自动删除旧记录,可通过清除按钮删除。 + +嵌入其他 Glyphix 应用后,键盘数据保存在该宿主自己的隔离空间。完成输入会将文本回填给宿主;宿主后续的网络行为适用其自身说明。腕上浏览的兼容模式会在明确告知后把网页输入交给服务器,详见其独立隐私政策。 + +键盘基于 MIT CrabKeyboard 与 jieba 离线词频,许可证随源码与运行包提供。应用未提供系统级输入法接管或跨应用剪贴板读取。 diff --git a/com.sydxky.rtos.inputmethod/README.md b/com.sydxky.rtos.inputmethod/README.md new file mode 100644 index 0000000..4b8d701 --- /dev/null +++ b/com.sydxky.rtos.inputmethod/README.md @@ -0,0 +1,67 @@ +# 蟹蟹输入 / Glyphix RTOS + +**蟹蟹输入 RTOS适配改版**:基于CrabKeyboard的独立改版,RTOS适配代码采用[MIT协议](./LICENSE),允许商用、修改及再分发,须保留版权与许可。上游与第三方署名见[NOTICE.md](./NOTICE.md)。公开源码:;免注册[匿名反馈](https://git2.sydxky.cn/feedback/herry/crab-input-rtos)。 + +独立应用 `com.sydxky.rtos.inputmethod`,版本 `1.1.0 / 10100`。基于 [CrabKeyboard](https://github.com/crabPangXie/crabKeyboard) 的 MIT 全量词库和拼音前缀消费方式,重写为 Glyphix `.ux` 页面;不依赖 HarmonyOS HML、Android 输入法服务、DOM 或网络。 + +1.0.1 按用户选择重做为浅色手机输入法风格:浅灰底、白色大按键、深色候选和蓝色主操作。候选栏与模式栏减少色块,英语和数字模式隐藏无用候选区;首页只突出一个输入主按钮。设计规范见 [DESIGN.md](./DESIGN.md)。 + +1.1.0 将全拼和英文改为标准 **QWERTY 10/9/7 三排**,新增公开华为风格的 **14键双字母拼音**,布局及独立离线消歧算法见 [14键说明](./KEYBOARD_14.md)。修复键盘容器被压缩导致末排裁切、四候选列表间距超出父容器、背景留黑的问题,圆屏下方逐行收窄。取消、退格、完成、编辑、键盘返回和翻页使用同一套 iconfont / Remix Icon 线性图标,保留“空格”和输入模式文字。SVG 原稿、来源和 Apache-2.0 许可见 [图标说明](./third_party/remix-icon/README.md);运行包采用本地透明 PNG,不增加网络请求。 + +上游固定提交 `93d36b5002dee80a3d253b1ec7ec9e29800a104c`(v2.0.0-patch01)。41 个原始词库文件均按 Git blob SHA-1 验证后保存于 `third_party/crabKeyboard/`,许可证和原始读取器一并保留。全量数据包含 **104,837 组拼音/缩写索引、163,238 个候选项**;这是索引项计数,不表示不同词语数。按原词库内容逐字/逐词组合输入长句。 + +## 使用 + +- 点击首页文字区或“开始输入”即打开满屏键盘。完成回填;取消保留原文。草稿需主动保存,仅保存在本应用。 +- 默认九宫大按键。“26键”切到标准 QWERTY 全拼,`v` 表示 `ü`;“九宫”切回九宫。“14键”直接进入双字母模式。连续点击对应字母组,选择候选后只消费匹配部分,剩余编码继续查词。存在未选词的九宫或14键编码时禁止切换模式/完成,避免丢字或把编码当文字提交。 +- 第一行显示三个候选。点击右侧下箭头按每页四项查看全部匹配候选,左右箭头翻页,不使用原项目扩展列表的 100 项截断。拼音候选保留每组原始顺序;九宫和14键歧义通过本地 jieba 0.42.1 词频排序,学习偏好可优先排序。 +- 支持英文大小写、数字、完整可打印 ASCII 标点与常用中文标点。符号分两组大按键,通过“下一组符号”切换。网址额外提供 `https://`、`.com`。 +- 点击文本预览或铅笔图标移动光标、换行、上屏全拼、清空全文。Unicode 字符按码点计数和删除,避免拆坏 emoji。清空全文需第二次点击确认;长按退格图标连续删除,松开/隐藏/销毁停止。勾号完成,叉号取消,键盘图标返回输入。 +- 学习默认关闭,可开启或清除,仅保存最多 100 组、每组三个已选择的真实候选,不记录整段正文。私密输入始终遮盖、不学习、不保存;切到其他应用时清空私密编辑状态。 + +## 自动弹出的实际范围 + +Glyphix 官方 [text-field 文档](https://docs.xfaith-tech.com/glyphix/docs/components/text-field.html)明确说明普通输入框不会弹出输入法。当前厂商工具没有提供系统级输入法注册/全局输入焦点注入接口。本项目交付独立文本应用和可接入页面,**不能接管未接入的第三方应用或固件界面**。 + +腕上浏览 `1.1.0` 同步使用此键盘:点击地址、配对码及兼容网页输入项后直接打开此页。输入法本身全离线;浏览器原有统一账号验证和兼容模式数据告知继续有效。其他应用可按下述方式接入。 + +```powershell +# 先构建本项目生成资源,再复制页面/引擎/资源并注册路由。 +& '.\build.ps1' +node .\scripts\install.mjs '<目标 Glyphix 工程根目录>' +``` + +在业务输入区的 `on:click` 中调用;页面上保留单一 `opening` 标志防止重复弹出: + +```javascript +const result = await router.push({uri: 'keyboard', params: { + initial: this.value, title: '输入内容', inputType: 'text', + maxLength: 2048, allowNewline: true, secret: false +}}) +if (result && result.accepted) this.value = result.text +``` + +`inputType` 支持 `text`、`url`、`number`、`pin`、`password`。`pin` 仅接受数字;`number` 允许小数点和负号;`maxLength` 上限 4096 个 Unicode 码点。业务仍需校验数值、网址或配对码的实际格式。不要在异步生命周期中等待页面结果。在宿主 `app.js` 的 `onHide` 中调用 `clearPrivateInputs(router)`(`common/ime-lifecycle.js`),覆盖厂商页面生命周期差异。宿主销毁后不得继续处理异步结果。 + +## 构建和复现 + +使用非 MSIX PowerShell 7,从本目录执行 `build.ps1`。脚本先无损转换词库、运行真实引擎和 UX 脚本测试,再用 RTOS 父目录的 OPPO 附件 `gx.exe` 生成圆/方普通镜像及圆/方 QuickApp 完整目录。SDK 不随源码分发。新机器另需从厂商附件准备工具,先构建/启动 `com.sydxky.rtos.snakeov` 一次取得 launcher/global 资源;通用 Inspector 支持位于 `../tests/emulator-validate.py`。 + +```powershell +python .\tests\native.py --work '<独立临时目录>' --output '<原生证据目录>' +python .\tests\visual.py --work '<另一个临时目录>' --output '<原生证据目录>/visual' +``` + +原生验收覆盖四配置,每配置 21 项,共 **84/84**;真实引擎及 UX 脚本分别通过 **40 + 29** 项断言。实际结果与运行包 SHA-256 见生成的 `results.json`。测试脚本使用既有离线参考应用执行真实前后台切换;不会接管被其他进程占用的 Inspector 14201。 + +1.1.0 的原生检查同时逐层核对可见 Button、图标、Label 与父容器边界;圆屏按钮四角限制在半径 230px 内。独立视觉回归覆盖四配置各11个页面状态,共 **44/44**,并统计靠近屏幕边缘的黑色缺口,避免只检查屏幕边界而漏掉内部裁切。截图保留实际原生画面,未绘制替代界面。详见 [本版验收记录](./VALIDATION_1.1.0.md)。 + +1.0.0 历史版本的 **72/72** 原生用例及 **26 + 22** 项逻辑断言见 [此前验收记录](./VALIDATION_2026-11-13.md)。1.0.1 浅色版重新完成四配置 **72/72** 原生验证,详见 [本版验收记录](./VALIDATION_1.0.1.md)。 + +## 内存与词库 + +`convert-dictionary.py` 将原始 V5 trie 无损转换为 **6779** 个编码分片(九宫/全拼4194个、14键2585个),最大分片 **116,686** 字节。九宫/全拼使用四位前缀,14键使用三位前缀;短编码单独索引。解码缓存限制为四项且 JSON 文本合计不超过 90,000 字符,大分片不缓存。仅当前页的四个候选进入展开列表,所有候选对象不放进响应式状态。转换器分别回读两种索引,验证与原始索引完全相同;不改写原始字典。 + +初期两位编码分片曾触发原生模拟器内存回收,已改为四位编码分片。验收保持既有 `--memory-profile 8192`,没有通过增大模拟器内存规避问题。模拟器耗时不能替代真机帧率、延迟或功耗结论。 + +MIT 许可副本也随运行包保留。jieba 词频只用于歧义候选排序,不联网、不分词上传。官方公开 API 不足以实现全局输入法安装;本轮仍按用户确认的模拟器与提交包范围交付,真机安装与平台审核单独处理。 diff --git a/com.sydxky.rtos.inputmethod/VALIDATION_1.1.0.md b/com.sydxky.rtos.inputmethod/VALIDATION_1.1.0.md new file mode 100644 index 0000000..69b93d8 --- /dev/null +++ b/com.sydxky.rtos.inputmethod/VALIDATION_1.1.0.md @@ -0,0 +1,56 @@ +# 蟹蟹输入1.1.0验收 + +版本`1.1.0 / 10100`,包名`com.sydxky.rtos.inputmethod`。本轮沿用厂商`gx v0.10.1 / 3ed38835`,普通/QuickApp圆屏466×466、方屏390×450四配置,内存配置保持8192 KiB。视觉记录时间:`2026-11-14T09:48:39.828292+08:00`。 + +## 本版修正 + +- 全拼和英文均为标准QWERTY:`qwertyuiop`、`asdfghjkl`、`zxcvbnm`,10/9/7三排。 +- 新增14键:`QW ER TY UI OP / AS DF GH JK L / ZX CV BN M`。参考公开布局,独立离线消歧;完整词库、连续前缀消费、候选分页和模式保护见[算法说明](./KEYBOARD_14.md)。 +- 键盘容器禁止纵向压缩,背景外层铺满屏幕,圆屏按行收窄;完整候选上下间距减少,页码禁止收缩,修复末排及四候选在父容器内被截断。 +- iconfont / Remix Icon统一操作图标,SVG原稿及Apache-2.0许可保留,运行用本地透明PNG。 + +## 验证结果 + +| 验证 | 结果 | +| --- | --- | +| 真实引擎逻辑 | 40项断言通过 | +| 真实UX脚本 | 29项断言通过 | +| 全量词库转换 | 两套索引分别回读与全部104,837组原始索引、163,238候选项一致 | +| 四配置构建 | 4/4,每个QuickApp目录含bundle.pkg、icon.png、manifest.json | +| 原生操作回归 | 84/84,四配置各21项 | +| 原生操作后布局 | 84/84 | +| 独立视觉/布局回归 | 44/44,四配置各11个状态 | + +原生回归保留原有18项,新增14键词组歧义与模式保护、连续输入、完整候选分页三项。实际点击原生按钮,检查“你好/不好”共存、未确认组合禁止完成/切模式、只消费选词匹配编码和候选前后翻页。全拼与英文额外检查实际三排顺序。 + +视觉状态覆盖首页、九宫、QWERTY全拼、四候选、编辑、14键、14键完整候选、英文、数字及两组符号。检查可见Button/ImageBox/Label是否超出任一父容器,容忍1px原生舍入;圆屏按钮/图标四角限制半径230px,实测按钮最大半径227.86px,小于屏幕233px。全部44个状态的指定边缘采样带黑色缺口像素为0。此结论针对验收状态,不代替未来新增页面的检查。 + +Inspector空闲时可能将新树关联到旧back buffer。视觉脚本通过给提示文本暂加空格后恢复,触发真实原生重绘,再重复snapshot保留新帧。没有绘制替代界面。`UI_PREVIEW.png`排除模拟器外壳周边像素,所有按钮完整保留;未裁剪原图及控件树在证据ZIP中。 + +## 查询与内存 + +八个全拼样本依次为nihao、zhongguo、shijie、shurufa、beijing、tianqi、shanghai、gongzuo,原生记录如下,单位ms: + +| 配置 | 八个全拼样本耗时 | +| --- | --- | +| round | 80, 45, 42, 41, 44, 45, 46, 43 | +| rect | 78, 45, 44, 22, 44, 45, 45, 45 | +| round-quickapp | 79, 45, 44, 22, 44, 46, 64, 44 | +| rect-quickapp | 77, 44, 43, 23, 44, 45, 47, 43 | + +上述为模拟器全拼查词样本;不是专门的14键基准,也不表示真机端到端延迟、帧率或功耗。14键原生用例等待词库稳定上限5秒并验证实际结果,未另行声明毫秒级指标。 + +共6779个分片(4194九宫/全拼、2585十四键),最大116,686字节;缓存最多四项、解码JSON文本合计90,000字符,大分片不驻留。未扩大模拟器内存。 + +## 运行包与交付 + +| 配置 | 实际运行 bundle.pkg SHA-256 | +| --- | --- | +| round | `D0D0BBCBACA78A3300F9CE7538BD39CC0810BCF9872B3B42F0E4ED040EDB0A51` | +| rect | `767D7A034D943D4B2D75CBE1EA623BEAAEF8CCBF0033B7944A3763513E74B291` | +| round-quickapp | `C82E78F47B4CDCBD33B6E39D2C08005CCFFD617608336A17CF5C4F808370E31F` | +| rect-quickapp | `A1CA39663B85D36FF464BE5D62949C7ADEFD2EFA7939C8E65ABD90DF41F192E8` | + +提交目录包含两屏QuickApp完整6文件,`crab-keyboard-1.1.0-quickapp.zip`为打包副本,源码和`keyboard-emulator-evidence.zip`另行提供。`results.json`与`visual-results.json`分别记录操作和视觉结果,交付脚本核对两份证据使用同一构建包;`archive-hashes.json`及`DELIVERY_INTEGRITY.json`记录归档哈希、CRC及敏感扫描。 + +自动弹出仅覆盖本应用与已接入宿主,厂商公开API没有已验证的系统级IME注册能力。输入法全离线,无账号或联网请求;浏览器联网维持统一账号验证。按用户确认仅做模拟器和提交包验收;RTOS真机安装、目标固件行为、性能、续航及平台审核未验收。 diff --git a/com.sydxky.rtos.inputmethod/build.ps1 b/com.sydxky.rtos.inputmethod/build.ps1 new file mode 100644 index 0000000..ba5bd51 --- /dev/null +++ b/com.sydxky.rtos.inputmethod/build.ps1 @@ -0,0 +1,9 @@ +param([switch]$QuickAppOnly, [switch]$Clean) +$ErrorActionPreference = 'Stop' +& python (Join-Path $PSScriptRoot 'scripts\convert-dictionary.py') +if ($LASTEXITCODE -ne 0) { throw 'Keyboard dictionary generation failed' } +& node (Join-Path $PSScriptRoot 'tests\engine.test.mjs') +if ($LASTEXITCODE -ne 0) { throw 'Keyboard engine tests failed' } +& node (Join-Path $PSScriptRoot 'tests\client.test.mjs') +if ($LASTEXITCODE -ne 0) { throw 'Keyboard client tests failed' } +& (Join-Path $PSScriptRoot '..\build-rtos-game.ps1') -ProjectRoot $PSScriptRoot -QuickAppOnly:$QuickAppOnly -Clean:$Clean diff --git a/com.sydxky.rtos.inputmethod/glyphix.config.js b/com.sydxky.rtos.inputmethod/glyphix.config.js new file mode 100644 index 0000000..9755278 --- /dev/null +++ b/com.sydxky.rtos.inputmethod/glyphix.config.js @@ -0,0 +1 @@ +module.exports = {minify: false} diff --git a/com.sydxky.rtos.inputmethod/scripts/build-icons.py b/com.sydxky.rtos.inputmethod/scripts/build-icons.py new file mode 100644 index 0000000..6d470c2 --- /dev/null +++ b/com.sydxky.rtos.inputmethod/scripts/build-icons.py @@ -0,0 +1,19 @@ +"""Rebuild local PNGs from the retained iconfont SVG originals (pip install resvg-py).""" +import hashlib,json,pathlib,xml.etree.ElementTree as ET +import resvg_py + +APP=pathlib.Path(__file__).resolve().parents[1] +SOURCE=APP/'third_party/remix-icon' +OUT=APP/'src/assets/ime-icons' +OUT.mkdir(parents=True,exist_ok=True) +metadata=json.loads((SOURCE/'icons.json').read_text(encoding='utf-8')) +for record in metadata['icons']: + raw=(SOURCE/(record['name']+'.iconfont.svg')).read_bytes() + assert hashlib.sha256(raw).hexdigest().upper()==record['iconfont_svg_sha256'] + root=ET.fromstring(raw);root.attrib.pop('style',None) + color='#FFFFFF' if record['name']=='check-line' else '#515866' if record['name']=='delete-back-2-line' else '#2867D9' + for node in root.iter():node.attrib.pop('fill',None) + root.set('fill',color) + (OUT/(record['name']+'.png')).write_bytes(resvg_py.svg_to_bytes(svg_string=ET.tostring(root,encoding='unicode'),width=72,height=72)) +(OUT/'LICENSE.txt').write_bytes((SOURCE/'LICENSE').read_bytes()) +print('Built',len(metadata['icons']),'local 72px transparent icons; display at 20/24px') diff --git a/com.sydxky.rtos.inputmethod/scripts/convert-dictionary.py b/com.sydxky.rtos.inputmethod/scripts/convert-dictionary.py new file mode 100644 index 0000000..5ca7a93 --- /dev/null +++ b/com.sydxky.rtos.inputmethod/scripts/convert-dictionary.py @@ -0,0 +1,82 @@ +"""Losslessly convert CrabKeyboard's V5 trie into bounded, digit-indexed shards.""" +import argparse, hashlib, json, pathlib, re + +APP = pathlib.Path(__file__).resolve().parents[1] +GROUPS = ['abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz'] +T9 = {letter: str(i + 2) for i, group in enumerate(GROUPS) for letter in group} +GROUPS14 = ['qw','er','ty','ui','op','as','df','gh','jk','l','zx','cv','bn','m'] +KEY14 = {letter: '0123456789abcd'[i] for i, group in enumerate(GROUPS14) for letter in group} + +def from94(value): + number = 0 + for ch in value: + assert 33 <= ord(ch) <= 126 + number = number * 94 + ord(ch) - 33 + return number + +def convert(source, target): + files = {p.name: p.read_bytes() for p in source.glob('*.dat')} + frequency_path=APP/'third_party/jieba/dict.txt' + frequencies={} + if frequency_path.exists(): + for line in frequency_path.read_text(encoding='utf-8').splitlines(): + parts=line.split(' ') + if len(parts)>=2 and parts[1].isdigit():frequencies[parts[0]]=int(parts[1]) + entries = {} + def walk(key, depth, position, length): + data = files['key%d.dat' % depth][position:position+length].decode('ascii') + for item in data.split('\x1e'): + if not item: continue + char = item[0] + off, size = map(from94, item[1:].split('\x1f')) + if char == '_': + words = files['value%d.dat' % depth][off:off+size].decode('utf-8').split(' ') + entries[key] = [word for word in words if word] + else: + walk(key+char, depth+1, off, size) + walk('', 0, 0, len(files['key0.dat'])) + shards = {} + for key, words in sorted(entries.items()): + digits = ''.join(T9[ch] for ch in key) + bucket = digits[:4] if len(digits) >= 4 else 's'+digits + shards.setdefault(bucket, {})[key] = words + code14 = ''.join(KEY14[ch] for ch in key) + bucket14 = 'd'+code14[:3] if len(code14)>=3 else 'ds'+code14 + shards.setdefault(bucket14,{})[key]=words + target.mkdir(parents=True, exist_ok=True) + assert target.resolve().is_relative_to((APP/'src/assets').resolve()) + # Only replace files listed in the previous generated manifest. + if (target/'meta.json').exists(): + previous=json.loads((target/'meta.json').read_text(encoding='utf-8')) + for name in previous['shards']: + assert re.fullmatch(r'(?:short|s?[0-9]+|ds?[0-9a-d]+)',name) + old=target/(name+'.json') + if name not in shards and old.exists():old.unlink() + for name, content in shards.items(): + weights={word:frequencies[word] for words in content.values() for word in words if word in frequencies} + output=target/(name+'.json') + data=json.dumps({'entries':content,'frequencies':weights}, ensure_ascii=False, separators=(',', ':')).encode('utf-8') + if not output.exists() or output.read_bytes()!=data:output.write_bytes(data) + # Every original candidate remains accessible, in original per-key order. + restored = {};restored14={} + for name in shards: + (restored14 if name.startswith('d')else restored).update(json.loads((target/(name+'.json')).read_text(encoding='utf-8'))['entries']) + assert restored == entries and restored14 == entries + report = dict(upstream='https://github.com/crabPangXie/crabKeyboard', + commit='93d36b5002dee80a3d253b1ec7ec9e29800a104c', + variant='pinyinDictV5_20260416', keys=len(entries), + candidates=sum(map(len, entries.values())), shards=sorted(shards), + t9_shards=sum(not name.startswith('d')for name in shards), + key14_shards=sum(name.startswith('d')for name in shards),key14_groups=GROUPS14,key14_prefix_length=3, + largest_shard_bytes=max((target/(name+'.json')).stat().st_size for name in shards), + source_files={name:hashlib.sha256(data).hexdigest() for name,data in sorted(files.items())}) + if frequency_path.exists():report['t9_frequency_source']={'project':'jieba 0.42.1','license':'MIT','sha256':hashlib.sha256(frequency_path.read_bytes()).hexdigest()} + output=target/'meta.json';data=(json.dumps(report, ensure_ascii=False, indent=2)+'\n').encode('utf-8') + if not output.exists() or output.read_bytes()!=data:output.write_bytes(data) + print(json.dumps({**{k:v for k,v in report.items() if k not in ['source_files','shards']},'shards':len(shards)}, ensure_ascii=False)) + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--source', type=pathlib.Path, default=APP/'third_party/crabKeyboard/pinyinDictV5_20260416') + parser.add_argument('--target', type=pathlib.Path, default=APP/'src/assets/ime-dict') + args = parser.parse_args(); convert(args.source, args.target) diff --git a/com.sydxky.rtos.inputmethod/scripts/install.mjs b/com.sydxky.rtos.inputmethod/scripts/install.mjs new file mode 100644 index 0000000..f086b0c --- /dev/null +++ b/com.sydxky.rtos.inputmethod/scripts/install.mjs @@ -0,0 +1,26 @@ +// Copy the canonical keyboard into another Glyphix sandbox; no cross-app data. +import fs from 'node:fs' +import path from 'node:path' +import {fileURLToPath} from 'node:url' +const app=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..') +const target=path.resolve(process.argv[2]||'') +if(!process.argv[2]||!fs.existsSync(path.join(target,'src/manifest.json')))throw Error('Usage: node scripts/install.mjs ') +const oldMeta=path.join(target,'src/assets/ime-dict/meta.json') +if(fs.existsSync(oldMeta)){ + const current=JSON.parse(fs.readFileSync(path.join(app,'src/assets/ime-dict/meta.json'),'utf8')).shards + for(const name of JSON.parse(fs.readFileSync(oldMeta,'utf8')).shards){ + if(!/^(short|s?[0-9]+|ds?[0-9a-d]+)$/.test(name))throw Error('Invalid generated shard name') + const old=path.join(target,'src/assets/ime-dict',name+'.json') + if(!current.includes(name)&&fs.existsSync(old))fs.unlinkSync(old) + } +} +for(const relative of ['keyboard/index.ux','common/ime-engine.js','common/ime-lifecycle.js','assets/ime-dict','assets/ime-icons']){ + const destination=path.join(target,'src',relative);fs.mkdirSync(path.dirname(destination),{recursive:true});fs.cpSync(path.join(app,'src',relative),destination,{recursive:true}) +} +const license=path.join(target,'src/assets/crab-keyboard-license.txt') +fs.copyFileSync(path.join(app,'third_party/crabKeyboard/LICENSE'),license) +fs.copyFileSync(path.join(app,'third_party/jieba/LICENSE'),path.join(target,'src/assets/jieba-license.txt')) +const manifestPath=path.join(target,'src/manifest.json'),manifest=JSON.parse(fs.readFileSync(manifestPath,'utf8')) +manifest.router.pages.keyboard={component:'index'} +fs.writeFileSync(manifestPath,JSON.stringify(manifest,null,2)+'\n') +console.log('Installed canonical offline keyboard:',manifest.package) diff --git a/com.sydxky.rtos.inputmethod/src/app.js b/com.sydxky.rtos.inputmethod/src/app.js new file mode 100644 index 0000000..880ef0e --- /dev/null +++ b/com.sydxky.rtos.inputmethod/src/app.js @@ -0,0 +1,3 @@ +import router from '@system.router' +import {clearPrivateInputs} from './common/ime-lifecycle.js' +export default {onCreate(){console.info('Crab RTOS input ready')},onHide(){clearPrivateInputs(router)}} diff --git a/com.sydxky.rtos.inputmethod/src/assets/crab-keyboard-license.txt b/com.sydxky.rtos.inputmethod/src/assets/crab-keyboard-license.txt new file mode 100644 index 0000000..097f88a --- /dev/null +++ b/com.sydxky.rtos.inputmethod/src/assets/crab-keyboard-license.txt @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2026 kqakqakqa +Copyright (c) 2026 crabKun + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/com.sydxky.rtos.inputmethod/src/assets/icon.png b/com.sydxky.rtos.inputmethod/src/assets/icon.png new file mode 100644 index 0000000..94a7ba0 Binary files /dev/null and b/com.sydxky.rtos.inputmethod/src/assets/icon.png differ diff --git a/com.sydxky.rtos.inputmethod/src/assets/ime-icons/LICENSE.txt b/com.sydxky.rtos.inputmethod/src/assets/ime-icons/LICENSE.txt new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/com.sydxky.rtos.inputmethod/src/assets/ime-icons/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/com.sydxky.rtos.inputmethod/src/assets/ime-icons/arrow-down-s-line.png b/com.sydxky.rtos.inputmethod/src/assets/ime-icons/arrow-down-s-line.png new file mode 100644 index 0000000..3c92b10 Binary files /dev/null and b/com.sydxky.rtos.inputmethod/src/assets/ime-icons/arrow-down-s-line.png differ diff --git a/com.sydxky.rtos.inputmethod/src/assets/ime-icons/arrow-left-s-line.png b/com.sydxky.rtos.inputmethod/src/assets/ime-icons/arrow-left-s-line.png new file mode 100644 index 0000000..84cd706 Binary files /dev/null and b/com.sydxky.rtos.inputmethod/src/assets/ime-icons/arrow-left-s-line.png differ diff --git a/com.sydxky.rtos.inputmethod/src/assets/ime-icons/arrow-right-s-line.png b/com.sydxky.rtos.inputmethod/src/assets/ime-icons/arrow-right-s-line.png new file mode 100644 index 0000000..26669d4 Binary files /dev/null and b/com.sydxky.rtos.inputmethod/src/assets/ime-icons/arrow-right-s-line.png differ diff --git a/com.sydxky.rtos.inputmethod/src/assets/ime-icons/check-line.png b/com.sydxky.rtos.inputmethod/src/assets/ime-icons/check-line.png new file mode 100644 index 0000000..2fe7be8 Binary files /dev/null and b/com.sydxky.rtos.inputmethod/src/assets/ime-icons/check-line.png differ diff --git a/com.sydxky.rtos.inputmethod/src/assets/ime-icons/close-line.png b/com.sydxky.rtos.inputmethod/src/assets/ime-icons/close-line.png new file mode 100644 index 0000000..eb1cb50 Binary files /dev/null and b/com.sydxky.rtos.inputmethod/src/assets/ime-icons/close-line.png differ diff --git a/com.sydxky.rtos.inputmethod/src/assets/ime-icons/delete-back-2-line.png b/com.sydxky.rtos.inputmethod/src/assets/ime-icons/delete-back-2-line.png new file mode 100644 index 0000000..5182ce2 Binary files /dev/null and b/com.sydxky.rtos.inputmethod/src/assets/ime-icons/delete-back-2-line.png differ diff --git a/com.sydxky.rtos.inputmethod/src/assets/ime-icons/edit-2-line.png b/com.sydxky.rtos.inputmethod/src/assets/ime-icons/edit-2-line.png new file mode 100644 index 0000000..81471c6 Binary files /dev/null and b/com.sydxky.rtos.inputmethod/src/assets/ime-icons/edit-2-line.png differ diff --git a/com.sydxky.rtos.inputmethod/src/assets/ime-icons/keyboard-box-line.png b/com.sydxky.rtos.inputmethod/src/assets/ime-icons/keyboard-box-line.png new file mode 100644 index 0000000..a26023b Binary files /dev/null and b/com.sydxky.rtos.inputmethod/src/assets/ime-icons/keyboard-box-line.png differ diff --git a/com.sydxky.rtos.inputmethod/src/assets/jieba-license.txt b/com.sydxky.rtos.inputmethod/src/assets/jieba-license.txt new file mode 100644 index 0000000..9d7e66b --- /dev/null +++ b/com.sydxky.rtos.inputmethod/src/assets/jieba-license.txt @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Sun Junyi + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/com.sydxky.rtos.inputmethod/src/common/ime-engine.js b/com.sydxky.rtos.inputmethod/src/common/ime-engine.js new file mode 100644 index 0000000..4086d8e --- /dev/null +++ b/com.sydxky.rtos.inputmethod/src/common/ime-engine.js @@ -0,0 +1,109 @@ +/* RTOS adaptation of CrabKeyboard, MIT. Original authors kqakqakqa and crabKun. + * Full V5 dictionary, prefix consumption and local preference ordering retained. + * New: T9 decoding, paged complete candidates and Unicode-safe editing. + */ +const GROUPS = ['abc','def','ghi','jkl','mno','pqrs','tuv','wxyz'] +// Adjacent QWERTY pairs used by the public 14-key layout. One token per key. +export const KEY14_GROUPS = ['qw','er','ty','ui','op','as','df','gh','jk','l','zx','cv','bn','m'] +const KEY14_CODES='0123456789abcd' +export function code14Of(text){ + let result='' + for(const ch of String(text||'').toLowerCase()){ + const index=KEY14_GROUPS.findIndex(group=>group.indexOf(ch)>=0) + if(index<0)return '' + result+=KEY14_CODES[index] + } + return result +} +export function display14(raw){return (raw.length>6?'… ':'')+Array.from(raw.slice(-6)).map(code=>KEY14_GROUPS[KEY14_CODES.indexOf(code)]||'').join(' ').toUpperCase()} +export function digitsOf(text) { + let result='' + for (const ch of text.toLowerCase()) { + const index=GROUPS.findIndex(group=>group.indexOf(ch)>=0) + if(index<0)return '' + result+=String(index+2) + } + return result +} +export function chars(text){return Array.from(String(text||''))} +export function edit(text,cursor,insertion,maxLength) { + const value=chars(text), at=Math.max(0,Math.min(value.length,cursor)) + const room=Math.max(0,maxLength-value.length), added=chars(insertion).slice(0,room) + return {text:value.slice(0,at).concat(added,value.slice(at)).join(''),cursor:at+added.length,limited:added.length15?'…':'')+(secret?'•'.repeat(left.length):left.join(''))+'│'+(secret?'•'.repeat(right.length):right.join(''))+(at+7/^(?:[a-z2-9]{1,32}|k14:[0-9a-d]{1,32})$/.test(key)).slice(-100).forEach(key=>{ + if(!Array.isArray(input[key]))return + result[key]=input[key].filter(x=>x&&typeof x.word==='string'&&chars(x.word).length<=32&&Number.isInteger(x.consume)&&x.consume>0&&x.consume<=32).slice(0,3).map(x=>({word:x.word,consume:x.consume})) + }) + return result +} +export function learn(preferences,input,candidate) { + const result=sanitizePreferences(preferences) + const previous=Array.isArray(result[input])?result[input]:[] + delete result[input] + result[input]=[{word:candidate.word,consume:candidate.consume}].concat(previous.filter(x=>x.word!==candidate.word||x.consume!==candidate.consume)).slice(0,3) + const keys=Object.keys(result) + if(keys.length>100)delete result[keys[0]] + return result +} + +export function createDictionary(read) { + // Small shards and a bounded LRU avoid parsing the whole lexicon on an MCU. + const cache=[],pending={};let manifest=null + async function load(name) { + const existing=cache.find(x=>x.name===name) + if(existing)return existing.data + if(pending[name])return pending[name] + pending[name]=(async()=>{ + const text=await read('/assets/ime-dict/'+name+'.json') + const data=typeof text==='string'?JSON.parse(text):text + const size=typeof text==='string'?text.length:0 + cache.push({name,data,size});while(cache.length>4||cache.reduce((sum,x)=>sum+x.size,0)>90000)cache.shift() + return data + })() + try{return await pending[name]}finally{delete pending[name]} + } + return { + async lookup(raw,t9,preferences) { + const input=String(raw||'').toLowerCase() + const dual=t9==='14',ambiguous=!!t9 + if(!(dual?/^[0-9a-d]{1,32}$/:t9?/^[2-9]{1,32}$/:/^[a-z]{1,32}$/).test(input))return [] + const digits=ambiguous?input:digitsOf(input) + const merged={},frequencies={} + function merge(data){Object.assign(merged,data.entries);Object.assign(frequencies,data.frequencies)} + if(!manifest){const meta=await read('/assets/ime-dict/meta.json');manifest=typeof meta==='string'?JSON.parse(meta):meta} + const names=[] + const prefixLength=dual?3:4 + for(let length=1;length<=Math.min(prefixLength-1,digits.length);length++)names.push((dual?'ds':'s')+digits.slice(0,length)) + if(digits.length>=prefixLength)names.push((dual?'d':'')+digits.slice(0,prefixLength)) + for(const name of names)if(manifest.shards.indexOf(name)>=0)try{merge(await load(name))}catch(error){throw new Error('词库读取失败')} + const matching=Object.keys(merged).filter(key=>input.indexOf(dual?code14Of(key):t9?digitsOf(key):key)===0) + matching.sort((a,b)=>b.length-a.length||(ab?1:0)) + const result=[],seen={} + for(const key of matching)for(const word of merged[key]){ + const identity=word+'@'+key.length + if(!seen[identity]){seen[identity]=true;result.push({word,pinyin:key,consume:key.length})} + } + const preferenceKey=dual?'k14:'+input:input + const prefs=preferences&&Array.isArray(preferences[preferenceKey])?preferences[preferenceKey]:[] + const rank=item=>{const i=prefs.findIndex(x=>x.word===item.word&&x.consume===item.consume);return i<0?100:i} + // Rank only real dictionary results: a damaged preference cannot inject text. + return result.map((item,index)=>({item,index})).sort((a,b)=>rank(a.item)-rank(b.item)||(ambiguous?(b.item.consume-a.item.consume||(frequencies[b.item.word]||0)-(frequencies[a.item.word]||0)):0)||a.index-b.index).map(x=>x.item) + }, + clear(){cache.length=0;manifest=null}, + get cacheSize(){return cache.length} + } +} diff --git a/com.sydxky.rtos.inputmethod/src/common/ime-lifecycle.js b/com.sydxky.rtos.inputmethod/src/common/ime-lifecycle.js new file mode 100644 index 0000000..d9cfd7e --- /dev/null +++ b/com.sydxky.rtos.inputmethod/src/common/ime-lifecycle.js @@ -0,0 +1,6 @@ +// Some vendor runtimes deliver application hide before the nested page callback. +export function clearPrivateInputs(router) { + for(const page of router.getPages()) { + if(page.secret&&typeof page.clearPrivate==='function')page.clearPrivate() + } +} diff --git a/com.sydxky.rtos.inputmethod/src/keyboard/index.ux b/com.sydxky.rtos.inputmethod/src/keyboard/index.ux new file mode 100644 index 0000000..f8ba31f --- /dev/null +++ b/com.sydxky.rtos.inputmethod/src/keyboard/index.ux @@ -0,0 +1,145 @@ + + + diff --git a/com.sydxky.rtos.inputmethod/src/main/index.ux b/com.sydxky.rtos.inputmethod/src/main/index.ux new file mode 100644 index 0000000..7a69486 --- /dev/null +++ b/com.sydxky.rtos.inputmethod/src/main/index.ux @@ -0,0 +1,32 @@ + + + diff --git a/com.sydxky.rtos.inputmethod/src/manifest.json b/com.sydxky.rtos.inputmethod/src/manifest.json new file mode 100644 index 0000000..56e9a4f --- /dev/null +++ b/com.sydxky.rtos.inputmethod/src/manifest.json @@ -0,0 +1,10 @@ +{ + "package": "com.sydxky.rtos.inputmethod", + "name": "蟹蟹输入", + "icon": "/assets/icon.png", + "versionName": "1.1.0", + "versionCode": 10100, + "features": [], + "router": {"entry": "main", "pages": {"main": {"component": "index"}, "keyboard": {"component": "index"}}}, + "config": {"designWidth": 410} +} diff --git a/com.sydxky.rtos.inputmethod/tests/client.test.mjs b/com.sydxky.rtos.inputmethod/tests/client.test.mjs new file mode 100644 index 0000000..2fe6804 --- /dev/null +++ b/com.sydxky.rtos.inputmethod/tests/client.test.mjs @@ -0,0 +1,44 @@ +import fs from 'node:fs' +import vm from 'node:vm' +import assert from 'node:assert/strict' +import * as engine from '../src/common/ime-engine.js' +const ux=fs.readFileSync(new URL('../src/keyboard/index.ux',import.meta.url),'utf8') +const source=ux.match(/