From 642f394eb3f8fd99a51f4c8279497c458c40ba65 Mon Sep 17 00:00:00 2001 From: nyx Date: Tue, 8 Apr 2025 11:36:29 -0400 Subject: [PATCH] xwayland: sync primary selection with wayland (#9952) --- src/xwayland/XDataSource.cpp | 8 +++- src/xwayland/XWM.cpp | 71 +++++++++++++++++++++++++++--------- src/xwayland/XWM.hpp | 1 + 3 files changed, 62 insertions(+), 18 deletions(-) diff --git a/src/xwayland/XDataSource.cpp b/src/xwayland/XDataSource.cpp index 003f6c9fa..14656d8b1 100644 --- a/src/xwayland/XDataSource.cpp +++ b/src/xwayland/XDataSource.cpp @@ -77,7 +77,13 @@ void CXDataSource::send(const std::string& mime, CFileDescriptor fd) { xcb_create_window(g_pXWayland->pWM->connection, XCB_COPY_FROM_PARENT, transfer->incomingWindow, g_pXWayland->pWM->screen->root, 0, 0, 10, 10, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, g_pXWayland->pWM->screen->root_visual, XCB_CW_EVENT_MASK, &MASK); - xcb_convert_selection(g_pXWayland->pWM->connection, transfer->incomingWindow, HYPRATOMS["CLIPBOARD"], mimeAtom, HYPRATOMS["_WL_SELECTION"], XCB_TIME_CURRENT_TIME); + xcb_atom_t selection_atom = HYPRATOMS["CLIPBOARD"]; + if (&selection == &g_pXWayland->pWM->primarySelection) + selection_atom = HYPRATOMS["PRIMARY"]; + else if (&selection == &g_pXWayland->pWM->dndSelection) + selection_atom = HYPRATOMS["XdndSelection"]; + + xcb_convert_selection(g_pXWayland->pWM->connection, transfer->incomingWindow, selection_atom, mimeAtom, HYPRATOMS["_WL_SELECTION"], XCB_TIME_CURRENT_TIME); xcb_flush(g_pXWayland->pWM->connection); diff --git a/src/xwayland/XWM.cpp b/src/xwayland/XWM.cpp index efb080d64..6439c4917 100644 --- a/src/xwayland/XWM.cpp +++ b/src/xwayland/XWM.cpp @@ -586,7 +586,7 @@ void CXWM::handleSelectionNotify(xcb_selection_notify_event_t* e) { Debug::log(TRACE, "[xwm] converting selection failed"); sel->transfers.erase(it); } - } else if (e->target == HYPRATOMS["TARGETS"] && sel == &clipboard) { + } else if (e->target == HYPRATOMS["TARGETS"]) { if (!focusedSurface) { Debug::log(TRACE, "[xwm] denying access to write to clipboard because no X client is in focus"); return; @@ -601,14 +601,16 @@ bool CXWM::handleSelectionPropertyNotify(xcb_property_notify_event_t* e) { if (e->state != XCB_PROPERTY_DELETE) return false; - auto it = std::ranges::find_if(clipboard.transfers, [e](const auto& t) { return t->incomingWindow == e->window; }); - if (it != clipboard.transfers.end()) { - if (!(*it)->getIncomingSelectionProp(true)) { - clipboard.transfers.erase(it); - return false; + for (auto* sel : {&clipboard, &primarySelection}) { + auto it = std::ranges::find_if(sel->transfers, [e](const auto& t) { return t->incomingWindow == e->window; }); + if (it != sel->transfers.end()) { + if (!(*it)->getIncomingSelectionProp(true)) { + sel->transfers.erase(it); + return false; + } + getTransferData(*sel); + return true; } - getTransferData(clipboard); - return true; } return false; @@ -617,6 +619,8 @@ bool CXWM::handleSelectionPropertyNotify(xcb_property_notify_event_t* e) { SXSelection* CXWM::getSelection(xcb_atom_t atom) { if (atom == HYPRATOMS["CLIPBOARD"]) return &clipboard; + else if (atom == HYPRATOMS["PRIMARY"]) + return &primarySelection; else if (atom == HYPRATOMS["XdndSelection"]) return &dndSelection; @@ -707,8 +711,12 @@ bool CXWM::handleSelectionXFixesNotify(xcb_xfixes_selection_notify_event_t* e) { return true; if (e->owner == XCB_WINDOW_NONE) { - if (sel->owner != sel->window && sel == &clipboard) - g_pSeatManager->setCurrentSelection(nullptr); + if (sel->owner != sel->window) { + if (sel == &clipboard) + g_pSeatManager->setCurrentSelection(nullptr); + else if (sel == &primarySelection) + g_pSeatManager->setCurrentPrimarySelection(nullptr); + } sel->owner = 0; return true; @@ -721,7 +729,10 @@ bool CXWM::handleSelectionXFixesNotify(xcb_xfixes_selection_notify_event_t* e) { return true; } - xcb_convert_selection(connection, sel->window, HYPRATOMS["CLIPBOARD"], HYPRATOMS["TARGETS"], HYPRATOMS["_WL_SELECTION"], e->timestamp); + if (sel == &clipboard) + xcb_convert_selection(connection, sel->window, HYPRATOMS["CLIPBOARD"], HYPRATOMS["TARGETS"], HYPRATOMS["_WL_SELECTION"], e->timestamp); + else if (sel == &primarySelection) + xcb_convert_selection(connection, sel->window, HYPRATOMS["PRIMARY"], HYPRATOMS["TARGETS"], HYPRATOMS["_WL_SELECTION"], e->timestamp); xcb_flush(connection); return true; @@ -1171,6 +1182,16 @@ void CXWM::initSelection() { clipboard.listeners.setSelection = g_pSeatManager->events.setSelection.registerListener([this](std::any d) { clipboard.onSelection(); }); clipboard.listeners.keyboardFocusChange = g_pSeatManager->events.keyboardFocusChange.registerListener([this](std::any d) { clipboard.onKeyboardFocus(); }); + primarySelection.window = xcb_generate_id(connection); + xcb_create_window(connection, XCB_COPY_FROM_PARENT, primarySelection.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, primarySelection.window, HYPRATOMS["PRIMARY"], XCB_TIME_CURRENT_TIME); + + xcb_xfixes_select_selection_input(connection, primarySelection.window, HYPRATOMS["PRIMARY"], mask2); + + primarySelection.listeners.setSelection = g_pSeatManager->events.setPrimarySelection.registerListener([this](std::any d) { primarySelection.onSelection(); }); + primarySelection.listeners.keyboardFocusChange = g_pSeatManager->events.keyboardFocusChange.registerListener([this](std::any d) { primarySelection.onKeyboardFocus(); }); + dndSelection.window = xcb_generate_id(connection); xcb_create_window(connection, XCB_COPY_FROM_PARENT, dndSelection.window, screen->root, 0, 0, 8192, 8192, 0, XCB_WINDOW_CLASS_INPUT_ONLY, screen->root_visual, XCB_CW_EVENT_MASK, mask); @@ -1182,14 +1203,18 @@ void CXWM::initSelection() { void CXWM::setClipboardToWayland(SXSelection& sel) { auto source = makeShared(sel); if (source->mimes().empty()) { - Debug::log(ERR, "[xwm] can't set clipboard: no MIMEs"); + Debug::log(ERR, "[xwm] can't set selection: no MIMEs"); return; } sel.dataSource = source; - Debug::log(LOG, "[xwm] X clipboard at {:x} takes clipboard", (uintptr_t)sel.dataSource.get()); - g_pSeatManager->setCurrentSelection(sel.dataSource); + Debug::log(LOG, "[xwm] X selection at {:x} takes {}", (uintptr_t)sel.dataSource.get(), (&sel == &clipboard) ? "clipboard" : "primary selection"); + + if (&sel == &clipboard) + g_pSeatManager->setCurrentSelection(sel.dataSource); + else if (&sel == &primarySelection) + g_pSeatManager->setCurrentPrimarySelection(sel.dataSource); } static int writeDataSource(int fd, uint32_t mask, void* data) { @@ -1305,22 +1330,32 @@ SP CXWM::createX11DataOffer(SP surf, SPselection.currentSelection && g_pSeatManager->selection.currentSelection->type() == DATA_SOURCE_TYPE_X11) + if ((this == &g_pXWayland->pWM->clipboard && g_pSeatManager->selection.currentSelection && g_pSeatManager->selection.currentSelection->type() == DATA_SOURCE_TYPE_X11) || + (this == &g_pXWayland->pWM->primarySelection && g_pSeatManager->selection.currentPrimarySelection && + g_pSeatManager->selection.currentPrimarySelection->type() == DATA_SOURCE_TYPE_X11)) return; - if (g_pSeatManager->selection.currentSelection) { + if (this == &g_pXWayland->pWM->clipboard && 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); g_pXWayland->pWM->clipboard.notifyOnFocus = true; + } else if (this == &g_pXWayland->pWM->primarySelection && g_pSeatManager->selection.currentPrimarySelection) { + xcb_set_selection_owner(g_pXWayland->pWM->connection, g_pXWayland->pWM->primarySelection.window, HYPRATOMS["PRIMARY"], XCB_TIME_CURRENT_TIME); + xcb_flush(g_pXWayland->pWM->connection); + g_pXWayland->pWM->primarySelection.notifyOnFocus = true; } } void SXSelection::onKeyboardFocus() { if (!g_pSeatManager->state.keyboardFocusResource || g_pSeatManager->state.keyboardFocusResource->client() != g_pXWayland->pServer->xwaylandClient) return; - if (g_pXWayland->pWM->clipboard.notifyOnFocus) { + + if (this == &g_pXWayland->pWM->clipboard && g_pXWayland->pWM->clipboard.notifyOnFocus) { onSelection(); g_pXWayland->pWM->clipboard.notifyOnFocus = false; + } else if (this == &g_pXWayland->pWM->primarySelection && g_pXWayland->pWM->primarySelection.notifyOnFocus) { + onSelection(); + g_pXWayland->pWM->primarySelection.notifyOnFocus = false; } } @@ -1370,6 +1405,8 @@ bool SXSelection::sendData(xcb_selection_request_event_t* e, std::string mime) { WP selection; if (this == &g_pXWayland->pWM->clipboard) selection = g_pSeatManager->selection.currentSelection; + else if (this == &g_pXWayland->pWM->primarySelection) + selection = g_pSeatManager->selection.currentPrimarySelection; else if (!g_pXWayland->pWM->dndDataOffers.empty()) selection = g_pXWayland->pWM->dndDataOffers.at(0)->getSource(); diff --git a/src/xwayland/XWM.hpp b/src/xwayland/XWM.hpp index efd2f85ad..25010b0b8 100644 --- a/src/xwayland/XWM.hpp +++ b/src/xwayland/XWM.hpp @@ -201,6 +201,7 @@ class CXWM { uint64_t lastFocusSeq = 0; SXSelection clipboard; + SXSelection primarySelection; SXSelection dndSelection; SP dndDataDevice = makeShared(); std::vector> dndDataOffers;