// ==WindhawkMod== // @id taskbar-auto-hide-when-maximized // @name Taskbar auto-hide when maximized // @description Makes the taskbar auto-hide only when a window is maximized or intersects the taskbar // @version 1.2.3 // @author m417z // @github https://github.com/m417z // @twitter https://twitter.com/m417z // @homepage https://m417z.com/ // @include explorer.exe // @architecture x86-64 // @compilerOptions -ldwmapi -lole32 -loleaut32 -lversion // ==/WindhawkMod== // Source code is published under The GNU General Public License v3.0. // // For bug reports and feature requests, please open an issue here: // https://github.com/ramensoftware/windhawk-mods/issues // // For pull requests, development takes place here: // https://github.com/m417z/my-windhawk-mods // ==WindhawkModReadme== /* # Taskbar auto-hide when maximized 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== // ==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 - foregroundWindowOnly: false $name: Apply only to foreground window $description: >- Enable this option to apply the auto-hide taskbar feature only to the selected window. - excludedPrograms: [""] $name: Excluded programs $description: >- The taskbar won't auto-hide due to windows of these programs being maximized or intersecting the taskbar. Entries can be process names, paths or application IDs, for example: mspaint.exe C:\Windows\System32\notepad.exe Microsoft.WindowsCalculator_8wekyb3d8bbwe!App - primaryMonitorOnly: false $name: Primary monitor only $description: >- Apply the mod's behavior only to the primary monitor taskbar. Secondary monitors will use Windows' default auto-hide behavior. - 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 #include #include #include #include #include #include #include enum class Mode { intersected, maximized, never, }; struct { Mode mode; bool foregroundWindowOnly; std::unordered_set excludedPrograms; bool primaryMonitorOnly; bool oldTaskbarOnWin11; } g_settings; enum class WinVersion { Unsupported, Win10, Win11, Win11_24H2, }; WinVersion g_winVersion; std::atomic g_initialized; std::atomic g_explorerPatcherInitialized; bool g_wasAutoHideProcessed; bool g_wasAutoHideDisabled; std::mutex g_winEventHookThreadMutex; std::atomic g_winEventHookThread; std::unordered_map g_taskbarsKeptShown; UINT_PTR g_pendingEventsTimer; // TrayUI::_HandleTrayPrivateSettingMessage constexpr UINT kHandleTrayPrivateSettingMessage = WM_USER + 0x1CA; enum { kTrayPrivateSettingAutoHideGet = 3, kTrayPrivateSettingAutoHideSet = 4, }; constexpr WCHAR kUpdateTaskbarStatePendingTickCount[] = L"Windhawk_UpdateTaskbarStatePendingTickCount_" WH_MOD_ID; 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, }; #if __cplusplus < 202302L // Missing in older MinGW headers. DECLARE_HANDLE(CO_MTA_USAGE_COOKIE); WINOLEAPI CoIncrementMTAUsage(CO_MTA_USAGE_COOKIE* pCookie); WINOLEAPI CoDecrementMTAUsage(CO_MTA_USAGE_COOKIE Cookie); #endif // 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 using IsWindowArranged_t = BOOL(WINAPI*)(HWND hwnd); IsWindowArranged_t pIsWindowArranged; // https://devblogs.microsoft.com/oldnewthing/20200302-00/?p=103507 bool IsWindowCloaked(HWND hwnd) { BOOL isCloaked = FALSE; return SUCCEEDED(DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &isCloaked, sizeof(isCloaked))) && isCloaked; } HWND FindCurrentProcessTaskbarWnd() { HWND hTaskbarWnd = nullptr; EnumWindows( [](HWND hWnd, LPARAM lParam) -> BOOL { DWORD dwProcessId; WCHAR className[32]; if (GetWindowThreadProcessId(hWnd, &dwProcessId) && dwProcessId == GetCurrentProcessId() && GetClassName(hWnd, className, ARRAYSIZE(className)) && _wcsicmp(className, L"Shell_TrayWnd") == 0) { *reinterpret_cast(lParam) = hWnd; return FALSE; } return TRUE; }, reinterpret_cast(&hTaskbarWnd)); 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 = FindCurrentProcessTaskbarWnd(); 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) -> BOOL { auto& proc = *reinterpret_cast(lParam); return proc(hWnd); }, reinterpret_cast(&enumWindowsProc)); return hTaskbarWnd; } bool GetTaskbarRectForMonitor(HMONITOR monitor, RECT* rect) { SetRectEmpty(rect); HWND hTaskbarWnd = FindCurrentProcessTaskbarWnd(); if (!hTaskbarWnd) { return false; } SendMessage(hTaskbarWnd, g_getTaskbarRectRegisteredMsg, (WPARAM)monitor, (LPARAM)rect); return true; } // https://gist.github.com/m417z/451dfc2dad88d7ba88ed1814779a26b4 std::wstring GetWindowAppId(HWND hWnd) { // {c8900b66-a973-584b-8cae-355b7f55341b} constexpr winrt::guid CLSID_StartMenuCacheAndAppResolver{ 0x660b90c8, 0x73a9, 0x4b58, {0x8c, 0xae, 0x35, 0x5b, 0x7f, 0x55, 0x34, 0x1b}}; // {de25675a-72de-44b4-9373-05170450c140} constexpr winrt::guid IID_IAppResolver_8{ 0xde25675a, 0x72de, 0x44b4, {0x93, 0x73, 0x05, 0x17, 0x04, 0x50, 0xc1, 0x40}}; struct IAppResolver_8 : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE GetAppIDForShortcut() = 0; virtual HRESULT STDMETHODCALLTYPE GetAppIDForShortcutObject() = 0; virtual HRESULT STDMETHODCALLTYPE GetAppIDForWindow(HWND hWnd, WCHAR** pszAppId, void* pUnknown1, void* pUnknown2, void* pUnknown3) = 0; virtual HRESULT STDMETHODCALLTYPE GetAppIDForProcess(DWORD dwProcessId, WCHAR** pszAppId, void* pUnknown1, void* pUnknown2, void* pUnknown3) = 0; }; HRESULT hr; std::wstring result; CO_MTA_USAGE_COOKIE cookie; bool mtaUsageIncreased = SUCCEEDED(CoIncrementMTAUsage(&cookie)); winrt::com_ptr appResolver; hr = CoCreateInstance(CLSID_StartMenuCacheAndAppResolver, nullptr, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, IID_IAppResolver_8, appResolver.put_void()); if (SUCCEEDED(hr)) { WCHAR* pszAppId; hr = appResolver->GetAppIDForWindow(hWnd, &pszAppId, nullptr, nullptr, nullptr); if (SUCCEEDED(hr)) { result = pszAppId; CoTaskMemFree(pszAppId); } } appResolver = nullptr; if (mtaUsageIncreased) { CoDecrementMTAUsage(cookie); } return result; } bool IsWindowExcluded(HWND hWnd) { if (g_settings.excludedPrograms.empty()) { return false; } DWORD resolvedWindowProcessPathLen = 0; WCHAR resolvedWindowProcessPath[MAX_PATH]; WCHAR resolvedWindowProcessPathUpper[MAX_PATH]; DWORD dwProcessId = 0; if (GetWindowThreadProcessId(hWnd, &dwProcessId)) { HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwProcessId); if (hProcess) { DWORD dwSize = ARRAYSIZE(resolvedWindowProcessPath); if (QueryFullProcessImageName(hProcess, 0, resolvedWindowProcessPath, &dwSize)) { resolvedWindowProcessPathLen = dwSize; } CloseHandle(hProcess); } } if (resolvedWindowProcessPathLen > 0) { LCMapStringEx(LOCALE_NAME_USER_DEFAULT, LCMAP_UPPERCASE, resolvedWindowProcessPath, resolvedWindowProcessPathLen + 1, resolvedWindowProcessPathUpper, resolvedWindowProcessPathLen + 1, nullptr, nullptr, 0); } else { *resolvedWindowProcessPath = L'\0'; *resolvedWindowProcessPathUpper = L'\0'; } if (resolvedWindowProcessPathLen > 0 && g_settings.excludedPrograms.contains(resolvedWindowProcessPathUpper)) { return true; } if (PCWSTR programFileNameUpper = wcsrchr(resolvedWindowProcessPathUpper, L'\\')) { programFileNameUpper++; if (*programFileNameUpper && g_settings.excludedPrograms.contains(programFileNameUpper)) { return true; } } std::wstring appId = GetWindowAppId(hWnd); LCMapStringEx(LOCALE_NAME_USER_DEFAULT, LCMAP_UPPERCASE, appId.data(), appId.length(), appId.data(), appId.length(), nullptr, nullptr, 0); if (g_settings.excludedPrograms.contains(appId.c_str())) { return true; } return false; } bool CanHideTaskbarForWindow(HWND hWnd, HMONITOR monitor, const MONITORINFO* monitorInfo, const RECT* taskbarRect) { if (!IsWindowVisible(hWnd) || IsWindowCloaked(hWnd) || IsIconic(hWnd) || (GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_NOACTIVATE)) { return false; } if (hWnd == GetShellWindow() || GetProp(hWnd, L"DesktopWindow")) { return false; } // Check this after the other checks, as it's the most expensive one. if (IsWindowExcluded(hWnd)) { return false; } WINDOWPLACEMENT wp{ .length = sizeof(WINDOWPLACEMENT), }; if (GetWindowPlacement(hWnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) { if (MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST) == monitor) { return true; } return false; } bool isWindowArranged = pIsWindowArranged && pIsWindowArranged(hWnd); if (isWindowArranged && MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST) != monitor) { return false; } RECT windowRect{}; DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, &windowRect, sizeof(windowRect)); // Don't keep the taskbar shown for a fullscreen window. if (EqualRect(&windowRect, &monitorInfo->rcMonitor)) { return true; } // It makes sense to treat arranged windows (e.g. with Win+left) as // maximized, as they occupy the whole monitor height. Still check for // intersection, as a window can also just occupy the upper side of the // screen (e.g. Win+left, Win+up). if (g_settings.mode == Mode::intersected || (g_settings.mode == Mode::maximized && isWindowArranged)) { RECT intersectRect; if (IntersectRect(&intersectRect, &windowRect, taskbarRect)) { return true; } } return false; } bool ShouldKeepTaskbarShown(HMONITOR monitor) { if (g_settings.primaryMonitorOnly && monitor != MonitorFromPoint({0, 0}, MONITOR_DEFAULTTOPRIMARY)) { return false; } if (g_settings.mode == Mode::never) { return true; } MONITORINFO monitorInfo{ .cbSize = sizeof(MONITORINFO), }; GetMonitorInfo(monitor, &monitorInfo); RECT taskbarRect{}; GetTaskbarRectForMonitor(monitor, &taskbarRect); if (g_settings.foregroundWindowOnly) { HWND hForegroundWnd = GetForegroundWindow(); return !hForegroundWnd || !CanHideTaskbarForWindow(hForegroundWnd, monitor, &monitorInfo, &taskbarRect); } bool canHideTaskbar = false; DWORD dwTaskbarThreadId = GetCurrentThreadId(); auto enumWindowsProc = [&](HWND hWnd) -> BOOL { if (GetWindowThreadProcessId(hWnd, nullptr) == dwTaskbarThreadId) { return TRUE; } canHideTaskbar = CanHideTaskbarForWindow(hWnd, monitor, &monitorInfo, &taskbarRect); if (!canHideTaskbar) { return TRUE; } Wh_Log(L"Can hide taskbar for window %p", hWnd); return FALSE; }; EnumWindows( [](HWND hWnd, LPARAM lParam) -> BOOL { auto& proc = *reinterpret_cast(lParam); return proc(hWnd); }, reinterpret_cast(&enumWindowsProc)); return !canHideTaskbar; } void* QueryViaVtable(void* object, void* vtable) { void* ptr = object; while (*(void**)ptr != vtable) { ptr = (void**)ptr + 1; } return ptr; } void* QueryViaVtableBackwards(void* object, void* vtable) { void* ptr = object; while (*(void**)ptr != vtable) { ptr = (void**)ptr - 1; } return ptr; } DWORD WINAPI WinEventHookThread(LPVOID lpThreadParameter); void AdjustTaskbar(HWND hMMTaskbarWnd, bool clearPendingWhenDone = false) { 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, clearPendingWhenDone, 0); } void AdjustAllTaskbars() { std::unordered_set secondaryTaskbarWindows; HWND hWnd = FindTaskbarWindows(&secondaryTaskbarWindows); if (hWnd) { AdjustTaskbar(hWnd); } for (HWND hSecondaryWnd : secondaryTaskbarWindows) { AdjustTaskbar(hSecondaryWnd); } } bool AdjustAllTaskbarsIfNotPending() { std::unordered_set secondaryTaskbarWindows; HWND hWnd = FindTaskbarWindows(&secondaryTaskbarWindows); DWORD currentTickCount = GetTickCount(); DWORD pendingTickCount = 0; if (hWnd) { DWORD tickCount = (DWORD)(DWORD_PTR)GetProp( hWnd, kUpdateTaskbarStatePendingTickCount); if (tickCount > pendingTickCount) { pendingTickCount = tickCount; } } for (HWND hSecondaryWnd : secondaryTaskbarWindows) { DWORD tickCount = (DWORD)(DWORD_PTR)GetProp( hSecondaryWnd, kUpdateTaskbarStatePendingTickCount); if (tickCount > pendingTickCount) { pendingTickCount = tickCount; } } // Consider times larger than 10 seconds as expired, to prevent having // it stuck in this state. if (pendingTickCount && currentTickCount - pendingTickCount < 1000 * 10) { return false; } if (hWnd) { SetProp(hWnd, kUpdateTaskbarStatePendingTickCount, (HANDLE)(DWORD_PTR)currentTickCount); AdjustTaskbar(hWnd, /*clearPendingWhenDone=*/true); } for (HWND hSecondaryWnd : secondaryTaskbarWindows) { SetProp(hSecondaryWnd, kUpdateTaskbarStatePendingTickCount, (HANDLE)(DWORD_PTR)currentTickCount); AdjustTaskbar(hSecondaryWnd, /*clearPendingWhenDone=*/true); } return true; } 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_taskbarsKeptShown.find(pThis); if (it != g_taskbarsKeptShown.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">"); auto it = g_taskbarsKeptShown.find(pThis); if (it != g_taskbarsKeptShown.end()) { KillTimer(it->second, kTrayUITimerHide); return; } 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 == WM_NCCREATE) { Wh_Log(L"WM_NCCREATE: %08X", (DWORD)(ULONG_PTR)hWnd); AdjustTaskbar(hWnd); } else if (Msg == kHandleTrayPrivateSettingMessage) { // Prevent auto-hide from being disabled while the mod is loaded. if ((DWORD)wParam == 4) { BOOL bSetAutoHideEnabled = (BOOL)lParam; if (!bSetAutoHideEnabled) { return 0; } } } else 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) { if (!g_wasAutoHideProcessed) { g_wasAutoHideProcessed = true; g_wasAutoHideDisabled = !SendMessage(hWnd, kHandleTrayPrivateSettingMessage, kTrayPrivateSettingAutoHideGet, 0); if (g_wasAutoHideDisabled) { SendMessage(hWnd, kHandleTrayPrivateSettingMessage, kTrayPrivateSettingAutoHideSet, TRUE); } } HMONITOR monitor = TrayUI_GetStuckMonitor_Original(pThis); bool keepShown = ShouldKeepTaskbarShown(monitor); void* pTrayUI_IInspectable = QueryViaVtableBackwards(pThis, TrayUI_vftable_IInspectable); bool keptShown = g_taskbarsKeptShown.contains(pTrayUI_IInspectable); if (keepShown != keptShown) { Wh_Log(L"> keepShown=%d", keepShown); if (keepShown) { g_taskbarsKeptShown[pTrayUI_IInspectable] = hWnd; void* pTrayUI_ITrayComponentHost = QueryViaVtable(pThis, TrayUI_vftable_ITrayComponentHost); TrayUI_Unhide_Original(pTrayUI_ITrayComponentHost, 0, 0); } else { g_taskbarsKeptShown.erase(pTrayUI_IInspectable); SetTimer(hWnd, kTrayUITimerHide, 0, nullptr); } } if (wParam) { RemoveProp(hWnd, kUpdateTaskbarStatePendingTickCount); } } 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 == WM_NCCREATE) { Wh_Log(L"WM_NCCREATE: %08X", (DWORD)(ULONG_PTR)hWnd); AdjustTaskbar(hWnd); } else if (Msg == g_updateTaskbarStateRegisteredMsg) { void* pCSecondaryTray_ISecondaryTray = QueryViaVtable(pThis, CSecondaryTray_vftable_ISecondaryTray); HMONITOR monitor = CSecondaryTray_GetMonitor_Original(pCSecondaryTray_ISecondaryTray); bool keepShown = ShouldKeepTaskbarShown(monitor); bool keptShown = g_taskbarsKeptShown.contains(pThis); if (keepShown != keptShown) { Wh_Log(L"> keepShown=%d", keepShown); if (keepShown) { g_taskbarsKeptShown[pThis] = hWnd; CSecondaryTray__Unhide_Original(pThis, 0, 0); } else { g_taskbarsKeptShown.erase(pThis); SetTimer(hWnd, kTrayUITimerHide, 0, nullptr); } } if (wParam) { RemoveProp(hWnd, kUpdateTaskbarStatePendingTickCount); } } LRESULT ret = CSecondaryTray_v_WndProc_Original(pThis, hWnd, Msg, wParam, lParam); return ret; } 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 UINT_PTR idEvent, // timer identifier DWORD dwTime // current system time ) { Wh_Log(L">"); if (!AdjustAllTaskbarsIfNotPending()) { Wh_Log(L"Adjustment already pending, will retry later..."); return; } KillTimer(nullptr, g_pendingEventsTimer); g_pendingEventsTimer = 0; }); } 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"); } 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"); } HWINEVENTHOOK winSystemEventHook1 = nullptr; if (g_settings.foregroundWindowOnly) { winSystemEventHook1 = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, nullptr, WinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT); if (!winSystemEventHook1) { 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); } if (winSystemEventHook1) { UnhookWinEvent(winSystemEventHook1); } 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; } } } } if (puPtrLen) { *puPtrLen = uPtrLen; } 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; } struct EXPLORER_PATCHER_HOOK { PCSTR symbol; void** pOriginalFunction; void* hookFunction = nullptr; bool optional = false; template EXPLORER_PATCHER_HOOK( PCSTR symbol, Prototype** originalFunction, std::type_identity_t hookFunction = nullptr, bool optional = false) : symbol(symbol), pOriginalFunction(reinterpret_cast(originalFunction)), hookFunction(reinterpret_cast(hookFunction)), optional(optional) {} }; bool HookExplorerPatcherSymbols(HMODULE explorerPatcherModule) { if (g_explorerPatcherInitialized.exchange(true)) { return true; } if (g_winVersion >= WinVersion::Win11) { g_winVersion = WinVersion::Win10; } EXPLORER_PATCHER_HOOK hooks[] = { {R"(??_7TrayUI@@6BITrayDeskBand@@@)", &TrayUI_vftable_IInspectable}, {R"(??_7TrayUI@@6BITrayComponentHost@@@)", &TrayUI_vftable_ITrayComponentHost}, {R"(??_7CSecondaryTray@@6BISecondaryTray@@@)", &CSecondaryTray_vftable_ISecondaryTray}, {R"(?GetStuckMonitor@TrayUI@@UEAAPEAUHMONITOR__@@XZ)", &TrayUI_GetStuckMonitor_Original}, {R"(?GetMonitor@CSecondaryTray@@UEAAPEAUHMONITOR__@@XZ)", &CSecondaryTray_GetMonitor_Original}, {R"(?GetStuckRectForMonitor@TrayUI@@UEAA_NPEAUHMONITOR__@@PEAUtagRECT@@@Z)", &TrayUI_GetStuckRectForMonitor_Original}, {R"(?_Hide@TrayUI@@QEAAXXZ)", &TrayUI__Hide_Original, TrayUI__Hide_Hook}, {R"(?_AutoHide@CSecondaryTray@@AEAAX_N@Z)", &CSecondaryTray__AutoHide_Original, CSecondaryTray__AutoHide_Hook}, {R"(?Unhide@TrayUI@@UEAAXW4TrayUnhideFlags@TrayCommon@@W4UnhideRequest@3@@Z)", &TrayUI_Unhide_Original}, {R"(?_Unhide@CSecondaryTray@@AEAAXW4TrayUnhideFlags@TrayCommon@@W4UnhideRequest@3@@Z)", &CSecondaryTray__Unhide_Original}, {R"(?WndProc@TrayUI@@UEAA_JPEAUHWND__@@I_K_JPEA_N@Z)", &TrayUI_WndProc_Original, TrayUI_WndProc_Hook}, {R"(?v_WndProc@CSecondaryTray@@EEAA_JPEAUHWND__@@I_K_J@Z)", &CSecondaryTray_v_WndProc_Original, CSecondaryTray_v_WndProc_Hook, // Available in versions newer than 67.1. true}, }; 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 (!succeeded) { Wh_Log(L"HookExplorerPatcherSymbols failed"); } else if (g_initialized) { Wh_ApplyHookOperations(); } return succeeded; } bool IsExplorerPatcherModule(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++; if (_wcsnicmp(L"ep_taskbar.", moduleFileName, sizeof("ep_taskbar.") - 1) == 0) { Wh_Log(L"ExplorerPatcher taskbar module: %s", moduleFileName); return true; } return false; } bool HandleLoadedExplorerPatcher() { HMODULE hMods[1024]; DWORD cbNeeded; if (EnumProcessModules(GetCurrentProcess(), hMods, sizeof(hMods), &cbNeeded)) { for (size_t i = 0; i < cbNeeded / sizeof(HMODULE); i++) { if (IsExplorerPatcherModule(hMods[i])) { return HookExplorerPatcherSymbols(hMods[i]); } } } return true; } 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) { if (IsExplorerPatcherModule(module)) { HookExplorerPatcherSymbols(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, }, { {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.foregroundWindowOnly = Wh_GetIntSetting(L"foregroundWindowOnly"); g_settings.excludedPrograms.clear(); for (int i = 0;; i++) { PCWSTR program = Wh_GetStringSetting(L"excludedPrograms[%d]", i); bool hasProgram = *program; if (hasProgram) { std::wstring programUpper = program; LCMapStringEx( LOCALE_NAME_USER_DEFAULT, LCMAP_UPPERCASE, &programUpper[0], static_cast(programUpper.length()), &programUpper[0], static_cast(programUpper.length()), nullptr, nullptr, 0); g_settings.excludedPrograms.insert(std::move(programUpper)); } Wh_FreeStringSetting(program); if (!hasProgram) { break; } } g_settings.primaryMonitorOnly = Wh_GetIntSetting(L"primaryMonitorOnly"); g_settings.oldTaskbarOnWin11 = Wh_GetIntSetting(L"oldTaskbarOnWin11"); } BOOL Wh_ModInit() { Wh_Log(L">"); LoadSettings(); HMODULE hUser32Module = LoadLibrary(L"user32.dll"); if (hUser32Module) { pIsWindowArranged = (IsWindowArranged_t)GetProcAddress( hUser32Module, "IsWindowArranged"); } 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; } if (!HandleLoadedExplorerPatcher()) { Wh_Log(L"HandleLoadedExplorerPatcher failed"); return FALSE; } HMODULE kernelBaseModule = GetModuleHandle(L"kernelbase.dll"); auto pKernelBaseLoadLibraryExW = (decltype(&LoadLibraryExW))GetProcAddress( kernelBaseModule, "LoadLibraryExW"); WindhawkUtils::Wh_SetFunctionHookT(pKernelBaseLoadLibraryExW, LoadLibraryExW_Hook, &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_winEventHookThread) { PostThreadMessage(GetThreadId(g_winEventHookThread), WM_APP, 0, 0); WaitForSingleObject(g_winEventHookThread, INFINITE); CloseHandle(g_winEventHookThread); g_winEventHookThread = nullptr; } if (g_wasAutoHideDisabled) { HWND hTaskbarWnd = FindCurrentProcessTaskbarWnd(); if (hTaskbarWnd) { SendMessage(hTaskbarWnd, kHandleTrayPrivateSettingMessage, kTrayPrivateSettingAutoHideSet, FALSE); } } } BOOL Wh_ModSettingsChanged(BOOL* bReload) { Wh_Log(L">"); bool prevForegroundWindowOnly = g_settings.foregroundWindowOnly; bool prevOldTaskbarOnWin11 = g_settings.oldTaskbarOnWin11; LoadSettings(); if (g_settings.oldTaskbarOnWin11 != prevOldTaskbarOnWin11) { *bReload = TRUE; return TRUE; } if (g_settings.mode == Mode::never || prevForegroundWindowOnly != g_settings.foregroundWindowOnly) { 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; }