Skip to content

Repaint windows when the display comes back on Windows - #59

Merged
Marc-André Moreau (mamoreau-devolutions) merged 1 commit into
masterfrom
claude/dt-blank-screen-c9d436
Oct 23, 2026
Merged

Marc-André Moreau (mamoreau-devolutions) merged 1 commit into
masterfrom
claude/dt-blank-screen-c9d436

Conversation

@mamoreau-devolutions

Copy link
Copy Markdown
Contributor

Problem

After DT is left idle for a while (display off / session locked), the window can come back completely blank: only the Mica backdrop is visible, with no title bar or terminal content, until something forces a repaint.

Cause

  • Avalonia renders each window into a Windows.UI.Composition drawing surface. Windows can discard that surface's contents while the display is off, the session is locked or the GPU resets.
  • Avalonia never subscribes to RenderingDeviceReplaced, so it doesn't know the contents are gone.
  • DT intentionally skips frames when nothing is damaged (TermControl blink timer), so an idle window presents no new frame.
  • TransparencyLevelHint="Mica,..." puts a Mica backdrop behind the window, which is what shows through the empty surface.

Fix

New WindowsSurfaceRecovery (Windows-only, installed once at app startup via Window.WindowOpenedEvent):

  • Each window registers for GUID_CONSOLE_DISPLAY_STATE power-setting notifications and WTS session notifications, and hooks its WndProc through Win32Properties.AddWndProcHookCallback.
  • On display on/dimmed, session unlock/connect, resume (PBT_APMRESUMESUSPEND/PBT_APMRESUMEAUTOMATIC) or WM_DISPLAYCHANGE, it calls InvalidateRect. Avalonia handles the resulting WM_PAINT with a full redraw, and recreates the render target if the device was lost.
  • A second repaint runs one second later in case the display stack is still settling.
  • Notifications are unregistered on WM_DESTROY, and the hook is removed on Closed.

Testing

  • WindowsSurfaceRecoveryTests cover which messages trigger a repaint.
  • Full Devolutions.Terminal.App.Tests suite: 623 passed.
  • Smoke-tested an isolated Debug build: it registers for the notifications (wtsapi32.dll loads, unlike the shipped build), and it keeps rendering correctly after it receives WM_DISPLAYCHANGE, unlock and resume messages.
  • Not yet verified against a real overnight display-off/lock repro.

🤖 Generated with Claude Code

Avalonia draws each window into a Windows.UI.Composition drawing surface.
Windows can discard that surface's contents while the display is off, the
session is locked or the GPU resets, and Avalonia does not observe
RenderingDeviceReplaced. Because DT skips frames while nothing is damaged,
an idle terminal then shows only the Mica backdrop until something repaints.

Register every window for display-state, session and resume notifications
and invalidate the HWND when the display returns; Avalonia answers WM_PAINT
with a full redraw (recreating the render target if the device was lost).
A second repaint runs a second later in case the display stack is still
settling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
publish-dry-run — 55a40972 Deployed Oct 23, 2026 by mamoreau-devolutions via MSIX packages #370
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant