/* SPDX-FileCopyrightText: 2014 Martin Gräßlin SPDX-FileCopyrightText: 2020 David Edmundson SPDX-FileCopyrightText: 2020 Vlad Zahorodnii SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "datadevice.h" #include "datadevice_p.h" #include "datadevicemanager.h" #include "dataoffer.h" #include "datasource.h" #include "display.h" #include "pointer.h" #include "seat.h" #include "seat_p.h" #include "surface.h" namespace KWin { class DragAndDropIconPrivate { public: explicit DragAndDropIconPrivate(SurfaceInterface *surface); QPointer surface; QPoint position; }; DragAndDropIconPrivate::DragAndDropIconPrivate(SurfaceInterface *surface) : surface(surface) { } DragAndDropIcon::DragAndDropIcon(SurfaceInterface *surface) : QObject(surface) , d(new DragAndDropIconPrivate(surface)) { connect(surface, &SurfaceInterface::committed, this, &DragAndDropIcon::commit); } DragAndDropIcon::~DragAndDropIcon() { } SurfaceRole *DragAndDropIcon::role() { static SurfaceRole role(QByteArrayLiteral("dnd_icon")); return &role; } void DragAndDropIcon::commit() { d->position += d->surface->offset(); Q_EMIT changed(); } QPoint DragAndDropIcon::position() const { return d->position; } SurfaceInterface *DragAndDropIcon::surface() const { return d->surface; } DataDeviceInterfacePrivate *DataDeviceInterfacePrivate::get(DataDeviceInterface *device) { return device->d.get(); } DataDeviceInterfacePrivate::DataDeviceInterfacePrivate(SeatInterface *seat, DataDeviceInterface *_q, wl_resource *resource) : QtWaylandServer::wl_data_device(resource) , seat(seat) , q(_q) { } void DataDeviceInterfacePrivate::data_device_start_drag(Resource *resource, wl_resource *sourceResource, wl_resource *originResource, wl_resource *iconResource, uint32_t serial) { SurfaceInterface *focusSurface = SurfaceInterface::get(originResource); DataSourceInterface *dataSource = nullptr; if (sourceResource) { dataSource = DataSourceInterface::get(sourceResource); } const bool pointerGrab = seat->hasImplicitPointerGrab(serial) && seat->focusedPointerSurface() == focusSurface; if (!pointerGrab) { // Client doesn't have pointer grab. const bool touchGrab = seat->hasImplicitTouchGrab(serial) && seat->isSurfaceTouched(focusSurface); if (!touchGrab) { // Client neither has pointer nor touch grab. No drag start allowed. return; } } DragAndDropIcon *dragIcon = nullptr; if (SurfaceInterface *iconSurface = SurfaceInterface::get(iconResource)) { if (const SurfaceRole *role = iconSurface->role()) { if (role != DragAndDropIcon::role()) { wl_resource_post_error(resource->handle, 0, "the wl_surface already has a role assigned %s", role->name().constData()); return; } } else { iconSurface->setRole(DragAndDropIcon::role()); } // drag icon lifespan is mapped to surface lifespan dragIcon = new DragAndDropIcon(iconSurface); } drag.serial = serial; Q_EMIT q->dragStarted(dataSource, focusSurface, serial, dragIcon); } void DataDeviceInterfacePrivate::data_device_set_selection(Resource *resource, wl_resource *source, uint32_t 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 (dataSource && dataSource->xdgToplevelDrag()) { wl_resource_post_error(resource->handle, 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; Q_EMIT q->selectionChanged(selection, serial); } 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_EMIT q->aboutToBeDestroyed(); delete q; } DataDeviceInterface::DataDeviceInterface(SeatInterface *seat, wl_resource *resource) : AbstractDropHandler(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::selection() const { return d->selection; } void DataDeviceInterface::sendSelection(AbstractDataSource *other) { auto r = other ? d->createDataOffer(other) : nullptr; d->send_selection(r ? r->resource() : nullptr); } void DataDeviceInterface::drop() { d->send_drop(); d->drag.surface = nullptr; // prevent sending wl_data_device.leave event disconnect(d->drag.posConnection); d->drag.posConnection = QMetaObject::Connection(); disconnect(d->drag.destroyConnection); d->drag.destroyConnection = QMetaObject::Connection(); if (d->seat->dragSource()->selectedDndAction() != DataDeviceManagerInterface::DnDAction::Ask) { disconnect(d->drag.sourceActionConnection); d->drag.sourceActionConnection = QMetaObject::Connection(); disconnect(d->drag.targetActionConnection); d->drag.targetActionConnection = QMetaObject::Connection(); disconnect(d->drag.keyboardModifiersConnection); d->drag.keyboardModifiersConnection = QMetaObject::Connection(); } } static DataDeviceManagerInterface::DnDAction chooseDndAction(AbstractDataSource *source, DataOfferInterface *offer, Qt::KeyboardModifiers keyboardModifiers) { // first compositor picks an action if modifiers are pressed and it's supported both sides if (keyboardModifiers.testFlag(Qt::ControlModifier)) { if (source->supportedDragAndDropActions().testFlag(DataDeviceManagerInterface::DnDAction::Copy) && offer->supportedDragAndDropActions().has_value() && offer->supportedDragAndDropActions()->testFlag(DataDeviceManagerInterface::DnDAction::Copy)) { return DataDeviceManagerInterface::DnDAction::Copy; } } if (keyboardModifiers.testFlag(Qt::ShiftModifier)) { if (source->supportedDragAndDropActions().testFlag(DataDeviceManagerInterface::DnDAction::Move) && offer->supportedDragAndDropActions().has_value() && offer->supportedDragAndDropActions()->testFlag(DataDeviceManagerInterface::DnDAction::Move)) { return DataDeviceManagerInterface::DnDAction::Move; } } // otherwise we pick the preferred action from the target if the source supported it if (offer->preferredDragAndDropAction().has_value()) { if (source->supportedDragAndDropActions().testFlag(*offer->preferredDragAndDropAction())) { return *offer->preferredDragAndDropAction(); } } // finally pick something everyone supports in a deterministic fashion if (offer->supportedDragAndDropActions().has_value()) { for (const auto &action : {DataDeviceManagerInterface::DnDAction::Copy, DataDeviceManagerInterface::DnDAction::Move, DataDeviceManagerInterface::DnDAction::Ask}) { if (source->supportedDragAndDropActions().testFlag(action) && offer->supportedDragAndDropActions()->testFlag(action)) { return action; } } } return DataDeviceManagerInterface::DnDAction::None; } void DataDeviceInterface::updateDragTarget(SurfaceInterface *surface, quint32 serial) { if (d->drag.surface == surface) { return; } if (d->drag.surface) { 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(); } if (d->drag.keyboardModifiersConnection) { disconnect(d->drag.keyboardModifiersConnection); d->drag.keyboardModifiersConnection = QMetaObject::Connection(); } // don't update serial, we need it } auto dragSource = d->seat->dragSource(); if (!surface || !dragSource) { if (auto s = dragSource) { s->dndAction(DataDeviceManagerInterface::DnDAction::None); } return; } if (dragSource) { dragSource->accept(QString()); } DataOfferInterface *offer = d->createDataOffer(dragSource); offer->sendSourceActions(); 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().count(), wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y())); }); } else if (d->seat->isDragTouch()) { // When dragging from one window to another, we may end up in a data_device // that didn't get "data_device_start_drag". In that case, the internal // touch point serial will be incorrect and we need to update it to the // serial from the seat. SeatInterfacePrivate *seatPrivate = SeatInterfacePrivate::get(seat()); if (seatPrivate->drag.dragImplicitGrabSerial != d->drag.serial) { d->drag.serial = seatPrivate->drag.dragImplicitGrabSerial.value(); } d->drag.posConnection = connect(d->seat, &SeatInterface::touchMoved, this, [this](qint32 id, quint32 serial, const QPointF &globalPosition) { 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().count(), wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y())); }); } d->drag.destroyConnection = connect(d->drag.surface, &SurfaceInterface::aboutToBeDestroyed, this, [this] { d->send_leave(); if (d->drag.posConnection) { disconnect(d->drag.posConnection); } if (d->drag.sourceActionConnection) { disconnect(d->drag.sourceActionConnection); } if (d->drag.targetActionConnection) { disconnect(d->drag.targetActionConnection); } d->drag = DataDeviceInterfacePrivate::Drag(); }); QPointF pos; if (d->seat->isDragPointer()) { pos = d->seat->dragSurfaceTransformation().map(d->seat->pointerPos()); } else if (d->seat->isDragTouch()) { pos = d->seat->dragSurfaceTransformation().map(d->seat->firstTouchPointPosition(surface)); } d->send_enter(serial, surface->resource(), wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y()), offer ? offer->resource() : nullptr); if (offer) { auto matchOffers = [this, dragSource, offer] { Qt::KeyboardModifiers keyboardModifiers; if (d->seat->isDrag()) { // ignore keyboard modifiers when in "ask" negotiation keyboardModifiers = dragSource->keyboardModifiers(); } const DataDeviceManagerInterface::DnDAction action = chooseDndAction(dragSource, offer, keyboardModifiers); offer->dndAction(action); dragSource->dndAction(action); }; matchOffers(); d->drag.targetActionConnection = connect(offer, &DataOfferInterface::dragAndDropActionsChanged, dragSource, matchOffers); d->drag.sourceActionConnection = connect(dragSource, &AbstractDataSource::supportedDragAndDropActionsChanged, offer, matchOffers); d->drag.keyboardModifiersConnection = connect(dragSource, &AbstractDataSource::keyboardModifiersChanged, offer, matchOffers); } } wl_client *DataDeviceInterface::client() { return d->resource()->client(); } } #include "moc_datadevice.cpp"