From: Stefan Dösinger Subject: [PATCH 1/3] wined3d: Show the device window when changing fullscreen resolutions (v3). Message-Id: <20171115222118.27357-1-stefan@codeweavers.com> Date: Wed, 15 Nov 2017 23:21:16 +0100 World of Warplanes calls SetWindowLong(window, GWL_STYLE, WS_POPUP) before calling reset, effectively hiding the window. It does not call SetWindowPos(SWP_FRAMECHANGED), so the effect isn't visible and the game displays fine after the reset. However, after the next mouse click WINPOS_WindowFromPoint skips the window because it doesn't have WS_VISIBLE and returns the desktop window. This in turn triggers a focus loss and d3d9 minimizes the game. Signed-off-by: Stefan Dösinger --- Version 3: Use SW_SHOW instead of SW_SHOWNORMAL to avoid knocking ddraw test windows out of WS_MAXIMIZED state. I originally thought SW_SHOWNORMAL is needed to create a WINDOWPOS structure that matches that of Windows, but that was apparently wrong. Version 2: Don't check the MoveWindow() WINDOWPOS structure. It differs between Windows versions - Windows 10 17.03 changed the behavior. The message that causes the window to be re-shown is consistent among all versions. I am quite sure the new Windows behavior breaks a game or two, but I haven't tested my game collection yet... --- dlls/d3d9/tests/d3d9ex.c | 118 +++++++++++++++++++++++++++++++++++++++++++-- dlls/d3d9/tests/device.c | 121 +++++++++++++++++++++++++++++++++++++++++++++-- dlls/wined3d/swapchain.c | 1 + 3 files changed, 232 insertions(+), 8 deletions(-) diff --git a/dlls/d3d9/tests/d3d9ex.c b/dlls/d3d9/tests/d3d9ex.c index 648d17bac4..c965af7264 100644 --- a/dlls/d3d9/tests/d3d9ex.c +++ b/dlls/d3d9/tests/d3d9ex.c @@ -2468,6 +2468,7 @@ struct message enum message_window window; BOOL check_wparam; WPARAM expect_wparam; + WINDOWPOS *store_wp; }; static const struct message *expect_messages; @@ -2516,6 +2517,9 @@ static LRESULT CALLBACK test_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM "Got unexpected wparam %lx for message %x, expected %lx.\n", wparam, message, expect_messages->expect_wparam); + if (expect_messages->store_wp) + *expect_messages->store_wp = *(WINDOWPOS *)lparam; + ++expect_messages; } } @@ -2583,9 +2587,10 @@ static void test_wndproc(void) D3DDISPLAYMODE d3ddm; DWORD d3d_width = 0, d3d_height = 0, user32_width = 0, user32_height = 0; DEVMODEW devmode; - LONG change_ret; + LONG change_ret, device_style; BOOL ret; IDirect3D9Ex *d3d9ex; + WINDOWPOS windowpos; static const struct message create_messages[] = { @@ -2678,16 +2683,59 @@ static void test_wndproc(void) /* WM_SIZE(SIZE_MAXIMIZED) is unreliable on native. */ {0, 0, FALSE, 0}, }; - static const struct + struct message mode_change_messages[] = + { + {WM_WINDOWPOSCHANGING, DEVICE_WINDOW, FALSE, 0}, + {WM_WINDOWPOSCHANGED, DEVICE_WINDOW, FALSE, 0}, + {WM_SIZE, DEVICE_WINDOW, FALSE, 0}, + /* TODO: WM_DISPLAYCHANGE is sent to the focus window too, but the order is + * differs between Wine and Windows. */ + /* TODO 2: Windows sends a second WM_WINDOWPOSCHANGING(SWP_NOMOVE | SWP_NOSIZE + * | SWP_NOACTIVATE) in this situation, suggesting a difference in their ShowWindow + * implementation. This SetWindowPos call could in theory affect the Z order. Wine's + * ShowWindow does not send such a message because the window is already visible. */ + {0, 0, FALSE, 0}, + }; + struct message mode_change_messages_hidden[] = + { + {WM_WINDOWPOSCHANGING, DEVICE_WINDOW, FALSE, 0}, + {WM_WINDOWPOSCHANGED, DEVICE_WINDOW, FALSE, 0}, + {WM_SIZE, DEVICE_WINDOW, FALSE, 0}, + {WM_SHOWWINDOW, DEVICE_WINDOW, FALSE, 0}, + {WM_WINDOWPOSCHANGING, DEVICE_WINDOW, FALSE, 0, &windowpos}, + {WM_WINDOWPOSCHANGED, DEVICE_WINDOW, FALSE, 0}, + /* TODO: WM_DISPLAYCHANGE is sent to the focus window too, but the order is + * differs between Wine and Windows. */ + {0, 0, FALSE, 0}, + }; + static const struct message mode_change_messages_nowc[] = + { + {WM_DISPLAYCHANGE, FOCUS_WINDOW, FALSE, 0}, + {0, 0, FALSE, 0}, + }; + struct { DWORD create_flags; const struct message *focus_loss_messages; + const struct message *mode_change_messages, *mode_change_messages_hidden; BOOL iconic; } tests[] = { - {0, focus_loss_messages, TRUE}, - {CREATE_DEVICE_NOWINDOWCHANGES, focus_loss_messages_nowc, FALSE}, + { + 0, + focus_loss_messages, + mode_change_messages, + mode_change_messages_hidden, + TRUE + }, + { + CREATE_DEVICE_NOWINDOWCHANGES, + focus_loss_messages_nowc, + mode_change_messages_nowc, + mode_change_messages_nowc, + FALSE + }, }; hr = pDirect3DCreate9Ex(D3D_SDK_VERSION, &d3d9ex); @@ -3051,6 +3099,68 @@ static void test_wndproc(void) skip("Failed to create a D3D device, skipping tests.\n"); goto done; } + filter_messages = NULL; + flush_events(); + + device_desc.width = user32_width; + device_desc.height = user32_height; + + expect_messages = tests[i].mode_change_messages; + filter_messages = focus_window; + hr = reset_device(device, &device_desc); + ok(SUCCEEDED(hr), "Failed to reset device, hr %#x.\n", hr); + filter_messages = NULL; + + /* The WINDOWPOS structure passed to the first WM_WINDOWPOSCHANGING differs between windows versions. + * Prior to Win10 17.03 it is consistent with a MoveWindow(0, 0, width, height) call. Since Windows + * 10 17.03 it has x = 0, y = 0, width = 0, height = 0, flags = SWP_NOCLIENTMOVE | SWP_NOCLIENTSIZE + * | SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER (0x1837). Visually + * it is clear that the window has not been resized. In previous Windows version the window is resized. */ + + flush_events(); + ok(!expect_messages->message, "Expected message %#x for window %#x, but didn't receive it, i=%u.\n", + expect_messages->message, expect_messages->window, i); + + /* World of Warplanes hides the window by removing WS_VISIBLE and expects Reset() to show it again. */ + device_style = GetWindowLongA(device_window, GWL_STYLE); + SetWindowLongA(device_window, GWL_STYLE, device_style & ~WS_VISIBLE); + + flush_events(); + device_desc.width = d3d_width; + device_desc.height = d3d_height; + memset(&windowpos, 0, sizeof(windowpos)); + + expect_messages = tests[i].mode_change_messages_hidden; + filter_messages = focus_window; + hr = reset_device(device, &device_desc); + ok(SUCCEEDED(hr), "Failed to reset device, hr %#x.\n", hr); + filter_messages = NULL; + + flush_events(); + ok(!expect_messages->message, "Expected message %#x for window %#x, but didn't receive it, i=%u.\n", + expect_messages->message, expect_messages->window, i); + + if (!(tests[i].create_flags & CREATE_DEVICE_NOWINDOWCHANGES)) + { + ok(windowpos.hwnd == device_window && !windowpos.hwndInsertAfter + && !windowpos.x && !windowpos.y && !windowpos.cx && !windowpos.cy + && windowpos.flags == (SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE), + "Got unexpected WINDOWPOS hwnd=%p, insertAfter=%p, x=%d, y=%d, cx=%d, cy=%d, flags=%x\n", + windowpos.hwnd, windowpos.hwndInsertAfter, windowpos.x, windowpos.y, windowpos.cx, + windowpos.cy, windowpos.flags); + } + + device_style = GetWindowLongA(device_window, GWL_STYLE); + if (tests[i].create_flags & CREATE_DEVICE_NOWINDOWCHANGES) + { + todo_wine ok(!(device_style & WS_VISIBLE), "Expected the device window to be hidden, i=%u.\n", i); + ShowWindow(device_window, SW_MINIMIZE); + ShowWindow(device_window, SW_RESTORE); + } + else + { + ok(device_style & WS_VISIBLE, "Expected the device window to be visible, i=%u.\n", i); + } proc = SetWindowLongPtrA(focus_window, GWLP_WNDPROC, (LONG_PTR)DefWindowProcA); ok(proc != (LONG_PTR)test_proc, "Expected wndproc != %#lx.\n", diff --git a/dlls/d3d9/tests/device.c b/dlls/d3d9/tests/device.c index 82ced8372b..388cda1d7a 100644 --- a/dlls/d3d9/tests/device.c +++ b/dlls/d3d9/tests/device.c @@ -3401,6 +3401,7 @@ struct message enum message_window window; BOOL check_wparam; WPARAM expect_wparam; + WINDOWPOS *store_wp; }; static const struct message *expect_messages; @@ -3449,6 +3450,9 @@ static LRESULT CALLBACK test_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM "Got unexpected wparam %lx for message %x, expected %lx.\n", wparam, message, expect_messages->expect_wparam); + if (expect_messages->store_wp) + *expect_messages->store_wp = *(WINDOWPOS *)lparam; + ++expect_messages; } } @@ -3517,8 +3521,9 @@ static void test_wndproc(void) D3DDISPLAYMODE d3ddm; DWORD d3d_width = 0, d3d_height = 0, user32_width = 0, user32_height = 0; DEVMODEW devmode; - LONG change_ret; + LONG change_ret, device_style; BOOL ret; + WINDOWPOS windowpos; static const struct message create_messages[] = { @@ -3631,16 +3636,61 @@ static void test_wndproc(void) /* WM_SIZE(SIZE_MAXIMIZED) is unreliable on native. */ {0, 0, FALSE, 0}, }; - static const struct + struct message mode_change_messages[] = + { + {WM_WINDOWPOSCHANGING, DEVICE_WINDOW, FALSE, 0}, + {WM_WINDOWPOSCHANGED, DEVICE_WINDOW, FALSE, 0}, + {WM_SIZE, DEVICE_WINDOW, FALSE, 0}, + /* TODO: WM_DISPLAYCHANGE is sent to the focus window too, but the order is + * differs between Wine and Windows. */ + /* TODO 2: Windows sends a second WM_WINDOWPOSCHANGING(SWP_NOMOVE | SWP_NOSIZE + * | SWP_NOACTIVATE) in this situation, suggesting a difference in their ShowWindow + * implementation. This SetWindowPos call could in theory affect the Z order. Wine's + * ShowWindow does not send such a message because the window is already visible. */ + {0, 0, FALSE, 0}, + }; + struct message mode_change_messages_hidden[] = + { + {WM_WINDOWPOSCHANGING, DEVICE_WINDOW, FALSE, 0}, + {WM_WINDOWPOSCHANGED, DEVICE_WINDOW, FALSE, 0}, + {WM_SIZE, DEVICE_WINDOW, FALSE, 0}, + {WM_SHOWWINDOW, DEVICE_WINDOW, FALSE, 0}, + {WM_WINDOWPOSCHANGING, DEVICE_WINDOW, FALSE, 0, &windowpos}, + {WM_WINDOWPOSCHANGED, DEVICE_WINDOW, FALSE, 0}, + /* TODO: WM_DISPLAYCHANGE is sent to the focus window too, but the order is + * differs between Wine and Windows. */ + {0, 0, FALSE, 0}, + }; + static const struct message mode_change_messages_nowc[] = + { + {WM_DISPLAYCHANGE, FOCUS_WINDOW, FALSE, 0}, + {0, 0, FALSE, 0}, + }; + struct { DWORD create_flags; const struct message *focus_loss_messages, *reactivate_messages; + const struct message *mode_change_messages, *mode_change_messages_hidden; BOOL iconic; } tests[] = { - {0, focus_loss_messages, reactivate_messages, TRUE}, - {CREATE_DEVICE_NOWINDOWCHANGES, focus_loss_messages_nowc, reactivate_messages_nowc, FALSE}, + { + 0, + focus_loss_messages, + reactivate_messages, + mode_change_messages, + mode_change_messages_hidden, + TRUE + }, + { + CREATE_DEVICE_NOWINDOWCHANGES, + focus_loss_messages_nowc, + reactivate_messages_nowc, + mode_change_messages_nowc, + mode_change_messages_nowc, + FALSE + }, }; d3d9 = Direct3DCreate9(D3D_SDK_VERSION); @@ -4026,11 +4076,74 @@ static void test_wndproc(void) skip("Failed to create a D3D device, skipping tests.\n"); goto done; } + filter_messages = NULL; + flush_events(); + + device_desc.width = user32_width; + device_desc.height = user32_height; + + expect_messages = tests[i].mode_change_messages; + filter_messages = focus_window; + hr = reset_device(device, &device_desc); + ok(SUCCEEDED(hr), "Failed to reset device, hr %#x.\n", hr); + filter_messages = NULL; + + /* The WINDOWPOS structure passed to the first WM_WINDOWPOSCHANGING differs between windows versions. + * Prior to Win10 17.03 it is consistent with a MoveWindow(0, 0, width, height) call. Since Windows + * 10 17.03 it has x = 0, y = 0, width = 0, height = 0, flags = SWP_NOCLIENTMOVE | SWP_NOCLIENTSIZE + * | SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER (0x1837). Visually + * it is clear that the window has not been resized. In previous Windows version the window is resized. */ + + flush_events(); + ok(!expect_messages->message, "Expected message %#x for window %#x, but didn't receive it, i=%u.\n", + expect_messages->message, expect_messages->window, i); + + /* World of Warplanes hides the window by removing WS_VISIBLE and expects Reset() to show it again. */ + device_style = GetWindowLongA(device_window, GWL_STYLE); + SetWindowLongA(device_window, GWL_STYLE, device_style & ~WS_VISIBLE); + + flush_events(); + device_desc.width = d3d_width; + device_desc.height = d3d_height; + memset(&windowpos, 0, sizeof(windowpos)); + + expect_messages = tests[i].mode_change_messages_hidden; + filter_messages = focus_window; + hr = reset_device(device, &device_desc); + ok(SUCCEEDED(hr), "Failed to reset device, hr %#x.\n", hr); + filter_messages = NULL; + + flush_events(); + ok(!expect_messages->message, "Expected message %#x for window %#x, but didn't receive it, i=%u.\n", + expect_messages->message, expect_messages->window, i); + + if (!(tests[i].create_flags & CREATE_DEVICE_NOWINDOWCHANGES)) + { + ok(windowpos.hwnd == device_window && !windowpos.hwndInsertAfter + && !windowpos.x && !windowpos.y && !windowpos.cx && !windowpos.cy + && windowpos.flags == (SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE), + "Got unexpected WINDOWPOS hwnd=%p, insertAfter=%p, x=%d, y=%d, cx=%d, cy=%d, flags=%x\n", + windowpos.hwnd, windowpos.hwndInsertAfter, windowpos.x, windowpos.y, windowpos.cx, + windowpos.cy, windowpos.flags); + } + + device_style = GetWindowLongA(device_window, GWL_STYLE); + if (tests[i].create_flags & CREATE_DEVICE_NOWINDOWCHANGES) + { + todo_wine ok(!(device_style & WS_VISIBLE), "Expected the device window to be hidden, i=%u.\n", i); + ShowWindow(device_window, SW_MINIMIZE); + ShowWindow(device_window, SW_RESTORE); + } + else + { + ok(device_style & WS_VISIBLE, "Expected the device window to be visible, i=%u.\n", i); + } proc = SetWindowLongPtrA(focus_window, GWLP_WNDPROC, (LONG_PTR)DefWindowProcA); ok(proc != (LONG_PTR)test_proc, "Expected wndproc != %#lx, i=%u.\n", (LONG_PTR)test_proc, i); + filter_messages = focus_window; ref = IDirect3DDevice9_Release(device); ok(ref == 0, "The device was not properly freed: refcount %u, i=%u.\n", ref, i); diff --git a/dlls/wined3d/swapchain.c b/dlls/wined3d/swapchain.c index e23e99b67b..dd3655f61c 100644 --- a/dlls/wined3d/swapchain.c +++ b/dlls/wined3d/swapchain.c @@ -1429,6 +1429,7 @@ HRESULT CDECL wined3d_swapchain_set_fullscreen(struct wined3d_swapchain *swapcha device->filter_messages = TRUE; MoveWindow(swapchain->device_window, 0, 0, width, height, TRUE); + ShowWindow(swapchain->device_window, SW_SHOW); device->filter_messages = filter_messages; } -- 2.13.6