Air 系列应用交流/AirDraft(from LibreCAD)|AirDraft · 鸿蒙 PC 适配全记录

AirDraft(from LibreCAD)|AirDraft · 鸿蒙 PC 适配全记录

【Air 系列 · 鸿蒙 PC 适配全记录】11 / 56

■ 应用:AirDraft(from LibreCAD)(AirDraft)
■ 定位:LibreCAD 移植,2D 工程制图
■ 上游:LibreCAD | 路线:Qt6 6.11.1+QPA | 版本:1.1.1
■ 开源: https://gitcode.com/Andy88/Harmony-LibreCAD
■ 市场状态:已上架 AGC 应用页: https://appgallery.huawei.com/app/6917616285238429466
■ 官网(免费下载): https://www.shunidc.cn/

————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/

PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper

适配开源地址:https://gitcode.com/Andy88/Harmony-LibreCAD

环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743

▍一、为什么要适配 LibreCAD

LibreCAD 是老牌的开源 2D 工程制图应用,GPL-2.0 协议,覆盖图层管理、图块、标注、DXF 读写等 CAD 基本盘。它的技术形态对移植相当有利:上游 master(2.2.2.6-alpha)已是 Qt6 Widgets + C++17,渲染全部走 QWidget + QPainter 的 raster 路径,零 OpenGL 引用——这意味着只要有一个能把 raster 内容送上屏幕的 Qt 平台插件,整个制图内核就不需要图形层改写。

把这类应用带到鸿蒙 PC 的价值不在"能编译",而在检验 Qt 系移植范式的完整度:菜单、对话框、文件选择、输入法、窗口生命周期,每一项桌面交互都要有明确的承接方案。AirDraft 以同系列的 AirCast(OBS 移植)为蓝本,复用 AirGis 的 Qt 6.11.1 预编译栈与 AirCast 沉淀的 ohos QPA 插件,目标设备为 HUAWEI MatePad Mini 悦读版(tablet,arm64-v8a),MateBook Pro 与 MatePad 11.5 备用。

工程包名 com.airdraft.app,版本 1.0.0,deviceTypes 覆盖 phone、tablet、2in1,Ability 锁定横屏。按 PORTING.md 里程碑记录,M1 至 M5 已完成:LibreCAD 交叉编译为 liblibrecad.so,三台设备全部跑到主窗,CAD 核心功能经视觉与日志双通道实证——MDI 子窗、网格点阵、十字光标、活体坐标流与命令行推进均在真机确认。产物规模为 HAP 185MB(18 个 so 加 59MB 数据包)。

▍二、先划清边界:不把编译通过当成适配完成

LibreCAD 是一个完整的桌面 Qt 程序:main() 里构造 QApplication、弹启动画面、挂 QMenuBar、按需弹 QFileDialog 与几十个 QDockWidget。直接交叉编译出一个 so 并不代表得到可用的制图工具——单表面窗口体系下,独立顶层窗不参与主窗合成,启动画面会成为永久黑块;原生菜单栏没有宿主可挂;打印、动态插件等依赖系统服务的能力在 QPA 上并不存在。工程把边界划为三条:内核全量进入(muparser、libdxfrw、jwwlib、shapelib 随 librecad_lib 静态归档编入),桌面外围由 ArkTS 壳层接管,依赖系统基础设施的能力显式裁剪并登记。

层次 上游 LibreCAD(桌面 Qt 程序) 鸿蒙侧当前方案
应用入口 main() 可执行程序 __OHOS__ 守卫改 librecad_ohos_main,由 Qt 宿主线程调入(GUI→SHARED 范式)
窗口承载 平台原生窗口体系 XComponent(draft_surface) → OHNativeWindow → ohos QPA
菜单栏 QMenuBar 原生渲染 ArkTS 顶栏接管:1 秒快照 JSON 轮询 + serial 校验回注触发
对话框 QWidget 独立顶层窗 SubWindowManager 自由多窗 / DialogHost 浮层双模式
文件选择 QFileDialog 本地对话框 QPA qohosfiledialoghelper 事件桥 → 系统 DocumentViewPicker
文本输入 平台 IME 直连 离屏 1×1 TextInput 通道,增量 diff 提交 + op 分级回车
数据资源 安装目录 share/ rawfile librecad-data.tar 首启解包 + LIBRECAD_DATA_DIR 指向沙箱
图标引擎 QPluginLoader 动态插件 .lci 引擎以 QT_STATICPLUGIN 静态编入

