-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Description
Checklist
- I have searched the issue tracker for open issues that relate to the same problem, before opening a new one.
- This issue only relates to a single bug. I will open new issues for any other problems.
Describe the bug
When a Fyne application creates a window, hides it (e.g., user closes it), and the system goes into deep sleep (S3/S4), calling Show() on that window after resume has no visible effect. The window never appears, no error is raised, and no panic occurs. The issue is permanent — subsequent calls to Show() on the same window also silently fail. The only recovery is restarting the application.
This affects tray-based applications (using desktop.App.SetSystemTrayMenu) that create windows on demand (e.g., preferences/settings windows) and reuse them via a singleton pattern.
Root Cause
The issue is in internal/driver/glfw/window.go, specifically the Show() → doShowAgain() code path.
Show() (line 137-178)
func (w *window) Show() {
async.EnsureMain(func() {
if w.view() != nil { // ← viewport pointer is non-nil (stale but valid Go memory)
w.doShowAgain() // ← takes this path
return
}
// ...window creation path (never reached)...
})
}doShowAgain() (line 956-975)
func (w *window) doShowAgain() {
if w.isClosing() { // only checks: w.closing || w.viewport == nil
return
}
view := w.view()
if !build.IsWayland {
view.SetPos(w.xpos, w.ypos)
}
view.Show() // ← calls ShowWindow(HWND) on a potentially stale handle
w.visible = true // ← marks as visible even though it may not be
}view() (window_desktop.go, line 809-814)
func (w *window) view() *glfw.Window {
if w.closing {
return nil
}
return w.viewport // ← returns the Go-side pointer unconditionally
}Why It Fails Silently
After deep sleep on Windows, the display driver restarts. The GLFW *Window Go struct is still valid in memory (it's a Go object), so w.viewport != nil is true. However, the underlying Win32 HWND may be stale/invalid.
GLFW's Show() ultimately calls Win32's ShowWindow(HWND, SW_SHOW). When called with an invalid HWND, ShowWindow simply returns FALSE — it does not raise an error, set GetLastError, or cause a crash. GLFW does not check the return value.
Since doShowAgain() unconditionally sets w.visible = true after the call, the Fyne runtime believes the window is visible even though it never appeared.
Why isClosing() Doesn't Help
func (w *window) isClosing() bool {
return w.closing || w.viewport == nil
}Neither condition is true for a sleep-invalidated window:
w.closingisfalse(the window wasn't closed, it was invalidated by the OS)w.viewportis non-nil (the Go struct still exists)
Impact
This bug affects any Fyne application on Windows that:
- Uses the system tray
- Creates windows on demand (e.g., settings/preferences)
- Runs across sleep/resume cycles
The symptom is subtle and permanent — the application appears healthy (tray icon visible, non-window menu items work), but windows can never be shown again until the entire app is restarted.
How to reproduce
- Create a tray-based Fyne app with a menu item that shows a window (singleton pattern):
- Click "Settings" to open the window. Close the window (triggers SetOnClosed, clears reference).
- Click "Settings" again to verify it works (creates fresh window).
- Close the window again.
- Put the Windows machine into deep sleep (Hibernate / S4, or a long S3 standby where the display driver restarts).
- Resume the machine.
- Click "Settings" — nothing happens. No window appears. No error logged.
- Click "Settings" again — still nothing. Permanent until app restart.
Note: Other tray menu items that do NOT create windows (e.g., items that just call Go functions) continue to work correctly after resume.
Screenshots
NA, a tray menu not openning an window cannot be capture by a screenshot.
Example code
var prefsWindow fyne.Window
menuItem := fyne.NewMenuItem("Settings", func() {
if prefsWindow != nil {
prefsWindow.Show() // Re-show existing
prefsWindow.RequestFocus()
return
}
prefsWindow = app.NewWindow("Settings")
prefsWindow.SetOnClosed(func() {
prefsWindow = nil
})
prefsWindow.SetContent(widget.NewLabel("Hello"))
prefsWindow.Resize(fyne.NewSize(400, 300))
prefsWindow.Show()
})Fyne version
2.7.3
Go compiler version
1.25.7
Operating system and version
Windows 11 25H2 26200.7840
Additional Information
Suggested Fix
Add a post-show visibility check in doShowAgain() using GLFW's GetAttrib(glfw.Visible):
func (w *window) doShowAgain() {
if w.isClosing() {
return
}
view := w.view()
if !build.IsWayland {
view.SetPos(w.xpos, w.ypos)
}
view.Show()
// Validate that the window actually became visible.
// After sleep/resume on Windows, the HWND may be stale and Show() silently fails.
if !build.IsWayland && view.GetAttrib(glfw.Visible) == glfw.False {
// Window handle is stale — recreate
w.viewport = nil
w.created = false
w.visible = false
w.created = true
w.create()
if w.view() != nil {
w.view().Show()
}
return
}
w.visible = true
if w.fullScreen {
w.doSetFullScreen(true)
}
w.RunWithContext(func() {
w.driver.repaintWindow(w)
})
}Alternatively, a simpler approach: check GetAttrib(glfw.Visible) in view() and return nil if the viewport exists but is not responding, which would cause Show() to fall through to the window creation path.