一、写在前面

欢迎加入鸿蒙PC开发者社区,共同打造开发者工具生态:鸿蒙PC开发者社区:https://harmonypc.csdn.net/

鸿蒙适配仓库:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_scribus

欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper

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

这篇文章记录的是 Scribus 在 HarmonyOS PC / OpenHarmony PC 环境中的一次完整适配过程,其中也记录了一段排查:第一次跑起来后,发现点某些按钮"没反应",怎样定位问题、修好,再用 hdc + uitest 把真机当测试机自己点一遍验证。

Scribus 不是 Electron 项目,也不是普通的 ArkUI 应用。它是一个 C++ / Qt 6 的开源桌面排版软件(Desktop Publishing,DTP),对标 Adobe InDesign / QuarkXPress——能做杂志、报纸、海报、宣传册、PDF。代码体量很大:约 1259 个 .cpp / .h,构建系统是 CMake,依赖一大堆原生库(poppler、cairo、harfbuzz、freetype、fontconfig、lcms2、libxml2、hunspell、icu、boost、podofo、GraphicsMagick……)。当前是 1.7.x 开发线(面向未来稳定版 1.8.0),版本号 1.7.4。

和 Web 套壳不同,把这样一个 Qt 桌面应用搬上鸿蒙 PC,难点在于:

  1. 怎样让一个 Qt Widgets 桌面应用进入鸿蒙 Stage 模型,由 libentry.so 启动(Qt for HarmonyOS 的 QPA 思路)。
  2. Scribus 全量是 Qt6 + 一大票原生库,短期内不可能全部交叉编译到 OHOS arm64,怎样先用一个自包含的"DTP 工作区外壳"把整条渲染/交互链路打通,把全量编译留作后续路线图。
  3. 第一次能跑、能进主界面,但点某些按钮没反应——这是 bug 还是没接事件?怎样定位、修复。
  4. 怎样自己控制鸿蒙 PC 真机,把改完的 HAP 装上去、注入真实点击和拖拽,逐项截图验证按钮真的响应了。
  5. 命令行构建一路 SDK component missing——怎样从 hvigor 的 SDK 解析逻辑里把正确的 SDK 路径、版本号、hvigor 版本一个个挖出来。

本次适配采用逐步验证的路线:保留 Scribus 原有 C++ 主体不动,新建 harmony_pc/ 作为鸿蒙工程壳;ArkTS 侧只负责 Ability、窗口和 XComponent;界面由 Qt 运行时承载;鸿蒙特有改动全部用 Q_OS_OPENHARMONY 收敛,桌面构建完全不受影响

在这里插入图片描述

二、项目背景:Scribus 是 Qt6 + CMake 的大型桌面排版软件

确认它是什么很简单:根目录有 CMakeLists.txtcmake_minimum_required(VERSION 3.16))和大量 find_package(Qt6 ...)README 里明确写着「As of version 1.7.x, Scribus is built using version 6 of the Qt toolkit. Scribus 1.7.0 or higher requires at least Qt 6.4.」入口在 scribus/main.cpp(非 Windows 走 main_nix.cpp),用的是 ScribusQApp(继承自 QApplication)。

原始项目结构(节选):

scribus/
├── CMakeLists.txt                 # 顶层 CMake(要求 Qt6.4+)
├── CMakeLists_Dependencies.cmake  # poppler/cairo/lcms2/harfbuzz/podofo... 一大票依赖
├── scribus/                       # 主源码目录(约 1259 个 .cpp/.h)
│   ├── main.cpp / main_nix.cpp    # 程序入口
│   ├── scribusapp.cpp             # ScribusQApp(QApplication 子类)
│   ├── canvas*.cpp                # 画布、各种画布手势/模式
│   ├── ui/                        # 大量 Qt Widgets 界面
│   └── plugins/                   # 导入/导出滤镜、脚本器等运行时插件
└── harmony_pc/                    # 本次新增的鸿蒙工程壳

鸿蒙适配工程集中放在 scribus/harmony_pc/

harmony_pc/
├── AppScope/app.json5                  # 包名 net.scribus.scribus
├── build-profile.json5                 # 应用级(签名、SDK 版本)
├── entry/
│   ├── build-profile.json5             # 传 -DQT_PREFIX / -DSCRIBUS_FULL_APP
│   ├── libs/arm64-v8a/                  # QPA 插件 + OpenSSL(TLS)
│   └── src/main/
│       ├── cpp/CMakeLists.txt          # 把壳编成 libentry.so(SHELL/FULL 两模式)
│       ├── cpp/scribus_ohos_shell.cpp  # 自包含的 DTP 工作区外壳
│       ├── ets/                         # AbilityStage / EntryAbility / Index
│       └── module.json5
├── hvigor/  oh-package.json5
├── HARMONYOS_PORT.md                    # 适配说明 + FULL 模式路线图
└── qtforharmony_sdk/                   # 项目自带的 Qt 5.15 for Harmony SDK(234MB)

这次没有把 Scribus 重写成 ArkUI,而是让 Qt Widgets 继续负责界面,鸿蒙工程壳负责承载和启动。

在这里插入图片描述

三、鸿蒙工程壳:Ability + XComponent + Qt QPA

Qt for Harmony 的关键思路:ArkTS 侧创建鸿蒙窗口,页面里放一个 XComponent,再由 Qt OpenHarmony QPA 插件把 Qt 窗口挂上去。Index.ets 很薄,核心就是这个 XComponent

XComponent({
  id: this.windowId,
  type: XComponentType.NODE,
  libraryname: 'plugins_platforms_qopenharmony'
})
  .width('100%').height('100%');

EntryAbility.ets 负责窗口生命周期并启动 Qt,onWindowStageCreate 里调用 qpa.startQtApplication(this),QPA 插件会去 dlopen 应用的 libentry.so 并调用它的 qtmain。所以在 Scribus 的入口 scribus/main_nix.cpp 里加一个仅鸿蒙生效的入口别名,转发到 Scribus 原本的 main()

#if defined(Q_OS_OPENHARMONY)
// QPA 插件 dlopen libentry.so 后调用的就是这个 qtmain
extern "C" __attribute__((visibility("default"))) int qtmain(int argc, char *argv[])
{
    return main(argc, argv);
}
#endif

同时把桌面那行只在 X11 下需要的 qputenv("QT_QPA_PLATFORM", "xcb")&& !defined(Q_OS_OPENHARMONY) 守住——鸿蒙用的是 qopenharmony 这个 QPA,不能再设 xcb。这些都在 Q_OS_OPENHARMONY 守卫里,Linux/macOS/Windows 构建一个字节都不变

应用身份用 Scribus 自己的:包名 net.scribus.scribus、label「Scribus」、版本 1.7.4,签名留空交给 DevEco Studio 生成——不要把参考模板项目的 logo / 包名 / 签名复制过来

四、自带 Qt SDK:把 SDK 放进自己项目、引用自己的

适配里有一条硬性要求:Qt for Harmony SDK 必须放进当前项目目录、引用项目内自己的 SDK,而不是引用别的工程的。所以把整套 234MB 的 Qt 5.15 for Harmony SDK 复制到 harmony_pc/qtforharmony_sdk/entry/src/main/cpp/CMakeLists.txtharmony_pc 反向定位项目根,并强制用项目内置 SDK:

get_filename_component(HARMONY_PROJECT_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../../.." ABSOLUTE)
set(QT_PREFIX "qtforharmony_sdk" CACHE STRING "Qt for HarmonyOS SDK path")
if (NOT IS_ABSOLUTE "${QT_PREFIX}")
    get_filename_component(QT_PREFIX "${HARMONY_PROJECT_ROOT}/${QT_PREFIX}" ABSOLUTE)
endif()
if (NOT EXISTS "${QT_PREFIX}/lib/cmake/Qt5/Qt5Config.cmake")
    message(FATAL_ERROR "QT_PREFIX must point to this project's bundled Qt 5 SDK")
endif()

这个 SDK 是 Qt 5.15。Scribus 全量需要 Qt6;对 SHELL 外壳来说 Qt5.15 已够用,Qt6 OHOS 工具链是后续 FULL 模式的前置条件。

五、先验证链路:SHELL 壳(保证能编、能起、能交互)

Scribus 全量是 Qt6 + 一大票还没有 OHOS arm64 版的原生库,直接上全量不现实。所以 CMakeLists.txt 里做了两种构建模式(SCRIBUS_FULL_APP = AUTO | ON | OFF):

  • SHELL(默认 AUTO):只编一个自包含的 scribus_ohos_shell.cpp——一个纯 Qt Widgets 的 DTP 工作区"外壳"不依赖 Scribus 的 scribus/ 源码,保证能编、能起、能交互,先把"ArkTS → XComponent → QPA → libentry.so → 可见 Qt 窗口"这条链路打通。
  • FULL-DSCRIBUS_FULL_APP=ON):编译真正的 Scribus 全量应用。当前会主动 FATAL_ERROR 并列出前置条件——因为 Scribus 是 Qt6,还需要 poppler / cairo / harfbuzz / lcms2 / freetype / fontconfig / libxml2 / hunspell / icu / boost / podofo / GraphicsMagick 等一整套依赖交叉编译到 OHOS arm64 才能开。详见 harmony_pc/HARMONYOS_PORT.md 的路线图。

这个外壳按 Scribus 的排版界面布局实现,包含完整的工作区元素:

  • 菜单栏:File / Edit / Item / Insert / Page / View / Extras / Script / Windows / Help;
  • File 工具栏:New / Open / Save / Close / Print / Save as PDF / Preflight / Undo / Redo / Cut / Copy / Paste;
  • 左侧工具面板:Select Item、Insert Text/Image/Render Frame、Insert Table、Shape、Polygon、Arc、Spiral、Line、Bezier、Freehand、Calligraphic、Rotate、Zoom、Edit Contents/Text、Link Frames、Measurements、Eye Dropper 等 21 个工具;
  • 中间画布:灰色台板上一张白色 A4 页面,画了蓝色边距参考线,选了插入工具后可以在页面上拖拽真的画出框,选择工具可以选中框、显示 8 个控制点;
  • 右侧 Properties 属性面板:Name + Geometry(X-Pos / Y-Pos / Width / Height / Rotation,和选中框双向绑定)+ Shape / Text / Image / Colors 分区;
  • 底部状态栏:单位下拉、缩放(- / % / + / Fit)、页码、item 数、鼠标坐标。

链接时有个老坑要注意:链了 Qt5::Gui 后 hvigor 的 collectAllLibs 会把 qsvg 等图片插件打进来,qsvg 依赖 libQt5Svg.so,所以必须同时链 Qt5::Svg,否则 QPA 加载 libentry.so 时找不到符号、白屏。另外把 QPA 插件、libssl/libcrypto 放进 entry/libs/arm64-v8a/

六、构建踩坑:SDK 版本 + hvigor 版本,一路 SDK component missing

命令行构建时,主要的阻塞不在编译,而是 hvigor 反复报:

hvigor ERROR: 00303168 Configuration Error
Error Message: SDK component missing.

排查过程如下(也可供后续其它 Qt 工程参考):

  1. DEVECO_SDK_HOME 指错。一开始指到 OpenHarmony 的 SDK,而本工程 runtimeOS 是 HarmonyOS。用一段 node 脚本把 hvigor 的 hos-local-component-loader 拿出来,对几个候选根目录分别 .load(),发现只有 /Applications/DevEco-Studio.app/Contents/sdk 这个根能识别出 HarmonyOS 组件。
  2. 版本号对不上。工程里写的是 5.0.5(17)(沿用兄弟工程),但本机根本没装 API 17 的 SDK——只装了 API 22 = 6.0.2(22)~/Library/Huawei/Sdk/21 只是个指向 OpenHarmony 的软链,是个干扰项)。怎么知道该写哪个?把 hvigor 的 hos-version-mapper.jsgetSupportVersions() 打印出来,得到 api↔版本对照表(6.0.2::226.0.1::215.0.5::17…),再结合本机实际装的组件版本,确定要写 6.0.2(22)
  3. hvigor 太旧~/ohos/command-line-tools 自带的 hvigor 是 6.21.2,它的版本表最高只到 api 21,不认识 api 22;必须改用 DevEco Studio 自带的 hvigor 6.22.3

三个一起改对,构建才通:

cd harmony_pc
export NODE_HOME=/Applications/DevEco-Studio.app/Contents/tools/node
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk
export JAVA_HOME=/Users/<>/jdk17/zulu-17.jdk/Contents/Home
export PATH=$NODE_HOME/bin:$JAVA_HOME/bin:$PATH
# 用 DevEco 自带的 hvigorw(6.22.3),不是 command-line-tools 里的旧版
/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw \
  --mode module -p module=entry@default -p product=default assembleHap --no-daemon

并把 harmony_pc/build-profile.json5 里的 compatibleSdkVersion / targetSdkVersion 改成 6.0.2(22)。最终 BUILD SUCCESSFULBuildNativeWithNinja 真正编了 scribus_ohos_shell.cpp(AUTOMOC + 链接),产出签名 HAP:

> hvigor Finished :entry:default@BuildNativeWithNinja... after 1 s 715 ms
> hvigor Finished :entry:default@SignHap... after 957 ms
> hvigor BUILD SUCCESSFUL in 4 s 34 ms
# 产物:entry/build/default/outputs/default/entry-default-signed.hap (约 33MB)

在这里插入图片描述

七、真机部署 + "按钮没反应"的排查与修复

排查:不是鸿蒙的问题,是我这版外壳里有控件没接事件

  • File 工具栏的 Open / Save / Print / Save as PDF / Undo / Redo / Cut / Copy / Paste 原本是死按钮(只有 New 接了,而且只调了一次没有可见效果的刷新);
  • 菜单栏里每个菜单只放了一条灰色(disabled)占位项;
  • 左侧工具按钮其实接了(会切换当前工具、按钮变蓝),但点完没有任何文字反馈,要等你在页面上拖拽才看得出来——所以"感觉没反应"。

