鸿蒙PC迁移:KTouch Qt/QML 打字训练器适配全记录
鸿蒙PC迁移:KTouch Qt/QML 打字训练器适配全记录
一、写在前面
欢迎加入鸿蒙PC开发者社区,共同打造开发者工具生态:鸿蒙PC开发者社区:https://harmonypc.csdn.net/
项目开源地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_KTouch
欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
这篇文章记录的是 KTouch 在 HarmonyOS / OpenHarmony PC 环境中的一次适配实践。
KTouch 是 KDE 项目里的打字训练器,主要用于学习键盘布局、练习课程文本、统计打字速度和错误率。它和很多 Electron 项目不同:KTouch 不是前端页面套壳,也不是一个简单的 Qt Widgets 应用,而是一个 C++ / Qt Quick / QML 项目,并且原始桌面版本依赖 KDE Frameworks、Kirigami、QtGraphicalEffects、Qt Quick Controls、课程数据、键盘布局数据和训练统计模型。
所以这次迁移的难点不是“把 HTML 资源放进 HAP”,而是下面这一组问题:
- 怎样让一个 Qt/QML 桌面应用进入鸿蒙 Stage 模型。
- 怎样让
libentry.so作为 HAP native library 被启动。 - 怎样把 Qt Quick 界面绘制到鸿蒙
XComponent上。 - 怎样把 Qt for Harmony 的 QPA 插件、Qt 动态库、QML 模块一起打包进去。
- 怎样在不完整移植 KDE Frameworks 的前提下,用本地 shim 支撑 KTouch 的首个 MVP。
- 怎样处理 Harmony Qt 运行时里 QML 属性、图标、特效、颜色计算和键盘渲染的兼容问题。
本次适配采用的是一条最快可验证路线:借鉴 Phototonic Qt 项目的鸿蒙适配思路,保留 KTouch 原有 C++ / Qt / QML 主体,把鸿蒙侧收敛成一个 harmony_pc/ 工程壳。这样可以先让应用在鸿蒙 PC 上启动、显示首页、进入打字训练页,再逐步补齐完整课程、设置页和更深层的 KDE 能力。

二、项目背景:这不是 Electron,而是 Qt/QML + KDE 风格应用
原始 KTouch 的主体在 ktouch-master/ 目录中,鸿蒙适配工程放在同级源码树下的 harmony_pc/:
ktouch-master/
├── src/
│ ├── application.cpp
│ ├── main.cpp
│ ├── mainwindow.cpp
│ ├── core/
│ ├── models/
│ ├── declarativeitems/
│ └── qml/
│ ├── common/
│ ├── homescreen/
│ ├── keyboard/
│ ├── trainingscreen/
│ ├── org/kde/kirigami/
│ └── QtGraphicalEffects/
└── harmony_pc/
├── AppScope/
├── build-profile.json5
├── entry/
│ ├── build-profile.json5
│ └── src/main/
│ ├── cpp/
│ ├── ets/
│ └── resources/
└── qtforharmony_sdk/
KTouch 的主界面不是 ArkUI 写出来的,而是原来的 Qt Quick / QML 界面。鸿蒙侧主要负责创建窗口、承载绘制区域、启动 native Qt 应用。真正的业务逻辑仍然来自原项目:
src/core/ 课程、键盘布局、训练数据等核心模型
src/models/ 首页课程、Lesson、进度等模型
src/declarativeitems/ 暴露给 QML 的自定义绘制和训练组件
src/qml/ KTouch 的 QML 页面和控件
harmony_pc/entry/src/main/cpp/openharmony_compat/
本次为鸿蒙 MVP 准备的 KDE/Qt 兼容 shim
harmony_pc/entry/src/main/cpp/openharmony_data/
首个 HAP 内置的最小课程与键盘布局数据

