diff --git a/CMakeLists.txt b/CMakeLists.txt
index fbbec3b7ea..f8eb8a19f3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -659,6 +659,10 @@ set(kwin_XWAYLAND_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/xwl/selection_source.cpp
${CMAKE_CURRENT_SOURCE_DIR}/xwl/transfer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/xwl/clipboard.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/xwl/dnd.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/xwl/drag.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/xwl/drag_wl.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/xwl/drag_x.cpp
)
include(ECMQtDeclareLoggingCategory)
ecm_qt_declare_logging_category(kwin_XWAYLAND_SRCS
diff --git a/atoms.cpp b/atoms.cpp
index 159c8f9b49..540ade8bae 100644
--- a/atoms.cpp
+++ b/atoms.cpp
@@ -43,8 +43,18 @@ Atoms::Atoms()
, kde_net_wm_user_creation_time(QByteArrayLiteral("_KDE_NET_WM_USER_CREATION_TIME"))
, net_wm_take_activity(QByteArrayLiteral("_NET_WM_TAKE_ACTIVITY"))
, net_wm_window_opacity(QByteArrayLiteral("_NET_WM_WINDOW_OPACITY"))
+ , xdnd_selection(QByteArrayLiteral("XdndSelection"))
, xdnd_aware(QByteArrayLiteral("XdndAware"))
+ , xdnd_enter(QByteArrayLiteral("XdndEnter"))
+ , xdnd_type_list(QByteArrayLiteral("XdndTypeList"))
, xdnd_position(QByteArrayLiteral("XdndPosition"))
+ , xdnd_status(QByteArrayLiteral("XdndStatus"))
+ , xdnd_action_copy(QByteArrayLiteral("XdndActionCopy"))
+ , xdnd_action_move(QByteArrayLiteral("XdndActionMove"))
+ , xdnd_action_ask(QByteArrayLiteral("XdndActionAsk"))
+ , xdnd_drop(QByteArrayLiteral("XdndDrop"))
+ , xdnd_leave(QByteArrayLiteral("XdndLeave"))
+ , xdnd_finished(QByteArrayLiteral("XdndFinished"))
, net_frame_extents(QByteArrayLiteral("_NET_FRAME_EXTENTS"))
, kde_net_wm_frame_strut(QByteArrayLiteral("_KDE_NET_WM_FRAME_STRUT"))
, net_wm_sync_request_counter(QByteArrayLiteral("_NET_WM_SYNC_REQUEST_COUNTER"))
diff --git a/atoms.h b/atoms.h
index e957998e47..0a6bfd414f 100644
--- a/atoms.h
+++ b/atoms.h
@@ -52,8 +52,18 @@ public:
Xcb::Atom kde_net_wm_user_creation_time;
Xcb::Atom net_wm_take_activity;
Xcb::Atom net_wm_window_opacity;
+ Xcb::Atom xdnd_selection;
Xcb::Atom xdnd_aware;
+ Xcb::Atom xdnd_enter;
+ Xcb::Atom xdnd_type_list;
Xcb::Atom xdnd_position;
+ Xcb::Atom xdnd_status;
+ Xcb::Atom xdnd_action_copy;
+ Xcb::Atom xdnd_action_move;
+ Xcb::Atom xdnd_action_ask;
+ Xcb::Atom xdnd_drop;
+ Xcb::Atom xdnd_leave;
+ Xcb::Atom xdnd_finished;
Xcb::Atom net_frame_extents;
Xcb::Atom kde_net_wm_frame_strut;
Xcb::Atom net_wm_sync_request_counter;
diff --git a/input.cpp b/input.cpp
index 4faa7a5359..2199970603 100644
--- a/input.cpp
+++ b/input.cpp
@@ -44,6 +44,7 @@ along with this program. If not, see .
#include "popup_input_filter.h"
#include "shell_client.h"
#include "wayland_server.h"
+#include "xwl/xwayland_interface.h"
#include
#include
#include
@@ -1473,7 +1474,20 @@ public:
case QEvent::MouseMove: {
const auto pos = input()->globalPointer();
seat->setPointerPos(pos);
- if (Toplevel *t = input()->pointer()->at()) {
+
+ const auto eventPos = event->globalPos();
+ // TODO: use InputDeviceHandler::at() here and check isClient()?
+ Toplevel *t = input()->findManagedToplevel(eventPos);
+ if (auto *xwl = xwayland()) {
+ const auto ret = xwl->dragMoveFilter(t, eventPos);
+ if (ret == Xwl::DragEventReply::Ignore) {
+ return false;
+ } else if (ret == Xwl::DragEventReply::Take) {
+ break;
+ }
+ }
+
+ if (t) {
// TODO: consider decorations
if (t->surface() != seat->dragSurface()) {
if (AbstractClient *c = qobject_cast(t)) {
@@ -2082,6 +2096,15 @@ Toplevel *InputRedirection::findToplevel(const QPoint &pos)
}
}
}
+ return findManagedToplevel(pos);
+}
+
+Toplevel *InputRedirection::findManagedToplevel(const QPoint &pos)
+{
+ if (!Workspace::self()) {
+ return nullptr;
+ }
+ const bool isScreenLocked = waylandServer() && waylandServer()->isScreenLocked();
const ToplevelList &stacking = Workspace::self()->stackingOrder();
if (stacking.isEmpty()) {
return NULL;
diff --git a/input.h b/input.h
index 8097a92b48..706850da7a 100644
--- a/input.h
+++ b/input.h
@@ -165,6 +165,7 @@ public:
void uninstallInputEventSpy(InputEventSpy *spy);
Toplevel *findToplevel(const QPoint &pos);
+ Toplevel *findManagedToplevel(const QPoint &pos);
GlobalShortcutsManager *shortcuts() const {
return m_shortcuts;
}
diff --git a/wayland_server.cpp b/wayland_server.cpp
index 74cebe6602..ca4381d873 100644
--- a/wayland_server.cpp
+++ b/wayland_server.cpp
@@ -30,6 +30,7 @@ along with this program. If not, see .
#include
#include
#include
+#include
#include
#include
#include
@@ -113,6 +114,7 @@ void WaylandServer::destroyInternalConnection()
}
delete m_internalConnection.registry;
+ delete m_internalConnection.compositor;
delete m_internalConnection.seat;
delete m_internalConnection.ddm;
delete m_internalConnection.shm;
@@ -567,6 +569,10 @@ void WaylandServer::createInternalConnection()
[this, registry] {
m_internalConnection.interfacesAnnounced = true;
+ const auto compInterface = registry->interface(Registry::Interface::Compositor);
+ if (compInterface.name != 0) {
+ m_internalConnection.compositor = registry->createCompositor(compInterface.name, compInterface.version, this);
+ }
const auto seatInterface = registry->interface(Registry::Interface::Seat);
if (seatInterface.name != 0) {
m_internalConnection.seat = registry->createSeat(seatInterface.name, seatInterface.version, this);
diff --git a/wayland_server.h b/wayland_server.h
index 5d732a28a7..b055e8ceba 100644
--- a/wayland_server.h
+++ b/wayland_server.h
@@ -34,6 +34,7 @@ namespace Client
{
class ConnectionThread;
class Registry;
+class Compositor;
class Seat;
class DataDeviceManager;
class ShmPool;
@@ -178,6 +179,9 @@ public:
KWayland::Server::ClientConnection *screenLockerClientConnection() const {
return m_screenLockerClientConnection;
}
+ KWayland::Client::Compositor *internalCompositor() {
+ return m_internalConnection.compositor;
+ }
KWayland::Client::Seat *internalSeat() {
return m_internalConnection.seat;
}
@@ -263,6 +267,7 @@ private:
KWayland::Client::ConnectionThread *client = nullptr;
QThread *clientThread = nullptr;
KWayland::Client::Registry *registry = nullptr;
+ KWayland::Client::Compositor *compositor = nullptr;
KWayland::Client::Seat *seat = nullptr;
KWayland::Client::DataDeviceManager *ddm = nullptr;
KWayland::Client::ShmPool *shm = nullptr;
diff --git a/xwl/clipboard.cpp b/xwl/clipboard.cpp
index b33f5400c1..c2b0c655d5 100644
--- a/xwl/clipboard.cpp
+++ b/xwl/clipboard.cpp
@@ -26,7 +26,7 @@ along with this program. If not, see .
#include "wayland_server.h"
#include "workspace.h"
-#include "abstract_client.h"
+#include "client.h"
#include
#include
@@ -135,6 +135,14 @@ void Clipboard::checkWlSource()
void Clipboard::doHandleXfixesNotify(xcb_xfixes_selection_notify_event_t *event)
{
+ createX11Source(NULL);
+
+ const auto *ac = workspace()->activeClient();
+ if (!qobject_cast(ac)) {
+ // clipboard is only allowed to be acquired when Xwayland has focus
+ // TODO: can we make this stronger (window id comparision)?
+ return;
+ }
createX11Source(event);
auto *xSrc = x11Source();
if (xSrc) {
diff --git a/xwl/databridge.cpp b/xwl/databridge.cpp
index 38433f93f1..9d8fcc495f 100644
--- a/xwl/databridge.cpp
+++ b/xwl/databridge.cpp
@@ -21,6 +21,7 @@ along with this program. If not, see .
#include "xwayland.h"
#include "selection.h"
#include "clipboard.h"
+#include "dnd.h"
#include "atoms.h"
#include "wayland_server.h"
@@ -76,6 +77,7 @@ DataBridge::~DataBridge()
void DataBridge::init()
{
m_clipboard = new Clipboard(atoms->clipboard, this);
+ m_dnd = new Dnd(atoms->xdnd_selection, this);
waylandServer()->dispatch();
}
@@ -84,6 +86,9 @@ bool DataBridge::filterEvent(xcb_generic_event_t *event)
if (m_clipboard->filterEvent(event)) {
return true;
}
+ if (m_dnd->filterEvent(event)) {
+ return true;
+ }
if (event->response_type - Xwayland::self()->xfixes()->first_event == XCB_XFIXES_SELECTION_NOTIFY) {
return handleXfixesNotify((xcb_xfixes_selection_notify_event_t *)event);
}
@@ -96,11 +101,22 @@ bool DataBridge::handleXfixesNotify(xcb_xfixes_selection_notify_event_t *event)
if (atom == atoms->clipboard) {
return m_clipboard;
}
+ if (atom == atoms->xdnd_selection) {
+ return m_dnd;
+ }
return nullptr;
};
auto *sel = getSelection(event->selection);
return sel && sel->handleXfixesNotify(event);
}
+DragEventReply DataBridge::dragMoveFilter(Toplevel *target, QPoint pos)
+{
+ if (!m_dnd) {
+ return DragEventReply::Wayland;
+ }
+ return m_dnd->dragMoveFilter(target, pos);
+}
+
}
}
diff --git a/xwl/databridge.h b/xwl/databridge.h
index ec8109ed3e..ca5d2b4e24 100644
--- a/xwl/databridge.h
+++ b/xwl/databridge.h
@@ -21,6 +21,7 @@ along with this program. If not, see .
#define KWIN_XWL_DATABRIDGE
#include
+#include
#include
@@ -38,11 +39,14 @@ class SurfaceInterface;
namespace KWin
{
+class Toplevel;
namespace Xwl
{
class Xwayland;
class Clipboard;
+class Dnd;
+enum class DragEventReply;
/*
* Interface class for all data sharing in the context of X selections
@@ -60,13 +64,20 @@ public:
~DataBridge();
bool filterEvent(xcb_generic_event_t *event);
+ DragEventReply dragMoveFilter(Toplevel *target, QPoint pos);
- KWayland::Client::DataDevice *dataDevice() const {
+ KWayland::Client::DataDevice *dataDevice() const
+ {
return m_dd;
}
- KWayland::Server::DataDeviceInterface *dataDeviceIface() const {
+ KWayland::Server::DataDeviceInterface *dataDeviceIface() const
+ {
return m_ddi;
}
+ Dnd* dnd() const
+ {
+ return m_dnd;
+ }
private:
void init();
@@ -74,6 +85,7 @@ private:
bool handleXfixesNotify(xcb_xfixes_selection_notify_event_t *event);
Clipboard *m_clipboard = nullptr;
+ Dnd *m_dnd = nullptr;
/* Internal data device interface */
KWayland::Client::DataDevice *m_dd = nullptr;
diff --git a/xwl/dnd.cpp b/xwl/dnd.cpp
new file mode 100644
index 0000000000..397d4de555
--- /dev/null
+++ b/xwl/dnd.cpp
@@ -0,0 +1,227 @@
+/********************************************************************
+ KWin - the KDE window manager
+ This file is part of the KDE project.
+
+Copyright 2019 Roman Gilg
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*********************************************************************/
+#include "dnd.h"
+
+#include "databridge.h"
+#include "selection_source.h"
+#include "drag_wl.h"
+#include "drag_x.h"
+
+#include "atoms.h"
+#include "wayland_server.h"
+#include "workspace.h"
+#include "xwayland.h"
+#include "abstract_client.h"
+
+#include
+#include
+
+#include
+#include
+
+#include
+
+#include
+
+namespace KWin
+{
+namespace Xwl
+{
+
+// version of DnD support in X
+const static uint32_t s_version = 5;
+uint32_t Dnd::version()
+{
+ return s_version;
+}
+
+Dnd::Dnd(xcb_atom_t atom, QObject *parent)
+ : Selection(atom, parent)
+{
+ auto *xcbConn = kwinApp()->x11Connection();
+
+ const uint32_t dndValues[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
+ XCB_EVENT_MASK_PROPERTY_CHANGE };
+ xcb_create_window(xcbConn,
+ XCB_COPY_FROM_PARENT,
+ window(),
+ kwinApp()->x11RootWindow(),
+ 0, 0,
+ 8192, 8192, // TODO: get current screen size and connect to changes
+ 0,
+ XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ Xwayland::self()->xcbScreen()->root_visual,
+ XCB_CW_EVENT_MASK,
+ dndValues);
+ registerXfixes();
+
+ xcb_change_property(xcbConn,
+ XCB_PROP_MODE_REPLACE,
+ window(),
+ atoms->xdnd_aware,
+ XCB_ATOM_ATOM,
+ 32, 1, &s_version);
+ xcb_flush(xcbConn);
+
+ connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragStarted, this, &Dnd::startDrag);
+ connect(waylandServer()->seat(), &KWayland::Server::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, &KWayland::Server::CompositorInterface::surfaceCreated, this,
+ [this, dc](KWayland::Server::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::Client")) {
+ 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)
+{
+ if (qobject_cast(m_currentDrag)) {
+ // X drag is in progress, rogue X client took over the selection.
+ return;
+ }
+ if (m_currentDrag) {
+ // Wl drag is in progress - don't overwrite by rogue X client,
+ // get it back instead!
+ ownSelection(true);
+ return;
+ }
+ createX11Source(NULL);
+ const auto *seat = waylandServer()->seat();
+ auto *originSurface = seat->focusedPointerSurface();
+ if (!originSurface) {
+ return;
+ }
+ if (originSurface->client() != waylandServer()->xWaylandConnection()) {
+ // focused surface client is not Xwayland - do not allow drag to start
+ // TODO: can we make this stronger (window id comparision)?
+ return;
+ }
+ if (!seat->isPointerButtonPressed(Qt::LeftButton)) {
+ // we only allow drags to be started on (left) pointer button being
+ // pressed for now
+ return;
+ }
+ createX11Source(event);
+ auto *xSrc = x11Source();
+ if (!xSrc) {
+ return;
+ }
+ DataBridge::self()->dataDeviceIface()->updateProxy(originSurface);
+ m_currentDrag = new XToWlDrag(xSrc);
+}
+
+void Dnd::x11OffersChanged(const QVector &added, const QVector &removed)
+{
+ Q_UNUSED(added);
+ Q_UNUSED(removed);
+ // TODO: handled internally
+}
+
+bool Dnd::handleClientMessage(xcb_client_message_event_t *event)
+{
+ for (auto *drag : m_oldDrags) {
+ if (drag->handleClientMessage(event)) {
+ return true;
+ }
+ }
+ if (m_currentDrag && m_currentDrag->handleClientMessage(event)) {
+ return true;
+ }
+ return false;
+}
+
+DragEventReply Dnd::dragMoveFilter(Toplevel *target, 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);
+ return;
+ }
+ // there can only ever be one Wl native drag at the same time
+ Q_ASSERT(!m_currentDrag);
+
+ // new Wl to X drag, init drag and Wl source
+ m_currentDrag = new WlToXDrag();
+ auto *wls = new WlSource(this, ddi);
+ wls->setDataSourceIface(ddi->dragSource());
+ setWlSource(wls);
+ ownSelection(true);
+}
+
+void Dnd::endDrag()
+{
+ Q_ASSERT(m_currentDrag);
+ if (m_currentDrag->end()) {
+ delete m_currentDrag;
+ } else {
+ connect(m_currentDrag, &Drag::finish, this, &Dnd::clearOldDrag);
+ m_oldDrags << m_currentDrag;
+ }
+ m_currentDrag = nullptr;
+}
+
+void Dnd::clearOldDrag(Drag *drag)
+{
+ m_oldDrags.removeOne(drag);
+ delete drag;
+}
+
+}
+}
diff --git a/xwl/dnd.h b/xwl/dnd.h
new file mode 100644
index 0000000000..9269b23582
--- /dev/null
+++ b/xwl/dnd.h
@@ -0,0 +1,90 @@
+/********************************************************************
+ KWin - the KDE window manager
+ This file is part of the KDE project.
+
+Copyright 2019 Roman Gilg
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*********************************************************************/
+#ifndef KWIN_XWL_DND
+#define KWIN_XWL_DND
+
+#include "selection.h"
+
+#include
+
+namespace KWayland
+{
+namespace Client
+{
+class Surface;
+}
+namespace Server
+{
+class SurfaceInterface;
+}
+}
+
+namespace KWin
+{
+class Toplevel;
+
+namespace Xwl
+{
+class Drag;
+enum class DragEventReply;
+
+/**
+ * Represents the drag and drop mechanism, on X side this is the XDND protocol.
+ * For more information on XDND see: http://johnlindal.wixsite.com/xdnd
+ */
+class Dnd : public Selection
+{
+ Q_OBJECT
+public:
+ explicit Dnd(xcb_atom_t atom, QObject *parent);
+
+ static uint32_t version();
+
+ void doHandleXfixesNotify(xcb_xfixes_selection_notify_event_t *event) override;
+ void x11OffersChanged(const QVector &added, const QVector &removed) override;
+ bool handleClientMessage(xcb_client_message_event_t *event) override;
+
+ DragEventReply dragMoveFilter(Toplevel *target, QPoint pos);
+
+ KWayland::Server::SurfaceInterface *surfaceIface() const {
+ return m_surfaceIface;
+ }
+ KWayland::Client::Surface *surface() const {
+ return m_surface;
+ }
+
+private:
+ // start and end Wl native client drags (Wl -> Xwl)
+ void startDrag();
+ void endDrag();
+ void clearOldDrag(Drag *drag);
+
+ // active drag or null when no drag active
+ Drag *m_currentDrag = nullptr;
+ QVector m_oldDrags;
+
+ KWayland::Client::Surface *m_surface;
+ KWayland::Server::SurfaceInterface *m_surfaceIface = nullptr;
+};
+
+}
+}
+
+#endif
diff --git a/xwl/drag.cpp b/xwl/drag.cpp
new file mode 100644
index 0000000000..26ed7fb839
--- /dev/null
+++ b/xwl/drag.cpp
@@ -0,0 +1,78 @@
+/********************************************************************
+ KWin - the KDE window manager
+ This file is part of the KDE project.
+
+Copyright 2019 Roman Gilg
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*********************************************************************/
+#include "drag.h"
+
+#include "atoms.h"
+
+namespace KWin
+{
+namespace Xwl
+{
+
+void Drag::sendClientMessage(xcb_window_t target, xcb_atom_t type, xcb_client_message_data_t *data)
+{
+ xcb_client_message_event_t event {
+ XCB_CLIENT_MESSAGE, // response_type
+ 32, // format
+ 0, // sequence
+ target, // window
+ type, // type
+ *data, // data
+ };
+
+ auto *xcbConn = kwinApp()->x11Connection();
+ xcb_send_event(xcbConn,
+ 0,
+ target,
+ XCB_EVENT_MASK_NO_EVENT,
+ reinterpret_cast(&event));
+ xcb_flush(xcbConn);
+}
+
+DnDAction Drag::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 Drag::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;
+}
+
+}
+}
diff --git a/xwl/drag.h b/xwl/drag.h
new file mode 100644
index 0000000000..d031581bbd
--- /dev/null
+++ b/xwl/drag.h
@@ -0,0 +1,63 @@
+/********************************************************************
+ KWin - the KDE window manager
+ This file is part of the KDE project.
+
+Copyright 2019 Roman Gilg
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*********************************************************************/
+#ifndef KWIN_XWL_DRAG
+#define KWIN_XWL_DRAG
+
+#include
+
+#include
+
+#include
+
+namespace KWin
+{
+class Toplevel;
+
+namespace Xwl
+{
+enum class DragEventReply;
+
+using DnDAction = KWayland::Client::DataDeviceManager::DnDAction;
+
+/**
+ * An ongoing drag operation.
+ */
+class Drag : public QObject
+{
+ Q_OBJECT
+public:
+ static void sendClientMessage(xcb_window_t target, xcb_atom_t type, xcb_client_message_data_t *data);
+ static DnDAction atomToClientAction(xcb_atom_t atom);
+ static xcb_atom_t clientActionToAtom(DnDAction action);
+
+ virtual ~Drag() = default;
+ virtual bool handleClientMessage(xcb_client_message_event_t *event) = 0;
+ virtual DragEventReply moveFilter(Toplevel *target, QPoint pos) = 0;
+
+ virtual bool end() = 0;
+
+Q_SIGNALS:
+ void finish(Drag *self);
+};
+
+}
+}
+
+#endif
diff --git a/xwl/drag_wl.cpp b/xwl/drag_wl.cpp
new file mode 100644
index 0000000000..5ad724e1fb
--- /dev/null
+++ b/xwl/drag_wl.cpp
@@ -0,0 +1,446 @@
+/********************************************************************
+ KWin - the KDE window manager
+ This file is part of the KDE project.
+
+Copyright 2019 Roman Gilg
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*********************************************************************/
+#include "drag_wl.h"
+
+#include "xwayland.h"
+#include "databridge.h"
+#include "dnd.h"
+
+#include "atoms.h"
+#include "wayland_server.h"
+#include "workspace.h"
+#include "client.h"
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+namespace KWin
+{
+namespace Xwl
+{
+
+WlToXDrag::WlToXDrag()
+{
+ m_dsi = waylandServer()->seat()->dragSource()->dragSource();
+}
+
+DragEventReply WlToXDrag::moveFilter(Toplevel *target, QPoint pos)
+{
+ AbstractClient *ac = qobject_cast(target);
+ auto *seat = waylandServer()->seat();
+ if (m_visit && m_visit->target() == ac) {
+ // no target change
+ return DragEventReply::Take;
+ }
+ // leave current target
+ if (m_visit) {
+ seat->setDragTarget(nullptr);
+ m_visit->leave();
+ delete m_visit;
+ m_visit = nullptr;
+ }
+ if (!qobject_cast(ac)) {
+ // no target or wayland native target,
+ // handled by input code directly
+ return DragEventReply::Wayland;
+ }
+ // new target
+ workspace()->activateClient(ac, false);
+ seat->setDragTarget(DataBridge::self()->dnd()->surfaceIface(), pos, ac->inputTransformation());
+ m_visit = new Xvisit(this, ac);
+ return DragEventReply::Take;
+}
+
+bool WlToXDrag::handleClientMessage(xcb_client_message_event_t *event)
+{
+ if (m_visit && m_visit->handleClientMessage(event)) {
+ return true;
+ }
+ return false;
+}
+
+bool WlToXDrag::end()
+{
+ if (!m_visit || m_visit->finished()) {
+ delete m_visit;
+ m_visit = nullptr;
+ return true;
+ }
+ connect(m_visit, &Xvisit::finish, this, [this](Xvisit *visit) {
+ Q_ASSERT(m_visit == visit);
+ delete visit;
+ m_visit = nullptr;
+ // we direclty allow to delete previous visits
+ Q_EMIT finish(this);
+ });
+ return false;
+}
+
+Xvisit::Xvisit(WlToXDrag *drag, AbstractClient *target)
+ : QObject(drag),
+ m_drag(drag),
+ m_target(target)
+{
+ // first check supported DND version
+ auto *xcbConn = kwinApp()->x11Connection();
+ xcb_get_property_cookie_t cookie = xcb_get_property(xcbConn,
+ 0,
+ m_target->window(),
+ atoms->xdnd_aware,
+ XCB_GET_PROPERTY_TYPE_ANY,
+ 0, 1);
+ auto *reply = xcb_get_property_reply(xcbConn, cookie, NULL);
+ if (reply == NULL) {
+ doFinish();
+ return;
+ }
+ if (reply->type != XCB_ATOM_ATOM) {
+ doFinish();
+ free(reply);
+ return;
+ }
+ xcb_atom_t *value = static_cast(xcb_get_property_value(reply));
+ m_version = qMin(*value, Dnd::version());
+ if (m_version < 1) {
+ // minimal version we accept is 1
+ doFinish();
+ free(reply);
+ return;
+ }
+ free(reply);
+
+ const auto *dd = DataBridge::self()->dataDevice();
+ // proxy drop
+ m_enterCon = connect(dd, &KWayland::Client::DataDevice::dragEntered,
+ this, &Xvisit::receiveOffer);
+ m_dropCon = connect(dd, &KWayland::Client::DataDevice::dropped,
+ this, &Xvisit::drop);
+}
+
+bool Xvisit::handleClientMessage(xcb_client_message_event_t *event)
+{
+ if (event->type == atoms->xdnd_status) {
+ return handleStatus(event);
+ } else if (event->type == atoms->xdnd_finished) {
+ return handleFinished(event);
+ }
+ return false;
+}
+
+bool Xvisit::handleStatus(xcb_client_message_event_t *ev)
+{
+ xcb_client_message_data_t *data = &ev->data;
+ if (data->data32[0] != m_target->window()) {
+ // wrong target window
+ return false;
+ }
+
+ m_accepts = data->data32[1] & 1;
+ xcb_atom_t actionAtom = data->data32[4];
+
+ // TODO: we could optimize via rectangle in data32[2] and data32[3]
+
+ // position round trip finished
+ m_pos.pending = false;
+
+ if (!m_state.dropped) {
+ // as long as the drop is not yet done determine requested action
+ m_preferredAction = Drag::atomToClientAction(actionAtom);
+ determineProposedAction();
+ requestDragAndDropAction();
+ }
+
+ if (m_pos.cached) {
+ // send cached position
+ m_pos.cached = false;
+ sendPosition(m_pos.cache);
+ } else if (m_state.dropped) {
+ // drop was done in between, now close it out
+ drop();
+ }
+ return true;
+}
+
+bool Xvisit::handleFinished(xcb_client_message_event_t *ev)
+{
+ xcb_client_message_data_t *data = &ev->data;
+
+ if (data->data32[0] != m_target->window()) {
+ // different target window
+ return false;
+ }
+
+ if (!m_state.dropped) {
+ // drop was never done
+ doFinish();
+ return true;
+ }
+
+ const bool success = m_version > 4 ? data->data32[1] & 1 : true;
+ const xcb_atom_t usedActionAtom = m_version > 4 ? data->data32[2] :
+ static_cast(XCB_ATOM_NONE);
+ Q_UNUSED(success);
+ Q_UNUSED(usedActionAtom);
+
+ // data offer might have been deleted already by the DataDevice
+ if (!m_dataOffer.isNull()) {
+ m_dataOffer->dragAndDropFinished();
+ delete m_dataOffer;
+ m_dataOffer = nullptr;
+ }
+ doFinish();
+ return true;
+}
+
+void Xvisit::sendPosition(const QPointF &globalPos)
+{
+ const int16_t x = globalPos.x();
+ const int16_t y = globalPos.y();
+
+ if (m_pos.pending) {
+ m_pos.cache = QPoint(x, y);
+ m_pos.cached = true;
+ return;
+ }
+ m_pos.pending = true;
+
+ xcb_client_message_data_t data = {0};
+ data.data32[0] = DataBridge::self()->dnd()->window();
+ data.data32[2] = (x << 16) | y;
+ data.data32[3] = XCB_CURRENT_TIME;
+ data.data32[4] = Drag::clientActionToAtom(m_proposedAction);
+
+ Drag::sendClientMessage(m_target->window(), atoms->xdnd_position, &data);
+}
+
+void Xvisit::leave()
+{
+ Q_ASSERT(!m_state.dropped);
+ if (m_state.finished) {
+ // was already finished
+ return;
+ }
+ // we only need to leave, when we entered before
+ if (m_state.entered) {
+ sendLeave();
+ }
+ doFinish();
+}
+
+void Xvisit::receiveOffer()
+{
+ if (m_state.finished) {
+ // already ended
+ return;
+ }
+
+ Q_ASSERT(m_dataOffer.isNull());
+ m_dataOffer = DataBridge::self()->dataDevice()->dragOffer();
+ Q_ASSERT(!m_dataOffer.isNull());
+
+ retrieveSupportedActions();
+ m_actionCon = connect(m_dataOffer, &KWayland::Client::DataOffer::sourceDragAndDropActionsChanged,
+ this, &Xvisit::retrieveSupportedActions);
+ enter();
+}
+
+void Xvisit::enter()
+{
+ m_state.entered = true;
+ // send enter event and current position to X client
+ sendEnter();
+ sendPosition(waylandServer()->seat()->pointerPos());
+
+ // proxy future pointer position changes
+ m_motionCon = connect(waylandServer()->seat(),
+ &KWayland::Server::SeatInterface::pointerPosChanged,
+ this, &Xvisit::sendPosition);
+}
+
+void Xvisit::sendEnter()
+{
+ xcb_client_message_data_t data = {0};
+ data.data32[0] = DataBridge::self()->dnd()->window();
+ data.data32[1] = m_version << 24;
+
+ // TODO: replace this with the mime type getter from m_dataOffer,
+ // then we can get rid of m_drag.
+ const auto mimeTypesNames = m_drag->dataSourceIface()->mimeTypes();
+ const int mimesCount = mimeTypesNames.size();
+ size_t cnt = 0;
+ size_t totalCnt = 0;
+ for (const auto mimeName : mimeTypesNames) {
+ // 3 mimes and less can be sent directly in the XdndEnter message
+ if (totalCnt == 3) {
+ break;
+ }
+ const auto atom = Selection::mimeTypeToAtom(mimeName);
+
+ if (atom != XCB_ATOM_NONE) {
+ data.data32[cnt + 2] = atom;
+ cnt++;
+ }
+ totalCnt++;
+ }
+ for (int i = cnt; i < 4; i++) {
+ data.data32[i + 2] = XCB_ATOM_NONE;
+ }
+
+ if (mimesCount > 3) {
+ // need to first transfer all available mime types
+ data.data32[1] |= 1;
+
+ QVector targets;
+ targets.resize(mimesCount);
+
+ size_t cnt = 0;
+ for (const auto mimeName : mimeTypesNames) {
+ const auto atom = Selection::mimeTypeToAtom(mimeName);
+ if (atom != XCB_ATOM_NONE) {
+ targets[cnt] = atom;
+ cnt++;
+ }
+ }
+
+ xcb_change_property(kwinApp()->x11Connection(),
+ XCB_PROP_MODE_REPLACE,
+ DataBridge::self()->dnd()->window(),
+ atoms->xdnd_type_list,
+ XCB_ATOM_ATOM,
+ 32, cnt, targets.data());
+ }
+ Drag::sendClientMessage(m_target->window(), atoms->xdnd_enter, &data);
+}
+
+void Xvisit::sendDrop(uint32_t time)
+{
+ xcb_client_message_data_t data = {0};
+ data.data32[0] = DataBridge::self()->dnd()->window();
+ data.data32[2] = time;
+
+ Drag::sendClientMessage(m_target->window(), atoms->xdnd_drop, &data);
+
+ if (m_version < 2) {
+ doFinish();
+ }
+}
+
+void Xvisit::sendLeave()
+{
+ xcb_client_message_data_t data = {0};
+ data.data32[0] = DataBridge::self()->dnd()->window();
+ Drag::sendClientMessage(m_target->window(), atoms->xdnd_leave, &data);
+}
+
+void Xvisit::retrieveSupportedActions()
+{
+ m_supportedActions = m_dataOffer->sourceDragAndDropActions();
+ determineProposedAction();
+ requestDragAndDropAction();
+}
+
+void Xvisit::determineProposedAction()
+{
+ DnDAction oldProposedAction = m_proposedAction;
+ if (m_supportedActions.testFlag(m_preferredAction)) {
+ m_proposedAction = m_preferredAction;
+ } else if (m_supportedActions.testFlag(DnDAction::Copy)) {
+ m_proposedAction = DnDAction::Copy;
+ } else {
+ m_proposedAction = DnDAction::None;
+ }
+ // send updated action to X target
+ if (oldProposedAction != m_proposedAction) {
+ sendPosition(waylandServer()->seat()->pointerPos());
+ }
+}
+
+void Xvisit::requestDragAndDropAction()
+{
+ if (m_dataOffer.isNull()) {
+ return;
+ }
+ const auto pref = m_preferredAction != DnDAction::None ? m_preferredAction:
+ DnDAction::Copy;
+ // we assume the X client supports Move, but this might be wrong - then
+ // the drag just cancels, if the user tries to force it.
+
+ m_dataOffer->setDragAndDropActions(DnDAction::Copy | DnDAction::Move, pref);
+ waylandServer()->dispatch();
+}
+
+void Xvisit::drop()
+{
+ Q_ASSERT(!m_state.finished);
+ m_state.dropped = true;
+ // stop further updates
+ // TODO: revisit when we allow ask action
+ stopConnections();
+ if (!m_state.entered) {
+ // wait for enter (init + offers)
+ return;
+ }
+ if (m_pos.pending) {
+ // wait for pending position roundtrip
+ return;
+ }
+ if (!m_accepts) {
+ // target does not accept current action/offer
+ sendLeave();
+ doFinish();
+ return;
+ }
+ // dnd session ended successfully
+ sendDrop(XCB_CURRENT_TIME);
+}
+
+void Xvisit::doFinish()
+{
+ m_state.finished = true;
+ m_pos.cached = false;
+ stopConnections();
+ Q_EMIT finish(this);
+}
+
+void Xvisit::stopConnections()
+{
+ // final outcome has been determined from Wayland side
+ // no more updates needed
+ disconnect(m_enterCon);
+ m_enterCon = QMetaObject::Connection();
+ disconnect(m_dropCon);
+ m_dropCon = QMetaObject::Connection();
+
+ disconnect(m_motionCon);
+ m_motionCon = QMetaObject::Connection();
+ disconnect(m_actionCon);
+ m_actionCon = QMetaObject::Connection();
+}
+
+}
+}
diff --git a/xwl/drag_wl.h b/xwl/drag_wl.h
new file mode 100644
index 0000000000..20e7965f77
--- /dev/null
+++ b/xwl/drag_wl.h
@@ -0,0 +1,160 @@
+/********************************************************************
+ KWin - the KDE window manager
+ This file is part of the KDE project.
+
+Copyright 2019 Roman Gilg
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*********************************************************************/
+#ifndef KWIN_XWL_DRAG_WL
+#define KWIN_XWL_DRAG_WL
+
+#include "drag.h"
+
+#include
+
+#include
+#include
+#include
+
+namespace KWayland
+{
+namespace Client
+{
+class Surface;
+}
+namespace Server
+{
+class DataDeviceInterface;
+class DataSourceInterface;
+class SurfaceInterface;
+}
+}
+
+namespace KWin
+{
+class Toplevel;
+class AbstractClient;
+
+namespace Xwl
+{
+class X11Source;
+enum class DragEventReply;
+class Xvisit;
+
+using DnDActions = KWayland::Client::DataDeviceManager::DnDActions;
+
+class WlToXDrag : public Drag
+{
+ Q_OBJECT
+public:
+ explicit WlToXDrag();
+
+ DragEventReply moveFilter(Toplevel *target, QPoint pos) override;
+ bool handleClientMessage(xcb_client_message_event_t *event) override;
+
+ bool end() override;
+
+ KWayland::Server::DataSourceInterface *dataSourceIface() const {
+ return m_dsi;
+ }
+
+private:
+ KWayland::Server::DataSourceInterface *m_dsi;
+ Xvisit *m_visit = nullptr;
+};
+
+// visit to an X window
+class Xvisit : public QObject
+{
+ Q_OBJECT
+public:
+ // TODO: handle ask action
+
+ Xvisit(WlToXDrag *drag, AbstractClient *target);
+
+ bool handleClientMessage(xcb_client_message_event_t *event);
+ bool handleStatus(xcb_client_message_event_t *ev);
+ bool handleFinished(xcb_client_message_event_t *ev);
+
+ void sendPosition(const QPointF &globalPos);
+ void leave();
+
+ bool finished() const {
+ return m_state.finished;
+ }
+ AbstractClient *target() const {
+ return m_target;
+ }
+
+Q_SIGNALS:
+ void finish(Xvisit *self);
+
+private:
+ void sendEnter();
+ void sendDrop(uint32_t time);
+ void sendLeave();
+
+ void receiveOffer();
+ void enter();
+
+ void retrieveSupportedActions();
+ void determineProposedAction();
+ void requestDragAndDropAction();
+ void setProposedAction();
+
+ void drop();
+
+ void doFinish();
+ void stopConnections();
+
+ WlToXDrag *m_drag;
+ AbstractClient *m_target;
+ uint32_t m_version = 0;
+
+ QMetaObject::Connection m_enterCon;
+ QMetaObject::Connection m_motionCon;
+ QMetaObject::Connection m_actionCon;
+ QMetaObject::Connection m_dropCon;
+
+ struct {
+ bool pending = false;
+ bool cached = false;
+ QPoint cache;
+ } m_pos;
+
+ // Must be QPointer, because KWayland::Client::DataDevice
+ // might delete it.
+ QPointer m_dataOffer;
+
+ // supported by the Wl source
+ DnDActions m_supportedActions = DnDAction::None;
+ // preferred by the X client
+ DnDAction m_preferredAction = DnDAction::None;
+ // decided upon by the compositor
+ DnDAction m_proposedAction = DnDAction::None;
+
+ struct {
+ bool entered = false;
+ bool dropped = false;
+ bool finished = false;
+ } m_state;
+
+ bool m_accepts = false;
+};
+
+}
+}
+
+#endif
diff --git a/xwl/drag_x.cpp b/xwl/drag_x.cpp
new file mode 100644
index 0000000000..2e73ed1213
--- /dev/null
+++ b/xwl/drag_x.cpp
@@ -0,0 +1,531 @@
+/********************************************************************
+ KWin - the KDE window manager
+ This file is part of the KDE project.
+
+Copyright 2019 Roman Gilg
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*********************************************************************/
+#include "drag_x.h"
+
+#include "databridge.h"
+#include "dnd.h"
+#include "selection_source.h"
+#include "xwayland.h"
+
+#include "abstract_client.h"
+#include "atoms.h"
+#include "wayland_server.h"
+#include "workspace.h"
+
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+
+namespace KWin {
+namespace Xwl {
+
+XToWlDrag::XToWlDrag(X11Source *source)
+ : m_src(source)
+{
+ connect(DataBridge::self()->dnd(), &Dnd::transferFinished, this, [this](xcb_timestamp_t eventTime) {
+ // we use this mechanism, because the finished call is not
+ // reliable done by Wayland clients
+ auto it = std::find_if(m_dataRequests.begin(), m_dataRequests.end(), [this, eventTime](QPair req) {
+ return req.first == eventTime;
+ });
+ if (it == m_dataRequests.end()) {
+ // transfer finished for a different drag
+ return;
+ }
+ Q_ASSERT(!(*it).second);
+ (*it).second = true;
+ checkForFinished();
+ });
+ connect(source, &X11Source::transferReady, this, [this](xcb_atom_t target, qint32 fd) {
+ Q_UNUSED(target);
+ Q_UNUSED(fd);
+ m_dataRequests << QPair(m_src->timestamp(), false);
+ });
+ auto *ddm = waylandServer()->internalDataDeviceManager();
+ m_dataSource = ddm->createDataSource(this);
+ connect(m_dataSource, &KWayland::Client::DataSource::dragAndDropPerformed, this, [this] {
+ m_performed = true;
+ if (m_visit) {
+ connect(m_visit, &WlVisit::finish, this, [this](WlVisit *visit) {
+ Q_UNUSED(visit);
+ checkForFinished();
+ });
+
+ QTimer::singleShot(2000, this, [this]{
+ if (!m_visit->entered() || !m_visit->dropHandled()) {
+ // X client timed out
+ Q_EMIT finish(this);
+ } else if (m_dataRequests.size() == 0) {
+ // Wl client timed out
+ m_visit->sendFinished();
+ Q_EMIT finish(this);
+ }
+ });
+ }
+ checkForFinished();
+ });
+ connect(m_dataSource, &KWayland::Client::DataSource::dragAndDropFinished, this, [this] {
+ // this call is not reliably initiated by Wayland clients
+ checkForFinished();
+ });
+
+ // source does _not_ take ownership of m_dataSource
+ source->setDataSource(m_dataSource);
+
+ auto *dc = new QMetaObject::Connection();
+ *dc = connect(waylandServer()->dataDeviceManager(), &KWayland::Server::DataDeviceManagerInterface::dataSourceCreated, this,
+ [this, dc](KWayland::Server::DataSourceInterface *dsi) {
+ Q_ASSERT(dsi);
+ if (dsi->client() != waylandServer()->internalConnection()) {
+ return;
+ }
+ QObject::disconnect(*dc);
+ delete dc;
+ connect(dsi, &KWayland::Server::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();
+}
+
+XToWlDrag::~XToWlDrag()
+{
+ delete m_dataSource;
+ m_dataSource = nullptr;
+}
+
+DragEventReply XToWlDrag::moveFilter(Toplevel *target, QPoint pos)
+{
+ Q_UNUSED(pos);
+
+ auto *seat = waylandServer()->seat();
+
+ if (m_visit && m_visit->target() == target) {
+ // still same Wl target, wait for X events
+ return DragEventReply::Ignore;
+ }
+ if (m_visit) {
+ if (m_visit->leave()) {
+ delete m_visit;
+ } else {
+ connect(m_visit, &WlVisit::finish, this, [this](WlVisit *visit) {
+ m_oldVisits.removeOne(visit);
+ delete visit;
+ });
+ m_oldVisits << m_visit;
+ }
+ }
+ const bool hasCurrent = m_visit;
+ m_visit = nullptr;
+
+ if (!target || !target->surface() ||
+ target->surface()->client() == waylandServer()->xWaylandConnection()) {
+ // currently there is no target or target is an Xwayland window
+ // handled here and by X directly
+ if (AbstractClient *ac = qobject_cast(target)) {
+ if (workspace()->activeClient() != ac) {
+ workspace()->activateClient(ac);
+ }
+ }
+ if (hasCurrent) {
+ // last received enter event is now void,
+ // wait for the next one
+ seat->setDragTarget(nullptr);
+ }
+ return DragEventReply::Ignore;
+ }
+ // new Wl native target
+ auto *ac = static_cast(target);
+ m_visit = new WlVisit(ac, this);
+ connect(m_visit, &WlVisit::offersReceived, this, &XToWlDrag::setOffers);
+ return DragEventReply::Ignore;
+}
+
+bool XToWlDrag::handleClientMessage(xcb_client_message_event_t *event)
+{
+ for (auto *visit : m_oldVisits) {
+ if (visit->handleClientMessage(event)) {
+ return true;
+ }
+ }
+ if (m_visit && m_visit->handleClientMessage(event)) {
+ return true;
+ }
+ return false;
+}
+
+void XToWlDrag::setDragAndDropAction(DnDAction action)
+{
+ m_dataSource->setDragAndDropActions(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;
+}
+
+void XToWlDrag::setOffers(const Mimes &offers)
+{
+ m_src->setOffers(offers);
+ if (offers.isEmpty()) {
+ // There are no offers, so just directly set the drag target,
+ // no transfer possible anyways.
+ setDragTarget();
+ return;
+ }
+ if (m_offers == offers) {
+ // offers had been set already by a previous visit
+ // Wl side is already configured
+ setDragTarget();
+ return;
+ }
+
+ // TODO: make sure that offers are not changed in between visits
+
+ m_offersPending = m_offers = offers;
+ for (const auto mimePair : offers) {
+ m_dataSource->offer(mimePair.first);
+ }
+}
+
+using Mime = QPair;
+
+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);
+ waylandServer()->seat()->setDragTarget(ac->surface(), ac->inputTransformation());
+}
+
+bool XToWlDrag::checkForFinished()
+{
+ if (!m_visit) {
+ // not dropped above Wl native target
+ Q_EMIT finish(this);
+ return true;
+ }
+ if (!m_visit->finished()) {
+ return false;
+ }
+ if (m_dataRequests.size() == 0) {
+ // need to wait for first data request
+ return false;
+ }
+ const bool transfersFinished = std::all_of(m_dataRequests.begin(), m_dataRequests.end(),
+ [](QPair req) { return req.second; });
+ if (transfersFinished) {
+ m_visit->sendFinished();
+ Q_EMIT finish(this);
+ }
+ return transfersFinished;
+}
+
+WlVisit::WlVisit(AbstractClient *target, XToWlDrag *drag)
+ : QObject(drag),
+ m_target(target),
+ m_drag(drag)
+{
+ auto *xcbConn = kwinApp()->x11Connection();
+
+ m_window = xcb_generate_id(xcbConn);
+ DataBridge::self()->dnd()->overwriteRequestorWindow(m_window);
+
+ const uint32_t dndValues[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
+ XCB_EVENT_MASK_PROPERTY_CHANGE };
+ xcb_create_window(xcbConn,
+ XCB_COPY_FROM_PARENT,
+ m_window,
+ kwinApp()->x11RootWindow(),
+ 0, 0,
+ 8192, 8192, // TODO: get current screen size and connect to changes
+ 0,
+ XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ Xwayland::self()->xcbScreen()->root_visual,
+ XCB_CW_EVENT_MASK,
+ dndValues);
+
+ uint32_t version = Dnd::version();
+ xcb_change_property(xcbConn,
+ XCB_PROP_MODE_REPLACE,
+ m_window,
+ atoms->xdnd_aware,
+ XCB_ATOM_ATOM,
+ 32, 1, &version);
+
+ xcb_map_window(xcbConn, m_window);
+
+ const uint32_t stackValues[] = { XCB_STACK_MODE_ABOVE };
+ xcb_configure_window (xcbConn,
+ m_window,
+ XCB_CONFIG_WINDOW_STACK_MODE,
+ stackValues);
+ xcb_flush(xcbConn);
+ m_mapped = true;
+}
+
+WlVisit::~WlVisit()
+{
+ auto *xcbConn = kwinApp()->x11Connection();
+ xcb_destroy_window(xcbConn, m_window);
+ xcb_flush(xcbConn);
+}
+
+bool WlVisit::leave()
+{
+ DataBridge::self()->dnd()->overwriteRequestorWindow(XCB_WINDOW_NONE);
+ unmapProxyWindow();
+ return m_finished;
+}
+
+bool WlVisit::handleClientMessage(xcb_client_message_event_t *event)
+{
+ if (event->window != m_window) {
+ // different window
+ return false;
+ }
+
+ if (event->type == atoms->xdnd_enter) {
+ return handleEnter(event);
+ } else if (event->type == atoms->xdnd_position) {
+ return handlePosition(event);
+ } else if (event->type == atoms->xdnd_drop) {
+ return handleDrop(event);
+ } else if (event->type == atoms->xdnd_leave) {
+ return handleLeave(event);
+ }
+ return false;
+}
+
+static bool hasMimeName(const Mimes &mimes, const QString &name)
+{
+ return std::any_of(mimes.begin(), mimes.end(),
+ [name](const Mime &m) { return m.first == name; });
+}
+
+bool WlVisit::handleEnter(xcb_client_message_event_t *ev)
+{
+ if (m_entered) {
+ // a drag already entered
+ return true;
+ }
+ m_entered = true;
+
+ xcb_client_message_data_t *data = &ev->data;
+ m_srcWindow = data->data32[0];
+ m_version = data->data32[1] >> 24;
+
+ // get types
+ Mimes offers;
+ if (!(data->data32[1] & 1)) {
+ // message has only max 3 types (which are directly in data)
+ for (size_t i = 0; i < 3; i++) {
+ xcb_atom_t mimeAtom = data->data32[2 + i];
+ const auto mimeStrings = Selection::atomToMimeTypes(mimeAtom);
+ for (const auto mime : mimeStrings ) {
+ if (!hasMimeName(offers, mime)) {
+ offers << Mime(mime, mimeAtom);
+ }
+ }
+ }
+ } else {
+ // more than 3 types -> in window property
+ getMimesFromWinProperty(offers);
+ }
+
+ Q_EMIT offersReceived(offers);
+ return true;
+}
+
+void WlVisit::getMimesFromWinProperty(Mimes &offers)
+{
+ auto *xcbConn = kwinApp()->x11Connection();
+ auto cookie = xcb_get_property(xcbConn,
+ 0,
+ m_srcWindow,
+ atoms->xdnd_type_list,
+ XCB_GET_PROPERTY_TYPE_ANY,
+ 0, 0x1fffffff);
+
+ auto *reply = xcb_get_property_reply(xcbConn, cookie, NULL);
+ if (reply == NULL) {
+ return;
+ }
+ if (reply->type != XCB_ATOM_ATOM || reply->value_len == 0) {
+ // invalid reply value
+ free(reply);
+ return;
+ }
+
+ xcb_atom_t *mimeAtoms = static_cast(xcb_get_property_value(reply));
+ for (size_t i = 0; i < reply->value_len; ++i) {
+ const auto mimeStrings = Selection::atomToMimeTypes(mimeAtoms[i]);
+ for (const auto mime : mimeStrings ) {
+ if (!hasMimeName(offers, mime)) {
+ offers << Mime(mime, mimeAtoms[i]);
+ }
+ }
+ }
+ free(reply);
+}
+
+bool WlVisit::handlePosition(xcb_client_message_event_t *ev)
+{
+ xcb_client_message_data_t *data = &ev->data;
+ m_srcWindow = data->data32[0];
+
+ if (!m_target) {
+ // not over Wl window at the moment
+ m_action = DnDAction::None;
+ m_actionAtom = XCB_ATOM_NONE;
+ sendStatus();
+ return true;
+ }
+ const uint32_t pos = data->data32[2];
+ Q_UNUSED(pos);
+
+ const xcb_timestamp_t timestamp = data->data32[3];
+ m_drag->x11Source()->setTimestamp(timestamp);
+
+ xcb_atom_t actionAtom = m_version > 1 ? data->data32[4] :
+ atoms->xdnd_action_copy;
+ auto action = Drag::atomToClientAction(actionAtom);
+
+ if (action == DnDAction::None) {
+ // copy action is always possible in XDND
+ action = DnDAction::Copy;
+ actionAtom = atoms->xdnd_action_copy;
+ }
+
+ if (m_action != action) {
+ m_action = action;
+ m_actionAtom = actionAtom;
+ m_drag->setDragAndDropAction(m_action);
+ }
+
+ sendStatus();
+ return true;
+}
+
+bool WlVisit::handleDrop(xcb_client_message_event_t *ev)
+{
+ m_dropHandled = true;
+
+ xcb_client_message_data_t *data = &ev->data;
+ m_srcWindow = data->data32[0];
+ const xcb_timestamp_t timestamp = data->data32[2];
+ m_drag->x11Source()->setTimestamp(timestamp);
+
+ // we do nothing more here, the drop is being processed
+ // through the X11Source object
+ doFinish();
+ return true;
+}
+
+void WlVisit::doFinish()
+{
+ m_finished = true;
+ unmapProxyWindow();
+ Q_EMIT finish(this);
+}
+
+bool WlVisit::handleLeave(xcb_client_message_event_t *ev)
+{
+ m_entered = false;
+ xcb_client_message_data_t *data = &ev->data;
+ m_srcWindow = data->data32[0];
+ doFinish();
+ return true;
+}
+
+void WlVisit::sendStatus()
+{
+ // receive position events
+ uint32_t flags = 1 << 1;
+ if (targetAcceptsAction()) {
+ // accept the drop
+ flags |= (1 << 0);
+ }
+ xcb_client_message_data_t data = {0};
+ data.data32[0] = m_window;
+ data.data32[1] = flags;
+ data.data32[4] = flags & (1 << 0) ? m_actionAtom : static_cast(XCB_ATOM_NONE);
+ Drag::sendClientMessage(m_srcWindow, atoms->xdnd_status, &data);
+}
+
+void WlVisit::sendFinished()
+{
+ const bool accepted = m_entered && m_action != DnDAction::None;
+ xcb_client_message_data_t data = {0};
+ data.data32[0] = m_window;
+ data.data32[1] = accepted;
+ data.data32[2] = accepted ? m_actionAtom : static_cast(XCB_ATOM_NONE);
+ Drag::sendClientMessage(m_srcWindow, atoms->xdnd_finished, &data);
+}
+
+bool WlVisit::targetAcceptsAction() const
+{
+ if (m_action == DnDAction::None) {
+ return false;
+ }
+ const auto selAction = m_drag->selectedDragAndDropAction();
+ return selAction == m_action || selAction == DnDAction::Copy;
+}
+
+void WlVisit::unmapProxyWindow()
+{
+ if (!m_mapped) {
+ return;
+ }
+ auto *xcbConn = kwinApp()->x11Connection();
+ xcb_unmap_window(xcbConn, m_window);
+ xcb_flush(xcbConn);
+ m_mapped = false;
+}
+
+}
+}
diff --git a/xwl/drag_x.h b/xwl/drag_x.h
new file mode 100644
index 0000000000..f381804ad6
--- /dev/null
+++ b/xwl/drag_x.h
@@ -0,0 +1,162 @@
+/********************************************************************
+ KWin - the KDE window manager
+ This file is part of the KDE project.
+
+Copyright 2019 Roman Gilg
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*********************************************************************/
+#ifndef KWIN_XWL_DRAG_X
+#define KWIN_XWL_DRAG_X
+
+#include "drag.h"
+
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+
+namespace KWayland {
+namespace Client {
+class DataSource;
+}
+}
+
+namespace KWin
+{
+class Toplevel;
+class AbstractClient;
+
+namespace Xwl
+{
+class X11Source;
+enum class DragEventReply;
+class WlVisit;
+
+using Mimes = QVector >;
+
+class XToWlDrag : public Drag
+{
+ Q_OBJECT
+public:
+ explicit XToWlDrag(X11Source *source);
+ ~XToWlDrag() override;
+
+ DragEventReply moveFilter(Toplevel *target, QPoint pos) override;
+ bool handleClientMessage(xcb_client_message_event_t *event) override;
+
+ void setDragAndDropAction(DnDAction action);
+ DnDAction selectedDragAndDropAction();
+
+ bool end() override {
+ return false;
+ }
+ X11Source* x11Source() const {
+ return m_src;
+ }
+
+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;
+
+ X11Source *m_src;
+ QVector > m_dataRequests;
+
+ WlVisit *m_visit = nullptr;
+ QVector m_oldVisits;
+
+ bool m_performed = false;
+ DnDAction m_lastSelectedDragAndDropAction = DnDAction::None;
+};
+
+class WlVisit : public QObject
+{
+ Q_OBJECT
+public:
+ WlVisit(AbstractClient *target, XToWlDrag *drag);
+ ~WlVisit();
+
+ bool handleClientMessage(xcb_client_message_event_t *event);
+ bool leave();
+
+ AbstractClient *target() const {
+ return m_target;
+ }
+ xcb_window_t window() const {
+ return m_window;
+ }
+ bool entered() const {
+ return m_entered;
+ }
+ bool dropHandled() const {
+ return m_dropHandled;
+ }
+ bool finished() const {
+ return m_finished;
+ }
+ void sendFinished();
+
+Q_SIGNALS:
+ void offersReceived(const Mimes &offers);
+ void finish(WlVisit *self);
+
+private:
+ bool handleEnter(xcb_client_message_event_t *ev);
+ bool handlePosition(xcb_client_message_event_t *ev);
+ bool handleDrop(xcb_client_message_event_t *ev);
+ bool handleLeave(xcb_client_message_event_t *ev);
+
+ void sendStatus();
+
+ void getMimesFromWinProperty(Mimes &offers);
+
+ bool targetAcceptsAction() const;
+
+ void doFinish();
+ void unmapProxyWindow();
+
+ AbstractClient *m_target;
+ xcb_window_t m_window;
+
+ xcb_window_t m_srcWindow = XCB_WINDOW_NONE;
+ XToWlDrag *m_drag;
+
+ uint32_t m_version = 0;
+
+ xcb_atom_t m_actionAtom;
+ DnDAction m_action = DnDAction::None;
+
+ bool m_mapped = false;
+ bool m_entered = false;
+ bool m_dropHandled = false;
+ bool m_finished = false;
+
+};
+
+}
+}
+
+#endif
diff --git a/xwl/selection.cpp b/xwl/selection.cpp
index dca9b3d131..944b0b7eed 100644
--- a/xwl/selection.cpp
+++ b/xwl/selection.cpp
@@ -83,6 +83,7 @@ Selection::Selection(xcb_atom_t atom, QObject *parent)
{
auto *xcbConn = kwinApp()->x11Connection();
m_window = xcb_generate_id(kwinApp()->x11Connection());
+ m_requestorWindow = m_window;
xcb_flush(xcbConn);
}
@@ -109,14 +110,6 @@ bool Selection::handleXfixesNotify(xcb_xfixes_selection_notify_event_t *event)
}
// Being here means some other X window has claimed the selection.
- delete m_xSrc;
- m_xSrc = nullptr;
- const auto *ac = workspace()->activeClient();
- if (!ac || !ac->inherits("KWin::Client")) {
- // selections are only allowed to be acquired when Xwayland has focus
- // TODO: can we make this stronger (window id comparision)?
- return true;
- }
doHandleXfixesNotify(event);
return true;
}
@@ -200,7 +193,7 @@ void Selection::createX11Source(xcb_xfixes_selection_notify_event_t *event)
delete m_xSrc;
m_wlSrc = nullptr;
m_xSrc = nullptr;
- if (event->owner == XCB_WINDOW_NONE) {
+ if (!event || event->owner == XCB_WINDOW_NONE) {
return;
}
m_xSrc = new X11Source(this, event);
@@ -227,6 +220,17 @@ void Selection::ownSelection(bool own)
xcb_flush(xcbConn);
}
+void Selection::overwriteRequestorWindow(xcb_window_t window)
+{
+ Q_ASSERT(m_xSrc);
+ if (window == XCB_WINDOW_NONE) {
+ // reset
+ window = m_window;
+ }
+ m_requestorWindow = window;
+ m_xSrc->setRequestor(window);
+}
+
bool Selection::handleSelRequest(xcb_selection_request_event_t *event)
{
if (event->selection != m_atom) {
@@ -283,7 +287,7 @@ bool Selection::handlePropNotify(xcb_property_notify_event_t *event)
void Selection::startTransferToWayland(xcb_atom_t target, qint32 fd)
{
// create new x to wl data transfer object
- auto *transfer = new TransferXtoWl(m_atom, target, fd, m_xSrc->timestamp(), m_window, this);
+ auto *transfer = new TransferXtoWl(m_atom, target, fd, m_xSrc->timestamp(), m_requestorWindow, this);
m_xToWlTransfers << transfer;
connect(transfer, &TransferXtoWl::finished, this, [this, transfer]() {
diff --git a/xwl/selection.h b/xwl/selection.h
index 45bd05bac7..79d2febaf9 100644
--- a/xwl/selection.h
+++ b/xwl/selection.h
@@ -60,11 +60,11 @@ public:
static xcb_atom_t mimeTypeToAtom(const QString &mimeType);
static xcb_atom_t mimeTypeToAtomLiteral(const QString &mimeType);
static QStringList atomToMimeTypes(xcb_atom_t atom);
+ static void sendSelNotify(xcb_selection_request_event_t *event, bool success);
// on selection owner changes by X clients (Xwl -> Wl)
bool handleXfixesNotify(xcb_xfixes_selection_notify_event_t *event);
bool filterEvent(xcb_generic_event_t *event);
- void sendSelNotify(xcb_selection_request_event_t *event, bool success);
xcb_atom_t atom() const {
return m_atom;
@@ -72,6 +72,7 @@ public:
xcb_window_t window() const {
return m_window;
}
+ void overwriteRequestorWindow(xcb_window_t window);
Q_SIGNALS:
void transferFinished(xcb_timestamp_t eventTime);
@@ -117,6 +118,7 @@ private:
xcb_atom_t m_atom = XCB_ATOM_NONE;
xcb_window_t m_window = XCB_WINDOW_NONE;
+ xcb_window_t m_requestorWindow = XCB_WINDOW_NONE;
xcb_timestamp_t m_timestamp;
// Active source, if any. Only one of them at max can exist
diff --git a/xwl/selection_source.cpp b/xwl/selection_source.cpp
index 513171be8f..05e719430f 100644
--- a/xwl/selection_source.cpp
+++ b/xwl/selection_source.cpp
@@ -42,7 +42,8 @@ namespace Xwl {
SelectionSource::SelectionSource(Selection *sel)
: QObject(sel),
- m_sel(sel)
+ m_sel(sel),
+ m_window(sel->window())
{
}
@@ -74,7 +75,7 @@ void WlSource::receiveOffer(const QString &mime)
void WlSource::sendSelNotify(xcb_selection_request_event_t *event, bool success)
{
- selection()->sendSelNotify(event, success);
+ Selection::sendSelNotify(event, success);
}
bool WlSource::handleSelRequest(xcb_selection_request_event_t *event)
@@ -183,7 +184,7 @@ void X11Source::getTargets()
auto *xcbConn = kwinApp()->x11Connection();
/* will lead to a selection request event for the new owner */
xcb_convert_selection(xcbConn,
- selection()->window(),
+ window(),
selection()->atom(),
atoms->targets,
atoms->wl_selection,
@@ -199,7 +200,7 @@ void X11Source::handleTargets()
auto *xcbConn = kwinApp()->x11Connection();
xcb_get_property_cookie_t cookie = xcb_get_property(xcbConn,
1,
- selection()->window(),
+ window(),
atoms->wl_selection,
XCB_GET_PROPERTY_TYPE_ANY,
0,
@@ -278,7 +279,7 @@ void X11Source::setOffers(const Mimes &offers)
bool X11Source::handleSelNotify(xcb_selection_notify_event_t *event)
{
- if (event->requestor != selection()->window()) {
+ if (event->requestor != window()) {
return false;
}
if (event->selection != selection()->atom()) {
diff --git a/xwl/selection_source.h b/xwl/selection_source.h
index b4ebb14cb3..0524f1bcb1 100644
--- a/xwl/selection_source.h
+++ b/xwl/selection_source.h
@@ -69,10 +69,17 @@ protected:
Selection *selection() const {
return m_sel;
}
+ void setWindow(xcb_window_t window) {
+ m_window = window;
+ }
+ xcb_window_t window() const {
+ return m_window;
+ }
private:
xcb_timestamp_t m_timestamp = XCB_CURRENT_TIME;
Selection *m_sel;
+ xcb_window_t m_window;
};
/**
@@ -135,6 +142,10 @@ public:
bool handleSelNotify(xcb_selection_notify_event_t *event);
+ void setRequestor(xcb_window_t window) {
+ setWindow(window);
+ }
+
Q_SIGNALS:
void offersChanged(QVector added, QVector removed);
void transferReady(xcb_atom_t target, qint32 fd);
diff --git a/xwl/xwayland.cpp b/xwl/xwayland.cpp
index a70872efb6..a521f27c0e 100644
--- a/xwl/xwayland.cpp
+++ b/xwl/xwayland.cpp
@@ -270,5 +270,13 @@ void Xwayland::continueStartupWithX()
m_app->notifyKSplash();
}
+DragEventReply Xwayland::dragMoveFilter(Toplevel *target, QPoint pos)
+{
+ if (!m_dataBridge) {
+ return DragEventReply::Wayland;
+ }
+ return m_dataBridge->dragMoveFilter(target, pos);
+}
+
}
}
diff --git a/xwl/xwayland.h b/xwl/xwayland.h
index a1ee8e20b9..2ee1e607fb 100644
--- a/xwl/xwayland.h
+++ b/xwl/xwayland.h
@@ -62,6 +62,8 @@ private:
void createX11Connection();
void continueStartupWithX();
+ DragEventReply dragMoveFilter(Toplevel *target, QPoint pos) override;
+
int m_xcbConnectionFd = -1;
QProcess *m_xwaylandProcess = nullptr;
QMetaObject::Connection m_xwaylandFailConnection;
diff --git a/xwl/xwayland_interface.h b/xwl/xwayland_interface.h
index 388fb0e49c..5b48d98a3a 100644
--- a/xwl/xwayland_interface.h
+++ b/xwl/xwayland_interface.h
@@ -23,9 +23,23 @@ along with this program. If not, see .
#include
#include
+#include
namespace KWin
{
+class Toplevel;
+
+namespace Xwl
+{
+enum class DragEventReply {
+ // event should be ignored by the filter
+ Ignore,
+ // event is filtered out
+ Take,
+ // event should be handled as a Wayland native one
+ Wayland,
+};
+}
class KWIN_EXPORT XwaylandInterface : public QObject
{
@@ -33,6 +47,8 @@ class KWIN_EXPORT XwaylandInterface : public QObject
public:
static XwaylandInterface *self();
+ virtual Xwl::DragEventReply dragMoveFilter(Toplevel *target, QPoint pos) = 0;
+
protected:
explicit XwaylandInterface(QObject *parent = nullptr);
virtual ~XwaylandInterface();