From 60a2872cbff38ce61f471a7883258eb97d792303 Mon Sep 17 00:00:00 2001 From: Mark Jansen Date: Tue, 8 Oct 2024 22:12:09 +0200 Subject: [PATCH 1/2] Add testcase showing wShowWindow behavior in Windows #9434 --- test/CMakeLists.txt | 1 + test/testcreateprocwndstatew32.c | 188 +++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 test/testcreateprocwndstatew32.c diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a9def98fe422c..b0d5f4932e71d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -273,6 +273,7 @@ if(MACOS) testnativex11.c ) elseif(WINDOWS) + add_sdl_test_executable(testcreateprocwndstatew32 SOURCES testcreateprocwndstatew32.c) add_sdl_test_executable(testnative BUILD_DEPENDENT NEEDS_RESOURCES TESTUTILS SOURCES testnative.c testnativew32.c) elseif(HAVE_X11 OR HAVE_WAYLAND) add_sdl_test_executable(testnative BUILD_DEPENDENT NO_C90 NEEDS_RESOURCES TESTUTILS SOURCES testnative.c) diff --git a/test/testcreateprocwndstatew32.c b/test/testcreateprocwndstatew32.c new file mode 100644 index 0000000000000..30c322e55cc14 --- /dev/null +++ b/test/testcreateprocwndstatew32.c @@ -0,0 +1,188 @@ +/* + Copyright (C) 1997-2024 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely. +*/ +/* Sample program: When starting a process, the parent can influence the initial window shown (minimized / maximized). We Test SDL's ability to defuse this. */ + +#include +#include +#include +#include + +enum +{ + TEST_CHILD_SUCCEEDED = 0, + TEST_CHILD_WINDOW_MAXIMIZED = 1, + TEST_CHILD_WINDOW_MINIMIZED = 2, + + TEST_CHILD_NO_WINDOW = 10, +}; +static const char test_window_title[] = "sdl_maximize_test_wnd"; +static HWND test_window_handle = NULL; +static int process_exit_code = TEST_CHILD_SUCCEEDED; +static HHOOK hook_handle = NULL; + +static void create_dlg() +{ + SDL_MessageBoxButtonData buttons[1] = { 0 }; + SDL_MessageBoxData mbdata = { 0 }; + int button = -1; + + buttons[0].buttonID = 0; + buttons[0].text = "Quit"; + + mbdata.flags = SDL_MESSAGEBOX_INFORMATION; + mbdata.message = "Maximize testcase"; + mbdata.title = test_window_title; + mbdata.numbuttons = 1; + mbdata.buttons = buttons; + + SDL_ShowMessageBox(&mbdata, &button); +} + +VOID CALLBACK TimerProc(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) +{ + if (!IsWindowVisible(test_window_handle)) { + /* Our timing might need to be relaxed a bit */ + process_exit_code = TEST_CHILD_NO_WINDOW; + } + PostMessageA(test_window_handle, WM_CLOSE, 0, 0); + KillTimer(NULL, idEvent); +} + +static LRESULT CALLBACK CallWndProc(_In_ int nCode, _In_ WPARAM wParam, _In_ LPARAM lParam) +{ + if (nCode == HC_ACTION) { + CWPSTRUCT *cwp = (CWPSTRUCT *)lParam; + CREATESTRUCT *cs; + switch (cwp->message) { + case WM_CREATE: + cs = (CREATESTRUCT *)cwp->lParam; + if (cs && cs->lpszName && !strcmp(cs->lpszName, test_window_title)) { + if (test_window_handle) { + SDL_Log("WARNING: test_window_handle already set!\n"); + } + test_window_handle = cwp->hwnd; + SDL_Log("WM_CREATE: %s\n", test_window_title); + /* Give the window max 3 seconds to show */ + SetTimer(NULL, 0, 3000, TimerProc); + } + break; + case WM_DESTROY: + if (test_window_handle == cwp->hwnd) { + test_window_handle = NULL; + SDL_Log("WM_DESTROY: %s\n", test_window_title); + } + break; + case WM_SIZE: + if (test_window_handle == cwp->hwnd) { + /* Inspect our initial size */ + if (cwp->wParam == SIZE_MAXIMIZED) { + SDL_Log("WM_SIZE: SIZE_MAXIMIZED\n"); + process_exit_code = TEST_CHILD_WINDOW_MAXIMIZED; + } else if (cwp->wParam == SIZE_MINIMIZED) { + SDL_Log("WM_SIZE: SIZE_MINIMIZED\n"); + process_exit_code = TEST_CHILD_WINDOW_MINIMIZED; + } + /* Now that we know the initial size, close the window asap */ + PostMessageA(test_window_handle, WM_CLOSE, 0, 0); + } + break; + } + } + return CallNextHookEx(hook_handle, nCode, wParam, lParam); +} + +static void spawn_child_process(void) +{ + char cmdline[MAX_PATH + 30] = { 0 }; + char path[MAX_PATH] = { 0 }; + STARTUPINFOA si = { sizeof(si) }; + PROCESS_INFORMATION pi = { 0 }; + + /* Retrieve our executable path */ + GetModuleFileNameA(NULL, path, SDL_arraysize(path)); + + /* Build the full command line for our child process */ + SDL_snprintf(cmdline, SDL_arraysize(cmdline), "\"%s\" --child-spawn-dialog", path); + + /* We set flags here asking Windows to maximize the initial window */ + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_MAXIMIZE; + if (CreateProcessA(NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { + if (WaitForSingleObject(pi.hProcess, 4000)) { + SDLTest_AssertCheck(false, "Child process did not quit!"); + /* Force it */ + TerminateProcess(pi.hProcess, 1); + } else { + /* The child process exits with a non-0 code when the window is minimized / maximized */ + DWORD exit_code = 0; + GetExitCodeProcess(pi.hProcess, &exit_code); + SDLTest_AssertCheck(exit_code == 0, "Child process failed with code %lu!", exit_code); + } + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } +} + +int main(int argc, char *argv[]) +{ + SDLTest_CommonState *state; + int i; + int spawn_test_dialog = 0; + + /* Initialize test framework */ + state = SDLTest_CommonCreateState(argv, 0); + if (state == NULL) { + return 1; + } + + /* Parse commandline */ + for (i = 1; i < argc;) { + int consumed; + + consumed = SDLTest_CommonArg(state, i); + + if (!consumed) { + if (SDL_strcmp(argv[i], "--child-spawn-dialog") == 0) { + consumed = 1; + spawn_test_dialog = 1; + } + } + + if (consumed <= 0) { + static const char *options[] = { "[--child-spawn-dialog]", NULL}; + SDLTest_CommonLogUsage(state, argv[0], options); + return 1; + } + + i += consumed; + } + + if (spawn_test_dialog) { + /* Install a hook to watch for events */ + hook_handle = SetWindowsHookExA(WH_CALLWNDPROC, CallWndProc, NULL, GetCurrentThreadId()); + if (hook_handle) { + /* The hook was installed succesfully, now spawn the dialog */ + create_dlg(); + /* We are done, remove the hook */ + UnhookWindowsHookEx(hook_handle); + hook_handle = NULL; + } + } else { + /* Spawn a child process containing the checks */ + spawn_child_process(); + } + + SDL_Quit(); + SDLTest_CommonDestroyState(state); + return process_exit_code; +} From e84442b1badf6ab63671073d254d3493a022cc05 Mon Sep 17 00:00:00 2001 From: Mark Jansen Date: Wed, 23 Oct 2024 22:04:45 +0200 Subject: [PATCH 2/2] Defuse wShowWindow behavior in Windows influencing the initial window created #9434 --- src/video/windows/SDL_windowsmessagebox.c | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/video/windows/SDL_windowsmessagebox.c b/src/video/windows/SDL_windowsmessagebox.c index be96da9e082d7..d23eccd35092d 100644 --- a/src/video/windows/SDL_windowsmessagebox.c +++ b/src/video/windows/SDL_windowsmessagebox.c @@ -902,6 +902,38 @@ static bool WIN_ShowOldMessageBox(const SDL_MessageBoxData *messageboxdata, int return result; } + +static INT_PTR CALLBACK EmptyDlgProc(HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam) +{ + return 0; +} + +// Windows stores an initial window state passed in by the shell (or another process) if STARTF_USESHOWWINDOW is set. +// This state is applied to the first window that receives a 'ShowWindow' call. +// To ensure our window is not influenced by this, we create a dummy window to catch this initial state. +// The idea is taken from putty, which uses this method to ensure its windows are always created in the correct state. +static bool s_DefusedInitialwShowWindow = false; +static void DefuseInitialwShowWindow(void) +{ + // Build up a dialog template + enum { DlgX = 0, DlgY = 0, DlgW = 100, DlgH = 60, ChildCount = 0, MenuId = 0, ClassName = 0, Title = 0, DialogStyle = DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU, DialogStyleEx = 0 }; + static const WORD DialogTemplate[] = + { + LOWORD(DialogStyle), HIWORD(DialogStyle), LOWORD(DialogStyleEx), HIWORD(DialogStyleEx), ChildCount, DlgX, DlgY, DlgW, DlgH, MenuId, ClassName, Title, + }; + if (s_DefusedInitialwShowWindow) { + return; + } + // Create a window using the template + HWND hWnd = CreateDialogIndirectParamW(NULL, (LPCDLGTEMPLATE)DialogTemplate, NULL, EmptyDlgProc, 0); + // Now catch the window state provided + ShowWindow(hWnd, SW_HIDE); + // We are done + DestroyWindow(hWnd); + s_DefusedInitialwShowWindow = true; +} + + /* TaskDialogIndirect procedure * This is because SDL targets Windows XP (0x501), so this is not defined in the platform SDK. */ @@ -930,6 +962,8 @@ bool WIN_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID) return SDL_OutOfMemory(); } + DefuseInitialwShowWindow(); + // If we cannot load comctl32.dll use the old messagebox! hComctl32 = LoadLibrary(TEXT("comctl32.dll")); if (!hComctl32) {