[xwl] Drag and drop between Xwayland and Wayland native clients
Summary: Building upon the generic X Selection support this patch establishes another selection class representing the XDND selection and provides interfaces to communicate drags originating from Xwayland windows to the Wayland server KWin and drags originating from Wayland native drags to Xwayland. For Wayland native drags KWin will claim the XDND selection as owner and will simply translate all relevant events to the XDND protocol and receive alike messages by X clients. When an X client claims the XDND selection KWin is notified via the X protocol and it decides if it allows the X drag to transcend into the Wayland protocol. If this is the case the mouse position is tracked and on entering a Wayland native window a proxy X Window is mapped to the top of the window stack. This proxy window acts as a drag destination for the drag origin window and again X messages will be translated into respective Wayland protocol calls. If the cursor leaves the Wayland window geometry before a drop is registered, the proxy window is unmapped, what triggers a subsequent drag leave event. In both directions the necessary core integration is minimal. There is a single call to be done in the drag and drop event filter through the Xwayland interface class. From my tests this patch facilitates drags between any Qt/KDE apps. What needs extra care are the browsers, which use target formats, that are not directly compatible with the Wayland protocol's MIME representation. For Chromium an additional integration step must be done in order to provide it with a net window stack containing the proxy window. Test Plan: Manually. Auto tests planned. Reviewers: #kwin Subscribers: zzag, kwin, alexde Tags: #kwin Maniphest Tasks: T4611 Differential Revision: https://phabricator.kde.org/D15627
This commit is contained in:
parent
ad1bcbecc7
commit
548978bfe1
25 changed files with 1916 additions and 20 deletions
|
@ -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
|
||||
|
|
10
atoms.cpp
10
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"))
|
||||
|
|
10
atoms.h
10
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;
|
||||
|
|
25
input.cpp
25
input.cpp
|
@ -44,6 +44,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#include "popup_input_filter.h"
|
||||
#include "shell_client.h"
|
||||
#include "wayland_server.h"
|
||||
#include "xwl/xwayland_interface.h"
|
||||
#include <KWayland/Server/display.h>
|
||||
#include <KWayland/Server/fakeinput_interface.h>
|
||||
#include <KWayland/Server/seat_interface.h>
|
||||
|
@ -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<AbstractClient*>(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;
|
||||
|
|
1
input.h
1
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;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#include <KWayland/Client/connection_thread.h>
|
||||
#include <KWayland/Client/event_queue.h>
|
||||
#include <KWayland/Client/registry.h>
|
||||
#include <KWayland/Client/compositor.h>
|
||||
#include <KWayland/Client/seat.h>
|
||||
#include <KWayland/Client/datadevicemanager.h>
|
||||
#include <KWayland/Client/shm_pool.h>
|
||||
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -26,7 +26,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
||||
#include "wayland_server.h"
|
||||
#include "workspace.h"
|
||||
#include "abstract_client.h"
|
||||
#include "client.h"
|
||||
|
||||
#include <KWayland/Client/connection_thread.h>
|
||||
#include <KWayland/Client/datadevice.h>
|
||||
|
@ -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<const KWin::Client *>(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) {
|
||||
|
|
|
@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#define KWIN_XWL_DATABRIDGE
|
||||
|
||||
#include <QObject>
|
||||
#include <QPoint>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
|
@ -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;
|
||||
|
|
227
xwl/dnd.cpp
Normal file
227
xwl/dnd.cpp
Normal file
|
@ -0,0 +1,227 @@
|
|||
/********************************************************************
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
Copyright 2019 Roman Gilg <subdiff@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************/
|
||||
#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 <KWayland/Client/compositor.h>
|
||||
#include <KWayland/Client/surface.h>
|
||||
|
||||
#include <KWayland/Server/seat_interface.h>
|
||||
#include <KWayland/Server/compositor_interface.h>
|
||||
|
||||
#include <QMouseEvent>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
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<XToWlDrag*>(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<QString> &added, const QVector<QString> &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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
90
xwl/dnd.h
Normal file
90
xwl/dnd.h
Normal file
|
@ -0,0 +1,90 @@
|
|||
/********************************************************************
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
Copyright 2019 Roman Gilg <subdiff@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************/
|
||||
#ifndef KWIN_XWL_DND
|
||||
#define KWIN_XWL_DND
|
||||
|
||||
#include "selection.h"
|
||||
|
||||
#include <QPoint>
|
||||
|
||||
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<QString> &added, const QVector<QString> &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<Drag*> m_oldDrags;
|
||||
|
||||
KWayland::Client::Surface *m_surface;
|
||||
KWayland::Server::SurfaceInterface *m_surfaceIface = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
78
xwl/drag.cpp
Normal file
78
xwl/drag.cpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
/********************************************************************
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
Copyright 2019 Roman Gilg <subdiff@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************/
|
||||
#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<const char *>(&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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
63
xwl/drag.h
Normal file
63
xwl/drag.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
/********************************************************************
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
Copyright 2019 Roman Gilg <subdiff@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************/
|
||||
#ifndef KWIN_XWL_DRAG
|
||||
#define KWIN_XWL_DRAG
|
||||
|
||||
#include <KWayland/Client/datadevicemanager.h>
|
||||
|
||||
#include <QPoint>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
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
|
446
xwl/drag_wl.cpp
Normal file
446
xwl/drag_wl.cpp
Normal file
|
@ -0,0 +1,446 @@
|
|||
/********************************************************************
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
Copyright 2019 Roman Gilg <subdiff@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************/
|
||||
#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 <KWayland/Client/datadevice.h>
|
||||
#include <KWayland/Client/datasource.h>
|
||||
|
||||
#include <KWayland/Server/seat_interface.h>
|
||||
#include <KWayland/Server/surface_interface.h>
|
||||
#include <KWayland/Server/datadevice_interface.h>
|
||||
#include <KWayland/Server/datasource_interface.h>
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QTimer>
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
namespace Xwl
|
||||
{
|
||||
|
||||
WlToXDrag::WlToXDrag()
|
||||
{
|
||||
m_dsi = waylandServer()->seat()->dragSource()->dragSource();
|
||||
}
|
||||
|
||||
DragEventReply WlToXDrag::moveFilter(Toplevel *target, QPoint pos)
|
||||
{
|
||||
AbstractClient *ac = qobject_cast<AbstractClient*>(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<KWin::Client *>(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_atom_t*>(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<uint32_t>(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<xcb_atom_t> 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
160
xwl/drag_wl.h
Normal file
160
xwl/drag_wl.h
Normal file
|
@ -0,0 +1,160 @@
|
|||
/********************************************************************
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
Copyright 2019 Roman Gilg <subdiff@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************/
|
||||
#ifndef KWIN_XWL_DRAG_WL
|
||||
#define KWIN_XWL_DRAG_WL
|
||||
|
||||
#include "drag.h"
|
||||
|
||||
#include <KWayland/Client/dataoffer.h>
|
||||
|
||||
#include <QPoint>
|
||||
#include <QPointer>
|
||||
#include <QVector>
|
||||
|
||||
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<KWayland::Client::DataOffer> 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
|
531
xwl/drag_x.cpp
Normal file
531
xwl/drag_x.cpp
Normal file
|
@ -0,0 +1,531 @@
|
|||
/********************************************************************
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
Copyright 2019 Roman Gilg <subdiff@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************/
|
||||
#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 <KWayland/Client/datadevice.h>
|
||||
#include <KWayland/Client/datasource.h>
|
||||
|
||||
#include <KWayland/Server/datasource_interface.h>
|
||||
#include <KWayland/Server/seat_interface.h>
|
||||
#include <KWayland/Server/surface_interface.h>
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QTimer>
|
||||
|
||||
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<xcb_timestamp_t, bool> 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<xcb_timestamp_t, bool>(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<AbstractClient*>(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<AbstractClient*>(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<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);
|
||||
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<xcb_timestamp_t, bool> 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_atom_t*>(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<uint32_t>(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<uint32_t>(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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
162
xwl/drag_x.h
Normal file
162
xwl/drag_x.h
Normal file
|
@ -0,0 +1,162 @@
|
|||
/********************************************************************
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
Copyright 2019 Roman Gilg <subdiff@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************/
|
||||
#ifndef KWIN_XWL_DRAG_X
|
||||
#define KWIN_XWL_DRAG_X
|
||||
|
||||
#include "drag.h"
|
||||
|
||||
#include <KWayland/Client/datadevicemanager.h>
|
||||
#include <KWayland/Client/dataoffer.h>
|
||||
|
||||
#include <KWayland/Server/datadevicemanager_interface.h>
|
||||
|
||||
#include <QPoint>
|
||||
#include <QPointer>
|
||||
#include <QVector>
|
||||
|
||||
namespace KWayland {
|
||||
namespace Client {
|
||||
class DataSource;
|
||||
}
|
||||
}
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
class Toplevel;
|
||||
class AbstractClient;
|
||||
|
||||
namespace Xwl
|
||||
{
|
||||
class X11Source;
|
||||
enum class DragEventReply;
|
||||
class WlVisit;
|
||||
|
||||
using Mimes = QVector<QPair<QString, xcb_atom_t> >;
|
||||
|
||||
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<QPair<xcb_timestamp_t, bool> > m_dataRequests;
|
||||
|
||||
WlVisit *m_visit = nullptr;
|
||||
QVector<WlVisit*> 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
|
|
@ -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]() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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<QString> added, QVector<QString> removed);
|
||||
void transferReady(xcb_atom_t target, qint32 fd);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -23,9 +23,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#include <kwinglobals.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QPoint>
|
||||
|
||||
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();
|
||||
|
|
Loading…
Reference in a new issue