diff options
| author | Vaxry <43317083+vaxerski@users.noreply.github.com> | 2024-05-25 22:43:51 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-05-25 22:43:51 +0200 |
| commit | addd3e7f1aeb670dd91d26005aaeccce3efb1ae7 (patch) | |
| tree | 7eae3cf1d780ab84ff34ee0fce5ed85d9ee69c90 /src/xwayland/XWM.cpp | |
| parent | a71207434c0bc2c8e05e94b1619e68059a002879 (diff) | |
xwayland: move to hyprland impl (#6086)
Diffstat (limited to 'src/xwayland/XWM.cpp')
| -rw-r--r-- | src/xwayland/XWM.cpp | 1210 |
1 files changed, 1210 insertions, 0 deletions
diff --git a/src/xwayland/XWM.cpp b/src/xwayland/XWM.cpp new file mode 100644 index 00000000..9e308314 --- /dev/null +++ b/src/xwayland/XWM.cpp @@ -0,0 +1,1210 @@ +#include "helpers/Vector2D.hpp" +#ifndef NO_XWAYLAND + +#include "XWayland.hpp" +#include "../defines.hpp" +#include <unordered_map> +#include "../Compositor.hpp" +#include "../protocols/XWaylandShell.hpp" +#include "../managers/SeatManager.hpp" +#include "../protocols/core/Seat.hpp" +#include <ranges> +#include <algorithm> +#include <fcntl.h> +#include <cstring> + +#include <xcb/xcb_icccm.h> + +#define XCB_EVENT_RESPONSE_TYPE_MASK 0x7f +#define INCR_CHUNK_SIZE (64 * 1024) + +static int onX11Event(int fd, uint32_t mask, void* data) { + return g_pXWayland->pWM->onEvent(fd, mask); +} + +SP<CXWaylandSurface> CXWM::windowForXID(xcb_window_t wid) { + for (auto& s : surfaces) { + if (s->xID == wid) + return s; + } + + return nullptr; +} + +void CXWM::handleCreate(xcb_create_notify_event_t* e) { + if (isWMWindow(e->window)) + return; + + const auto XSURF = surfaces.emplace_back(SP<CXWaylandSurface>(new CXWaylandSurface(e->window, CBox{e->x, e->y, e->width, e->height}, e->override_redirect))); + XSURF->self = XSURF; + Debug::log(LOG, "[xwm] New XSurface at {:x} with xid of {}", (uintptr_t)XSURF.get(), e->window); + + const auto WINDOW = CWindow::create(XSURF); + g_pCompositor->m_vWindows.emplace_back(WINDOW); + WINDOW->m_pSelf = WINDOW; + Debug::log(LOG, "[xwm] New XWayland window at {:x} for surf {:x}", (uintptr_t)WINDOW.get(), (uintptr_t)XSURF.get()); +} + +void CXWM::handleDestroy(xcb_destroy_notify_event_t* e) { + const auto XSURF = windowForXID(e->window); + + if (!XSURF) + return; + + XSURF->events.destroy.emit(); + std::erase_if(surfaces, [XSURF](const auto& other) { return XSURF == other; }); +} + +void CXWM::handleConfigure(xcb_configure_request_event_t* e) { + const auto XSURF = windowForXID(e->window); + + if (!XSURF) + return; + + const uint16_t MASK = e->value_mask; + constexpr uint16_t GEOMETRY = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; + if (!(MASK & GEOMETRY)) + return; + + XSURF->events.configure.emit(CBox{MASK & XCB_CONFIG_WINDOW_X ? e->x : XSURF->geometry.x, MASK & XCB_CONFIG_WINDOW_Y ? e->y : XSURF->geometry.y, + MASK & XCB_CONFIG_WINDOW_WIDTH ? e->width : XSURF->geometry.width, MASK & XCB_CONFIG_WINDOW_HEIGHT ? e->height : XSURF->geometry.height}); +} + +void CXWM::handleConfigureNotify(xcb_configure_notify_event_t* e) { + const auto XSURF = windowForXID(e->window); + + if (!XSURF) + return; + + if (XSURF->geometry == CBox{e->x, e->y, e->width, e->height}) + return; + + XSURF->geometry = {e->x, e->y, e->width, e->height}; + XSURF->events.setGeometry.emit(); +} + +void CXWM::handleMapRequest(xcb_map_request_event_t* e) { + const auto XSURF = windowForXID(e->window); + + if (!XSURF) + return; + + xcb_map_window(connection, e->window); + + XSURF->restackToTop(); + + const bool SMALL = + XSURF->geometry.size() < Vector2D{2, 2} || (XSURF->sizeHints && XSURF->geometry.size() < Vector2D{XSURF->sizeHints->min_width, XSURF->sizeHints->min_height}); + const bool HAS_HINTS = XSURF->sizeHints && Vector2D{XSURF->sizeHints->base_width, XSURF->sizeHints->base_height} > Vector2D{5, 5}; + const auto DESIREDSIZE = HAS_HINTS ? Vector2D{XSURF->sizeHints->base_width, XSURF->sizeHints->base_height} : Vector2D{800, 800}; + + // if it's too small, or its base size is set, configure it. + if ((SMALL || HAS_HINTS) && !XSURF->overrideRedirect) // default to 800 x 800 + XSURF->configure({XSURF->geometry.pos(), DESIREDSIZE}); + + Debug::log(LOG, "[xwm] Mapping window {} in X (geometry {}x{} at {}x{}))", e->window, XSURF->geometry.width, XSURF->geometry.height, XSURF->geometry.x, XSURF->geometry.y); + + // read data again. Some apps for some reason fail to send WINDOW_TYPE + // this shouldn't happen but does, I prolly fucked up somewhere, this is a band-aid + readWindowData(XSURF); + + XSURF->considerMap(); +} + +void CXWM::handleMapNotify(xcb_map_notify_event_t* e) { + const auto XSURF = windowForXID(e->window); + + if (!XSURF || XSURF->overrideRedirect) + return; + + XSURF->setWithdrawn(false); + sendState(XSURF); + xcb_flush(connection); + + XSURF->considerMap(); +} + +void CXWM::handleUnmapNotify(xcb_unmap_notify_event_t* e) { + const auto XSURF = windowForXID(e->window); + + if (!XSURF) + return; + + XSURF->unmap(); + dissociate(XSURF); + + if (XSURF->overrideRedirect) + return; + + XSURF->setWithdrawn(true); + sendState(XSURF); + xcb_flush(connection); +} + +static bool lookupParentExists(SP<CXWaylandSurface> XSURF, SP<CXWaylandSurface> prospectiveChild) { + while (XSURF->parent) { + if (XSURF->parent == prospectiveChild) + return true; + XSURF = XSURF->parent.lock(); + } + + return false; +} + +void CXWM::readProp(SP<CXWaylandSurface> XSURF, uint32_t atom, xcb_get_property_reply_t* reply) { + std::string propName = "?"; + for (auto& ha : HYPRATOMS) { + if (ha.second != atom) + continue; + + propName = ha.first; + break; + } + + if (atom == XCB_ATOM_WM_CLASS) { + size_t len = xcb_get_property_value_length(reply); + char* string = (char*)xcb_get_property_value(reply); + XSURF->state.appid = std::string{string, len}; + if (std::count(XSURF->state.appid.begin(), XSURF->state.appid.end(), '\000') == 2) + XSURF->state.appid = XSURF->state.appid.substr(XSURF->state.appid.find_first_of('\000') + 1); // fuck you X + if (!XSURF->state.appid.empty()) + XSURF->state.appid.pop_back(); + XSURF->events.metadataChanged.emit(); + } else if (atom == XCB_ATOM_WM_NAME || atom == HYPRATOMS["_NET_WM_NAME"]) { + size_t len = xcb_get_property_value_length(reply); + char* string = (char*)xcb_get_property_value(reply); + XSURF->state.title = std::string{string, len}; + XSURF->events.metadataChanged.emit(); + } else if (atom == HYPRATOMS["_NET_WM_WINDOW_TYPE"]) { + xcb_atom_t* atomsArr = (xcb_atom_t*)xcb_get_property_value(reply); + size_t atomsNo = reply->value_len; + XSURF->atoms.clear(); + for (size_t i = 0; i < atomsNo; ++i) { + XSURF->atoms.push_back(atomsArr[i]); + } + } else if (atom == HYPRATOMS["_NET_WM_STATE"]) { + xcb_atom_t* atoms = (xcb_atom_t*)xcb_get_property_value(reply); + for (uint32_t i = 0; i < reply->value_len; i++) { + if (atoms[i] == HYPRATOMS["_NET_WM_STATE_MODAL"]) + XSURF->modal = true; + } + } else if (atom == HYPRATOMS["WM_HINTS"]) { + if (reply->value_len != 0) { + XSURF->hints = std::make_unique<xcb_icccm_wm_hints_t>(); + xcb_icccm_get_wm_hints_from_reply(XSURF->hints.get(), reply); + + if (!(XSURF->hints->flags & XCB_ICCCM_WM_HINT_INPUT)) + XSURF->hints->input = true; + } + } else if (atom == HYPRATOMS["WM_WINDOW_ROLE"]) { + size_t len = xcb_get_property_value_length(reply); + + if (len <= 0) + XSURF->role = ""; + else { + XSURF->role = std::string{(char*)xcb_get_property_value(reply), len}; + XSURF->role = XSURF->role.substr(0, XSURF->role.find_first_of('\000')); + } + } else if (atom == XCB_ATOM_WM_TRANSIENT_FOR) { + if (reply->type == XCB_ATOM_WINDOW) { + const auto XID = (xcb_window_t*)xcb_get_property_value(reply); + XSURF->transient = XID; + if (XID) { + if (const auto NEWXSURF = windowForXID(*XID); !lookupParentExists(XSURF, NEWXSURF)) { + XSURF->parent = NEWXSURF; + NEWXSURF->children.push_back(XSURF); + } else + Debug::log(LOG, "[xwm] Denying transient because it would create a loop"); + } + } + } else if (atom == HYPRATOMS["WM_NORMAL_HINTS"]) { + if (reply->type == HYPRATOMS["WM_SIZE_HINTS"] && reply->value_len > 0) { + XSURF->sizeHints = std::make_unique<xcb_size_hints_t>(); + std::memset(XSURF->sizeHints.get(), 0, sizeof(xcb_size_hints_t)); + + xcb_icccm_get_wm_size_hints_from_reply(XSURF->sizeHints.get(), reply); + + const int32_t FLAGS = XSURF->sizeHints->flags; + const bool HASMIN = (FLAGS & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE); + const bool HASBASE = (FLAGS & XCB_ICCCM_SIZE_HINT_BASE_SIZE); + + if (!HASMIN && !HASBASE) { + XSURF->sizeHints->min_width = -1; + XSURF->sizeHints->min_height = -1; + XSURF->sizeHints->base_width = -1; + XSURF->sizeHints->base_height = -1; + } else if (!HASBASE) { + XSURF->sizeHints->base_width = XSURF->sizeHints->min_width; + XSURF->sizeHints->base_height = XSURF->sizeHints->min_height; + } else if (!HASMIN) { + XSURF->sizeHints->min_width = XSURF->sizeHints->base_width; + XSURF->sizeHints->min_height = XSURF->sizeHints->base_height; + } + + if (!(FLAGS & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE)) { + XSURF->sizeHints->max_width = -1; + XSURF->sizeHints->max_height = -1; + } + } + } else { + Debug::log(LOG, "[xwm] Unhandled prop {} -> {}", atom, propName); + return; + } + + Debug::log(LOG, "[xwm] Handled prop {} -> {}", atom, propName); +} + +void CXWM::handlePropertyNotify(xcb_property_notify_event_t* e) { + const auto XSURF = windowForXID(e->window); + + if (!XSURF) + return; + + xcb_get_property_cookie_t cookie = xcb_get_property(connection, 0, XSURF->xID, e->atom, XCB_ATOM_ANY, 0, 2048); + xcb_get_property_reply_t* reply = xcb_get_property_reply(connection, cookie, nullptr); + if (!reply) { + Debug::log(ERR, "[xwm] Failed to read property notify cookie"); + return; + } + + readProp(XSURF, e->atom, reply); + + free(reply); +} + +void CXWM::handleClientMessage(xcb_client_message_event_t* e) { + const auto XSURF = windowForXID(e->window); + + if (!XSURF) + return; + + std::string propName = "?"; + for (auto& ha : HYPRATOMS) { + if (ha.second != e->type) + continue; + + propName = ha.first; + break; + } + + if (e->type == HYPRATOMS["WL_SURFACE_ID"]) { + if (XSURF->surface) { + Debug::log(WARN, "[xwm] Re-assignment of WL_SURFACE_ID"); + dissociate(XSURF); + } + + auto id = e->data.data32[0]; + auto resource = wl_client_get_object(g_pXWayland->pServer->xwaylandClient, id); + if (resource) { + auto wlrSurface = wlr_surface_from_resource(resource); + associate(XSURF, wlrSurface); + } + } else if (e->type == HYPRATOMS["WL_SURFACE_SERIAL"]) { + if (XSURF->wlSerial) { + Debug::log(WARN, "[xwm] Re-assignment of WL_SURFACE_SERIAL"); + dissociate(XSURF); + } + + uint32_t serialLow = e->data.data32[0]; + uint32_t serialHigh = e->data.data32[1]; + XSURF->wlSerial = ((uint64_t)serialHigh << 32) | serialLow; + + Debug::log(LOG, "[xwm] surface {:x} requests serial {:x}", (uintptr_t)XSURF.get(), XSURF->wlSerial); + + for (auto& res : shellResources) { + if (!res) + continue; + + if (res->serial != XSURF->wlSerial || !XSURF->wlSerial) + continue; + + associate(XSURF, res->surface); + break; + } + + } else if (e->type == HYPRATOMS["_NET_WM_STATE"]) { + if (e->format == 32) { + uint32_t action = e->data.data32[0]; + for (size_t i = 0; i < 2; ++i) { + xcb_atom_t prop = e->data.data32[1 + i]; + + auto updateState = [XSURF](int action, bool current) -> bool { + switch (action) { + case 0: + /* remove */ + return false; + case 1: + /* add */ + return true; + case 2: + /* toggle */ + return !current; + default: return false; + } + return false; + }; + + if (prop == HYPRATOMS["_NET_WM_STATE_FULLSCREEN"]) + XSURF->state.requestsFullscreen = updateState(action, XSURF->fullscreen); + } + + XSURF->events.stateChanged.emit(); + } + } else if (e->type == HYPRATOMS["_NET_ACTIVE_WINDOW"]) { + XSURF->events.activate.emit(); + } else { + Debug::log(LOG, "[xwm] Unhandled message prop {} -> {}", e->type, propName); + return; + } + + Debug::log(LOG, "[xwm] Handled message prop {} -> {}", e->type, propName); +} + +void CXWM::handleFocusIn(xcb_focus_in_event_t* e) { + if (e->mode == XCB_NOTIFY_MODE_GRAB || e->mode == XCB_NOTIFY_MODE_UNGRAB || e->detail == XCB_NOTIFY_DETAIL_POINTER) + return; + + const auto XSURF = windowForXID(e->event); + + if (!XSURF) + return; + + if (focusedSurface && focusedSurface->pid == XSURF->pid && e->sequence - lastFocusSeq <= 255) + focusWindow(XSURF); + else + focusWindow(focusedSurface.lock()); +} + +void CXWM::sendWMMessage(SP<CXWaylandSurface> surf, xcb_client_message_data_t* data, uint32_t mask) { + xcb_client_message_event_t event = { + .response_type = XCB_CLIENT_MESSAGE, + .format = 32, + .sequence = 0, + .window = surf->xID, + .type = HYPRATOMS["WM_PROTOCOLS"], + .data = *data, + }; + + xcb_send_event(connection, 0, surf->xID, mask, (const char*)&event); + xcb_flush(connection); +} + +void CXWM::focusWindow(SP<CXWaylandSurface> surf) { + if (surf == focusedSurface) + return; + + auto oldSurf = focusedSurface.lock(); + focusedSurface = surf; + + if (oldSurf) + sendState(oldSurf); + + if (!surf) { + xcb_set_input_focus_checked(connection, XCB_INPUT_FOCUS_POINTER_ROOT, XCB_NONE, XCB_CURRENT_TIME); + return; + } + + if (surf->overrideRedirect) + return; + + xcb_client_message_data_t msg = {0}; + msg.data32[0] = HYPRATOMS["WM_TAKE_FOCUS"]; + msg.data32[1] = XCB_TIME_CURRENT_TIME; + + if (surf->hints && !surf->hints->input) + sendWMMessage(surf, &msg, XCB_EVENT_MASK_NO_EVENT); + else { + sendWMMessage(surf, &msg, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT); + + xcb_void_cookie_t cookie = xcb_set_input_focus(connection, XCB_INPUT_FOCUS_POINTER_ROOT, surf->xID, XCB_CURRENT_TIME); + lastFocusSeq = cookie.sequence; + } +} + +void CXWM::handleError(xcb_value_error_t* e) { + const char* major_name = xcb_errors_get_name_for_major_code(errors, e->major_opcode); + if (!major_name) { + Debug::log(ERR, "xcb error happened, but could not get major name"); + return; + } + + const char* minor_name = xcb_errors_get_name_for_minor_code(errors, e->major_opcode, e->minor_opcode); + + const char* extension; + const char* error_name = xcb_errors_get_name_for_error(errors, e->error_code, &extension); + if (!error_name) { + Debug::log(ERR, "xcb error happened, but could not get error name"); + return; + } + + Debug::log(ERR, "[xwm] xcb error: {} ({}), code {} ({}), seq {}, val {}", major_name, minor_name ? minor_name : "no minor", error_name, extension ? extension : "no extension", + e->sequence, e->bad_value); +} + +void CXWM::selectionSendNotify(xcb_selection_request_event_t* e, bool success) { + xcb_selection_notify_event_t selection_notify = { + .response_type = XCB_SELECTION_NOTIFY, + .sequence = 0, + .time = e->time, + .requestor = e->requestor, + .selection = e->selection, + .target = e->target, + .property = success ? e->property : (uint32_t)XCB_ATOM_NONE, + }; + + xcb_send_event(connection, 0, e->requestor, XCB_EVENT_MASK_NO_EVENT, (const char*)&selection_notify); + xcb_flush(connection); +} + +xcb_atom_t CXWM::mimeToAtom(const std::string& mime) { + if (mime == "text/plain;charset=utf-8") + return HYPRATOMS["UTF8_STRING"]; + if (mime == "text/plain") + return HYPRATOMS["TEXT"]; + + xcb_intern_atom_cookie_t cookie = xcb_intern_atom(connection, 0, mime.length(), mime.c_str()); + xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(connection, cookie, nullptr); + if (!reply) + return XCB_ATOM_NONE; + xcb_atom_t atom = reply->atom; + free(reply); + return atom; +} + +std::string CXWM::mimeFromAtom(xcb_atom_t atom) { + if (atom == HYPRATOMS["UTF8_STRING"]) + return "text/plain;charset=utf-8"; + if (atom == HYPRATOMS["TEXT"]) + return "text/plain"; + + xcb_get_atom_name_cookie_t cookie = xcb_get_atom_name(connection, atom); + xcb_get_atom_name_reply_t* reply = xcb_get_atom_name_reply(connection, cookie, nullptr); + if (!reply) + return "INVALID"; + size_t len = xcb_get_atom_name_name_length(reply); + char* buf = xcb_get_atom_name_name(reply); // not a C string + std::string SZNAME{buf, len}; + free(reply); + return SZNAME; +} + +void CXWM::handleSelectionNotify(xcb_selection_notify_event_t* e) { + Debug::log(LOG, "[xwm] Selection notify for {} prop {} target {}", e->selection, e->property, e->target); + + SXSelection& sel = clipboard; + + if (e->property == XCB_ATOM_NONE) { + if (sel.transfer) { + Debug::log(ERR, "[xwm] converting selection failed"); + sel.transfer.reset(); + } + } else if (e->target == HYPRATOMS["TARGETS"]) { + if (!focusedSurface) { + Debug::log(LOG, "[xwm] denying access to write to clipboard because no X client is in focus"); + return; + } + + setClipboardToWayland(sel); + } else if (sel.transfer) + getTransferData(sel); +} + +bool CXWM::handleSelectionPropertyNotify(xcb_property_notify_event_t* e) { + // Debug::log(LOG, "[xwm] Selection property notify for {} target {}", e->atom, e->window); + + // Debug::log(ERR, "[xwm] FIXME: CXWM::handleSelectionPropertyNotify stub"); + + return true; +} + +void CXWM::handleSelectionRequest(xcb_selection_request_event_t* e) { + Debug::log(LOG, "[xwm] Selection request for {} prop {} target {} time {} requestor {} selection {}", e->selection, e->property, e->target, e->time, e->requestor, + e->selection); + + SXSelection& sel = clipboard; + + if (!g_pSeatManager->selection.currentSelection) { + Debug::log(ERR, "[xwm] No selection"); + selectionSendNotify(e, false); + return; + } + + if (e->selection == HYPRATOMS["CLIPBOARD_MANAGER"]) { + selectionSendNotify(e, true); + return; + } + + if (sel.window != e->owner && e->time != XCB_CURRENT_TIME && e->time < sel.timestamp) { + Debug::log(ERR, "[xwm] outdated selection request. Time {} < {}", e->time, sel.timestamp); + selectionSendNotify(e, false); + return; + } + + if (!g_pSeatManager->state.keyboardFocusResource || g_pSeatManager->state.keyboardFocusResource->client() != g_pXWayland->pServer->xwaylandClient) { + Debug::log(LOG, "[xwm] Ignoring clipboard access: xwayland not in focus"); + selectionSendNotify(e, false); + return; + } + + if (e->target == HYPRATOMS["TARGETS"]) { + // send mime types + auto mimes = g_pSeatManager->selection.currentSelection->mimes(); + + std::vector<xcb_atom_t> atoms; + atoms.push_back(HYPRATOMS["TIMESTAMP"]); + atoms.push_back(HYPRATOMS["TARGETS"]); + + for (auto& m : mimes) { + atoms.push_back(mimeToAtom(m)); + } + + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, e->requestor, e->property, XCB_ATOM_ATOM, 32, atoms.size(), atoms.data()); + selectionSendNotify(e, true); + } else if (e->target == HYPRATOMS["TIMESTAMP"]) { + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, e->requestor, e->property, XCB_ATOM_INTEGER, 32, 1, &sel.timestamp); + selectionSendNotify(e, true); + } else if (e->target == HYPRATOMS["DELETE"]) { + selectionSendNotify(e, true); + } else { + + std::string mime = mimeFromAtom(e->target); + + if (mime == "INVALID") { + Debug::log(LOG, "[xwm] Ignoring clipboard access: invalid mime atom {}", e->target); + selectionSendNotify(e, false); + return; + } + + if (!sel.sendData(e, mime)) { + Debug::log(LOG, "[xwm] Failed to send selection :("); + selectionSendNotify(e, false); + return; + } + } +} + +bool CXWM::handleSelectionXFixesNotify(xcb_xfixes_selection_notify_event_t* e) { + Debug::log(LOG, "[xwm] Selection xfixes notify for {}", e->selection); + + // IMPORTANT: mind the g_pSeatManager below + SXSelection& sel = clipboard; + + if (e->owner == XCB_WINDOW_NONE) { + if (sel.owner != sel.window) + g_pSeatManager->setCurrentSelection(nullptr); + + sel.owner = 0; + return true; + } + + sel.owner = e->owner; + + if (sel.owner == sel.window) { + sel.timestamp = e->timestamp; + return true; + } + + xcb_convert_selection(connection, sel.window, HYPRATOMS["CLIPBOARD"], HYPRATOMS["TARGETS"], HYPRATOMS["_WL_SELECTION"], e->timestamp); + xcb_flush(connection); + + return true; +} + +bool CXWM::handleSelectionEvent(xcb_generic_event_t* e) { + switch (e->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) { + case XCB_SELECTION_NOTIFY: { + handleSelectionNotify((xcb_selection_notify_event_t*)e); + return true; + } + case XCB_PROPERTY_NOTIFY: { + return handleSelectionPropertyNotify((xcb_property_notify_event_t*)e); + } + case XCB_SELECTION_REQUEST: { + handleSelectionRequest((xcb_selection_request_event_t*)e); + return true; + } + } + + if (e->response_type - xfixes->first_event == XCB_XFIXES_SELECTION_NOTIFY) + return handleSelectionXFixesNotify((xcb_xfixes_selection_notify_event_t*)e); + + return 0; +} + +int CXWM::onEvent(int fd, uint32_t mask) { + + if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { + Debug::log(ERR, "XWayland has yeeten the xwm off?!"); + Debug::log(CRIT, "XWayland has yeeten the xwm off?!"); + g_pXWayland->pWM.reset(); + g_pXWayland->pServer.reset(); + return 0; + } + + int count = 0; + + while (42069) { + xcb_generic_event_t* event = xcb_poll_for_event(connection); + if (!event) + break; + + count++; + + if (handleSelectionEvent(event)) { + free(event); + continue; + } + + switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) { + case XCB_CREATE_NOTIFY: handleCreate((xcb_create_notify_event_t*)event); break; + case XCB_DESTROY_NOTIFY: handleDestroy((xcb_destroy_notify_event_t*)event); break; + case XCB_CONFIGURE_REQUEST: handleConfigure((xcb_configure_request_event_t*)event); break; + case XCB_CONFIGURE_NOTIFY: handleConfigureNotify((xcb_configure_notify_event_t*)event); break; + case XCB_MAP_REQUEST: handleMapRequest((xcb_map_request_event_t*)event); break; + case XCB_MAP_NOTIFY: handleMapNotify((xcb_map_notify_event_t*)event); break; + case XCB_UNMAP_NOTIFY: handleUnmapNotify((xcb_unmap_notify_event_t*)event); break; + case XCB_PROPERTY_NOTIFY: handlePropertyNotify((xcb_property_notify_event_t*)event); break; + case XCB_CLIENT_MESSAGE: handleClientMessage((xcb_client_message_event_t*)event); break; + case XCB_FOCUS_IN: handleFocusIn((xcb_focus_in_event_t*)event); break; + case 0: handleError((xcb_value_error_t*)event); break; + default: { + ; + } + } + free(event); + } + + if (count) + xcb_flush(connection); + + return count; +} + +void CXWM::gatherResources() { + xcb_prefetch_extension_data(connection, &xcb_xfixes_id); + xcb_prefetch_extension_data(connection, &xcb_composite_id); + xcb_prefetch_extension_data(connection, &xcb_res_id); + + for (auto& ATOM : HYPRATOMS) { + xcb_intern_atom_cookie_t cookie = xcb_intern_atom(connection, 0, ATOM.first.length(), ATOM.first.c_str()); + xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(connection, cookie, nullptr); + + if (!reply) { + Debug::log(ERR, "[xwm] Atom failed: {}", ATOM.first); + continue; + } + + ATOM.second = reply->atom; + free(reply); + } + + xfixes = xcb_get_extension_data(connection, &xcb_xfixes_id); + + if (!xfixes || !xfixes->present) + Debug::log(WARN, "XFixes not available"); + + xcb_xfixes_query_version_cookie_t xfixes_cookie; + xcb_xfixes_query_version_reply_t* xfixes_reply; + xfixes_cookie = xcb_xfixes_query_version(connection, XCB_XFIXES_MAJOR_VERSION, XCB_XFIXES_MINOR_VERSION); + xfixes_reply = xcb_xfixes_query_version_reply(connection, xfixes_cookie, NULL); + + Debug::log(LOG, "xfixes version: {}.{}", xfixes_reply->major_version, xfixes_reply->minor_version); + xfixesMajor = xfixes_reply->major_version; + + free(xfixes_reply); + + const xcb_query_extension_reply_t* xresReply1 = xcb_get_extension_data(connection, &xcb_res_id); + if (!xresReply1 || !xresReply1->present) + return; + + xcb_res_query_version_cookie_t xres_cookie = xcb_res_query_version(connection, XCB_RES_MAJOR_VERSION, XCB_RES_MINOR_VERSION); + xcb_res_query_version_reply_t* xres_reply = xcb_res_query_version_reply(connection, xres_cookie, NULL); + if (xres_reply == NULL) + return; + + Debug::log(LOG, "xres version: {}.{}", xres_reply->server_major, xres_reply->server_minor); + if (xres_reply->server_major > 1 || (xres_reply->server_major == 1 && xres_reply->server_minor >= 2)) { + xres = xresReply1; + } + free(xres_reply); +} + +void CXWM::getVisual() { + xcb_depth_iterator_t d_iter; + xcb_visualtype_iterator_t vt_iter; + xcb_visualtype_t* visualtype; + + d_iter = xcb_screen_allowed_depths_iterator(screen); + visualtype = NULL; + while (d_iter.rem > 0) { + if (d_iter.data->depth == 32) { + vt_iter = xcb_depth_visuals_iterator(d_iter.data); + visualtype = vt_iter.data; + break; + } + + xcb_depth_next(&d_iter); + } + + if (visualtype == NULL) { + wlr_log(WLR_DEBUG, "No 32 bit visualtype\n"); + return; + } + + visual_id = visualtype->visual_id; + colormap = xcb_generate_id(connection); + xcb_create_colormap(connection, XCB_COLORMAP_ALLOC_NONE, colormap, screen->root, visual_id); +} + +void CXWM::getRenderFormat() { + xcb_render_query_pict_formats_cookie_t cookie = xcb_render_query_pict_formats(connection); + xcb_render_query_pict_formats_reply_t* reply = xcb_render_query_pict_formats_reply(connection, cookie, NULL); + if (!reply) { + wlr_log(WLR_ERROR, "Did not get any reply from xcb_render_query_pict_formats"); + return; + } + xcb_render_pictforminfo_iterator_t iter = xcb_render_query_pict_formats_formats_iterator(reply); + xcb_render_pictforminfo_t* format = NULL; + while (iter.rem > 0) { + if (iter.data->depth == 32) { + format = iter.data; + break; + } + + xcb_render_pictforminfo_next(&iter); + } + + if (format == NULL) { + wlr_log(WLR_DEBUG, "No 32 bit render format"); + free(reply); + return; + } + + render_format_id = format->id; + free(reply); +} + +CXWM::CXWM() { + connection = xcb_connect_to_fd(g_pXWayland->pServer->xwmFDs[0], nullptr); + + if (int ret = xcb_connection_has_error(connection); ret) { + Debug::log(ERR, "[xwm] Couldn't start, error {}", ret); + return; + } + + if (xcb_errors_context_new(connection, &errors)) { + Debug::log(ERR, "[xwm] Couldn't allocate errors context"); + return; + } + + xcb_screen_iterator_t screen_iterator = xcb_setup_roots_iterator(xcb_get_setup(connection)); + screen = screen_iterator.data; + + eventSource = wl_event_loop_add_fd(g_pCompositor->m_sWLEventLoop, g_pXWayland->pServer->xwmFDs[0], WL_EVENT_READABLE, ::onX11Event, nullptr); + wl_event_source_check(eventSource); + + gatherResources(); + getVisual(); + getRenderFormat(); + + uint32_t values[] = { + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_PROPERTY_CHANGE, + }; + xcb_change_window_attributes(connection, screen->root, XCB_CW_EVENT_MASK, values); + + xcb_composite_redirect_subwindows(connection, screen->root, XCB_COMPOSITE_REDIRECT_MANUAL); + + xcb_atom_t supported[] = { + HYPRATOMS["_NET_WM_STATE"], HYPRATOMS["_NET_ACTIVE_WINDOW"], HYPRATOMS["_NET_WM_MOVERESIZE"], HYPRATOMS["_NET_WM_STATE_FOCUSED"], + HYPRATOMS["_NET_WM_STATE_MODAL"], HYPRATOMS["_NET_WM_STATE_FULLSCREEN"], HYPRATOMS["_NET_WM_STATE_MAXIMIZED_VERT"], HYPRATOMS["_NET_WM_STATE_MAXIMIZED_HORZ"], + HYPRATOMS["_NET_WM_STATE_HIDDEN"], HYPRATOMS["_NET_CLIENT_LIST"], HYPRATOMS["_NET_CLIENT_LIST_STACKING"], + }; + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, screen->root, HYPRATOMS["_NET_SUPPORTED"], XCB_ATOM_ATOM, 32, sizeof(supported) / sizeof(*supported), supported); + + xcb_flush(connection); + + setActiveWindow(XCB_WINDOW_NONE); + + initSelection(); + + hyprListener_newSurface.initCallback( + &g_pCompositor->m_sWLRCompositor->events.new_surface, [this](void* owner, void* data) { onNewSurface((wlr_surface*)data); }, nullptr, "XWM"); + + listeners.newXShellSurface = PROTO::xwaylandShell->events.newSurface.registerListener([this](std::any d) { onNewResource(std::any_cast<SP<CXWaylandSurfaceResource>>(d)); }); + + createWMWindow(); + + xcb_flush(connection); +} + +void CXWM::setActiveWindow(xcb_window_t window) { + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, screen->root, HYPRATOMS["_NET_ACTIVE_WINDOW"], HYPRATOMS["WINDOW"], 32, 1, &window); +} + +void CXWM::createWMWindow() { + constexpr const char* wmName = "Hyprland :D"; + wmWindow = xcb_generate_id(connection); + xcb_create_window(connection, XCB_COPY_FROM_PARENT, wmWindow, screen->root, 0, 0, 10, 10, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, 0, NULL); + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, wmWindow, HYPRATOMS["_NET_WM_NAME"], HYPRATOMS["UTF8_STRING"], + 8, // format + strlen(wmName), wmName); + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, screen->root, HYPRATOMS["_NET_SUPPORTING_WM_CHECK"], XCB_ATOM_WINDOW, + 32, // format + 1, &wmWindow); + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, wmWindow, HYPRATOMS["_NET_SUPPORTING_WM_CHECK"], XCB_ATOM_WINDOW, + 32, // format + 1, &wmWindow); + xcb_set_selection_owner(connection, wmWindow, HYPRATOMS["WM_S0"], XCB_CURRENT_TIME); + xcb_set_selection_owner(connection, wmWindow, HYPRATOMS["_NET_WM_CM_S0"], XCB_CURRENT_TIME); +} + +void CXWM::activateSurface(SP<CXWaylandSurface> surf) { + if (surf == focusedSurface || (surf && surf->overrideRedirect)) + return; + + setActiveWindow(surf ? surf->xID : (uint32_t)XCB_WINDOW_NONE); + + focusWindow(surf); + + xcb_flush(connection); +} + +void CXWM::sendState(SP<CXWaylandSurface> surf) { + if (surf->withdrawn) { + xcb_delete_property(connection, surf->xID, HYPRATOMS["_NET_WM_STATE"]); + return; + } + + std::vector<uint32_t> props; + if (surf->modal) + props.push_back(HYPRATOMS["_NET_WM_STATE_MODAL"]); + if (surf->fullscreen) + props.push_back(HYPRATOMS["_NET_WM_STATE_FULLSCREEN"]); + if (surf->maximized) { + props.push_back(HYPRATOMS["NET_WM_STATE_MAXIMIZED_VERT"]); + props.push_back(HYPRATOMS["NET_WM_STATE_MAXIMIZED_HORZ"]); + } + if (surf->minimized) + props.push_back(HYPRATOMS["_NET_WM_STATE_HIDDEN"]); + if (surf == focusedSurface) + props.push_back(HYPRATOMS["_NET_WM_STATE_FOCUSED"]); + + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, surf->xID, HYPRATOMS["_NET_WM_STATE"], XCB_ATOM_ATOM, 32, props.size(), props.data()); +} + +void CXWM::onNewSurface(wlr_surface* surf) { + if (wl_resource_get_client(surf->resource) != g_pXWayland->pServer->xwaylandClient) + return; + + Debug::log(LOG, "[xwm] New XWayland surface at {:x}", (uintptr_t)surf); + + const auto WLID = wl_resource_get_id(surf->resource); + + for (auto& sr : surfaces) { + if (sr->surface || sr->wlID != WLID) + continue; + + associate(sr, surf); + return; + } + + Debug::log(WARN, "[xwm] CXWM::onNewSurface: no matching xwaylandSurface"); +} + +void CXWM::onNewResource(SP<CXWaylandSurfaceResource> resource) { + Debug::log(LOG, "[xwm] New XWayland resource at {:x}", (uintptr_t)resource.get()); + + std::erase_if(shellResources, [](const auto& e) { return e.expired(); }); + shellResources.push_back(resource); + + for (auto& surf : surfaces) { + if (surf->resource || surf->wlSerial != resource->serial) + continue; + + associate(surf, resource->surface); + break; + } +} + +void CXWM::readWindowData(SP<CXWaylandSurface> surf) { + const std::array<xcb_atom_t, 8> interestingProps = { + XCB_ATOM_WM_CLASS, XCB_ATOM_WM_NAME, XCB_ATOM_WM_TRANSIENT_FOR, HYPRATOMS["WM_HINTS"], + HYPRATOMS["_NET_WM_STATE"], HYPRATOMS["_NET_WM_NAME"], HYPRATOMS["_NET_WM_WINDOW_TYPE"], HYPRATOMS["WM_NORMAL_HINTS"], + }; + + for (size_t i = 0; i < interestingProps.size(); i++) { + xcb_get_property_cookie_t cookie = xcb_get_property(connection, 0, surf->xID, interestingProps.at(i), XCB_ATOM_ANY, 0, 2048); + xcb_get_property_reply_t* reply = xcb_get_property_reply(connection, cookie, nullptr); + if (!reply) { + Debug::log(ERR, "[xwm] Failed to get window property"); + continue; + } + readProp(surf, interestingProps.at(i), reply); + free(reply); + } +} + +void CXWM::associate(SP<CXWaylandSurface> surf, wlr_surface* wlSurf) { + if (surf->surface) + return; + + auto existing = std::find_if(surfaces.begin(), surfaces.end(), [wlSurf](const auto& e) { return e->surface == wlSurf; }); + + if (existing != surfaces.end()) { + Debug::log(WARN, "[xwm] associate() called but surface is already associated to {:x}, ignoring...", (uintptr_t)surf.get()); + return; + } + + surf->surface = wlSurf; + surf->ensureListeners(); + + readWindowData(surf); + + surf->events.resourceChange.emit(); +} + +void CXWM::dissociate(SP<CXWaylandSurface> surf) { + if (!surf->surface) + return; + + if (surf->mapped) + surf->unmap(); + + surf->surface = nullptr; + surf->events.resourceChange.emit(); + + Debug::log(LOG, "Dissociate for {:x}", (uintptr_t)surf.get()); +} + +void CXWM::updateClientList() { + std::erase_if(mappedSurfaces, [](const auto& e) { return e.expired() || !e->mapped; }); + std::erase_if(mappedSurfacesStacking, [](const auto& e) { return e.expired() || !e->mapped; }); + + std::vector<xcb_window_t> windows; + for (auto& m : mappedSurfaces) { + windows.push_back(m->xID); + } + + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, screen->root, HYPRATOMS["_NET_CLIENT_LIST"], XCB_ATOM_WINDOW, 32, windows.size(), windows.data()); + + windows.clear(); + + for (auto& m : mappedSurfacesStacking) { + windows.push_back(m->xID); + } + + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, screen->root, HYPRATOMS["_NET_CLIENT_LIST_STACKING"], XCB_ATOM_WINDOW, 32, windows.size(), windows.data()); +} + +bool CXWM::isWMWindow(xcb_window_t w) { + return w == wmWindow || w == clipboard.window; +} + +void CXWM::initSelection() { + clipboard.window = xcb_generate_id(connection); + uint32_t mask[1] = {XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE}; + xcb_create_window(connection, XCB_COPY_FROM_PARENT, clipboard.window, screen->root, 0, 0, 10, 10, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, XCB_CW_EVENT_MASK, + mask); + xcb_set_selection_owner(connection, clipboard.window, HYPRATOMS["CLIPBOARD_MANAGER"], XCB_TIME_CURRENT_TIME); + + uint32_t mask2 = + XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER | XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY | XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE; + xcb_xfixes_select_selection_input(connection, clipboard.window, HYPRATOMS["CLIPBOARD"], mask2); + + clipboard.listeners.setSelection = g_pSeatManager->events.setSelection.registerListener([this](std::any d) { clipboard.onSelection(); }); +} + +void CXWM::setClipboardToWayland(SXSelection& sel) { + sel.dataSource = makeShared<CXDataSource>(sel); + if (sel.dataSource->mimes().empty()) { + Debug::log(ERR, "[xwm] can't set clipboard: no MIMEs"); + sel.dataSource.reset(); + } + + if (sel.dataSource) { + Debug::log(LOG, "[xwm] X clipboard at {:x} takes clipboard", (uintptr_t)sel.dataSource.get()); + g_pSeatManager->setCurrentSelection(sel.dataSource); + } +} + +void CXWM::getTransferData(SXSelection& sel) { + Debug::log(LOG, "[xwm] getTransferData"); + + sel.transfer->getIncomingSelectionProp(true); + + if (sel.transfer->propertyReply->type == HYPRATOMS["INCR"]) { + Debug::log(ERR, "[xwm] Transfer is INCR, which we don't support :("); + close(sel.transfer->wlFD); + sel.transfer.reset(); + return; + } else { + char* property = (char*)xcb_get_property_value(sel.transfer->propertyReply); + int remainder = xcb_get_property_value_length(sel.transfer->propertyReply) - sel.transfer->propertyStart; + + ssize_t len = write(sel.transfer->wlFD, property + sel.transfer->propertyStart, remainder); + if (len == -1) { + Debug::log(ERR, "[xwm] write died in transfer get"); + close(sel.transfer->wlFD); + sel.transfer.reset(); + return; + } + + if (len < remainder) { + sel.transfer->propertyStart += len; + Debug::log(ERR, "[xwm] wl client read partially: len {}", len); + return; + } else { + Debug::log(LOG, "[xwm] cb transfer to wl client complete, read {} bytes", len); + close(sel.transfer->wlFD); + sel.transfer.reset(); + } + } +} + +void CXWM::setCursor(unsigned char* pixData, uint32_t stride, const Vector2D& size, const Vector2D& hotspot) { + if (!render_format_id) { + Debug::log(ERR, "[xwm] can't set cursor: no render format"); + return; + } + + if (cursorXID) + xcb_free_cursor(connection, cursorXID); + + constexpr int CURSOR_DEPTH = 32; + + xcb_pixmap_t pix = xcb_generate_id(connection); + xcb_create_pixmap(connection, CURSOR_DEPTH, pix, screen->root, size.x, size.y); + + xcb_render_picture_t pic = xcb_generate_id(connection); + xcb_render_create_picture(connection, pic, pix, render_format_id, 0, 0); + + xcb_gcontext_t gc = xcb_generate_id(connection); + xcb_create_gc(connection, gc, pix, 0, NULL); + + xcb_put_image(connection, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc, size.x, size.y, 0, 0, 0, CURSOR_DEPTH, stride * size.y * sizeof(uint8_t), pixData); + xcb_free_gc(connection, gc); + + cursorXID = xcb_generate_id(connection); + xcb_render_create_cursor(connection, cursorXID, pic, hotspot.x, hotspot.y); + xcb_free_pixmap(connection, pix); + xcb_render_free_picture(connection, pic); + + uint32_t values[] = {cursorXID}; + xcb_change_window_attributes(connection, screen->root, XCB_CW_CURSOR, values); + xcb_flush(connection); +} + +void SXSelection::onSelection() { + if (g_pSeatManager->selection.currentSelection && g_pSeatManager->selection.currentSelection->type() == DATA_SOURCE_TYPE_X11) + return; + + if (g_pSeatManager->selection.currentSelection) { + xcb_set_selection_owner(g_pXWayland->pWM->connection, g_pXWayland->pWM->clipboard.window, HYPRATOMS["CLIPBOARD"], XCB_TIME_CURRENT_TIME); + xcb_flush(g_pXWayland->pWM->connection); + } +} + +int SXSelection::onRead(int fd, uint32_t mask) { + // TODO: support INCR + + size_t pre = transfer->data.size(); + transfer->data.resize(INCR_CHUNK_SIZE + pre); + + auto len = read(fd, transfer->data.data() + pre, INCR_CHUNK_SIZE - 1); + if (len < 0) { + Debug::log(ERR, "[xwm] readDataSource died"); + g_pXWayland->pWM->selectionSendNotify(&transfer->request, false); + transfer.reset(); + return 0; + } + + transfer->data.resize(pre + len); + + if (len == 0) { + Debug::log(LOG, "[xwm] Received all the bytes, final length {}", transfer->data.size()); + xcb_change_property(g_pXWayland->pWM->connection, XCB_PROP_MODE_REPLACE, transfer->request.requestor, transfer->request.property, transfer->request.target, 8, + transfer->data.size(), transfer->data.data()); + xcb_flush(g_pXWayland->pWM->connection); + g_pXWayland->pWM->selectionSendNotify(&transfer->request, true); + transfer.reset(); + } else + Debug::log(LOG, "[xwm] Received {} bytes, waiting...", len); + + return 1; +} + +static int readDataSource(int fd, uint32_t mask, void* data) { + Debug::log(LOG, "[xwm] readDataSource on fd {}", fd); + + auto selection = (SXSelection*)data; + + return selection->onRead(fd, mask); +} + +bool SXSelection::sendData(xcb_selection_request_event_t* e, std::string mime) { + WP<IDataSource> selection = g_pSeatManager->selection.currentSelection; + + if (!selection) + return false; + + const auto MIMES = selection->mimes(); + + if (MIMES.empty()) + return false; + + if (std::find(MIMES.begin(), MIMES.end(), mime) == MIMES.end()) { + Debug::log(ERR, "[xwm] X Client asked for an invalid MIME, sending the first advertised. THIS SHIT MAY BREAK!"); + mime = *MIMES.begin(); + } + + transfer = std::make_unique<SXTransfer>(*this); + transfer->request = *e; + + int p[2]; + if (pipe(p) == -1) { + Debug::log(ERR, "[xwm] selection: pipe() failed"); + return false; + } + + fcntl(p[0], F_SETFD, FD_CLOEXEC); + fcntl(p[0], F_SETFL, O_NONBLOCK); + fcntl(p[1], F_SETFD, FD_CLOEXEC); + fcntl(p[1], F_SETFL, O_NONBLOCK); + + transfer->wlFD = p[0]; + + Debug::log(LOG, "[xwm] sending wayland selection to xwayland with mime {}, target {}, fds {} {}", mime, e->target, p[0], p[1]); + + selection->send(mime, p[1]); + + transfer->eventSource = wl_event_loop_add_fd(g_pCompositor->m_sWLEventLoop, transfer->wlFD, WL_EVENT_READABLE, ::readDataSource, this); + + return true; +} + +SXTransfer::~SXTransfer() { + if (wlFD) + close(wlFD); + if (eventSource) + wl_event_source_remove(eventSource); + if (incomingWindow) + xcb_destroy_window(g_pXWayland->pWM->connection, incomingWindow); + if (propertyReply) + free(propertyReply); +} + +bool SXTransfer::getIncomingSelectionProp(bool erase) { + xcb_get_property_cookie_t cookie = xcb_get_property(g_pXWayland->pWM->connection, erase, incomingWindow, HYPRATOMS["_WL_SELECTION"], XCB_GET_PROPERTY_TYPE_ANY, 0, 0x1fffffff); + + propertyStart = 0; + propertyReply = xcb_get_property_reply(g_pXWayland->pWM->connection, cookie, nullptr); + + if (!propertyReply) { + Debug::log(ERR, "[SXTransfer] couldn't get a prop reply"); + return false; + } + + return true; +} + +#endif |
