Use a direct connection for X to wayland drags

We already use a direct DataSource object for transfer.

Use of the new public SeatInterface::startDrag allows us to start our drag directly.
This commit is contained in:
David Edmundson 2021-09-03 12:48:09 +01:00
parent 400dd31db6
commit 65f168e144
11 changed files with 123 additions and 254 deletions

View file

@ -18,16 +18,11 @@
#include "wayland_server.h"
#include "workspace.h"
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/datadevicemanager.h>
#include <KWayland/Client/seat.h>
#include <KWaylandServer/clientconnection.h>
#include <KWaylandServer/datadevicemanager_interface.h>
#include <KWaylandServer/datadevice_interface.h>
#include <KWaylandServer/seat_interface.h>
using namespace KWayland::Client;
using namespace KWaylandServer;
namespace KWin
@ -46,31 +41,7 @@ DataBridge::DataBridge(QObject *parent)
: QObject(parent)
{
s_self = this;
DataDeviceManager *dataDeviceManager = waylandServer()->internalDataDeviceManager();
Seat *seat = waylandServer()->internalSeat();
m_dataDevice = dataDeviceManager->getDataDevice(seat, this);
const DataDeviceManagerInterface *dataDeviceManagerInterface =
waylandServer()->dataDeviceManager();
auto *dc = new QMetaObject::Connection();
*dc = connect(dataDeviceManagerInterface, &DataDeviceManagerInterface::dataDeviceCreated, this,
[this, dc](DataDeviceInterface *dataDeviceInterface) {
if (m_dataDeviceInterface) {
return;
}
if (dataDeviceInterface->client() != *waylandServer()->internalConnection()) {
return;
}
QObject::disconnect(*dc);
delete dc;
m_dataDeviceInterface = dataDeviceInterface;
init();
}
);
waylandServer()->dispatch();
init();
}
DataBridge::~DataBridge()
@ -83,7 +54,6 @@ void DataBridge::init()
m_clipboard = new Clipboard(atoms->clipboard, this);
m_dnd = new Dnd(atoms->xdnd_selection, this);
m_primary = new Primary(atoms->primary, this);
waylandServer()->dispatch();
kwinApp()->installNativeEventFilter(this);
}
@ -98,9 +68,6 @@ bool DataBridge::nativeEventFilter(const QByteArray &eventType, void *message, l
DragEventReply DataBridge::dragMoveFilter(Toplevel *target, const QPoint &pos)
{
if (!m_dnd) {
return DragEventReply::Wayland;
}
return m_dnd->dragMoveFilter(target, pos);
}

View file

@ -15,13 +15,6 @@
#include <QObject>
#include <QPoint>
namespace KWayland
{
namespace Client
{
class DataDevice;
}
}
namespace KWaylandServer
{
class DataDeviceInterface;
@ -56,14 +49,6 @@ public:
DragEventReply dragMoveFilter(Toplevel *target, const QPoint &pos);
KWayland::Client::DataDevice *dataDevice() const
{
return m_dataDevice;
}
KWaylandServer::DataDeviceInterface *dataDeviceIface() const
{
return m_dataDeviceInterface;
}
Dnd *dnd() const
{
return m_dnd;
@ -78,10 +63,6 @@ private:
Dnd *m_dnd = nullptr;
Primary *m_primary = nullptr;
/* Internal data device interface */
KWayland::Client::DataDevice *m_dataDevice = nullptr;
KWaylandServer::DataDeviceInterface *m_dataDeviceInterface = nullptr;
KWIN_SINGLETON(DataBridge)
};

View file

@ -26,5 +26,31 @@ void XwlDataSource::setMimeTypes(const QStringList &mimeTypes)
{
m_mimeTypes = mimeTypes;
}
void XwlDataSource::accept(const QString &mimeType)
{
m_accepted = !mimeType.isEmpty();
}
KWaylandServer::DataDeviceManagerInterface::DnDActions XwlDataSource::supportedDragAndDropActions() const
{
return m_supportedDndActions;
}
void XwlDataSource::setSupportedDndActions(KWaylandServer::DataDeviceManagerInterface::DnDActions dndActions)
{
m_supportedDndActions = dndActions;
Q_EMIT supportedDragAndDropActionsChanged();
}
void XwlDataSource::dndAction(KWaylandServer::DataDeviceManagerInterface::DnDAction action)
{
m_dndAction = action;
}
bool XwlDataSource::isAccepted() const
{
return m_accepted;
}
}
}

