xwayland: improve dnd and cleanup (#9405)

Minor changes to xwayland dnd / regular dnd to fix various issues

---------

Co-authored-by: Vaxry <vaxry@vaxry.net>
This commit is contained in:
nyx 2025-02-23 21:52:10 -05:00 committed by GitHub
parent e59464629f
commit 3458d7ac93
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 177 additions and 96 deletions

View file

@ -6,9 +6,14 @@
#endif
#include "../managers/XWaylandManager.hpp"
#include "../desktop/WLSurface.hpp"
#include "../protocols/core/Compositor.hpp"
using namespace Hyprutils::OS;
#define PROPERTY_FORMAT_32BIT 32
#define PROPERTY_LENGTH 1
#define PROPERTY_OFFSET 0
#ifndef NO_XWAYLAND
static xcb_atom_t dndActionToAtom(uint32_t actions) {
if (actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)
@ -20,6 +25,51 @@ static xcb_atom_t dndActionToAtom(uint32_t actions) {
return XCB_ATOM_NONE;
}
void CX11DataDevice::sendDndEvent(xcb_window_t targetWindow, xcb_atom_t type, xcb_client_message_data_t& data) {
xcb_client_message_event_t event = {
.response_type = XCB_CLIENT_MESSAGE,
.format = 32,
.sequence = 0,
.window = targetWindow,
.type = type,
.data = data,
};
xcb_send_event(g_pXWayland->pWM->connection, 0, targetWindow, XCB_EVENT_MASK_NO_EVENT, (const char*)&event);
xcb_flush(g_pXWayland->pWM->connection);
}
xcb_window_t CX11DataDevice::getProxyWindow(xcb_window_t window) {
xcb_window_t targetWindow = window;
xcb_get_property_cookie_t proxyCookie =
xcb_get_property(g_pXWayland->pWM->connection, PROPERTY_OFFSET, window, HYPRATOMS["XdndProxy"], XCB_ATOM_WINDOW, PROPERTY_OFFSET, PROPERTY_LENGTH);
xcb_get_property_reply_t* proxyReply = xcb_get_property_reply(g_pXWayland->pWM->connection, proxyCookie, nullptr);
const auto isValidPropertyReply = [](xcb_get_property_reply_t* reply) {
return reply && reply->type == XCB_ATOM_WINDOW && reply->format == PROPERTY_FORMAT_32BIT && xcb_get_property_value_length(reply) == sizeof(xcb_window_t);
};
if (isValidPropertyReply(proxyReply)) {
xcb_window_t proxyWindow = *(xcb_window_t*)xcb_get_property_value(proxyReply);
xcb_get_property_cookie_t proxyVerifyCookie =
xcb_get_property(g_pXWayland->pWM->connection, PROPERTY_OFFSET, proxyWindow, HYPRATOMS["XdndProxy"], XCB_ATOM_WINDOW, PROPERTY_OFFSET, PROPERTY_LENGTH);
xcb_get_property_reply_t* proxyVerifyReply = xcb_get_property_reply(g_pXWayland->pWM->connection, proxyVerifyCookie, nullptr);
if (isValidPropertyReply(proxyVerifyReply)) {
xcb_window_t verifyWindow = *(xcb_window_t*)xcb_get_property_value(proxyVerifyReply);
if (verifyWindow == proxyWindow) {
targetWindow = proxyWindow;
Debug::log(LOG, "Using XdndProxy window {:x} for window {:x}", proxyWindow, window);
}
}
free(proxyVerifyReply);
}
free(proxyReply);
return targetWindow;
}
#endif
eDataSourceType CX11DataOffer::type() {
@ -52,9 +102,6 @@ void CX11DataDevice::sendEnter(uint32_t serial, SP<CWLSurfaceResource> surf, con
#ifndef NO_XWAYLAND
auto XSURF = g_pXWayland->pWM->windowForWayland(surf);
if (offer == lastOffer)
return;
if (!XSURF) {
Debug::log(ERR, "CX11DataDevice::sendEnter: No xwayland surface for destination");
return;
@ -67,56 +114,62 @@ void CX11DataDevice::sendEnter(uint32_t serial, SP<CWLSurfaceResource> surf, con
return;
}
xcb_set_selection_owner(g_pXWayland->pWM->connection, g_pXWayland->pWM->dndSelection.window, HYPRATOMS["XdndSelection"], XCB_TIME_CURRENT_TIME);
xcb_client_message_data_t data = {0};
data.data32[0] = g_pXWayland->pWM->dndSelection.window;
data.data32[1] = XDND_VERSION << 24;
// let the client know it needs to check for DND_TYPE_LIST
data.data32[1] |= 1;
std::vector<xcb_atom_t> targets;
// reserve to avoid reallocations
targets.reserve(SOURCE->mimes().size());
for (auto& mime : SOURCE->mimes()) {
targets.emplace_back(g_pXWayland->pWM->mimeToAtom(mime));
for (auto const& m : SOURCE->mimes()) {
targets.push_back(g_pXWayland->pWM->mimeToAtom(m));
}
xcb_change_property(g_pXWayland->pWM->connection, XCB_PROP_MODE_REPLACE, g_pXWayland->pWM->dndSelection.window, HYPRATOMS["XdndTypeList"], XCB_ATOM_ATOM, 32, targets.size(),
targets.data());
g_pXWayland->pWM->sendDndEvent(surf, HYPRATOMS["XdndEnter"], data);
xcb_set_selection_owner(g_pXWayland->pWM->connection, g_pXWayland->pWM->dndSelection.window, HYPRATOMS["XdndSelection"], XCB_TIME_CURRENT_TIME);
xcb_flush(g_pXWayland->pWM->connection);
xcb_window_t targetWindow = getProxyWindow(XSURF->xID);
xcb_client_message_data_t data = {0};
data.data32[0] = g_pXWayland->pWM->dndSelection.window;
data.data32[1] = XDND_VERSION << 24;
data.data32[1] |= 1;
sendDndEvent(targetWindow, HYPRATOMS["XdndEnter"], data);
lastSurface = XSURF;
lastOffer = offer;
auto hlSurface = CWLSurface::fromResource(surf);
auto hlSurface = XSURF->surface.lock();
if (!hlSurface) {
Debug::log(ERR, "CX11DataDevice::sendEnter: Non desktop x surface?!");
lastSurfaceCoords = {};
return;
}
lastSurfaceCoords = hlSurface->getSurfaceBoxGlobal().value_or(CBox{}).pos();
lastSurfaceCoords = g_pXWaylandManager->xwaylandToWaylandCoords(XSURF->geometry.pos());
#endif
}
void CX11DataDevice::cleanupState() {
lastSurface.reset();
lastOffer.reset();
lastSurfaceCoords = {};
lastTime = 0;
}
void CX11DataDevice::sendLeave() {
#ifndef NO_XWAYLAND
if (!lastSurface)
return;
xcb_window_t targetWindow = getProxyWindow(lastSurface->xID);
xcb_client_message_data_t data = {0};
data.data32[0] = g_pXWayland->pWM->dndSelection.window;
g_pXWayland->pWM->sendDndEvent(lastSurface->surface.lock(), HYPRATOMS["XdndLeave"], data);
sendDndEvent(targetWindow, HYPRATOMS["XdndLeave"], data);
lastSurface.reset();
lastOffer.reset();
xcb_set_selection_owner(g_pXWayland->pWM->connection, g_pXWayland->pWM->dndSelection.window, XCB_ATOM_NONE, XCB_TIME_CURRENT_TIME);
cleanupState();
#endif
}
@ -125,32 +178,39 @@ void CX11DataDevice::sendMotion(uint32_t timeMs, const Vector2D& local) {
if (!lastSurface || !lastOffer || !lastOffer->getSource())
return;
xcb_window_t targetWindow = getProxyWindow(lastSurface->xID);
const auto XCOORDS = g_pXWaylandManager->waylandToXWaylandCoords(lastSurfaceCoords + local);
const uint32_t coords = ((uint32_t)XCOORDS.x << 16) | (uint32_t)XCOORDS.y;
xcb_client_message_data_t data = {0};
data.data32[0] = g_pXWayland->pWM->dndSelection.window;
data.data32[2] = (((int32_t)XCOORDS.x) << 16) | (int32_t)XCOORDS.y;
data.data32[2] = coords;
data.data32[3] = timeMs;
data.data32[4] = dndActionToAtom(lastOffer->getSource()->actions());
g_pXWayland->pWM->sendDndEvent(lastSurface->surface.lock(), HYPRATOMS["XdndPosition"], data);
sendDndEvent(targetWindow, HYPRATOMS["XdndPosition"], data);
lastTime = timeMs;
#endif
}
void CX11DataDevice::sendDrop() {
#ifndef NO_XWAYLAND
if (!lastSurface || !lastOffer)
if (!lastSurface || !lastOffer) {
Debug::log(ERR, "CX11DataDevice::sendDrop: No surface or offer");
return;
}
xcb_window_t targetWindow = getProxyWindow(lastSurface->xID);
// we don't have timeMs here, just send last time + 1
xcb_client_message_data_t data = {0};
data.data32[0] = g_pXWayland->pWM->dndSelection.window;
data.data32[2] = lastTime + 1;
data.data32[2] = lastTime;
g_pXWayland->pWM->sendDndEvent(lastSurface->surface.lock(), HYPRATOMS["XdndDrop"], data);
sendDndEvent(targetWindow, HYPRATOMS["XdndDrop"], data);
sendLeave();
cleanupState();
#endif
}
@ -175,15 +235,16 @@ std::vector<std::string> CX11DataSource::mimes() {
}
void CX11DataSource::send(const std::string& mime, CFileDescriptor fd) {
;
; // no-op
}
void CX11DataSource::accepted(const std::string& mime) {
;
; // no-op
}
void CX11DataSource::cancelled() {
;
dndSuccess = false;
dropped = false;
}
bool CX11DataSource::hasDnd() {
@ -195,11 +256,13 @@ bool CX11DataSource::dndDone() {
}
void CX11DataSource::error(uint32_t code, const std::string& msg) {
Debug::log(ERR, "CX11DataSource::error: this fn is a stub: code {} msg {}", code, msg);
Debug::log(ERR, "CX11DataSource error: code {} msg {}", code, msg);
dndSuccess = false;
dropped = false;
}
void CX11DataSource::sendDndFinished() {
;
dndSuccess = true;
}
uint32_t CX11DataSource::actions() {
@ -211,9 +274,29 @@ eDataSourceType CX11DataSource::type() {
}
void CX11DataSource::sendDndDropPerformed() {
;
dropped = true;
}
void CX11DataSource::sendDndAction(wl_data_device_manager_dnd_action a) {
;
; // no-op
}
void CX11DataDevice::forceCleanupDnd() {
#ifndef NO_XWAYLAND
if (lastOffer) {
auto source = lastOffer->getSource();
if (source) {
source->cancelled();
source->sendDndFinished();
}
}
xcb_set_selection_owner(g_pXWayland->pWM->connection, XCB_ATOM_NONE, HYPRATOMS["XdndSelection"], XCB_TIME_CURRENT_TIME);
xcb_flush(g_pXWayland->pWM->connection);
cleanupState();
g_pSeatManager->setPointerFocus(nullptr, {});
g_pInputManager->simulateMouseMovement();
#endif
}