diff --git a/autotests/wayland/pointer_input.cpp b/autotests/wayland/pointer_input.cpp index 974cb5eef3..f16d3cdd05 100644 --- a/autotests/wayland/pointer_input.cpp +++ b/autotests/wayland/pointer_input.cpp @@ -63,6 +63,7 @@ private Q_SLOTS: void testModifierScrollOpacity_data(); void testModifierScrollOpacity(); void testScrollAction(); + void testFocusFollowsMouse(); private: void render(KWayland::Client::Surface *surface, const QSize &size = QSize(100, 50)); @@ -518,6 +519,90 @@ void PointerInputTest::testScrollAction() QTest::qWait(100); } +void PointerInputTest::testFocusFollowsMouse() +{ + using namespace KWayland::Client; + // need to create a pointer, otherwise it doesn't accept focus + auto pointer = m_seat->createPointer(m_seat); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + // move cursor out of the way of first window to be created + Cursor::setPos(900, 900); + + // first modify the config for this run + KConfigGroup group = kwinApp()->config()->group("Windows"); + group.writeEntry("AutoRaise", true); + group.writeEntry("AutoRaiseInterval", 20); + group.writeEntry("DelayFocusInterval", 200); + group.writeEntry("FocusPolicy", "FocusFollowsMouse"); + group.sync(); + workspace()->slotReconfigure(); + // verify the settings + QCOMPARE(options->focusPolicy(), Options::FocusFollowsMouse); + QVERIFY(options->isAutoRaise()); + QCOMPARE(options->autoRaiseInterval(), 20); + QCOMPARE(options->delayFocusInterval(), 200); + + // create two windows + QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QVERIFY(clientAddedSpy.isValid()); + Surface *surface1 = m_compositor->createSurface(m_compositor); + QVERIFY(surface1); + ShellSurface *shellSurface1 = m_shell->createSurface(surface1, surface1); + QVERIFY(shellSurface1); + render(surface1, QSize(800, 800)); + QVERIFY(clientAddedSpy.wait()); + AbstractClient *window1 = workspace()->activeClient(); + QVERIFY(window1); + Surface *surface2 = m_compositor->createSurface(m_compositor); + QVERIFY(surface2); + ShellSurface *shellSurface2 = m_shell->createSurface(surface2, surface2); + QVERIFY(shellSurface2); + render(surface2, QSize(800, 800)); + QVERIFY(clientAddedSpy.wait()); + AbstractClient *window2 = workspace()->activeClient(); + QVERIFY(window2); + QVERIFY(window1 != window2); + QCOMPARE(workspace()->topClientOnDesktop(1, -1), window2); + // geometry of the two windows should be overlapping + QVERIFY(window1->geometry().intersects(window2->geometry())); + + // signal spies for active window changed and stacking order changed + QSignalSpy activeWindowChangedSpy(workspace(), &Workspace::clientActivated); + QVERIFY(activeWindowChangedSpy.isValid()); + QSignalSpy stackingOrderChangedSpy(workspace(), &Workspace::stackingOrderChanged); + QVERIFY(stackingOrderChangedSpy.isValid()); + + QVERIFY(!window1->isActive()); + QVERIFY(window2->isActive()); + + // move on top of first window + QVERIFY(window1->geometry().contains(10, 10)); + QVERIFY(!window2->geometry().contains(10, 10)); + Cursor::setPos(10, 10); + QVERIFY(stackingOrderChangedSpy.wait()); + QCOMPARE(stackingOrderChangedSpy.count(), 1); + QCOMPARE(workspace()->topClientOnDesktop(1, -1), window1); + QTRY_VERIFY(window1->isActive()); + + // move on second window, but move away before active window change delay hits + Cursor::setPos(810, 810); + QVERIFY(stackingOrderChangedSpy.wait()); + QCOMPARE(stackingOrderChangedSpy.count(), 2); + QCOMPARE(workspace()->topClientOnDesktop(1, -1), window2); + Cursor::setPos(10, 10); + QVERIFY(!activeWindowChangedSpy.wait(250)); + QVERIFY(window1->isActive()); + QCOMPARE(workspace()->topClientOnDesktop(1, -1), window1); + // as we moved back on window 1 that should been raised in the mean time + QCOMPARE(stackingOrderChangedSpy.count(), 3); + + // quickly move on window 2 and back on window 1 should not raise window 2 + Cursor::setPos(810, 810); + Cursor::setPos(10, 10); + QVERIFY(!stackingOrderChangedSpy.wait(250)); +} + } WAYLANDTEST_MAIN(KWin::PointerInputTest) diff --git a/pointer_input.cpp b/pointer_input.cpp index c757424215..0bd322e113 100644 --- a/pointer_input.cpp +++ b/pointer_input.cpp @@ -205,11 +205,15 @@ void PointerInputRedirection::update() } // TODO: handle pointer grab aka popups Toplevel *t = m_input->findToplevel(m_pos.toPoint()); + const auto oldDeco = m_decoration; updateInternalWindow(); if (!m_internalWindow) { updateDecoration(t); } else { // TODO: send hover leave to decoration + if (m_decoration) { + m_decoration->client()->leaveEvent(); + } m_decoration.clear(); } if (m_decoration || m_internalWindow) { @@ -222,6 +226,9 @@ void PointerInputRedirection::update() 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(); if (auto p = seat->focusedPointer()) { @@ -230,6 +237,13 @@ void PointerInputRedirection::update() } } } + if (AbstractClient *c = qobject_cast(t)) { + // only send enter if it wasn't on deco for the same client before + if (m_decoration.isNull() || m_decoration->client() != c) { + c->enterEvent(m_pos.toPoint()); + workspace()->updateFocusMousePosition(m_pos.toPoint()); + } + } if (t && t->surface()) { seat->setFocusedPointerSurface(t->surface(), t->inputTransformation()); m_windowGeometryConnection = connect(t, &Toplevel::geometryChanged, this, @@ -343,7 +357,18 @@ void PointerInputRedirection::updateDecoration(Toplevel *t) 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); @@ -352,6 +377,10 @@ void PointerInputRedirection::updateDecoration(Toplevel *t) } } 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);