▍三、鸿蒙版的整体架构

工程采用 Air 系双层结构:ArkTS 壳负责窗口、菜单、对话框宿主与输入法;Native 侧三层 so 各司其职——libdraftbridge.so 是只链系统库的 NAPI 薄壳,libairdraft_core.so 负责环境注入、数据解包与 dlopen 装配,librecad.so 承载 LibreCAD 全本体。Qt 栈、QPA、zstd/ssl/crypto/png16 依赖按 NEEDED 顺序展开,宿主线程最后调入 librecad_ohos_main。

text
EntryAbility(Stage 模型,横屏)
└── Index.ets(1039 行壳层)
├── XComponent(draft_surface) → OHNativeWindow → QPA(QOhos_SetNativeWindow)
├── ArkTS 顶栏菜单:getMenuSnapshot 轮询 → 叶子项 triggerMenuAction 回注
├── 对话框双模式:SubWindowManager 自由多窗 / DialogHost 浮层(QPA PATCH-91/96/97 链)
├── 文件选择:QPA pick 请求 → TSFN → DocumentViewPicker → 结果回流
├── IME:离屏 1×1 TextInput 通道(增量 diff + 条件渲染卸载收起)
└── Back 键语义:qtBusy>0 转 qtEsc(三路幂等投递),空闲放行退桌面
libdraftbridge.so(NAPI 薄壳,模块 draftbridge,只链系统库)
└── dlopen 拓扑:zstd/ssl/crypto → Qt6 栈 → libpng16 → libqohos(QPA) → librecad.so
libairdraft_core.so(宿主核心)
├── HOME/XDG_*/LIBRECAD_DATA_DIR 环境注入 → 沙箱
├── librecad-data.tar 首启解包(home/ 种子已存在则跳过)
└── Qt 宿主线程调 librecad_ohos_main
librecad.so(LibreCAD 全本体 + librecad_lib 静态归档:muparser/libdxfrw/jwwlib/shapelib)


主要目录如下:

text
AirDraft/
├── AppScope/ # app.json5:com.airdraft.app
├── PORTING.md # 移植手册:补丁清单、风险登记册、修复链全景
├── ai_log/ diag/ # 实施日志与真机取证产物
├── entry/src/main/
│ ├── module.json5 # 横屏锁定、三设备类型
│ ├── cpp/
│ │ ├── airdraft_core.cpp # 宿主核心:环境、数据解包、dlopen 链(608 行)
│ │ ├── napi_init.cpp # NAPI 桥:菜单/IME/对话框/文件选择(1012 行)
│ │ ├── patch_libs.py fix_elf_align.py # 部署期 so 修补
│ │ └── qt_platform/ohos_qt6/ # ohos QPA:qohosintegration、filedialoghelper 等
│ └── ets/
│ ├── entryability/EntryAbility.ets
│ ├── pages/Index.ets # 壳层主页面:XComponent、顶栏、IME、对话框
│ ├── pages/DialogPage.ets
│ ├── components/DialogHost.ets DialogTitleBar.ets
│ └── utils/SubWindowManager.ets FileManager.ets
└── native/
├── env.sh
├── install/include/boost # Boost 1.85 头(ublas/math/geometry/tuple)
├── scripts/ # build_librecad.sh、deploy.sh、汉化脚本
└── sources/librecad # 已含鸿蒙移植补丁的 LibreCAD 源码树


▍四、核心适配过程

### 1. GUI→SHARED:把可执行程序变成宿主线程里的库

