From 1c38d0c6ef830a597a1adb64f68bd27d31df0548 Mon Sep 17 00:00:00 2001 From: XYAIStudio Date: Wed, 30 Sep 2026 20:30:19 +0800 Subject: [PATCH] fix(desktop): keep restored window visible --- desktop/src/main.go | 47 +++++++++++++++++++++++++++++++++- desktop/src/tray_click_test.go | 30 +++++++++++++++++++++- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/desktop/src/main.go b/desktop/src/main.go index 656342e8..a6a1963f 100644 --- a/desktop/src/main.go +++ b/desktop/src/main.go @@ -21,7 +21,10 @@ import ( //go:embed assets/* var assets embed.FS -const trayDoubleClick = 400 * time.Millisecond +const ( + trayDoubleClick = 400 * time.Millisecond + windowRestoreGrace = 1500 * time.Millisecond +) func webviewAcceptanceArgs() []string { args := []string{"--disable-gpu", "--disable-gpu-compositing"} @@ -50,6 +53,8 @@ type App struct { quitting bool trayHidePending bool trayHideGeneration uint64 + restoreGeneration uint64 + restoreUntil time.Time trayClickMu sync.Mutex lastTrayClick time.Time @@ -333,6 +338,42 @@ func (a *App) showWindow() { if a.window == nil { return } + generation := a.beginWindowRestore(time.Now()) + a.restoreWindowOnce() + for _, delay := range []time.Duration{75 * time.Millisecond, 250 * time.Millisecond, 750 * time.Millisecond, 1250 * time.Millisecond} { + delay := delay + time.AfterFunc(delay, func() { + if !a.windowRestoreCurrent(generation, time.Now()) { + return + } + a.restoreWindowOnce() + }) + } +} + +func (a *App) beginWindowRestore(now time.Time) uint64 { + a.mu.Lock() + defer a.mu.Unlock() + a.trayHidePending = false + a.trayHideGeneration++ + a.restoreGeneration++ + a.restoreUntil = now.Add(windowRestoreGrace) + return a.restoreGeneration +} + +func (a *App) windowRestoreCurrent(generation uint64, now time.Time) bool { + a.mu.Lock() + defer a.mu.Unlock() + return a.restoreGeneration == generation && now.Before(a.restoreUntil) +} + +func (a *App) windowRestoreInProgress(now time.Time) bool { + a.mu.Lock() + defer a.mu.Unlock() + return now.Before(a.restoreUntil) +} + +func (a *App) restoreWindowOnce() { if a.window.IsMinimised() { a.window.UnMinimise() } @@ -532,6 +573,10 @@ func main() { }) win.OnWindowEvent(events.Common.WindowMinimise, func(_ *application.WindowEvent) { log.Printf("WindowMinimise: minimised=%v", api.window.IsMinimised()) + if api.windowRestoreInProgress(time.Now()) { + log.Printf("WindowMinimise ignored while restoring the main window") + return + } if api.store.get().MinimizeToTray && api.window.IsMinimised() { api.hideToTray() } diff --git a/desktop/src/tray_click_test.go b/desktop/src/tray_click_test.go index eb6de934..abb824ff 100644 --- a/desktop/src/tray_click_test.go +++ b/desktop/src/tray_click_test.go @@ -1,6 +1,9 @@ package main -import "testing" +import ( + "testing" + "time" +) func TestDarwinTrayLeftClickShowsSettings(t *testing.T) { if !trayLeftClickShowsSettings("darwin") { @@ -25,3 +28,28 @@ func TestTrayHideClosingIsConsumedOnce(t *testing.T) { t.Fatal("a later user close must not be mistaken for the tray-hide event") } } + +func TestWindowRestoreIgnoresDelayedMinimiseEvent(t *testing.T) { + now := time.Now() + app := &App{trayHidePending: true, trayHideGeneration: 4} + generation := app.beginWindowRestore(now) + + if app.trayHidePending { + t.Fatal("restoring must cancel a pending hide-to-tray close event") + } + if app.trayHideGeneration != 5 { + t.Fatal("restoring must invalidate the pending hide-to-tray timeout") + } + if !app.windowRestoreInProgress(now.Add(windowRestoreGrace - time.Millisecond)) { + t.Fatal("a delayed minimise event must be ignored during restore") + } + if app.windowRestoreInProgress(now.Add(windowRestoreGrace)) { + t.Fatal("a later user minimise must not be ignored") + } + if !app.windowRestoreCurrent(generation, now.Add(time.Second)) { + t.Fatal("scheduled restore attempts must remain active during the grace period") + } + if app.windowRestoreCurrent(generation+1, now.Add(time.Second)) { + t.Fatal("stale restore attempts must not affect a newer restore") + } +}