This prevents a race where OS-initiated minimizes prematurely
remove windows that should be transiently restored.
Problem: a display-change can trigger a a fast reconciliation path (same
count), and shortly after Windows may emit a SystemMinimizeStart for
affected windows. The minimize handler treated those as user-initiated
and removed the window, making later reconciliation unable to restore
it.
Fix: timestamp display-change notifications and add a
display_change_in_progress(period) check to the minimize handler. While
that grace period is active the minimize handler skips remove_window(),
preserving windows so the reconciliator can restore them.
This change adds verification steps and robustness improvements
to the monitor reconciliator.
- Retry display enumeration if it fails, up to 5 times, instead of silently
ignoring errors.
- When a potential monitor removal is detected, drop locks and wait briefly,
then re-query the Win32 display API. If the monitor reappears, treat the
event as transient and do not remove the monitor.
- Re-acquire locks when needed, so the verification wait doesn't hold
internal state locks.
Why:
- Win32 device/display notifications are noisy and not always immediately
available; enumeration can fail (e.g. "Invalid monitor handle" / HRESULT
0x80070585), or multiple DBT_DEVICEARRIVAL/DBT_DEVICEREMOVECOMPLETE events
can arrive in quick succession.
- Without verification the reconciliator could treat transient events (for
example, power-plan–induced monitor sleep where Removes+Adds occur within
milliseconds) as real removals, resulting in fewer detected monitors than
are actually present.
When a border is destroyed, the main thread was forcefully releasing D2D
resources, while the border window thread might still be trying to use
them in its message loop.
Releasing the RenderTarget on one thread while another is calling
EndDraw on it leads to a use-after-free scenario or invalid state for
the COM object, resulting in the crash.
This commit applies a fix that moves the resource cleanup logic from the
main thread to the window thread. Now, resources are only released
during the WM_DESTROY message processing, which guarantees
synchronization with other window messages like WM_PAINT.
Apparently there is a quirk of GetWindowLongPtr when querying styles. If
the style bits are genuinely 0, the API returns 0 but does not clear the
last error.
If a previous API call set an error, GetWindowLongPtr might define
"failure" as "return 0 AND GetLastError != 0".
Hence, it is important to clear the last/previous error before calling
GetWindowLongPtrW to ensure that we don't misinterpret a valid 0 return
value as an error.