diff options
-rw-r--r-- | src/managers/XWaylandManager.cpp | 30 | ||||
-rw-r--r-- | src/managers/XWaylandManager.hpp | 1 | ||||
-rw-r--r-- | src/protocols/core/DataDevice.cpp | 151 | ||||
-rw-r--r-- | src/protocols/core/DataDevice.hpp | 69 | ||||
-rw-r--r-- | src/protocols/types/DataDevice.cpp | 12 | ||||
-rw-r--r-- | src/protocols/types/DataDevice.hpp | 39 | ||||
-rw-r--r-- | src/xwayland/Dnd.cpp | 211 | ||||
-rw-r--r-- | src/xwayland/Dnd.hpp | 82 | ||||
-rw-r--r-- | src/xwayland/XWM.cpp | 147 | ||||
-rw-r--r-- | src/xwayland/XWM.hpp | 69 |
10 files changed, 701 insertions, 110 deletions
diff --git a/src/managers/XWaylandManager.cpp b/src/managers/XWaylandManager.cpp index 832173eb..5e5c6262 100644 --- a/src/managers/XWaylandManager.cpp +++ b/src/managers/XWaylandManager.cpp @@ -213,6 +213,36 @@ void CHyprXWaylandManager::setWindowFullscreen(PHLWINDOW pWindow, bool fullscree pWindow->m_pXDGSurface->toplevel->setFullscreen(fullscreen); } +Vector2D CHyprXWaylandManager::waylandToXWaylandCoords(const Vector2D& coord) { + static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>("xwayland:force_zero_scaling"); + + PHLMONITOR pMonitor = nullptr; + double bestDistance = __FLT_MAX__; + for (const auto& m : g_pCompositor->m_vMonitors) { + const auto SIZ = *PXWLFORCESCALEZERO ? m->vecTransformedSize : m->vecSize; + + double distance = vecToRectDistanceSquared(coord, {m->vecPosition.x, m->vecPosition.y}, {m->vecPosition.x + SIZ.x - 1, m->vecPosition.y + SIZ.y - 1}); + + if (distance < bestDistance) { + bestDistance = distance; + pMonitor = m; + } + } + + if (!pMonitor) + return Vector2D{}; + + // get local coords + Vector2D result = coord - pMonitor->vecPosition; + // if scaled, scale + if (*PXWLFORCESCALEZERO) + result *= pMonitor->scale; + // add pos + result += pMonitor->vecXWaylandPosition; + + return result; +} + Vector2D CHyprXWaylandManager::xwaylandToWaylandCoords(const Vector2D& coord) { static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>("xwayland:force_zero_scaling"); diff --git a/src/managers/XWaylandManager.hpp b/src/managers/XWaylandManager.hpp index 508a20d6..18627a78 100644 --- a/src/managers/XWaylandManager.hpp +++ b/src/managers/XWaylandManager.hpp @@ -22,6 +22,7 @@ class CHyprXWaylandManager { bool shouldBeFloated(PHLWINDOW, bool pending = false); void checkBorders(PHLWINDOW); Vector2D xwaylandToWaylandCoords(const Vector2D&); + Vector2D waylandToXWaylandCoords(const Vector2D&); }; inline std::unique_ptr<CHyprXWaylandManager> g_pXWaylandManager;
\ No newline at end of file diff --git a/src/protocols/core/DataDevice.cpp b/src/protocols/core/DataDevice.cpp index d23e3415..9447f04f 100644 --- a/src/protocols/core/DataDevice.cpp +++ b/src/protocols/core/DataDevice.cpp @@ -6,6 +6,8 @@ #include "../../Compositor.hpp" #include "Seat.hpp" #include "Compositor.hpp" +#include "../../xwayland/XWayland.hpp" +#include "../../xwayland/Server.hpp" CWLDataOfferResource::CWLDataOfferResource(SP<CWlDataOffer> resource_, SP<IDataSource> source_) : source(source_), resource(resource_) { if (!good()) @@ -103,6 +105,22 @@ void CWLDataOfferResource::sendData() { } } +eDataSourceType CWLDataOfferResource::type() { + return DATA_SOURCE_TYPE_WAYLAND; +} + +SP<CWLDataOfferResource> CWLDataOfferResource::getWayland() { + return self.lock(); +} + +SP<CX11DataOffer> CWLDataOfferResource::getX11() { + return nullptr; +} + +SP<IDataSource> CWLDataOfferResource::getSource() { + return source.lock(); +} + CWLDataSourceResource::CWLDataSourceResource(SP<CWlDataSource> resource_, SP<CWLDataDeviceResource> device_) : device(device_), resource(resource_) { if (!good()) return; @@ -209,6 +227,10 @@ uint32_t CWLDataSourceResource::actions() { return supportedActions; } +eDataSourceType CWLDataSourceResource::type() { + return DATA_SOURCE_TYPE_WAYLAND; +} + CWLDataDeviceResource::CWLDataDeviceResource(SP<CWlDataDevice> resource_) : resource(resource_) { if (!good()) return; @@ -260,15 +282,18 @@ wl_client* CWLDataDeviceResource::client() { return pClient; } -void CWLDataDeviceResource::sendDataOffer(SP<CWLDataOfferResource> offer) { - if (offer) - resource->sendDataOffer(offer->resource.get()); - else +void CWLDataDeviceResource::sendDataOffer(SP<IDataOffer> offer) { + if (!offer) resource->sendDataOfferRaw(nullptr); + else if (const auto WL = offer->getWayland(); WL) + resource->sendDataOffer(WL->resource.get()); + //FIXME: X11 } -void CWLDataDeviceResource::sendEnter(uint32_t serial, SP<CWLSurfaceResource> surf, const Vector2D& local, SP<CWLDataOfferResource> offer) { - resource->sendEnterRaw(serial, surf->getResource()->resource(), wl_fixed_from_double(local.x), wl_fixed_from_double(local.y), offer->resource->resource()); +void CWLDataDeviceResource::sendEnter(uint32_t serial, SP<CWLSurfaceResource> surf, const Vector2D& local, SP<IDataOffer> offer) { + if (const auto WL = offer->getWayland(); WL) + resource->sendEnterRaw(serial, surf->getResource()->resource(), wl_fixed_from_double(local.x), wl_fixed_from_double(local.y), WL->resource->resource()); + // FIXME: X11 } void CWLDataDeviceResource::sendLeave() { @@ -283,11 +308,23 @@ void CWLDataDeviceResource::sendDrop() { resource->sendDrop(); } -void CWLDataDeviceResource::sendSelection(SP<CWLDataOfferResource> offer) { +void CWLDataDeviceResource::sendSelection(SP<IDataOffer> offer) { if (!offer) resource->sendSelectionRaw(nullptr); - else - resource->sendSelection(offer->resource.get()); + else if (const auto WL = offer->getWayland(); WL) + resource->sendSelection(WL->resource.get()); +} + +eDataSourceType CWLDataDeviceResource::type() { + return DATA_SOURCE_TYPE_WAYLAND; +} + +SP<CWLDataDeviceResource> CWLDataDeviceResource::getWayland() { + return self.lock(); +} + +SP<CX11DataDevice> CWLDataDeviceResource::getX11() { + return nullptr; } CWLDataDeviceManagerResource::CWLDataDeviceManagerResource(SP<CWlDataDeviceManager> resource_) : resource(resource_) { @@ -377,32 +414,53 @@ void CWLDataDeviceProtocol::destroyResource(CWLDataOfferResource* resource) { std::erase_if(m_vOffers, [&](const auto& other) { return other.get() == resource; }); } -SP<CWLDataDeviceResource> CWLDataDeviceProtocol::dataDeviceForClient(wl_client* c) { +SP<IDataDevice> CWLDataDeviceProtocol::dataDeviceForClient(wl_client* c) { +#ifndef NO_XWAYLAND + if (c == g_pXWayland->pServer->xwaylandClient) + return g_pXWayland->pWM->getDataDevice(); +#endif + auto it = std::find_if(m_vDevices.begin(), m_vDevices.end(), [c](const auto& e) { return e->client() == c; }); if (it == m_vDevices.end()) return nullptr; return *it; } -void CWLDataDeviceProtocol::sendSelectionToDevice(SP<CWLDataDeviceResource> dev, SP<IDataSource> sel) { +void CWLDataDeviceProtocol::sendSelectionToDevice(SP<IDataDevice> dev, SP<IDataSource> sel) { if (!sel) { dev->sendSelection(nullptr); return; } - const auto OFFER = m_vOffers.emplace_back(makeShared<CWLDataOfferResource>(makeShared<CWlDataOffer>(dev->resource->client(), dev->resource->version(), 0), sel)); + SP<IDataOffer> offer; - if (!OFFER->good()) { - dev->resource->noMemory(); - m_vOffers.pop_back(); + if (const auto WL = dev->getWayland(); WL) { + const auto OFFER = m_vOffers.emplace_back(makeShared<CWLDataOfferResource>(makeShared<CWlDataOffer>(WL->resource->client(), WL->resource->version(), 0), sel)); + if (!OFFER->good()) { + WL->resource->noMemory(); + m_vOffers.pop_back(); + return; + } + OFFER->source = sel; + OFFER->self = OFFER; + offer = OFFER; + } +#ifndef NO_XWAYLAND + else if (const auto X11 = dev->getX11(); X11) + offer = g_pXWayland->pWM->createX11DataOffer(g_pSeatManager->state.keyboardFocus.lock(), sel); +#endif + + if (!offer) { + LOGM(ERR, "No offer could be created in sendSelectionToDevice"); return; } - LOGM(LOG, "New offer {:x} for data source {:x}", (uintptr_t)OFFER.get(), (uintptr_t)sel.get()); + LOGM(LOG, "New {} offer {:x} for data source {:x}", offer->type() == DATA_SOURCE_TYPE_WAYLAND ? "wayland" : "X11", (uintptr_t)offer.get(), (uintptr_t)sel.get()); - dev->sendDataOffer(OFFER); - OFFER->sendData(); - dev->sendSelection(OFFER); + dev->sendDataOffer(offer); + if (const auto WL = offer->getWayland(); WL) + WL->sendData(); + dev->sendSelection(offer); } void CWLDataDeviceProtocol::onDestroyDataSource(WP<CWLDataSourceResource> source) { @@ -424,7 +482,7 @@ void CWLDataDeviceProtocol::setSelection(SP<IDataSource> source) { return; auto DESTDEVICE = dataDeviceForClient(g_pSeatManager->state.keyboardFocusResource->client()); - if (DESTDEVICE) + if (DESTDEVICE && DESTDEVICE->type() == DATA_SOURCE_TYPE_WAYLAND) sendSelectionToDevice(DESTDEVICE, nullptr); return; @@ -442,6 +500,11 @@ void CWLDataDeviceProtocol::setSelection(SP<IDataSource> source) { return; } + if (DESTDEVICE->type() != DATA_SOURCE_TYPE_WAYLAND) { + LOGM(LOG, "CWLDataDeviceProtocol::setSelection: ignoring X11 data device"); + return; + } + sendSelectionToDevice(DESTDEVICE, source); } @@ -589,22 +652,38 @@ void CWLDataDeviceProtocol::updateDrag() { if (!dnd.focusedDevice) return; - // make a new offer - const auto OFFER = m_vOffers.emplace_back( - makeShared<CWLDataOfferResource>(makeShared<CWlDataOffer>(dnd.focusedDevice->resource->client(), dnd.focusedDevice->resource->version(), 0), dnd.currentSource.lock())); + SP<IDataOffer> offer; + + if (const auto WL = dnd.focusedDevice->getWayland(); WL) { + const auto OFFER = + m_vOffers.emplace_back(makeShared<CWLDataOfferResource>(makeShared<CWlDataOffer>(WL->resource->client(), WL->resource->version(), 0), dnd.currentSource.lock())); + if (!OFFER->good()) { + WL->resource->noMemory(); + m_vOffers.pop_back(); + return; + } + OFFER->source = dnd.currentSource; + OFFER->self = OFFER; + offer = OFFER; + } +#ifndef NO_XWAYLAND + else if (const auto X11 = dnd.focusedDevice->getX11(); X11) + offer = g_pXWayland->pWM->createX11DataOffer(g_pSeatManager->state.keyboardFocus.lock(), dnd.currentSource.lock()); +#endif - if (!OFFER->good()) { - dnd.currentSource->resource->noMemory(); - m_vOffers.pop_back(); + if (!offer) { + LOGM(ERR, "No offer could be created in updateDrag"); return; } - LOGM(LOG, "New dnd offer {:x} for data source {:x}", (uintptr_t)OFFER.get(), (uintptr_t)dnd.currentSource.get()); + LOGM(LOG, "New {} dnd offer {:x} for data source {:x}", offer->type() == DATA_SOURCE_TYPE_WAYLAND ? "wayland" : "X11", (uintptr_t)offer.get(), + (uintptr_t)dnd.currentSource.get()); - dnd.focusedDevice->sendDataOffer(OFFER); - OFFER->sendData(); + dnd.focusedDevice->sendDataOffer(offer); + if (const auto WL = offer->getWayland(); WL) + WL->sendData(); dnd.focusedDevice->sendEnter(wl_display_next_serial(g_pCompositor->m_sWLDisplay), g_pSeatManager->state.dndPointerFocus.lock(), - g_pSeatManager->state.dndPointerFocus->current.size / 2.F, OFFER); + g_pSeatManager->state.dndPointerFocus->current.size / 2.F, offer); } void CWLDataDeviceProtocol::resetDndState() { @@ -651,6 +730,18 @@ bool CWLDataDeviceProtocol::wasDragSuccessful() { return true; } +#ifndef NO_XWAYLAND + 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; + } +#endif + return false; } diff --git a/src/protocols/core/DataDevice.hpp b/src/protocols/core/DataDevice.hpp index bf22b511..ae22e474 100644 --- a/src/protocols/core/DataDevice.hpp +++ b/src/protocols/core/DataDevice.hpp @@ -26,21 +26,27 @@ class CWLDataOfferResource; class CWLSurfaceResource; class CMonitor; -class CWLDataOfferResource { +class CWLDataOfferResource : public IDataOffer { public: CWLDataOfferResource(SP<CWlDataOffer> resource_, SP<IDataSource> source_); ~CWLDataOfferResource(); - bool good(); - void sendData(); + bool good(); + void sendData(); + + virtual eDataSourceType type(); + virtual SP<CWLDataOfferResource> getWayland(); + virtual SP<CX11DataOffer> getX11(); + virtual SP<IDataSource> getSource(); - WP<IDataSource> source; + WP<IDataSource> source; + WP<CWLDataOfferResource> self; - bool dead = false; - bool accepted = false; - bool recvd = false; + bool dead = false; + bool accepted = false; + bool recvd = false; - uint32_t actions = 0; + uint32_t actions = 0; private: SP<CWlDataOffer> resource; @@ -66,9 +72,9 @@ class CWLDataSourceResource : public IDataSource { virtual void error(uint32_t code, const std::string& msg); virtual void sendDndFinished(); virtual uint32_t actions(); // wl_data_device_manager.dnd_action - - void sendDndDropPerformed(); - void sendDndAction(wl_data_device_manager_dnd_action a); + virtual eDataSourceType type(); + virtual void sendDndDropPerformed(); + virtual void sendDndAction(wl_data_device_manager_dnd_action a); bool used = false; bool dnd = false; @@ -88,21 +94,24 @@ class CWLDataSourceResource : public IDataSource { friend class CWLDataDeviceProtocol; }; -class CWLDataDeviceResource { +class CWLDataDeviceResource : public IDataDevice { public: CWLDataDeviceResource(SP<CWlDataDevice> resource_); - bool good(); - wl_client* client(); + bool good(); + wl_client* client(); - void sendDataOffer(SP<CWLDataOfferResource> offer); - void sendEnter(uint32_t serial, SP<CWLSurfaceResource> surf, const Vector2D& local, SP<CWLDataOfferResource> offer); - void sendLeave(); - void sendMotion(uint32_t timeMs, const Vector2D& local); - void sendDrop(); - void sendSelection(SP<CWLDataOfferResource> offer); + virtual SP<CWLDataDeviceResource> getWayland(); + virtual SP<CX11DataDevice> getX11(); + virtual void sendDataOffer(SP<IDataOffer> offer); + virtual void sendEnter(uint32_t serial, SP<CWLSurfaceResource> surf, const Vector2D& local, SP<IDataOffer> offer); + virtual void sendLeave(); + virtual void sendMotion(uint32_t timeMs, const Vector2D& local); + virtual void sendDrop(); + virtual void sendSelection(SP<IDataOffer> offer); + virtual eDataSourceType type(); - WP<CWLDataDeviceResource> self; + WP<CWLDataDeviceResource> self; private: SP<CWlDataDevice> resource; @@ -152,19 +161,19 @@ class CWLDataDeviceProtocol : public IWaylandProtocol { void onDestroyDataSource(WP<CWLDataSourceResource> source); void setSelection(SP<IDataSource> source); - void sendSelectionToDevice(SP<CWLDataDeviceResource> dev, SP<IDataSource> sel); + void sendSelectionToDevice(SP<IDataDevice> dev, SP<IDataSource> sel); void updateSelection(); void onKeyboardFocus(); void onDndPointerFocus(); struct { - WP<CWLDataDeviceResource> focusedDevice; - WP<CWLDataSourceResource> currentSource; - WP<CWLSurfaceResource> dndSurface; - WP<CWLSurfaceResource> originSurface; - bool overriddenCursor = false; - CHyprSignalListener dndSurfaceDestroy; - CHyprSignalListener dndSurfaceCommit; + WP<IDataDevice> focusedDevice; + WP<IDataSource> currentSource; + WP<CWLSurfaceResource> dndSurface; + WP<CWLSurfaceResource> originSurface; + bool overriddenCursor = false; + CHyprSignalListener dndSurfaceDestroy; + CHyprSignalListener dndSurfaceCommit; // for ending a dnd SP<HOOK_CALLBACK_FN> mouseMove; @@ -182,7 +191,7 @@ class CWLDataDeviceProtocol : public IWaylandProtocol { bool wasDragSuccessful(); // - SP<CWLDataDeviceResource> dataDeviceForClient(wl_client*); + SP<IDataDevice> dataDeviceForClient(wl_client*); friend class CSeatManager; friend class CWLDataDeviceManagerResource; diff --git a/src/protocols/types/DataDevice.cpp b/src/protocols/types/DataDevice.cpp index 36a7a157..fef11b64 100644 --- a/src/protocols/types/DataDevice.cpp +++ b/src/protocols/types/DataDevice.cpp @@ -27,3 +27,15 @@ void IDataSource::sendDndFinished() { uint32_t IDataSource::actions() { return 7; // all } + +void IDataSource::sendDndDropPerformed() { + ; +} + +void IDataSource::sendDndAction(wl_data_device_manager_dnd_action a) { + ; +} + +void IDataOffer::markDead() { + ; +} diff --git a/src/protocols/types/DataDevice.hpp b/src/protocols/types/DataDevice.hpp index 80f75b8e..62f10de2 100644 --- a/src/protocols/types/DataDevice.hpp +++ b/src/protocols/types/DataDevice.hpp @@ -4,6 +4,15 @@ #include <vector> #include <cstdint> #include "../../helpers/signal/Signal.hpp" +#include <wayland-server-protocol.h> +#include "../../helpers/memory/Memory.hpp" +#include "../../helpers/math/Math.hpp" + +class CWLDataOfferResource; +class CX11DataOffer; +class CX11DataDevice; +class CWLDataDeviceResource; +class CWLSurfaceResource; enum eDataSourceType : uint8_t { DATA_SOURCE_TYPE_WAYLAND = 0, @@ -27,6 +36,8 @@ class IDataSource { virtual void error(uint32_t code, const std::string& msg) = 0; virtual eDataSourceType type(); virtual uint32_t actions(); // wl_data_device_manager.dnd_action + virtual void sendDndDropPerformed(); + virtual void sendDndAction(wl_data_device_manager_dnd_action a); struct { CSignal destroy; @@ -35,3 +46,31 @@ class IDataSource { private: bool wasUsed = false; }; + +class IDataOffer { + public: + IDataOffer() = default; + virtual ~IDataOffer() = default; + + virtual eDataSourceType type() = 0; + virtual SP<CWLDataOfferResource> getWayland() = 0; + virtual SP<CX11DataOffer> getX11() = 0; + virtual SP<IDataSource> getSource() = 0; + virtual void markDead(); +}; + +class IDataDevice { + public: + IDataDevice() = default; + virtual ~IDataDevice() = default; + + virtual SP<CWLDataDeviceResource> getWayland() = 0; + virtual SP<CX11DataDevice> getX11() = 0; + virtual void sendDataOffer(SP<IDataOffer> offer) = 0; + virtual void sendEnter(uint32_t serial, SP<CWLSurfaceResource> surf, const Vector2D& local, SP<IDataOffer> offer) = 0; + virtual void sendLeave() = 0; + virtual void sendMotion(uint32_t timeMs, const Vector2D& local) = 0; + virtual void sendDrop() = 0; + virtual void sendSelection(SP<IDataOffer> offer) = 0; + virtual eDataSourceType type() = 0; +}; diff --git a/src/xwayland/Dnd.cpp b/src/xwayland/Dnd.cpp new file mode 100644 index 00000000..488ee8dd --- /dev/null +++ b/src/xwayland/Dnd.cpp @@ -0,0 +1,211 @@ +#include "Dnd.hpp" +#include "XWM.hpp" +#include "XWayland.hpp" +#include "Server.hpp" +#include "../managers/XWaylandManager.hpp" +#include "../desktop/WLSurface.hpp" + +static xcb_atom_t dndActionToAtom(uint32_t actions) { + if (actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) + return HYPRATOMS["XdndActionCopy"]; + else if (actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) + return HYPRATOMS["XdndActionMove"]; + else if (actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) + return HYPRATOMS["XdndActionAsk"]; + + return XCB_ATOM_NONE; +} + +eDataSourceType CX11DataOffer::type() { + return DATA_SOURCE_TYPE_X11; +} + +SP<CWLDataOfferResource> CX11DataOffer::getWayland() { + return nullptr; +} + +SP<CX11DataOffer> CX11DataOffer::getX11() { + return self.lock(); +} + +SP<IDataSource> CX11DataOffer::getSource() { + return source.lock(); +} + +void CX11DataOffer::markDead() { +#ifndef NO_XWAYLAND + std::erase(g_pXWayland->pWM->dndDataOffers, self); +#endif +} + +void CX11DataDevice::sendDataOffer(SP<IDataOffer> offer) { + ; // no-op, I don't think this has an X equiv +} + +void CX11DataDevice::sendEnter(uint32_t serial, SP<CWLSurfaceResource> surf, const Vector2D& local, SP<IDataOffer> offer) { +#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; + } + + auto SOURCE = offer->getSource(); + + if (!SOURCE) { + Debug::log(ERR, "CX11DataDevice::sendEnter: No source"); + 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<xcb_atom_t> targets; + + for (auto& mime : SOURCE->mimes()) { + targets.emplace_back(g_pXWayland->pWM->mimeToAtom(mime)); + } + + 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); + + lastSurface = XSURF; + lastOffer = offer; + + auto hlSurface = CWLSurface::fromResource(surf); + if (!hlSurface) { + Debug::log(ERR, "CX11DataDevice::sendEnter: Non desktop x surface?!"); + lastSurfaceCoords = {}; + return; + } + + lastSurfaceCoords = hlSurface->getSurfaceBoxGlobal().value_or(CBox{}).pos(); +#endif +} + +void CX11DataDevice::sendLeave() { +#ifndef NO_XWAYLAND + if (!lastSurface) + return; + + 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); + + lastSurface.reset(); + lastOffer.reset(); + + xcb_set_selection_owner(g_pXWayland->pWM->connection, g_pXWayland->pWM->dndSelection.window, XCB_ATOM_NONE, XCB_TIME_CURRENT_TIME); +#endif +} + +void CX11DataDevice::sendMotion(uint32_t timeMs, const Vector2D& local) { +#ifndef NO_XWAYLAND + if (!lastSurface || !lastOffer || !lastOffer->getSource()) + return; + + const auto XCOORDS = g_pXWaylandManager->waylandToXWaylandCoords(lastSurfaceCoords + local); + + 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[3] = timeMs; + data.data32[4] = dndActionToAtom(lastOffer->getSource()->actions()); + + g_pXWayland->pWM->sendDndEvent(lastSurface->surface.lock(), HYPRATOMS["XdndPosition"], data); + lastTime = timeMs; +#endif +} + +void CX11DataDevice::sendDrop() { +#ifndef NO_XWAYLAND + if (!lastSurface || !lastOffer) + return; + + // 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; + + g_pXWayland->pWM->sendDndEvent(lastSurface->surface.lock(), HYPRATOMS["XdndDrop"], data); + + sendLeave(); +#endif +} + +void CX11DataDevice::sendSelection(SP<IDataOffer> offer) { + ; // no-op. Selection is done separately. +} + +eDataSourceType CX11DataDevice::type() { + return DATA_SOURCE_TYPE_X11; +} + +SP<CWLDataDeviceResource> CX11DataDevice::getWayland() { + return nullptr; +} + +SP<CX11DataDevice> CX11DataDevice::getX11() { + return self.lock(); +} + +std::vector<std::string> CX11DataSource::mimes() { + return mimeTypes; +} + +void CX11DataSource::send(const std::string& mime, uint32_t fd) { + ; +} + +void CX11DataSource::accepted(const std::string& mime) { + ; +} + +void CX11DataSource::cancelled() { + ; +} + +bool CX11DataSource::hasDnd() { + return dnd; +} + +bool CX11DataSource::dndDone() { + return dropped; +} + +void CX11DataSource::error(uint32_t code, const std::string& msg) { + Debug::log(ERR, "CX11DataSource::error: this fn is a stub: code {} msg {}", code, msg); +} + +void CX11DataSource::sendDndFinished() { + ; +} + +uint32_t CX11DataSource::actions() { + return supportedActions; +} + +eDataSourceType CX11DataSource::type() { + return DATA_SOURCE_TYPE_X11; +} + +void CX11DataSource::sendDndDropPerformed() { + ; +} + +void CX11DataSource::sendDndAction(wl_data_device_manager_dnd_action a) { + ; +} diff --git a/src/xwayland/Dnd.hpp b/src/xwayland/Dnd.hpp new file mode 100644 index 00000000..8da60ddd --- /dev/null +++ b/src/xwayland/Dnd.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include "../protocols/types/DataDevice.hpp" +#include <wayland-server-protocol.h> + +#define XDND_VERSION 5 + +class CXWaylandSurface; + +class CX11DataOffer : public IDataOffer { + public: + CX11DataOffer() = default; + ~CX11DataOffer() = default; + + virtual eDataSourceType type(); + virtual SP<CWLDataOfferResource> getWayland(); + virtual SP<CX11DataOffer> getX11(); + virtual SP<IDataSource> getSource(); + virtual void markDead(); + + WP<IDataSource> source; + WP<CX11DataOffer> self; + WP<CXWaylandSurface> xwaylandSurface; + + bool dead = false; + bool accepted = false; + bool recvd = false; + + uint32_t actions = 0; +}; + +class CX11DataSource : public IDataSource { + public: + CX11DataSource() = default; + ~CX11DataSource() = default; + + virtual std::vector<std::string> mimes(); + virtual void send(const std::string& mime, uint32_t fd); + virtual void accepted(const std::string& mime); + virtual void cancelled(); + virtual bool hasDnd(); + virtual bool dndDone(); + virtual void error(uint32_t code, const std::string& msg); + virtual void sendDndFinished(); + virtual uint32_t actions(); // wl_data_device_manager.dnd_action + virtual eDataSourceType type(); + virtual void sendDndDropPerformed(); + virtual void sendDndAction(wl_data_device_manager_dnd_action a); + + bool used = false; + bool dnd = true; + bool dndSuccess = false; + bool dropped = false; + + WP<CX11DataSource> self; + + std::vector<std::string> mimeTypes; + uint32_t supportedActions = 0; +}; + +class CX11DataDevice : public IDataDevice { + public: + CX11DataDevice() = default; + + virtual SP<CWLDataDeviceResource> getWayland(); + virtual SP<CX11DataDevice> getX11(); + virtual void sendDataOffer(SP<IDataOffer> offer); + virtual void sendEnter(uint32_t serial, SP<CWLSurfaceResource> surf, const Vector2D& local, SP<IDataOffer> offer); + virtual void sendLeave(); + virtual void sendMotion(uint32_t timeMs, const Vector2D& local); + virtual void sendDrop(); + virtual void sendSelection(SP<IDataOffer> offer); + virtual eDataSourceType type(); + + WP<CX11DataDevice> self; + + private: + WP<CXWaylandSurface> lastSurface; + WP<IDataOffer> lastOffer; + Vector2D lastSurfaceCoords; + uint32_t lastTime = 0; +}; diff --git a/src/xwayland/XWM.cpp b/src/xwayland/XWM.cpp index efdf4b5f..ec563375 100644 --- a/src/xwayland/XWM.cpp +++ b/src/xwayland/XWM.cpp @@ -387,6 +387,28 @@ void CXWM::handleClientMessage(xcb_client_message_event_t* e) { } } else if (e->type == HYPRATOMS["_NET_ACTIVE_WINDOW"]) { XSURF->events.activate.emit(); + } else if (e->type == HYPRATOMS["XdndStatus"]) { + if (dndDataOffers.empty() || !dndDataOffers.at(0)->getSource()) { + Debug::log(TRACE, "[xwm] Rejecting XdndStatus message: nothing to get"); + return; + } + + xcb_client_message_data_t* data = &e->data; + const bool ACCEPTED = data->data32[1] & 1; + + if (ACCEPTED) + dndDataOffers.at(0)->getSource()->accepted(""); + + Debug::log(LOG, "[xwm] XdndStatus: accepted: {}"); + } else if (e->type == HYPRATOMS["XdndFinished"]) { + if (dndDataOffers.empty() || !dndDataOffers.at(0)->getSource()) { + Debug::log(TRACE, "[xwm] Rejecting XdndFinished message: nothing to get"); + return; + } + + dndDataOffers.at(0)->getSource()->sendDndFinished(); + + Debug::log(LOG, "[xwm] XdndFinished"); } else { Debug::log(TRACE, "[xwm] Unhandled message prop {} -> {}", e->type, propName); return; @@ -545,22 +567,22 @@ std::string CXWM::mimeFromAtom(xcb_atom_t atom) { void CXWM::handleSelectionNotify(xcb_selection_notify_event_t* e) { Debug::log(TRACE, "[xwm] Selection notify for {} prop {} target {}", e->selection, e->property, e->target); - SXSelection& sel = clipboard; + SXSelection* sel = getSelection(e->selection); if (e->property == XCB_ATOM_NONE) { - if (sel.transfer) { + if (sel->transfer) { Debug::log(TRACE, "[xwm] converting selection failed"); - sel.transfer.reset(); + sel->transfer.reset(); } - } else if (e->target == HYPRATOMS["TARGETS"]) { + } else if (e->target == HYPRATOMS["TARGETS"] && sel == &clipboard) { if (!focusedSurface) { Debug::log(TRACE, "[xwm] denying access to write to clipboard because no X client is in focus"); return; } - setClipboardToWayland(sel); - } else if (sel.transfer) - getTransferData(sel); + setClipboardToWayland(*sel); + } else if (sel->transfer) + getTransferData(*sel); } bool CXWM::handleSelectionPropertyNotify(xcb_property_notify_event_t* e) { @@ -571,13 +593,22 @@ bool CXWM::handleSelectionPropertyNotify(xcb_property_notify_event_t* e) { return false; } +SXSelection* CXWM::getSelection(xcb_atom_t atom) { + if (atom == HYPRATOMS["CLIPBOARD"]) + return &clipboard; + else if (atom == HYPRATOMS["XdndSelection"]) + return &dndSelection; + + return nullptr; +} + void CXWM::handleSelectionRequest(xcb_selection_request_event_t* e) { Debug::log(TRACE, "[xwm] Selection request for {} prop {} target {} time {} requestor {} selection {}", e->selection, e->property, e->target, e->time, e->requestor, e->selection); - SXSelection& sel = clipboard; + SXSelection* sel = getSelection(e->selection); - if (!g_pSeatManager->selection.currentSelection) { + if (!sel) { Debug::log(ERR, "[xwm] No selection"); selectionSendNotify(e, false); return; @@ -588,8 +619,8 @@ void CXWM::handleSelectionRequest(xcb_selection_request_event_t* e) { 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); + 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; } @@ -615,12 +646,11 @@ void CXWM::handleSelectionRequest(xcb_selection_request_event_t* e) { 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); + 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") { @@ -629,7 +659,7 @@ void CXWM::handleSelectionRequest(xcb_selection_request_event_t* e) { return; } - if (!sel.sendData(e, mime)) { + if (!sel->sendData(e, mime)) { Debug::log(LOG, "[xwm] Failed to send selection :("); selectionSendNotify(e, false); return; @@ -641,24 +671,27 @@ bool CXWM::handleSelectionXFixesNotify(xcb_xfixes_selection_notify_event_t* e) { Debug::log(TRACE, "[xwm] Selection xfixes notify for {}", e->selection); // IMPORTANT: mind the g_pSeatManager below - SXSelection& sel = clipboard; + SXSelection* sel = getSelection(e->selection); + + if (sel == &dndSelection) + return true; if (e->owner == XCB_WINDOW_NONE) { - if (sel.owner != sel.window) + if (sel->owner != sel->window && sel == &clipboard) g_pSeatManager->setCurrentSelection(nullptr); - sel.owner = 0; + sel->owner = 0; return true; } - sel.owner = e->owner; + sel->owner = e->owner; - if (sel.owner == sel.window) { - sel.timestamp = e->timestamp; + 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_convert_selection(connection, sel->window, HYPRATOMS["CLIPBOARD"], HYPRATOMS["TARGETS"], HYPRATOMS["_WL_SELECTION"], e->timestamp); xcb_flush(connection); return true; @@ -854,6 +887,8 @@ CXWM::CXWM() : connection(g_pXWayland->pServer->xwmFDs[0]) { return; } + dndDataDevice->self = dndDataDevice; + xcb_screen_iterator_t screen_iterator = xcb_setup_roots_iterator(xcb_get_setup(connection)); screen = screen_iterator.data; @@ -1015,6 +1050,15 @@ void CXWM::readWindowData(SP<CXWaylandSurface> surf) { } } +SP<CXWaylandSurface> CXWM::windowForWayland(SP<CWLSurfaceResource> surf) { + for (auto& s : surfaces) { + if (s->surface == surf) + return s; + } + + return nullptr; +} + void CXWM::associate(SP<CXWaylandSurface> surf, SP<CWLSurfaceResource> wlSurf) { if (surf->surface) return; @@ -1068,7 +1112,7 @@ void CXWM::updateClientList() { } bool CXWM::isWMWindow(xcb_window_t w) { - return w == wmWindow || w == clipboard.window; + return w == wmWindow || w == clipboard.window || w == dndSelection.window; } void CXWM::updateOverrideRedirect(SP<CXWaylandSurface> surf, bool overrideRedirect) { @@ -1090,6 +1134,13 @@ void CXWM::initSelection() { 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(); }); + + 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); + + uint32_t val1 = XDND_VERSION; + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, dndSelection.window, HYPRATOMS["XdndAware"], XCB_ATOM_ATOM, 32, 1, &val1); } void CXWM::setClipboardToWayland(SXSelection& sel) { @@ -1172,6 +1223,52 @@ void CXWM::setCursor(unsigned char* pixData, uint32_t stride, const Vector2D& si xcb_flush(connection); } +void CXWM::sendDndEvent(SP<CWLSurfaceResource> 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<CX11DataDevice> CXWM::getDataDevice() { + return dndDataDevice; +} + +SP<IDataOffer> CXWM::createX11DataOffer(SP<CWLSurfaceResource> surf, SP<IDataSource> source) { + auto XSURF = windowForWayland(surf); + + if (!XSURF) { + Debug::log(ERR, "[xwm] No xwayland surface for destination in createX11DataOffer"); + return nullptr; + } + + // invalidate old + g_pXWayland->pWM->dndDataOffers.clear(); + + auto offer = dndDataOffers.emplace_back(makeShared<CX11DataOffer>()); + offer->self = offer; + offer->xwaylandSurface = XSURF; + offer->source = source; + + return offer; +} + void SXSelection::onSelection() { if (g_pSeatManager->selection.currentSelection && g_pSeatManager->selection.currentSelection->type() == DATA_SOURCE_TYPE_X11) return; @@ -1220,7 +1317,11 @@ static int readDataSource(int fd, uint32_t mask, void* data) { } bool SXSelection::sendData(xcb_selection_request_event_t* e, std::string mime) { - WP<IDataSource> selection = g_pSeatManager->selection.currentSelection; + WP<IDataSource> selection; + if (this == &g_pXWayland->pWM->clipboard) + selection = g_pSeatManager->selection.currentSelection; + else if (!g_pXWayland->pWM->dndDataOffers.empty()) + selection = g_pXWayland->pWM->dndDataOffers.at(0)->getSource(); if (!selection) return false; diff --git a/src/xwayland/XWM.hpp b/src/xwayland/XWM.hpp index d6d4f6f5..38fdab94 100644 --- a/src/xwayland/XWM.hpp +++ b/src/xwayland/XWM.hpp @@ -2,6 +2,7 @@ #include "../macros.hpp" #include "XDataSource.hpp" +#include "Dnd.hpp" #include "../helpers/memory/Memory.hpp" #include "../helpers/signal/Signal.hpp" @@ -104,7 +105,9 @@ class CXWM { CXWM(); ~CXWM(); - int onEvent(int fd, uint32_t mask); + int onEvent(int fd, uint32_t mask); + SP<CX11DataDevice> getDataDevice(); + SP<IDataOffer> createX11DataOffer(SP<CWLSurfaceResource> surf, SP<IDataSource> source); private: void setCursor(unsigned char* pixData, uint32_t stride, const Vector2D& size, const Vector2D& hotspot); @@ -128,6 +131,7 @@ class CXWM { void sendWMMessage(SP<CXWaylandSurface> surf, xcb_client_message_data_t* data, uint32_t mask); SP<CXWaylandSurface> windowForXID(xcb_window_t wid); + SP<CXWaylandSurface> windowForWayland(SP<CWLSurfaceResource> surf); void readWindowData(SP<CXWaylandSurface> surf); void associate(SP<CXWaylandSurface> surf, SP<CWLSurfaceResource> wlSurf); @@ -135,33 +139,37 @@ class CXWM { void updateClientList(); + void sendDndEvent(SP<CWLSurfaceResource> 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); - void handleConfigure(xcb_configure_request_event_t* e); - void handleConfigureNotify(xcb_configure_notify_event_t* e); - void handleMapRequest(xcb_map_request_event_t* e); - void handleMapNotify(xcb_map_notify_event_t* e); - void handleUnmapNotify(xcb_unmap_notify_event_t* e); - void handlePropertyNotify(xcb_property_notify_event_t* e); - void handleClientMessage(xcb_client_message_event_t* e); - void handleFocusIn(xcb_focus_in_event_t* e); - void handleFocusOut(xcb_focus_out_event_t* e); - void handleError(xcb_value_error_t* e); - - bool handleSelectionEvent(xcb_generic_event_t* e); - void handleSelectionNotify(xcb_selection_notify_event_t* e); - bool handleSelectionPropertyNotify(xcb_property_notify_event_t* e); - void handleSelectionRequest(xcb_selection_request_event_t* e); - bool handleSelectionXFixesNotify(xcb_xfixes_selection_notify_event_t* e); - - void selectionSendNotify(xcb_selection_request_event_t* e, bool success); - xcb_atom_t mimeToAtom(const std::string& mime); - std::string mimeFromAtom(xcb_atom_t atom); - void setClipboardToWayland(SXSelection& sel); - void getTransferData(SXSelection& sel); - std::string getAtomName(uint32_t atom); - void readProp(SP<CXWaylandSurface> XSURF, uint32_t atom, xcb_get_property_reply_t* reply); + void handleCreate(xcb_create_notify_event_t* e); + void handleDestroy(xcb_destroy_notify_event_t* e); + void handleConfigure(xcb_configure_request_event_t* e); + void handleConfigureNotify(xcb_configure_notify_event_t* e); + void handleMapRequest(xcb_map_request_event_t* e); + void handleMapNotify(xcb_map_notify_event_t* e); + void handleUnmapNotify(xcb_unmap_notify_event_t* e); + void handlePropertyNotify(xcb_property_notify_event_t* e); + void handleClientMessage(xcb_client_message_event_t* e); + void handleFocusIn(xcb_focus_in_event_t* e); + void handleFocusOut(xcb_focus_out_event_t* e); + void handleError(xcb_value_error_t* e); + + bool handleSelectionEvent(xcb_generic_event_t* e); + void handleSelectionNotify(xcb_selection_notify_event_t* e); + bool handleSelectionPropertyNotify(xcb_property_notify_event_t* e); + void handleSelectionRequest(xcb_selection_request_event_t* e); + bool handleSelectionXFixesNotify(xcb_xfixes_selection_notify_event_t* e); + + void selectionSendNotify(xcb_selection_request_event_t* e, bool success); + xcb_atom_t mimeToAtom(const std::string& mime); + std::string mimeFromAtom(xcb_atom_t atom); + void setClipboardToWayland(SXSelection& sel); + void getTransferData(SXSelection& sel); + std::string getAtomName(uint32_t atom); + void readProp(SP<CXWaylandSurface> XSURF, uint32_t atom, xcb_get_property_reply_t* reply); + + SXSelection* getSelection(xcb_atom_t atom); // CXCBConnection connection; @@ -191,6 +199,9 @@ class CXWM { uint64_t lastFocusSeq = 0; SXSelection clipboard; + SXSelection dndSelection; + SP<CX11DataDevice> dndDataDevice = makeShared<CX11DataDevice>(); + std::vector<SP<CX11DataOffer>> dndDataOffers; struct { CHyprSignalListener newWLSurface; @@ -200,6 +211,10 @@ class CXWM { friend class CXWaylandSurface; friend class CXWayland; friend class CXDataSource; + friend class CX11DataDevice; + friend class CX11DataSource; + friend class CX11DataOffer; + friend class CWLDataDeviceProtocol; friend struct SXSelection; friend struct SXTransfer; }; |