From 2e29711323789319bffb3c39e20079fa667f7f6e Mon Sep 17 00:00:00 2001 From: Roman Gilg Date: Sat, 15 Sep 2018 02:00:24 +0200 Subject: [PATCH] Rework InputDeviceHandler focus tracking Summary: This patch aims at improving the Toplevel, internal window and decoration focus tracking. In detail the goals are: * Clean tracking of beneath and focus Toplevel as well as decoration and internal windows. Splitting this up in well defined sub routines. * Minimal find Toplevel operations on window stack. * Reduce code duplication in pointer and touch child classes. * Reuse tracking in drag operations. * Allow direct usage of Wayland input interfaces for decoration and internal windows in the future. * Update touch focus on external events like VD switches correctly. Test Plan: Manually and existing autotests. Reviewers: #kwin Subscribers: kwin, zzag Tags: #kwin Differential Revision: https://phabricator.kde.org/D15595 --- .../integration/decoration_input_test.cpp | 4 +- autotests/integration/internal_window.cpp | 6 +- .../integration/pointer_constraints_test.cpp | 10 +- autotests/integration/pointer_input.cpp | 4 +- .../integration/window_selection_test.cpp | 26 +- input.cpp | 370 ++++++++++------- input.h | 88 ++-- pointer_input.cpp | 386 ++++++++++-------- pointer_input.h | 16 +- touch_input.cpp | 143 ++++--- touch_input.h | 17 +- 11 files changed, 639 insertions(+), 431 deletions(-) diff --git a/autotests/integration/decoration_input_test.cpp b/autotests/integration/decoration_input_test.cpp index 34eb190904..68b0e691aa 100644 --- a/autotests/integration/decoration_input_test.cpp +++ b/autotests/integration/decoration_input_test.cpp @@ -836,8 +836,8 @@ void DecorationInputTest::testTouchEvents() QCOMPARE(hoverMoveSpy.count(), 3); QCOMPARE(hoverLeaveSpy.count(), 1); kwinApp()->platform()->touchUp(0, timestamp++); - QCOMPARE(hoverMoveSpy.count(), 4); - QCOMPARE(hoverLeaveSpy.count(), 1); + QCOMPARE(hoverMoveSpy.count(), 3); + QCOMPARE(hoverLeaveSpy.count(), 2); } void DecorationInputTest::testTooltipDoesntEatKeyEvents_data() diff --git a/autotests/integration/internal_window.cpp b/autotests/integration/internal_window.cpp index a1fb1a6c42..8936b91893 100644 --- a/autotests/integration/internal_window.cpp +++ b/autotests/integration/internal_window.cpp @@ -230,11 +230,11 @@ void InternalWindowTest::testEnterLeave() quint32 timestamp = 1; kwinApp()->platform()->pointerMotion(QPoint(50, 50), timestamp++); - QTRY_COMPARE(enterSpy.count(), 1); + QTRY_COMPARE(moveSpy.count(), 1); kwinApp()->platform()->pointerMotion(QPoint(60, 50), timestamp++); - QTRY_COMPARE(moveSpy.count(), 1); - QCOMPARE(moveSpy.first().first().toPoint(), QPoint(60, 50)); + QTRY_COMPARE(moveSpy.count(), 2); + QCOMPARE(moveSpy[1].first().toPoint(), QPoint(60, 50)); kwinApp()->platform()->pointerMotion(QPoint(101, 50), timestamp++); QTRY_COMPARE(leaveSpy.count(), 1); diff --git a/autotests/integration/pointer_constraints_test.cpp b/autotests/integration/pointer_constraints_test.cpp index 43b813c7a1..c2cb021368 100644 --- a/autotests/integration/pointer_constraints_test.cpp +++ b/autotests/integration/pointer_constraints_test.cpp @@ -232,7 +232,7 @@ void TestPointerConstraints::testConfinedPointer() QVERIFY(unconfinedSpy2.isValid()); // activate it again, this confines again - workspace()->activateClient(static_cast(input()->pointer()->window().data())); + workspace()->activateClient(static_cast(input()->pointer()->focus().data())); QVERIFY(confinedSpy2.wait()); QCOMPARE(input()->pointer()->isConstrained(), true); @@ -241,7 +241,7 @@ void TestPointerConstraints::testConfinedPointer() QVERIFY(unconfinedSpy2.wait()); QCOMPARE(input()->pointer()->isConstrained(), false); // activate it again, this confines again - workspace()->activateClient(static_cast(input()->pointer()->window().data())); + workspace()->activateClient(static_cast(input()->pointer()->focus().data())); QVERIFY(confinedSpy2.wait()); QCOMPARE(input()->pointer()->isConstrained(), true); @@ -272,7 +272,7 @@ void TestPointerConstraints::testConfinedPointer() confinedPointer.reset(nullptr); Test::flushWaylandConnection(); - QSignalSpy constraintsChangedSpy(input()->pointer()->window()->surface(), &KWayland::Server::SurfaceInterface::pointerConstraintsChanged); + QSignalSpy constraintsChangedSpy(input()->pointer()->focus()->surface(), &KWayland::Server::SurfaceInterface::pointerConstraintsChanged); QVERIFY(constraintsChangedSpy.isValid()); QVERIFY(constraintsChangedSpy.wait()); @@ -348,7 +348,7 @@ void TestPointerConstraints::testLockedPointer() QVERIFY(lockedSpy2.isValid()); // activate the client again, this should lock again - workspace()->activateClient(static_cast(input()->pointer()->window().data())); + workspace()->activateClient(static_cast(input()->pointer()->focus().data())); QVERIFY(lockedSpy2.wait()); QCOMPARE(input()->pointer()->isConstrained(), true); @@ -361,7 +361,7 @@ void TestPointerConstraints::testLockedPointer() lockedPointer.reset(nullptr); Test::flushWaylandConnection(); - QSignalSpy constraintsChangedSpy(input()->pointer()->window()->surface(), &KWayland::Server::SurfaceInterface::pointerConstraintsChanged); + QSignalSpy constraintsChangedSpy(input()->pointer()->focus()->surface(), &KWayland::Server::SurfaceInterface::pointerConstraintsChanged); QVERIFY(constraintsChangedSpy.isValid()); QVERIFY(constraintsChangedSpy.wait()); diff --git a/autotests/integration/pointer_input.cpp b/autotests/integration/pointer_input.cpp index 1302fbf0ad..dfc859ff9f 100644 --- a/autotests/integration/pointer_input.cpp +++ b/autotests/integration/pointer_input.cpp @@ -963,7 +963,7 @@ void PointerInputTest::testCursorImage() // move cursor to center of window, this should first set a null pointer, so we still show old cursor Cursor::setPos(window->geometry().center()); - QCOMPARE(p->window().data(), window); + QCOMPARE(p->focus().data(), window); QCOMPARE(p->cursorImage(), fallbackCursor); QVERIFY(enteredSpy.wait()); @@ -1018,7 +1018,7 @@ void PointerInputTest::testCursorImage() // move cursor somewhere else, should reset to fallback cursor Cursor::setPos(window->geometry().bottomLeft() + QPoint(20, 20)); - QVERIFY(p->window().isNull()); + QVERIFY(p->focus().isNull()); QVERIFY(!p->cursorImage().isNull()); QCOMPARE(p->cursorImage(), fallbackCursor); } diff --git a/autotests/integration/window_selection_test.cpp b/autotests/integration/window_selection_test.cpp index 3b70a37a7a..d163c809d3 100644 --- a/autotests/integration/window_selection_test.cpp +++ b/autotests/integration/window_selection_test.cpp @@ -115,7 +115,7 @@ void TestWindowSelection::testSelectOnWindowPointer() QVERIFY(client); QVERIFY(keyboardEnteredSpy.wait()); KWin::Cursor::setPos(client->geometry().center()); - QCOMPARE(input()->pointer()->window().data(), client); + QCOMPARE(input()->pointer()->focus().data(), client); QVERIFY(pointerEnteredSpy.wait()); Toplevel *selectedWindow = nullptr; @@ -142,11 +142,11 @@ void TestWindowSelection::testSelectOnWindowPointer() // should not have ended the mode QCOMPARE(input()->isSelectingWindow(), true); QVERIFY(!selectedWindow); - QVERIFY(input()->pointer()->window().isNull()); + QVERIFY(input()->pointer()->focus().isNull()); // updating the pointer should not change anything input()->pointer()->update(); - QVERIFY(input()->pointer()->window().isNull()); + QVERIFY(input()->pointer()->focus().isNull()); // updating keyboard should also not change input()->keyboard()->update(); @@ -160,7 +160,7 @@ void TestWindowSelection::testSelectOnWindowPointer() kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QCOMPARE(input()->isSelectingWindow(), false); QCOMPARE(selectedWindow, client); - QCOMPARE(input()->pointer()->window().data(), client); + QCOMPARE(input()->pointer()->focus().data(), client); // should give back keyboard and pointer QVERIFY(pointerEnteredSpy.wait()); if (keyboardEnteredSpy.count() != 2) { @@ -240,7 +240,7 @@ void TestWindowSelection::testSelectOnWindowKeyboard() kwinApp()->platform()->keyboardKeyPressed(key, timestamp++); QCOMPARE(input()->isSelectingWindow(), false); QCOMPARE(selectedWindow, client); - QCOMPARE(input()->pointer()->window().data(), client); + QCOMPARE(input()->pointer()->focus().data(), client); // should give back keyboard and pointer QVERIFY(pointerEnteredSpy.wait()); if (keyboardEnteredSpy.count() != 2) { @@ -336,7 +336,7 @@ void TestWindowSelection::testCancelOnWindowPointer() QVERIFY(client); QVERIFY(keyboardEnteredSpy.wait()); KWin::Cursor::setPos(client->geometry().center()); - QCOMPARE(input()->pointer()->window().data(), client); + QCOMPARE(input()->pointer()->focus().data(), client); QVERIFY(pointerEnteredSpy.wait()); Toplevel *selectedWindow = nullptr; @@ -363,7 +363,7 @@ void TestWindowSelection::testCancelOnWindowPointer() kwinApp()->platform()->pointerButtonReleased(BTN_RIGHT, timestamp++); QCOMPARE(input()->isSelectingWindow(), false); QVERIFY(!selectedWindow); - QCOMPARE(input()->pointer()->window().data(), client); + QCOMPARE(input()->pointer()->focus().data(), client); // should give back keyboard and pointer QVERIFY(pointerEnteredSpy.wait()); if (keyboardEnteredSpy.count() != 2) { @@ -395,7 +395,7 @@ void TestWindowSelection::testCancelOnWindowKeyboard() QVERIFY(client); QVERIFY(keyboardEnteredSpy.wait()); KWin::Cursor::setPos(client->geometry().center()); - QCOMPARE(input()->pointer()->window().data(), client); + QCOMPARE(input()->pointer()->focus().data(), client); QVERIFY(pointerEnteredSpy.wait()); Toplevel *selectedWindow = nullptr; @@ -421,7 +421,7 @@ void TestWindowSelection::testCancelOnWindowKeyboard() kwinApp()->platform()->keyboardKeyPressed(KEY_ESC, timestamp++); QCOMPARE(input()->isSelectingWindow(), false); QVERIFY(!selectedWindow); - QCOMPARE(input()->pointer()->window().data(), client); + QCOMPARE(input()->pointer()->focus().data(), client); // should give back keyboard and pointer QVERIFY(pointerEnteredSpy.wait()); if (keyboardEnteredSpy.count() != 2) { @@ -454,7 +454,7 @@ void TestWindowSelection::testSelectPointPointer() QVERIFY(client); QVERIFY(keyboardEnteredSpy.wait()); KWin::Cursor::setPos(client->geometry().center()); - QCOMPARE(input()->pointer()->window().data(), client); + QCOMPARE(input()->pointer()->focus().data(), client); QVERIFY(pointerEnteredSpy.wait()); QPoint point; @@ -488,11 +488,11 @@ void TestWindowSelection::testSelectPointPointer() // should not have ended the mode QCOMPARE(input()->isSelectingWindow(), true); QCOMPARE(point, QPoint()); - QVERIFY(input()->pointer()->window().isNull()); + QVERIFY(input()->pointer()->focus().isNull()); // updating the pointer should not change anything input()->pointer()->update(); - QVERIFY(input()->pointer()->window().isNull()); + QVERIFY(input()->pointer()->focus().isNull()); // updating keyboard should also not change input()->keyboard()->update(); @@ -506,7 +506,7 @@ void TestWindowSelection::testSelectPointPointer() kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QCOMPARE(input()->isSelectingWindow(), false); QCOMPARE(point, input()->globalPointer().toPoint()); - QCOMPARE(input()->pointer()->window().data(), client); + QCOMPARE(input()->pointer()->focus().data(), client); // should give back keyboard and pointer QVERIFY(pointerEnteredSpy.wait()); if (keyboardEnteredSpy.count() != 2) { diff --git a/input.cpp b/input.cpp index 98f7406b20..d1baacfe24 100644 --- a/input.cpp +++ b/input.cpp @@ -3,6 +3,7 @@ This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin +Copyright (C) 2018 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -227,12 +228,14 @@ public: auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); if (event->type() == QEvent::MouseMove) { - input()->pointer()->update(); if (pointerSurfaceAllowed()) { + // TODO: should the pointer position always stay in sync, i.e. not do the check? seat->setPointerPos(event->screenPos().toPoint()); } } else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) { if (pointerSurfaceAllowed()) { + // TODO: can we leak presses/releases here when we move the mouse in between from an allowed surface to + // disallowed one or vice versa? event->type() == QEvent::MouseButtonPress ? seat->pointerButtonPressed(nativeButton) : seat->pointerButtonReleased(nativeButton); } } @@ -293,9 +296,6 @@ public: } auto seat = waylandServer()->seat(); seat->setTimestamp(time); - if (!seat->isTouchSequence()) { - input()->touch()->update(pos); - } if (touchSurfaceAllowed()) { input()->touch()->insertId(id, seat->touchDown(pos)); } @@ -477,6 +477,52 @@ public: } return true; } + + bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { + Q_UNUSED(id) + Q_UNUSED(pos) + Q_UNUSED(time) + AbstractClient *c = workspace()->getMovingClient(); + if (!c) { + return false; + } + return true; + } + + bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { + Q_UNUSED(time) + AbstractClient *c = workspace()->getMovingClient(); + if (!c) { + return false; + } + if (!m_set) { + m_id = id; + m_set = true; + } + if (m_id == id) { + c->updateMoveResize(pos.toPoint()); + } + return true; + } + + bool touchUp(quint32 id, quint32 time) override { + Q_UNUSED(time) + AbstractClient *c = workspace()->getMovingClient(); + if (!c) { + return false; + } + if (m_id == id || !m_set) { + c->endMoveResize(); + m_set = false; + // pass through to update decoration filter later on + return false; + } + m_set = false; + return true; + } +private: + quint32 m_id = 0; + bool m_set = false; }; class WindowSelectorFilter : public InputEventFilter { @@ -764,13 +810,6 @@ class InternalWindowEventFilter : public InputEventFilter { if (!internal) { return false; } - if (event->buttons() == Qt::NoButton) { - // update pointer window only if no button is pressed - input()->pointer()->update(); - } - if (!internal) { - return false; - } // find client switch (event->type()) { @@ -883,12 +922,11 @@ class InternalWindowEventFilter : public InputEventFilter { } auto touch = input()->touch(); if (touch->internalPressId() != -1) { - // already on a decoration, ignore further touch points, but filter out + // already on internal window, 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; @@ -897,6 +935,10 @@ class InternalWindowEventFilter : public InputEventFilter { // 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()); + + QEnterEvent enterEvent(m_lastLocalTouchPos, m_lastLocalTouchPos, pos); + QCoreApplication::sendEvent(internal.data(), &enterEvent); + QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); @@ -918,6 +960,7 @@ class InternalWindowEventFilter : public InputEventFilter { } 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; @@ -941,6 +984,9 @@ class InternalWindowEventFilter : public InputEventFilter { e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); + QEvent leaveEvent(QEvent::Leave); + QCoreApplication::sendEvent(internal.data(), &leaveEvent); + m_lastGlobalTouchPos = QPointF(); m_lastLocalTouchPos = QPointF(); input()->touch()->setInternalPressId(-1); @@ -962,9 +1008,6 @@ public: const QPointF p = event->globalPos() - decoration->client()->pos(); switch (event->type()) { case QEvent::MouseMove: { - if (event->buttons() == Qt::NoButton) { - return false; - } QHoverEvent e(QEvent::HoverMove, p, p); QCoreApplication::instance()->sendEvent(decoration->decoration(), &e); decoration->client()->processDecorationMove(p.toPoint(), event->globalPos()); @@ -1034,14 +1077,18 @@ public: 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(); + + QHoverEvent hoverEvent(QEvent::HoverMove, m_lastLocalTouchPos, m_lastLocalTouchPos); + QCoreApplication::sendEvent(decoration->decoration(), &hoverEvent); + QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration->decoration(), &e); @@ -1065,13 +1112,10 @@ public: } m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - decoration->client()->pos(); - if (auto c = workspace()->getMovingClient()) { - c->updateMoveResize(pos); - } else { - QHoverEvent e(QEvent::HoverMove, m_lastLocalTouchPos, m_lastLocalTouchPos); - QCoreApplication::instance()->sendEvent(decoration->decoration(), &e); - decoration->client()->processDecorationMove(m_lastLocalTouchPos.toPoint(), pos.toPoint()); - } + + 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 { @@ -1087,25 +1131,15 @@ public: // ignore, but filter out return true; } + // send mouse up - if (auto c = workspace()->getMovingClient()) { - c->endMoveResize(); - } else { - 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); - if (input()->pointer()->decoration() == decoration) { - // send motion to current pointer position - const QPointF p = input()->pointer()->pos() - decoration->client()->pos(); - QHoverEvent event(QEvent::HoverMove, p, p); - QCoreApplication::instance()->sendEvent(decoration->decoration(), &event); - } else { - // send leave - QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); - QCoreApplication::instance()->sendEvent(decoration->decoration(), &event); - } - } + 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); + + QHoverEvent leaveEvent(QEvent::HoverLeave, QPointF(), QPointF()); + QCoreApplication::sendEvent(decoration->decoration(), &leaveEvent); m_lastGlobalTouchPos = QPointF(); m_lastLocalTouchPos = QPointF(); @@ -1218,7 +1252,7 @@ public: if (event->type() != QEvent::MouseButtonPress) { return false; } - AbstractClient *c = dynamic_cast(input()->pointer()->window().data()); + AbstractClient *c = dynamic_cast(input()->pointer()->focus().data()); if (!c) { return false; } @@ -1233,7 +1267,7 @@ public: // only actions on vertical scroll return false; } - AbstractClient *c = dynamic_cast(input()->pointer()->window().data()); + AbstractClient *c = dynamic_cast(input()->pointer()->focus().data()); if (!c) { return false; } @@ -1250,8 +1284,7 @@ public: if (seat->isTouchSequence()) { return false; } - input()->touch()->update(pos); - AbstractClient *c = dynamic_cast(input()->touch()->window().data()); + AbstractClient *c = dynamic_cast(input()->touch()->focus().data()); if (!c) { return false; } @@ -1275,11 +1308,6 @@ public: seat->setTimestamp(event->timestamp()); switch (event->type()) { case QEvent::MouseMove: { - if (event->buttons() == Qt::NoButton) { - // update pointer window only if no button is pressed - input()->pointer()->update(); - input()->pointer()->updatePointerConstraints(); - } seat->setPointerPos(event->globalPos()); MouseEvent *e = static_cast(event); if (e->delta() != QSizeF()) { @@ -1292,9 +1320,6 @@ public: break; case QEvent::MouseButtonRelease: seat->pointerButtonReleased(nativeButton); - if (event->buttons() == Qt::NoButton) { - input()->pointer()->update(); - } break; default: break; @@ -1328,9 +1353,6 @@ public: } auto seat = waylandServer()->seat(); seat->setTimestamp(time); - if (!seat->isTouchSequence()) { - input()->touch()->update(pos); - } input()->touch()->insertId(id, seat->touchDown(pos)); return true; } @@ -1447,7 +1469,7 @@ public: case QEvent::MouseMove: { const auto pos = input()->globalPointer(); seat->setPointerPos(pos); - if (Toplevel *t = input()->findToplevel(pos.toPoint())) { + if (Toplevel *t = input()->pointer()->at()) { // TODO: consider decorations if (t->surface() != seat->dragSurface()) { if (AbstractClient *c = qobject_cast(t)) { @@ -1670,8 +1692,8 @@ void InputRedirection::setupInputFilters() installInputEventFilter(new TabBoxInputFilter); #endif installInputEventFilter(new GlobalShortcutFilter); - installInputEventFilter(new InternalWindowEventFilter); installInputEventFilter(new DecorationEventFilter); + installInputEventFilter(new InternalWindowEventFilter); if (waylandServer()) { installInputEventFilter(new WindowActionInputFilter); installInputEventFilter(new ForwardInputFilter); @@ -2083,109 +2105,167 @@ InputDeviceHandler::InputDeviceHandler(InputRedirection *input) InputDeviceHandler::~InputDeviceHandler() = default; -void InputDeviceHandler::updateDecoration(Toplevel *t, const QPointF &pos) +void InputDeviceHandler::init() { - 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; + connect(workspace(), &Workspace::stackingOrderChanged, this, &InputDeviceHandler::update); + connect(workspace(), &Workspace::clientMinimizedChanged, this, &InputDeviceHandler::update); + connect(VirtualDesktopManager::self(), &VirtualDesktopManager::currentChanged, this, &InputDeviceHandler::update); +} + +bool InputDeviceHandler::setAt(Toplevel *toplevel) +{ + if (m_at == toplevel) { + return false; + } + auto old = m_at; + m_at = toplevel; + emit atChanged(old, toplevel); + return true; +} + +void InputDeviceHandler::setFocus(Toplevel *toplevel) +{ + m_focus.focus = toplevel; + //TODO: call focusUpdate? +} + +void InputDeviceHandler::setDecoration(QPointer decoration) +{ + auto oldDeco = m_focus.decoration; + m_focus.decoration = decoration; + cleanupDecoration(oldDeco.data(), m_focus.decoration.data()); + emit decorationChanged(); +} + +void InputDeviceHandler::setInternalWindow(QWindow *window) +{ + m_focus.internalWindow = window; + //TODO: call internalWindowUpdate? +} + +void InputDeviceHandler::updateFocus() +{ + auto oldFocus = m_focus.focus; + m_focus.focus = m_at; + focusUpdate(oldFocus, m_focus.focus); +} + +bool InputDeviceHandler::updateDecoration() +{ + const auto oldDeco = m_focus.decoration; + m_focus.decoration = nullptr; + + auto *ac = qobject_cast(m_at); + if (ac && ac->decoratedClient()) { + const QRect clientRect = QRect(ac->clientPos(), ac->clientSize()).translated(ac->pos()); + if (!clientRect.contains(position().toPoint())) { + // input device above decoration + m_focus.decoration = ac->decoratedClient(); } + } + + if (m_focus.decoration == oldDeco) { + // no change to decoration + return false; + } + cleanupDecoration(oldDeco.data(), m_focus.decoration.data()); + emit decorationChanged(); + return true; +} + +void InputDeviceHandler::updateInternalWindow(QWindow *window) +{ + if (m_focus.internalWindow == window) { + // no change + return; + } + const auto oldInternal = m_focus.internalWindow; + m_focus.internalWindow = window; + cleanupInternalWindow(oldInternal, window); +} + +void InputDeviceHandler::update() +{ + if (!m_inited) { + return; + } + const auto pos = position().toPoint(); + auto internalWindow = findInternalWindow(pos); + + Toplevel *toplevel; + if (internalWindow) { + toplevel = waylandServer()->findClient(internalWindow); } else { - needsReset = true; - } - if (needsReset) { - m_decoration.clear(); + toplevel = input()->findToplevel(pos); } - bool leftSend = false; - auto oldWindow = qobject_cast(window().data()); - if (oldWindow && (m_decoration && m_decoration->client() != oldWindow)) { - leftSend = true; - oldWindow->leaveEvent(); + // Always set the toplevel at the position of the input device. + setAt(toplevel); + + if (focusUpdatesBlocked()) { + return; } - if (oldDeco && oldDeco != m_decoration) { - if (oldDeco->client() != t && !leftSend) { - leftSend = true; - oldDeco->client()->leaveEvent(); + if (internalWindow) { + if (m_focus.internalWindow != internalWindow) { + // changed internal window + updateDecoration(); + updateInternalWindow(internalWindow); + updateFocus(); + } else if (updateDecoration()) { + // went onto or off from decoration, update focus + updateFocus(); } - // send leave - QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); - QCoreApplication::instance()->sendEvent(oldDeco->decoration(), &event); + return; } - 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()); + updateInternalWindow(nullptr); + + if (m_focus.focus != m_at) { + // focus change + updateDecoration(); + updateFocus(); + return; + } + // check if switched to/from decoration while staying on the same Toplevel + if (updateDecoration()) { + // went onto or off from decoration, update focus + updateFocus(); } } -void InputDeviceHandler::updateInternalWindow(const QPointF &pos) +QWindow* InputDeviceHandler::findInternalWindow(const QPoint &pos) const { - const auto oldInternalWindow = m_internalWindow; - bool found = false; - // TODO: screen locked check without going through wayland server - bool needsReset = waylandServer()->isScreenLocked(); + if (waylandServer()->isScreenLocked()) { + return nullptr; + } + 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 ((*it)->geometry().contains(pos.toPoint())) { - // check input mask - const QRegion mask = w->mask().translated(w->geometry().topLeft()); - if (!mask.isEmpty() && !mask.contains(pos.toPoint())) { - continue; - } - if (w->property("outputOnly").toBool()) { - continue; - } - m_internalWindow = QPointer(w); - found = true; - break; - } - } - } while (it != internalClients.begin()); - if (!found) { - needsReset = true; - } + if (internalClients.isEmpty()) { + return nullptr; } - if (needsReset) { - m_internalWindow.clear(); - } - if (oldInternalWindow != m_internalWindow) { - // changed - if (oldInternalWindow) { - QEvent event(QEvent::Leave); - QCoreApplication::sendEvent(oldInternalWindow.data(), &event); + + auto it = internalClients.end(); + do { + --it; + QWindow *w = (*it)->internalWindow(); + if (!w || !w->isVisible()) { + continue; } - if (m_internalWindow) { - QEnterEvent event(pos - m_internalWindow->position(), - pos - m_internalWindow->position(), - pos); - QCoreApplication::sendEvent(m_internalWindow.data(), &event); + if (!(*it)->geometry().contains(pos)) { + continue; } - emit internalWindowChanged(); - } + // check input mask + const QRegion mask = w->mask().translated(w->geometry().topLeft()); + if (!mask.isEmpty() && !mask.contains(pos)) { + continue; + } + if (w->property("outputOnly").toBool()) { + continue; + } + return w; + } while (it != internalClients.begin()); + + return nullptr; } } // namespace diff --git a/input.h b/input.h index 78f0642619..ac4a78562d 100644 --- a/input.h +++ b/input.h @@ -3,6 +3,7 @@ This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin +Copyright (C) 2018 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -365,50 +366,91 @@ protected: void passToWaylandServer(QKeyEvent *event); }; -class InputDeviceHandler : public QObject +class KWIN_EXPORT InputDeviceHandler : public QObject { Q_OBJECT public: virtual ~InputDeviceHandler(); + virtual void init(); - QPointer window() const { - return m_window; + void update(); + + /** + * @brief First Toplevel currently at the position of the input device + * according to the stacking order. + * @return Toplevel* at device position. + */ + QPointer at() const { + return m_at; } + /** + * @brief Toplevel currently having pointer input focus (this might + * be different from the Toplevel at the position of the pointer). + * @return Toplevel* with pointer focus. + */ + QPointer focus() const { + return m_focus.focus; + } + /** + * @brief The Decoration currently receiving events. + * @return decoration with pointer focus. + **/ QPointer decoration() const { - return m_decoration; + return m_focus.decoration; } + /** + * @brief The internal window currently receiving events. + * @return QWindow with pointer focus. + **/ QPointer internalWindow() const { - return m_internalWindow; + return m_focus.internalWindow; } + virtual QPointF position() const = 0; + + void setFocus(Toplevel *toplevel); + void setDecoration(QPointer decoration); + void setInternalWindow(QWindow *window); + Q_SIGNALS: + void atChanged(Toplevel *old, Toplevel *now); void decorationChanged(); - void internalWindowChanged(); protected: explicit InputDeviceHandler(InputRedirection *parent); - void updateDecoration(Toplevel *t, const QPointF &pos); - void updateInternalWindow(const QPointF &pos); - void setWindow(QPointer window = QPointer()) { - m_window = window; + + virtual void cleanupInternalWindow(QWindow *old, QWindow *now) = 0; + virtual void cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now) = 0; + + virtual void focusUpdate(Toplevel *old, Toplevel *now) = 0; + + virtual bool focusUpdatesBlocked() { + return false; } - void clearDecoration() { - m_decoration.clear(); + + inline bool inited() const { + return m_inited; } - void clearInternalWindow() { - m_internalWindow.clear(); + inline void setInited(bool set) { + m_inited = set; } private: - /** - * @brief The Toplevel which currently receives events - */ - QPointer m_window; - /** - * @brief The Decoration which currently receives events. - **/ - QPointer m_decoration; - QPointer m_internalWindow; + bool setAt(Toplevel *toplevel); + void updateFocus(); + bool updateDecoration(); + void updateInternalWindow(QWindow *window); + + QWindow* findInternalWindow(const QPoint &pos) const; + + QPointer m_at; + struct { + QPointer focus; + QPointer decoration; + QPointer internalWindow; + } m_focus; + + bool m_inited = false; }; inline diff --git a/pointer_input.cpp b/pointer_input.cpp index 00fc376f5e..eb1b203f85 100644 --- a/pointer_input.cpp +++ b/pointer_input.cpp @@ -3,6 +3,7 @@ This file is part of the KDE project. Copyright (C) 2013, 2016 Martin Gräßlin +Copyright (C) 2018 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -134,13 +135,14 @@ PointerInputRedirection::~PointerInputRedirection() = default; void PointerInputRedirection::init() { - Q_ASSERT(!m_inited); + Q_ASSERT(!inited()); m_cursor = new CursorImage(this); - m_inited = true; + setInited(true); + InputDeviceHandler::init(); + connect(m_cursor, &CursorImage::changed, kwinApp()->platform(), &Platform::cursorChanged); emit m_cursor->changed(); - connect(workspace(), &Workspace::stackingOrderChanged, this, &PointerInputRedirection::update); - connect(workspace(), &Workspace::clientMinimizedChanged, this, &PointerInputRedirection::update); + connect(screens(), &Screens::changed, this, &PointerInputRedirection::updateAfterScreenChange); if (waylandServer()->hasScreenLockerIntegration()) { connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, @@ -151,51 +153,16 @@ void PointerInputRedirection::init() } ); } - connect(workspace(), &QObject::destroyed, this, [this] { m_inited = false; }); - connect(waylandServer(), &QObject::destroyed, this, [this] { m_inited = false; }); + connect(workspace(), &QObject::destroyed, this, [this] { setInited(false); }); + connect(waylandServer(), &QObject::destroyed, this, [this] { setInited(false); }); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragEnded, this, [this] { // need to force a focused pointer change waylandServer()->seat()->setFocusedPointerSurface(nullptr); - setWindow(); + setFocus(nullptr); update(); } ); - connect(this, &PointerInputRedirection::internalWindowChanged, this, - [this] { - disconnect(m_internalWindowConnection); - m_internalWindowConnection = QMetaObject::Connection(); - if (internalWindow()) { - m_internalWindowConnection = connect(internalWindow().data(), &QWindow::visibleChanged, this, - [this] (bool visible) { - if (!visible) { - update(); - } - } - ); - } - } - ); - connect(this, &PointerInputRedirection::decorationChanged, this, - [this] { - disconnect(m_decorationGeometryConnection); - m_decorationGeometryConnection = QMetaObject::Connection(); - if (decoration()) { - m_decorationGeometryConnection = connect(decoration()->client(), &AbstractClient::geometryChanged, this, - [this] { - // ensure maximize button gets the leave event when maximizing/restore a window, see BUG 385140 - const auto oldDeco = decoration(); - update(); - if (oldDeco && oldDeco == decoration() && !decoration()->client()->isMove() && !decoration()->client()->isResize() && !areButtonsPressed()) { - // position of window did not change, we need to send HoverMotion manually - const QPointF p = m_pos - decoration()->client()->pos(); - QHoverEvent event(QEvent::HoverMove, p, p); - QCoreApplication::instance()->sendEvent(decoration()->decoration(), &event); - } - }, Qt::QueuedConnection); - } - } - ); // connect the move resize of all window auto setupMoveResizeConnection = [this] (AbstractClient *c) { connect(c, &AbstractClient::clientStartUserMovedResized, this, &PointerInputRedirection::updateOnStartMoveResize); @@ -213,9 +180,9 @@ void PointerInputRedirection::init() void PointerInputRedirection::updateOnStartMoveResize() { - breakPointerConstraints(window() ? window()->surface() : nullptr); + breakPointerConstraints(focus() ? focus()->surface() : nullptr); disconnectPointerConstraintsConnection(); - setWindow(); + setFocus(nullptr); waylandServer()->seat()->setFocusedPointerSurface(nullptr); } @@ -226,22 +193,22 @@ void PointerInputRedirection::updateToReset() m_internalWindowConnection = QMetaObject::Connection(); QEvent event(QEvent::Leave); QCoreApplication::sendEvent(internalWindow().data(), &event); - clearInternalWindow(); + setInternalWindow(nullptr); } if (decoration()) { QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); QCoreApplication::instance()->sendEvent(decoration()->decoration(), &event); - clearDecoration(); + setDecoration(nullptr); } - if (window()) { - if (AbstractClient *c = qobject_cast(window().data())) { + if (focus()) { + if (AbstractClient *c = qobject_cast(focus().data())) { c->leaveEvent(); } - disconnect(m_windowGeometryConnection); - m_windowGeometryConnection = QMetaObject::Connection(); - breakPointerConstraints(window()->surface()); + disconnect(m_focusGeometryConnection); + m_focusGeometryConnection = QMetaObject::Connection(); + breakPointerConstraints(focus()->surface()); disconnectPointerConstraintsConnection(); - setWindow(); + setFocus(nullptr); } waylandServer()->seat()->setFocusedPointerSurface(nullptr); } @@ -296,7 +263,7 @@ QVector PositionUpdateBlocker::s_sched void PointerInputRedirection::processMotion(const QPointF &pos, const QSizeF &delta, const QSizeF &deltaNonAccelerated, uint32_t time, quint64 timeUsec, LibInput::Device *device) { - if (!m_inited) { + if (!inited()) { return; } if (PositionUpdateBlocker::isPositionBlocked()) { @@ -311,14 +278,13 @@ void PointerInputRedirection::processMotion(const QPointF &pos, const QSizeF &de delta, deltaNonAccelerated, timeUsec, device); event.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts()); + update(); input()->processSpies(std::bind(&InputEventSpy::pointerEvent, std::placeholders::_1, &event)); input()->processFilters(std::bind(&InputEventFilter::pointerEvent, std::placeholders::_1, &event, 0)); } void PointerInputRedirection::processButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time, LibInput::Device *device) { - updateButton(button, state); - QEvent::Type type; switch (state) { case InputRedirection::PointerButtonReleased: @@ -326,12 +292,15 @@ void PointerInputRedirection::processButton(uint32_t button, InputRedirection::P break; case InputRedirection::PointerButtonPressed: type = QEvent::MouseButtonPress; + update(); break; default: Q_UNREACHABLE(); return; } + updateButton(button, state); + MouseEvent event(type, m_pos, buttonToQtMouseButton(button), m_qtButtons, input()->keyboardModifiers(), time, QSizeF(), QSizeF(), 0, device); event.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts()); @@ -339,11 +308,15 @@ void PointerInputRedirection::processButton(uint32_t button, InputRedirection::P input()->processSpies(std::bind(&InputEventSpy::pointerEvent, std::placeholders::_1, &event)); - if (!m_inited) { + if (!inited()) { return; } input()->processFilters(std::bind(&InputEventFilter::pointerEvent, std::placeholders::_1, &event, button)); + + if (state == InputRedirection::PointerButtonReleased) { + update(); + } } void PointerInputRedirection::processAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time, LibInput::Device *device) @@ -351,6 +324,7 @@ void PointerInputRedirection::processAxis(InputRedirection::PointerAxis axis, qr if (delta == 0) { return; } + update(); emit input()->pointerAxisChanged(axis, delta); @@ -361,7 +335,7 @@ void PointerInputRedirection::processAxis(InputRedirection::PointerAxis axis, qr input()->processSpies(std::bind(&InputEventSpy::wheelEvent, std::placeholders::_1, &wheelEvent)); - if (!m_inited) { + if (!inited()) { return; } input()->processFilters(std::bind(&InputEventFilter::wheelEvent, std::placeholders::_1, &wheelEvent)); @@ -370,7 +344,7 @@ void PointerInputRedirection::processAxis(InputRedirection::PointerAxis axis, qr void PointerInputRedirection::processSwipeGestureBegin(int fingerCount, quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) - if (!m_inited) { + if (!inited()) { return; } @@ -381,9 +355,10 @@ void PointerInputRedirection::processSwipeGestureBegin(int fingerCount, quint32 void PointerInputRedirection::processSwipeGestureUpdate(const QSizeF &delta, quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) - if (!m_inited) { + if (!inited()) { return; } + update(); input()->processSpies(std::bind(&InputEventSpy::swipeGestureUpdate, std::placeholders::_1, delta, time)); input()->processFilters(std::bind(&InputEventFilter::swipeGestureUpdate, std::placeholders::_1, delta, time)); @@ -392,9 +367,10 @@ void PointerInputRedirection::processSwipeGestureUpdate(const QSizeF &delta, qui void PointerInputRedirection::processSwipeGestureEnd(quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) - if (!m_inited) { + if (!inited()) { return; } + update(); input()->processSpies(std::bind(&InputEventSpy::swipeGestureEnd, std::placeholders::_1, time)); input()->processFilters(std::bind(&InputEventFilter::swipeGestureEnd, std::placeholders::_1, time)); @@ -403,9 +379,10 @@ void PointerInputRedirection::processSwipeGestureEnd(quint32 time, KWin::LibInpu void PointerInputRedirection::processSwipeGestureCancelled(quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) - if (!m_inited) { + if (!inited()) { return; } + update(); input()->processSpies(std::bind(&InputEventSpy::swipeGestureCancelled, std::placeholders::_1, time)); input()->processFilters(std::bind(&InputEventFilter::swipeGestureCancelled, std::placeholders::_1, time)); @@ -414,9 +391,10 @@ void PointerInputRedirection::processSwipeGestureCancelled(quint32 time, KWin::L void PointerInputRedirection::processPinchGestureBegin(int fingerCount, quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) - if (!m_inited) { + if (!inited()) { return; } + update(); input()->processSpies(std::bind(&InputEventSpy::pinchGestureBegin, std::placeholders::_1, fingerCount, time)); input()->processFilters(std::bind(&InputEventFilter::pinchGestureBegin, std::placeholders::_1, fingerCount, time)); @@ -425,9 +403,10 @@ void PointerInputRedirection::processPinchGestureBegin(int fingerCount, quint32 void PointerInputRedirection::processPinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) - if (!m_inited) { + if (!inited()) { return; } + update(); input()->processSpies(std::bind(&InputEventSpy::pinchGestureUpdate, std::placeholders::_1, scale, angleDelta, delta, time)); input()->processFilters(std::bind(&InputEventFilter::pinchGestureUpdate, std::placeholders::_1, scale, angleDelta, delta, time)); @@ -436,9 +415,10 @@ void PointerInputRedirection::processPinchGestureUpdate(qreal scale, qreal angle void PointerInputRedirection::processPinchGestureEnd(quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) - if (!m_inited) { + if (!inited()) { return; } + update(); input()->processSpies(std::bind(&InputEventSpy::pinchGestureEnd, std::placeholders::_1, time)); input()->processFilters(std::bind(&InputEventFilter::pinchGestureEnd, std::placeholders::_1, time)); @@ -447,9 +427,10 @@ void PointerInputRedirection::processPinchGestureEnd(quint32 time, KWin::LibInpu void PointerInputRedirection::processPinchGestureCancelled(quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) - if (!m_inited) { + if (!inited()) { return; } + update(); input()->processSpies(std::bind(&InputEventSpy::pinchGestureCancelled, std::placeholders::_1, time)); input()->processFilters(std::bind(&InputEventFilter::pinchGestureCancelled, std::placeholders::_1, time)); @@ -465,104 +446,160 @@ bool PointerInputRedirection::areButtonsPressed() const return false; } -static bool s_cursorUpdateBlocking = false; - -void PointerInputRedirection::update() +bool PointerInputRedirection::focusUpdatesBlocked() { - if (!m_inited) { - return; + if (!inited()) { + return true; } if (waylandServer()->seat()->isDragPointer()) { // ignore during drag and drop - return; + return true; + } + if (waylandServer()->seat()->isTouchSequence()) { + // ignore during touch operations + return true; } if (input()->isSelectingWindow()) { - return; + return true; } if (areButtonsPressed()) { - return; + return true; } - Toplevel *t = input()->findToplevel(m_pos.toPoint()); - const auto oldDeco = decoration(); - updateInternalWindow(m_pos); - if (!internalWindow()) { - updateDecoration(t, m_pos); - } else { - updateDecoration(waylandServer()->findClient(internalWindow()), m_pos); - if (decoration()) { - disconnect(m_internalWindowConnection); - m_internalWindowConnection = QMetaObject::Connection(); - QEvent event(QEvent::Leave); - QCoreApplication::sendEvent(internalWindow().data(), &event); - clearInternalWindow(); - } + return false; +} + +void PointerInputRedirection::cleanupInternalWindow(QWindow *old, QWindow *now) +{ + disconnect(m_internalWindowConnection); + m_internalWindowConnection = QMetaObject::Connection(); + + if (old) { + // leave internal window + // TODO: do this instead via Wayland protocol as below + QEvent leaveEvent(QEvent::Leave); + QCoreApplication::sendEvent(old, &leaveEvent); } - if (decoration() || internalWindow()) { - t = nullptr; - } - if (decoration() != oldDeco) { - emit decorationChanged(); - } - auto oldWindow = window(); - if (!oldWindow.isNull() && t == window().data()) { - return; - } - auto seat = waylandServer()->seat(); - // disconnect old surface - if (oldWindow) { - if (AbstractClient *c = qobject_cast(oldWindow.data())) { - c->leaveEvent(); - } - disconnect(m_windowGeometryConnection); - m_windowGeometryConnection = QMetaObject::Connection(); - breakPointerConstraints(oldWindow->surface()); - disconnectPointerConstraintsConnection(); - } - if (AbstractClient *c = qobject_cast(t)) { - // only send enter if it wasn't on deco for the same client before - if (decoration().isNull() || decoration()->client() != c) { - c->enterEvent(m_pos.toPoint()); - workspace()->updateFocusMousePosition(m_pos.toPoint()); - } - } - if (t && t->surface()) { - setWindow(t); - // TODO: add convenient API to update global pos together with updating focused surface - warpXcbOnSurfaceLeft(t->surface()); - s_cursorUpdateBlocking = true; - seat->setFocusedPointerSurface(nullptr); - s_cursorUpdateBlocking = false; - seat->setPointerPos(m_pos.toPoint()); - seat->setFocusedPointerSurface(t->surface(), t->inputTransformation()); - m_windowGeometryConnection = connect(t, &Toplevel::geometryChanged, this, - [this] { - if (window().isNull()) { - return; + + if (now) { + m_internalWindowConnection = connect(internalWindow().data(), &QWindow::visibleChanged, this, + [this] (bool visible) { + if (!visible) { + update(); } - // TODO: can we check on the client instead? - if (workspace()->getMovingClient()) { - // don't update while moving - return; - } - auto seat = waylandServer()->seat(); - if (window().data()->surface() != seat->focusedPointerSurface()) { - return; - } - seat->setFocusedPointerSurfaceTransformation(window().data()->inputTransformation()); } ); - m_constraintsConnection = connect(window()->surface(), &KWayland::Server::SurfaceInterface::pointerConstraintsChanged, - this, &PointerInputRedirection::updatePointerConstraints); - m_constraintsActivatedConnection = connect(workspace(), &Workspace::clientActivated, - this, &PointerInputRedirection::updatePointerConstraints); - // check whether a pointer confinement/lock fires - updatePointerConstraints(); - } else { - setWindow(); + } +} + +void PointerInputRedirection::cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now) +{ + disconnect(m_decorationGeometryConnection); + m_decorationGeometryConnection = QMetaObject::Connection(); + workspace()->updateFocusMousePosition(position().toPoint()); + + if (old) { + // send leave event to old decoration + QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); + QCoreApplication::instance()->sendEvent(old->decoration(), &event); + } + if (!now) { + // left decoration + return; + } + + waylandServer()->seat()->setFocusedPointerSurface(nullptr); + + auto pos = m_pos - now->client()->pos(); + QHoverEvent event(QEvent::HoverEnter, pos, pos); + QCoreApplication::instance()->sendEvent(now->decoration(), &event); + now->client()->processDecorationMove(pos.toPoint(), m_pos.toPoint()); + + m_decorationGeometryConnection = connect(decoration()->client(), &AbstractClient::geometryChanged, this, + [this] { + // ensure maximize button gets the leave event when maximizing/restore a window, see BUG 385140 + const auto oldDeco = decoration(); + update(); + if (oldDeco && + oldDeco == decoration() && + !decoration()->client()->isMove() && + !decoration()->client()->isResize() && + !areButtonsPressed()) { + // position of window did not change, we need to send HoverMotion manually + const QPointF p = m_pos - decoration()->client()->pos(); + QHoverEvent event(QEvent::HoverMove, p, p); + QCoreApplication::instance()->sendEvent(decoration()->decoration(), &event); + } + }, Qt::QueuedConnection); +} + +static bool s_cursorUpdateBlocking = false; + +void PointerInputRedirection::focusUpdate(Toplevel *focusOld, Toplevel *focusNow) +{ + if (AbstractClient *ac = qobject_cast(focusOld)) { + ac->leaveEvent(); + breakPointerConstraints(ac->surface()); + disconnectPointerConstraintsConnection(); + } + disconnect(m_focusGeometryConnection); + m_focusGeometryConnection = QMetaObject::Connection(); + + if (AbstractClient *ac = qobject_cast(focusNow)) { + ac->enterEvent(m_pos.toPoint()); + workspace()->updateFocusMousePosition(m_pos.toPoint()); + } + + auto seat = waylandServer()->seat(); + if (!focusNow || !focusNow->surface() || decoration()) { + // no new surface or internal window or on decoration -> cleanup warpXcbOnSurfaceLeft(nullptr); seat->setFocusedPointerSurface(nullptr); - t = nullptr; + return; } + + if (internalWindow()) { + // enter internal window + // TODO: do this instead via Wayland protocol as below + const auto pos = at()->pos(); + QEnterEvent enterEvent(pos, pos, m_pos); + QCoreApplication::sendEvent(internalWindow().data(), &enterEvent); + } + + // TODO: add convenient API to update global pos together with updating focused surface + warpXcbOnSurfaceLeft(focusNow->surface()); + + // TODO: why? in order to reset the cursor icon? + s_cursorUpdateBlocking = true; + seat->setFocusedPointerSurface(nullptr); + s_cursorUpdateBlocking = false; + + seat->setPointerPos(m_pos.toPoint()); + seat->setFocusedPointerSurface(focusNow->surface(), focusNow->inputTransformation()); + + m_focusGeometryConnection = connect(focusNow, &Toplevel::geometryChanged, this, + [this] { + // TODO: why no assert possible? + if (!focus()) { + return; + } + // TODO: can we check on the client instead? + if (workspace()->getMovingClient()) { + // don't update while moving + return; + } + auto seat = waylandServer()->seat(); + if (focus()->surface() != seat->focusedPointerSurface()) { + return; + } + seat->setFocusedPointerSurfaceTransformation(focus()->inputTransformation()); + } + ); + + m_constraintsConnection = connect(focusNow->surface(), &KWayland::Server::SurfaceInterface::pointerConstraintsChanged, + this, &PointerInputRedirection::updatePointerConstraints); + m_constraintsActivatedConnection = connect(workspace(), &Workspace::clientActivated, + this, &PointerInputRedirection::updatePointerConstraints); + updatePointerConstraints(); } void PointerInputRedirection::breakPointerConstraints(KWayland::Server::SurfaceInterface *surface) @@ -624,10 +661,10 @@ void PointerInputRedirection::setEnableConstraints(bool set) void PointerInputRedirection::updatePointerConstraints() { - if (window().isNull()) { + if (focus().isNull()) { return; } - const auto s = window()->surface(); + const auto s = focus()->surface(); if (!s) { return; } @@ -637,7 +674,7 @@ void PointerInputRedirection::updatePointerConstraints() if (!supportsWarping()) { return; } - const bool canConstrain = m_enableConstraints && window() == workspace()->activeClient(); + const bool canConstrain = m_enableConstraints && focus() == workspace()->activeClient(); const auto cf = s->confinedPointer(); if (cf) { if (cf->isConfined()) { @@ -648,21 +685,21 @@ void PointerInputRedirection::updatePointerConstraints() } return; } - const QRegion r = getConstraintRegion(window().data(), cf.data()); + const QRegion r = getConstraintRegion(focus().data(), cf.data()); if (canConstrain && r.contains(m_pos.toPoint())) { cf->setConfined(true); m_confined = true; m_confinedPointerRegionConnection = connect(cf.data(), &KWayland::Server::ConfinedPointerInterface::regionChanged, this, [this] { - if (!window()) { + if (!focus()) { return; } - const auto s = window()->surface(); + const auto s = focus()->surface(); if (!s) { return; } const auto cf = s->confinedPointer(); - if (!getConstraintRegion(window().data(), cf.data()).contains(m_pos.toPoint())) { + if (!getConstraintRegion(focus().data(), cf.data()).contains(m_pos.toPoint())) { // pointer no longer in confined region, break the confinement cf->setConfined(false); m_confined = false; @@ -688,13 +725,13 @@ void PointerInputRedirection::updatePointerConstraints() lock->setLocked(false); m_locked = false; disconnectLockedPointerAboutToBeUnboundConnection(); - if (! (hint.x() < 0 || hint.y() < 0) && window()) { - processMotion(window()->pos() - window()->clientContentPos() + hint, waylandServer()->seat()->timestamp()); + if (! (hint.x() < 0 || hint.y() < 0) && focus()) { + processMotion(focus()->pos() - focus()->clientContentPos() + hint, waylandServer()->seat()->timestamp()); } } return; } - const QRegion r = getConstraintRegion(window().data(), lock.data()); + const QRegion r = getConstraintRegion(focus().data(), lock.data()); if (canConstrain && r.contains(m_pos.toPoint())) { lock->setLocked(true); m_locked = true; @@ -704,10 +741,10 @@ void PointerInputRedirection::updatePointerConstraints() m_lockedPointerAboutToBeUnboundConnection = connect(lock.data(), &KWayland::Server::LockedPointerInterface::aboutToBeUnbound, this, [this, lock]() { const auto hint = lock->cursorPositionHint(); - if (hint.x() < 0 || hint.y() < 0 || !window()) { + if (hint.x() < 0 || hint.y() < 0 || !focus()) { return; } - auto globalHint = window()->pos() - window()->clientContentPos() + hint; + auto globalHint = focus()->pos() - focus()->clientContentPos() + hint; // When the resource finally goes away, reposition the cursor according to the hint connect(lock.data(), &KWayland::Server::LockedPointerInterface::unbound, this, @@ -755,10 +792,10 @@ void PointerInputRedirection::warpXcbOnSurfaceLeft(KWayland::Server::SurfaceInte QPointF PointerInputRedirection::applyPointerConfinement(const QPointF &pos) const { - if (!window()) { + if (!focus()) { return pos; } - auto s = window()->surface(); + auto s = focus()->surface(); if (!s) { return pos; } @@ -770,7 +807,7 @@ QPointF PointerInputRedirection::applyPointerConfinement(const QPointF &pos) con return pos; } - const QRegion confinementRegion = getConstraintRegion(window().data(), cf.data()); + const QRegion confinementRegion = getConstraintRegion(focus().data(), cf.data()); if (confinementRegion.contains(pos.toPoint())) { return pos; } @@ -843,7 +880,7 @@ void PointerInputRedirection::warp(const QPointF &pos) bool PointerInputRedirection::supportsWarping() const { - if (!m_inited) { + if (!inited()) { return false; } if (m_supportsWarping) { @@ -857,7 +894,7 @@ bool PointerInputRedirection::supportsWarping() const void PointerInputRedirection::updateAfterScreenChange() { - if (!m_inited) { + if (!inited()) { return; } if (screenContainsPos(m_pos)) { @@ -872,7 +909,7 @@ void PointerInputRedirection::updateAfterScreenChange() QImage PointerInputRedirection::cursorImage() const { - if (!m_inited) { + if (!inited()) { return QImage(); } return m_cursor->image(); @@ -880,7 +917,7 @@ QImage PointerInputRedirection::cursorImage() const QPoint PointerInputRedirection::cursorHotSpot() const { - if (!m_inited) { + if (!inited()) { return QPoint(); } return m_cursor->hotSpot(); @@ -888,15 +925,20 @@ QPoint PointerInputRedirection::cursorHotSpot() const void PointerInputRedirection::markCursorAsRendered() { - if (!m_inited) { + if (!inited()) { return; } m_cursor->markAsRendered(); } +QPointF PointerInputRedirection::position() const +{ + return m_pos.toPoint(); +} + void PointerInputRedirection::setEffectsOverrideCursor(Qt::CursorShape shape) { - if (!m_inited) { + if (!inited()) { return; } // current pointer focus window should get a leave event @@ -906,7 +948,7 @@ void PointerInputRedirection::setEffectsOverrideCursor(Qt::CursorShape shape) void PointerInputRedirection::removeEffectsOverrideCursor() { - if (!m_inited) { + if (!inited()) { return; } // cursor position might have changed while there was an effect in place @@ -916,7 +958,7 @@ void PointerInputRedirection::removeEffectsOverrideCursor() void PointerInputRedirection::setWindowSelectionCursor(const QByteArray &shape) { - if (!m_inited) { + if (!inited()) { return; } // send leave to current pointer focus window @@ -926,7 +968,7 @@ void PointerInputRedirection::setWindowSelectionCursor(const QByteArray &shape) void PointerInputRedirection::removeWindowSelectionCursor() { - if (!m_inited) { + if (!inited()) { return; } update(); @@ -1302,7 +1344,7 @@ void CursorImage::reevaluteSource() setSource(CursorSource::Decoration); return; } - if (!m_pointer->window().isNull() && waylandServer()->seat()->focusedPointer()) { + if (!m_pointer->focus().isNull() && waylandServer()->seat()->focusedPointer()) { setSource(CursorSource::PointerSurface); return; } diff --git a/pointer_input.h b/pointer_input.h index f94269de26..8bdc0d99f3 100644 --- a/pointer_input.h +++ b/pointer_input.h @@ -3,6 +3,7 @@ This file is part of the KDE project. Copyright (C) 2013, 2016 Martin Gräßlin +Copyright (C) 2018 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -64,7 +65,6 @@ public: void init(); - void update(); void updateAfterScreenChange(); bool supportsWarping() const; void warp(const QPointF &pos); @@ -75,6 +75,7 @@ public: Qt::MouseButtons buttons() const { return m_qtButtons; } + bool areButtonsPressed() const; QImage cursorImage() const; QPoint cursorHotSpot() const; @@ -92,6 +93,8 @@ public: return m_confined || m_locked; } + bool focusUpdatesBlocked() override; + /** * @internal */ @@ -142,6 +145,13 @@ public: void processPinchGestureCancelled(quint32 time, KWin::LibInput::Device *device = nullptr); private: + void cleanupInternalWindow(QWindow *old, QWindow *now) override; + void cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now) override; + + void focusUpdate(Toplevel *focusOld, Toplevel *focusNow) override; + + QPointF position() const override; + void updateOnStartMoveResize(); void updateToReset(); void updatePosition(const QPointF &pos); @@ -152,14 +162,12 @@ private: void disconnectLockedPointerAboutToBeUnboundConnection(); void disconnectPointerConstraintsConnection(); void breakPointerConstraints(KWayland::Server::SurfaceInterface *surface); - bool areButtonsPressed() const; CursorImage *m_cursor; - bool m_inited = false; bool m_supportsWarping; QPointF m_pos; QHash m_buttons; Qt::MouseButtons m_qtButtons; - QMetaObject::Connection m_windowGeometryConnection; + QMetaObject::Connection m_focusGeometryConnection; QMetaObject::Connection m_internalWindowConnection; QMetaObject::Connection m_constraintsConnection; QMetaObject::Connection m_constraintsActivatedConnection; diff --git a/touch_input.cpp b/touch_input.cpp index 5d14abdc7b..4cf685cfbd 100644 --- a/touch_input.cpp +++ b/touch_input.cpp @@ -3,6 +3,7 @@ This file is part of the KDE project. Copyright (C) 2013, 2016 Martin Gräßlin +Copyright (C) 2018 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,6 +21,7 @@ along with this program. If not, see . #include "touch_input.h" #include "abstract_client.h" #include "input.h" +#include "pointer_input.h" #include "input_event_spy.h" #include "toplevel.h" #include "wayland_server.h" @@ -47,8 +49,9 @@ TouchInputRedirection::~TouchInputRedirection() = default; void TouchInputRedirection::init() { - Q_ASSERT(!m_inited); - m_inited = true; + Q_ASSERT(!inited()); + setInited(true); + InputDeviceHandler::init(); if (waylandServer()->hasScreenLockerIntegration()) { connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, @@ -59,73 +62,83 @@ void TouchInputRedirection::init() } ); } - connect(workspace(), &QObject::destroyed, this, [this] { m_inited = false; }); - connect(waylandServer(), &QObject::destroyed, this, [this] { m_inited = false; }); + connect(workspace(), &QObject::destroyed, this, [this] { setInited(false); }); + connect(waylandServer(), &QObject::destroyed, this, [this] { setInited(false); }); } -void TouchInputRedirection::update(const QPointF &pos) +bool TouchInputRedirection::focusUpdatesBlocked() { - if (!m_inited) { - return; + if (!inited()) { + return true; } if (m_windowUpdatedInCycle) { - return; + return true; } m_windowUpdatedInCycle = true; + if (m_touches > 0) { + // first touch defines focus + return true; + } + return false; +} + +void TouchInputRedirection::focusUpdate(Toplevel *focusOld, Toplevel *focusNow) +{ // TODO: handle pointer grab aka popups - Toplevel *t = input()->findToplevel(pos.toPoint()); - auto oldWindow = window(); - updateInternalWindow(pos); - if (!internalWindow()) { - updateDecoration(t, pos); - } else { - // TODO: send hover leave to decoration - if (decoration()) { - decoration()->client()->leaveEvent(); - } - clearDecoration(); + + if (AbstractClient *ac = qobject_cast(focusOld)) { + ac->leaveEvent(); } - if (decoration() || internalWindow()) { - t = nullptr; - } else if (!decoration()) { - m_decorationId = -1; - } else if (!internalWindow()) { - m_internalId = -1; - } - if (!oldWindow.isNull() && t == oldWindow.data()) { - return; + disconnect(m_focusGeometryConnection); + m_focusGeometryConnection = QMetaObject::Connection(); + + if (AbstractClient *ac = qobject_cast(focusNow)) { + ac->enterEvent(m_lastPosition.toPoint()); + workspace()->updateFocusMousePosition(m_lastPosition.toPoint()); } + auto seat = waylandServer()->seat(); - // disconnect old surface - if (oldWindow) { - disconnect(m_windowGeometryConnection); - m_windowGeometryConnection = QMetaObject::Connection(); - } - if (t && t->surface()) { - // FIXME: add input transformation API to KWayland::Server::SeatInterface for touch input - seat->setFocusedTouchSurface(t->surface(), -1 * t->inputTransformation().map(t->pos()) + t->pos()); - m_windowGeometryConnection = connect(t, &Toplevel::geometryChanged, this, - [this] { - if (window().isNull()) { - return; - } - auto seat = waylandServer()->seat(); - if (window().data()->surface() != seat->focusedTouchSurface()) { - return; - } - auto t = window().data(); - seat->setFocusedTouchSurfacePosition(-1 * t->inputTransformation().map(t->pos()) + t->pos()); - } - ); - } else { + if (!focusNow || !focusNow->surface() || decoration()) { + // no new surface or internal window or on decoration -> cleanup seat->setFocusedTouchSurface(nullptr); - t = nullptr; - } - if (!t) { - setWindow(); return; } - setWindow(t); + + // TODO: invalidate pointer focus? + + // FIXME: add input transformation API to KWayland::Server::SeatInterface for touch input + seat->setFocusedTouchSurface(focusNow->surface(), -1 * focusNow->inputTransformation().map(focusNow->pos()) + focusNow->pos()); + m_focusGeometryConnection = connect(focusNow, &Toplevel::geometryChanged, this, + [this] { + if (focus().isNull()) { + return; + } + auto seat = waylandServer()->seat(); + if (focus().data()->surface() != seat->focusedTouchSurface()) { + return; + } + seat->setFocusedTouchSurfacePosition(-1 * focus()->inputTransformation().map(focus()->pos()) + focus()->pos()); + } + ); +} + +void TouchInputRedirection::cleanupInternalWindow(QWindow *old, QWindow *now) +{ + Q_UNUSED(old); + Q_UNUSED(now); + + // nothing to do +} + +void TouchInputRedirection::cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now) +{ + Q_UNUSED(now); + + if (old) { + // send leave event to old decoration + QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); + QCoreApplication::instance()->sendEvent(old->decoration(), &event); + } } void TouchInputRedirection::insertId(quint32 internalId, qint32 kwaylandId) @@ -150,10 +163,15 @@ void TouchInputRedirection::removeId(quint32 internalId) void TouchInputRedirection::processDown(qint32 id, const QPointF &pos, quint32 time, LibInput::Device *device) { Q_UNUSED(device) - if (!m_inited) { + if (!inited()) { return; } + m_lastPosition = pos; m_windowUpdatedInCycle = false; + if (m_touches == 0) { + update(); + } + m_touches++; input()->processSpies(std::bind(&InputEventSpy::touchDown, std::placeholders::_1, id, pos, time)); input()->processFilters(std::bind(&InputEventFilter::touchDown, std::placeholders::_1, id, pos, time)); m_windowUpdatedInCycle = false; @@ -162,21 +180,26 @@ void TouchInputRedirection::processDown(qint32 id, const QPointF &pos, quint32 t void TouchInputRedirection::processUp(qint32 id, quint32 time, LibInput::Device *device) { Q_UNUSED(device) - if (!m_inited) { + if (!inited()) { return; } m_windowUpdatedInCycle = false; input()->processSpies(std::bind(&InputEventSpy::touchUp, std::placeholders::_1, id, time)); input()->processFilters(std::bind(&InputEventFilter::touchUp, std::placeholders::_1, id, time)); m_windowUpdatedInCycle = false; + m_touches--; + if (m_touches == 0) { + update(); + } } void TouchInputRedirection::processMotion(qint32 id, const QPointF &pos, quint32 time, LibInput::Device *device) { Q_UNUSED(device) - if (!m_inited) { + if (!inited()) { return; } + m_lastPosition = pos; m_windowUpdatedInCycle = false; input()->processSpies(std::bind(&InputEventSpy::touchMotion, std::placeholders::_1, id, pos, time)); input()->processFilters(std::bind(&InputEventFilter::touchMotion, std::placeholders::_1, id, pos, time)); @@ -185,7 +208,7 @@ void TouchInputRedirection::processMotion(qint32 id, const QPointF &pos, quint32 void TouchInputRedirection::cancel() { - if (!m_inited) { + if (!inited()) { return; } waylandServer()->seat()->cancelTouchSequence(); @@ -194,7 +217,7 @@ void TouchInputRedirection::cancel() void TouchInputRedirection::frame() { - if (!m_inited) { + if (!inited()) { return; } waylandServer()->seat()->touchFrame(); diff --git a/touch_input.h b/touch_input.h index 900c0056a1..53a03166b4 100644 --- a/touch_input.h +++ b/touch_input.h @@ -3,6 +3,7 @@ This file is part of the KDE project. Copyright (C) 2013, 2016 Martin Gräßlin +Copyright (C) 2018 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -49,7 +50,7 @@ public: explicit TouchInputRedirection(InputRedirection *parent); virtual ~TouchInputRedirection(); - void update(const QPointF &pos = QPointF()); + bool focusUpdatesBlocked() override; void init(); void processDown(qint32 id, const QPointF &pos, quint32 time, LibInput::Device *device = nullptr); @@ -75,7 +76,16 @@ public: return m_internalId; } + QPointF position() const override { + return m_lastPosition; + } + private: + void cleanupInternalWindow(QWindow *old, QWindow *now) override; + void cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now) override; + + void focusUpdate(Toplevel *focusOld, Toplevel *focusNow) override; + bool m_inited = false; qint32 m_decorationId = -1; qint32 m_internalId = -1; @@ -83,8 +93,11 @@ private: * external/kwayland **/ QHash m_idMapper; - QMetaObject::Connection m_windowGeometryConnection; + QMetaObject::Connection m_focusGeometryConnection; bool m_windowUpdatedInCycle = false; + QPointF m_lastPosition; + + int m_touches = 0; }; }