diff --git a/src/server/datadevice_interface.h b/src/server/datadevice_interface.h --- a/src/server/datadevice_interface.h +++ b/src/server/datadevice_interface.h @@ -32,6 +32,7 @@ { class DataDeviceManagerInterface; +class DataOfferInterface; class DataSourceInterface; class SeatInterface; class SurfaceInterface; @@ -53,10 +54,34 @@ SurfaceInterface *origin() const; SurfaceInterface *icon() const; + /** + * @returns the serial of the implicit grab which started the drag + * @since 5.6 + **/ + quint32 dragImplicitGrabSerial() const; + DataSourceInterface *selection() const; void sendSelection(DataDeviceInterface *other); void sendClearSelection(); + /** + * The event is sent when a drag-and-drop operation is ended because the implicit grab is removed. + * @since 5.6 + **/ + void drop(); + /** + * Updates the SurfaceInterface to which drag motion events are sent. + * + * If a SurfaceInterface was registered in this DataDeviceInterface for drag motion events, it + * will be sent a leave event. + * + * If @p surface is not null it will be sent a drag enter event. + * + * @param surface The SurfaceInterface which gets motion events + * @param serial The serial to be used for enter/leave + * @since 5.6 + **/ + void updateDragTarget(SurfaceInterface *surface, quint32 serial); Q_SIGNALS: void dragStarted(); diff --git a/src/server/datadevice_interface.cpp b/src/server/datadevice_interface.cpp --- a/src/server/datadevice_interface.cpp +++ b/src/server/datadevice_interface.cpp @@ -49,11 +49,19 @@ DataSourceInterface *selection = nullptr; + struct Drag { + SurfaceInterface *surface = nullptr; + QMetaObject::Connection destroyConnection; + QMetaObject::Connection pointerPosConnection; + quint32 serial = 0; + }; + Drag drag; + private: DataDeviceInterface *q_func() { return reinterpret_cast(q); } - void startDrag(DataSourceInterface *dataSource, SurfaceInterface *origin, SurfaceInterface *icon); + void startDrag(DataSourceInterface *dataSource, SurfaceInterface *origin, SurfaceInterface *icon, quint32 serial); void setSelection(DataSourceInterface *dataSource); static void startDragCallback(wl_client *client, wl_resource *resource, wl_resource *source, wl_resource *origin, wl_resource *icon, uint32_t serial); static void setSelectionCallback(wl_client *client, wl_resource *resource, wl_resource *source, uint32_t serial); @@ -83,18 +91,21 @@ Q_UNUSED(client) Q_UNUSED(serial) // TODO: verify serial - cast(resource)->startDrag(DataSourceInterface::get(source), SurfaceInterface::get(origin), SurfaceInterface::get(icon)); + cast(resource)->startDrag(DataSourceInterface::get(source), SurfaceInterface::get(origin), SurfaceInterface::get(icon), serial); } -void DataDeviceInterface::Private::startDrag(DataSourceInterface *dataSource, SurfaceInterface *origin, SurfaceInterface *i) +void DataDeviceInterface::Private::startDrag(DataSourceInterface *dataSource, SurfaceInterface *origin, SurfaceInterface *i, quint32 serial) { - if (seat->focusedPointerSurface() != origin) { + // TODO: allow touch + if (seat->hasImplicitPointerGrab(serial) && seat->focusedPointerSurface() != origin) { wl_resource_post_error(resource, 0, "Surface doesn't have pointer grab"); return; } + // TODO: source is allowed to be null, handled client internally! source = dataSource; surface = origin; icon = i; + drag.serial = serial; Q_Q(DataDeviceInterface); emit q->dragStarted(); } @@ -201,6 +212,75 @@ wl_data_device_send_selection(d->resource, nullptr); } +void DataDeviceInterface::drop() +{ + Q_D(); + if (!d->resource) { + return; + } + wl_data_device_send_drop(d->resource); + client()->flush(); +} + +void DataDeviceInterface::updateDragTarget(SurfaceInterface *surface, quint32 serial) +{ + Q_D(); + if (d->drag.surface) { + if (d->resource && d->drag.surface->resource()) { + wl_data_device_send_leave(d->resource); + } + if (d->drag.pointerPosConnection) { + disconnect(d->drag.pointerPosConnection); + d->drag.pointerPosConnection = QMetaObject::Connection(); + } + disconnect(d->drag.destroyConnection); + d->drag.destroyConnection = QMetaObject::Connection(); + d->drag.surface = nullptr; + // don't update serial, we need it + } + if (!surface) { + return; + } + DataOfferInterface *offer = d->createDataOffer(d->seat->dragSource()->dragSource()); + d->drag.surface = surface; + if (d->seat->isDragPointer()) { + d->drag.pointerPosConnection = connect(d->seat, &SeatInterface::pointerPosChanged, this, + [this] { + Q_D(); + const QPointF pos = d->seat->dragSurfaceTransformation().map(d->seat->pointerPos()); + wl_data_device_send_motion(d->resource, d->seat->timestamp(), + wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y())); + client()->flush(); + } + ); + } + // TODO: same for touch + d->drag.destroyConnection = connect(d->drag.surface, &QObject::destroyed, this, + [this] { + Q_D(); + if (d->resource) { + wl_data_device_send_leave(d->resource); + } + if (d->drag.pointerPosConnection) { + disconnect(d->drag.pointerPosConnection); + } + d->drag = Private::Drag(); + } + ); + + // TODO: handle touch position + const QPointF pos = d->seat->dragSurfaceTransformation().map(d->seat->pointerPos()); + wl_data_device_send_enter(d->resource, serial, surface->resource(), + wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y()), offer ? offer->resource() : nullptr); + d->client->flush(); +} + +quint32 DataDeviceInterface::dragImplicitGrabSerial() const +{ + Q_D(); + return d->drag.serial; +} + DataDeviceInterface::Private *DataDeviceInterface::d_func() const { return reinterpret_cast(d.data()); diff --git a/src/server/dataoffer_interface.cpp b/src/server/dataoffer_interface.cpp --- a/src/server/dataoffer_interface.cpp +++ b/src/server/dataoffer_interface.cpp @@ -72,9 +72,12 @@ void DataOfferInterface::Private::acceptCallback(wl_client *client, wl_resource *resource, uint32_t serial, const char *mimeType) { Q_UNUSED(client) - Q_UNUSED(resource) Q_UNUSED(serial) - Q_UNUSED(mimeType) + auto p = cast(resource); + if (!p->source) { + return; + } + p->source->accept(mimeType ? QString::fromUtf8(mimeType) : QString()); } void DataOfferInterface::Private::destroyCallback(wl_client *client, wl_resource *resource) @@ -106,6 +109,12 @@ wl_data_offer_send_offer(d->resource, mimeType.toUtf8().constData()); } ); + QObject::connect(source, &QObject::destroyed, this, + [this] { + Q_D(); + d->source = nullptr; + } + ); } DataOfferInterface::~DataOfferInterface() = default; diff --git a/src/server/datasource_interface.cpp b/src/server/datasource_interface.cpp --- a/src/server/datasource_interface.cpp +++ b/src/server/datasource_interface.cpp @@ -69,7 +69,10 @@ void DataSourceInterface::Private::destroyCallack(wl_client *client, wl_resource *resource) { Q_UNUSED(client) + auto p = cast(resource); wl_resource_destroy(resource); + p->resource = nullptr; + p->q->deleteLater(); } void DataSourceInterface::Private::offerCallback(wl_client *client, wl_resource *resource, const char *mimeType) diff --git a/src/server/pointer_interface.cpp b/src/server/pointer_interface.cpp --- a/src/server/pointer_interface.cpp +++ b/src/server/pointer_interface.cpp @@ -99,8 +99,13 @@ PointerInterface::PointerInterface(SeatInterface *parent, wl_resource *parentResource) : Resource(new Private(parent, parentResource, this), parent) { + // TODO: handle touch connect(parent, &SeatInterface::pointerPosChanged, this, [this] { Q_D(); + if (d->seat->isDragPointer()) { + // handled by DataDevice + return; + } if (d->focusedSurface && d->resource) { const QPointF pos = d->seat->focusedPointerSurfaceTransformation().map(d->seat->pointerPos()); wl_pointer_send_motion(d->resource, d->seat->timestamp(), diff --git a/src/server/seat_interface.h b/src/server/seat_interface.h --- a/src/server/seat_interface.h +++ b/src/server/seat_interface.h @@ -38,6 +38,7 @@ namespace Server { +class DataDeviceInterface; class Display; class SurfaceInterface; @@ -134,6 +135,76 @@ quint32 timestamp() const; /** + * @name Drag'n'Drop related methods + **/ + ///@{ + /** + * @returns whether there is currently a drag'n'drop going on. + * @since 5.6 + * @see isDragPointer + * @see isDragTouch + * @see dragStarted + * @see dragEnded + **/ + bool isDrag() const; + /** + * @returns whether the drag'n'drop is operated through the pointer device + * @since 5.6 + * @see isDrag + * @see isDragTouch + **/ + bool isDragPointer() const; + /** + * @returns whether the drag'n'drop is operated through the touch device + * @since 5.6 + * @see isDrag + * @see isDragPointer + **/ + bool isDragTouch() const; + /** + * @returns The transformation applied to go from global to local coordinates for drag motion events. + * @see dragSurfaceTransformation + * @since 5.6 + **/ + QMatrix4x4 dragSurfaceTransformation() const; + /** + * @returns The currently focused Surface for drag motion events. + * @since 5.6 + * @see dragSurfaceTransformation + * @see dragSurfaceChanged + **/ + SurfaceInterface *dragSurface() const; + /** + * @returns The PointerInterface which triggered the drag operation + * @since 5.6 + * @see isDragPointer + **/ + PointerInterface *dragPointer() const; + /** + * @returns The DataDeviceInterface which started the drag and drop operation. + * @see isDrag + * @since 5.6 + **/ + DataDeviceInterface *dragSource() const; + /** + * Sets the current drag target to @p surface. + * + * Sends a drag leave event to the current target and an enter event to @p surface. + * The enter position is derived from @p globalPosition and transformed by @p inputTransformation. + * @since 5.6 + **/ + void setDragTarget(SurfaceInterface *surface, const QPointF &globalPosition, const QMatrix4x4 &inputTransformation); + /** + * Sets the current drag target to @p surface. + * + * Sends a drag leave event to the current target and an enter event to @p surface. + * The enter position is derived from current global position and transformed by @p inputTransformation. + * @since 5.6 + **/ + void setDragTarget(SurfaceInterface *surface, const QMatrix4x4 &inputTransformation = QMatrix4x4()); + ///@} + + /** * @name Pointer related methods **/ ///@{ @@ -290,6 +361,11 @@ **/ quint32 pointerButtonSerial(Qt::MouseButton button) const; void pointerAxis(Qt::Orientation orientation, quint32 delta); + /** + * @returns true if there is a pressed button with the given @p serial + * @since 5.6 + **/ + bool hasImplicitPointerGrab(quint32 serial) const; ///@} /** @@ -379,6 +455,25 @@ **/ void focusedPointerChanged(KWayland::Server::PointerInterface*); + /** + * Emitted when a drag'n'drop operation is started + * @since 5.6 + * @see dragEnded + **/ + void dragStarted(); + /** + * Emitted when a drag'n'drop operation ended, either by dropping or canceling. + * @since 5.6 + * @see dragStarted + **/ + void dragEnded(); + /** + * Emitted whenever the drag surface for motion events changed. + * @since 5.6 + * @see dragSurface + **/ + void dragSurfaceChanged(); + private: friend class Display; friend class DataDeviceManagerInterface; diff --git a/src/server/seat_interface.cpp b/src/server/seat_interface.cpp --- a/src/server/seat_interface.cpp +++ b/src/server/seat_interface.cpp @@ -231,6 +231,30 @@ updateSelection(dataDevice, false); } ); + QObject::connect(dataDevice, &DataDeviceInterface::dragStarted, q, + [this, dataDevice] { + if (q->hasImplicitPointerGrab(dataDevice->dragImplicitGrabSerial())) { + drag.mode = Drag::Mode::Pointer; + } else { + // TODO: touch + return; + } + drag.source = dataDevice; + drag.target = dataDevice; + drag.surface = dataDevice->origin(); + drag.sourcePointer = interfaceForSurface(drag.surface, pointers); + // TODO: transformation needs to be either pointer or touch + drag.transformation = globalPointer.focus.transformation; + drag.destroyConnection = QObject::connect(dataDevice, &QObject::destroyed, q, + [this] { + endDrag(display->nextSerial()); + } + ); + dataDevice->updateDragTarget(dataDevice->origin(), dataDevice->dragImplicitGrabSerial()); + emit q->dragStarted(); + emit q->dragSurfaceChanged(); + } + ); // is the new DataDevice for the current keyoard focus? if (keys.focus.surface && !keys.focus.selection) { // same client? @@ -243,6 +267,19 @@ } } +void SeatInterface::Private::endDrag(quint32 serial) +{ + auto target = drag.target; + QObject::disconnect(drag.destroyConnection); + if (target) { + target->drop(); + target->updateDragTarget(nullptr, serial); + } + drag = Drag(); + emit q->dragSurfaceChanged(); + emit q->dragEnded(); +} + void SeatInterface::Private::updateSelection(DataDeviceInterface *dataDevice, bool set) { if (keys.focus.surface && (keys.focus.surface->client() == dataDevice->client())) { @@ -477,6 +514,39 @@ emit timestampChanged(time); } +void SeatInterface::setDragTarget(SurfaceInterface *surface, const QPointF &globalPosition, const QMatrix4x4 &inputTransformation) +{ + Q_D(); + if (surface == d->drag.surface) { + // no change + return; + } + const quint32 serial = d->display->nextSerial(); + if (d->drag.target) { + d->drag.target->updateDragTarget(nullptr, serial); + } + d->drag.target = d->dataDeviceForSurface(surface); + // TODO: update touch + if (d->drag.mode == Private::Drag::Mode::Pointer) { + setPointerPos(globalPosition); + } + if (d->drag.target) { + d->drag.surface = surface; + d->drag.transformation = inputTransformation; + d->drag.target->updateDragTarget(surface, serial); + } else { + d->drag.surface = nullptr; + } + emit dragSurfaceChanged(); + return; +} + +void SeatInterface::setDragTarget(SurfaceInterface *surface, const QMatrix4x4 &inputTransformation) +{ + // TODO: handle touch + setDragTarget(surface, pointerPos(), inputTransformation); +} + SurfaceInterface *SeatInterface::focusedPointerSurface() const { Q_D(); @@ -497,6 +567,10 @@ void SeatInterface::setFocusedPointerSurface(SurfaceInterface *surface, const QMatrix4x4 &transformation) { Q_D(); + if (d->drag.mode == Private::Drag::Mode::Pointer) { + // ignore + return; + } const quint32 serial = d->display->nextSerial(); if (d->globalPointer.focus.pointer) { d->globalPointer.focus.pointer->setFocusedSurface(nullptr, serial); @@ -610,6 +684,10 @@ void SeatInterface::pointerAxis(Qt::Orientation orientation, quint32 delta) { Q_D(); + if (d->drag.mode == Private::Drag::Mode::Pointer) { + // ignore + return; + } if (d->globalPointer.focus.pointer && d->globalPointer.focus.surface) { d->globalPointer.focus.pointer->axis(orientation, delta); } @@ -630,6 +708,10 @@ const quint32 serial = d->display->nextSerial(); d->updatePointerButtonSerial(button, serial); d->updatePointerButtonState(button, Private::Pointer::State::Pressed); + if (d->drag.mode == Private::Drag::Mode::Pointer) { + // ignore + return; + } if (d->globalPointer.focus.pointer && d->globalPointer.focus.surface) { d->globalPointer.focus.pointer->buttonPressed(button, serial); } @@ -648,8 +730,17 @@ { Q_D(); const quint32 serial = d->display->nextSerial(); + const quint32 currentButtonSerial = pointerButtonSerial(button); d->updatePointerButtonSerial(button, serial); d->updatePointerButtonState(button, Private::Pointer::State::Released); + if (d->drag.mode == Private::Drag::Mode::Pointer) { + if (d->drag.source->dragImplicitGrabSerial() != currentButtonSerial) { + // not our drag button - ignore + return; + } + d->endDrag(serial); + return; + } if (d->globalPointer.focus.pointer && d->globalPointer.focus.surface) { d->globalPointer.focus.pointer->buttonReleased(button, serial); } @@ -997,5 +1088,63 @@ } } +bool SeatInterface::isDrag() const +{ + Q_D(); + return d->drag.mode != Private::Drag::Mode::None; +} + +bool SeatInterface::isDragPointer() const +{ + Q_D(); + return d->drag.mode == Private::Drag::Mode::Pointer; +} + +bool SeatInterface::isDragTouch() const +{ + Q_D(); + return d->drag.mode == Private::Drag::Mode::Touch; +} + +bool SeatInterface::hasImplicitPointerGrab(quint32 serial) const +{ + Q_D(); + const auto &serials = d->globalPointer.buttonSerials; + for (auto it = serials.begin(), end = serials.end(); it != end; it++) { + if (it.value() == serial) { + return isPointerButtonPressed(it.key()); + } + } + return false; +} + +QMatrix4x4 SeatInterface::dragSurfaceTransformation() const +{ + Q_D(); + return d->drag.transformation; +} + +SurfaceInterface *SeatInterface::dragSurface() const +{ + Q_D(); + return d->drag.surface; +} + +PointerInterface *SeatInterface::dragPointer() const +{ + Q_D(); + if (d->drag.mode != Private::Drag::Mode::Pointer) { + return nullptr; + } + + return d->drag.sourcePointer; +} + +DataDeviceInterface *SeatInterface::dragSource() const +{ + Q_D(); + return d->drag.source; +} + } } diff --git a/src/server/seat_interface_p.h b/src/server/seat_interface_p.h --- a/src/server/seat_interface_p.h +++ b/src/server/seat_interface_p.h @@ -47,6 +47,7 @@ TouchInterface *touchForSurface(SurfaceInterface *surface) const; DataDeviceInterface *dataDeviceForSurface(SurfaceInterface *surface) const; void registerDataDevice(DataDeviceInterface *dataDevice); + void endDrag(quint32 serial); QString name; bool pointer = false; @@ -133,6 +134,22 @@ }; Touch touchInterface; + struct Drag { + enum class Mode { + None, + Pointer, + Touch + }; + Mode mode = Mode::None; + DataDeviceInterface *source = nullptr; + DataDeviceInterface *target = nullptr; + SurfaceInterface *surface = nullptr; + PointerInterface *sourcePointer = nullptr; + QMatrix4x4 transformation; + QMetaObject::Connection destroyConnection; + }; + Drag drag; + static SeatInterface *get(wl_resource *native) { auto s = cast(native); return s ? s->q : nullptr;