From: Paul Gofman pgofman@codeweavers.com
--- dlls/user32/tests/win.c | 190 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+)
diff --git a/dlls/user32/tests/win.c b/dlls/user32/tests/win.c index 1a8aa801df9..163d1cfdb52 100644 --- a/dlls/user32/tests/win.c +++ b/dlls/user32/tests/win.c @@ -127,6 +127,17 @@ static void flush_events( BOOL remove_messages ) } }
+static void pump_messages(void) +{ + MSG msg; + + while (PeekMessageA( &msg, 0, 0, 0, PM_REMOVE )) + { + TranslateMessage( &msg ); + DispatchMessageA( &msg ); + } +} + /* check the values returned by the various parent/owner functions on a given window */ static void check_parents( HWND hwnd, HWND ga_parent, HWND gwl_parent, HWND get_parent, HWND gw_owner, HWND ga_root, HWND ga_root_owner ) @@ -13585,6 +13596,178 @@ static void test_SetProcessLaunchForegroundPolicy(void) ok(!ret && (GetLastError() == ERROR_ACCESS_DENIED), "SetProcessLaunchForegroundPolicy failed: %d error %lu\n", ret, GetLastError()); }
+struct test_startupinfo_showwindow_test +{ + DWORD style; + enum + { + TEST_SHOW_WS_VISIBLE, + TEST_SHOW_SHOWWINDOW, + TEST_SHOW_SETWINDOWPOS, + } + show_type; + HWND parent; + int cmd_show; + BOOL counted_as_first; + BOOL show_affected; +}; + +static const struct test_startupinfo_showwindow_test test_startupinfo_showwindow_tests[] = +{ + /* Affected window types. */ + { WS_OVERLAPPED, TEST_SHOW_SHOWWINDOW, NULL, SW_SHOW, TRUE, TRUE }, + { WS_POPUP | WS_CAPTION, TEST_SHOW_SHOWWINDOW, NULL, SW_SHOW, TRUE, TRUE }, + { 0, TEST_SHOW_SHOWWINDOW, HWND_MESSAGE, SW_SHOW, TRUE, TRUE }, + + /* Types of showing window. */ + { WS_OVERLAPPED, TEST_SHOW_WS_VISIBLE, NULL, SW_SHOW, TRUE, TRUE }, + { WS_OVERLAPPED, TEST_SHOW_SETWINDOWPOS, NULL, SW_SHOW, FALSE, FALSE }, + + /* Various cmd_show values */ + { WS_OVERLAPPED, TEST_SHOW_SHOWWINDOW, NULL, SW_NORMAL, TRUE, TRUE }, + { WS_OVERLAPPED, TEST_SHOW_SHOWWINDOW, NULL, SW_SHOWDEFAULT, TRUE, TRUE }, + { WS_OVERLAPPED, TEST_SHOW_SHOWWINDOW, NULL, SW_SHOWMAXIMIZED, TRUE, FALSE }, + { WS_OVERLAPPED, TEST_SHOW_SHOWWINDOW, NULL, SW_FORCEMINIMIZE, TRUE, FALSE }, +#if 1 + /* Excluding these tests to reduce test run time. */ + { WS_OVERLAPPED, TEST_SHOW_SHOWWINDOW, NULL, SW_HIDE, TRUE, FALSE }, + { WS_OVERLAPPED, TEST_SHOW_SHOWWINDOW, NULL, SW_SHOWMINIMIZED, TRUE, FALSE }, + { WS_OVERLAPPED, TEST_SHOW_SHOWWINDOW, NULL, SW_SHOWNOACTIVATE, TRUE, FALSE }, + { WS_OVERLAPPED, TEST_SHOW_SHOWWINDOW, NULL, SW_MINIMIZE, TRUE, FALSE }, + { WS_OVERLAPPED, TEST_SHOW_SHOWWINDOW, NULL, SW_SHOWMINNOACTIVE, TRUE, FALSE }, + { WS_OVERLAPPED, TEST_SHOW_SHOWWINDOW, NULL, SW_SHOWNA, TRUE, FALSE }, + { WS_OVERLAPPED, TEST_SHOW_SHOWWINDOW, NULL, SW_RESTORE, TRUE, FALSE }, +#endif +}; + +static void test_startupinfo_showwindow_proc( int test_id ) +{ + const struct test_startupinfo_showwindow_test *test = &test_startupinfo_showwindow_tests[test_id]; + static const DWORD ignored_window_styles[] = + { + WS_CHILD, + WS_POPUP, /* WS_POPUP windows are not ignored when used with WS_CAPTION (which is WS_BORDER | WS_DLGFRAME) */ + WS_CHILD | WS_POPUP, + WS_POPUP | WS_BORDER, + WS_POPUP | WS_DLGFRAME, + WS_POPUP | WS_SYSMENU | WS_THICKFRAME| WS_MINIMIZEBOX | WS_MAXIMIZEBOX, + }; + BOOL bval, expected; + STARTUPINFOW sa; + unsigned int i; + DWORD style; + HWND hwnd; + + GetStartupInfoW( &sa ); + + winetest_push_context( "show %u, test %d", sa.wShowWindow, test_id ); + + ok( sa.dwFlags & STARTF_USESHOWWINDOW, "got %#lx.\n", sa.dwFlags ); + + /* First test windows which are not affected by startup info. ShowWindow() called for those doesn't count as + * consuming startup info, it is still used with the next applicable window. + * + * SW_ variants for ShowWindow() which are not altered by startup info still consume startup info usage so can + * only be tested once per process. */ + + hwnd = CreateWindowA( "static", "parent", WS_OVERLAPPED, 0, 0, 0, 0, NULL, NULL, + GetModuleHandleW( NULL ), NULL ); + pump_messages(); + for (i = 0; i < ARRAY_SIZE(ignored_window_styles); ++i) + { + winetest_push_context( "%u", i ); + hwnd = CreateWindowA( "static", "overlapped", ignored_window_styles[i], 0, 0, 0, 0, + ignored_window_styles[i] & WS_CHILD ? hwnd : NULL, NULL, + GetModuleHandleW( NULL ), NULL ); + ok( !!hwnd, "got NULL.\n" ); + ShowWindow( hwnd, SW_SHOW ); + bval = IsWindowVisible( hwnd ); + if ((ignored_window_styles[i] & (WS_CHILD | WS_POPUP)) == WS_CHILD) + ok( !bval, "unexpectedly visible.\n" ); + else + ok( bval, "unexpectedly invisible.\n" ); + pump_messages(); + winetest_pop_context(); + } + DestroyWindow( hwnd ); + pump_messages(); + + style = test->style; + if (test->show_type == TEST_SHOW_WS_VISIBLE) style |= WS_VISIBLE; + hwnd = CreateWindowA( "static", "overlapped", style, 0, 0, 0, 0, NULL, NULL, + GetModuleHandleW( NULL ), NULL ); + ok( !!hwnd, "got NULL.\n" ); + pump_messages(); + if (test->show_type == TEST_SHOW_SHOWWINDOW) + ShowWindow( hwnd, test->cmd_show ); + else if (test->show_type == TEST_SHOW_SETWINDOWPOS) + SetWindowPos( hwnd, NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_SHOWWINDOW ); + pump_messages(); + expected = test->cmd_show != SW_HIDE && test->cmd_show != SW_FORCEMINIMIZE + && (!test->show_affected || sa.wShowWindow != SW_HIDE); + bval = !!IsWindowVisible( hwnd ); + + todo_wine_if((test->show_affected && sa.wShowWindow == SW_HIDE) || test->cmd_show == SW_FORCEMINIMIZE) + ok( bval == expected, "got %d, expected %d.\n", bval, expected ); + + /* After default args were used once SW_SHOWDEFAULT doesn't use startupinfo. */ + ShowWindow( hwnd, SW_SHOWDEFAULT ); + bval = !!IsWindowVisible( hwnd ); + expected = test->counted_as_first || sa.wShowWindow != SW_HIDE; + todo_wine_if(!test->counted_as_first && sa.wShowWindow == SW_HIDE) ok( bval == expected, "got %d, expected %d.\n", bval, expected ); + DestroyWindow( hwnd ); + pump_messages(); + + hwnd = CreateWindowA( "static", "overlapped2", WS_OVERLAPPED, 0, 0, 0, 0, NULL, NULL, GetModuleHandleW(NULL), NULL ); + ok( !!hwnd, "got NULL.\n" ); + pump_messages(); + /* After default args were used once SW_SHOWDEFAULT doesn't use startupinfo, even with another window. */ + ShowWindow( hwnd, SW_SHOWDEFAULT ); + bval = IsWindowVisible( hwnd ); + ok( bval, "got %d, expected %d.\n", bval, expected ); + DestroyWindow( hwnd ); + pump_messages(); + + winetest_pop_context(); +} + +static void test_startupinfo_showwindow( char **argv ) +{ + STARTUPINFOA sa = {.cb = sizeof(STARTUPINFOA)}; + PROCESS_INFORMATION info; + char cmdline[MAX_PATH]; + unsigned int i; + BOOL ret; + + sa.dwFlags = STARTF_USESHOWWINDOW; + + sa.wShowWindow = SW_HIDE; + for (i = 0; i < ARRAY_SIZE(test_startupinfo_showwindow_tests); ++i) + { + sprintf( cmdline, "%s %s showwindow_proc %d", argv[0], argv[1], i ); + ret = CreateProcessA( NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &sa, &info ); + ok( ret, "got error %lu\n", GetLastError() ); + wait_child_process( info.hProcess ); + CloseHandle( info.hProcess ); + CloseHandle( info.hThread ); + } + + if (1) + { + /* Excluding these tests to reduce test run time. */ + sa.wShowWindow = SW_SHOWMAXIMIZED; + for (i = 0; i < ARRAY_SIZE(test_startupinfo_showwindow_tests); ++i) + { + sprintf( cmdline, "%s %s showwindow_proc %d", argv[0], argv[1], i ); + ret = CreateProcessA( NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &sa, &info ); + ok( ret, "got error %lu\n", GetLastError() ); + wait_child_process( info.hProcess ); + CloseHandle( info.hProcess ); + CloseHandle( info.hThread ); + } + } +} + START_TEST(win) { char **argv; @@ -13640,6 +13823,12 @@ START_TEST(win) return; }
+ if (argc == 4 && !strcmp(argv[2], "showwindow_proc")) + { + test_startupinfo_showwindow_proc( atoi( argv[3] )); + return; + } + if (!RegisterWindowClasses()) assert(0);
hwndMain = CreateWindowExA(/*WS_EX_TOOLWINDOW*/ 0, "MainWindowClass", "Main window", @@ -13771,6 +13960,7 @@ START_TEST(win) test_WM_NCCALCSIZE(); test_ReleaseCapture(); test_SetProcessLaunchForegroundPolicy(); + test_startupinfo_showwindow(argv);
/* add the tests above this line */ if (hhook) UnhookWindowsHookEx(hhook);
From: Paul Gofman pgofman@codeweavers.com
--- dlls/user32/tests/win.c | 4 ++-- dlls/win32u/window.c | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/dlls/user32/tests/win.c b/dlls/user32/tests/win.c index 163d1cfdb52..8d3eb4b9ac7 100644 --- a/dlls/user32/tests/win.c +++ b/dlls/user32/tests/win.c @@ -13707,14 +13707,14 @@ static void test_startupinfo_showwindow_proc( int test_id ) && (!test->show_affected || sa.wShowWindow != SW_HIDE); bval = !!IsWindowVisible( hwnd );
- todo_wine_if((test->show_affected && sa.wShowWindow == SW_HIDE) || test->cmd_show == SW_FORCEMINIMIZE) + todo_wine_if(test->cmd_show == SW_FORCEMINIMIZE) ok( bval == expected, "got %d, expected %d.\n", bval, expected );
/* After default args were used once SW_SHOWDEFAULT doesn't use startupinfo. */ ShowWindow( hwnd, SW_SHOWDEFAULT ); bval = !!IsWindowVisible( hwnd ); expected = test->counted_as_first || sa.wShowWindow != SW_HIDE; - todo_wine_if(!test->counted_as_first && sa.wShowWindow == SW_HIDE) ok( bval == expected, "got %d, expected %d.\n", bval, expected ); + ok( bval == expected, "got %d, expected %d.\n", bval, expected ); DestroyWindow( hwnd ); pump_messages();
diff --git a/dlls/win32u/window.c b/dlls/win32u/window.c index a82462e44e4..5cb7b9f4d2d 100644 --- a/dlls/win32u/window.c +++ b/dlls/win32u/window.c @@ -4515,6 +4515,7 @@ void update_window_state( HWND hwnd ) */ static BOOL show_window( HWND hwnd, INT cmd ) { + static volatile LONG first_window = 1; WND *win; HWND parent; DWORD style = get_window_long( hwnd, GWL_STYLE ), new_style; @@ -4527,6 +4528,19 @@ static BOOL show_window( HWND hwnd, INT cmd )
context = set_thread_dpi_awareness_context( get_window_dpi_awareness_context( hwnd ));
+ if ((!(style & (WS_POPUP | WS_CHILD)) + || ((style & (WS_POPUP | WS_CHILD | WS_CAPTION)) == (WS_POPUP | WS_CAPTION))) + && InterlockedExchange( &first_window, 0 )) + { + RTL_USER_PROCESS_PARAMETERS *params = NtCurrentTeb()->Peb->ProcessParameters; + + if (params->dwFlags & STARTF_USESHOWWINDOW && (cmd == SW_SHOW || cmd == SW_SHOWNORMAL || cmd == SW_SHOWDEFAULT)) + { + cmd = params->wShowWindow; + TRACE( "hwnd=%p, using cmd %d from startup info.\n", hwnd, cmd ); + } + } + switch(cmd) { case SW_HIDE:
I hope I deduced all the important rules how startup info affect ShowWindow behaviour. To summarize test results and implementation:
1. STARTF_USESHOWWINDOW in startup info affects the first ShowWindow (explicitly called or implicit from creating window with WS_VISIBLE) for eligible windows. Eligible windows are those which are not child windows. Popup windows (owned or not) are eligible if and only if they have WS_CAPTION (which is WS_BORDER | WS_DLGFRAME). Whenever eligible window is shown, the first ShowWindow use is counted (even if the actual ShowWindow command wasn't overridden due to rule 2). SetWindowPos(SWP_SHOWWINDOW) doesn't count as showing window, it neither consumes the 'firstness' of showing window nor the result is affected by startup info. Message windows are eligible.
2. For explicit ShowWindow call, eligible as per p. 1., the actual show command is only overridden if it is SW_SHOW, SW_NORMAL or SW_SHOWDEFAULT. The other show commands count as first ShowWindow use but are not altered.
3. STARTUPINFO docs suggest that Windows GUI application might have some different behaviour. I failed detect such (tried with both my separate ad-hoc test app linked as Windows GUI, as well as user32_test.exe, both 64 and 32 bit).
4. While SW_SHOWDEFAULT is supposed to use wShowWindow from startup info (as per both ShowWindow and STARTUPINFO structure MS docs), I failed to find any way to trigger that behaviour. For me once ShowWindow call is not the first (in the sense of p. 1) SW_SHOWDEFAULT is never overridden by startup info. I tried Windows GUI executable and even setting compatibility mode down to Win95 on Windows 11, the included tests still succeed.
The remaining todo is orthogonal to the issue and patch and is related to SW_FORCEMINIMIZE current handling.