From 6df5dee215b8d1858b93680745d35264ffa804b6 Mon Sep 17 00:00:00 2001 From: Stanislav Popov Date: Sun, 12 Jul 2026 23:44:22 +0500 Subject: [PATCH 1/5] fix(taskbar): repair work-area collapse and restore windows after display re-init (#3045 #4827 #2086) After a display re-init (idle display-off + wake, sleep/resume, DP deep sleep, refresh-rate/resolution change) the Win10 / ep_taskbar desktop work area collapses and is never recomputed, and snapped windows the shell shoved into the transient collapsed band are left stranded. Restarting explorer was the only fix. Add a work-area watchdog on EP's service window: - Listens for display power/resume/mode events, debounces, and validates each monitor's work area against its taskbar band (per-monitor, EP-tray only, so third-party appbars are never touched). - On a detected collapse: soft-kick the taskbar recompute (WM_DISPLAYCHANGE), then escalate to SystemParametersInfoW(SPI_SETWORKAREA) for the broken monitors (the proven repair) with an anti-loop cooldown. - Snapshot top-level window rects on display-OFF/suspend (which precede the wake-time collapse); after a verified repair, restore any snapshotted window the shell displaced into the former collapsed band to its snapshot rect (position + size), with a conservative filter and a band anti-conflict guard so user-placed and maximized windows are left alone. - Everything is logged to %TEMP%\ep_workarea_watchdog.log for field diagnosis. Gated by a new "FixWorkAreaAfterDisplayReinit" setting (default on), exposed as a taskbar checkbox in the GUI. Co-Authored-By: Claude Opus 4.8 --- ExplorerPatcher/dllmain.c | 714 +++++++++++++++++++++++++ ep_gui/resources/EPSettingsResources.h | 1 + ep_gui/resources/lang/ep_gui.en-US.rc | 1 + ep_gui/resources/settings.reg | 2 + ep_gui/resources/settings10.reg | 2 + 5 files changed, 720 insertions(+) diff --git a/ExplorerPatcher/dllmain.c b/ExplorerPatcher/dllmain.c index 5d7b99d0d..e760b6d3d 100644 --- a/ExplorerPatcher/dllmain.c +++ b/ExplorerPatcher/dllmain.c @@ -3,6 +3,7 @@ #endif #include #include +#include #include #include #include @@ -103,6 +104,7 @@ DWORD dwTaskbarAl = 1; DWORD bShowUpdateToast = FALSE; DWORD bToolbarSeparators = FALSE; DWORD bTaskbarAutohideOnDoubleClick = FALSE; +DWORD bFixWorkAreaAfterDisplayReinit = TRUE; DWORD dwOrbStyle = 0; DWORD bEnableSymbolDownload = TRUE; DWORD dwAltTabSettings = 0; @@ -617,6 +619,559 @@ void FixUpCenteredTaskbar() } } +// Work-area watchdog: after a display re-init (resume, display-on, mode change), +// the Win10 taskbar (classic TrayUI or the prebuilt ep_taskbar.ge.dll) can compute +// its stuck rect against a transient monitor geometry and never recompute the work +// area once the topology settles, collapsing the desktop work area. We watch for +// display power/resume/mode events, wait for the topology to stabilize, then +// validate each monitor's work area against its taskbar band. If broken we first +// "kick" the taskbar's own recompute path by posting WM_DISPLAYCHANGE to the tray +// windows (soft, the same thing an explorer restart triggers); if that fails to +// heal, we escalate to writing SPI_SETWORKAREA directly for the broken monitors +// (the proven method - it is exactly what the user's external watcher did). +// Everything here is logged to %TEMP%\ep_workarea_watchdog.log so the event -> +// arm -> detect -> repair chain is observable in the field. +#define EP_WORKAREA_WATCHDOG_TIMER_ID 200 +#define EP_WORKAREA_WATCHDOG_DEBOUNCE 2000 // ms - wait for topology to stabilize +#define EP_WORKAREA_WATCHDOG_VERIFY 1500 // ms - recheck after a kick/repair +#define EP_WORKAREA_WATCHDOG_MAX_KICKS 3 +#define EP_WORKAREA_REPAIR_COOLDOWN 15000 // ms - ignore new arms this long after a direct SPI repair (anti-loop) +#define EP_WORKAREA_MAX_BROKEN 8 +static UINT g_epWorkAreaKickCount = 0; +static ULONGLONG g_epLastDirectRepairTick = 0; +static HPOWERNOTIFY g_epDisplayStateNotify = NULL; +static const GUID EP_GUID_CONSOLE_DISPLAY_STATE = // 6fe69556-704a-47a0-8f24-c28d936fda47 + { 0x6fe69556, 0x704a, 0x47a0, { 0x8f, 0x24, 0xc2, 0x8d, 0x93, 0x6f, 0xda, 0x47 } }; + +typedef struct +{ + HMONITOR hMonitor; + RECT expectedWork; // healthy work area we repair to + RECT observedWork; // rcWork at detect time = the transient collapsed band + RECT rcMonitor; // for fallback matching when the HMONITOR handle changes +} EP_BROKEN_WORKAREA; + +typedef struct +{ + EP_BROKEN_WORKAREA items[EP_WORKAREA_MAX_BROKEN]; + int count; +} EP_WORKAREA_BROKEN_LIST; + +// --- Window layout snapshot/restore (iteration 3) ------------------------------- +// SPI_SETWORKAREA repairs the work area but only reflows *maximized* windows; a +// snapped window that the shell shoved into the transient collapsed band on wake is +// left stranded. We snapshot top-level window rects on EP-taskbar monitors on +// display-OFF/suspend (which precedes the wake-time collapse), and after a verified +// repair we move any window that landed inside the former collapsed band back to its +// snapshot rect. All snapshot/restore state is touched only on the service-window +// thread (WM_POWERBROADCAST + WM_TIMER); the tray thread only calls +// EP_ArmWorkAreaWatchdog (SetTimer), so no locking is needed. +#define EP_WINRESTORE_MAX_WINDOWS 64 // snapshot capacity +#define EP_WINRESTORE_WAKE_WINDOW 90000 // ms after display-ON within which a snapshot may be applied +#define EP_WINRESTORE_BAND_TOL 32 // px tolerance for "window sits inside the collapsed band" + +typedef struct { HWND hWnd; RECT rc; HMONITOR hMonitor; RECT rcMonitor; BOOL bZoomed; } EP_WINRESTORE_ENTRY; +typedef struct { EP_WINRESTORE_ENTRY items[EP_WINRESTORE_MAX_WINDOWS]; int count; } EP_WINRESTORE_SNAPSHOT; + +static EP_WINRESTORE_SNAPSHOT g_epWinSnapshot; +static ULONGLONG g_epWinSnapshotTick = 0; +static ULONGLONG g_epLastDisplayOnTick = 0; +static EP_WORKAREA_BROKEN_LIST g_epWorkAreaBrokenAtDetect; // broken list captured at the FIRST detect of a cycle + +// Append one timestamped line to %TEMP%\ep_workarea_watchdog.log. Events are rare +// (display re-inits), so open/write/close per call is fine and keeps the log intact +// across crashes. Enabled unconditionally - it is also the field diagnostic tool for +// issue reporters. +static void EP_WorkAreaLog(const char* fmt, ...) +{ + WCHAR wszDir[MAX_PATH]; + DWORD n = GetTempPathW(MAX_PATH, wszDir); + if (n == 0 || n >= MAX_PATH) return; + + WCHAR wszPath[MAX_PATH + 32]; + _snwprintf_s(wszPath, _countof(wszPath), _TRUNCATE, L"%sep_workarea_watchdog.log", wszDir); + + HANDLE hFile = CreateFileW(wszPath, FILE_APPEND_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) return; + + SYSTEMTIME st; + GetLocalTime(&st); + + char szLine[1024]; + int len = _snprintf_s(szLine, _countof(szLine), _TRUNCATE, + "%04d-%02d-%02d %02d:%02d:%02d.%03d ", + st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); + if (len < 0 || len >= (int)_countof(szLine)) len = (int)strlen(szLine); + + va_list args; + va_start(args, fmt); + _vsnprintf_s(szLine + len, _countof(szLine) - len, _TRUNCATE, fmt, args); + va_end(args); + + size_t total = strlen(szLine); + if (total + 2 < _countof(szLine)) + { + szLine[total++] = '\r'; + szLine[total++] = '\n'; + szLine[total] = '\0'; + } + + DWORD written; + WriteFile(hFile, szLine, (DWORD)strlen(szLine), &written, NULL); + CloseHandle(hFile); +} + +// Find the EP taskbar on a monitor: the primary tray (Shell_TrayWnd) when bPrimary, +// else a Shell_SecondaryTrayWnd that sits on this monitor. Returns NULL when no EP +// tray is on the monitor (so we never touch third-party appbar reservations). +static HWND EP_FindEPTrayOnMonitor(HMONITOR hMonitor, BOOL bPrimary) +{ + HWND hTray = NULL; + if (bPrimary) + { + hTray = FindWindowW(L"Shell_TrayWnd", NULL); + if (hTray && MonitorFromWindow(hTray, MONITOR_DEFAULTTONULL) != hMonitor) hTray = NULL; + } + if (!hTray) + { + HWND hWnd = NULL; + do + { + hWnd = FindWindowExW(NULL, hWnd, L"Shell_SecondaryTrayWnd", NULL); + if (hWnd && MonitorFromWindow(hWnd, MONITOR_DEFAULTTONULL) == hMonitor) + { + hTray = hWnd; + break; + } + } while (hWnd); + } + return hTray; +} + +// Examine one monitor; if its work area matches a known bug signature, append a +// {HMONITOR, expectedWork} record to the broken list. Never stops enumeration so we +// collect every broken monitor. Logs the full geometry + verdict for every monitor. +static BOOL CALLBACK EP_WorkAreaCollectMonitorProc(HMONITOR hMonitor, HDC hdc, LPRECT lprc, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(hdc); + UNREFERENCED_PARAMETER(lprc); + EP_WORKAREA_BROKEN_LIST* list = (EP_WORKAREA_BROKEN_LIST*)lParam; + + MONITORINFO mi; + mi.cbSize = sizeof(mi); + if (!GetMonitorInfoW(hMonitor, &mi)) + { + EP_WorkAreaLog(" mon %p: GetMonitorInfo failed err=%lu -> skip", hMonitor, GetLastError()); + return TRUE; + } + + RECT rcMonitor = mi.rcMonitor; + RECT rcWork = mi.rcWork; + + // Find the EP taskbar on this monitor (primary: Shell_TrayWnd; else a Shell_SecondaryTrayWnd) + HWND hTray = EP_FindEPTrayOnMonitor(hMonitor, (mi.dwFlags & MONITORINFOF_PRIMARY) != 0); + if (!hTray) + { + // No EP taskbar here -> don't touch third-party appbar reservations. + EP_WorkAreaLog(" mon %p rcMon=(%ld,%ld,%ld,%ld) rcWork=(%ld,%ld,%ld,%ld): no EP tray -> skip", + hMonitor, rcMonitor.left, rcMonitor.top, rcMonitor.right, rcMonitor.bottom, + rcWork.left, rcWork.top, rcWork.right, rcWork.bottom); + return TRUE; + } + + RECT rcTray; + if (!GetWindowRect(hTray, &rcTray)) + { + EP_WorkAreaLog(" mon %p tray=%p: GetWindowRect failed -> skip", hMonitor, hTray); + return TRUE; + } + RECT rcBand; + if (!IntersectRect(&rcBand, &rcTray, &rcMonitor)) + { + EP_WorkAreaLog(" mon %p tray=%p: tray not on monitor -> skip", hMonitor, hTray); + return TRUE; + } + + int monW = rcMonitor.right - rcMonitor.left; + int monH = rcMonitor.bottom - rcMonitor.top; + if (monW <= 0 || monH <= 0) return TRUE; + + int bandW = rcBand.right - rcBand.left; + int bandH = rcBand.bottom - rcBand.top; + BOOL horizontal = (bandW >= bandH); // horizontal strip -> docked top/bottom + + // Global autohide state (same query pattern as ToggleTaskbarAutohide) + BOOL bAutohide = FALSE; + { + APPBARDATA abd; + abd.cbSize = sizeof(APPBARDATA); + if (SHAppBarMessage(ABM_GETSTATE, &abd) == ABS_AUTOHIDE) bAutohide = TRUE; + } + + // Expected work area = monitor minus the taskbar band (or the whole monitor when autohide) + RECT expectedWork = rcMonitor; + if (!bAutohide) + { + if (horizontal) + { + if ((rcBand.top - rcMonitor.top) <= (rcMonitor.bottom - rcBand.bottom)) + expectedWork.top = rcBand.bottom; // docked to top + else + expectedWork.bottom = rcBand.top; // docked to bottom + } + else + { + if ((rcBand.left - rcMonitor.left) <= (rcMonitor.right - rcBand.right)) + expectedWork.left = rcBand.right; // docked to left + else + expectedWork.right = rcBand.left; // docked to right + } + } + + int band = horizontal ? bandH : bandW; + int tol = band / 4; + if (tol < 8) tol = 8; + + int expW = expectedWork.right - expectedWork.left; + int expH = expectedWork.bottom - expectedWork.top; + int workW = rcWork.right - rcWork.left; + int workH = rcWork.bottom - rcWork.top; + + // Signature #1 (Collapsed, #3045/#2086): work area is much smaller than expected, + // by more than ~25% of the monitor along either axis. + BOOL sig1 = ((expW - workW) > (monW / 4)) || ((expH - workH) > (monH / 4)); + + // Signature #2 (Not recalculated, #3045): a visible (non-autohide) taskbar whose + // band still overlaps the work area beyond tolerance (windows leak under the bar). + BOOL sig2 = FALSE; + if (!bAutohide) + { + RECT rcOverlap; + if (IntersectRect(&rcOverlap, &rcWork, &rcBand)) + { + int overlapDepth = horizontal + ? (rcOverlap.bottom - rcOverlap.top) + : (rcOverlap.right - rcOverlap.left); + if (overlapDepth > tol) sig2 = TRUE; + } + } + + EP_WorkAreaLog(" mon %p rcMon=(%ld,%ld,%ld,%ld) rcWork=(%ld,%ld,%ld,%ld) exp=(%ld,%ld,%ld,%ld) tray=%p autohide=%d sig1=%d sig2=%d -> %s", + hMonitor, rcMonitor.left, rcMonitor.top, rcMonitor.right, rcMonitor.bottom, + rcWork.left, rcWork.top, rcWork.right, rcWork.bottom, + expectedWork.left, expectedWork.top, expectedWork.right, expectedWork.bottom, + hTray, bAutohide, sig1, sig2, (sig1 || sig2) ? "BROKEN" : "sane"); + + if ((sig1 || sig2) && list->count < EP_WORKAREA_MAX_BROKEN) + { + list->items[list->count].hMonitor = hMonitor; + list->items[list->count].expectedWork = expectedWork; + list->items[list->count].observedWork = rcWork; // the transient collapsed band + list->items[list->count].rcMonitor = rcMonitor; // fallback match when HMONITOR changes + list->count++; + } + return TRUE; // keep enumerating all monitors +} + +// Collect every broken monitor. Returns the count. +static int EP_CollectBrokenWorkAreas(EP_WORKAREA_BROKEN_LIST* list) +{ + list->count = 0; + EnumDisplayMonitors(NULL, NULL, EP_WorkAreaCollectMonitorProc, (LPARAM)list); + return list->count; +} + +// Wrapper kept for symmetry: TRUE when no monitor matches a bug signature. +static BOOL EP_AreWorkAreasSane(void) +{ + EP_WORKAREA_BROKEN_LIST list; + return EP_CollectBrokenWorkAreas(&list) == 0; +} + +// Post WM_DISPLAYCHANGE to the tray windows to make the taskbar run its full +// stuck-rect + work-area recompute path. Posted (not sent) and addressed directly to +// the tray windows. This is the soft first attempt (works for classic TrayUI). +static void EP_KickTaskbarWorkAreaRecompute(void) +{ + int bpp = 32; + HDC hdc = GetDC(NULL); + if (hdc) + { + bpp = GetDeviceCaps(hdc, BITSPIXEL); + ReleaseDC(NULL, hdc); + } + LPARAM lp = MAKELPARAM(GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)); + + HWND hTray = FindWindowW(L"Shell_TrayWnd", NULL); + if (hTray) PostMessageW(hTray, WM_DISPLAYCHANGE, (WPARAM)bpp, lp); + + HWND hWnd = NULL; + do + { + hWnd = FindWindowExW(NULL, hWnd, L"Shell_SecondaryTrayWnd", NULL); + if (hWnd) PostMessageW(hWnd, WM_DISPLAYCHANGE, (WPARAM)bpp, lp); + } while (hWnd); + + EP_WorkAreaLog(" kick: posted WM_DISPLAYCHANGE to tray windows"); +} + +// Directly set the correct work area for each broken monitor via SPI_SETWORKAREA. +// This is the proven repair (exactly what the user's external watcher did): +// SPI_SETWORKAREA applies to the monitor that contains the rect. Escalation fallback +// when the soft kick did not heal the collapse. Stamps the cooldown clock. +static void EP_RepairWorkAreasDirect(EP_WORKAREA_BROKEN_LIST* list) +{ + for (int i = 0; i < list->count; i++) + { + RECT rc = list->items[i].expectedWork; + BOOL ok = SystemParametersInfoW(SPI_SETWORKAREA, 0, &rc, SPIF_SENDCHANGE); + EP_WorkAreaLog(" SPI_SETWORKAREA mon=%p rect=(%ld,%ld,%ld,%ld) result=%d err=%lu", + list->items[i].hMonitor, rc.left, rc.top, rc.right, rc.bottom, + ok, ok ? 0UL : GetLastError()); + } + g_epLastDirectRepairTick = GetTickCount64(); +} + +// --- Window layout snapshot/restore implementation ------------------------------ + +// Monitors that currently host an EP taskbar. Restricting snapshot/restore to these +// keeps us consistent with the work-area fix (never touch a third-party appbar's +// monitor). +typedef struct { HMONITOR items[16]; int count; } EP_TRAY_MONITOR_LIST; + +static BOOL CALLBACK EP_CollectTrayMonitorsProc(HMONITOR hMonitor, HDC hdc, LPRECT lprc, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(hdc); + UNREFERENCED_PARAMETER(lprc); + EP_TRAY_MONITOR_LIST* list = (EP_TRAY_MONITOR_LIST*)lParam; + if (list->count >= (int)_countof(list->items)) return TRUE; + MONITORINFO mi; + mi.cbSize = sizeof(mi); + if (!GetMonitorInfoW(hMonitor, &mi)) return TRUE; + if (EP_FindEPTrayOnMonitor(hMonitor, (mi.dwFlags & MONITORINFOF_PRIMARY) != 0)) + list->items[list->count++] = hMonitor; + return TRUE; +} + +static BOOL EP_IsTrayMonitor(EP_TRAY_MONITOR_LIST* list, HMONITOR hMonitor) +{ + for (int i = 0; i < list->count; i++) + if (list->items[i] == hMonitor) return TRUE; + return FALSE; +} + +typedef struct { EP_TRAY_MONITOR_LIST* trays; EP_WINRESTORE_SNAPSHOT* snap; BOOL overflow; } EP_WINSNAP_CTX; + +// Conservative top-level-window filter: a window is snapshotted only if it is visible, +// non-minimized, unowned, not a tool window, not cloaked, not a shell/desktop window, +// has a non-empty rect, and sits on an EP-tray monitor. +static BOOL CALLBACK EP_WinSnapshotEnumProc(HWND hWnd, LPARAM lParam) +{ + EP_WINSNAP_CTX* ctx = (EP_WINSNAP_CTX*)lParam; + if (ctx->snap->count >= EP_WINRESTORE_MAX_WINDOWS) + { + if (!ctx->overflow) + { + EP_WorkAreaLog(" snapshot: capacity %d reached, stopping enumeration", EP_WINRESTORE_MAX_WINDOWS); + ctx->overflow = TRUE; + } + return FALSE; // stop enumerating + } + + if (!IsWindowVisible(hWnd) || IsIconic(hWnd)) return TRUE; + if (GetWindow(hWnd, GW_OWNER) != NULL) return TRUE; + + LONG_PTR exStyle = GetWindowLongPtrW(hWnd, GWL_EXSTYLE); + if (exStyle & WS_EX_TOOLWINDOW) return TRUE; + + BOOL isCloaked = FALSE; + if (SUCCEEDED(DwmGetWindowAttribute(hWnd, DWMWA_CLOAKED, &isCloaked, sizeof(BOOL))) && isCloaked) + return TRUE; + + WCHAR cls[64] = { 0 }; + if (GetClassNameW(hWnd, cls, (int)_countof(cls))) + { + if (!wcscmp(cls, L"Shell_TrayWnd") || !wcscmp(cls, L"Shell_SecondaryTrayWnd") || + !wcscmp(cls, L"Progman") || !wcscmp(cls, L"WorkerW")) + return TRUE; + } + + RECT rc; + if (!GetWindowRect(hWnd, &rc)) return TRUE; + if (rc.right <= rc.left || rc.bottom <= rc.top) return TRUE; + + HMONITOR hMon = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONULL); + if (!hMon || !EP_IsTrayMonitor(ctx->trays, hMon)) return TRUE; + + RECT rcMon = { 0 }; + MONITORINFO mi; + mi.cbSize = sizeof(mi); + if (GetMonitorInfoW(hMon, &mi)) rcMon = mi.rcMonitor; + + EP_WINRESTORE_ENTRY* e = &ctx->snap->items[ctx->snap->count++]; + e->hWnd = hWnd; + e->rc = rc; + e->hMonitor = hMon; + e->rcMonitor = rcMon; + e->bZoomed = IsZoomed(hWnd); + + EP_WorkAreaLog(" snapshot win %p cls=%ls rc=(%ld,%ld,%ld,%ld) mon=%p zoomed=%d", + hWnd, cls, rc.left, rc.top, rc.right, rc.bottom, hMon, e->bZoomed); + return TRUE; +} + +// Snapshot the top-level window layout on EP-tray monitors. Triggered on display-OFF/ +// suspend, which precede the wake-time work-area collapse, so the snapshot captures +// the pre-damage baseline. Skipped mid-repair so a shifted layout never overwrites a +// good snapshot. +static void EP_SnapshotWindowLayout(void) +{ + if (g_epWorkAreaKickCount > 0) + { + EP_WorkAreaLog("snapshot skipped: repair cycle in progress (kickCount=%u)", g_epWorkAreaKickCount); + return; + } + + EP_TRAY_MONITOR_LIST trays; + trays.count = 0; + EnumDisplayMonitors(NULL, NULL, EP_CollectTrayMonitorsProc, (LPARAM)&trays); + if (trays.count == 0) + { + EP_WorkAreaLog("snapshot skipped: no EP-tray monitors"); + return; + } + + g_epWinSnapshot.count = 0; + EP_WINSNAP_CTX ctx = { &trays, &g_epWinSnapshot, FALSE }; + EnumWindows(EP_WinSnapshotEnumProc, (LPARAM)&ctx); + + g_epWinSnapshotTick = GetTickCount64(); + EP_WorkAreaLog("snapshot: %d windows on %d EP-tray monitors", g_epWinSnapshot.count, trays.count); +} + +// Match a snapshot entry to a broken-at-detect record: first by HMONITOR handle, then +// by monitor rect (the handle can change across a display re-init). +static EP_BROKEN_WORKAREA* EP_FindBrokenForWindow(EP_WINRESTORE_ENTRY* e) +{ + for (int i = 0; i < g_epWorkAreaBrokenAtDetect.count; i++) + if (g_epWorkAreaBrokenAtDetect.items[i].hMonitor == e->hMonitor) + return &g_epWorkAreaBrokenAtDetect.items[i]; + for (int i = 0; i < g_epWorkAreaBrokenAtDetect.count; i++) + if (EqualRect(&g_epWorkAreaBrokenAtDetect.items[i].rcMonitor, &e->rcMonitor)) + return &g_epWorkAreaBrokenAtDetect.items[i]; + return NULL; +} + +// After a verified work-area repair, move any snapshotted window that the shell shoved +// into the former collapsed band back to its snapshot rect. One-shot: consumes both +// the snapshot and the broken-at-detect list. +static void EP_RestoreWindowsFromSnapshot(void) +{ + if (g_epWorkAreaBrokenAtDetect.count == 0 || g_epWinSnapshot.count == 0) + { + EP_WorkAreaLog("restore skipped: no broken-at-detect (%d) or snapshot (%d)", + g_epWorkAreaBrokenAtDetect.count, g_epWinSnapshot.count); + g_epWinSnapshot.count = 0; + g_epWorkAreaBrokenAtDetect.count = 0; + return; + } + + // Freshness: the snapshot must predate the last display-ON, and the wake must be + // recent. Otherwise it is stale (the user likely rearranged windows since). + ULONGLONG now = GetTickCount64(); + if (!(g_epWinSnapshotTick < g_epLastDisplayOnTick) || + (now - g_epLastDisplayOnTick) > EP_WINRESTORE_WAKE_WINDOW) + { + EP_WorkAreaLog("restore skipped: stale snapshot (snapTick=%llu onTick=%llu now=%llu)", + g_epWinSnapshotTick, g_epLastDisplayOnTick, now); + g_epWinSnapshot.count = 0; + g_epWorkAreaBrokenAtDetect.count = 0; + return; + } + + int restored = 0; + for (int i = 0; i < g_epWinSnapshot.count; i++) + { + EP_WINRESTORE_ENTRY* e = &g_epWinSnapshot.items[i]; + HWND hWnd = e->hWnd; + + if (!IsWindow(hWnd)) { EP_WorkAreaLog(" restore skip %p: window gone", hWnd); continue; } + if (e->bZoomed || IsZoomed(hWnd)) { EP_WorkAreaLog(" restore skip %p: maximized", hWnd); continue; } + if (IsIconic(hWnd)) { EP_WorkAreaLog(" restore skip %p: minimized", hWnd); continue; } + if (!IsWindowVisible(hWnd)) { EP_WorkAreaLog(" restore skip %p: hidden", hWnd); continue; } + + EP_BROKEN_WORKAREA* brk = EP_FindBrokenForWindow(e); + if (!brk) continue; // window's monitor did not collapse -> nothing to undo (no log) + + RECT cur; + if (!GetWindowRect(hWnd, &cur)) { EP_WorkAreaLog(" restore skip %p: GetWindowRect failed", hWnd); continue; } + if (EqualRect(&cur, &e->rc)) { EP_WorkAreaLog(" restore skip %p: unchanged", hWnd); continue; } + + if (MonitorFromWindow(hWnd, MONITOR_DEFAULTTONULL) != e->hMonitor) + { + EP_WorkAreaLog(" restore skip %p: now on another monitor", hWnd); + continue; + } + + // Anti-conflict: only restore a window whose current rect lies fully inside the + // collapsed band (inflated by tolerance). A window the user moved elsewhere is + // left alone. + RECT band = brk->observedWork; + band.left -= EP_WINRESTORE_BAND_TOL; band.top -= EP_WINRESTORE_BAND_TOL; + band.right += EP_WINRESTORE_BAND_TOL; band.bottom += EP_WINRESTORE_BAND_TOL; + RECT rcTest; + if (!IntersectRect(&rcTest, &cur, &band) || !EqualRect(&rcTest, &cur)) + { + EP_WorkAreaLog(" restore skip %p: not inside collapsed band cur=(%ld,%ld,%ld,%ld) band=(%ld,%ld,%ld,%ld)", + hWnd, cur.left, cur.top, cur.right, cur.bottom, band.left, band.top, band.right, band.bottom); + continue; + } + + int w = e->rc.right - e->rc.left; + int h = e->rc.bottom - e->rc.top; + // Restore both position AND size (the reflow may have shrunk the window). + // SWP_ASYNCWINDOWPOS so a hung app cannot block the service thread. + BOOL ok = SetWindowPos(hWnd, NULL, e->rc.left, e->rc.top, w, h, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_ASYNCWINDOWPOS); + EP_WorkAreaLog(" restore %p to (%ld,%ld,%ld,%ld) result=%d", + hWnd, e->rc.left, e->rc.top, e->rc.right, e->rc.bottom, ok); + if (ok) restored++; + } + + EP_WorkAreaLog("restore pass done: %d restored of %d", restored, g_epWinSnapshot.count); + g_epWinSnapshot.count = 0; + g_epWorkAreaBrokenAtDetect.count = 0; +} + +// (Re)arm the debounce timer. Guards against re-arming while a repair cycle is +// already running (our own kick comes back as WM_DISPLAYCHANGE) and against a +// pathological fight with a third-party appbar right after a direct SPI repair. +static void EP_ArmWorkAreaWatchdog(HWND hWnd) +{ + if (!hWnd) return; + + if (g_epWorkAreaKickCount > 0) + { + EP_WorkAreaLog("arm ignored: repair cycle in progress (kickCount=%u)", g_epWorkAreaKickCount); + return; + } + + if (g_epLastDirectRepairTick) + { + ULONGLONG elapsed = GetTickCount64() - g_epLastDirectRepairTick; + if (elapsed < EP_WORKAREA_REPAIR_COOLDOWN) + { + EP_WorkAreaLog("arm ignored: cooldown (%llu ms since last direct repair)", elapsed); + return; + } + } + + EP_WorkAreaLog("arm: debounce %d ms", EP_WORKAREA_WATCHDOG_DEBOUNCE); + g_epWorkAreaKickCount = 0; + SetTimer(hWnd, EP_WORKAREA_WATCHDOG_TIMER_ID, EP_WORKAREA_WATCHDOG_DEBOUNCE, NULL); +} + #define EP_SERVICE_WINDOW_CLASS_NAME L"EP_Service_Window_" _T(EP_CLSID) LRESULT CALLBACK EP_Service_Window_WndProc( HWND hWnd, @@ -628,6 +1183,85 @@ LRESULT CALLBACK EP_Service_Window_WndProc( if (uMsg == WM_CREATE) { s_uTaskbarRestart = RegisterWindowMessageW(L"TaskbarCreated"); + // Register unconditionally; the setting is checked in the handler so it can be + // toggled live without re-registering. Hosted on the service window because it + // outlives taskbar recreation (a notify on the tray window would die with it). + g_epDisplayStateNotify = RegisterPowerSettingNotification(hWnd, &EP_GUID_CONSOLE_DISPLAY_STATE, DEVICE_NOTIFY_WINDOW_HANDLE); + EP_WorkAreaLog("WM_CREATE: RegisterPowerSettingNotification -> %p err=%lu bOldTaskbar=%lu bFix=%lu", + g_epDisplayStateNotify, g_epDisplayStateNotify ? 0UL : GetLastError(), + (unsigned long)bOldTaskbar, (unsigned long)bFixWorkAreaAfterDisplayReinit); + } + else if (uMsg == WM_DESTROY) + { + if (g_epDisplayStateNotify) + { + UnregisterPowerSettingNotification(g_epDisplayStateNotify); + g_epDisplayStateNotify = NULL; + } + } + else if (uMsg == WM_POWERBROADCAST) + { + DWORD dwDisplayState = 0xFFFFFFFF; + if (wParam == PBT_POWERSETTINGCHANGE && lParam) + { + POWERBROADCAST_SETTING* pbs = (POWERBROADCAST_SETTING*)lParam; + if (IsEqualGUID(&pbs->PowerSetting, &EP_GUID_CONSOLE_DISPLAY_STATE) && + pbs->DataLength >= sizeof(DWORD)) + { + dwDisplayState = pbs->Data[0]; + } + } + EP_WorkAreaLog("WM_POWERBROADCAST wParam=%llu displayState=%ld guard(bOldTaskbar=%lu bFix=%lu)", + (unsigned long long)wParam, (long)dwDisplayState, + (unsigned long)bOldTaskbar, (unsigned long)bFixWorkAreaAfterDisplayReinit); + if (bOldTaskbar && bFixWorkAreaAfterDisplayReinit) + { + if (wParam == PBT_APMRESUMEAUTOMATIC || wParam == PBT_APMRESUMESUSPEND) + { + g_epLastDisplayOnTick = GetTickCount64(); + EP_ArmWorkAreaWatchdog(hWnd); + } + else if (wParam == PBT_POWERSETTINGCHANGE && dwDisplayState == 1) // display ON + { + g_epLastDisplayOnTick = GetTickCount64(); + EP_ArmWorkAreaWatchdog(hWnd); + } + else if (wParam == PBT_POWERSETTINGCHANGE && dwDisplayState == 0) // display OFF (precedes wake collapse) + { + EP_SnapshotWindowLayout(); + } + else if (wParam == PBT_APMSUSPEND) // about to sleep (precedes resume collapse) + { + EP_SnapshotWindowLayout(); + } + } + return TRUE; + } + else if (uMsg == WM_DISPLAYCHANGE) + { + // Top-level window -> receives the display-change broadcast. Covers frequency/ + // resolution changes, monitor add/remove, and primary-monitor changes. + EP_WorkAreaLog("WM_DISPLAYCHANGE on service window guard(bOldTaskbar=%lu bFix=%lu)", + (unsigned long)bOldTaskbar, (unsigned long)bFixWorkAreaAfterDisplayReinit); + if (bOldTaskbar && bFixWorkAreaAfterDisplayReinit) + { + EP_ArmWorkAreaWatchdog(hWnd); + } + } + else if (uMsg == WM_SETTINGCHANGE) + { + // The most direct "someone changed the work area" signal (this is exactly what + // the user's external watcher listened for). A loop with our own SPI repair is + // broken by the sane-detect + the 15s repair cooldown. + if (wParam == SPI_SETWORKAREA) + { + EP_WorkAreaLog("WM_SETTINGCHANGE SPI_SETWORKAREA on service window guard(bOldTaskbar=%lu bFix=%lu)", + (unsigned long)bOldTaskbar, (unsigned long)bFixWorkAreaAfterDisplayReinit); + if (bOldTaskbar && bFixWorkAreaAfterDisplayReinit) + { + EP_ArmWorkAreaWatchdog(hWnd); + } + } } else if (uMsg == WM_HOTKEY && (wParam == 1 || wParam == 2)) { @@ -660,6 +1294,61 @@ LRESULT CALLBACK EP_Service_Window_WndProc( if (IsSpotlightEnabled()) SpotlightHelper(SPOP_CLICKMENU_NEXTPIC, hWnd, NULL, NULL); printf("Refreshed Spotlight\n"); } + else if (uMsg == WM_TIMER && wParam == EP_WORKAREA_WATCHDOG_TIMER_ID) + { + KillTimer(hWnd, EP_WORKAREA_WATCHDOG_TIMER_ID); + + EP_WORKAREA_BROKEN_LIST list; + int broken = EP_CollectBrokenWorkAreas(&list); + EP_WorkAreaLog("timer fired: broken=%d kickCount=%u bOldTaskbar=%lu bFix=%lu", + broken, g_epWorkAreaKickCount, (unsigned long)bOldTaskbar, (unsigned long)bFixWorkAreaAfterDisplayReinit); + + // On the FIRST detect of a cycle, capture the broken list before any repair + // overwrites observedWork - it records the transient collapsed bands that the + // window restore pass needs to recognize stranded windows. + if (broken > 0 && g_epWorkAreaKickCount == 0) + { + g_epWorkAreaBrokenAtDetect = list; + EP_WorkAreaLog("captured %d broken record(s) at detect for window restore", broken); + } + + if (broken == 0) + { + EP_WorkAreaLog("verdict: all work areas sane, cycle done"); + // Restore windows only if this cycle actually detected+repaired a collapse + // (kickCount>0). Covers both soft-kick and SPI heal: window damage happens + // at the collapse, not at the repair. + if (g_epWorkAreaKickCount > 0) + { + EP_RestoreWindowsFromSnapshot(); + } + g_epWorkAreaKickCount = 0; + return 0; + } + if (g_epWorkAreaKickCount >= EP_WORKAREA_WATCHDOG_MAX_KICKS) + { + EP_WorkAreaLog("giving up after %u attempts, %d monitor(s) still broken", g_epWorkAreaKickCount, broken); + // Work area not healed -> restoring windows is pointless (the shell will + // shove them back). Drop the captured broken list. + g_epWorkAreaBrokenAtDetect.count = 0; + g_epWorkAreaKickCount = 0; + return 0; + } + + g_epWorkAreaKickCount++; + if (g_epWorkAreaKickCount == 1) + { + EP_WorkAreaLog("attempt %u: soft kick", g_epWorkAreaKickCount); + EP_KickTaskbarWorkAreaRecompute(); // soft first try (classic TrayUI) + } + else + { + EP_WorkAreaLog("attempt %u: direct SPI_SETWORKAREA repair (%d monitor(s))", g_epWorkAreaKickCount, broken); + EP_RepairWorkAreasDirect(&list); // proven fallback (ep_taskbar, etc.) + } + SetTimer(hWnd, EP_WORKAREA_WATCHDOG_TIMER_ID, EP_WORKAREA_WATCHDOG_VERIFY, NULL); // re-verify + return 0; + } return DefWindowProcW(hWnd, uMsg, wParam, lParam); } DWORD EP_ServiceWindowThread(DWORD unused) @@ -2006,6 +2695,16 @@ INT64 Shell_TrayWndSubclassProc( { UpdateStartMenuPositioning(MAKELPARAM(TRUE, FALSE)); } + // The tray reliably receives WM_DISPLAYCHANGE even when the service window + // does not; arm the work-area watchdog from here too. EP_ArmWorkAreaWatchdog + // guards against re-arming from our own kick (kickCount>0) and the repair + // cooldown, so this cannot loop. SetTimer on the service window from the + // taskbar thread is legal (WM_TIMER is delivered to the service thread). + if (bOldTaskbar && bFixWorkAreaAfterDisplayReinit && hWndServiceWindow) + { + EP_WorkAreaLog("tray WM_DISPLAYCHANGE (isPrimary=%d) -> arm", (int)bIsPrimaryTaskbar); + EP_ArmWorkAreaWatchdog(hWndServiceWindow); + } break; } /*case WM_PARENTNOTIFY: @@ -2070,6 +2769,12 @@ INT64 Shell_TrayWndSubclassProc( { UpdateStartMenuPositioning(MAKELPARAM(TRUE, FALSE)); } + // Monitor/stuck-place change - another reliable arm source from the tray. + if (bOldTaskbar && bFixWorkAreaAfterDisplayReinit && hWndServiceWindow) + { + EP_WorkAreaLog("tray 0x5C3 stuck-place change (isPrimary=%d) -> arm", (int)bIsPrimaryTaskbar); + EP_ArmWorkAreaWatchdog(hWndServiceWindow); + } break; } } @@ -6159,6 +6864,15 @@ void WINAPI LoadSettings(LPARAM lParam) &bTaskbarAutohideOnDoubleClick, &dwSize ); + dwSize = sizeof(DWORD); + RegQueryValueExW( + hKey, + TEXT("FixWorkAreaAfterDisplayReinit"), + 0, + NULL, + &bFixWorkAreaAfterDisplayReinit, + &dwSize + ); dwTemp = ORB_STYLE_WINDOWS10; dwSize = sizeof(DWORD); RegQueryValueExW( diff --git a/ep_gui/resources/EPSettingsResources.h b/ep_gui/resources/EPSettingsResources.h index 8ae7cbee5..d95b81f7f 100644 --- a/ep_gui/resources/EPSettingsResources.h +++ b/ep_gui/resources/EPSettingsResources.h @@ -49,6 +49,7 @@ #define IDS_TB_ICONSIZE_0 1046 #define IDS_TB_STYLE_2 1047 #define IDS_TB_ALTIMPL_NOTICE 1048 +#define IDS_TB_FIXWORKAREA 1049 #define IDS_TRAY 1101 #define IDS_TRAY_SKINMENUS 1102 diff --git a/ep_gui/resources/lang/ep_gui.en-US.rc b/ep_gui/resources/lang/ep_gui.en-US.rc index ae8fe290b..be1cc4df5 100644 --- a/ep_gui/resources/lang/ep_gui.en-US.rc +++ b/ep_gui/resources/lang/ep_gui.en-US.rc @@ -85,6 +85,7 @@ BEGIN IDS_TB_ICONSIZE_1 "Small" IDS_TB_ICONSIZE_0 "Large (default)" IDS_TB_ALTIMPL_NOTICE "Important notice regarding ExplorerPatcher's taskbar implementation (online)" + IDS_TB_FIXWORKAREA "Automatically repair the desktop work area after the display wakes or changes mode" IDS_TRAY "System tray" diff --git a/ep_gui/resources/settings.reg b/ep_gui/resources/settings.reg index 66a1ba3b2..7d64561af 100644 --- a/ep_gui/resources/settings.reg +++ b/ep_gui/resources/settings.reg @@ -79,6 +79,8 @@ ;x 3 %R:1034% ;x 7 %R:1035% "MMOldTaskbarAl"=dword:00000000 +;b %R:1049% +"FixWorkAreaAfterDisplayReinit"=dword:00000001 ;g Taskbar_Windows10Section [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced] ;c 3 %R:1037% diff --git a/ep_gui/resources/settings10.reg b/ep_gui/resources/settings10.reg index e479e767d..753cb4094 100644 --- a/ep_gui/resources/settings10.reg +++ b/ep_gui/resources/settings10.reg @@ -63,6 +63,8 @@ ;x 3 %R:1034% ;x 7 %R:1035% "MMOldTaskbarAl"=dword:00000000 +;b %R:1049% +"FixWorkAreaAfterDisplayReinit"=dword:00000001 [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced] ;c 3 %R:1037% ;x 0 %R:1038% From a8ac135428077eb720df74a4a23e5bde3cabb355 Mon Sep 17 00:00:00 2001 From: Stanislav Popov Date: Sun, 12 Jul 2026 23:53:19 +0500 Subject: [PATCH 2/5] perf(taskbar): cut work-area restore latency (~9s -> ~6s) Two avoidable delays in the watchdog repair chain: - The soft WM_DISPLAYCHANGE kick never heals the prebuilt ep_taskbar (OldTaskbar=2), yet attempt 1 always spent it before escalating - a wasted ~1.5s verify round-trip. Skip straight to the SPI repair for ep_taskbar; classic TrayUI still gets the soft kick first. - SPI_SETWORKAREA with SPIF_SENDCHANGE is synchronous, so the work area is already committed when it returns. Re-check sanity inline and restore the displaced windows right away instead of arming another ~1.5s verify timer. Measured on the test machine: window shift->restore dropped from ~7s to ~4.4s. The remaining latency is inherent (2s settle debounce + ~3.5s blocking SENDCHANGE broadcast). Co-Authored-By: Claude Opus 4.8 --- ExplorerPatcher/dllmain.c | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/ExplorerPatcher/dllmain.c b/ExplorerPatcher/dllmain.c index e760b6d3d..3b8f09eb0 100644 --- a/ExplorerPatcher/dllmain.c +++ b/ExplorerPatcher/dllmain.c @@ -1336,16 +1336,37 @@ LRESULT CALLBACK EP_Service_Window_WndProc( } g_epWorkAreaKickCount++; - if (g_epWorkAreaKickCount == 1) + + // The soft WM_DISPLAYCHANGE kick only heals classic TrayUI; the prebuilt + // ep_taskbar (OldTaskbar=2) always ignores it. Skip the wasted soft-kick + // attempt there and go straight to the SPI repair - saves one ~1.5s verify + // round-trip on the common path. Classic TrayUI still gets the soft kick first. + BOOL bSoftKick = (g_epWorkAreaKickCount == 1) && (bOldTaskbar != 2); + if (bSoftKick) { EP_WorkAreaLog("attempt %u: soft kick", g_epWorkAreaKickCount); - EP_KickTaskbarWorkAreaRecompute(); // soft first try (classic TrayUI) + EP_KickTaskbarWorkAreaRecompute(); + SetTimer(hWnd, EP_WORKAREA_WATCHDOG_TIMER_ID, EP_WORKAREA_WATCHDOG_VERIFY, NULL); // re-verify + return 0; } - else + + EP_WorkAreaLog("attempt %u: direct SPI_SETWORKAREA repair (%d monitor(s))", g_epWorkAreaKickCount, broken); + EP_RepairWorkAreasDirect(&list); // proven fallback (ep_taskbar, etc.) + + // SPI_SETWORKAREA with SPIF_SENDCHANGE is synchronous - by the time it returns + // the work area is committed, so re-check inline instead of burning another + // ~1.5s verify timer. If sane, restore the displaced windows right away. + EP_WORKAREA_BROKEN_LIST after; + int stillBroken = EP_CollectBrokenWorkAreas(&after); + if (stillBroken == 0) { - EP_WorkAreaLog("attempt %u: direct SPI_SETWORKAREA repair (%d monitor(s))", g_epWorkAreaKickCount, broken); - EP_RepairWorkAreasDirect(&list); // proven fallback (ep_taskbar, etc.) + EP_WorkAreaLog("verdict: all work areas sane after SPI repair, cycle done"); + EP_RestoreWindowsFromSnapshot(); + g_epWorkAreaKickCount = 0; + return 0; } + + EP_WorkAreaLog("still %d broken after SPI repair, re-verify", stillBroken); SetTimer(hWnd, EP_WORKAREA_WATCHDOG_TIMER_ID, EP_WORKAREA_WATCHDOG_VERIFY, NULL); // re-verify return 0; } From 13ba9d21d15bdab2423a548626864b7e980f4759 Mon Sep 17 00:00:00 2001 From: Stanislav Popov Date: Mon, 13 Jul 2026 00:13:55 +0500 Subject: [PATCH 3/5] perf(taskbar): drop SPIF_SENDCHANGE, reflow maximized windows ourselves The SPI_SETWORKAREA repair used SPIF_SENDCHANGE, whose synchronous WM_SETTINGCHANGE broadcast to every top-level window blocked the service thread ~3.5s - the bulk of the remaining restore latency. Drop the flag: without it the work-area metric is still set synchronously (GetMonitorInfo reads the new value immediately), so we only lose the broadcast that reflows maximized windows. Handle that ourselves instead: - Snapped/displaced windows: restored from the snapshot as before. - Maximized windows on a repaired monitor: reflow with a targeted SetWindowPos to the repaired work area (inflated by the maximize frame overhang), instead of skipping them and relying on the broadcast. - A non-blocking PostMessage(HWND_BROADCAST, WM_SETTINGCHANGE) is posted as a courtesy so other work-area consumers refresh without stalling us; our own re-arm is suppressed by the existing repair cooldown. Measured on the test machine: SPI call ~3.5s -> ~3ms; snapped window shift->restore ~4.4s -> ~0.8s. Maximized reflow lands pixel-identical to the old SENDCHANGE result; window z-order preserved (SWP_NOZORDER). Co-Authored-By: Claude Opus 4.8 --- ExplorerPatcher/dllmain.c | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/ExplorerPatcher/dllmain.c b/ExplorerPatcher/dllmain.c index 3b8f09eb0..b9c00afaf 100644 --- a/ExplorerPatcher/dllmain.c +++ b/ExplorerPatcher/dllmain.c @@ -925,12 +925,23 @@ static void EP_RepairWorkAreasDirect(EP_WORKAREA_BROKEN_LIST* list) for (int i = 0; i < list->count; i++) { RECT rc = list->items[i].expectedWork; - BOOL ok = SystemParametersInfoW(SPI_SETWORKAREA, 0, &rc, SPIF_SENDCHANGE); + // No SPIF_SENDCHANGE: the synchronous broadcast blocks ~3s waiting on every + // top-level window. Without the flag the work-area metric is still set + // synchronously (GetMonitorInfo reads the new value at once) - we just skip the + // stall. Displaced windows are reflowed ourselves in the restore pass (snapped + // from the snapshot, maximized via a targeted WM_SETTINGCHANGE), and a + // non-blocking courtesy broadcast is posted below so other apps refresh their + // cached work area without stalling this thread. + BOOL ok = SystemParametersInfoW(SPI_SETWORKAREA, 0, &rc, 0); EP_WorkAreaLog(" SPI_SETWORKAREA mon=%p rect=(%ld,%ld,%ld,%ld) result=%d err=%lu", list->items[i].hMonitor, rc.left, rc.top, rc.right, rc.bottom, ok, ok ? 0UL : GetLastError()); } g_epLastDirectRepairTick = GetTickCount64(); + // Courtesy async notification for other work-area consumers. Our own service window + // handles this too, but the 15s repair cooldown (stamped above) suppresses re-arm. + if (list->count > 0) + PostMessageW(HWND_BROADCAST, WM_SETTINGCHANGE, SPI_SETWORKAREA, 0); } // --- Window layout snapshot/restore implementation ------------------------------ @@ -1097,13 +1108,32 @@ static void EP_RestoreWindowsFromSnapshot(void) HWND hWnd = e->hWnd; if (!IsWindow(hWnd)) { EP_WorkAreaLog(" restore skip %p: window gone", hWnd); continue; } - if (e->bZoomed || IsZoomed(hWnd)) { EP_WorkAreaLog(" restore skip %p: maximized", hWnd); continue; } if (IsIconic(hWnd)) { EP_WorkAreaLog(" restore skip %p: minimized", hWnd); continue; } if (!IsWindowVisible(hWnd)) { EP_WorkAreaLog(" restore skip %p: hidden", hWnd); continue; } EP_BROKEN_WORKAREA* brk = EP_FindBrokenForWindow(e); if (!brk) continue; // window's monitor did not collapse -> nothing to undo (no log) + // Maximized window: with SPIF_SENDCHANGE gone, reflow it to the repaired work + // area ourselves (targeted, no global broadcast). Inflate by the maximize frame + // overhang so the visible edge meets the work area exactly like a real maximize + // (matches what SENDCHANGE produced in experiment (a)). If it is already correct + // this SetWindowPos is a no-op. + if (e->bZoomed || IsZoomed(hWnd)) + { + int fx = GetSystemMetrics(SM_CXSIZEFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); + int fy = GetSystemMetrics(SM_CYSIZEFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); + RECT wa = brk->expectedWork; + int mw = (wa.right - wa.left) + 2 * fx; + int mh = (wa.bottom - wa.top) + 2 * fy; + BOOL okz = SetWindowPos(hWnd, NULL, wa.left - fx, wa.top - fy, mw, mh, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_ASYNCWINDOWPOS); + EP_WorkAreaLog(" restore reflow %p: maximized -> (%ld,%ld,%ld,%ld) result=%d", + hWnd, wa.left - fx, wa.top - fy, wa.left - fx + mw, wa.top - fy + mh, okz); + if (okz) restored++; + continue; + } + RECT cur; if (!GetWindowRect(hWnd, &cur)) { EP_WorkAreaLog(" restore skip %p: GetWindowRect failed", hWnd); continue; } if (EqualRect(&cur, &e->rc)) { EP_WorkAreaLog(" restore skip %p: unchanged", hWnd); continue; } From c946c92ea97e431cb2d5c3dec473f86f00148a9f Mon Sep 17 00:00:00 2001 From: Stanislav Popov Date: Mon, 13 Jul 2026 00:33:02 +0500 Subject: [PATCH 4/5] perf(taskbar): cap the watchdog debounce so a wake event storm can't stretch it Each display event (WM_DISPLAYCHANGE / WM_SETTINGCHANGE / tray stuck-place change) re-armed the 2000ms debounce from scratch. On a noisy wake the storm kept resetting it, so detection - and thus the window restore - fired only ~2000ms after the LAST event, stretching the displaced-window duration to ~3.3s in one measured cycle. Track the first arm of a cycle (g_epFirstArmTick) and clamp each re-arm's interval so the timer never fires later than MAX_DEBOUNCE (2500ms) after that first arm; once the cap is reached, stop extending and let the pending timer fire. Reset the tick at every cycle-completion point. Early re-arms still get the full 2000ms debounce (topology settling); only a prolonged storm is bounded. Measured: displaced-window duration went from a 0.76-3.35s spread down to a tight ~0.95-1.3s. Collapse still detected and repaired every cycle; no empty cycles. (Residual: WM_TIMER is low-priority and can still be briefly starved while the wake queue drains - the cap bounds timer expiry, not queue drain.) Co-Authored-By: Claude Opus 4.8 --- ExplorerPatcher/dllmain.c | 40 ++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/ExplorerPatcher/dllmain.c b/ExplorerPatcher/dllmain.c index b9c00afaf..17c14ada5 100644 --- a/ExplorerPatcher/dllmain.c +++ b/ExplorerPatcher/dllmain.c @@ -633,12 +633,14 @@ void FixUpCenteredTaskbar() // arm -> detect -> repair chain is observable in the field. #define EP_WORKAREA_WATCHDOG_TIMER_ID 200 #define EP_WORKAREA_WATCHDOG_DEBOUNCE 2000 // ms - wait for topology to stabilize +#define EP_WORKAREA_WATCHDOG_MAX_DEBOUNCE 2500 // ms - cap: the detect timer fires at most this long after the FIRST arm of a cycle, so a wake event storm (many DISPLAYCHANGE/SETTINGCHANGE) cannot keep resetting the debounce and stretch the restore latency #define EP_WORKAREA_WATCHDOG_VERIFY 1500 // ms - recheck after a kick/repair #define EP_WORKAREA_WATCHDOG_MAX_KICKS 3 #define EP_WORKAREA_REPAIR_COOLDOWN 15000 // ms - ignore new arms this long after a direct SPI repair (anti-loop) #define EP_WORKAREA_MAX_BROKEN 8 static UINT g_epWorkAreaKickCount = 0; static ULONGLONG g_epLastDirectRepairTick = 0; +static ULONGLONG g_epFirstArmTick = 0; // tick of the first arm of the current debounce cycle (0 = idle); bounds the debounce so a wake event storm cannot extend it past MAX_DEBOUNCE static HPOWERNOTIFY g_epDisplayStateNotify = NULL; static const GUID EP_GUID_CONSOLE_DISPLAY_STATE = // 6fe69556-704a-47a0-8f24-c28d936fda47 { 0x6fe69556, 0x704a, 0x47a0, { 0x8f, 0x24, 0xc2, 0x8d, 0x93, 0x6f, 0xda, 0x47 } }; @@ -1197,9 +1199,33 @@ static void EP_ArmWorkAreaWatchdog(HWND hWnd) } } - EP_WorkAreaLog("arm: debounce %d ms", EP_WORKAREA_WATCHDOG_DEBOUNCE); + ULONGLONG now = GetTickCount64(); + UINT interval; + if (g_epFirstArmTick == 0) + { + // First arm of a new cycle: full debounce. + g_epFirstArmTick = now; + interval = EP_WORKAREA_WATCHDOG_DEBOUNCE; + EP_WorkAreaLog("arm: debounce %u ms (cycle start)", interval); + } + else + { + // Re-arm during the wake event storm: keep coalescing, but clamp the interval so + // the timer never fires later than MAX_DEBOUNCE after the first arm. Without this + // a burst of DISPLAYCHANGE/SETTINGCHANGE events resets the 2000ms debounce over + // and over and stretches the restore latency (observed up to ~3.3s). + ULONGLONG sinceFirst = now - g_epFirstArmTick; + if (sinceFirst >= EP_WORKAREA_WATCHDOG_MAX_DEBOUNCE) + { + EP_WorkAreaLog("arm: cap reached (%llu ms since first arm), not extending", sinceFirst); + return; // let the pending timer fire + } + ULONGLONG headroom = EP_WORKAREA_WATCHDOG_MAX_DEBOUNCE - sinceFirst; + interval = (headroom < EP_WORKAREA_WATCHDOG_DEBOUNCE) ? (UINT)headroom : EP_WORKAREA_WATCHDOG_DEBOUNCE; + EP_WorkAreaLog("arm: debounce %u ms (%llu ms since first arm)", interval, sinceFirst); + } g_epWorkAreaKickCount = 0; - SetTimer(hWnd, EP_WORKAREA_WATCHDOG_TIMER_ID, EP_WORKAREA_WATCHDOG_DEBOUNCE, NULL); + SetTimer(hWnd, EP_WORKAREA_WATCHDOG_TIMER_ID, interval, NULL); } #define EP_SERVICE_WINDOW_CLASS_NAME L"EP_Service_Window_" _T(EP_CLSID) @@ -1353,6 +1379,7 @@ LRESULT CALLBACK EP_Service_Window_WndProc( EP_RestoreWindowsFromSnapshot(); } g_epWorkAreaKickCount = 0; + g_epFirstArmTick = 0; return 0; } if (g_epWorkAreaKickCount >= EP_WORKAREA_WATCHDOG_MAX_KICKS) @@ -1362,6 +1389,7 @@ LRESULT CALLBACK EP_Service_Window_WndProc( // shove them back). Drop the captured broken list. g_epWorkAreaBrokenAtDetect.count = 0; g_epWorkAreaKickCount = 0; + g_epFirstArmTick = 0; return 0; } @@ -1383,9 +1411,10 @@ LRESULT CALLBACK EP_Service_Window_WndProc( EP_WorkAreaLog("attempt %u: direct SPI_SETWORKAREA repair (%d monitor(s))", g_epWorkAreaKickCount, broken); EP_RepairWorkAreasDirect(&list); // proven fallback (ep_taskbar, etc.) - // SPI_SETWORKAREA with SPIF_SENDCHANGE is synchronous - by the time it returns - // the work area is committed, so re-check inline instead of burning another - // ~1.5s verify timer. If sane, restore the displaced windows right away. + // SPI_SETWORKAREA sets the work-area metric synchronously (even without + // SPIF_SENDCHANGE), so by the time it returns GetMonitorInfo reads the new value + // - re-check inline instead of burning another ~1.5s verify timer. If sane, + // restore the displaced windows right away. EP_WORKAREA_BROKEN_LIST after; int stillBroken = EP_CollectBrokenWorkAreas(&after); if (stillBroken == 0) @@ -1393,6 +1422,7 @@ LRESULT CALLBACK EP_Service_Window_WndProc( EP_WorkAreaLog("verdict: all work areas sane after SPI repair, cycle done"); EP_RestoreWindowsFromSnapshot(); g_epWorkAreaKickCount = 0; + g_epFirstArmTick = 0; return 0; } From eb8b2d25114cb874705d70a379af690a3bd1d6ce Mon Sep 17 00:00:00 2001 From: Stanislav Popov Date: Mon, 13 Jul 2026 01:03:21 +0500 Subject: [PATCH 5/5] refactor(taskbar): single-thread watchdog state, DPI-aware reflow, drop leftovers Review-pass fixes and simplification, no functional change to the happy path: - tray thread now posts EP_WM_ARM_WORKAREA_WATCHDOG instead of calling EP_ArmWorkAreaWatchdog directly, so all watchdog/debounce/snapshot state is genuinely single-threaded (the old direct call mutated shared state unlocked) - restore pass: monitor identity check falls back to the monitor rect when the HMONITOR handle changed across the re-init (previously every window would be silently skipped), and runs before the maximized branch too - maximized reflow decides on current IsZoomed only (a window the user un-maximized since the snapshot is no longer blown up to a maximized rect) and computes the frame overhang with GetSystemMetricsForDpi for the window's own DPI instead of the system DPI - watchdog log is gated on the fix being active (no more perpetual log for Win11-taskbar users); empty log now means "fix disabled" - removed dead EP_AreWorkAreasSane, the unreachable snapshot overflow flag, a stale comment, duplicated WM_POWERBROADCAST branches, triple snapshot cleanup Co-Authored-By: Claude Fable 5 --- ExplorerPatcher/dllmain.c | 128 ++++++++++++++++++++------------------ 1 file changed, 66 insertions(+), 62 deletions(-) diff --git a/ExplorerPatcher/dllmain.c b/ExplorerPatcher/dllmain.c index 17c14ada5..a0ea54462 100644 --- a/ExplorerPatcher/dllmain.c +++ b/ExplorerPatcher/dllmain.c @@ -638,6 +638,7 @@ void FixUpCenteredTaskbar() #define EP_WORKAREA_WATCHDOG_MAX_KICKS 3 #define EP_WORKAREA_REPAIR_COOLDOWN 15000 // ms - ignore new arms this long after a direct SPI repair (anti-loop) #define EP_WORKAREA_MAX_BROKEN 8 +#define EP_WM_ARM_WORKAREA_WATCHDOG (WM_APP + 200) // posted by the tray thread; keeps all watchdog state single-threaded (service-window thread) static UINT g_epWorkAreaKickCount = 0; static ULONGLONG g_epLastDirectRepairTick = 0; static ULONGLONG g_epFirstArmTick = 0; // tick of the first arm of the current debounce cycle (0 = idle); bounds the debounce so a wake event storm cannot extend it past MAX_DEBOUNCE @@ -665,14 +666,14 @@ typedef struct // left stranded. We snapshot top-level window rects on EP-taskbar monitors on // display-OFF/suspend (which precedes the wake-time collapse), and after a verified // repair we move any window that landed inside the former collapsed band back to its -// snapshot rect. All snapshot/restore state is touched only on the service-window -// thread (WM_POWERBROADCAST + WM_TIMER); the tray thread only calls -// EP_ArmWorkAreaWatchdog (SetTimer), so no locking is needed. +// snapshot rect. All watchdog/snapshot/restore state is touched only on the +// service-window thread (WM_POWERBROADCAST + WM_TIMER + EP_WM_ARM_WORKAREA_WATCHDOG); +// the tray thread only posts EP_WM_ARM_WORKAREA_WATCHDOG, so no locking is needed. #define EP_WINRESTORE_MAX_WINDOWS 64 // snapshot capacity #define EP_WINRESTORE_WAKE_WINDOW 90000 // ms after display-ON within which a snapshot may be applied #define EP_WINRESTORE_BAND_TOL 32 // px tolerance for "window sits inside the collapsed band" -typedef struct { HWND hWnd; RECT rc; HMONITOR hMonitor; RECT rcMonitor; BOOL bZoomed; } EP_WINRESTORE_ENTRY; +typedef struct { HWND hWnd; RECT rc; HMONITOR hMonitor; RECT rcMonitor; } EP_WINRESTORE_ENTRY; typedef struct { EP_WINRESTORE_ENTRY items[EP_WINRESTORE_MAX_WINDOWS]; int count; } EP_WINRESTORE_SNAPSHOT; static EP_WINRESTORE_SNAPSHOT g_epWinSnapshot; @@ -682,10 +683,13 @@ static EP_WORKAREA_BROKEN_LIST g_epWorkAreaBrokenAtDetect; // broken list captur // Append one timestamped line to %TEMP%\ep_workarea_watchdog.log. Events are rare // (display re-inits), so open/write/close per call is fine and keeps the log intact -// across crashes. Enabled unconditionally - it is also the field diagnostic tool for -// issue reporters. +// across crashes. Always on while the fix is active - it is also the field diagnostic +// tool for issue reporters; an empty log means the fix is disabled (Win11 taskbar or +// setting off). static void EP_WorkAreaLog(const char* fmt, ...) { + if (!bOldTaskbar || !bFixWorkAreaAfterDisplayReinit) return; + WCHAR wszDir[MAX_PATH]; DWORD n = GetTempPathW(MAX_PATH, wszDir); if (n == 0 || n >= MAX_PATH) return; @@ -884,13 +888,6 @@ static int EP_CollectBrokenWorkAreas(EP_WORKAREA_BROKEN_LIST* list) return list->count; } -// Wrapper kept for symmetry: TRUE when no monitor matches a bug signature. -static BOOL EP_AreWorkAreasSane(void) -{ - EP_WORKAREA_BROKEN_LIST list; - return EP_CollectBrokenWorkAreas(&list) == 0; -} - // Post WM_DISPLAYCHANGE to the tray windows to make the taskbar run its full // stuck-rect + work-area recompute path. Posted (not sent) and addressed directly to // the tray windows. This is the soft first attempt (works for classic TrayUI). @@ -931,9 +928,9 @@ static void EP_RepairWorkAreasDirect(EP_WORKAREA_BROKEN_LIST* list) // top-level window. Without the flag the work-area metric is still set // synchronously (GetMonitorInfo reads the new value at once) - we just skip the // stall. Displaced windows are reflowed ourselves in the restore pass (snapped - // from the snapshot, maximized via a targeted WM_SETTINGCHANGE), and a - // non-blocking courtesy broadcast is posted below so other apps refresh their - // cached work area without stalling this thread. + // ones back to their snapshot rect, maximized ones via a targeted SetWindowPos + // to the repaired work area), and a non-blocking courtesy broadcast is posted + // below so other apps refresh their cached work area without stalling this thread. BOOL ok = SystemParametersInfoW(SPI_SETWORKAREA, 0, &rc, 0); EP_WorkAreaLog(" SPI_SETWORKAREA mon=%p rect=(%ld,%ld,%ld,%ld) result=%d err=%lu", list->items[i].hMonitor, rc.left, rc.top, rc.right, rc.bottom, @@ -974,7 +971,7 @@ static BOOL EP_IsTrayMonitor(EP_TRAY_MONITOR_LIST* list, HMONITOR hMonitor) return FALSE; } -typedef struct { EP_TRAY_MONITOR_LIST* trays; EP_WINRESTORE_SNAPSHOT* snap; BOOL overflow; } EP_WINSNAP_CTX; +typedef struct { EP_TRAY_MONITOR_LIST* trays; EP_WINRESTORE_SNAPSHOT* snap; } EP_WINSNAP_CTX; // Conservative top-level-window filter: a window is snapshotted only if it is visible, // non-minimized, unowned, not a tool window, not cloaked, not a shell/desktop window, @@ -984,11 +981,7 @@ static BOOL CALLBACK EP_WinSnapshotEnumProc(HWND hWnd, LPARAM lParam) EP_WINSNAP_CTX* ctx = (EP_WINSNAP_CTX*)lParam; if (ctx->snap->count >= EP_WINRESTORE_MAX_WINDOWS) { - if (!ctx->overflow) - { - EP_WorkAreaLog(" snapshot: capacity %d reached, stopping enumeration", EP_WINRESTORE_MAX_WINDOWS); - ctx->overflow = TRUE; - } + EP_WorkAreaLog(" snapshot: capacity %d reached, stopping enumeration", EP_WINRESTORE_MAX_WINDOWS); return FALSE; // stop enumerating } @@ -1027,10 +1020,9 @@ static BOOL CALLBACK EP_WinSnapshotEnumProc(HWND hWnd, LPARAM lParam) e->rc = rc; e->hMonitor = hMon; e->rcMonitor = rcMon; - e->bZoomed = IsZoomed(hWnd); EP_WorkAreaLog(" snapshot win %p cls=%ls rc=(%ld,%ld,%ld,%ld) mon=%p zoomed=%d", - hWnd, cls, rc.left, rc.top, rc.right, rc.bottom, hMon, e->bZoomed); + hWnd, cls, rc.left, rc.top, rc.right, rc.bottom, hMon, IsZoomed(hWnd)); return TRUE; } @@ -1056,7 +1048,7 @@ static void EP_SnapshotWindowLayout(void) } g_epWinSnapshot.count = 0; - EP_WINSNAP_CTX ctx = { &trays, &g_epWinSnapshot, FALSE }; + EP_WINSNAP_CTX ctx = { &trays, &g_epWinSnapshot }; EnumWindows(EP_WinSnapshotEnumProc, (LPARAM)&ctx); g_epWinSnapshotTick = GetTickCount64(); @@ -1081,13 +1073,13 @@ static EP_BROKEN_WORKAREA* EP_FindBrokenForWindow(EP_WINRESTORE_ENTRY* e) // the snapshot and the broken-at-detect list. static void EP_RestoreWindowsFromSnapshot(void) { + int restored = 0; + if (g_epWorkAreaBrokenAtDetect.count == 0 || g_epWinSnapshot.count == 0) { EP_WorkAreaLog("restore skipped: no broken-at-detect (%d) or snapshot (%d)", g_epWorkAreaBrokenAtDetect.count, g_epWinSnapshot.count); - g_epWinSnapshot.count = 0; - g_epWorkAreaBrokenAtDetect.count = 0; - return; + goto done; } // Freshness: the snapshot must predate the last display-ON, and the wake must be @@ -1098,12 +1090,9 @@ static void EP_RestoreWindowsFromSnapshot(void) { EP_WorkAreaLog("restore skipped: stale snapshot (snapTick=%llu onTick=%llu now=%llu)", g_epWinSnapshotTick, g_epLastDisplayOnTick, now); - g_epWinSnapshot.count = 0; - g_epWorkAreaBrokenAtDetect.count = 0; - return; + goto done; } - int restored = 0; for (int i = 0; i < g_epWinSnapshot.count; i++) { EP_WINRESTORE_ENTRY* e = &g_epWinSnapshot.items[i]; @@ -1116,15 +1105,33 @@ static void EP_RestoreWindowsFromSnapshot(void) EP_BROKEN_WORKAREA* brk = EP_FindBrokenForWindow(e); if (!brk) continue; // window's monitor did not collapse -> nothing to undo (no log) - // Maximized window: with SPIF_SENDCHANGE gone, reflow it to the repaired work - // area ourselves (targeted, no global broadcast). Inflate by the maximize frame - // overhang so the visible edge meets the work area exactly like a real maximize - // (matches what SENDCHANGE produced in experiment (a)). If it is already correct - // this SetWindowPos is a no-op. - if (e->bZoomed || IsZoomed(hWnd)) + // Still on its snapshot monitor? Compare handles first, then monitor rects (the + // HMONITOR handle can change across a display re-init). A window the user moved + // to another monitor is left alone. + HMONITOR hMonNow = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONULL); + if (hMonNow != e->hMonitor) + { + MONITORINFO miNow; + miNow.cbSize = sizeof(miNow); + if (!hMonNow || !GetMonitorInfoW(hMonNow, &miNow) || !EqualRect(&miNow.rcMonitor, &e->rcMonitor)) + { + EP_WorkAreaLog(" restore skip %p: now on another monitor", hWnd); + continue; + } + } + + // Currently maximized window: with SPIF_SENDCHANGE gone, reflow it to the + // repaired work area ourselves (targeted, no global broadcast). Inflate by the + // maximize frame overhang so the visible edge meets the work area exactly like a + // real maximize (matches what SENDCHANGE produced). If it is already correct + // this SetWindowPos is a no-op. Current state decides on purpose: a window the + // user un-maximized since the snapshot must not be blown up to a maximized rect. + if (IsZoomed(hWnd)) { - int fx = GetSystemMetrics(SM_CXSIZEFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); - int fy = GetSystemMetrics(SM_CYSIZEFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); + UINT dpi = GetDpiForWindow(hWnd); + if (!dpi) dpi = 96; + int fx = GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) + GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi); + int fy = GetSystemMetricsForDpi(SM_CYSIZEFRAME, dpi) + GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi); RECT wa = brk->expectedWork; int mw = (wa.right - wa.left) + 2 * fx; int mh = (wa.bottom - wa.top) + 2 * fy; @@ -1140,12 +1147,6 @@ static void EP_RestoreWindowsFromSnapshot(void) if (!GetWindowRect(hWnd, &cur)) { EP_WorkAreaLog(" restore skip %p: GetWindowRect failed", hWnd); continue; } if (EqualRect(&cur, &e->rc)) { EP_WorkAreaLog(" restore skip %p: unchanged", hWnd); continue; } - if (MonitorFromWindow(hWnd, MONITOR_DEFAULTTONULL) != e->hMonitor) - { - EP_WorkAreaLog(" restore skip %p: now on another monitor", hWnd); - continue; - } - // Anti-conflict: only restore a window whose current rect lies fully inside the // collapsed band (inflated by tolerance). A window the user moved elsewhere is // left alone. @@ -1172,6 +1173,7 @@ static void EP_RestoreWindowsFromSnapshot(void) } EP_WorkAreaLog("restore pass done: %d restored of %d", restored, g_epWinSnapshot.count); +done: // one-shot: always consume both the snapshot and the broken-at-detect list g_epWinSnapshot.count = 0; g_epWorkAreaBrokenAtDetect.count = 0; } @@ -1272,21 +1274,14 @@ LRESULT CALLBACK EP_Service_Window_WndProc( (unsigned long)bOldTaskbar, (unsigned long)bFixWorkAreaAfterDisplayReinit); if (bOldTaskbar && bFixWorkAreaAfterDisplayReinit) { - if (wParam == PBT_APMRESUMEAUTOMATIC || wParam == PBT_APMRESUMESUSPEND) - { - g_epLastDisplayOnTick = GetTickCount64(); - EP_ArmWorkAreaWatchdog(hWnd); - } - else if (wParam == PBT_POWERSETTINGCHANGE && dwDisplayState == 1) // display ON + if (wParam == PBT_APMRESUMEAUTOMATIC || wParam == PBT_APMRESUMESUSPEND || + (wParam == PBT_POWERSETTINGCHANGE && dwDisplayState == 1)) // display ON / resume { g_epLastDisplayOnTick = GetTickCount64(); EP_ArmWorkAreaWatchdog(hWnd); } - else if (wParam == PBT_POWERSETTINGCHANGE && dwDisplayState == 0) // display OFF (precedes wake collapse) - { - EP_SnapshotWindowLayout(); - } - else if (wParam == PBT_APMSUSPEND) // about to sleep (precedes resume collapse) + else if (wParam == PBT_APMSUSPEND || + (wParam == PBT_POWERSETTINGCHANGE && dwDisplayState == 0)) // display OFF / about to sleep (precedes the wake collapse) { EP_SnapshotWindowLayout(); } @@ -1319,6 +1314,15 @@ LRESULT CALLBACK EP_Service_Window_WndProc( } } } + else if (uMsg == EP_WM_ARM_WORKAREA_WATCHDOG) + { + // Posted by the tray thread (its WM_DISPLAYCHANGE / stuck-place handlers) so the + // arm - and all the debounce state it touches - runs on this thread only. + if (bOldTaskbar && bFixWorkAreaAfterDisplayReinit) + { + EP_ArmWorkAreaWatchdog(hWnd); + } + } else if (uMsg == WM_HOTKEY && (wParam == 1 || wParam == 2)) { InvokeClockFlyout(); @@ -2777,14 +2781,14 @@ INT64 Shell_TrayWndSubclassProc( UpdateStartMenuPositioning(MAKELPARAM(TRUE, FALSE)); } // The tray reliably receives WM_DISPLAYCHANGE even when the service window - // does not; arm the work-area watchdog from here too. EP_ArmWorkAreaWatchdog + // does not; arm the work-area watchdog from here too. Posted (not called) so + // all watchdog state stays on the service-window thread. EP_ArmWorkAreaWatchdog // guards against re-arming from our own kick (kickCount>0) and the repair - // cooldown, so this cannot loop. SetTimer on the service window from the - // taskbar thread is legal (WM_TIMER is delivered to the service thread). + // cooldown, so this cannot loop. if (bOldTaskbar && bFixWorkAreaAfterDisplayReinit && hWndServiceWindow) { EP_WorkAreaLog("tray WM_DISPLAYCHANGE (isPrimary=%d) -> arm", (int)bIsPrimaryTaskbar); - EP_ArmWorkAreaWatchdog(hWndServiceWindow); + PostMessageW(hWndServiceWindow, EP_WM_ARM_WORKAREA_WATCHDOG, 0, 0); } break; } @@ -2854,7 +2858,7 @@ INT64 Shell_TrayWndSubclassProc( if (bOldTaskbar && bFixWorkAreaAfterDisplayReinit && hWndServiceWindow) { EP_WorkAreaLog("tray 0x5C3 stuck-place change (isPrimary=%d) -> arm", (int)bIsPrimaryTaskbar); - EP_ArmWorkAreaWatchdog(hWndServiceWindow); + PostMessageW(hWndServiceWindow, EP_WM_ARM_WORKAREA_WATCHDOG, 0, 0); } break; }