Skip to content

Commit a749ee3

Browse files
committed
Viewer: cover full-screen transition stale frames (#86)
Full-screen enter/exit can expose one stale DWM composition frame while the frame style, menu, and placement are changing. Instead of hiding the app or using LockWindowUpdate, capture the monitor contents before the structural change and show that snapshot as a temporary topmost cover until the final Viewer frame has been repainted and flushed. This removes the old LockWindowUpdate/DWM-transition timer workaround, which could produce classic-theme flashes, and keeps the placement/copy-bits safeguards from the partial mitigation. Closes #86
1 parent 514a14a commit a749ee3

1 file changed

Lines changed: 106 additions & 40 deletions

File tree

Viewer/ViewerView.cpp

Lines changed: 106 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ const bool printPlaySpeed = false;
4141

4242
#define WM_VIEWER_PLAY_TIMER (WM_APP + 1)
4343

44-
static const UINT_PTR VIEWER_FULLSCREEN_DWM_TIMER = 0x5100;
45-
static const UINT VIEWER_FULLSCREEN_DWM_DELAY_MS = 250;
4644

4745
enum QMouseMenuID
4846
{
@@ -61,6 +59,80 @@ static const QMouseMenuInfo QMouseMenu[] = {
6159
{ "Sync Input", QMouseMenuSyncInput },
6260
};
6361

62+
class CTransitionSnapshotCover {
63+
public:
64+
CTransitionSnapshotCover() : mHwnd(NULL), mBitmap(NULL) {}
65+
66+
~CTransitionSnapshotCover()
67+
{
68+
Destroy();
69+
}
70+
71+
bool Create(const RECT &rc)
72+
{
73+
Destroy();
74+
75+
const int w = rc.right - rc.left;
76+
const int h = rc.bottom - rc.top;
77+
if (w <= 0 || h <= 0)
78+
return false;
79+
80+
HDC screenDC = ::GetDC(NULL);
81+
if (screenDC == NULL)
82+
return false;
83+
84+
HDC memDC = ::CreateCompatibleDC(screenDC);
85+
if (memDC == NULL) {
86+
::ReleaseDC(NULL, screenDC);
87+
return false;
88+
}
89+
90+
mBitmap = ::CreateCompatibleBitmap(screenDC, w, h);
91+
if (mBitmap != NULL) {
92+
HGDIOBJ oldBitmap = ::SelectObject(memDC, mBitmap);
93+
::BitBlt(memDC, 0, 0, w, h, screenDC, rc.left, rc.top, SRCCOPY);
94+
::SelectObject(memDC, oldBitmap);
95+
}
96+
97+
::DeleteDC(memDC);
98+
::ReleaseDC(NULL, screenDC);
99+
100+
if (mBitmap == NULL)
101+
return false;
102+
103+
mHwnd = ::CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_NOACTIVATE,
104+
_T("STATIC"), NULL, WS_POPUP | SS_BITMAP,
105+
rc.left, rc.top, w, h, NULL, NULL, AfxGetInstanceHandle(), NULL);
106+
if (mHwnd == NULL) {
107+
::DeleteObject(mBitmap);
108+
mBitmap = NULL;
109+
return false;
110+
}
111+
112+
::SendMessage(mHwnd, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)mBitmap);
113+
::ShowWindow(mHwnd, SW_SHOWNOACTIVATE);
114+
::UpdateWindow(mHwnd);
115+
::DwmFlush();
116+
return true;
117+
}
118+
119+
void Destroy()
120+
{
121+
if (mHwnd != NULL) {
122+
::DestroyWindow(mHwnd);
123+
mHwnd = NULL;
124+
}
125+
if (mBitmap != NULL) {
126+
::DeleteObject(mBitmap);
127+
mBitmap = NULL;
128+
}
129+
}
130+
131+
private:
132+
HWND mHwnd;
133+
HBITMAP mBitmap;
134+
};
135+
64136
using namespace std;
65137