三、最快路线:借鉴 Phototonic 的 Qt for Harmony wrapper
这次没有选择重写 ArkUI,也没有一开始就完整移植 KDE Frameworks。最快路线是:
- 保留 KTouch 原来的 C++ / QML 代码。
- 新增
harmony_pc/作为鸿蒙 Stage 工程。 - ArkTS 侧创建 Ability 和
XComponent。 - 通过 Qt for Harmony 的 QPA 插件把 Qt 窗口挂到鸿蒙窗口上。
- CMake 把 KTouch 编译成
libentry.so。 - 将 Qt 动态库、QML 模块、平台插件和 KTouch 资源一起打入 HAP。
- 用少量本地 shim 替代首个版本里必须用到的 KDE Frameworks 能力。
这条路线的关键点在于:先让 KTouch 以 Qt 应用的身份跑起来,而不是把它改造成一个新的 ArkUI 应用。
鸿蒙工程的应用信息已经改成 KTouch:
{
"app": {
"bundleName": "org.kde.ktouch",
"vendor": "KDE",
"versionCode": 1000000,
"versionName": "1.0.0-ohos",
"icon": "$media:layered_image",
"label": "$string:app_name"
}
}
Entry Ability 使用:
BundleName: org.kde.ktouch
Ability: EntryAbility
Module: entry
这里有一个适配经验:如果复用 Phototonic 或其他 Qt 项目的鸿蒙模板,不能只改 native 源码。包名、应用名、图标、启动页图标、Ability label、layered_image 都要同步替换。否则应用可以启动,但桌面图标和启动窗口仍然会保留模板项目的痕迹。

四、CMake 打包策略:让 KTouch 生成 libentry.so
鸿蒙 native 侧的入口在:
harmony_pc/entry/src/main/cpp/CMakeLists.txt
它会从 harmony_pc 反向定位到 KTouch 源码根目录,然后把 KTouch 的核心 C++ 文件、QML 资源、图片资源和鸿蒙本地兼容代码一起编进 entry:
add_library(entry SHARED ${KTOUCH_SOURCES})
本次 MVP 的编译定义包括:
KTOUCH_HARMONY_MVP
OPENHARMONY
NAPI_DISABLE_CPP_EXCEPTIONS
链接的 Qt 模块不只包含基础的 Core 和 Gui,还要包含 KTouch QML 界面实际依赖的模块:
Qt5::Core
Qt5::Gui
Qt5::OhExtras
Qt5::Qml
Qt5::Quick
Qt5::QuickControls2
Qt5::QuickTemplates2
Qt5::QuickWidgets
Qt5::Sql
Qt5::Svg
Qt5::Widgets
Qt5::Xml
Qt5::QOpenHarmonyPlatformIntegrationPlugin
Qt5::QSQLiteDriverPlugin
其中 Qt5::QuickTemplates2 很容易被忽略,但它恰好是这次白屏问题的核心之一。KTouch 使用 Qt Quick Controls 2,而 libQt5QuickControls2.so 运行时还需要 libQt5QuickTemplates2.so。如果 HAP 里没有把它带进去,native 应用入口会加载失败,界面自然只能白屏。
五、第一次运行白屏:不是 QML 页面问题,而是缺少 Qt 动态库
签名完成后安装 HAP,启动应用时出现白屏。此时最关键的不是盲目改 QML,而是先看 hilog。
当时日志里有非常明确的一行:
Failed to load QT application '/data/storage/el1/bundle/libs/arm64/libentry.so':
Error loading shared library libQt5QuickTemplates2.so:
No such file or directory
(needed by /data/storage/el1/bundle/libs/arm64/libQt5QuickControls2.so)
这说明问题发生在 Qt 应用加载阶段,libentry.so 还没有真正跑起来。也就是说,白屏不是首页 QML 写错了,也不是课程数据没加载,而是 native library 的依赖链不完整。
修复思路很直接:在鸿蒙 native 打包链路中加入 libQt5QuickTemplates2.so,同时 CMake 链接 Qt5::QuickTemplates2,确保 HAP 安装后 /data/storage/el1/bundle/libs/arm64/ 下能找到它。
修复后,日志能进入 Qt/QML 初始化阶段,并看到类似状态:
KTouch QQuickWidget status changed "Ready"
这个状态非常重要,它说明 Qt 应用已经加载,QML 界面也已经进入 Ready 状态。之后看到的黑块、图标缺失、键盘颜色异常,就不再是启动链路问题,而是 QML 兼容和视觉渲染问题。
六、构建、安装和截图命令
本次鸿蒙工程位于:
~/XM/ktouch-master/harmony_pc
命令行构建 HAP:
cd ~/XM/ktouch-master/harmony_pc
~/ohos/command-line-tools/bin/hvigorw assembleHap
签名后的 HAP 路径:
~/XM/ktouch-master/harmony_pc/entry/build/default/outputs/default/entry-default-signed.hap
安装到鸿蒙设备:
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc install -r ~/XM/ktouch-master/harmony_pc/entry/build/default/outputs/default/entry-default-signed.hap
启动应用:
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc shell aa force-stop org.kde.ktouch || true
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc shell aa start -b org.kde.ktouch -a EntryAbility
清理并查看日志:
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc shell hilog -r || true
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc shell aa start -b org.kde.ktouch -a EntryAbility
抓取真机截图:
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc shell snapshot_display -f /data/local/tmp/ktouch.jpeg
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc file recv /data/local/tmp/ktouch.jpeg ~/XM/ktouch-master/ktouch.jpeg

七、KDE Frameworks 不完整移植:先用本地 shim 支撑 MVP
KTouch 原始项目使用了一些 KDE 生态能力,例如:
KLocalizedString
KAboutData
KColorScheme
KCategorizedSortFilterProxyModel
Kirigami Icon
org.kde.coreaddons
org.kde.charts
如果一开始就完整移植 KDE Frameworks,工程量会明显变大,而且会把问题从“让 KTouch 跑起来”扩散到“先移植一批 KDE 基础库”。这不符合首个 HAP 验证的目标。
因此本次采用 MVP shim 思路:只实现 KTouch 当前页面必须使用的最小行为。鸿蒙侧兼容代码位于:
harmony_pc/entry/src/main/cpp/openharmony_compat/
这些 shim 的作用包括:
KLocalizedString:返回原始字符串,并支持简单%1替换。OhosI18nContext:向 QML 暴露i18n()、i18nc()。KAboutData:给错误对话框等位置提供应用显示名。KColorScheme:用 Qt palette 兜底 KTouch 需要的颜色角色。KCategorizedSortFilterProxyModel:提供资源模型里使用的分类角色常量。preferences.h:用QSettings存储首个版本需要的偏好设置。
这种方式的好处是边界清晰:不是伪装成完整 KDE Frameworks,而是明确服务于 KTouch 首个可运行版本。等首页、训练页和数据链路稳定后,再决定是否继续扩展 shim,或者移植更完整的 KDE 模块。
八、QML 兼容坑:有些桌面 Qt 写法在鸿蒙上不能直接用
应用从白屏进入首页以后,又遇到了一类更细的问题:QML 在桌面 Qt 上能跑,但在 Harmony Qt 运行时下会出现属性不支持、类型转换异常或渲染结果不对。
本次重点规避了这些写法:
font.pixelSize
font.pointSize
font.bold
font.weight
font.family
font.italic
Qt.vector3d
origin.x / origin.y
Qt.tint
color.r / color.g / color.b / color.a
其中一个典型报错是:
Cannot assign to non-existent property "pixelSize"
还有一个训练页里的运行时问题来自:
running: trainingLine.active && trainingLine.activeFocus && Qt.application.active
在鸿蒙运行时中,Qt.application.active 有可能拿到 undefined,导致 bool 绑定失败。修复后改成更宽容的判断:
running: trainingLine.active && trainingLine.activeFocus && (!Qt.application || Qt.application.active !== false)
这个改动的含义是:只要应用对象不存在,或者没有明确处于 inactive,就允许光标动画继续运行。它避免了 Harmony Qt 下 undefined 被直接绑定到 bool 属性的问题。
九、首页黑块:QtGraphicalEffects 需要做 fallback
修掉缺库以后,KTouch 首页能出来,但视觉上仍然有明显异常:部分区域出现黑块,阴影、发光和遮罩效果不像桌面版那样正常。
这类问题主要集中在 QtGraphicalEffects。桌面 Qt 中的 DropShadow、Glow、RectangularGlow、FastBlur、OpacityMask 等效果依赖 shader 和离屏渲染,在 Harmony Qt 环境里表现不稳定时,很容易出现大块不透明黑色矩形。
这次没有强行保留所有特效,而是在项目里提供了一组最小 fallback:
src/qml/QtGraphicalEffects/DropShadow.qml
src/qml/QtGraphicalEffects/Glow.qml
src/qml/QtGraphicalEffects/RectangularGlow.qml
src/qml/QtGraphicalEffects/FastBlur.qml
src/qml/QtGraphicalEffects/OpacityMask.qml
src/qml/QtGraphicalEffects/HueSaturation.qml
src/qml/QtGraphicalEffects/Desaturate.qml
处理原则是:
- 对容易产生黑块的
DropShadow、Glow,先用不可见 Item 兜底,避免污染界面。 - 对键盘高亮需要的
RectangularGlow,用低透明度 Rectangle 模拟可接受的发光区域。 - 对
OpacityMask、FastBlur等效果,尽量保留源内容显示,弱化特效本身。
对首个鸿蒙版本来说,视觉效果可以比桌面版简化,但不能出现黑块、遮挡和不可点击区域。这是 Qt/QML 桌面应用迁移到鸿蒙时非常实用的一条原则:先保证界面可读、可点、可验证,再追求完全一致的 shader 效果。