View file

@ -19,11 +19,35 @@ public:
void cancel() override;
QStringList mimeTypes() const override;
void setMimeTypes(const QStringList &mimeTypes);
void accept(const QString &mimeType) override;
KWaylandServer::DataDeviceManagerInterface::DnDActions supportedDragAndDropActions() const override;
void setSupportedDndActions(KWaylandServer::DataDeviceManagerInterface::DnDActions dndActions);
void dndAction(KWaylandServer::DataDeviceManagerInterface::DnDAction action) override;
KWaylandServer::DataDeviceManagerInterface::DnDAction selectedDragAndDropAction() {
return m_dndAction;
}
void dropPerformed() override {
Q_EMIT dropped();
}
void dndFinished() override {
Q_EMIT finished();
}
bool isAccepted() const override;
Q_SIGNALS:
void dataRequested(const QString &mimeType, qint32 fd);
void dropped();
void finished();
private:
QStringList m_mimeTypes;
KWaylandServer::DataDeviceManagerInterface::DnDActions m_supportedDndActions;
KWaylandServer::DataDeviceManagerInterface::DnDAction m_dndAction = KWaylandServer::DataDeviceManagerInterface::DnDAction::None;
bool m_accepted = false;
};
}
}

View file

@ -21,9 +21,6 @@
#include "xwayland.h"
#include "xwldrophandler.h"
#include <KWayland/Client/compositor.h>
#include <KWayland/Client/surface.h>
#include <KWaylandServer/compositor_interface.h>
#include <KWaylandServer/seat_interface.h>
#include <KWaylandServer/datasource_interface.h>
@ -80,44 +77,6 @@ Dnd::Dnd(xcb_atom_t atom, QObject *parent)
connect(waylandServer()->seat(), &KWaylandServer::SeatInterface::dragStarted, this, &Dnd::startDrag);
connect(waylandServer()->seat(), &KWaylandServer::SeatInterface::dragEnded, this, &Dnd::endDrag);
const auto *comp = waylandServer()->compositor();
m_surface = waylandServer()->internalCompositor()->createSurface(this);
m_surface->setInputRegion(nullptr);
m_surface->commit(KWayland::Client::Surface::CommitFlag::None);
auto *dc = new QMetaObject::Connection();
*dc = connect(comp, &KWaylandServer::CompositorInterface::surfaceCreated, this,
[this, dc](KWaylandServer::SurfaceInterface *si) {
// TODO: how to make sure that it is the iface of m_surface?
if (m_surfaceIface || si->client() != waylandServer()->internalConnection()) {
return;
}
QObject::disconnect(*dc);
delete dc;
m_surfaceIface = si;
connect(workspace(), &Workspace::clientActivated, this,
[this](AbstractClient *ac) {
if (!ac || !ac->inherits("KWin::X11Client")) {
return;
}
auto *surface = ac->surface();
if (surface) {
surface->setDataProxy(m_surfaceIface);
} else {
auto *dc = new QMetaObject::Connection();
*dc = connect(ac, &AbstractClient::surfaceChanged, this, [this, ac, dc] {
if (auto *surface = ac->surface()) {
surface->setDataProxy(m_surfaceIface);
QObject::disconnect(*dc);
delete dc;
}
}
);
}
});
}
);
waylandServer()->dispatch();
}
void Dnd::doHandleXfixesNotify(xcb_xfixes_selection_notify_event_t *event)
@ -153,7 +112,6 @@ void Dnd::doHandleXfixesNotify(xcb_xfixes_selection_notify_event_t *event)
if (!source) {
return;
}
DataBridge::self()->dataDeviceIface()->updateProxy(originSurface);
m_currentDrag = new XToWlDrag(source);
}
@ -161,7 +119,6 @@ void Dnd::x11OffersChanged(const QStringList &added, const QStringList &removed)
{
Q_UNUSED(added);
Q_UNUSED(removed);
// TODO: handled internally
}
bool Dnd::handleClientMessage(xcb_client_message_event_t *event)
@ -179,17 +136,14 @@ bool Dnd::handleClientMessage(xcb_client_message_event_t *event)
DragEventReply Dnd::dragMoveFilter(Toplevel *target, const QPoint &pos)
{
// This filter only is used when a drag is in process.
Q_ASSERT(m_currentDrag);
return m_currentDrag->moveFilter(target, pos);
}
void Dnd::startDrag()
{
auto *ddi = waylandServer()->seat()->dragSource();
if (ddi == DataBridge::self()->dataDeviceIface()) {
// X to Wl drag, started by us, is in progress.
Q_ASSERT(m_currentDrag);
auto dragSource = waylandServer()->seat()->dragSource();
if (qobject_cast<XwlDataSource*>(dragSource)) {
return;
}
@ -199,7 +153,7 @@ void Dnd::startDrag()
// New Wl to X drag, init drag and Wl source.
m_currentDrag = new WlToXDrag();
auto source = new WlSource(this);
source->setDataSourceIface(ddi->dragSource());
source->setDataSourceIface(dragSource);
setWlSource(source);
ownSelection(true);
}
@ -223,5 +177,36 @@ void Dnd::clearOldDrag(Drag *drag)
delete drag;
}
using DnDAction = KWaylandServer::DataDeviceManagerInterface::DnDAction;
using DnDActions = KWaylandServer::DataDeviceManagerInterface::DnDActions;
DnDAction Dnd::atomToClientAction(xcb_atom_t atom)
{
if (atom == atoms->xdnd_action_copy) {
return DnDAction::Copy;
} else if (atom == atoms->xdnd_action_move) {
return DnDAction::Move;
} else if (atom == atoms->xdnd_action_ask) {
// we currently do not support it - need some test client first
return DnDAction::None;
// return DnDAction::Ask;
}
return DnDAction::None;
}
xcb_atom_t Dnd::clientActionToAtom(DnDAction action)
{
if (action == DnDAction::Copy) {
return atoms->xdnd_action_copy;
} else if (action == DnDAction::Move) {
return atoms->xdnd_action_move;
} else if (action == DnDAction::Ask) {
// we currently do not support it - need some test client first
return XCB_ATOM_NONE;
// return atoms->xdnd_action_ask;
}
return XCB_ATOM_NONE;
}
} // namespace Xwl
} // namespace KWin