补丁 P1 把 main 改名为 __OHOS__ 守卫下的 librecad_ohos_main,P7 配套把 CMake 的 qt_add_executable 换成 qt_add_library SHARED(DEFINE_SYMBOL "" 保导出),LinguistTools、ttf2lff、install 规则与 QPluginLoader 插件子目录一并裁剪。装配责任落在 airdraft_core.cpp:先注入 HOME 与 XDG_* 环境把 Qt/LibreCAD 的目录语义指进沙箱,再解包数据,最后沿 dlopen 链唤醒 Qt,在宿主线程调用入口函数。构建用 cmake 交叉加 ninja,1037 个 cpp 与 170 个 .ui 的 AUTOMOC/AUTOUIC 依赖 QT_HOST_PATH 指向 Homebrew qtbase 6.11.1 的宿主工具。

### 2. 壳层接管:菜单、对话框与文件选择

P3 新增 lc_ohos_menubar.cpp,沿 AirCast 的 OHOSMenuBar 范式让 Qt 侧每秒快照菜单树为 JSON,NAPI 暴露 getMenuSnapshot/triggerMenuAction,ArkTS 顶栏渲染叶子项并以构建期快照代次(serial)校验回注,避免旧快照触发错代动作。对话框走 QPA 既有管线:QPA 判定对话框事件后经 registerDialogOpsCallback 通知 ArkTS,由 SubWindowManager 在自由多窗与 DialogHost 浮层两种模式间实例化;attachDialogSurface/detachDialogSurface/dialogInput 构成对话框表面的完整控制面。文件打开保存经 P5 把 UseQtFileOpenDialog 默认置零,走 qohosfiledialoghelper 到系统 DocumentViewPicker,napi_init.cpp 的 registerFilePickerCallback/completeFilePick 承接事件往返。

### 3. 数据包、配置种子与翻译装载

LibreCAD 期望 <dataDir> 下有 fonts、patterns、library、qm 等目录。工程把 57MB support 数据打包为 rawfile librecad-data.tar,宿主核心首启解包到 files/librecad-data/(P4 同时在 rs_system.cpp 的 getDirectoryList 前插沙箱数据目录,保证优先命中);files/home/.config/LibreCAD/LibreCAD-2.conf 种子预设 zh_cn、禁版本检查与最大化。翻译文件用 Homebrew lrelease 离线生成后随数据包分发,rs_system.cpp loadTranslation 从文件系统装载,规避资源系统差异。

### 4. 触控、IME 与 Back 键的交互重建

CAD 应用对指针语义敏感。P21 关闭 Qt 的 AA_SynthesizeMouseForUnhandledTouchEvents,让桥层合成的单路鼠标成为唯一指针流;P26 进一步把左列工具按钮改为"轻点直激组首工具、长按 600ms 弹变体菜单"(setMenu + setDefaultAction + DelayedPopup),消灭 flyout 常驻带来的误触面。命令行输入走离屏 TextInput:onImeChange 以 lastImeCommitted 做差分,追加提交后缀、变短回退退格,commitImeText(text, op, count) 的 op=2 对应 Qt 回车键;提交后条件渲染卸载该 TextInput 并设 1.2 秒收起抑制窗,根治键盘收起后被 Qt 焦点往返重放拉回的问题(PATCH-AirDraft-4/5)。P-A6 用 qtBusy 原子标记(弹窗/命令进行中)把系统 Back 转为 qtEsc 三路幂等投递,空闲时才放行退桌面。

### 5. 图标引擎静态化与界面裁剪

LibreCAD 的工具图标是私有 .lci 格式,由 libraries/lciconengine 的 QIconEnginePlugin 解析,动态插件路径在鸿蒙不可用。P11 三步静态化:源文件加 QT_STATICPLUGIN 编入、目标级定义宏(moc 生成进 mocs_compilation.cpp,per-source 定义盖不到是 dlopen "symbol not found" 的真机实证)、main.cpp 里 Q_IMPORT_PLUGIN 锚定,并以渲染探针验证(line.lci 输出 48×48 不透明像素)。界面侧 P10/P16/P20 组成小屏治理:关闭非核心 dock、图标 24→20px、隐藏三个低价值状态工具条,使 1077 逻辑宽下工具与状态栏完整可达。

▍五、适配中遇到的几个难点

### 难点一:DPI 舍入策略让屏幕出现结构性黑带

上游为 Windows 125%/150% 缩放设置了 RoundPreferFloor,会把 QPA 注入的非整数 dpr(2.375/2.2/1.9)舍成整数;Qt 实际缩放与 QPA 的单一 dpr 源从此失配,QPA 每次 flush 前的尺寸自对齐都会以透明色清空 backing store,静态区域(工具条、左列)被清后无人重绘,表现为固定黑带,而持续更新的动态区域看似正常——"截图完整、屏幕发黑"的二分现象一度误导排查方向。P13 在 __OHOS__ 下跳过该设置保持 PassThrough,双机验证黑带消失;同期记录的"首绘慢 35 秒"实为失配清空循环的假象,根修后重启完整上屏约 12 秒。

### 难点二:安装后首启的零错误僵尸 surface

用户主诉"建模窗口不显示"而进程心跳、swap 全部正常。真机取证把时间轴钉死:ArkUI 在 OnSurfaceCreated/Changed 后约 0.5-1.3 秒内会无回调地重配置 XComponent texture 队列,期间建立的 EGL 链路 swap 全部成功但合成器永不显示——字节级截图哈希对比(跨重装 16 分钟逐字节相同)给出画面冻结铁证。P17 三层修复:settle 守卫让末次 surface 事件 1.6 秒内拒绝建链、由定时器统一重试建链并判活;flush 快路径在 blit 后读回 5×5 像素,源图非零而 back buffer 全零即判定队列已断并武装复活链;Index.ets onPageShow 调 reanchorSurface 做前台幂等重注入。安装加启动竞态三连测全部健康。

### 难点三:双鼠标合成流与 flyout 驻留的叠加误触

左列工具栏"快速触发打断"的用户主诉经历了三轮递进:v8 发现 NAPI 触摸合成与 Qt 自带触摸合成各出一套 press/release,灭掉 src=2 冗余流;v10 取证发现残余通道是 flyout 菜单与按钮列无缝相接、覆盖右侧带状区,驻留期偏右 50-60px 的点击直接命中菜单项;v3 定位才确认左列根本是 QToolBar categories_toolbar 而非此前两轮扑空的 dock 容器。P26 的终态方案是交互语义替换——轻点即激活主工具、长按才弹变体菜单,快速操作期不再有驻留菜单。33 次快连仪表化验证无吞击,八项终验全过。

### 难点四:IME 的增量语义与键盘收起

程序化清空 imeBuffer 对 IME 活动中的 TextInput 无效,onChange 携带的是累计全文,整串 commit 让输入 "line" 上屏 "llilinline";提交字面 '\n' 文本又被 QLineEdit 丢弃,命令永不执行。P18 以 lastCommitted 差分重建增量语义并引入 op 分级。键盘收起(I15)则叠加三层:hideSoftKeyboard 对三方应用静默失败、TextInput 保持焦点时输入会话下一帧重绑、Qt 焦点 churn 约 110ms 后重放面板请求——四轮迭代后落到"卸载离屏 TextInput + 1.2 秒抑制窗"的组合,真机收起 3/3 无反弹。

### 难点五:上游翻译资产的系统性缺口

汉化不是"缺几条词条":上游 zh_cn.ts 长期把已译词条标为 unfinished,lrelease -nounfinished 静默丢弃 1890 条;master 重构改出 2466 条空翻译;新菜单工厂 LC_MenuFactoryMain 上下文为零。native/scripts/fill_zh_ts_gaps.py 与 patch_zh_menu_ts.py 组成三层修复:去 unfinished 放行真译文、跨上下文复用同源串、人工词典补交互提示与命令词,qm 词条从 4867 增至 7100。P24 再让命令语言跟随 UI 语言,P25 顺手根修了上游 cmdToAction 备好小写串却用原串查表、句首大写 "Line" 报未知命令的 bug。最终"指定第一点""辐射线""[撤销]"全链上屏。