十、图标全部消失:Kirigami Icon 不能直接依赖桌面主题
KTouch 首页和训练页里有很多图标,例如:
go-home
go-next-view
view-refresh
dialog-ok
dialog-cancel
dialog-warning
object-locked
view-statistics
application-menu
桌面 KDE 环境中,这些图标通常来自系统图标主题和 Kirigami。但在鸿蒙 HAP 里,系统图标主题并不会像 Linux 桌面那样天然存在。如果仍然按桌面方式加载,结果就是:按钮位置还在,文字还在,但图标全部不显示。
本次修复思路是给 org.kde.kirigami/Icon.qml 做一个本地 Harmony fallback,用 QML Canvas 画出常用图标。核心不是画得多复杂,而是要解决两个问题:
- 图标必须可见。
- 图标尺寸必须稳定,不能把按钮、标题栏、Lesson 卡片撑变形。
因此 Icon.qml 中根据 source 做简单映射:
if (source.indexOf("go-home") !== -1)
return "home"
if (source.indexOf("refresh") !== -1)
return "refresh"
if (source.indexOf("lock") !== -1)
return "lock"
if (source.indexOf("menu") !== -1)
return "menu"
然后在 Canvas 里画 home、next、refresh、check、warning、lock、chart、menu、keyboard、user、plus、edit、info 等基础图形。
同时,通用控件也做了尺寸收敛:
src/qml/common/IconLabel.qml
src/qml/common/IconToolButton.qml
src/qml/homescreen/LessonLockedNotice.qml
例如 IconLabel.qml 增加稳定的图标尺寸:
property int iconSize: 22
Kirigami.Icon {
width: label.iconSize
height: label.iconSize
}
IconToolButton.qml 则给纯图标按钮设置更稳定的隐式尺寸:
implicitWidth: text === "" ? 36 : content.implicitWidth
implicitHeight: Math.max(36, content.implicitHeight)
LessonLockedNotice.qml 中,锁图标也从过大的视觉尺寸收敛到更适合首页卡片的大小,避免图标压住提示文字。
十一、进入打字页:返回按钮不是问题,图标缺失才是问题
进入 KTouch 的打字训练页后,第一眼容易误判为“没有返回按钮”。实际进一步看原始软件设计可以发现,KTouch 的返回入口通常是左上角的 Home 图标按钮,而不是一个写着“返回”的文本按钮。
也就是说,问题不是原设计缺少返回按钮,而是图标没有渲染出来时,这个入口看起来像消失了。等 Kirigami Icon fallback 生效后,左上角 Home 图标就能作为返回首页入口显示出来。
这个问题提醒我们:迁移桌面软件时,不能只判断“按钮有没有占位”,还要看图标主题、控件尺寸、颜色和 hover/focus 状态是否实际可见。尤其是 KDE/Kirigami 风格应用,很多导航语义都藏在图标里。一旦图标资源丢失,用户会直接认为功能丢了。
十二、键盘颜色异常:不能依赖 color.r/g/b/a 和 Qt.tint
进入训练页后,还遇到一个更影响体验的问题:虚拟键盘颜色不对。按键区域一度接近全黑,手指分区颜色也没有按预期显示。
KTouch 的键盘颜色本来有一套逻辑:普通按键有灰色渐变,不同手指负责的按键再叠加不同颜色。桌面端这样可以让用户直观看到每个手指应该负责哪些键。
但在 Harmony Qt 下,直接读取颜色的 r/g/b/a 子字段、或者依赖 Qt.tint 这类桌面写法并不稳。为了让键盘颜色恢复,本次在 src/qml/keyboard/KeyItem.qml 中改成更直接的数值计算方式。
核心思路是:不要从一个 color 对象里拆 r/g/b/a,而是根据 fingerIndex 显式返回 RGB 通道,再和基础灰色做混合。
简化后的逻辑类似:
function fingerChannel(index, channel) {
switch (index) {
case 0:
case 7:
return channel === 0 ? 1.0 : 0.0
case 1:
case 6:
return channel === 2 ? 1.0 : 0.0
case 2:
case 5:
return channel === 1 ? 1.0 : 0.0
case 3:
return channel === 2 ? 0.0 : 1.0
case 4:
return channel === 1 ? 0.0 : 1.0
}
return 0.0
}
function tintedColor(baseR, baseG, baseB) {
if (!key || key.keyType() != "key") {
return Qt.rgba(baseR, baseG, baseB, 1)
}
var opacity = tintOpacity()
var fingerIndex = key.fingerIndex
return Qt.rgba(
baseR * (1 - opacity) + fingerChannel(fingerIndex, 0) * opacity,
baseG * (1 - opacity) + fingerChannel(fingerIndex, 1) * opacity,
baseB * (1 - opacity) + fingerChannel(fingerIndex, 2) * opacity,
1)
}
同时把 disabled 状态的渐变从过暗的颜色调亮:
State {
name: "disabled"
PropertyChanges {
target: gradientStop0
color: item.tintedColor(0.42, 0.42, 0.42)
}
PropertyChanges {
target: gradientStop1
color: item.tintedColor(0.34, 0.34, 0.34)
}
PropertyChanges {
target: gradientStop2
color: item.tintedColor(0.27, 0.27, 0.27)
}
}
修复后,训练页能看到不同手指负责区域的彩色键位,特殊键保持灰白或深灰,F/J 的触觉定位标记也更容易识别。

十三、应用图标也要单独处理:不要留下模板项目 logo
这次还有一个非常容易在复用模板时出现的问题:应用已经是 KTouch,但桌面 logo 仍然像之前的 phototonic-ohos。
原因通常不是 native 代码,而是鸿蒙资源还沿用了模板:
harmony_pc/AppScope/app.json5
harmony_pc/entry/src/main/module.json5
harmony_pc/entry/src/main/resources/base/element/string.json
harmony_pc/entry/src/main/resources/base/media/layered_image.json
harmony_pc/entry/src/main/resources/base/media/background.png
harmony_pc/entry/src/main/resources/base/media/foreground.png
harmony_pc/entry/src/main/resources/base/media/startIcon.png
最终需要确认:
bundleName是org.kde.ktouch。vendor是KDE或实际发布方。- 应用名、Ability label 显示为
KTouch。 layered_image、foreground.png、background.png、startIcon.png都已经换成 KTouch 相关图标。- 安装前最好卸载旧包或覆盖安装后重启应用,避免桌面缓存导致误判。
Qt 应用迁移时,大家容易把注意力都放在 libentry.so 和 QML 上,但用户第一眼看到的是图标、标题和启动页。这个地方如果还保留模板项目,会直接影响适配完成度的观感。
十四、真机验证:从白屏到可进入训练页
最终验证不只看应用能不能启动,而是要走一遍完整链路:
- 安装 HAP。
- 从桌面启动 KTouch。
- 首页能显示课程和 Lesson。
- 锁图标、Home 图标、菜单图标能显示。
- 点击 Lesson 进入训练页。
- 训练文本区域正常显示。
- 虚拟键盘完整显示。
- 键盘分区颜色正常。
- 左上角 Home 图标能作为返回入口。
- 日志没有再出现致命的 QML 属性错误或 native 缺库错误。
这次排障过程中,关键节点其实很清晰:
白屏
↓
hilog 定位 libQt5QuickTemplates2.so 缺失
↓
补齐 Qt Quick Templates 依赖
↓
QML Ready,但首页出现黑块
↓
QtGraphicalEffects fallback
↓
首页可读,但图标缺失
↓
Kirigami Icon Canvas fallback
↓
训练页可进入,但键盘颜色过暗
↓
KeyItem.qml 改为显式 finger color 混合
↓
首页和训练页均达到可验证状态
十五、迁移经验总结
这次 KTouch 适配下来,有几个经验比较值得复用。
第一,Qt 项目的鸿蒙迁移可以优先走 wrapper 路线。对于已经有成熟 C++ / Qt / QML 代码的应用,最快的路线不是重写,而是先通过 Ability + XComponent + Qt QPA + libentry.so 把原应用跑起来。这样可以快速暴露真实设备上的问题。
第二,白屏要先看 native 依赖链。KTouch 的第一次白屏不是 QML 写错,而是缺少 libQt5QuickTemplates2.so。Qt Quick Controls 2 相关库之间有运行时依赖,HAP 里少一个 .so,整个 Qt 应用入口就会失败。
第三,KDE Frameworks 不一定要一次性完整移植。对首个 MVP 来说,KLocalizedString、KColorScheme、Kirigami Icon 等能力可以先用本地 shim 支撑,等应用可运行后再决定是否扩展。
第四,Harmony Qt 下的 QML 兼容要保守。像 font.pixelSize、font.bold、Qt.tint、Qt.vector3d、origin.x/y、color.r/g/b/a 这些桌面写法,在鸿蒙环境里要重点排查。能用显式数值和简单绑定解决的,就不要依赖复杂隐式行为。
第五,视觉 fallback 的优先级是“不要遮挡”。阴影、发光、模糊这些效果如果不能稳定渲染,宁可降级,也不要让它们变成黑块盖住内容。
第六,图标不是装饰,而是功能入口。KTouch 的返回首页入口就是左上角 Home 图标。图标主题丢失时,用户看到的不是“图标没了”,而是“返回功能没了”。所以 Kirigami Icon fallback 是这次适配中非常关键的一环。
第七,模板复用一定要清干净。包名、应用名、logo、启动图标、Ability label 都要替换,否则软件功能跑起来了,外观却像另一个项目。
十六、最终结果
本次 KTouch 鸿蒙 PC 适配已经完成了首个可验证版本:
harmony_pc/工程可以构建签名 HAP。- HAP 可以安装到鸿蒙 PC 设备。
org.kde.ktouch / EntryAbility可以启动。- 缺失
libQt5QuickTemplates2.so导致的白屏问题已定位并修复。 - KTouch 首页可以渲染课程和 Lesson。
- QtGraphicalEffects 黑块问题通过 fallback 缓解。
- Kirigami 图标通过 Canvas fallback 恢复显示。
- 训练页可以进入,Home 图标作为返回入口显示。
- 虚拟键盘颜色恢复,手指分区可见。
- 应用名称和图标资源从模板项目替换为 KTouch。
后续如果继续完善,可以从三个方向推进:
- 扩展内置课程和键盘布局数据,让 HAP 不只包含最小 US 数据集。
- 继续完善 KDE/Kirigami shim,让设置、统计、偏好项等页面更接近桌面版。
- 对 QtGraphicalEffects 做更精细的 Harmony 实现,逐步恢复阴影、发光、遮罩等视觉细节。
这次适配最有价值的地方在于,它验证了 Qt/QML + KDE 风格应用在鸿蒙 PC 上的迁移路径:先通过 Qt for Harmony wrapper 接入,再用最小 shim 和 QML fallback 解决运行时差异。对类似的 Qt Quick 桌面应用来说,这条路线比从零重写更快,也更容易保留原项目的核心体验。
更多推荐


所有评论(0)