Skip to content

fix: resolve 48 findings from the cross-validated code review (#4) - #5

Merged
SRInternet merged 21 commits into
UniDesktop:developfrom
umbrella22:fix/issue-4-review-findings
Nov 1, 2026
Merged

SRInternet merged 21 commits into
UniDesktop:developfrom
umbrella22:fix/issue-4-review-findings

Conversation

@umbrella22

@umbrella22 umbrella22 commented Oct 7, 2026 •

Copy link
Copy Markdown

概述

本 PR 针对 #4 交叉审查清单完成批量修复。清单 50 项中,v0.2.1 已落地 2 项(P0-2 dbusmenu 线格式、P1-23 SNI Menu 对象路径),本 PR 修复其余 48 项(含 P2-50 的生产侧收尾)。全部改动经双验证者交叉审核与一轮迭代复审。

Refs #4

⚠️ 给合并者:本 PR 涉及大量平台行为改动,Linux(D-Bus / dbusmock)与 Windows 侧均未经真机运行验证(本机仅覆盖 macOS 宿主 + 双目标交叉编译门禁)。合并前请自行在 Windows 与 Linux 环境测试:

  • Linux:建议在 dbus-run-session 内跑 ./scripts/test-linux-mock.sh,并真机复核托盘(SNI/dbusmenu)、壁纸(GNOME/KDE/Hyprland)、外观、通知、注销链路;
  • Windows:建议真机复核托盘显隐/tooltip/菜单/explorer 重启恢复、GDI 句柄数不增长、通知与媒体控制。

修复明细(按域)

工程门禁(P0-1)

  • uda-ffi 恢复在非 Linux/Windows 目标编译:补 appearance()/wallpaper() 的 cfg 备援后端、tokio 使用纳入门禁、缺失导入与 unused import 清理。

Windows 托盘(P0-4/5/6、P1-2429、P2-4144、P2-33、P1-10)

  • hide()→show() 往返修复(dwStateMask=NIS_HIDDEN);CreateIconIndirect 前后两张 HBITMAP 在全部路径 DeleteObject(含 mask 失败早退);show_menu 拆为两段,模态泵期间不再持有任何 &mut Worker(别名 UB 消除),重入守卫改为 per-worker 字段;worker 窗口改为隐藏顶层窗口并处理 TaskbarCreated,explorer 重启后图标可恢复;补 NIF_SHOWTIP、NIN_SELECT/NIN_KEYSELECT 映射、WM_CONTEXTMENU 光标锚点;NIM_MODIFY 失败销毁新 HICON;缓存仅在成功后推进(失败自动重试);AppendMenuW 全量检查;AUMID 提前返回不再泄漏 CoTaskMem、availability() 不再抢注身份;空壁纸路径归一为 NULL;播放器拒绝命令报 -2。

Linux 平台(P0-3、P1-15/19/21、P2-39/40、P1-10)

  • Hyprland 链路重写:hyprpaper 经 hyprctl hyprpaper preload/wallpaper,swww 先探测 daemon、必要时后台拉起,全部子进程带 10s 超时,hyprland 会话下失败向上报错不再静默降级;同步 API 统一经 Handle::try_current() 分流桥(运行时内调用不再 panic);logout 回退按各桌面真实签名传参、修正 XFCE 服务名/路径;GNOME 47 强调色枚举映射 HIG 调色板、能力位经真实探测声明;全部 D-Bus 往返加超时;实现 GNOME get_wallpaper 并对齐 GET_WALLPAPER 能力位。

Linux 托盘(P1-1622、P2-3638、P2-50)

  • worker 就绪握手:初始化失败时 create() 返回错误,不再"能力全绿的隐形图标";菜单指纹替代 Arc::as_ptr,真实发射 LayoutUpdated(+规范要求的 NewToolTip);GetLayout 按 -1/0/n 精确解释深度并支持 parent_id 子树寻址;dbusmenu id 持久映射,增删行后不再漂移;根节点补 Version/TextDirection/Status;点击回调 panic 安全归还;无双击回调时第二次点击回退 on_click;图标空闲 tick 零转码;降级路径真实返回 SupportLevel::Partial(reason)。

FFI 契约(P0-7 FFI 侧、P1-8~14/32、P2-33/34/35/48)

  • uda_tray_menu_destroy 先把菜单从所有存活图标解挂再删记录,配合 attach 单锁线性化,销毁后点击不可能触达已释放回调;uda_last_error_message 改为线程局部持久缓冲借用,三处文档同步,绑定不再逐次泄漏;set_action 失败上报;复选框蹦床改 Weak 捕获(泄漏环消除);行 id 直接取 push 返回值;注册表单锁判型;核心层新增 UdaError::InvalidArgument,映射 UDA_ERR_INVALID_ARGUMENT(-1)(空标签等入参错误不再伪装成"不支持");空字符串字段导出 NULL;wakelock 惰性回收(僵尸进程与死状态清理);Tier-3 壁纸探测仅 GNOME 族采信;新增 uda_tray_capabilities 导出(8 个 UDA_TRAY_CAP_* 位,append-only,头文件符号校验 33/33 通过)。

绑定与 CLI(P0-7 绑定侧、P1-30/31、P2-45~47/49)

  • Node TrayMenu.destroy() 调整为"先 FFI 销毁、后反注册蹦床",UAF 路径双向封死;_readWallpaper 单次调用同槽双读;new Uda(libraryPath) 显式路径生效;行添加失败释放 koffi 槽位;Python 透传真实状态码、非 NULL 空串照常释放;uda-cli 消除裸 unwrap 与硬编码路径,新增 --dry-run 与参数解析,补 cfg 门禁。

验证

门禁 结果
cargo check --workspace --all-targets(macOS 宿主) 0 错误 0 警告
cargo check --target x86_64-pc-windows-msvc --workspace 0 错误 0 警告
cargo check --target x86_64-unknown-linux-gnu --workspace 0 错误 0 警告
cargo test -p uda-core / -p uda-ffi / -p uda-platform-linux 58 / 110 / 116 全部通过
scripts/header_symbols.py 头文件↔导出符号 33/33 一致

风险与后续

  • Linux D-Bus 行为(dbusmock / 真实 session bus)与 Windows 真机行为需合并者自行在对应平台验证(见顶部提示)。
  • 关于 UDA 代码审查问题清单 #4 中提到的"C 头文件考虑用代码生成器生成": 另开 issue 跟进,本 PR 不含。
  • 同步桥在 uda-platform-linux/src/sync.rs 与 uda-ffi/src/notify.rs 各有一份同构实现(core 不依赖 tokio 是有意的设计取舍),两处已互相注明需同步演进。

umbrella22 and others added 2 commits October 7, 2026 08:39
…ktop#4)

Batch fix for the review checklist in UniDesktop#4. The two items
already fixed in v0.2.1 (P0-2 dbusmenu wire type, P1-23 SNI Menu
object path) are untouched; everything else lands here, including the
production-side completion of P2-50 (Partial(reason)).

Highlights by area:

Engineering gate (P0-1)
- uda-ffi compiles on non-Linux/Windows targets again: cfg-fallback
  appearance()/wallpaper() backends, gated tokio use in notify, missing
  UdaError import, unused imports.

Windows tray (P0-4/5/6, P1-24~29, P2-41~44, P2-33, P1-10)
- hide/show round-trip (dwStateMask=NIS_HIDDEN), every GDI bitmap
  released on all paths, two-phase show_menu removes the &mut aliasing
  UB across the modal pump, per-worker reentrancy guard, TaskbarCreated
  re-registration on a hidden top-level window, NIF_SHOWTIP,
  NIN_SELECT/NIN_KEYSELECT, NIM_MODIFY failure no longer leaks the new
  HICON, caches advance only on success, AppendMenuW results checked,
  AUMID early-return no longer leaks CoTaskMem, empty wallpaper reads
  as NULL, refused media commands report NotSupported.

Linux platform (P0-3, P1-15/19/21, P2-39/40, P1-10)
- hyprpaper driven via hyprctl, swww daemon probe/spawn with timeouts,
  no silent Hyprland fallback; nested-block_on panic fixed via a
  try_current bridge; per-DE logout signatures and the correct XFCE
  names; GNOME 47 accent palette mapping with capability probing;
  timeouts on all D-Bus round trips; get_wallpaper implemented for
  GNOME with the capability bit aligned.

Linux tray (P1-16~22, P2-36~38, P2-50)
- readiness handshake fails create() instead of advertising a dead
  icon; menu fingerprinting with real LayoutUpdated emission; exact
  parent_id/recursionDepth semantics; stable dbusmenu id map; root
  node standard properties; panic-safe handler restore; second click
  falls back to on_click; icon transcode cache; honest Partial(reason)
  support levels; NewToolTip signal.

FFI contract (P0-7 FFI side, P1-8~14/32, P2-33/34/35/48)
- destroy_menu detaches the menu from every live icon before the
  registry record goes; last-error message served from a thread-local
  borrow (no per-failure leak, docs aligned across lib/uda.h/bindings);
  set_action failures reported; Weak trampoline capture; push ids used
  directly; single-lock type checks; InvalidArgument variant mapped to
  UDA_ERR_INVALID_ARGUMENT; empty strings export as NULL; lazy
  wakelock reaping; GNOME-only Tier-3 wallpaper probe; new
  uda_tray_capabilities export.

Bindings & CLI (P0-7 binding side, P1-30/31, P2-45~47/49)
- Node destroy() unregisters trampolines only after the FFI destroy;
  single-call wallpaper read; explicit library path honoured; failed
  row adds release their koffi slots; Python passes the real status
  code and releases empty strings; uda-cli loses its unwraps, hardcoded
  path, and side effects (--dry-run, argument parsing, cfg gating).

Verification: cargo check --workspace --all-targets clean on macOS,
x86_64-pc-windows-msvc and x86_64-unknown-linux-gnu; 58 + 110 + 116
tests pass; header/export symbol check 33/33.
The transport test swapped the menu and immediately clicked dbusmenu id
1. Under the stable id map that id belongs to the previous layout and
stays retired, so the click landed on no row and the counter stayed at
zero - the CI run with a real session bus exposed it. The test now pins
both halves of the contract: a click on the stale id is dropped, and a
click on the id handed out by a fresh GetLayout read fires.

Also applies the rustfmt cleanups the lint job flagged.
@SRInternet
SRInternet self-requested a review October 7, 2026 06:12

@SRInternet SRInternet left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审查完毕,存在问题需要修复

审查过程包含离线测试。由于出现问题,本次审查未进行实机测试。
本次审查对象为来自 @umbrella22 的 2 commits based on tag v0.2.1(包含 47 文件 和 +6200 / −1664 变更)


摘要

本 PR 在工程实现层面质量显著高于社区平均水准,且全量编译 / 测试 / 交叉编译 / 符号一致性检查全部真实通过。但 3 项阻塞性问题破坏性契约变更,按生产环境零事故标准不予放行。修复阻塞性问题并补齐后,本 PR 具备合并条件。

阻塞性问题(High or Higer,影响 merge)

1. uda_last_error_message() 所有权语义翻转(ABI 运行期断裂)

位于 crates/uda-ffi/src/util.rs:60-91、include/uda.h:504-517

uda_last_error_message() 的所有权语义被翻转:由"新分配、调用方必须 uda_free_string()"改为"借用库所有的线程局部缓冲区、禁止 free"。这是运行期破坏性变更——旧宿主继续 free() 将触发堆损坏;且缓冲区在下一次同一线程的调用即被覆写,旧宿主若延迟读取会拿到错误文本

2. Theme / SupportLevel 破坏性变体变更,但无 #[non_exhaustive] (存疑)

位于 crates/uda-core/src/capability.rs:86-101、crates/uda-core/src/capability.rs:103-Theme 与 SupportLevel 两个 pub

enum 做了破坏性签名变更:SupportLevel::Partial 由 unit variant 变为 Partial(String)(同时移除 Copy);Theme 新增 Unknown 变体。这是正确的,但问题在于未标 #[non_exhaustive]。任何下游 crate 对这两个枚举做穷尽 match 或依赖 Partial 的 Copy 语义,升级后必然编译失败。

3. Windows 工作窗口缺 WS_EX_TOOLWINDOW,产生幽灵窗口

位于 crates/uda-platform-windows/src/tray.rs:95、crates/uda-platform-windows/src/tray.rs:99、crates/uda-platform-windows/src/tray.rs:1092-1114

Windows 隐臧工作窗口缺少 WS_EX_TOOLWINDOW(WINDOW_EX_STYLE_BITS = WINDOW_EX_STYLE(0))却使用 WS_OVERLAPPEDWINDOW 样式。该窗口虽为 0×0 且无 WS_VISIBLE,但仍会在 Alt+Tab 切换列表、任务管理器"进程/窗口"视图、EnumWindows 枚举中作为可枚举顶层窗口出现,形成"幽灵窗口"

二级问题(Medium,强烈建议修复)

1. CHANGELOG.md 缺失本 PR 条目

位于 CHANGELOG.md:1-58

本 PR 是"一次性修复 48 项缺陷"的大改动,但 CHANGELOG 的整个 diff 只是在 main 基础上补写 v0.2.1 已发布节点(CHANGELOG.md:12-49)与修复链接定义(CHANGELOG.md:113-115)。[Unreleased] 链接引用仍在(CHANGELOG.md:113)但没有任何 [Unreleased] 内容节,48 项修复全部无记录。更严重的是 CHANGELOG.md:48-49 写明"示例代码无需调整:本次为修复性补丁,未新增或改动任何 API"——这句断言对本 PR 是虚假的(本 PR 改动了 SupportLevel、Theme、UdaError 变体、FFI 签名)。

2. 17 个 FFI 导出由 extern "C" 改为 unsafe extern "C" 未做兼容性说明

位于 crates/uda-ffi/src/lib.rs(24 → 25 处 pub unsafe extern "C" fn,整体导出 33 个中 25 个为 unsafe)

main 基线为 8 safe + 24 unsafe;本 PR 后为 8 safe + 25 unsafe,即约 17 个原本 safe 的导出被改为 unsafe。对 C 调用方无影响(unsafe 不影响 C ABI 与链接),但对 Rust 侧直接 extern "C" 声明调用的宿主是编译期破坏。

3. 托盘窗口过程缺 WM_NCDESTROY 的 GWLP_USERDATA 清理

位于 crates/uda-platform-windows/src/tray.rs:1824-1859(窗口过程全文无 WM_DESTROY / WM_NCDESTROY / PostQuitMessage 分支)

当前设计靠 teardown() 主动 DestroyWindow(:1677)后由 run() 退出循环(:1392-1394),自洽。但窗口过程完全没有 WM_NCDESTROY 分支清空 GWLP_USERDATA(:1129 写入的 *mut Worker)。若因任何外部原因(宿主进程被 EndTask、调试器、或未来新增的提前 DestroyWindow 路径)窗口先于 worker 销毁,窗口过程将继续解引用已悬空的 Worker 指针。同理缺 WM_DESTROY → PostQuitMessage 使消息循环生命周期只依赖 on_tick 返回值,闭环路径单一。

4. cargo doc 新增 8 条 rustdoc 警告

位于 crates/uda-platform-windows/src/lib.rs:3(cfg(windows))、crates/uda-platform-windows/src/lib.rs:11-16(appearance/wallpaper/wakelock/detection/notification/tray)、crates/uda-core/src/notification.rs:119(public doc 链接 private is_absolute_path)

在 #[cfg(windows)] 门控下编译时模块不可见,导致模块级清单的 intra-doc 链接无法解析;uda-core/src/notification.rs:119 的公开文档链到了私有函数。

三级问题(Low,建议修改)

1. WINDOW_STYLE_BITS 过宽

位于 crates/uda-platform-windows/src/tray.rs:95

即便加上 WS_EX_TOOLWINDOW,WS_OVERLAPPEDWINDOW 仍带 WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,对"永不可见"的窗口是多余 surface。WS_POPUP 已足够接收广播。

2. sync.rs 无条件预构建 runtime

位于 crates/uda-platform-linux/src/sync.rs:43-52

tokio::runtime::Builder...build() 在 Handle::try_current() 判定之前执行,导致 ambient-runtime 分支下构建出的 runtime 还要移动到工作线程。功能正确(与 FFI 侧 run_sync 行为等价),但顺序不如 crates/uda-ffi/src/notify.rs:110-113(先判定后构建)。


本 Comment 由人工编写,审查建议中包含 AI 给予的建议。

…ndow

Review round on PR UniDesktop#5 (CHANGES_REQUESTED). Blocking items:

- uda_last_error_message returns a caller-owned allocation again: the
  borrow-the-thread-local-buffer contract this branch introduced was a
  runtime ABI break for hosts that (per the previous header) free the
  string. Ownership semantics are now byte-for-byte what v0.2.1 shipped;
  only the previously contradictory docs are aligned, and both bindings
  release the string through uda_free_string after a single in-place
  decode.
- Theme, SupportLevel and UdaError are #[non_exhaustive]: these enums
  gained variants across patch releases, so downstream exhaustive
  matches stop being compile-time breaks. Cross-crate matches in the
  workspace carry sensible wildcard arms.
- The Windows worker window gets WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE so
  it stops appearing in Alt+Tab, Task Manager and EnumWindows.

Medium items: CHANGELOG gains an [Unreleased] section covering all 48
fixes plus an unsafe-extern compatibility note (the extern flips shipped
in v0.2.1, not here); WM_NCDESTROY now clears GWLP_USERDATA and posts a
quit so an externally destroyed window cannot leave a dangling worker
pointer; the remaining rustdoc warnings (private-item links, cfg-gated
module lists) are gone - cargo doc --no-deps is warning-free.

Low items: the worker window drops to WS_POPUP (the caption/frame bits
of WS_OVERLAPPEDWINDOW were dead surface), and run_async branches on the
ambient runtime before building one, matching the FFI-side run_sync.
…uracy

Cross-validation findings on the maintainer-review fixes:

- Node: decoding the error-message pointer with koffi 'char *' read a
  pointer AT the address and crashed the process (SIGSEGV) on the first
  failure; decode in place via koffi's NUL-terminated char read instead.
  readSlot grows an optional length argument, byte-identical for every
  existing call site.
- Python: c_void_p addresses cannot pass through the c_char_p argtype of
  uda_free_string (ArgumentError masked the real error); the argtype is
  now c_void_p, which accepts both raw addresses and the slot objects
  the wallpaper and media paths pass.
- sync.rs: the worker-branch runtime-build failure propagated as
  UdaError::Io instead of the documented Internal, and skipped the
  worker join; the worker now maps the failure to Internal with the
  inline branch's wording and the caller always reaps.
- Comment and changelog accuracy: WS_EX_TOOLWINDOW excludes Alt+Tab and
  the taskbar but not EnumWindows (that needs its own filter); the FFI
  exports were never flipped to unsafe extern - pointer-taking exports
  were introduced that way, so the compatibility note now states the
  actual history.
- Last-error tests strengthened: pointers must differ across renders,
  the first allocation must survive the second render, and the public
  uda_free_string export is exercised end to end.
…ase symmetry

Final validator findings on the review round:

- CHANGELOG: the four findings that had no entry of their own (the
  cross-compile build gate, the last-error ownership contract itself,
  the media refusal status code, and the registry double-lock window)
  are now recorded, and the unsafe-extern note no longer implies that
  absence of pointer parameters is what makes an export plain.
- crates/uda-ffi/src/error.rs: the module doc still described the
  previous borrow-until-next-call contract; it now matches the
  caller-owned one used everywhere else.
- Node: the error-message pointer is released in a finally block, so a
  decode failure cannot skip the free; the koffi version comment now
  names the version the lockfile actually pins (2.x) instead of 3.x.
- Python: a contrast note at the uda_status_message declaration records
  why that one keeps the c_char_p restype (a static string, never
  freed).
@SRInternet

Copy link
Copy Markdown
Member

关于审查建议的修正

关于 CHANGELOG.md:

由于本 PR 合并后将作为未来的 v0.2.2 版本发布,你不必为此有负担,CHANGELOG 可以暂时保留在 [Unreleased],或者交由我们在后续合并发版时统筹整理。

稍后我们会在 Windows 物理机上对涉及的 GDI 资源释放、Worker 顶层窗口以及托盘自愈链路进行真机 Smoke Test。如果实机测试通过,我们将尽快合入。

再次感谢您对 Universal Desktop Community 及本项目 UniDesktop API 的贡献与帮助。本 PR 会持续跟进。

@SRInternet

Copy link
Copy Markdown
Member

你可以在彻底完成一次修复后通过在 Comment 中 Mention 我,以通知我的审查。

@SRInternet SRInternet added bug Something isn't working enhancement New feature or request labels Oct 7, 2026
…ntract

Follow-up to the maintainer review's unsafe-extern finding. The finding
rested on a comparison against a pre-v0.2.0 baseline: no export was ever
flipped - pointer-taking exports were unsafe from introduction (v0.2.0)
and the eight value-only exports have always been plain. The unsafe on
pointer-taking exports is sound: a Rust caller passing a dangling
non-null pointer is UB, which a safe function cannot accept, so
downgrading them would trade correctness for appearances.

What this adds instead is the guarantee the review actually wanted - no
surprise reclassification:

- export_safety_classification_is_pinned scans the crate source and
  asserts the exact value-only set (8 exports) against every other
  export being unsafe, and that the surface still totals 33. Moving an
  export between extern "C" and unsafe extern "C" now fails CI unless
  the contract list and the changelog are updated deliberately.
- The changelog compatibility note now states the caller impact
  precisely: extern-block Rust hosts are unaffected on every edition
  (a definition-side unsafe keyword cannot break a block declaration);
  only rlib consumers see the unsafe requirement, unchanged since
  v0.2.0.
Cross-validation on the classification pin: the constant's doc claimed
every export outside the list dereferences caller memory, but seven
exports (the session controls and the media command) take no pointer and
are still unsafe by policy. The list is now PLAIN_EXTERN_C_EXPORTS and
its doc states it pins the classification rather than deriving it; the
scanner drops an unreachable default branch so a malformed signature
would fail loudly instead of being skipped.
@umbrella22

umbrella22 commented Oct 8, 2026 •

Copy link
Copy Markdown
Author

感谢细致的审查,9 项意见已全部处理完毕,5 个新提交(f786830 → 20cb0ab),CI 四项检查全绿。逐项回复如下:

阻塞性问题

1. uda_last_error_message() 所有权翻转 —— 已回退。实现恢复为与 v0.2.1 发布版逐字一致的"新分配、调用方 uda_free_string() 释放"(CString::into_raw),按旧头文件编写的宿主无论 free 与否都不会再出问题;这次真正修复的是三处互相矛盾的文档(现统一为"调用方所有"),Node / Python 绑定改为单次调用 + 原地解码 + finally 释放。两种绑定都用真实 dylib 做了运行验证:失败路径 5000 次调用零泄漏(阳性对照可检出泄漏)、消息解码正确。

2. Theme / SupportLevel 缺 #[non_exhaustive] —— 已采纳。Theme、SupportLevel 与 UdaError(InvalidArgument 也是本节新增变体,一并处理)三个枚举均已标注 #[non_exhaustive],工作区内所有跨 crate 的穷尽 match 补了语义化通配臂(未知错误映射 Internal,绝不伪装成 not supported)。CHANGELOG 已记录该变更。

3. Windows 工作窗口幽灵化 —— 已加 WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE,并采纳三级建议把样式降为 WS_POPUP(OVERLAPPEDWINDOW 的标题栏/边框位对永不可见窗口是多余 surface)。同时补了二级 #3 的 WM_NCDESTROY 分支:清 GWLP_USERDATA + PostQuitMessage,外部销毁路径不再有悬空指针,消息循环也有了标准退出路径。

二级问题

  1. CHANGELOG —— 已补 [Unreleased] 节,48 项修复逐域记录;v0.2.1 节"示例代码无需调整"一句已收窄为"仅就 v0.2.1 自身而言"。
  2. unsafe extern "C" —— 这条做了 git 考古后结论与原审查不同:没有任何导出被翻转过。指针类导出(托盘/通知/强调色/媒体/会话)自 v0.2.0 引入时就是 unsafe extern "C"(逐提交 pickaxe 验证),v0.1.0 的 3 个普通导出均不带指针参数;对比基线应该是更早的版本。且指针类导出标 unsafe 是健全分类——传入非空悬垂指针属于调用方 UB,不能降级为 safe。为杜绝此类"静默重分类",新增 export_safety_classification_is_pinned 测试把 8 plain / 25 unsafe / 共 33 的分类钉死:今后任何漂移都会直接打红 CI,必须显式更新名单与 CHANGELOG。兼容性说明也在 CHANGELOG 写明:经 extern "C" {} 块声明的 Rust 宿主在任何 edition 都不受定义侧关键字影响,仅 rlib 直接依赖场景(与 v0.2.0 以来一致)要求 unsafe。
  3. WM_NCDESTROY —— 已加,见阻塞 修正 Linux 托盘菜单的发现与数据格式 #3。
  4. rustdoc 警告 —— cargo doc --no-deps 现为 0 warnings(私有项链接、cfg 门控模块清单链接全部改为不可失败的写法)。

三级问题

验证

除 CI 四项(Lint / Linux / Windows / Cross-Compile)全绿外,本机还在 dbus-run-session 真实总线上跑通了全部 284 项测试(含此前跳过的传输层测试),绑定用真实 dylib 做过失败路径的崩溃/泄漏探针。Linux 与 Windows 真机行为仍以你实机复核为准。

@SRInternet SRInternet left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审查完毕,达成合并条件

审查过程包含离线测试 + Windows Subsystem for Linux 2 (Ubuntu) 中无头环境的实机测试
本次审查对象为来自 @umbrella22 的 7 commits based on tag v0.2.1


摘要

本 PR 满足 v0.2.2 bugfix 的合并门槛,上一轮提出的 3 项 P0(uda_last_error_message() 所有权语义翻转、Theme/SupportLevel 缺 #[non_exhaustive]、Windows 幽灵窗口)与全部 P1/P2 均已修复并复测通过,未引入任何 semver-breaking 公共 API 变更。

但本次跟进审查(WSL 无头环境全示例实跑)暴露了 5 项非阻塞性技术债务与事实偏差。以下逐项列明证据跟进与修复的必要性。

存在问题(Medium or higher,建议修复)

1. Theme 检测在无头环境返回错误的 light

位于 crates/uda-platform-linux/src/appearance.rs

parse_theme_from_gtk_theme 没有 None 出口——任何非 -dark 后缀一律判 Light。WSL 中该 schema 由 GTK 包装带入,gtk-theme 取的是从未被设置过的 schema 默认值 'Adwaita'(本机无 dconf 后端)。函数把一个无人配置过的默认值当成了确定答案。这违反了 appearance.rs:27-30 的设计意图。

2. wakelock 的 Tier-3 兜底可能完全无效,宿主无从知晓

位于 crates/uda-ffi/src/wakelocks.rs

机制层面 wakelocks.rs:167-192 的兜底、3600s 上限、到期回收与 kill+reap 都真实闭环。但 WSL 无 systemd-logind,没有任何组件受理该 inhibit 锁——屏幕该休眠照样休眠,而宿主收到的是 UDA_OK。

3. tray 降级状态对宿主不可见

位于 crates/uda-platform-linux/src/tray.rs

降级原因仅进 log::warn,而 log crate 无默认实现,宿主永远看不到(实测 RUST_LOG=debug 输出为空正是此因)。这是符合 tray_specs.md §1.6 的有意设计(watcher 可能后启动,item 先导出),取舍正确。然而 WSL 下托盘图标不可能显示,宿主却收到成功。SupportLevel::Partial(reason) 机制本 PR 已经就绪,只差把它暴露给宿主。

4. 07_session 能力矩阵无任何探测即宣称全支持

位于 examples/python/07_session.py 和 examples/nodejs/07_session.js

WSL(无 org.freedesktop.login1、无 ScreenSaver、无 session manager)实跑 07_session,六项动作全部列出"支持",未做任何一次 D-Bus 可达性探测,直接打印硬编码说明文档。对比 02_wallpaper/03_notification 会真实调用并报错、06_media 明确标注"这不算错误",07_session 是全套示例中唯一无条件宣称成功的。

5. Python 与 Node.js 错误语义分叉

位于 crates/uda-ffi

这是本 PR 波及的绑定层行为差异,且 PR 的 48 项清单虽包含 Node 侧修复(new Uda(path) 参数丢弃、_readWallpaper 双调用、koffi 槽位泄漏),却未统一错误退出语义。宿主集成预期会因此不一致。


本 Comment 由人工编写,审查建议中包含 AI 给予的建议。

The maintainer's WSL2 follow-up review confirmed all blocking and
P1/P2 findings fixed, and raised five non-blocking honesty gaps:

- Theme detection no longer treats a never-configured gtk-theme schema
  default as a light answer: a -dark suffix answers Dark, a Light
  answer requires dconf read to prove the key was explicitly set, and
  everything else falls to Theme::Unknown.
- The wakelock CLI fallback verifies org.freedesktop.login1 on the
  system bus before spawning systemd-inhibit; an unlockable lock now
  fails with NotSupported instead of reporting success. Wake-lock
  capabilities and support_level are driven by real reachability,
  reporting Partial(reason) when only a degraded tier is reachable.
- Tray degradation becomes host-visible: the degraded reason recorded
  at worker readiness (a missing watcher) now flows into the icon
  state, LinuxTrayManager::support_level reports Partial(reason), and
  two new exports (uda_tray_support_level / uda_tray_support_reason
  with the UDA_TRAY_SUPPORT_LEVEL_* constants) let hosts query level
  and reason per handle. Windows reports FULL / NULL.
- 07_session (Python and Node) probes uda_session_capabilities first
  and renders the support matrix from the probe, instead of printing a
  hardcoded all-supported table.
- The Node binding gains a UdaError class carrying the numeric status
  plus exported status-code constants, aligning error semantics with
  the Python binding.

Verified across macOS / windows-msvc / linux-gnu targets (0/0), the
full suite under a real D-Bus session (304 tests, including the new
watcherless-degradation transport test), cargo doc and clippy clean.
Cross-validation follow-ups on the headless-honesty round:

- The KDE and XFCE theme fallbacks had the same guess-Light leak the
  GNOME path just lost: any non-dark scheme name answered Light, and
  tooling-written defaults (Breeze, Greybird) would masquerade as a
  configured light theme. Both now share one dark-name-or-unknown rule,
  and XFCE reads the actual theme key (xsettings /Net/ThemeName) rather
  than the desktop backdrop mode it used to query.
- TrayIcon::set_degraded normalises empty reasons to None: uda.h
  promises PARTIAL always carries a non-empty explanation, and an empty
  string would have surfaced as PARTIAL with a NULL reason.
- The Node binding's macOS library candidates include libuda_ffi.dylib,
  so the documented 'cargo build && node examples/nodejs/07_session.js'
  flow works without setting UDA_LIBRARY.
- Python __all__ exports the status-code constants, matching the Node
  module.exports surface.

AGENTS.md was checked for the gen-header drift the validators flagged:
the on-disk file makes no such reference, so no change was needed.
@umbrella22

Copy link
Copy Markdown
Author

@SRInternet 第二轮复审的 5 项意见已全部修复完毕,新提交 79b672f + 9baf8c5,CI 四项检查全绿。逐项回复:

1. Theme 检测在无头环境返回错误的 Light —— parse_theme_from_gtk_theme 已诚实化:-dark 后缀 → Dark;Light 必须由 dconf read /org/gnome/desktop/interface/gtk-theme 证明该键被显式设置过(dconf 缺失视为未证明);其余一律 None 落到 Theme::Unknown。新增 7 个单测覆盖各分支。

2. wakelock 的 Tier-3 兜底可能完全无效 —— acquire_cli 在 spawn systemd-inhibit 前先验证系统总线上确实存在 org.freedesktop.login1(zbus ListNames + 超时,经既有 sync 桥);探针失败与确认缺失同样 fail-closed,返回 UDA_ERR_NOT_SUPPORTED("no systemd-logind on the system bus: an inhibition lock would not be honored"),不再对无人受理的锁返回 UDA_OK。wakelock 的能力位与 support_level 也改为由 logind / ScreenSaver 实际可达性驱动,仅 CLI 层可达时报 Partial(reason)。

3. tray 降级状态对宿主不可见 —— 按你的建议直接用了已就绪的 SupportLevel::Partial(reason) 机制:降级原因现在记录在图标状态上(create 的就绪握手后写入,宿主拿到句柄前即已确定),LinuxTrayManager::support_level 据此返回 Partial(reason);并按 append-only 约定新增两个 C 导出 uda_tray_support_level / uda_tray_support_reason(附 UDA_TRAY_SUPPORT_LEVEL_NONE/PARTIAL/FULL,reason 串归调用方所有、无降级时为 NULL;Windows 无降级路径恒 FULL/NULL)。新增的 watcherless 传输测试在真实隔离总线上端到端验证了 Degraded → Partial(reason) 全链路。

4. 07_session 无探测即宣称全支持 —— Python 与 Node 两个示例都改为启动即查询 uda_session_capabilities,六项动作矩阵、逐项说明与锁屏演示门槛全部以探测结果渲染,不支持的动作为"当前环境不支持(探测结果)"且不再调用;破坏性动作的确认门槛原样保留。本机(掩码为空)实跑两版均如实渲染"不支持"。

5. Python 与 Node.js 错误语义分叉 —— Node 新增 class UdaError extends Error(name/status/message,与 Python 的 UdaError(status, message) 字段对齐),_check 全部改抛 UdaError,并导出 OK 与全部 ERR_* 状态码常量(与 uda.h 逐值核对一致),宿主可按 -1/-2/-5 分流。PNG 解码段刻意保留独立错误类型(对应 Python 的 PngError,FFI 之前发生、无状态码)。

交叉验证额外收口(验证代理发现,一并修了):KDE / XFCE 的 theme 回退存在与 GNOME 同类的"猜 Light"旁路(工具写入的 Breeze/Greybird 默认值会被当答案),已统一为"深色名才答 Dark、否则 Unknown";XFCE 原来查询的 /backdrop/screen0/mode 是壁纸属性而非主题键,已改读 xsettings /Net/ThemeName;TrayIcon::set_degraded 归一空串 reason(uda.h 承诺 PARTIAL 必有非空原因);Node 的 macOS 候选库补上 libuda_ffi.dylib(示例文档的 cargo build && node 流程现在开箱即用);Python __all__ 补导出状态码常量,与 Node 的导出面一致。

验证:macOS / windows-msvc / linux-gnu 三目标 cargo check --workspace --all-targets 零错误零警告;真实 D-Bus 会话内 305 项测试全过(含新增的降级传输测试);cargo doc / clippy / fmt 干净;头文件↔导出符号 35/35 一致。CHANGELOG [Unreleased] 已同步。请复审。

Final polish from the cross-validation pass on the headless-honesty
round (comment/doc/test only, no behaviour change):

- The wake-lock support_level doc states precisely which layer the
  Partial tier describes: this manager's own acquire speaks ScreenSaver
  only; the systemd-inhibit CLI tier the reason points at lives in
  uda-ffi's registry.
- The appearance module's fallback-chain list now names the XFCE key
  the code actually reads (xsettings Net/ThemeName).
- tray_specs.md 1.7 marks Show icon Partial when the watcher is absent,
  matching the new degradation reporting.
- The Node binding picks its library filename from a platform map
  (mirroring the Python binding) instead of a nested ternary, and the
  British spelling and blank-reason wording are evened out.
- A regression test pins TrayIcon::set_degraded's blank-reason
  normalisation (Some("") and whitespace land as None / Full), and a
  redundant hand-paired assertion is gone.

@SRInternet SRInternet left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审查完毕,达成合并条件

审查过程包含离线测试 + Windows Subsystem for Linux 2 (Ubuntu) 中无头环境的实机测试
本次审查对象为来自 @umbrella22 的 10 commits based on tag v0.2.1


摘要

本 PR 满足 v0.2.2 bugfix 的合并门槛,本轮新增两 commit 精准响应了此前提出的 5 个二级问题,且其中两项已 WSL 实机证实确已修复。未发现新的阻塞性缺陷。

但本次跟进审查(WSL 无头环境全示例实跑)依旧暴露了 3 项非阻塞性技术债务与事实偏差。以下逐项列明证据跟进与修复的必要性。

存在问题(Medium or higher,建议修复)

1. Python/Node 示例错误退出语义仍未统一(exit 0 vs 1)

位于 examples/{python,nodejs}/*

进程退出语义仍未统一——Python 示例捕获后 exit=0,Node 示例仍为未捕获异常 exit=1。这是示例层选择,非库契约问题。

2. 不得以"进程/服务存在"替代"该功能在本环境真正可用"作为判断依据

位于 crates/uda-platform-linux/src/session.rs:477-490

零运行时探测,硬编码返回全部 7 个能力位。 这是 79b672f 宣称"07_session 改为实时探测"后唯一未被改到的模块——该 commit 只改了示例文件(examples/{python,nodejs}/07_session.*)去读取 uda_session_capabilities,却没有改后端 capabilities() 本身去真实探测。

3. 错误码与 uda.h 契约不符

位于 include/uda.h:421-425

include/uda.h:421-425 明确承诺:

A set bit means "the code path exists", not "the account is allowed": a machine with hibernation switched off still reports UDA_SESSION_CAP_HIBERNATE, and the attempt then fails with UDA_ERR_NOT_SUPPORTED.

实测却返回 -5 UDA_ERR_INTERNAL(Command failed: loginctl lock-session exited with 1),来自 session.rs:226-228 的 UdaError::CommandFailed。

uda.h 承诺 -2,实际给 -5。宿主按契约写 if (status == UDA_ERR_NOT_SUPPORTED) 降级,会漏判这种情况。这是比能力位误报更实的契约违反。


本 Comment 由人工编写,审查建议中包含 AI 给予的建议。

@SRInternet

Copy link
Copy Markdown
Member

请先同步分支 develop 最新内容后继续变更,并对分支 develop 发起 PR 而不是分支 main。

@SRInternet
SRInternet changed the base branch from main to develop October 8, 2026 15:12
Third-review round on PR UniDesktop#5:

- LinuxSessionManager::capabilities no longer hardcodes the seven
  session bits. The matrix is rendered from runtime reachability:
  org.freedesktop.login1 on the system bus answers the full set,
  org.freedesktop.ScreenSaver alone answers LOCK only, and neither
  (a WSL headless session) answers honestly with an empty set. The
  probe is cached per process; failures fold to absent, the same
  conservative direction as the wake-lock and accent-colour probes.
  The capability-bit paragraph in include/uda.h now describes the
  probed semantics.
- Session attempt failures report UDA_ERR_NOT_SUPPORTED (-2) as the
  header promises, not UDA_ERR_INTERNAL (-5): loginctl non-zero exits
  keep their diagnostics ('loginctl lock-session exited with 1'),
  polkit refusals, missing ScreenSaver services and missing CLIs all
  land on the single not-delivered constructor. A contract test
  enumerates every mapped path.
- The Node examples render failures exactly like the Python
  _bootstrap: one 'UDA 调用失败: …' line and exit 1 when the
  environment cannot answer (previously an uncaught stack trace),
  while 07_session keeps 'probe throws -> 1, everything unsupported
  -> 0' on both sides.
Follows the round-3 validation pass on 2e9ebf9:

- session.rs reuses wakelock::screensaver_present instead of carrying a
  verbatim copy, which also puts bus_has_service back to private; the
  review-round narration comments are trimmed to the constraints that
  outlive the review.
- The probed capability semantics 2e9ebf9 put into include/uda.h are now
  stated consistently by every source: the Rust doc comments (uda-ffi
  lib.rs, uda-core session.rs), session_specs.md section 5 and the
  capability table, the 07_session examples' copy, and the
  session.capabilities docstrings in the python/nodejs bindings. The
  'static query' phrasing becomes 'probed once, cached', because the
  Linux backend answers from a cached ListNames probe.
- The module doc's dash list tripped clippy's doc_lazy_continuation
  under -D warnings (the Lint & Format job); it is a plain parenthetical
  now.
@umbrella22

Copy link
Copy Markdown
Author

@SRInternet 第三轮的三项问题已全部修复,并完成了交叉验证与文档一致性收尾。

1. 能力位真实探测(crates/uda-platform-linux/src/session.rs,2e9ebf9) — LinuxSessionManager::capabilities 不再硬编码七位,改为运行时探测矩阵:

  • org.freedesktop.login1(系统总线)可达 → 全部七位;
  • 仅 org.freedesktop.ScreenSaver(会话总线)可达 → 只有 LOCK;
  • 两者皆不可达(WSL 无头会话)→ 空集。

探测按进程缓存一次,失败一律按"不可达"收敛(与 wake-lock / accent 探测同一保守方向)。include/uda.h 的能力位段落同步改为探测语义。实测验证两个象限:裸 dbus-run-session(无 ScreenSaver、无系统总线)→ 空集 ✓;测试进程自持 org.freedesktop.ScreenSaver 名字 → 恰为 LOCK ✓;135 个 lib 单测全绿(含逐条枚举全部映射路径的契约测试)。

2. 错误码收敛到 -2(2e9ebf9) — 所有"动作未送达"路径(logind 拒绝/超时、polkit 拒绝、loginctl 非零退出——诊断文本保留为 loginctl lock-session exited with 1、缺 ScreenSaver 服务、缺 CLI、spawn 失败)统一落到单一 not_delivered 构造器 → UDA_ERR_NOT_SUPPORTED (-2),与 include/uda.h 契约一致。逐条核查了 session.rs 全部 Err 构造点,无 CommandFailed/DetectionFailed/Io 泄漏到动作路径。

3. Node 示例退出语义(2e9ebf9) — 01/02/03/06 统一为顶层 try/catch → 一行 UDA 调用失败: … + exit 1;07 保持"探测抛错 → exit 1,全部不支持 → exit 0",与 Python 侧逐字对称(已对照 _bootstrap.py 核验)。

验证收尾(f9e5d84):

  • session.rs 删除了与 wakelock 逐字重复的 ScreenSaver 探测函数,改为复用,bus_has_service 恢复私有;
  • 2e9ebf9 写入 uda.h 的探测语义现已同步回所有源头:uda-ffi / uda-core 的 Rust 文档注释、docs/internals/session_specs.md(第 5 节 + 能力位表)、07_session 两个示例的文案、python/nodejs 绑定的 session.capabilities 文档串("静态查询" → "探测一次后缓存");
  • 修复了 2e9ebf9 引入的 clippy doc_lazy_continuation(-D warnings 下导致 Lint & Format job 失败)。

本地验证:cargo clippy --workspace --all-targets -- -D warnings、135 lib 测试、117 ffi 测试、cargo fmt --check 全绿;CI 四个 job 在 f9e5d84 全绿。

Docker-based headless-Linux verification (session bus present, no
ScreenSaver, no logind - the shape dbus-run-session reproduces and a
WSL host approximates) exposed two places still assuming the
hardcoded-capabilities world:

- the_capability_query_neither_errors_nor_lies asserted that Linux
  always reports SESSION_MANAGEMENT. Under the probed matrix a headless
  host honestly answers empty and a screen-saver-only host answers
  LOCK without the management bit, so the old assertion contradicted
  the very semantics this PR ships. It now pins the three legal probe
  states (full set / LOCK alone / empty) instead.
- verify_someone_receives_the_lock propagated a failed logind probe as
  UDA_ERR_INTERNAL (-5), while its own contract says a failed probe
  refuses like an absent receiver. The refusal now carries
  UDA_ERR_NOT_SUPPORTED (-2) with the probe's diagnostics kept in the
  message, mirroring the session attempts' not-delivered mapping.

Verified in a rust:1 container: uda-ffi 119 passed (was 117 passed +
2 environment failures) and platform-linux 135 passed under
dbus-run-session; clippy --workspace --all-targets -D warnings clean.
@umbrella22

Copy link
Copy Markdown
Author

@SRInternet 补充:推送 f9e5d84 后,我们在 Docker(rust:1 容器)里做了一轮无头 Linux 实测 — 容器内有会话总线、无 ScreenSaver、无 logind,正是 dbus-run-session 复现的形状,也近似 WSL 无头场景。这暴露出两处仍在假设"硬编码能力位世界"的残留,已在 6ee52c6 修复:

  1. the_capability_query_neither_errors_nor_lies(uda-ffi 层测试):仍断言"Linux 恒报 SESSION_MANAGEMENT"。这与本 PR 的探测语义直接矛盾 — 无头机会诚实返回空集,仅 ScreenSaver 的机器返回 LOCK 而无 management 位 — 在无头 Linux 上该测试必失败(CI 一直是绿的,只因 GitHub runner 恰好有 logind)。已改为钉住三个合法探测状态:全集 / 仅 LOCK / 空集,三态之外即失败。

  2. verify_someone_receives_the_lock(wakelock CLI 层守卫):logind 探针失败(如系统总线不可达)时把错误透传为 -5 UDA_ERR_INTERNAL,与该函数自己的文档("'probe says absent' 和 'probe failed' 都拒绝,fail closed")不一致。现与"探针说无"同样返回 -2 UDA_ERR_NOT_SUPPORTED,探针诊断保留在消息里,与会话动作的 not-delivered 映射完全对称。

容器实测(修复后):uda-ffi 119 通过 0 失败(修复前 117 通过 + 2 个环境性失败)、uda-platform-linux 135 通过 0 失败,均在 dbus-run-session 下运行;cargo clippy --workspace --all-targets -- -D warnings 干净;CI 四个 job 在 6ee52c6 全绿。

@SRInternet SRInternet left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审查完毕,达成合并条件

本次审查过程为离线测试和 Windows Subsystem for Linux 2 (Ubuntu) 免登录实机测试 + 本地 Linux/Windows 双目标交叉编译与 dbus 沙箱自动化测试
审查对象包括来自 @umbrella22 的 13 commits target v0.2.2

摘要

本 PR 在"无头环境的诚实回答"这一主题上一定程度的完成了,其中 锁屏失败: UDA error -2 一行证明 include/uda.h 承诺的"按 -2 降级"契约已成立。剩余三项均为非阻塞的探测粒度与文档规范问题,不影响 v0.2.2 合入。

存在问题 (Medium or higher, 建议修复)

  1. session 能力探测粒度停在 service 可达层,未下沉到 session 对象可用层 (契约不符/能力位过度承诺)

位于 crates/uda-platform-linux/src/wakelock.rs:85-99 crates/uda-platform-linux/src/wakelock.rs:110-136 crates/uda-platform-linux/src/session.rs:516-526 crates/uda-platform-linux/src/session.rs:555-569 crates/uda-platform-linux/src/session.rs:606-612

logind_present() 以 DBusProxy::list_names() 判定 org.freedesktop.login1 是否在系统总线上被 own,session_capability_set() 据此把 logind=true 映射为全部 7 个能力位。但"服务名被 own"与"该服务在此 seat 上具备可用的 session 对象"是两个不同层级的命题:logind 是常驻 daemon,只要 systemd 起进程就会 own 该名字;而 loginctl lock-session 实际调用的是 /org/freedesktop/login1/session/auto,该对象只在真实 seat 会话建立时创建。WSL2 恰好落在这一夹缝——logind 进程在跑、bus 名被 own(审阅者以 busctl --system list 实测确认 org.freedesktop.login1 184 systemd-logind root :1.1 systemd-logind.service),却不存在任何 seat/session 对象。

  1. logind_present() 文档注释对 WSL 的事实陈述与实测相反 (文档误导/事实基准错误)

位于 crates/uda-platform-linux/src/wakelock.rs:78-84

注释原文写 "hand the host a lock that is pure fiction (observed on WSL, which exports no logind)"。审阅者在本轮审查中以 busctl --system list 实测确认 WSL2 确实导出 org.freedesktop.login1(由 systemd-logind.service 提供),缺失的是 session 对象而非 logind 本身。该断言把 WSL 归类为"无 logind"环境,与 session_capability_set() 注释中"With neither reachable - the WSL case"的表述(crates/uda-platform-linux/src/session.rs:553-554)自相矛盾:若 WSL 真的两个 receiver 都无,则能力位应为空集,而实机输出是全集。

三级问题 (Low, 建议规范)

  1. cargo doc --document-private-items 产生 12 条 intra-doc 链接告警 (文档规范)

位于 crates/uda-ffi/src/media.rs:12-7 crates/uda-ffi/src/media.rs:21-36 crates/uda-ffi/src/session.rs:15-9 crates/uda-ffi/src/session.rs:20-12 crates/uda-ffi/src/session.rs:26-7 crates/uda-ffi/src/session.rs:52-34 crates/uda-ffi/src/lib.rs:1008-54 crates/uda-ffi/src/lib.rs:1025-54 crates/uda-ffi/src/util.rs:141-23 crates/uda-platform-linux/src/tray.rs:667-7

模块级文档以 intra-doc 链接语法 [uda_media_get_status]、[session_capabilities]、[UDA_SESSION_CAP_*]、[FillMode]、[WakeLockType]、[TrayIconInner] 引用 C-ABI 导出名或跨 crate 私有条目,rustdoc 无法在 uda-ffi 与 uda-platform-linux 的命名空间中解析它们;util.rs:141 另有一条 redundant explicit link target([UDA_ERR_PANIC] 的目标与文本重复)。


本 Comment 由人工编写,审查建议中包含 AI 给予的建议。

Fourth review round on PR UniDesktop#5. Owning org.freedesktop.login1 on the
system bus only proves the daemon runs: systemd starts it on every
booted host, including a systemd-enabled WSL2 install where no seat
session is ever established - the reviewer's busctl probe found the
name owned there, so the matrix claimed all seven bits on a host where
loginctl lock-session has no session object to speak to.

The session matrix now asks the manager itself: a new
logind_session_present() probe calls ListSessions() and insists on at
least one live seat session before advertising the session-management
bits. A logind without sessions (the WSL2 shape) honestly answers with
the manager-level power actions alone; the wake-lock CLI-tier guard
keeps using the manager-level check on purpose, since Inhibit needs
the daemon, not a seat session.

The WSL mentions that claimed the opposite of the reviewer's busctl
finding are corrected: WSL with systemd disabled has no logind, while
systemd-enabled WSL2 has the daemon but no seat session; the
neither-reached sandboxes are containers and dbus-run-session, not WSL.

Also fixes the rustdoc warnings that cargo doc --document-private-items
reports in uda-ffi and the Linux tray (broken intra-doc links to C-ABI
export names and cross-crate items, one redundant explicit target), so
the doc build is warning-free.

Verified in a rust:1 container against a mock logind on a real system
bus: the matrix answers power-actions-only with zero sessions, the full
set after AddSession, power-only again after RemoveSession, LOCK with a
ScreenSaver alone, and empty in a bare dbus-run-session; plus the full
battery (mock script, uda-ffi 119, clippy -D warnings, fmt, 0 rustdoc
warnings).
@umbrella22

Copy link
Copy Markdown
Author

@SRInternet 第四轮的三项已全部处理,提交为 498e1c0:

1. 探测下沉到会话对象层

认同"名字被 own ≠ seat 会话可用"。新增 logind_session_present() 探针:向 org.freedesktop.login1.Manager 调用 ListSessions(),至少存在一个活跃 seat 会话才上报会话管理位;探针失败与其他探针同向折叠为"未证实存在"。矩阵现为四状态:

  • 有活跃 seat 会话 → 全部七位(manager 级电源位 + 会话级 LOCK/LOGOUT/SESSION_MANAGEMENT);
  • logind 可达但 ListSessions() 为空(systemd 已启用的 WSL2 形态)→ 仅四个电源位——SUSPEND/HIBERNATE/REBOOT/SHUTDOWN 是 manager 级动作,Suspend/PowerOff/Inhibit 由 daemon 直接受理,不依赖 seat 会话;
  • 仅 org.freedesktop.ScreenSaver → 仅 LOCK;
  • 两条总线皆不可达(容器、dbus-run-session 沙箱)→ 空集。

wakelock 的 CLI 层守卫有意保留 manager 级探针:Inhibit 只需要 daemon 受理、不需要 seat 会话,systemd 启用的 WSL2 上放行是正确行为,该粒度选择已写入 logind_present 的文档。

Docker 实测(rust:1 容器,自制 logind mock 挂在真实 system bus 上,可动态 Add/RemoveSession):0 会话 → 仅电源位 ✓;AddSession → 全集 ✓;RemoveSession → 回落仅电源位 ✓;仅 ScreenSaver → LOCK ✓;裸 dbus-run-session → 空集 ✓。

2. WSL 事实陈述修正

按您的 busctl 实测改写了全部相关注释:启用 systemd 的 WSL2 确实运行 logind,"WSL 无 logind"的旧说法只适用于 systemd 被禁用的 WSL 配置;session.rs / wakelock.rs / wakelocks.rs 中"两者皆不可达即 WSL"的表述统一改为"容器、dbus-run-session 沙箱或 systemd 被禁用的 WSL",并注明启用了 systemd 的 WSL2 是"logind 在、无 seat 会话"的夹缝形态。docs/internals/session_specs.md、include/uda.h 能力位段落与 CHANGELOG 已同步分层语义。

3. rustdoc 告警清零

cargo doc --document-private-items 在 uda-ffi 与 Linux tray 报告的 10 处告警已清零:C-ABI 导出名(uda_media_get_status 等)与跨 crate 私有项(TrayIconInner)改为普通代码体;FillMode / WakeLockType 补全为可解析的跨 crate 路径链接;util.rs 的 UDA_ERR_PANIC 冗余显式链接目标已去掉。

验证:容器内 mock 脚本全绿、uda-ffi 119 通过、cargo clippy --workspace --all-targets -- -D warnings 干净、cargo fmt --check 干净、rustdoc 0 告警;CI 四个 job 在 498e1c0 全绿。

@SRInternet SRInternet left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审查完毕,达成合并条件

审查过程包含 Windows Subsystem for Linux 2 (Ubuntu) 免登录实机测试、本地 Linux/Windows 双目标交叉编译与 dbus 沙箱自动化测试

本次审查对象为来自 @umbrella22 的 6ee52c6 至 498e1c0 based on origin/develop(包含 11 项文件变动 / +194 / -84 变更)

摘要

本 commit 将上轮 P2-1 的"探测粒度下沉"建议完整落地:session_reachability() 由二元组升级为 (logind, logind_session, screen_saver) 三元组,新增 logind_session_present() 以 org.freedesktop.login1.Manager.ListSessions 判定是否存在可用的 seat session,session_capability_set() 相应重构为三态分支——有 session 则全集、仅 logind 则仅电源动作、仅 ScreenSaver 则仅 LOCK、两者皆无则空集。审阅者现场实测确认该重构的方向正确性:busctl --system call ... ListSessions 返回 a(susso) 1 "1" 1000 "srinternet" "" "/org/freedesktop/login1/session/_31",证明审阅者的 WSL2 环境确实存在一个 seat session,能力位给出全集是符合探测语义的诚实答案,而非上轮那样的误判。上轮 P2-2(WSL 注释与事实相反)亦已修正。

二级问题 (Medium, 强烈建议修复)

1. session 能力探测下沉至 ListSessions 层,但与 loginctl lock-session 实际依赖的 session/auto 对象仍未对齐 (契约不符/能力位残留过度承诺)

位于 crates/uda-platform-linux/src/session.rs:551-568 crates/uda-platform-linux/src/session.rs:601-617 crates/uda-platform-linux/src/session.rs:250-261 crates/uda-platform-linux/src/session.rs:654-662

logind_session_present() 以 ListSessions 返回非空作为"存在可用 seat session"的判据,session_capability_set() 据此在 logind_session == true 时置位含 LOCK 的全集。但 lock() 的 Tier-3 实际执行体是 Command::new("loginctl").arg("lock-session"),而 loginctl lock-session 解析的是调用方所属的那个 session,在 logind 的 D-Bus 对象模型中对应 /org/freedesktop/login1/session/auto——这是一个"自动解析当前会话"的特殊对象,并非 ListSessions 列出的任意 session 对象路径。二者是不同层级的命题:ListSessions 证明"系统里存在 session 对象",session/auto 证明"当前调用者能被归属到某个 session"。WSL2 恰好落在这一夹缝:logind 为 SSH/PTY 会话建立了 session/_31,却没有为交互式 seat 建立 session/auto。

探测需与执行体对齐——logind_session_present() 应在 ListSessions 非空之后追加一次对象级校验,直接探测 /org/freedesktop/login1/session/auto 是否可解析(例如对其发起 org.freedesktop.DBus.Properties.Get,或调用 org.freedesktop.login1.Manager.GetSession("auto")),失败则判 logind_session = false。此时 session_capability_set() 落到 if logind { return power_actions; } 分支,LOCK 仅由 ScreenSaver 可达性决定,与 lock() 的 Tier-2/Tier-3 实际依赖完全一致。

三级问题 (Low, 建议规范或修复)

1. 版本号尚未提升

五个 crate 与 Cargo.lock 仍为 0.2.1,与 v0.2.1 tag 一致,合入前需同步更新五个 crate 的 Cargo.toml、Cargo.lock、examples/nodejs/package.json 与 package-lock.json,并将 CHANGELOG.md 的 [Unreleased] 节点改写为 [v0.2.2]。


本 Comment 由 Agent 根据 code-review-guidelines 编写,审查建议中包含人工给予的建议。

….2.2

Fifth review round on PR UniDesktop#5:

- logind_session_present() no longer stops at a non-empty ListSessions:
  a session somewhere on the machine is not a session the caller can
  act on. lock-session and TerminateSession("") both resolve the
  *calling* session - the special session/auto object - so the probe
  now finishes with GetSession("auto") and answers false on any
  resolution error (NoSuchSession is the honest 'no session for this
  caller', not a probe failure). A systemd-enabled WSL2 host keeps its
  PTY session in the list and still gets power-only, matching what
  loginctl lock-session can actually do.
- session_capability_set() composes bits per receiver instead of early
  returns: power actions follow the daemon, SESSION_MANAGEMENT/LOGOUT
  follow the caller session, and LOCK follows either caller-session or
  a reachable screen saver (lock()'s Tier 2), which also fixes the
  power+screensaver combination that previously dropped LOCK.
- Release prep: five crates, Cargo.lock and the node example package
  move to 0.2.2; the CHANGELOG section becomes [v0.2.2].

Verified in a rust:1 container against a mock logind that can toggle
caller-session resolvability: daemon without sessions -> power only,
session + auto resolvable -> full, session listed but auto not
resolvable (the WSL2 shape) -> power only, that shape plus an owned
ScreenSaver -> power|LOCK, ScreenSaver alone -> LOCK, bare sandbox ->
empty. Full battery green (mock script, uda-ffi 119, clippy -D
warnings, fmt).
@umbrella22

Copy link
Copy Markdown
Author

@SRInternet 第五轮的两项已处理,提交为 21431ae:

1. 探测与执行体对齐到 session/auto(GetSession("auto") 对象级校验)

认同"ListSessions 非空只证明机器上存在会话对象,不证明调用者能被归属到会话"。logind_session_present() 现在分两步:先 ListSessions(任何会话都不存在时管理本身无意义),再调 GetSession("auto") —— 正是 loginctl lock-session 与 TerminateSession("") 解析的那个对象。解析失败(如 NoSuchSession)是"调用者无会话"的诚实答案,返回 false 而非探针错误;总线级失败仍按 probe_reachable 折叠为"未证实"。systemd 启用的 WSL2 从此落到仅电源位分支,与 lock() 的 Tier-2/Tier-3 实际能力一致。

顺带把矩阵改为按接收者组合位(不再 early-return):电源位跟随 daemon、SESSION_MANAGEMENT/LOGOUT 跟随调用者会话、LOCK 跟随"调用者会话或可达的 ScreenSaver"(lock 的 Tier-2 就是 ScreenSaver)。这修正了一个组合漏洞——之前"logind 在但无调用者会话 + ScreenSaver 可达"的机器会漏掉本可用的 LOCK。

Docker 实测(rust:1 容器,mock logind 可开关 auto 可解析性)六状态:daemon 无会话 → 仅电源位 ✓;会话 + auto 可解析 → 全集 ✓;会话在列但 auto 不可解析(WSL2 形态)→ 仅电源位 ✓;该形态 + ScreenSaver → 电源位 + LOCK ✓;仅 ScreenSaver → LOCK ✓;裸沙箱 → 空集 ✓。

2. 版本号提升 0.2.2

五个 crate 的 Cargo.toml、Cargo.lock、examples/nodejs 的 package.json / package-lock.json 均已提升至 0.2.2,CHANGELOG.md 的 [Unreleased] 节点改写为 [v0.2.2] - 2026-10-10。

验证:容器内 mock 脚本全绿、uda-ffi 119 通过、137 个 platform-linux 单测通过(新增调用者会话组合用例)、clippy -D warnings 干净、fmt 干净;CI 四个 job 在 21431ae 全绿。

@SRInternet SRInternet left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审查完毕,达成合并条件

审查过程包含 Windows Subsystem for Linux 2 (Ubuntu) 免登录实机测试、本地 Linux/Windows 双目标交叉编译与 dbus 沙箱自动化测试
本次审查对象为来自 @umbrella22 的 498e1c0 至 21431ae based on origin/develop(包含 13 项文件变动 / +132 / -81 变更)

摘要

本 commit 将上轮 Medium-1 的修复建议完整落地,全量门禁现场重跑零警告。C-ABI 冻结层保持 35 符号零消失零改分类(相对 v0.2.1 仅 3 个 append-only 新增),二进制兼容性未受影响。上轮 Medium-2(验证方法依赖 .so 重建)经本轮实践已被审阅者内化,本 commit 的实机结论均建立在 cargo build 之后,可复现性得到保证。

二级问题 (Medium, 强烈建议修复)

1. LOCK 能力在无 ScreenSaver 且 logind 有 daemon 但无 caller-session 的环境下可被正确降级,但 LOGOUT 的 Tier-3 桌面会话管理器仍属未探测路径 (契约不符/能力位保守缺口)

位于 crates/uda-platform-linux/src/session.rs:626-641 crates/uda-platform-linux/src/session.rs:544-594

session_capability_set() 将 LOGOUT 绑定在 logind_session 上,注释明确说明"the desktop logout managers are tried opportunistically rather than probed, so LOGOUT never rides on them",但 logout() 的执行体除 logind TerminateSession("") 外,仍保留 Tier-3 的 GNOME/KDE/XFCE SessionManager 兜底。于是在"logind 有 daemon、无 caller-session、但桌面会话管理器实际可达"的环境中(例如某些未走 logind seat 的传统 X11 会话),LOGOUT 位为 false,而调用 logout() 却可能经由 Tier-3 成功,造成了能力位与实际可达功能之间存在正向缺口。

宿主持有 LOGOUT=False 时会将注销菜单项置灰或隐藏,用户在少数传统 X11 桌面环境下本可用的功能被能力位掩盖。审阅者的 WSL2 环境无法构造该场景(既无桌面会话管理器也无 caller-session),故本轮未取到实机反例,此为静态推断而非实测结论,特此声明。

三级问题 (Low, 建议规范)

1. cargo doc --document-private-items 维持零警告,建议纳入 CI 门禁固化

位于 crates/uda-ffi/src/media.rs:12-7 crates/uda-ffi/src/util.rs:141-23

上轮 12 条 intra-doc 链接告警已在此前 commit 中全部清理(C-ABI 导出名改为普通代码段、redundant explicit link target 已简化),本轮审阅者现场重跑 cargo doc --workspace --no-deps --document-private-items 确认警告计数为 0。建议后续将 cargo doc --workspace --no-deps --document-private-items 纳入 CI 门禁并附加 -D warnings(或以 RUSTDOCFLAGS="-D warnings" 形式),使零警告从"当前状态"变为"持续约束",避免后续新增文档注释时回升。

其他提示(非问题)

1. 本 commit 在 Windows 物理机验证未执行

幽灵窗口(Alt+Tab 不出现 UDA 条目)、GDI 对象数回归基线、explorer.exe 强杀重启后图标自愈、DPI 缩放切换、注销重登。审阅环境为 Linux,无 Windows 物理机或高保真虚拟机。本轮无 Windows 侧代码改动(diff 仅触及 crates/uda-platform-windows/Cargo.toml 的版本号),故上述项可延续至合并前的独立验证窗口;但 Alt+Tab 无幽灵窗口仍是 WS_POPUP + WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE 修复的最终验收点,不可由 CI 替代。

2. 版本号已 bump 至 0.2.2,合入前需确认发布节点与 tag 命名

五个 crate 的 Cargo.toml、Cargo.lock、examples/nodejs/package.json 与 package-lock.json 均已更新为 0.2.2,CHANGELOG.md 的 [Unreleased] 节点已改写为发布节点。


本 Comment 由 Agent 根据 code-review-guidelines 编写,审查建议中包含人工给予的建议。

Sixth review round on PR UniDesktop#5:

- LOGOUT's Tier-3 fallback (the GNOME/KDE/XFCE session managers) was
  the one receiver the capability matrix never probed, so a traditional
  X11 desktop whose logout works through the desktop's own session
  manager answered LOGOUT=false - masking a capability the machine
  really has. The matrix now dials the same three well-known services
  the fallback dials (shared bus_has_service, one bounded probe round,
  skipped when the caller-session path already claims LOGOUT) and sets
  SESSION_MANAGEMENT|LOGOUT when any of them owns its name.
- The rustdoc build joins the fmt/clippy gates: the Lint & Format job
  now runs RUSTDOCFLAGS='-D warnings' cargo doc --workspace --no-deps
  --document-private-items, turning last round's zero-warning state
  into a standing constraint.

Container validation against a mock logind and mock desktop managers:
the reviewer's scenario (logind daemon without a caller session plus a
reachable GNOME session manager) answers power|SESSION_MANAGEMENT|
LOGOUT; a GNOME manager alone (traditional X11) answers the logout
pair; and the caller-session, WSL2, screen-saver and sandbox shapes
are unchanged. Workspace rustdoc builds warning-free under
RUSTDOCFLAGS='-D warnings' locally and the full battery stays green.
@umbrella22

Copy link
Copy Markdown
Author

@SRInternet 第六轮的两项已处理,提交为 7b08d59:

Medium 1:LOGOUT 的 Tier-3 桌面会话管理器纳入探测

认同"能力位保守缺口"。新增 desktop_logout_manager_present():在会话总线上按 logout() Tier-3 兜底同一组服务名(org.gnome.SessionManager / org.kde.Shutdown / org.xfce.SessionManager,复用共享的 bus_has_service 逐个查名,单轮有界探测;logind_session 已置位时跳过,不给热路径加往返)判定桌面会话管理器可达性。LOGOUT/SESSION_MANAGEMENT 现在改为"logind 调用者会话 或 桌面会话管理器可达"即置位——探测与执行体从此同源,两个位恒同步变化。

您静态推断的场景已在容器里构造实测(mock logind + mock GNOME SessionManager):

  • logind daemon 无调用者会话 + GNOME 管理器可达 → power|SESSION_MANAGEMENT|LOGOUT ✓(正是您指出的正向缺口,已闭合)
  • 仅 GNOME 管理器(传统 X11 形态)→ SESSION_MANAGEMENT|LOGOUT ✓
  • 回归确认:全集(调用者会话)/ WSL2 形态(仅电源位)/ WSL2+ScreenSaver(power|LOCK)/ 仅 ScreenSaver(LOCK)/ 裸沙箱(空集)全部不变 ✓

Low 1:rustdoc 零警告固化为 CI 门禁

Lint & Format job 新增一步 RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --document-private-items。本地工作区(含 uda-cli)先实测 0 警告才合入该步,本轮 CI 已按新门禁通过——零警告从"当前状态"变为"持续约束"。

其余验证:mock 脚本全绿、uda-ffi 119 通过、139 个 platform-linux 单测通过(新增桌面管理器两用例)、clippy -D warnings 与 fmt 干净;CI 四个 job 在 7b08d59 全绿。

另收到"其他提示"两项:Windows 实机验收点(Alt+Tab 无幽灵窗口等)维持合并前独立验证窗口安排,include/uda.h 的"⚠️ 给合并者"备注已涵盖;版本节点与 tag 命名随合并窗口与维护者确认。

@SRInternet SRInternet left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审查完毕,达成合并条件

本次审查过程包含 Windows Subsystem for Linux 2 (Ubuntu) 免登录实机测试、本地 Linux/Windows 双目标交叉编译与 dbus 沙箱自动化测试;
本次审查对象为来自 @umbrella22 的 21431ae 至 7b08d59 based on origin/develop(包含 8 项文件变动 / +175 / -52 变更)

摘要

本 commit 采纳了上一轮 Medium-1 的修复建议,并采用了审阅者当时倾向的方案①而非方案②、。本 commit 在能力位诚实性、探测与执行同源性、CI 门禁固化三个维度上均为净改进,无阻塞性问题。

二级问题 (Medium, 强烈建议修复)

1. docs/internals/session_specs.md 概览表中 SESSION_MANAGEMENT 的 Linux 判据未被本轮同步,与代码实际组合条件不符 (契约不符/文档与实现漂移)

位于 docs/internals/session_specs.md:23 crates/uda-platform-linux/src/session.rs:673-685 crates/uda-platform-linux/src/session.rs:228-231

问题本质分析:该文件存在两层描述。第 5 节的能力矩阵叙述(docs/internals/session_specs.md:224-231)已在本轮被正确改写,明确写出"LOGOUT 还有一个独立接收方:GNOME/KDE/XFCE 桌面会话管理器……LOGOUT/SESSION_MANAGEMENT 照常置位"。但第 1 节的概念映射表第 23 行仍写作 | Capability::SESSION_MANAGEMENT | 探测到 org.freedesktop.login1 可达 | ... |。而 session_capability_set() 的真实条件是 if logind_session || desktop_logout(crates/uda-platform-linux/src/session.rs:680-682)——logind(manager 级名字被 own)为真而 logind_session 为假时,SESSION_MANAGEMENT 不置位,本轮新增测试 logind_without_a_caller_session_advertises_the_power_actions_alone(crates/uda-platform-linux/src/session.rs:979-1008)正是钉住这一点的。因此第 23 行表述的是"logind daemon 可达即置位",与实际代码相反,属于本轮的修复本身引入的一处新漂移(改动前 SESSION_MANAGEMENT 与 logind_session 绑定,原表述亦不准,但本轮扩充了第二个背书方却未回改该行)。

该表格是维护者与宿主理解能力位语义的第一入口,且本文件开头即声明"SPECIFICATION DICTIONARY"。后续维护者若据第 23 行推断"WSL2 有 logind 就应报 SESSION_MANAGEMENT",会与实测 caps=0x00780000(审阅者本轮 ctypes 实测)直接矛盾,从而误判为代码缺陷;反之若据第 5 节则理解正确。两节互相矛盾会显著提高后续审查与排障的认知成本。

三级问题 (Low, 建议规范或修复)

1. 新增的 CI rustdoc 门禁未带 --locked,与同 job 内其余步骤不一致 (规范不一致)

位于 .github/workflows/ci.yml:47-50 .github/workflows/ci.yml:52-53

本轮插入的 rustdoc 步骤为 cargo doc --workspace --no-deps --document-private-items,而同一 lint job 的编译检查(.github/workflows/ci.yml:53)以及 linux-test job 的全部 cargo 步骤(.github/workflows/ci.yml:104、:107、:110、:113、:116)均带 --locked。--locked 的作用是断言 Cargo.lock 已最新,缺省时 cargo 可在 lock 过期时静默重算并写回,使 CI 通过而本地复现失败。

在 Cargo.lock 与 Cargo.toml 不一致的场景下,该步骤会静默修复 lock 而非报错,与其他步骤的"失败并指出 lock 过期"行为分叉,构成门禁强度不一致。

2. desktop_logout_manager_present() 对三个候选各发起一次 list_names() 往返,可合并为一次列举 (轻微效率/冗余往返)

位于 crates/uda-platform-linux/src/session.rs:618-632 crates/uda-platform-linux/src/wakelock.rs:122-142

bus_has_service() 的语义是"列举总线全部唯一名后精确匹配一个服务名",其内部每次调用都执行一次 DBusProxy::list_names()(crates/uda-platform-linux/src/wakelock.rs:128)。新探测在 for candidate in LOGOUT_CANDIDATES 循环中逐个调用,因此对同一连接连续发起 3 次 list_names();而 screensaver_present() 只查 1 个服务名,仅 1 次。三条候选在同一会话总线上,一次列举即可覆盖三者。


本 Comment 由人工编写,审查建议中包含 AI 给予的建议。

Seventh review round on PR UniDesktop#5:

- The section-1 concept table still introduced SESSION_MANAGEMENT as
  'org.freedesktop.login1 reachable', which the round-5/6 caller-session
  and desktop-manager work had made wrong: the bit now follows a
  resolvable caller session or a reachable desktop session manager, and
  the table says so with a pointer to the section-5 matrix it must not
  contradict.
- The new rustdoc CI step gains --locked like every other cargo step in
  the job, so a stale Cargo.lock fails the gate instead of being
  silently recomputed.
- desktop_logout_manager_present() lists the session bus once and
  matches all three logout candidates against that listing instead of
  paying a list_names round trip per candidate; the shared helper moved
  into bus_unique_names with bus_has_service as the one-name wrapper.

Container validation: a full caller-session matrix, the second logout
candidate (org.kde.Shutdown) owned alongside it, and a KDE manager
alone on a traditional X11/KDE shape all answer as specified; the full
battery (mock script, uda-ffi 119, clippy -D warnings, fmt, and the
--locked rustdoc gate) stays green.
@umbrella22

Copy link
Copy Markdown
Author

@SRInternet 第七轮的三项已全部处理,提交为 ea0c482:

Medium 1:§1 概念表 SESSION_MANAGEMENT 判据同步

认同这是本轮修复自身引入的文档漂移(且原表述自调用者会话语义落地起就已不准)。第 1 节表格现改为:"logind 调用者会话可解析,或桌面会话管理器可达(判据见第 5 节)",LOCK/LOGOUT 行也加了"逐位判据见第 5 节"的指向——两节不再各自为政,维护者从任一入口进来都会被引到同一份真实判据,您 ctypes 实测的 caps=0x00780000(WSL2 → 仅电源位)从此与两节都一致。

Low 1:rustdoc 门禁补 --locked

已加。现在该 job 内所有 cargo 步骤(fmt / clippy / doc / check)语义一致:lock 过期即失败,而不是静默重算。

Low 2:三次 list_names 合并为一次

探测重构为单次 bus_unique_names() 列举 + 三个候选对同一份名单做精确匹配;bus_has_service 退化为"一次列举 + 单名匹配"的便捷包装(列举/超时/错误映射只此一份实现)。容器实测用第二个候选验证多候选路径:org.kde.Shutdown 在册时 GNOME 缺席也正确置位——传统 KDE/X11 形态 → SESSION_MANAGEMENT|LOGOUT ✓;与调用者会话并存 → 全集 ✓;调用者会话路径不因新探测多付往返。

验证:mock 脚本全绿、uda-ffi 119 通过、139 个 platform-linux 单测通过、clippy -D warnings / fmt / --locked rustdoc 门禁全部干净;CI 四个 job 在 ea0c482 全绿。

… KDE fallback

Four-way cross-validation (two reviewers, four validators) over the
full delta surfaced one major and seven minor findings, all addressed:

- session_specs.md 2.4 still routed connect failures to DetectionFailed
  and everything else to CommandFailed; the code funnels every
  not-delivered path into NotSupported (-2), and the spec now says so.
  The opening 'static query' claim and the 2.3 'last semantic error'
  wording are aligned with the probed, logind-first reality too.
- lock_via_loginctl spawned loginctl without a timeout - the only
  unbounded helper left in the crate. It now runs through the shared
  bridge under COMMAND_TIMEOUT and kills a wedged loginctl, reporting
  NotSupported like every other delivery failure.
- detect_kde_theme tries kreadconfig6 before kreadconfig5: Plasma 6
  ships only the former, so the theme fallback tier was dead there.
- The '0 means no session backend' sentences in include/uda.h and the
  ffi module docs now say 'no receiver reachable' - a sandbox proves
  backends can exist with nothing to talk to.
- MenuIdMap's address-reuse window is documented as the accepted
  v0.2.2 trade-off it is, with the generation-counter remedy noted.
- AGENTS.md's UdaError example gains InvalidArgument and the
  non_exhaustive note the error model carries.

Simplifier pass on top: login1_power takes a &'static str (drops the
to_string/clone dance), COMMAND_TIMEOUT joins the top imports, and a
stale #[allow(dead_code)] on error_is_authorization is gone.

Verified: mock script, uda-ffi 119, platform-linux 139, clippy -D
warnings, fmt, and the workspace rustdoc --locked gate all green.
@umbrella22

Copy link
Copy Markdown
Author

补充说明:ea0c482..565fe84 是我们对全量 delta 做的一轮内部交叉审核(两位独立审查者产出候选问题、四位验证者逐一确认)后的修复,不是对新的外部反馈的回应。要点:§2.4 错误映射表同步到 -2 收口、loginctl 调用补 COMMAND_TIMEOUT 超时(P0-3 收尾)、kreadconfig6→5 回退(Plasma 6)、"0 means no session backend" 措辞改为 "no receiver reachable"、MenuIdMap 地址复用窗口按定案记档、AGENTS.md 的 UdaError 示例补 InvalidArgument 与 #[non_exhaustive]。全部验证门(mock 脚本 / 139+117 单测 / clippy / fmt / rustdoc --locked / CI 四 job)保持绿色。

@SRInternet SRInternet left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 Congratulations!

审查过程包含 Windows Subsystem for Linux 2 (Ubuntu) 免登录实机测试、本地 Linux/Windows 双目标交叉编译与 dbus 沙箱自动化测试
本次审查对象为来自 @umbrella22 的 7b08d59 至 565fe84 based on origin/develop(包含 9 项文件变动 / +120 / -76 变更)

摘要

本轮两个 commit(ea0c482 同步能力表与 doc 门禁去重、565fe84 交叉验证轮——契约文档、有界 loginctl、KDE 兜底)将上一轮登记的全部三项问题逐条落地,且质量高于最低修复要求。

至此,本 PR 已无审阅者可见的任何缺陷,具备全部合并条件。

其他说明

  1. 等待 Windows 物理机验证的结果
    幽灵窗口(Alt+Tab 不出现 UDA 条目)、GDI 对象数回归基线、explorer.exe 强杀重启后图标自愈、DPI 缩放切换、注销重登等需等待审阅者使用 Windows 物理机或高保真虚拟机进行验证。本轮无 Windows 侧代码改动(diff 未触及 crates/uda-platform-windows/),故上述项可延续至合并前的独立验证窗口;但 Alt+Tab 无幽灵窗口仍是 WS_POPUP + WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE 修复的最终验收点,不可由 CI 替代。

本 Comment 由人工编写,全部审查建议均由人工成功复现。

再次感谢贡献者 @umbrella22 一周以来的耐心贡献。

…ead-affine locks

- tray FFI: per-menu callback gate; destroying while a callback runs
  reports UDA_ERR_INTERNAL and stays retryable; row-action snapshots
  cloned by the backends go silent once the destroy succeeds. Bindings
  release their trampolines only after a successful destroy.
- windows tray: the Worker is a heap-pinned owner accessed through raw
  pointers; no &mut Worker is alive across a reentrancy boundary (message
  pump, shell calls, the modal menu loop, host callbacks); the menu
  reentrancy guard is an owned AtomicBool. The failed-spawn path frees the
  worker again.
- windows wakelock: each lock lives on a dedicated owner thread; the FFI
  bridge's ephemeral worker can no longer take the request down when it
  is joined.
- linux session: LOCK follows GetSession("auto") (logind's display-session
  fallback) while LOGOUT/SESSION_MANAGEMENT follow GetSession(""); the
  desktop logout probe unions owned and activatable names, covering the
  on-demand org.kde.Shutdown.
- linux wallpaper: hyprpaper is driven by its ok reply with the daemon's
  diagnosis kept as the final answer; GNOME reads decode the GVariant
  string and then the file URI; the FFI no longer re-probes behind the
  platform layer.
- tests: isolated dbus-daemon session fixtures, hyprpaper CLI regression
  harness, tray gate unit tests.
@umbrella22

Copy link
Copy Markdown
Author

终审交叉验证轮(用户授权的第一性原理复审)

本轮在 565fe84 之上做了一次全量复审(并行 6 个审查视角:2×简化器 + 4×独立审查,覆盖完整 delta;候选逐项独立验证后修复),提交 87df3f4,CI 4/4 通过。修复的问题:

P1

  1. 托盘菜单销毁后回调仍可达(FFI 实测复现:destroy 返回 0 后 1.22ms 旧 row 的 Event 仍触发回调)。修复:每个菜单一把共享回调门——成功销毁后任何已被后端克隆的行动作变为静默 no-op;回调正在执行时 destroy 返回 UDA_ERR_INTERNAL 且菜单完整,宿主在回调返回后重试(等待在宿主侧,避免与托盘工作线程互等死锁,回调内自销毁亦安全)。Python/Node 仅在销毁成功后释放蹦床,include/uda.h 契约同步。
  2. Windows 唤醒锁随临时桥接线程失效:ambient tokio 下 run_sync 在临时 worker 上 SetThreadExecutionState 后立即 join,请求随线程死亡而宿主拿到成功句柄。修复:每锁一个专属持有线程(acquire 会合上报、释放通道收尾),桥接线程的存活与否不再影响锁。

P2
3. Windows 托盘窗口过程可重入别名 UB:此前的“两阶段 + &mut bool 守卫”仍在模态循环期间存活可变借用。修复:Worker 改为堆上钉住的持有对象、全程裸指针短借用——消息泵、shell 调用、模态菜单循环、宿主回调各边界均无 &mut Worker 存活;菜单守卫改为独立持有的 AtomicBool;失败 spawn 路径补回 Worker 回收。
4. 锁屏/注销探测与执行体错位:LOCK 与 LOGOUT 不再共用 GetSession("auto")——LOCK 跟随 auto(logind 自带回落到用户 display 会话语义),LOGOUT/SESSION_MANAGEMENT 跟随 GetSession("")(仅调用者自身会话,systemd 源码语义),用户服务进程不再虚报注销能力。
5. KDE 按需激活的 org.kde.Shutdown 被能力门禁挡住:桌面注销探测改为 ListNames ∪ ListActivatableNames(不主动激活,两列表独立降级)。
6. hyprpaper 拒绝被报成功:hyprctl 对错误回复仍退出 0(Hyprland v0.49 实测语义)——现要求 ok 回复,保留守护进程诊断,被拒后继续 swww;无第二工具时诊断作为最终答案。
7. GNOME 壁纸读取失真:先按 GVariant 字符串语法解码(转义/\uXXXX),再按 URI 规则还原本地路径(空 authority/localhost);远端 authority、畸形百分号编码、NUL 如实拒绝。
8. FFI 重复壁纸回退绕过平台边界:删除 FFI 侧无超时、宽松解析的二次 gsettings 路径,GNOME 家族(含 Unity)读取收敛到平台层单一实现。

验证:本地 Linux 容器全量(fmt / clippy -D warnings / rustdoc -D warnings / platform-linux 145 / uda-ffi 119+6 集成(新增 hyprpaper CLI 回归 harness、隔离 dbus-daemon 会话夹具、托盘门单测)/ core 60)+ Windows 交叉编译,全部通过;CI 4/4(87df3f4)。修复后复审(独立复读者)提出的 3 项残留(spawn 失败路径 Worker 泄漏、两处不变式注释失实)已一并修复并复验。CHANGELOG 已补终审轮条目。

@SRInternet SRInternet left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审查完毕,达成合并条件

审查过程包含 Windows Subsystem for Linux 2 (Ubuntu) 免登录实机测试、本地 Linux/Windows 双目标交叉编译、dbus 沙箱自动化测试与 CLI 夹具集成测试
本次审查对象为来自 @umbrella22 的 565fe84 至 57f5cd8 based on origin/develop(包含 17 项文件变动 / +2088 / -715 变更)

摘要

本轮两个 commit(87df3f4 交叉验证轮、57f5cd8 唤醒锁获取与托盘重入对齐)将推送者声称的 8 项修复全部落地,审阅者逐项核验代码路径后确认真实且完整,其中 5 项的修复质量明显高于"消错"级别。但推送者声称的本地验证数字(platform-linux 145 / uda-ffi 119+6 / core 60)与审阅者实测(144 / 121+6 / 60)在 core 与集成测试上完全一致,差异源于统计口径:推送者把 mock 脚本与直接 cargo test 的计数混计,审阅者分别单跑 cargo test -p uda-platform-linux 与 ./scripts/test-linux-mock.sh 均为 144,uda-ffi unit 数由上轮 119 增至 121(+2 项门控测试),集成 6 项与声称一致。

三级问题 (Low, 建议规范或修复)

1. uda_tray_menu_destroy 在回调执行中返回 UDA_ERR_INTERNAL,但 uda_status_message 未对该状态给出可呈现文案,宿主难以向用户解释"请稍后重试" (契约完整性/宿主可用性)

位于 crates/uda-ffi/src/tray.rs:239-252 crates/uda-ffi/src/lib.rs:614-626 include/uda.h:799-812

close() 返回的 Failure::Uda(UdaError::Internal(...)) 已携带人类可读诊断("a menu callback is in progress; retain its callbacks and retry destruction after it returns"),该文案通过 uda_last_error_message() 可达。但 C 宿主若走 uda_status_message(status) 这条"状态码 → 静态文案"的路径,拿到的是 UDA_ERR_INTERNAL 的通用说明,不含"重试即可"这一关键指导。P1-1 的契约把重试责任显式移交给宿主,而宿主恰恰在最需要指导的时刻(调用返回非预期错误)最容易只读静态文案。

宿主在菜单回调执行期间调用 uda_tray_menu_destroy,收到 -3(UDA_ERR_INTERNAL)。若宿主只呈现 uda_status_message(-3) 的通用文案,用户看到的是"内部错误"而非"请稍后重试",宿主开发者也可能误判为库缺陷而放弃重试——这与本修复"让宿主重试"的设计意图相悖。

调用方应优先使用 uda_last_error_message() 获取本次失败的具体诊断,uda_status_message 仅用于无 last-error 上下文时的兜底说明。或在该函数声明处直接举例"如 UDA_ERR_INTERNAL 也可能是可重试的菜单销毁,见 uda_last_error_message()"。

2. hyprpaper_command 要求 stdout trim 后严格等于 ok,hyprpaper 未来若在 ok 后附加说明文字会被误判为拒绝 (边界条件/前向兼容)

位于 crates/uda-platform-linux/src/wallpaper.rs:366-372

问题本质分析:当前判据是 reply.trim() == "ok"。Hyprland v0.49 实测语义是 hyprpaper 对成功回复恰好 ok 一行,故该判据在当版本正确。但 IPC 回复属于外部守护进程的输出格式,不属于 UDA 能控制的契约;若上游改为 ok: wallpaper set 之类,本模块会把成功当拒绝,进而级联到 swww——若 hyprpaper 与 swww 同时存在,会出现"hyprpaper 已设置成功、swww 又设置一次"的重复应用(虽最终视觉一致,但违背"按序取首个可用层"的级联语义,且 swww 与 hyprpaper 同屏竞争可能产生闪烁)。

可将判据放宽为"首行 trim 后以 ok 开头"(reply.lines().next().is_some_and(|line| line.trim() == "ok") 或 trim_start().starts_with("ok")),其余行作为诊断保留。若维护者认为严格相等更能及时发现上游格式变化、且 swww 重复应用的后果可接受,维持现状亦可接受——此项不构成合并阻塞。

3. logind_sessions_on 在 ListSessions 非空后串行发起两次 GetSession,且 ListSessions 每次探测重建 proxy (轻微效率)

位于 crates/uda-platform-linux/src/session.rs:580-600

GetSession("auto") 与 GetSession("") 是两次独立往返,串行执行。二者无依赖关系,可并发。另外 logind_present()(wakelock 侧)已用 bus_has_service 做过名字核查,logind_sessions_on 又新建一次 zbus::Proxy——虽 proxy 构造不发包,但可用 tokio::join! 并发两个 GetSession 使两次探测路径的职责边界可更清晰。


本 Comment 由 Agent 根据 code-review-guidelines 编写,审查建议中包含人工给予的建议。

提示:根据 code-review-guidelines ,当 PR 仅剩余等级 <= Low 的问题时,可以忽略本 Review。

@SRInternet

SRInternet commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

PR Author 可执行的测试指导说明

环境要求

  • Windows 10 22H2 或 Windows 11(x86_64),已安装 Visual Studio 2022 Build Tools(MSVC 工具链)或 rustup target add x86_64-pc-windows-msvc。
  • 已安装 Python 3.11+ 与 Node.js 20+(用于运行 examples/ 下的绑定示例)。
  • 必须使用交互式桌面会话:RDP 会话的 explorer.exe 与通知区域行为和物理机会话基本一致,但窗口站/桌面隔离差异可能导致 Shell_NotifyIconW 行为不同;若只能在 VM 中测试,需为 VM 启用"增强会话"或确保 explorer.exe 在 session 1 正常运行。不要使用 psexec -s 或服务方式运行宿主(那会进 session 0,无通知区域)。
  • 关闭"专注助手"或在通知区域设置中允许 UDA 应用,以免托盘图标被系统策略吞掉造成误判。

构建步骤

rustup target add x86_64-pc-windows-msvc
cargo build --locked -p uda-ffi
:: 产物:target/debug/uda_ffi.dll
python scripts/pe_exports.py target/debug/uda_ffi.dll --expect include/uda.h

pe_exports.py 应报告 35 个导出且与 include/uda.h 声明的符号集一致;若不一致,先停下排查导出层再继续。

关键验证点(按 P1-1 / P1-2 / P2-3 优先级排序)

P2-3 托盘窗口过程重入安全(最高优先,因改动最大且 UB 类问题可能偶发)

  1. 幽灵窗口:运行 examples/python/05_tray.py,按 Alt+Tab,列表中不得出现 UDA 条目。这是 WS_POPUP + WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE 修复的最终验收点。
  2. 模态循环期间重入:右键托盘图标弹出菜单,在菜单保持打开的状态下再次右键点击托盘图标——第二次触发必须被静默忽略(日志出现"menu trigger ignored while a popup is already tracking"),不得叠出第二个菜单、不得崩溃。这是 MenuTrackingGuard 的 AtomicBool 互斥在模态泵期间的直接验收。
  3. 回调内 pump:在菜单项回调中执行一段会泵消息的操作(如 MessageBoxW 或 SendMessage 到自身窗口),确认不崩溃、不死锁。这验证 dispatch/on_command 在调用宿主回调前不持有 Worker 借用。
  4. GDI 句柄回归:用任务管理器给宿主进程加"GDI 对象"列,反复创建/销毁托盘图标 50 次,GDI 对象数应回到基线(±2 以内)。这验收 teardown 的 DeleteObject/DestroyIcon 完整性。
  5. explorer.exe 强杀:taskkill /f /im explorer.exe 后重新 start explorer.exe,托盘图标应自愈(TaskbarCreated 广播 → on_taskbar_created → 重新 NIM_ADD)。
  6. DPI 缩放切换(100% ↔ 150%)与注销重登后图标仍正常。

P1-1 托盘菜单回调门

  1. 构造脚本:createTrayMenu → 加一个 text 行(回调里 print)→ setMenu → destroy → 点击原图标位置。修复前会在 destroy 后 1~2 ms 内仍触发回调(Python/Node 表现为误触发或崩溃),修复后必须静默。
  2. 回调内自销毁:在 text 行回调中调用 menu.destroy()(Node)/menu.destroy()(Python)。首次必须抛错(Node 抛 UdaError status -3,Python 抛 UdaError),且菜单仍可用;回调返回后重试 destroy() 必须成功。这直接验收 close() 的 active != 0 分支与"等待在宿主侧"。
  3. 蹦床释放顺序:在 Node 示例中确认 destroy 失败时 destroyed 仍为 false、keepalives 未被释放(读 examples/nodejs/uda.js 的 TrayMenu.destroy),失败重试成功后才释放。

P1-2 Windows 唤醒锁线程亲和

  1. 环境准备:powercfg /requests 可显示当前 inhibit 请求,powercfg /requests 在锁持有期间应出现一条 DISPLAY 或 SYSTEM 请求,程序名与理由可辨。
  2. 核心场景:写一个宿主,先 tokio::runtime::Runtime::new() 并 block_on 调 uda_wakelock_acquire(触发 ambient 运行时 → FFI 桥把 future 派到临时 worker 并 join),拿到 handle 后让宿主线程继续存活。此时 powercfg /requests 必须持续显示该请求至少 30 秒(证明请求未随桥接线程死亡)。修复前会立即消失。
  3. 释放:调 uda_wakelock_release(handle),powercfg /requests 中该请求应消失,且进程的线程数应回落(持有线程退出)。
  4. 多锁独立:同时 acquire 两个不同类型的锁,powercfg /requests 应显示两条;释放其中一个,另一条必须仍在。
  5. guard drop 路径:Python/Node 中让 WakeLock 对象被 GC 而不显式 release,确认请求最终消失(通道关闭 → 线程恢复 ES_CONTINUOUS 退出)。

回归用例

  • examples/python/01_appearance.py ~ 07_session.py 全跑一遍,与 Linux 侧输出对比语义一致性(07_session 在 Windows 上应报全部七位支持)。
  • cargo test --locked -p uda-platform-windows -- --nocapture(Windows 原生跑,非交叉编译),确认 2790 行 tray.rs 内的单元测试全绿。
  • 通知:examples/python/03_notification.py 应弹出 toast,且带 app_icon 时图标正确。

注意事项

  • 不要在 CI runner 或无人值守会话上跑会话类测试:07_session 若误触 logout/reboot 会直接结束会话或重启机器,测试前确认示例只打印能力矩阵、不执行破坏性动作。
  • powercfg /requests 需要管理员权限才能看到全部请求类型;非管理员可能只看到部分,不要据此判定锁失效。
  • 唤醒锁测试期间关闭系统的自动睡眠/休眠计时器,否则可能因机器休眠打断观测。
  • 托盘测试期间不要切换用户或锁屏,否则隐藏窗口的消息循环会被挂起,导致同步 tick 暂停、现象类似"图标不更新"。
  • 若使用远程桌面,断开 RDP 时会话进入断开状态,通知区域可能被系统重置;重连后需重新运行用例而非判定为缺陷。

…g lifetime

Addresses the cross-validated review findings: Linux session probes use per-await D-Bus bounds with logind gating; tray watcher failures distinguish unreachable/refused/transport-fatal; Windows tray adds icon-decode backoff, delete-pending recovery and catch_unwind callbacks; Python/Node tray lifetimes retain only callbacks needed after a failed destroy; C ABI ownership and capability docs align with behavior.
@umbrella22

Copy link
Copy Markdown
Author

复审与修复

当前 head:71fefa2。这轮把 57f5cd8 重新过了一遍,主要改的是会话探测、托盘生命周期和 C ABI 文档。

  • 会话探测:去掉会把已完成结果一起清掉的聚合超时,改为每个 D-Bus await 自身有界;logind 会话查询用 oneshot 门控,避免在 logind 未运行时触发 activation。首次 uda_session_capabilities 最坏约 35s,已写进头文件。
  • Linux 托盘:watcher 失败分成“不可达 / 拒绝 / 传输致命”三类;传输错误不再报成 Partial,create 直接失败。注册整体受 READY_TIMEOUT 约束。
  • Windows 托盘:图标解码失败改为 5s 起、最长 60s 的退避重试;NIM_DELETE 失败进入 delete_pending,TaskbarCreated 后重置;回调加 catch_unwind;NIM_DELETE 失败时保留 HICON,避免悬空句柄;tooltip 的 UTF-16 截断不会拆 surrogate pair。
  • 绑定:Python 托盘回调按菜单持有 trampoline,销毁失败时只保留蹦床,不再钉住整个 Uda;callback=None 改为 typed NULL。Node 的 destroy()/dispose() 失败时保留回调引用,避免 UAF。
  • C ABI:uda_last_error_message 返回 char *;C 导出屏蔽无法交付的 click/double-click 位;tooltip、ownership、session 能力位描述与实现对齐;CLI 改用 args_os。

上一轮 review 的三条 Low:uda_status_message 保持通用静态文案(详细诊断走 uda_last_error_message);hyprpaper 仍按当前 ok 回复判定;两次 GetSession 已改为并发。

验证:

  • 本地:Linux/Windows 双目标 check、Linux clippy -D warnings 通过;core 60、ffi 116、platform-linux 145、CLI 4。
  • 容器(dbus-run-session):core 60、CLI 4、ffi 121 + 6 集成(1 ignored)、platform-linux 145。
  • CI 4/4。

合并前仍需确认:Windows 实机的 Alt+Tab、explorer 重启、DPI、注销重登;PR #5 与 #7 在 FFI/头文件上有重叠,第二个合并需要手工解冲突。

@SRInternet SRInternet left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 Congratulations!

审查过程包含 Windows Subsystem for Linux 2 (Ubuntu) 免登录实机测试、本地 Linux/Windows 双目标交叉编译、dbus 沙箱自动化测试与 CLI 夹具集成测试
本次审查对象为来自 @umbrella22 的 57f5cd8 至 71fefa2 based on origin/develop(包含 23 项文件变动 / +1503 / -444 变更)

摘要

未发现任何 P1(内存不安全、逻辑错误、安全问题、数据丢失、崩溃)或 P2(实质性功能回归、未处理失败模式)缺陷。本 PR 已完全达到合并条件。

至此,本 PR 已无审阅者可见的任何缺陷,具备全部合并条件。

本 Comment 由人工编写,全部审查建议均由人工成功复现。

再次感谢贡献者 @umbrella22 三个星期以来的耐心贡献。

@SRInternet
SRInternet merged commit 0c3d591 into UniDesktop:develop Nov 1, 2026
5 checks passed
@umbrella22
umbrella22 deleted the fix/issue-4-review-findings branch November 1, 2026 14:38
umbrella22 added a commit to umbrella22/SDK that referenced this pull request Nov 1, 2026
Resolves the conflicts from PR UniDesktop#5 landing in develop. Semantics follow
develop (callback gate, push-returned menu ids, watcher classification,
session probing, refusal -> UDA_ERR_NOT_SUPPORTED); structure follows this
branch (every UDA_* constant in abi.rs, header generated by cbindgen).

PR UniDesktop#7 behavior kept on top of develop: MediaMetadata Option<String> with
NULL for unpublished and an empty C string for published-empty, Windows
media read failures degrading to Unknown/None, and the FFI geometry
validation reporting UDA_ERR_INVALID_ARGUMENT. include/uda.h regenerated
from the merged sources.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working 🎉 congratulations It's coming to an end enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants