kwin/src/wayland/datadevice_interface.cpp
Aleix Pol b2d648df5d datadevice: do not call a null dragSource
Fixes the following backtrace:
*0  KWaylandServer::DataDeviceInterface::dragSource() const (this=0x0)
at ./src/server/datadevice_interface.cpp:199
*1  0x00007f10d67b0c71 in
KWaylandServer::DataDeviceInterface::updateDragTarget(KWaylandServer::SurfaceInterface*,
unsigned int) (this=0x55c42e3ee9a0,
surface=surface@entry=0x55c42e4b3170, serial=serial@entry=3104)
    at ./src/server/datadevice_interface.cpp:278
*2  0x00007f10d67d8e52 in
KWaylandServer::SeatInterface::setDragTarget(KWaylandServer::SurfaceInterface*,
QPointF const&, QMatrix4x4 const&)
(this=this@entry=0x55c42d422ed0, surface=0x55c42e4b3170,
globalPosition=..., inputTransformation=...) at
/usr/include/c++/9/bits/atomic_base.h:413
*3  0x00007f10d67d9209 in
KWaylandServer::SeatInterface::setDragTarget(KWaylandServer::SurfaceInterface*,
QMatrix4x4 const&) (this=this@entry=0x55c42d422ed0, surface=<optimized
out>, inputTransformation=...)
    at ./src/server/seat_interface.cpp:578
*4  0x000055c42cb4563a in KWin::Xwl::XToWlDrag::setDragTarget()
(this=this@entry=0x55c42ea07a00) at ./src/toplevel.h:990
*5  0x000055c42cb47a68 in KWin::Xwl::XToWlDrag::offerCallback(QString
const&) (mime=..., this=0x55c42ea07a00) at ./src/xwl/drag_x.cpp:242
*6  KWin::Xwl::XToWlDrag::offerCallback(QString const&)
(this=0x55c42ea07a00, mime=...) at ./src/xwl/drag_x.cpp:237
*7  0x00007f10d5dc06fe in  () at /lib/x86_64-linux-gnu/libQt5Core.so.5
2021-04-22 12:21:42 +02:00

366 lines
12 KiB
C++

