From 350f82b7f5076ccb9a1070a8da3f149cfb900af9 Mon Sep 17 00:00:00 2001 From: Michael Maltsev <4129781+m417z@users.noreply.github.com> Date: Sun, 3 Nov 2024 02:05:25 +0200 Subject: [PATCH] Taskbar auto-hide when maximized v1.1 (#1193) * Most code was rewritten, and the mod works in a slightly different way now - instead of switching auto-hide on and off, it's always left on, but the mod prevents the taskbar from hiding in certain conditions. That has benefits like a smoother transition between the states. * The taskbar now auto-hides when a window intersects it. This can be disabled in the options. * Auto-hiding can be suppressed altogether in the settings. In this case, maximized windows will be covered by the taskbar. This might be desirable if the taskbar is mostly transparent. --- mods/taskbar-auto-hide-when-maximized.wh.cpp | 893 +++++++++++++++++-- 1 file changed, 797 insertions(+), 96 deletions(-) diff --git a/mods/taskbar-auto-hide-when-maximized.wh.cpp b/mods/taskbar-auto-hide-when-maximized.wh.cpp index 9ea9e3950..42619ff5b 100644 --- a/mods/taskbar-auto-hide-when-maximized.wh.cpp +++ b/mods/taskbar-auto-hide-when-maximized.wh.cpp @@ -1,15 +1,15 @@ // ==WindhawkMod== // @id taskbar-auto-hide-when-maximized // @name Taskbar auto-hide when maximized -// @description Automatically hides the taskbar when a window is maximized, and brings it back when there are no maximized windows -// @version 1.0 +// @description When auto-hide is enabled, makes the taskbar auto-hide only when a window is maximized or intersects the taskbar +// @version 1.1 // @author m417z // @github https://github.com/m417z // @twitter https://twitter.com/m417z // @homepage https://m417z.com/ // @include explorer.exe // @architecture x86-64 -// @compilerOptions -ldwmapi +// @compilerOptions -ldwmapi -lversion // ==/WindhawkMod== // Source code is published under The GNU General Public License v3.0. @@ -24,18 +24,88 @@ /* # Taskbar auto-hide when maximized -Automatically hides the taskbar when a window is maximized, and brings it back -when there are no maximized windows. +When auto-hide is enabled, makes the taskbar auto-hide only when a window is +maximized or intersects the taskbar. + +**Note:** To customize the old taskbar on Windows 11 (if using ExplorerPatcher +or a similar tool), enable the relevant option in the mod's settings. + +![Demonstration](https://i.imgur.com/hEz1lhs.gif) */ // ==/WindhawkModReadme== -#include +// ==WindhawkModSettings== +/* +- mode: intersected + $name: Mode + $options: + - intersected: Auto-hide when a window is maximized or intersects the taskbar + - maximized: Auto-hide only when a window is maximized + - never: Never auto-hide +- oldTaskbarOnWin11: false + $name: Customize the old taskbar on Windows 11 + $description: >- + Enable this option to customize the old taskbar on Windows 11 (if using + ExplorerPatcher or a similar tool). +*/ +// ==/WindhawkModSettings== + +#include #include - -HANDLE g_winObjectLocationChangeThread; -UINT_PTR g_pendingLocationChangeTimer; -std::optional g_wasAutoHideSet; +#include + +#include +#include +#include +#include + +enum class Mode { + intersected, + maximized, + never, +}; + +struct { + Mode mode; + bool oldTaskbarOnWin11; +} g_settings; + +enum class WinVersion { + Unsupported, + Win10, + Win11, + Win11_24H2, +}; + +WinVersion g_winVersion; + +std::atomic g_initialized; +std::atomic g_explorerPatcherInitialized; + +std::mutex g_winEventHookThreadMutex; +std::atomic g_winEventHookThread; +std::unordered_map g_alwaysShowTaskbars; +UINT_PTR g_pendingEventsTimer; + +static const UINT g_getTaskbarRectRegisteredMsg = + RegisterWindowMessage(L"Windhawk_GetTaskbarRect_" WH_MOD_ID); + +static const UINT g_updateTaskbarStateRegisteredMsg = + RegisterWindowMessage(L"Windhawk_UpdateTaskbarState_" WH_MOD_ID); + +enum { + kTrayUITimerHide = 2, + kTrayUITimerUnhide = 3, +}; + +// Missing in older MinGW headers. +#ifndef EVENT_OBJECT_CLOAKED +#define EVENT_OBJECT_CLOAKED 0x8017 +#endif +#ifndef EVENT_OBJECT_UNCLOAKED +#define EVENT_OBJECT_UNCLOAKED 0x8018 +#endif // https://devblogs.microsoft.com/oldnewthing/20200302-00/?p=103507 bool IsWindowCloaked(HWND hwnd) { @@ -45,46 +115,128 @@ bool IsWindowCloaked(HWND hwnd) { isCloaked; } -bool SetTaskbarAutoHide(bool set) { - APPBARDATA abd; +HWND GetTaskbarWnd() { + static HWND hTaskbarWnd; - // Both ABM_GETSTATE and ABM_SETSTATE require cbSize to be set. - abd.cbSize = sizeof(APPBARDATA); - // Get state. - UINT state = (UINT)SHAppBarMessage(ABM_GETSTATE, &abd); - // Set auto hide state. - if (set) { - abd.lParam = state | ABS_AUTOHIDE; - } else { - abd.lParam = state & ~ABS_AUTOHIDE; + if (!hTaskbarWnd) { + HWND hWnd = FindWindow(L"Shell_TrayWnd", nullptr); + + DWORD processId = 0; + if (hWnd && GetWindowThreadProcessId(hWnd, &processId) && + processId == GetCurrentProcessId()) { + hTaskbarWnd = hWnd; + } + } + + return hTaskbarWnd; +} + +bool IsTaskbarWindow(HWND hWnd) { + WCHAR szClassName[32]; + if (!GetClassName(hWnd, szClassName, ARRAYSIZE(szClassName))) { + return false; + } + + return _wcsicmp(szClassName, L"Shell_TrayWnd") == 0 || + _wcsicmp(szClassName, L"Shell_SecondaryTrayWnd") == 0; +} + +HWND FindTaskbarWindows(std::unordered_set* secondaryTaskbarWindows) { + secondaryTaskbarWindows->clear(); + + HWND hTaskbarWnd = GetTaskbarWnd(); + if (!hTaskbarWnd) { + return nullptr; + } + + DWORD taskbarThreadId = GetWindowThreadProcessId(hTaskbarWnd, nullptr); + if (!taskbarThreadId) { + return nullptr; + } + + auto enumWindowsProc = [&secondaryTaskbarWindows](HWND hWnd) -> BOOL { + WCHAR szClassName[32]; + if (GetClassName(hWnd, szClassName, ARRAYSIZE(szClassName)) == 0) { + return TRUE; + } + + if (_wcsicmp(szClassName, L"Shell_SecondaryTrayWnd") == 0) { + secondaryTaskbarWindows->insert(hWnd); + } + + return TRUE; + }; + + EnumThreadWindows( + taskbarThreadId, + [](HWND hWnd, LPARAM lParam) WINAPI -> BOOL { + auto& proc = *reinterpret_cast(lParam); + return proc(hWnd); + }, + reinterpret_cast(&enumWindowsProc)); + + return hTaskbarWnd; +} + +bool GetTaskbarRectForMonitor(HMONITOR monitor, RECT* rect) { + SetRectEmpty(rect); + + HWND hTaskbarWnd = GetTaskbarWnd(); + if (!hTaskbarWnd) { + return false; } - // Set state. - SHAppBarMessage(ABM_SETSTATE, &abd); - return state & ABS_AUTOHIDE; + + SendMessage(hTaskbarWnd, g_getTaskbarRectRegisteredMsg, (WPARAM)monitor, + (LPARAM)rect); + return true; } -bool DoesAnyMonitorHaveMaximizedWindow() { - bool hasMaximized = false; +bool ShouldAlwaysShowTaskbar(HWND hMMTaskbarWnd, HMONITOR monitor) { + if (g_settings.mode == Mode::never) { + return true; + } + + bool canHideTaskbar = false; + + RECT taskbarRect{}; + GetTaskbarRectForMonitor(monitor, &taskbarRect); + + HWND hShellWindow = GetShellWindow(); - auto enumWindowsProc = [&hasMaximized](HWND hWnd) -> BOOL { - if (!IsWindowVisible(hWnd) || IsWindowCloaked(hWnd) || IsIconic(hWnd)) { + auto enumWindowsProc = [&](HWND hWnd) -> BOOL { + if (hWnd == hShellWindow || GetProp(hWnd, L"DesktopWindow") || + IsTaskbarWindow(hWnd) || !IsWindowVisible(hWnd) || + IsWindowCloaked(hWnd) || IsIconic(hWnd)) { return TRUE; } - if (GetWindowLong(hWnd, GWL_EXSTYLE) & - (WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW)) { + if (GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_NOACTIVATE) { return TRUE; } WINDOWPLACEMENT wp{ .length = sizeof(WINDOWPLACEMENT), }; - if (!GetWindowPlacement(hWnd, &wp) || wp.showCmd != SW_SHOWMAXIMIZED) { + if (GetWindowPlacement(hWnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) { + if (MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST) == monitor) { + canHideTaskbar = true; + return FALSE; + } + return TRUE; } - hasMaximized = true; - return FALSE; + if (g_settings.mode == Mode::intersected) { + RECT rc; + RECT intersectRect; + if (GetWindowRect(hWnd, &rc) && + IntersectRect(&intersectRect, &rc, &taskbarRect)) { + canHideTaskbar = true; + return FALSE; + } + } + + return TRUE; }; EnumWindows( @@ -94,41 +246,234 @@ bool DoesAnyMonitorHaveMaximizedWindow() { }, reinterpret_cast(&enumWindowsProc)); - return hasMaximized; + return !canHideTaskbar; } -void AdjustTaskbarStyle() { - bool wasAutoHideSet = - SetTaskbarAutoHide(DoesAnyMonitorHaveMaximizedWindow()); - if (!g_wasAutoHideSet) { - g_wasAutoHideSet = wasAutoHideSet; +void* QueryViaVtable(void* object, void* vtable) { + void* ptr = object; + while (*(void**)ptr != vtable) { + ptr = (void**)ptr + 1; } + return ptr; } -void ResetTaskbarStyle() { - if (g_wasAutoHideSet) { - SetTaskbarAutoHide(*g_wasAutoHideSet); +void* QueryViaVtableBackwards(void* object, void* vtable) { + void* ptr = object; + while (*(void**)ptr != vtable) { + ptr = (void**)ptr - 1; } + return ptr; } -void CALLBACK LocationChangeWinEventProc(HWINEVENTHOOK hWinEventHook, - DWORD event, - HWND hWnd, - LONG idObject, - LONG idChild, - DWORD dwEventThread, - DWORD dwmsEventTime) { - if (idObject != OBJID_WINDOW) { +void* TrayUI_vftable_IInspectable; +void* TrayUI_vftable_ITrayComponentHost; +void* CSecondaryTray_vftable_ISecondaryTray; + +using TrayUI_GetStuckMonitor_t = HMONITOR(WINAPI*)(void* pThis); +TrayUI_GetStuckMonitor_t TrayUI_GetStuckMonitor_Original; + +using CSecondaryTray_GetMonitor_t = HMONITOR(WINAPI*)(void* pThis); +CSecondaryTray_GetMonitor_t CSecondaryTray_GetMonitor_Original; + +using TrayUI_GetStuckRectForMonitor_t = bool(WINAPI*)(void* pThis, + HMONITOR hMonitor, + RECT* rect); +TrayUI_GetStuckRectForMonitor_t TrayUI_GetStuckRectForMonitor_Original; + +using TrayUI_GetStuckRectForMonitor_Win10_t = RECT*(WINAPI*)(void* pThis, + RECT* rect, + HMONITOR hMonitor); +TrayUI_GetStuckRectForMonitor_Win10_t + TrayUI_GetStuckRectForMonitor_Win10_Original; + +using TrayUI__Hide_t = void(WINAPI*)(void* pThis); +TrayUI__Hide_t TrayUI__Hide_Original; +void WINAPI TrayUI__Hide_Hook(void* pThis) { + Wh_Log(L">"); + + auto it = g_alwaysShowTaskbars.find(pThis); + if (it != g_alwaysShowTaskbars.end()) { + KillTimer(it->second, kTrayUITimerHide); return; } + TrayUI__Hide_Original(pThis); +} + +using CSecondaryTray__AutoHide_t = void(WINAPI*)(void* pThis, bool param1); +CSecondaryTray__AutoHide_t CSecondaryTray__AutoHide_Original; +void WINAPI CSecondaryTray__AutoHide_Hook(void* pThis, bool param1) { Wh_Log(L">"); - if (g_pendingLocationChangeTimer) { + auto it = g_alwaysShowTaskbars.find(pThis); + if (it != g_alwaysShowTaskbars.end()) { + KillTimer(it->second, kTrayUITimerHide); return; } - g_pendingLocationChangeTimer = + CSecondaryTray__AutoHide_Original(pThis, param1); +} + +using TrayUI_Unhide_t = void(WINAPI*)(void* pThis, + int trayUnhideFlags, + int unhideRequest); +TrayUI_Unhide_t TrayUI_Unhide_Original; + +using CSecondaryTray__Unhide_t = void(WINAPI*)(void* pThis, + int trayUnhideFlags, + int unhideRequest); +CSecondaryTray__Unhide_t CSecondaryTray__Unhide_Original; + +using TrayUI_WndProc_t = LRESULT(WINAPI*)(void* pThis, + HWND hWnd, + UINT Msg, + WPARAM wParam, + LPARAM lParam, + bool* flag); +TrayUI_WndProc_t TrayUI_WndProc_Original; +LRESULT WINAPI TrayUI_WndProc_Hook(void* pThis, + HWND hWnd, + UINT Msg, + WPARAM wParam, + LPARAM lParam, + bool* flag) { + if (Msg == g_getTaskbarRectRegisteredMsg) { + HMONITOR monitor = (HMONITOR)wParam; + RECT* rect = (RECT*)lParam; + if (TrayUI_GetStuckRectForMonitor_Original) { + if (!TrayUI_GetStuckRectForMonitor_Original(pThis, monitor, rect)) { + SetRectEmpty(rect); + } + } else if (TrayUI_GetStuckRectForMonitor_Win10_Original) { + TrayUI_GetStuckRectForMonitor_Win10_Original(pThis, rect, monitor); + } else { + SetRectEmpty(rect); + } + } else if (Msg == g_updateTaskbarStateRegisteredMsg) { + HMONITOR monitor = TrayUI_GetStuckMonitor_Original(pThis); + bool alwaysShow = ShouldAlwaysShowTaskbar(hWnd, monitor); + + void* pTrayUI_IInspectable = + QueryViaVtableBackwards(pThis, TrayUI_vftable_IInspectable); + + bool alwaysShown = g_alwaysShowTaskbars.contains(pTrayUI_IInspectable); + + if (alwaysShow != alwaysShown) { + Wh_Log(L"> alwaysShow=%d", alwaysShow); + + if (alwaysShow) { + g_alwaysShowTaskbars[pTrayUI_IInspectable] = hWnd; + + void* pTrayUI_ITrayComponentHost = + QueryViaVtable(pThis, TrayUI_vftable_ITrayComponentHost); + TrayUI_Unhide_Original(pTrayUI_ITrayComponentHost, 0, 0); + } else { + g_alwaysShowTaskbars.erase(pTrayUI_IInspectable); + + SetTimer(hWnd, kTrayUITimerHide, 0, nullptr); + } + } + } + + LRESULT ret = + TrayUI_WndProc_Original(pThis, hWnd, Msg, wParam, lParam, flag); + + return ret; +} + +using CSecondaryTray_v_WndProc_t = LRESULT( + WINAPI*)(void* pThis, HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); +CSecondaryTray_v_WndProc_t CSecondaryTray_v_WndProc_Original; +LRESULT WINAPI CSecondaryTray_v_WndProc_Hook(void* pThis, + HWND hWnd, + UINT Msg, + WPARAM wParam, + LPARAM lParam) { + if (Msg == g_updateTaskbarStateRegisteredMsg) { + void* pCSecondaryTray_ISecondaryTray = + QueryViaVtable(pThis, CSecondaryTray_vftable_ISecondaryTray); + + HMONITOR monitor = + CSecondaryTray_GetMonitor_Original(pCSecondaryTray_ISecondaryTray); + + bool alwaysShow = ShouldAlwaysShowTaskbar(hWnd, monitor); + + bool alwaysShown = g_alwaysShowTaskbars.contains(pThis); + + if (alwaysShow != alwaysShown) { + Wh_Log(L"> alwaysShow=%d", alwaysShow); + + if (alwaysShow) { + g_alwaysShowTaskbars[pThis] = hWnd; + + CSecondaryTray__Unhide_Original(pThis, 0, 0); + } else { + g_alwaysShowTaskbars.erase(pThis); + + SetTimer(hWnd, kTrayUITimerHide, 0, nullptr); + } + } + } + + LRESULT ret = + CSecondaryTray_v_WndProc_Original(pThis, hWnd, Msg, wParam, lParam); + + return ret; +} + +DWORD WINAPI WinEventHookThread(LPVOID lpThreadParameter); + +void AdjustTaskbar(HWND hMMTaskbarWnd) { + if (g_settings.mode != Mode::never) { + if (!g_winEventHookThread) { + std::lock_guard guard(g_winEventHookThreadMutex); + + if (!g_winEventHookThread) { + g_winEventHookThread = CreateThread( + nullptr, 0, WinEventHookThread, nullptr, 0, nullptr); + } + } + } + + PostMessage(hMMTaskbarWnd, g_updateTaskbarStateRegisteredMsg, 0, 0); +} + +void AdjustAllTaskbars() { + std::unordered_set secondaryTaskbarWindows; + HWND hWnd = FindTaskbarWindows(&secondaryTaskbarWindows); + if (hWnd) { + AdjustTaskbar(hWnd); + } + + for (HWND hSecondaryWnd : secondaryTaskbarWindows) { + AdjustTaskbar(hSecondaryWnd); + } +} + +void CALLBACK WinEventProc(HWINEVENTHOOK hWinEventHook, + DWORD event, + HWND hWnd, + LONG idObject, + LONG idChild, + DWORD dwEventThread, + DWORD dwmsEventTime) { + if (idObject != OBJID_WINDOW || + (GetWindowLong(hWnd, GWL_STYLE) & WS_CHILD) || IsTaskbarWindow(hWnd)) { + return; + } + + HWND hParentWnd = GetAncestor(hWnd, GA_PARENT); + if (hParentWnd && hParentWnd != GetDesktopWindow()) { + return; + } + + Wh_Log(L"> %08X", (DWORD)(ULONG_PTR)hWnd); + + if (g_pendingEventsTimer) { + return; + } + + g_pendingEventsTimer = SetTimer(nullptr, 0, 200, [](HWND hwnd, // handle of window for timer messages UINT uMsg, // WM_TIMER message @@ -137,69 +482,425 @@ void CALLBACK LocationChangeWinEventProc(HWINEVENTHOOK hWinEventHook, ) WINAPI { Wh_Log(L">"); - KillTimer(nullptr, g_pendingLocationChangeTimer); - g_pendingLocationChangeTimer = 0; + KillTimer(nullptr, g_pendingEventsTimer); + g_pendingEventsTimer = 0; - AdjustTaskbarStyle(); + AdjustAllTaskbars(); }); } -BOOL Wh_ModInit() { - Wh_Log(L">"); +DWORD WINAPI WinEventHookThread(LPVOID lpThreadParameter) { + HWINEVENTHOOK winObjectEventHook1 = + SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_HIDE, nullptr, + WinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT); + if (!winObjectEventHook1) { + Wh_Log(L"Error: SetWinEventHook"); + } - return TRUE; + HWINEVENTHOOK winObjectEventHook2 = SetWinEventHook( + EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_LOCATIONCHANGE, nullptr, + WinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT); + if (!winObjectEventHook2) { + Wh_Log(L"Error: SetWinEventHook"); + } + + HWINEVENTHOOK winObjectEventHook3 = + SetWinEventHook(EVENT_OBJECT_CLOAKED, EVENT_OBJECT_UNCLOAKED, nullptr, + WinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT); + if (!winObjectEventHook3) { + Wh_Log(L"Error: SetWinEventHook"); + } + + BOOL bRet; + MSG msg; + while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) { + if (bRet == -1) { + msg.wParam = 0; + break; + } + + if (msg.hwnd == NULL && msg.message == WM_APP) { + PostQuitMessage(0); + continue; + } + + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + if (winObjectEventHook1) { + UnhookWinEvent(winObjectEventHook1); + } + + if (winObjectEventHook2) { + UnhookWinEvent(winObjectEventHook2); + } + + if (winObjectEventHook3) { + UnhookWinEvent(winObjectEventHook3); + } + + return 0; } -void Wh_ModAfterInit() { - WNDCLASS wndclass; - if (GetClassInfo(GetModuleHandle(NULL), L"Shell_TrayWnd", &wndclass)) { - AdjustTaskbarStyle(); - } - - g_winObjectLocationChangeThread = CreateThread( - nullptr, 0, - [](LPVOID lpParameter) WINAPI -> DWORD { - HWINEVENTHOOK winObjectLocationChangeEventHook = SetWinEventHook( - EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_LOCATIONCHANGE, - nullptr, LocationChangeWinEventProc, 0, 0, - WINEVENT_OUTOFCONTEXT); - if (!winObjectLocationChangeEventHook) { - Wh_Log(L"Error: SetWinEventHook"); - return 0; +VS_FIXEDFILEINFO* GetModuleVersionInfo(HMODULE hModule, UINT* puPtrLen) { + void* pFixedFileInfo = nullptr; + UINT uPtrLen = 0; + + HRSRC hResource = + FindResource(hModule, MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION); + if (hResource) { + HGLOBAL hGlobal = LoadResource(hModule, hResource); + if (hGlobal) { + void* pData = LockResource(hGlobal); + if (pData) { + if (!VerQueryValue(pData, L"\\", &pFixedFileInfo, &uPtrLen) || + uPtrLen == 0) { + pFixedFileInfo = nullptr; + uPtrLen = 0; + } } + } + } - BOOL bRet; - MSG msg; - while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) { - if (bRet == -1) { - msg.wParam = 0; - break; - } + if (puPtrLen) { + *puPtrLen = uPtrLen; + } - if (msg.hwnd == NULL && msg.message == WM_APP) { - PostQuitMessage(0); - continue; - } + return (VS_FIXEDFILEINFO*)pFixedFileInfo; +} + +WinVersion GetExplorerVersion() { + VS_FIXEDFILEINFO* fixedFileInfo = GetModuleVersionInfo(nullptr, nullptr); + if (!fixedFileInfo) { + return WinVersion::Unsupported; + } + + WORD major = HIWORD(fixedFileInfo->dwFileVersionMS); + WORD minor = LOWORD(fixedFileInfo->dwFileVersionMS); + WORD build = HIWORD(fixedFileInfo->dwFileVersionLS); + WORD qfe = LOWORD(fixedFileInfo->dwFileVersionLS); + + Wh_Log(L"Version: %u.%u.%u.%u", major, minor, build, qfe); + + switch (major) { + case 10: + if (build < 22000) { + return WinVersion::Win10; + } else if (build < 26100) { + return WinVersion::Win11; + } else { + return WinVersion::Win11_24H2; + } + break; + } + + return WinVersion::Unsupported; +} + +bool HookExplorerPatcherSymbols(HMODULE explorerPatcherModule) { + if (g_explorerPatcherInitialized.exchange(true)) { + return true; + } + + if (g_winVersion >= WinVersion::Win11) { + g_winVersion = WinVersion::Win10; + } + + struct EXPLORER_PATCHER_HOOK { + PCSTR symbol; + void** pOriginalFunction; + void* hookFunction = nullptr; + bool optional = false; + }; - TranslateMessage(&msg); - DispatchMessage(&msg); + EXPLORER_PATCHER_HOOK hooks[] = { + {R"(??_7TrayUI@@6BITrayDeskBand@@@)", + (void**)&TrayUI_vftable_IInspectable}, + {R"(??_7TrayUI@@6BITrayComponentHost@@@)", + (void**)&TrayUI_vftable_ITrayComponentHost}, + // {R"(TODO)", (void**)&CSecondaryTray_vftable_ISecondaryTray}, + {R"(?GetStuckMonitor@TrayUI@@UEAAPEAUHMONITOR__@@XZ)", + (void**)&TrayUI_GetStuckMonitor_Original}, + // {R"(TODO)", (void**)&CSecondaryTray_GetMonitor_Original}, + {R"(?GetStuckRectForMonitor@TrayUI@@UEAA_NPEAUHMONITOR__@@PEAUtagRECT@@@Z)", + (void**)&TrayUI_GetStuckRectForMonitor_Original}, + {R"(?_Hide@TrayUI@@QEAAXXZ)", (void**)&TrayUI__Hide_Original, + (void*)TrayUI__Hide_Hook}, + // {R"(TODO)", (void**)&CSecondaryTray__AutoHide_Original, + // (void*)CSecondaryTray__AutoHide_Hook}, + {R"(?Unhide@TrayUI@@UEAAXW4TrayUnhideFlags@TrayCommon@@W4UnhideRequest@3@@Z)", + (void**)&TrayUI_Unhide_Original}, + // {R"(TODO)", (void**)&CSecondaryTray__Unhide_Original}, + {R"(?WndProc@TrayUI@@UEAA_JPEAUHWND__@@I_K_JPEA_N@Z)", + (void**)&TrayUI_WndProc_Original, (void*)TrayUI_WndProc_Hook}, + // {R"(TODO)", (void**)&CSecondaryTray_v_WndProc_Original, + // (void*)CSecondaryTray_v_WndProc_Hook}, + }; + + bool succeeded = true; + + for (const auto& hook : hooks) { + void* ptr = (void*)GetProcAddress(explorerPatcherModule, hook.symbol); + if (!ptr) { + Wh_Log(L"ExplorerPatcher symbol%s doesn't exist: %S", + hook.optional ? L" (optional)" : L"", hook.symbol); + if (!hook.optional) { + succeeded = false; } + continue; + } + + if (hook.hookFunction) { + Wh_SetFunctionHook(ptr, hook.hookFunction, hook.pOriginalFunction); + } else { + *hook.pOriginalFunction = ptr; + } + } + + if (g_initialized) { + Wh_ApplyHookOperations(); + } + + return succeeded; +} + +bool HandleModuleIfExplorerPatcher(HMODULE module) { + WCHAR moduleFilePath[MAX_PATH]; + switch ( + GetModuleFileName(module, moduleFilePath, ARRAYSIZE(moduleFilePath))) { + case 0: + case ARRAYSIZE(moduleFilePath): + return false; + } + + PCWSTR moduleFileName = wcsrchr(moduleFilePath, L'\\'); + if (!moduleFileName) { + return false; + } + + moduleFileName++; - UnhookWinEvent(winObjectLocationChangeEventHook); - return 0; + if (_wcsnicmp(L"ep_taskbar.", moduleFileName, sizeof("ep_taskbar.") - 1) != + 0) { + return true; + } + + Wh_Log(L"ExplorerPatcher taskbar loaded: %s", moduleFileName); + return HookExplorerPatcherSymbols(module); +} + +void HandleLoadedExplorerPatcher() { + HMODULE hMods[1024]; + DWORD cbNeeded; + if (EnumProcessModules(GetCurrentProcess(), hMods, sizeof(hMods), + &cbNeeded)) { + for (size_t i = 0; i < cbNeeded / sizeof(HMODULE); i++) { + HandleModuleIfExplorerPatcher(hMods[i]); + } + } +} + +using LoadLibraryExW_t = decltype(&LoadLibraryExW); +LoadLibraryExW_t LoadLibraryExW_Original; +HMODULE WINAPI LoadLibraryExW_Hook(LPCWSTR lpLibFileName, + HANDLE hFile, + DWORD dwFlags) { + HMODULE module = LoadLibraryExW_Original(lpLibFileName, hFile, dwFlags); + if (module && !((ULONG_PTR)module & 3) && !g_explorerPatcherInitialized) { + HandleModuleIfExplorerPatcher(module); + } + + return module; +} + +bool HookTaskbarSymbols() { + HMODULE module; + if (g_winVersion <= WinVersion::Win10) { + module = GetModuleHandle(nullptr); + } else { + module = LoadLibrary(L"taskbar.dll"); + if (!module) { + Wh_Log(L"Couldn't load taskbar.dll"); + return false; + } + } + + // Taskbar.dll, explorer.exe + WindhawkUtils::SYMBOL_HOOK symbolHooks[] = { + { + { + // Windows 11. + LR"(const TrayUI::`vftable'{for `IInspectable'})", + + // Windows 10. + LR"(const TrayUI::`vftable'{for `ITrayDeskBand'})", + }, + &TrayUI_vftable_IInspectable, + }, + { + {LR"(const TrayUI::`vftable'{for `ITrayComponentHost'})"}, + &TrayUI_vftable_ITrayComponentHost, + }, + { + {LR"(const CSecondaryTray::`vftable'{for `ISecondaryTray'})"}, + &CSecondaryTray_vftable_ISecondaryTray, + }, + { + {LR"(public: virtual struct HMONITOR__ * __cdecl TrayUI::GetStuckMonitor(void))"}, + &TrayUI_GetStuckMonitor_Original, + }, + { + {LR"(public: virtual struct HMONITOR__ * __cdecl CSecondaryTray::GetMonitor(void))"}, + &CSecondaryTray_GetMonitor_Original, + }, + { + {LR"(public: virtual bool __cdecl TrayUI::GetStuckRectForMonitor(struct HMONITOR__ *,struct tagRECT *))"}, + &TrayUI_GetStuckRectForMonitor_Original, + nullptr, + true, // Windows 11. + }, + { + {LR"(public: virtual struct tagRECT __cdecl TrayUI::GetStuckRectForMonitor(struct HMONITOR__ *))"}, + &TrayUI_GetStuckRectForMonitor_Win10_Original, + nullptr, + true, // Windows 10. + }, + { + {LR"(public: void __cdecl TrayUI::_Hide(void))"}, + &TrayUI__Hide_Original, + TrayUI__Hide_Hook, }, - nullptr, 0, nullptr); + { + {LR"(private: void __cdecl CSecondaryTray::_AutoHide(bool))"}, + &CSecondaryTray__AutoHide_Original, + CSecondaryTray__AutoHide_Hook, + }, + { + {LR"(public: virtual void __cdecl TrayUI::Unhide(enum TrayCommon::TrayUnhideFlags,enum TrayCommon::UnhideRequest))"}, + &TrayUI_Unhide_Original, + }, + { + {LR"(private: void __cdecl CSecondaryTray::_Unhide(enum TrayCommon::TrayUnhideFlags,enum TrayCommon::UnhideRequest))"}, + &CSecondaryTray__Unhide_Original, + }, + { + {LR"(public: virtual __int64 __cdecl TrayUI::WndProc(struct HWND__ *,unsigned int,unsigned __int64,__int64,bool *))"}, + &TrayUI_WndProc_Original, + TrayUI_WndProc_Hook, + }, + { + {LR"(private: virtual __int64 __cdecl CSecondaryTray::v_WndProc(struct HWND__ *,unsigned int,unsigned __int64,__int64))"}, + &CSecondaryTray_v_WndProc_Original, + CSecondaryTray_v_WndProc_Hook, + }, + }; + + return HookSymbols(module, symbolHooks, ARRAYSIZE(symbolHooks)); +} + +void LoadSettings() { + PCWSTR mode = Wh_GetStringSetting(L"mode"); + g_settings.mode = Mode::intersected; + if (wcscmp(mode, L"maximized") == 0) { + g_settings.mode = Mode::maximized; + } else if (wcscmp(mode, L"never") == 0) { + g_settings.mode = Mode::never; + } + Wh_FreeStringSetting(mode); + + g_settings.oldTaskbarOnWin11 = Wh_GetIntSetting(L"oldTaskbarOnWin11"); +} + +BOOL Wh_ModInit() { + Wh_Log(L">"); + + LoadSettings(); + + g_winVersion = GetExplorerVersion(); + if (g_winVersion == WinVersion::Unsupported) { + Wh_Log(L"Unsupported Windows version"); + return FALSE; + } + + if (g_settings.oldTaskbarOnWin11) { + bool hasWin10Taskbar = g_winVersion < WinVersion::Win11_24H2; + + if (g_winVersion >= WinVersion::Win11) { + g_winVersion = WinVersion::Win10; + } + + if (hasWin10Taskbar && !HookTaskbarSymbols()) { + return FALSE; + } + } else if (!HookTaskbarSymbols()) { + return FALSE; + } + + HandleLoadedExplorerPatcher(); + + HMODULE kernelBaseModule = GetModuleHandle(L"kernelbase.dll"); + FARPROC pKernelBaseLoadLibraryExW = + GetProcAddress(kernelBaseModule, "LoadLibraryExW"); + Wh_SetFunctionHook((void*)pKernelBaseLoadLibraryExW, + (void*)LoadLibraryExW_Hook, + (void**)&LoadLibraryExW_Original); + + g_initialized = true; + + return TRUE; +} + +void Wh_ModAfterInit() { + Wh_Log(L">"); + + // Try again in case there's a race between the previous attempt and the + // LoadLibraryExW hook. + if (!g_explorerPatcherInitialized) { + HandleLoadedExplorerPatcher(); + } + + WNDCLASS wndclass; + if (GetClassInfo(GetModuleHandle(nullptr), L"Shell_TrayWnd", &wndclass)) { + AdjustAllTaskbars(); + } } void Wh_ModUninit() { Wh_Log(L">"); - if (g_winObjectLocationChangeThread) { - PostThreadMessage(GetThreadId(g_winObjectLocationChangeThread), WM_APP, - 0, 0); - WaitForSingleObject(g_winObjectLocationChangeThread, INFINITE); - CloseHandle(g_winObjectLocationChangeThread); + if (g_winEventHookThread) { + PostThreadMessage(GetThreadId(g_winEventHookThread), WM_APP, 0, 0); + WaitForSingleObject(g_winEventHookThread, INFINITE); + CloseHandle(g_winEventHookThread); + g_winEventHookThread = nullptr; } +} + +BOOL Wh_ModSettingsChanged(BOOL* bReload) { + Wh_Log(L">"); + + bool prevOldTaskbarOnWin11 = g_settings.oldTaskbarOnWin11; - ResetTaskbarStyle(); + LoadSettings(); + + if (g_settings.oldTaskbarOnWin11 != prevOldTaskbarOnWin11) { + *bReload = TRUE; + return TRUE; + } + + if (g_settings.mode == Mode::never) { + std::lock_guard guard(g_winEventHookThreadMutex); + + if (g_winEventHookThread) { + PostThreadMessage(GetThreadId(g_winEventHookThread), WM_APP, 0, 0); + WaitForSingleObject(g_winEventHookThread, INFINITE); + CloseHandle(g_winEventHookThread); + g_winEventHookThread = nullptr; + } + } + + AdjustAllTaskbars(); + + return TRUE; }