├── .clang-format ├── .vscode ├── c_cpp_properties.json ├── settings.json └── windhawk_headers_1.6.1 │ ├── windhawk_api.h │ ├── windhawk_api_internal.h │ └── windhawk_utils.h ├── README.md ├── images ├── 19PL0ss.png ├── 2ipCKJn.png ├── 3JiXwjT.png ├── 3b8h40F.png ├── 4NcGU5O.png ├── 4PxMvLg.png ├── 4hgxHJ0.png ├── 51q6OKl.png ├── 62DSgxs.png ├── 6Fg5h0d.png ├── 6UVVORa.png ├── 78eRcAJ.png ├── 7M5x5EJ.png ├── 7ZeEfYK.png ├── 8WU4YCX.png ├── B6mtUj9.gif ├── Buh8KnZ.png ├── BvXJlkj.png ├── ErUN0YU.png ├── Fe9K7v2.png ├── FtpUjt1.gif ├── GB4guXU.gif ├── GIdzI5V.png ├── GWCsO70.gif ├── Hnvjquv.png ├── JloORIB.png ├── LNPcw0G.png ├── LRhREtJ.png ├── LqBwGVn.png ├── MSKYKbE.png ├── PM4MfMs.png ├── RBK5Mv9.png ├── SCBbirW.png ├── Si3siPm.gif ├── T7YjTfk.png ├── TLza5fp.png ├── TsJX013.gif ├── VztpH9B.png ├── Xy04Zcu.png ├── Y5HA6Xv.png ├── YfO676m.gif ├── YiPSZdI.png ├── ZV47UCC.png ├── ZpzoDXy.png ├── a5qBigL.png ├── aEaxCWe.png ├── cErw24I.png ├── ecYYtGU.gif ├── ez0jyuW.png ├── fOmI2Rs.png ├── fsx8C56.png ├── gM9kbH5.png ├── gqTawuD.png ├── hEz1lhs.gif ├── hFU9oyK.gif ├── ie8Q9cl.gif ├── lMp8OLp.gif ├── lQEHQyR.png ├── mR5c3F5.png ├── moj4nOV.png ├── nGxrBYG.png ├── nVmyZrM.png ├── q6pgi0Z.gif ├── qeO9tLG.gif ├── rnnwOss.gif ├── sSI0Kh6.gif ├── tCQIKVh.png ├── tblTr3Q.png ├── uLITliK.png ├── v8Idmjy.png ├── vu6z164.png ├── wGGe2RS.gif ├── x7pg5xX.gif ├── zDATi9K.png └── zWxTGRb.png ├── mods ├── chrome-wheel-scroll-tabs.wh.cpp ├── desktop-icons-view.wh.cpp ├── disable-rounded-corners.wh.cpp ├── explorer-context-menu-classic.wh.cpp ├── explorer-details-better-file-sizes.wh.cpp ├── explorer-frame-classic.wh.cpp ├── explorer-name-windows.wh.cpp ├── extension-change-no-warning.wh.cpp ├── file-explorer-remove-suffixes.wh.cpp ├── flight-simulator-focus-helper.wh.cpp ├── icon-resource-redirect.wh.cpp ├── more-space-in-language-indicator.wh.cpp ├── no-flash-window.wh.cpp ├── notepad-dark-mode.wh.cpp ├── notifications-placement.wh.cpp ├── pinned-items-double-click.wh.cpp ├── slick-window-arrangement.wh.cpp ├── start-menu-all-apps.wh.cpp ├── start-menu-open-location.wh.cpp ├── taskbar-auto-hide-keyboard-only.wh.cpp ├── taskbar-auto-hide-speed.wh.cpp ├── taskbar-auto-hide-when-maximized.wh.cpp ├── taskbar-background-helper.wh.cpp ├── taskbar-button-click.wh.cpp ├── taskbar-button-scroll.wh.cpp ├── taskbar-classic-menu.wh.cpp ├── taskbar-clock-customization.wh.cpp ├── taskbar-grouping.wh.cpp ├── taskbar-hung-rearrangement-fix.wh.cpp ├── taskbar-icon-size.wh.cpp ├── taskbar-jump-list-on-cursor-pos.wh.cpp ├── taskbar-labels.wh.cpp ├── taskbar-left-click-cycle.wh.cpp ├── taskbar-multirow.wh.cpp ├── taskbar-notification-icon-spacing.wh.cpp ├── taskbar-notification-icons-show-all.wh.cpp ├── taskbar-on-top.wh.cpp ├── taskbar-primary-on-secondary-monitor.wh.cpp ├── taskbar-scroll-actions.wh.cpp ├── taskbar-show-desktop-button-aero-peek.wh.cpp ├── taskbar-start-button-position.wh.cpp ├── taskbar-thumbnail-reorder.wh.cpp ├── taskbar-thumbnail-size.wh.cpp ├── taskbar-thumbnails.wh.cpp ├── taskbar-tray-system-icon-tweaks.wh.cpp ├── taskbar-vertical.wh.cpp ├── taskbar-volume-control.wh.cpp ├── taskbar-wheel-cycle.wh.cpp ├── text-replace.wh.cpp ├── timer-resolution-control.wh.cpp ├── virtual-desktop-taskbar-order.wh.cpp ├── visual-studio-anti-rich-header.wh.cpp ├── vmware-disable-upgrade-dialog.wh.cpp ├── vscode-tweaker.wh.cpp ├── windows-11-file-explorer-styler.wh.cpp ├── windows-11-notification-center-styler.wh.cpp ├── windows-11-start-menu-styler.wh.cpp └── windows-11-taskbar-styler.wh.cpp └── scripts └── backup-images.py /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Chromium 2 | IndentWidth: 4 3 | CommentPragmas: ^[ \t]+@[a-zA-Z]+ 4 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "MSVC x64", 5 | "includePath": [ 6 | "${workspaceFolder}/.vscode/windhawk_headers_1.6.1" 7 | ], 8 | "defines": [ 9 | "UNICODE", 10 | "_UNICODE", 11 | "WINVER=0x0A00", 12 | "_WIN32_WINNT=0x0A00", 13 | "_WIN32_IE=0x0A00", 14 | "NTDDI_VERSION=0x0A000008", 15 | "__USE_MINGW_ANSI_STDIO=0", 16 | "WH_MOD", 17 | "WH_EDITING", 18 | "NOMINMAX" 19 | ], 20 | "windowsSdkVersion": "10.0.22621.0", 21 | "compilerPath": "cl.exe", 22 | "cStandard": "c17", 23 | "cppStandard": "c++23", 24 | "intelliSenseMode": "windows-msvc-x64", 25 | "forcedInclude": [ 26 | "${workspaceFolder}/.vscode/windhawk_headers_1.6.1/windhawk_api.h" 27 | ] 28 | } 29 | ], 30 | "version": 4 31 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "ARRAYSIZE", 4 | "DISASM", 5 | "DLGPROC", 6 | "dwmapi", 7 | "FARPROC", 8 | "HBITMAP", 9 | "HCURSOR", 10 | "HICON", 11 | "HINSTANCE", 12 | "HMENU", 13 | "HMONITOR", 14 | "HRESULT", 15 | "hstring", 16 | "HWND", 17 | "INTRESOURCE", 18 | "LPARAM", 19 | "LPCSTR", 20 | "LPWSTR", 21 | "NOZORDER", 22 | "NTAPI", 23 | "NTSTATUS", 24 | "PCSTR", 25 | "PCWSTR", 26 | "PWSTR", 27 | "Tweaker", 28 | "Unsubclass", 29 | "wcsicmp", 30 | "winrt", 31 | "WPARAM" 32 | ], 33 | "cSpell.ignoreRegExpList": [ 34 | "^// @compilerOptions .*$", 35 | "^#include .*$" 36 | ] 37 | } -------------------------------------------------------------------------------- /.vscode/windhawk_headers_1.6.1/windhawk_api_internal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct tagWH_FIND_SYMBOL_OPTIONS WH_FIND_SYMBOL_OPTIONS; 6 | typedef struct tagWH_FIND_SYMBOL WH_FIND_SYMBOL; 7 | typedef struct tagWH_SYMBOL_HOOK { 8 | const struct { 9 | PCWSTR string; 10 | size_t length; 11 | }* symbols; 12 | size_t symbolsCount; 13 | void** pOriginalFunction; 14 | void* hookFunction; 15 | bool optional; 16 | } WH_SYMBOL_HOOK; 17 | typedef struct tagWH_HOOK_SYMBOLS_OPTIONS WH_HOOK_SYMBOLS_OPTIONS; 18 | typedef struct tagWH_DISASM_RESULT WH_DISASM_RESULT; 19 | typedef struct tagWH_GET_URL_CONTENT_OPTIONS WH_GET_URL_CONTENT_OPTIONS; 20 | typedef struct tagWH_URL_CONTENT WH_URL_CONTENT; 21 | 22 | // Internal functions, do not call directly. 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | BOOL InternalWh_IsLogEnabled(void* mod); 28 | void InternalWh_Log(void* mod, PCWSTR format, va_list args); 29 | 30 | int InternalWh_GetIntValue(void* mod, PCWSTR valueName, int defaultValue); 31 | BOOL InternalWh_SetIntValue(void* mod, PCWSTR valueName, int value); 32 | size_t InternalWh_GetStringValue(void* mod, 33 | PCWSTR valueName, 34 | PWSTR stringBuffer, 35 | size_t bufferChars); 36 | BOOL InternalWh_SetStringValue(void* mod, PCWSTR valueName, PCWSTR value); 37 | size_t InternalWh_GetBinaryValue(void* mod, 38 | PCWSTR valueName, 39 | void* buffer, 40 | size_t bufferSize); 41 | BOOL InternalWh_SetBinaryValue(void* mod, 42 | PCWSTR valueName, 43 | const void* buffer, 44 | size_t bufferSize); 45 | BOOL InternalWh_DeleteValue(void* mod, PCWSTR valueName); 46 | 47 | size_t InternalWh_GetModStoragePath(void* mod, 48 | PWSTR pathBuffer, 49 | size_t bufferChars); 50 | 51 | int InternalWh_GetIntSetting(void* mod, PCWSTR valueName, va_list args); 52 | PCWSTR InternalWh_GetStringSetting(void* mod, PCWSTR valueName, va_list args); 53 | void InternalWh_FreeStringSetting(void* mod, PCWSTR string); 54 | 55 | BOOL InternalWh_SetFunctionHook(void* mod, 56 | void* targetFunction, 57 | void* hookFunction, 58 | void** originalFunction); 59 | BOOL InternalWh_RemoveFunctionHook(void* mod, void* targetFunction); 60 | BOOL InternalWh_ApplyHookOperations(void* mod); 61 | 62 | HANDLE InternalWh_FindFirstSymbol4(void* mod, 63 | HMODULE hModule, 64 | const WH_FIND_SYMBOL_OPTIONS* options, 65 | WH_FIND_SYMBOL* findData); 66 | BOOL InternalWh_FindNextSymbol2(void* mod, 67 | HANDLE symSearch, 68 | WH_FIND_SYMBOL* findData); 69 | void InternalWh_FindCloseSymbol(void* mod, HANDLE symSearch); 70 | 71 | BOOL InternalWh_HookSymbols(void* mod, 72 | HMODULE module, 73 | const WH_SYMBOL_HOOK* symbolHooks, 74 | size_t symbolHooksCount, 75 | const WH_HOOK_SYMBOLS_OPTIONS* options); 76 | 77 | BOOL InternalWh_Disasm(void* mod, void* address, WH_DISASM_RESULT* result); 78 | 79 | const WH_URL_CONTENT* InternalWh_GetUrlContent( 80 | void* mod, 81 | PCWSTR url, 82 | const WH_GET_URL_CONTENT_OPTIONS* options); 83 | void InternalWh_FreeUrlContent(void* mod, const WH_URL_CONTENT* content); 84 | 85 | #ifdef __cplusplus 86 | } 87 | #endif 88 | 89 | // Internal definitions for mods. 90 | #ifdef WH_MOD 91 | 92 | inline void* InternalWhModPtr; 93 | 94 | inline void InternalWh_Log_Wrapper(PCWSTR format, ...) { 95 | va_list args; 96 | va_start(args, format); 97 | InternalWh_Log(InternalWhModPtr, format, args); 98 | va_end(args); 99 | } 100 | 101 | inline BOOL InternalWh_HookSymbols_Wrapper( 102 | HMODULE module, 103 | const WH_SYMBOL_HOOK* symbolHooks, 104 | size_t symbolHooksCount, 105 | const WH_HOOK_SYMBOLS_OPTIONS* options) { 106 | return InternalWh_HookSymbols(InternalWhModPtr, module, symbolHooks, 107 | symbolHooksCount, options); 108 | } 109 | 110 | #endif // WH_MOD 111 | -------------------------------------------------------------------------------- /.vscode/windhawk_headers_1.6.1/windhawk_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | // Windhawk utilities that can be used by including this header file. 14 | namespace WindhawkUtils { 15 | 16 | struct SYMBOL_HOOK { 17 | /** 18 | * @brief A strong-type constructor. 19 | * @since Windhawk v1.4 20 | * @param symbols The list of symbol names to match. The first symbol that 21 | * will match one of the names will be used. 22 | * @param originalFunction The pointer to the function pointer that will 23 | * receive the symbol address. Can be `nullptr`. 24 | * @param hookFunction The hook function to set, or `nullptr` to only 25 | * retrieve's the symbol's address. 26 | * @param optional If set to `true`, the absence of this symbol isn't 27 | * considered an error. If the symbol isn't found, `originalFunction` 28 | * will be left unchanged. 29 | */ 30 | template 31 | SYMBOL_HOOK(std::initializer_list symbols, 32 | Prototype** originalFunction, 33 | std::type_identity_t hookFunction = nullptr, 34 | bool optional = false) 35 | : symbols(CopySymbols(symbols)), 36 | symbolsCount(symbols.size()), 37 | originalFunction(reinterpret_cast(originalFunction)), 38 | hookFunction(reinterpret_cast(hookFunction)), 39 | optional(optional) {} 40 | 41 | #ifdef WH_ENABLE_DEPRECATED_PARTS 42 | SYMBOL_HOOK(std::vector, void***, std::nullptr_t, bool) { 43 | Wh_Log( 44 | L"WARNING!!! Your mod triggered code which is no longer supported. " 45 | L"It only compiles for backwards compatibility. Please update your " 46 | L"mod."); 47 | } 48 | 49 | SYMBOL_HOOK(const SYMBOL_HOOK&) { 50 | Wh_Log( 51 | L"WARNING!!! Your mod triggered code which is no longer supported. " 52 | L"It only compiles for backwards compatibility. Please update your " 53 | L"mod."); 54 | } 55 | #endif // WH_ENABLE_DEPRECATED_PARTS 56 | 57 | /** 58 | * @brief A default constructor. 59 | * @since Windhawk v1.4.1 60 | */ 61 | SYMBOL_HOOK() = default; 62 | 63 | private: 64 | struct SymbolString { 65 | std::unique_ptr string; 66 | size_t length = 0; 67 | 68 | // We assume that `std::unique_ptr` is ABI-compatible with a pointer. 69 | static_assert(sizeof(string) == sizeof(string.get())); 70 | }; 71 | 72 | std::unique_ptr CopySymbols( 73 | std::initializer_list symbols) { 74 | auto result = std::make_unique(symbols.size()); 75 | SymbolString* p = result.get(); 76 | for (const auto& symbol : symbols) { 77 | p->string = std::make_unique(symbol.size()); 78 | std::copy(symbol.begin(), symbol.end(), p->string.get()); 79 | p->length = symbol.size(); 80 | p++; 81 | } 82 | return result; 83 | } 84 | 85 | std::unique_ptr symbols; 86 | size_t symbolsCount = 0; 87 | void** originalFunction = nullptr; 88 | void* hookFunction = nullptr; 89 | bool optional = false; 90 | 91 | // We assume that `std::unique_ptr` is ABI-compatible with a pointer. 92 | static_assert(sizeof(symbols) == sizeof(symbols.get())); // NOLINT 93 | }; 94 | 95 | /** 96 | * @brief Finds module functions by their symbol names and optionally hooks 97 | * them. Caches the result to avoid loading symbols each time. 98 | * @since Windhawk v1.3, `options` param since v1.5 99 | * @param module The module to look up. 100 | * @param symbolHooks The array of lookup entries. 101 | * @param symbolHooksCount The size of the array. 102 | * @param options Can be used to customize the symbol enumeration. Pass 103 | * `nullptr` to use the default options. 104 | * @return A boolean value indicating whether the function succeeded. 105 | */ 106 | bool HookSymbols(HMODULE module, 107 | const SYMBOL_HOOK* symbolHooks, 108 | size_t symbolHooksCount, 109 | const WH_HOOK_SYMBOLS_OPTIONS* options = nullptr); 110 | 111 | using WH_SUBCLASSPROC = LRESULT(CALLBACK*)(HWND hWnd, 112 | UINT uMsg, 113 | WPARAM wParam, 114 | LPARAM lParam, 115 | DWORD_PTR dwRefData); 116 | 117 | /** 118 | * @brief Subclasses a window. Similar to `SetWindowSubclass`, but can be called 119 | * from any thread, unlike `SetWindowSubclass` which can only be called in 120 | * the thread of the target window. 121 | * @since Windhawk v1.3 122 | * @param hWnd The window to subclass. 123 | * @param pfnSubclass The subclass window procedure. 124 | * @param dwRefData Custom reference data. This value is passed to the subclass 125 | * procedure in the `dwRefData` parameter. 126 | * @return A boolean value indicating whether the function succeeded. 127 | */ 128 | BOOL SetWindowSubclassFromAnyThread(HWND hWnd, 129 | WH_SUBCLASSPROC pfnSubclass, 130 | DWORD_PTR dwRefData); 131 | 132 | /** 133 | * @brief Removes a subclass from a window. 134 | * @since Windhawk v1.3 135 | * @param hWnd The window being subclassed. 136 | * @param pfnSubclass The subclass window procedure. 137 | */ 138 | void RemoveWindowSubclassFromAnyThread(HWND hWnd, WH_SUBCLASSPROC pfnSubclass); 139 | 140 | /** 141 | * @brief A strong-type wrapper for `Wh_SetFunctionHook`. Registers a hook for 142 | * the specified target function. Can't be called after `Wh_ModBeforeUninit` 143 | * returns. Registered hook operations can be applied with 144 | * `Wh_ApplyHookOperations`. 145 | * @since Windhawk v1.5 146 | * @param targetFunction A pointer to the target function, which will be 147 | * overridden by the detour function. 148 | * @param hookFunction A pointer to the detour function, which will override the 149 | * target function. 150 | * @param originalFunction A pointer to the trampoline function, which will be 151 | * used to call the original target function. 152 | * @return A boolean value indicating whether the function succeeded. 153 | */ 154 | template 155 | BOOL SetFunctionHook(Prototype* targetFunction, 156 | Prototype* hookFunction, 157 | Prototype** originalFunction) { 158 | return Wh_SetFunctionHook(reinterpret_cast(targetFunction), 159 | reinterpret_cast(hookFunction), 160 | reinterpret_cast(originalFunction)); 161 | } 162 | 163 | /** 164 | * @brief A deprecated alias for `SetFunctionHook`. 165 | * @since Windhawk v1.4 166 | */ 167 | template 168 | [[deprecated("Use WindhawkUtils::SetFunctionHook() instead.")]] 169 | BOOL Wh_SetFunctionHookT(Prototype* targetFunction, 170 | Prototype* hookFunction, 171 | Prototype** originalFunction) { 172 | return Wh_SetFunctionHook(reinterpret_cast(targetFunction), 173 | reinterpret_cast(hookFunction), 174 | reinterpret_cast(originalFunction)); 175 | } 176 | 177 | /** 178 | * @brief A RAII wrapper for a string setting. Can be created with 179 | * `auto string = StringSetting::make("SettingName")` or 180 | * `auto string = StringSetting(Wh_GetStringSetting("SettingName"))`. 181 | * `Wh_FreeStringSetting` is called upon destruction. 182 | * @since Windhawk v1.4 183 | */ 184 | class StringSetting { 185 | public: 186 | StringSetting() = default; 187 | explicit StringSetting(PCWSTR valueName) : m_stringSetting(valueName) {} 188 | operator PCWSTR() const { return m_stringSetting.get(); } 189 | PCWSTR get() const { return m_stringSetting.get(); } 190 | 191 | template 192 | static StringSetting make(PCWSTR valueName, Args... args) { 193 | return StringSetting(Wh_GetStringSetting(valueName, args...)); 194 | } 195 | 196 | private: 197 | // https://stackoverflow.com/a/51274008 198 | template 199 | struct deleter_from_fn { 200 | template 201 | constexpr void operator()(T* arg) const { 202 | fn(arg); 203 | } 204 | }; 205 | using string_setting_unique_ptr = 206 | std::unique_ptr>; 207 | 208 | string_setting_unique_ptr m_stringSetting; 209 | }; 210 | 211 | //////////////////////////////////////////////////////////////////////////////// 212 | // Implementation 213 | 214 | inline bool HookSymbols(HMODULE module, 215 | const SYMBOL_HOOK* symbolHooks, 216 | size_t symbolHooksCount, 217 | const WH_HOOK_SYMBOLS_OPTIONS* options) { 218 | #ifndef WH_EDITING 219 | // Both types are assumed to be ABI-compatible. 220 | static_assert(sizeof(*symbolHooks) == sizeof(WH_SYMBOL_HOOK)); 221 | 222 | return InternalWh_HookSymbols_Wrapper( 223 | module, reinterpret_cast(symbolHooks), 224 | symbolHooksCount, options); 225 | #else 226 | return false; 227 | #endif 228 | } 229 | 230 | // Implementation details. 231 | namespace detail { 232 | 233 | // wParam - TRUE to subclass, FALSE to unsubclass 234 | // lParam - if wParam is TRUE, subclass data 235 | // lParam - if wParam is FALSE, the subclass window procedure 236 | inline UINT GetSubclassRegisteredMsg() { 237 | static UINT subclassRegisteredMsg = RegisterWindowMessage( 238 | L"Windhawk_SetWindowSubclassFromAnyThread_" WH_MOD_ID); 239 | return subclassRegisteredMsg; 240 | } 241 | 242 | inline LRESULT WINAPI SubclassProcWrapper(HWND hWnd, 243 | UINT uMsg, 244 | WPARAM wParam, 245 | LPARAM lParam, 246 | UINT_PTR uIdSubclass, 247 | DWORD_PTR dwRefData) { 248 | // Turns out we must remove the window subclass before the window being 249 | // subclassed is destroyed. 250 | // https://devblogs.microsoft.com/oldnewthing/20031111-00/?p=41883 251 | if (uMsg == WM_NCDESTROY || (uMsg == GetSubclassRegisteredMsg() && 252 | !wParam && (UINT_PTR)lParam == uIdSubclass)) { 253 | RemoveWindowSubclass(hWnd, SubclassProcWrapper, uIdSubclass); 254 | } 255 | 256 | auto pfnSubclass = (WH_SUBCLASSPROC)uIdSubclass; 257 | return pfnSubclass(hWnd, uMsg, wParam, lParam, dwRefData); 258 | } 259 | 260 | } // namespace detail 261 | 262 | inline BOOL SetWindowSubclassFromAnyThread(HWND hWnd, 263 | WH_SUBCLASSPROC pfnSubclass, 264 | DWORD_PTR dwRefData) { 265 | struct SET_WINDOW_SUBCLASS_FROM_ANY_THREAD_PARAM { 266 | SUBCLASSPROC pfnSubclass; 267 | UINT_PTR uIdSubclass; 268 | DWORD_PTR dwRefData; 269 | BOOL result; 270 | }; 271 | 272 | DWORD dwThreadId = GetWindowThreadProcessId(hWnd, nullptr); 273 | if (dwThreadId == 0) { 274 | return FALSE; 275 | } 276 | 277 | if (dwThreadId == GetCurrentThreadId()) { 278 | return SetWindowSubclass(hWnd, detail::SubclassProcWrapper, 279 | (UINT_PTR)pfnSubclass, dwRefData); 280 | } 281 | 282 | HHOOK hook = SetWindowsHookEx( 283 | WH_CALLWNDPROC, 284 | [](int nCode, WPARAM wParam, LPARAM lParam) WINAPI -> LRESULT { 285 | if (nCode == HC_ACTION) { 286 | const CWPSTRUCT* cwp = (const CWPSTRUCT*)lParam; 287 | if (cwp->message == detail::GetSubclassRegisteredMsg() && 288 | cwp->wParam) { 289 | SET_WINDOW_SUBCLASS_FROM_ANY_THREAD_PARAM* param = 290 | (SET_WINDOW_SUBCLASS_FROM_ANY_THREAD_PARAM*)cwp->lParam; 291 | param->result = 292 | SetWindowSubclass(cwp->hwnd, param->pfnSubclass, 293 | param->uIdSubclass, param->dwRefData); 294 | } 295 | } 296 | 297 | return CallNextHookEx(nullptr, nCode, wParam, lParam); 298 | }, 299 | nullptr, dwThreadId); 300 | if (!hook) { 301 | return FALSE; 302 | } 303 | 304 | SET_WINDOW_SUBCLASS_FROM_ANY_THREAD_PARAM param; 305 | param.pfnSubclass = detail::SubclassProcWrapper; 306 | param.uIdSubclass = (UINT_PTR)pfnSubclass; 307 | param.dwRefData = dwRefData; 308 | param.result = FALSE; 309 | SendMessage(hWnd, detail::GetSubclassRegisteredMsg(), TRUE, (WPARAM)¶m); 310 | 311 | UnhookWindowsHookEx(hook); 312 | 313 | return param.result; 314 | } 315 | 316 | inline void RemoveWindowSubclassFromAnyThread(HWND hWnd, 317 | WH_SUBCLASSPROC pfnSubclass) { 318 | SendMessage(hWnd, detail::GetSubclassRegisteredMsg(), FALSE, 319 | (LPARAM)pfnSubclass); 320 | } 321 | 322 | } // namespace WindhawkUtils 323 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # My Windhawk Mods 2 | 3 | My collection of [Windhawk](https://windhawk.net/) mods. 4 | 5 | This repository is used for development. The actual mods are available at [The 6 | official collection of Windhawk 7 | mods](https://github.com/ramensoftware/windhawk-mods). 8 | 9 | Pull requests are welcome. For bug reports and feature requests, please use the 10 | [Windhawk mods issue 11 | tracker](https://github.com/ramensoftware/windhawk-mods/issues). 12 | -------------------------------------------------------------------------------- /images/19PL0ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/19PL0ss.png -------------------------------------------------------------------------------- /images/2ipCKJn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/2ipCKJn.png -------------------------------------------------------------------------------- /images/3JiXwjT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/3JiXwjT.png -------------------------------------------------------------------------------- /images/3b8h40F.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/3b8h40F.png -------------------------------------------------------------------------------- /images/4NcGU5O.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/4NcGU5O.png -------------------------------------------------------------------------------- /images/4PxMvLg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/4PxMvLg.png -------------------------------------------------------------------------------- /images/4hgxHJ0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/4hgxHJ0.png -------------------------------------------------------------------------------- /images/51q6OKl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/51q6OKl.png -------------------------------------------------------------------------------- /images/62DSgxs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/62DSgxs.png -------------------------------------------------------------------------------- /images/6Fg5h0d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/6Fg5h0d.png -------------------------------------------------------------------------------- /images/6UVVORa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/6UVVORa.png -------------------------------------------------------------------------------- /images/78eRcAJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/78eRcAJ.png -------------------------------------------------------------------------------- /images/7M5x5EJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/7M5x5EJ.png -------------------------------------------------------------------------------- /images/7ZeEfYK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/7ZeEfYK.png -------------------------------------------------------------------------------- /images/8WU4YCX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/8WU4YCX.png -------------------------------------------------------------------------------- /images/B6mtUj9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/B6mtUj9.gif -------------------------------------------------------------------------------- /images/Buh8KnZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/Buh8KnZ.png -------------------------------------------------------------------------------- /images/BvXJlkj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/BvXJlkj.png -------------------------------------------------------------------------------- /images/ErUN0YU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/ErUN0YU.png -------------------------------------------------------------------------------- /images/Fe9K7v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/Fe9K7v2.png -------------------------------------------------------------------------------- /images/FtpUjt1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/FtpUjt1.gif -------------------------------------------------------------------------------- /images/GB4guXU.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/GB4guXU.gif -------------------------------------------------------------------------------- /images/GIdzI5V.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/GIdzI5V.png -------------------------------------------------------------------------------- /images/GWCsO70.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/GWCsO70.gif -------------------------------------------------------------------------------- /images/Hnvjquv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/Hnvjquv.png -------------------------------------------------------------------------------- /images/JloORIB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/JloORIB.png -------------------------------------------------------------------------------- /images/LNPcw0G.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/LNPcw0G.png -------------------------------------------------------------------------------- /images/LRhREtJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/LRhREtJ.png -------------------------------------------------------------------------------- /images/LqBwGVn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/LqBwGVn.png -------------------------------------------------------------------------------- /images/MSKYKbE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/MSKYKbE.png -------------------------------------------------------------------------------- /images/PM4MfMs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/PM4MfMs.png -------------------------------------------------------------------------------- /images/RBK5Mv9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/RBK5Mv9.png -------------------------------------------------------------------------------- /images/SCBbirW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/SCBbirW.png -------------------------------------------------------------------------------- /images/Si3siPm.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/Si3siPm.gif -------------------------------------------------------------------------------- /images/T7YjTfk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/T7YjTfk.png -------------------------------------------------------------------------------- /images/TLza5fp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/TLza5fp.png -------------------------------------------------------------------------------- /images/TsJX013.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/TsJX013.gif -------------------------------------------------------------------------------- /images/VztpH9B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/VztpH9B.png -------------------------------------------------------------------------------- /images/Xy04Zcu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/Xy04Zcu.png -------------------------------------------------------------------------------- /images/Y5HA6Xv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/Y5HA6Xv.png -------------------------------------------------------------------------------- /images/YfO676m.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/YfO676m.gif -------------------------------------------------------------------------------- /images/YiPSZdI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/YiPSZdI.png -------------------------------------------------------------------------------- /images/ZV47UCC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/ZV47UCC.png -------------------------------------------------------------------------------- /images/ZpzoDXy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/ZpzoDXy.png -------------------------------------------------------------------------------- /images/a5qBigL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/a5qBigL.png -------------------------------------------------------------------------------- /images/aEaxCWe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/aEaxCWe.png -------------------------------------------------------------------------------- /images/cErw24I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/cErw24I.png -------------------------------------------------------------------------------- /images/ecYYtGU.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/ecYYtGU.gif -------------------------------------------------------------------------------- /images/ez0jyuW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/ez0jyuW.png -------------------------------------------------------------------------------- /images/fOmI2Rs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/fOmI2Rs.png -------------------------------------------------------------------------------- /images/fsx8C56.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/fsx8C56.png -------------------------------------------------------------------------------- /images/gM9kbH5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/gM9kbH5.png -------------------------------------------------------------------------------- /images/gqTawuD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/gqTawuD.png -------------------------------------------------------------------------------- /images/hEz1lhs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/hEz1lhs.gif -------------------------------------------------------------------------------- /images/hFU9oyK.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/hFU9oyK.gif -------------------------------------------------------------------------------- /images/ie8Q9cl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/ie8Q9cl.gif -------------------------------------------------------------------------------- /images/lMp8OLp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/lMp8OLp.gif -------------------------------------------------------------------------------- /images/lQEHQyR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/lQEHQyR.png -------------------------------------------------------------------------------- /images/mR5c3F5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/mR5c3F5.png -------------------------------------------------------------------------------- /images/moj4nOV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/moj4nOV.png -------------------------------------------------------------------------------- /images/nGxrBYG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/nGxrBYG.png -------------------------------------------------------------------------------- /images/nVmyZrM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/nVmyZrM.png -------------------------------------------------------------------------------- /images/q6pgi0Z.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/q6pgi0Z.gif -------------------------------------------------------------------------------- /images/qeO9tLG.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/qeO9tLG.gif -------------------------------------------------------------------------------- /images/rnnwOss.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/rnnwOss.gif -------------------------------------------------------------------------------- /images/sSI0Kh6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/sSI0Kh6.gif -------------------------------------------------------------------------------- /images/tCQIKVh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/tCQIKVh.png -------------------------------------------------------------------------------- /images/tblTr3Q.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/tblTr3Q.png -------------------------------------------------------------------------------- /images/uLITliK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/uLITliK.png -------------------------------------------------------------------------------- /images/v8Idmjy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/v8Idmjy.png -------------------------------------------------------------------------------- /images/vu6z164.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/vu6z164.png -------------------------------------------------------------------------------- /images/wGGe2RS.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/wGGe2RS.gif -------------------------------------------------------------------------------- /images/x7pg5xX.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/x7pg5xX.gif -------------------------------------------------------------------------------- /images/zDATi9K.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/zDATi9K.png -------------------------------------------------------------------------------- /images/zWxTGRb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m417z/my-windhawk-mods/07da50b336e35147070d3a96cfe27f9e3ee44bef/images/zWxTGRb.png -------------------------------------------------------------------------------- /mods/desktop-icons-view.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id desktop-icons-view 3 | // @name Desktop icons view 4 | // @description Change desktop icons view to list, details, small icons, or tiles 5 | // @version 1.0.2 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include explorer.exe 11 | // @architecture x86-64 12 | // @compilerOptions -lcomdlg32 13 | // ==/WindhawkMod== 14 | 15 | // ==WindhawkModReadme== 16 | /* 17 | # Desktop icons view 18 | 19 | Change desktop icons view to list, details, small icons, or tiles. 20 | 21 | Based on [List view desktop](https://github.com/deanm/lvd). 22 | 23 | ### Acknowledgements 24 | 25 | * **nvhhr**: Coming up with the mod idea, testing. 26 | 27 | ### Screenshots 28 | 29 | ![List](https://i.imgur.com/51q6OKl.png) \ 30 | *List* 31 | 32 | ![Details](https://i.imgur.com/a5qBigL.png) \ 33 | *Details* 34 | 35 | ![Small icons](https://i.imgur.com/PM4MfMs.png) \ 36 | *Small icons* 37 | 38 | ![Tiles](https://i.imgur.com/gqTawuD.png) \ 39 | *Tiles* 40 | */ 41 | // ==/WindhawkModReadme== 42 | 43 | // ==WindhawkModSettings== 44 | /* 45 | - style: list 46 | $name: Desktop icons style 47 | $options: 48 | - list: List 49 | - details: Details 50 | - smallicon: Small icons 51 | - tile: Tiles 52 | - colwidth: 500 53 | $name: Column Width 54 | $description: Sets the width of filenames 55 | - monitor: 1 56 | $name: Monitor 57 | $description: The monitor number to have your desktop icons on 58 | */ 59 | // ==/WindhawkModSettings== 60 | 61 | #include 62 | 63 | struct { 64 | int style; 65 | int colwidth; 66 | int monitor; 67 | } settings; 68 | 69 | HWND FindChild(HWND parent, LPCWSTR cls, LPCWSTR win) { 70 | if (parent == nullptr) { 71 | return nullptr; 72 | } 73 | return FindWindowEx(parent, nullptr, cls, win); 74 | } 75 | 76 | bool IsFolderViewWnd(HWND hWnd) { 77 | WCHAR buffer[64]; 78 | 79 | if (!GetClassName(hWnd, buffer, ARRAYSIZE(buffer)) || 80 | _wcsicmp(buffer, L"SysListView32")) { 81 | return false; 82 | } 83 | 84 | if (!GetWindowText(hWnd, buffer, ARRAYSIZE(buffer)) || 85 | _wcsicmp(buffer, L"FolderView")) { 86 | return false; 87 | } 88 | 89 | HWND hParentWnd = GetAncestor(hWnd, GA_PARENT); 90 | if (!hParentWnd) { 91 | return false; 92 | } 93 | 94 | if (!GetClassName(hParentWnd, buffer, ARRAYSIZE(buffer)) || 95 | _wcsicmp(buffer, L"SHELLDLL_DefView")) { 96 | return false; 97 | } 98 | 99 | if (GetWindowTextLength(hParentWnd) > 0) { 100 | return false; 101 | } 102 | 103 | HWND hParentWnd2 = GetAncestor(hParentWnd, GA_PARENT); 104 | if (!hParentWnd2) { 105 | return false; 106 | } 107 | 108 | if ((!GetClassName(hParentWnd2, buffer, ARRAYSIZE(buffer)) || 109 | _wcsicmp(buffer, L"Progman")) && 110 | hParentWnd2 != GetShellWindow()) { 111 | return false; 112 | } 113 | 114 | return true; 115 | } 116 | 117 | HWND GetFolderViewWnd() { 118 | HWND hFolderFolderViewWnd = 119 | FindChild(FindChild(GetShellWindow(), L"SHELLDLL_DefView", L""), 120 | L"SysListView32", L"FolderView"); 121 | if (!hFolderFolderViewWnd) { 122 | return nullptr; 123 | } 124 | 125 | DWORD dwProcessId = 0; 126 | if (!GetWindowThreadProcessId(hFolderFolderViewWnd, &dwProcessId) || 127 | dwProcessId != GetCurrentProcessId()) { 128 | return nullptr; 129 | } 130 | 131 | return hFolderFolderViewWnd; 132 | } 133 | 134 | HMONITOR GetMonitorById(int monitorId) { 135 | HMONITOR monitorResult = nullptr; 136 | int currentMonitorId = 0; 137 | 138 | auto monitorEnumProc = [&monitorResult, ¤tMonitorId, 139 | monitorId](HMONITOR hMonitor) -> BOOL { 140 | if (currentMonitorId == monitorId) { 141 | monitorResult = hMonitor; 142 | return FALSE; 143 | } 144 | currentMonitorId++; 145 | return TRUE; 146 | }; 147 | 148 | EnumDisplayMonitors( 149 | nullptr, nullptr, 150 | [](HMONITOR hMonitor, HDC hdc, LPRECT lprcMonitor, 151 | LPARAM dwData) -> BOOL { 152 | auto& proc = *reinterpret_cast(dwData); 153 | return proc(hMonitor); 154 | }, 155 | reinterpret_cast(&monitorEnumProc)); 156 | 157 | return monitorResult; 158 | } 159 | 160 | void SetDesktopStyle(HWND hFolderViewWnd, int view) { 161 | DWORD style = GetWindowLong(hFolderViewWnd, GWL_STYLE); 162 | if (view == LV_VIEW_ICON) { 163 | SendMessage(hFolderViewWnd, LVM_SETVIEW, LV_VIEW_ICON, 0); 164 | 165 | SetWindowLong(hFolderViewWnd, GWL_STYLE, style | LVS_NOSCROLL); 166 | } else { 167 | // Setup the style (removing noscroll), otherwise list style doesn't 168 | // seem to work correctly (docs say noscroll is incompatible with list). 169 | SetWindowLong(hFolderViewWnd, GWL_STYLE, style & ~LVS_NOSCROLL); 170 | // Switch view. 171 | SendMessage(hFolderViewWnd, LVM_SETVIEW, view, 0); 172 | SendMessage(hFolderViewWnd, LVM_SETEXTENDEDLISTVIEWSTYLE, 173 | LVS_EX_DOUBLEBUFFER, 0); 174 | // Set the column width. 175 | ListView_SetColumnWidth(hFolderViewWnd, 0, settings.colwidth); 176 | } 177 | 178 | // Get working area of desktop and position desktop window accordingly. 179 | HMONITOR monitor = GetMonitorById(settings.monitor - 1); 180 | if (!monitor) { 181 | monitor = MonitorFromPoint(POINT{0, 0}, MONITOR_DEFAULTTONEAREST); 182 | } 183 | 184 | MONITORINFO monitorInfo{ 185 | .cbSize = sizeof(monitorInfo), 186 | }; 187 | if (GetMonitorInfo(monitor, &monitorInfo)) { 188 | RECT rc = monitorInfo.rcWork; 189 | MapWindowPoints(nullptr, GetAncestor(hFolderViewWnd, GA_PARENT), 190 | (POINT*)(&rc), sizeof(RECT) / sizeof(POINT)); 191 | int x = rc.left; 192 | int y = rc.top; 193 | int cx = rc.right - rc.left; 194 | int cy = rc.bottom - rc.top; 195 | SetWindowPos(hFolderViewWnd, nullptr, x, y, cx, cy, SWP_NOZORDER); 196 | } 197 | // Refresh. 198 | UpdateWindow(hFolderViewWnd); 199 | } 200 | 201 | using CreateWindowExW_t = decltype(&CreateWindowExW); 202 | CreateWindowExW_t CreateWindowExW_Original; 203 | HWND WINAPI CreateWindowExW_Hook(DWORD dwExStyle, 204 | LPCWSTR lpClassName, 205 | LPCWSTR lpWindowName, 206 | DWORD dwStyle, 207 | int X, 208 | int Y, 209 | int nWidth, 210 | int nHeight, 211 | HWND hWndParent, 212 | HMENU hMenu, 213 | HINSTANCE hInstance, 214 | PVOID lpParam) { 215 | HWND hWnd = CreateWindowExW_Original(dwExStyle, lpClassName, lpWindowName, 216 | dwStyle, X, Y, nWidth, nHeight, 217 | hWndParent, hMenu, hInstance, lpParam); 218 | if (!hWnd || !IsFolderViewWnd(hWnd)) { 219 | return hWnd; 220 | } 221 | 222 | Wh_Log(L"FolderView window created: %08X", (DWORD)(ULONG_PTR)hWnd); 223 | // SetDesktopStyle(hWnd, settings.style); 224 | 225 | static UINT s_timer = 0; 226 | static HWND s_hWnd; 227 | s_hWnd = hWnd; 228 | s_timer = SetTimer(nullptr, s_timer, 1000, 229 | [](HWND hwnd, // handle of window for timer messages 230 | UINT uMsg, // WM_TIMER message 231 | UINT_PTR idEvent, // timer identifier 232 | DWORD dwTime // current system time 233 | ) WINAPI { 234 | Wh_Log(L">"); 235 | 236 | KillTimer(nullptr, s_timer); 237 | s_timer = 0; 238 | 239 | SetDesktopStyle(s_hWnd, settings.style); 240 | s_hWnd = nullptr; 241 | }); 242 | 243 | return hWnd; 244 | } 245 | 246 | void LoadSettings() { 247 | PCWSTR style = Wh_GetStringSetting(L"style"); 248 | settings.style = LV_VIEW_ICON; 249 | if (wcscmp(style, L"details") == 0) { 250 | settings.style = LV_VIEW_DETAILS; 251 | } else if (wcscmp(style, L"smallicon") == 0) { 252 | settings.style = LV_VIEW_SMALLICON; 253 | } else if (wcscmp(style, L"list") == 0) { 254 | settings.style = LV_VIEW_LIST; 255 | } else if (wcscmp(style, L"tile") == 0) { 256 | settings.style = LV_VIEW_TILE; 257 | } 258 | Wh_FreeStringSetting(style); 259 | 260 | settings.colwidth = Wh_GetIntSetting(L"colwidth"); 261 | settings.monitor = Wh_GetIntSetting(L"monitor"); 262 | } 263 | 264 | BOOL Wh_ModInit() { 265 | Wh_Log(L">"); 266 | 267 | LoadSettings(); 268 | 269 | Wh_SetFunctionHook((void*)CreateWindowExW, (void*)CreateWindowExW_Hook, 270 | (void**)&CreateWindowExW_Original); 271 | 272 | return TRUE; 273 | } 274 | 275 | void Wh_ModAfterInit() { 276 | Wh_Log(L">"); 277 | 278 | if (HWND hFolderFolderViewWnd = GetFolderViewWnd()) { 279 | SetDesktopStyle(hFolderFolderViewWnd, settings.style); 280 | } 281 | } 282 | 283 | void Wh_ModUninit() { 284 | Wh_Log(L">"); 285 | 286 | if (HWND hFolderFolderViewWnd = GetFolderViewWnd()) { 287 | SetDesktopStyle(hFolderFolderViewWnd, LV_VIEW_ICON); 288 | } 289 | } 290 | 291 | BOOL Wh_ModSettingsChanged(BOOL* bReload) { 292 | Wh_Log(L"Settings changed"); 293 | *bReload = TRUE; 294 | return TRUE; 295 | } 296 | -------------------------------------------------------------------------------- /mods/disable-rounded-corners.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id disable-rounded-corners 3 | // @name Disable rounded corners in Windows 11 4 | // @description A simple mod to disable window rounded corners in Windows 11 5 | // @version 1.0.1 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include dwm.exe 11 | // @architecture x86-64 12 | // ==/WindhawkMod== 13 | 14 | // Source code is published under The GNU General Public License v3.0. 15 | // 16 | // For bug reports and feature requests, please open an issue here: 17 | // https://github.com/ramensoftware/windhawk-mods/issues 18 | // 19 | // For pull requests, development takes place here: 20 | // https://github.com/m417z/my-windhawk-mods 21 | 22 | // ==WindhawkModReadme== 23 | /* 24 | # Disable rounded corners in Windows 11 25 | 26 | A simple mod to disable window rounded corners in Windows 11. 27 | 28 | Based on the 29 | [Win11DisableRoundedCorners](https://github.com/valinet/Win11DisableRoundedCorners) 30 | project by Valentin Radu. 31 | 32 | ![Screenshot](https://i.imgur.com/ez0jyuW.png) 33 | 34 | ## ⚠ Important usage note ⚠ 35 | 36 | In order to use this mod, you must allow Windhawk to inject into the **dwm.exe** 37 | system process. To do so, add it to the process inclusion list in the advanced 38 | settings. If you do not do this, it will silently fail to inject. 39 | 40 | ![Advanced settings screenshot](https://i.imgur.com/LRhREtJ.png) 41 | */ 42 | // ==/WindhawkModReadme== 43 | 44 | #include 45 | 46 | int (*WINAPI GetEffectiveCornerStyle_Original)(); 47 | int WINAPI GetEffectiveCornerStyle_Hook() { 48 | return 0; 49 | } 50 | 51 | BOOL Wh_ModInit() { 52 | Wh_Log(L">"); 53 | 54 | HMODULE udwm = GetModuleHandle(L"udwm.dll"); 55 | if (!udwm) { 56 | Wh_Log(L"udwm.dll isn't loaded"); 57 | return FALSE; 58 | } 59 | 60 | WindhawkUtils::SYMBOL_HOOK symbolHooks[] = { 61 | { 62 | {LR"(private: enum CORNER_STYLE __cdecl CTopLevelWindow::GetEffectiveCornerStyle(void))"}, 63 | (void**)&GetEffectiveCornerStyle_Original, 64 | (void*)GetEffectiveCornerStyle_Hook, 65 | }, 66 | }; 67 | 68 | return HookSymbols(udwm, symbolHooks, ARRAYSIZE(symbolHooks)); 69 | } 70 | 71 | void Wh_ModUninit() { 72 | Wh_Log(L">"); 73 | } 74 | -------------------------------------------------------------------------------- /mods/explorer-context-menu-classic.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id explorer-context-menu-classic 3 | // @name Classic context menu on Windows 11 4 | // @description Always show the classic context menu without having to select "Show More Options" or hold Shift 5 | // @version 1.0.2 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include explorer.exe 11 | // @architecture x86-64 12 | // ==/WindhawkMod== 13 | 14 | // ==WindhawkModReadme== 15 | /* 16 | # Classic context menu on Windows 11 17 | 18 | Always show the classic context menu without having to select "Show More 19 | Options" or hold Shift. 20 | 21 | You can hold the Ctrl key to temporarily disable the mod and open the new menu. 22 | 23 | ![Demonstration](https://i.imgur.com/GIdzI5V.png) 24 | */ 25 | // ==/WindhawkModReadme== 26 | 27 | // ==WindhawkModSettings== 28 | /* 29 | - overrideWithCtrl: true 30 | $name: Override with Ctrl 31 | $description: >- 32 | If enabled, you can hold the Ctrl key to temporarily disable the mod and 33 | open the new menu 34 | */ 35 | // ==/WindhawkModSettings== 36 | 37 | #include 38 | 39 | #include 40 | 41 | struct { 42 | bool overrideWithCtrl; 43 | } g_settings; 44 | 45 | using IUnknown_QueryService_t = decltype(&IUnknown_QueryService); 46 | IUnknown_QueryService_t IUnknown_QueryService_Original; 47 | HRESULT WINAPI IUnknown_QueryService_Hook(IUnknown* punk, 48 | const GUID& guidService, 49 | const IID& riid, 50 | void** ppvOut) { 51 | // GUIDs from shell32.dll, CDefView::TryGetContextMenuPresenter. 52 | 53 | // {B306C5B1-B4F2-473C-B6FF-701B246CE2D2} 54 | constexpr GUID guidServiceTarget = { 55 | 0xb306c5b1, 56 | 0xb4f2, 57 | 0x473c, 58 | {0xb6, 0xff, 0x70, 0x1b, 0x24, 0x6c, 0xe2, 0xd2}}; 59 | 60 | // {706461D1-AC5F-4730-BFE3-CAC6CAD5EF5E} 61 | constexpr GUID riidTarget = { 62 | 0x706461d1, 63 | 0xac5f, 64 | 0x4730, 65 | {0xbf, 0xe3, 0xca, 0xc6, 0xca, 0xd5, 0xef, 0x5e}}; 66 | 67 | // {37A472F7-63CF-4CCF-A88B-5231A3C7D8B6} 68 | // Changed to this version in update KB5052093 of Windows 11 version 24H2. 69 | constexpr GUID riidTarget2 = { 70 | 0x37a472f7, 71 | 0x63cf, 72 | 0x4ccf, 73 | {0xa8, 0x8b, 0x52, 0x31, 0xa3, 0xc7, 0xd8, 0xb6}}; 74 | 75 | if (IsEqualGUID(guidService, guidServiceTarget) && 76 | (IsEqualGUID(riid, riidTarget) || IsEqualGUID(riid, riidTarget2))) { 77 | Wh_Log(L">"); 78 | 79 | if (g_settings.overrideWithCtrl && GetKeyState(VK_CONTROL) < 0) { 80 | // Temporarily off. 81 | } else { 82 | Wh_Log(L"Disallowing new menu"); 83 | return E_FAIL; 84 | } 85 | } 86 | 87 | return IUnknown_QueryService_Original(punk, guidService, riid, ppvOut); 88 | } 89 | 90 | using CNscTree_ShouldShowMiniMenu_t = bool(WINAPI*)(void* pThis, void* param1); 91 | CNscTree_ShouldShowMiniMenu_t CNscTree_ShouldShowMiniMenu_Original; 92 | bool WINAPI CNscTree_ShouldShowMiniMenu_Hook(void* pThis, void* param1) { 93 | Wh_Log(L">"); 94 | 95 | if (g_settings.overrideWithCtrl && GetKeyState(VK_CONTROL) < 0) { 96 | // Temporarily off. 97 | } else { 98 | Wh_Log(L"Disallowing new menu"); 99 | return false; 100 | } 101 | 102 | return CNscTree_ShouldShowMiniMenu_Original(pThis, param1); 103 | } 104 | 105 | bool HookExplorerFrameSymbols() { 106 | HMODULE module = LoadLibrary(L"explorerframe.dll"); 107 | if (!module) { 108 | Wh_Log(L"Couldn't load explorerframe.dll"); 109 | return false; 110 | } 111 | 112 | WindhawkUtils::SYMBOL_HOOK explorerFrameDllHooks[] = { 113 | { 114 | {LR"(private: bool __cdecl CNscTree::ShouldShowMiniMenu(struct _TREEITEM *))"}, 115 | &CNscTree_ShouldShowMiniMenu_Original, 116 | CNscTree_ShouldShowMiniMenu_Hook, 117 | true, 118 | }, 119 | }; 120 | 121 | return HookSymbols(module, explorerFrameDllHooks, 122 | ARRAYSIZE(explorerFrameDllHooks)); 123 | } 124 | 125 | void LoadSettings() { 126 | g_settings.overrideWithCtrl = Wh_GetIntSetting(L"overrideWithCtrl"); 127 | } 128 | 129 | BOOL Wh_ModInit() { 130 | Wh_Log(L">"); 131 | 132 | LoadSettings(); 133 | 134 | if (!HookExplorerFrameSymbols()) { 135 | Wh_Log(L"Error hooking explorer frame symbols"); 136 | return FALSE; 137 | } 138 | 139 | HMODULE shcoreModule = LoadLibrary(L"shcore.dll"); 140 | if (!shcoreModule) { 141 | Wh_Log(L"Error loading shcore.dll"); 142 | return FALSE; 143 | } 144 | 145 | IUnknown_QueryService_t pIUnknown_QueryService = 146 | (IUnknown_QueryService_t)GetProcAddress(shcoreModule, 147 | "IUnknown_QueryService"); 148 | if (!pIUnknown_QueryService) { 149 | Wh_Log(L"Error getting IUnknown_QueryService"); 150 | return FALSE; 151 | } 152 | 153 | WindhawkUtils::Wh_SetFunctionHookT(pIUnknown_QueryService, 154 | IUnknown_QueryService_Hook, 155 | &IUnknown_QueryService_Original); 156 | 157 | return TRUE; 158 | } 159 | 160 | void Wh_ModUninit() { 161 | Wh_Log(L">"); 162 | } 163 | 164 | void Wh_ModSettingsChanged() { 165 | Wh_Log(L">"); 166 | 167 | LoadSettings(); 168 | } 169 | -------------------------------------------------------------------------------- /mods/explorer-name-windows.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id explorer-name-windows 3 | // @name Name explorer windows 4 | // @description Assign custom names to explorer windows, just like in Chrome 5 | // @version 1.0 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include explorer.exe 11 | // @compilerOptions -lcomctl32 -lgdi32 12 | // ==/WindhawkMod== 13 | 14 | // Source code is published under The GNU General Public License v3.0. 15 | // 16 | // For bug reports and feature requests, please open an issue here: 17 | // https://github.com/ramensoftware/windhawk-mods/issues 18 | // 19 | // For pull requests, development takes place here: 20 | // https://github.com/m417z/my-windhawk-mods 21 | 22 | // ==WindhawkModReadme== 23 | /* 24 | # Name explorer windows 25 | 26 | Assign custom names to explorer windows, just like in Chrome. 27 | 28 | ![Demonstration](https://i.imgur.com/sSI0Kh6.gif) 29 | */ 30 | // ==/WindhawkModReadme== 31 | 32 | #include 33 | 34 | #include 35 | #include 36 | #include 37 | 38 | struct CabinetWindowData { 39 | std::wstring originalText; 40 | std::wstring customText; 41 | }; 42 | 43 | std::unordered_map g_cabinetWindows; 44 | 45 | constexpr UINT_PTR IDM_MYSYSTEM = 1001; 46 | 47 | constexpr WCHAR g_szClassName[] = L"Windhawk_explorer-name-windows"; 48 | 49 | constexpr int DIALOG_MARGIN = 7; 50 | constexpr int DIALOG_WIDTH = 300; 51 | constexpr int DIALOG_EDIT_HEIGHT = 20; 52 | constexpr int DIALOG_BUTTON_HEIGHT = 20; 53 | constexpr int DIALOG_BUTTON_WIDTH = 50; 54 | constexpr int DIALOG_HEIGHT = 55 | (DIALOG_MARGIN + DIALOG_EDIT_HEIGHT + DIALOG_MARGIN + DIALOG_BUTTON_HEIGHT + 56 | DIALOG_MARGIN); 57 | 58 | constexpr int IDC_MAIN_EDIT = 101; 59 | 60 | HINSTANCE g_hInstDLL; 61 | 62 | BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) { 63 | switch (fdwReason) { 64 | case DLL_PROCESS_ATTACH: 65 | g_hInstDLL = hinstDLL; 66 | break; 67 | 68 | case DLL_THREAD_ATTACH: 69 | case DLL_THREAD_DETACH: 70 | case DLL_PROCESS_DETACH: 71 | break; 72 | } 73 | 74 | return TRUE; 75 | } 76 | 77 | void ClientResize(HWND hWnd, int nWidth, int nHeight) { 78 | RECT rcClient, rcWind; 79 | POINT ptDiff; 80 | GetClientRect(hWnd, &rcClient); 81 | GetWindowRect(hWnd, &rcWind); 82 | ptDiff.x = (rcWind.right - rcWind.left) - rcClient.right; 83 | ptDiff.y = (rcWind.bottom - rcWind.top) - rcClient.bottom; 84 | MoveWindow(hWnd, rcWind.left, rcWind.top, nWidth + ptDiff.x, 85 | nHeight + ptDiff.y, TRUE); 86 | } 87 | 88 | void CenterWindow(HWND hWnd, HWND hParentWnd) { 89 | RECT rcParent; 90 | GetWindowRect(hParentWnd, &rcParent); 91 | 92 | RECT rc; 93 | GetWindowRect(hWnd, &rc); 94 | 95 | int width = rc.right - rc.left; 96 | int height = rc.bottom - rc.top; 97 | 98 | int x = (rcParent.left + rcParent.right) / 2 - width / 2; 99 | int y = (rcParent.top + rcParent.bottom) / 2 - height / 2; 100 | 101 | SetWindowPos(hWnd, nullptr, x, y, width, height, SWP_NOZORDER); 102 | } 103 | 104 | LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { 105 | switch (msg) { 106 | case WM_CREATE: { 107 | CREATESTRUCT* createStruct = (CREATESTRUCT*)lParam; 108 | SetWindowLongPtr(hWnd, GWLP_USERDATA, 109 | (LONG_PTR)createStruct->lpCreateParams); 110 | 111 | HWND hParentWnd = GetParent(hWnd); 112 | EnableWindow(hParentWnd, FALSE); 113 | 114 | HFONT hfDefault = (HFONT)GetStockObject(DEFAULT_GUI_FONT); 115 | SendMessage(hWnd, WM_SETFONT, (WPARAM)hfDefault, 116 | MAKELPARAM(FALSE, 0)); 117 | 118 | ClientResize(hWnd, DIALOG_WIDTH, DIALOG_HEIGHT); 119 | 120 | HWND hEdit = CreateWindowEx( 121 | WS_EX_CLIENTEDGE, L"EDIT", L"", WS_CHILD | WS_VISIBLE, 122 | DIALOG_MARGIN, DIALOG_MARGIN, DIALOG_WIDTH - DIALOG_MARGIN * 2, 123 | DIALOG_EDIT_HEIGHT, hWnd, (HMENU)IDC_MAIN_EDIT, 124 | GetModuleHandle(nullptr), nullptr); 125 | 126 | SendMessage(hEdit, WM_SETFONT, (WPARAM)hfDefault, 127 | MAKELPARAM(FALSE, 0)); 128 | 129 | HWND hButtonOK = CreateWindowEx( 130 | 0, L"BUTTON", L"OK", WS_CHILD | WS_VISIBLE, 131 | DIALOG_WIDTH - (DIALOG_MARGIN + DIALOG_BUTTON_WIDTH) * 2, 132 | DIALOG_MARGIN + DIALOG_EDIT_HEIGHT + DIALOG_MARGIN, 133 | DIALOG_BUTTON_WIDTH, DIALOG_BUTTON_HEIGHT, hWnd, (HMENU)IDOK, 134 | GetModuleHandle(nullptr), nullptr); 135 | 136 | SendMessage(hButtonOK, WM_SETFONT, (WPARAM)hfDefault, 137 | MAKELPARAM(FALSE, 0)); 138 | 139 | HWND hButtonCancel = CreateWindowEx( 140 | 0, L"BUTTON", L"Cancel", WS_CHILD | WS_VISIBLE, 141 | DIALOG_WIDTH - (DIALOG_MARGIN + DIALOG_BUTTON_WIDTH), 142 | DIALOG_MARGIN + DIALOG_EDIT_HEIGHT + DIALOG_MARGIN, 143 | DIALOG_BUTTON_WIDTH, DIALOG_BUTTON_HEIGHT, hWnd, 144 | (HMENU)IDCANCEL, GetModuleHandle(nullptr), nullptr); 145 | 146 | SendMessage(hButtonCancel, WM_SETFONT, (WPARAM)hfDefault, 147 | MAKELPARAM(FALSE, 0)); 148 | 149 | CenterWindow(hWnd, hParentWnd); 150 | 151 | SetFocus(hEdit); 152 | } break; 153 | 154 | case WM_COMMAND: 155 | switch (LOWORD(wParam)) { 156 | case IDOK: { 157 | WCHAR text[1024]; 158 | GetDlgItemText(hWnd, IDC_MAIN_EDIT, text, ARRAYSIZE(text)); 159 | 160 | auto* str = (std::optional*)GetWindowLongPtr( 161 | hWnd, GWLP_USERDATA); 162 | *str = text; 163 | 164 | SendMessage(hWnd, WM_CLOSE, 0, 0); 165 | } break; 166 | 167 | case IDCANCEL: 168 | SendMessage(hWnd, WM_CLOSE, 0, 0); 169 | break; 170 | } 171 | break; 172 | 173 | case WM_CLOSE: { 174 | HWND hParentWnd = GetParent(hWnd); 175 | EnableWindow(hParentWnd, TRUE); 176 | 177 | DestroyWindow(hWnd); 178 | } break; 179 | 180 | case WM_DESTROY: 181 | PostQuitMessage(0); 182 | break; 183 | 184 | default: 185 | return DefWindowProc(hWnd, msg, wParam, lParam); 186 | break; 187 | } 188 | 189 | return 0; 190 | } 191 | 192 | std::optional PromptForNewTitle(HWND hParentWnd) { 193 | std::optional result; 194 | HWND hwnd = CreateWindowEx(WS_EX_TOOLWINDOW, g_szClassName, 195 | L"Name this window", WS_POPUPWINDOW | WS_CAPTION, 196 | CW_USEDEFAULT, CW_USEDEFAULT, 100, 100, 197 | hParentWnd, nullptr, g_hInstDLL, &result); 198 | if (!hwnd) { 199 | return std::nullopt; 200 | } 201 | 202 | ShowWindow(hwnd, SW_SHOWDEFAULT); 203 | UpdateWindow(hwnd); 204 | 205 | MSG msg; 206 | while (GetMessage(&msg, nullptr, 0, 0) > 0) { 207 | TranslateMessage(&msg); 208 | DispatchMessage(&msg); 209 | } 210 | 211 | return result; 212 | } 213 | 214 | LRESULT CALLBACK CabinetWindowSubclassProc(HWND hWnd, 215 | UINT uMsg, 216 | WPARAM wParam, 217 | LPARAM lParam, 218 | DWORD_PTR dwRefData) { 219 | LRESULT result = 0; 220 | 221 | switch (uMsg) { 222 | case WM_SYSCOMMAND: 223 | if (wParam == IDM_MYSYSTEM) { 224 | auto newTitle = PromptForNewTitle(hWnd); 225 | if (newTitle) { 226 | auto it = g_cabinetWindows.find(hWnd); 227 | if (it != g_cabinetWindows.end()) { 228 | it->second.customText.clear(); 229 | 230 | if (!newTitle->empty()) { 231 | if (it->second.originalText.empty()) { 232 | WCHAR text[1024]; 233 | GetWindowText(hWnd, text, ARRAYSIZE(text)); 234 | 235 | it->second.originalText = text; 236 | } 237 | 238 | SetWindowText(hWnd, newTitle->c_str()); 239 | it->second.customText = *newTitle; 240 | } else if (!it->second.originalText.empty()) { 241 | SetWindowText(hWnd, 242 | it->second.originalText.c_str()); 243 | it->second.originalText.clear(); 244 | } 245 | } 246 | } 247 | 248 | result = 0; 249 | } else { 250 | result = DefSubclassProc(hWnd, uMsg, wParam, lParam); 251 | } 252 | break; 253 | 254 | case WM_SETTEXT: { 255 | auto it = g_cabinetWindows.find(hWnd); 256 | if (it != g_cabinetWindows.end()) { 257 | PCWSTR str = it->second.customText.c_str(); 258 | if (*str != L'\0') { 259 | it->second.originalText = lParam ? (PCWSTR)lParam : L""; 260 | 261 | lParam = (LPARAM)str; 262 | } 263 | } 264 | 265 | result = DefSubclassProc(hWnd, uMsg, wParam, lParam); 266 | } break; 267 | 268 | case WM_NCDESTROY: 269 | g_cabinetWindows.erase(hWnd); 270 | 271 | result = DefSubclassProc(hWnd, uMsg, wParam, lParam); 272 | break; 273 | 274 | default: 275 | result = DefSubclassProc(hWnd, uMsg, wParam, lParam); 276 | break; 277 | } 278 | 279 | return result; 280 | } 281 | 282 | void HandleIdentifiedCabinetWindow(HWND hWnd) { 283 | HMENU hMenu = GetSystemMenu(hWnd, FALSE); 284 | InsertMenu(hMenu, SC_CLOSE, MF_BYCOMMAND | MF_STRING, IDM_MYSYSTEM, 285 | L"Name window..."); 286 | InsertMenu(hMenu, SC_CLOSE, MF_BYCOMMAND | MF_SEPARATOR, 0, nullptr); 287 | 288 | g_cabinetWindows.try_emplace(hWnd); 289 | WindhawkUtils::SetWindowSubclassFromAnyThread(hWnd, 290 | CabinetWindowSubclassProc, 0); 291 | } 292 | 293 | int GetMenuItemPositionFromCommandID(HMENU hMenu, UINT_PTR commandID) { 294 | int itemCount = GetMenuItemCount(hMenu); 295 | for (int i = 0; i < itemCount; i++) { 296 | MENUITEMINFO menuItemInfo{ 297 | .cbSize = sizeof(MENUITEMINFO), 298 | .fMask = MIIM_FTYPE | MIIM_ID, 299 | }; 300 | if (GetMenuItemInfo(hMenu, i, TRUE, &menuItemInfo) && 301 | (menuItemInfo.wID == commandID)) { 302 | return i; 303 | } 304 | } 305 | 306 | return -1; 307 | } 308 | 309 | void ResetIdentifiedCabinetWindow(HWND hWnd) { 310 | WindhawkUtils::RemoveWindowSubclassFromAnyThread(hWnd, 311 | CabinetWindowSubclassProc); 312 | 313 | HMENU hMenu = GetSystemMenu(hWnd, FALSE); 314 | int pos = GetMenuItemPositionFromCommandID(hMenu, IDM_MYSYSTEM); 315 | if (pos != -1) { 316 | DeleteMenu(hMenu, pos, MF_BYPOSITION); 317 | 318 | MENUITEMINFO menuItemInfo{ 319 | .cbSize = sizeof(MENUITEMINFO), 320 | .fMask = MIIM_FTYPE, 321 | }; 322 | if (GetMenuItemInfo(hMenu, pos, TRUE, &menuItemInfo) && 323 | (menuItemInfo.fType & MFT_SEPARATOR)) { 324 | DeleteMenu(hMenu, pos, MF_BYPOSITION); 325 | } 326 | } 327 | } 328 | 329 | using CreateWindowExW_t = decltype(&CreateWindowExW); 330 | CreateWindowExW_t CreateWindowExW_Original; 331 | HWND WINAPI CreateWindowExW_Hook(DWORD dwExStyle, 332 | LPCWSTR lpClassName, 333 | LPCWSTR lpWindowName, 334 | DWORD dwStyle, 335 | int X, 336 | int Y, 337 | int nWidth, 338 | int nHeight, 339 | HWND hWndParent, 340 | HMENU hMenu, 341 | HINSTANCE hInstance, 342 | PVOID lpParam) { 343 | HWND hWnd = CreateWindowExW_Original(dwExStyle, lpClassName, lpWindowName, 344 | dwStyle, X, Y, nWidth, nHeight, 345 | hWndParent, hMenu, hInstance, lpParam); 346 | if (!hWnd) { 347 | return hWnd; 348 | } 349 | 350 | BOOL bTextualClassName = ((ULONG_PTR)lpClassName & ~(ULONG_PTR)0xffff) != 0; 351 | 352 | if (bTextualClassName && _wcsicmp(lpClassName, L"CabinetWClass") == 0) { 353 | Wh_Log(L"CabinetWClass window created: %08X", (DWORD)(ULONG_PTR)hWnd); 354 | HandleIdentifiedCabinetWindow(hWnd); 355 | } 356 | 357 | return hWnd; 358 | } 359 | 360 | void HandleCurrentProcessCabinetWindows() { 361 | EnumWindows( 362 | [](HWND hWnd, LPARAM lParam) WINAPI -> BOOL { 363 | DWORD dwProcessId = 0; 364 | if (!GetWindowThreadProcessId(hWnd, &dwProcessId) || 365 | dwProcessId != GetCurrentProcessId()) { 366 | return TRUE; 367 | } 368 | 369 | WCHAR szClassName[32]; 370 | if (GetClassName(hWnd, szClassName, ARRAYSIZE(szClassName)) == 0) { 371 | return TRUE; 372 | } 373 | 374 | if (_wcsicmp(szClassName, L"CabinetWClass") == 0) { 375 | Wh_Log(L"CabinetWClass window found: %08X", 376 | (DWORD)(ULONG_PTR)hWnd); 377 | HandleIdentifiedCabinetWindow(hWnd); 378 | } 379 | 380 | return TRUE; 381 | }, 382 | 0); 383 | } 384 | 385 | BOOL Wh_ModInit() { 386 | Wh_Log(L">"); 387 | 388 | WNDCLASSEX wc{ 389 | .cbSize = sizeof(WNDCLASSEX), 390 | .style = 0, 391 | .lpfnWndProc = WndProc, 392 | .cbClsExtra = 0, 393 | .cbWndExtra = 0, 394 | .hInstance = g_hInstDLL, 395 | .hIcon = LoadIcon(nullptr, IDI_APPLICATION), 396 | .hCursor = LoadCursor(nullptr, IDC_ARROW), 397 | .hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1), 398 | .lpszMenuName = nullptr, 399 | .lpszClassName = g_szClassName, 400 | .hIconSm = LoadIcon(nullptr, IDI_APPLICATION), 401 | }; 402 | if (!RegisterClassEx(&wc)) { 403 | Wh_Log(L"RegisterClassEx failed"); 404 | } 405 | 406 | Wh_SetFunctionHook((void*)CreateWindowExW, (void*)CreateWindowExW_Hook, 407 | (void**)&CreateWindowExW_Original); 408 | 409 | return TRUE; 410 | } 411 | 412 | void Wh_ModAfterInit() { 413 | Wh_Log(L">"); 414 | 415 | HandleCurrentProcessCabinetWindows(); 416 | } 417 | 418 | void Wh_ModUninit() { 419 | Wh_Log(L">"); 420 | 421 | for (const auto& [hWnd, _] : g_cabinetWindows) { 422 | ResetIdentifiedCabinetWindow(hWnd); 423 | } 424 | 425 | UnregisterClass(g_szClassName, g_hInstDLL); 426 | } 427 | 428 | void Wh_ModSettingsChanged() { 429 | Wh_Log(L">"); 430 | } 431 | -------------------------------------------------------------------------------- /mods/extension-change-no-warning.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id extension-change-no-warning 3 | // @name Turn off change file extension warning 4 | // @description When a file is renamed and its extension is changed, a confirmation warning appears, this mod turns it off 5 | // @version 1.0 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include explorer.exe 11 | // ==/WindhawkMod== 12 | 13 | // Source code is published under The GNU General Public License v3.0. 14 | // 15 | // For bug reports and feature requests, please open an issue here: 16 | // https://github.com/ramensoftware/windhawk-mods/issues 17 | // 18 | // For pull requests, development takes place here: 19 | // https://github.com/m417z/my-windhawk-mods 20 | 21 | // ==WindhawkModReadme== 22 | /* 23 | # Turn off change file extension warning 24 | 25 | When a file is renamed and its extension is changed, a confirmation warning 26 | appears, this mod turns it off. 27 | 28 | ![Screenshot](https://i.imgur.com/ZV47UCC.png) 29 | */ 30 | // ==/WindhawkModReadme== 31 | 32 | bool IsRenameMessageBoxParams(HINSTANCE hAppInst, 33 | LPCWSTR lpcText, 34 | LPCWSTR lpcTitle, 35 | UINT fuStyle) { 36 | return hAppInst && lpcText == MAKEINTRESOURCE(4112) && 37 | lpcTitle == MAKEINTRESOURCE(4148) && 38 | fuStyle == (MB_ICONEXCLAMATION | MB_YESNO) && 39 | hAppInst == GetModuleHandle(L"shell32.dll"); 40 | } 41 | 42 | // Forwarding arguments of varadic functions isn't supported in C/C++: 43 | // https://stackoverflow.com/q/3530771 44 | // 45 | // Therefore, instead of using `decltype(&ShellMessageBoxW)`, only declare the 46 | // first arguments (must be at least 4 as they're passed in registers in x86-64) 47 | // and use the musttail attribute to ensure that a tail call is used, which in 48 | // turn ensures that the rest of the arguments are passed along in the stack. 49 | using ShellMessageBoxW_t = int(__cdecl*)(HINSTANCE hAppInst, 50 | HWND hWnd, 51 | LPCWSTR lpcText, 52 | LPCWSTR lpcTitle, 53 | UINT fuStyle); 54 | ShellMessageBoxW_t ShellMessageBoxW_Original; 55 | int __cdecl ShellMessageBoxW_Hook(HINSTANCE hAppInst, 56 | HWND hWnd, 57 | LPCWSTR lpcText, 58 | LPCWSTR lpcTitle, 59 | UINT fuStyle) { 60 | Wh_Log(L">"); 61 | 62 | if (IsRenameMessageBoxParams(hAppInst, lpcText, lpcTitle, fuStyle)) { 63 | return IDYES; 64 | } 65 | 66 | [[clang::musttail]] return ShellMessageBoxW_Original( 67 | hAppInst, hWnd, lpcText, lpcTitle, fuStyle); 68 | } 69 | 70 | using ShellMessageBoxInternal_t = int(__cdecl*)(HINSTANCE hAppInst, 71 | HWND hWnd, 72 | DWORD dwFlags, 73 | LPCWSTR lpcText, 74 | LPCWSTR lpcTitle, 75 | UINT fuStyle); 76 | ShellMessageBoxInternal_t ShellMessageBoxInternal_Original; 77 | int __cdecl ShellMessageBoxInternal_Hook(HINSTANCE hAppInst, 78 | HWND hWnd, 79 | DWORD dwFlags, 80 | LPCWSTR lpcText, 81 | LPCWSTR lpcTitle, 82 | UINT fuStyle) { 83 | Wh_Log(L">"); 84 | 85 | if (IsRenameMessageBoxParams(hAppInst, lpcText, lpcTitle, fuStyle)) { 86 | return IDYES; 87 | } 88 | 89 | [[clang::musttail]] return ShellMessageBoxInternal_Original( 90 | hAppInst, hWnd, dwFlags, lpcText, lpcTitle, fuStyle); 91 | } 92 | 93 | BOOL Wh_ModInit() { 94 | Wh_Log(L">"); 95 | 96 | Wh_SetFunctionHook((void*)ShellMessageBoxW, (void*)ShellMessageBoxW_Hook, 97 | (void**)&ShellMessageBoxW_Original); 98 | 99 | // Also hook ShellMessageBoxInternal which was added in newer builds around 100 | // April 2025. 101 | HMODULE shlwapiModule = LoadLibrary(L"shlwapi.dll"); 102 | if (shlwapiModule) { 103 | if (auto pShellMessageBoxInternal = 104 | GetProcAddress(shlwapiModule, "ShellMessageBoxInternal")) { 105 | Wh_SetFunctionHook((void*)pShellMessageBoxInternal, 106 | (void*)ShellMessageBoxInternal_Hook, 107 | (void**)&ShellMessageBoxInternal_Original); 108 | } else { 109 | Wh_Log(L"Couldn't find ShellMessageBoxInternal"); 110 | } 111 | } else { 112 | Wh_Log(L"Couldn't load shlwapi.dll"); 113 | } 114 | 115 | return TRUE; 116 | } 117 | 118 | void Wh_ModUninit() { 119 | Wh_Log(L">"); 120 | } 121 | -------------------------------------------------------------------------------- /mods/file-explorer-remove-suffixes.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id file-explorer-remove-suffixes 3 | // @name Remove File Explorer Suffixes 4 | // @description Windows appends a " - File Explorer" suffix for each folder on the taskbar, this mod gets rid of these redundant suffixes 5 | // @version 1.0 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include explorer.exe 11 | // ==/WindhawkMod== 12 | 13 | // ==WindhawkModReadme== 14 | /* 15 | # Remove File Explorer Suffixes 16 | 17 | Windows appends a " - File Explorer" suffix for each folder on the taskbar, this 18 | mod gets rid of these redundant suffixes. 19 | 20 | ![Before](https://i.imgur.com/ErUN0YU.png) \ 21 | _Before_ 22 | 23 | ![After](https://i.imgur.com/tblTr3Q.png) \ 24 | _After_ 25 | */ 26 | // ==/WindhawkModReadme== 27 | 28 | using FindResourceExW_t = decltype(&FindResourceExW); 29 | FindResourceExW_t FindResourceExW_Original; 30 | HRSRC WINAPI FindResourceExW_Hook(HMODULE hModule, 31 | LPCWSTR lpType, 32 | LPCWSTR lpName, 33 | WORD wLanguage) { 34 | if (hModule && lpType == RT_STRING && lpName == MAKEINTRESOURCE(2195) && 35 | hModule == GetModuleHandle(L"explorerframe.dll")) { 36 | Wh_Log(L">"); 37 | SetLastError(ERROR_RESOURCE_NAME_NOT_FOUND); 38 | return nullptr; 39 | } 40 | 41 | return FindResourceExW_Original(hModule, lpType, lpName, wLanguage); 42 | } 43 | 44 | BOOL Wh_ModInit() { 45 | Wh_Log(L">"); 46 | 47 | HMODULE kernelBaseModule = GetModuleHandle(L"kernelbase.dll"); 48 | HMODULE kernel32Module = GetModuleHandle(L"kernel32.dll"); 49 | 50 | auto setKernelFunctionHook = [kernelBaseModule, kernel32Module]( 51 | PCSTR targetName, void* hookFunction, 52 | void** originalFunction) { 53 | void* targetFunction = 54 | (void*)GetProcAddress(kernelBaseModule, targetName); 55 | if (!targetFunction) { 56 | targetFunction = (void*)GetProcAddress(kernel32Module, targetName); 57 | if (!targetFunction) { 58 | return FALSE; 59 | } 60 | } 61 | 62 | return Wh_SetFunctionHook(targetFunction, hookFunction, 63 | originalFunction); 64 | }; 65 | 66 | setKernelFunctionHook("FindResourceExW", (void*)FindResourceExW_Hook, 67 | (void**)&FindResourceExW_Original); 68 | 69 | return TRUE; 70 | } 71 | 72 | void Wh_ModUninit() { 73 | Wh_Log(L">"); 74 | } 75 | -------------------------------------------------------------------------------- /mods/flight-simulator-focus-helper.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id flight-simulator-focus-helper 3 | // @name Flight Simulator window focus helper 4 | // @description Makes the game window active on mouse hover and inactive on mouse leave 5 | // @version 1.1 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include FlightSimulator.exe 11 | // @compilerOptions -lcomctl32 12 | // ==/WindhawkMod== 13 | 14 | // Source code is published under The GNU General Public License v3.0. 15 | // 16 | // For bug reports and feature requests, please open an issue here: 17 | // https://github.com/ramensoftware/windhawk-mods/issues 18 | // 19 | // For pull requests, development takes place here: 20 | // https://github.com/m417z/my-windhawk-mods 21 | 22 | // ==WindhawkModReadme== 23 | /* 24 | # Flight Simulator window focus helper 25 | 26 | Makes the game window active on mouse hover and inactive on mouse leave. 27 | 28 | The Microsoft Flight Simulator game has non-standard mouse scrolling handling - 29 | the scrolling affects the game depending on whether the window is active or not, 30 | unlike other apps for which mouse scrolling works when the mouse is hovered over 31 | the window. This mod helps with this quirk by making the game window inactive 32 | when the mouse leaves it, so mouse scrolling no longer affects it, and by making 33 | the game window active when the mouse hovers over it, making mouse scrolling 34 | work again. 35 | */ 36 | // ==/WindhawkModReadme== 37 | 38 | // ==WindhawkModSettings== 39 | /* 40 | - bringToFrontOnMouseClick: true 41 | $name: Bring to front on mouse click 42 | */ 43 | // ==/WindhawkModSettings== 44 | 45 | #include 46 | 47 | struct { 48 | bool bringToFrontOnMouseClick; 49 | } g_settings; 50 | 51 | HWND g_gameWnd; 52 | bool g_settingForegroundWindow = false; 53 | 54 | // wParam - TRUE to subclass, FALSE to unsubclass 55 | // lParam - subclass data 56 | UINT g_subclassRegisteredMsg = RegisterWindowMessage( 57 | L"Windhawk_SetWindowSubclassFromAnyThread_flight-simulator-wheel-fix"); 58 | 59 | struct SET_WINDOW_SUBCLASS_FROM_ANY_THREAD_PARAM { 60 | SUBCLASSPROC pfnSubclass; 61 | UINT_PTR uIdSubclass; 62 | DWORD_PTR dwRefData; 63 | BOOL result; 64 | }; 65 | 66 | LRESULT CALLBACK CallWndProcForWindowSubclass(int nCode, 67 | WPARAM wParam, 68 | LPARAM lParam) { 69 | if (nCode == HC_ACTION) { 70 | const CWPSTRUCT* cwp = (const CWPSTRUCT*)lParam; 71 | if (cwp->message == g_subclassRegisteredMsg && cwp->wParam) { 72 | SET_WINDOW_SUBCLASS_FROM_ANY_THREAD_PARAM* param = 73 | (SET_WINDOW_SUBCLASS_FROM_ANY_THREAD_PARAM*)cwp->lParam; 74 | param->result = 75 | SetWindowSubclass(cwp->hwnd, param->pfnSubclass, 76 | param->uIdSubclass, param->dwRefData); 77 | } 78 | } 79 | 80 | return CallNextHookEx(nullptr, nCode, wParam, lParam); 81 | } 82 | 83 | BOOL SetWindowSubclassFromAnyThread(HWND hWnd, 84 | SUBCLASSPROC pfnSubclass, 85 | UINT_PTR uIdSubclass, 86 | DWORD_PTR dwRefData) { 87 | DWORD dwThreadId = GetWindowThreadProcessId(hWnd, nullptr); 88 | if (dwThreadId == 0) { 89 | return FALSE; 90 | } 91 | 92 | if (dwThreadId == GetCurrentThreadId()) { 93 | return SetWindowSubclass(hWnd, pfnSubclass, uIdSubclass, dwRefData); 94 | } 95 | 96 | HHOOK hook = SetWindowsHookEx(WH_CALLWNDPROC, CallWndProcForWindowSubclass, 97 | nullptr, dwThreadId); 98 | if (!hook) { 99 | return FALSE; 100 | } 101 | 102 | SET_WINDOW_SUBCLASS_FROM_ANY_THREAD_PARAM param; 103 | param.pfnSubclass = pfnSubclass; 104 | param.uIdSubclass = uIdSubclass; 105 | param.dwRefData = dwRefData; 106 | param.result = FALSE; 107 | SendMessage(hWnd, g_subclassRegisteredMsg, TRUE, (LPARAM)¶m); 108 | 109 | UnhookWindowsHookEx(hook); 110 | 111 | return param.result; 112 | } 113 | 114 | HWND GetTaskbarWindow() { 115 | return FindWindow(L"Shell_TrayWnd", nullptr); 116 | } 117 | 118 | LRESULT CALLBACK GameWindowSubclassProc(_In_ HWND hWnd, 119 | _In_ UINT uMsg, 120 | _In_ WPARAM wParam, 121 | _In_ LPARAM lParam, 122 | _In_ UINT_PTR uIdSubclass, 123 | _In_ DWORD_PTR dwRefData) { 124 | if (uMsg == WM_NCDESTROY || (uMsg == g_subclassRegisteredMsg && !wParam)) { 125 | RemoveWindowSubclass(hWnd, GameWindowSubclassProc, 0); 126 | } 127 | 128 | switch (uMsg) { 129 | case WM_MOUSEMOVE: 130 | if (GetForegroundWindow() != hWnd) { 131 | // Allows to steal focus. 132 | INPUT input{}; 133 | SendInput(1, &input, sizeof(INPUT)); 134 | 135 | g_settingForegroundWindow = true; 136 | SetForegroundWindow(hWnd); 137 | g_settingForegroundWindow = false; 138 | } 139 | break; 140 | 141 | case WM_MOUSELEAVE: 142 | if (GetForegroundWindow() == hWnd) { 143 | if (HWND hTaskbarWnd = GetTaskbarWindow()) { 144 | SetForegroundWindow(hTaskbarWnd); 145 | } 146 | } 147 | break; 148 | 149 | case WM_WINDOWPOSCHANGING: 150 | if (g_settingForegroundWindow) { 151 | auto pwpos = (WINDOWPOS*)lParam; 152 | pwpos->flags |= SWP_NOZORDER; 153 | } 154 | break; 155 | 156 | case WM_LBUTTONDOWN: 157 | case WM_RBUTTONDOWN: 158 | case WM_MBUTTONDOWN: 159 | // Bring to top on mouse click, as it might be the foreground window 160 | // but not on top due to the trick in WM_WINDOWPOSCHANGING. 161 | if (g_settings.bringToFrontOnMouseClick) { 162 | SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, 163 | SWP_NOSIZE | SWP_NOMOVE); 164 | } 165 | break; 166 | 167 | case WM_NCDESTROY: 168 | g_gameWnd = nullptr; 169 | break; 170 | } 171 | 172 | return DefSubclassProc(hWnd, uMsg, wParam, lParam); 173 | } 174 | 175 | bool IsGameWindow(HWND hWnd) { 176 | WCHAR szClassName[32]; 177 | if (!GetClassName(hWnd, szClassName, ARRAYSIZE(szClassName)) || 178 | _wcsicmp(szClassName, L"AceApp") != 0) { 179 | return false; 180 | } 181 | 182 | return true; 183 | } 184 | 185 | BOOL CALLBACK InitialEnumGameWindowsFunc(HWND hWnd, LPARAM lParam) { 186 | DWORD dwProcessId = 0; 187 | if (!GetWindowThreadProcessId(hWnd, &dwProcessId) || 188 | dwProcessId != GetCurrentProcessId()) { 189 | return TRUE; 190 | } 191 | 192 | if (!IsGameWindow(hWnd)) { 193 | return TRUE; 194 | } 195 | 196 | Wh_Log(L"Game window found: %08X", (DWORD)(ULONG_PTR)hWnd); 197 | 198 | g_gameWnd = hWnd; 199 | SetWindowSubclassFromAnyThread(hWnd, GameWindowSubclassProc, 0, 0); 200 | 201 | return FALSE; 202 | } 203 | 204 | using CreateWindowExW_t = decltype(&CreateWindowExW); 205 | CreateWindowExW_t pOriginalCreateWindowExW; 206 | HWND WINAPI CreateWindowExWHook(DWORD dwExStyle, 207 | LPCWSTR lpClassName, 208 | LPCWSTR lpWindowName, 209 | DWORD dwStyle, 210 | int X, 211 | int Y, 212 | int nWidth, 213 | int nHeight, 214 | HWND hWndParent, 215 | HMENU hMenu, 216 | HINSTANCE hInstance, 217 | LPVOID lpParam) { 218 | HWND hWnd = pOriginalCreateWindowExW(dwExStyle, lpClassName, lpWindowName, 219 | dwStyle, X, Y, nWidth, nHeight, 220 | hWndParent, hMenu, hInstance, lpParam); 221 | if (!hWnd) { 222 | return hWnd; 223 | } 224 | 225 | if (!g_gameWnd && IsGameWindow(hWnd)) { 226 | Wh_Log(L"Game window created: %08X", (DWORD)(ULONG_PTR)hWnd); 227 | 228 | g_gameWnd = hWnd; 229 | SetWindowSubclass(hWnd, GameWindowSubclassProc, 0, 0); 230 | } 231 | 232 | return hWnd; 233 | } 234 | 235 | void LoadSettings() { 236 | g_settings.bringToFrontOnMouseClick = 237 | Wh_GetIntSetting(L"bringToFrontOnMouseClick"); 238 | } 239 | 240 | BOOL Wh_ModInit() { 241 | Wh_Log(L">"); 242 | 243 | LoadSettings(); 244 | 245 | Wh_SetFunctionHook((void*)CreateWindowExW, (void*)CreateWindowExWHook, 246 | (void**)&pOriginalCreateWindowExW); 247 | 248 | return TRUE; 249 | } 250 | 251 | void Wh_ModAfterInit() { 252 | Wh_Log(L">"); 253 | 254 | EnumWindows(InitialEnumGameWindowsFunc, 0); 255 | } 256 | 257 | void Wh_ModUninit() { 258 | Wh_Log(L">"); 259 | 260 | if (g_gameWnd) { 261 | SendMessage(g_gameWnd, g_subclassRegisteredMsg, FALSE, 0); 262 | } 263 | } 264 | 265 | void Wh_ModSettingsChanged() { 266 | Wh_Log(L">"); 267 | 268 | LoadSettings(); 269 | } 270 | -------------------------------------------------------------------------------- /mods/more-space-in-language-indicator.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id more-space-in-language-indicator 3 | // @name More space in language indicator 4 | // @description Enables to see two lines in the language indicator with small taskbar icons (Windows 10) 5 | // @version 1.0 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include explorer.exe 11 | // ==/WindhawkMod== 12 | 13 | // Source code is published under The GNU General Public License v3.0. 14 | // 15 | // For bug reports and feature requests, please open an issue here: 16 | // https://github.com/ramensoftware/windhawk-mods/issues 17 | // 18 | // For pull requests, development takes place here: 19 | // https://github.com/m417z/my-windhawk-mods 20 | 21 | // ==WindhawkModReadme== 22 | /* 23 | # More space in language indicator 24 | 25 | Enables to see two lines in the language indicator with small taskbar icons 26 | (Windows 10). 27 | 28 | Example: 29 | 30 | ![Screenshot](https://i.imgur.com/VztpH9B.png) 31 | 32 | By default, Windows only shows a single line (ENG) with small taskbar buttons. 33 | */ 34 | // ==/WindhawkModReadme== 35 | 36 | HWND g_hTrayInputIndicator; 37 | 38 | using DeferWindowPos_t = decltype(&DeferWindowPos); 39 | DeferWindowPos_t DeferWindowPos_Original; 40 | HDWP WINAPI DeferWindowPos_Hook(HDWP hWinPosInfo, 41 | HWND hWnd, 42 | HWND hWndInsertAfter, 43 | int x, 44 | int y, 45 | int cx, 46 | int cy, 47 | UINT uFlags) { 48 | if (!g_hTrayInputIndicator) { 49 | WCHAR szClassName[32]; 50 | GetClassName(hWnd, szClassName, ARRAYSIZE(szClassName)); 51 | if (_wcsicmp(szClassName, L"TrayInputIndicatorWClass") == 0) { 52 | g_hTrayInputIndicator = hWnd; 53 | } 54 | } 55 | 56 | if (g_hTrayInputIndicator && hWnd == g_hTrayInputIndicator && cy < 32) { 57 | cy = 32; 58 | } 59 | 60 | return DeferWindowPos_Original(hWinPosInfo, hWnd, hWndInsertAfter, x, y, cx, 61 | cy, uFlags); 62 | } 63 | 64 | BOOL Wh_ModInit() { 65 | Wh_Log(L">"); 66 | 67 | Wh_SetFunctionHook((void*)DeferWindowPos, (void*)DeferWindowPos_Hook, 68 | (void**)&DeferWindowPos_Original); 69 | 70 | return TRUE; 71 | } 72 | -------------------------------------------------------------------------------- /mods/no-flash-window.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id no-flash-window 3 | // @name NoFlashWindow 4 | // @description Prevent programs from flashing their windows on the taskbar 5 | // @version 1.0 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include * 11 | // ==/WindhawkMod== 12 | 13 | // For bug reports and feature requests, please open an issue here: 14 | // https://github.com/ramensoftware/windhawk-mods/issues 15 | // 16 | // For pull requests, development takes place here: 17 | // https://github.com/m417z/my-windhawk-mods 18 | 19 | // ==WindhawkModReadme== 20 | /* 21 | # NoFlashWindow 22 | 23 | Prevent programs from flashing their windows on the taskbar. 24 | 25 | Inspired by [NoFlashWindow](https://github.com/mrexodia/NoFlashWindow) by Duncan 26 | Ogilvie. 27 | 28 | **Note**: Windows 11 seems to have [a native option for 29 | this](https://www.elevenforum.com/t/enable-or-disable-show-flashing-on-taskbar-apps-in-windows-11.9968/). 30 | 31 | ![Screenshot](https://i.imgur.com/nVmyZrM.png) 32 | */ 33 | // ==/WindhawkModReadme== 34 | 35 | // ==WindhawkModSettings== 36 | /* 37 | - mode: prevent 38 | $name: Mode 39 | $options: 40 | - prevent: Prevent flashing 41 | - limitToOne: Limit flashing to one time 42 | */ 43 | // ==/WindhawkModSettings== 44 | 45 | enum class Mode { 46 | prevent, 47 | limitToOne, 48 | }; 49 | 50 | struct { 51 | Mode mode; 52 | } g_settings; 53 | 54 | using FlashWindow_t = decltype(&FlashWindow); 55 | FlashWindow_t FlashWindow_Original; 56 | BOOL WINAPI FlashWindow_Hook(HWND hWnd, BOOL bInvert) { 57 | Wh_Log(L">"); 58 | 59 | if (g_settings.mode == Mode::prevent) { 60 | return TRUE; 61 | } 62 | 63 | return FlashWindow_Original(hWnd, bInvert); 64 | } 65 | 66 | using FlashWindowEx_t = decltype(&FlashWindowEx); 67 | FlashWindowEx_t FlashWindowEx_Original; 68 | BOOL WINAPI FlashWindowEx_Hook(PFLASHWINFO pfwi) { 69 | Wh_Log(L">"); 70 | 71 | if (g_settings.mode == Mode::prevent) { 72 | return TRUE; 73 | } 74 | 75 | FLASHWINFO newFwi = *pfwi; 76 | newFwi.dwFlags &= ~(FLASHW_TIMER | FLASHW_TIMERNOFG); 77 | if (newFwi.uCount > 1) { 78 | newFwi.uCount = 1; 79 | } 80 | 81 | return FlashWindowEx_Original(&newFwi); 82 | } 83 | 84 | void LoadSettings() { 85 | PCWSTR mode = Wh_GetStringSetting(L"mode"); 86 | g_settings.mode = Mode::prevent; 87 | if (wcscmp(mode, L"limitToOne") == 0) { 88 | g_settings.mode = Mode::limitToOne; 89 | } 90 | Wh_FreeStringSetting(mode); 91 | } 92 | 93 | BOOL Wh_ModInit() { 94 | Wh_Log(L">"); 95 | 96 | LoadSettings(); 97 | 98 | Wh_SetFunctionHook((void*)FlashWindow, (void*)FlashWindow_Hook, 99 | (void**)&FlashWindow_Original); 100 | 101 | Wh_SetFunctionHook((void*)FlashWindowEx, (void*)FlashWindowEx_Hook, 102 | (void**)&FlashWindowEx_Original); 103 | 104 | return TRUE; 105 | } 106 | 107 | void Wh_ModUninit() { 108 | Wh_Log(L">"); 109 | } 110 | 111 | void Wh_ModSettingsChanged() { 112 | Wh_Log(L">"); 113 | 114 | LoadSettings(); 115 | } 116 | -------------------------------------------------------------------------------- /mods/pinned-items-double-click.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id pinned-items-double-click 3 | // @name Open pinned items with double click 4 | // @description Only open pinned items when double clicking on them to avoid accidental clicks 5 | // @version 1.0.2 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include explorer.exe 11 | // @architecture x86-64 12 | // @compilerOptions -lversion 13 | // ==/WindhawkMod== 14 | 15 | // Source code is published under The GNU General Public License v3.0. 16 | // 17 | // For bug reports and feature requests, please open an issue here: 18 | // https://github.com/ramensoftware/windhawk-mods/issues 19 | // 20 | // For pull requests, development takes place here: 21 | // https://github.com/m417z/my-windhawk-mods 22 | 23 | // ==WindhawkModReadme== 24 | /* 25 | # Open pinned items with double click 26 | 27 | Only open pinned items when double clicking on them to avoid accidental clicks. 28 | 29 | ![Demonstration](https://i.imgur.com/Si3siPm.gif) 30 | 31 | Only Windows 10 64-bit and Windows 11 are supported. For older Windows versions 32 | check out [7+ Taskbar Tweaker](https://tweaker.ramensoftware.com/). 33 | 34 | **Note:** To customize the old taskbar on Windows 11 (if using ExplorerPatcher 35 | or a similar tool), enable the relevant option in the mod's settings. 36 | */ 37 | // ==/WindhawkModReadme== 38 | 39 | // ==WindhawkModSettings== 40 | /* 41 | - oldTaskbarOnWin11: false 42 | $name: Customize the old taskbar on Windows 11 43 | $description: >- 44 | Enable this option to customize the old taskbar on Windows 11 (if using 45 | ExplorerPatcher or a similar tool). 46 | */ 47 | // ==/WindhawkModSettings== 48 | 49 | #include 50 | 51 | #include 52 | #include 53 | 54 | #include 55 | 56 | struct { 57 | bool oldTaskbarOnWin11; 58 | } g_settings; 59 | 60 | enum class WinVersion { 61 | Unsupported, 62 | Win10, 63 | Win11, 64 | Win11_24H2, 65 | }; 66 | 67 | WinVersion g_winVersion; 68 | 69 | std::atomic g_initialized; 70 | std::atomic g_explorerPatcherInitialized; 71 | 72 | bool IsDoubleClickDistance(DWORD pos1, DWORD pos2) { 73 | return abs(GET_X_LPARAM(pos1) - GET_X_LPARAM(pos2)) <= 74 | GetSystemMetrics(SM_CXDOUBLECLK) && 75 | abs(GET_Y_LPARAM(pos1) - GET_Y_LPARAM(pos2)) <= 76 | GetSystemMetrics(SM_CYDOUBLECLK); 77 | } 78 | 79 | using CTaskBtnGroup_GetGroupType_t = int(WINAPI*)(PVOID pThis); 80 | CTaskBtnGroup_GetGroupType_t CTaskBtnGroup_GetGroupType_Original; 81 | 82 | using CTaskListWnd__HandleClick_t = void(WINAPI*)(PVOID pThis, 83 | PVOID taskBtnGroup, 84 | int taskItemIndex, 85 | int clickAction, 86 | int param4, 87 | int param5); 88 | CTaskListWnd__HandleClick_t CTaskListWnd__HandleClick_Original; 89 | void WINAPI CTaskListWnd__HandleClick_Hook(PVOID pThis, 90 | PVOID taskBtnGroup, 91 | int taskItemIndex, 92 | int clickAction, 93 | int param4, 94 | int param5) { 95 | Wh_Log(L"> %d", clickAction); 96 | 97 | auto original = [&]() { 98 | CTaskListWnd__HandleClick_Original(pThis, taskBtnGroup, taskItemIndex, 99 | clickAction, param4, param5); 100 | }; 101 | 102 | if (clickAction != 0) { 103 | return original(); 104 | } 105 | 106 | // Group types: 107 | // 1 - Single item or multiple uncombined items 108 | // 2 - Pinned item 109 | // 3 - Multiple combined items 110 | int groupType = CTaskBtnGroup_GetGroupType_Original(taskBtnGroup); 111 | if (groupType != 2) { 112 | return original(); 113 | } 114 | 115 | static ULONGLONG firstClickTickCount = 0; 116 | static DWORD firstClickMessagePos; 117 | static PVOID firstClickTaskBtnGroup; 118 | 119 | ULONGLONG tickCount = GetTickCount64(); 120 | DWORD messagePos = GetMessagePos(); 121 | 122 | if (firstClickTickCount && 123 | IsDoubleClickDistance(firstClickMessagePos, messagePos) && 124 | firstClickTaskBtnGroup == taskBtnGroup && 125 | tickCount - firstClickTickCount <= GetDoubleClickTime()) { 126 | // Double click detected, proceed. 127 | firstClickTickCount = 0; 128 | return original(); 129 | } 130 | 131 | firstClickTickCount = tickCount; 132 | firstClickMessagePos = messagePos; 133 | firstClickTaskBtnGroup = taskBtnGroup; 134 | } 135 | 136 | using CTaskListWnd__HandleMouseButtonDown_t = void(WINAPI*)(PVOID pThis, 137 | ULONGLONG param1, 138 | const POINT* point, 139 | bool isDoubleClick); 140 | CTaskListWnd__HandleMouseButtonDown_t 141 | CTaskListWnd__HandleMouseButtonDown_Original; 142 | void WINAPI CTaskListWnd__HandleMouseButtonDown_Hook(PVOID pThis, 143 | ULONGLONG param1, 144 | const POINT* point, 145 | bool isDoubleClick) { 146 | Wh_Log(L">"); 147 | 148 | // In Windows 10, double clicks on pinned items are ignored. Make all clicks 149 | // seem like single clicks. 150 | isDoubleClick = false; 151 | 152 | CTaskListWnd__HandleMouseButtonDown_Original(pThis, param1, point, 153 | isDoubleClick); 154 | } 155 | 156 | bool HookTaskbarSymbols() { 157 | HMODULE module; 158 | if (g_winVersion <= WinVersion::Win10) { 159 | module = GetModuleHandle(nullptr); 160 | } else { 161 | module = LoadLibrary(L"taskbar.dll"); 162 | if (!module) { 163 | Wh_Log(L"Couldn't load taskbar.dll"); 164 | return false; 165 | } 166 | } 167 | 168 | // Taskbar.dll, explorer.exe 169 | WindhawkUtils::SYMBOL_HOOK symbolHooks[] = { 170 | { 171 | {LR"(public: virtual enum eTBGROUPTYPE __cdecl CTaskBtnGroup::GetGroupType(void))"}, 172 | &CTaskBtnGroup_GetGroupType_Original, 173 | }, 174 | { 175 | {LR"(protected: void __cdecl CTaskListWnd::_HandleClick(struct ITaskBtnGroup *,int,enum CTaskListWnd::eCLICKACTION,int,int))"}, 176 | &CTaskListWnd__HandleClick_Original, 177 | CTaskListWnd__HandleClick_Hook, 178 | }, 179 | { 180 | // Windows 10 only. 181 | {LR"(protected: void __cdecl CTaskListWnd::_HandleMouseButtonDown(unsigned __int64,struct tagPOINT const &,bool))"}, 182 | &CTaskListWnd__HandleMouseButtonDown_Original, 183 | CTaskListWnd__HandleMouseButtonDown_Hook, 184 | true, 185 | }, 186 | }; 187 | 188 | if (!HookSymbols(module, symbolHooks, ARRAYSIZE(symbolHooks))) { 189 | Wh_Log(L"HookSymbols failed"); 190 | return false; 191 | } 192 | 193 | return true; 194 | } 195 | 196 | VS_FIXEDFILEINFO* GetModuleVersionInfo(HMODULE hModule, UINT* puPtrLen) { 197 | void* pFixedFileInfo = nullptr; 198 | UINT uPtrLen = 0; 199 | 200 | HRSRC hResource = 201 | FindResource(hModule, MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION); 202 | if (hResource) { 203 | HGLOBAL hGlobal = LoadResource(hModule, hResource); 204 | if (hGlobal) { 205 | void* pData = LockResource(hGlobal); 206 | if (pData) { 207 | if (!VerQueryValue(pData, L"\\", &pFixedFileInfo, &uPtrLen) || 208 | uPtrLen == 0) { 209 | pFixedFileInfo = nullptr; 210 | uPtrLen = 0; 211 | } 212 | } 213 | } 214 | } 215 | 216 | if (puPtrLen) { 217 | *puPtrLen = uPtrLen; 218 | } 219 | 220 | return (VS_FIXEDFILEINFO*)pFixedFileInfo; 221 | } 222 | 223 | WinVersion GetExplorerVersion() { 224 | VS_FIXEDFILEINFO* fixedFileInfo = GetModuleVersionInfo(nullptr, nullptr); 225 | if (!fixedFileInfo) { 226 | return WinVersion::Unsupported; 227 | } 228 | 229 | WORD major = HIWORD(fixedFileInfo->dwFileVersionMS); 230 | WORD minor = LOWORD(fixedFileInfo->dwFileVersionMS); 231 | WORD build = HIWORD(fixedFileInfo->dwFileVersionLS); 232 | WORD qfe = LOWORD(fixedFileInfo->dwFileVersionLS); 233 | 234 | Wh_Log(L"Version: %u.%u.%u.%u", major, minor, build, qfe); 235 | 236 | switch (major) { 237 | case 10: 238 | if (build < 22000) { 239 | return WinVersion::Win10; 240 | } else if (build < 26100) { 241 | return WinVersion::Win11; 242 | } else { 243 | return WinVersion::Win11_24H2; 244 | } 245 | break; 246 | } 247 | 248 | return WinVersion::Unsupported; 249 | } 250 | 251 | struct EXPLORER_PATCHER_HOOK { 252 | PCSTR symbol; 253 | void** pOriginalFunction; 254 | void* hookFunction = nullptr; 255 | bool optional = false; 256 | 257 | template 258 | EXPLORER_PATCHER_HOOK( 259 | PCSTR symbol, 260 | Prototype** originalFunction, 261 | std::type_identity_t hookFunction = nullptr, 262 | bool optional = false) 263 | : symbol(symbol), 264 | pOriginalFunction(reinterpret_cast(originalFunction)), 265 | hookFunction(reinterpret_cast(hookFunction)), 266 | optional(optional) {} 267 | }; 268 | 269 | bool HookExplorerPatcherSymbols(HMODULE explorerPatcherModule) { 270 | if (g_explorerPatcherInitialized.exchange(true)) { 271 | return true; 272 | } 273 | 274 | if (g_winVersion >= WinVersion::Win11) { 275 | g_winVersion = WinVersion::Win10; 276 | } 277 | 278 | EXPLORER_PATCHER_HOOK hooks[] = { 279 | {R"(?GetGroupType@CTaskBtnGroup@@UEAA?AW4eTBGROUPTYPE@@XZ)", 280 | &CTaskBtnGroup_GetGroupType_Original}, 281 | {R"(?_HandleClick@CTaskListWnd@@IEAAXPEAUITaskBtnGroup@@HW4eCLICKACTION@1@HH@Z)", 282 | &CTaskListWnd__HandleClick_Original, CTaskListWnd__HandleClick_Hook}, 283 | {R"(?_HandleMouseButtonDown@CTaskListWnd@@IEAAX_KAEBUtagPOINT@@_N@Z)", 284 | &CTaskListWnd__HandleMouseButtonDown_Original, 285 | CTaskListWnd__HandleMouseButtonDown_Hook}, 286 | }; 287 | 288 | bool succeeded = true; 289 | 290 | for (const auto& hook : hooks) { 291 | void* ptr = (void*)GetProcAddress(explorerPatcherModule, hook.symbol); 292 | if (!ptr) { 293 | Wh_Log(L"ExplorerPatcher symbol%s doesn't exist: %S", 294 | hook.optional ? L" (optional)" : L"", hook.symbol); 295 | if (!hook.optional) { 296 | succeeded = false; 297 | } 298 | continue; 299 | } 300 | 301 | if (hook.hookFunction) { 302 | Wh_SetFunctionHook(ptr, hook.hookFunction, hook.pOriginalFunction); 303 | } else { 304 | *hook.pOriginalFunction = ptr; 305 | } 306 | } 307 | 308 | if (!succeeded) { 309 | Wh_Log(L"HookExplorerPatcherSymbols failed"); 310 | } else if (g_initialized) { 311 | Wh_ApplyHookOperations(); 312 | } 313 | 314 | return succeeded; 315 | } 316 | 317 | bool IsExplorerPatcherModule(HMODULE module) { 318 | WCHAR moduleFilePath[MAX_PATH]; 319 | switch ( 320 | GetModuleFileName(module, moduleFilePath, ARRAYSIZE(moduleFilePath))) { 321 | case 0: 322 | case ARRAYSIZE(moduleFilePath): 323 | return false; 324 | } 325 | 326 | PCWSTR moduleFileName = wcsrchr(moduleFilePath, L'\\'); 327 | if (!moduleFileName) { 328 | return false; 329 | } 330 | 331 | moduleFileName++; 332 | 333 | if (_wcsnicmp(L"ep_taskbar.", moduleFileName, sizeof("ep_taskbar.") - 1) == 334 | 0) { 335 | Wh_Log(L"ExplorerPatcher taskbar module: %s", moduleFileName); 336 | return true; 337 | } 338 | 339 | return false; 340 | } 341 | 342 | bool HandleLoadedExplorerPatcher() { 343 | HMODULE hMods[1024]; 344 | DWORD cbNeeded; 345 | if (EnumProcessModules(GetCurrentProcess(), hMods, sizeof(hMods), 346 | &cbNeeded)) { 347 | for (size_t i = 0; i < cbNeeded / sizeof(HMODULE); i++) { 348 | if (IsExplorerPatcherModule(hMods[i])) { 349 | return HookExplorerPatcherSymbols(hMods[i]); 350 | } 351 | } 352 | } 353 | 354 | return true; 355 | } 356 | 357 | using LoadLibraryExW_t = decltype(&LoadLibraryExW); 358 | LoadLibraryExW_t LoadLibraryExW_Original; 359 | HMODULE WINAPI LoadLibraryExW_Hook(LPCWSTR lpLibFileName, 360 | HANDLE hFile, 361 | DWORD dwFlags) { 362 | HMODULE module = LoadLibraryExW_Original(lpLibFileName, hFile, dwFlags); 363 | if (module && !((ULONG_PTR)module & 3) && !g_explorerPatcherInitialized) { 364 | if (IsExplorerPatcherModule(module)) { 365 | HookExplorerPatcherSymbols(module); 366 | } 367 | } 368 | 369 | return module; 370 | } 371 | 372 | void LoadSettings() { 373 | g_settings.oldTaskbarOnWin11 = Wh_GetIntSetting(L"oldTaskbarOnWin11"); 374 | } 375 | 376 | BOOL Wh_ModInit() { 377 | Wh_Log(L">"); 378 | 379 | LoadSettings(); 380 | 381 | g_winVersion = GetExplorerVersion(); 382 | if (g_winVersion == WinVersion::Unsupported) { 383 | Wh_Log(L"Unsupported Windows version"); 384 | return FALSE; 385 | } 386 | 387 | if (g_settings.oldTaskbarOnWin11) { 388 | bool hasWin10Taskbar = g_winVersion < WinVersion::Win11_24H2; 389 | 390 | if (g_winVersion >= WinVersion::Win11) { 391 | g_winVersion = WinVersion::Win10; 392 | } 393 | 394 | if (hasWin10Taskbar && !HookTaskbarSymbols()) { 395 | return FALSE; 396 | } 397 | } else if (!HookTaskbarSymbols()) { 398 | return FALSE; 399 | } 400 | 401 | if (!HandleLoadedExplorerPatcher()) { 402 | Wh_Log(L"HandleLoadedExplorerPatcher failed"); 403 | return FALSE; 404 | } 405 | 406 | HMODULE kernelBaseModule = GetModuleHandle(L"kernelbase.dll"); 407 | auto pKernelBaseLoadLibraryExW = (decltype(&LoadLibraryExW))GetProcAddress( 408 | kernelBaseModule, "LoadLibraryExW"); 409 | WindhawkUtils::Wh_SetFunctionHookT(pKernelBaseLoadLibraryExW, 410 | LoadLibraryExW_Hook, 411 | &LoadLibraryExW_Original); 412 | 413 | g_initialized = true; 414 | 415 | return TRUE; 416 | } 417 | 418 | void Wh_ModAfterInit() { 419 | Wh_Log(L">"); 420 | 421 | // Try again in case there's a race between the previous attempt and the 422 | // LoadLibraryExW hook. 423 | if (!g_explorerPatcherInitialized) { 424 | HandleLoadedExplorerPatcher(); 425 | } 426 | } 427 | 428 | void Wh_ModUninit() { 429 | Wh_Log(L">"); 430 | } 431 | 432 | BOOL Wh_ModSettingsChanged(BOOL* bReload) { 433 | Wh_Log(L">"); 434 | 435 | bool prevOldTaskbarOnWin11 = g_settings.oldTaskbarOnWin11; 436 | 437 | LoadSettings(); 438 | 439 | *bReload = g_settings.oldTaskbarOnWin11 != prevOldTaskbarOnWin11; 440 | 441 | return TRUE; 442 | } 443 | -------------------------------------------------------------------------------- /mods/start-menu-all-apps.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id start-menu-all-apps 3 | // @name Show all apps by default in start menu 4 | // @description When the Windows 11 start menu is opened, show all apps right away 5 | // @version 1.0.4 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include StartMenuExperienceHost.exe 11 | // @architecture x86-64 12 | // ==/WindhawkMod== 13 | 14 | // Source code is published under The GNU General Public License v3.0. 15 | // 16 | // For bug reports and feature requests, please open an issue here: 17 | // https://github.com/ramensoftware/windhawk-mods/issues 18 | // 19 | // For pull requests, development takes place here: 20 | // https://github.com/m417z/my-windhawk-mods 21 | 22 | // ==WindhawkModReadme== 23 | /* 24 | # Show all apps by default in start menu 25 | 26 | When the Windows 11 start menu is opened, show all apps right away without 27 | having to click on the "All apps" button. 28 | 29 | Before: 30 | 31 | ![Before screenshot](https://i.imgur.com/2ipCKJn.png) 32 | 33 | After (when the start menu is opened): 34 | 35 | ![After screenshot](https://i.imgur.com/6UVVORa.png) 36 | */ 37 | // ==/WindhawkModReadme== 38 | 39 | #include 40 | 41 | bool g_inStartInnerFrameConstructor; 42 | void* g_IDockedStartControllerOverrides_as_pThis; 43 | 44 | using ShowAllApps_t = void(WINAPI*)(void* pThis); 45 | ShowAllApps_t ShowAllApps_Original; 46 | 47 | using HideAllApps_t = void(WINAPI*)(void* pThis); 48 | HideAllApps_t HideAllApps_Original; 49 | void WINAPI HideAllApps_Hook(void* pThis) { 50 | Wh_Log(L">"); 51 | 52 | // HideAllApps is called when the start menu is closed to reset the state. 53 | // The call happens in: 54 | // winrt::StartMenu::implementation::StartInnerFrame::OnWindowVisibilityChanged 55 | // What we do is calling ShowAllApps instead. 56 | ShowAllApps_Original(pThis); 57 | } 58 | 59 | using StartInnerFrameConstructor_t = void*(WINAPI*)(void* pThis, 60 | void* dockedStartController, 61 | void* param2); 62 | StartInnerFrameConstructor_t StartInnerFrameConstructor_Original; 63 | void* WINAPI StartInnerFrameConstructor_Hook(void* pThis, 64 | void* dockedStartController, 65 | void* param2) { 66 | Wh_Log(L">"); 67 | 68 | g_inStartInnerFrameConstructor = true; 69 | 70 | void* ret = StartInnerFrameConstructor_Original( 71 | pThis, dockedStartController, param2); 72 | 73 | g_inStartInnerFrameConstructor = false; 74 | 75 | void* controllerOverrides = g_IDockedStartControllerOverrides_as_pThis; 76 | if (!controllerOverrides) { 77 | // In older versions, IDockedStartControllerOverrides_as doesn't exist, 78 | // and the param is the right variable. 79 | controllerOverrides = dockedStartController; 80 | } 81 | 82 | // Show all apps on initialization. Prepares the start menu for the first 83 | // time it's opened. 84 | ShowAllApps_Original(controllerOverrides); 85 | 86 | return ret; 87 | } 88 | 89 | using IDockedStartControllerOverrides_as_t = void*(WINAPI*)(void* pThis, 90 | void* param1); 91 | IDockedStartControllerOverrides_as_t 92 | IDockedStartControllerOverrides_as_Original; 93 | void* WINAPI IDockedStartControllerOverrides_as_Hook(void* pThis, 94 | void* param1) { 95 | Wh_Log(L">"); 96 | 97 | if (g_inStartInnerFrameConstructor && 98 | !g_IDockedStartControllerOverrides_as_pThis) { 99 | g_IDockedStartControllerOverrides_as_pThis = pThis; 100 | } 101 | 102 | void* ret = IDockedStartControllerOverrides_as_Original(pThis, param1); 103 | 104 | return ret; 105 | } 106 | 107 | BOOL Wh_ModInit() { 108 | Wh_Log(L">"); 109 | 110 | WCHAR szWindowsDirectory[MAX_PATH]; 111 | if (!GetWindowsDirectory(szWindowsDirectory, 112 | ARRAYSIZE(szWindowsDirectory))) { 113 | Wh_Log(L"GetWindowsDirectory failed"); 114 | return FALSE; 115 | } 116 | 117 | HMODULE module; 118 | 119 | // Try the path for Windows 11 version 22H2. 120 | WCHAR szStartmenuDllPath[MAX_PATH]; 121 | wcscpy_s(szStartmenuDllPath, szWindowsDirectory); 122 | wcscat_s( 123 | szStartmenuDllPath, 124 | LR"(\SystemApps\MicrosoftWindows.Client.Core_cw5n1h2txyewy\StartMenu.dll)"); 125 | if (GetFileAttributes(szStartmenuDllPath) != INVALID_FILE_ATTRIBUTES) { 126 | module = LoadLibraryEx(szStartmenuDllPath, nullptr, 127 | LOAD_WITH_ALTERED_SEARCH_PATH); 128 | } else { 129 | // Try the path for Windows 11 before version 22H2. 130 | wcscpy_s(szStartmenuDllPath, szWindowsDirectory); 131 | wcscat_s( 132 | szStartmenuDllPath, 133 | LR"(\SystemApps\MicrosoftWindows.Client.CBS_cw5n1h2txyewy\StartMenu.dll)"); 134 | module = LoadLibraryEx(szStartmenuDllPath, nullptr, 135 | LOAD_WITH_ALTERED_SEARCH_PATH); 136 | } 137 | 138 | if (!module) { 139 | Wh_Log(L"LoadLibrary failed"); 140 | return FALSE; 141 | } 142 | 143 | WindhawkUtils::SYMBOL_HOOK startMenuDllHooks[] = { 144 | { 145 | { 146 | // First seen in StartMenu.dll version 2124.33803.0.0. 147 | LR"(public: __cdecl winrt::impl::consume_WindowsUdk_UI_StartScreen_Implementation_IDockedStartControllerOverrides::ShowAllApps(void)const )", 148 | // Older symbol. 149 | LR"(public: __cdecl winrt::impl::consume_WindowsUdk_UI_StartScreen_Implementation_IDockedStartControllerOverrides::ShowAllApps(void)const )", 150 | // Even older symbol. 151 | LR"(public: void __cdecl winrt::impl::consume_WindowsUdk_UI_StartScreen_Implementation_IDockedStartControllerOverrides::ShowAllApps(void)const )", 152 | }, 153 | &ShowAllApps_Original, 154 | }, 155 | { 156 | { 157 | // First seen in StartMenu.dll version 2124.33803.0.0. 158 | LR"(public: __cdecl winrt::impl::consume_WindowsUdk_UI_StartScreen_Implementation_IDockedStartControllerOverrides::HideAllApps(void)const )", 159 | // Older symbol. 160 | LR"(public: __cdecl winrt::impl::consume_WindowsUdk_UI_StartScreen_Implementation_IDockedStartControllerOverrides::HideAllApps(void)const )", 161 | // Even older symbol. 162 | LR"(public: void __cdecl winrt::impl::consume_WindowsUdk_UI_StartScreen_Implementation_IDockedStartControllerOverrides::HideAllApps(void)const )", 163 | }, 164 | &HideAllApps_Original, 165 | HideAllApps_Hook, 166 | }, 167 | { 168 | {LR"(public: __cdecl winrt::StartMenu::implementation::StartInnerFrame::StartInnerFrame(struct winrt::WindowsUdk::UI::StartScreen::Implementation::DockedStartController const &,struct winrt::Windows::Foundation::IInspectable const &))"}, 169 | &StartInnerFrameConstructor_Original, 170 | StartInnerFrameConstructor_Hook, 171 | }, 172 | { 173 | {LR"(struct winrt::WindowsUdk::UI::StartScreen::Implementation::IDockedStartControllerOverrides __cdecl winrt::impl::as::type,0>(struct winrt::impl::abi::type *))"}, 174 | &IDockedStartControllerOverrides_as_Original, 175 | IDockedStartControllerOverrides_as_Hook, 176 | true, // Added in KB5055627. 177 | }, 178 | }; 179 | 180 | if (!HookSymbols(module, startMenuDllHooks, ARRAYSIZE(startMenuDllHooks))) { 181 | Wh_Log(L"HookSymbols failed"); 182 | return FALSE; 183 | } 184 | 185 | return TRUE; 186 | } 187 | -------------------------------------------------------------------------------- /mods/start-menu-open-location.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id start-menu-open-location 3 | // @name Start menu open location 4 | // @description When clicking the Start button, opens the Start Menu on the monitor where the mouse cursor is located, or in a custom monitor of choice 5 | // @version 1.0 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include explorer.exe 11 | // @architecture x86-64 12 | // ==/WindhawkMod== 13 | 14 | // Source code is published under The GNU General Public License v3.0. 15 | // 16 | // For bug reports and feature requests, please open an issue here: 17 | // https://github.com/ramensoftware/windhawk-mods/issues 18 | // 19 | // For pull requests, development takes place here: 20 | // https://github.com/m417z/my-windhawk-mods 21 | 22 | // ==WindhawkModReadme== 23 | /* 24 | # Start menu open location 25 | 26 | When clicking the Start button, opens the Start Menu on the monitor where the 27 | mouse cursor is located, or in a custom monitor of choice. 28 | */ 29 | // ==/WindhawkModReadme== 30 | 31 | // ==WindhawkModSettings== 32 | /* 33 | - monitor: 0 34 | $name: Monitor 35 | $description: >- 36 | The monitor number that the start menu will appear on. Set to zero to use 37 | the monitor where the mouse cursor is located. 38 | - monitorInterfaceName: "" 39 | $name: Monitor interface name 40 | $description: >- 41 | If not empty, the given monitor interface name (can also be an interface 42 | name substring) will be used instead of the monitor number. Can be useful if 43 | the monitor numbers change often. To see all available interface names, set 44 | any interface name, enable mod logs, open the start menu and look for "Found 45 | display device" messages. 46 | */ 47 | // ==/WindhawkModSettings== 48 | 49 | #include 50 | 51 | struct { 52 | int monitor; 53 | WindhawkUtils::StringSetting monitorInterfaceName; 54 | } g_settings; 55 | 56 | thread_local bool g_inShowStartView; 57 | 58 | HMONITOR GetMonitorById(int monitorId) { 59 | HMONITOR monitorResult = nullptr; 60 | int currentMonitorId = 0; 61 | 62 | auto monitorEnumProc = [&](HMONITOR hMonitor) -> BOOL { 63 | if (currentMonitorId == monitorId) { 64 | monitorResult = hMonitor; 65 | return FALSE; 66 | } 67 | currentMonitorId++; 68 | return TRUE; 69 | }; 70 | 71 | EnumDisplayMonitors( 72 | nullptr, nullptr, 73 | [](HMONITOR hMonitor, HDC hdc, LPRECT lprcMonitor, 74 | LPARAM dwData) -> BOOL { 75 | auto& proc = *reinterpret_cast(dwData); 76 | return proc(hMonitor); 77 | }, 78 | reinterpret_cast(&monitorEnumProc)); 79 | 80 | return monitorResult; 81 | } 82 | 83 | HMONITOR GetMonitorByInterfaceNameSubstr(PCWSTR interfaceNameSubstr) { 84 | HMONITOR monitorResult = nullptr; 85 | 86 | auto monitorEnumProc = [&](HMONITOR hMonitor) -> BOOL { 87 | MONITORINFOEX monitorInfo = {}; 88 | monitorInfo.cbSize = sizeof(monitorInfo); 89 | 90 | if (GetMonitorInfo(hMonitor, &monitorInfo)) { 91 | DISPLAY_DEVICE displayDevice = { 92 | .cb = sizeof(displayDevice), 93 | }; 94 | 95 | if (EnumDisplayDevices(monitorInfo.szDevice, 0, &displayDevice, 96 | EDD_GET_DEVICE_INTERFACE_NAME)) { 97 | Wh_Log(L"Found display device %s, interface name: %s", 98 | monitorInfo.szDevice, displayDevice.DeviceID); 99 | 100 | if (wcsstr(displayDevice.DeviceID, interfaceNameSubstr)) { 101 | Wh_Log(L"Matched display device"); 102 | monitorResult = hMonitor; 103 | return FALSE; 104 | } 105 | } 106 | } 107 | return TRUE; 108 | }; 109 | 110 | EnumDisplayMonitors( 111 | nullptr, nullptr, 112 | [](HMONITOR hMonitor, HDC hdc, LPRECT lprcMonitor, 113 | LPARAM dwData) -> BOOL { 114 | auto& proc = *reinterpret_cast(dwData); 115 | return proc(hMonitor); 116 | }, 117 | reinterpret_cast(&monitorEnumProc)); 118 | 119 | return monitorResult; 120 | } 121 | 122 | using ImmersiveMonitorHelper_ConnectToMonitor_t = bool(WINAPI*)(void* pThis, 123 | HWND hWnd, 124 | POINT point); 125 | ImmersiveMonitorHelper_ConnectToMonitor_t 126 | ImmersiveMonitorHelper_ConnectToMonitor_Original; 127 | 128 | using XamlLauncher_ShowStartView_t = 129 | HRESULT(WINAPI*)(void* pThis, 130 | int immersiveLauncherShowMethod, 131 | int immersiveLauncherShowFlags); 132 | XamlLauncher_ShowStartView_t XamlLauncher_ShowStartView_Original; 133 | HRESULT WINAPI XamlLauncher_ShowStartView_Hook(void* pThis, 134 | int immersiveLauncherShowMethod, 135 | int immersiveLauncherShowFlags) { 136 | Wh_Log(L">"); 137 | 138 | g_inShowStartView = true; 139 | 140 | HRESULT ret = XamlLauncher_ShowStartView_Original( 141 | pThis, immersiveLauncherShowMethod, immersiveLauncherShowFlags); 142 | 143 | g_inShowStartView = false; 144 | 145 | return ret; 146 | } 147 | 148 | using ImmersiveMonitorHelper_AdjustMonitorConnectedIfNeeded_t = 149 | HRESULT(WINAPI*)(void* pThis); 150 | ImmersiveMonitorHelper_AdjustMonitorConnectedIfNeeded_t 151 | ImmersiveMonitorHelper_AdjustMonitorConnectedIfNeeded_Original; 152 | HRESULT WINAPI 153 | ImmersiveMonitorHelper_AdjustMonitorConnectedIfNeeded_Hook(void* pThis) { 154 | Wh_Log(L">"); 155 | 156 | auto original = [=]() { 157 | return ImmersiveMonitorHelper_AdjustMonitorConnectedIfNeeded_Original( 158 | pThis); 159 | }; 160 | 161 | if (!g_inShowStartView) { 162 | return original(); 163 | } 164 | 165 | HMONITOR destMonitor = nullptr; 166 | 167 | if (*g_settings.monitorInterfaceName.get()) { 168 | destMonitor = GetMonitorByInterfaceNameSubstr( 169 | g_settings.monitorInterfaceName.get()); 170 | } else if (g_settings.monitor == 0) { 171 | POINT pt; 172 | GetCursorPos(&pt); 173 | destMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST); 174 | } else if (g_settings.monitor >= 1) { 175 | destMonitor = GetMonitorById(g_settings.monitor - 1); 176 | } 177 | 178 | if (!destMonitor) { 179 | return original(); 180 | } 181 | 182 | MONITORINFO monitorInfo{ 183 | .cbSize = sizeof(MONITORINFO), 184 | }; 185 | GetMonitorInfo(destMonitor, &monitorInfo); 186 | 187 | RECT rc = monitorInfo.rcMonitor; 188 | 189 | POINT pt = { 190 | rc.left + (rc.right - rc.left) / 2, 191 | rc.top + (rc.bottom - rc.top) / 2, 192 | }; 193 | 194 | ImmersiveMonitorHelper_ConnectToMonitor_Original(pThis, nullptr, pt); 195 | 196 | return S_OK; 197 | } 198 | 199 | void LoadSettings() { 200 | g_settings.monitor = Wh_GetIntSetting(L"monitor"); 201 | g_settings.monitorInterfaceName = 202 | WindhawkUtils::StringSetting::make(L"monitorInterfaceName"); 203 | } 204 | 205 | BOOL Wh_ModInit() { 206 | Wh_Log(L">"); 207 | 208 | LoadSettings(); 209 | 210 | // twinui.pcshell.dll 211 | WindhawkUtils::SYMBOL_HOOK twinuiPcshellSymbolHooks[] = { 212 | { 213 | {LR"(public: bool __cdecl ImmersiveMonitorHelper::ConnectToMonitor(struct HWND__ *,struct tagPOINT))"}, 214 | &ImmersiveMonitorHelper_ConnectToMonitor_Original, 215 | }, 216 | { 217 | {LR"(public: virtual long __cdecl XamlLauncher::ShowStartView(enum IMMERSIVELAUNCHERSHOWMETHOD,enum IMMERSIVELAUNCHERSHOWFLAGS))"}, 218 | &XamlLauncher_ShowStartView_Original, 219 | XamlLauncher_ShowStartView_Hook, 220 | }, 221 | { 222 | {LR"(public: long __cdecl ImmersiveMonitorHelper::AdjustMonitorConnectedIfNeeded(void))"}, 223 | &ImmersiveMonitorHelper_AdjustMonitorConnectedIfNeeded_Original, 224 | ImmersiveMonitorHelper_AdjustMonitorConnectedIfNeeded_Hook, 225 | }, 226 | }; 227 | 228 | HMODULE twinuiPcshellModule = LoadLibrary(L"twinui.pcshell.dll"); 229 | if (!twinuiPcshellModule) { 230 | Wh_Log(L"Couldn't load twinui.pcshell.dll"); 231 | return FALSE; 232 | } 233 | 234 | if (!HookSymbols(twinuiPcshellModule, twinuiPcshellSymbolHooks, 235 | ARRAYSIZE(twinuiPcshellSymbolHooks))) { 236 | Wh_Log(L"HookSymbols failed"); 237 | return FALSE; 238 | } 239 | 240 | return TRUE; 241 | } 242 | 243 | void Wh_ModUninit() { 244 | Wh_Log(L">"); 245 | } 246 | 247 | void Wh_ModSettingsChanged() { 248 | Wh_Log(L">"); 249 | 250 | LoadSettings(); 251 | } 252 | -------------------------------------------------------------------------------- /mods/taskbar-auto-hide-speed.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id taskbar-auto-hide-speed 3 | // @name Taskbar auto-hide speed 4 | // @description Customize the taskbar auto-hide animation speed and frame rate to make it feel less sluggish and janky 5 | // @version 1.0 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include explorer.exe 11 | // @architecture x86-64 12 | // @compilerOptions -lversion 13 | // ==/WindhawkMod== 14 | 15 | // Source code is published under The GNU General Public License v3.0. 16 | // 17 | // For bug reports and feature requests, please open an issue here: 18 | // https://github.com/ramensoftware/windhawk-mods/issues 19 | // 20 | // For pull requests, development takes place here: 21 | // https://github.com/m417z/my-windhawk-mods 22 | 23 | // ==WindhawkModReadme== 24 | /* 25 | # Taskbar auto-hide speed 26 | 27 | Customize the taskbar auto-hide animation speed and frame rate to make it feel 28 | less sluggish and janky. 29 | 30 | ![Demonstration](https://i.imgur.com/x7pg5xX.gif) 31 | */ 32 | // ==/WindhawkModReadme== 33 | 34 | // ==WindhawkModSettings== 35 | /* 36 | - showSpeedup: 250 37 | $name: Show animation speedup 38 | $description: In percentage of the original speed 39 | - hideSpeedup: 250 40 | $name: Hide animation speedup 41 | $description: In percentage of the original speed 42 | - frameRate: 90 43 | $name: Animation frame rate 44 | $description: >- 45 | Frames per second, higher frame rate will use more CPU 46 | - oldTaskbarOnWin11: false 47 | $name: Customize the old taskbar on Windows 11 48 | $description: >- 49 | Enable this option to customize the old taskbar on Windows 11 (if using 50 | ExplorerPatcher or a similar tool). 51 | */ 52 | // ==/WindhawkModSettings== 53 | 54 | #include 55 | 56 | #include 57 | 58 | #include 59 | 60 | struct { 61 | int showSpeedup; 62 | int hideSpeedup; 63 | int frameRate; 64 | bool oldTaskbarOnWin11; 65 | } g_settings; 66 | 67 | enum class WinVersion { 68 | Unsupported, 69 | Win10, 70 | Win11, 71 | Win11_24H2, 72 | }; 73 | 74 | WinVersion g_winVersion; 75 | 76 | std::atomic g_initialized; 77 | std::atomic g_explorerPatcherInitialized; 78 | 79 | double g_recipCyclesPerSecond; 80 | 81 | std::atomic g_slideWindowThreadId; 82 | int g_slideWindowSpeedup; 83 | double g_slideWindowStartTime; 84 | double g_slideWindowLastFrameStartTime; 85 | 86 | void TimerInitialize() { 87 | LARGE_INTEGER freq; 88 | QueryPerformanceFrequency(&freq); 89 | double cyclesPerSecond = static_cast(freq.QuadPart); 90 | g_recipCyclesPerSecond = 1.0 / cyclesPerSecond; 91 | } 92 | 93 | double TimerGetCycles() { 94 | LARGE_INTEGER T1; 95 | QueryPerformanceCounter(&T1); 96 | return static_cast(T1.QuadPart); 97 | } 98 | 99 | double TimerGetSeconds() { 100 | return TimerGetCycles() * g_recipCyclesPerSecond; 101 | } 102 | 103 | using TrayUI_SlideWindow_t = void(WINAPI*)(void* pThis, 104 | HWND hWnd, 105 | const RECT* rect, 106 | HMONITOR monitor, 107 | bool show, 108 | bool animate); 109 | TrayUI_SlideWindow_t TrayUI_SlideWindow_Original; 110 | void WINAPI TrayUI_SlideWindow_Hook(void* pThis, 111 | HWND hWnd, 112 | const RECT* rect, 113 | HMONITOR monitor, 114 | bool show, 115 | bool animate) { 116 | Wh_Log(L">"); 117 | 118 | g_slideWindowSpeedup = 119 | show ? g_settings.showSpeedup : g_settings.hideSpeedup; 120 | g_slideWindowStartTime = TimerGetSeconds(); 121 | g_slideWindowLastFrameStartTime = g_slideWindowStartTime; 122 | g_slideWindowThreadId = GetCurrentThreadId(); 123 | 124 | TrayUI_SlideWindow_Original(pThis, hWnd, rect, monitor, show, animate); 125 | 126 | g_slideWindowThreadId = 0; 127 | } 128 | 129 | using GetTickCount_t = decltype(&GetTickCount); 130 | GetTickCount_t GetTickCount_Original; 131 | DWORD WINAPI GetTickCount_Hook() { 132 | if (g_slideWindowThreadId != GetCurrentThreadId()) { 133 | return GetTickCount_Original(); 134 | } 135 | 136 | DWORD ms = (g_slideWindowLastFrameStartTime - g_slideWindowStartTime) * 137 | 1000.0 * (g_slideWindowSpeedup / 100.0) + 138 | 0.5; 139 | 140 | Wh_Log(L"> %u", ms); 141 | 142 | return ms; 143 | } 144 | 145 | using Sleep_t = decltype(&Sleep); 146 | Sleep_t Sleep_Original; 147 | void WINAPI Sleep_Hook(DWORD dwMilliseconds) { 148 | if (g_slideWindowThreadId != GetCurrentThreadId()) { 149 | Sleep_Original(dwMilliseconds); 150 | return; 151 | } 152 | 153 | double frameTotalTime = 1000.0 / g_settings.frameRate; 154 | 155 | double frameElapsedTime = 156 | (TimerGetSeconds() - g_slideWindowLastFrameStartTime) * 1000.0; 157 | 158 | int sleepTime = frameTotalTime - frameElapsedTime + 0.5; 159 | 160 | Wh_Log(L"> %f - %f = %d", frameTotalTime, frameElapsedTime, sleepTime); 161 | 162 | if (sleepTime > 0) { 163 | Sleep_Original(sleepTime); 164 | } 165 | 166 | g_slideWindowLastFrameStartTime = TimerGetSeconds(); 167 | } 168 | 169 | bool HookTaskbarSymbols() { 170 | HMODULE module; 171 | if (g_winVersion <= WinVersion::Win10) { 172 | module = GetModuleHandle(nullptr); 173 | } else { 174 | module = LoadLibrary(L"taskbar.dll"); 175 | if (!module) { 176 | Wh_Log(L"Couldn't load taskbar.dll"); 177 | return false; 178 | } 179 | } 180 | 181 | // Taskbar.dll, explorer.exe 182 | WindhawkUtils::SYMBOL_HOOK symbolHooks[] = { 183 | { 184 | {LR"(public: virtual void __cdecl TrayUI::SlideWindow(struct HWND__ *,struct tagRECT const *,struct HMONITOR__ *,bool,bool))"}, 185 | &TrayUI_SlideWindow_Original, 186 | TrayUI_SlideWindow_Hook, 187 | }, 188 | }; 189 | 190 | if (!HookSymbols(module, symbolHooks, ARRAYSIZE(symbolHooks))) { 191 | Wh_Log(L"HookSymbols failed"); 192 | return false; 193 | } 194 | 195 | return true; 196 | } 197 | 198 | VS_FIXEDFILEINFO* GetModuleVersionInfo(HMODULE hModule, UINT* puPtrLen) { 199 | void* pFixedFileInfo = nullptr; 200 | UINT uPtrLen = 0; 201 | 202 | HRSRC hResource = 203 | FindResource(hModule, MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION); 204 | if (hResource) { 205 | HGLOBAL hGlobal = LoadResource(hModule, hResource); 206 | if (hGlobal) { 207 | void* pData = LockResource(hGlobal); 208 | if (pData) { 209 | if (!VerQueryValue(pData, L"\\", &pFixedFileInfo, &uPtrLen) || 210 | uPtrLen == 0) { 211 | pFixedFileInfo = nullptr; 212 | uPtrLen = 0; 213 | } 214 | } 215 | } 216 | } 217 | 218 | if (puPtrLen) { 219 | *puPtrLen = uPtrLen; 220 | } 221 | 222 | return (VS_FIXEDFILEINFO*)pFixedFileInfo; 223 | } 224 | 225 | WinVersion GetExplorerVersion() { 226 | VS_FIXEDFILEINFO* fixedFileInfo = GetModuleVersionInfo(nullptr, nullptr); 227 | if (!fixedFileInfo) { 228 | return WinVersion::Unsupported; 229 | } 230 | 231 | WORD major = HIWORD(fixedFileInfo->dwFileVersionMS); 232 | WORD minor = LOWORD(fixedFileInfo->dwFileVersionMS); 233 | WORD build = HIWORD(fixedFileInfo->dwFileVersionLS); 234 | WORD qfe = LOWORD(fixedFileInfo->dwFileVersionLS); 235 | 236 | Wh_Log(L"Version: %u.%u.%u.%u", major, minor, build, qfe); 237 | 238 | switch (major) { 239 | case 10: 240 | if (build < 22000) { 241 | return WinVersion::Win10; 242 | } else if (build < 26100) { 243 | return WinVersion::Win11; 244 | } else { 245 | return WinVersion::Win11_24H2; 246 | } 247 | break; 248 | } 249 | 250 | return WinVersion::Unsupported; 251 | } 252 | 253 | struct EXPLORER_PATCHER_HOOK { 254 | PCSTR symbol; 255 | void** pOriginalFunction; 256 | void* hookFunction = nullptr; 257 | bool optional = false; 258 | 259 | template 260 | EXPLORER_PATCHER_HOOK( 261 | PCSTR symbol, 262 | Prototype** originalFunction, 263 | std::type_identity_t hookFunction = nullptr, 264 | bool optional = false) 265 | : symbol(symbol), 266 | pOriginalFunction(reinterpret_cast(originalFunction)), 267 | hookFunction(reinterpret_cast(hookFunction)), 268 | optional(optional) {} 269 | }; 270 | 271 | bool HookExplorerPatcherSymbols(HMODULE explorerPatcherModule) { 272 | if (g_explorerPatcherInitialized.exchange(true)) { 273 | return true; 274 | } 275 | 276 | if (g_winVersion >= WinVersion::Win11) { 277 | g_winVersion = WinVersion::Win10; 278 | } 279 | 280 | EXPLORER_PATCHER_HOOK hooks[] = { 281 | {R"(?SlideWindow@TrayUI@@UEAAXPEAUHWND__@@PEBUtagRECT@@PEAUHMONITOR__@@_N3@Z)", 282 | &TrayUI_SlideWindow_Original, TrayUI_SlideWindow_Hook}, 283 | }; 284 | 285 | bool succeeded = true; 286 | 287 | for (const auto& hook : hooks) { 288 | void* ptr = (void*)GetProcAddress(explorerPatcherModule, hook.symbol); 289 | if (!ptr) { 290 | Wh_Log(L"ExplorerPatcher symbol%s doesn't exist: %S", 291 | hook.optional ? L" (optional)" : L"", hook.symbol); 292 | if (!hook.optional) { 293 | succeeded = false; 294 | } 295 | continue; 296 | } 297 | 298 | if (hook.hookFunction) { 299 | Wh_SetFunctionHook(ptr, hook.hookFunction, hook.pOriginalFunction); 300 | } else { 301 | *hook.pOriginalFunction = ptr; 302 | } 303 | } 304 | 305 | if (!succeeded) { 306 | Wh_Log(L"HookExplorerPatcherSymbols failed"); 307 | } else if (g_initialized) { 308 | Wh_ApplyHookOperations(); 309 | } 310 | 311 | return succeeded; 312 | } 313 | 314 | bool IsExplorerPatcherModule(HMODULE module) { 315 | WCHAR moduleFilePath[MAX_PATH]; 316 | switch ( 317 | GetModuleFileName(module, moduleFilePath, ARRAYSIZE(moduleFilePath))) { 318 | case 0: 319 | case ARRAYSIZE(moduleFilePath): 320 | return false; 321 | } 322 | 323 | PCWSTR moduleFileName = wcsrchr(moduleFilePath, L'\\'); 324 | if (!moduleFileName) { 325 | return false; 326 | } 327 | 328 | moduleFileName++; 329 | 330 | if (_wcsnicmp(L"ep_taskbar.", moduleFileName, sizeof("ep_taskbar.") - 1) == 331 | 0) { 332 | Wh_Log(L"ExplorerPatcher taskbar module: %s", moduleFileName); 333 | return true; 334 | } 335 | 336 | return false; 337 | } 338 | 339 | bool HandleLoadedExplorerPatcher() { 340 | HMODULE hMods[1024]; 341 | DWORD cbNeeded; 342 | if (EnumProcessModules(GetCurrentProcess(), hMods, sizeof(hMods), 343 | &cbNeeded)) { 344 | for (size_t i = 0; i < cbNeeded / sizeof(HMODULE); i++) { 345 | if (IsExplorerPatcherModule(hMods[i])) { 346 | return HookExplorerPatcherSymbols(hMods[i]); 347 | } 348 | } 349 | } 350 | 351 | return true; 352 | } 353 | 354 | using LoadLibraryExW_t = decltype(&LoadLibraryExW); 355 | LoadLibraryExW_t LoadLibraryExW_Original; 356 | HMODULE WINAPI LoadLibraryExW_Hook(LPCWSTR lpLibFileName, 357 | HANDLE hFile, 358 | DWORD dwFlags) { 359 | HMODULE module = LoadLibraryExW_Original(lpLibFileName, hFile, dwFlags); 360 | if (module && !((ULONG_PTR)module & 3) && !g_explorerPatcherInitialized) { 361 | if (IsExplorerPatcherModule(module)) { 362 | HookExplorerPatcherSymbols(module); 363 | } 364 | } 365 | 366 | return module; 367 | } 368 | 369 | void LoadSettings() { 370 | g_settings.showSpeedup = Wh_GetIntSetting(L"showSpeedup"); 371 | g_settings.hideSpeedup = Wh_GetIntSetting(L"hideSpeedup"); 372 | g_settings.frameRate = Wh_GetIntSetting(L"frameRate"); 373 | g_settings.oldTaskbarOnWin11 = Wh_GetIntSetting(L"oldTaskbarOnWin11"); 374 | } 375 | 376 | BOOL Wh_ModInit() { 377 | Wh_Log(L">"); 378 | 379 | LoadSettings(); 380 | 381 | g_winVersion = GetExplorerVersion(); 382 | if (g_winVersion == WinVersion::Unsupported) { 383 | Wh_Log(L"Unsupported Windows version"); 384 | return FALSE; 385 | } 386 | 387 | if (g_settings.oldTaskbarOnWin11) { 388 | bool hasWin10Taskbar = g_winVersion < WinVersion::Win11_24H2; 389 | 390 | if (g_winVersion >= WinVersion::Win11) { 391 | g_winVersion = WinVersion::Win10; 392 | } 393 | 394 | if (hasWin10Taskbar && !HookTaskbarSymbols()) { 395 | return FALSE; 396 | } 397 | } else if (!HookTaskbarSymbols()) { 398 | return FALSE; 399 | } 400 | 401 | if (!HandleLoadedExplorerPatcher()) { 402 | Wh_Log(L"HandleLoadedExplorerPatcher failed"); 403 | return FALSE; 404 | } 405 | 406 | HMODULE kernelBaseModule = GetModuleHandle(L"kernelbase.dll"); 407 | auto pKernelBaseLoadLibraryExW = (decltype(&LoadLibraryExW))GetProcAddress( 408 | kernelBaseModule, "LoadLibraryExW"); 409 | WindhawkUtils::Wh_SetFunctionHookT(pKernelBaseLoadLibraryExW, 410 | LoadLibraryExW_Hook, 411 | &LoadLibraryExW_Original); 412 | 413 | auto pKernelBaseGetTickCount = (decltype(&GetTickCount))GetProcAddress( 414 | kernelBaseModule, "GetTickCount"); 415 | WindhawkUtils::Wh_SetFunctionHookT( 416 | pKernelBaseGetTickCount, GetTickCount_Hook, &GetTickCount_Original); 417 | 418 | auto pKernelBaseSleep = 419 | (decltype(&Sleep))GetProcAddress(kernelBaseModule, "Sleep"); 420 | WindhawkUtils::Wh_SetFunctionHookT(pKernelBaseSleep, Sleep_Hook, 421 | &Sleep_Original); 422 | 423 | TimerInitialize(); 424 | 425 | g_initialized = true; 426 | 427 | return TRUE; 428 | } 429 | 430 | void Wh_ModAfterInit() { 431 | Wh_Log(L">"); 432 | 433 | // Try again in case there's a race between the previous attempt and the 434 | // LoadLibraryExW hook. 435 | if (!g_explorerPatcherInitialized) { 436 | HandleLoadedExplorerPatcher(); 437 | } 438 | } 439 | 440 | void Wh_ModUninit() { 441 | Wh_Log(L">"); 442 | } 443 | 444 | BOOL Wh_ModSettingsChanged(BOOL* bReload) { 445 | Wh_Log(L">"); 446 | 447 | bool prevOldTaskbarOnWin11 = g_settings.oldTaskbarOnWin11; 448 | 449 | LoadSettings(); 450 | 451 | *bReload = g_settings.oldTaskbarOnWin11 != prevOldTaskbarOnWin11; 452 | 453 | return TRUE; 454 | } 455 | -------------------------------------------------------------------------------- /mods/taskbar-hung-rearrangement-fix.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id taskbar-hung-rearrangement-fix 3 | // @name Taskbar hung windows rearrangement fix 4 | // @description Fixes a taskbar bug which causes taskbar items of hung windows to move to the end of the taskbar 5 | // @version 1.0 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include explorer.exe 11 | // @architecture x86-64 12 | // @compilerOptions -lversion 13 | // ==/WindhawkMod== 14 | 15 | // Source code is published under The GNU General Public License v3.0. 16 | // 17 | // For bug reports and feature requests, please open an issue here: 18 | // https://github.com/ramensoftware/windhawk-mods/issues 19 | // 20 | // For pull requests, development takes place here: 21 | // https://github.com/m417z/my-windhawk-mods 22 | 23 | // ==WindhawkModReadme== 24 | /* 25 | # Taskbar hung windows rearrangement fix 26 | 27 | Fixes a taskbar bug which causes taskbar items of hung windows to move to the 28 | end of the taskbar. For more details about the bug, check out the following blog 29 | post: [Hung windows and taskbar buttons 30 | rearrangement](https://ramensoftware.com/hung-windows-and-taskbar-buttons-rearrangement). 31 | 32 | Only Windows 10 64-bit and Windows 11 are supported. For older Windows versions 33 | check out [7+ Taskbar Tweaker](https://tweaker.ramensoftware.com/). 34 | 35 | **Note:** To customize the old taskbar on Windows 11 (if using ExplorerPatcher 36 | or a similar tool), enable the relevant option in the mod's settings. 37 | 38 | ![Demonstration](https://i.imgur.com/8WU4YCX.png) 39 | */ 40 | // ==/WindhawkModReadme== 41 | 42 | // ==WindhawkModSettings== 43 | /* 44 | - oldTaskbarOnWin11: false 45 | $name: Customize the old taskbar on Windows 11 46 | $description: >- 47 | Enable this option to customize the old taskbar on Windows 11 (if using 48 | ExplorerPatcher or a similar tool). 49 | */ 50 | // ==/WindhawkModSettings== 51 | 52 | #include 53 | 54 | #include 55 | 56 | #include 57 | 58 | struct { 59 | bool oldTaskbarOnWin11; 60 | } g_settings; 61 | 62 | enum class WinVersion { 63 | Unsupported, 64 | Win10, 65 | Win11, 66 | Win11_24H2, 67 | }; 68 | 69 | WinVersion g_winVersion; 70 | 71 | std::atomic g_initialized; 72 | std::atomic g_explorerPatcherInitialized; 73 | 74 | bool IsGhostWindowClass(HWND hWnd) { 75 | static ATOM ghostAtom; 76 | 77 | if (!ghostAtom) { 78 | WNDCLASS wndClass; 79 | ghostAtom = (ATOM)GetClassInfo(NULL, L"Ghost", &wndClass); 80 | if (!ghostAtom) { 81 | return false; 82 | } 83 | } 84 | 85 | ATOM atom = (ATOM)GetClassLong(hWnd, GCW_ATOM); 86 | return atom && atom == ghostAtom; 87 | } 88 | 89 | HWND MyGhostWindowFromHungWindow(HWND hwndHung) { 90 | using GhostWindowFromHungWindow_t = HWND(WINAPI*)(HWND hwndHung); 91 | static GhostWindowFromHungWindow_t pGhostWindowFromHungWindow = []() { 92 | HMODULE hUser32 = GetModuleHandle(L"user32.dll"); 93 | if (hUser32) { 94 | return (GhostWindowFromHungWindow_t)GetProcAddress( 95 | hUser32, "GhostWindowFromHungWindow"); 96 | } 97 | 98 | return (GhostWindowFromHungWindow_t) nullptr; 99 | }(); 100 | 101 | if (!pGhostWindowFromHungWindow) { 102 | return nullptr; 103 | } 104 | 105 | return pGhostWindowFromHungWindow(hwndHung); 106 | } 107 | 108 | HWND MyHungWindowFromGhostWindow(HWND hwndGhost) { 109 | using HungWindowFromGhostWindow_t = HWND(WINAPI*)(HWND hwndGhost); 110 | static HungWindowFromGhostWindow_t pHungWindowFromGhostWindow = []() { 111 | HMODULE hUser32 = GetModuleHandle(L"user32.dll"); 112 | if (hUser32) { 113 | return (HungWindowFromGhostWindow_t)GetProcAddress( 114 | hUser32, "HungWindowFromGhostWindow"); 115 | } 116 | 117 | return (HungWindowFromGhostWindow_t) nullptr; 118 | }(); 119 | 120 | if (!pHungWindowFromGhostWindow) { 121 | return nullptr; 122 | } 123 | 124 | return pHungWindowFromGhostWindow(hwndGhost); 125 | } 126 | 127 | using CTaskBand__HandleReplaceWindow_t = LRESULT(WINAPI*)(void* pThis, 128 | HWND hFromWnd, 129 | HWND hToWnd); 130 | CTaskBand__HandleReplaceWindow_t CTaskBand__HandleReplaceWindow_Original; 131 | LRESULT WINAPI CTaskBand__HandleReplaceWindow_Hook(void* pThis, 132 | HWND hFromWnd, 133 | HWND hToWnd) { 134 | Wh_Log(L">"); 135 | 136 | if (hToWnd && !IsWindow(hToWnd)) { 137 | return 0; 138 | } 139 | 140 | LRESULT ret = 141 | CTaskBand__HandleReplaceWindow_Original(pThis, hFromWnd, hToWnd); 142 | 143 | return ret; 144 | } 145 | 146 | using CWindowTaskItem_GetWindow_t = HWND(WINAPI*)(void* pThis); 147 | CWindowTaskItem_GetWindow_t CWindowTaskItem_GetWindow_Original; 148 | 149 | using CWindowTaskItem_SetWindow_t = HRESULT(WINAPI*)(void* pThis, HWND hWnd); 150 | CWindowTaskItem_SetWindow_t CWindowTaskItem_SetWindow_Original; 151 | 152 | HWND WINAPI CWindowTaskItem_GetWindow_Hook(void* pThis) { 153 | HWND ret = CWindowTaskItem_GetWindow_Original(pThis); 154 | 155 | if ((LONG_PTR)ret & 1) { 156 | Wh_Log(L">"); 157 | 158 | ret = (HWND)((LONG_PTR)ret & ~1); 159 | 160 | HWND hGhostWnd = MyGhostWindowFromHungWindow(ret); 161 | if (!hGhostWnd) { 162 | CWindowTaskItem_SetWindow_Original(pThis, ret); 163 | } else { 164 | ret = hGhostWnd; 165 | } 166 | } 167 | 168 | return ret; 169 | } 170 | 171 | HRESULT WINAPI CWindowTaskItem_SetWindow_Hook(void* pThis, HWND hWnd) { 172 | HWND hOldWnd = CWindowTaskItem_GetWindow_Original(pThis); 173 | 174 | if (hWnd != hOldWnd && IsGhostWindowClass(hWnd) && 175 | MyHungWindowFromGhostWindow(hWnd) == hOldWnd) { 176 | Wh_Log(L">"); 177 | 178 | hWnd = (HWND)((LONG_PTR)hOldWnd | 1); 179 | } 180 | 181 | HRESULT ret = CWindowTaskItem_SetWindow_Original(pThis, hWnd); 182 | 183 | return ret; 184 | } 185 | 186 | VS_FIXEDFILEINFO* GetModuleVersionInfo(HMODULE hModule, UINT* puPtrLen) { 187 | void* pFixedFileInfo = nullptr; 188 | UINT uPtrLen = 0; 189 | 190 | HRSRC hResource = 191 | FindResource(hModule, MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION); 192 | if (hResource) { 193 | HGLOBAL hGlobal = LoadResource(hModule, hResource); 194 | if (hGlobal) { 195 | void* pData = LockResource(hGlobal); 196 | if (pData) { 197 | if (!VerQueryValue(pData, L"\\", &pFixedFileInfo, &uPtrLen) || 198 | uPtrLen == 0) { 199 | pFixedFileInfo = nullptr; 200 | uPtrLen = 0; 201 | } 202 | } 203 | } 204 | } 205 | 206 | if (puPtrLen) { 207 | *puPtrLen = uPtrLen; 208 | } 209 | 210 | return (VS_FIXEDFILEINFO*)pFixedFileInfo; 211 | } 212 | 213 | WinVersion GetExplorerVersion() { 214 | VS_FIXEDFILEINFO* fixedFileInfo = GetModuleVersionInfo(nullptr, nullptr); 215 | if (!fixedFileInfo) { 216 | return WinVersion::Unsupported; 217 | } 218 | 219 | WORD major = HIWORD(fixedFileInfo->dwFileVersionMS); 220 | WORD minor = LOWORD(fixedFileInfo->dwFileVersionMS); 221 | WORD build = HIWORD(fixedFileInfo->dwFileVersionLS); 222 | WORD qfe = LOWORD(fixedFileInfo->dwFileVersionLS); 223 | 224 | Wh_Log(L"Version: %u.%u.%u.%u", major, minor, build, qfe); 225 | 226 | switch (major) { 227 | case 10: 228 | if (build < 22000) { 229 | return WinVersion::Win10; 230 | } else if (build < 26100) { 231 | return WinVersion::Win11; 232 | } else { 233 | return WinVersion::Win11_24H2; 234 | } 235 | break; 236 | } 237 | 238 | return WinVersion::Unsupported; 239 | } 240 | 241 | bool HookExplorerPatcherSymbols(HMODULE explorerPatcherModule) { 242 | if (g_explorerPatcherInitialized.exchange(true)) { 243 | return true; 244 | } 245 | 246 | if (g_winVersion >= WinVersion::Win11) { 247 | g_winVersion = WinVersion::Win10; 248 | } 249 | 250 | struct EXPLORER_PATCHER_HOOK { 251 | PCSTR symbol; 252 | void** pOriginalFunction; 253 | void* hookFunction = nullptr; 254 | bool optional = false; 255 | }; 256 | 257 | EXPLORER_PATCHER_HOOK hooks[] = { 258 | {R"(?_HandleReplaceWindow@CTaskBand@@IEAA_JPEAUHWND__@@0@Z)", 259 | (void**)&CTaskBand__HandleReplaceWindow_Original, 260 | (void*)CTaskBand__HandleReplaceWindow_Hook}, 261 | {R"(?GetWindow@CWindowTaskItem@@UEAAPEAUHWND__@@XZ)", 262 | (void**)&CWindowTaskItem_GetWindow_Original, 263 | (void*)CWindowTaskItem_GetWindow_Hook}, 264 | {R"(?SetWindow@CWindowTaskItem@@UEAAJPEAUHWND__@@@Z)", 265 | (void**)&CWindowTaskItem_SetWindow_Original, 266 | (void*)CWindowTaskItem_SetWindow_Hook}, 267 | }; 268 | 269 | bool succeeded = true; 270 | 271 | for (const auto& hook : hooks) { 272 | void* ptr = (void*)GetProcAddress(explorerPatcherModule, hook.symbol); 273 | if (!ptr) { 274 | Wh_Log(L"ExplorerPatcher symbol%s doesn't exist: %S", 275 | hook.optional ? L" (optional)" : L"", hook.symbol); 276 | if (!hook.optional) { 277 | succeeded = false; 278 | } 279 | continue; 280 | } 281 | 282 | if (hook.hookFunction) { 283 | Wh_SetFunctionHook(ptr, hook.hookFunction, hook.pOriginalFunction); 284 | } else { 285 | *hook.pOriginalFunction = ptr; 286 | } 287 | } 288 | 289 | if (g_initialized) { 290 | Wh_ApplyHookOperations(); 291 | } 292 | 293 | return succeeded; 294 | } 295 | 296 | bool HandleModuleIfExplorerPatcher(HMODULE module) { 297 | WCHAR moduleFilePath[MAX_PATH]; 298 | switch ( 299 | GetModuleFileName(module, moduleFilePath, ARRAYSIZE(moduleFilePath))) { 300 | case 0: 301 | case ARRAYSIZE(moduleFilePath): 302 | return false; 303 | } 304 | 305 | PCWSTR moduleFileName = wcsrchr(moduleFilePath, L'\\'); 306 | if (!moduleFileName) { 307 | return false; 308 | } 309 | 310 | moduleFileName++; 311 | 312 | if (_wcsnicmp(L"ep_taskbar.", moduleFileName, sizeof("ep_taskbar.") - 1) != 313 | 0) { 314 | return true; 315 | } 316 | 317 | Wh_Log(L"ExplorerPatcher taskbar loaded: %s", moduleFileName); 318 | return HookExplorerPatcherSymbols(module); 319 | } 320 | 321 | void HandleLoadedExplorerPatcher() { 322 | HMODULE hMods[1024]; 323 | DWORD cbNeeded; 324 | if (EnumProcessModules(GetCurrentProcess(), hMods, sizeof(hMods), 325 | &cbNeeded)) { 326 | for (size_t i = 0; i < cbNeeded / sizeof(HMODULE); i++) { 327 | HandleModuleIfExplorerPatcher(hMods[i]); 328 | } 329 | } 330 | } 331 | 332 | using LoadLibraryExW_t = decltype(&LoadLibraryExW); 333 | LoadLibraryExW_t LoadLibraryExW_Original; 334 | HMODULE WINAPI LoadLibraryExW_Hook(LPCWSTR lpLibFileName, 335 | HANDLE hFile, 336 | DWORD dwFlags) { 337 | HMODULE module = LoadLibraryExW_Original(lpLibFileName, hFile, dwFlags); 338 | if (module && !((ULONG_PTR)module & 3) && !g_explorerPatcherInitialized) { 339 | HandleModuleIfExplorerPatcher(module); 340 | } 341 | 342 | return module; 343 | } 344 | 345 | bool HookTaskbarSymbols() { 346 | HMODULE module; 347 | if (g_winVersion <= WinVersion::Win10) { 348 | module = GetModuleHandle(nullptr); 349 | } else { 350 | module = LoadLibrary(L"taskbar.dll"); 351 | if (!module) { 352 | Wh_Log(L"Couldn't load taskbar.dll"); 353 | return false; 354 | } 355 | } 356 | 357 | // Taskbar.dll, explorer.exe 358 | WindhawkUtils::SYMBOL_HOOK symbolHooks[] = { 359 | { 360 | {LR"(protected: __int64 __cdecl CTaskBand::_HandleReplaceWindow(struct HWND__ *,struct HWND__ *))"}, 361 | &CTaskBand__HandleReplaceWindow_Original, 362 | CTaskBand__HandleReplaceWindow_Hook, 363 | }, 364 | { 365 | {LR"(public: virtual struct HWND__ * __cdecl CWindowTaskItem::GetWindow(void))"}, 366 | &CWindowTaskItem_GetWindow_Original, 367 | CWindowTaskItem_GetWindow_Hook, 368 | }, 369 | { 370 | {LR"(public: virtual long __cdecl CWindowTaskItem::SetWindow(struct HWND__ *))"}, 371 | &CWindowTaskItem_SetWindow_Original, 372 | CWindowTaskItem_SetWindow_Hook, 373 | }, 374 | }; 375 | 376 | return HookSymbols(module, symbolHooks, ARRAYSIZE(symbolHooks)); 377 | } 378 | 379 | void LoadSettings() { 380 | g_settings.oldTaskbarOnWin11 = Wh_GetIntSetting(L"oldTaskbarOnWin11"); 381 | } 382 | 383 | BOOL Wh_ModInit() { 384 | Wh_Log(L">"); 385 | 386 | LoadSettings(); 387 | 388 | g_winVersion = GetExplorerVersion(); 389 | if (g_winVersion == WinVersion::Unsupported) { 390 | Wh_Log(L"Unsupported Windows version"); 391 | return FALSE; 392 | } 393 | 394 | if (g_settings.oldTaskbarOnWin11) { 395 | bool hasWin10Taskbar = g_winVersion < WinVersion::Win11_24H2; 396 | 397 | if (g_winVersion >= WinVersion::Win11) { 398 | g_winVersion = WinVersion::Win10; 399 | } 400 | 401 | if (hasWin10Taskbar && !HookTaskbarSymbols()) { 402 | return FALSE; 403 | } 404 | } else if (!HookTaskbarSymbols()) { 405 | return FALSE; 406 | } 407 | 408 | HandleLoadedExplorerPatcher(); 409 | 410 | HMODULE kernelBaseModule = GetModuleHandle(L"kernelbase.dll"); 411 | FARPROC pKernelBaseLoadLibraryExW = 412 | GetProcAddress(kernelBaseModule, "LoadLibraryExW"); 413 | Wh_SetFunctionHook((void*)pKernelBaseLoadLibraryExW, 414 | (void*)LoadLibraryExW_Hook, 415 | (void**)&LoadLibraryExW_Original); 416 | 417 | g_initialized = true; 418 | 419 | return TRUE; 420 | } 421 | 422 | void Wh_ModAfterInit() { 423 | Wh_Log(L">"); 424 | 425 | // Try again in case there's a race between the previous attempt and the 426 | // LoadLibraryExW hook. 427 | if (!g_explorerPatcherInitialized) { 428 | HandleLoadedExplorerPatcher(); 429 | } 430 | } 431 | 432 | BOOL Wh_ModSettingsChanged(BOOL* bReload) { 433 | Wh_Log(L">"); 434 | 435 | bool prevOldTaskbarOnWin11 = g_settings.oldTaskbarOnWin11; 436 | 437 | LoadSettings(); 438 | 439 | *bReload = g_settings.oldTaskbarOnWin11 != prevOldTaskbarOnWin11; 440 | 441 | return TRUE; 442 | } 443 | -------------------------------------------------------------------------------- /mods/taskbar-jump-list-on-cursor-pos.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id taskbar-jump-list-on-cursor-pos 3 | // @name Taskbar jump list on cursor pos 4 | // @description Open the taskbar jump list context menus near the mouse cursor, not in the middle of the taskbar group 5 | // @version 1.0 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include explorer.exe 11 | // @architecture x86-64 12 | // ==/WindhawkMod== 13 | 14 | // Source code is published under The GNU General Public License v3.0. 15 | // 16 | // For bug reports and feature requests, please open an issue here: 17 | // https://github.com/ramensoftware/windhawk-mods/issues 18 | // 19 | // For pull requests, development takes place here: 20 | // https://github.com/m417z/my-windhawk-mods 21 | 22 | // ==WindhawkModReadme== 23 | /* 24 | # Taskbar jump list on cursor pos 25 | 26 | Open the taskbar jump list context menus near the mouse cursor, not in the 27 | middle of the task group. 28 | 29 | When multiple instances of a program are grouped on the taskbar, right-clicking 30 | any icon opens the menu centered across all icons, not above the one clicked. As 31 | a result, extra cursor movement is required to reach the menu. This mod causes 32 | the menu to always open just above the mouse cursor. 33 | 34 | ![Before](https://i.imgur.com/Fe9K7v2.png) \ 35 | _Before_ 36 | 37 | ![After](https://i.imgur.com/4NcGU5O.png) \ 38 | _After_ 39 | */ 40 | // ==/WindhawkModReadme== 41 | 42 | #include 43 | 44 | #include 45 | 46 | #undef GetCurrentTime 47 | 48 | #include 49 | #include 50 | #include 51 | 52 | using namespace winrt::Windows::UI::Xaml; 53 | 54 | using CTaskListWnd_ComputeJumpViewPosition_t = 55 | HRESULT(WINAPI*)(void* pThis, 56 | void* taskBtnGroup, 57 | int param2, 58 | winrt::Windows::Foundation::Point* point, 59 | HorizontalAlignment* horizontalAlignment, 60 | VerticalAlignment* verticalAlignment); 61 | CTaskListWnd_ComputeJumpViewPosition_t 62 | CTaskListWnd_ComputeJumpViewPosition_Original; 63 | HRESULT WINAPI CTaskListWnd_ComputeJumpViewPosition_Hook( 64 | void* pThis, 65 | void* taskBtnGroup, 66 | int param2, 67 | winrt::Windows::Foundation::Point* point, 68 | HorizontalAlignment* horizontalAlignment, 69 | VerticalAlignment* verticalAlignment) { 70 | Wh_Log(L">"); 71 | 72 | HRESULT ret = CTaskListWnd_ComputeJumpViewPosition_Original( 73 | pThis, taskBtnGroup, param2, point, horizontalAlignment, 74 | verticalAlignment); 75 | 76 | DWORD messagePos = GetMessagePos(); 77 | POINT pt{ 78 | GET_X_LPARAM(messagePos), 79 | GET_Y_LPARAM(messagePos), 80 | }; 81 | 82 | point->X = pt.x; 83 | 84 | return ret; 85 | } 86 | 87 | bool HookTaskbarDllSymbols() { 88 | HMODULE module = LoadLibrary(L"taskbar.dll"); 89 | if (!module) { 90 | Wh_Log(L"Failed to load taskbar.dll"); 91 | return false; 92 | } 93 | 94 | WindhawkUtils::SYMBOL_HOOK taskbarDllHooks[] = { 95 | { 96 | {LR"(protected: long __cdecl CTaskListWnd::_ComputeJumpViewPosition(struct ITaskBtnGroup *,int,struct Windows::Foundation::Point &,enum Windows::UI::Xaml::HorizontalAlignment &,enum Windows::UI::Xaml::VerticalAlignment &)const )"}, 97 | &CTaskListWnd_ComputeJumpViewPosition_Original, 98 | CTaskListWnd_ComputeJumpViewPosition_Hook, 99 | }, 100 | }; 101 | 102 | if (!HookSymbols(module, taskbarDllHooks, ARRAYSIZE(taskbarDllHooks))) { 103 | Wh_Log(L"HookSymbols failed"); 104 | return false; 105 | } 106 | 107 | return true; 108 | } 109 | 110 | BOOL Wh_ModInit() { 111 | Wh_Log(L">"); 112 | 113 | if (!HookTaskbarDllSymbols()) { 114 | return FALSE; 115 | } 116 | 117 | return TRUE; 118 | } 119 | 120 | void Wh_ModUninit() { 121 | Wh_Log(L">"); 122 | } 123 | -------------------------------------------------------------------------------- /mods/taskbar-notification-icons-show-all.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id taskbar-notification-icons-show-all 3 | // @name Always show all taskbar tray icons 4 | // @description Restore the missing Windows option to always show all tray icons (Windows 11 only) 5 | // @version 1.0 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include explorer.exe 11 | // @architecture x86-64 12 | // ==/WindhawkMod== 13 | 14 | // Source code is published under The GNU General Public License v3.0. 15 | // 16 | // For bug reports and feature requests, please open an issue here: 17 | // https://github.com/ramensoftware/windhawk-mods/issues 18 | // 19 | // For pull requests, development takes place here: 20 | // https://github.com/m417z/my-windhawk-mods 21 | 22 | // ==WindhawkModReadme== 23 | /* 24 | # Always show all taskbar tray icons 25 | 26 | Restore the missing Windows option to always show all tray icons. 27 | 28 | The mod has two modes that can be selected in the mod settings: 29 | 30 | 1. All icons are shown. That's the default, and like in the animation below, all 31 | icons become shown when the mod is enabled. 32 | 2. New icons are shown, existing icons are unaffected. If this mode is selected, 33 | the mod only affects icons of new apps, which become visible by default 34 | instead of being hidden. 35 | 36 | Only Windows 11 is supported. 37 | 38 | ![demonstration](https://i.imgur.com/q6pgi0Z.gif) 39 | */ 40 | // ==/WindhawkModReadme== 41 | 42 | // ==WindhawkModSettings== 43 | /* 44 | - mode: showAll 45 | $name: Mode 46 | $options: 47 | - showAll: All icons are shown 48 | - showNew: New icons are shown, existing icons are unaffected 49 | */ 50 | // ==/WindhawkModSettings== 51 | 52 | #include 53 | 54 | #include 55 | #include 56 | #include 57 | 58 | enum class Mode { 59 | showAll, 60 | showNew, 61 | }; 62 | 63 | struct { 64 | Mode mode; 65 | } g_settings; 66 | 67 | // https://github.com/valinet/wh-mods/blob/61319815c7e018e392a08077dc364559548ade02/mods/valinet-unserver.wh.cpp#L95 68 | // https://stackoverflow.com/questions/937044/determine-path-to-registry-key-from-hkey-handle-in-c 69 | std::wstring GetPathFromHKEY(HKEY key) { 70 | std::wstring keyPath; 71 | if (key) { 72 | HMODULE dll = GetModuleHandleW(L"ntdll.dll"); 73 | if (dll) { 74 | typedef NTSTATUS(__stdcall * NtQueryKeyType)( 75 | HANDLE KeyHandle, int KeyInformationClass, PVOID KeyInformation, 76 | ULONG Length, PULONG ResultLength); 77 | NtQueryKeyType func = reinterpret_cast( 78 | GetProcAddress(dll, "NtQueryKey")); 79 | if (func) { 80 | DWORD size = 0; 81 | NTSTATUS result = func(key, 3, 0, 0, &size); 82 | if (result == STATUS_BUFFER_TOO_SMALL) { 83 | size = size + 2; 84 | wchar_t* buffer = new (std::nothrow) 85 | wchar_t[size / sizeof(wchar_t)]; // size is in bytes 86 | if (buffer) { 87 | result = func(key, 3, buffer, size, &size); 88 | if (result == STATUS_SUCCESS) { 89 | buffer[size / sizeof(wchar_t)] = L'\0'; 90 | keyPath = std::wstring(buffer + 2); 91 | } 92 | delete[] buffer; 93 | } 94 | } 95 | } 96 | } 97 | } 98 | return keyPath; 99 | } 100 | 101 | // https://stackoverflow.com/a/46931770 102 | std::vector SplitStringView(std::wstring_view s, 103 | WCHAR delimiter) { 104 | size_t pos_start = 0, pos_end; 105 | std::wstring token; 106 | std::vector res; 107 | 108 | while ((pos_end = s.find(delimiter, pos_start)) != 109 | std::wstring_view::npos) { 110 | token = s.substr(pos_start, pos_end - pos_start); 111 | pos_start = pos_end + 1; 112 | res.push_back(std::move(token)); 113 | } 114 | 115 | token = s.substr(pos_start); 116 | res.push_back(std::move(token)); 117 | return res; 118 | } 119 | 120 | std::wstring GetNotifyIconSettingsNameFromRegKey(HKEY hKey) { 121 | // Example path: 122 | // \REGISTRY\USER\S-1-5-21-3623811015-3361044348-30300820-1013 123 | // \Control Panel\NotifyIconSettings\12345678901234567890 124 | auto path = SplitStringView(GetPathFromHKEY(hKey), L'\\'); 125 | if (path.size() == 7 && path[0].size() == 0 && 126 | _wcsicmp(path[1].c_str(), L"REGISTRY") == 0 && 127 | _wcsicmp(path[2].c_str(), L"USER") == 0 && 128 | _wcsicmp(path[4].c_str(), L"Control Panel") == 0 && 129 | _wcsicmp(path[5].c_str(), L"NotifyIconSettings") == 0) { 130 | return path[6]; 131 | } 132 | 133 | return L""; 134 | } 135 | 136 | using RegSetValueExW_t = decltype(&RegSetValueExW); 137 | RegSetValueExW_t RegSetValueExW_Original; 138 | LONG WINAPI RegSetValueExW_Hook(HKEY hKey, 139 | LPCWSTR lpValueName, 140 | DWORD Reserved, 141 | DWORD dwType, 142 | CONST BYTE* lpData, 143 | DWORD cbData) { 144 | if (g_settings.mode == Mode::showAll && lpValueName && 145 | _wcsicmp(lpValueName, L"IsPromoted") == 0) { 146 | auto entry = GetNotifyIconSettingsNameFromRegKey(hKey); 147 | if (!entry.empty()) { 148 | Wh_Log(L"Setting IsPromoted for %s", entry.c_str()); 149 | return ERROR_SUCCESS; 150 | } 151 | } 152 | 153 | return RegSetValueExW_Original(hKey, lpValueName, Reserved, dwType, lpData, 154 | cbData); 155 | } 156 | 157 | bool SetIsPromoted(PCWSTR entry) { 158 | Wh_Log(L"Setting IsPromoted for %s", entry); 159 | 160 | std::wstring subKey = L"Control Panel\\NotifyIconSettings\\"; 161 | subKey += entry; 162 | 163 | HKEY key; 164 | LONG result = 165 | RegOpenKeyEx(HKEY_CURRENT_USER, subKey.c_str(), 0, KEY_SET_VALUE, &key); 166 | if (result != ERROR_SUCCESS) { 167 | Wh_Log(L"Failed to open %s: %d", subKey.c_str(), result); 168 | return false; 169 | } 170 | 171 | DWORD dw = 1; 172 | result = RegSetValueExW_Original(key, L"IsPromoted", 0, REG_DWORD, 173 | (const BYTE*)&dw, sizeof(dw)); 174 | if (result != ERROR_SUCCESS) { 175 | Wh_Log(L"Failed to write to %s: %d", subKey.c_str(), result); 176 | } 177 | 178 | RegCloseKey(key); 179 | return result == ERROR_SUCCESS; 180 | } 181 | 182 | using RegGetValueW_t = decltype(&RegGetValueW); 183 | RegGetValueW_t RegGetValueW_Original; 184 | LONG WINAPI RegGetValueW_Hook(HKEY hkey, 185 | LPCWSTR lpSubKey, 186 | LPCWSTR lpValue, 187 | DWORD dwFlags, 188 | LPDWORD pdwType, 189 | PVOID pvData, 190 | LPDWORD pcbData) { 191 | if (!lpSubKey && lpValue && (dwFlags & RRF_RT_REG_DWORD) && pvData && 192 | pcbData && *pcbData >= sizeof(DWORD) && 193 | _wcsicmp(lpValue, L"IsPromoted") == 0) { 194 | auto entry = GetNotifyIconSettingsNameFromRegKey(hkey); 195 | if (!entry.empty()) { 196 | Wh_Log(L"Getting IsPromoted for %s", entry.c_str()); 197 | 198 | if (g_settings.mode != Mode::showAll) { 199 | LONG result = RegGetValueW_Original( 200 | hkey, lpSubKey, lpValue, dwFlags, pdwType, pvData, pcbData); 201 | if (result != ERROR_FILE_NOT_FOUND) { 202 | return result; 203 | } 204 | 205 | Wh_Log(L"Result of reading original value: %d", result); 206 | 207 | SetIsPromoted(entry.c_str()); 208 | } 209 | 210 | if (pdwType) { 211 | *pdwType = REG_DWORD; 212 | } 213 | 214 | *(DWORD*)pvData = 1; 215 | *pcbData = sizeof(DWORD); 216 | return ERROR_SUCCESS; 217 | } 218 | } 219 | 220 | return RegGetValueW_Original(hkey, lpSubKey, lpValue, dwFlags, pdwType, 221 | pvData, pcbData); 222 | } 223 | 224 | // Explorer has a registry watcher for each notify icon item. Trigger the 225 | // watcher by writing and then removing a temporary value. 226 | void TouchAllNotifyIconSettings() { 227 | constexpr WCHAR kBaseKeyPath[] = L"Control Panel\\NotifyIconSettings"; 228 | constexpr WCHAR kTempValueName[] = 229 | L"_temp_windhawk_taskbar-notification-icons-show-all"; 230 | 231 | HKEY hKey; 232 | LONG result = RegOpenKeyEx(HKEY_CURRENT_USER, kBaseKeyPath, 0, 233 | KEY_READ | KEY_WRITE, &hKey); 234 | if (result != ERROR_SUCCESS) { 235 | Wh_Log(L"Failed to open base key: %d", result); 236 | return; 237 | } 238 | 239 | DWORD index = 0; 240 | WCHAR subKeyName[MAX_PATH]; 241 | DWORD subKeyNameSize = MAX_PATH; 242 | 243 | while (RegEnumKeyEx(hKey, index, subKeyName, &subKeyNameSize, nullptr, 244 | nullptr, nullptr, nullptr) == ERROR_SUCCESS) { 245 | HKEY hSubKey; 246 | result = RegOpenKeyEx(hKey, subKeyName, 0, KEY_WRITE, &hSubKey); 247 | if (result == ERROR_SUCCESS) { 248 | if (RegSetValueEx(hSubKey, kTempValueName, 0, REG_SZ, 249 | (const BYTE*)L"", 250 | sizeof(WCHAR)) != ERROR_SUCCESS) { 251 | Wh_Log(L"Failed to create temp value: %s", subKeyName); 252 | } else if (RegDeleteValue(hSubKey, kTempValueName) != 253 | ERROR_SUCCESS) { 254 | Wh_Log(L"Failed to remove temp value: %s", subKeyName); 255 | } 256 | 257 | RegCloseKey(hSubKey); 258 | } else { 259 | Wh_Log(L"Failed to open subkey: %d", result); 260 | } 261 | 262 | subKeyNameSize = MAX_PATH; 263 | index++; 264 | } 265 | 266 | RegCloseKey(hKey); 267 | } 268 | 269 | void LoadSettings() { 270 | PCWSTR mode = Wh_GetStringSetting(L"mode"); 271 | g_settings.mode = Mode::showAll; 272 | if (wcscmp(mode, L"showNew") == 0) { 273 | g_settings.mode = Mode::showNew; 274 | } 275 | Wh_FreeStringSetting(mode); 276 | } 277 | 278 | BOOL Wh_ModInit() { 279 | Wh_Log(L">"); 280 | 281 | LoadSettings(); 282 | 283 | HMODULE kernelBaseModule = GetModuleHandle(L"kernelbase.dll"); 284 | if (!kernelBaseModule) { 285 | Wh_Log(L"kernelBaseModule not found"); 286 | return FALSE; 287 | } 288 | 289 | FARPROC pRegGetValueW = GetProcAddress(kernelBaseModule, "RegGetValueW"); 290 | if (!pRegGetValueW) { 291 | Wh_Log(L"RegGetValueW not found"); 292 | return FALSE; 293 | } 294 | 295 | FARPROC pRegSetValueExW = 296 | GetProcAddress(kernelBaseModule, "RegSetValueExW"); 297 | if (!pRegSetValueExW) { 298 | Wh_Log(L"RegSetValueExW not found"); 299 | return FALSE; 300 | } 301 | 302 | Wh_SetFunctionHook((void*)pRegSetValueExW, (void*)RegSetValueExW_Hook, 303 | (void**)&RegSetValueExW_Original); 304 | 305 | Wh_SetFunctionHook((void*)pRegGetValueW, (void*)RegGetValueW_Hook, 306 | (void**)&RegGetValueW_Original); 307 | 308 | return TRUE; 309 | } 310 | 311 | void Wh_ModAfterInit() { 312 | Wh_Log(L">"); 313 | 314 | TouchAllNotifyIconSettings(); 315 | } 316 | 317 | void Wh_ModUninit() { 318 | Wh_Log(L">"); 319 | 320 | TouchAllNotifyIconSettings(); 321 | } 322 | 323 | void Wh_ModSettingsChanged() { 324 | Wh_Log(L">"); 325 | 326 | LoadSettings(); 327 | 328 | TouchAllNotifyIconSettings(); 329 | } 330 | -------------------------------------------------------------------------------- /mods/taskbar-show-desktop-button-aero-peek.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id taskbar-show-desktop-button-aero-peek 3 | // @name Aero Peek on "Show desktop" button hover 4 | // @description Enable Aero Peek when hovering over the "Show Desktop" button, like it was possible before Windows 11 5 | // @version 1.0.1 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include explorer.exe 11 | // @architecture x86-64 12 | // @compilerOptions -lole32 -loleaut32 -lruntimeobject 13 | // ==/WindhawkMod== 14 | 15 | // Source code is published under The GNU General Public License v3.0. 16 | // 17 | // For bug reports and feature requests, please open an issue here: 18 | // https://github.com/ramensoftware/windhawk-mods/issues 19 | // 20 | // For pull requests, development takes place here: 21 | // https://github.com/m417z/my-windhawk-mods 22 | 23 | // ==WindhawkModReadme== 24 | /* 25 | # Aero Peek on "Show desktop" button hover 26 | 27 | Enable Aero Peek when hovering over the "Show Desktop" button, like it was 28 | possible before Windows 11. 29 | 30 | Note that Aero Peek can also be activated with the `Win+,` (Win key, then the 31 | comma key) keyboard shortcut. 32 | 33 | Only Windows 11 is supported. This mod isn't needed for Windows 7 to 10, where 34 | this behavior is already present. 35 | 36 | ![Demonstration](https://i.imgur.com/TsJX013.gif) 37 | */ 38 | // ==/WindhawkModReadme== 39 | 40 | // ==WindhawkModSettings== 41 | /* 42 | - hoverDelay: 500 43 | $name: Hover delay 44 | $description: >- 45 | The delay, in milliseconds, before activating the aero peek 46 | */ 47 | // ==/WindhawkModSettings== 48 | 49 | #include 50 | 51 | #include 52 | #include 53 | #include 54 | 55 | #undef GetCurrentTime 56 | 57 | #include 58 | #include 59 | #include 60 | 61 | using namespace winrt::Windows::UI::Xaml; 62 | 63 | struct { 64 | int hoverDelay; 65 | } g_settings; 66 | 67 | std::atomic g_taskbarViewDllLoaded; 68 | 69 | std::optional g_startPointerPos; 70 | UINT g_hoverTimer; 71 | BOOL g_aeroPeekOn; 72 | 73 | using DwmpActivateLivePreview_t = HRESULT(WINAPI*)(BOOL peekOn, 74 | HWND hPeekWindow, 75 | HWND hTopmostWindow, 76 | UINT peekType, 77 | void* param5); 78 | DwmpActivateLivePreview_t pDwmpActivateLivePreview; 79 | 80 | bool IsDragDistance(winrt::Windows::Foundation::Point p1, 81 | winrt::Windows::Foundation::Point p2) { 82 | return std::abs(p1.X - p2.X) > GetSystemMetrics(SM_CXDRAG) || 83 | std::abs(p1.Y - p2.Y) > GetSystemMetrics(SM_CYDRAG); 84 | } 85 | 86 | FrameworkElement EnumParentElements( 87 | FrameworkElement element, 88 | std::function enumCallback) { 89 | auto parent = element; 90 | while (true) { 91 | parent = Media::VisualTreeHelper::GetParent(parent) 92 | .try_as(); 93 | if (!parent) { 94 | return nullptr; 95 | } 96 | 97 | if (enumCallback(parent)) { 98 | return parent; 99 | } 100 | } 101 | } 102 | 103 | FrameworkElement GetParentElementByName(FrameworkElement element, PCWSTR name) { 104 | return EnumParentElements(element, [name](FrameworkElement parent) { 105 | return parent.Name() == name; 106 | }); 107 | } 108 | 109 | bool IsChildOfElementByName(FrameworkElement element, PCWSTR name) { 110 | return !!GetParentElementByName(element, name); 111 | } 112 | 113 | FrameworkElement GetParentElementByClassName(FrameworkElement element, 114 | PCWSTR className) { 115 | return EnumParentElements(element, [className](FrameworkElement parent) { 116 | return winrt::get_class_name(parent) == className; 117 | }); 118 | } 119 | 120 | bool IsChildOfElementByClassName(FrameworkElement element, PCWSTR className) { 121 | return !!GetParentElementByClassName(element, className); 122 | } 123 | 124 | void AeroPeekOnHoverStart() { 125 | if (!g_aeroPeekOn) { 126 | pDwmpActivateLivePreview(true, nullptr, nullptr, 3, nullptr); 127 | g_aeroPeekOn = true; 128 | } 129 | } 130 | 131 | void AeroPeekOnHoverStartTimer() { 132 | g_hoverTimer = 133 | SetTimer(nullptr, g_hoverTimer, g_settings.hoverDelay, 134 | [](HWND hwnd, // handle of window for timer messages 135 | UINT uMsg, // WM_TIMER message 136 | UINT_PTR idEvent, // timer identifier 137 | DWORD dwTime // current system time 138 | ) { 139 | Wh_Log(L">"); 140 | 141 | KillTimer(nullptr, g_hoverTimer); 142 | g_hoverTimer = 0; 143 | 144 | AeroPeekOnHoverStart(); 145 | }); 146 | } 147 | 148 | void AeroPeekOnHoverStop() { 149 | if (g_hoverTimer) { 150 | KillTimer(nullptr, g_hoverTimer); 151 | g_hoverTimer = 0; 152 | } 153 | 154 | if (g_aeroPeekOn) { 155 | pDwmpActivateLivePreview(false, nullptr, nullptr, 3, nullptr); 156 | g_aeroPeekOn = false; 157 | } 158 | } 159 | 160 | using IconView_OnPointerMoved_t = int(WINAPI*)(void* pThis, void* pArgs); 161 | IconView_OnPointerMoved_t IconView_OnPointerMoved_Original; 162 | int WINAPI IconView_OnPointerMoved_Hook(void* pThis, void* pArgs) { 163 | auto original = [=]() { 164 | return IconView_OnPointerMoved_Original(pThis, pArgs); 165 | }; 166 | 167 | FrameworkElement element = nullptr; 168 | ((IUnknown*)pThis) 169 | ->QueryInterface(winrt::guid_of(), 170 | winrt::put_abi(element)); 171 | 172 | if (!element) { 173 | return original(); 174 | } 175 | 176 | auto className = winrt::get_class_name(element); 177 | 178 | if (className != L"SystemTray.IconView" || 179 | !IsChildOfElementByName(element, L"ShowDesktopStack")) { 180 | return original(); 181 | } 182 | 183 | Wh_Log(L"> %s", className.c_str()); 184 | 185 | Input::PointerRoutedEventArgs args = nullptr; 186 | ((IUnknown*)pArgs) 187 | ->QueryInterface(winrt::guid_of(), 188 | winrt::put_abi(args)); 189 | if (!args) { 190 | return original(); 191 | } 192 | 193 | winrt::Windows::Foundation::Point pointerPos = 194 | args.GetCurrentPoint(element).Position(); 195 | 196 | if (!g_startPointerPos) { 197 | g_startPointerPos = pointerPos; 198 | AeroPeekOnHoverStartTimer(); 199 | } else if (!g_aeroPeekOn && 200 | IsDragDistance(*g_startPointerPos, pointerPos)) { 201 | AeroPeekOnHoverStop(); 202 | g_startPointerPos = pointerPos; 203 | AeroPeekOnHoverStartTimer(); 204 | } 205 | 206 | return original(); 207 | } 208 | 209 | using IconView_OnPointerExited_t = int(WINAPI*)(void* pThis, void* pArgs); 210 | IconView_OnPointerExited_t IconView_OnPointerExited_Original; 211 | int WINAPI IconView_OnPointerExited_Hook(void* pThis, void* pArgs) { 212 | auto original = [=]() { 213 | return IconView_OnPointerExited_Original(pThis, pArgs); 214 | }; 215 | 216 | FrameworkElement element = nullptr; 217 | ((IUnknown*)pThis) 218 | ->QueryInterface(winrt::guid_of(), 219 | winrt::put_abi(element)); 220 | 221 | if (!element) { 222 | return original(); 223 | } 224 | 225 | auto className = winrt::get_class_name(element); 226 | 227 | if (className != L"SystemTray.IconView" || 228 | !IsChildOfElementByName(element, L"ShowDesktopStack")) { 229 | return original(); 230 | } 231 | 232 | Wh_Log(L"> %s", className.c_str()); 233 | 234 | AeroPeekOnHoverStop(); 235 | g_startPointerPos.reset(); 236 | 237 | return original(); 238 | } 239 | 240 | bool HookTaskbarViewDllSymbols(HMODULE module) { 241 | // Taskbar.View.dll 242 | WindhawkUtils::SYMBOL_HOOK symbolHooks[] = { 243 | { 244 | {LR"(public: virtual int __cdecl winrt::impl::produce::OnPointerMoved(void *))"}, 245 | &IconView_OnPointerMoved_Original, 246 | IconView_OnPointerMoved_Hook, 247 | }, 248 | { 249 | {LR"(public: virtual int __cdecl winrt::impl::produce::OnPointerExited(void *))"}, 250 | &IconView_OnPointerExited_Original, 251 | IconView_OnPointerExited_Hook, 252 | }, 253 | }; 254 | 255 | if (!HookSymbols(module, symbolHooks, ARRAYSIZE(symbolHooks))) { 256 | Wh_Log(L"HookSymbols failed"); 257 | return false; 258 | } 259 | 260 | return true; 261 | } 262 | 263 | HMODULE GetTaskbarViewModuleHandle() { 264 | HMODULE module = GetModuleHandle(L"Taskbar.View.dll"); 265 | if (!module) { 266 | module = GetModuleHandle(L"ExplorerExtensions.dll"); 267 | } 268 | 269 | return module; 270 | } 271 | 272 | void HandleLoadedModuleIfTaskbarView(HMODULE module, LPCWSTR lpLibFileName) { 273 | if (!g_taskbarViewDllLoaded && GetTaskbarViewModuleHandle() == module && 274 | !g_taskbarViewDllLoaded.exchange(true)) { 275 | Wh_Log(L"Loaded %s", lpLibFileName); 276 | 277 | if (HookTaskbarViewDllSymbols(module)) { 278 | Wh_ApplyHookOperations(); 279 | } 280 | } 281 | } 282 | 283 | using LoadLibraryExW_t = decltype(&LoadLibraryExW); 284 | LoadLibraryExW_t LoadLibraryExW_Original; 285 | HMODULE WINAPI LoadLibraryExW_Hook(LPCWSTR lpLibFileName, 286 | HANDLE hFile, 287 | DWORD dwFlags) { 288 | HMODULE module = LoadLibraryExW_Original(lpLibFileName, hFile, dwFlags); 289 | if (module) { 290 | HandleLoadedModuleIfTaskbarView(module, lpLibFileName); 291 | } 292 | 293 | return module; 294 | } 295 | 296 | void LoadSettings() { 297 | g_settings.hoverDelay = Wh_GetIntSetting(L"hoverDelay"); 298 | } 299 | 300 | BOOL Wh_ModInit() { 301 | Wh_Log(L">"); 302 | 303 | LoadSettings(); 304 | 305 | HMODULE dwmapiModule = LoadLibrary(L"dwmapi.dll"); 306 | if (dwmapiModule) { 307 | pDwmpActivateLivePreview = 308 | (DwmpActivateLivePreview_t)GetProcAddress(dwmapiModule, (PCSTR)113); 309 | } 310 | 311 | if (!pDwmpActivateLivePreview) { 312 | Wh_Log(L"Couldn't get DwmpActivateLivePreview"); 313 | return FALSE; 314 | } 315 | 316 | if (HMODULE taskbarViewModule = GetTaskbarViewModuleHandle()) { 317 | g_taskbarViewDllLoaded = true; 318 | if (!HookTaskbarViewDllSymbols(taskbarViewModule)) { 319 | return FALSE; 320 | } 321 | } else { 322 | Wh_Log(L"Taskbar view module not loaded yet"); 323 | 324 | HMODULE kernelBaseModule = GetModuleHandle(L"kernelbase.dll"); 325 | auto pKernelBaseLoadLibraryExW = 326 | (decltype(&LoadLibraryExW))GetProcAddress(kernelBaseModule, 327 | "LoadLibraryExW"); 328 | WindhawkUtils::Wh_SetFunctionHookT(pKernelBaseLoadLibraryExW, 329 | LoadLibraryExW_Hook, 330 | &LoadLibraryExW_Original); 331 | } 332 | 333 | return TRUE; 334 | } 335 | 336 | void Wh_ModAfterInit() { 337 | Wh_Log(L">"); 338 | 339 | if (!g_taskbarViewDllLoaded) { 340 | if (HMODULE taskbarViewModule = GetTaskbarViewModuleHandle()) { 341 | if (!g_taskbarViewDllLoaded.exchange(true)) { 342 | Wh_Log(L"Got Taskbar.View.dll"); 343 | 344 | if (HookTaskbarViewDllSymbols(taskbarViewModule)) { 345 | Wh_ApplyHookOperations(); 346 | } 347 | } 348 | } 349 | } 350 | } 351 | 352 | void Wh_ModUninit() { 353 | Wh_Log(L">"); 354 | } 355 | 356 | void Wh_ModSettingsChanged() { 357 | Wh_Log(L">"); 358 | 359 | LoadSettings(); 360 | } 361 | -------------------------------------------------------------------------------- /mods/taskbar-thumbnail-size.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id taskbar-thumbnail-size 3 | // @name Taskbar Thumbnail Size 4 | // @description Customize the size of the new taskbar thumbnails in Windows 11 5 | // @version 1.0 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include explorer.exe 11 | // @architecture x86-64 12 | // @compilerOptions -lole32 -loleaut32 -lruntimeobject 13 | // ==/WindhawkMod== 14 | 15 | // Source code is published under The GNU General Public License v3.0. 16 | // 17 | // For bug reports and feature requests, please open an issue here: 18 | // https://github.com/ramensoftware/windhawk-mods/issues 19 | // 20 | // For pull requests, development takes place here: 21 | // https://github.com/m417z/my-windhawk-mods 22 | 23 | // ==WindhawkModReadme== 24 | /* 25 | # Taskbar Thumbnail Size 26 | 27 | Customize the size of the new taskbar thumbnails in Windows 11. 28 | 29 | For older Windows versions, the size of taskbar thumbnails can be changed via the registry: 30 | 31 | * [Change Size of Taskbar Thumbnail Previews in Windows 32 | 11](https://www.elevenforum.com/t/change-size-of-taskbar-thumbnail-previews-in-windows-11.6340/) 33 | (before Windows 11 version 24H2) 34 | * [How to Change the Size of Taskbar Thumbnails in Windows 35 | 10](https://www.tenforums.com/tutorials/26105-change-size-taskbar-thumbnails-windows-10-a.html) 36 | 37 | ![Demonstration](https://i.imgur.com/nGxrBYG.png) 38 | */ 39 | // ==/WindhawkModReadme== 40 | 41 | // ==WindhawkModSettings== 42 | /* 43 | - size: 150 44 | $name: Thumbnail size 45 | $description: Percentage of the original size. 46 | */ 47 | // ==/WindhawkModSettings== 48 | 49 | #include 50 | 51 | #include 52 | 53 | #undef GetCurrentTime 54 | 55 | #include 56 | #include 57 | 58 | using namespace winrt::Windows::UI::Xaml; 59 | 60 | struct { 61 | int size; 62 | } g_settings; 63 | 64 | std::atomic g_taskbarViewDllLoaded; 65 | 66 | using ThumbnailHelpers_GetScaledThumbnailSize_t = 67 | winrt::Windows::Foundation::Size*( 68 | WINAPI*)(winrt::Windows::Foundation::Size* result, 69 | winrt::Windows::Foundation::Size size, 70 | float scale); 71 | ThumbnailHelpers_GetScaledThumbnailSize_t 72 | ThumbnailHelpers_GetScaledThumbnailSize_Original; 73 | winrt::Windows::Foundation::Size* WINAPI 74 | ThumbnailHelpers_GetScaledThumbnailSize_Hook( 75 | winrt::Windows::Foundation::Size* result, 76 | winrt::Windows::Foundation::Size size, 77 | float scale) { 78 | Wh_Log(L"> %fx%f %f", size.Width, size.Height, scale); 79 | 80 | winrt::Windows::Foundation::Size* ret = 81 | ThumbnailHelpers_GetScaledThumbnailSize_Original( 82 | result, size, scale * g_settings.size / 100.0); 83 | 84 | Wh_Log(L"%fx%f", ret->Width, ret->Height); 85 | 86 | return ret; 87 | } 88 | 89 | using TaskItemThumbnailView_OnApplyTemplate_t = void(WINAPI*)(void* pThis); 90 | TaskItemThumbnailView_OnApplyTemplate_t 91 | TaskItemThumbnailView_OnApplyTemplate_Original; 92 | void WINAPI TaskItemThumbnailView_OnApplyTemplate_Hook(void* pThis) { 93 | Wh_Log(L">"); 94 | 95 | TaskItemThumbnailView_OnApplyTemplate_Original(pThis); 96 | 97 | IUnknown* unknownPtr = *((IUnknown**)pThis + 1); 98 | if (!unknownPtr) { 99 | return; 100 | } 101 | 102 | FrameworkElement element = nullptr; 103 | unknownPtr->QueryInterface(winrt::guid_of(), 104 | winrt::put_abi(element)); 105 | if (!element) { 106 | return; 107 | } 108 | 109 | try { 110 | Wh_Log(L"maxWidth=%f", element.MaxWidth()); 111 | element.MaxWidth(std::numeric_limits::infinity()); 112 | } catch (...) { 113 | HRESULT hr = winrt::to_hresult(); 114 | Wh_Log(L"Error %08X", hr); 115 | } 116 | } 117 | 118 | bool HookTaskbarViewDllSymbols(HMODULE module) { 119 | // Taskbar.View.dll 120 | WindhawkUtils::SYMBOL_HOOK symbolHooks[] = { 121 | { 122 | {LR"(struct winrt::Windows::Foundation::Size __cdecl winrt::Taskbar::implementation::ThumbnailHelpers::GetScaledThumbnailSize(struct winrt::Windows::Foundation::Size,float))"}, 123 | &ThumbnailHelpers_GetScaledThumbnailSize_Original, 124 | ThumbnailHelpers_GetScaledThumbnailSize_Hook, 125 | }, 126 | { 127 | {LR"(public: void __cdecl winrt::Taskbar::implementation::TaskItemThumbnailView::OnApplyTemplate(void))"}, 128 | &TaskItemThumbnailView_OnApplyTemplate_Original, 129 | TaskItemThumbnailView_OnApplyTemplate_Hook, 130 | }, 131 | }; 132 | 133 | if (!HookSymbols(module, symbolHooks, ARRAYSIZE(symbolHooks))) { 134 | Wh_Log(L"HookSymbols failed"); 135 | return false; 136 | } 137 | 138 | return true; 139 | } 140 | 141 | HMODULE GetTaskbarViewModuleHandle() { 142 | HMODULE module = GetModuleHandle(L"Taskbar.View.dll"); 143 | if (!module) { 144 | module = GetModuleHandle(L"ExplorerExtensions.dll"); 145 | } 146 | 147 | return module; 148 | } 149 | 150 | void HandleLoadedModuleIfTaskbarView(HMODULE module, LPCWSTR lpLibFileName) { 151 | if (!g_taskbarViewDllLoaded && GetTaskbarViewModuleHandle() == module && 152 | !g_taskbarViewDllLoaded.exchange(true)) { 153 | Wh_Log(L"Loaded %s", lpLibFileName); 154 | 155 | if (HookTaskbarViewDllSymbols(module)) { 156 | Wh_ApplyHookOperations(); 157 | } 158 | } 159 | } 160 | 161 | using LoadLibraryExW_t = decltype(&LoadLibraryExW); 162 | LoadLibraryExW_t LoadLibraryExW_Original; 163 | HMODULE WINAPI LoadLibraryExW_Hook(LPCWSTR lpLibFileName, 164 | HANDLE hFile, 165 | DWORD dwFlags) { 166 | HMODULE module = LoadLibraryExW_Original(lpLibFileName, hFile, dwFlags); 167 | if (module) { 168 | HandleLoadedModuleIfTaskbarView(module, lpLibFileName); 169 | } 170 | 171 | return module; 172 | } 173 | 174 | void LoadSettings() { 175 | g_settings.size = Wh_GetIntSetting(L"size"); 176 | } 177 | 178 | BOOL Wh_ModInit() { 179 | Wh_Log(L">"); 180 | 181 | LoadSettings(); 182 | 183 | if (HMODULE taskbarViewModule = GetTaskbarViewModuleHandle()) { 184 | g_taskbarViewDllLoaded = true; 185 | if (!HookTaskbarViewDllSymbols(taskbarViewModule)) { 186 | return FALSE; 187 | } 188 | } else { 189 | Wh_Log(L"Taskbar view module not loaded yet"); 190 | } 191 | 192 | HMODULE kernelBaseModule = GetModuleHandle(L"kernelbase.dll"); 193 | auto pKernelBaseLoadLibraryExW = (decltype(&LoadLibraryExW))GetProcAddress( 194 | kernelBaseModule, "LoadLibraryExW"); 195 | WindhawkUtils::Wh_SetFunctionHookT(pKernelBaseLoadLibraryExW, 196 | LoadLibraryExW_Hook, 197 | &LoadLibraryExW_Original); 198 | 199 | return TRUE; 200 | } 201 | 202 | void Wh_ModAfterInit() { 203 | Wh_Log(L">"); 204 | 205 | if (!g_taskbarViewDllLoaded) { 206 | if (HMODULE taskbarViewModule = GetTaskbarViewModuleHandle()) { 207 | if (!g_taskbarViewDllLoaded.exchange(true)) { 208 | Wh_Log(L"Got Taskbar.View.dll"); 209 | 210 | if (HookTaskbarViewDllSymbols(taskbarViewModule)) { 211 | Wh_ApplyHookOperations(); 212 | } 213 | } 214 | } 215 | } 216 | } 217 | 218 | void Wh_ModUninit() { 219 | Wh_Log(L">"); 220 | } 221 | 222 | void Wh_ModSettingsChanged() { 223 | Wh_Log(L">"); 224 | 225 | LoadSettings(); 226 | } 227 | -------------------------------------------------------------------------------- /mods/timer-resolution-control.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id timer-resolution-control 3 | // @name Timer Resolution Control 4 | // @description Prevent programs from changing the Windows timer resolution and increasing power consumption 5 | // @version 1.0 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include * 11 | // ==/WindhawkMod== 12 | 13 | // Source code is published under The GNU General Public License v3.0. 14 | // 15 | // For bug reports and feature requests, please open an issue here: 16 | // https://github.com/ramensoftware/windhawk-mods/issues 17 | // 18 | // For pull requests, development takes place here: 19 | // https://github.com/m417z/my-windhawk-mods 20 | 21 | // ==WindhawkModReadme== 22 | /* 23 | # Timer Resolution Control 24 | 25 | The default timer resolution on Windows is 15.6 milliseconds. When programs increase the timer 26 | frequency they increase power consumption and harm battery life. This mod provides configuration 27 | to determine which programs are allowed to change the timer resolution. 28 | 29 | More details: 30 | [Windows Timer Resolution: Megawatts Wasted](https://randomascii.wordpress.com/2013/07/08/windows-timer-resolution-megawatts-wasted/) 31 | 32 | The changes will apply the next time the target program(s) will change the timer resolution. To make 33 | sure the changes apply, you might want to restart the target program(s) or restart the computer. 34 | */ 35 | // ==/WindhawkModReadme== 36 | 37 | // ==WindhawkModSettings== 38 | /* 39 | - DefaultConfig: allow 40 | $name: Default configuration 41 | $description: Configuration for all programs, can be overridden for specific programs below 42 | $options: 43 | - allow: Allow changing the timer resolution 44 | - block: Disallow changing the timer resolution 45 | - limit: Limit changing the timer resolution 46 | - DefaultLimit: 10 47 | $name: Default timer resolution limit (for the limit configuration) 48 | $description: The lowest possible delay between timer events, in milliseconds 49 | - PerProgramConfig: 50 | - - Name: notepad.exe 51 | $name: Program name or path 52 | - Config: allow 53 | $name: Program configuration 54 | $options: 55 | - allow: Allow changing the timer resolution 56 | - block: Disallow changing the timer resolution 57 | - limit: Limit changing the timer resolution 58 | - Limit: 10 59 | $name: Program timer resolution limit (for the limit configuration) 60 | $name: Per-program configuration 61 | */ 62 | // ==/WindhawkModSettings== 63 | 64 | #include 65 | #include 66 | 67 | enum class Config { 68 | allow, 69 | block, 70 | limit 71 | }; 72 | 73 | ULONG g_minimumResolution; 74 | ULONG g_maximumResolution; 75 | ULONG g_limitResolution; 76 | 77 | Config ConfigFromString(PCWSTR string) { 78 | if (wcscmp(string, L"block") == 0) { 79 | return Config::block; 80 | } 81 | 82 | if (wcscmp(string, L"limit") == 0) { 83 | return Config::limit; 84 | } 85 | 86 | return Config::allow; 87 | } 88 | 89 | typedef NTSTATUS (WINAPI *NtSetTimerResolution_t)(ULONG, BOOLEAN, PULONG); 90 | NtSetTimerResolution_t pOriginalNtSetTimerResolution; 91 | 92 | NTSTATUS WINAPI NtSetTimerResolutionHook(ULONG DesiredResolution, BOOLEAN SetResolution, PULONG CurrentResolution) 93 | { 94 | if (!SetResolution) { 95 | Wh_Log(L"< SetResolution is FALSE"); 96 | return pOriginalNtSetTimerResolution(DesiredResolution, SetResolution, CurrentResolution); 97 | } 98 | 99 | Wh_Log(L"> DesiredResolution: %f milliseconds", (double)DesiredResolution / 10000.0); 100 | 101 | ULONG limitResolution = g_limitResolution; 102 | 103 | if (limitResolution == ULONG_MAX) { 104 | Wh_Log(L"* Blocking resolution change"); 105 | return STATUS_SUCCESS; 106 | } 107 | 108 | if (DesiredResolution < limitResolution) { 109 | Wh_Log(L"* Overriding resolution: %f milliseconds", (double)limitResolution / 10000.0); 110 | DesiredResolution = limitResolution; 111 | } 112 | 113 | return pOriginalNtSetTimerResolution(DesiredResolution, SetResolution, CurrentResolution); 114 | } 115 | 116 | void LoadSettings() 117 | { 118 | WCHAR programPath[1024]; 119 | DWORD dwSize = ARRAYSIZE(programPath); 120 | if (!QueryFullProcessImageName(GetCurrentProcess(), 0, programPath, &dwSize)) { 121 | *programPath = L'\0'; 122 | } 123 | 124 | PCWSTR programFileName = wcsrchr(programPath, L'\\'); 125 | if (programFileName) { 126 | programFileName++; 127 | if (!*programFileName) { 128 | programFileName = nullptr; 129 | } 130 | } 131 | 132 | bool matched = false; 133 | Config config = Config::allow; 134 | int limit = 0; 135 | 136 | for (int i = 0; ; i++) { 137 | PCWSTR name = Wh_GetStringSetting(L"PerProgramConfig[%d].Name", i); 138 | bool hasName = *name; 139 | if (hasName) { 140 | if (programFileName && wcsicmp(programFileName, name) == 0) { 141 | matched = true; 142 | } 143 | else if (wcsicmp(programPath, name) == 0) { 144 | matched = true; 145 | } 146 | } 147 | 148 | Wh_FreeStringSetting(name); 149 | 150 | if (!hasName) { 151 | break; 152 | } 153 | 154 | if (matched) { 155 | PCWSTR configString = Wh_GetStringSetting(L"PerProgramConfig[%d].Config", i); 156 | config = ConfigFromString(configString); 157 | Wh_FreeStringSetting(configString); 158 | 159 | if (config == Config::limit) { 160 | limit = Wh_GetIntSetting(L"PerProgramConfig[%d].Limit", i); 161 | } 162 | 163 | break; 164 | } 165 | } 166 | 167 | if (!matched) { 168 | PCWSTR configString = Wh_GetStringSetting(L"DefaultConfig"); 169 | config = ConfigFromString(configString); 170 | Wh_FreeStringSetting(configString); 171 | 172 | if (config == Config::limit) { 173 | limit = Wh_GetIntSetting(L"DefaultLimit"); 174 | } 175 | } 176 | 177 | if (config == Config::block) { 178 | Wh_Log(L"Config loaded: Disallowing changes"); 179 | g_limitResolution = ULONG_MAX; 180 | } 181 | else if (config == Config::limit) { 182 | ULONG limitResolution = limit * 10000; 183 | if (limitResolution > g_minimumResolution) { 184 | limitResolution = g_minimumResolution; 185 | } 186 | else if (limitResolution < g_maximumResolution) { 187 | limitResolution = g_maximumResolution; 188 | } 189 | 190 | Wh_Log(L"Config loaded: Limiting to %f milliseconds", (double)limitResolution / 10000.0); 191 | g_limitResolution = limitResolution; 192 | } 193 | else { 194 | Wh_Log(L"Config loaded: Allowing changes"); 195 | g_limitResolution = 0; 196 | } 197 | } 198 | 199 | BOOL Wh_ModInit() 200 | { 201 | Wh_Log(L"Init"); 202 | 203 | HMODULE hNtdll = GetModuleHandle(L"ntdll.dll"); 204 | if (!hNtdll) { 205 | return FALSE; 206 | } 207 | 208 | FARPROC pNtQueryTimerResolution = GetProcAddress(hNtdll, "NtQueryTimerResolution"); 209 | if (!pNtQueryTimerResolution) { 210 | return FALSE; 211 | } 212 | 213 | FARPROC pNtSetTimerResolution = GetProcAddress(hNtdll, "NtSetTimerResolution"); 214 | if (!pNtSetTimerResolution) { 215 | return FALSE; 216 | } 217 | 218 | ULONG MinimumResolution; 219 | ULONG MaximumResolution; 220 | ULONG CurrentResolution; 221 | NTSTATUS status = ((NTSTATUS(WINAPI*)(PULONG, PULONG, PULONG))pNtQueryTimerResolution)( 222 | &MinimumResolution, &MaximumResolution, &CurrentResolution); 223 | if (NT_SUCCESS(status)) { 224 | Wh_Log(L"NtQueryTimerResolution: min=%f, max=%f, current=%f", 225 | (double)MinimumResolution / 10000.0, 226 | (double)MaximumResolution / 10000.0, 227 | (double)CurrentResolution / 10000.0); 228 | g_minimumResolution = MinimumResolution; 229 | g_maximumResolution = MaximumResolution; 230 | } 231 | 232 | LoadSettings(); 233 | 234 | Wh_SetFunctionHook((void*)pNtSetTimerResolution, (void*)NtSetTimerResolutionHook, (void**)&pOriginalNtSetTimerResolution); 235 | 236 | return TRUE; 237 | } 238 | 239 | void Wh_ModSettingsChanged() 240 | { 241 | Wh_Log(L"SettingsChanged"); 242 | 243 | LoadSettings(); 244 | } 245 | -------------------------------------------------------------------------------- /mods/visual-studio-anti-rich-header.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id visual-studio-anti-rich-header 3 | // @name Visual Studio Anti-Rich-Header 4 | // @description Prevent the Visual Studio linker from embedding the Rich header into new executables 5 | // @version 1.0.3 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include link.exe 11 | // ==/WindhawkMod== 12 | 13 | // Source code is published under The GNU General Public License v3.0. 14 | // 15 | // For bug reports and feature requests, please open an issue here: 16 | // https://github.com/ramensoftware/windhawk-mods/issues 17 | // 18 | // For pull requests, development takes place here: 19 | // https://github.com/m417z/my-windhawk-mods 20 | 21 | // ==WindhawkModReadme== 22 | /* 23 | # Visual Studio Anti-Rich-Header 24 | 25 | Prevent the Visual Studio linker from embedding the Rich header into new executables. 26 | 27 | ![Screenshot](https://i.imgur.com/7ZeEfYK.png) \ 28 | *A Rich header example* 29 | */ 30 | // ==/WindhawkModReadme== 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | using namespace std::string_literals; 37 | 38 | std::string ReplaceAll(std::string str, const std::string& from, const std::string& to) 39 | { 40 | size_t start_pos = 0; 41 | while ((start_pos = str.find(from, start_pos)) != std::string::npos) { 42 | str.replace(start_pos, from.length(), to); 43 | start_pos += to.length(); // Handles case where 'to' is a substring of 'from' 44 | } 45 | return str; 46 | } 47 | 48 | BOOL Wh_ModInit() 49 | { 50 | Wh_Log(L"Init"); 51 | 52 | #ifdef _WIN64 53 | std::string targetRegex = 54 | R"(\x41\x8B\xC7)" // mov eax,r15d 55 | R"(\x49\x8B\x73\x28)" // mov rsi,qword ptr ds:[r11+28] 56 | R"(\x49\x8B\x7B\x30)" // mov rdi,qword ptr ds:[r11+30] 57 | R"(\x4D\x8B\x63\x38)" // mov r12,qword ptr ds:[r11+38] 58 | R"(\x41\xC7\x06\x52\x69\x63\x68\x49\x8B)" // mov dword ptr ds:[r14],68636952 59 | ; 60 | std::string targetPatch = "\x31\xC0\x90"s; // xor eax,eax ; nop 61 | #else 62 | std::string targetRegex = 63 | R"(\x8B\x44\x24.)" // mov eax,dword ptr ss:[esp+??] 64 | R"(()" 65 | 66 | // VS 2019 67 | R"(\x5F)" // pop edi 68 | R"(\x5E)" // pop esi 69 | R"(\xC7\x03\x52\x69\x63\x68)" // mov dword ptr ds:[ebx],68636952 70 | R"(\x89\x4B\x04)" // mov dword ptr ds:[ebx+4],ecx 71 | R"(|)" 72 | // VS 2022 73 | R"(\x5F)" // pop edi 74 | R"(\x5E)" // pop esi 75 | R"(\x89\x59\x04)" // mov dword ptr ds:[ecx+4],ebx 76 | R"(\xC7\x01\x52\x69\x63\x68)" // mov dword ptr ds:[ecx],68636952 77 | R"(|)" 78 | // VS 2022 17.12.1 79 | R"(\x89\x7B\x04)" // mov dword ptr ds:[ebx+4],ebx 80 | R"(\x5F)" // pop edi 81 | R"(\x5E)" // pop esi 82 | R"(\xC7\x03\x52\x69\x63\x68)" // mov dword ptr ds:[ebx],68636952 83 | 84 | R"())" 85 | R"(\x5B)" // pop ebx 86 | ; 87 | std::string targetPatch = "\x31\xC0\x90\x90"s; // xor eax,eax ; nop ; nop 88 | #endif 89 | 90 | char* pbExecutable = (char*)GetModuleHandle(nullptr); 91 | 92 | IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)pbExecutable; 93 | IMAGE_NT_HEADERS* pNtHeader = (IMAGE_NT_HEADERS*)((char*)pDosHeader + pDosHeader->e_lfanew); 94 | IMAGE_SECTION_HEADER* pSectionHeader = (IMAGE_SECTION_HEADER*)((char*)&pNtHeader->OptionalHeader + pNtHeader->FileHeader.SizeOfOptionalHeader); 95 | 96 | char* from = pbExecutable + pSectionHeader[0].VirtualAddress; 97 | char* to = from + pSectionHeader[0].SizeOfRawData; 98 | 99 | std::string_view search(from, to - from); 100 | std::match_results match; 101 | std::regex regex(ReplaceAll(targetRegex, ".", R"([\s\S])")); 102 | if (std::regex_search(search.begin(), search.end(), match, regex)) { 103 | auto pos = from + match.position(0); 104 | 105 | DWORD dwOldProtect; 106 | VirtualProtect(pos, targetPatch.size(), PAGE_EXECUTE_READWRITE, &dwOldProtect); 107 | memcpy(pos, targetPatch.data(), targetPatch.size()); 108 | VirtualProtect(pos, targetPatch.size(), dwOldProtect, &dwOldProtect); 109 | } 110 | else { 111 | bool proceed = MessageBox( 112 | nullptr, 113 | L"link.exe is incompatible, Rich header won't be removed. Proceed?", 114 | L"Windhawk mod: Visual Studio Anti-Rich-Header", 115 | MB_ICONWARNING | MB_TOPMOST | MB_YESNO 116 | ) == IDYES; 117 | if (!proceed) { 118 | ExitProcess(1); 119 | } 120 | } 121 | 122 | return TRUE; 123 | } 124 | -------------------------------------------------------------------------------- /mods/vmware-disable-upgrade-dialog.wh.cpp: -------------------------------------------------------------------------------- 1 | // ==WindhawkMod== 2 | // @id vmware-disable-upgrade-dialog 3 | // @name Disable VMware upgrade dialog 4 | // @description Disable the VMware Workstation "old encryption algorithm" upgrade dialog 5 | // @version 1.0 6 | // @author m417z 7 | // @github https://github.com/m417z 8 | // @twitter https://twitter.com/m417z 9 | // @homepage https://m417z.com/ 10 | // @include vmware.exe 11 | // @architecture x86 12 | // ==/WindhawkMod== 13 | 14 | // Source code is published under The GNU General Public License v3.0. 15 | // 16 | // For bug reports and feature requests, please open an issue here: 17 | // https://github.com/ramensoftware/windhawk-mods/issues 18 | // 19 | // For pull requests, development takes place here: 20 | // https://github.com/m417z/my-windhawk-mods 21 | 22 | // ==WindhawkModReadme== 23 | /* 24 | # Disable VMware upgrade dialog 25 | 26 | Disable the VMware Workstation "old encryption algorithm" upgrade dialog. 27 | 28 | When the dialog is displayed, the mod automatically selects the action of 29 | choice. 30 | 31 | ![Screenshot](https://i.imgur.com/vu6z164.png) 32 | */ 33 | // ==/WindhawkModReadme== 34 | 35 | // ==WindhawkModSettings== 36 | /* 37 | - action: upgrade 38 | $name: The action to take 39 | $options: 40 | - upgrade: Upgrade 41 | - dontUpgrade: Don't upgrade 42 | */ 43 | // ==/WindhawkModSettings== 44 | 45 | enum class Action { 46 | upgrade, 47 | dontUpgrade, 48 | }; 49 | 50 | struct { 51 | Action action; 52 | } g_settings; 53 | 54 | HWND g_dialog; 55 | HWND g_staticControl; 56 | HWND g_upgradeBtn; 57 | HWND g_dontUpgradeBtn; 58 | 59 | constexpr WCHAR kDialogText[] = 60 | L"The virtual machine is encrypted using old encryption algorithm. You can " 61 | L"upgrade VM to latest supported encryption mode. Please note that the " 62 | L"upgrade may break backward compatibility which means that the virtual " 63 | L"machine after upgrade may not work on older versions of Workstation. " 64 | L"Before upgrading, refer below KB for more details.\r\n\r\n " 65 | L"https://kb.vmware.com/s/article/93071"; 66 | constexpr WCHAR kUpgradeBtnText[] = L"Upgrade"; 67 | constexpr WCHAR kDontUpgradeBtnText[] = L"Don't Upgrade"; 68 | 69 | using CreateWindowExW_t = decltype(&CreateWindowExW); 70 | CreateWindowExW_t CreateWindowExW_Original; 71 | HWND WINAPI CreateWindowExW_Hook(DWORD dwExStyle, 72 | LPCWSTR lpClassName, 73 | LPCWSTR lpWindowName, 74 | DWORD dwStyle, 75 | int X, 76 | int Y, 77 | int nWidth, 78 | int nHeight, 79 | HWND hWndParent, 80 | HMENU hMenu, 81 | HINSTANCE hInstance, 82 | LPVOID lpParam) { 83 | HWND hWnd = CreateWindowExW_Original(dwExStyle, lpClassName, lpWindowName, 84 | dwStyle, X, Y, nWidth, nHeight, 85 | hWndParent, hMenu, hInstance, lpParam); 86 | if (!hWnd || !hWndParent) { 87 | return hWnd; 88 | } 89 | 90 | WCHAR szClassName[32]; 91 | if (!GetClassName(hWnd, szClassName, ARRAYSIZE(szClassName))) { 92 | return hWnd; 93 | } 94 | 95 | HWND* phWnd = nullptr; 96 | 97 | if (_wcsicmp(szClassName, L"VMwareStaticLink") == 0 && lpWindowName && 98 | wcscmp(lpWindowName, kDialogText) == 0) { 99 | Wh_Log(L"Static control created: %08X", (DWORD)(ULONG_PTR)hWnd); 100 | phWnd = &g_staticControl; 101 | } else if (_wcsicmp(szClassName, L"button") == 0 && lpWindowName) { 102 | if (wcscmp(lpWindowName, kUpgradeBtnText) == 0) { 103 | Wh_Log(L"Upgrade button created: %08X", (DWORD)(ULONG_PTR)hWnd); 104 | phWnd = &g_upgradeBtn; 105 | } else if (wcscmp(lpWindowName, kDontUpgradeBtnText) == 0) { 106 | Wh_Log(L"Don't upgrade button created: %08X", 107 | (DWORD)(ULONG_PTR)hWnd); 108 | phWnd = &g_dontUpgradeBtn; 109 | } 110 | } 111 | 112 | if (phWnd) { 113 | if (g_dialog != hWndParent) { 114 | g_dialog = hWndParent; 115 | g_staticControl = nullptr; 116 | g_upgradeBtn = nullptr; 117 | g_dontUpgradeBtn = nullptr; 118 | } 119 | 120 | *phWnd = hWnd; 121 | 122 | if (g_staticControl && g_upgradeBtn && g_dontUpgradeBtn) { 123 | if (g_settings.action == Action::upgrade) { 124 | PostMessage(hWndParent, WM_COMMAND, 125 | (BN_CLICKED << 16) | GetDlgCtrlID(g_upgradeBtn), 126 | (LPARAM)g_upgradeBtn); 127 | } else { 128 | PostMessage(hWndParent, WM_COMMAND, 129 | (BN_CLICKED << 16) | GetDlgCtrlID(g_dontUpgradeBtn), 130 | (LPARAM)g_dontUpgradeBtn); 131 | } 132 | 133 | g_dialog = nullptr; 134 | g_staticControl = nullptr; 135 | g_upgradeBtn = nullptr; 136 | g_dontUpgradeBtn = nullptr; 137 | } 138 | } 139 | 140 | return hWnd; 141 | } 142 | 143 | void LoadSettings() { 144 | PCWSTR action = Wh_GetStringSetting(L"action"); 145 | g_settings.action = Action::upgrade; 146 | if (wcscmp(action, L"dontUpgrade") == 0) { 147 | g_settings.action = Action::dontUpgrade; 148 | } 149 | Wh_FreeStringSetting(action); 150 | } 151 | 152 | BOOL Wh_ModInit() { 153 | Wh_Log(L">"); 154 | 155 | LoadSettings(); 156 | 157 | Wh_SetFunctionHook((void*)CreateWindowExW, (void*)CreateWindowExW_Hook, 158 | (void**)&CreateWindowExW_Original); 159 | 160 | return TRUE; 161 | } 162 | 163 | void Wh_ModUninit() { 164 | Wh_Log(L">"); 165 | } 166 | 167 | void Wh_ModSettingsChanged() { 168 | Wh_Log(L">"); 169 | 170 | LoadSettings(); 171 | } 172 | -------------------------------------------------------------------------------- /scripts/backup-images.py: -------------------------------------------------------------------------------- 1 | import re 2 | import requests 3 | from pathlib import Path 4 | from typing import List 5 | 6 | url_pattern = r"https://i\.imgur\.com/\w+\.\w+" 7 | script_dir = Path(__file__).parent 8 | code_folder_path = script_dir.parent / "mods" 9 | images_folder_path = script_dir.parent / "images" 10 | 11 | session = requests.Session() 12 | session.headers.update({'User-Agent': 'Mozilla/5.0'}) 13 | 14 | 15 | def find_image_urls(file_path: Path) -> List[str]: 16 | with file_path.open('r', encoding='utf-8') as file: 17 | content = file.read() 18 | return re.findall(url_pattern, content) 19 | 20 | 21 | def download_image(url: str, save_path: Path): 22 | if save_path.exists(): 23 | return 24 | 25 | response = session.get(url) 26 | response.raise_for_status() 27 | 28 | with save_path.open('wb') as file: 29 | file.write(response.content) 30 | 31 | print(f"Downloaded and saved: {save_path.name}") 32 | 33 | 34 | def process_code_files(code_folder: Path, images_folder: Path): 35 | stale_images = list(images_folder.glob('*')) 36 | 37 | image_urls: set[str] = set() 38 | for file_path in code_folder.glob('*.wh.cpp'): 39 | image_urls.update(find_image_urls(file_path)) 40 | 41 | for url in image_urls: 42 | # Extract image name from URL 43 | image_name = url.split('/')[-1] 44 | image_path = images_folder / image_name 45 | download_image(url, image_path) 46 | 47 | if image_path in stale_images: 48 | stale_images.remove(image_path) 49 | 50 | if stale_images: 51 | print("Deleting stale images:") 52 | for image_path in stale_images: 53 | print(f"- {image_path.name}") 54 | image_path.unlink() 55 | 56 | 57 | if __name__ == "__main__": 58 | process_code_files(code_folder_path, images_folder_path) 59 | --------------------------------------------------------------------------------