/*
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org>
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "datadevice_interface.h"
#include "datadevice_interface_p.h"
#include "datadevicemanager_interface.h"
#include "datasource_interface.h"
#include "dataoffer_interface.h"
#include "display.h"
#include "pointer_interface.h"
#include "seat_interface.h"
#include "seat_interface_p.h"
#include "surface_interface.h"
#include "surfacerole_p.h"
namespace KWaylandServer
{
class DragAndDropIconPrivate : public SurfaceRole
{
public:
explicit DragAndDropIconPrivate(SurfaceInterface *surface);
void commit() override;
QPoint position;
};
DragAndDropIconPrivate::DragAndDropIconPrivate(SurfaceInterface *surface)
: SurfaceRole(surface, QByteArrayLiteral("dnd_icon"))
{
}
void DragAndDropIconPrivate::commit()
{
position += surface()->offset();
}
DragAndDropIcon::DragAndDropIcon(SurfaceInterface *surface, QObject *parent)
: QObject(parent)
, d(new DragAndDropIconPrivate(surface))
{
}
DragAndDropIcon::~DragAndDropIcon()
{
}
QPoint DragAndDropIcon::position() const
{
return d->position;
}
SurfaceInterface *DragAndDropIcon::surface() const
{
return d->surface();
}
DataDeviceInterfacePrivate *DataDeviceInterfacePrivate::get(DataDeviceInterface *device)
{
return device->d.data();
}
DataDeviceInterfacePrivate::DataDeviceInterfacePrivate(SeatInterface *seat, DataDeviceInterface *_q, wl_resource *resource)
: QtWaylandServer::wl_data_device(resource)
, seat(seat)
, q(_q)
{
}
void DataDeviceInterfacePrivate::endDrag()
{
icon.reset();
}
void DataDeviceInterfacePrivate::data_device_start_drag(Resource *resource, wl_resource *sourceResource, wl_resource *originResource, wl_resource *iconResource, uint32_t serial)
{
SurfaceInterface *iconSurface = SurfaceInterface::get(iconResource);
const SurfaceRole *surfaceRole = SurfaceRole::get(iconSurface);
if (surfaceRole) {
wl_resource_post_error(resource->handle, error_role,
"the icon surface already has a role assigned %s",
surfaceRole->name().constData());
return;
}
SurfaceInterface *focusSurface = SurfaceInterface::get(originResource);
DataSourceInterface *dataSource = nullptr;
if (sourceResource) {
dataSource = DataSourceInterface::get(sourceResource);
}
if (proxyRemoteSurface) {
// origin is a proxy surface
focusSurface = proxyRemoteSurface.data();
}
const bool pointerGrab = seat->hasImplicitPointerGrab(serial) && seat->focusedPointerSurface() == focusSurface;
if (!pointerGrab) {
// Client doesn't have pointer grab.
const bool touchGrab = seat->hasImplicitTouchGrab(serial) && seat->focusedTouchSurface() == focusSurface;
if (!touchGrab) {
// Client neither has pointer nor touch grab. No drag start allowed.
return;
}
}
// TODO: source is allowed to be null, handled client internally!
source = dataSource;
if (dataSource) {
QObject::connect(dataSource, &AbstractDataSource::aboutToBeDestroyed, q, [this] { source = nullptr; });
}
if (iconSurface) {
icon.reset(new DragAndDropIcon(iconSurface));
QObject::connect(iconSurface, &SurfaceInterface::aboutToBeDestroyed, icon.data(), [this] { icon.reset(); });
}
surface = focusSurface;
drag.serial = serial;
emit q->dragStarted();
}
void DataDeviceInterfacePrivate::data_device_set_selection(Resource *resource, wl_resource *source, uint32_t serial)
{
Q_UNUSED(resource)
Q_UNUSED(serial)
DataSourceInterface *dataSource = DataSourceInterface::get(source);
if (dataSource && dataSource->supportedDragAndDropActions() && wl_resource_get_version(dataSource->resource()) >= WL_DATA_SOURCE_ACTION_SINCE_VERSION) {
wl_resource_post_error(dataSource->resource(), QtWaylandServer::wl_data_source::error_invalid_source, "Data source is for drag and drop");
return;
}
if (selection == dataSource) {
return;
}
if (selection) {
selection->cancel();
}
selection = dataSource;
if (selection) {
emit q->selectionChanged(selection);
} else {
emit q->selectionCleared();
}
}
void DataDeviceInterfacePrivate::data_device_release(QtWaylandServer::wl_data_device::Resource *resource)
{
wl_resource_destroy(resource->handle);
}
DataOfferInterface *DataDeviceInterfacePrivate::createDataOffer(AbstractDataSource *source)
{
if (!source) {
// a data offer can only exist together with a source
return nullptr;
}
wl_resource *data_offer_resource = wl_resource_create(resource()->client(), &wl_data_offer_interface, resource()->version(), 0);
if (!data_offer_resource) {
wl_resource_post_no_memory(resource()->handle);
return nullptr;
}
DataOfferInterface *offer = new DataOfferInterface(source, data_offer_resource);
send_data_offer(offer->resource());
offer->sendAllOffers();
return offer;
}
void DataDeviceInterfacePrivate::data_device_destroy_resource(QtWaylandServer::wl_data_device::Resource *resource)
{
Q_UNUSED(resource)
emit q->aboutToBeDestroyed();
delete q;
}
DataDeviceInterface::DataDeviceInterface(SeatInterface *seat, wl_resource *resource)
: QObject(nullptr)
, d(new DataDeviceInterfacePrivate(seat, this, resource))
{
SeatInterfacePrivate *seatPrivate = SeatInterfacePrivate::get(seat);
seatPrivate->registerDataDevice(this);
}
DataDeviceInterface::~DataDeviceInterface() = default;
SeatInterface *DataDeviceInterface::seat() const
{
return d->seat;
}
DataSourceInterface *DataDeviceInterface::dragSource() const
{
return d->source;
}
DragAndDropIcon *DataDeviceInterface::icon() const
{
return d->icon.data();
}
SurfaceInterface *DataDeviceInterface::origin() const
{
return d->proxyRemoteSurface ? d->proxyRemoteSurface.data() : d->surface;
}
DataSourceInterface *DataDeviceInterface::selection() const
{
return d->selection;
}
void DataDeviceInterface::sendSelection(AbstractDataSource *other)
{
auto r = d->createDataOffer(other);
if (!r) {
return;
}
d->send_selection(r->resource());
}
void DataDeviceInterface::sendClearSelection()
{
d->send_selection(nullptr);
}
void DataDeviceInterface::drop()
{
d->send_drop();
if (d->drag.posConnection) {
disconnect(d->drag.posConnection);
d->drag.posConnection = QMetaObject::Connection();
}
disconnect(d->drag.destroyConnection);
d->drag.destroyConnection = QMetaObject::Connection();
d->drag.surface = nullptr;
wl_client_flush(d->resource()->client());
}
void DataDeviceInterface::updateDragTarget(SurfaceInterface *surface, quint32 serial)
{
if (d->drag.surface) {
if (d->drag.surface->resource()) {
d->send_leave();
}
if (d->drag.posConnection) {
disconnect(d->drag.posConnection);
d->drag.posConnection = QMetaObject::Connection();
}
disconnect(d->drag.destroyConnection);
d->drag.destroyConnection = QMetaObject::Connection();
d->drag.surface = nullptr;
if (d->drag.sourceActionConnection) {
disconnect(d->drag.sourceActionConnection);
d->drag.sourceActionConnection = QMetaObject::Connection();
}
if (d->drag.targetActionConnection) {
disconnect(d->drag.targetActionConnection);
d->drag.targetActionConnection = QMetaObject::Connection();
}
// don't update serial, we need it
}
auto dragSourceDevice = d->seat->dragSource();
if (!surface || !dragSourceDevice) {
if (auto s = dragSourceDevice->dragSource()) {
s->dndAction(DataDeviceManagerInterface::DnDAction::None);
}
return;
}
if (d->proxyRemoteSurface && d->proxyRemoteSurface == surface) {
// A proxy can not have the remote surface as target.
// TODO: do this for all client's surfaces?
return;
}
auto *source = dragSourceDevice->dragSource();
if (source) {
source->setAccepted(false);
}
DataOfferInterface *offer = d->createDataOffer(source);
d->drag.surface = surface;
if (d->seat->isDragPointer()) {
d->drag.posConnection = connect(d->seat, &SeatInterface::pointerPosChanged, this,
[this] {
const QPointF pos = d->seat->dragSurfaceTransformation().map(d->seat->pointerPos());
d->send_motion(d->seat->timestamp(),
wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y()));
wl_client_flush(d->resource()->client());
}
);
} else if (d->seat->isDragTouch()) {
d->drag.posConnection = connect(d->seat, &SeatInterface::touchMoved, this,
[this](qint32 id, quint32 serial, const QPointF &globalPosition) {
Q_UNUSED(id);
if (serial != d->drag.serial) {
// different touch down has been moved
return;
}
const QPointF pos = d->seat->dragSurfaceTransformation().map(globalPosition);
d->send_motion(d->seat->timestamp(),
wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y()));
wl_client_flush(d->resource()->client());
}
);
}
d->drag.destroyConnection = connect(d->drag.surface, &QObject::destroyed, this,
[this] {
d->send_leave();
if (d->drag.posConnection) {
disconnect(d->drag.posConnection);
}
d->drag = DataDeviceInterfacePrivate::Drag();
}
);
// TODO: handle touch position
const QPointF pos = d->seat->dragSurfaceTransformation().map(d->seat->pointerPos());
d->send_enter(serial, surface->resource(),
wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y()), offer ? offer->resource() : nullptr);
if (offer) {
offer->sendSourceActions();
auto matchOffers = [source, offer] {
DataDeviceManagerInterface::DnDAction action{DataDeviceManagerInterface::DnDAction::None};
if (source->supportedDragAndDropActions().testFlag(offer->preferredDragAndDropAction())) {
action = offer->preferredDragAndDropAction();
} else {
if (source->supportedDragAndDropActions().testFlag(DataDeviceManagerInterface::DnDAction::Copy) &&
offer->supportedDragAndDropActions().testFlag(DataDeviceManagerInterface::DnDAction::Copy)) {
action = DataDeviceManagerInterface::DnDAction::Copy;
} else if (source->supportedDragAndDropActions().testFlag(DataDeviceManagerInterface::DnDAction::Move) &&
offer->supportedDragAndDropActions().testFlag(DataDeviceManagerInterface::DnDAction::Move)) {
action = DataDeviceManagerInterface::DnDAction::Move;
} else if (source->supportedDragAndDropActions().testFlag(DataDeviceManagerInterface::DnDAction::Ask) &&
offer->supportedDragAndDropActions().testFlag(DataDeviceManagerInterface::DnDAction::Ask)) {
action = DataDeviceManagerInterface::DnDAction::Ask;
}
}
offer->dndAction(action);
source->dndAction(action);
};
d->drag.targetActionConnection = connect(offer, &DataOfferInterface::dragAndDropActionsChanged, source, matchOffers);
d->drag.sourceActionConnection = connect(source, &DataSourceInterface::supportedDragAndDropActionsChanged, source, matchOffers);
}
wl_client_flush(d->resource()->client());
}
quint32 DataDeviceInterface::dragImplicitGrabSerial() const
{
return d->drag.serial;
}
void DataDeviceInterface::updateProxy(SurfaceInterface *remote)
{
// TODO: connect destroy signal?
d->proxyRemoteSurface = remote;
}
wl_client *DataDeviceInterface::client()
{
return d->resource()->client();
}
}