From cc67873b712ad0b45957b34be2db6b8095420603 Mon Sep 17 00:00:00 2001 From: nnyyxxxx Date: Thu, 13 Feb 2025 22:14:18 -0500 Subject: [PATCH] refactor(xwayland): improve dnd and cleanup --- src/protocols/core/DataDevice.cpp | 62 +++++++----- src/protocols/core/DataDevice.hpp | 2 +- src/xwayland/Dnd.cpp | 153 ++++++++++++++++++++++-------- src/xwayland/Dnd.hpp | 13 +++ src/xwayland/XWM.cpp | 39 ++------ src/xwayland/XWM.hpp | 2 - 6 files changed, 176 insertions(+), 95 deletions(-) diff --git a/src/protocols/core/DataDevice.cpp b/src/protocols/core/DataDevice.cpp index 25117265d11..f4044ec5baf 100644 --- a/src/protocols/core/DataDevice.cpp +++ b/src/protocols/core/DataDevice.cpp @@ -13,6 +13,7 @@ #include "../../managers/HookSystemManager.hpp" #include "../../helpers/Monitor.hpp" #include "../../render/Renderer.hpp" +#include "../../xwayland/Dnd.hpp" using namespace Hyprutils::OS; CWLDataOfferResource::CWLDataOfferResource(SP resource_, SP source_) : source(source_), resource(resource_) { @@ -689,7 +690,7 @@ void CWLDataDeviceProtocol::updateDrag() { g_pSeatManager->state.dndPointerFocus->current.size / 2.F, offer); } -void CWLDataDeviceProtocol::resetDndState() { +void CWLDataDeviceProtocol::cleanupDndState(bool resetDevice, bool resetSource, bool simulateInput) { dnd.dndSurface.reset(); dnd.dndSurfaceCommit.reset(); dnd.dndSurfaceDestroy.reset(); @@ -697,6 +698,16 @@ void CWLDataDeviceProtocol::resetDndState() { dnd.mouseMove.reset(); dnd.touchUp.reset(); dnd.touchMove.reset(); + + if (resetDevice) + dnd.focusedDevice.reset(); + if (resetSource) + dnd.currentSource.reset(); + + if (simulateInput) { + g_pInputManager->simulateMouseMovement(); + g_pSeatManager->resendEnterEvents(); + } } void CWLDataDeviceProtocol::dropDrag() { @@ -712,21 +723,31 @@ void CWLDataDeviceProtocol::dropDrag() { } dnd.focusedDevice->sendDrop(); - dnd.focusedDevice->sendLeave(); - resetDndState(); +#ifndef NO_XWAYLAND + if (dnd.focusedDevice->getX11()) { + dnd.focusedDevice->sendLeave(); + if (dnd.overriddenCursor) + g_pInputManager->unsetCursorImage(); + dnd.overriddenCursor = false; + cleanupDndState(true, true, true); + return; + } +#endif + dnd.focusedDevice->sendLeave(); if (dnd.overriddenCursor) g_pInputManager->unsetCursorImage(); dnd.overriddenCursor = false; + cleanupDndState(false, false, false); } bool CWLDataDeviceProtocol::wasDragSuccessful() { - if (!dnd.focusedDevice || !dnd.currentSource) + if (!dnd.currentSource) return false; for (auto const& o : m_vOffers) { - if (o->dead || !o->source || !o->source->hasDnd()) + if (o->dead || o->source != dnd.currentSource) continue; if (o->recvd || o->accepted) @@ -734,25 +755,14 @@ bool CWLDataDeviceProtocol::wasDragSuccessful() { } #ifndef NO_XWAYLAND - if (g_pXWayland->pWM) { - for (auto const& o : g_pXWayland->pWM->dndDataOffers) { - if (o->dead || !o->source || !o->source->hasDnd()) - continue; - - if (o->source != dnd.currentSource) - continue; - - return true; - } - } + if (dnd.focusedDevice->getX11()) + return true; #endif return false; } void CWLDataDeviceProtocol::completeDrag() { - resetDndState(); - if (!dnd.focusedDevice && !dnd.currentSource) return; @@ -761,15 +771,11 @@ void CWLDataDeviceProtocol::completeDrag() { dnd.currentSource->sendDndFinished(); } - dnd.focusedDevice.reset(); - dnd.currentSource.reset(); - - g_pInputManager->simulateMouseMovement(); - g_pSeatManager->resendEnterEvents(); + cleanupDndState(true, true, true); } void CWLDataDeviceProtocol::abortDrag() { - resetDndState(); + cleanupDndState(false, false, false); if (dnd.overriddenCursor) g_pInputManager->unsetCursorImage(); @@ -778,8 +784,14 @@ void CWLDataDeviceProtocol::abortDrag() { if (!dnd.focusedDevice && !dnd.currentSource) return; - if (dnd.focusedDevice) + if (dnd.focusedDevice) { +#ifndef NO_XWAYLAND + if (auto x11Device = dnd.focusedDevice->getX11(); x11Device) + x11Device->forceCleanupDnd(); +#endif dnd.focusedDevice->sendLeave(); + } + if (dnd.currentSource) dnd.currentSource->cancelled(); diff --git a/src/protocols/core/DataDevice.hpp b/src/protocols/core/DataDevice.hpp index dfea4a71efd..be0b68b3cd9 100644 --- a/src/protocols/core/DataDevice.hpp +++ b/src/protocols/core/DataDevice.hpp @@ -190,7 +190,7 @@ class CWLDataDeviceProtocol : public IWaylandProtocol { void updateDrag(); void dropDrag(); void completeDrag(); - void resetDndState(); + void cleanupDndState(bool resetDevice, bool resetSource, bool simulateInput); bool wasDragSuccessful(); // diff --git a/src/xwayland/Dnd.cpp b/src/xwayland/Dnd.cpp index 16d166ce2f9..dd02e6e2608 100644 --- a/src/xwayland/Dnd.cpp +++ b/src/xwayland/Dnd.cpp @@ -6,6 +6,7 @@ #endif #include "../managers/XWaylandManager.hpp" #include "../desktop/WLSurface.hpp" +#include "../protocols/core/Compositor.hpp" using namespace Hyprutils::OS; @@ -20,6 +21,51 @@ static xcb_atom_t dndActionToAtom(uint32_t actions) { return XCB_ATOM_NONE; } + +void CX11DataDevice::sendDndEvent(xcb_window_t targetWindow, xcb_atom_t type, xcb_client_message_data_t& data) { + xcb_client_message_event_t event = { + .response_type = XCB_CLIENT_MESSAGE, + .format = 32, + .sequence = 0, + .window = targetWindow, + .type = type, + .data = data, + }; + + xcb_send_event(g_pXWayland->pWM->connection, 0, targetWindow, XCB_EVENT_MASK_NO_EVENT, (const char*)&event); + xcb_flush(g_pXWayland->pWM->connection); +} + +xcb_window_t CX11DataDevice::getProxyWindow(xcb_window_t window) { + xcb_window_t targetWindow = window; + xcb_get_property_cookie_t proxyCookie = + xcb_get_property(g_pXWayland->pWM->connection, XCB_PROPERTY_OFFSET, window, HYPRATOMS["XdndProxy"], XCB_ATOM_WINDOW, XCB_PROPERTY_OFFSET, XCB_PROPERTY_LENGTH); + xcb_get_property_reply_t* proxyReply = xcb_get_property_reply(g_pXWayland->pWM->connection, proxyCookie, nullptr); + + const auto isValidPropertyReply = [](xcb_get_property_reply_t* reply) { + return reply && reply->type == XCB_ATOM_WINDOW && reply->format == XCB_PROPERTY_FORMAT_32BIT && xcb_get_property_value_length(reply) == sizeof(xcb_window_t); + }; + + if (isValidPropertyReply(proxyReply)) { + xcb_window_t proxyWindow = *(xcb_window_t*)xcb_get_property_value(proxyReply); + + xcb_get_property_cookie_t proxyVerifyCookie = + xcb_get_property(g_pXWayland->pWM->connection, XCB_PROPERTY_OFFSET, proxyWindow, HYPRATOMS["XdndProxy"], XCB_ATOM_WINDOW, XCB_PROPERTY_OFFSET, XCB_PROPERTY_LENGTH); + xcb_get_property_reply_t* proxyVerifyReply = xcb_get_property_reply(g_pXWayland->pWM->connection, proxyVerifyCookie, nullptr); + + if (isValidPropertyReply(proxyVerifyReply)) { + xcb_window_t verifyWindow = *(xcb_window_t*)xcb_get_property_value(proxyVerifyReply); + if (verifyWindow == proxyWindow) { + targetWindow = proxyWindow; + Debug::log(LOG, "Using XdndProxy window {:x} for window {:x}", proxyWindow, window); + } + } + free(proxyVerifyReply); + } + free(proxyReply); + + return targetWindow; +} #endif eDataSourceType CX11DataOffer::type() { @@ -52,9 +98,6 @@ void CX11DataDevice::sendEnter(uint32_t serial, SP surf, con #ifndef NO_XWAYLAND auto XSURF = g_pXWayland->pWM->windowForWayland(surf); - if (offer == lastOffer) - return; - if (!XSURF) { Debug::log(ERR, "CX11DataDevice::sendEnter: No xwayland surface for destination"); return; @@ -67,56 +110,62 @@ void CX11DataDevice::sendEnter(uint32_t serial, SP surf, con return; } - xcb_set_selection_owner(g_pXWayland->pWM->connection, g_pXWayland->pWM->dndSelection.window, HYPRATOMS["XdndSelection"], XCB_TIME_CURRENT_TIME); - - xcb_client_message_data_t data = {0}; - data.data32[0] = g_pXWayland->pWM->dndSelection.window; - data.data32[1] = XDND_VERSION << 24; - - // let the client know it needs to check for DND_TYPE_LIST - data.data32[1] |= 1; - std::vector targets; // reserve to avoid reallocations targets.reserve(SOURCE->mimes().size()); - - for (auto& mime : SOURCE->mimes()) { - targets.emplace_back(g_pXWayland->pWM->mimeToAtom(mime)); + for (auto const& m : SOURCE->mimes()) { + targets.push_back(g_pXWayland->pWM->mimeToAtom(m)); } xcb_change_property(g_pXWayland->pWM->connection, XCB_PROP_MODE_REPLACE, g_pXWayland->pWM->dndSelection.window, HYPRATOMS["XdndTypeList"], XCB_ATOM_ATOM, 32, targets.size(), targets.data()); - g_pXWayland->pWM->sendDndEvent(surf, HYPRATOMS["XdndEnter"], data); + xcb_set_selection_owner(g_pXWayland->pWM->connection, g_pXWayland->pWM->dndSelection.window, HYPRATOMS["XdndSelection"], XCB_TIME_CURRENT_TIME); + xcb_flush(g_pXWayland->pWM->connection); + + xcb_window_t targetWindow = getProxyWindow(XSURF->xID); + + xcb_client_message_data_t data = {0}; + data.data32[0] = g_pXWayland->pWM->dndSelection.window; + data.data32[1] = XDND_VERSION << 24; + data.data32[1] |= 1; + + sendDndEvent(targetWindow, HYPRATOMS["XdndEnter"], data); lastSurface = XSURF; lastOffer = offer; - auto hlSurface = CWLSurface::fromResource(surf); + auto hlSurface = XSURF->surface.lock(); if (!hlSurface) { Debug::log(ERR, "CX11DataDevice::sendEnter: Non desktop x surface?!"); lastSurfaceCoords = {}; return; } - lastSurfaceCoords = hlSurface->getSurfaceBoxGlobal().value_or(CBox{}).pos(); + lastSurfaceCoords = g_pXWaylandManager->xwaylandToWaylandCoords(XSURF->geometry.pos()); #endif } +void CX11DataDevice::cleanupState() { + lastSurface.reset(); + lastOffer.reset(); + lastSurfaceCoords = {}; + lastTime = 0; +} + void CX11DataDevice::sendLeave() { #ifndef NO_XWAYLAND if (!lastSurface) return; + xcb_window_t targetWindow = getProxyWindow(lastSurface->xID); + xcb_client_message_data_t data = {0}; data.data32[0] = g_pXWayland->pWM->dndSelection.window; - g_pXWayland->pWM->sendDndEvent(lastSurface->surface.lock(), HYPRATOMS["XdndLeave"], data); + sendDndEvent(targetWindow, HYPRATOMS["XdndLeave"], data); - lastSurface.reset(); - lastOffer.reset(); - - xcb_set_selection_owner(g_pXWayland->pWM->connection, g_pXWayland->pWM->dndSelection.window, XCB_ATOM_NONE, XCB_TIME_CURRENT_TIME); + cleanupState(); #endif } @@ -125,32 +174,39 @@ void CX11DataDevice::sendMotion(uint32_t timeMs, const Vector2D& local) { if (!lastSurface || !lastOffer || !lastOffer->getSource()) return; + xcb_window_t targetWindow = getProxyWindow(lastSurface->xID); + const auto XCOORDS = g_pXWaylandManager->waylandToXWaylandCoords(lastSurfaceCoords + local); + const uint32_t coords = ((uint32_t)XCOORDS.x << 16) | (uint32_t)XCOORDS.y; xcb_client_message_data_t data = {0}; data.data32[0] = g_pXWayland->pWM->dndSelection.window; - data.data32[2] = (((int32_t)XCOORDS.x) << 16) | (int32_t)XCOORDS.y; + data.data32[2] = coords; data.data32[3] = timeMs; data.data32[4] = dndActionToAtom(lastOffer->getSource()->actions()); - g_pXWayland->pWM->sendDndEvent(lastSurface->surface.lock(), HYPRATOMS["XdndPosition"], data); + sendDndEvent(targetWindow, HYPRATOMS["XdndPosition"], data); + lastTime = timeMs; #endif } void CX11DataDevice::sendDrop() { #ifndef NO_XWAYLAND - if (!lastSurface || !lastOffer) + if (!lastSurface || !lastOffer) { + Debug::log(ERR, "CX11DataDevice::sendDrop: No surface or offer"); return; + } + + xcb_window_t targetWindow = getProxyWindow(lastSurface->xID); - // we don't have timeMs here, just send last time + 1 xcb_client_message_data_t data = {0}; data.data32[0] = g_pXWayland->pWM->dndSelection.window; - data.data32[2] = lastTime + 1; + data.data32[2] = lastTime; - g_pXWayland->pWM->sendDndEvent(lastSurface->surface.lock(), HYPRATOMS["XdndDrop"], data); + sendDndEvent(targetWindow, HYPRATOMS["XdndDrop"], data); - sendLeave(); + cleanupState(); #endif } @@ -175,15 +231,16 @@ std::vector CX11DataSource::mimes() { } void CX11DataSource::send(const std::string& mime, CFileDescriptor fd) { - ; + ; // no-op } void CX11DataSource::accepted(const std::string& mime) { - ; + ; // no-op } void CX11DataSource::cancelled() { - ; + dndSuccess = false; + dropped = false; } bool CX11DataSource::hasDnd() { @@ -195,11 +252,13 @@ bool CX11DataSource::dndDone() { } void CX11DataSource::error(uint32_t code, const std::string& msg) { - Debug::log(ERR, "CX11DataSource::error: this fn is a stub: code {} msg {}", code, msg); + Debug::log(ERR, "CX11DataSource error: code {} msg {}", code, msg); + dndSuccess = false; + dropped = false; } void CX11DataSource::sendDndFinished() { - ; + dndSuccess = true; } uint32_t CX11DataSource::actions() { @@ -211,9 +270,29 @@ eDataSourceType CX11DataSource::type() { } void CX11DataSource::sendDndDropPerformed() { - ; + dropped = true; } void CX11DataSource::sendDndAction(wl_data_device_manager_dnd_action a) { - ; + ; // no-op +} + +void CX11DataDevice::forceCleanupDnd() { +#ifndef NO_XWAYLAND + if (lastOffer) { + auto source = lastOffer->getSource(); + if (source) { + source->cancelled(); + source->sendDndFinished(); + } + } + + xcb_set_selection_owner(g_pXWayland->pWM->connection, XCB_ATOM_NONE, HYPRATOMS["XdndSelection"], XCB_TIME_CURRENT_TIME); + xcb_flush(g_pXWayland->pWM->connection); + + cleanupState(); + + g_pSeatManager->setPointerFocus(nullptr, {}); + g_pInputManager->simulateMouseMovement(); +#endif } diff --git a/src/xwayland/Dnd.hpp b/src/xwayland/Dnd.hpp index fb0307963f4..a23f4c004f7 100644 --- a/src/xwayland/Dnd.hpp +++ b/src/xwayland/Dnd.hpp @@ -1,11 +1,18 @@ #pragma once #include "../protocols/types/DataDevice.hpp" +#include "../managers/SeatManager.hpp" +#include "../managers/input/InputManager.hpp" #include #include +#include #define XDND_VERSION 5 +#define XCB_PROPERTY_FORMAT_32BIT 32 +#define XCB_PROPERTY_LENGTH 1 +#define XCB_PROPERTY_OFFSET 0 + class CXWaylandSurface; class CX11DataOffer : public IDataOffer { @@ -72,10 +79,16 @@ class CX11DataDevice : public IDataDevice { virtual void sendDrop(); virtual void sendSelection(SP offer); virtual eDataSourceType type(); + void forceCleanupDnd(); WP self; private: + void cleanupState(); +#ifndef NO_XWAYLAND + xcb_window_t getProxyWindow(xcb_window_t window); + void sendDndEvent(xcb_window_t targetWindow, xcb_atom_t type, xcb_client_message_data_t& data); +#endif WP lastSurface; WP lastOffer; Vector2D lastSurfaceCoords; diff --git a/src/xwayland/XWM.cpp b/src/xwayland/XWM.cpp index 68552dd6580..6bdef8181b3 100644 --- a/src/xwayland/XWM.cpp +++ b/src/xwayland/XWM.cpp @@ -1104,20 +1104,22 @@ void CXWM::dissociate(SP surf) { } 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 windows; - for (auto const& m : mappedSurfaces) { - windows.push_back(m->xID); + windows.reserve(mappedSurfaces.size()); + + for (auto const& s : mappedSurfaces) { + if (auto surf = s.lock(); surf) + windows.push_back(surf->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(); + windows.reserve(mappedSurfacesStacking.size()); - for (auto const& m : mappedSurfacesStacking) { - windows.push_back(m->xID); + for (auto const& s : mappedSurfacesStacking) { + if (auto surf = s.lock(); surf) + windows.push_back(surf->xID); } xcb_change_property(connection, XCB_PROP_MODE_REPLACE, screen->root, HYPRATOMS["_NET_CLIENT_LIST_STACKING"], XCB_ATOM_WINDOW, 32, windows.size(), windows.data()); @@ -1223,29 +1225,6 @@ void CXWM::setCursor(unsigned char* pixData, uint32_t stride, const Vector2D& si xcb_flush(connection); } -void CXWM::sendDndEvent(SP destination, xcb_atom_t type, xcb_client_message_data_t& data) { - auto XSURF = windowForWayland(destination); - - if (!XSURF) { - Debug::log(ERR, "[xwm] No xwayland surface for destination in sendDndEvent"); - return; - } - - xcb_client_message_event_t event = { - .response_type = XCB_CLIENT_MESSAGE, - .format = 32, - .sequence = 0, - .window = XSURF->xID, - .type = type, - .data = data, - }; - - xcb_send_event(g_pXWayland->pWM->connection, - 0, // propagate - XSURF->xID, XCB_EVENT_MASK_NO_EVENT, (const char*)&event); - xcb_flush(g_pXWayland->pWM->connection); -} - SP CXWM::getDataDevice() { return dndDataDevice; } diff --git a/src/xwayland/XWM.hpp b/src/xwayland/XWM.hpp index 73a02a861a9..cdf51c4e342 100644 --- a/src/xwayland/XWM.hpp +++ b/src/xwayland/XWM.hpp @@ -143,8 +143,6 @@ class CXWM { void updateClientList(); - void sendDndEvent(SP destination, xcb_atom_t type, xcb_client_message_data_t& data); - // event handlers void handleCreate(xcb_create_notify_event_t* e); void handleDestroy(xcb_destroy_notify_event_t* e);