160 lines
7.0 KiB
C++
160 lines
7.0 KiB
C++
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
#include "../foreground_focus.h"
|
|
#include "../review_x11.h"
|
|
#pragma push_macro("_WIN32")
|
|
#undef _WIN32
|
|
#include <xcb/xcb.h>
|
|
#pragma pop_macro("_WIN32")
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
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<WCHAR> 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<xcb_window_t>(reinterpret_cast<uintptr_t>(
|
|
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<xcb_window_t>(reinterpret_cast<uintptr_t>(
|
|
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;
|
|
}
|