diff --git a/input.cpp b/input.cpp index 1118fda195..0f737fa9d6 100644 --- a/input.cpp +++ b/input.cpp @@ -687,6 +687,47 @@ public: } }; +class DragAndDropInputFilter : public InputEventFilter +{ +public: + bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { + auto seat = waylandServer()->seat(); + if (!seat->isDragPointer()) { + return false; + } + seat->setTimestamp(event->timestamp()); + switch (event->type()) { + case QEvent::MouseMove: { + if (Toplevel *t = input()->findToplevel(event->globalPos())) { + // TODO: consider decorations + if (t->surface() != seat->dragSurface()) { + if (AbstractClient *c = qobject_cast(t)) { + workspace()->raiseClient(c); + } + seat->setPointerPos(event->globalPos()); + seat->setDragTarget(t->surface(), event->globalPos(), t->inputTransformation()); + } + } else { + // no window at that place, if we have a surface we need to reset + seat->setDragTarget(nullptr); + } + seat->setPointerPos(event->globalPos()); + break; + } + case QEvent::MouseButtonPress: + seat->pointerButtonPressed(nativeButton); + break; + case QEvent::MouseButtonRelease: + seat->pointerButtonReleased(nativeButton); + break; + default: + break; + } + // TODO: should we pass through effects? + return true; + } +}; + KWIN_SINGLETON_FACTORY(InputRedirection) InputRedirection::InputRedirection(QObject *parent) @@ -808,6 +849,7 @@ void InputRedirection::setupInputFilters() } #endif if (waylandServer()) { + installInputEventFilter(new DragAndDropInputFilter); installInputEventFilter(new LockScreenFilter); } installInputEventFilter(new ScreenEdgeInputFilter); diff --git a/pointer_input.cpp b/pointer_input.cpp index ef2d8145ed..229c738292 100644 --- a/pointer_input.cpp +++ b/pointer_input.cpp @@ -32,6 +32,7 @@ along with this program. If not, see . #include #include #include +#include #include #include #include @@ -128,6 +129,14 @@ void PointerInputRedirection::init() connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &PointerInputRedirection::update); connect(workspace(), &QObject::destroyed, this, [this] { m_inited = false; }); connect(waylandServer(), &QObject::destroyed, this, [this] { m_inited = false; }); + connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragEnded, this, + [this] { + // need to force a focused pointer change + waylandServer()->seat()->setFocusedPointerSurface(nullptr); + m_window.clear(); + update(); + } + ); // warp the cursor to center of screen warp(screens()->geometry().center()); @@ -216,6 +225,10 @@ void PointerInputRedirection::update() if (!m_inited) { return; } + if (waylandServer()->seat()->isDragPointer()) { + // ignore during drag and drop + return; + } // TODO: handle pointer grab aka popups Toplevel *t = m_input->findToplevel(m_pos.toPoint()); const auto oldDeco = m_decoration; @@ -507,6 +520,13 @@ CursorImage::CursorImage(PointerInputRedirection *parent) , m_pointer(parent) { connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::focusedPointerChanged, this, &CursorImage::update); + connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragStarted, this, &CursorImage::updateDrag); + connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragEnded, this, + [this] { + disconnect(m_drag.connection); + reevaluteSource(); + } + ); connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &CursorImage::reevaluteSource); connect(m_pointer, &PointerInputRedirection::decorationChanged, this, &CursorImage::updateDecoration); // connect the move resize of all window @@ -536,6 +556,28 @@ CursorImage::~CursorImage() = default; void CursorImage::markAsRendered() { + if (m_currentSource == CursorSource::DragAndDrop) { + // always sending a frame rendered to the drag icon surface to not freeze QtWayland (see https://bugreports.qt.io/browse/QTBUG-51599 ) + if (auto ddi = waylandServer()->seat()->dragSource()) { + if (auto s = ddi->icon()) { + s->frameRendered(m_surfaceRenderedTimer.elapsed()); + } + } + auto p = waylandServer()->seat()->dragPointer(); + if (!p) { + return; + } + auto c = p->cursor(); + if (!c) { + return; + } + auto cursorSurface = c->surface(); + if (cursorSurface.isNull()) { + return; + } + cursorSurface->frameRendered(m_surfaceRenderedTimer.elapsed()); + return; + } if (m_currentSource != CursorSource::LockScreen && m_currentSource != CursorSource::PointerSurface) { return; } @@ -680,6 +722,70 @@ void CursorImage::removeEffectsOverrideCursor() reevaluteSource(); } +void CursorImage::updateDrag() +{ + using namespace KWayland::Server; + disconnect(m_drag.connection); + m_drag.cursor.image = QImage(); + m_drag.cursor.hotSpot = QPoint(); + reevaluteSource(); + if (auto p = waylandServer()->seat()->dragPointer()) { + m_drag.connection = connect(p, &PointerInterface::cursorChanged, this, &CursorImage::updateDragCursor); + } else { + m_drag.connection = QMetaObject::Connection(); + } + updateDragCursor(); +} + +void CursorImage::updateDragCursor() +{ + m_drag.cursor.image = QImage(); + m_drag.cursor.hotSpot = QPoint(); + const bool needsEmit = m_currentSource == CursorSource::DragAndDrop; + QImage additionalIcon; + if (auto ddi = waylandServer()->seat()->dragSource()) { + if (auto dragIcon = ddi->icon()) { + if (auto buffer = dragIcon->buffer()) { + additionalIcon = buffer->data().copy(); + } + } + } + auto p = waylandServer()->seat()->dragPointer(); + if (!p) { + if (needsEmit) { + emit changed(); + } + return; + } + auto c = p->cursor(); + if (!c) { + if (needsEmit) { + emit changed(); + } + return; + } + auto cursorSurface = c->surface(); + if (cursorSurface.isNull()) { + if (needsEmit) { + emit changed(); + } + return; + } + auto buffer = cursorSurface.data()->buffer(); + if (!buffer) { + if (needsEmit) { + emit changed(); + } + return; + } + m_drag.cursor.hotSpot = c->hotspot(); + m_drag.cursor.image = buffer->data().copy(); + if (needsEmit) { + emit changed(); + } + // TODO: add the cursor image +} + void CursorImage::loadThemeCursor(Qt::CursorShape shape, Image *image) { loadTheme(); @@ -712,6 +818,11 @@ void CursorImage::loadThemeCursor(Qt::CursorShape shape, Image *image) void CursorImage::reevaluteSource() { + if (waylandServer()->seat()->isDragPointer()) { + // TODO: touch drag? + setSource(CursorSource::DragAndDrop); + return; + } if (waylandServer()->isScreenLocked()) { setSource(CursorSource::LockScreen); return; @@ -757,6 +868,8 @@ QImage CursorImage::image() const return m_serverCursor.image; case CursorSource::Decoration: return m_decorationCursor.image; + case CursorSource::DragAndDrop: + return m_drag.cursor.image; case CursorSource::Fallback: return m_fallbackCursor.image; default: @@ -777,6 +890,8 @@ QPoint CursorImage::hotSpot() const return m_serverCursor.hotSpot; case CursorSource::Decoration: return m_decorationCursor.hotSpot; + case CursorSource::DragAndDrop: + return m_drag.cursor.hotSpot; case CursorSource::Fallback: return m_fallbackCursor.hotSpot; default: diff --git a/pointer_input.h b/pointer_input.h index 9177dcce99..ff0476d1c0 100644 --- a/pointer_input.h +++ b/pointer_input.h @@ -144,6 +144,8 @@ private: void updateDecoration(); void updateDecorationCursor(); void updateMoveResize(); + void updateDrag(); + void updateDragCursor(); void loadTheme(); struct Image { QImage image; @@ -157,6 +159,7 @@ private: MoveResize, PointerSurface, Decoration, + DragAndDrop, Fallback }; void setSource(CursorSource source); @@ -177,6 +180,10 @@ private: Image m_moveResizeCursor; QHash m_cursors; QElapsedTimer m_surfaceRenderedTimer; + struct { + Image cursor; + QMetaObject::Connection connection; + } m_drag; }; }