Skip to content

feat(linux): 新增基于 GDBus 的 StatusNotifierItem 托盘后端,替代失效的 libappindicator 链条 - #59

Open
FarnaHerry wants to merge 3 commits into
sudoevolve:devfrom
FarnaHerry:feat/linux-tray-sni-dev
Open

feat(linux): 新增基于 GDBus 的 StatusNotifierItem 托盘后端,替代失效的 libappindicator 链条#59
FarnaHerry wants to merge 3 commits into
sudoevolve:devfrom
FarnaHerry:feat/linux-tray-sni-dev

Conversation

@FarnaHerry

@FarnaHerry FarnaHerry commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

概述

为 Linux 平台新增一个基于 StatusNotifierItem(SNI)协议 的托盘后端,只依赖 glib/gio(GDBus),用来替代目前已经事实失效的 GTK3 + libappindicator 兼容链条。

背景与动机

当前 Linux 托盘走的是 GTK3 + libappindicator 链条,存在两个现实问题:

  1. libappindicator 上游已停止维护。 Canonical 原版仓库多年无更新,社区接盘的是 Ayatana 分叉,而分叉把 pkg-config 模块名从 appindicator3-0.1 改成了 ayatana-appindicator3-0.13rd/dependencies.cmake 里探测的是旧名字,所以在只装 Ayatana 版本的发行版上探测直接失败,托盘被静默禁用。
  2. 协议本身已被取代。 libappindicator 是对 KDE 主导的 freedesktop StatusNotifierItem 协议的封装。如今 KDE Plasma、GNOME(AppIndicator 扩展)、COSMIC 等主流面板都原生实现 StatusNotifierWatcher,SNI 才是事实标准。

既然 SNI 是纯 D-Bus 协议,而 glib/gio 在任何桌面 Linux 上都必然存在(连最小容器镜像都常带),完全可以不经过任何中间库直接讲这个协议。

实现内容

core/platform/tray_bridge.c 新增 EUI_TRAY_SNI 分支(插在 appindicator 分支之前),用纯 GDBus 实现协议要求的全部表面:

  • org.kde.StatusNotifierItem(/ 路径下的 item 对象):Id / Title / IconName / IconThemePath / Status / Menu 等只读属性,以及 Activate / SecondaryActivate / Scroll 方法。Activate 置位 g_show_requested,与既有 eui_tray_consume_show_requested() 公共契约对接,对外 API 零改动
  • com.canonical.dbusmenu(/org/freedesktop/DBusMenu):GetLayout / GetGroupProperties / GetProperty / Event / EventGroup / AboutToShow( Group)方法,提供 Show / 分隔符 / Exit 三个菜单项,点击经 Event(clicked) 分别置位 show / exit 标志。
  • watcher 注册:启动时调用 org.kde.StatusNotifierWatcher.RegisterStatusNotifierItem,并在 watcher 重启时自动重新注册。

公开头文件 tray_bridge.h 未做任何修改。

CMake 依赖链条改动与影响面论证

改动只有两处,且都是纯增量,不删除任何既有探测:

3rd/dependencies.cmake:在既有 pkg_check_modules(EUI_APPINDICATOR ...) / EUI_GTK3 之前,新增一行

pkg_check_modules(EUI_GIO QUIET gio-2.0 gobject-2.0 glib-2.0)
  • QUIET:glib 不存在时 configure 不报错、不警告,与 appindicator 探测的行为完全一致,不构成新的硬性构建依赖
  • gobject-2.0 显式列出:正常情况下由 gio-2.0.pcRequires 自动带入,显式写出可保证链接行在被裁剪过的 .pc 环境(如部分交叉 sysroot)下依然完整。

CMakeLists.txt 平台分支:在 elseif(UNIX AND EUI_APPINDICATOR_FOUND AND EUI_GTK3_FOUND) 之前插入 elseif(UNIX AND EUI_GIO_FOUND) 分支,原 appindicator 分支原样保留:

if(WIN32)              ... EUI_TRAY_WINAPI ...   # 未动
elseif(APPLE)          ... EUI_TRAY_APPKIT ...   # 未动
elseif(UNIX AND EUI_GIO_FOUND)                # 新增:SNI
elseif(UNIX AND EUI_APPINDICATOR_FOUND ...)   # 原样保留为 fallback

影响面分析(基于全仓库 grep):

场景 行为变化
Windows / macOS 无。两个分支及对应源码路径完全未触碰
UNIX,无 glib 无。EUI_GIO_FOUND 为假,控制流落到与改动前完全相同的位置
UNIX,有 glib 无 appindicator 从"托盘静默缺失"变为"托盘可用"。这是本 PR 的主要收益场景,也是现代发行版的普遍情况
UNIX,两者都有 改用 SNI 后端,GTK3/appindicator 不再被链接。两后端实现同一套 eui_tray_* 契约,上层无感
  • EUI_TRAY_* 宏在全仓库仅有两个消费方:CMakeLists.txt(定义)与 core/platform/tray_bridge.c(#if/#elif 实现),没有任何其他源文件按平台分支处理托盘,因此不存在隐藏的联动改动。
  • EUI_GIO_* / EUI_APPINDICATOR_* / EUI_GTK3_* 变量同样只在上述两个 cmake 文件中使用,不影响其他第三方依赖的探测顺序与结果。
  • 运行时层面:SNI 是 freedesktop 标准协议,与面板的交互面(两个 well-known 接口 + watcher)比 appindicator 更窄;glib 本身已被绝大多数 Linux 图形栈间接引入,实际新增磁盘依赖为零。

测试与验证

在 Fedora 44 / KDE Plasma(glib 2.88)上完成三层验证:

  1. 协议级端到端 harness:直接编译 tray_bridge.c(-DEUI_TRAY_SNI=1),在真实 session bus 上断言——init 成功、item 出现在 watcher 的 RegisteredStatusNotifierItems、跨连接模拟 Activate 与菜单 Event(Show/Exit) 均能翻转对应 consume 标志、GetLayout 返回结构完整的 (u(ia{sv}av))、shutdown 干净注销,全部通过。
  2. 真实 D-Bus 线路抓包:用系统 gdbus CLI 从独立进程调用,GetLayout 实际字节流为
    (uint32 1, (0, @a{sv} {}, [<(1, {'label': <'Show'>}, @av [])>, <(2, {'type': <'separator'>}, @av [])>, <(3, {'label': <'Exit'>}, @av [])>]))
    
    GetGroupProperties / GetProperty / AboutToShow 返回亦符合 DBusMenu 规范。
  3. 全量 CMake 构建:EUI_TRAY_SNI=1 正确进入编译定义,tray_bridge.c 无警告通过,最终可执行文件仅 NEEDED libgio-2.0 / libgobject-2.0 / libglib-2.0,无任何 GTK/appindicator 依赖。

GTK3 + appindicator 分支的代码路径未做任何修改,其既有行为(在仍提供旧栈的系统上)保持不变。

更新:按 review 意见补齐(2026-08-13)

针对 #58 的 review 反馈,追加提交 134a8af:

  1. xmake 支持:xmake.lua 新增 tray 选项(默认开),Linux 下通过 on_load 里的 find_package("pkgconfig::gio-2.0") 探测(描述作用域拿不到包探测 API,故放脚本作用域),gio 优先、appindicator3-0.1 兜底,与 CMake 行为完全一致。此前 xmake 在 Linux 上不定义任何托盘宏,只会产出 stub。
  2. 依赖缺失时报错:CMake(-DEUI_ENABLE_TRAY=OFF 可关闭)与 xmake(--tray=n)现在在没有任何可用托盘依赖时直接终止 configure 并给出安装提示,不再静默产出 EUI_TRAY_HAS_BACKEND=0 的空壳。
  3. fallback 关系不变:SNI 是纯新增分支,appindicator 路径代码零改动,glib 缺失而旧栈存在时仍选中旧栈。

两个构建系统各自验证三种场景:有 glib → SNI 宏+链接正确;无依赖 → 明确报错;关闭 tray → stub 正常编译。

验证状态

  • KDE Plasma 6 (Fedora 44, glib 2.88):已通过。除协议级 harness 与 gdbus 线路验证外,另用一个完整的 EUI 应用(窗口+按钮,DSL 开 .tray(true))实机验证:托盘图标显示、窗口关闭最小化到托盘、托盘 Show 恢复窗口、Exit 正常退出,全部正常。
  • GNOME(AppIndicator 扩展):本人无 GNOME 设备,正在请社区协助测试,结果会更新到本 PR。测试包是一个 3.6MB 的自包含二进制(仅依赖 glib/OpenGL,任何桌面 Linux 可直跑)。

The existing Linux tray path depends on GTK3 + libappindicator, whose
upstream is unmaintained and whose Ayatana fork renamed the pkg-config
module, so tray support silently breaks on modern distributions.

Add an EUI_TRAY_SNI backend that speaks the freedesktop StatusNotifierItem
and com.canonical.dbusmenu protocols directly over GDBus, needing only
glib/gio -- present on every desktop Linux. The SNI branch is preferred
when gio-2.0 is found; the GTK3 + libappindicator chain remains as a
fallback.

The backend exports org.kde.StatusNotifierItem (Activate / scroll /
secondary-activate) and a DBusMenu server with Show / Exit items, and
registers with org.kde.StatusNotifierWatcher. Verified end-to-end on a
KDE Plasma session bus: watcher registration, Activate, menu Event
dispatch and GetLayout (Show / separator / Exit) all confirmed via
cross-process gdbus calls.
…ssing

Follow-up to the review on issue sudoevolve#58:

- xmake.lua gains the same Linux tray wiring as CMake: a new tray option
  (default on), pkg-config detection of gio-2.0 (SNI, preferred) and
  appindicator3-0.1 (legacy fallback), with the define and link flags
  applied in on_load (package detection is script-scope only).
- Both build systems now stop configuration with a clear error when no
  tray backend dependency is found, instead of silently compiling the
  empty EUI_TRAY_HAS_BACKEND=0 stub. Opt out with -DEUI_ENABLE_TRAY=OFF
  or --tray=n.
- The SNI branch remains additive: appindicator builds are untouched and
  still selected when glib is absent but the old stack is present.

Verified locally: CMake + xmake, each with (a) glib present -> SNI
define + link, (b) no tray deps -> clear configure error, (c) tray
disabled -> stub builds fine.
@FarnaHerry

Copy link
Copy Markdown
Contributor Author

#58 的 review 意见逐条对齐了,见追加提交 134a8af:

  • CMake + xmake 都支持:xmake.lua 新增 tray 选项,Linux 下走 pkgconfig::gio-2.0 探测(SNI 优先),appindicator3-0.1 兜底,行为与 CMake 完全一致。此前 xmake 在 Linux 没有任何托盘宏,只会静默产出 stub。
  • 保留 AppIndicator fallback:确认是新增分支而非替换,fallback 代码零改动,glib 缺失且旧栈存在时仍走旧路。
  • 依赖缺失返回错误:两个构建系统现在都会在无可用托盘依赖时终止 configure 并提示安装 glib2 开发包,或显式关闭(-DEUI_ENABLE_TRAY=OFF / --tray=n)。
  • KDE 验证:已通过(Plasma 6,协议 harness + gdbus 线路 + 完整 EUI 应用实机:图标/最小化到托盘/Show 恢复/Exit 退出全正常)。
  • GNOME 验证:我没有 GNOME 设备,已打好自包含测试包(3.6MB,只依赖 glib)请社区帮忙跑,结果会更新到 PR。

两个构建系统的三种场景(有 glib / 无依赖报错 / 关闭 tray 出 stub)都在本地逐一验证过。

…bbing

eui_begin/end_quiet_third_party_config replaces PKG_CONFIG_EXECUTABLE with
a stub and FORCE-restores the previous (usually empty) value into the
cache. That poisoned cache entry made find_package(PkgConfig) fail for the
tray probes below, so CI runners (which have both pkg-config and
libglib2.0-dev) hit the new "no tray backend" FATAL_ERROR. Local builds
never saw it because they passed -DPKG_CONFIG_EXECUTABLE explicitly.

Drop the poisoned cache entry before find_package(PkgConfig) so the tray
probes see the real pkg-config. The bundled third-party scopes are
unaffected: they install the stub themselves inside their own scope.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant