Tidy Cauldron (2708320), a Unity game, uses EnumWindows() to find the first window in the current thread and maximizes the window when changing to windowed mode. However, before this patch, the IME UI window and the DXGI fallback device window are on top of the game window at creation and thus they could get maximized instead. This causes the game window to lose focus and freeze.
From: Zhiyi Zhang zzhang@codeweavers.com
--- dlls/imm32/tests/imm32.c | 54 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+)
diff --git a/dlls/imm32/tests/imm32.c b/dlls/imm32/tests/imm32.c index db73463ef49..9e7497f2d97 100644 --- a/dlls/imm32/tests/imm32.c +++ b/dlls/imm32/tests/imm32.c @@ -8037,8 +8037,61 @@ static void test_nihongo_no(void) ime_call_count = 0; }
+static BOOL CALLBACK enum_first_current_thread_window_proc(HWND hwnd, LPARAM lp) +{ + if (GetWindowThreadProcessId(hwnd, NULL) == GetCurrentThreadId()) + { + *(HWND *)lp = hwnd; + return FALSE; + } + return TRUE; +} + +static void test_ime_ui_window_child(void) +{ + char window_name[80], class_name[80]; + HWND hwnd, result_hwnd; + + /* Unity expects the first window in the current thread to be its game window, not Wine IME */ + hwnd = CreateWindowA("static", "", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 100, 100, NULL, NULL, NULL, NULL); + EnumWindows(enum_first_current_thread_window_proc, (LPARAM)&result_hwnd); + GetClassNameA(result_hwnd, class_name, ARRAY_SIZE(class_name)); + GetWindowTextA(result_hwnd, window_name, ARRAY_SIZE(window_name)); + todo_wine + ok(result_hwnd == hwnd, "Got unexpected window %p %s %s.\n", result_hwnd, window_name, class_name); + DestroyWindow(hwnd); +} + +static void test_ime_ui_window(const char *argv0) +{ + PROCESS_INFORMATION info; + STARTUPINFOA startup; + char cmd[MAX_PATH]; + + /* Run in a new process to avoid interference from windows in the current process */ + sprintf(cmd, "%s imm32 test_ime_ui_window_child", argv0); + memset(&startup, 0, sizeof(startup)); + startup.cb = sizeof(startup); + ok(CreateProcessA(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &startup, &info), "CreateProcess failed.\n"); + + wait_child_process(info.hProcess); + CloseHandle(info.hProcess); + CloseHandle(info.hThread); +} + START_TEST(imm32) { + char **argv; + int argc; + + argc = winetest_get_mainargs( &argv ); + + if (argc == 3 && !strcmp(argv[2], "test_ime_ui_window_child")) + { + test_ime_ui_window_child(); + return; + } + default_hkl = GetKeyboardLayout( 0 );
test_class.hInstance = GetModuleHandleW( NULL ); @@ -8050,6 +8103,7 @@ START_TEST(imm32) return; }
+ test_ime_ui_window(argv[0]); test_com_initialization();
test_ImmEnumInputContext();
From: Zhiyi Zhang zzhang@codeweavers.com
Tidy Cauldron (2708320), a Unity game, uses EnumWindows() to find the first window in the current thread and maximizes the window when changing to windowed mode. However, before this patch, the IME UI window and the DXGI fallback device window are on top of the game window at creation and thus they could get maximized instead. This causes the game window to lose focus and freeze. --- dlls/imm32/imm.c | 1 + dlls/imm32/tests/imm32.c | 1 - 2 files changed, 1 insertion(+), 1 deletion(-)
diff --git a/dlls/imm32/imm.c b/dlls/imm32/imm.c index dbf8db63e11..5639c2ae11c 100644 --- a/dlls/imm32/imm.c +++ b/dlls/imm32/imm.c @@ -948,6 +948,7 @@ static HWND get_ime_ui_window(void) { imc->ui_hwnd = CreateWindowExW( WS_EX_TOOLWINDOW, ime->ui_class, NULL, WS_POPUP, 0, 0, 1, 1, ImmGetDefaultIMEWnd( 0 ), 0, ime->module, 0 ); + SetWindowPos( imc->ui_hwnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE ); SetWindowLongPtrW( imc->ui_hwnd, IMMGWL_IMC, (LONG_PTR)NtUserGetWindowInputContext( GetFocus() ) ); } return imc->ui_hwnd; diff --git a/dlls/imm32/tests/imm32.c b/dlls/imm32/tests/imm32.c index 9e7497f2d97..3ee2afa53cb 100644 --- a/dlls/imm32/tests/imm32.c +++ b/dlls/imm32/tests/imm32.c @@ -8057,7 +8057,6 @@ static void test_ime_ui_window_child(void) EnumWindows(enum_first_current_thread_window_proc, (LPARAM)&result_hwnd); GetClassNameA(result_hwnd, class_name, ARRAY_SIZE(class_name)); GetWindowTextA(result_hwnd, window_name, ARRAY_SIZE(window_name)); - todo_wine ok(result_hwnd == hwnd, "Got unexpected window %p %s %s.\n", result_hwnd, window_name, class_name); DestroyWindow(hwnd); }
From: Zhiyi Zhang zzhang@codeweavers.com
--- dlls/d3d11/tests/d3d11.c | 89 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-)
diff --git a/dlls/d3d11/tests/d3d11.c b/dlls/d3d11/tests/d3d11.c index 3e9ccc42e33..d0031a81a7c 100644 --- a/dlls/d3d11/tests/d3d11.c +++ b/dlls/d3d11/tests/d3d11.c @@ -2046,6 +2046,71 @@ static void draw_color_quad_(unsigned int line, struct d3d11_test_context *conte draw_quad_vs_(line, context, vs_code, vs_code_size); }
+static BOOL CALLBACK enum_first_current_thread_window_proc(HWND hwnd, LPARAM lp) +{ + if (GetWindowThreadProcessId(hwnd, NULL) == GetCurrentThreadId()) + { + *(HWND *)lp = hwnd; + return FALSE; + } + return TRUE; +} + +static void test_create_device_child(void) +{ + char window_name[80], class_name[80]; + DXGI_SWAP_CHAIN_DESC swapchain_desc; + HWND hwnd, result_hwnd; + ID3D11Device *device; + HRESULT hr; + + /* Unity expects the first window in the current thread to be its game window, not DXGI device window */ + hwnd = CreateWindowA("static", "", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 100, 100, NULL, NULL, NULL, NULL); + + /* Create a device without a device window in windowed mode */ + swapchain_desc.BufferDesc.Width = 800; + swapchain_desc.BufferDesc.Height = 600; + swapchain_desc.BufferDesc.RefreshRate.Numerator = 60; + swapchain_desc.BufferDesc.RefreshRate.Denominator = 60; + swapchain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + swapchain_desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; + swapchain_desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; + swapchain_desc.SampleDesc.Count = 1; + swapchain_desc.SampleDesc.Quality = 0; + swapchain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swapchain_desc.BufferCount = 1; + swapchain_desc.OutputWindow = NULL; + swapchain_desc.Windowed = TRUE; + swapchain_desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + swapchain_desc.Flags = 0; + hr = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, NULL, 0, D3D11_SDK_VERSION, + &swapchain_desc, NULL, &device, NULL, NULL); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + EnumWindows(enum_first_current_thread_window_proc, (LPARAM)&result_hwnd); + GetClassNameA(result_hwnd, class_name, ARRAY_SIZE(class_name)); + GetWindowTextA(result_hwnd, window_name, ARRAY_SIZE(window_name)); + todo_wine + ok(result_hwnd == hwnd, "Got unexpected window %p %s %s.\n", result_hwnd, window_name, class_name); + + ID3D11Device_Release(device); + + /* Create a device without a device window in fullscreen mode */ + swapchain_desc.Windowed = FALSE; + hr = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, NULL, 0, D3D11_SDK_VERSION, + &swapchain_desc, NULL, &device, NULL, NULL); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + EnumWindows(enum_first_current_thread_window_proc, (LPARAM)&result_hwnd); + GetClassNameA(result_hwnd, class_name, ARRAY_SIZE(class_name)); + GetWindowTextA(result_hwnd, window_name, ARRAY_SIZE(window_name)); + todo_wine + ok(result_hwnd == hwnd, "Got unexpected window %p %s %s.\n", result_hwnd, window_name, class_name); + + ID3D11Device_Release(device); + DestroyWindow(hwnd); +} + static void test_create_device(void) { static const D3D_FEATURE_LEVEL default_feature_levels[] = @@ -2060,8 +2125,11 @@ static void test_create_device(void) D3D_FEATURE_LEVEL feature_level, supported_feature_level; DXGI_SWAP_CHAIN_DESC swapchain_desc, obtained_desc; ID3D11DeviceContext *immediate_context; + char **argv, cmd[MAX_PATH]; IDXGISwapChain *swapchain; + PROCESS_INFORMATION info; ID3D11Device *device; + STARTUPINFOA startup; ULONG refcount; HWND window; HRESULT hr; @@ -2268,6 +2336,19 @@ static void test_create_device(void) ok(!immediate_context, "Got unexpected immediate context pointer %p.\n", immediate_context);
DestroyWindow(window); + + /* Test that creating a swapchain without a device window shouldn't create a fallback device + * window that's on top of normal windows at creation. Run the tests in a new process to avoid + * interference from windows in the current process */ + winetest_get_mainargs(&argv); + sprintf(cmd, "%s d3d11 test_create_device_child", argv[0]); + memset(&startup, 0, sizeof(startup)); + startup.cb = sizeof(startup); + ok(CreateProcessA(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &startup, &info), "CreateProcess failed.\n"); + + wait_child_process(info.hProcess); + CloseHandle(info.hProcess); + CloseHandle(info.hThread); }
static void test_device_interfaces(const D3D_FEATURE_LEVEL feature_level) @@ -36580,6 +36661,13 @@ START_TEST(d3d11) HMODULE wined3d; char **argv;
+ argc = winetest_get_mainargs(&argv); + if (argc == 3 && !strcmp(argv[2], "test_create_device_child")) + { + test_create_device_child(); + return; + } + if ((wined3d = GetModuleHandleA("wined3d.dll"))) { enum wined3d_renderer (CDECL *p_wined3d_get_renderer)(void); @@ -36596,7 +36684,6 @@ START_TEST(d3d11) if (sizeof(void *) == 4 && !strcmp(winetest_platform, "wine")) use_mt = FALSE;
- argc = winetest_get_mainargs(&argv); for (i = 2; i < argc; ++i) { if (!strcmp(argv[i], "--validate"))
From: Zhiyi Zhang zzhang@codeweavers.com
Tidy Cauldron (2708320), a Unity game, uses EnumWindows() to find the first window in the current thread and maximizes the window when changing to windowed mode. However, before this patch, the IME UI window and the DXGI fallback device window are on top of the game window at creation and thus they could get maximized instead. This causes the game window to lose focus and freeze. --- dlls/d3d11/tests/d3d11.c | 2 -- dlls/dxgi/factory.c | 1 + 2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/dlls/d3d11/tests/d3d11.c b/dlls/d3d11/tests/d3d11.c index d0031a81a7c..0a1dfa06160 100644 --- a/dlls/d3d11/tests/d3d11.c +++ b/dlls/d3d11/tests/d3d11.c @@ -2090,7 +2090,6 @@ static void test_create_device_child(void) EnumWindows(enum_first_current_thread_window_proc, (LPARAM)&result_hwnd); GetClassNameA(result_hwnd, class_name, ARRAY_SIZE(class_name)); GetWindowTextA(result_hwnd, window_name, ARRAY_SIZE(window_name)); - todo_wine ok(result_hwnd == hwnd, "Got unexpected window %p %s %s.\n", result_hwnd, window_name, class_name);
ID3D11Device_Release(device); @@ -2104,7 +2103,6 @@ static void test_create_device_child(void) EnumWindows(enum_first_current_thread_window_proc, (LPARAM)&result_hwnd); GetClassNameA(result_hwnd, class_name, ARRAY_SIZE(class_name)); GetWindowTextA(result_hwnd, window_name, ARRAY_SIZE(window_name)); - todo_wine ok(result_hwnd == hwnd, "Got unexpected window %p %s %s.\n", result_hwnd, window_name, class_name);
ID3D11Device_Release(device); diff --git a/dlls/dxgi/factory.c b/dlls/dxgi/factory.c index 32d54f033eb..ebdb42a3806 100644 --- a/dlls/dxgi/factory.c +++ b/dlls/dxgi/factory.c @@ -617,6 +617,7 @@ HWND dxgi_factory_get_device_window(struct dxgi_factory *factory) ERR("Failed to create a window.\n"); return NULL; } + SetWindowPos(factory->device_window, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); TRACE("Created device window %p for factory %p.\n", factory->device_window, factory); }
Rémi Bernon (@rbernon) commented about dlls/imm32/tests/imm32.c:
- return TRUE;
+}
+static void test_ime_ui_window_child(void) +{
- char window_name[80], class_name[80];
- HWND hwnd, result_hwnd;
- /* Unity expects the first window in the current thread to be its game window, not Wine IME */
- hwnd = CreateWindowA("static", "", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 100, 100, NULL, NULL, NULL, NULL);
- EnumWindows(enum_first_current_thread_window_proc, (LPARAM)&result_hwnd);
- GetClassNameA(result_hwnd, class_name, ARRAY_SIZE(class_name));
- GetWindowTextA(result_hwnd, window_name, ARRAY_SIZE(window_name));
- todo_wine
- ok(result_hwnd == hwnd, "Got unexpected window %p %s %s.\n", result_hwnd, window_name, class_name);
- DestroyWindow(hwnd);
Please lets keep the code style consistent with the rest of the (recent) tests. I've fixed it with a couple of other tweaks in https://gitlab.winehq.org/rbernon/wine/-/commit/ba2e61592a72a37d55c7344eb9cd...