// SPDX-License-Identifier: LGPL-2.1-or-later #include "../foreground_focus.h" #include "../review_x11.h" #pragma push_macro("_WIN32") #undef _WIN32 #include #pragma pop_macro("_WIN32") #include #include #include #include namespace { void pump() { const auto deadline = GetTickCount64() + 100; do { MSG message{}; while (PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE)) { TranslateMessage(&message); DispatchMessageW(&message); } Sleep(5); } while (GetTickCount64() < deadline); } bool check(bool passed, const char *message) { if (!passed) std::fprintf(stderr, "FAIL: %s\n", message); return passed; } } int main(int argc, char **argv) { if (argc != 2) return 2; const std::string command = "\"Z:" + std::string(argv[1]) + "\""; const int units = MultiByteToWideChar(CP_UTF8, 0, command.c_str(), -1, nullptr, 0); std::vector wideCommand(units); MultiByteToWideChar(CP_UTF8, 0, command.c_str(), -1, wideCommand.data(), units); STARTUPINFOW startup{}; startup.cb = sizeof(startup); PROCESS_INFORMATION process{}; if (!CreateProcessW(nullptr, wideCommand.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startup, &process)) return 2; HWND launcher = nullptr; const auto deadline = GetTickCount64() + 5000; while (!(launcher = FindWindowW(L"WineAccessForegroundFixture", nullptr)) && GetTickCount64() < deadline) Sleep(10); if (!launcher) return 2; HWND button = GetDlgItem(launcher, 1); auto *connection = xcb_connect(nullptr, nullptr); if (xcb_connection_has_error(connection)) return 2; auto *screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data; const auto desktop = static_cast(reinterpret_cast( GetPropW(GetDesktopWindow(), L"__wine_x11_whole_window"))); if (!desktop) return 2; constexpr char name[] = "_NET_ACTIVE_WINDOW"; auto *atomReply = xcb_intern_atom_reply(connection, xcb_intern_atom(connection, 0, sizeof(name) - 1, name), nullptr); if (!atomReply) return 2; const auto activeAtom = atomReply->atom; std::free(atomReply); const auto linuxWindow = xcb_generate_id(connection); xcb_create_window(connection, XCB_COPY_FROM_PARENT, linuxWindow, screen->root, 0, 0, 20, 20, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, 0, nullptr); xcb_map_window(connection, linuxWindow); auto focus = [&](xcb_window_t target, xcb_window_t active) { xcb_change_property(connection, XCB_PROP_MODE_REPLACE, screen->root, activeAtom, XCB_ATOM_WINDOW, 32, 1, &active); xcb_set_input_focus(connection, XCB_INPUT_FOCUS_POINTER_ROOT, target, XCB_CURRENT_TIME); xcb_flush(connection); pump(); }; WNDCLASSW cls{}; cls.hInstance = GetModuleHandleW(nullptr); cls.lpfnWndProc = DefWindowProcW; cls.lpszClassName = L"WineAccessOtherFixture"; RegisterClassW(&cls); HWND other = CreateWindowW(cls.lpszClassName, L"Second fixture", WS_OVERLAPPEDWINDOW, 380, 50, 300, 200, nullptr, nullptr, cls.hInstance, nullptr); pump(); bool ok = true; // Returning a launcher without activation leaves Wine's desktop with no // remembered foreground, as when an external game window has closed. ShowWindow(launcher, SW_HIDE); pump(); ShowWindow(launcher, SW_SHOWNOACTIVATE); pump(); focus(linuxWindow, linuxWindow); ok &= check(!GetForegroundWindow(), "Linux focus clears Wine foreground"); ok &= check(!restore_virtual_desktop_focus() && !GetForegroundWindow(), "Linux focus is never taken"); focus(desktop, linuxWindow); ok &= check(!restore_virtual_desktop_focus(), "window manager must also select desktop"); focus(linuxWindow, desktop); ok &= check(!restore_virtual_desktop_focus(), "X11 focus must also select desktop"); focus(desktop, desktop); ok &= check(wine_x11_desktop_focused() && !GetForegroundWindow(), "reproduce orphaned virtual desktop focus"); EnableWindow(launcher, FALSE); ok &= check(!restore_virtual_desktop_focus(), "disabled application is not activated"); EnableWindow(launcher, TRUE); ShowWindow(other, SW_SHOWNOACTIVATE); pump(); focus(linuxWindow, linuxWindow); focus(desktop, desktop); ok &= check(!restore_virtual_desktop_focus() && !GetForegroundWindow(), "ambiguous applications are not chosen"); ShowWindow(other, SW_HIDE); ok &= check(restore_virtual_desktop_focus(), "single application recovers foreground"); pump(); GUITHREADINFO gui{}; gui.cbSize = sizeof(gui); GetGUIThreadInfo(GetWindowThreadProcessId(launcher, nullptr), &gui); ok &= check(GetForegroundWindow() == launcher && gui.hwndFocus == button, "launcher restores its control focus"); ok &= check(!restore_virtual_desktop_focus() && GetForegroundWindow() == launcher, "existing Wine focus stays unchanged"); ShowWindow(launcher, SW_HIDE); pump(); ShowWindow(launcher, SW_SHOWNOACTIVATE); pump(); focus(linuxWindow, linuxWindow); focus(desktop, desktop); ok &= check(restore_virtual_desktop_focus(), "later workspace return recovers again"); pump(); ShowWindow(launcher, SW_HIDE); pump(); ShowWindow(launcher, SW_SHOWNOACTIVATE); pump(); focus(linuxWindow, linuxWindow); const auto launcherXid = static_cast(reinterpret_cast( GetPropW(launcher, L"__wine_x11_whole_window"))); focus(launcherXid, linuxWindow); ok &= check(!restore_virtual_desktop_focus(), "focused child requires window manager desktop ownership"); ShowWindow(other, SW_SHOWNOACTIVATE); pump(); focus(launcherXid, desktop); ok &= check(!restore_virtual_desktop_focus(), "focused child does not bypass ambiguous applications"); ShowWindow(other, SW_HIDE); const auto siblingXid = xcb_generate_id(connection); xcb_create_window(connection, XCB_COPY_FROM_PARENT, siblingXid, desktop, 0, 0, 20, 20, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, 0, nullptr); xcb_map_window(connection, siblingXid); focus(siblingXid, desktop); ok &= check(!restore_virtual_desktop_focus(), "unrelated desktop child is never redirected to launcher"); xcb_destroy_window(connection, siblingXid); focus(launcherXid, desktop); EnableWindow(launcher, FALSE); ok &= check(!restore_virtual_desktop_focus(), "focused child does not bypass disabled application guard"); EnableWindow(launcher, TRUE); ok &= check(!GetForegroundWindow(), "reproduce focused X11 child without Win32 foreground"); ok &= check(restore_virtual_desktop_focus(), "focused launcher child recovers foreground"); pump(); GetGUIThreadInfo(GetWindowThreadProcessId(launcher, nullptr), &gui); ok &= check(GetForegroundWindow() == launcher && gui.hwndFocus == button, "focused launcher child restores its control focus"); DestroyWindow(other); PostMessageW(launcher, WM_CLOSE, 0, 0); WaitForSingleObject(process.hProcess, 5000); CloseHandle(process.hProcess); CloseHandle(process.hThread); xcb_destroy_window(connection, linuxWindow); xcb_disconnect(connection); if (ok) std::puts("PASS: virtual desktop foreground recovery and focus guards"); return ok ? 0 : 1; }