From 0d950d46f33f82488121b3206bcb7386ce09ebd7 Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Wed, 10 Jun 2026 18:24:20 +0200 Subject: [PATCH] fix recenter? --- client/ui/recenter_linux.go | 57 +++++++++++++++++++++++++---- client/ui/services/windowmanager.go | 17 +++++---- 2 files changed, 59 insertions(+), 15 deletions(-) diff --git a/client/ui/recenter_linux.go b/client/ui/recenter_linux.go index 2c574e08f..d25666306 100644 --- a/client/ui/recenter_linux.go +++ b/client/ui/recenter_linux.go @@ -2,16 +2,57 @@ package main +import ( + "os" + "strings" +) + // recenterOnShowPredicate returns the predicate WindowManager uses to decide // whether to re-center its Go-shown windows (main, Settings) on each show. // -// On Linux this is xembedTrayAvailable: re-centering is needed only in the -// minimal-WM / in-process-XEmbed-tray environment, where the window manager -// neither centers small windows for us nor restores their position across a -// hide -> show round-trip. The predicate is evaluated per show (not once at -// startup) because the XEmbed tray can appear after the UI starts — the panel -// and the autostarted app race at login — and xembedTrayAvailable is a cheap, -// side-effect-free selection-owner probe, fine to call repeatedly. +// Re-centering is needed only on bare WMs (fluxbox, IceWM, twm …) that neither +// place small windows for us nor restore their position across a hide -> show +// round-trip — the same environment the in-process XEmbed tray host serves. +// xembedTrayAvailable (a _NET_SYSTEM_TRAY_S0 selection-owner probe) detects +// that host, but it is NOT a clean proxy for "bare WM": full desktops like +// Cinnamon, MATE, XFCE and LXDE ship a legacy XEmbed systray AND a real +// compositing WM (Muffin/Marco/xfwm4) that places windows itself. On those, +// running the post-show X11 re-center makes the window visibly jump from the +// WM's placement to ours (reported on Cinnamon/Mint/X11). So we additionally +// require that the session does NOT advertise a known full desktop. +// +// Evaluated per show (not once at startup) because the XEmbed tray can appear +// after the UI starts — the panel and the autostarted app race at login — and +// xembedTrayAvailable is a cheap, side-effect-free selection-owner probe. func recenterOnShowPredicate() func() bool { - return xembedTrayAvailable + return func() bool { + return xembedTrayAvailable() && !inFullDesktopEnvironment() + } +} + +// fullDesktopTokens are desktop-environment identifiers whose window manager +// places and restores windows for us. Several also expose a legacy XEmbed +// systray, so they must be excluded from the re-center path explicitly. +// Matched case-insensitively against the colon-separated XDG_CURRENT_DESKTOP / +// DESKTOP_SESSION tokens. Bare WMs leave these unset (or report their own name, +// e.g. "Fluxbox"), which is absent here — so they still re-center. +var fullDesktopTokens = []string{ + "cinnamon", "mate", "xfce", "gnome", "kde", "plasma", + "lxde", "lxqt", "unity", "budgie", "deepin", "pantheon", +} + +// inFullDesktopEnvironment reports whether the session advertises one of the +// known full desktop environments via XDG_CURRENT_DESKTOP or DESKTOP_SESSION. +func inFullDesktopEnvironment() bool { + for _, env := range []string{"XDG_CURRENT_DESKTOP", "DESKTOP_SESSION"} { + for _, tok := range strings.Split(os.Getenv(env), ":") { + tok = strings.ToLower(strings.TrimSpace(tok)) + for _, full := range fullDesktopTokens { + if strings.Contains(tok, full) { + return true + } + } + } + } + return false } diff --git a/client/ui/services/windowmanager.go b/client/ui/services/windowmanager.go index d3d06324b..27777d7cb 100644 --- a/client/ui/services/windowmanager.go +++ b/client/ui/services/windowmanager.go @@ -163,10 +163,12 @@ type WindowManager struct { hiddenForLogin []application.Window mu sync.Mutex // recenterOnShow reports whether Go should re-center the Go-shown - // windows (main, Settings) on each show. Only true in the minimal-WM / - // in-process XEmbed-tray environment, where the WM neither centers small - // windows for us nor restores their position across a hide -> show - // round-trip. On full desktops (GNOME/KDE) the WM handles placement, so + // windows (main, Settings) on each show. Only true on bare WMs (the + // in-process XEmbed-tray environment minus the full desktops — Cinnamon, + // MATE, XFCE … — that also expose an XEmbed systray), where the WM neither + // centers small windows for us nor restores their position across a + // hide -> show round-trip. On full desktops (GNOME/KDE/Cinnamon) the WM + // handles placement, so // re-centering is unnecessary and would fight a window the user moved — // there this stays nil and centerWhenReady is a no-op. Set by the Linux // startup path via SetRecenterOnShow; nil on macOS/Windows and in tests. @@ -649,9 +651,10 @@ func (s *WindowManager) ShowMain() { // SetRecenterOnShow installs the predicate that gates Go-side re-centering of // the main and Settings windows (see the recenterOnShow field). The Linux -// startup path passes xembedTrayAvailable so re-centering happens only in the -// minimal-WM / in-process-XEmbed-tray environment; macOS/Windows and tests -// leave it unset, making centerWhenReady a no-op. +// startup path passes a predicate that is true only on bare WMs (XEmbed tray +// present and no full desktop advertised), so re-centering happens only where +// the WM won't place windows for us; macOS/Windows and tests leave it unset, +// making centerWhenReady a no-op. func (s *WindowManager) SetRecenterOnShow(pred func() bool) { s.recenterOnShow = pred }