// 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() { 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 || focus->focus != xid) 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 window = 0; if (!active || active->type != XCB_ATOM_WINDOW || active->format != 32 || xcb_get_property_value_length(active.get()) != sizeof(window)) return false; std::memcpy(&window, xcb_get_property_value(active.get()), sizeof(window)); return window == xid; } bool wine_x11_restore_focus(HWND window) { if (!wine_x11_desktop_focused()) 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; // Native focus informs Wine's application thread of activation. A helper's // SetForegroundWindow can be denied by the Win32 foreground-lock policy. constexpr char protocolsName[] = "WM_PROTOCOLS"; constexpr char focusName[] = "WM_TAKE_FOCUS"; auto protocols = reply(xcb_intern_atom(connection.value, 1, sizeof(protocolsName) - 1, protocolsName).sequence, deadline); auto takeFocus = reply(xcb_intern_atom(connection.value, 1, sizeof(focusName) - 1, focusName).sequence, deadline); if (!protocols || !protocols->atom || !takeFocus || !takeFocus->atom) return false; if (GetForegroundWindow() || !IsWindowVisible(window) || !IsWindowEnabled(window) || IsIconic(window) || !wine_x11_desktop_focused()) return false; xcb_client_message_event_t event{}; event.response_type = XCB_CLIENT_MESSAGE; event.format = 32; event.window = xid; event.type = protocols->atom; event.data.data32[0] = takeFocus->atom; event.data.data32[1] = XCB_CURRENT_TIME; xcb_send_event(connection.value, 0, xid, XCB_EVENT_MASK_NO_EVENT, reinterpret_cast(&event)); 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; }