From 81dbecb89624aa82cabfcda01d90291777808ffd Mon Sep 17 00:00:00 2001 From: pascal Date: Thu, 11 Jun 2026 17:07:52 +0200 Subject: [PATCH] Left-click support for linux --- client/ui/tray.go | 27 +++++++++++++++------------ client/ui/tray_click_linux.go | 31 +++++++++++++++++++++++++++++++ client/ui/tray_click_other.go | 11 ++++++++--- 3 files changed, 54 insertions(+), 15 deletions(-) create mode 100644 client/ui/tray_click_linux.go diff --git a/client/ui/tray.go b/client/ui/tray.go index e1d93e431..af5190fd4 100644 --- a/client/ui/tray.go +++ b/client/ui/tray.go @@ -231,18 +231,21 @@ func NewTray(app *application.App, window *application.WebviewWindow, svc TraySe } t.menu = t.buildMenu() t.tray.SetMenu(t.menu) - // Left-click on the tray icon opens the menu, and the window is reached - // through the explicit "Open NetBird" entry. This matches macOS - // NSStatusItem convention (click → menu), the Linux StatusNotifierItem - // spec, and the legacy Fyne client. macOS and Linux give us click→menu - // natively, so bindTrayClick is a no-op there (binding OnClick→OpenMenu - // on macOS would freeze the tray — see tray_click_other.go). Windows has - // no native left-click handler, so bindTrayClick wires one explicitly - // (see tray_click_windows.go). On Linux we deliberately skip AttachWindow: - // it plus Wails3's applySmartDefaults would pop the window alongside the - // menu on environments like GNOME Shell with the AppIndicator extension. - // Right-click opens the menu through Wails' default rightClickHandler on - // every platform. + // Tray click behaviour is per-platform (see tray_click_{linux,windows, + // other}.go), bound here by bindTrayClick: + // - macOS: no-op. NSStatusItem opens the menu on left-click natively; + // binding OnClick→OpenMenu there froze the tray (blocking mouseDown: + // starves the main GCD queue — see tray_click_windows.go). + // - Windows: left-click opens the menu, double-click opens the window + // (Wails' Windows systray has no useful default left-click handler). + // - Linux: left-click opens the main window via ShowWindow(); the menu + // is reached by right-click (Wails' SecondaryActivate→OpenMenu, and + // the XEmbed GTK popup on minimal WMs). Both the real-SNI-host and the + // in-process-watcher/XEmbed paths route left-click through Wails' + // linuxSystemTray.Activate, so one OnClick covers both. We deliberately + // skip AttachWindow on Linux: it plus Wails3's applySmartDefaults would + // pop the window alongside the menu on GNOME Shell + AppIndicator. + // The explicit "Open NetBird" menu entry still opens the window everywhere. bindTrayClick(t) app.Event.On(services.EventStatusSnapshot, t.onStatusEvent) diff --git a/client/ui/tray_click_linux.go b/client/ui/tray_click_linux.go new file mode 100644 index 000000000..34a364fd9 --- /dev/null +++ b/client/ui/tray_click_linux.go @@ -0,0 +1,31 @@ +//go:build linux && !(linux && 386) + +package main + +// bindTrayClick wires the tray icon's left-click handler on Linux. +// +// Both Linux click paths converge on Wails' linuxSystemTray.Activate, which +// fires the registered clickHandler: +// - Real SNI hosts (KDE Plasma, Waybar, GNOME Shell + AppIndicator) invoke +// org.kde.StatusNotifierItem.Activate over D-Bus on left-click. +// - The in-process StatusNotifierWatcher + XEmbed host used on minimal WMs +// (Fluxbox, i3, dwm, OpenBox) maps a Button1 press to that same Activate +// call itself (xembed_host_linux.go), so it routes through the same hook. +// Registering OnClick here therefore covers both paths with one handler — no +// changes to the watcher or XEmbed C code are needed. Left-click now opens the +// main window; right-click still opens the menu via Wails' default +// SecondaryActivate→OpenMenu handler (and the XEmbed GTK popup on minimal WMs). +// +// We do NOT register OnDoubleClick: Wails' Linux SNI backend never fires it +// (unlike Windows). And we deliberately skip AttachWindow — it plus Wails3's +// applySmartDefaults would pop the window alongside the menu on GNOME Shell +// with the AppIndicator extension (see the bindTrayClick comment in tray.go). +// +// ShowWindow() is the same dispatcher the explicit "Open NetBird" menu entry +// and SIGUSR1 use: it brings the install-progress / browser-login window +// forward when one of those flows is active, otherwise routes through +// WindowManager.ShowMain so the window re-centers on minimal WMs / the XEmbed +// path instead of landing in the top-left corner. +func bindTrayClick(t *Tray) { + t.tray.OnClick(func() { t.ShowWindow() }) +} diff --git a/client/ui/tray_click_other.go b/client/ui/tray_click_other.go index df2dc5afe..e6a29e419 100644 --- a/client/ui/tray_click_other.go +++ b/client/ui/tray_click_other.go @@ -1,8 +1,13 @@ -//go:build !windows && !android && !ios && !freebsd && !js +//go:build !windows && !android && !ios && !freebsd && !js && (!linux || (linux && 386)) package main func bindTrayClick(*Tray) { - // No-op: macOS/Linux native trays open the menu on click themselves. - // Only Windows needs an explicit handler (tray_click_windows.go). + // No-op: macOS's native NSStatusItem opens the menu on click itself, and + // binding OnClick→anything blocking there froze the tray historically + // (see tray_click_windows.go). Windows wires an explicit handler + // (tray_click_windows.go); Linux opens the window on left-click + // (tray_click_linux.go). The (linux && 386) arm keeps a no-op fallback for + // the i386 Linux build, which excludes the cgo XEmbed/SNI files that + // tray_click_linux.go's build tag matches. }