diff --git a/debian/patches/xwayland.patch b/debian/patches/xwayland.patch new file mode 100644 index 0000000..507a703 --- /dev/null +++ b/debian/patches/xwayland.patch @@ -0,0 +1,554 @@ +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); + + \ No newline at end of file