View file

@ -11,6 +11,7 @@
#include "selection.h"
#include <KWaylandServer/datadevicemanager_interface.h>
#include <QPoint>
namespace KWaylandServer

View file

@ -3,6 +3,8 @@
This file is part of the KDE project.
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2021 David Edmundson <davidedmundson@kde.org>
SPDX-FileCopyrightText: 2021 David Redondo <kde@david-redondo.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
@ -12,18 +14,17 @@
#include "dnd.h"
#include "selection_source.h"
#include "xwayland.h"
#include "datasource.h"
#include "abstract_client.h"
#include "atoms.h"
#include "wayland_server.h"
#include "workspace.h"
#include <KWayland/Client/datadevice.h>
#include <KWayland/Client/datasource.h>
#include <KWaylandServer/datasource_interface.h>
#include <KWaylandServer/seat_interface.h>
#include <KWaylandServer/surface_interface.h>
#include <KWaylandServer/datadevice_interface.h>
#include <QMouseEvent>
#include <QTimer>
@ -33,36 +34,8 @@ namespace KWin
namespace Xwl
{
using DnDAction = KWayland::Client::DataDeviceManager::DnDAction;
using DnDActions = KWayland::Client::DataDeviceManager::DnDActions;
static DnDAction atomToClientAction(xcb_atom_t atom)
{
if (atom == atoms->xdnd_action_copy) {
return DnDAction::Copy;
} else if (atom == atoms->xdnd_action_move) {
return DnDAction::Move;
} else if (atom == atoms->xdnd_action_ask) {
// we currently do not support it - need some test client first
return DnDAction::None;
// return DnDAction::Ask;
}
return DnDAction::None;
}
xcb_atom_t clientActionToAtom(DnDAction action)
{
if (action == DnDAction::Copy) {
return atoms->xdnd_action_copy;
} else if (action == DnDAction::Move) {
return atoms->xdnd_action_move;
} else if (action == DnDAction::Ask) {
// we currently do not support it - need some test client first
return XCB_ATOM_NONE;
// return atoms->xdnd_action_ask;
}
return XCB_ATOM_NONE;
}
using DnDAction = KWaylandServer::DataDeviceManagerInterface::DnDAction;
using DnDActions = KWaylandServer::DataDeviceManagerInterface::DnDActions;
static QStringList atomToMimeTypes(xcb_atom_t atom)
{
@ -103,9 +76,7 @@ XToWlDrag::XToWlDrag(X11Source *source)
Q_UNUSED(fd);
m_dataRequests << QPair<xcb_timestamp_t, bool>(m_source->timestamp(), false);
});
auto *ddm = waylandServer()->internalDataDeviceManager();
m_dataSource = ddm->createDataSource(this);
connect(m_dataSource, &KWayland::Client::DataSource::dragAndDropPerformed, this, [this] {
connect(&m_selectionSource, &XwlDataSource::dropped, this, [this] {
m_performed = true;
if (m_visit) {
connect(m_visit, &WlVisit::finish, this, [this](WlVisit *visit) {
@ -124,43 +95,22 @@ XToWlDrag::XToWlDrag(X11Source *source)
}
});
}
// Dave do we need this async finish check anymore?
checkForFinished();
});
connect(m_dataSource, &KWayland::Client::DataSource::dragAndDropFinished, this, [this] {
// this call is not reliably initiated by Wayland clients
connect(&m_selectionSource, &XwlDataSource::finished, this, [this] {
checkForFinished();
});
connect(&m_selectionSource, &XwlDataSource::dataRequested, source, &X11Source::startTransfer);
// source does _not_ take ownership of m_dataSource
source->setDataSource(m_dataSource);
auto *dc = new QMetaObject::Connection();
*dc = connect(waylandServer()->dataDeviceManager(), &KWaylandServer::DataDeviceManagerInterface::dataSourceCreated, this,
[this, dc](KWaylandServer::DataSourceInterface *dsi) {
Q_ASSERT(dsi);
if (dsi->client() != waylandServer()->internalConnection()->client()) {
return;
}
QObject::disconnect(*dc);
delete dc;
connect(dsi, &KWaylandServer::DataSourceInterface::mimeTypeOffered, this, &XToWlDrag::offerCallback);
}
);
// Start drag with serial of last left pointer button press.
// This means X to Wl drags can only be executed with the left pointer button being pressed.
// For touch and (maybe) other pointer button drags we have to revisit this.
//
// Until then we accept the restriction for Xwayland clients.
DataBridge::self()->dataDevice()->startDrag(waylandServer()->seat()->pointerButtonSerial(Qt::LeftButton),
m_dataSource,
DataBridge::self()->dnd()->surface());
waylandServer()->dispatch();
auto *seat = waylandServer()->seat();
int serial = waylandServer()->seat()->pointerButtonSerial(Qt::LeftButton);
// we know we are the focussed surface as dnd checks
seat->startDrag(&m_selectionSource, seat->focusedPointerSurface(), serial);
}
XToWlDrag::~XToWlDrag()
{
delete m_dataSource;
m_dataSource = nullptr;
}
DragEventReply XToWlDrag::moveFilter(Toplevel *target, const QPoint &pos)
@ -194,7 +144,7 @@ DragEventReply XToWlDrag::moveFilter(Toplevel *target, const QPoint &pos)
if (hasCurrent) {
// last received enter event is now void,
// wait for the next one
seat->setDragTarget(nullptr);
seat->setDragTarget(nullptr, nullptr);
}
return DragEventReply::Ignore;
}
@ -220,18 +170,12 @@ bool XToWlDrag::handleClientMessage(xcb_client_message_event_t *event)
void XToWlDrag::setDragAndDropAction(DnDAction action)
{
m_dataSource->setDragAndDropActions(action);
m_selectionSource.setSupportedDndActions(action);
}
DnDAction XToWlDrag::selectedDragAndDropAction()
{
// Take the last received action only from before the drag was performed,
// because the action gets reset as soon as the drag is performed
// (this seems to be a bug in KWayland -> TODO).
if (!m_performed) {
m_lastSelectedDragAndDropAction = m_dataSource->selectedDragAndDropAction();
}
return m_lastSelectedDragAndDropAction;
return m_selectionSource.selectedDragAndDropAction();
}
void XToWlDrag::setOffers(const Mimes &offers)
@ -250,32 +194,25 @@ void XToWlDrag::setOffers(const Mimes &offers)
return;
}
// TODO: make sure that offers are not changed in between visits
m_offersPending = m_offers = offers;
m_offers = offers;
QStringList mimeTypes;
mimeTypes.reserve(offers.size());
for (const auto &mimePair : offers) {
m_dataSource->offer(mimePair.first);
mimeTypes.append(mimePair.first);
}
m_selectionSource.setMimeTypes(mimeTypes);
setDragTarget();
}
using Mime = QPair<QString, xcb_atom_t>;
void XToWlDrag::offerCallback(const QString &mime)
{
m_offersPending.erase(std::remove_if(m_offersPending.begin(), m_offersPending.end(),
[mime](const Mime &m) { return m.first == mime; }));
if (m_offersPending.isEmpty() && m_visit && m_visit->entered()) {
setDragTarget();
}
}
void XToWlDrag::setDragTarget()
{
auto *ac = m_visit->target();
workspace()->activateClient(ac);
auto seat = waylandServer()->seat();
auto dropTarget = seat->dropHandlerForSurface(ac->surface());
if (!dropTarget || !ac->surface()) {
return;
}
@ -470,7 +407,7 @@ bool WlVisit::handlePosition(xcb_client_message_event_t *event)
xcb_atom_t actionAtom = m_version > 1 ? data->data32[4] :
atoms->xdnd_action_copy;
auto action = atomToClientAction(actionAtom);
auto action = Dnd::atomToClientAction(actionAtom);
if (action == DnDAction::None) {
// copy action is always possible in XDND

View file

@ -10,21 +10,14 @@
#define KWIN_XWL_DRAG_X
#include "drag.h"
#include "datasource.h"
#include <KWayland/Client/datadevicemanager.h>
#include <KWaylandServer/datadevicemanager_interface.h>
#include <QPoint>
#include <QPointer>
#include <QVector>
namespace KWayland
{
namespace Client
{
class DataSource;
}
}
namespace KWin
{
class Toplevel;
@ -49,8 +42,8 @@ public:
DragEventReply moveFilter(Toplevel *target, const QPoint &pos) override;
bool handleClientMessage(xcb_client_message_event_t *event) override;
void setDragAndDropAction(KWayland::Client::DataDeviceManager::DnDAction action);
KWayland::Client::DataDeviceManager::DnDAction selectedDragAndDropAction();
void setDragAndDropAction(KWaylandServer::DataDeviceManagerInterface::DnDAction action);
KWaylandServer::DataDeviceManagerInterface::DnDAction selectedDragAndDropAction();
bool end() override {
return false;
@ -61,15 +54,13 @@ public:
private:
void setOffers(const Mimes &offers);
void offerCallback(const QString &mime);
void setDragTarget();
bool checkForFinished();
KWayland::Client::DataSource *m_dataSource;
Mimes m_offers;
Mimes m_offersPending;
XwlDataSource m_selectionSource;
X11Source *m_source;
QVector<QPair<xcb_timestamp_t, bool> > m_dataRequests;
@ -78,7 +69,7 @@ private:
QVector<WlVisit *> m_oldVisits;
bool m_performed = false;
KWayland::Client::DataDeviceManager::DnDAction m_lastSelectedDragAndDropAction = KWayland::Client::DataDeviceManager::DnDAction::None;
KWaylandServer::DataDeviceManagerInterface::DnDAction m_lastSelectedDragAndDropAction = KWaylandServer::DataDeviceManagerInterface::DnDAction::None;
Q_DISABLE_COPY(XToWlDrag)
};
@ -139,7 +130,7 @@ private:
uint32_t m_version = 0;
xcb_atom_t m_actionAtom;
KWayland::Client::DataDeviceManager::DnDAction m_action = KWayland::Client::DataDeviceManager::DnDAction::None;
KWaylandServer::DataDeviceManagerInterface::DnDAction m_action = KWaylandServer::DataDeviceManagerInterface::DnDAction::None;
bool m_mapped = false;
bool m_entered = false;

View file

@ -13,11 +13,6 @@
#include "atoms.h"
#include "wayland_server.h"
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/datadevicemanager.h>
#include <KWayland/Client/datadevice.h>
#include <KWayland/Client/datasource.h>
#include <KWaylandServer/datadevice_interface.h>
#include <KWaylandServer/datasource_interface.h>
#include <KWaylandServer/seat_interface.h>
@ -51,9 +46,13 @@ void WlSource::setDataSourceIface(KWaylandServer::AbstractDataSource *dsi)
for (const auto &mime : dsi->mimeTypes()) {
m_offers << mime;
}
// TODO, this can probably be removed after some testing
// all mime types should be constant after a data source is set
m_offerConnection = connect(dsi,
&KWaylandServer::DataSourceInterface::mimeTypeOffered,
this, &WlSource::receiveOffer);
m_dsi = dsi;
}
@ -155,7 +154,6 @@ bool WlSource::checkStartTransfer(xcb_selection_request_event_t *event)
}
m_dsi->requestData(*mimeIt, p[1]);
waylandServer()->dispatch();
Q_EMIT transferReady(new xcb_selection_request_event_t(*event), p[0]);
return true;
@ -250,26 +248,8 @@ void X11Source::handleTargets()
free(reply);
}
void X11Source::setDataSource(KWayland::Client::DataSource *dataSource)
{
Q_ASSERT(dataSource);
if (m_dataSource) {
delete m_dataSource;
}
m_dataSource = dataSource;
for (const Mime &offer : qAsConst(m_offers)) {
dataSource->offer(offer.first);
}
connect(dataSource, &KWayland::Client::DataSource::sendDataRequested,
this, &X11Source::startTransfer);
}
void X11Source::setOffers(const Mimes &offers)
{
// TODO: share code with handleTargets and emit signals accordingly?
m_offers = offers;
}

