Files
wine-access/review_x11.cpp

139 lines
6.8 KiB
C++

// 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 <xcb/xcb.h>
#include <xcb/xcbext.h>
#pragma pop_macro("_WIN32")
#include <poll.h>
#include <cstdlib>
#include <memory>
#include <cstring>
namespace {
struct Connection {
xcb_connection_t *value=xcb_connect(nullptr,nullptr);
~Connection() { if (value) xcb_disconnect(value); }
};
thread_local Connection connection;
template<class T> std::unique_ptr<T,decltype(&std::free)> reply(unsigned sequence, uint64_t deadline) {
void *value=nullptr;
xcb_generic_error_t *error=nullptr;
xcb_flush(connection.value);
while (GetTickCount64()<deadline && !xcb_connection_has_error(connection.value)) {
if (xcb_poll_for_reply(connection.value,sequence,&value,&error)) {
std::free(error);
return {static_cast<T*>(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<int>(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<uintptr_t>(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_reply_t>(xcb_get_geometry(connection.value,xid).sequence,deadline);
if (!size || !size->width || !size->height) return false;
auto position=reply<xcb_translate_coordinates_reply_t>(
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<uintptr_t>(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_reply_t>(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<uintptr_t>(GetPropW(window, L"__wine_x11_whole_window"));
if (!child || child > UINT32_MAX) return false;
auto tree = reply<xcb_query_tree_reply_t>(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_reply_t>(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_reply_t>(xcb_get_geometry(connection.value, xid).sequence, deadline);
if (!size) return false;
constexpr char name[] = "_NET_ACTIVE_WINDOW";
auto atom = reply<xcb_intern_atom_reply_t>(xcb_intern_atom(connection.value, 1, sizeof(name) - 1, name).sequence, deadline);
if (!atom || !atom->atom) return false;
auto active = reply<xcb_get_property_reply_t>(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<uintptr_t>(GetPropW(window, L"__wine_x11_whole_window"));
const auto desktop = reinterpret_cast<uintptr_t>(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_reply_t>(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_reply_t>(xcb_intern_atom(connection.value, 1,
sizeof(protocolsName) - 1, protocolsName).sequence, deadline);
auto takeFocus = reply<xcb_intern_atom_reply_t>(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(window))
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<const char *>(&event));
xcb_set_input_focus(connection.value, XCB_INPUT_FOCUS_PARENT, xid, XCB_CURRENT_TIME);
auto focus = reply<xcb_get_input_focus_reply_t>(xcb_get_input_focus(connection.value).sequence, deadline);
return focus && focus->focus == xid;
}