diff --git a/autotests/wayland/internal_window.cpp b/autotests/wayland/internal_window.cpp --- a/autotests/wayland/internal_window.cpp +++ b/autotests/wayland/internal_window.cpp @@ -46,6 +46,7 @@ void testPointerAxis(); void testKeyboard_data(); void testKeyboard(); + void testTouch(); }; class HelperWindow : public QRasterWindow @@ -55,6 +56,13 @@ HelperWindow(); ~HelperWindow(); + QPoint latestGlobalMousePos() const { + return m_latestGlobalMousePos; + } + Qt::MouseButtons pressedButtons() const { + return m_pressedButtons; + } + Q_SIGNALS: void entered(); void left(); @@ -74,6 +82,10 @@ void wheelEvent(QWheelEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override; + +private: + QPoint m_latestGlobalMousePos; + Qt::MouseButtons m_pressedButtons = Qt::MouseButtons(); }; HelperWindow::HelperWindow() @@ -103,18 +115,21 @@ void HelperWindow::mouseMoveEvent(QMouseEvent *event) { + m_latestGlobalMousePos = event->globalPos(); emit mouseMoved(event->globalPos()); } void HelperWindow::mousePressEvent(QMouseEvent *event) { - Q_UNUSED(event) + m_latestGlobalMousePos = event->globalPos(); + m_pressedButtons = event->buttons(); emit mousePressed(); } void HelperWindow::mouseReleaseEvent(QMouseEvent *event) { - Q_UNUSED(event) + m_latestGlobalMousePos = event->globalPos(); + m_pressedButtons = event->buttons(); emit mouseReleased(); } @@ -282,6 +297,72 @@ QCOMPARE(pressSpy.count(), 1); } +void InternalWindowTest::testTouch() +{ + // touch events for internal windows are emulated through mouse events + QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QVERIFY(clientAddedSpy.isValid()); + HelperWindow win; + win.setGeometry(0, 0, 100, 100); + win.show(); + QVERIFY(clientAddedSpy.wait()); + QCOMPARE(clientAddedSpy.count(), 1); + + QSignalSpy pressSpy(&win, &HelperWindow::mousePressed); + QVERIFY(pressSpy.isValid()); + QSignalSpy releaseSpy(&win, &HelperWindow::mouseReleased); + QVERIFY(releaseSpy.isValid()); + QSignalSpy moveSpy(&win, &HelperWindow::mouseMoved); + QVERIFY(moveSpy.isValid()); + + quint32 timestamp = 1; + QCOMPARE(win.pressedButtons(), Qt::MouseButtons()); + kwinApp()->platform()->touchDown(0, QPointF(50, 50), timestamp++); + QCOMPARE(pressSpy.count(), 1); + QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50)); + QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); + + // further touch down should not trigger + kwinApp()->platform()->touchDown(1, QPointF(75, 75), timestamp++); + QCOMPARE(pressSpy.count(), 1); + kwinApp()->platform()->touchUp(1, timestamp++); + QCOMPARE(releaseSpy.count(), 0); + QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50)); + QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); + + // another press + kwinApp()->platform()->touchDown(1, QPointF(10, 10), timestamp++); + QCOMPARE(pressSpy.count(), 1); + QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50)); + QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); + + // simulate the move + QCOMPARE(moveSpy.count(), 0); + kwinApp()->platform()->touchMotion(0, QPointF(80, 90), timestamp++); + QCOMPARE(moveSpy.count(), 1); + QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90)); + QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); + + // move on other ID should not do anything + kwinApp()->platform()->touchMotion(1, QPointF(20, 30), timestamp++); + QCOMPARE(moveSpy.count(), 1); + QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90)); + QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); + + // now up our main point + kwinApp()->platform()->touchUp(0, timestamp++); + QCOMPARE(releaseSpy.count(), 1); + QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90)); + QCOMPARE(win.pressedButtons(), Qt::MouseButtons()); + + // and up the additional point + kwinApp()->platform()->touchUp(1, timestamp++); + QCOMPARE(releaseSpy.count(), 1); + QCOMPARE(moveSpy.count(), 1); + QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90)); + QCOMPARE(win.pressedButtons(), Qt::MouseButtons()); +} + } WAYLANDTEST_MAIN(KWin::InternalWindowTest) diff --git a/input.h b/input.h --- a/input.h +++ b/input.h @@ -298,13 +298,18 @@ QPointer decoration() const { return m_decoration; } + QPointer internalWindow() const { + return m_internalWindow; + } Q_SIGNALS: void decorationChanged(); + void internalWindowChanged(); protected: explicit InputDeviceHandler(InputRedirection *parent); void updateDecoration(Toplevel *t, const QPointF &pos); + void updateInternalWindow(const QPointF &pos); InputRedirection *m_input; /** * @brief The Toplevel which currently receives events @@ -314,6 +319,7 @@ * @brief The Decoration which currently receives events. **/ QPointer m_decoration; + QPointer m_internalWindow; }; inline diff --git a/input.cpp b/input.cpp --- a/input.cpp +++ b/input.cpp @@ -427,6 +427,81 @@ event->setAccepted(false); return QCoreApplication::sendEvent(found, event); } + + bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { + auto seat = waylandServer()->seat(); + if (seat->isTouchSequence()) { + // something else is getting the events + return false; + } + auto touch = input()->touch(); + if (touch->internalPressId() != -1) { + // already on a decoration, ignore further touch points, but filter out + return true; + } + // a new touch point + seat->setTimestamp(time); + touch->update(pos); + auto internal = touch->internalWindow(); + if (!internal) { + return false; + } + touch->setInternalPressId(id); + // Qt's touch event API is rather complex, let's do fake mouse events instead + m_lastGlobalTouchPos = pos; + m_lastLocalTouchPos = pos - QPointF(internal->x(), internal->y()); + QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); + e.setAccepted(false); + QCoreApplication::sendEvent(internal.data(), &e); + return true; + } + bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { + auto touch = input()->touch(); + auto internal = touch->internalWindow(); + if (!internal) { + return false; + } + if (touch->internalPressId() == -1) { + return false; + } + waylandServer()->seat()->setTimestamp(time); + if (touch->internalPressId() != qint32(id)) { + // ignore, but filter out + return true; + } + m_lastGlobalTouchPos = pos; + m_lastLocalTouchPos = pos - QPointF(internal->x(), internal->y()); + QMouseEvent e(QEvent::MouseMove, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); + QCoreApplication::instance()->sendEvent(internal.data(), &e); + return true; + } + bool touchUp(quint32 id, quint32 time) override { + auto touch = input()->touch(); + auto internal = touch->internalWindow(); + if (!internal) { + return false; + } + if (touch->internalPressId() == -1) { + return false; + } + waylandServer()->seat()->setTimestamp(time); + if (touch->internalPressId() != qint32(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(internal.data(), &e); + + m_lastGlobalTouchPos = QPointF(); + m_lastLocalTouchPos = QPointF(); + input()->touch()->setInternalPressId(-1); + return true; + } +private: + QPointF m_lastGlobalTouchPos; + QPointF m_lastLocalTouchPos; }; class DecorationEventFilter : public InputEventFilter { @@ -1338,4 +1413,55 @@ } } +void InputDeviceHandler::updateInternalWindow(const QPointF &pos) +{ + const auto oldInternalWindow = m_internalWindow; + bool found = false; + // TODO: screen locked check without going through wayland server + bool needsReset = waylandServer()->isScreenLocked(); + const auto &internalClients = waylandServer()->internalClients(); + const bool change = m_internalWindow.isNull() || !(m_internalWindow->flags().testFlag(Qt::Popup) && m_internalWindow->isVisible()); + if (!internalClients.isEmpty() && change) { + auto it = internalClients.end(); + do { + it--; + if (QWindow *w = (*it)->internalWindow()) { + if (!w->isVisible()) { + continue; + } + if (w->geometry().contains(pos.toPoint())) { + // check input mask + const QRegion mask = w->mask().translated(w->geometry().topLeft()); + if (!mask.isEmpty() && !mask.contains(pos.toPoint())) { + continue; + } + m_internalWindow = QPointer(w); + found = true; + break; + } + } + } while (it != internalClients.begin()); + if (!found) { + needsReset = true; + } + } + if (needsReset) { + m_internalWindow.clear(); + } + if (oldInternalWindow != m_internalWindow) { + // changed + if (oldInternalWindow) { + QEvent event(QEvent::Leave); + QCoreApplication::sendEvent(oldInternalWindow.data(), &event); + } + if (m_internalWindow) { + QEnterEvent event(pos - m_internalWindow->position(), + pos - m_internalWindow->position(), + pos); + QCoreApplication::sendEvent(m_internalWindow.data(), &event); + } + emit internalWindowChanged(); + } +} + } // namespace diff --git a/pointer_input.h b/pointer_input.h --- a/pointer_input.h +++ b/pointer_input.h @@ -62,9 +62,6 @@ Qt::MouseButtons buttons() const { return m_qtButtons; } - QPointer internalWindow() const { - return m_internalWindow; - } QImage cursorImage() const; QPoint cursorHotSpot() const; @@ -88,14 +85,12 @@ private: void updatePosition(const QPointF &pos); void updateButton(uint32_t button, InputRedirection::PointerButtonState state); - void updateInternalWindow(); CursorImage *m_cursor; bool m_inited = false; bool m_supportsWarping; QPointF m_pos; QHash m_buttons; Qt::MouseButtons m_qtButtons; - 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 @@ -138,6 +138,21 @@ update(); } ); + connect(this, &PointerInputRedirection::internalWindowChanged, this, + [this] { + disconnect(m_internalWindowConnection); + m_internalWindowConnection = QMetaObject::Connection(); + if (m_internalWindow) { + m_internalWindowConnection = connect(m_internalWindow.data(), &QWindow::visibleChanged, this, + [this] (bool visible) { + if (!visible) { + update(); + } + } + ); + } + } + ); // warp the cursor to center of screen warp(screens()->geometry().center()); @@ -233,7 +248,7 @@ // TODO: handle pointer grab aka popups Toplevel *t = m_input->findToplevel(m_pos.toPoint()); const auto oldDeco = m_decoration; - updateInternalWindow(); + updateInternalWindow(m_pos); if (!m_internalWindow) { updateDecoration(t, m_pos); } else { @@ -296,65 +311,6 @@ } } -void PointerInputRedirection::updateInternalWindow() -{ - const auto oldInternalWindow = m_internalWindow; - bool found = false; - // TODO: screen locked check without going through wayland server - bool needsReset = waylandServer()->isScreenLocked(); - const auto &internalClients = waylandServer()->internalClients(); - const bool change = m_internalWindow.isNull() || !(m_internalWindow->flags().testFlag(Qt::Popup) && m_internalWindow->isVisible()); - if (!internalClients.isEmpty() && change) { - auto it = internalClients.end(); - do { - it--; - if (QWindow *w = (*it)->internalWindow()) { - if (!w->isVisible()) { - continue; - } - if (w->geometry().contains(m_pos.toPoint())) { - // check input mask - const QRegion mask = w->mask().translated(w->geometry().topLeft()); - if (!mask.isEmpty() && !mask.contains(m_pos.toPoint())) { - continue; - } - m_internalWindow = QPointer(w); - found = true; - break; - } - } - } while (it != internalClients.begin()); - if (!found) { - needsReset = true; - } - } - if (needsReset) { - m_internalWindow.clear(); - } - if (oldInternalWindow != m_internalWindow) { - // changed - if (oldInternalWindow) { - disconnect(m_internalWindowConnection); - m_internalWindowConnection = QMetaObject::Connection(); - QEvent event(QEvent::Leave); - QCoreApplication::sendEvent(oldInternalWindow.data(), &event); - } - if (m_internalWindow) { - m_internalWindowConnection = connect(m_internalWindow.data(), &QWindow::visibleChanged, this, - [this] (bool visible) { - if (!visible) { - update(); - } - }); - QEnterEvent event(m_pos - m_internalWindow->position(), - m_pos - m_internalWindow->position(), - m_pos); - QCoreApplication::sendEvent(m_internalWindow.data(), &event); - return; - } - } -} - 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 @@ -63,15 +63,23 @@ qint32 decorationPressId() const { return m_decorationId; } + void setInternalPressId(qint32 id) { + m_internalId = id; + } + qint32 internalPressId() const { + return m_internalId; + } private: bool m_inited = false; qint32 m_decorationId = -1; + qint32 m_internalId = -1; /** * external/kwayland **/ QHash m_idMapper; QMetaObject::Connection m_windowGeometryConnection; + bool m_windowUpdatedInCycle = false; }; } diff --git a/touch_input.cpp b/touch_input.cpp --- a/touch_input.cpp +++ b/touch_input.cpp @@ -32,6 +32,7 @@ #include // Qt #include +#include namespace KWin { @@ -66,14 +67,29 @@ if (!m_inited) { return; } + if (m_windowUpdatedInCycle) { + return; + } + m_windowUpdatedInCycle = true; // 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; + updateInternalWindow(pos); + if (!m_internalWindow) { + updateDecoration(t, pos); } else { + // TODO: send hover leave to decoration + if (m_decoration) { + m_decoration->client()->leaveEvent(); + } + m_decoration.clear(); + } + if (m_decoration || m_internalWindow) { + t = nullptr; + } else if (!m_decoration) { m_decorationId = -1; + } else if (!m_internalWindow) { + m_internalId = -1; } if (!oldWindow.isNull() && t == oldWindow.data()) { return; @@ -133,38 +149,44 @@ if (!m_inited) { return; } + m_windowUpdatedInCycle = false; const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->touchDown(id, pos, time)) { return; } } + m_windowUpdatedInCycle = false; } void TouchInputRedirection::processUp(qint32 id, quint32 time) { if (!m_inited) { return; } + m_windowUpdatedInCycle = false; const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->touchUp(id, time)) { return; } } + m_windowUpdatedInCycle = false; } void TouchInputRedirection::processMotion(qint32 id, const QPointF &pos, quint32 time) { if (!m_inited) { return; } + m_windowUpdatedInCycle = false; const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->touchMotion(id, pos, time)) { return; } } + m_windowUpdatedInCycle = false; } void TouchInputRedirection::cancel()