修复:给每个可点控件都接上行为 + 状态栏即时反馈(statusBar()->showMessage(...))。工具按钮点击→高亮 + 提示"drag on the page to create a frame";File / Edit / Item / Insert / View 菜单和工具栏→真实操作或清晰提示;新增了新建 / 剪切 / 复制 / 粘贴 / 删除的真实逻辑和一个帧剪贴板;从菜单选工具会同步左侧面板的选中态。

改完重新构建、重装,然后自己把鸿蒙 PC 当测试机,用 hdc 注入真实点击和拖拽,逐项验证。这里有个关键点:Qt 的控件是画在 XComponent 里的,鸿蒙的 uitest dumpLayout 看不到这些控件,所以只能按屏幕像素坐标点(先截图、量坐标、再点)。

第一步,点左侧"Insert Image Frame"工具:

HDC=/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc
"$HDC" shell uitest uiInput click 1083 778    # 坐标按你截图量出来的为准

点完按钮变蓝高亮,状态栏出现 Insert Image Frame — drag on the page to create a frame——按钮响应了

在这里插入图片描述

第二步,在页面上拖拽画一个图像框:

"$HDC" shell uitest uiInput drag 1331 828 1559 1102 500   # 起点 → 终点,500ms

页面上真的出现了一个带 X 交叉线的图像框 Image1、带 8 个控制点;右侧 Properties 面板同步绑定了它的几何(X-Pos 125 / Y-Pos 258 / Width 228 / Height 274),状态栏从 0 items 变成 1 items。这说明工具选择、画布交互、属性面板绑定整条都通了。

在这里插入图片描述

第三步,点之前完全失灵的"Save"工具栏按钮:

"$HDC" shell uitest uiInput click 1198 617

状态栏显示 Saved (demo) — 1 item(s)——原本的死按钮现在也响应了。至此三类原本"没反应"的控件(工具按钮、画布交互、File 工具栏按钮)全部验证通过。

在这里插入图片描述

八、阶段性结果与边界

到目前为止,Scribus 在鸿蒙 PC 上完成了一个可安装、可启动、可交互的外壳版本:

  1. 可以作为 HAP 安装到鸿蒙 PC,通过 Stage UIAbility 启动 Qt 应用。
  2. 跑的是一个自包含的 DTP 工作区外壳(菜单栏、File 工具栏、21 个工具的工具面板、页面+台板画布、Properties 属性面板、状态栏),打通了"ArkTS → XComponent → QPA → libentry.so → 可见、可交互的 Qt 窗口"整条链路。
  3. 项目内置自己的 qtforharmony_sdk,DevEco Studio 导入后可直接构建;桌面构建(Linux/macOS/Windows)完全不受影响(鸿蒙改动全在 Q_OS_OPENHARMONY 守卫里)。
  4. 交互验证通过:工具按钮高亮 + 状态栏提示、页面拖拽建框、Properties 与选中项双向绑定、原本失灵的工具栏按钮全部响应——并且是用 hdc + uitest真机上逐项点出来、截图确认的。
  5. 摸清了本机构建鸿蒙 HAP 的正确组合:DevEco 自带 hvigor 6.22.3 + DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk + 版本号 6.0.2(22)

同时也明确目前的边界(后续可继续推进):

  1. 这是外壳,不是 Scribus 全量。全量(FULL 模式)需要先把 Qt 6 for HarmonyOS 工具链(当前自带 SDK 是 Qt5.15,够壳不够全量)以及 poppler / cairo / harfbuzz / lcms2 / freetype / fontconfig / libxml2 / hunspell / icu / boost / podofo / GraphicsMagick 等交叉编译到 OHOS arm64 再 bundle 进来,CMake 的 FULL 分支才能从 FATAL_ERROR 占位换成真正镜像 scribus/CMakeLists.txt
  2. 全量后还要把 Scribus 运行时加载的导入/导出插件改成静态编入 + Q_IMPORT_PLUGIN,并把 share/scribus(配色、模板、连字典典、翻译等)打进 HAP、在沙箱里用 ScPaths 指对位置(EntryAbility 里已预留了"首启把 rawfile 解压到沙箱"的逻辑)。

这次适配的两点记录:其一,大型 Qt 工程上鸿蒙 PC,先用一个自包含外壳打通链路、再推进全量,是一条可行的路线;其二,控件"没反应"时先确认是否绑定了事件——很可能是外壳里未接事件,给每个控件接上可见反馈(状态栏一行提示)即可避免"看起来没响应"的情况;验证方式是用 hdc + uitest 在真机上逐项点击确认。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