diff --git a/src/dllmain.cpp b/src/dllmain.cpp index 5ea841b..ca3122a 100644 --- a/src/dllmain.cpp +++ b/src/dllmain.cpp @@ -956,12 +956,32 @@ HWND hWndGame = NULL; BOOL bWindowFocused = FALSE; BOOL bSizeMove = FALSE; +SafetyHookInline ShowWindow_sh{}; +BOOL +WINAPI +ShowWindow_hk( + _In_ HWND hWnd, + _In_ int nCmdShow) +{ + // Never let the game hide the window. + // + // That will cause spurious window activation after resuming from + // display sleep/screensaver + if (hWnd == hWndGame && hWnd != 0) { + if (nCmdShow == SW_HIDE) { + return TRUE; + } + } + + return ShowWindow_sh.stdcall(hWnd, nCmdShow); +} + SafetyHookInline ClipCursor_sh{}; BOOL ClipCursor_hk(const RECT *lpRect) { RECT bounds = {}; - if (bWindowFocused == FALSE || bSizeMove) { + if (bWindowFocused == FALSE || bSizeMove || GetForegroundWindow () != hWndGame) { lpRect = nullptr; } @@ -974,14 +994,39 @@ BOOL ClipCursor_hk(const RECT *lpRect) return ClipCursor_sh.stdcall(lpRect); } - LRESULT __stdcall NewWndProc(HWND window, UINT message_type, WPARAM w_param, LPARAM l_param) { if (window != hWndGame) return CallWindowProc(OldWndProc, window, message_type, w_param, l_param); + // Game does not process Win32 keyboard or mouse messages, and this causes + // windows to beep on system keys and other events. Just block all of these + if ((message_type >= WM_KEYFIRST && message_type <= WM_KEYLAST) || + message_type >= WM_MOUSEFIRST && message_type <= WM_MOUSELAST) + { + DefWindowProcW(window, message_type, w_param, l_param); + return 0; + } + + BOOL style_changed = FALSE; BOOL need_clip_cursor = FALSE; + HWND hWndForeground = + GetForegroundWindow (); + + if (hWndForeground == hWndGame || GetTopWindow (NULL) == hWndGame) { + if (!std::exchange(bWindowFocused, true)) { + need_clip_cursor = TRUE; + CallWindowProc(OldWndProc, hWndGame, WM_ACTIVATE, WA_CLICKACTIVE, 0); + } + } + else if (! bBackgroundAudio) { + if (std::exchange(bWindowFocused, false)) { + need_clip_cursor = TRUE; + CallWindowProc(OldWndProc, hWndGame, WM_ACTIVATE, WA_INACTIVE, 0); + } + } + switch (message_type) { case WM_SYSCOMMAND: switch (LOWORD (w_param & 0xFFF0)) @@ -1006,54 +1051,54 @@ LRESULT __stdcall NewWndProc(HWND window, UINT message_type, WPARAM w_param, LPA case WM_KILLFOCUS: case WM_SETFOCUS: - bWindowFocused = (message_type == WM_SETFOCUS); + { need_clip_cursor = TRUE; + } break; + + case WM_ACTIVATE: + case WM_ACTIVATEAPP: + case WM_NCACTIVATE: + // Get styles + LONG lStyle = GetWindowLong(window, GWL_STYLE); + LONG lExStyle = GetWindowLong(window, GWL_EXSTYLE); // Add re-sizable style and enable maximize button if (bResizableWindow) { - // Get styles - LONG lStyle = GetWindowLong(window, GWL_STYLE); - LONG lExStyle = GetWindowLong(window, GWL_EXSTYLE); - // Check for borderless/fullscreen styles if ((lStyle & WS_THICKFRAME) != WS_THICKFRAME && (lStyle & WS_POPUP) == 0 && (lExStyle & WS_EX_TOPMOST) == 0) { // Add resizable + maximize styles lStyle |= (WS_THICKFRAME | WS_MAXIMIZEBOX); SetWindowLong(window, GWL_STYLE, lStyle); - // Force window to update - SetWindowPos(window, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_ASYNCWINDOWPOS); - } - } - break; - - case WM_ACTIVATE: - case WM_ACTIVATEAPP: - if (w_param == WA_INACTIVE) { - // Enable background audio - if (bBackgroundAudio) { - DefWindowProcW(window, message_type, w_param, l_param); // Without this, mouse input would continue working - return 0; + style_changed = TRUE; } } + return DefWindowProcW(window, message_type, w_param, l_param); } if (need_clip_cursor) ClipCursor_hk(NULL); // The hook will calculate the actual rectangle to use + if (style_changed) // Force window to update + SetWindowPos(window, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | + SWP_NOSIZE | SWP_NOZORDER | SWP_NOREPOSITION); + return CallWindowProc(OldWndProc, window, message_type, w_param, l_param); }; void SubclassGameWindow (HWND hWnd) { - hWndGame = hWnd; - FARPROC ClipCursor_fn = GetProcAddress(GetModuleHandleW (L"user32.dll"), "ClipCursor"); ClipCursor_sh = safetyhook::create_inline(ClipCursor_fn, reinterpret_cast(ClipCursor_hk)); + FARPROC ShowWindow_fn = GetProcAddress(GetModuleHandleW (L"user32.dll"), "ShowWindow"); + ShowWindow_sh = safetyhook::create_inline(ShowWindow_fn, reinterpret_cast(ShowWindow_hk)); + // Set new wnd proc OldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)NewWndProc); spdlog::info("Window Focus: Subclassed Game Window"); + + hWndGame = hWnd; } LRESULT CALLBACK CallWndProcHook ( @@ -1074,6 +1119,23 @@ LRESULT CALLBACK CallWndProcHook ( if (! wcscmp (wnd_class_name, sWindowClassName)) { UnhookWindowsHookEx (hkCallWndProc); SubclassGameWindow (hWndMsg); + + auto lExStyle = GetWindowLongPtrW(hWndGame, GWL_EXSTYLE); + + // The game does not have AppWindow style, so it does not + // correctly register itself to appear in the taskbar and + // activate the way applications are supposed to... + if ((lExStyle & WS_EX_APPWINDOW) == 0) { + lExStyle |= WS_EX_APPWINDOW; + SetWindowLongPtrW(hWndGame, GWL_EXSTYLE, lExStyle); + } + + // Force window to update, and activate it + SetWindowPos (hWndGame, GetForegroundWindow (), + 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | + SWP_NOSIZE | SWP_SHOWWINDOW); + BringWindowToTop (hWndGame); + SetForegroundWindow (hWndGame); } } @@ -1107,7 +1169,20 @@ void WindowFocus() SetWindowsHookExW (WH_CALLWNDPROC, CallWndProcHook, 0, GetMainThreadId ()); if (hkCallWndProc != 0) - Sleep (INFINITE); + { + // Loop and periodically wake the game's window thread to deal with + // stupid handling of window activation by the game + MSG msg = {}; + do + { + Sleep (250UL); + + if (hWndGame != 0) + SendMessage (hWndGame, WM_NULL, 0, 0); + + PeekMessage (&msg, 0, 0, 0, 0); + } while (msg.message != WM_QUIT); + } } }