66138
// CViewerView
@@ -1713,38 +1785,47 @@ void CViewerView::ToggleFullScreen()
17131785
CMainFrame *pMainFrm = static_cast<CMainFrame *>(AfxGetMainWnd());
17141786
if (pMainFrm == NULL)
17151787
return;
1788+
HWND hFrame = pMainFrm->GetSafeHwnd();
17161789

17171790
const bool entering = !mFullMode;
1791+
MONITORINFO monitorInfo = {};
1792+
monitorInfo.cbSize = sizeof(monitorInfo);
1793+
HMONITOR hMonitor = ::MonitorFromWindow(hFrame, MONITOR_DEFAULTTONEAREST);
1794+
const bool haveMonitorInfo = ::GetMonitorInfo(hMonitor, &monitorInfo) != FALSE;
1795+
1796+
CTransitionSnapshotCover transitionCover;
1797+
if (haveMonitorInfo)
1798+
transitionCover.Create(monitorInfo.rcMonitor);
1799+
1800+
// Batch the structural full-screen change without LockWindowUpdate. That API
1801+
// can make themed non-client areas flash as classic controls during the frame
1802+
// swap. The snapshot cover keeps DWM's one-frame stale surface out of view
1803+
// without flashing through to the desktop or a black placeholder.
17181804
pMainFrm->SetRedraw(FALSE);
17191805
SetRedraw(FALSE);
1720-
::LockWindowUpdate(pMainFrm->GetSafeHwnd());
1721-
KillTimer(VIEWER_FULLSCREEN_DWM_TIMER);
1722-
BOOL disableDwmTransitions = TRUE;
1723-
::DwmSetWindowAttribute(pMainFrm->GetSafeHwnd(),
1724-
DWMWA_TRANSITIONS_FORCEDISABLED,
1725-
&disableDwmTransitions,
1726-
sizeof(disableDwmTransitions));
17271806

17281807
if (entering) {
17291808
mPreFullPlacement.length = sizeof(mPreFullPlacement);
17301809
mHavePreFullPlacement = pMainFrm->GetWindowPlacement(&mPreFullPlacement) != FALSE;
1731-
if (mPreFullPlacement.showCmd == SW_SHOWMINIMIZED)
1810+
if (mPreFullPlacement.showCmd == SW_SHOWMINIMIZED || !pMainFrm->IsZoomed())
17321811
mPreFullPlacement.showCmd = SW_SHOWNORMAL;
17331812

17341813
mFullMode = true;
17351814

17361815
MONITORINFO mi = {};
17371816
mi.cbSize = sizeof(mi);
17381817
HMONITOR hmon = ::MonitorFromWindow(pMainFrm->GetSafeHwnd(), MONITOR_DEFAULTTONEAREST);
1739-
if (::GetMonitorInfo(hmon, &mi)) {
1818+
if (haveMonitorInfo || ::GetMonitorInfo(hmon, &mi)) {
1819+
if (haveMonitorInfo)
1820+
mi = monitorInfo;
17401821
CRect rc(mi.rcMonitor);
17411822
pMainFrm->SetMenu(NULL);
17421823
pMainFrm->ModifyStyle(
17431824
WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
17441825
WS_POPUP, 0);
17451826
pMainFrm->SetWindowPos(&CWnd::wndTop, rc.left, rc.top,
17461827
rc.Width(), rc.Height(),
1747-
SWP_NOOWNERZORDER | SWP_FRAMECHANGED | SWP_NOREDRAW);
1828+
SWP_NOOWNERZORDER | SWP_FRAMECHANGED | SWP_NOREDRAW | SWP_NOCOPYBITS);
17481829
}
17491830
} else {
17501831
mFullMode = false;
@@ -1756,24 +1837,32 @@ void CViewerView::ToggleFullScreen()
17561837
0);
17571838
pMainFrm->AddMainMenu();
17581839
GetDocument()->UpdateMenu();
1759-
if (mHavePreFullPlacement)
1840+
if (mHavePreFullPlacement && mPreFullPlacement.showCmd != SW_SHOWMAXIMIZED) {
1841+
const CRect rc(mPreFullPlacement.rcNormalPosition);
1842+
pMainFrm->SetWindowPos(NULL, rc.left, rc.top,
1843+
rc.Width(), rc.Height(),
1844+
SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED |
1845+
SWP_NOREDRAW | SWP_NOCOPYBITS);
1846+
} else if (mHavePreFullPlacement) {
17601847
pMainFrm->SetWindowPlacement(&mPreFullPlacement);
1761-
else
1848+
} else {
17621849
pMainFrm->ShowWindow(SW_RESTORE);
1850+
}
17631851
pMainFrm->SetWindowPos(NULL, 0, 0, 0, 0,
17641852
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
1765-
SWP_NOOWNERZORDER | SWP_FRAMECHANGED | SWP_NOREDRAW);
1853+
SWP_NOOWNERZORDER | SWP_FRAMECHANGED | SWP_NOREDRAW | SWP_NOCOPYBITS);
17661854
}
17671855

