Skip to content

Fix WindowPersistence when closing from FullScreen#256

Open
botMorten wants to merge 2 commits intodotMorten:mainfrom
botMorten:fix/window-persistence-fullscreen
Open

Fix WindowPersistence when closing from FullScreen#256
botMorten wants to merge 2 commits intodotMorten:mainfrom
botMorten:fix/window-persistence-fullscreen

Conversation

@botMorten
Copy link
Copy Markdown
Contributor

@botMorten botMorten commented Mar 24, 2026

Summary

Fixes a persistence race where closing while in FullScreen could save fullscreen-sized bounds instead of the prior overlapped placement.

Root cause

Window size/bounds changes can fire before presenter transition settles, so persistence captured at close could be fullscreen geometry.

Changes

  • Add deferred overlapped placement capture (RequestOverlappedPlacementCapture)
  • Cache last known overlapped WINDOWPLACEMENT
  • Update capture on position/state/presenter transitions
  • In SavePersistence(), when current presenter is not overlapped, persist cached overlapped placement instead of fullscreen placement

Notes

This preserves existing default behavior (do not persist fullscreen presenter kind), while avoiding corrupted restore geometry after closing from fullscreen.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a window persistence race where closing while in a non-overlapped presenter (notably FullScreen) could persist fullscreen-sized bounds instead of the prior overlapped placement, leading to incorrect restore geometry on next launch.

Changes:

  • Added deferred overlapped WINDOWPLACEMENT capture and caching of the last known overlapped placement.
  • Triggered placement capture on state/position/presenter transitions and after persistence restore.
  • Updated SavePersistence() to persist cached overlapped placement when current presenter isn’t overlapped.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +499 to +508
_window.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
{
if (version != _overlappedPlacementUpdateVersion)
return;
TryCaptureOverlappedPlacement();
});
}

private bool TryCaptureOverlappedPlacement()
{
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The capture callback can run after the window manager is disposed/closing (it’s queued asynchronously and not cancelled). In that case GetWindowHandle()/GetWindowPlacement may fail or throw. Add a guard (e.g., check _isDisposed / window handle validity) inside the queued callback and/or skip scheduling when disposed. Also consider handling TryEnqueue returning false (e.g., fall back to synchronous capture).

Suggested change
_window.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
{
if (version != _overlappedPlacementUpdateVersion)
return;
TryCaptureOverlappedPlacement();
});
}
private bool TryCaptureOverlappedPlacement()
{
// If we've already been disposed, don't schedule any more work.
if (_isDisposed)
return;
bool enqueued = _window.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
{
// The callback may run after dispose; bail out early in that case.
if (_isDisposed)
return;
if (version != _overlappedPlacementUpdateVersion)
return;
TryCaptureOverlappedPlacement();
});
// If we failed to enqueue (e.g., dispatcher is shutting down), try capturing synchronously.
if (!enqueued && !_isDisposed && version == _overlappedPlacementUpdateVersion)
{
TryCaptureOverlappedPlacement();
}
}
private bool TryCaptureOverlappedPlacement()
{
// Avoid accessing window/handle after disposal.
if (_isDisposed || _window is null)
return false;

Copilot uses AI. Check for mistakes.
@botMorten
Copy link
Copy Markdown
Contributor Author

Thanks — great catches. I pushed follow-up commit addressing the review feedback:

  • Debounced so only one queued callback is pending at a time (reduces queue churn during move/resize).
  • Added disposal guards before scheduling and inside callback; also fallback to synchronous capture if enqueue fails.
  • Updated the persistence comment to match behavior for any non-overlapped presenter (not just fullscreen).
  • Added failure guard in before serializing placement.

On test coverage: agreed on adding a fullscreen-close persistence regression test. I can add that in a follow-up if maintainers want it in this PR (I wasn’t able to run local WinUI test builds in my current environment).

@botMorten
Copy link
Copy Markdown
Contributor Author

Thanks — great catches. I pushed a follow-up commit addressing the review feedback:

  • Debounced RequestOverlappedPlacementCapture so only one queued callback is pending at a time (reduces queue churn during move/resize).
  • Added disposal guards before scheduling and inside callback; also fallback to synchronous capture if enqueue fails.
  • Updated the persistence comment to match behavior for any non-overlapped presenter (not just fullscreen).
  • Added GetWindowPlacement failure guard in SavePersistence before serializing placement.

On test coverage: agreed on adding a fullscreen-close persistence regression test. I can add that in a follow-up if maintainers want it in this PR (I wasn’t able to run local WinUI test builds in my current environment).

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@dotMorten dotMorten self-requested a review March 24, 2026 22:15
@dotMorten dotMorten self-assigned this Mar 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants