diff --git a/autotests/wayland/decoration_input_test.cpp b/autotests/wayland/decoration_input_test.cpp --- a/autotests/wayland/decoration_input_test.cpp +++ b/autotests/wayland/decoration_input_test.cpp @@ -22,6 +22,7 @@ #include "abstract_client.h" #include "cursor.h" #include "pointer_input.h" +#include "touch_input.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" @@ -63,9 +64,13 @@ void testAxis(); void testDoubleClick_data(); void testDoubleClick(); + void testDoubleTap_data(); + void testDoubleTap(); void testHover(); void testPressToMove_data(); void testPressToMove(); + void testTapToMove_data(); + void testTapToMove(); private: AbstractClient *showWindow(); @@ -344,6 +349,55 @@ QVERIFY(c->isOnAllDesktops()); } +void DecorationInputTest::testDoubleTap_data() +{ + QTest::addColumn("decoPoint"); + QTest::addColumn("expectedSection"); + + QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection; + QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection; + QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection; +} + +void KWin::DecorationInputTest::testDoubleTap() +{ + AbstractClient *c = showWindow(); + QVERIFY(c); + QVERIFY(c->isDecorated()); + QVERIFY(!c->noBorder()); + QVERIFY(!c->isOnAllDesktops()); + quint32 timestamp = 1; + const QPoint tapPoint(c->geometry().center().x(), c->clientPos().y() / 2); + + // double tap + kwinApp()->platform()->touchDown(0, tapPoint, timestamp++); + kwinApp()->platform()->touchUp(0, timestamp++); + kwinApp()->platform()->touchDown(0, tapPoint, timestamp++); + kwinApp()->platform()->touchUp(0, timestamp++); + QVERIFY(c->isOnAllDesktops()); + // double tap again + kwinApp()->platform()->touchDown(0, tapPoint, timestamp++); + kwinApp()->platform()->touchUp(0, timestamp++); + QVERIFY(c->isOnAllDesktops()); + kwinApp()->platform()->touchDown(0, tapPoint, timestamp++); + kwinApp()->platform()->touchUp(0, timestamp++); + QVERIFY(!c->isOnAllDesktops()); + + // test top most deco pixel, BUG: 362860 + c->move(0, 0); + QFETCH(QPoint, decoPoint); + // double click + kwinApp()->platform()->touchDown(0, decoPoint, timestamp++); + QVERIFY(!input()->touch()->decoration().isNull()); + QCOMPARE(input()->touch()->decoration()->client(), c); + QTEST(input()->touch()->decoration()->decoration()->sectionUnderMouse(), "expectedSection"); + kwinApp()->platform()->touchUp(0, timestamp++); + QVERIFY(!c->isOnAllDesktops()); + kwinApp()->platform()->touchDown(0, decoPoint, timestamp++); + kwinApp()->platform()->touchUp(0, timestamp++); + QVERIFY(c->isOnAllDesktops()); +} + void DecorationInputTest::testHover() { AbstractClient *c = showWindow(); @@ -439,6 +493,66 @@ QCOMPARE(c->pos(), oldPos + offset2 + offset3); } +void DecorationInputTest::testTapToMove_data() +{ + QTest::addColumn("offset"); + QTest::addColumn("offset2"); + QTest::addColumn("offset3"); + + QTest::newRow("To right") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0); + QTest::newRow("To left") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0); + QTest::newRow("To bottom") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30); + QTest::newRow("To top") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30); +} + +void DecorationInputTest::testTapToMove() +{ + AbstractClient *c = showWindow(); + QVERIFY(c); + QVERIFY(c->isDecorated()); + QVERIFY(!c->noBorder()); + c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2)); + QSignalSpy startMoveResizedSpy(c, &AbstractClient::clientStartUserMovedResized); + QVERIFY(startMoveResizedSpy.isValid()); + QSignalSpy clientFinishUserMovedResizedSpy(c, &AbstractClient::clientFinishUserMovedResized); + QVERIFY(clientFinishUserMovedResizedSpy.isValid()); + + quint32 timestamp = 1; + QPoint p = QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2); + + kwinApp()->platform()->touchDown(0, p, timestamp++); + QVERIFY(!c->isMove()); + QFETCH(QPoint, offset); + QCOMPARE(input()->touch()->decorationPressId(), 0); + kwinApp()->platform()->touchMotion(0, p + offset, timestamp++); + const QPoint oldPos = c->pos(); + QVERIFY(c->isMove()); + QCOMPARE(startMoveResizedSpy.count(), 1); + + kwinApp()->platform()->touchUp(0, timestamp++); + QTRY_VERIFY(!c->isMove()); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); + QEXPECT_FAIL("", "Just trigger move doesn't move the window", Continue); + QCOMPARE(c->pos(), oldPos + offset); + + // again + kwinApp()->platform()->touchDown(1, p + offset, timestamp++); + QCOMPARE(input()->touch()->decorationPressId(), 1); + QVERIFY(!c->isMove()); + QFETCH(QPoint, offset2); + kwinApp()->platform()->touchMotion(1, QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2) + offset2, timestamp++); + QVERIFY(c->isMove()); + QCOMPARE(startMoveResizedSpy.count(), 2); + QFETCH(QPoint, offset3); + kwinApp()->platform()->touchMotion(1, QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2) + offset3, timestamp++); + + kwinApp()->platform()->touchUp(1, timestamp++); + QTRY_VERIFY(!c->isMove()); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 2); + // TODO: the offset should also be included + QCOMPARE(c->pos(), oldPos + offset2 + offset3); +} + } WAYLANDTEST_MAIN(KWin::DecorationInputTest) diff --git a/input.h b/input.h --- a/input.h +++ b/input.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -42,6 +43,11 @@ class PointerInputRedirection; class TouchInputRedirection; +namespace Decoration +{ +class DecoratedClientImpl; +} + namespace LibInput { class Connection; @@ -280,6 +286,36 @@ virtual bool touchUp(quint32 id, quint32 time); }; +class InputDeviceHandler : public QObject +{ + Q_OBJECT +public: + virtual ~InputDeviceHandler(); + + QPointer window() const { + return m_window; + } + QPointer decoration() const { + return m_decoration; + } + +Q_SIGNALS: + void decorationChanged(); + +protected: + explicit InputDeviceHandler(InputRedirection *parent); + void updateDecoration(Toplevel *t, const QPointF &pos); + InputRedirection *m_input; + /** + * @brief The Toplevel which currently receives events + */ + QPointer m_window; + /** + * @brief The Decoration which currently receives events. + **/ + QPointer m_decoration; +}; + inline InputRedirection *input() { diff --git a/input.cpp b/input.cpp --- a/input.cpp +++ b/input.cpp @@ -491,6 +491,79 @@ } return true; } + bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { + auto seat = waylandServer()->seat(); + if (seat->isTouchSequence()) { + return false; + } + if (input()->touch()->decorationPressId() != -1) { + // already on a decoration, ignore further touch points, but filter out + return true; + } + seat->setTimestamp(time); + input()->touch()->update(pos); + auto decoration = input()->touch()->decoration(); + if (!decoration) { + return false; + } + input()->touch()->setDecorationPressId(id); + m_lastGlobalTouchPos = pos; + m_lastLocalTouchPos = pos - decoration->client()->pos(); + QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); + e.setAccepted(false); + QCoreApplication::sendEvent(decoration->decoration(), &e); + if (!e.isAccepted()) { + decoration->client()->processDecorationButtonPress(&e); + } + return true; + } + bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { + Q_UNUSED(time) + auto decoration = input()->touch()->decoration(); + if (!decoration) { + return false; + } + if (input()->touch()->decorationPressId() == -1) { + return false; + } + if (input()->touch()->decorationPressId() != id) { + // ignore, but filter out + return true; + } + m_lastGlobalTouchPos = pos; + m_lastLocalTouchPos = pos - decoration->client()->pos(); + QHoverEvent e(QEvent::HoverMove, m_lastLocalTouchPos, m_lastLocalTouchPos); + QCoreApplication::instance()->sendEvent(decoration->decoration(), &e); + decoration->client()->processDecorationMove(m_lastLocalTouchPos.toPoint(), pos.toPoint()); + return true; + } + bool touchUp(quint32 id, quint32 time) override { + Q_UNUSED(time); + auto decoration = input()->touch()->decoration(); + if (!decoration) { + return false; + } + if (input()->touch()->decorationPressId() == -1) { + return false; + } + if (input()->touch()->decorationPressId() != id) { + // ignore, but filter out + return true; + } + // send mouse up + QMouseEvent e(QEvent::MouseButtonRelease, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::MouseButtons(), input()->keyboardModifiers()); + e.setAccepted(false); + QCoreApplication::sendEvent(decoration->decoration(), &e); + decoration->client()->processDecorationButtonRelease(&e); + + m_lastGlobalTouchPos = QPointF(); + m_lastLocalTouchPos = QPointF(); + input()->touch()->setDecorationPressId(-1); + return true; + } +private: + QPointF m_lastGlobalTouchPos; + QPointF m_lastLocalTouchPos; }; #ifdef KWIN_BUILD_TABBOX @@ -1206,4 +1279,63 @@ return m_pointer->pos(); } +InputDeviceHandler::InputDeviceHandler(InputRedirection *input) + : QObject(input) + , m_input(input) +{ +} + +InputDeviceHandler::~InputDeviceHandler() = default; + +void InputDeviceHandler::updateDecoration(Toplevel *t, const QPointF &pos) +{ + const auto oldDeco = m_decoration; + bool needsReset = waylandServer()->isScreenLocked(); + if (AbstractClient *c = dynamic_cast(t)) { + // check whether it's on a Decoration + if (c->decoratedClient()) { + const QRect clientRect = QRect(c->clientPos(), c->clientSize()).translated(c->pos()); + if (!clientRect.contains(pos.toPoint())) { + m_decoration = c->decoratedClient(); + } else { + needsReset = true; + } + } else { + needsReset = true; + } + } else { + needsReset = true; + } + if (needsReset) { + m_decoration.clear(); + } + + bool leftSend = false; + auto oldWindow = qobject_cast(m_window.data()); + if (oldWindow && (m_decoration && m_decoration->client() != oldWindow)) { + leftSend = true; + oldWindow->leaveEvent(); + } + + if (oldDeco && oldDeco != m_decoration) { + if (oldDeco->client() != t && !leftSend) { + leftSend = true; + oldDeco->client()->leaveEvent(); + } + // send leave + QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); + QCoreApplication::instance()->sendEvent(oldDeco->decoration(), &event); + } + if (m_decoration) { + if (m_decoration->client() != oldWindow) { + m_decoration->client()->enterEvent(pos.toPoint()); + workspace()->updateFocusMousePosition(pos.toPoint()); + } + const QPointF p = pos - t->pos(); + QHoverEvent event(QEvent::HoverMove, p, p); + QCoreApplication::instance()->sendEvent(m_decoration->decoration(), &event); + m_decoration->client()->processDecorationMove(p.toPoint(), pos.toPoint()); + } +} + } // namespace diff --git a/pointer_input.h b/pointer_input.h --- a/pointer_input.h +++ b/pointer_input.h @@ -42,7 +42,7 @@ class DecoratedClientImpl; } -class KWIN_EXPORT PointerInputRedirection : public QObject +class KWIN_EXPORT PointerInputRedirection : public InputDeviceHandler { Q_OBJECT public: @@ -62,12 +62,6 @@ Qt::MouseButtons buttons() const { return m_qtButtons; } - QPointer window() const { - return m_window; - } - QPointer decoration() const { - return m_decoration; - } QPointer internalWindow() const { return m_internalWindow; } @@ -91,30 +85,16 @@ */ void processAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time); -Q_SIGNALS: - void decorationChanged(); - private: void updatePosition(const QPointF &pos); void updateButton(uint32_t button, InputRedirection::PointerButtonState state); void updateInternalWindow(); - void updateDecoration(Toplevel *t); - InputRedirection *m_input; CursorImage *m_cursor; bool m_inited = false; bool m_supportsWarping; QPointF m_pos; QHash m_buttons; Qt::MouseButtons m_qtButtons; - /** - * @brief The Toplevel which currently receives pointer events - */ - QPointer m_window; - /** - * @brief The Decoration which currently receives pointer events. - * Decoration belongs to the pointerWindow - **/ - QPointer m_decoration; QPointer m_internalWindow; QMetaObject::Connection m_windowGeometryConnection; QMetaObject::Connection m_internalWindowConnection; diff --git a/pointer_input.cpp b/pointer_input.cpp --- a/pointer_input.cpp +++ b/pointer_input.cpp @@ -108,8 +108,7 @@ } PointerInputRedirection::PointerInputRedirection(InputRedirection* parent) - : QObject(parent) - , m_input(parent) + : InputDeviceHandler(parent) , m_cursor(nullptr) , m_supportsWarping(Application::usesLibinput()) { @@ -236,7 +235,7 @@ const auto oldDeco = m_decoration; updateInternalWindow(); if (!m_internalWindow) { - updateDecoration(t); + updateDecoration(t, m_pos); } else { // TODO: send hover leave to decoration if (m_decoration) { @@ -356,57 +355,6 @@ } } -void PointerInputRedirection::updateDecoration(Toplevel *t) -{ - const auto oldDeco = m_decoration; - bool needsReset = waylandServer()->isScreenLocked(); - if (AbstractClient *c = dynamic_cast(t)) { - // check whether it's on a Decoration - if (c->decoratedClient()) { - const QRect clientRect = QRect(c->clientPos(), c->clientSize()).translated(c->pos()); - if (!clientRect.contains(m_pos.toPoint())) { - m_decoration = c->decoratedClient(); - } else { - needsReset = true; - } - } else { - needsReset = true; - } - } else { - needsReset = true; - } - if (needsReset) { - m_decoration.clear(); - } - - bool leftSend = false; - auto oldWindow = qobject_cast(m_window.data()); - if (oldWindow && (m_decoration && m_decoration->client() != oldWindow)) { - leftSend = true; - oldWindow->leaveEvent(); - } - - if (oldDeco && oldDeco != m_decoration) { - if (oldDeco->client() != t && !leftSend) { - leftSend = true; - oldDeco->client()->leaveEvent(); - } - // send leave - QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); - QCoreApplication::instance()->sendEvent(oldDeco->decoration(), &event); - } - if (m_decoration) { - if (m_decoration->client() != oldWindow) { - m_decoration->client()->enterEvent(m_pos.toPoint()); - workspace()->updateFocusMousePosition(m_pos.toPoint()); - } - const QPointF p = m_pos - t->pos(); - QHoverEvent event(QEvent::HoverMove, p, p); - QCoreApplication::instance()->sendEvent(m_decoration->decoration(), &event); - m_decoration->client()->processDecorationMove(p.toPoint(), m_pos.toPoint()); - } -} - void PointerInputRedirection::updatePosition(const QPointF &pos) { // verify that at least one screen contains the pointer position diff --git a/touch_input.h b/touch_input.h --- a/touch_input.h +++ b/touch_input.h @@ -19,6 +19,7 @@ *********************************************************************/ #ifndef KWIN_TOUCH_INPUT_H #define KWIN_TOUCH_INPUT_H +#include "input.h" #include #include @@ -31,7 +32,12 @@ class InputRedirection; class Toplevel; -class TouchInputRedirection : public QObject +namespace Decoration +{ +class DecoratedClientImpl; +} + +class TouchInputRedirection : public InputDeviceHandler { Q_OBJECT public: @@ -51,20 +57,16 @@ void removeId(quint32 internalId); qint32 mappedId(quint32 internalId); - /** - * @brief The Toplevel which currently receives touch events - */ - QPointer window() const { - return m_window; + void setDecorationPressId(qint32 id) { + m_decorationId = id; + } + qint32 decorationPressId() const { + return m_decorationId; } private: - InputRedirection *m_input; bool m_inited = false; - /** - * @brief The Toplevel which currently receives touch events - */ - QPointer m_window; + qint32 m_decorationId = -1; /** * external/kwayland **/ diff --git a/touch_input.cpp b/touch_input.cpp --- a/touch_input.cpp +++ b/touch_input.cpp @@ -18,21 +18,26 @@ along with this program. If not, see . *********************************************************************/ #include "touch_input.h" +#include "abstract_client.h" #include "input.h" #include "toplevel.h" #include "wayland_server.h" #include "workspace.h" +#include "decorations/decoratedclient.h" +// KDecoration +#include // KWayland #include // screenlocker #include +// Qt +#include namespace KWin { TouchInputRedirection::TouchInputRedirection(InputRedirection *parent) - : QObject(parent) - , m_input(parent) + : InputDeviceHandler(parent) { } @@ -64,6 +69,12 @@ // TODO: handle pointer grab aka popups Toplevel *t = m_input->findToplevel(pos.toPoint()); auto oldWindow = m_window; + updateDecoration(t, pos); + if (m_decoration) { + t = nullptr; + } else { + m_decorationId = -1; + } if (!oldWindow.isNull() && t == oldWindow.data()) { return; }