View file

@ -19,13 +19,6 @@ class QSocketNotifier;
struct xcb_selection_request_event_t;
struct xcb_xfixes_selection_notify_event_t;
namespace KWayland
{
namespace Client
{
class DataSource;
}
}
namespace KWaylandServer
{
class DataDeviceInterface;
@ -119,16 +112,6 @@ class X11Source : public SelectionSource
public:
X11Source(Selection *selection, xcb_xfixes_selection_notify_event_t *event);
/**
* @param ds must exist.
*
* X11Source does not take ownership of it in general, but if the function
* is called again, it will delete the previous data source.
*/
void setDataSource(KWayland::Client::DataSource *dataSource);
KWayland::Client::DataSource *dataSource() const {
return m_dataSource;
}
void getTargets();
Mimes offers() const {
@ -151,7 +134,6 @@ private:
void handleTargets();
xcb_window_t m_owner;
KWayland::Client::DataSource *m_dataSource = nullptr;
Mimes m_offers;

View file

@ -16,11 +16,6 @@
#include "wayland_server.h"
#include "workspace.h"
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/datadevicemanager.h>
#include <KWayland/Client/datadevice.h>
#include <KWayland/Client/datasource.h>
#include <KWaylandServer/datadevice_interface.h>
#include <KWaylandServer/datasource_interface.h>
#include <KWaylandServer/seat_interface.h>