From 05c533f77f808b5b5e805e2597337a6a5e5ee88b Mon Sep 17 00:00:00 2001 From: TheatriChris <66391302+chrisc44890@users.noreply.github.com> Date: Fri, 10 Jul 2026 19:47:16 -0400 Subject: [PATCH 01/11] Create dynamic-alt-tab.wh.cpp --- mods/dynamic-alt-tab.wh.cpp | 1238 +++++++++++++++++++++++++++++++++++ 1 file changed, 1238 insertions(+) create mode 100644 mods/dynamic-alt-tab.wh.cpp diff --git a/mods/dynamic-alt-tab.wh.cpp b/mods/dynamic-alt-tab.wh.cpp new file mode 100644 index 0000000000..8ccededc1c --- /dev/null +++ b/mods/dynamic-alt-tab.wh.cpp @@ -0,0 +1,1238 @@ +// ==WindhawkMod== +// @id dynamic-alt-tab +// @name Dynamic Alt-Tab +// @description Replaces the native Windows Alt-Tab with a fluid, hardware-accelerated live glass carousel and custom themes. +// @version 1.0 +// @author TheatriChris +// @github https://github.com/chrisc44890 +// @include * +// @compilerOptions -ld2d1 -ldwmapi -lole32 -lgdi32 -lshell32 -ldwrite -lversion -luuid +// ==/WindhawkMod== + +// ==WindhawkModReadme== +/* +# Dynamic Alt-Tab +Replaces the native Windows Alt-Tab screen with a fluid, hardware-accelerated live glass carousel and custom themes. + +**Note:** This mod relies on internal Windows 11 APIs (`twinui.pcshell.dll`) and is designed specifically for Windows 11 and tested on 25H2. It may not hook successfully on Windows 10 or unsupported Insider builds. + +### Features +* **Hardware Accelerated:** Built with Direct2D 1.1 for buttery smooth 60FPS rendering. +* **Live Thumbnails:** Uses DWM thumbnail routing to show real-time, live previews of your apps. +* **Custom Themes:** + * *3D Glass Carousel:* A gorgeous spatial rotating cylinder with physical depth shadows. + ![3D Carousel](https://i.imgur.com/n8FrdUV.gif) + * *macOS Style Flat Grid:* An Exposé-style grid with clean, silver-aluminum highlights. + ![MacOS](https://i.imgur.com/no7EoWE.gif) + * *Apple Liquid Glass:* Waterdrop theme inspired by Apple's Liquid Glass design language + ![Liquid Glass](https://i.imgur.com/pfgvH3z.gif) + * *Material 3 Expressive:* Playful pastel pills that dynamically stretch and squish. + ![Material 3](https://i.imgur.com/eycgEo9.gif) +*/ +// ==/WindhawkModReadme== + +// ==WindhawkModSettings== +/* +- theme: "Apple Liquid Glass" + $name: Visual Theme + $description: Choose your aesthetic. + $options: + - "3D Glass Carousel": 3D Glass Carousel + - "macOS Style Flat Grid": macOS Style Flat Grid + - "Apple Liquid Glass": Apple Liquid Glass + - "Material 3 Expressive": Material 3 Expressive +- bg_opacity: 60 + $name: Background Dim Opacity + $description: How dark the background will be. (0 = clear, 100 = pitch black). +*/ +// ==/WindhawkModSettings== + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef UINT (WINAPI* timeBeginPeriod_t)(UINT); +typedef UINT (WINAPI* timeEndPeriod_t)(UINT); + +struct AccentPolicy { + int state; + int flags; + DWORD gradientColor; + int animationId; +}; + +struct CompositionAttributeData { + int attribute; + void* data; + SIZE_T size; +}; + +typedef BOOL(WINAPI* SetWindowCompositionAttribute_t)(HWND, CompositionAttributeData*); + +HWND g_overlayHwnd = NULL; +HANDLE g_renderThreadHandle = NULL; +HANDLE g_hookThreadHandle = NULL; +DWORD g_hookThreadId = 0; +HHOOK g_keyboardHook = NULL; +std::atomic g_threadRunning(false); + +ID2D1Factory1* g_pD2DFactory = nullptr; +ID2D1DCRenderTarget* g_pDCRenderTarget = nullptr; +ID2D1DeviceContext* g_pDeviceContext = nullptr; + +IDWriteFactory* g_pDWriteFactory = nullptr; +IDWriteTextFormat* g_pTextFormat = nullptr; + +ID2D1SolidColorBrush* g_pGlassBrush = nullptr; +ID2D1SolidColorBrush* g_pBorderBrush = nullptr; +ID2D1SolidColorBrush* g_pCloseBrush = nullptr; +ID2D1SolidColorBrush* g_pTextBrush = nullptr; +ID2D1SolidColorBrush* g_pDimBrush = nullptr; +ID2D1SolidColorBrush* g_pWhiteBrush = nullptr; +ID2D1LinearGradientBrush* g_pSpecularBrush = nullptr; + +HDC g_hdcMem = NULL; +HBITMAP g_hBitmap = NULL; +int g_vW = 0, g_vH = 0; + +bool g_isAltTabbing = false; +bool g_isClosing = false; +bool g_isFirstFrame = false; +int g_selectedIndex = 0; +POINT g_mousePos = { 0, 0 }; + +float g_introOutroProgress = 0.0f; +float g_introOutroTarget = 0.0f; + +std::atomic g_theme(2); +std::atomic g_bgOpacity(60); + +std::atomic g_wantsToOpen(false); +std::atomic g_cancelAltTab(false); +std::atomic g_tabSteps(0); +std::atomic g_threadIdForAltTabShowWindow(0); + +float g_leftPoolX1 = 0.0f; +float g_leftPoolX2 = 0.0f; +float g_leftPoolY = 0.0f; +float g_leftPoolScale = 0.0f; +float g_leftPoolVx1 = 0.0f; +float g_leftPoolVx2 = 0.0f; +float g_leftPoolVy = 0.0f; +float g_leftPoolVScale = 0.0f; + +float g_rightPoolX1 = 0.0f; +float g_rightPoolX2 = 0.0f; +float g_rightPoolY = 0.0f; +float g_rightPoolScale = 0.0f; +float g_rightPoolVx1 = 0.0f; +float g_rightPoolVx2 = 0.0f; +float g_rightPoolVy = 0.0f; +float g_rightPoolVScale = 0.0f; + +struct AppCard { + HWND hwnd; + HTHUMBNAIL thumbnail; + std::wstring title; + float currentX, targetX, vx; + float currentY, targetY, vy; + float currentScale, targetScale, vScale; + float currentCornerRadius, targetCornerRadius, vCornerRadius; + float currentWidthRadius, targetWidthRadius, vWidthRadius; +}; + +std::vector g_cards; + +void LoadSettings() { + PCWSTR themeStrRaw = Wh_GetStringSetting(L"theme"); + std::wstring themeStr = themeStrRaw ? themeStrRaw : L""; + if (themeStrRaw) Wh_FreeStringSetting(themeStrRaw); + + if (themeStr == L"3D Glass Carousel") g_theme.store(0); + else if (themeStr == L"macOS Style Flat Grid") g_theme.store(1); + else if (themeStr == L"Apple Liquid Glass") g_theme.store(2); + else if (themeStr == L"Material 3 Expressive") g_theme.store(3); + else g_theme.store(2); // Default + + int opacity = Wh_GetIntSetting(L"bg_opacity"); + if (opacity < 0) opacity = 0; + if (opacity > 100) opacity = 100; + g_bgOpacity.store(opacity); +} + +void Wh_ModSettingsChanged() { + LoadSettings(); +} + +std::wstring GetWindowTextSafe(HWND hwnd) { + wchar_t buf[256] = {0}; + DWORD pid = 0; + GetWindowThreadProcessId(hwnd, &pid); + + if (pid == GetCurrentProcessId()) { + DWORD_PTR result = 0; + if (SendMessageTimeoutW(hwnd, WM_GETTEXT, 256, (LPARAM)buf, SMTO_ABORTIFHUNG | SMTO_NORMAL, 10, &result) && result > 0) { + return std::wstring(buf); + } + wchar_t className[256] = {0}; + GetClassNameW(hwnd, className, 256); + if (wcscmp(className, L"CabinetWClass") == 0) return L"File Explorer"; + return L""; + } else { + GetWindowTextW(hwnd, buf, 256); + return std::wstring(buf); + } +} + +bool IsValidAppWindow(HWND hwnd) { + if (!hwnd || !IsWindow(hwnd) || !IsWindowVisible(hwnd) || hwnd == GetShellWindow()) return false; + + LONG exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); + if (exStyle & WS_EX_TOOLWINDOW) return false; + if (GetWindow(hwnd, GW_OWNER) != NULL) return false; + + int cloakedVal = 0; + if (SUCCEEDED(DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloakedVal, sizeof(cloakedVal))) && cloakedVal != 0) { + return false; + } + + RECT rc; + if (GetWindowRect(hwnd, &rc)) { + if ((rc.right - rc.left) <= 10 || (rc.bottom - rc.top) <= 10) return false; + } + + wchar_t className[256]; + GetClassNameW(hwnd, className, 256); + std::wstring cls(className); + + if (cls == L"Progman" || cls == L"WorkerW" || cls == L"Shell_TrayWnd" || + cls == L"Shell_SecondaryTrayWnd" || cls == L"Windows.UI.Core.CoreWindow" || + cls == L"GlassAltTabOverlayClass" || cls == L"XamlExplorerHostIslandWindow" || + cls == L"SmearFrameOverlayClass") { + return false; + } + + std::wstring title = GetWindowTextSafe(hwnd); + if (title.empty()) return false; + + return true; +} + +BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) { + if (IsValidAppWindow(hwnd)) { + AppCard card = {0}; + card.hwnd = hwnd; + card.title = GetWindowTextSafe(hwnd); + card.vx = 0.0f; + card.vy = 0.0f; + card.vScale = 0.0f; + card.vCornerRadius = 0.0f; + card.vWidthRadius = 0.0f; + g_cards.push_back(card); + } + return TRUE; +} + +D2D1_COLOR_F GetM3Color(int index, float alpha) { + int val = index % 5; + if (val == 0) return D2D1::ColorF(0.48f, 0.82f, 0.68f, alpha); // Soft Mint + if (val == 1) return D2D1::ColorF(0.72f, 0.62f, 0.88f, alpha); // Soft Lavender + if (val == 2) return D2D1::ColorF(0.92f, 0.58f, 0.58f, alpha); // Soft Coral + if (val == 3) return D2D1::ColorF(0.92f, 0.82f, 0.48f, alpha); // Soft Lemon + return D2D1::ColorF(0.48f, 0.72f, 0.92f, alpha); // Soft Sky Blue +} + +void StartAltTab() { + g_isAltTabbing = true; + g_isClosing = false; + g_isFirstFrame = true; + g_introOutroProgress = 0.0f; + g_introOutroTarget = 1.0f; + g_cancelAltTab = false; + + for (auto& card : g_cards) { + if (card.thumbnail) DwmUnregisterThumbnail(card.thumbnail); + } + g_cards.clear(); + EnumWindows(EnumWindowsProc, 0); + + if (g_cards.empty()) { + g_isAltTabbing = false; + return; + } + + g_selectedIndex = 0; + + for (auto& card : g_cards) { + DwmRegisterThumbnail(g_overlayHwnd, card.hwnd, &card.thumbnail); + card.currentX = 0.0f; + card.currentY = 0.0f; + card.currentScale = 0.1f; + card.currentCornerRadius = 190.0f; + card.currentWidthRadius = 300.0f; + } + + int vW = GetSystemMetrics(SM_CXVIRTUALSCREEN), vH = GetSystemMetrics(SM_CYVIRTUALSCREEN); + g_leftPoolX1 = vW / 2.0f; + g_leftPoolX2 = vW / 2.0f; + g_leftPoolY = vH / 2.0f + 140.0f; + g_leftPoolScale = 0.0f; + g_leftPoolVx1 = 0.0f; g_leftPoolVx2 = 0.0f; g_leftPoolVy = 0.0f; g_leftPoolVScale = 0.0f; + + g_rightPoolX1 = vW / 2.0f; + g_rightPoolX2 = vW / 2.0f; + g_rightPoolY = vH / 2.0f + 140.0f; + g_rightPoolScale = 0.0f; + g_rightPoolVx1 = 0.0f; g_rightPoolVx2 = 0.0f; g_rightPoolVy = 0.0f; g_rightPoolVScale = 0.0f; + + ShowWindow(g_overlayHwnd, SW_SHOWNA); +} + +void StopAltTab() { + if (g_isClosing) return; + g_isClosing = true; + g_introOutroTarget = 0.0f; + + if (g_cancelAltTab) { + keybd_event(VK_MENU, 0, KEYEVENTF_KEYUP, 0); + return; + } + + if (!g_cards.empty() && g_selectedIndex >= 0 && g_selectedIndex < (int)g_cards.size()) { + HWND target = g_cards[g_selectedIndex].hwnd; + + if (IsIconic(target)) ShowWindow(target, SW_RESTORE); + + keybd_event(VK_MENU, 0, KEYEVENTF_KEYUP, 0); + + SetForegroundWindow(g_overlayHwnd); + + DWORD fgThread = GetWindowThreadProcessId(GetForegroundWindow(), NULL); + DWORD targetThread = GetWindowThreadProcessId(target, NULL); + + if (fgThread != targetThread) { + AttachThreadInput(fgThread, targetThread, TRUE); + SetWindowPos(target, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + SetWindowPos(target, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); + SetForegroundWindow(target); + SetFocus(target); + AttachThreadInput(fgThread, targetThread, FALSE); + } else { + SetForegroundWindow(target); + SetFocus(target); + } + + SwitchToThisWindow(target, TRUE); + } +} + +LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { + static bool s_tabDown = false; + if (nCode == HC_ACTION) { + KBDLLHOOKSTRUCT* p = (KBDLLHOOKSTRUCT*)lParam; + bool altDown = ((p->flags & LLKHF_ALTDOWN) != 0) || (GetAsyncKeyState(VK_MENU) & 0x8000) != 0; + + if (p->vkCode == VK_TAB && altDown) { + if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) { + if (!s_tabDown) { + s_tabDown = true; + g_wantsToOpen = true; + bool shiftDown = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0; + g_tabSteps.fetch_add(shiftDown ? -1 : 1); + } + } else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) { + s_tabDown = false; + } + return 1; + } + + if ((p->vkCode == VK_LMENU || p->vkCode == VK_RMENU || p->vkCode == VK_MENU) && (wParam == WM_KEYUP || wParam == WM_SYSKEYUP)) { + g_wantsToOpen = false; + s_tabDown = false; + } + } + return CallNextHookEx(NULL, nCode, wParam, lParam); +} + +DWORD WINAPI HookThreadProc(LPVOID lpParam) { + HANDLE hEvent = (LPVOID)lpParam; + g_keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), 0); + if (hEvent) SetEvent(hEvent); + + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) { + DispatchMessage(&msg); + } + + if (g_keyboardHook) UnhookWindowsHookEx(g_keyboardHook); + return 0; +} + +LRESULT CALLBACK OverlayWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + if (uMsg == WM_MOUSEMOVE) { + g_mousePos.x = (int)(short)LOWORD(lParam); + g_mousePos.y = (int)(short)HIWORD(lParam); + } + + if (uMsg == WM_MOUSEWHEEL && g_isAltTabbing && !g_isClosing) { + int zDelta = (short)HIWORD(wParam); + int count = (int)g_cards.size(); + if (count > 0) { + if (zDelta > 0) { + g_selectedIndex = (g_selectedIndex - 1 + count) % count; + } else { + g_selectedIndex = (g_selectedIndex + 1) % count; + } + } + return 0; + } + + if (uMsg == WM_LBUTTONUP && g_isAltTabbing && !g_isClosing) { + int x = (int)(short)LOWORD(lParam); + int y = (int)(short)HIWORD(lParam); + + bool clickedCard = false; + + std::vector clickOrder(g_cards.size()); + for (int i = 0; i < (int)g_cards.size(); ++i) clickOrder[i] = i; + std::sort(clickOrder.begin(), clickOrder.end(), [](int a, int b) { + return fabsf((float)a - (float)g_selectedIndex) < fabsf((float)b - (float)g_selectedIndex); + }); + + for (int i : clickOrder) { + float cardW = g_cards[i].currentWidthRadius * g_cards[i].currentScale; + float cardH = 380.0f * g_cards[i].currentScale; + float left = g_cards[i].currentX - cardW; + float right = g_cards[i].currentX + cardW; + float top = g_cards[i].currentY - cardH / 2.0f; + float bottom = g_cards[i].currentY + cardH / 2.0f; + + float cx = right - 25.0f * g_cards[i].currentScale; + float cy = top + 25.0f * g_cards[i].currentScale; + float distSq = (x - cx) * (x - cx) + (y - cy) * (y - cy); + + if (distSq < (144.0f * g_cards[i].currentScale * g_cards[i].currentScale)) { + PostMessage(g_cards[i].hwnd, WM_CLOSE, 0, 0); + if (g_cards[i].thumbnail) DwmUnregisterThumbnail(g_cards[i].thumbnail); + g_cards.erase(g_cards.begin() + i); + if (g_selectedIndex >= (int)g_cards.size()) g_selectedIndex = (int)g_cards.size() - 1; + if (g_cards.empty()) { + g_cancelAltTab = true; + g_wantsToOpen = false; + } + return 0; + } + + if (x >= left && x <= right && y >= top && y <= bottom) { + clickedCard = true; + g_selectedIndex = i; + g_wantsToOpen = false; + return 0; + } + } + + if (!clickedCard) { + g_cancelAltTab = true; + g_wantsToOpen = false; + return 0; + } + } + return DefWindowProc(hwnd, uMsg, wParam, lParam); +} + +VOID CALLBACK RenderTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) { + bool wantsToOpen = g_wantsToOpen.load(); + if (wantsToOpen && !g_isAltTabbing) { + StartAltTab(); + } else if (!wantsToOpen && g_isAltTabbing && !g_isClosing) { + StopAltTab(); + } + + int steps = g_tabSteps.exchange(0); + if (steps != 0 && g_isAltTabbing && !g_cards.empty()) { + g_selectedIndex = (g_selectedIndex + steps) % (int)g_cards.size(); + if (g_selectedIndex < 0) g_selectedIndex += (int)g_cards.size(); + } + + if (!g_isAltTabbing && !g_isClosing) return; + + g_introOutroProgress += (g_introOutroTarget - g_introOutroProgress) * 0.22f; + + int vW = GetSystemMetrics(SM_CXVIRTUALSCREEN), vH = GetSystemMetrics(SM_CYVIRTUALSCREEN); + int vX = GetSystemMetrics(SM_XVIRTUALSCREEN), vY = GetSystemMetrics(SM_YVIRTUALSCREEN); + HDC hdcScreen = GetDC(NULL); + + if (g_isClosing && g_introOutroProgress < 0.02f) { + g_isAltTabbing = false; + g_isClosing = false; + g_introOutroProgress = 0.0f; + + for (auto& card : g_cards) { + if (card.thumbnail) DwmUnregisterThumbnail(card.thumbnail); + } + g_cards.clear(); + ShowWindow(hwnd, SW_HIDE); + ReleaseDC(NULL, hdcScreen); + return; + } + + if (!g_hBitmap || g_vW != vW || g_vH != vH) { + if (g_hBitmap) DeleteObject(g_hBitmap); + if (g_hdcMem) DeleteDC(g_hdcMem); + g_hdcMem = CreateCompatibleDC(hdcScreen); + g_hBitmap = CreateCompatibleBitmap(hdcScreen, vW, vH); + SelectObject(g_hdcMem, g_hBitmap); + g_vW = vW; g_vH = vH; + + if (g_pDCRenderTarget) { + g_pDCRenderTarget->Release(); + g_pDCRenderTarget = nullptr; + if (g_pDeviceContext) { g_pDeviceContext->Release(); g_pDeviceContext = nullptr; } + } + } + + if (!g_pDCRenderTarget && g_pD2DFactory) { + D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties( + D2D1_RENDER_TARGET_TYPE_DEFAULT, + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED), + 0, 0, D2D1_RENDER_TARGET_USAGE_NONE, D2D1_FEATURE_LEVEL_DEFAULT + ); + g_pD2DFactory->CreateDCRenderTarget(&props, &g_pDCRenderTarget); + + if (g_pDCRenderTarget) { + g_pDCRenderTarget->QueryInterface(__uuidof(ID2D1DeviceContext), (void**)&g_pDeviceContext); + } + + g_pDCRenderTarget->CreateSolidColorBrush(D2D1::ColorF(0.0f, 0.0f, 0.0f, 1.0f), &g_pGlassBrush); + g_pDCRenderTarget->CreateSolidColorBrush(D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f), &g_pBorderBrush); + g_pDCRenderTarget->CreateSolidColorBrush(D2D1::ColorF(1.0f, 0.0f, 0.0f, 1.0f), &g_pCloseBrush); + g_pDCRenderTarget->CreateSolidColorBrush(D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f), &g_pTextBrush); + g_pDCRenderTarget->CreateSolidColorBrush(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f), &g_pDimBrush); + g_pDCRenderTarget->CreateSolidColorBrush(D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f), &g_pWhiteBrush); + + ID2D1GradientStopCollection *pGradientStops = NULL; + D2D1_GRADIENT_STOP gradientStops[3]; + gradientStops[0].color = D2D1::ColorF(1.0f, 1.0f, 1.0f, 0.85f); + gradientStops[0].position = 0.0f; + gradientStops[1].color = D2D1::ColorF(1.0f, 1.0f, 1.0f, 0.0f); + gradientStops[1].position = 0.45f; + gradientStops[2].color = D2D1::ColorF(1.0f, 1.0f, 1.0f, 0.50f); + gradientStops[2].position = 1.0f; + + g_pDCRenderTarget->CreateGradientStopCollection( + gradientStops, 3, D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP, &pGradientStops + ); + if (pGradientStops) { + g_pDCRenderTarget->CreateLinearGradientBrush( + D2D1::LinearGradientBrushProperties(D2D1::Point2F(0, 0), D2D1::Point2F(100, 100)), + pGradientStops, &g_pSpecularBrush + ); + pGradientStops->Release(); + } + } + + if (g_pDCRenderTarget) { + RECT rc = { 0, 0, vW, vH }; + g_pDCRenderTarget->BindDC(g_hdcMem, &rc); + g_pDCRenderTarget->BeginDraw(); + + g_pDCRenderTarget->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.01f)); + + if (g_pDimBrush) { + float dimAmount = (float)g_bgOpacity.load() / 100.0f; + g_pDimBrush->SetColor(D2D1::ColorF(0.0f, 0.0f, 0.0f, dimAmount * g_introOutroProgress)); + g_pDCRenderTarget->FillRectangle(D2D1::RectF(0, 0, (float)vW, (float)vH), g_pDimBrush); + } + + for (int i = 0; i < (int)g_cards.size(); ++i) { + AppCard& card = g_cards[i]; + float offset = (float)i - (float)g_selectedIndex; + float distance = fabsf(offset); + (void)distance; + + if (g_theme == 1) { + // macOS Flat Grid Style + int cols = (int)g_cards.size(); + if (cols > 5) cols = 5; + if (cols < 1) cols = 1; + int rows = ((int)g_cards.size() + cols - 1) / cols; + int col = i % cols; + int row = i / cols; + card.targetX = (vW / 2.0f) - ((cols - 1) * 360.0f) / 2.0f + (col * 360.0f); + card.targetY = (vH / 2.0f) - ((rows - 1) * 260.0f) / 2.0f + (row * 260.0f); + card.targetScale = (i == g_selectedIndex) ? 0.72f : 0.48f; + card.targetCornerRadius = 14.0f; + card.targetWidthRadius = 310.0f; + } else if (g_theme == 2) { + // Apple Liquid Glass Dynamic Layout + float dir = (offset > 0.0f) ? 1.0f : (offset < 0.0f ? -1.0f : 0.0f); + if (i == g_selectedIndex) { + card.targetX = vW / 2.0f; + card.targetY = vH / 2.0f - 110.0f; + card.targetScale = 0.85f; + card.targetCornerRadius = 24.0f; + card.targetWidthRadius = 300.0f; + } else { + card.targetX = (vW / 2.0f) + (dir * 330.0f) + (offset * 110.0f); + card.targetY = vH / 2.0f + 140.0f; + card.targetScale = 0.35f; + card.targetCornerRadius = 16.0f; + card.targetWidthRadius = 140.0f; + } + } else if (g_theme == 3) { + // Material 3 Expressive + card.targetX = (vW / 2.0f) + (offset * 330.0f); + card.targetY = vH / 2.0f - 20.0f; + card.targetScale = (i == g_selectedIndex) ? 0.78f : 0.42f; + card.targetCornerRadius = (i == g_selectedIndex) ? 28.0f : 190.0f; + card.targetWidthRadius = (i == g_selectedIndex) ? 300.0f : 190.0f; + } else { + // 3D Glass Carousel + float angle = std::clamp(offset * 0.35f, -1.4f, 1.4f); + float depth = cosf(angle); + card.targetX = (vW / 2.0f) + (sinf(angle) * 580.0f); + card.targetY = (vH / 2.0f) + (depth * 80.0f) - 60.0f; + card.targetScale = (i == g_selectedIndex ? 0.95f : 0.45f) * (0.6f + 0.4f * depth); + card.targetCornerRadius = 16.0f; + card.targetWidthRadius = 300.0f * (0.5f + 0.5f * depth); + } + + card.targetY += (1.0f - g_introOutroProgress) * 400.0f; + card.targetScale *= (0.5f + 0.5f * g_introOutroProgress); + + if (g_isFirstFrame) { + card.currentX = card.targetX; + card.currentY = card.targetY + 200.0f; + card.currentScale = card.targetScale * 0.5f; + card.currentCornerRadius = card.targetCornerRadius; + card.currentWidthRadius = card.targetWidthRadius; + } + + if (g_theme == 3) { + float stiffness = 0.16f; + float damping = 0.45f; + card.vx = (card.vx + (card.targetX - card.currentX) * stiffness) * damping; + card.vy = (card.vy + (card.targetY - card.currentY) * stiffness) * damping; + card.vScale = (card.vScale + (card.targetScale - card.currentScale) * stiffness) * damping; + card.vCornerRadius = (card.vCornerRadius + (card.targetCornerRadius - card.currentCornerRadius) * stiffness) * damping; + card.vWidthRadius = (card.vWidthRadius + (card.targetWidthRadius - card.currentWidthRadius) * stiffness) * damping; + } else if (g_theme == 2) { + float stiffness = (i == g_selectedIndex) ? 0.32f : 0.24f; + float damping = (i == g_selectedIndex) ? 0.62f : 0.65f; + + card.vx = (card.vx + (card.targetX - card.currentX) * stiffness) * damping; + card.vy = (card.vy + (card.targetY - card.currentY) * stiffness) * damping; + card.vScale = (card.vScale + (card.targetScale - card.currentScale) * stiffness) * damping; + card.vCornerRadius = (card.vCornerRadius + (card.targetCornerRadius - card.currentCornerRadius) * stiffness) * damping; + card.vWidthRadius = (card.vWidthRadius + (card.targetWidthRadius - card.currentWidthRadius) * stiffness) * damping; + } else if (g_theme == 1) { + float stiffness = 0.35f; + float damping = 0.72f; + card.vx = (card.vx + (card.targetX - card.currentX) * stiffness) * damping; + card.vy = (card.vy + (card.targetY - card.currentY) * stiffness) * damping; + card.vScale = (card.vScale + (card.targetScale - card.currentScale) * stiffness) * damping; + card.vCornerRadius = (card.vCornerRadius + (card.targetCornerRadius - card.currentCornerRadius) * stiffness) * damping; + card.vWidthRadius = (card.vWidthRadius + (card.targetWidthRadius - card.currentWidthRadius) * stiffness) * damping; + } else { + card.vx = (card.vx + (card.targetX - card.currentX) * 0.14f) * 0.78f; + card.vy = (card.vy + (card.targetY - card.currentY) * 0.14f) * 0.78f; + card.vScale = (card.vScale + (card.targetScale - card.currentScale) * 0.14f) * 0.78f; + card.vCornerRadius = (card.vCornerRadius + (card.targetCornerRadius - card.currentCornerRadius) * 0.14f) * 0.78f; + card.vWidthRadius = (card.vWidthRadius + (card.targetWidthRadius - card.currentWidthRadius) * 0.14f) * 0.78f; + } + + card.currentX += card.vx; + card.currentY += card.vy; + card.currentScale += card.vScale; + card.currentCornerRadius += card.vCornerRadius; + card.currentWidthRadius += card.vWidthRadius; + } + + g_isFirstFrame = false; + + if (g_theme == 2) { + float targetX1_L = 99999.0f; + float targetX2_L = -99999.0f; + float targetY_L = 0.0f; + float count_L = 0.0f; + + float targetX1_R = 99999.0f; + float targetX2_R = -99999.0f; + float targetY_R = 0.0f; + float count_R = 0.0f; + + for (int i = 0; i < (int)g_cards.size(); ++i) { + AppCard& card = g_cards[i]; + float cardW = card.currentWidthRadius * card.currentScale; + if (i < g_selectedIndex) { + targetX1_L = (std::min)(targetX1_L, card.currentX - cardW); + targetX2_L = (std::max)(targetX2_L, card.currentX + cardW); + targetY_L += card.currentY; + count_L += 1.0f; + } else if (i > g_selectedIndex) { + targetX1_R = (std::min)(targetX1_R, card.currentX - cardW); + targetX2_R = (std::max)(targetX2_R, card.currentX + cardW); + targetY_R += card.currentY; + count_R += 1.0f; + } + } + + float targetScale_L = 0.0f; + if (count_L > 0.1f) { + targetY_L /= count_L; + targetScale_L = 1.0f; + targetX1_L -= 32.0f; + targetX2_L += 32.0f; + } else { + AppCard& activeCard = g_cards[g_selectedIndex]; + float activeW = activeCard.currentWidthRadius * activeCard.currentScale; + targetX1_L = activeCard.currentX - activeW; + targetX2_L = targetX1_L; + targetY_L = activeCard.currentY + 140.0f; + targetScale_L = 0.0f; + } + + float targetScale_R = 0.0f; + if (count_R > 0.1f) { + targetY_R /= count_R; + targetScale_R = 1.0f; + targetX1_R -= 32.0f; + targetX2_R += 32.0f; + } else { + AppCard& activeCard = g_cards[g_selectedIndex]; + float activeW = activeCard.currentWidthRadius * activeCard.currentScale; + targetX1_R = activeCard.currentX + activeW; + targetX2_R = targetX1_R; + targetY_R = activeCard.currentY + 140.0f; + targetScale_R = 0.0f; + } + + float poolStiffness = 0.22f; + float poolDamping = 0.65f; + + g_leftPoolVx1 = (g_leftPoolVx1 + (targetX1_L - g_leftPoolX1) * poolStiffness) * poolDamping; + g_leftPoolVx2 = (g_leftPoolVx2 + (targetX2_L - g_leftPoolX2) * poolStiffness) * poolDamping; + g_leftPoolVy = (g_leftPoolVy + (targetY_L - g_leftPoolY) * poolStiffness) * poolDamping; + g_leftPoolVScale = (g_leftPoolVScale + (targetScale_L - g_leftPoolScale) * poolStiffness) * poolDamping; + + g_leftPoolX1 += g_leftPoolVx1; + g_leftPoolX2 += g_leftPoolVx2; + g_leftPoolY += g_leftPoolVy; + g_leftPoolScale += g_leftPoolVScale; + + g_rightPoolVx1 = (g_rightPoolVx1 + (targetX1_R - g_rightPoolX1) * poolStiffness) * poolDamping; + g_rightPoolVx2 = (g_rightPoolVx2 + (targetX2_R - g_rightPoolX2) * poolStiffness) * poolDamping; + g_rightPoolVy = (g_rightPoolVy + (targetY_R - g_rightPoolY) * poolStiffness) * poolDamping; + g_rightPoolVScale = (g_rightPoolVScale + (targetScale_R - g_rightPoolScale) * poolStiffness) * poolDamping; + + g_rightPoolX1 += g_rightPoolVx1; + g_rightPoolX2 += g_rightPoolVx2; + g_rightPoolY += g_rightPoolVy; + g_rightPoolScale += g_rightPoolVScale; + } + + if (g_theme == 2) { + if (g_leftPoolScale > 0.01f) { + float poolH = (380.0f * 0.35f + 64.0f) * g_leftPoolScale; + float cornerRadius = poolH * 0.5f; + D2D1_RECT_F poolRect = D2D1::RectF(g_leftPoolX1, g_leftPoolY - poolH * 0.5f, g_leftPoolX2, g_leftPoolY + poolH * 0.5f); + D2D1_ROUNDED_RECT roundedPool = { poolRect, cornerRadius, cornerRadius }; + + // Obsidian deep-dark puddle color + g_pGlassBrush->SetColor(D2D1::ColorF(0.04f, 0.04f, 0.06f, 0.40f * g_introOutroProgress * g_leftPoolScale)); + g_pDCRenderTarget->FillRoundedRectangle(roundedPool, g_pGlassBrush); + + if (g_pSpecularBrush) { + g_pSpecularBrush->SetStartPoint(D2D1::Point2F(poolRect.left, poolRect.top)); + g_pSpecularBrush->SetEndPoint(D2D1::Point2F(poolRect.right, poolRect.bottom)); + g_pSpecularBrush->SetOpacity(g_introOutroProgress * g_leftPoolScale * 0.65f); + g_pDCRenderTarget->DrawRoundedRectangle(roundedPool, g_pSpecularBrush, 1.2f); + } + + if (g_pWhiteBrush) { + g_pWhiteBrush->SetColor(D2D1::ColorF(1.0f, 1.0f, 1.0f, 0.35f * g_introOutroProgress * g_leftPoolScale)); + D2D1_RECT_F topGloss = D2D1::RectF(poolRect.left + cornerRadius, poolRect.top, poolRect.right - cornerRadius, poolRect.top + 1.5f); + g_pDCRenderTarget->FillRectangle(topGloss, g_pWhiteBrush); + } + } + + if (g_rightPoolScale > 0.01f) { + float poolH = (380.0f * 0.35f + 64.0f) * g_rightPoolScale; + float cornerRadius = poolH * 0.5f; + D2D1_RECT_F poolRect = D2D1::RectF(g_rightPoolX1, g_rightPoolY - poolH * 0.5f, g_rightPoolX2, g_rightPoolY + poolH * 0.5f); + D2D1_ROUNDED_RECT roundedPool = { poolRect, cornerRadius, cornerRadius }; + + g_pGlassBrush->SetColor(D2D1::ColorF(0.04f, 0.04f, 0.06f, 0.40f * g_introOutroProgress * g_rightPoolScale)); + g_pDCRenderTarget->FillRoundedRectangle(roundedPool, g_pGlassBrush); + + if (g_pSpecularBrush) { + g_pSpecularBrush->SetStartPoint(D2D1::Point2F(poolRect.left, poolRect.top)); + g_pSpecularBrush->SetEndPoint(D2D1::Point2F(poolRect.right, poolRect.bottom)); + g_pSpecularBrush->SetOpacity(g_introOutroProgress * g_rightPoolScale * 0.65f); + g_pDCRenderTarget->DrawRoundedRectangle(roundedPool, g_pSpecularBrush, 1.2f); + } + + if (g_pWhiteBrush) { + g_pWhiteBrush->SetColor(D2D1::ColorF(1.0f, 1.0f, 1.0f, 0.35f * g_introOutroProgress * g_rightPoolScale)); + D2D1_RECT_F topGloss = D2D1::RectF(poolRect.left + cornerRadius, poolRect.top, poolRect.right - cornerRadius, poolRect.top + 1.5f); + g_pDCRenderTarget->FillRectangle(topGloss, g_pWhiteBrush); + } + } + } + + std::vector renderOrder(g_cards.size()); + for (int i = 0; i < (int)g_cards.size(); ++i) renderOrder[i] = i; + std::sort(renderOrder.begin(), renderOrder.end(), [](int a, int b) { + float distA = fabsf((float)a - (float)g_selectedIndex); + float distB = fabsf((float)b - (float)g_selectedIndex); + return distA > distB; + }); + + for (int i : renderOrder) { + AppCard& card = g_cards[i]; + float offset = (float)i - (float)g_selectedIndex; + float distance = fabsf(offset); + (void)distance; + + float cardOpacity = g_introOutroProgress; + if (g_theme != 1 && g_theme != 3 && g_theme != 0) { + cardOpacity = g_introOutroProgress / (1.0f + distance * 0.40f); + } + + float squishX = 0.0f; + if (g_theme == 3) { + squishX = card.vx * 0.16f; + } + + float cardW = (card.currentWidthRadius + squishX) * card.currentScale; + float cardH = 380.0f * card.currentScale; + bool isHovered = (g_mousePos.x > (card.currentX - cardW) && + g_mousePos.x < (card.currentX + cardW) && + g_mousePos.y > (card.currentY - cardH / 2.0f) && + g_mousePos.y < (card.currentY + cardH / 2.0f)); + + if (isHovered && i != g_selectedIndex) { + card.currentScale += (card.targetScale * 1.05f - card.currentScale) * 0.4f; + } + + D2D1_RECT_F cr = D2D1::RectF( + card.currentX - cardW, + card.currentY - cardH / 2.0f, + card.currentX + cardW, + card.currentY + cardH / 2.0f + ); + + float cornerRadius = card.currentCornerRadius * card.currentScale; + ID2D1Brush* activeBorderBrush = g_pBorderBrush; + + float textOpac = cardOpacity; + float thumbOpac = (i == g_selectedIndex ? 1.0f : 0.6f); + + if (g_theme == 0) { + // Completely solid cards that fade to black but do NOT become transparent to each other + float depthDarken = 1.0f / (1.0f + distance * 0.50f); + cardOpacity = g_introOutroProgress; + thumbOpac = (i == g_selectedIndex ? 1.0f : 0.8f * depthDarken); + textOpac = depthDarken * g_introOutroProgress; + + g_pGlassBrush->SetColor(D2D1::ColorF(0.12f * depthDarken, 0.12f * depthDarken, 0.15f * depthDarken, 1.0f * g_introOutroProgress)); + if (i == g_selectedIndex) { + g_pBorderBrush->SetColor(D2D1::ColorF(0.85f, 0.35f, 0.95f, 1.0f * g_introOutroProgress)); + } else { + g_pBorderBrush->SetColor(D2D1::ColorF(0.85f * depthDarken, 0.85f * depthDarken, 0.95f * depthDarken, 0.4f * g_introOutroProgress)); + } + g_pTextBrush->SetColor(D2D1::ColorF(1.0f, 1.0f, 1.0f, textOpac)); + } else if (g_theme == 2) { + float scaleRatio = (card.currentScale - 0.20f) / (0.85f - 0.20f); + if (scaleRatio < 0.0f) scaleRatio = 0.0f; + if (scaleRatio > 1.0f) scaleRatio = 1.0f; + + textOpac = cardOpacity * (0.15f + 0.85f * powf(scaleRatio, 1.5f)); + thumbOpac = 0.35f + (0.65f * scaleRatio); + + g_pTextBrush->SetColor(D2D1::ColorF(1.0f, 1.0f, 1.0f, textOpac)); + } else if (g_theme == 3) { + D2D1_COLOR_F baseColor = GetM3Color(i, (i == g_selectedIndex ? 0.90f : 0.65f) * cardOpacity); + if (i == g_selectedIndex) { + g_pTextBrush->SetColor(D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f * cardOpacity)); + } else { + g_pTextBrush->SetColor(D2D1::ColorF(0.1f, 0.1f, 0.15f, 1.0f * cardOpacity)); + } + g_pGlassBrush->SetColor(baseColor); + g_pBorderBrush->SetColor(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f)); + } else { + g_pGlassBrush->SetColor(D2D1::ColorF(0.08f, 0.08f, 0.1f, 0.65f * cardOpacity)); + if (i == g_selectedIndex) { + if (g_theme == 1) { + g_pBorderBrush->SetColor(D2D1::ColorF(0.92f, 0.92f, 0.95f, 0.95f * cardOpacity)); + } else { + g_pBorderBrush->SetColor(D2D1::ColorF(0.85f, 0.35f, 0.95f, 0.85f * cardOpacity)); + } + } else { + g_pBorderBrush->SetColor(D2D1::ColorF(1.0f, 1.0f, 1.0f, 0.20f * cardOpacity)); + } + g_pTextBrush->SetColor(D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f * cardOpacity)); + } + + D2D1_ROUNDED_RECT roundedCard = { cr, cornerRadius, cornerRadius }; + + if (g_theme == 0 && g_introOutroProgress > 0.01f) { + D2D1_ROUNDED_RECT shadowCard = roundedCard; + shadowCard.rect.left += 15.0f * card.currentScale; + shadowCard.rect.right += 15.0f * card.currentScale; + shadowCard.rect.top += 25.0f * card.currentScale; + shadowCard.rect.bottom += 25.0f * card.currentScale; + g_pDimBrush->SetColor(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.6f * g_introOutroProgress)); + g_pDCRenderTarget->FillRoundedRectangle(shadowCard, g_pDimBrush); + } + + if (g_theme != 2) { + g_pDCRenderTarget->FillRoundedRectangle(roundedCard, g_pGlassBrush); + if (g_theme != 3) { + g_pDCRenderTarget->DrawRoundedRectangle(roundedCard, activeBorderBrush, 2.0f); + } else if (i == g_selectedIndex) { + g_pBorderBrush->SetColor(D2D1::ColorF(1.0f, 1.0f, 1.0f, 0.50f * cardOpacity)); + g_pDCRenderTarget->DrawRoundedRectangle(roundedCard, g_pBorderBrush, 3.0f); + } + } else if (g_theme == 2) { + if (i == g_selectedIndex) { + g_pGlassBrush->SetColor(D2D1::ColorF(0.92f, 0.94f, 0.98f, 0.24f * cardOpacity)); + g_pDCRenderTarget->FillRoundedRectangle(roundedCard, g_pGlassBrush); + + if (g_pSpecularBrush) { + g_pSpecularBrush->SetStartPoint(D2D1::Point2F(cr.left, cr.top)); + g_pSpecularBrush->SetEndPoint(D2D1::Point2F(cr.right, cr.bottom)); + g_pSpecularBrush->SetOpacity(cardOpacity * 0.95f); + g_pDCRenderTarget->DrawRoundedRectangle(roundedCard, g_pSpecularBrush, 1.5f); + } + if (g_pWhiteBrush) { + g_pWhiteBrush->SetColor(D2D1::ColorF(1.0f, 1.0f, 1.0f, 0.45f * cardOpacity)); + D2D1_RECT_F topGloss = D2D1::RectF(cr.left + cornerRadius, cr.top, cr.right - cornerRadius, cr.top + 1.5f); + g_pDCRenderTarget->FillRectangle(topGloss, g_pWhiteBrush); + } + } + } + + if (g_pTextFormat && g_pTextBrush && textOpac > 0.01f) { + float textPadding = cornerRadius + (10.0f * card.currentScale); + if (g_theme != 3 && g_theme != 2) textPadding = 20.0f * card.currentScale; + + g_pDCRenderTarget->DrawText( + card.title.c_str(), + (UINT32)card.title.length(), + g_pTextFormat, + D2D1::RectF(cr.left + textPadding, cr.top + 12 * card.currentScale, cr.right - textPadding, cr.top + 36 * card.currentScale), + g_pTextBrush + ); + } + + float cx = cr.right - 25.0f * card.currentScale; + float cy = cr.top + 25.0f * card.currentScale; + float distSq = (g_mousePos.x - cx) * (g_mousePos.x - cx) + (g_mousePos.y - cy) * (g_mousePos.y - cy); + bool isCloseHovered = (distSq < (144.0f * card.currentScale * card.currentScale)); + + if (thumbOpac > 0.01f && textOpac > 0.01f) { + if (g_theme == 3) { + if (isCloseHovered) g_pCloseBrush->SetColor(D2D1::ColorF(0.95f, 0.15f, 0.2f, 1.0f * cardOpacity)); + else g_pCloseBrush->SetColor(D2D1::ColorF(0.1f, 0.1f, 0.15f, 0.35f * cardOpacity)); + } else { + if (isCloseHovered) g_pCloseBrush->SetColor(D2D1::ColorF(1.0f, 0.1f, 0.1f, textOpac)); + else g_pCloseBrush->SetColor(D2D1::ColorF(0.8f, 0.2f, 0.2f, 0.7f * textOpac)); + } + + if (g_theme != 2 || i == g_selectedIndex) { + D2D1_ELLIPSE closeEllipse = { {cx, cy}, 11.0f * card.currentScale, 11.0f * card.currentScale }; + g_pDCRenderTarget->FillEllipse(closeEllipse, g_pCloseBrush); + + if (g_pWhiteBrush) { + g_pWhiteBrush->SetColor(D2D1::ColorF(1.0f, 1.0f, 1.0f, textOpac)); + g_pDCRenderTarget->DrawLine(D2D1::Point2F(cx - 4 * card.currentScale, cy - 4 * card.currentScale), D2D1::Point2F(cx + 4 * card.currentScale, cy + 4 * card.currentScale), g_pWhiteBrush, 2.0f); + g_pDCRenderTarget->DrawLine(D2D1::Point2F(cx + 4 * card.currentScale, cy - 4 * card.currentScale), D2D1::Point2F(cx - 4 * card.currentScale, cy + 4 * card.currentScale), g_pWhiteBrush, 2.0f); + } + } + } + + if (card.thumbnail) { + DWM_THUMBNAIL_PROPERTIES tp = {0}; + tp.dwFlags = DWM_TNP_RECTDESTINATION | DWM_TNP_VISIBLE | DWM_TNP_OPACITY; + tp.fVisible = (thumbOpac > 0.02f) ? TRUE : FALSE; + + int padX = (int)(cornerRadius * 0.35f) + (int)(10.0f * card.currentScale); + int padYBottom = (int)(cornerRadius * 0.35f) + (int)(10.0f * card.currentScale); + int padYTop = (int)(45.0f * card.currentScale); + + tp.rcDestination = { + (int)(cr.left + padX), + (int)(cr.top + padYTop), + (int)(cr.right - padX), + (int)(cr.bottom - padYBottom) + }; + tp.opacity = (BYTE)(255.0f * cardOpacity * thumbOpac); + + if (thumbOpac > 0.02f && g_pDimBrush) { + float totalThumbAlpha = (g_theme == 0) ? g_introOutroProgress : (cardOpacity * thumbOpac); + g_pDimBrush->SetColor(D2D1::ColorF(0.06f, 0.06f, 0.08f, totalThumbAlpha)); + D2D1_RECT_F thumbRect = D2D1::RectF(tp.rcDestination.left, tp.rcDestination.top, tp.rcDestination.right, tp.rcDestination.bottom); + g_pDCRenderTarget->FillRectangle(thumbRect, g_pDimBrush); + } + + DwmUpdateThumbnailProperties(card.thumbnail, &tp); + } + } + + HRESULT hr = g_pDCRenderTarget->EndDraw(); + if (hr == D2DERR_RECREATE_TARGET) { + g_pDCRenderTarget->Release(); + g_pDCRenderTarget = nullptr; + if (g_pDeviceContext) { g_pDeviceContext->Release(); g_pDeviceContext = nullptr; } + } + } + + BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; + POINT p0 = { 0, 0 }; + SIZE s0 = { vW, vH }; + POINT p1 = { vX, vY }; + UpdateLayeredWindow(hwnd, hdcScreen, &p1, &s0, g_hdcMem, &p0, 0, &bf, ULW_ALPHA); + ReleaseDC(NULL, hdcScreen); +} + +DWORD WINAPI OverlayThreadProc(LPVOID lpParam) { + HANDLE hEvent = (HANDLE)lpParam; + + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + + D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &g_pD2DFactory); + DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast(&g_pDWriteFactory)); + + if (g_pDWriteFactory) { + if (!g_pTextFormat) { + g_pDWriteFactory->CreateTextFormat(L"Segoe UI Variable Text", NULL, DWRITE_FONT_WEIGHT_BOLD, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 14.0f, L"en-us", &g_pTextFormat); + if (g_pTextFormat) { + g_pTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER); + g_pTextFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER); + g_pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP); + + IDWriteInlineObject* pEllipsis = nullptr; + if (SUCCEEDED(g_pDWriteFactory->CreateEllipsisTrimmingSign(g_pTextFormat, &pEllipsis))) { + DWRITE_TRIMMING trimming = { DWRITE_TRIMMING_GRANULARITY_CHARACTER, 0, 0 }; + g_pTextFormat->SetTrimming(&trimming, pEllipsis); + pEllipsis->Release(); + } + } + } + } + + HINSTANCE hInstance = GetModuleHandle(NULL); + const wchar_t CLASS_NAME[] = L"GlassAltTabOverlayClass"; + + WNDCLASS wc = { }; + wc.lpfnWndProc = OverlayWndProc; + wc.hInstance = hInstance; + wc.lpszClassName = CLASS_NAME; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + + RegisterClass(&wc); + + int screenX = GetSystemMetrics(SM_XVIRTUALSCREEN); + int screenY = GetSystemMetrics(SM_YVIRTUALSCREEN); + int screenW = GetSystemMetrics(SM_CXVIRTUALSCREEN); + int screenH = GetSystemMetrics(SM_CYVIRTUALSCREEN); + + g_overlayHwnd = CreateWindowEx( + WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE, + CLASS_NAME, L"GlassAltTabOverlay", WS_POPUP, + screenX, screenY, screenW, screenH, + NULL, NULL, hInstance, NULL + ); + + if (!g_overlayHwnd) { + if (hEvent) SetEvent(hEvent); + return 0; + } + + int backdrop = 3; + DwmSetWindowAttribute(g_overlayHwnd, 38, &backdrop, sizeof(backdrop)); + + HMODULE user32 = GetModuleHandleW(L"user32.dll"); + if (user32) { + auto setWindowCompositionAttribute = (SetWindowCompositionAttribute_t)GetProcAddress(user32, "SetWindowCompositionAttribute"); + if (setWindowCompositionAttribute) { + AccentPolicy policy = { 4, 1, 0x90000000, 0 }; + CompositionAttributeData data = { 19, &policy, sizeof(policy) }; + setWindowCompositionAttribute(g_overlayHwnd, &data); + } + } + + if (hEvent) SetEvent(hEvent); + + timeBeginPeriod_t pTimeBeginPeriod = nullptr; + timeEndPeriod_t pTimeEndPeriod = nullptr; + HMODULE hWinmm = LoadLibraryW(L"winmm.dll"); + if (hWinmm) { + pTimeBeginPeriod = (timeBeginPeriod_t)GetProcAddress(hWinmm, "timeBeginPeriod"); + pTimeEndPeriod = (timeEndPeriod_t)GetProcAddress(hWinmm, "timeEndPeriod"); + } + + if (pTimeBeginPeriod) pTimeBeginPeriod(1); + SetTimer(g_overlayHwnd, 1, 8, RenderTimerProc); + + MSG msg; + while (g_threadRunning && GetMessage(&msg, NULL, 0, 0)) { + DispatchMessage(&msg); + } + + KillTimer(g_overlayHwnd, 1); + if (pTimeEndPeriod) pTimeEndPeriod(1); + if (hWinmm) FreeLibrary(hWinmm); + + for (auto& card : g_cards) { + if (card.thumbnail) DwmUnregisterThumbnail(card.thumbnail); + } + g_cards.clear(); + + if (g_pSpecularBrush) { g_pSpecularBrush->Release(); g_pSpecularBrush = nullptr; } + if (g_pGlassBrush) { g_pGlassBrush->Release(); g_pGlassBrush = nullptr; } + if (g_pBorderBrush) { g_pBorderBrush->Release(); g_pBorderBrush = nullptr; } + if (g_pCloseBrush) { g_pCloseBrush->Release(); g_pCloseBrush = nullptr; } + if (g_pTextBrush) { g_pTextBrush->Release(); g_pTextBrush = nullptr; } + if (g_pDimBrush) { g_pDimBrush->Release(); g_pDimBrush = nullptr; } + if (g_pWhiteBrush) { g_pWhiteBrush->Release(); g_pWhiteBrush = nullptr; } + + if (g_pDeviceContext) { g_pDeviceContext->Release(); g_pDeviceContext = nullptr; } + + if (g_pTextFormat) { g_pTextFormat->Release(); g_pTextFormat = nullptr; } + if (g_pDWriteFactory) { g_pDWriteFactory->Release(); g_pDWriteFactory = nullptr; } + if (g_pDCRenderTarget) { g_pDCRenderTarget->Release(); g_pDCRenderTarget = nullptr; } + if (g_pD2DFactory) { g_pD2DFactory->Release(); g_pD2DFactory = nullptr; } + if (g_hBitmap) { DeleteObject(g_hBitmap); g_hBitmap = NULL; } + if (g_hdcMem) { DeleteDC(g_hdcMem); g_hdcMem = NULL; } + + DestroyWindow(g_overlayHwnd); + UnregisterClass(CLASS_NAME, hInstance); + CoUninitialize(); + return 0; +} + +struct AutoThreadId { + AutoThreadId() { g_threadIdForAltTabShowWindow = GetCurrentThreadId(); } + ~AutoThreadId() { g_threadIdForAltTabShowWindow = 0; } +}; + +using XamlAltTabViewHost_ViewLoaded_t = void(WINAPI*)(void*); +XamlAltTabViewHost_ViewLoaded_t XamlAltTabViewHost_ViewLoaded_Original; +void WINAPI XamlAltTabViewHost_ViewLoaded_Hook(void* pThis) { + AutoThreadId lock; + XamlAltTabViewHost_ViewLoaded_Original(pThis); +} + +using XamlAltTabViewHost_DisplayAltTab_t = void(WINAPI*)(void*); +XamlAltTabViewHost_DisplayAltTab_t XamlAltTabViewHost_DisplayAltTab_Original; +void WINAPI XamlAltTabViewHost_DisplayAltTab_Hook(void* pThis) { + if (g_threadIdForAltTabShowWindow != 0) return XamlAltTabViewHost_DisplayAltTab_Original(pThis); + AutoThreadId lock; + XamlAltTabViewHost_DisplayAltTab_Original(pThis); +} + +using CAltTabViewHost_Show_t = HRESULT(WINAPI*)(void*, void*, void*, void*); +CAltTabViewHost_Show_t CAltTabViewHost_Show_Original; +HRESULT WINAPI CAltTabViewHost_Show_Hook(void* pThis, void* p1, void* p2, void* p3) { + AutoThreadId lock; + return CAltTabViewHost_Show_Original(pThis, p1, p2, p3); +} + +using ShowWindow_t = decltype(&ShowWindow); +ShowWindow_t ShowWindow_Original; +BOOL WINAPI ShowWindow_Hook(HWND hWnd, int nCmdShow) { + if (nCmdShow != SW_HIDE) { + // Note: This suppresses ShowWindow calls for ANY window on the thread while the + // internal immersive layout methods are executing. Explorer's Alt-Tab thread is + // highly isolated, so this safely catches the native switcher without collateral damage. + if (g_threadIdForAltTabShowWindow == GetCurrentThreadId()) { + return TRUE; + } + + wchar_t className[256]; + if (GetClassNameW(hWnd, className, 256)) { + if (wcscmp(className, L"XamlExplorerHostIslandWindow") == 0 || + wcscmp(className, L"MultitaskingViewFrame") == 0 || + wcscmp(className, L"TaskSwitcherWnd") == 0 || + wcscmp(className, L"Shell_InputSwitchTopLevelWindow") == 0) { + return TRUE; + } + } + } + return ShowWindow_Original(hWnd, nCmdShow); +} + +BOOL Wh_ModInit() { + LoadSettings(); + HMODULE twinui = LoadLibraryW(L"twinui.pcshell.dll"); + if (twinui) { + WindhawkUtils::SYMBOL_HOOK hooks[] = { + { {LR"(public: virtual long __cdecl XamlAltTabViewHost::ViewLoaded(void))"}, &XamlAltTabViewHost_ViewLoaded_Original, XamlAltTabViewHost_ViewLoaded_Hook, true }, + { {LR"(private: void __cdecl XamlAltTabViewHost::DisplayAltTab(void))"}, &XamlAltTabViewHost_DisplayAltTab_Original, XamlAltTabViewHost_DisplayAltTab_Hook, true }, + { {LR"(public: virtual long __cdecl CAltTabViewHost::Show(struct IImmersiveMonitor *,enum ALT_TAB_VIEW_FLAGS,struct IApplicationView *))"}, &CAltTabViewHost_Show_Original, CAltTabViewHost_Show_Hook, true } + }; + if (!WindhawkUtils::HookSymbols(twinui, hooks, ARRAYSIZE(hooks))) { + Wh_Log(L"Dynamic Alt-Tab: Failed to hook twinui.pcshell.dll symbols. Mod may not function correctly."); + return FALSE; + } + } else { + Wh_Log(L"Dynamic Alt-Tab: twinui.pcshell.dll not found. System unsupported."); + return FALSE; + } + + if (!WindhawkUtils::SetFunctionHook((void*)ShowWindow, (void*)ShowWindow_Hook, (void**)&ShowWindow_Original)) { + Wh_Log(L"Dynamic Alt-Tab: Failed to hook ShowWindow. Mod may not function correctly."); + return FALSE; + } + + HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!hEvent) return FALSE; + + g_threadRunning = true; + g_renderThreadHandle = CreateThread(NULL, 0, OverlayThreadProc, hEvent, 0, NULL); + if (!g_renderThreadHandle) { + CloseHandle(hEvent); + return FALSE; + } + + WaitForSingleObject(hEvent, 3000); + CloseHandle(hEvent); + + HANDLE hHookEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + g_hookThreadHandle = CreateThread(NULL, 0, HookThreadProc, hHookEvent, 0, &g_hookThreadId); + if (g_hookThreadHandle) { + WaitForSingleObject(hHookEvent, 3000); + CloseHandle(hHookEvent); + } + + return TRUE; +} + +void Wh_ModUninit() { + if (g_hookThreadId) { + PostThreadMessage(g_hookThreadId, WM_QUIT, 0, 0); + WaitForSingleObject(g_hookThreadHandle, INFINITE); + CloseHandle(g_hookThreadHandle); + g_hookThreadHandle = NULL; + } + g_threadRunning = false; + if (g_overlayHwnd) { + PostMessage(g_overlayHwnd, WM_QUIT, 0, 0); + } + if (g_renderThreadHandle) { + WaitForSingleObject(g_renderThreadHandle, INFINITE); + CloseHandle(g_renderThreadHandle); + g_renderThreadHandle = NULL; + } +} From 1e34480e14ad9858fc6a3298bf14bd4e566003e6 Mon Sep 17 00:00:00 2001 From: TheatriChris <66391302+chrisc44890@users.noreply.github.com> Date: Fri, 10 Jul 2026 19:51:50 -0400 Subject: [PATCH 02/11] Update dynamic-alt-tab.wh.cpp --- mods/dynamic-alt-tab.wh.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mods/dynamic-alt-tab.wh.cpp b/mods/dynamic-alt-tab.wh.cpp index 8ccededc1c..07aca172f3 100644 --- a/mods/dynamic-alt-tab.wh.cpp +++ b/mods/dynamic-alt-tab.wh.cpp @@ -1,7 +1,7 @@ // ==WindhawkMod== // @id dynamic-alt-tab // @name Dynamic Alt-Tab -// @description Replaces the native Windows Alt-Tab with a fluid, hardware-accelerated live glass carousel and custom themes. +// @description Replaces the native Windows Alt-Tab with a different fluidly animated interfaces. // @version 1.0 // @author TheatriChris // @github https://github.com/chrisc44890 @@ -12,7 +12,7 @@ // ==WindhawkModReadme== /* # Dynamic Alt-Tab -Replaces the native Windows Alt-Tab screen with a fluid, hardware-accelerated live glass carousel and custom themes. +Replaces the native Windows Alt-Tab screen with different fluidly animated themes. **Note:** This mod relies on internal Windows 11 APIs (`twinui.pcshell.dll`) and is designed specifically for Windows 11 and tested on 25H2. It may not hook successfully on Windows 10 or unsupported Insider builds. From 006afc087e2b1b397a3fa8080a0881ddf5a461c2 Mon Sep 17 00:00:00 2001 From: TheatriChris <66391302+chrisc44890@users.noreply.github.com> Date: Fri, 10 Jul 2026 20:01:16 -0400 Subject: [PATCH 03/11] Update dynamic-alt-tab.wh.cpp Changed hooks --- mods/dynamic-alt-tab.wh.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mods/dynamic-alt-tab.wh.cpp b/mods/dynamic-alt-tab.wh.cpp index 07aca172f3..c3c481a2ef 100644 --- a/mods/dynamic-alt-tab.wh.cpp +++ b/mods/dynamic-alt-tab.wh.cpp @@ -1,7 +1,7 @@ // ==WindhawkMod== // @id dynamic-alt-tab // @name Dynamic Alt-Tab -// @description Replaces the native Windows Alt-Tab with a different fluidly animated interfaces. +// @description Replaces the native Windows Alt-Tab with a fluid, hardware-accelerated live glass carousel and custom themes. // @version 1.0 // @author TheatriChris // @github https://github.com/chrisc44890 @@ -12,9 +12,9 @@ // ==WindhawkModReadme== /* # Dynamic Alt-Tab -Replaces the native Windows Alt-Tab screen with different fluidly animated themes. +Replaces the native Windows Alt-Tab screen with a fluid, hardware-accelerated live glass carousel and custom themes. -**Note:** This mod relies on internal Windows 11 APIs (`twinui.pcshell.dll`) and is designed specifically for Windows 11 and tested on 25H2. It may not hook successfully on Windows 10 or unsupported Insider builds. +**Note:** This mod relies on internal Windows 11 APIs (`twinui.pcshell.dll`) and is designed specifically for Windows 11. It may not hook successfully on Windows 10 or unsupported Insider builds. ### Features * **Hardware Accelerated:** Built with Direct2D 1.1 for buttery smooth 60FPS rendering. @@ -1177,12 +1177,13 @@ BOOL Wh_ModInit() { LoadSettings(); HMODULE twinui = LoadLibraryW(L"twinui.pcshell.dll"); if (twinui) { - WindhawkUtils::SYMBOL_HOOK hooks[] = { + // twinui.pcshell.dll + WindhawkUtils::SYMBOL_HOOK twinui_pcshell_dll_hooks[] = { { {LR"(public: virtual long __cdecl XamlAltTabViewHost::ViewLoaded(void))"}, &XamlAltTabViewHost_ViewLoaded_Original, XamlAltTabViewHost_ViewLoaded_Hook, true }, { {LR"(private: void __cdecl XamlAltTabViewHost::DisplayAltTab(void))"}, &XamlAltTabViewHost_DisplayAltTab_Original, XamlAltTabViewHost_DisplayAltTab_Hook, true }, { {LR"(public: virtual long __cdecl CAltTabViewHost::Show(struct IImmersiveMonitor *,enum ALT_TAB_VIEW_FLAGS,struct IApplicationView *))"}, &CAltTabViewHost_Show_Original, CAltTabViewHost_Show_Hook, true } }; - if (!WindhawkUtils::HookSymbols(twinui, hooks, ARRAYSIZE(hooks))) { + if (!WindhawkUtils::HookSymbols(twinui, twinui_pcshell_dll_hooks, ARRAYSIZE(twinui_pcshell_dll_hooks))) { Wh_Log(L"Dynamic Alt-Tab: Failed to hook twinui.pcshell.dll symbols. Mod may not function correctly."); return FALSE; } From cda6ad9fdf1190f4fcd16c4301834c7fd7e5f1ad Mon Sep 17 00:00:00 2001 From: TheatriChris <66391302+chrisc44890@users.noreply.github.com> Date: Fri, 10 Jul 2026 20:53:43 -0400 Subject: [PATCH 04/11] Update dynamic-alt-tab.wh.cpp Attempt to fix Symbols error and hooks warning --- mods/dynamic-alt-tab.wh.cpp | 42 +++++++++++++++---------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/mods/dynamic-alt-tab.wh.cpp b/mods/dynamic-alt-tab.wh.cpp index c3c481a2ef..a925ad8bb6 100644 --- a/mods/dynamic-alt-tab.wh.cpp +++ b/mods/dynamic-alt-tab.wh.cpp @@ -14,7 +14,7 @@ # Dynamic Alt-Tab Replaces the native Windows Alt-Tab screen with a fluid, hardware-accelerated live glass carousel and custom themes. -**Note:** This mod relies on internal Windows 11 APIs (`twinui.pcshell.dll`) and is designed specifically for Windows 11. It may not hook successfully on Windows 10 or unsupported Insider builds. +**Note:** This mod relies on internal Windows 11 APIs (`twinui.pcshell.dll`) and is designed specifically for Windows 11 and tested on version 25H2. It may not hook successfully on Windows 10 or unsupported Insider builds. ### Features * **Hardware Accelerated:** Built with Direct2D 1.1 for buttery smooth 60FPS rendering. @@ -1153,20 +1153,15 @@ using ShowWindow_t = decltype(&ShowWindow); ShowWindow_t ShowWindow_Original; BOOL WINAPI ShowWindow_Hook(HWND hWnd, int nCmdShow) { if (nCmdShow != SW_HIDE) { - // Note: This suppresses ShowWindow calls for ANY window on the thread while the - // internal immersive layout methods are executing. Explorer's Alt-Tab thread is - // highly isolated, so this safely catches the native switcher without collateral damage. - if (g_threadIdForAltTabShowWindow == GetCurrentThreadId()) { - return TRUE; - } - - wchar_t className[256]; - if (GetClassNameW(hWnd, className, 256)) { - if (wcscmp(className, L"XamlExplorerHostIslandWindow") == 0 || - wcscmp(className, L"MultitaskingViewFrame") == 0 || - wcscmp(className, L"TaskSwitcherWnd") == 0 || - wcscmp(className, L"Shell_InputSwitchTopLevelWindow") == 0) { - return TRUE; + if (g_threadIdForAltTabShowWindow == GetCurrentThreadId() || g_wantsToOpen.load() || g_isAltTabbing) { + wchar_t className[256]; + if (GetClassNameW(hWnd, className, 256)) { + if (wcscmp(className, L"XamlExplorerHostIslandWindow") == 0 || + wcscmp(className, L"MultitaskingViewFrame") == 0 || + wcscmp(className, L"TaskSwitcherWnd") == 0 || + wcscmp(className, L"Shell_InputSwitchTopLevelWindow") == 0) { + return TRUE; + } } } } @@ -1177,19 +1172,16 @@ BOOL Wh_ModInit() { LoadSettings(); HMODULE twinui = LoadLibraryW(L"twinui.pcshell.dll"); if (twinui) { - // twinui.pcshell.dll - WindhawkUtils::SYMBOL_HOOK twinui_pcshell_dll_hooks[] = { - { {LR"(public: virtual long __cdecl XamlAltTabViewHost::ViewLoaded(void))"}, &XamlAltTabViewHost_ViewLoaded_Original, XamlAltTabViewHost_ViewLoaded_Hook, true }, - { {LR"(private: void __cdecl XamlAltTabViewHost::DisplayAltTab(void))"}, &XamlAltTabViewHost_DisplayAltTab_Original, XamlAltTabViewHost_DisplayAltTab_Hook, true }, - { {LR"(public: virtual long __cdecl CAltTabViewHost::Show(struct IImmersiveMonitor *,enum ALT_TAB_VIEW_FLAGS,struct IApplicationView *))"}, &CAltTabViewHost_Show_Original, CAltTabViewHost_Show_Hook, true } + WindhawkUtils::SYMBOL_HOOK twinuiPcshellDllHooks[] = { + { {LR"(public: virtual long __cdecl XamlAltTabViewHost::ViewLoaded(void))"}, &XamlAltTabViewHost_ViewLoaded_Original, XamlAltTabViewHost_ViewLoaded_Hook, false }, + { {LR"(private: void __cdecl XamlAltTabViewHost::DisplayAltTab(void))"}, &XamlAltTabViewHost_DisplayAltTab_Original, XamlAltTabViewHost_DisplayAltTab_Hook, false }, + { {LR"(public: virtual long __cdecl CAltTabViewHost::Show(struct IImmersiveMonitor *,enum ALT_TAB_VIEW_FLAGS,struct IApplicationView *))"}, &CAltTabViewHost_Show_Original, CAltTabViewHost_Show_Hook, false } }; - if (!WindhawkUtils::HookSymbols(twinui, twinui_pcshell_dll_hooks, ARRAYSIZE(twinui_pcshell_dll_hooks))) { - Wh_Log(L"Dynamic Alt-Tab: Failed to hook twinui.pcshell.dll symbols. Mod may not function correctly."); - return FALSE; + if (!WindhawkUtils::HookSymbols(twinui, twinuiPcshellDllHooks, ARRAYSIZE(twinuiPcshellDllHooks))) { + Wh_Log(L"Dynamic Alt-Tab: Failed to hook twinui.pcshell.dll symbols. Falling back to aggressive ShowWindow blocking."); } } else { - Wh_Log(L"Dynamic Alt-Tab: twinui.pcshell.dll not found. System unsupported."); - return FALSE; + Wh_Log(L"Dynamic Alt-Tab: twinui.pcshell.dll not found. Falling back to aggressive ShowWindow blocking."); } if (!WindhawkUtils::SetFunctionHook((void*)ShowWindow, (void*)ShowWindow_Hook, (void**)&ShowWindow_Original)) { From ddf3f4dd3cac6b2a0f6bf81e91a91cc853d43030 Mon Sep 17 00:00:00 2001 From: TheatriChris <66391302+chrisc44890@users.noreply.github.com> Date: Fri, 10 Jul 2026 20:59:16 -0400 Subject: [PATCH 05/11] Update dynamic-alt-tab.wh.cpp Tried to kill two birds with one stone... The stone was unnafective, trying again --- mods/dynamic-alt-tab.wh.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mods/dynamic-alt-tab.wh.cpp b/mods/dynamic-alt-tab.wh.cpp index a925ad8bb6..19caa37fb5 100644 --- a/mods/dynamic-alt-tab.wh.cpp +++ b/mods/dynamic-alt-tab.wh.cpp @@ -1172,12 +1172,12 @@ BOOL Wh_ModInit() { LoadSettings(); HMODULE twinui = LoadLibraryW(L"twinui.pcshell.dll"); if (twinui) { - WindhawkUtils::SYMBOL_HOOK twinuiPcshellDllHooks[] = { + WindhawkUtils::SYMBOL_HOOK twinui_pcshell_dll_hooks[] = { { {LR"(public: virtual long __cdecl XamlAltTabViewHost::ViewLoaded(void))"}, &XamlAltTabViewHost_ViewLoaded_Original, XamlAltTabViewHost_ViewLoaded_Hook, false }, { {LR"(private: void __cdecl XamlAltTabViewHost::DisplayAltTab(void))"}, &XamlAltTabViewHost_DisplayAltTab_Original, XamlAltTabViewHost_DisplayAltTab_Hook, false }, { {LR"(public: virtual long __cdecl CAltTabViewHost::Show(struct IImmersiveMonitor *,enum ALT_TAB_VIEW_FLAGS,struct IApplicationView *))"}, &CAltTabViewHost_Show_Original, CAltTabViewHost_Show_Hook, false } }; - if (!WindhawkUtils::HookSymbols(twinui, twinuiPcshellDllHooks, ARRAYSIZE(twinuiPcshellDllHooks))) { + if (!WindhawkUtils::HookSymbols(twinui, twinui_pcshell_dll_hooks, ARRAYSIZE(twinui_pcshell_dll_hooks))) { Wh_Log(L"Dynamic Alt-Tab: Failed to hook twinui.pcshell.dll symbols. Falling back to aggressive ShowWindow blocking."); } } else { From 419ae0d9292c36c9846b84fa4b11a87b4b1f0f40 Mon Sep 17 00:00:00 2001 From: TheatriChris <66391302+chrisc44890@users.noreply.github.com> Date: Fri, 10 Jul 2026 21:05:35 -0400 Subject: [PATCH 06/11] Update dynamic-alt-tab.wh.cpp Save me Claude --- mods/dynamic-alt-tab.wh.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/mods/dynamic-alt-tab.wh.cpp b/mods/dynamic-alt-tab.wh.cpp index 19caa37fb5..a0f5587ce4 100644 --- a/mods/dynamic-alt-tab.wh.cpp +++ b/mods/dynamic-alt-tab.wh.cpp @@ -1170,14 +1170,15 @@ BOOL WINAPI ShowWindow_Hook(HWND hWnd, int nCmdShow) { BOOL Wh_ModInit() { LoadSettings(); - HMODULE twinui = LoadLibraryW(L"twinui.pcshell.dll"); - if (twinui) { - WindhawkUtils::SYMBOL_HOOK twinui_pcshell_dll_hooks[] = { - { {LR"(public: virtual long __cdecl XamlAltTabViewHost::ViewLoaded(void))"}, &XamlAltTabViewHost_ViewLoaded_Original, XamlAltTabViewHost_ViewLoaded_Hook, false }, - { {LR"(private: void __cdecl XamlAltTabViewHost::DisplayAltTab(void))"}, &XamlAltTabViewHost_DisplayAltTab_Original, XamlAltTabViewHost_DisplayAltTab_Hook, false }, - { {LR"(public: virtual long __cdecl CAltTabViewHost::Show(struct IImmersiveMonitor *,enum ALT_TAB_VIEW_FLAGS,struct IApplicationView *))"}, &CAltTabViewHost_Show_Original, CAltTabViewHost_Show_Hook, false } - }; - if (!WindhawkUtils::HookSymbols(twinui, twinui_pcshell_dll_hooks, ARRAYSIZE(twinui_pcshell_dll_hooks))) { + HMODULE twinui = LoadLibraryW(L"twinui.pcshell.dll"); + if (twinui) { + // twinui.pcshell.dll + WindhawkUtils::SYMBOL_HOOK twinuiPcshellHooks[] = { + { {LR"(public: virtual long __cdecl XamlAltTabViewHost::ViewLoaded(void))"}, &XamlAltTabViewHost_ViewLoaded_Original, XamlAltTabViewHost_ViewLoaded_Hook, false }, + { {LR"(private: void __cdecl XamlAltTabViewHost::DisplayAltTab(void))"}, &XamlAltTabViewHost_DisplayAltTab_Original, XamlAltTabViewHost_DisplayAltTab_Hook, false }, + { {LR"(public: virtual long __cdecl CAltTabViewHost::Show(struct IImmersiveMonitor *,enum ALT_TAB_VIEW_FLAGS,struct IApplicationView *))"}, &CAltTabViewHost_Show_Original, CAltTabViewHost_Show_Hook, false } + }; + if (!WindhawkUtils::HookSymbols(twinui, twinuiPcshellHooks, ARRAYSIZE(twinuiPcshellHooks))) { Wh_Log(L"Dynamic Alt-Tab: Failed to hook twinui.pcshell.dll symbols. Falling back to aggressive ShowWindow blocking."); } } else { From 85698da5698fd75a0e37622ef43b3cb503e2cd6e Mon Sep 17 00:00:00 2001 From: TheatriChris <66391302+chrisc44890@users.noreply.github.com> Date: Sat, 11 Jul 2026 19:24:08 -0400 Subject: [PATCH 07/11] Update dynamic-alt-tab.wh.cpp I think this may have done the trick, also added some checks to prevent the interface from drawing on top of itself. --- mods/dynamic-alt-tab.wh.cpp | 176 ++++++++++++++++++++++++++++-------- 1 file changed, 137 insertions(+), 39 deletions(-) diff --git a/mods/dynamic-alt-tab.wh.cpp b/mods/dynamic-alt-tab.wh.cpp index a0f5587ce4..e6eba782f6 100644 --- a/mods/dynamic-alt-tab.wh.cpp +++ b/mods/dynamic-alt-tab.wh.cpp @@ -5,7 +5,7 @@ // @version 1.0 // @author TheatriChris // @github https://github.com/chrisc44890 -// @include * +// @include explorer.exe // @compilerOptions -ld2d1 -ldwmapi -lole32 -lgdi32 -lshell32 -ldwrite -lversion -luuid // ==/WindhawkMod== @@ -14,13 +14,18 @@ # Dynamic Alt-Tab Replaces the native Windows Alt-Tab screen with a fluid, hardware-accelerated live glass carousel and custom themes. -**Note:** This mod relies on internal Windows 11 APIs (`twinui.pcshell.dll`) and is designed specifically for Windows 11 and tested on version 25H2. It may not hook successfully on Windows 10 or unsupported Insider builds. +**Note:** This mod prefers to trigger off internal Windows 11 shell APIs +(`twinui.pcshell.dll`), which is the most reliable method and is immune to +focus-related quirks with elevated windows. On Windows builds where those +internal symbols aren't available, the mod automatically falls back to a +low-level keyboard hook instead; in that fallback mode, the carousel may +occasionally fail to appear while an elevated (Run as Administrator) window +is focused, due to Windows' UIPI input isolation between integrity levels. ### Features * **Hardware Accelerated:** Built with Direct2D 1.1 for buttery smooth 60FPS rendering. * **Live Thumbnails:** Uses DWM thumbnail routing to show real-time, live previews of your apps. -* **Custom Themes:** - * *3D Glass Carousel:* A gorgeous spatial rotating cylinder with physical depth shadows. +* **Custom Themes:** * *3D Glass Carousel:* A gorgeous spatial rotating cylinder with physical depth shadows. ![3D Carousel](https://i.imgur.com/n8FrdUV.gif) * *macOS Style Flat Grid:* An Exposé-style grid with clean, silver-aluminum highlights. ![MacOS](https://i.imgur.com/no7EoWE.gif) @@ -60,6 +65,9 @@ Replaces the native Windows Alt-Tab screen with a fluid, hardware-accelerated li #include #include +#define WM_ALTTAB_TRIGGER (WM_APP + 101) +#define WM_ALTTAB_CANCEL (WM_APP + 102) + typedef UINT (WINAPI* timeBeginPeriod_t)(UINT); typedef UINT (WINAPI* timeEndPeriod_t)(UINT); @@ -80,10 +88,11 @@ typedef BOOL(WINAPI* SetWindowCompositionAttribute_t)(HWND, CompositionAttribute HWND g_overlayHwnd = NULL; HANDLE g_renderThreadHandle = NULL; +std::atomic g_threadRunning(false); + HANDLE g_hookThreadHandle = NULL; DWORD g_hookThreadId = 0; HHOOK g_keyboardHook = NULL; -std::atomic g_threadRunning(false); ID2D1Factory1* g_pD2DFactory = nullptr; ID2D1DCRenderTarget* g_pDCRenderTarget = nullptr; @@ -251,7 +260,25 @@ D2D1_COLOR_F GetM3Color(int index, float alpha) { return D2D1::ColorF(0.48f, 0.72f, 0.92f, alpha); // Soft Sky Blue } +void ForceForegroundOverlay() { + HWND curFg = GetForegroundWindow(); + DWORD fgThread = curFg ? GetWindowThreadProcessId(curFg, NULL) : 0; + DWORD myThread = GetCurrentThreadId(); + + if (fgThread != 0 && fgThread != myThread) { + AttachThreadInput(fgThread, myThread, TRUE); + SetForegroundWindow(g_overlayHwnd); + SetFocus(g_overlayHwnd); + AttachThreadInput(fgThread, myThread, FALSE); + } else { + SetForegroundWindow(g_overlayHwnd); + SetFocus(g_overlayHwnd); + } +} + void StartAltTab() { + if (g_isAltTabbing) return; + g_isAltTabbing = true; g_isClosing = false; g_isFirstFrame = true; @@ -295,6 +322,7 @@ void StartAltTab() { g_rightPoolVx1 = 0.0f; g_rightPoolVx2 = 0.0f; g_rightPoolVy = 0.0f; g_rightPoolVScale = 0.0f; ShowWindow(g_overlayHwnd, SW_SHOWNA); + ForceForegroundOverlay(); } void StopAltTab() { @@ -335,6 +363,26 @@ void StopAltTab() { } } +// Global debounced trigger to handle both native hooks and fallback hooks gracefully +void TriggerOpenIfOutermost(bool shiftDown) { + static DWORD lastTriggerTime = 0; + DWORD now = GetTickCount(); + if (now - lastTriggerTime < 50) return; // 50ms debounce + lastTriggerTime = now; + + if (!g_wantsToOpen.load()) { + g_wantsToOpen = true; + g_tabSteps = shiftDown ? -1 : 1; + } else { + g_tabSteps.fetch_add(shiftDown ? -1 : 1); + } +} + +// --------------------------------------------------------------------------- +// Fallback trigger path: low-level keyboard hook. +// IPC via PostMessage lets high-integrity processes (Windhawk) alert +// the medium-integrity process (Explorer) without spanning multiple overlays. +// --------------------------------------------------------------------------- LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { static bool s_tabDown = false; if (nCode == HC_ACTION) { @@ -345,9 +393,9 @@ LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) { if (!s_tabDown) { s_tabDown = true; - g_wantsToOpen = true; bool shiftDown = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0; - g_tabSteps.fetch_add(shiftDown ? -1 : 1); + HWND overlay = FindWindowW(L"GlassAltTabOverlayClass", L"GlassAltTabOverlay"); + if (overlay) PostMessageW(overlay, WM_ALTTAB_TRIGGER, shiftDown ? 1 : 0, 0); } } else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) { s_tabDown = false; @@ -356,7 +404,8 @@ LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { } if ((p->vkCode == VK_LMENU || p->vkCode == VK_RMENU || p->vkCode == VK_MENU) && (wParam == WM_KEYUP || wParam == WM_SYSKEYUP)) { - g_wantsToOpen = false; + HWND overlay = FindWindowW(L"GlassAltTabOverlayClass", L"GlassAltTabOverlay"); + if (overlay) PostMessageW(overlay, WM_ALTTAB_CANCEL, 0, 0); s_tabDown = false; } } @@ -364,7 +413,7 @@ LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { } DWORD WINAPI HookThreadProc(LPVOID lpParam) { - HANDLE hEvent = (LPVOID)lpParam; + HANDLE hEvent = (HANDLE)lpParam; g_keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), 0); if (hEvent) SetEvent(hEvent); @@ -378,10 +427,39 @@ DWORD WINAPI HookThreadProc(LPVOID lpParam) { } LRESULT CALLBACK OverlayWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + // Inter-Process Communication handler + if (uMsg == WM_ALTTAB_TRIGGER) { + TriggerOpenIfOutermost(wParam == 1); + return 0; + } + if (uMsg == WM_ALTTAB_CANCEL) { + g_wantsToOpen = false; + return 0; + } + if (uMsg == WM_MOUSEMOVE) { g_mousePos.x = (int)(short)LOWORD(lParam); g_mousePos.y = (int)(short)HIWORD(lParam); } + + if ((uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN) && g_isAltTabbing && !g_isClosing) { + if (wParam == VK_TAB) { + bool shiftDown = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0; + g_tabSteps.fetch_add(shiftDown ? -1 : 1); + return 0; + } + if (wParam == VK_ESCAPE) { + g_cancelAltTab = true; + g_wantsToOpen = false; + return 0; + } + } + + if ((uMsg == WM_KEYUP || uMsg == WM_SYSKEYUP) && + (wParam == VK_MENU || wParam == VK_LMENU || wParam == VK_RMENU)) { + g_wantsToOpen = false; + return 0; + } if (uMsg == WM_MOUSEWHEEL && g_isAltTabbing && !g_isClosing) { int zDelta = (short)HIWORD(wParam); @@ -545,6 +623,7 @@ VOID CALLBACK RenderTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTi g_pDCRenderTarget->BindDC(g_hdcMem, &rc); g_pDCRenderTarget->BeginDraw(); + // Use 0.01f to prevent DWM from discarding click events due to WS_EX_LAYERED perfect transparency g_pDCRenderTarget->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.01f)); if (g_pDimBrush) { @@ -1137,6 +1216,7 @@ void WINAPI XamlAltTabViewHost_ViewLoaded_Hook(void* pThis) { using XamlAltTabViewHost_DisplayAltTab_t = void(WINAPI*)(void*); XamlAltTabViewHost_DisplayAltTab_t XamlAltTabViewHost_DisplayAltTab_Original; void WINAPI XamlAltTabViewHost_DisplayAltTab_Hook(void* pThis) { + TriggerOpenIfOutermost(false); if (g_threadIdForAltTabShowWindow != 0) return XamlAltTabViewHost_DisplayAltTab_Original(pThis); AutoThreadId lock; XamlAltTabViewHost_DisplayAltTab_Original(pThis); @@ -1145,6 +1225,7 @@ void WINAPI XamlAltTabViewHost_DisplayAltTab_Hook(void* pThis) { using CAltTabViewHost_Show_t = HRESULT(WINAPI*)(void*, void*, void*, void*); CAltTabViewHost_Show_t CAltTabViewHost_Show_Original; HRESULT WINAPI CAltTabViewHost_Show_Hook(void* pThis, void* p1, void* p2, void* p3) { + TriggerOpenIfOutermost(false); AutoThreadId lock; return CAltTabViewHost_Show_Original(pThis, p1, p2, p3); } @@ -1153,7 +1234,7 @@ using ShowWindow_t = decltype(&ShowWindow); ShowWindow_t ShowWindow_Original; BOOL WINAPI ShowWindow_Hook(HWND hWnd, int nCmdShow) { if (nCmdShow != SW_HIDE) { - if (g_threadIdForAltTabShowWindow == GetCurrentThreadId() || g_wantsToOpen.load() || g_isAltTabbing) { + if (g_threadIdForAltTabShowWindow == GetCurrentThreadId() || g_wantsToOpen.load() || g_isAltTabbing || g_isClosing) { wchar_t className[256]; if (GetClassNameW(hWnd, className, 256)) { if (wcscmp(className, L"XamlExplorerHostIslandWindow") == 0 || @@ -1170,39 +1251,56 @@ BOOL WINAPI ShowWindow_Hook(HWND hWnd, int nCmdShow) { BOOL Wh_ModInit() { LoadSettings(); - HMODULE twinui = LoadLibraryW(L"twinui.pcshell.dll"); - if (twinui) { - // twinui.pcshell.dll - WindhawkUtils::SYMBOL_HOOK twinuiPcshellHooks[] = { - { {LR"(public: virtual long __cdecl XamlAltTabViewHost::ViewLoaded(void))"}, &XamlAltTabViewHost_ViewLoaded_Original, XamlAltTabViewHost_ViewLoaded_Hook, false }, - { {LR"(private: void __cdecl XamlAltTabViewHost::DisplayAltTab(void))"}, &XamlAltTabViewHost_DisplayAltTab_Original, XamlAltTabViewHost_DisplayAltTab_Hook, false }, - { {LR"(public: virtual long __cdecl CAltTabViewHost::Show(struct IImmersiveMonitor *,enum ALT_TAB_VIEW_FLAGS,struct IApplicationView *))"}, &CAltTabViewHost_Show_Original, CAltTabViewHost_Show_Hook, false } - }; - if (!WindhawkUtils::HookSymbols(twinui, twinuiPcshellHooks, ARRAYSIZE(twinuiPcshellHooks))) { - Wh_Log(L"Dynamic Alt-Tab: Failed to hook twinui.pcshell.dll symbols. Falling back to aggressive ShowWindow blocking."); - } - } else { - Wh_Log(L"Dynamic Alt-Tab: twinui.pcshell.dll not found. Falling back to aggressive ShowWindow blocking."); - } - - if (!WindhawkUtils::SetFunctionHook((void*)ShowWindow, (void*)ShowWindow_Hook, (void**)&ShowWindow_Original)) { - Wh_Log(L"Dynamic Alt-Tab: Failed to hook ShowWindow. Mod may not function correctly."); - return FALSE; + + WCHAR exeName[MAX_PATH]; + GetModuleFileNameW(NULL, exeName, MAX_PATH); + _wcslwr_s(exeName); + + bool isExplorer = (wcsstr(exeName, L"explorer.exe") != NULL); + bool isWindhawk = (wcsstr(exeName, L"windhawk.exe") != NULL); + + + if (!isExplorer && !isWindhawk) { + return TRUE; } - HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - if (!hEvent) return FALSE; - - g_threadRunning = true; - g_renderThreadHandle = CreateThread(NULL, 0, OverlayThreadProc, hEvent, 0, NULL); - if (!g_renderThreadHandle) { + if (isExplorer) { + HMODULE twinui = LoadLibraryW(L"twinui.pcshell.dll"); + if (twinui) { + // twinui.pcshell.dll + WindhawkUtils::SYMBOL_HOOK hooks[] = { + { {LR"(public: virtual long __cdecl XamlAltTabViewHost::ViewLoaded(void))"}, &XamlAltTabViewHost_ViewLoaded_Original, XamlAltTabViewHost_ViewLoaded_Hook, false }, + { {LR"(private: void __cdecl XamlAltTabViewHost::DisplayAltTab(void))"}, &XamlAltTabViewHost_DisplayAltTab_Original, XamlAltTabViewHost_DisplayAltTab_Hook, false }, + { {LR"(public: virtual long __cdecl CAltTabViewHost::Show(struct IImmersiveMonitor *,enum ALT_TAB_VIEW_FLAGS,struct IApplicationView *))"}, &CAltTabViewHost_Show_Original, CAltTabViewHost_Show_Hook, false } + }; + if (!WindhawkUtils::HookSymbols(twinui, hooks, ARRAYSIZE(hooks))) { + Wh_Log(L"Dynamic Alt-Tab: Failed to hook twinui.pcshell.dll symbols. Falling back to keyboard hook trigger."); + } + } else { + Wh_Log(L"Dynamic Alt-Tab: twinui.pcshell.dll not found on this build. Falling back to keyboard hook trigger."); + } + + if (!WindhawkUtils::SetFunctionHook((void*)ShowWindow, (void*)ShowWindow_Hook, (void**)&ShowWindow_Original)) { + Wh_Log(L"Dynamic Alt-Tab: Failed to hook ShowWindow. Mod may not function correctly."); + return FALSE; + } + + HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!hEvent) return FALSE; + + g_threadRunning = true; + g_renderThreadHandle = CreateThread(NULL, 0, OverlayThreadProc, hEvent, 0, NULL); + if (!g_renderThreadHandle) { + CloseHandle(hEvent); + return FALSE; + } + + WaitForSingleObject(hEvent, 3000); CloseHandle(hEvent); - return FALSE; } - - WaitForSingleObject(hEvent, 3000); - CloseHandle(hEvent); - + + + // The keys are piped instantly to the Explorer overlay via PostMessage. HANDLE hHookEvent = CreateEvent(NULL, TRUE, FALSE, NULL); g_hookThreadHandle = CreateThread(NULL, 0, HookThreadProc, hHookEvent, 0, &g_hookThreadId); if (g_hookThreadHandle) { From f1d695ddf46a476cc5bf8a2090e54af6f9ae54cd Mon Sep 17 00:00:00 2001 From: TheatriChris <66391302+chrisc44890@users.noreply.github.com> Date: Sat, 11 Jul 2026 19:36:41 -0400 Subject: [PATCH 08/11] Update dynamic-alt-tab.wh.cpp --- mods/dynamic-alt-tab.wh.cpp | 105 ++++++++++++++++++++++-------------- 1 file changed, 66 insertions(+), 39 deletions(-) diff --git a/mods/dynamic-alt-tab.wh.cpp b/mods/dynamic-alt-tab.wh.cpp index e6eba782f6..5e4d908a9b 100644 --- a/mods/dynamic-alt-tab.wh.cpp +++ b/mods/dynamic-alt-tab.wh.cpp @@ -5,7 +5,7 @@ // @version 1.0 // @author TheatriChris // @github https://github.com/chrisc44890 -// @include explorer.exe +// @include * // @compilerOptions -ld2d1 -ldwmapi -lole32 -lgdi32 -lshell32 -ldwrite -lversion -luuid // ==/WindhawkMod== @@ -118,6 +118,7 @@ bool g_isClosing = false; bool g_isFirstFrame = false; int g_selectedIndex = 0; POINT g_mousePos = { 0, 0 }; +HWND g_targetHwnd = NULL; float g_introOutroProgress = 0.0f; float g_introOutroTarget = 0.0f; @@ -285,6 +286,7 @@ void StartAltTab() { g_introOutroProgress = 0.0f; g_introOutroTarget = 1.0f; g_cancelAltTab = false; + g_targetHwnd = NULL; for (auto& card : g_cards) { if (card.thumbnail) DwmUnregisterThumbnail(card.thumbnail); @@ -330,37 +332,15 @@ void StopAltTab() { g_isClosing = true; g_introOutroTarget = 0.0f; - if (g_cancelAltTab) { - keybd_event(VK_MENU, 0, KEYEVENTF_KEYUP, 0); + // Release the physical Alt key immediately so it doesn't get stuck during the animation + keybd_event(VK_MENU, 0, KEYEVENTF_KEYUP, 0); + + if (g_cancelAltTab || g_cards.empty() || g_selectedIndex < 0 || g_selectedIndex >= (int)g_cards.size()) { + g_targetHwnd = NULL; return; } - if (!g_cards.empty() && g_selectedIndex >= 0 && g_selectedIndex < (int)g_cards.size()) { - HWND target = g_cards[g_selectedIndex].hwnd; - - if (IsIconic(target)) ShowWindow(target, SW_RESTORE); - - keybd_event(VK_MENU, 0, KEYEVENTF_KEYUP, 0); - - SetForegroundWindow(g_overlayHwnd); - - DWORD fgThread = GetWindowThreadProcessId(GetForegroundWindow(), NULL); - DWORD targetThread = GetWindowThreadProcessId(target, NULL); - - if (fgThread != targetThread) { - AttachThreadInput(fgThread, targetThread, TRUE); - SetWindowPos(target, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); - SetWindowPos(target, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); - SetForegroundWindow(target); - SetFocus(target); - AttachThreadInput(fgThread, targetThread, FALSE); - } else { - SetForegroundWindow(target); - SetFocus(target); - } - - SwitchToThisWindow(target, TRUE); - } + g_targetHwnd = g_cards[g_selectedIndex].hwnd; } // Global debounced trigger to handle both native hooks and fallback hooks gracefully @@ -483,7 +463,10 @@ LRESULT CALLBACK OverlayWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lPar std::vector clickOrder(g_cards.size()); for (int i = 0; i < (int)g_cards.size(); ++i) clickOrder[i] = i; std::sort(clickOrder.begin(), clickOrder.end(), [](int a, int b) { - return fabsf((float)a - (float)g_selectedIndex) < fabsf((float)b - (float)g_selectedIndex); + float distA = fabsf((float)a - (float)g_selectedIndex); + float distB = fabsf((float)b - (float)g_selectedIndex); + if (distA == distB) return a < b; // Stable tie breaker for perfectly overlapping Z-indexes + return distA < distB; // Sort front-to-back }); for (int i : clickOrder) { @@ -560,6 +543,29 @@ VOID CALLBACK RenderTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTi g_cards.clear(); ShowWindow(hwnd, SW_HIDE); ReleaseDC(NULL, hdcScreen); + + // NOW we actually switch the window, after the GPU is done with our animation + if (g_targetHwnd) { + if (IsIconic(g_targetHwnd)) ShowWindow(g_targetHwnd, SW_RESTORE); + + DWORD fgThread = GetWindowThreadProcessId(GetForegroundWindow(), NULL); + DWORD targetThread = GetWindowThreadProcessId(g_targetHwnd, NULL); + + if (fgThread != targetThread) { + AttachThreadInput(fgThread, targetThread, TRUE); + SetWindowPos(g_targetHwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + SetWindowPos(g_targetHwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); + SetForegroundWindow(g_targetHwnd); + SetFocus(g_targetHwnd); + AttachThreadInput(fgThread, targetThread, FALSE); + } else { + SetForegroundWindow(g_targetHwnd); + SetFocus(g_targetHwnd); + } + + SwitchToThisWindow(g_targetHwnd, TRUE); + g_targetHwnd = NULL; + } return; } @@ -873,9 +879,29 @@ VOID CALLBACK RenderTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTi std::sort(renderOrder.begin(), renderOrder.end(), [](int a, int b) { float distA = fabsf((float)a - (float)g_selectedIndex); float distB = fabsf((float)b - (float)g_selectedIndex); - return distA > distB; + if (distA == distB) return a > b; // Stable tie breaker + return distA > distB; // Render back-to-front }); + // Pre-calculate the absolute top-most card that the mouse is hovering over + // so we don't accidentally hover 4 overlapping cards at once in the 3D carousel + int topHoveredIndex = -1; + for (auto it = renderOrder.rbegin(); it != renderOrder.rend(); ++it) { + int i = *it; + AppCard& card = g_cards[i]; + float squishX = (g_theme == 3) ? card.vx * 0.16f : 0.0f; + float cardW = (card.currentWidthRadius + squishX) * card.currentScale; + float cardH = 380.0f * card.currentScale; + + if (g_mousePos.x >= (card.currentX - cardW) && + g_mousePos.x <= (card.currentX + cardW) && + g_mousePos.y >= (card.currentY - cardH / 2.0f) && + g_mousePos.y <= (card.currentY + cardH / 2.0f)) { + topHoveredIndex = i; + break; + } + } + for (int i : renderOrder) { AppCard& card = g_cards[i]; float offset = (float)i - (float)g_selectedIndex; @@ -894,10 +920,7 @@ VOID CALLBACK RenderTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTi float cardW = (card.currentWidthRadius + squishX) * card.currentScale; float cardH = 380.0f * card.currentScale; - bool isHovered = (g_mousePos.x > (card.currentX - cardW) && - g_mousePos.x < (card.currentX + cardW) && - g_mousePos.y > (card.currentY - cardH / 2.0f) && - g_mousePos.y < (card.currentY + cardH / 2.0f)); + bool isHovered = (i == topHoveredIndex); if (isHovered && i != g_selectedIndex) { card.currentScale += (card.targetScale * 1.05f - card.currentScale) * 0.4f; @@ -923,7 +946,8 @@ VOID CALLBACK RenderTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTi thumbOpac = (i == g_selectedIndex ? 1.0f : 0.8f * depthDarken); textOpac = depthDarken * g_introOutroProgress; - g_pGlassBrush->SetColor(D2D1::ColorF(0.12f * depthDarken, 0.12f * depthDarken, 0.15f * depthDarken, 1.0f * g_introOutroProgress)); + // Deep solid charcoal for the 3D cards + g_pGlassBrush->SetColor(D2D1::ColorF(0.04f * depthDarken, 0.04f * depthDarken, 0.05f * depthDarken, 1.0f * g_introOutroProgress)); if (i == g_selectedIndex) { g_pBorderBrush->SetColor(D2D1::ColorF(0.85f, 0.35f, 0.95f, 1.0f * g_introOutroProgress)); } else { @@ -1259,12 +1283,14 @@ BOOL Wh_ModInit() { bool isExplorer = (wcsstr(exeName, L"explorer.exe") != NULL); bool isWindhawk = (wcsstr(exeName, L"windhawk.exe") != NULL); - + // If we're inside a random game/browser, bail out instantly to save resources. + // The explorer and windhawk hooks are enough to catch elevated and non-elevated keys. if (!isExplorer && !isWindhawk) { return TRUE; } if (isExplorer) { + // EXPLORER ONLY: We only want ONE overlay window rendering at a time! HMODULE twinui = LoadLibraryW(L"twinui.pcshell.dll"); if (twinui) { // twinui.pcshell.dll @@ -1299,8 +1325,9 @@ BOOL Wh_ModInit() { CloseHandle(hEvent); } - - // The keys are piped instantly to the Explorer overlay via PostMessage. + // EXPLORER & WINDHAWK: We install the keyboard hook so we can catch keys in + // Medium Integrity (Explorer) AND High Integrity (Windhawk). + // The keys are piped instantly to the Explorer overlay via PostMessage! HANDLE hHookEvent = CreateEvent(NULL, TRUE, FALSE, NULL); g_hookThreadHandle = CreateThread(NULL, 0, HookThreadProc, hHookEvent, 0, &g_hookThreadId); if (g_hookThreadHandle) { From f9a26c6e4c2dec5b6c423302f8fa1296d04810bb Mon Sep 17 00:00:00 2001 From: TheatriChris <66391302+chrisc44890@users.noreply.github.com> Date: Sat, 11 Jul 2026 20:10:16 -0400 Subject: [PATCH 09/11] Update dynamic-alt-tab.wh.cpp --- mods/dynamic-alt-tab.wh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mods/dynamic-alt-tab.wh.cpp b/mods/dynamic-alt-tab.wh.cpp index 5e4d908a9b..dbb97466b1 100644 --- a/mods/dynamic-alt-tab.wh.cpp +++ b/mods/dynamic-alt-tab.wh.cpp @@ -5,7 +5,7 @@ // @version 1.0 // @author TheatriChris // @github https://github.com/chrisc44890 -// @include * +// @include explorer.exe // @compilerOptions -ld2d1 -ldwmapi -lole32 -lgdi32 -lshell32 -ldwrite -lversion -luuid // ==/WindhawkMod== From 56cf8098837c47b62078d2d62e3d63c998db4464 Mon Sep 17 00:00:00 2001 From: TheatriChris <66391302+chrisc44890@users.noreply.github.com> Date: Sun, 12 Jul 2026 09:32:14 -0400 Subject: [PATCH 10/11] Update dynamic-alt-tab.wh.cpp --- mods/dynamic-alt-tab.wh.cpp | 338 +++++++++++++++++++++++------------- 1 file changed, 220 insertions(+), 118 deletions(-) diff --git a/mods/dynamic-alt-tab.wh.cpp b/mods/dynamic-alt-tab.wh.cpp index dbb97466b1..363ab57846 100644 --- a/mods/dynamic-alt-tab.wh.cpp +++ b/mods/dynamic-alt-tab.wh.cpp @@ -2,11 +2,11 @@ // @id dynamic-alt-tab // @name Dynamic Alt-Tab // @description Replaces the native Windows Alt-Tab with a fluid, hardware-accelerated live glass carousel and custom themes. -// @version 1.0 +// @version 1.3 // @author TheatriChris // @github https://github.com/chrisc44890 // @include explorer.exe -// @compilerOptions -ld2d1 -ldwmapi -lole32 -lgdi32 -lshell32 -ldwrite -lversion -luuid +// @compilerOptions -ld2d1 -ldwmapi -lole32 -lgdi32 -ldwrite // ==/WindhawkMod== // ==WindhawkModReadme== @@ -14,13 +14,18 @@ # Dynamic Alt-Tab Replaces the native Windows Alt-Tab screen with a fluid, hardware-accelerated live glass carousel and custom themes. -**Note:** This mod prefers to trigger off internal Windows 11 shell APIs -(`twinui.pcshell.dll`), which is the most reliable method and is immune to -focus-related quirks with elevated windows. On Windows builds where those -internal symbols aren't available, the mod automatically falls back to a -low-level keyboard hook instead; in that fallback mode, the carousel may -occasionally fail to appear while an elevated (Run as Administrator) window -is focused, due to Windows' UIPI input isolation between integrity levels. +**Note:** This mod triggers off the low-level keyboard hook, since Explorer +runs at Medium Integrity and cannot see input while an elevated +(Run as Administrator) window has focus - Windows' UIPI input isolation +blocks it. If the carousel doesn't appear while an elevated window is +focused, this is that limitation, not a bug. Internal Windows shell symbols +(`twinui.pcshell.dll`) are used opportunistically as a secondary trigger and +to suppress the native Alt-Tab UI, but availability varies by build and the +mod works without them. + +Only one running `explorer.exe` process (the one hosting the taskbar/shell) +owns the overlay and keyboard hook; any secondary Explorer window processes +stay inert to avoid duplicate overlays. ### Features * **Hardware Accelerated:** Built with Direct2D 1.1 for buttery smooth 60FPS rendering. @@ -63,6 +68,7 @@ is focused, due to Windows' UIPI input isolation between integrity levels. #include #include #include +#include #include #define WM_ALTTAB_TRIGGER (WM_APP + 101) @@ -86,7 +92,10 @@ struct CompositionAttributeData { typedef BOOL(WINAPI* SetWindowCompositionAttribute_t)(HWND, CompositionAttributeData*); -HWND g_overlayHwnd = NULL; +// g_overlayHwnd is read from the hook thread and from whichever thread +// Explorer calls its own alt-tab entry points on, and written once by the +// render thread - atomic for a well-defined cross-thread read. +std::atomic g_overlayHwnd(NULL); HANDLE g_renderThreadHandle = NULL; std::atomic g_threadRunning(false); @@ -96,7 +105,6 @@ HHOOK g_keyboardHook = NULL; ID2D1Factory1* g_pD2DFactory = nullptr; ID2D1DCRenderTarget* g_pDCRenderTarget = nullptr; -ID2D1DeviceContext* g_pDeviceContext = nullptr; IDWriteFactory* g_pDWriteFactory = nullptr; IDWriteTextFormat* g_pTextFormat = nullptr; @@ -131,6 +139,13 @@ std::atomic g_cancelAltTab(false); std::atomic g_tabSteps(0); std::atomic g_threadIdForAltTabShowWindow(0); +// High-resolution timer / 8ms render timer are only active while the +// carousel is open or animating closed - see StartAltTab / RenderTimerProc. +HMODULE g_hWinmm = NULL; +timeBeginPeriod_t g_pTimeBeginPeriod = nullptr; +timeEndPeriod_t g_pTimeEndPeriod = nullptr; +bool g_fastTimerActive = false; + float g_leftPoolX1 = 0.0f; float g_leftPoolX2 = 0.0f; float g_leftPoolY = 0.0f; @@ -183,6 +198,39 @@ void Wh_ModSettingsChanged() { LoadSettings(); } +// Only one running explorer.exe process should own the overlay and keyboard +// hook. Windows can run multiple explorer.exe processes at once (e.g. with +// "Launch folder windows in a separate process" enabled, or transient +// per-window processes); we identify the actual shell process - the one +// hosting the taskbar/desktop - instantly via PID or command line args. +bool IsMainShellProcess() { + HWND shellWnd = GetShellWindow(); + if (shellWnd) { + DWORD shellPid = 0; + GetWindowThreadProcessId(shellWnd, &shellPid); + return shellPid == GetCurrentProcessId(); + } + + // If the shell window isn't created yet (e.g. during a fresh boot or explorer restart), + // we check the command line. The main desktop shell does NOT use the /factory flag. + // Folder windows running in separate processes do. + int argc; + LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); + if (argv) { + for (int i = 1; i < argc; i++) { + std::wstring arg = argv[i]; + std::transform(arg.begin(), arg.end(), arg.begin(), ::towlower); + if (arg.find(L"/factory") != std::wstring::npos) { + LocalFree(argv); + return false; + } + } + LocalFree(argv); + } + + return true; +} + std::wstring GetWindowTextSafe(HWND hwnd) { wchar_t buf[256] = {0}; DWORD pid = 0; @@ -262,21 +310,41 @@ D2D1_COLOR_F GetM3Color(int index, float alpha) { } void ForceForegroundOverlay() { + HWND overlay = g_overlayHwnd.load(); HWND curFg = GetForegroundWindow(); DWORD fgThread = curFg ? GetWindowThreadProcessId(curFg, NULL) : 0; DWORD myThread = GetCurrentThreadId(); if (fgThread != 0 && fgThread != myThread) { AttachThreadInput(fgThread, myThread, TRUE); - SetForegroundWindow(g_overlayHwnd); - SetFocus(g_overlayHwnd); + SetForegroundWindow(overlay); + SetFocus(overlay); AttachThreadInput(fgThread, myThread, FALSE); } else { - SetForegroundWindow(g_overlayHwnd); - SetFocus(g_overlayHwnd); + SetForegroundWindow(overlay); + SetFocus(overlay); } } +VOID CALLBACK RenderTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime); + +// Starts (or resumes) the fast 8ms render timer and bumps the system timer +// resolution to 1ms - only while the carousel is actually visible/animating, +// not for the mod's whole lifetime. +void EnsureFastTimerRunning() { + if (g_fastTimerActive) return; + g_fastTimerActive = true; + if (g_pTimeBeginPeriod) g_pTimeBeginPeriod(1); + SetTimer(g_overlayHwnd.load(), 1, 8, RenderTimerProc); +} + +void StopFastTimer(HWND hwnd) { + if (!g_fastTimerActive) return; + g_fastTimerActive = false; + KillTimer(hwnd, 1); + if (g_pTimeEndPeriod) g_pTimeEndPeriod(1); +} + void StartAltTab() { if (g_isAltTabbing) return; @@ -302,7 +370,7 @@ void StartAltTab() { g_selectedIndex = 0; for (auto& card : g_cards) { - DwmRegisterThumbnail(g_overlayHwnd, card.hwnd, &card.thumbnail); + DwmRegisterThumbnail(g_overlayHwnd.load(), card.hwnd, &card.thumbnail); card.currentX = 0.0f; card.currentY = 0.0f; card.currentScale = 0.1f; @@ -323,8 +391,10 @@ void StartAltTab() { g_rightPoolScale = 0.0f; g_rightPoolVx1 = 0.0f; g_rightPoolVx2 = 0.0f; g_rightPoolVy = 0.0f; g_rightPoolVScale = 0.0f; - ShowWindow(g_overlayHwnd, SW_SHOWNA); + ShowWindow(g_overlayHwnd.load(), SW_SHOWNA); ForceForegroundOverlay(); + + EnsureFastTimerRunning(); } void StopAltTab() { @@ -343,39 +413,34 @@ void StopAltTab() { g_targetHwnd = g_cards[g_selectedIndex].hwnd; } -// Global debounced trigger to handle both native hooks and fallback hooks gracefully -void TriggerOpenIfOutermost(bool shiftDown) { - static DWORD lastTriggerTime = 0; - DWORD now = GetTickCount(); - if (now - lastTriggerTime < 50) return; // 50ms debounce - lastTriggerTime = now; - - if (!g_wantsToOpen.load()) { - g_wantsToOpen = true; - g_tabSteps = shiftDown ? -1 : 1; - } else { - g_tabSteps.fetch_add(shiftDown ? -1 : 1); - } -} - // --------------------------------------------------------------------------- -// Fallback trigger path: low-level keyboard hook. -// IPC via PostMessage lets high-integrity processes (Windhawk) alert -// the medium-integrity process (Explorer) without spanning multiple overlays. +// Low-level keyboard hook: the mod's primary trigger, since it works +// regardless of whether the twinui.pcshell.dll symbols resolve on this +// Windows build. Only swallows Alt+Tab when it can actually hand off to the +// overlay - if the overlay doesn't exist (init failed, still starting up, +// or mid-teardown), the real Windows Alt+Tab is left to run normally so the +// user is never left without ANY way to switch windows. // --------------------------------------------------------------------------- LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { static bool s_tabDown = false; if (nCode == HC_ACTION) { KBDLLHOOKSTRUCT* p = (KBDLLHOOKSTRUCT*)lParam; bool altDown = ((p->flags & LLKHF_ALTDOWN) != 0) || (GetAsyncKeyState(VK_MENU) & 0x8000) != 0; - + if (p->vkCode == VK_TAB && altDown) { + HWND overlay = g_overlayHwnd.load(); + if (!overlay) { + // No overlay to hand off to - let the native Alt+Tab through + // rather than eating the keystroke with nothing to show. + return CallNextHookEx(NULL, nCode, wParam, lParam); + } + if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) { if (!s_tabDown) { s_tabDown = true; + g_wantsToOpen = true; // Block native window instantly bool shiftDown = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0; - HWND overlay = FindWindowW(L"GlassAltTabOverlayClass", L"GlassAltTabOverlay"); - if (overlay) PostMessageW(overlay, WM_ALTTAB_TRIGGER, shiftDown ? 1 : 0, 0); + PostMessageW(overlay, WM_ALTTAB_TRIGGER, shiftDown ? 1 : 0, 0); } } else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) { s_tabDown = false; @@ -384,8 +449,11 @@ LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { } if ((p->vkCode == VK_LMENU || p->vkCode == VK_RMENU || p->vkCode == VK_MENU) && (wParam == WM_KEYUP || wParam == WM_SYSKEYUP)) { - HWND overlay = FindWindowW(L"GlassAltTabOverlayClass", L"GlassAltTabOverlay"); - if (overlay) PostMessageW(overlay, WM_ALTTAB_CANCEL, 0, 0); + HWND overlay = g_overlayHwnd.load(); + if (overlay) { + g_wantsToOpen = false; + PostMessageW(overlay, WM_ALTTAB_CANCEL, 0, 0); + } s_tabDown = false; } } @@ -406,8 +474,27 @@ DWORD WINAPI HookThreadProc(LPVOID lpParam) { return 0; } +// Single entry point for opening/cycling the carousel. Only ever called from +// the render thread (via WM_ALTTAB_TRIGGER in OverlayWndProc) since it may +// call StartAltTab(), which touches the overlay window, D2D resources, and +// DWM thumbnails - all of which should stay on the thread that created them. +void TriggerOpenIfOutermost(bool shiftDown) { + static DWORD lastTriggerTime = 0; + DWORD now = GetTickCount(); + if (now - lastTriggerTime < 50) return; // 50ms debounce + lastTriggerTime = now; + + g_wantsToOpen = true; + + if (!g_isAltTabbing) { + g_tabSteps.store(shiftDown ? -1 : 1); + StartAltTab(); + } else { + g_tabSteps.fetch_add(shiftDown ? -1 : 1); + } +} + LRESULT CALLBACK OverlayWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - // Inter-Process Communication handler if (uMsg == WM_ALTTAB_TRIGGER) { TriggerOpenIfOutermost(wParam == 1); return 0; @@ -513,6 +600,8 @@ LRESULT CALLBACK OverlayWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lPar VOID CALLBACK RenderTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) { bool wantsToOpen = g_wantsToOpen.load(); if (wantsToOpen && !g_isAltTabbing) { + // Defensive fallback only; the normal path calls StartAltTab() + // directly from WM_ALTTAB_TRIGGER the moment a press comes in. StartAltTab(); } else if (!wantsToOpen && g_isAltTabbing && !g_isClosing) { StopAltTab(); @@ -544,6 +633,10 @@ VOID CALLBACK RenderTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTi ShowWindow(hwnd, SW_HIDE); ReleaseDC(NULL, hdcScreen); + // Carousel is fully closed - stop the fast timer and drop the + // system timer resolution back down until the next Alt+Tab press. + StopFastTimer(hwnd); + // NOW we actually switch the window, after the GPU is done with our animation if (g_targetHwnd) { if (IsIconic(g_targetHwnd)) ShowWindow(g_targetHwnd, SW_RESTORE); @@ -580,7 +673,6 @@ VOID CALLBACK RenderTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTi if (g_pDCRenderTarget) { g_pDCRenderTarget->Release(); g_pDCRenderTarget = nullptr; - if (g_pDeviceContext) { g_pDeviceContext->Release(); g_pDeviceContext = nullptr; } } } @@ -591,10 +683,6 @@ VOID CALLBACK RenderTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTi 0, 0, D2D1_RENDER_TARGET_USAGE_NONE, D2D1_FEATURE_LEVEL_DEFAULT ); g_pD2DFactory->CreateDCRenderTarget(&props, &g_pDCRenderTarget); - - if (g_pDCRenderTarget) { - g_pDCRenderTarget->QueryInterface(__uuidof(ID2D1DeviceContext), (void**)&g_pDeviceContext); - } g_pDCRenderTarget->CreateSolidColorBrush(D2D1::ColorF(0.0f, 0.0f, 0.0f, 1.0f), &g_pGlassBrush); g_pDCRenderTarget->CreateSolidColorBrush(D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f), &g_pBorderBrush); @@ -641,8 +729,6 @@ VOID CALLBACK RenderTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTi for (int i = 0; i < (int)g_cards.size(); ++i) { AppCard& card = g_cards[i]; float offset = (float)i - (float)g_selectedIndex; - float distance = fabsf(offset); - (void)distance; if (g_theme == 1) { // macOS Flat Grid Style @@ -906,7 +992,6 @@ VOID CALLBACK RenderTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTi AppCard& card = g_cards[i]; float offset = (float)i - (float)g_selectedIndex; float distance = fabsf(offset); - (void)distance; float cardOpacity = g_introOutroProgress; if (g_theme != 1 && g_theme != 3 && g_theme != 0) { @@ -1096,7 +1181,6 @@ VOID CALLBACK RenderTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTi if (hr == D2DERR_RECREATE_TARGET) { g_pDCRenderTarget->Release(); g_pDCRenderTarget = nullptr; - if (g_pDeviceContext) { g_pDeviceContext->Release(); g_pDeviceContext = nullptr; } } } @@ -1150,20 +1234,21 @@ DWORD WINAPI OverlayThreadProc(LPVOID lpParam) { int screenW = GetSystemMetrics(SM_CXVIRTUALSCREEN); int screenH = GetSystemMetrics(SM_CYVIRTUALSCREEN); - g_overlayHwnd = CreateWindowEx( + HWND overlayHwnd = CreateWindowEx( WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE, CLASS_NAME, L"GlassAltTabOverlay", WS_POPUP, screenX, screenY, screenW, screenH, NULL, NULL, hInstance, NULL ); + g_overlayHwnd = overlayHwnd; - if (!g_overlayHwnd) { + if (!overlayHwnd) { if (hEvent) SetEvent(hEvent); return 0; } int backdrop = 3; - DwmSetWindowAttribute(g_overlayHwnd, 38, &backdrop, sizeof(backdrop)); + DwmSetWindowAttribute(overlayHwnd, 38, &backdrop, sizeof(backdrop)); HMODULE user32 = GetModuleHandleW(L"user32.dll"); if (user32) { @@ -1171,31 +1256,29 @@ DWORD WINAPI OverlayThreadProc(LPVOID lpParam) { if (setWindowCompositionAttribute) { AccentPolicy policy = { 4, 1, 0x90000000, 0 }; CompositionAttributeData data = { 19, &policy, sizeof(policy) }; - setWindowCompositionAttribute(g_overlayHwnd, &data); + setWindowCompositionAttribute(overlayHwnd, &data); } } if (hEvent) SetEvent(hEvent); - timeBeginPeriod_t pTimeBeginPeriod = nullptr; - timeEndPeriod_t pTimeEndPeriod = nullptr; - HMODULE hWinmm = LoadLibraryW(L"winmm.dll"); - if (hWinmm) { - pTimeBeginPeriod = (timeBeginPeriod_t)GetProcAddress(hWinmm, "timeBeginPeriod"); - pTimeEndPeriod = (timeEndPeriod_t)GetProcAddress(hWinmm, "timeEndPeriod"); + // Loaded once up front so timeBeginPeriod/timeEndPeriod are available + // whenever the carousel opens/closes, but the resolution bump itself is + // only applied for the duration of an open carousel (see + // EnsureFastTimerRunning / StopFastTimer). + g_hWinmm = LoadLibraryExW(L"winmm.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (g_hWinmm) { + g_pTimeBeginPeriod = (timeBeginPeriod_t)GetProcAddress(g_hWinmm, "timeBeginPeriod"); + g_pTimeEndPeriod = (timeEndPeriod_t)GetProcAddress(g_hWinmm, "timeEndPeriod"); } - if (pTimeBeginPeriod) pTimeBeginPeriod(1); - SetTimer(g_overlayHwnd, 1, 8, RenderTimerProc); - MSG msg; while (g_threadRunning && GetMessage(&msg, NULL, 0, 0)) { DispatchMessage(&msg); } - KillTimer(g_overlayHwnd, 1); - if (pTimeEndPeriod) pTimeEndPeriod(1); - if (hWinmm) FreeLibrary(hWinmm); + StopFastTimer(overlayHwnd); + if (g_hWinmm) FreeLibrary(g_hWinmm); for (auto& card : g_cards) { if (card.thumbnail) DwmUnregisterThumbnail(card.thumbnail); @@ -1210,8 +1293,6 @@ DWORD WINAPI OverlayThreadProc(LPVOID lpParam) { if (g_pDimBrush) { g_pDimBrush->Release(); g_pDimBrush = nullptr; } if (g_pWhiteBrush) { g_pWhiteBrush->Release(); g_pWhiteBrush = nullptr; } - if (g_pDeviceContext) { g_pDeviceContext->Release(); g_pDeviceContext = nullptr; } - if (g_pTextFormat) { g_pTextFormat->Release(); g_pTextFormat = nullptr; } if (g_pDWriteFactory) { g_pDWriteFactory->Release(); g_pDWriteFactory = nullptr; } if (g_pDCRenderTarget) { g_pDCRenderTarget->Release(); g_pDCRenderTarget = nullptr; } @@ -1219,7 +1300,8 @@ DWORD WINAPI OverlayThreadProc(LPVOID lpParam) { if (g_hBitmap) { DeleteObject(g_hBitmap); g_hBitmap = NULL; } if (g_hdcMem) { DeleteDC(g_hdcMem); g_hdcMem = NULL; } - DestroyWindow(g_overlayHwnd); + DestroyWindow(overlayHwnd); + g_overlayHwnd = NULL; UnregisterClass(CLASS_NAME, hInstance); CoUninitialize(); return 0; @@ -1230,6 +1312,11 @@ struct AutoThreadId { ~AutoThreadId() { g_threadIdForAltTabShowWindow = 0; } }; +// Secondary trigger path via Explorer's own internal shell APIs, used +// opportunistically when twinui.pcshell.dll's symbols resolve (see +// Wh_ModInit). These run on whatever thread Explorer calls them on, so - like +// the keyboard hook - they only ever post a message to the overlay rather +// than touching the overlay/D2D state directly. using XamlAltTabViewHost_ViewLoaded_t = void(WINAPI*)(void*); XamlAltTabViewHost_ViewLoaded_t XamlAltTabViewHost_ViewLoaded_Original; void WINAPI XamlAltTabViewHost_ViewLoaded_Hook(void* pThis) { @@ -1240,7 +1327,13 @@ void WINAPI XamlAltTabViewHost_ViewLoaded_Hook(void* pThis) { using XamlAltTabViewHost_DisplayAltTab_t = void(WINAPI*)(void*); XamlAltTabViewHost_DisplayAltTab_t XamlAltTabViewHost_DisplayAltTab_Original; void WINAPI XamlAltTabViewHost_DisplayAltTab_Hook(void* pThis) { - TriggerOpenIfOutermost(false); + HWND overlay = g_overlayHwnd.load(); + if (overlay) { + g_wantsToOpen = true; // Synchronously block native window + bool shiftDown = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0; + PostMessageW(overlay, WM_ALTTAB_TRIGGER, shiftDown ? 1 : 0, 0); + } + if (g_threadIdForAltTabShowWindow != 0) return XamlAltTabViewHost_DisplayAltTab_Original(pThis); AutoThreadId lock; XamlAltTabViewHost_DisplayAltTab_Original(pThis); @@ -1249,7 +1342,13 @@ void WINAPI XamlAltTabViewHost_DisplayAltTab_Hook(void* pThis) { using CAltTabViewHost_Show_t = HRESULT(WINAPI*)(void*, void*, void*, void*); CAltTabViewHost_Show_t CAltTabViewHost_Show_Original; HRESULT WINAPI CAltTabViewHost_Show_Hook(void* pThis, void* p1, void* p2, void* p3) { - TriggerOpenIfOutermost(false); + HWND overlay = g_overlayHwnd.load(); + if (overlay) { + g_wantsToOpen = true; // Synchronously block native window + bool shiftDown = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0; + PostMessageW(overlay, WM_ALTTAB_TRIGGER, shiftDown ? 1 : 0, 0); + } + AutoThreadId lock; return CAltTabViewHost_Show_Original(pThis, p1, p2, p3); } @@ -1276,63 +1375,66 @@ BOOL WINAPI ShowWindow_Hook(HWND hWnd, int nCmdShow) { BOOL Wh_ModInit() { LoadSettings(); - WCHAR exeName[MAX_PATH]; - GetModuleFileNameW(NULL, exeName, MAX_PATH); - _wcslwr_s(exeName); - - bool isExplorer = (wcsstr(exeName, L"explorer.exe") != NULL); - bool isWindhawk = (wcsstr(exeName, L"windhawk.exe") != NULL); - - // If we're inside a random game/browser, bail out instantly to save resources. - // The explorer and windhawk hooks are enough to catch elevated and non-elevated keys. - if (!isExplorer && !isWindhawk) { + if (!IsMainShellProcess()) { + // Not the primary shell explorer.exe (e.g. a secondary File Explorer + // window process spawned when "Launch folder windows in a separate + // process" is enabled, or any other transient explorer.exe). Only + // one process should own the overlay and global keyboard hook, so + // every other instance stays fully inert. return TRUE; } - if (isExplorer) { - // EXPLORER ONLY: We only want ONE overlay window rendering at a time! - HMODULE twinui = LoadLibraryW(L"twinui.pcshell.dll"); - if (twinui) { - // twinui.pcshell.dll - WindhawkUtils::SYMBOL_HOOK hooks[] = { - { {LR"(public: virtual long __cdecl XamlAltTabViewHost::ViewLoaded(void))"}, &XamlAltTabViewHost_ViewLoaded_Original, XamlAltTabViewHost_ViewLoaded_Hook, false }, - { {LR"(private: void __cdecl XamlAltTabViewHost::DisplayAltTab(void))"}, &XamlAltTabViewHost_DisplayAltTab_Original, XamlAltTabViewHost_DisplayAltTab_Hook, false }, - { {LR"(public: virtual long __cdecl CAltTabViewHost::Show(struct IImmersiveMonitor *,enum ALT_TAB_VIEW_FLAGS,struct IApplicationView *))"}, &CAltTabViewHost_Show_Original, CAltTabViewHost_Show_Hook, false } - }; - if (!WindhawkUtils::HookSymbols(twinui, hooks, ARRAYSIZE(hooks))) { - Wh_Log(L"Dynamic Alt-Tab: Failed to hook twinui.pcshell.dll symbols. Falling back to keyboard hook trigger."); - } - } else { - Wh_Log(L"Dynamic Alt-Tab: twinui.pcshell.dll not found on this build. Falling back to keyboard hook trigger."); - } - - if (!WindhawkUtils::SetFunctionHook((void*)ShowWindow, (void*)ShowWindow_Hook, (void**)&ShowWindow_Original)) { - Wh_Log(L"Dynamic Alt-Tab: Failed to hook ShowWindow. Mod may not function correctly."); - return FALSE; + // twinui.pcshell.dll is already loaded inside the real shell process by + // the time this runs, so GetModuleHandle is enough - no LoadLibrary/ + // FreeLibrary bookkeeping needed, and it sidesteps any DLL search-order + // surface entirely. + HMODULE twinui = GetModuleHandleW(L"twinui.pcshell.dll"); + if (twinui) { + // twinui.pcshell.dll + WindhawkUtils::SYMBOL_HOOK hooks[] = { + { {LR"(public: virtual long __cdecl XamlAltTabViewHost::ViewLoaded(void))"}, &XamlAltTabViewHost_ViewLoaded_Original, XamlAltTabViewHost_ViewLoaded_Hook, false }, + { {LR"(private: void __cdecl XamlAltTabViewHost::DisplayAltTab(void))"}, &XamlAltTabViewHost_DisplayAltTab_Original, XamlAltTabViewHost_DisplayAltTab_Hook, false }, + { {LR"(public: virtual long __cdecl CAltTabViewHost::Show(struct IImmersiveMonitor *,enum ALT_TAB_VIEW_FLAGS,struct IApplicationView *))"}, &CAltTabViewHost_Show_Original, CAltTabViewHost_Show_Hook, false } + }; + if (!WindhawkUtils::HookSymbols(twinui, hooks, ARRAYSIZE(hooks))) { + Wh_Log(L"Failed to hook twinui.pcshell.dll symbols; relying on the keyboard hook as the sole trigger."); } + } else { + Wh_Log(L"twinui.pcshell.dll not loaded in this process; relying on the keyboard hook as the sole trigger."); + } + + if (!WindhawkUtils::SetFunctionHook((void*)ShowWindow, (void*)ShowWindow_Hook, (void**)&ShowWindow_Original)) { + Wh_Log(L"Failed to hook ShowWindow. Mod cannot function."); + return FALSE; + } - HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - if (!hEvent) return FALSE; - - g_threadRunning = true; - g_renderThreadHandle = CreateThread(NULL, 0, OverlayThreadProc, hEvent, 0, NULL); - if (!g_renderThreadHandle) { - CloseHandle(hEvent); - return FALSE; - } - - WaitForSingleObject(hEvent, 3000); + HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!hEvent) return FALSE; + + g_threadRunning = true; + g_renderThreadHandle = CreateThread(NULL, 0, OverlayThreadProc, hEvent, 0, NULL); + if (!g_renderThreadHandle) { CloseHandle(hEvent); + return FALSE; } - - // EXPLORER & WINDHAWK: We install the keyboard hook so we can catch keys in - // Medium Integrity (Explorer) AND High Integrity (Windhawk). - // The keys are piped instantly to the Explorer overlay via PostMessage! + + WaitForSingleObject(hEvent, 3000); + CloseHandle(hEvent); + + // The keyboard hook is the mod's primary/only reliable trigger on builds + // where twinui.pcshell.dll's symbols don't resolve, and remains active + // as a redundant trigger even when they do (see TriggerOpenIfOutermost's + // debounce, which collapses duplicate fires from both paths). Note this + // only ever runs at Explorer's Medium Integrity level - see README for + // the elevated-window limitation that follows from that. HANDLE hHookEvent = CreateEvent(NULL, TRUE, FALSE, NULL); g_hookThreadHandle = CreateThread(NULL, 0, HookThreadProc, hHookEvent, 0, &g_hookThreadId); if (g_hookThreadHandle) { WaitForSingleObject(hHookEvent, 3000); CloseHandle(hHookEvent); + } else { + Wh_Log(L"Failed to start keyboard hook thread. Mod cannot function."); + return FALSE; } return TRUE; @@ -1346,8 +1448,8 @@ void Wh_ModUninit() { g_hookThreadHandle = NULL; } g_threadRunning = false; - if (g_overlayHwnd) { - PostMessage(g_overlayHwnd, WM_QUIT, 0, 0); + if (g_overlayHwnd.load()) { + PostMessage(g_overlayHwnd.load(), WM_QUIT, 0, 0); } if (g_renderThreadHandle) { WaitForSingleObject(g_renderThreadHandle, INFINITE); From 80d534dec0b0ad56980cce17e926e047337b04f4 Mon Sep 17 00:00:00 2001 From: TheatriChris <66391302+chrisc44890@users.noreply.github.com> Date: Sun, 12 Jul 2026 09:41:27 -0400 Subject: [PATCH 11/11] Update dynamic-alt-tab.wh.cpp --- mods/dynamic-alt-tab.wh.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mods/dynamic-alt-tab.wh.cpp b/mods/dynamic-alt-tab.wh.cpp index 363ab57846..3a61cff3b0 100644 --- a/mods/dynamic-alt-tab.wh.cpp +++ b/mods/dynamic-alt-tab.wh.cpp @@ -6,6 +6,7 @@ // @author TheatriChris // @github https://github.com/chrisc44890 // @include explorer.exe +// @include windhawk.exe // @compilerOptions -ld2d1 -ldwmapi -lole32 -lgdi32 -ldwrite // ==/WindhawkMod== @@ -27,6 +28,10 @@ Only one running `explorer.exe` process (the one hosting the taskbar/shell) owns the overlay and keyboard hook; any secondary Explorer window processes stay inert to avoid duplicate overlays. +There is a known issue with elevated processes when a user is logged off, the system is shut down, or explorer.exe restarts for any reason. +The default alt-tab interface will appear on first press of alt-tab. This can be fixed by disabling and enabling the mod again. +A fix is being looked into for the next version. + ### Features * **Hardware Accelerated:** Built with Direct2D 1.1 for buttery smooth 60FPS rendering. * **Live Thumbnails:** Uses DWM thumbnail routing to show real-time, live previews of your apps.