// SPDX-License-Identifier: LGPL-2.1-or-later #include "review_x11.h" // This is a Unix library in a Winelib translation unit, not Windows XCB. #pragma push_macro("_WIN32") #undef _WIN32 #include #include #pragma pop_macro("_WIN32") #include #include #include #include namespace { struct Connection { xcb_connection_t *value=xcb_connect(nullptr,nullptr); ~Connection() { if (value) xcb_disconnect(value); } }; thread_local Connection connection; template std::unique_ptr reply(unsigned sequence, uint64_t deadline) { void *value=nullptr; xcb_generic_error_t *error=nullptr; xcb_flush(connection.value); while (GetTickCount64()(value),&std::free}; } auto now=GetTickCount64(); if (now>=deadline) break; pollfd fd{xcb_get_file_descriptor(connection.value),POLLIN,0}; if (poll(&fd,1,static_cast(deadline-now))<=0) break; } xcb_discard_reply(connection.value,sequence); return {nullptr,&std::free}; } bool geometry(HWND window, RECT &bounds, uint64_t deadline) { auto xid=reinterpret_cast(GetPropW(window,L"__wine_x11_whole_window")); if (!xid || xid>UINT32_MAX || !connection.value || xcb_connection_has_error(connection.value)) return false; auto size=reply(xcb_get_geometry(connection.value,xid).sequence,deadline); if (!size || !size->width || !size->height) return false; auto position=reply( xcb_translate_coordinates(connection.value,xid,size->root,0,0).sequence,deadline); if (!position || !position->same_screen) return false; bounds={position->dst_x,position->dst_y,position->dst_x+size->width,position->dst_y+size->height}; return true; } } bool review_x11_geometry(HWND window, RECT &bounds, POINT &origin) { origin={0,0}; // Metadata reads cannot wait indefinitely behind an unresponsive X server. const uint64_t deadline=GetTickCount64()+20; if (!geometry(window,bounds,deadline)) return false; USEROBJECTFLAGS flags{}; if (GetUserObjectInformationW(GetThreadDesktop(GetCurrentThreadId()),UOI_FLAGS,&flags,sizeof(flags),nullptr) && (flags.dwFlags&0x80000000)) { // Wine's DF_WINE_VIRTUAL_DESKTOP. RECT desktop{}; if (!geometry(GetDesktopWindow(),desktop,deadline)) return false; origin={desktop.left,desktop.top}; } return true; } bool wine_x11_desktop_focused(HWND window) { USEROBJECTFLAGS flags{}; if (!GetUserObjectInformationW(GetThreadDesktop(GetCurrentThreadId()), UOI_FLAGS, &flags, sizeof(flags), nullptr) || !(flags.dwFlags & 0x80000000)) return false; const auto xid = reinterpret_cast(GetPropW(GetDesktopWindow(), L"__wine_x11_whole_window")); if (!xid || xid > UINT32_MAX || !connection.value || xcb_connection_has_error(connection.value)) return false; const uint64_t deadline = GetTickCount64() + 20; auto focus = reply(xcb_get_input_focus(connection.value).sequence, deadline); if (!focus) return false; if (focus->focus != xid) { if (!window) return false; const auto child = reinterpret_cast(GetPropW(window, L"__wine_x11_whole_window")); if (!child || child > UINT32_MAX) return false; auto tree = reply(xcb_query_tree(connection.value, child).sequence, deadline); if (!tree || tree->parent != xid) return false; // A focused native client can outlive Wine's remembered Win32 foreground. // Accept only this application's hierarchy, never a sibling Wine child. auto focused = focus->focus; for (unsigned depth = 0; focused != child && depth < 64; ++depth) { if (!focused || focused == XCB_INPUT_FOCUS_POINTER_ROOT || focused == xid) return false; tree = reply(xcb_query_tree(connection.value, focused).sequence, deadline); if (!tree || tree->parent == focused) return false; focused = tree->parent; } if (focused != child) return false; } auto size = reply(xcb_get_geometry(connection.value, xid).sequence, deadline); if (!size) return false; constexpr char name[] = "_NET_ACTIVE_WINDOW"; auto atom = reply(xcb_intern_atom(connection.value, 1, sizeof(name) - 1, name).sequence, deadline); if (!atom || !atom->atom) return false; auto active = reply(xcb_get_property(connection.value, 0, size->root, atom->atom, XCB_ATOM_WINDOW, 0, 1).sequence, deadline); uint32_t activeWindow = 0; if (!active || active->type != XCB_ATOM_WINDOW || active->format != 32 || xcb_get_property_value_length(active.get()) != sizeof(activeWindow)) return false; std::memcpy(&activeWindow, xcb_get_property_value(active.get()), sizeof(activeWindow)); return activeWindow == xid; } bool wine_x11_restore_focus(HWND window) { if (!wine_x11_desktop_focused(window)) return false; const auto xid = reinterpret_cast(GetPropW(window, L"__wine_x11_whole_window")); const auto desktop = reinterpret_cast(GetPropW(GetDesktopWindow(), L"__wine_x11_whole_window")); if (!xid || xid > UINT32_MAX) return false; const uint64_t deadline = GetTickCount64() + 20; auto tree = reply(xcb_query_tree(connection.value, xid).sequence, deadline); if (!tree || tree->parent != desktop) return false; if (GetForegroundWindow() || !IsWindowVisible(window) || !IsWindowEnabled(window) || IsIconic(window) || !wine_x11_desktop_focused(window)) return false; // WM_TAKE_FOCUS can be ignored while Wine awaits WM_STATE, which can remain // unset for virtual-desktop children. Queue activation on the owner's thread: // a helper-thread SetForegroundWindow can be denied by Wine's input policy. // Async dispatch avoids waiting on an unresponsive application's window proc. if (!SetWindowPos(window, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_ASYNCWINDOWPOS) || !wine_x11_desktop_focused(window)) return false; xcb_set_input_focus(connection.value, XCB_INPUT_FOCUS_PARENT, xid, XCB_CURRENT_TIME); auto focus = reply(xcb_get_input_focus(connection.value).sequence, deadline); return focus && focus->focus == xid; }