Skip to content

Window.Show() silently fails after Windows deep sleep/resume (stale GLFW viewport) #6147

@dixieflatline76

Description

@dixieflatline76

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.closing is false (the window wasn't closed, it was invalidated by the OS)
  • w.viewport is non-nil (the Go struct still exists)

Impact

This bug affects any Fyne application on Windows that:

  1. Uses the system tray
  2. Creates windows on demand (e.g., settings/preferences)
  3. 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

  1. Create a tray-based Fyne app with a menu item that shows a window (singleton pattern):
  2. Click "Settings" to open the window. Close the window (triggers SetOnClosed, clears reference).
  3. Click "Settings" again to verify it works (creates fresh window).
  4. Close the window again.
  5. Put the Windows machine into deep sleep (Hibernate / S4, or a long S3 standby where the display driver restarts).
  6. Resume the machine.
  7. Click "Settings" — nothing happens. No window appears. No error logged.
  8. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    information-neededFurther information is requestedunverifiedA bug that has been reported but not verified

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions