Last active
March 20, 2025 10:37
-
-
Save m417z/69815de59bc1c2b724bde88054884f3c to your computer and use it in GitHub Desktop.
Prevents some UWP popups from closing due to UWPSpy stealing focus. See https://github.com/m417z/UWPSpy/issues/8.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==WindhawkMod== | |
// @id prevent-uwpspy-focus-steal | |
// @name Prevent UWPSpy Focus Steal | |
// @description Prevents some UWP popups from closing due to UWPSpy stealing focus | |
// @version 0.1 | |
// @author m417z | |
// @github https://github.com/m417z | |
// @twitter https://x.com/m417z | |
// @homepage https://m417z.com/ | |
// @compilerOptions -lcomctl32 | |
// @include * | |
// ==/WindhawkMod== | |
// ==WindhawkModReadme== | |
/* | |
# Prevent UWPSpy Focus Steal | |
Prevents some UWP popups from closing due to UWPSpy stealing focus. See [UWPSpy | |
Feature Request: Ability to "pause" or | |
"save"](https://github.com/m417z/UWPSpy/issues/8). | |
*/ | |
// ==/WindhawkModReadme== | |
#include <windhawk_utils.h> | |
// Suppress the messages we want to ignore | |
// and prevent them from reaching the main windowProc | |
LRESULT CALLBACK TargetWindowSubclassProc(HWND hWnd, | |
UINT uMsg, | |
WPARAM wParam, | |
LPARAM lParam, | |
DWORD_PTR dwRefData) { | |
switch (uMsg) { | |
case WM_NCACTIVATE: // Non-client area activation (e.g. titlebar) | |
if (!wParam) { | |
return FALSE; | |
} | |
break; | |
} | |
return DefSubclassProc(hWnd, uMsg, wParam, lParam); | |
} | |
bool IsTargetWindow(HWND hwnd) { | |
WCHAR className[64]; | |
if (!GetClassName(hwnd, className, ARRAYSIZE(className))) { | |
return false; | |
} | |
return _wcsicmp(className, L"UWPSpy") == 0; | |
// return _wcsicmp(className, L"XamlExplorerHostIslandWindow_WASDK") == 0; | |
} | |
// ------------------ Dealing with newly created windows ------------------ | |
// Hooks for creating and destroying new windows to ensure that | |
// new windows created after the mod is initially enabled get subclassed | |
using PUNICODE_STRING = PVOID; | |
// https://github.com/sandboxie-plus/Sandboxie/blob/294966c7d6e99cd153ede87ad09aa39ef29e34c3/Sandboxie/core/dll/Win32.c#L25 | |
using NtUserCreateWindowEx_t = HWND(WINAPI*)(DWORD dwExStyle, | |
PUNICODE_STRING UnsafeClassName, | |
LPCWSTR VersionedClass, | |
PUNICODE_STRING UnsafeWindowName, | |
DWORD dwStyle, | |
LONG x, | |
LONG y, | |
LONG nWidth, | |
LONG nHeight, | |
HWND hWndParent, | |
HMENU hMenu, | |
HINSTANCE hInstance, | |
LPVOID lpParam, | |
DWORD dwShowMode, | |
DWORD dwUnknown1, | |
DWORD dwUnknown2, | |
VOID* qwUnknown3); | |
NtUserCreateWindowEx_t NtUserCreateWindowEx_Original; | |
HWND WINAPI NtUserCreateWindowEx_Hook(DWORD dwExStyle, | |
PUNICODE_STRING UnsafeClassName, | |
LPCWSTR VersionedClass, | |
PUNICODE_STRING UnsafeWindowName, | |
DWORD dwStyle, | |
LONG x, | |
LONG y, | |
LONG nWidth, | |
LONG nHeight, | |
HWND hWndParent, | |
HMENU hMenu, | |
HINSTANCE hInstance, | |
LPVOID lpParam, | |
DWORD dwShowMode, | |
DWORD dwUnknown1, | |
DWORD dwUnknown2, | |
VOID* qwUnknown3) { | |
HWND hWnd = NtUserCreateWindowEx_Original( | |
dwExStyle, UnsafeClassName, VersionedClass, UnsafeWindowName, dwStyle, | |
x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam, | |
dwShowMode, dwUnknown1, dwUnknown2, qwUnknown3); | |
if (!hWnd || !IsTargetWindow(hWnd)) | |
return hWnd; | |
WindhawkUtils::SetWindowSubclassFromAnyThread(hWnd, | |
TargetWindowSubclassProc, 0); | |
Wh_Log(L"New window subclassed: %08X", (DWORD)(UINT_PTR)hWnd); | |
return hWnd; | |
} | |
// ------------------ Dealing with existing windows ------------------ | |
// We subclass all existing windows when the mod is enabled | |
// while the program is already running | |
BOOL CALLBACK InitialEnumTargetWindowsFunc(HWND hWnd, LPARAM lParam) { | |
DWORD dwProcessId = 0; | |
DWORD dwThreadId = GetWindowThreadProcessId(hWnd, &dwProcessId); | |
if (!dwThreadId || dwProcessId != GetCurrentProcessId()) { | |
return TRUE; | |
} | |
if (IsTargetWindow(hWnd)) { | |
Wh_Log(L"Existing window subclassed: %08X", (DWORD)(UINT_PTR)hWnd); | |
WindhawkUtils::SetWindowSubclassFromAnyThread( | |
hWnd, TargetWindowSubclassProc, 0); | |
} | |
return TRUE; | |
} | |
// ------------------ Clean up when mod is disabled ------------------ | |
// Send a message to each windowProc in the process | |
// If it was subclassed, it will process the message and unsubclass itself | |
BOOL CALLBACK EnumTargetWindowsUnsubclassFunc(HWND hWnd, LPARAM lParam) { | |
DWORD dwProcessId = 0; | |
DWORD dwThreadId = GetWindowThreadProcessId(hWnd, &dwProcessId); | |
if (!dwThreadId || dwProcessId != GetCurrentProcessId()) { | |
return TRUE; | |
} | |
if (IsTargetWindow(hWnd)) { | |
Wh_Log(L"Unsubclass: %08X", (DWORD)(UINT_PTR)hWnd); | |
WindhawkUtils::RemoveWindowSubclassFromAnyThread( | |
hWnd, TargetWindowSubclassProc); | |
} | |
return TRUE; | |
} | |
BOOL Wh_ModInit() { | |
Wh_Log(L">"); | |
// Ensure subclass will be added to newly created windows | |
HMODULE hModule = GetModuleHandle(L"win32u.dll"); | |
if (!hModule) { | |
return FALSE; | |
} | |
NtUserCreateWindowEx_t pNtUserCreateWindowEx = | |
(NtUserCreateWindowEx_t)GetProcAddress(hModule, "NtUserCreateWindowEx"); | |
if (!pNtUserCreateWindowEx) { | |
return FALSE; | |
} | |
Wh_SetFunctionHook((void*)pNtUserCreateWindowEx, | |
(void*)NtUserCreateWindowEx_Hook, | |
(void**)&NtUserCreateWindowEx_Original); | |
return TRUE; | |
} | |
void Wh_ModAfterInit() { | |
Wh_Log(L">"); | |
EnumWindows(InitialEnumTargetWindowsFunc, 0); | |
} | |
void Wh_ModUninit() { | |
Wh_Log(L">"); | |
EnumWindows(EnumTargetWindowsUnsubclassFunc, 0); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment