From bd042fcd4ee98a55cae84bf7a1a4fff0fff63ebd Mon Sep 17 00:00:00 2001 From: Onyx Date: Sun, 18 Oct 2026 04:02:50 +0200 Subject: [PATCH] Initialise GTK before the Linux tray, run headless if it cannot start tray-icon builds its menu with GTK, which panics on first use unless gtk::init() ran on the main thread. On Wayland the tray panel panicked, relaunched and eventually exited, taking the HTTP API with it. Initialise GTK first and fall back to headless mode when it fails. Fixes #26. --- Cargo.lock | 1 + Cargo.toml | 2 ++ src/desktop.rs | 19 +++++++++++++++++-- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab10592..3db71b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2935,6 +2935,7 @@ dependencies = [ "directories", "eframe", "epaint_default_fonts", + "gtk", "hidapi", "notify-rust", "objc2 0.6.4", diff --git a/Cargo.toml b/Cargo.toml index f0895e5..ee64ab6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,8 @@ vello_cpu = { version = "0.1", default-features = false, features = ["std", "u8_ tray-icon = { version = "0.24.2", default-features = false, features = ["gtk"] } [target.'cfg(target_os = "linux")'.dependencies] +# tray-icon's GTK menu needs gtk::init() on the main thread before it is built. +gtk = "0.18" # X11 overlay banner and foreground-window lookup. Pure Rust; no system # libraries beyond what eframe/tray-icon already require. x11rb = "0.13" diff --git a/src/desktop.rs b/src/desktop.rs index 7739574..ad35ccd 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -49,7 +49,8 @@ fn headless_requested() -> bool { fn gui_available() -> bool { #[cfg(target_os = "linux")] { - env::var_os("WAYLAND_DISPLAY").is_some() || env::var_os("DISPLAY").is_some() + (env::var_os("WAYLAND_DISPLAY").is_some() || env::var_os("DISPLAY").is_some()) + && gtk_ready() } #[cfg(not(target_os = "linux"))] { @@ -57,6 +58,20 @@ fn gui_available() -> bool { } } +/// The tray menu is built with GTK, which panics on first use unless it was +/// initialised on this thread. If it cannot start there is no tray, so Bridge +/// runs headless instead of crash-looping (issue #26). +#[cfg(target_os = "linux")] +fn gtk_ready() -> bool { + match gtk::init() { + Ok(()) => true, + Err(error) => { + tracing::warn!(%error, "GTK could not start"); + false + } + } +} + /// Serves the Bridge runtime without a window: drains notices as native /// notifications, applies update restarts, and blocks on Ctrl-C. fn run_headless( @@ -1094,7 +1109,7 @@ pub fn run() -> Result<()> { if headless_requested() || !gui_available() { if !headless_requested() { tracing::warn!( - "no display found (set WAYLAND_DISPLAY or DISPLAY for the tray panel); running headless: HTTP API, HID, and game profiles still work" + "no usable display for the tray panel (set WAYLAND_DISPLAY or DISPLAY, and GTK must start); running headless: HTTP API, HID, and game profiles still work" ); } let Some((server, snapshots, commands, updates, _panel_requests, _show_panel)) =