A
Air官方
【Air 系列 · 鸿蒙 PC 适配全记录】56 / 56
■ 应用:AirXC(from KeePassXC)(AirXC)
■ 定位:KeePassXC 移植,密码管理
■ 上游:KeePassXC | 路线:Qt6 6.11.1+QPA | 版本:1.0.0
■ 开源: https://gitcode.com/Andy88/Harmony-KeePassXC
■ 市场状态:开发中,即将上架
■ 官网(免费下载): https://www.shunidc.cn/
————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/
PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:https://gitcode.com/Andy88/Harmony-KeePassXC
环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743
▍一、为什么要适配 AirXC
KeePassXC 是开源社区里使用最广的本地密码管理器之一。它把密码库以 kdbx 格式落在用户自己的磁盘上,用 Argon2 做密钥派生、AES 做加密,不依赖任何云服务。对经常在多台设备之间整理凭据的桌面用户来说,这类"数据完全自持"的工具是刚需,而且它恰好是一个 Qt6/CMake 工程,上游代码本身就有跨平台基因。
把 KeePassXC 带到鸿蒙 PC,真正要回答的问题是:一个依赖完整桌面窗口管理器、QMenuBar、QFileDialog 和进程模型的大型 Qt 应用,能否在沙箱化、以触摸为先的 HarmonyOS 上重新获得完整的交互闭环。这个问题的答案对大量同类 Qt 桌面软件都有参考价值,因此本工程没有走"重写界面"的捷径,而是选择把上游 GUI 本体改造后直接运行。
当前鸿蒙工程的应用名为 AirXC,包名 com.airxc.app,版本 1.0.0,deviceTypes 覆盖 phone、tablet、2in1,仅声明 ohos.permission.INTERNET 权限。技术路线为 Qt6 + 自研 ohos QPA:Qt 版本由上游 find_package(Qt6 6.2.4) 决定,工程取 Qt 6.11.1 预编译栈,QPA 插件 libqohos 含 IME、文件选择器与 SVG 插件。PORTING.md 记载的验证设备包括 HUAWEI MateBook Pro(2in1)、MatePad Mini 与 MatePad 11.5(平板)、Pura X(手机),SDK 为 HarmonyOS 6.1.1(24)、NDK clang 15。
▍二、先划清边界:不把编译通过当成适配完成
KeePassXC 的桌面体验建立在三类系统服务之上:窗口管理器(窗口装饰、激活、多窗)、输入法与剪贴板等桌面服务、以及 QFileDialog/QMessageBox 这类依赖窗口层级的对话框。鸿蒙应用运行在沙箱中,没有传统意义上的 WM,ArkTS 与 native 又分属两套渲染体系。如果只把上游 CMake 跑通、生成一个动态库,得到的并不是密码管理器。
因此工程在第一天就划定了边界:上游的加密内核、数据模型与 GUI 本体进入可运行产物,桌面系统集成点全部替换为鸿蒙等价链路,少数依赖 Linux 桌面环境的能力显式裁剪。
层次 上游 KeePassXC(桌面方案) 鸿蒙侧当前方案
应用入口 main() 直启 Qt 事件循环 Stage 模型 EntryAbility + NAPI 桥 dlopen 链
窗口承载 系统 WM 窗口与装饰 XComponent(TEXTURE) + libqohos QPA,2in1 形态迁移专用子窗加系统标题栏
菜单栏 QMenuBar 原生菜单 ArkTS bindMenu + Menu/MenuItem 官方组件,经 menubar JSON 导出与 triggerMenuAction 回注
文件对话框 QFileDialog / QSaveFile 直写 QPA filedialoghelper 桥系统 picker,picker_in/picker_out staging 中转
数据库原子保存 QSaveFile(O_TMPFILE 匿名 inode) 命名临时文件 + POSIX rename() 原子覆盖(P11/P11b/Q1)
加密内核 上游加密栈 Botan 2.19.5 交叉编译进 Native 产物
硬件与桌面集成 YubiKey、AutoType、浏览器集成、SSH agent、FdoSecrets PCSC 桩与 ykcore 空后端,其余按既定范围裁剪
这张表也是验收口径:每一行右侧的能力都必须在真机上可操作,才算进入"已适配"范围。
▍三、鸿蒙版的整体架构
工程采用 Air 双层结构:ArkTS 壳负责 Ability 生命周期、菜单栏、IME 宿主与数据部署;NAPI 薄桥只做 dlsym 转发与触摸注入;真正的 KeePassXC 运行在 native 宿主线程里,由 airxc_core 按拓扑序加载 Qt 栈与 libkeepassxc_gui 后进入上游主函数。
text
EntryAbility(ArkTS 壳:生命周期、强制浅色、目录权限)
└── pages/Index.ets
├── 顶部菜单栏(bindMenu/Menu/MenuItem 官方组件)
├── XComponent(TEXTURE) ← Qt 画面呈现
├── IME 宿主 TextInput(隐形节点 + 差分回注)
├── DialogHost(浮层对话框卡片)/ SubWindowManager(子窗承载)
└── NAPI libairxcbridge.so(薄桥:core API + TSFN + QOhos_Inject*)
└── libairxc_core.so(native 宿主线程)
├── env 前置(HOME/XDG_*)→ airxc-data.tar 解包门控
├── 拓扑序 dlopen:Qt 6.11.1 全家 → libqohos.so → libkeepassxc_gui.so
└── airxc_keepassxc_main() → KeePassXC GUI 本体
entry/libs/arm64-v8a/:Qt 栈 + qohos + keepassxc_gui + botan/zlib/minizip/PCSC 桩(21 库白名单通道)
rawfile:airxc-data.tar(icons/wordlists/wizard/translations .qm + 配置种子)
仓库主要目录如下:
text
AirXC/
├── AppScope/app.json5 # 包名 com.airxc.app、版本与应用资源
├── build-profile.json5 # SDK、产品与签名配置
├── PORTING.md # 移植手册:依赖矩阵、补丁清单、风险登记册与实录
├── entry/src/main/
│ ├── ets/pages/Index.ets # 壳主页面:菜单/XComponent/IME/对话框宿主
│ ├── ets/components/DialogHost.ets # Qt 对话框浮层卡片
│ ├── ets/utils/SubWindowManager.ets # 子窗承载与 PC 窗口化
│ └── cpp/
│ ├── airxc_core.cpp # 宿主线程:dlopen 拓扑序 + HTTP 调试桥
│ ├── napi_init.cpp # NAPI 薄桥
│ └── qt_platform/ohos_qt6/ # 自研 ohos QPA(qohosintegration 等)
└── native/
├── sources/ # keepassxc 工作树、Botan-2.19.5、zlib-1.3.1、qrencode-4.1.1
├── scripts/ # build_deps/build_keepassxc/deploy.sh 等构建部署脚本
├── patches/ # Qt 翻译等补丁资源
└── stubs/ # pcsclite 桩与 ykcore_ohos 空后端
▍四、核心适配过程
### 1. GUI 从可执行程序改造为共享库
上游 src/main.cpp 的 int main() 在 OHOS 构建下被补丁 P6 改为 extern "C" int airxc_keepassxc_main(int, char**),src/CMakeLists.txt 的 P2 把 GUI 目标从 exe 改为 SHARED,导出 libkeepassxc_gui.so。这个改造是整个移植的支点:只有 GUI 变成库,才能由 airxc_core 在 native 宿主线程里按"Qt Core → … → Widgets → Svg → libqohos → keepassxc_gui"的拓扑序 dlopen 后拉起,而 ArkTS 主线程完全不承担 Qt 的事件循环。构建 profile 同步裁掉依赖 fork 与 DBus daemon 的能力:KPXC_FEATURE_BROWSER=OFF、SSHAGENT=OFF、FDOSECRETS=OFF。
### 2. 自研 QPA 把 Qt 窗口接进 ArkUI 世界
entry/src/main/cpp/qt_platform/ohos_qt6/ 的 qohosintegration.cpp 承担了桌面 WM 的全部职责:窗口呈现与 flush、InjectMouse/InjectTouch/InjectKey 输入注入、inputcontext 驱动软键盘弹收、filedialoghelper 把 QFileDialog 映射为系统文件 picker、对话框以浮层卡片或真子窗两种形态承载。由于没有 WM 负责窗口激活,P13 系列补丁在注入链前置激活序列,保证点击 QLineEdit 能真实迁移焦点;P29 系列则把软键盘避让高度从 ArkTS 经 NAPI 送进 QPA,由 Qt 几何作为唯一真源重排对话框并同步触摸映射。
### 3. 菜单栏与对话框的 ArkTS 合规改造
鸿蒙应用规范要求菜单栏由 ArkTS 组件承载。P13/P15 两轮改造把 Qt QMenuBar 递归序列化为 JSON(动态子菜单由 aboutToShow 驱动填充),ArkTS 侧用 bindMenu + Menu/MenuItemGroup/MenuItem 官方组件渲染,叶子项经 triggerMenuAction 回注 Qt。这条链上藏着一个典型死锁:大量菜单项的触发槽内会 QDialog::exec() 进入嵌套模态循环,ArkTS 主线程同步等待必然挂死,P15b 因此把 NAPI 触发改成一次性工作线程异步派发。对话框系统同样双轨:浮层模式由 DialogHost 按快照重建卡片,真子窗模式由 SubWindowManager 承载,几何与触摸坐标都从 Qt 侧贯通。
### 4. 数据落盘:从 O_TMPFILE 到可覆盖的原子保存
密码库保存路径是安全底线。MatePad Mini 上 QSaveFile 的 commit 返回 err10 的根因,是较新内核支持 O_TMPFILE 匿名 inode,而 commit 阶段的 linkat(AT_EMPTY_PATH) 被应用沙箱拒绝。修复是双保险:P11 在 Database.cpp 改用 filePath + ".airxc-tmp" 命名临时文件加 rename(2),Q1 直接给 Qt 的 qtemporaryfile.cpp 打补丁禁用 O_TMPFILE(QSettings 等所有 QSaveFile 用户一并受益,Qt6Core 重建后以符号一致、NEEDED 修平、真机探针、持久化四闸门验收)。P11b 进一步修正覆盖保存语义:Qt 的 QFile::rename "目标存在即拒绝"与编辑器场景相反,改为直调 POSIX rename() 原子覆盖。文件进出沙箱则由 FileManager 的 staging 目录与 watcher 完成:打开的库从 Download 拷入 picker_in,保存后按基线变化回写源 uri。
### 5. 汉化链与品牌化
鸿蒙侧 Qt 没有系统 locale 后端,QLocale() 恒为 en_US。P12 建立完整汉化链:ArkTS 读系统语言经 NAPI 注入 env,Translator 归一化解析,qtbase_zh_CN.qm 随数据包部署补齐标准按钮翻译。在汉化之上,P16 用自定义 AirxcTranslator 在 translate() 出口统一做译后品牌清洗,一处覆盖全部用户可见字符串;P14/P15c/P19 移除上游 logo、水印与帮助菜单;P17/P18/P20 在 Qt 与 ArkTS 两侧强制浅色主题。这一组补丁让产物以独立应用名 AirXC 呈现,而不是上游的换壳。
▍五、适配中遇到的几个难点
### 难点一:无 WM 环境下窗口从未被激活
现象是向导里的文本框点击无焦点、软键盘不弹。排查发现三层问题叠加:激活守卫的 flags 掩码把 Qt::Dialog 这类含公共 Window 位的窗口类型全部误判为 popup;QWindow::isActive() 在对话框 transientParent 链 active 时恒真,激活逻辑被早退;QWindowSystemInterface 异步入队的焦点事件在真机时序下不落。修复后统一为 GUI 线程同步 requestActivate() 加 processEvents flush,并把判据改成"当前焦点窗口不是自己"。这类问题的共性是桌面 Qt 的隐含前提在无 WM 环境下逐条失效,必须逐项补齐。
### 难点二:musl 上 QThreadPool 过期线程永不复活
云测报"新建数据库最后一步点完成无响应"。探针实锤:线程闲置超过 30 秒过期后,musl 上的重启路径失效,后续 QtConcurrent 任务入队后 worker 永不启动,向导加密页的嵌套事件循环无限等待。此前端到端测试通过,只是因为测试员点击间隔没超过 30 秒。修复是 P22 在 OHOS 下 setExpiryTimeout(-1) 禁用过期机制。它与退出路径的静态析构竞态(P22b 以请求退出、有界等待、_exit(0) 有序停机)共同说明:多线程 native 应用在 musl 沙箱里,线程生命周期管理必须显式接管。
### 难点三:系统 picker 接管后主画面永久冻结
任何系统文件 picker(UIExtension 模态)关闭后,主 XComponent 的画面定格,而 Qt 逻辑全部存活。判别实验证明 UIExtension 接管会把共享槽位上的 EGL surface 永久废弃。修复分四层落地:P25 把整个工作区迁移进专用子窗,主窗退化为壳;P26 给主窗建立独立的呈现面并支持失败自愈重挂;P26b 在挂接窗口期整体挂起 flush 防止死面盲跑挂死驱动;P26d 把迁移提前到启动期,picker 只剩藏显舞蹈。后续 P30/P31 又补上重挂载重同步与渲染冻结看门狗,把"swap 成功但合成器不落屏"这类簿记无法区分的僵尸态纳入心跳闭环。
### 难点四:KDF 密钥派生冻结 GUI
用户报"切换界面卡一两秒",相位仪表化(P33)后定位到解锁/建库时 m_db->open() 的 Argon2 派生同步跑在 GUI 线程,期间零帧上屏;且上游向导默认"解密时间"1 秒,等于给每次解锁定价一秒以上。P34 按上游 AsyncTask 先例把 KDF 下沉工作线程,解锁后 0.2 秒内即出反馈帧;P35 把新库默认解密时间改为 300 毫秒,既有库不受影响。这个难点的价值在于先量化归因再动手,避免把框架级成本误判为渲染瓶颈。
### 难点五:品牌与上游痕迹在译文里
去品牌需求提出后,源码层清洗仍有残留。排查发现残留不在源码而在 zh_CN.ts 的 119 处译文里,逐点改源串会破坏翻译键。最终以 AirxcTranslator 译后统一替换收口,并从桌面图标名、数据包文件名到 About 对话框逐层清理。移植类工程做二次分发时,这类"数据层残留"值得单独立项。
▍六、编译、安装与启动
工程含交叉编译前置步骤,顺序如下(脚本均在 native/scripts/,Qt6Core 仅在补丁后需要重建):
bash
bash native/scripts/build_deps.sh all # zlib/minizip/qrencode/PCSC 桩/Botan
(cd entry/src/main/cpp/qt_platform/ohos_qt6 && bash build_libqohos_qt6.sh) # 自研 QPA
bash native/scripts/build_keepassxc.sh all # 配置并构建 libkeepassxc_gui.so
bash native/scripts/deploy.sh # 库白名单部署进 entry/libs + 数据 tar 进 rawfile
ArkTS 与打包环节使用标准 hvigorw 流程:
bash
export JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk
hvigorw --mode module -p module=entry assembleHap --no-daemon
签名产物位于 entry/build/default/outputs/default/entry-default-signed.hap。PORTING.md 记录了两条部署纪律:预编译 .so 变更必须全量重装(devecocli run --uninstall),且清理构建树后必须先重建产物再跑 deploy.sh。连接设备后安装启动:
bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airxc.app
▍七、当前功能边界
PORTING.md 实录与真机验证记录覆盖的已交付能力:
- 可安装 HAP、三层启动链与 2in1/平板/手机三形态部署;
- 新建数据库向导全流程:基本信息、加密参数(默认解密时间 300 毫秒)、口令设置、弱密码确认;
- 库的打开、解锁、保存、另存为:系统 picker 进出沙箱、POSIX 原子覆盖保存、自动保存、Download 源文件回写;
- 主界面条目管理:根群组树、Title/Username/URL/Notes 列表、搜索过滤、密码生成器;
- ArkTS 官方菜单栏、浮层/子窗双模对话框、软键盘避让与触摸/键盘双模输入;
- 全面汉化(含 Qt 标准部件)、译后品牌清洗、强制浅色主题;
- 2in1 窗口化(系统标题栏、85% 居中、关闭按钮走有序退出链)。
尚未进入当前产物、属既定裁剪或登记在册的能力:YubiKey 硬件密钥(PCSC 桩与 ykcore 空后端)、AutoType 全局自动输入、浏览器集成、SSH agent、FdoSecrets、系统级剪贴板桥(当前 QPA 为内存剪贴板,跨应用复制密码是登记在册的 S9 缺口)、深色主题切换。这些缺口都在风险登记册 R7 与标准审计表中有明确记录,不构成对核心密码库管理能力的遮挡。
因此,当前版本更准确的定位是"KeePassXC 鸿蒙多形态可用版":密码库的创建、解锁、编辑与安全落盘这条主链已在四类真机上闭环,桌面集成能力则按沙箱现实逐项重建或显式裁剪。
▍八、总结
AirXC 的适配过程验证了一个判断:大型 Qt 桌面应用进入鸿蒙,难点不在编译而在系统语义的重建。工程用 GUI→SHARED 改造保住上游本体,用自研 QPA 补齐窗口、输入与对话框的桌面语义,再把 O_TMPFILE、线程生命周期、KDF 阻塞这类平台差异逐个以根因修复的方式收口,最终交付的是可以创建和解锁真实密码库的可安装应用,而非界面演示。
后续工作的重心也已经明确:补齐系统剪贴板桥以打通"复制密码"这一核心高频路径,评估竖屏手机形态的全局锁横屏策略,并把多设备部署缺口这类流程问题制度化。每一步继续以可构建的 HAP、真机端到端链路与 PORTING.md 实录作为验收依据。
■ 应用:AirXC(from KeePassXC)(AirXC)
■ 定位:KeePassXC 移植,密码管理
■ 上游:KeePassXC | 路线:Qt6 6.11.1+QPA | 版本:1.0.0
■ 开源: https://gitcode.com/Andy88/Harmony-KeePassXC
■ 市场状态:开发中,即将上架
■ 官网(免费下载): https://www.shunidc.cn/
————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/
PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:https://gitcode.com/Andy88/Harmony-KeePassXC
环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743
▍一、为什么要适配 AirXC
KeePassXC 是开源社区里使用最广的本地密码管理器之一。它把密码库以 kdbx 格式落在用户自己的磁盘上,用 Argon2 做密钥派生、AES 做加密,不依赖任何云服务。对经常在多台设备之间整理凭据的桌面用户来说,这类"数据完全自持"的工具是刚需,而且它恰好是一个 Qt6/CMake 工程,上游代码本身就有跨平台基因。
把 KeePassXC 带到鸿蒙 PC,真正要回答的问题是:一个依赖完整桌面窗口管理器、QMenuBar、QFileDialog 和进程模型的大型 Qt 应用,能否在沙箱化、以触摸为先的 HarmonyOS 上重新获得完整的交互闭环。这个问题的答案对大量同类 Qt 桌面软件都有参考价值,因此本工程没有走"重写界面"的捷径,而是选择把上游 GUI 本体改造后直接运行。
当前鸿蒙工程的应用名为 AirXC,包名 com.airxc.app,版本 1.0.0,deviceTypes 覆盖 phone、tablet、2in1,仅声明 ohos.permission.INTERNET 权限。技术路线为 Qt6 + 自研 ohos QPA:Qt 版本由上游 find_package(Qt6 6.2.4) 决定,工程取 Qt 6.11.1 预编译栈,QPA 插件 libqohos 含 IME、文件选择器与 SVG 插件。PORTING.md 记载的验证设备包括 HUAWEI MateBook Pro(2in1)、MatePad Mini 与 MatePad 11.5(平板)、Pura X(手机),SDK 为 HarmonyOS 6.1.1(24)、NDK clang 15。
▍二、先划清边界:不把编译通过当成适配完成
KeePassXC 的桌面体验建立在三类系统服务之上:窗口管理器(窗口装饰、激活、多窗)、输入法与剪贴板等桌面服务、以及 QFileDialog/QMessageBox 这类依赖窗口层级的对话框。鸿蒙应用运行在沙箱中,没有传统意义上的 WM,ArkTS 与 native 又分属两套渲染体系。如果只把上游 CMake 跑通、生成一个动态库,得到的并不是密码管理器。
因此工程在第一天就划定了边界:上游的加密内核、数据模型与 GUI 本体进入可运行产物,桌面系统集成点全部替换为鸿蒙等价链路,少数依赖 Linux 桌面环境的能力显式裁剪。
层次 上游 KeePassXC(桌面方案) 鸿蒙侧当前方案
应用入口 main() 直启 Qt 事件循环 Stage 模型 EntryAbility + NAPI 桥 dlopen 链
窗口承载 系统 WM 窗口与装饰 XComponent(TEXTURE) + libqohos QPA,2in1 形态迁移专用子窗加系统标题栏
菜单栏 QMenuBar 原生菜单 ArkTS bindMenu + Menu/MenuItem 官方组件,经 menubar JSON 导出与 triggerMenuAction 回注
文件对话框 QFileDialog / QSaveFile 直写 QPA filedialoghelper 桥系统 picker,picker_in/picker_out staging 中转
数据库原子保存 QSaveFile(O_TMPFILE 匿名 inode) 命名临时文件 + POSIX rename() 原子覆盖(P11/P11b/Q1)
加密内核 上游加密栈 Botan 2.19.5 交叉编译进 Native 产物
硬件与桌面集成 YubiKey、AutoType、浏览器集成、SSH agent、FdoSecrets PCSC 桩与 ykcore 空后端,其余按既定范围裁剪
这张表也是验收口径:每一行右侧的能力都必须在真机上可操作,才算进入"已适配"范围。
▍三、鸿蒙版的整体架构
工程采用 Air 双层结构:ArkTS 壳负责 Ability 生命周期、菜单栏、IME 宿主与数据部署;NAPI 薄桥只做 dlsym 转发与触摸注入;真正的 KeePassXC 运行在 native 宿主线程里,由 airxc_core 按拓扑序加载 Qt 栈与 libkeepassxc_gui 后进入上游主函数。
text
EntryAbility(ArkTS 壳:生命周期、强制浅色、目录权限)
└── pages/Index.ets
├── 顶部菜单栏(bindMenu/Menu/MenuItem 官方组件)
├── XComponent(TEXTURE) ← Qt 画面呈现
├── IME 宿主 TextInput(隐形节点 + 差分回注)
├── DialogHost(浮层对话框卡片)/ SubWindowManager(子窗承载)
└── NAPI libairxcbridge.so(薄桥:core API + TSFN + QOhos_Inject*)
└── libairxc_core.so(native 宿主线程)
├── env 前置(HOME/XDG_*)→ airxc-data.tar 解包门控
├── 拓扑序 dlopen:Qt 6.11.1 全家 → libqohos.so → libkeepassxc_gui.so
└── airxc_keepassxc_main() → KeePassXC GUI 本体
entry/libs/arm64-v8a/:Qt 栈 + qohos + keepassxc_gui + botan/zlib/minizip/PCSC 桩(21 库白名单通道)
rawfile:airxc-data.tar(icons/wordlists/wizard/translations .qm + 配置种子)
仓库主要目录如下:
text
AirXC/
├── AppScope/app.json5 # 包名 com.airxc.app、版本与应用资源
├── build-profile.json5 # SDK、产品与签名配置
├── PORTING.md # 移植手册:依赖矩阵、补丁清单、风险登记册与实录
├── entry/src/main/
│ ├── ets/pages/Index.ets # 壳主页面:菜单/XComponent/IME/对话框宿主
│ ├── ets/components/DialogHost.ets # Qt 对话框浮层卡片
│ ├── ets/utils/SubWindowManager.ets # 子窗承载与 PC 窗口化
│ └── cpp/
│ ├── airxc_core.cpp # 宿主线程:dlopen 拓扑序 + HTTP 调试桥
│ ├── napi_init.cpp # NAPI 薄桥
│ └── qt_platform/ohos_qt6/ # 自研 ohos QPA(qohosintegration 等)
└── native/
├── sources/ # keepassxc 工作树、Botan-2.19.5、zlib-1.3.1、qrencode-4.1.1
├── scripts/ # build_deps/build_keepassxc/deploy.sh 等构建部署脚本
├── patches/ # Qt 翻译等补丁资源
└── stubs/ # pcsclite 桩与 ykcore_ohos 空后端
▍四、核心适配过程
### 1. GUI 从可执行程序改造为共享库
上游 src/main.cpp 的 int main() 在 OHOS 构建下被补丁 P6 改为 extern "C" int airxc_keepassxc_main(int, char**),src/CMakeLists.txt 的 P2 把 GUI 目标从 exe 改为 SHARED,导出 libkeepassxc_gui.so。这个改造是整个移植的支点:只有 GUI 变成库,才能由 airxc_core 在 native 宿主线程里按"Qt Core → … → Widgets → Svg → libqohos → keepassxc_gui"的拓扑序 dlopen 后拉起,而 ArkTS 主线程完全不承担 Qt 的事件循环。构建 profile 同步裁掉依赖 fork 与 DBus daemon 的能力:KPXC_FEATURE_BROWSER=OFF、SSHAGENT=OFF、FDOSECRETS=OFF。
### 2. 自研 QPA 把 Qt 窗口接进 ArkUI 世界
entry/src/main/cpp/qt_platform/ohos_qt6/ 的 qohosintegration.cpp 承担了桌面 WM 的全部职责:窗口呈现与 flush、InjectMouse/InjectTouch/InjectKey 输入注入、inputcontext 驱动软键盘弹收、filedialoghelper 把 QFileDialog 映射为系统文件 picker、对话框以浮层卡片或真子窗两种形态承载。由于没有 WM 负责窗口激活,P13 系列补丁在注入链前置激活序列,保证点击 QLineEdit 能真实迁移焦点;P29 系列则把软键盘避让高度从 ArkTS 经 NAPI 送进 QPA,由 Qt 几何作为唯一真源重排对话框并同步触摸映射。
### 3. 菜单栏与对话框的 ArkTS 合规改造
鸿蒙应用规范要求菜单栏由 ArkTS 组件承载。P13/P15 两轮改造把 Qt QMenuBar 递归序列化为 JSON(动态子菜单由 aboutToShow 驱动填充),ArkTS 侧用 bindMenu + Menu/MenuItemGroup/MenuItem 官方组件渲染,叶子项经 triggerMenuAction 回注 Qt。这条链上藏着一个典型死锁:大量菜单项的触发槽内会 QDialog::exec() 进入嵌套模态循环,ArkTS 主线程同步等待必然挂死,P15b 因此把 NAPI 触发改成一次性工作线程异步派发。对话框系统同样双轨:浮层模式由 DialogHost 按快照重建卡片,真子窗模式由 SubWindowManager 承载,几何与触摸坐标都从 Qt 侧贯通。
### 4. 数据落盘:从 O_TMPFILE 到可覆盖的原子保存
密码库保存路径是安全底线。MatePad Mini 上 QSaveFile 的 commit 返回 err10 的根因,是较新内核支持 O_TMPFILE 匿名 inode,而 commit 阶段的 linkat(AT_EMPTY_PATH) 被应用沙箱拒绝。修复是双保险:P11 在 Database.cpp 改用 filePath + ".airxc-tmp" 命名临时文件加 rename(2),Q1 直接给 Qt 的 qtemporaryfile.cpp 打补丁禁用 O_TMPFILE(QSettings 等所有 QSaveFile 用户一并受益,Qt6Core 重建后以符号一致、NEEDED 修平、真机探针、持久化四闸门验收)。P11b 进一步修正覆盖保存语义:Qt 的 QFile::rename "目标存在即拒绝"与编辑器场景相反,改为直调 POSIX rename() 原子覆盖。文件进出沙箱则由 FileManager 的 staging 目录与 watcher 完成:打开的库从 Download 拷入 picker_in,保存后按基线变化回写源 uri。
### 5. 汉化链与品牌化
鸿蒙侧 Qt 没有系统 locale 后端,QLocale() 恒为 en_US。P12 建立完整汉化链:ArkTS 读系统语言经 NAPI 注入 env,Translator 归一化解析,qtbase_zh_CN.qm 随数据包部署补齐标准按钮翻译。在汉化之上,P16 用自定义 AirxcTranslator 在 translate() 出口统一做译后品牌清洗,一处覆盖全部用户可见字符串;P14/P15c/P19 移除上游 logo、水印与帮助菜单;P17/P18/P20 在 Qt 与 ArkTS 两侧强制浅色主题。这一组补丁让产物以独立应用名 AirXC 呈现,而不是上游的换壳。
▍五、适配中遇到的几个难点
### 难点一:无 WM 环境下窗口从未被激活
现象是向导里的文本框点击无焦点、软键盘不弹。排查发现三层问题叠加:激活守卫的 flags 掩码把 Qt::Dialog 这类含公共 Window 位的窗口类型全部误判为 popup;QWindow::isActive() 在对话框 transientParent 链 active 时恒真,激活逻辑被早退;QWindowSystemInterface 异步入队的焦点事件在真机时序下不落。修复后统一为 GUI 线程同步 requestActivate() 加 processEvents flush,并把判据改成"当前焦点窗口不是自己"。这类问题的共性是桌面 Qt 的隐含前提在无 WM 环境下逐条失效,必须逐项补齐。
### 难点二:musl 上 QThreadPool 过期线程永不复活
云测报"新建数据库最后一步点完成无响应"。探针实锤:线程闲置超过 30 秒过期后,musl 上的重启路径失效,后续 QtConcurrent 任务入队后 worker 永不启动,向导加密页的嵌套事件循环无限等待。此前端到端测试通过,只是因为测试员点击间隔没超过 30 秒。修复是 P22 在 OHOS 下 setExpiryTimeout(-1) 禁用过期机制。它与退出路径的静态析构竞态(P22b 以请求退出、有界等待、_exit(0) 有序停机)共同说明:多线程 native 应用在 musl 沙箱里,线程生命周期管理必须显式接管。
### 难点三:系统 picker 接管后主画面永久冻结
任何系统文件 picker(UIExtension 模态)关闭后,主 XComponent 的画面定格,而 Qt 逻辑全部存活。判别实验证明 UIExtension 接管会把共享槽位上的 EGL surface 永久废弃。修复分四层落地:P25 把整个工作区迁移进专用子窗,主窗退化为壳;P26 给主窗建立独立的呈现面并支持失败自愈重挂;P26b 在挂接窗口期整体挂起 flush 防止死面盲跑挂死驱动;P26d 把迁移提前到启动期,picker 只剩藏显舞蹈。后续 P30/P31 又补上重挂载重同步与渲染冻结看门狗,把"swap 成功但合成器不落屏"这类簿记无法区分的僵尸态纳入心跳闭环。
### 难点四:KDF 密钥派生冻结 GUI
用户报"切换界面卡一两秒",相位仪表化(P33)后定位到解锁/建库时 m_db->open() 的 Argon2 派生同步跑在 GUI 线程,期间零帧上屏;且上游向导默认"解密时间"1 秒,等于给每次解锁定价一秒以上。P34 按上游 AsyncTask 先例把 KDF 下沉工作线程,解锁后 0.2 秒内即出反馈帧;P35 把新库默认解密时间改为 300 毫秒,既有库不受影响。这个难点的价值在于先量化归因再动手,避免把框架级成本误判为渲染瓶颈。
### 难点五:品牌与上游痕迹在译文里
去品牌需求提出后,源码层清洗仍有残留。排查发现残留不在源码而在 zh_CN.ts 的 119 处译文里,逐点改源串会破坏翻译键。最终以 AirxcTranslator 译后统一替换收口,并从桌面图标名、数据包文件名到 About 对话框逐层清理。移植类工程做二次分发时,这类"数据层残留"值得单独立项。
▍六、编译、安装与启动
工程含交叉编译前置步骤,顺序如下(脚本均在 native/scripts/,Qt6Core 仅在补丁后需要重建):
bash
bash native/scripts/build_deps.sh all # zlib/minizip/qrencode/PCSC 桩/Botan
(cd entry/src/main/cpp/qt_platform/ohos_qt6 && bash build_libqohos_qt6.sh) # 自研 QPA
bash native/scripts/build_keepassxc.sh all # 配置并构建 libkeepassxc_gui.so
bash native/scripts/deploy.sh # 库白名单部署进 entry/libs + 数据 tar 进 rawfile
ArkTS 与打包环节使用标准 hvigorw 流程:
bash
export JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk
hvigorw --mode module -p module=entry assembleHap --no-daemon
签名产物位于 entry/build/default/outputs/default/entry-default-signed.hap。PORTING.md 记录了两条部署纪律:预编译 .so 变更必须全量重装(devecocli run --uninstall),且清理构建树后必须先重建产物再跑 deploy.sh。连接设备后安装启动:
bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airxc.app
▍七、当前功能边界
PORTING.md 实录与真机验证记录覆盖的已交付能力:
- 可安装 HAP、三层启动链与 2in1/平板/手机三形态部署;
- 新建数据库向导全流程:基本信息、加密参数(默认解密时间 300 毫秒)、口令设置、弱密码确认;
- 库的打开、解锁、保存、另存为:系统 picker 进出沙箱、POSIX 原子覆盖保存、自动保存、Download 源文件回写;
- 主界面条目管理:根群组树、Title/Username/URL/Notes 列表、搜索过滤、密码生成器;
- ArkTS 官方菜单栏、浮层/子窗双模对话框、软键盘避让与触摸/键盘双模输入;
- 全面汉化(含 Qt 标准部件)、译后品牌清洗、强制浅色主题;
- 2in1 窗口化(系统标题栏、85% 居中、关闭按钮走有序退出链)。
尚未进入当前产物、属既定裁剪或登记在册的能力:YubiKey 硬件密钥(PCSC 桩与 ykcore 空后端)、AutoType 全局自动输入、浏览器集成、SSH agent、FdoSecrets、系统级剪贴板桥(当前 QPA 为内存剪贴板,跨应用复制密码是登记在册的 S9 缺口)、深色主题切换。这些缺口都在风险登记册 R7 与标准审计表中有明确记录,不构成对核心密码库管理能力的遮挡。
因此,当前版本更准确的定位是"KeePassXC 鸿蒙多形态可用版":密码库的创建、解锁、编辑与安全落盘这条主链已在四类真机上闭环,桌面集成能力则按沙箱现实逐项重建或显式裁剪。
▍八、总结
AirXC 的适配过程验证了一个判断:大型 Qt 桌面应用进入鸿蒙,难点不在编译而在系统语义的重建。工程用 GUI→SHARED 改造保住上游本体,用自研 QPA 补齐窗口、输入与对话框的桌面语义,再把 O_TMPFILE、线程生命周期、KDF 阻塞这类平台差异逐个以根因修复的方式收口,最终交付的是可以创建和解锁真实密码库的可安装应用,而非界面演示。
后续工作的重心也已经明确:补齐系统剪贴板桥以打通"复制密码"这一核心高频路径,评估竖屏手机形态的全局锁横屏策略,并把多设备部署缺口这类流程问题制度化。每一步继续以可构建的 HAP、真机端到端链路与 PORTING.md 实录作为验收依据。
楼主
8 天前