17681856
pMainFrm->RecalcLayout(TRUE);
17691857
FitToWindow();
17701858
pMainFrm->SetRedraw(TRUE);
17711859
SetRedraw(TRUE);
1772-
::LockWindowUpdate(NULL);
17731860
pMainFrm->SetForegroundWindow();
17741861
pMainFrm->RedrawWindow(NULL, NULL,
17751862
RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN | RDW_FRAME | RDW_ERASE);
1776-
SetTimer(VIEWER_FULLSCREEN_DWM_TIMER, VIEWER_FULLSCREEN_DWM_DELAY_MS, NULL);
1863+
::DwmFlush();
1864+
transitionCover.Destroy();
1865+
::DwmFlush();
17771866
}
17781867

17791868
void CViewerView::ToggleHelp()
@@ -2101,16 +2190,6 @@ void CViewerView::OnDestroy()
21012190
{
21022191
CView::OnDestroy();
21032192

2104-
KillTimer(VIEWER_FULLSCREEN_DWM_TIMER);
2105-
CMainFrame *pMainFrm = static_cast<CMainFrame *>(AfxGetMainWnd());
2106-
if (pMainFrm != NULL) {
2107-
BOOL disableDwmTransitions = FALSE;
2108-
::DwmSetWindowAttribute(pMainFrm->GetSafeHwnd(),
2109-
DWMWA_TRANSITIONS_FORCEDISABLED,
2110-
&disableDwmTransitions,
2111-
sizeof(disableDwmTransitions));
2112-
}
2113-
21142193
mBufferPool->disable();
21152194
mBufferQueue->destroy();
21162195
KillPlayTimer();
@@ -2154,19 +2233,6 @@ void CViewerView::OnSize(UINT nType, int cx, int cy)
21542233

21552234
void CViewerView::OnTimer(UINT_PTR nIDEvent)
21562235
{
2157-
if (nIDEvent == VIEWER_FULLSCREEN_DWM_TIMER) {
2158-
KillTimer(VIEWER_FULLSCREEN_DWM_TIMER);
2159-
CMainFrame *pMainFrm = static_cast<CMainFrame *>(AfxGetMainWnd());
2160-
if (pMainFrm != NULL) {
2161-
BOOL disableDwmTransitions = FALSE;
2162-
::DwmSetWindowAttribute(pMainFrm->GetSafeHwnd(),
2163-
DWMWA_TRANSITIONS_FORCEDISABLED,
2164-
&disableDwmTransitions,
2165-
sizeof(disableDwmTransitions));
2166-
}
2167-
return;
2168-
}
2169-
21702236
CView::OnTimer(nIDEvent);
21712237
}
21722238

0 commit comments

Comments
 (0)