mirror of
https://github.com/hyprwm/Hyprland
synced 2024-12-23 18:29:49 +01:00
xwayland: Support cross DnD from Wayland (#8708)
Adds support for drag-and-drop from Wayland clients to XWayland ones
This commit is contained in:
parent
9f7a96b997
commit
db24964877
10 changed files with 699 additions and 108 deletions
|
@ -213,6 +213,36 @@ void CHyprXWaylandManager::setWindowFullscreen(PHLWINDOW pWindow, bool fullscree
|
||||||
pWindow->m_pXDGSurface->toplevel->setFullscreen(fullscreen);
|
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) {
|
Vector2D CHyprXWaylandManager::xwaylandToWaylandCoords(const Vector2D& coord) {
|
||||||
|
|
||||||
static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>("xwayland:force_zero_scaling");
|
static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>("xwayland:force_zero_scaling");
|
||||||
|
|
|
@ -22,6 +22,7 @@ class CHyprXWaylandManager {
|
||||||
bool shouldBeFloated(PHLWINDOW, bool pending = false);
|
bool shouldBeFloated(PHLWINDOW, bool pending = false);
|
||||||
void checkBorders(PHLWINDOW);
|
void checkBorders(PHLWINDOW);
|
||||||
Vector2D xwaylandToWaylandCoords(const Vector2D&);
|
Vector2D xwaylandToWaylandCoords(const Vector2D&);
|
||||||
|
Vector2D waylandToXWaylandCoords(const Vector2D&);
|
||||||
};
|
};
|
||||||
|
|
||||||
inline std::unique_ptr<CHyprXWaylandManager> g_pXWaylandManager;
|
inline std::unique_ptr<CHyprXWaylandManager> g_pXWaylandManager;
|
|
@ -6,6 +6,8 @@
|
||||||
#include "../../Compositor.hpp"
|
#include "../../Compositor.hpp"
|
||||||
#include "Seat.hpp"
|
#include "Seat.hpp"
|
||||||
#include "Compositor.hpp"
|
#include "Compositor.hpp"
|
||||||
|
#include "../../xwayland/XWayland.hpp"
|
||||||
|
#include "../../xwayland/Server.hpp"
|
||||||
|
|
||||||
CWLDataOfferResource::CWLDataOfferResource(SP<CWlDataOffer> resource_, SP<IDataSource> source_) : source(source_), resource(resource_) {
|
CWLDataOfferResource::CWLDataOfferResource(SP<CWlDataOffer> resource_, SP<IDataSource> source_) : source(source_), resource(resource_) {
|
||||||
if (!good())
|
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_) {
|
CWLDataSourceResource::CWLDataSourceResource(SP<CWlDataSource> resource_, SP<CWLDataDeviceResource> device_) : device(device_), resource(resource_) {
|
||||||
if (!good())
|
if (!good())
|
||||||
return;
|
return;
|
||||||
|
@ -209,6 +227,10 @@ uint32_t CWLDataSourceResource::actions() {
|
||||||
return supportedActions;
|
return supportedActions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eDataSourceType CWLDataSourceResource::type() {
|
||||||
|
return DATA_SOURCE_TYPE_WAYLAND;
|
||||||
|
}
|
||||||
|
|
||||||
CWLDataDeviceResource::CWLDataDeviceResource(SP<CWlDataDevice> resource_) : resource(resource_) {
|
CWLDataDeviceResource::CWLDataDeviceResource(SP<CWlDataDevice> resource_) : resource(resource_) {
|
||||||
if (!good())
|
if (!good())
|
||||||
return;
|
return;
|
||||||
|
@ -260,15 +282,18 @@ wl_client* CWLDataDeviceResource::client() {
|
||||||
return pClient;
|
return pClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CWLDataDeviceResource::sendDataOffer(SP<CWLDataOfferResource> offer) {
|
void CWLDataDeviceResource::sendDataOffer(SP<IDataOffer> offer) {
|
||||||
if (offer)
|
if (!offer)
|
||||||
resource->sendDataOffer(offer->resource.get());
|
|
||||||
else
|
|
||||||
resource->sendDataOfferRaw(nullptr);
|
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) {
|
void CWLDataDeviceResource::sendEnter(uint32_t serial, SP<CWLSurfaceResource> surf, const Vector2D& local, SP<IDataOffer> offer) {
|
||||||
resource->sendEnterRaw(serial, surf->getResource()->resource(), wl_fixed_from_double(local.x), wl_fixed_from_double(local.y), offer->resource->resource());
|
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() {
|
void CWLDataDeviceResource::sendLeave() {
|
||||||
|
@ -283,11 +308,23 @@ void CWLDataDeviceResource::sendDrop() {
|
||||||
resource->sendDrop();
|
resource->sendDrop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CWLDataDeviceResource::sendSelection(SP<CWLDataOfferResource> offer) {
|
void CWLDataDeviceResource::sendSelection(SP<IDataOffer> offer) {
|
||||||
if (!offer)
|
if (!offer)
|
||||||
resource->sendSelectionRaw(nullptr);
|
resource->sendSelectionRaw(nullptr);
|
||||||
else
|
else if (const auto WL = offer->getWayland(); WL)
|
||||||
resource->sendSelection(offer->resource.get());
|
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_) {
|
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; });
|
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; });
|
auto it = std::find_if(m_vDevices.begin(), m_vDevices.end(), [c](const auto& e) { return e->client() == c; });
|
||||||
if (it == m_vDevices.end())
|
if (it == m_vDevices.end())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
return *it;
|
return *it;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CWLDataDeviceProtocol::sendSelectionToDevice(SP<CWLDataDeviceResource> dev, SP<IDataSource> sel) {
|
void CWLDataDeviceProtocol::sendSelectionToDevice(SP<IDataDevice> dev, SP<IDataSource> sel) {
|
||||||
if (!sel) {
|
if (!sel) {
|
||||||
dev->sendSelection(nullptr);
|
dev->sendSelection(nullptr);
|
||||||
return;
|
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()) {
|
if (const auto WL = dev->getWayland(); WL) {
|
||||||
dev->resource->noMemory();
|
const auto OFFER = m_vOffers.emplace_back(makeShared<CWLDataOfferResource>(makeShared<CWlDataOffer>(WL->resource->client(), WL->resource->version(), 0), sel));
|
||||||
m_vOffers.pop_back();
|
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;
|
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);
|
dev->sendDataOffer(offer);
|
||||||
OFFER->sendData();
|
if (const auto WL = offer->getWayland(); WL)
|
||||||
dev->sendSelection(OFFER);
|
WL->sendData();
|
||||||
|
dev->sendSelection(offer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CWLDataDeviceProtocol::onDestroyDataSource(WP<CWLDataSourceResource> source) {
|
void CWLDataDeviceProtocol::onDestroyDataSource(WP<CWLDataSourceResource> source) {
|
||||||
|
@ -424,7 +482,7 @@ void CWLDataDeviceProtocol::setSelection(SP<IDataSource> source) {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto DESTDEVICE = dataDeviceForClient(g_pSeatManager->state.keyboardFocusResource->client());
|
auto DESTDEVICE = dataDeviceForClient(g_pSeatManager->state.keyboardFocusResource->client());
|
||||||
if (DESTDEVICE)
|
if (DESTDEVICE && DESTDEVICE->type() == DATA_SOURCE_TYPE_WAYLAND)
|
||||||
sendSelectionToDevice(DESTDEVICE, nullptr);
|
sendSelectionToDevice(DESTDEVICE, nullptr);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -442,6 +500,11 @@ void CWLDataDeviceProtocol::setSelection(SP<IDataSource> source) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (DESTDEVICE->type() != DATA_SOURCE_TYPE_WAYLAND) {
|
||||||
|
LOGM(LOG, "CWLDataDeviceProtocol::setSelection: ignoring X11 data device");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
sendSelectionToDevice(DESTDEVICE, source);
|
sendSelectionToDevice(DESTDEVICE, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -589,22 +652,38 @@ void CWLDataDeviceProtocol::updateDrag() {
|
||||||
if (!dnd.focusedDevice)
|
if (!dnd.focusedDevice)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// make a new offer
|
SP<IDataOffer> offer;
|
||||||
const auto OFFER = m_vOffers.emplace_back(
|
|
||||||
makeShared<CWLDataOfferResource>(makeShared<CWlDataOffer>(dnd.focusedDevice->resource->client(), dnd.focusedDevice->resource->version(), 0), dnd.currentSource.lock()));
|
|
||||||
|
|
||||||
if (!OFFER->good()) {
|
if (const auto WL = dnd.focusedDevice->getWayland(); WL) {
|
||||||
dnd.currentSource->resource->noMemory();
|
const auto OFFER =
|
||||||
m_vOffers.pop_back();
|
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) {
|
||||||
|
LOGM(ERR, "No offer could be created in updateDrag");
|
||||||
return;
|
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);
|
dnd.focusedDevice->sendDataOffer(offer);
|
||||||
OFFER->sendData();
|
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(),
|
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() {
|
void CWLDataDeviceProtocol::resetDndState() {
|
||||||
|
@ -651,6 +730,18 @@ bool CWLDataDeviceProtocol::wasDragSuccessful() {
|
||||||
return true;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,21 +26,27 @@ class CWLDataOfferResource;
|
||||||
class CWLSurfaceResource;
|
class CWLSurfaceResource;
|
||||||
class CMonitor;
|
class CMonitor;
|
||||||
|
|
||||||
class CWLDataOfferResource {
|
class CWLDataOfferResource : public IDataOffer {
|
||||||
public:
|
public:
|
||||||
CWLDataOfferResource(SP<CWlDataOffer> resource_, SP<IDataSource> source_);
|
CWLDataOfferResource(SP<CWlDataOffer> resource_, SP<IDataSource> source_);
|
||||||
~CWLDataOfferResource();
|
~CWLDataOfferResource();
|
||||||
|
|
||||||
bool good();
|
bool good();
|
||||||
void sendData();
|
void sendData();
|
||||||
|
|
||||||
WP<IDataSource> source;
|
virtual eDataSourceType type();
|
||||||
|
virtual SP<CWLDataOfferResource> getWayland();
|
||||||
|
virtual SP<CX11DataOffer> getX11();
|
||||||
|
virtual SP<IDataSource> getSource();
|
||||||
|
|
||||||
bool dead = false;
|
WP<IDataSource> source;
|
||||||
bool accepted = false;
|
WP<CWLDataOfferResource> self;
|
||||||
bool recvd = false;
|
|
||||||
|
|
||||||
uint32_t actions = 0;
|
bool dead = false;
|
||||||
|
bool accepted = false;
|
||||||
|
bool recvd = false;
|
||||||
|
|
||||||
|
uint32_t actions = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SP<CWlDataOffer> resource;
|
SP<CWlDataOffer> resource;
|
||||||
|
@ -66,9 +72,9 @@ class CWLDataSourceResource : public IDataSource {
|
||||||
virtual void error(uint32_t code, const std::string& msg);
|
virtual void error(uint32_t code, const std::string& msg);
|
||||||
virtual void sendDndFinished();
|
virtual void sendDndFinished();
|
||||||
virtual uint32_t actions(); // wl_data_device_manager.dnd_action
|
virtual uint32_t actions(); // wl_data_device_manager.dnd_action
|
||||||
|
virtual eDataSourceType type();
|
||||||
void sendDndDropPerformed();
|
virtual void sendDndDropPerformed();
|
||||||
void sendDndAction(wl_data_device_manager_dnd_action a);
|
virtual void sendDndAction(wl_data_device_manager_dnd_action a);
|
||||||
|
|
||||||
bool used = false;
|
bool used = false;
|
||||||
bool dnd = false;
|
bool dnd = false;
|
||||||
|
@ -88,21 +94,24 @@ class CWLDataSourceResource : public IDataSource {
|
||||||
friend class CWLDataDeviceProtocol;
|
friend class CWLDataDeviceProtocol;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CWLDataDeviceResource {
|
class CWLDataDeviceResource : public IDataDevice {
|
||||||
public:
|
public:
|
||||||
CWLDataDeviceResource(SP<CWlDataDevice> resource_);
|
CWLDataDeviceResource(SP<CWlDataDevice> resource_);
|
||||||
|
|
||||||
bool good();
|
bool good();
|
||||||
wl_client* client();
|
wl_client* client();
|
||||||
|
|
||||||
void sendDataOffer(SP<CWLDataOfferResource> offer);
|
virtual SP<CWLDataDeviceResource> getWayland();
|
||||||
void sendEnter(uint32_t serial, SP<CWLSurfaceResource> surf, const Vector2D& local, SP<CWLDataOfferResource> offer);
|
virtual SP<CX11DataDevice> getX11();
|
||||||
void sendLeave();
|
virtual void sendDataOffer(SP<IDataOffer> offer);
|
||||||
void sendMotion(uint32_t timeMs, const Vector2D& local);
|
virtual void sendEnter(uint32_t serial, SP<CWLSurfaceResource> surf, const Vector2D& local, SP<IDataOffer> offer);
|
||||||
void sendDrop();
|
virtual void sendLeave();
|
||||||
void sendSelection(SP<CWLDataOfferResource> offer);
|
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:
|
private:
|
||||||
SP<CWlDataDevice> resource;
|
SP<CWlDataDevice> resource;
|
||||||
|
@ -152,19 +161,19 @@ class CWLDataDeviceProtocol : public IWaylandProtocol {
|
||||||
|
|
||||||
void onDestroyDataSource(WP<CWLDataSourceResource> source);
|
void onDestroyDataSource(WP<CWLDataSourceResource> source);
|
||||||
void setSelection(SP<IDataSource> 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 updateSelection();
|
||||||
void onKeyboardFocus();
|
void onKeyboardFocus();
|
||||||
void onDndPointerFocus();
|
void onDndPointerFocus();
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
WP<CWLDataDeviceResource> focusedDevice;
|
WP<IDataDevice> focusedDevice;
|
||||||
WP<CWLDataSourceResource> currentSource;
|
WP<IDataSource> currentSource;
|
||||||
WP<CWLSurfaceResource> dndSurface;
|
WP<CWLSurfaceResource> dndSurface;
|
||||||
WP<CWLSurfaceResource> originSurface;
|
WP<CWLSurfaceResource> originSurface;
|
||||||
bool overriddenCursor = false;
|
bool overriddenCursor = false;
|
||||||
CHyprSignalListener dndSurfaceDestroy;
|
CHyprSignalListener dndSurfaceDestroy;
|
||||||
CHyprSignalListener dndSurfaceCommit;
|
CHyprSignalListener dndSurfaceCommit;
|
||||||
|
|
||||||
// for ending a dnd
|
// for ending a dnd
|
||||||
SP<HOOK_CALLBACK_FN> mouseMove;
|
SP<HOOK_CALLBACK_FN> mouseMove;
|
||||||
|
@ -182,7 +191,7 @@ class CWLDataDeviceProtocol : public IWaylandProtocol {
|
||||||
bool wasDragSuccessful();
|
bool wasDragSuccessful();
|
||||||
|
|
||||||
//
|
//
|
||||||
SP<CWLDataDeviceResource> dataDeviceForClient(wl_client*);
|
SP<IDataDevice> dataDeviceForClient(wl_client*);
|
||||||
|
|
||||||
friend class CSeatManager;
|
friend class CSeatManager;
|
||||||
friend class CWLDataDeviceManagerResource;
|
friend class CWLDataDeviceManagerResource;
|
||||||
|
|
|
@ -27,3 +27,15 @@ void IDataSource::sendDndFinished() {
|
||||||
uint32_t IDataSource::actions() {
|
uint32_t IDataSource::actions() {
|
||||||
return 7; // all
|
return 7; // all
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IDataSource::sendDndDropPerformed() {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IDataSource::sendDndAction(wl_data_device_manager_dnd_action a) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IDataOffer::markDead() {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,15 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include "../../helpers/signal/Signal.hpp"
|
#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 {
|
enum eDataSourceType : uint8_t {
|
||||||
DATA_SOURCE_TYPE_WAYLAND = 0,
|
DATA_SOURCE_TYPE_WAYLAND = 0,
|
||||||
|
@ -27,6 +36,8 @@ class IDataSource {
|
||||||
virtual void error(uint32_t code, const std::string& msg) = 0;
|
virtual void error(uint32_t code, const std::string& msg) = 0;
|
||||||
virtual eDataSourceType type();
|
virtual eDataSourceType type();
|
||||||
virtual uint32_t actions(); // wl_data_device_manager.dnd_action
|
virtual uint32_t actions(); // wl_data_device_manager.dnd_action
|
||||||
|
virtual void sendDndDropPerformed();
|
||||||
|
virtual void sendDndAction(wl_data_device_manager_dnd_action a);
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
CSignal destroy;
|
CSignal destroy;
|
||||||
|
@ -35,3 +46,31 @@ class IDataSource {
|
||||||
private:
|
private:
|
||||||
bool wasUsed = false;
|
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;
|
||||||
|
};
|
||||||
|
|
211
src/xwayland/Dnd.cpp
Normal file
211
src/xwayland/Dnd.cpp
Normal file
|
@ -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) {
|
||||||
|
;
|
||||||
|
}
|
82
src/xwayland/Dnd.hpp
Normal file
82
src/xwayland/Dnd.hpp
Normal file
|
@ -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;
|
||||||
|
};
|
|
@ -387,6 +387,28 @@ void CXWM::handleClientMessage(xcb_client_message_event_t* e) {
|
||||||
}
|
}
|
||||||
} else if (e->type == HYPRATOMS["_NET_ACTIVE_WINDOW"]) {
|
} else if (e->type == HYPRATOMS["_NET_ACTIVE_WINDOW"]) {
|
||||||
XSURF->events.activate.emit();
|
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 {
|
} else {
|
||||||
Debug::log(TRACE, "[xwm] Unhandled message prop {} -> {}", e->type, propName);
|
Debug::log(TRACE, "[xwm] Unhandled message prop {} -> {}", e->type, propName);
|
||||||
return;
|
return;
|
||||||
|
@ -545,22 +567,22 @@ std::string CXWM::mimeFromAtom(xcb_atom_t atom) {
|
||||||
void CXWM::handleSelectionNotify(xcb_selection_notify_event_t* e) {
|
void CXWM::handleSelectionNotify(xcb_selection_notify_event_t* e) {
|
||||||
Debug::log(TRACE, "[xwm] Selection notify for {} prop {} target {}", e->selection, e->property, e->target);
|
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 (e->property == XCB_ATOM_NONE) {
|
||||||
if (sel.transfer) {
|
if (sel->transfer) {
|
||||||
Debug::log(TRACE, "[xwm] converting selection failed");
|
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) {
|
if (!focusedSurface) {
|
||||||
Debug::log(TRACE, "[xwm] denying access to write to clipboard because no X client is in focus");
|
Debug::log(TRACE, "[xwm] denying access to write to clipboard because no X client is in focus");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setClipboardToWayland(sel);
|
setClipboardToWayland(*sel);
|
||||||
} else if (sel.transfer)
|
} else if (sel->transfer)
|
||||||
getTransferData(sel);
|
getTransferData(*sel);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CXWM::handleSelectionPropertyNotify(xcb_property_notify_event_t* e) {
|
bool CXWM::handleSelectionPropertyNotify(xcb_property_notify_event_t* e) {
|
||||||
|
@ -571,13 +593,22 @@ bool CXWM::handleSelectionPropertyNotify(xcb_property_notify_event_t* e) {
|
||||||
return false;
|
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) {
|
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,
|
Debug::log(TRACE, "[xwm] Selection request for {} prop {} target {} time {} requestor {} selection {}", e->selection, e->property, e->target, e->time, e->requestor,
|
||||||
e->selection);
|
e->selection);
|
||||||
|
|
||||||
SXSelection& sel = clipboard;
|
SXSelection* sel = getSelection(e->selection);
|
||||||
|
|
||||||
if (!g_pSeatManager->selection.currentSelection) {
|
if (!sel) {
|
||||||
Debug::log(ERR, "[xwm] No selection");
|
Debug::log(ERR, "[xwm] No selection");
|
||||||
selectionSendNotify(e, false);
|
selectionSendNotify(e, false);
|
||||||
return;
|
return;
|
||||||
|
@ -588,8 +619,8 @@ void CXWM::handleSelectionRequest(xcb_selection_request_event_t* e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sel.window != e->owner && e->time != XCB_CURRENT_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);
|
Debug::log(ERR, "[xwm] outdated selection request. Time {} < {}", e->time, sel->timestamp);
|
||||||
selectionSendNotify(e, false);
|
selectionSendNotify(e, false);
|
||||||
return;
|
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());
|
xcb_change_property(connection, XCB_PROP_MODE_REPLACE, e->requestor, e->property, XCB_ATOM_ATOM, 32, atoms.size(), atoms.data());
|
||||||
selectionSendNotify(e, true);
|
selectionSendNotify(e, true);
|
||||||
} else if (e->target == HYPRATOMS["TIMESTAMP"]) {
|
} 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);
|
selectionSendNotify(e, true);
|
||||||
} else if (e->target == HYPRATOMS["DELETE"]) {
|
} else if (e->target == HYPRATOMS["DELETE"]) {
|
||||||
selectionSendNotify(e, true);
|
selectionSendNotify(e, true);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
std::string mime = mimeFromAtom(e->target);
|
std::string mime = mimeFromAtom(e->target);
|
||||||
|
|
||||||
if (mime == "INVALID") {
|
if (mime == "INVALID") {
|
||||||
|
@ -629,7 +659,7 @@ void CXWM::handleSelectionRequest(xcb_selection_request_event_t* e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sel.sendData(e, mime)) {
|
if (!sel->sendData(e, mime)) {
|
||||||
Debug::log(LOG, "[xwm] Failed to send selection :(");
|
Debug::log(LOG, "[xwm] Failed to send selection :(");
|
||||||
selectionSendNotify(e, false);
|
selectionSendNotify(e, false);
|
||||||
return;
|
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);
|
Debug::log(TRACE, "[xwm] Selection xfixes notify for {}", e->selection);
|
||||||
|
|
||||||
// IMPORTANT: mind the g_pSeatManager below
|
// 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 (e->owner == XCB_WINDOW_NONE) {
|
||||||
if (sel.owner != sel.window)
|
if (sel->owner != sel->window && sel == &clipboard)
|
||||||
g_pSeatManager->setCurrentSelection(nullptr);
|
g_pSeatManager->setCurrentSelection(nullptr);
|
||||||
|
|
||||||
sel.owner = 0;
|
sel->owner = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
sel.owner = e->owner;
|
sel->owner = e->owner;
|
||||||
|
|
||||||
if (sel.owner == sel.window) {
|
if (sel->owner == sel->window) {
|
||||||
sel.timestamp = e->timestamp;
|
sel->timestamp = e->timestamp;
|
||||||
return true;
|
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);
|
xcb_flush(connection);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -854,6 +887,8 @@ CXWM::CXWM() : connection(g_pXWayland->pServer->xwmFDs[0]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dndDataDevice->self = dndDataDevice;
|
||||||
|
|
||||||
xcb_screen_iterator_t screen_iterator = xcb_setup_roots_iterator(xcb_get_setup(connection));
|
xcb_screen_iterator_t screen_iterator = xcb_setup_roots_iterator(xcb_get_setup(connection));
|
||||||
screen = screen_iterator.data;
|
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) {
|
void CXWM::associate(SP<CXWaylandSurface> surf, SP<CWLSurfaceResource> wlSurf) {
|
||||||
if (surf->surface)
|
if (surf->surface)
|
||||||
return;
|
return;
|
||||||
|
@ -1068,7 +1112,7 @@ void CXWM::updateClientList() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CXWM::isWMWindow(xcb_window_t w) {
|
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) {
|
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);
|
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(); });
|
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) {
|
void CXWM::setClipboardToWayland(SXSelection& sel) {
|
||||||
|
@ -1172,6 +1223,52 @@ void CXWM::setCursor(unsigned char* pixData, uint32_t stride, const Vector2D& si
|
||||||
xcb_flush(connection);
|
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() {
|
void SXSelection::onSelection() {
|
||||||
if (g_pSeatManager->selection.currentSelection && g_pSeatManager->selection.currentSelection->type() == DATA_SOURCE_TYPE_X11)
|
if (g_pSeatManager->selection.currentSelection && g_pSeatManager->selection.currentSelection->type() == DATA_SOURCE_TYPE_X11)
|
||||||
return;
|
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) {
|
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)
|
if (!selection)
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "../macros.hpp"
|
#include "../macros.hpp"
|
||||||
#include "XDataSource.hpp"
|
#include "XDataSource.hpp"
|
||||||
|
#include "Dnd.hpp"
|
||||||
#include "../helpers/memory/Memory.hpp"
|
#include "../helpers/memory/Memory.hpp"
|
||||||
#include "../helpers/signal/Signal.hpp"
|
#include "../helpers/signal/Signal.hpp"
|
||||||
|
|
||||||
|
@ -104,7 +105,9 @@ class CXWM {
|
||||||
CXWM();
|
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:
|
private:
|
||||||
void setCursor(unsigned char* pixData, uint32_t stride, const Vector2D& size, const Vector2D& hotspot);
|
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);
|
void sendWMMessage(SP<CXWaylandSurface> surf, xcb_client_message_data_t* data, uint32_t mask);
|
||||||
|
|
||||||
SP<CXWaylandSurface> windowForXID(xcb_window_t wid);
|
SP<CXWaylandSurface> windowForXID(xcb_window_t wid);
|
||||||
|
SP<CXWaylandSurface> windowForWayland(SP<CWLSurfaceResource> surf);
|
||||||
|
|
||||||
void readWindowData(SP<CXWaylandSurface> surf);
|
void readWindowData(SP<CXWaylandSurface> surf);
|
||||||
void associate(SP<CXWaylandSurface> surf, SP<CWLSurfaceResource> wlSurf);
|
void associate(SP<CXWaylandSurface> surf, SP<CWLSurfaceResource> wlSurf);
|
||||||
|
@ -135,33 +139,37 @@ class CXWM {
|
||||||
|
|
||||||
void updateClientList();
|
void updateClientList();
|
||||||
|
|
||||||
|
void sendDndEvent(SP<CWLSurfaceResource> destination, xcb_atom_t type, xcb_client_message_data_t& data);
|
||||||
|
|
||||||
// event handlers
|
// event handlers
|
||||||
void handleCreate(xcb_create_notify_event_t* e);
|
void handleCreate(xcb_create_notify_event_t* e);
|
||||||
void handleDestroy(xcb_destroy_notify_event_t* e);
|
void handleDestroy(xcb_destroy_notify_event_t* e);
|
||||||
void handleConfigure(xcb_configure_request_event_t* e);
|
void handleConfigure(xcb_configure_request_event_t* e);
|
||||||
void handleConfigureNotify(xcb_configure_notify_event_t* e);
|
void handleConfigureNotify(xcb_configure_notify_event_t* e);
|
||||||
void handleMapRequest(xcb_map_request_event_t* e);
|
void handleMapRequest(xcb_map_request_event_t* e);
|
||||||
void handleMapNotify(xcb_map_notify_event_t* e);
|
void handleMapNotify(xcb_map_notify_event_t* e);
|
||||||
void handleUnmapNotify(xcb_unmap_notify_event_t* e);
|
void handleUnmapNotify(xcb_unmap_notify_event_t* e);
|
||||||
void handlePropertyNotify(xcb_property_notify_event_t* e);
|
void handlePropertyNotify(xcb_property_notify_event_t* e);
|
||||||
void handleClientMessage(xcb_client_message_event_t* e);
|
void handleClientMessage(xcb_client_message_event_t* e);
|
||||||
void handleFocusIn(xcb_focus_in_event_t* e);
|
void handleFocusIn(xcb_focus_in_event_t* e);
|
||||||
void handleFocusOut(xcb_focus_out_event_t* e);
|
void handleFocusOut(xcb_focus_out_event_t* e);
|
||||||
void handleError(xcb_value_error_t* e);
|
void handleError(xcb_value_error_t* e);
|
||||||
|
|
||||||
bool handleSelectionEvent(xcb_generic_event_t* e);
|
bool handleSelectionEvent(xcb_generic_event_t* e);
|
||||||
void handleSelectionNotify(xcb_selection_notify_event_t* e);
|
void handleSelectionNotify(xcb_selection_notify_event_t* e);
|
||||||
bool handleSelectionPropertyNotify(xcb_property_notify_event_t* e);
|
bool handleSelectionPropertyNotify(xcb_property_notify_event_t* e);
|
||||||
void handleSelectionRequest(xcb_selection_request_event_t* e);
|
void handleSelectionRequest(xcb_selection_request_event_t* e);
|
||||||
bool handleSelectionXFixesNotify(xcb_xfixes_selection_notify_event_t* e);
|
bool handleSelectionXFixesNotify(xcb_xfixes_selection_notify_event_t* e);
|
||||||
|
|
||||||
void selectionSendNotify(xcb_selection_request_event_t* e, bool success);
|
void selectionSendNotify(xcb_selection_request_event_t* e, bool success);
|
||||||
xcb_atom_t mimeToAtom(const std::string& mime);
|
xcb_atom_t mimeToAtom(const std::string& mime);
|
||||||
std::string mimeFromAtom(xcb_atom_t atom);
|
std::string mimeFromAtom(xcb_atom_t atom);
|
||||||
void setClipboardToWayland(SXSelection& sel);
|
void setClipboardToWayland(SXSelection& sel);
|
||||||
void getTransferData(SXSelection& sel);
|
void getTransferData(SXSelection& sel);
|
||||||
std::string getAtomName(uint32_t atom);
|
std::string getAtomName(uint32_t atom);
|
||||||
void readProp(SP<CXWaylandSurface> XSURF, uint32_t atom, xcb_get_property_reply_t* reply);
|
void readProp(SP<CXWaylandSurface> XSURF, uint32_t atom, xcb_get_property_reply_t* reply);
|
||||||
|
|
||||||
|
SXSelection* getSelection(xcb_atom_t atom);
|
||||||
|
|
||||||
//
|
//
|
||||||
CXCBConnection connection;
|
CXCBConnection connection;
|
||||||
|
@ -191,6 +199,9 @@ class CXWM {
|
||||||
uint64_t lastFocusSeq = 0;
|
uint64_t lastFocusSeq = 0;
|
||||||
|
|
||||||
SXSelection clipboard;
|
SXSelection clipboard;
|
||||||
|
SXSelection dndSelection;
|
||||||
|
SP<CX11DataDevice> dndDataDevice = makeShared<CX11DataDevice>();
|
||||||
|
std::vector<SP<CX11DataOffer>> dndDataOffers;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
CHyprSignalListener newWLSurface;
|
CHyprSignalListener newWLSurface;
|
||||||
|
@ -200,6 +211,10 @@ class CXWM {
|
||||||
friend class CXWaylandSurface;
|
friend class CXWaylandSurface;
|
||||||
friend class CXWayland;
|
friend class CXWayland;
|
||||||
friend class CXDataSource;
|
friend class CXDataSource;
|
||||||
|
friend class CX11DataDevice;
|
||||||
|
friend class CX11DataSource;
|
||||||
|
friend class CX11DataOffer;
|
||||||
|
friend class CWLDataDeviceProtocol;
|
||||||
friend struct SXSelection;
|
friend struct SXSelection;
|
||||||
friend struct SXTransfer;
|
friend struct SXTransfer;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue