A
Air官方
【Air 系列 · 鸿蒙 PC 适配全记录】36 / 56
■ 应用:AirRaw(from DarkTable)(AirRaw)
■ 定位:darktable 移植,RAW 摄影后期
■ 上游:darktable | 路线:GTK3 全栈 | 版本:1.1.0
■ 开源: https://gitcode.com/Andy88/Harmony-darktable
■ 市场状态:已上架 AGC 应用页: https://appgallery.huawei.com/app/6917612411209402902
■ 官网(免费下载): https://www.shunidc.cn/
————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/
PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:https://gitcode.com/Andy88/Harmony-darktable
环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743
▍一、为什么要适配 AirRaw
darktable 是开源摄影工作流软件里结构最特殊的一个:光台(lighttable)负责图库管理与筛选,暗房(darkroom)负责 RAW 的非破坏性冲洗,两者之间由 95 个 iop 图像处理算子、mipmap 缓存、样式与蒙版体系支撑。它的 UI 全部是 GTK 自绘——dtgtk 与 bauhaus 控件不是标准对话框堆叠,而是与像素管线深度耦合的交互面。对摄影师而言,这 95 个面板就是软件的核心价值本身。
正因如此,AirRaw 在立项审计后明确否决了"无头核心 + ArkUI 重写界面"的路线:GLib 渗透核心层(仅 common 目录就有 882 处 GList),剥离不现实;重写 95 个面板的工程量远超依赖栈移植。工程选择方案 A——darktable 5.8.0 源码原样交叉编译为 arm64 共享库与其插件群,GTK 3.24.53 全栈随之移植,在 ArkUI 宿主窗口内由 XComponent 承载真正的 darktable 窗口,渲染走 cairo 软件合成加 OHNativeWindow 整帧 blit 的纯 CPU 路径。
工程当前版本 1.0.0,包名 com.airraw.app,支持 tablet、2in1、phone,目标 ABI arm64-v8a,SDK 为 HarmonyOS 6.1.0(API 23)起、OHOS NDK clang 15.0.4。README 记载状态为 M0–M12 全部落地、补丁 P1–P25 应用:光台到暗房全链、93 个 iop 算子、系统 Picker 导入导出与全中文界面均已运行,HAP 约 95MB,整体以 GPL-3.0-or-later 发布。
▍二、先划清边界:不把编译通过当成适配完成
darktable 在 Linux 桌面上的每一个前提条件——可扫描的插件目录、系统 OpenMP、家目录配置、XDG 数据路径、自由窗口——在鸿蒙沙箱里都要重新安置。本项目把"编译通过"与"适配完成"之间的差距列成矩阵并逐项落地,当前边界如下。
层次 传统桌面方案(Linux 版 darktable) 鸿蒙侧当前方案
界面栈 系统 GTK3 + dtgtk/bauhaus 自绘控件 GTK 3.24.53 + 自研 OHOS GDK 后端,cairo 合成整帧上屏
插件加载 g_dir_open 扫描插件目录 编译期清单 dt_plugin_manifest.h 按名 g_module_open(146 个插件)
并行模型 系统 libgomp 提供 OpenMP LLVM libomp 15.0.4 交叉编译随包,SHM 注册路径裁剪
文件访问 直接 POSIX 路径 系统 Picker + 沙箱中转(picker_in/ 导入、picker_out/ staging 导出)
数据文件 /usr/share/darktable rawfile 携带 darktable-share 768 项,首启部署沙箱
图像库与配置 家目录 ~/.config/darktable XDG 环境变量导向沙箱,M8 起使用文件库持久化
窗口方向 桌面自由窗口 orientation: landscape 强制横屏(竖屏下面板挤压不可用)
崩溃诊断 fork + gdb 附加 沙箱禁 fork,SIGSEGV handler 写 .txt 文本后转 hilog(P1)
裁剪同样是边界的一部分:OpenCL、Lua、Map、Colord、gphoto、LibRaw(CR3 二期)、OpenEXR/HEIF/JXL/WebP/OpenJPEG 格式族在构建配置中显式关闭,USE_OPENMP 保持开启并配自编 libomp。已交付的能力以 PATCHES-APPLIED.md 与验证实录为准,不做超出配置的宣称。
▍三、鸿蒙版的整体架构
运行时架构是典型的 Air 双层:ArkTS 壳只承担 splash、Picker、关于页与部署门控,libdtbridge.so 作为薄 NAPI 壳注册 XComponent 回调;OnSurfaceCreated 时按依赖拓扑序 dlopen 全栈,进入 libairraw_core.so 的线程宿主。
text
ArkTS 壳(entry/src/main/ets)
├── Index.ets splash / 沉浸式 / picker / 关于页
├── FileManager.ets 系统 Picker 与沙箱中转
└── EntryAbility.ets rawfile 数据部署 → stamp 幂等 → notifyDatafilesReady
▼ NAPI:libdtbridge.so(薄壳,仅链系统库)
OnSurfaceCreated:按依赖拓扑序 dlopen → dlsym core API
▼
libairraw_core.so(线程宿主:环境变量/沙箱/gettext/日志/SIGSEGV handler/picker 桥)
├── airraw_shim.c 稳定 C ABI(airraw_dt_init / gui_run / import_file…)
├── libdarktable.so darktable 主体原样编译 + P1–P25 补丁集
├── 146 个插件 .so 93 iop / 3 views / 38 lighttable libs /
│ 7 imageio format / 5 storage(清单按名 dlopen)
└── 依赖栈 .so libgtk-3/libgdk-3(OHOS 后端)/glib/cairo/pango/
gdk-pixbuf/rsvg/lcms2/exiv2/libomp/sqlite3/lensfun…
主要目录如下:
text
AirRaw/
├── AppScope/ # 应用配置
├── PORTING.md # 移植方案:踩坑映射/依赖矩阵/P1–P25/附录 A–G
├── README.md # 工程总览与运行时架构
├── entry/src/main/
│ ├── ets/ # Index / FileManager / EntryAbility
│ ├── cpp/ # napi_init.cpp + airraw_core.cpp
│ │ └── libs/arm64-v8a/ # 链接闭包通道(核心依赖 .so)
│ ├── libs/arm64-v8a/ # 模块级通道(146 插件 + out-of-closure 库)
│ └── resources/rawfile/ # darktable-share 768 项、fonts.conf、.mo
├── native/
│ ├── patches/darktable/ # P01–P25 补丁 + PATCHES-APPLIED.md
│ ├── patches/gtk3-ohos/ # GTK3 OHOS GDK 后端权威副本(16 个 .c/.h)
│ ├── scripts/ # env.sh、build_darktable.sh、双通道部署、ELF 三件套
│ ├── cross/ # meson cross file + CMake 工具链
│ ├── sources/ # darktable 5.8.0 快照 + 6 个依赖源码树
│ └── install/ # 预编译依赖栈(AirGimp 只读拷贝 + 5 新编)
└── native/logs/ # 真机验证截图与 trace 存档(vr 系列)
▍四、核心适配过程
### 1. 依赖矩阵:复用四个已验证栈,新编五个缺口库
PORTING.md 第 4 节的依赖审计把工作切成两半:GTK3 3.24.53、GLib 2.80.4、cairo/pango/gdk-pixbuf/librsvg、lcms2、exiv2、potrace 与自研 gettext 实现 airlibintl 直接复用 AirGimp 预编译栈;darktable 必需而栈内缺失的 SQLite3(amalgamation 单文件)、LensFun 0.3.x、pugixml、libcurl 最小构建与 LLVM libomp 15.0.4 由本项目新编,脚本位于 native/scripts/。裁剪配置一次性写死:DefineOptions 对应的二十余项可选特性全部 OFF,避免 AUTO 探测在交叉环境里产生不可复现的依赖。
### 2. airraw_shim.c:穿越宿主编译墙的稳定 C ABI
entry 侧的 libairraw_core.so 是 C++ 翻译单元,无法直接包含 darktable 内部的 C-only 头(isnan/logf 未声明、生成头缺失级联、<cmath> 被 darktable 的 common/math.h 遮蔽)。P17 补丁新增 src/airraw_shim.c,让它随 lib_darktable 在 darktable 自身构建环境里编译,对外只导出 airraw_dt_init、airraw_dt_gui_run、airraw_dt_cleanup、airraw_dt_import_file、airraw_dt_import_finish、airraw_dt_version_string 等稳定 C ABI;宿主侧 airraw_core.cpp 仅做 extern "C" 声明,两侧头文件彻底解耦。
### 3. 插件加载:编译期清单替代目录扫描
darktable 的三个加载器依赖 g_dir_open 扫描插件目录,而 HAP 内的 .so 扁平化后无目录可列。P4 是整个项目的核心补丁:新增 cmake/GeneratePluginManifest.cmake 在构建期生成 dt_plugin_manifest.h(iop/views/lighttable libs/imageio format/imageio storage 五组),加载器在目录扫描失败时改按纯 soname g_module_open。附录 B 记载了该对策在真机上被真实触发——设备上插件目录为空,光台与暗房模块面板完整渲染即为清单回退生效的铁证。配套的 P16 解决了扁平打包同名互覆:imageio 的 libjpeg.so/libtiff.so 插件与依赖栈真实库同名,改名为 dtio_jpeg/dtio_tiff 避让。
### 4. 打包双通道与 ELF 纪律
hvigor 只对 cpp/libs/<abi> 打 NEEDED 传递闭包,运行时 dlopen 的插件会被静默丢弃(首轮 192 个 .so 仅 41 个进 HAP)。工程采用双通道部署:libdarktable.so 与闭包依赖走 cpp/libs,146 个插件与 8 个闭包外库走模块级 entry/libs/arm64-v8a 整体打包。ELF 侧执行统一纪律:module.json5 的 compressNativeLibs:false 加 build-profile.json5 的 strip:false(且必须置于 buildOptionSet[] 顶层才过 schema 校验),部署管线用 elf_edit.py --strip-version 与 --remove-needed libc.so 原位规整、fix_elf_align.py 做 64K 对齐。M5 出口核验:187 个 .so 全部 PT_LOAD 同余校验通过,NEEDED 闭包审计 0 缺口。
### 5. 启动时序与环境引导
core 线程的启动顺序是硬约束:环境变量最前置(HOME/XDG 指向沙箱)→ loaders.cache 自举(dlsym 取 SVG loader 真实路径手写 cache,含签名行与完整 mime 列表)→ gtk_init → gdk_ohos_display_set_native_window 预绑定 → notify_resize → 注入 --conf ui/show_welcome_screen=FALSE 后才进 dt_init。两处血的教训固化在其中:darktable 自带的 grealpath.h 在 realpath 失败时直接 exit(1),而 /proc/self/exe 在鸿蒙上指向 appspawn,导致 locale 目录判定失败静默退场(P18 补 DARKTABLE_LOCALEDIR/DARKTABLE_SHAREDIR 环境变量回退);首跑欢迎屏是 g_main_loop_run 模态阻塞,不旁路则 dt_init 永不返回。
▍五、适配中遇到的几个难点
### 难点一:libomp 的 /dev/shm 设备差异
MateBook Pro 模拟器上运行正常,Mate 60 Pro 真机上 dt_init 中途 SIGABRT:OMP: Error #179: Function Can't open SHM2 failed。根因是 libomp 的库注册机制 __kmp_register_library_startup 走 shm_open 路径,依赖 /dev/shm,而真机沙箱不提供。修复是把 kmp_runtime.cpp 六处 #if defined(KMP_USE_SHM) 收紧为 && !defined(__OHOS__),注册回落到环境变量分支,进程内单 libomp 场景语义等价;补丁以幂等 sed 固化进 build_libomp.sh。取证上也有教训:MUSL-SIGCHAIN 会吞掉 SIGABRT 栈,abort 消息只在沙箱 stderr 文本里。
### 难点二:触摸点按事件链的三层根修
真机上"胶片条装饰"触发率仅 6.7% 且位置漂移。对照实验锁定根因:纯 Down+Up 的触摸点击不产生 motion 事件,hover 链路完全依赖 MOVE 流。修复分三层:GDK 后端在 TOUCH_DOWN 前注入一次仿真 pointer MOTION(对齐 X11"指针先移动到位再按下"时序);嗅探器把 GTK 内部 tooltip 弹层排除出 dismiss 流程(避免事件栈内重入后转发不可达);press/release 按坐标下钻重定向到最深子窗口(弹层带 grab 时 window 字段已被改写,原样转发会死在已隐藏窗口)。三层修复后 8/8 回归通过,全过程记录于 PORTING 附录 F。
### 难点三:多选导入光台只刷一张(P24)
用户报障"勾选导入 3 张实际只显示 1 张"。设备采证逐段分流:Picker 勾选、ArkTS 拷贝、native import done: 3/3 全部正常,丢失发生在导入后的集合视图刷新。根因是 darktable 桌面版的批量导入收尾会 raise FILMROLLS_IMPORTED 触发 dt_collection_update_query 重建集合,而移植 shim 的批量导入只逐张 raise IMAGE_IMPORT,集合只做计数不重建。修复是 airraw_shim.c 新增 airraw_dt_import_finish() 对齐桌面收尾语义,导入循环后在 GTK 主线程调用一次。验证实录:修复后 3 张立即全部显示、重启保持、零 faultlog,符号经 nm -D 锚点核验(附录 G)。
### 难点四:构建工具链的静默陷阱
三处构建期问题都以"无报错但结果错误"的形态出现:elf_edit.py 不存在的 --replace-needed 选项被 || true 静默吞错,版本化 NEEDED 在增量构建后让 146 个插件集体复发拒载;cmake --install 后不重跑 normalize_darktable.sh,未 strip 的 24MB 版本化 libdarktable.so 顽固进包三轮;gtk.h 的 g_signal_connect 静态断言依赖 gcc 的常量折叠,clang 下直接硬编译错(P14 改为 gcc 展开断言、clang 展开为空)。对策全部落在脚本与补丁里,使错误在构建出口被核验拦截而非依赖运行期发现。
### 难点五:横屏、子窗口与多实例的窗口协同
darktable 的左右面板布局在竖屏下不可用,module.json5 固定 orientation: landscape,实测 GDK 后端先以竖屏开 display、ArkUI 窗口随后旋转、surface 以横屏尺寸重建,主窗按最终 surface 布局。模态对话框走 GDK OHOS 后端的子窗口通道:GDK_OHOS_DIALOG_SUBWINDOW=1 门控加 gdk_ohos_display_set_dialog_ops 注册,对话框类 toplevel 迁入子窗口 surface,框内临时弹层由后端合成回对话框,多实例场景下每个子窗口独立承载。
▍六、编译、安装与启动
Native 侧以 native/scripts/env.sh 为总入口,按依赖顺序执行(SQLite3/lensfun/pugixml/curl/libomp 各有独立脚本):
bash
source native/scripts/env.sh
native/scripts/build_darktable.sh # darktable 5.8.0 + rawspeed(P1–P25 就地应用)
native/scripts/normalize_darktable.sh # ELF 规整:去版本号/删 libc.so/strip/64K 对齐
native/scripts/deploy_stack.sh # 双通道部署:闭包库→cpp/libs,插件→entry/libs
native/scripts/deploy_darktable.sh # libdarktable.so 与 146 插件分发
native/scripts/deploy_datafiles.sh # darktable-share 数据部署 rawfile
随后构建 HAP(签名材料按工程风险登记册 R5 由 DevEco 生成):
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
安装并启动:
bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airraw.app
工程实测约定:CLI 重装前先 hdc shell bm uninstall -n com.airraw.app,避免与 IDE 已装实例签名不一致导致 9568332 错误。
▍七、当前功能边界
README 标注 M0–M12 全部落地,PORTING 附录记录至 2026-09-29,当前已覆盖:
- lighttable 到 darkroom 全链:导入、光台缩略图与集合管理、暗房中央视图/快照/历史/副本/胶片条/EXIF 渲染;
- 93 个 iop 算子全部编译加载,编译期清单回退在真机实战生效;
- rawspeed 与 cameras.xml 随包部署,JPEG 样本的像素管线产出经实录验证;
- 系统 Picker 导入导出全流程,多选导入集合刷新经 P24 根修后 3/3 实测通过;
- 全中文界面(airlibintl + zh_CN .mo)、强制横屏、多实例对话框子窗口;
- 触摸点按链三层根修后 8/8 回归通过(附录 F 截图存档 native/logs/vr7~vr11);
- 图库/导入持久化、缩略图装饰自动消失、暗房回归等 M8 观察项真机闭环(附录 E)。
尚未覆盖或有明确登记的问题:多张导入后第 2/3 张缩略图持续占位(mipmap 生成层,附录 G.4 登记未追);CR3(LibRaw)与 OpenEXR/HEIF/JXL/WebP/OpenJPEG 格式族按二期计划开启;OpenCL、Lua、Map、打印在构建期显式裁剪;Picker 格式下拉默认首项为 .nef 的可用性项待产品取向;跨设备部署存在旧包滞留风险(3QC 设备滞后 6 周的实测教训),需部署台账约束。
因此,当前版本更准确的定位是"darktable 鸿蒙完整原生移植版(GPL-3.0-or-later 发布)":光台与暗房的主工作流已在 2in1 与手机真机闭环,格式族扩展与长尾交互缺陷按登记册逐项推进。
▍八、总结
AirRaw 的适配对象是这批移植工程里结构最复杂的一类:主体加 146 个运行时插件、GLib 深度渗透、UI 即核心价值。它的答案是把每一层桌面前提换成鸿蒙等价物——目录扫描换编译期清单、系统 OpenMP 换自编 libomp、家目录换 XDG 沙箱、fork 诊断换文本 trace——而 darktable 源码本身只承受了 25 个有出处的补丁。P4 清单回退在真机上被真实触发并撑住全部面板,说明这套架构不是纸面设计。
后续工作按已登记的清单推进:缩略图占位问题需开启 darktable 自身的 imageio 调试日志定位 mipmap 层;格式族二期按 LibRaw 优先的顺序补齐;跨设备部署台账从这次 3QC 滞后六周的教训开始建立。PORTING 附录 A 到 G 的逐条实录本身就是这个工程最重要的交付物之一——每个结论都有文件级出处,后续移植者可以沿着它复现全部路径。
■ 应用:AirRaw(from DarkTable)(AirRaw)
■ 定位:darktable 移植,RAW 摄影后期
■ 上游:darktable | 路线:GTK3 全栈 | 版本:1.1.0
■ 开源: https://gitcode.com/Andy88/Harmony-darktable
■ 市场状态:已上架 AGC 应用页: https://appgallery.huawei.com/app/6917612411209402902
■ 官网(免费下载): https://www.shunidc.cn/
————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/
PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:https://gitcode.com/Andy88/Harmony-darktable
环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743
▍一、为什么要适配 AirRaw
darktable 是开源摄影工作流软件里结构最特殊的一个:光台(lighttable)负责图库管理与筛选,暗房(darkroom)负责 RAW 的非破坏性冲洗,两者之间由 95 个 iop 图像处理算子、mipmap 缓存、样式与蒙版体系支撑。它的 UI 全部是 GTK 自绘——dtgtk 与 bauhaus 控件不是标准对话框堆叠,而是与像素管线深度耦合的交互面。对摄影师而言,这 95 个面板就是软件的核心价值本身。
正因如此,AirRaw 在立项审计后明确否决了"无头核心 + ArkUI 重写界面"的路线:GLib 渗透核心层(仅 common 目录就有 882 处 GList),剥离不现实;重写 95 个面板的工程量远超依赖栈移植。工程选择方案 A——darktable 5.8.0 源码原样交叉编译为 arm64 共享库与其插件群,GTK 3.24.53 全栈随之移植,在 ArkUI 宿主窗口内由 XComponent 承载真正的 darktable 窗口,渲染走 cairo 软件合成加 OHNativeWindow 整帧 blit 的纯 CPU 路径。
工程当前版本 1.0.0,包名 com.airraw.app,支持 tablet、2in1、phone,目标 ABI arm64-v8a,SDK 为 HarmonyOS 6.1.0(API 23)起、OHOS NDK clang 15.0.4。README 记载状态为 M0–M12 全部落地、补丁 P1–P25 应用:光台到暗房全链、93 个 iop 算子、系统 Picker 导入导出与全中文界面均已运行,HAP 约 95MB,整体以 GPL-3.0-or-later 发布。
▍二、先划清边界:不把编译通过当成适配完成
darktable 在 Linux 桌面上的每一个前提条件——可扫描的插件目录、系统 OpenMP、家目录配置、XDG 数据路径、自由窗口——在鸿蒙沙箱里都要重新安置。本项目把"编译通过"与"适配完成"之间的差距列成矩阵并逐项落地,当前边界如下。
层次 传统桌面方案(Linux 版 darktable) 鸿蒙侧当前方案
界面栈 系统 GTK3 + dtgtk/bauhaus 自绘控件 GTK 3.24.53 + 自研 OHOS GDK 后端,cairo 合成整帧上屏
插件加载 g_dir_open 扫描插件目录 编译期清单 dt_plugin_manifest.h 按名 g_module_open(146 个插件)
并行模型 系统 libgomp 提供 OpenMP LLVM libomp 15.0.4 交叉编译随包,SHM 注册路径裁剪
文件访问 直接 POSIX 路径 系统 Picker + 沙箱中转(picker_in/ 导入、picker_out/ staging 导出)
数据文件 /usr/share/darktable rawfile 携带 darktable-share 768 项,首启部署沙箱
图像库与配置 家目录 ~/.config/darktable XDG 环境变量导向沙箱,M8 起使用文件库持久化
窗口方向 桌面自由窗口 orientation: landscape 强制横屏(竖屏下面板挤压不可用)
崩溃诊断 fork + gdb 附加 沙箱禁 fork,SIGSEGV handler 写 .txt 文本后转 hilog(P1)
裁剪同样是边界的一部分:OpenCL、Lua、Map、Colord、gphoto、LibRaw(CR3 二期)、OpenEXR/HEIF/JXL/WebP/OpenJPEG 格式族在构建配置中显式关闭,USE_OPENMP 保持开启并配自编 libomp。已交付的能力以 PATCHES-APPLIED.md 与验证实录为准,不做超出配置的宣称。
▍三、鸿蒙版的整体架构
运行时架构是典型的 Air 双层:ArkTS 壳只承担 splash、Picker、关于页与部署门控,libdtbridge.so 作为薄 NAPI 壳注册 XComponent 回调;OnSurfaceCreated 时按依赖拓扑序 dlopen 全栈,进入 libairraw_core.so 的线程宿主。
text
ArkTS 壳(entry/src/main/ets)
├── Index.ets splash / 沉浸式 / picker / 关于页
├── FileManager.ets 系统 Picker 与沙箱中转
└── EntryAbility.ets rawfile 数据部署 → stamp 幂等 → notifyDatafilesReady
▼ NAPI:libdtbridge.so(薄壳,仅链系统库)
OnSurfaceCreated:按依赖拓扑序 dlopen → dlsym core API
▼
libairraw_core.so(线程宿主:环境变量/沙箱/gettext/日志/SIGSEGV handler/picker 桥)
├── airraw_shim.c 稳定 C ABI(airraw_dt_init / gui_run / import_file…)
├── libdarktable.so darktable 主体原样编译 + P1–P25 补丁集
├── 146 个插件 .so 93 iop / 3 views / 38 lighttable libs /
│ 7 imageio format / 5 storage(清单按名 dlopen)
└── 依赖栈 .so libgtk-3/libgdk-3(OHOS 后端)/glib/cairo/pango/
gdk-pixbuf/rsvg/lcms2/exiv2/libomp/sqlite3/lensfun…
主要目录如下:
text
AirRaw/
├── AppScope/ # 应用配置
├── PORTING.md # 移植方案:踩坑映射/依赖矩阵/P1–P25/附录 A–G
├── README.md # 工程总览与运行时架构
├── entry/src/main/
│ ├── ets/ # Index / FileManager / EntryAbility
│ ├── cpp/ # napi_init.cpp + airraw_core.cpp
│ │ └── libs/arm64-v8a/ # 链接闭包通道(核心依赖 .so)
│ ├── libs/arm64-v8a/ # 模块级通道(146 插件 + out-of-closure 库)
│ └── resources/rawfile/ # darktable-share 768 项、fonts.conf、.mo
├── native/
│ ├── patches/darktable/ # P01–P25 补丁 + PATCHES-APPLIED.md
│ ├── patches/gtk3-ohos/ # GTK3 OHOS GDK 后端权威副本(16 个 .c/.h)
│ ├── scripts/ # env.sh、build_darktable.sh、双通道部署、ELF 三件套
│ ├── cross/ # meson cross file + CMake 工具链
│ ├── sources/ # darktable 5.8.0 快照 + 6 个依赖源码树
│ └── install/ # 预编译依赖栈(AirGimp 只读拷贝 + 5 新编)
└── native/logs/ # 真机验证截图与 trace 存档(vr 系列)
▍四、核心适配过程
### 1. 依赖矩阵:复用四个已验证栈,新编五个缺口库
PORTING.md 第 4 节的依赖审计把工作切成两半:GTK3 3.24.53、GLib 2.80.4、cairo/pango/gdk-pixbuf/librsvg、lcms2、exiv2、potrace 与自研 gettext 实现 airlibintl 直接复用 AirGimp 预编译栈;darktable 必需而栈内缺失的 SQLite3(amalgamation 单文件)、LensFun 0.3.x、pugixml、libcurl 最小构建与 LLVM libomp 15.0.4 由本项目新编,脚本位于 native/scripts/。裁剪配置一次性写死:DefineOptions 对应的二十余项可选特性全部 OFF,避免 AUTO 探测在交叉环境里产生不可复现的依赖。
### 2. airraw_shim.c:穿越宿主编译墙的稳定 C ABI
entry 侧的 libairraw_core.so 是 C++ 翻译单元,无法直接包含 darktable 内部的 C-only 头(isnan/logf 未声明、生成头缺失级联、<cmath> 被 darktable 的 common/math.h 遮蔽)。P17 补丁新增 src/airraw_shim.c,让它随 lib_darktable 在 darktable 自身构建环境里编译,对外只导出 airraw_dt_init、airraw_dt_gui_run、airraw_dt_cleanup、airraw_dt_import_file、airraw_dt_import_finish、airraw_dt_version_string 等稳定 C ABI;宿主侧 airraw_core.cpp 仅做 extern "C" 声明,两侧头文件彻底解耦。
### 3. 插件加载:编译期清单替代目录扫描
darktable 的三个加载器依赖 g_dir_open 扫描插件目录,而 HAP 内的 .so 扁平化后无目录可列。P4 是整个项目的核心补丁:新增 cmake/GeneratePluginManifest.cmake 在构建期生成 dt_plugin_manifest.h(iop/views/lighttable libs/imageio format/imageio storage 五组),加载器在目录扫描失败时改按纯 soname g_module_open。附录 B 记载了该对策在真机上被真实触发——设备上插件目录为空,光台与暗房模块面板完整渲染即为清单回退生效的铁证。配套的 P16 解决了扁平打包同名互覆:imageio 的 libjpeg.so/libtiff.so 插件与依赖栈真实库同名,改名为 dtio_jpeg/dtio_tiff 避让。
### 4. 打包双通道与 ELF 纪律
hvigor 只对 cpp/libs/<abi> 打 NEEDED 传递闭包,运行时 dlopen 的插件会被静默丢弃(首轮 192 个 .so 仅 41 个进 HAP)。工程采用双通道部署:libdarktable.so 与闭包依赖走 cpp/libs,146 个插件与 8 个闭包外库走模块级 entry/libs/arm64-v8a 整体打包。ELF 侧执行统一纪律:module.json5 的 compressNativeLibs:false 加 build-profile.json5 的 strip:false(且必须置于 buildOptionSet[] 顶层才过 schema 校验),部署管线用 elf_edit.py --strip-version 与 --remove-needed libc.so 原位规整、fix_elf_align.py 做 64K 对齐。M5 出口核验:187 个 .so 全部 PT_LOAD 同余校验通过,NEEDED 闭包审计 0 缺口。
### 5. 启动时序与环境引导
core 线程的启动顺序是硬约束:环境变量最前置(HOME/XDG 指向沙箱)→ loaders.cache 自举(dlsym 取 SVG loader 真实路径手写 cache,含签名行与完整 mime 列表)→ gtk_init → gdk_ohos_display_set_native_window 预绑定 → notify_resize → 注入 --conf ui/show_welcome_screen=FALSE 后才进 dt_init。两处血的教训固化在其中:darktable 自带的 grealpath.h 在 realpath 失败时直接 exit(1),而 /proc/self/exe 在鸿蒙上指向 appspawn,导致 locale 目录判定失败静默退场(P18 补 DARKTABLE_LOCALEDIR/DARKTABLE_SHAREDIR 环境变量回退);首跑欢迎屏是 g_main_loop_run 模态阻塞,不旁路则 dt_init 永不返回。
▍五、适配中遇到的几个难点
### 难点一:libomp 的 /dev/shm 设备差异
MateBook Pro 模拟器上运行正常,Mate 60 Pro 真机上 dt_init 中途 SIGABRT:OMP: Error #179: Function Can't open SHM2 failed。根因是 libomp 的库注册机制 __kmp_register_library_startup 走 shm_open 路径,依赖 /dev/shm,而真机沙箱不提供。修复是把 kmp_runtime.cpp 六处 #if defined(KMP_USE_SHM) 收紧为 && !defined(__OHOS__),注册回落到环境变量分支,进程内单 libomp 场景语义等价;补丁以幂等 sed 固化进 build_libomp.sh。取证上也有教训:MUSL-SIGCHAIN 会吞掉 SIGABRT 栈,abort 消息只在沙箱 stderr 文本里。
### 难点二:触摸点按事件链的三层根修
真机上"胶片条装饰"触发率仅 6.7% 且位置漂移。对照实验锁定根因:纯 Down+Up 的触摸点击不产生 motion 事件,hover 链路完全依赖 MOVE 流。修复分三层:GDK 后端在 TOUCH_DOWN 前注入一次仿真 pointer MOTION(对齐 X11"指针先移动到位再按下"时序);嗅探器把 GTK 内部 tooltip 弹层排除出 dismiss 流程(避免事件栈内重入后转发不可达);press/release 按坐标下钻重定向到最深子窗口(弹层带 grab 时 window 字段已被改写,原样转发会死在已隐藏窗口)。三层修复后 8/8 回归通过,全过程记录于 PORTING 附录 F。
### 难点三:多选导入光台只刷一张(P24)
用户报障"勾选导入 3 张实际只显示 1 张"。设备采证逐段分流:Picker 勾选、ArkTS 拷贝、native import done: 3/3 全部正常,丢失发生在导入后的集合视图刷新。根因是 darktable 桌面版的批量导入收尾会 raise FILMROLLS_IMPORTED 触发 dt_collection_update_query 重建集合,而移植 shim 的批量导入只逐张 raise IMAGE_IMPORT,集合只做计数不重建。修复是 airraw_shim.c 新增 airraw_dt_import_finish() 对齐桌面收尾语义,导入循环后在 GTK 主线程调用一次。验证实录:修复后 3 张立即全部显示、重启保持、零 faultlog,符号经 nm -D 锚点核验(附录 G)。
### 难点四:构建工具链的静默陷阱
三处构建期问题都以"无报错但结果错误"的形态出现:elf_edit.py 不存在的 --replace-needed 选项被 || true 静默吞错,版本化 NEEDED 在增量构建后让 146 个插件集体复发拒载;cmake --install 后不重跑 normalize_darktable.sh,未 strip 的 24MB 版本化 libdarktable.so 顽固进包三轮;gtk.h 的 g_signal_connect 静态断言依赖 gcc 的常量折叠,clang 下直接硬编译错(P14 改为 gcc 展开断言、clang 展开为空)。对策全部落在脚本与补丁里,使错误在构建出口被核验拦截而非依赖运行期发现。
### 难点五:横屏、子窗口与多实例的窗口协同
darktable 的左右面板布局在竖屏下不可用,module.json5 固定 orientation: landscape,实测 GDK 后端先以竖屏开 display、ArkUI 窗口随后旋转、surface 以横屏尺寸重建,主窗按最终 surface 布局。模态对话框走 GDK OHOS 后端的子窗口通道:GDK_OHOS_DIALOG_SUBWINDOW=1 门控加 gdk_ohos_display_set_dialog_ops 注册,对话框类 toplevel 迁入子窗口 surface,框内临时弹层由后端合成回对话框,多实例场景下每个子窗口独立承载。
▍六、编译、安装与启动
Native 侧以 native/scripts/env.sh 为总入口,按依赖顺序执行(SQLite3/lensfun/pugixml/curl/libomp 各有独立脚本):
bash
source native/scripts/env.sh
native/scripts/build_darktable.sh # darktable 5.8.0 + rawspeed(P1–P25 就地应用)
native/scripts/normalize_darktable.sh # ELF 规整:去版本号/删 libc.so/strip/64K 对齐
native/scripts/deploy_stack.sh # 双通道部署:闭包库→cpp/libs,插件→entry/libs
native/scripts/deploy_darktable.sh # libdarktable.so 与 146 插件分发
native/scripts/deploy_datafiles.sh # darktable-share 数据部署 rawfile
随后构建 HAP(签名材料按工程风险登记册 R5 由 DevEco 生成):
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
安装并启动:
bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airraw.app
工程实测约定:CLI 重装前先 hdc shell bm uninstall -n com.airraw.app,避免与 IDE 已装实例签名不一致导致 9568332 错误。
▍七、当前功能边界
README 标注 M0–M12 全部落地,PORTING 附录记录至 2026-09-29,当前已覆盖:
- lighttable 到 darkroom 全链:导入、光台缩略图与集合管理、暗房中央视图/快照/历史/副本/胶片条/EXIF 渲染;
- 93 个 iop 算子全部编译加载,编译期清单回退在真机实战生效;
- rawspeed 与 cameras.xml 随包部署,JPEG 样本的像素管线产出经实录验证;
- 系统 Picker 导入导出全流程,多选导入集合刷新经 P24 根修后 3/3 实测通过;
- 全中文界面(airlibintl + zh_CN .mo)、强制横屏、多实例对话框子窗口;
- 触摸点按链三层根修后 8/8 回归通过(附录 F 截图存档 native/logs/vr7~vr11);
- 图库/导入持久化、缩略图装饰自动消失、暗房回归等 M8 观察项真机闭环(附录 E)。
尚未覆盖或有明确登记的问题:多张导入后第 2/3 张缩略图持续占位(mipmap 生成层,附录 G.4 登记未追);CR3(LibRaw)与 OpenEXR/HEIF/JXL/WebP/OpenJPEG 格式族按二期计划开启;OpenCL、Lua、Map、打印在构建期显式裁剪;Picker 格式下拉默认首项为 .nef 的可用性项待产品取向;跨设备部署存在旧包滞留风险(3QC 设备滞后 6 周的实测教训),需部署台账约束。
因此,当前版本更准确的定位是"darktable 鸿蒙完整原生移植版(GPL-3.0-or-later 发布)":光台与暗房的主工作流已在 2in1 与手机真机闭环,格式族扩展与长尾交互缺陷按登记册逐项推进。
▍八、总结
AirRaw 的适配对象是这批移植工程里结构最复杂的一类:主体加 146 个运行时插件、GLib 深度渗透、UI 即核心价值。它的答案是把每一层桌面前提换成鸿蒙等价物——目录扫描换编译期清单、系统 OpenMP 换自编 libomp、家目录换 XDG 沙箱、fork 诊断换文本 trace——而 darktable 源码本身只承受了 25 个有出处的补丁。P4 清单回退在真机上被真实触发并撑住全部面板,说明这套架构不是纸面设计。
后续工作按已登记的清单推进:缩略图占位问题需开启 darktable 自身的 imageio 调试日志定位 mipmap 层;格式族二期按 LibRaw 优先的顺序补齐;跨设备部署台账从这次 3QC 滞后六周的教训开始建立。PORTING 附录 A 到 G 的逐条实录本身就是这个工程最重要的交付物之一——每个结论都有文件级出处,后续移植者可以沿着它复现全部路径。
楼主
8 天前