▍六、编译、安装与启动

构建有明确的前后顺序(PORTING.md §4),四步脚本链不可乱序:

bash

bash entry/src/main/cpp/qt_platform/ohos_qt6/build_libqohos_qt6.sh

bash native/scripts/build_librecad.sh

bash native/scripts/deploy.sh


之后是标准 HAP 构建(等效 devecocli build):

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,安装启动命令为:

bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airdraft.app


PORTING.md 记载的部署终态为 devecocli run 推至主验证机。首次启动会解包数据包并落配置种子,启动遮罩保留到首个菜单快照到达才撤;native 产物在 out/ 目录需经 deploy.sh 拷入 entry/libs 再打包,这是修复链中登记过的踩坑点。

▍七、当前功能边界

按 PORTING.md 里程碑与修复链记录,当前已覆盖的能力:

- 三设备(MatePad Mini 悦读版、MatePad 11.5、MateBook Pro)部署首启,主窗完整上屏;
- CAD 核心链路实证:MDI 子窗、网格点阵、十字光标拾取框、活体坐标流、绘图命令推进;
- 命令行真实可用:中文提示与命令词("指定第一点""[撤销]")、大小写不敏感命令解析;
- ArkTS 顶栏菜单接管:七个顶级菜单快照实时、serial 校验回注触发;
- 对话框双模式宿主、系统 DocumentViewPicker 文件链路、全中文界面(qm 7100 词条);
- 触控交互重建:轻点直激/长按弹变体菜单、单路鼠标指针流、Back 键 Esc 语义、IME 增量提交与键盘收起根治。

尚未进入当前产物或登记在册的能力:

- 打印未接:Qt6PrintSupport 在无打印后端的 QPA 上属风险项 R3,当前不触达;
- QPluginLoader 动态插件体系裁剪(P7):.lci 图标以静态方式编入,其余插件首版不含;
- ttf2lff 转换工具与 install 规则跳过(P7);
- 小屏触控的系统性优化仍在排期(R4,按 standards.md §9 清单);
- 57MB 数据包首启拷贝的耗时与内存峰值待持续观察(R7),超标需改分段按需字体集;
- IME 会话首建偶发迟至数秒、孤例"返回按钮未退出命令"等新观察已登记未定案。

因此当前版本更准确的定位是"AirDraft——LibreCAD 鸿蒙原生可交互移植版":制图内核、命令系统与中文界面已达到日常可用的实证水平,打印、插件生态与触控细节打磨是后续阶段目标。

▍八、总结

AirDraft 展示了 Qt 系大型桌面应用移植从"能编译"到"可交付"之间真实的距离。内核侧,Qt 6.11 预编译栈加 ohos QPA 让 1037 个源文件的 QPainter 应用原样跑进鸿蒙;壳层侧,菜单快照、对话框双模式、文件选择事件桥与 IME 通道把桌面交互逐项翻译成系统能力。真正决定可用性的是修复链里那些跨层根因——DPI 舍入策略、surface 生命周期竞态、双鼠标合成流、翻译资产缺口——每一个都要求取证手段先行、以真机日志与像素证据定案,而不是停留在接口调通。

后续工作的重心在风险登记册的三个方向:打印链路需要 QPA 打印后端或显式裁剪菜单入口;触控优化按 S 清单逐项推进,包括已登记的 IME 首建时延观察;数据包装载在实测超标的场景下改分段按需。工程保留的探针设施(lci 渲染探针、Spy 双击仪表、METRICS 级日志)为这些后续项提供了现成的取证基座,也让每一次改动继续保持"证据定案、终验清单收口"的节奏。
楼主 8 天前
想参与讨论?登录 或 30 秒注册。