diff --git a/ThunkLibs/include/common/X11Manager.h b/ThunkLibs/include/common/X11Manager.h new file mode 100644 index 000000000..01ad7093a --- /dev/null +++ b/ThunkLibs/include/common/X11Manager.h @@ -0,0 +1,112 @@ +#include "Host.h" + +#include +#include +#include +#include +#include + +#include +#include + +/** + * Guest X11 displays and xcb connections can't be used by the host, so + * instead an intermediary object is created and mapped to the original + * guest display/connection. + */ +struct X11Manager { + std::mutex mutex; + + // Maps guest connection to intermediary host connection + std::unordered_map connections; + + xcb_connection_t* GuestToHostConnection(xcb_connection_t* GuestConnection) { + std::unique_lock lock(mutex); + auto [it, inserted] = connections.emplace(GuestConnection, nullptr); + if (inserted) { + // NOTE: There's no easy way to query the display name from the guest, so just connect to the default display. + static void* libxcb = dlopen("libxcb.so.1", RTLD_LAZY); + static auto ptr_xcb_connect = (decltype(&xcb_connect))dlsym(libxcb, "xcb_connect"); + static auto ptr_xcb_connection_has_error = (decltype(&xcb_connection_has_error))dlsym(libxcb, "xcb_connection_has_error"); + it->second = ptr_xcb_connect(nullptr, nullptr); + if (ptr_xcb_connection_has_error(it->second)) { + fprintf(stderr, "ERROR: Could not open xcb connection\n"); + std::abort(); + } + } + return it->second; + } + + // Maps guest display to intermediary host display + std::unordered_map<_XDisplay*, _XDisplay*> displays; + + _XDisplay* GuestToHostDisplay(_XDisplay* GuestDisplay) { + // Flush event queue to make effects of the guest-side connection visible + GuestXSync(GuestDisplay, 0); + + std::unique_lock lock(mutex); + auto [it, inserted] = displays.emplace(GuestDisplay, nullptr); + if (inserted) { + auto host_display = HostXOpenDisplay(DisplayString(GuestDisplay)); + fprintf(stderr, "Opening host-side X11 display: %p -> %p\n", GuestDisplay, host_display); + if (!host_display) { + fprintf(stderr, "ERROR: Could not open X display\n"); + std::abort(); + } else { + it->second = host_display; + } + } + return it->second; + } + + guest_layout<_XDisplay*> HostToGuestDisplay(const _XDisplay* from) { + if (from == nullptr) { + return {.data = 0}; + } + + std::unique_lock lock(mutex); + for (auto& [guest, host] : displays) { + if (host == from) { + guest_layout<_XDisplay*> ret; + ret.data = reinterpret_cast(guest); + return ret; + } + } + + fprintf(stderr, "ERROR: Could not map host display %p back to guest\n", from); + std::abort(); + } + + static void* GetLibX11() { + static void* libx11 = dlopen("libX11.so.6", RTLD_LAZY); + return libx11; + } + + static int HostXFree(void* Ptr) { + static auto func = reinterpret_cast(dlsym(GetLibX11(), "XFree")); + return func(Ptr); + } + + static int HostXFlush(Display* Dis) { + static auto func = reinterpret_cast(dlsym(GetLibX11(), "XFlush")); + return func(Dis); + } + + static Display* HostXOpenDisplay(const char* Name) { + static auto func = reinterpret_cast(dlsym(GetLibX11(), "XOpenDisplay")); + return func(Name); + } + + static XVisualInfo* HostXGetVisualInfo(Display* a, long b, XVisualInfo* c, int* d) { + static auto func = reinterpret_cast(dlsym(GetLibX11(), "XGetVisualInfo")); + return func(a, b, c, d); + } + + // NOTE: Struct pointers are replaced by void* to avoid involving data layout conversion here. + int (*GuestXSync)(void*, int) = nullptr; + void* (*GuestXGetVisualInfo)(void*, long, void*, int*) = nullptr; + + // XDisplayString internally just reads data from _XDisplay's internal struct definition. + // This breaks when data layout is different, so allow reading from a guest context instead. + char* (*GuestXDisplayString)(void*) = nullptr; +};