Windows: bring the main window back on screen when its restore position is off screen (#1858)

* Windows: bring the main window back on screen when its restore position is off screen

A minimized window is parked by Windows at (-32000,-32000). When the display
topology changes while it is minimized - which happens on every session lock
that powers off a monitor - Windows can copy that parking position into the
window's restore rectangle and clear WS_MINIMIZE. The window then is, by every
API, a normal visible window living 32000 pixels off screen: IsIconic returns
FALSE, IsWindowVisible returns TRUE, and ShowWindow (SW_RESTORE) is a no-op
because the restore rectangle is exactly where the window already is.

The user is left with an application that answers its notification area icon
but whose main window cannot be shown by any means, and only restarting
VeraCrypt recovers it.

Validate the restore rectangle and re-center it on the primary monitor when it
intersects none. The repair runs from the three paths that show the window and,
because clicking the taskbar button of an already visible window runs no
application code at all, also on WM_DISPLAYCHANGE. showCmd is left as
GetWindowPlacement returned it, so a legitimately minimized window stays
minimized and only its future restore position is corrected.

The window size is captured once after the dialog has been laid out: a parked
window carries the parking size, so it cannot serve as a source for the normal
dimensions.

Verified on Windows 11 26200.8875 with two monitors at different scale factors,
the primary one being powered off by the session lock: GetWindowPlacement
returned rcNormalPosition = (-32000,-32000)-(-31840,-31972), and neither a
notification area click nor an explicit minimize/restore cycle brought the
window back.

* Windows: fix the size, offset and trigger of the main window repair

The size used by RepairMainWindowPlacement was measured on the window at
the end of InitMainDialog whenever the window was not minimized. In the
state the repair handles, the window is not minimized but has the
parking dimensions, and InitMainDialog can run on it, for example when
the language is changed from Preferences opened from the notification
area. The font compensation then grows the height and keeps the parking
width, so the kept size became 160x456. The size is now taken once from
the newly created window, whose width never changes, and follows the
font compensation only while the window has that width: in a window as
narrow as a parked one the menu bar wraps and moves the controls down,
so the compensation height is meaningless there too.

A restore rectangle that is no larger than a minimized window in either
dimension is now repaired. Before, both dimensions had to be that small,
so a 160x456 rectangle was left as it was.

The repaired position was converted with the work area origin of the
primary monitor, and the visibility test passed rcNormalPosition to
MonitorFromRect unconverted. Workspace coordinates are offset by the
position of the work area within the monitor associated with the
window, so the offset of that monitor is now used, both to convert a
copy of the rectangle to screen coordinates for the visibility test and
to express the repaired position. With an appbar on the left, a window
just beyond the right edge was taken as visible; with an appbar on top
of the primary monitor only, a window repaired on another monitor was
placed too high.

The repair also runs on WM_ACTIVATE. The session notification that
triggers it after an unlock is registered only while the background task
is enabled, and activating a window that is not minimized, from its
taskbar button or with Alt+Tab, runs none of the other repair paths.
Two comments that said a taskbar click runs none of our code are
corrected.
This commit is contained in:
amail80
2026-10-11 22:13:35 +09:00
committed by GitHub
parent 83b4fc23cb
commit 689507c41c
+162 -8
View File
@@ -958,6 +958,15 @@ void EndMainDlg (HWND hwndDlg)
}
}
/* Outer size of the main window, used by RepairMainWindowPlacement. The user cannot
resize the dialog: its width is the one it was created with, and its height only
changes through the font compensation in InitMainDialog, which can only increase
it. The size is therefore taken once from the newly created window and then
follows the compensation, instead of being measured on the window later: in the
state RepairMainWindowPlacement handles, the window is not minimized but still has
the parking dimensions, so neither IsIconic nor the current size can be trusted. */
static SIZE MainDlgNormalSize = {0};
static void InitMainDialog (HWND hwndDlg)
{
MENUITEMINFOW info;
@@ -965,6 +974,18 @@ static void InitMainDialog (HWND hwndDlg)
wchar_t *str;
int i;
if (MainDlgNormalSize.cx == 0)
{
/* First call, from WM_INITDIALOG: the window has just been created from the
dialog template and has never been shown, so its size can be trusted. */
RECT rcCreated;
if (GetWindowRect (hwndDlg, &rcCreated))
{
MainDlgNormalSize.cx = rcCreated.right - rcCreated.left;
MainDlgNormalSize.cy = rcCreated.bottom - rcCreated.top;
}
}
if (!Silent)
{
/* Call the common dialog init code */
@@ -1089,6 +1110,16 @@ static void InitMainDialog (HWND hwndDlg)
{
SetWindowPos (hwndDlg, NULL, 0, 0, mainWidth, correctHeigth , SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE);
}
/* Apply the same compensation to the size kept for RepairMainWindowPlacement,
but only when the window has the width it was created with. This can run
while the window is minimized or has the parking dimensions - for instance
when the language is changed from Preferences opened from the notification
area - and in a window that narrow the menu bar wraps, which moves the
controls down and makes correctHeigth meaningless. The kept size then stays
as it was. */
if ((LONG) mainWidth == MainDlgNormalSize.cx && (LONG) correctHeigth > MainDlgNormalSize.cy)
MainDlgNormalSize.cy = (LONG) correctHeigth;
}
}
@@ -8005,6 +8036,85 @@ static void SignalExitCode (int exitCode)
}
}
/* Repair the main window's restore position when it points outside every monitor.
A minimized window is parked by Windows at (-32000,-32000). When the display
topology changes while the window is minimized - which happens on every
session lock that powers off a monitor - Windows can copy that parking
position into the window's restore rectangle and clear WS_MINIMIZE. The
window is then, by every API, a normal visible window that happens to live
32000 pixels off screen, and no amount of ShowWindow (SW_RESTORE) brings it
back: the restore rectangle is exactly where it already is.
showCmd is deliberately left as GetWindowPlacement returned it, so that a
legitimately minimized window stays minimized and only its future restore
position is corrected. Returns TRUE when a repair was performed. */
static BOOL RepairMainWindowPlacement (HWND hwnd)
{
WINDOWPLACEMENT wp;
MONITORINFO mi;
HMONITOR hMonitor;
RECT rcScreen;
LONG width, height, offsetX, offsetY;
memset (&wp, 0, sizeof (wp));
wp.length = sizeof (wp);
if (!GetWindowPlacement (hwnd, &wp))
return FALSE;
width = MainDlgNormalSize.cx;
height = MainDlgNormalSize.cy;
if (width <= 0 || height <= 0)
return FALSE;
/* rcNormalPosition is in workspace coordinates. They differ from screen
coordinates by the offset of the work area within the monitor associated
with the window: the origin of its rcWork minus the origin of its rcMonitor.
For a minimized window, MonitorFromWindow uses the restore position. A window
that intersects no monitor falls back to the primary one, which is also where
the repair puts it. */
hMonitor = MonitorFromWindow (hwnd, MONITOR_DEFAULTTOPRIMARY);
mi.cbSize = sizeof (mi);
if (!hMonitor || !GetMonitorInfo (hMonitor, &mi))
return FALSE;
offsetX = mi.rcWork.left - mi.rcMonitor.left;
offsetY = mi.rcWork.top - mi.rcMonitor.top;
/* Repair when the restore rectangle, converted to screen coordinates, lies
outside every monitor, and also when it is no larger than a minimized window
in either dimension: the position may have been pulled back inside the
desktop while a parking dimension was left behind, which would restore the
dialog as a sliver. Neither state can be reached by resizing, the dialog
having a fixed size. */
rcScreen = wp.rcNormalPosition;
OffsetRect (&rcScreen, offsetX, offsetY);
if (MonitorFromRect (&rcScreen, MONITOR_DEFAULTTONULL)
&& (rcScreen.right - rcScreen.left) > GetSystemMetrics (SM_CXMINIMIZED)
&& (rcScreen.bottom - rcScreen.top) > GetSystemMetrics (SM_CYMINIMIZED))
return FALSE;
/* Center the window on the work area of that monitor, then convert the result
to workspace coordinates with the same offset. */
wp.rcNormalPosition.left = mi.rcWork.left + ((mi.rcWork.right - mi.rcWork.left) - width) / 2;
wp.rcNormalPosition.top = mi.rcWork.top + ((mi.rcWork.bottom - mi.rcWork.top) - height) / 2;
wp.rcNormalPosition.right = wp.rcNormalPosition.left + width;
wp.rcNormalPosition.bottom = wp.rcNormalPosition.top + height;
OffsetRect (&wp.rcNormalPosition, -offsetX, -offsetY);
SetWindowPlacement (hwnd, &wp);
return TRUE;
}
static void ShowMainWindow (HWND hwnd)
{
RepairMainWindowPlacement (hwnd);
ShowWindow (hwnd, SW_SHOW);
ShowWindow (hwnd, SW_RESTORE);
}
/* Except in response to the WM_INITDIALOG and WM_ENDSESSION messages, the dialog box procedure
should return nonzero if it processes a message, and zero if it does not. */
BOOL CALLBACK MainDialogProc (HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
@@ -8540,12 +8650,37 @@ BOOL CALLBACK MainDialogProc (HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPa
DialogBoxW (hInst, MAKEINTRESOURCEW (IDD_ABOUT_DLG), hwndDlg, (DLGPROC) AboutDlgProc);
return 1;
}
if ((wParam & 0xFFF0) == SC_RESTORE)
{
/* Sent when the taskbar button of a minimized window is clicked.
WM_ACTIVATE skips a minimized window, so this is the only repair
on that path. Repair the restore position first; when it had to
be corrected, restore the window here as well, the default
handling having already read the placement that was in effect
when the message was dispatched. */
if (RepairMainWindowPlacement (hwndDlg))
{
ShowWindow (hwndDlg, SW_RESTORE);
return 1;
}
}
return 0;
case WM_HELP:
OpenPageHelp (hwndDlg, 0);
return 1;
case WM_ACTIVATE:
/* Activating a window that is not minimized, by clicking its taskbar button
or with Alt+Tab, runs none of the other code that repairs the restore
position, and the session notifications used below are only registered
while the background task is enabled. Checking here as well means that
activating the window always brings it back. The default handling is
left to run. */
if (LOWORD (wParam) != WA_INACTIVE && !HIWORD (wParam) && IsWindowVisible (hwndDlg))
RepairMainWindowPlacement (hwndDlg);
return 0;
case WM_WTSSESSION_CHANGE:
if (TaskBarIconMutex != NULL)
{
@@ -8563,6 +8698,27 @@ BOOL CALLBACK MainDialogProc (HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPa
DismountAll (hwndDlg, bForceAutoDismount, TRUE, UNMOUNT_MAX_AUTO_RETRIES, UNMOUNT_AUTO_RETRY_DELAY);
}
}
if (WTS_SESSION_UNLOCK == wParam)
{
/* Late in the sequence that follows a session lock, after the
deferred window repositioning the system performs when the
display topology changed while the session was locked. The
WM_DISPLAYCHANGE handler may run before that repositioning is
applied, so repeat the check here. */
if (IsWindowVisible (hwndDlg))
RepairMainWindowPlacement (hwndDlg);
}
return 0;
case WM_DISPLAYCHANGE:
/* The display topology changed. Correct the restore position now if it
was left outside every monitor, instead of waiting until the window is
activated or shown. A window hidden in the notification area is
skipped; its position is repaired by ShowMainWindow when the user asks
for it. */
if (IsWindowVisible (hwndDlg))
RepairMainWindowPlacement (hwndDlg);
return 0;
case WM_ENDSESSION:
@@ -8803,8 +8959,7 @@ BOOL CALLBACK MainDialogProc (HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPa
case WM_LBUTTONDOWN:
SetForegroundWindow (hwndDlg);
MainWindowHidden = FALSE;
ShowWindow (hwndDlg, SW_SHOW);
ShowWindow (hwndDlg, SW_RESTORE);
ShowMainWindow (hwndDlg);
return 1;
case WM_RBUTTONUP:
@@ -10055,8 +10210,7 @@ BOOL CALLBACK MainDialogProc (HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPa
case TC_APPMSG_MOUNT_SHOW_WINDOW:
MainWindowHidden = FALSE;
ShowWindow (hwndDlg, SW_SHOW);
ShowWindow (hwndDlg, SW_RESTORE);
ShowMainWindow (hwndDlg);
return 1;
case VC_APPMSG_CREATE_RESCUE_DISK:
@@ -12198,10 +12352,10 @@ void ChangeMainWindowVisibility ()
if (!MainWindowHidden)
SetForegroundWindow (MainDlg);
ShowWindow (MainDlg, !MainWindowHidden ? SW_SHOW : SW_HIDE);
if (!MainWindowHidden)
ShowWindow (MainDlg, SW_RESTORE);
if (MainWindowHidden)
ShowWindow (MainDlg, SW_HIDE);
else
ShowMainWindow (MainDlg);
}