A
Air官方
【Air 系列 · 鸿蒙 PC 适配全记录】51 / 56
■ 应用:轻载(from qBittorrent)(AirTorrent)
■ 定位:BT 下载(qBittorrent,μTorrent 同类工具)
■ 上游:qBittorrent | 路线:Qt6 6.11.1+QPA | 版本:1.0.0
■ 市场状态:开发中,即将上架
■ 官网(免费下载): https://www.shunidc.cn/
————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/
PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:(AtomGit 仓库建立后回填)
环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743
▍一、为什么要适配 qBittorrent
qBittorrent 是桌面 Linux/Windows/macOS 上使用最广的开源 BT 客户端之一:libtorrent-rasterbar 引擎加 Qt 界面,覆盖种子解析、磁力链接、DHT、uTP、做种与 WebUI 远程管理。它依赖链条长、后台线程多、网络行为复杂,向来是检验一个新系统桌面承载能力的硬题目。把它带到鸿蒙 PC,验证的不只是"窗口能不能画出来",而是沙箱网络栈、C++20 工具链与 Qt 桌面栈能否支撑一个真实的重型网络应用。
本工程(应用名轻种子,工程 AirTorrent)选择的是直移路线:上游源码不做界面重写,而是把 qBittorrent 5.2.3 与 libtorrent-rasterbar 分别交叉编译为动态库,由 ArkTS 壳经 XComponent 承载 Qt 界面。这条路在工程族内有明确血统——Qt6.11 预编译栈 + ohos QPA 与 AirCast/AirCad 一脉相承,Qt Widgets raster 渲染线与 AirDraft/AirBase 同款,构建脚本与桥接层以 AirBase(sqlitebrowser 移植)为蓝本照抄改前缀。
当前鸿蒙工程版本为 1.0.0,包名 com.airtorrent.app,支持 phone、tablet 与 2in1,声明 ohos.permission.INTERNET 权限并携带 backup 扩展。产物为 80.4 MB 签名 HAP,内含 26 个动态库。主验证设备为 MatePad 11.5(tablet),参考机为 MateBook Pro 2in1 与 Mate 60 Pro。工程于 2026-09-25 立项,当日完成从骨架到真机首启再到网络栈定位的全部六个里程碑,PORTING.md 对每一步都有记录。
▍二、先划清边界:不把编译通过当成适配完成
qBittorrent 原版以桌面可执行程序形态发布,主窗口、菜单、系统托盘、文件对话框全部假定运行在有窗口管理器的桌面环境。交叉编译出 libqbittorrent.so 只说明代码能通过 musl 工具链,不等于 BT 会话能在沙箱里联网、界面能被触摸操作。工程因此把验收锚点放在三层:库能加载、会话能通网、交互能闭环——当前前两层已经真实达成,第三层仍在进行中。
层次 原项目实现(桌面 Linux) 鸿蒙侧当前方案
应用入口 桌面 main() 可执行程序 P1 补丁导出 airtorrent_main,EntryAbility 经桥启动
窗口承载 X11/Wayland + 原生 QMenuBar XComponent SURFACE + ArkTS bindMenu 菜单桥
BT 引擎 libtorrent-rasterbar 系统库 本工程交叉编译 libtorrent-rasterbar.so 2.0.11
网络状态通知 Linux netlink ip_notifier P6 补丁在 __OHOS__ 下跳过(沙箱拒 AF_NETLINK bind)
会话线程模型 主进程直接构造 Session 宿主后台线程驱动,规避 appfreeze 看门狗
下载落盘 任意用户目录 沙箱 files/downloads,用户目录授权待接
文件对话框 桌面 QFileDialog QPA pick 请求经 TSFN 转 DocumentViewPicker
配置与状态 QSettings + QSQLITE 同栈 libqsqlite.so 部署子目录 + QT_PLUGIN_PATH
必须说明:截至 PORTING.md 最新记录,磁力链接的界面全链路(对话框焦点、软键盘、按钮时序)尚不稳定,菜单的 ArkTS 化桥接符号也未接通。当前版本的准确形态是"网络栈已闭环、交互闭环进行中"的直移工程,不是开箱即用的成品。
▍三、鸿蒙版的整体架构
工程按 Air 双层范式组织:ArkTS 壳只负责窗口、权限与系统能力代理;native 侧由一个薄 NAPI 桥和一个 core 库组成,core 负责按拓扑序 dlopen 全部依赖、前置 Qt 环境变量,并在宿主线程上调用 airtorrent_main。
text
ArkTS 壳(entry/src/main/ets/pages/Index.ets)
├── libairtorrentbridge.so(薄 NAPI:TSFN 六通道 pick/cursor/ime/clip/ui/dialog)
▼ OnSurfaceCreated → 拓扑序 dlopen(RTLD_GLOBAL)
libairtorrent_core.so(env 前置 + QPA 接线 + 宿主线程驱动)
├── Qt6Core→Gui→Widgets→Network→Sql→Xml→Svg→Concurrent→OpenGL
├── libqohos.so(QPA)→ libssl→libcrypto→libicuuc→libzstd→libpng16
├── libtorrent-rasterbar.so → libqbittorrent.so(上游 GUI→库)
└── sqldrivers/libqsqlite.so(QT_PLUGIN_PATH 指向 bundle libs)
主要目录如下:
text
AirTorrent/
├── PORTING.md # 依赖矩阵、补丁清单、风险登记、踩坑映射
├── AppScope/app.json5 # 包名 com.airtorrent.app
├── native/
│ ├── env.sh # NDK SDK24 + AirGis Qt6.11 前缀环境
│ ├── patches/
│ │ ├── 0001-qbt-5.2.3-ohos-p1-p5.patch
│ │ ├── 0002-libtorrent-2.0.11-ohos-p6.patch
│ │ └── qbt_ranges_compat.h # std::ranges 兼容层
│ ├── scripts/
│ │ ├── build_libtorrent.sh # 2.0.11 交叉编译
│ │ ├── build_qbittorrent.sh # 上游 SHARED 库构建
│ │ └── deploy.sh # 26 个 .so 收集、strip 与 NEEDED 改写
│ ├── install/ # libtorrent 产物与 cmake config
│ └── install-static/ # 探针静态链前缀
└── entry/src/main/
├── ets/pages/Index.ets # XComponent、菜单桥、IME、选择器
├── ets/utils/FileManager.ets # 系统选择器封装
├── ets/utils/SubWindowManager.ets
└── cpp/
├── airtorrent_core.cpp # dlopen 拓扑、宿主线程、/netprobe 探针
├── napi_init.cpp # 六通道 TSFN 桥
└── qt_platform/ohos_qt6/ # libqohos QPA(AirBase 复用)
▍四、核心适配过程
### 1. 上游 GUI 改库:P1 与 P2 补丁
补丁以 Upstreams/qbittorrent 内的 git 基线维护,git diff 归档为 native/patches/0001-qbt-5.2.3-ohos-p1-p5.patch。P1 在 src/app/main.cpp 用 __OHOS__ 守卫导出 extern "C" int airtorrent_main(int, char**),入口先关闭触摸/鼠标互合成(AA_SynthesizeMouseForUnhandledTouchEvents=false,AirDraft 验证过的做法),再进入原版 main 逻辑。P2 改 CMakeLists.txt:以编译器 triple 探测 OHOS 交叉环境,把可执行目标改为 add_library(SHARED) 输出 libqbittorrent.so,并跳过 WIN32/MACOSX_BUNDLE/CPack/DBUS/SYSTEMD 等桌面专属分支——上游 CMAKE_SYSTEM_NAME=Linux 会条件性打开 DBUS,这里显式关闭。
### 2. libtorrent 双版本试错与依赖矩阵
build_libtorrent.sh 先试 2.1.2,因 qBittorrent 5.2.3 依赖的 mapped_files API 在 2.1 中被移除而回退 2.0.11(qbt 下限 2.0.10)。构建开关有三处来自实测:webtorrent=OFF(release tarball 不含 libdatachannel 子模块)、deprecated-functions=ON(qbt 调用的 torrent_info(bdecode_node, error_code&) 构造位于 deprecated 区块)、ld-version-script=OFF(musl 不认版本化导出,AirBase 律)。依赖全部来自工程族共享栈:Qt6 取 AirGis 的 6.11.1 预编译前缀,Boost 1.85 头文件取 AirCad 前缀,OpenSSL 3.4.1 由 Qt6Network 与 libtorrent 双方消费。
### 3. core 库的 dlopen 拓扑与 QPA 接线
airtorrent_core.cpp 不静态链接 Qt,而是在运行时按依赖拓扑 dlopen 约 20 个库(RTLD_GLOBAL,musl 按名解析),顺序为 Qt6 栈 → QPA → sqlite 插件 → libtorrent → 本体。环境变量在 QApplication 构造前前置:QT_QPA_PLATFORM=ohos、插件路径指向 bundle libs、QT_QPA_FONTDIR=/system/fonts、HOME/XDG/TMPDIR 全部落沙箱。QPA 插件 libqohos.so(1.3 MB)从 AirBase 整目录复用零修改。宿主线程在 dlopen 链完成后调用 airtorrent_main,保证 QApplication 构造线程即 Qt 主线程,同时把重初始化隔离在后台线程以避开 5 秒 appfreeze 看门狗。
### 4. TSFN 六通道桥与 ArkTS 侧代偿
napi_init.cpp(1146 行)建立六条 napi_threadsafe_function 通道:文件选择、光标样式、输入法、剪贴板、UI 状态、对话框。ArkTS 侧 Index.ets 对应实现:顶栏 Text.bindMenu 弹系统菜单组件,QMenuBar 内容经快照轮询刷新、叶子项回注 triggerMenuAction;输入法走离屏 1×1 TextInput 通道,退格按 Unicode 码点计数处理 emoji 代理对;QPA 光标协议码映射为 pointer.PointerStyle 枚举成员。早期注入的重放缓存处理了 ArkTS 调用早于 dlopen 完成的时序问题(AirCast PATCH-82 范式)。
### 5. 部署五件套与字节级验证
deploy.sh 收集 26 个 .so 到 entry/libs/arm64-v8a/(含 sqldrivers/ 子目录),strip 后用 patchelf/脚本改写 NEEDED 去版本号(libssl.so.3 改 libssl.so),fix_elf_align.py 修正 ELF 段对齐。每次部署后以 unzip -p HAP ... | strings 做字节级特征串核对,防止"修复从未进设备"的增量部署陷阱——这条纪律来自 M6 期间三次真实踩坑的总结。
▍五、适配中遇到的几个难点
### 难点一:libtorrent 2.1.2 的 API 断裂
最初按依赖矩阵选 libtorrent 2.1.2,qbt 编译报 storage_params.mapped_files 不存在。排查确认 2.1 移除了 mapped_files 存储后端,qBittorrent 5.2.3 只适配 2.0.x,完整适配要等 qbt 更高版本。回退 2.0.11 后又遇到第二层:deprecated-functions=OFF 会把 qbt 正在调用的 deprecated 构造整块裁掉,只能重开该开关重编。两次回退都记录在 PORTING.md 踩坑表,依赖矩阵随之修正为"qbt 5.2.3 锁 libtorrent 2.0.11"。
### 难点二:NDK clang 15 的 C++20 库缺口
qBittorrent 5.2.3 广泛使用 std::ranges,而 NDK clang 15/libc++ 的 <ranges> 是空壳。工程没有改动六处业务源文件的算法调用,而是写了 qbt_ranges_compat.h 兼容层经 clang -include 注入,63 处调用零源码改动。兼容层本身经过三轮修复:算法需处理 views::drop 临时对象的右值语义(改转发引用)、qTokenize 的 iterator/sentinel 异类型(谓词类算法改手写循环)、同语句 auto it, e 推导冲突(分开声明)。另一处缺口是结构化绑定捕获(P1091R4,clang 16+ 才支持),P5 补丁在 sessionimpl.cpp 把绑定改名并用引用别名绕过。
### 难点三:AF_NETLINK bind 被沙箱拒绝,会话整体冻结
M6 首验网络时,qbt 启动即 session_error: bind: Permission denied 并 pause(),DHT 归零、磁力零进度。工程用六轮探针逐层切分——裸 socket、asio、enum_net、变体矩阵、settings_pack 逐项打点——最终定位到 libtorrent 2.0 默认开启的 enable_ip_notifier:其 create_ip_notifier 创建 AF_NETLINK socket 并 bind 订阅组播组,这一步被沙箱 SELinux 拒绝,而同进程的 AF_INET bind(TCP listen 与 uTP)全部正常。异常沿 run_all_updates 冒泡被捕获后整个 Session 暂停。P6 补丁在 session_impl.cpp 的 update_ip_notifier 中 __OHOS__ 下直接停用该功能,重验后探针 session 复活,qbt 主 Session DHT 结点非零、BT listen 绑定四接口、出站流量正常。代价是失去网络变化通知,属可接受裁剪。
### 难点四:预编译栈闭包不由上游开关决定
DBUS=OFF 是 qbt 侧的编译开关,但 AirGis 预编译 libQt6Gui.so 的传递 NEEDED 仍含 libQt6DBus.so,首启 dlopen 直接失败。教训是闭包完整性要以实际 ELF 依赖为准:deploy 清单补入 Qt6DBus 后 20 个 dlopen 全通。同族问题还有 hvigor 闭包与 entry/libs 部署件重名(target_link_libraries 绝对路径 .so 被复制进中间产物,pickFirsts 又会选中未 normalize 的原件),工程把网络探针改为全静态链(libtorrent/ssl/crypto 各自独立 install-static 前缀)彻底绕开。
### 难点五:沙箱文件不能以 shell 视图判死
hdc shell ls 看不到应用沙箱 files/home,一度误判配置目录未创建。实际上应用进程的写入经挂载代理,与 shell 视图不一致。工程改用行为签名取证:qbt 法律声明在首次确认后重启不再出现,证明配置确实落盘。这条"勿以 shell ls 判死"的经验与 unzip + strings 字节核对一起,构成了工程的两条取证纪律。
▍六、编译、安装与启动
构建分 native 交叉编译与 HAP 打包两段。native 侧先配置环境再依次构建(PORTING.md 与 native/scripts/ 为准):
bash
source native/env.sh # NDK SDK24、AirGis Qt6.11、AirCad Boost
bash native/scripts/build_libtorrent.sh # libtorrent-rasterbar 2.0.11
bash native/scripts/build_qbittorrent.sh # libqbittorrent.so(P1-P5 已注入)
bash native/scripts/deploy.sh # 26 个 .so 收集进 entry/libs/arm64-v8a
随后打包 HAP:
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
签名产物位于:
text
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.airtorrent.app
PORTING.md 记录的最近一轮验证:MatePad 11.5 真机首启,20 个 dlopen 全通,法律声明与主界面全中文渲染(OCR 通道取证),触摸注入链生效,qbt 配置持久化经重启验证;M6 网络栈验证中 DHT 结点非零、BT listen 绑定四接口、出站流量正常。
▍七、当前功能边界
当前已真实达成的能力(依据 PORTING.md 里程碑 M0-M6):
- qBittorrent 5.2.3 与 libtorrent 2.0.11 交叉编译产物入库,80.4 MB 签名 HAP 打包部署;
- ArkTS 壳 → TSFN 桥 → core → QPA → Qt 全链加载,真机渲染法律声明与完整中文主界面;
- 触摸注入链生效,ArkTS 文本注入可进入 Qt 焦点输入框(AddNewTorrentDialog 实测两次成功);
- qbt 配置经沙箱持久化,重启免法律声明;
- BT 网络栈闭环:DHT 公网 UDP、四接口 BT listen、uTP、出站流量,磁力元数据获取具备网络条件;
- WebUI 代码保留编译(内置 HTTP 服务器在包内)。
尚未覆盖或规划中的能力(依据 PORTING.md M7/M8 与遗留清单):
- 磁力链接 UI 全链路:对话框焦点、软键盘遮挡与按钮时序偶发失效,IME 焦点治理待做;
- 菜单 ArkTS 化:qbt 侧菜单桥 airtorrent_menu_* 未实现,当前 core poller 挂空安全;
- 系统文件选择器、剪贴板、暗黑模式的最终验证;
- 用户可见目录落盘(当前固定沙箱 files/downloads,FSA 授权待接,R6);
- WebUI 端到端验证(ArkWeb 打开 127.0.0.1:8080,R10);
- 首启界面"正在检查/正在移动"两个幽灵列表行的来源待查。
因此,当前版本更准确的定位是"网络栈已闭环的 qBittorrent 鸿蒙直移中期工程":引擎联网能力已经用六轮探针和补丁实证,剩下的是把交互闭环与验收清单(M8,standards.md §9)逐项补完。
▍八、总结
AirTorrent 的价值在于把鸿蒙 PC 的承载边界从"能跑 Qt 界面"推进到"能跑真实网络引擎"。qBittorrent 的直移没有重写任何业务逻辑,靠的是三层工程能力:一套以 AirBase 为蓝本可复制的构建与部署流水线,一层把桌面假设逐条翻译为沙箱现实的补丁集(GUI 改库、ranges 兼容、ip_notifier 停用),以及"字节级部署核对 + 行为签名取证"的验证纪律。M6 的 AF_NETLINK 根因全案尤其有参考价值——它说明沙箱网络策略必须按 socket 族逐项实测,不能以"有 INTERNET 权限"推断。
后续工作重心在 PORTING.md 里已经排好:M7 优先做 IME 焦点治理补完磁力 UI 闭环,随后接通菜单 ArkTS 桥与系统选择器,最后按 standards.md §9 逐项验收。风险登记册中 R6(落盘目录授权)与 R10(WebUI 验证)构成功能完整性的下一段台阶。
■ 应用:轻载(from qBittorrent)(AirTorrent)
■ 定位:BT 下载(qBittorrent,μTorrent 同类工具)
■ 上游:qBittorrent | 路线:Qt6 6.11.1+QPA | 版本:1.0.0
■ 市场状态:开发中,即将上架
■ 官网(免费下载): https://www.shunidc.cn/
————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/
PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:(AtomGit 仓库建立后回填)
环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743
▍一、为什么要适配 qBittorrent
qBittorrent 是桌面 Linux/Windows/macOS 上使用最广的开源 BT 客户端之一:libtorrent-rasterbar 引擎加 Qt 界面,覆盖种子解析、磁力链接、DHT、uTP、做种与 WebUI 远程管理。它依赖链条长、后台线程多、网络行为复杂,向来是检验一个新系统桌面承载能力的硬题目。把它带到鸿蒙 PC,验证的不只是"窗口能不能画出来",而是沙箱网络栈、C++20 工具链与 Qt 桌面栈能否支撑一个真实的重型网络应用。
本工程(应用名轻种子,工程 AirTorrent)选择的是直移路线:上游源码不做界面重写,而是把 qBittorrent 5.2.3 与 libtorrent-rasterbar 分别交叉编译为动态库,由 ArkTS 壳经 XComponent 承载 Qt 界面。这条路在工程族内有明确血统——Qt6.11 预编译栈 + ohos QPA 与 AirCast/AirCad 一脉相承,Qt Widgets raster 渲染线与 AirDraft/AirBase 同款,构建脚本与桥接层以 AirBase(sqlitebrowser 移植)为蓝本照抄改前缀。
当前鸿蒙工程版本为 1.0.0,包名 com.airtorrent.app,支持 phone、tablet 与 2in1,声明 ohos.permission.INTERNET 权限并携带 backup 扩展。产物为 80.4 MB 签名 HAP,内含 26 个动态库。主验证设备为 MatePad 11.5(tablet),参考机为 MateBook Pro 2in1 与 Mate 60 Pro。工程于 2026-09-25 立项,当日完成从骨架到真机首启再到网络栈定位的全部六个里程碑,PORTING.md 对每一步都有记录。
▍二、先划清边界:不把编译通过当成适配完成
qBittorrent 原版以桌面可执行程序形态发布,主窗口、菜单、系统托盘、文件对话框全部假定运行在有窗口管理器的桌面环境。交叉编译出 libqbittorrent.so 只说明代码能通过 musl 工具链,不等于 BT 会话能在沙箱里联网、界面能被触摸操作。工程因此把验收锚点放在三层:库能加载、会话能通网、交互能闭环——当前前两层已经真实达成,第三层仍在进行中。
层次 原项目实现(桌面 Linux) 鸿蒙侧当前方案
应用入口 桌面 main() 可执行程序 P1 补丁导出 airtorrent_main,EntryAbility 经桥启动
窗口承载 X11/Wayland + 原生 QMenuBar XComponent SURFACE + ArkTS bindMenu 菜单桥
BT 引擎 libtorrent-rasterbar 系统库 本工程交叉编译 libtorrent-rasterbar.so 2.0.11
网络状态通知 Linux netlink ip_notifier P6 补丁在 __OHOS__ 下跳过(沙箱拒 AF_NETLINK bind)
会话线程模型 主进程直接构造 Session 宿主后台线程驱动,规避 appfreeze 看门狗
下载落盘 任意用户目录 沙箱 files/downloads,用户目录授权待接
文件对话框 桌面 QFileDialog QPA pick 请求经 TSFN 转 DocumentViewPicker
配置与状态 QSettings + QSQLITE 同栈 libqsqlite.so 部署子目录 + QT_PLUGIN_PATH
必须说明:截至 PORTING.md 最新记录,磁力链接的界面全链路(对话框焦点、软键盘、按钮时序)尚不稳定,菜单的 ArkTS 化桥接符号也未接通。当前版本的准确形态是"网络栈已闭环、交互闭环进行中"的直移工程,不是开箱即用的成品。
▍三、鸿蒙版的整体架构
工程按 Air 双层范式组织:ArkTS 壳只负责窗口、权限与系统能力代理;native 侧由一个薄 NAPI 桥和一个 core 库组成,core 负责按拓扑序 dlopen 全部依赖、前置 Qt 环境变量,并在宿主线程上调用 airtorrent_main。
text
ArkTS 壳(entry/src/main/ets/pages/Index.ets)
├── libairtorrentbridge.so(薄 NAPI:TSFN 六通道 pick/cursor/ime/clip/ui/dialog)
▼ OnSurfaceCreated → 拓扑序 dlopen(RTLD_GLOBAL)
libairtorrent_core.so(env 前置 + QPA 接线 + 宿主线程驱动)
├── Qt6Core→Gui→Widgets→Network→Sql→Xml→Svg→Concurrent→OpenGL
├── libqohos.so(QPA)→ libssl→libcrypto→libicuuc→libzstd→libpng16
├── libtorrent-rasterbar.so → libqbittorrent.so(上游 GUI→库)
└── sqldrivers/libqsqlite.so(QT_PLUGIN_PATH 指向 bundle libs)
主要目录如下:
text
AirTorrent/
├── PORTING.md # 依赖矩阵、补丁清单、风险登记、踩坑映射
├── AppScope/app.json5 # 包名 com.airtorrent.app
├── native/
│ ├── env.sh # NDK SDK24 + AirGis Qt6.11 前缀环境
│ ├── patches/
│ │ ├── 0001-qbt-5.2.3-ohos-p1-p5.patch
│ │ ├── 0002-libtorrent-2.0.11-ohos-p6.patch
│ │ └── qbt_ranges_compat.h # std::ranges 兼容层
│ ├── scripts/
│ │ ├── build_libtorrent.sh # 2.0.11 交叉编译
│ │ ├── build_qbittorrent.sh # 上游 SHARED 库构建
│ │ └── deploy.sh # 26 个 .so 收集、strip 与 NEEDED 改写
│ ├── install/ # libtorrent 产物与 cmake config
│ └── install-static/ # 探针静态链前缀
└── entry/src/main/
├── ets/pages/Index.ets # XComponent、菜单桥、IME、选择器
├── ets/utils/FileManager.ets # 系统选择器封装
├── ets/utils/SubWindowManager.ets
└── cpp/
├── airtorrent_core.cpp # dlopen 拓扑、宿主线程、/netprobe 探针
├── napi_init.cpp # 六通道 TSFN 桥
└── qt_platform/ohos_qt6/ # libqohos QPA(AirBase 复用)
▍四、核心适配过程
### 1. 上游 GUI 改库:P1 与 P2 补丁
补丁以 Upstreams/qbittorrent 内的 git 基线维护,git diff 归档为 native/patches/0001-qbt-5.2.3-ohos-p1-p5.patch。P1 在 src/app/main.cpp 用 __OHOS__ 守卫导出 extern "C" int airtorrent_main(int, char**),入口先关闭触摸/鼠标互合成(AA_SynthesizeMouseForUnhandledTouchEvents=false,AirDraft 验证过的做法),再进入原版 main 逻辑。P2 改 CMakeLists.txt:以编译器 triple 探测 OHOS 交叉环境,把可执行目标改为 add_library(SHARED) 输出 libqbittorrent.so,并跳过 WIN32/MACOSX_BUNDLE/CPack/DBUS/SYSTEMD 等桌面专属分支——上游 CMAKE_SYSTEM_NAME=Linux 会条件性打开 DBUS,这里显式关闭。
### 2. libtorrent 双版本试错与依赖矩阵
build_libtorrent.sh 先试 2.1.2,因 qBittorrent 5.2.3 依赖的 mapped_files API 在 2.1 中被移除而回退 2.0.11(qbt 下限 2.0.10)。构建开关有三处来自实测:webtorrent=OFF(release tarball 不含 libdatachannel 子模块)、deprecated-functions=ON(qbt 调用的 torrent_info(bdecode_node, error_code&) 构造位于 deprecated 区块)、ld-version-script=OFF(musl 不认版本化导出,AirBase 律)。依赖全部来自工程族共享栈:Qt6 取 AirGis 的 6.11.1 预编译前缀,Boost 1.85 头文件取 AirCad 前缀,OpenSSL 3.4.1 由 Qt6Network 与 libtorrent 双方消费。
### 3. core 库的 dlopen 拓扑与 QPA 接线
airtorrent_core.cpp 不静态链接 Qt,而是在运行时按依赖拓扑 dlopen 约 20 个库(RTLD_GLOBAL,musl 按名解析),顺序为 Qt6 栈 → QPA → sqlite 插件 → libtorrent → 本体。环境变量在 QApplication 构造前前置:QT_QPA_PLATFORM=ohos、插件路径指向 bundle libs、QT_QPA_FONTDIR=/system/fonts、HOME/XDG/TMPDIR 全部落沙箱。QPA 插件 libqohos.so(1.3 MB)从 AirBase 整目录复用零修改。宿主线程在 dlopen 链完成后调用 airtorrent_main,保证 QApplication 构造线程即 Qt 主线程,同时把重初始化隔离在后台线程以避开 5 秒 appfreeze 看门狗。
### 4. TSFN 六通道桥与 ArkTS 侧代偿
napi_init.cpp(1146 行)建立六条 napi_threadsafe_function 通道:文件选择、光标样式、输入法、剪贴板、UI 状态、对话框。ArkTS 侧 Index.ets 对应实现:顶栏 Text.bindMenu 弹系统菜单组件,QMenuBar 内容经快照轮询刷新、叶子项回注 triggerMenuAction;输入法走离屏 1×1 TextInput 通道,退格按 Unicode 码点计数处理 emoji 代理对;QPA 光标协议码映射为 pointer.PointerStyle 枚举成员。早期注入的重放缓存处理了 ArkTS 调用早于 dlopen 完成的时序问题(AirCast PATCH-82 范式)。
### 5. 部署五件套与字节级验证
deploy.sh 收集 26 个 .so 到 entry/libs/arm64-v8a/(含 sqldrivers/ 子目录),strip 后用 patchelf/脚本改写 NEEDED 去版本号(libssl.so.3 改 libssl.so),fix_elf_align.py 修正 ELF 段对齐。每次部署后以 unzip -p HAP ... | strings 做字节级特征串核对,防止"修复从未进设备"的增量部署陷阱——这条纪律来自 M6 期间三次真实踩坑的总结。
▍五、适配中遇到的几个难点
### 难点一:libtorrent 2.1.2 的 API 断裂
最初按依赖矩阵选 libtorrent 2.1.2,qbt 编译报 storage_params.mapped_files 不存在。排查确认 2.1 移除了 mapped_files 存储后端,qBittorrent 5.2.3 只适配 2.0.x,完整适配要等 qbt 更高版本。回退 2.0.11 后又遇到第二层:deprecated-functions=OFF 会把 qbt 正在调用的 deprecated 构造整块裁掉,只能重开该开关重编。两次回退都记录在 PORTING.md 踩坑表,依赖矩阵随之修正为"qbt 5.2.3 锁 libtorrent 2.0.11"。
### 难点二:NDK clang 15 的 C++20 库缺口
qBittorrent 5.2.3 广泛使用 std::ranges,而 NDK clang 15/libc++ 的 <ranges> 是空壳。工程没有改动六处业务源文件的算法调用,而是写了 qbt_ranges_compat.h 兼容层经 clang -include 注入,63 处调用零源码改动。兼容层本身经过三轮修复:算法需处理 views::drop 临时对象的右值语义(改转发引用)、qTokenize 的 iterator/sentinel 异类型(谓词类算法改手写循环)、同语句 auto it, e 推导冲突(分开声明)。另一处缺口是结构化绑定捕获(P1091R4,clang 16+ 才支持),P5 补丁在 sessionimpl.cpp 把绑定改名并用引用别名绕过。
### 难点三:AF_NETLINK bind 被沙箱拒绝,会话整体冻结
M6 首验网络时,qbt 启动即 session_error: bind: Permission denied 并 pause(),DHT 归零、磁力零进度。工程用六轮探针逐层切分——裸 socket、asio、enum_net、变体矩阵、settings_pack 逐项打点——最终定位到 libtorrent 2.0 默认开启的 enable_ip_notifier:其 create_ip_notifier 创建 AF_NETLINK socket 并 bind 订阅组播组,这一步被沙箱 SELinux 拒绝,而同进程的 AF_INET bind(TCP listen 与 uTP)全部正常。异常沿 run_all_updates 冒泡被捕获后整个 Session 暂停。P6 补丁在 session_impl.cpp 的 update_ip_notifier 中 __OHOS__ 下直接停用该功能,重验后探针 session 复活,qbt 主 Session DHT 结点非零、BT listen 绑定四接口、出站流量正常。代价是失去网络变化通知,属可接受裁剪。
### 难点四:预编译栈闭包不由上游开关决定
DBUS=OFF 是 qbt 侧的编译开关,但 AirGis 预编译 libQt6Gui.so 的传递 NEEDED 仍含 libQt6DBus.so,首启 dlopen 直接失败。教训是闭包完整性要以实际 ELF 依赖为准:deploy 清单补入 Qt6DBus 后 20 个 dlopen 全通。同族问题还有 hvigor 闭包与 entry/libs 部署件重名(target_link_libraries 绝对路径 .so 被复制进中间产物,pickFirsts 又会选中未 normalize 的原件),工程把网络探针改为全静态链(libtorrent/ssl/crypto 各自独立 install-static 前缀)彻底绕开。
### 难点五:沙箱文件不能以 shell 视图判死
hdc shell ls 看不到应用沙箱 files/home,一度误判配置目录未创建。实际上应用进程的写入经挂载代理,与 shell 视图不一致。工程改用行为签名取证:qbt 法律声明在首次确认后重启不再出现,证明配置确实落盘。这条"勿以 shell ls 判死"的经验与 unzip + strings 字节核对一起,构成了工程的两条取证纪律。
▍六、编译、安装与启动
构建分 native 交叉编译与 HAP 打包两段。native 侧先配置环境再依次构建(PORTING.md 与 native/scripts/ 为准):
bash
source native/env.sh # NDK SDK24、AirGis Qt6.11、AirCad Boost
bash native/scripts/build_libtorrent.sh # libtorrent-rasterbar 2.0.11
bash native/scripts/build_qbittorrent.sh # libqbittorrent.so(P1-P5 已注入)
bash native/scripts/deploy.sh # 26 个 .so 收集进 entry/libs/arm64-v8a
随后打包 HAP:
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
签名产物位于:
text
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.airtorrent.app
PORTING.md 记录的最近一轮验证:MatePad 11.5 真机首启,20 个 dlopen 全通,法律声明与主界面全中文渲染(OCR 通道取证),触摸注入链生效,qbt 配置持久化经重启验证;M6 网络栈验证中 DHT 结点非零、BT listen 绑定四接口、出站流量正常。
▍七、当前功能边界
当前已真实达成的能力(依据 PORTING.md 里程碑 M0-M6):
- qBittorrent 5.2.3 与 libtorrent 2.0.11 交叉编译产物入库,80.4 MB 签名 HAP 打包部署;
- ArkTS 壳 → TSFN 桥 → core → QPA → Qt 全链加载,真机渲染法律声明与完整中文主界面;
- 触摸注入链生效,ArkTS 文本注入可进入 Qt 焦点输入框(AddNewTorrentDialog 实测两次成功);
- qbt 配置经沙箱持久化,重启免法律声明;
- BT 网络栈闭环:DHT 公网 UDP、四接口 BT listen、uTP、出站流量,磁力元数据获取具备网络条件;
- WebUI 代码保留编译(内置 HTTP 服务器在包内)。
尚未覆盖或规划中的能力(依据 PORTING.md M7/M8 与遗留清单):
- 磁力链接 UI 全链路:对话框焦点、软键盘遮挡与按钮时序偶发失效,IME 焦点治理待做;
- 菜单 ArkTS 化:qbt 侧菜单桥 airtorrent_menu_* 未实现,当前 core poller 挂空安全;
- 系统文件选择器、剪贴板、暗黑模式的最终验证;
- 用户可见目录落盘(当前固定沙箱 files/downloads,FSA 授权待接,R6);
- WebUI 端到端验证(ArkWeb 打开 127.0.0.1:8080,R10);
- 首启界面"正在检查/正在移动"两个幽灵列表行的来源待查。
因此,当前版本更准确的定位是"网络栈已闭环的 qBittorrent 鸿蒙直移中期工程":引擎联网能力已经用六轮探针和补丁实证,剩下的是把交互闭环与验收清单(M8,standards.md §9)逐项补完。
▍八、总结
AirTorrent 的价值在于把鸿蒙 PC 的承载边界从"能跑 Qt 界面"推进到"能跑真实网络引擎"。qBittorrent 的直移没有重写任何业务逻辑,靠的是三层工程能力:一套以 AirBase 为蓝本可复制的构建与部署流水线,一层把桌面假设逐条翻译为沙箱现实的补丁集(GUI 改库、ranges 兼容、ip_notifier 停用),以及"字节级部署核对 + 行为签名取证"的验证纪律。M6 的 AF_NETLINK 根因全案尤其有参考价值——它说明沙箱网络策略必须按 socket 族逐项实测,不能以"有 INTERNET 权限"推断。
后续工作重心在 PORTING.md 里已经排好:M7 优先做 IME 焦点治理补完磁力 UI 闭环,随后接通菜单 ArkTS 桥与系统选择器,最后按 standards.md §9 逐项验收。风险登记册中 R6(落盘目录授权)与 R10(WebUI 验证)构成功能完整性的下一段台阶。
楼主
8 天前