diff --git a/abstract_client.h b/abstract_client.h --- a/abstract_client.h +++ b/abstract_client.h @@ -578,6 +578,8 @@ */ virtual void showContextHelp(); + QRect inputGeometry() const override; + // TODO: remove boolean trap static bool belongToSameApplication(const AbstractClient* c1, const AbstractClient* c2, bool active_hack = false); diff --git a/abstract_client.cpp b/abstract_client.cpp --- a/abstract_client.cpp +++ b/abstract_client.cpp @@ -1627,4 +1627,12 @@ return candidateGeom.translated(candidatePanel->pos()); } +QRect AbstractClient::inputGeometry() const +{ + if (isDecorated()) { + return Toplevel::inputGeometry() + decoration()->resizeOnlyBorders(); + } + return Toplevel::inputGeometry(); +} + } diff --git a/autotests/integration/decoration_input_test.cpp b/autotests/integration/decoration_input_test.cpp --- a/autotests/integration/decoration_input_test.cpp +++ b/autotests/integration/decoration_input_test.cpp @@ -70,6 +70,8 @@ void testPressToMove(); void testTapToMove_data(); void testTapToMove(); + void testResizeOutsideWindow_data(); + void testResizeOutsideWindow(); private: AbstractClient *showWindow(Test::ShellSurfaceType type); @@ -498,6 +500,69 @@ QCOMPARE(c->pos(), oldPos + offset2 + offset3); } +void DecorationInputTest::testResizeOutsideWindow_data() +{ + QTest::addColumn("type"); + QTest::addColumn("edge"); + QTest::addColumn("expectedCursor"); + + QTest::newRow("wlShell - left") << Test::ShellSurfaceType::WlShell << Qt::LeftEdge << Qt::SizeHorCursor; + QTest::newRow("xdgShellV5 - left") << Test::ShellSurfaceType::XdgShellV5 << Qt::LeftEdge << Qt::SizeHorCursor; + QTest::newRow("wlShell - right") << Test::ShellSurfaceType::WlShell << Qt::RightEdge << Qt::SizeHorCursor; + QTest::newRow("xdgShellV5 - right") << Test::ShellSurfaceType::XdgShellV5 << Qt::RightEdge << Qt::SizeHorCursor; + QTest::newRow("wlShell - bottom") << Test::ShellSurfaceType::WlShell << Qt::BottomEdge << Qt::SizeVerCursor; + QTest::newRow("xdgShellV5 - bottom") << Test::ShellSurfaceType::XdgShellV5 << Qt::BottomEdge << Qt::SizeVerCursor; +} + +void DecorationInputTest::testResizeOutsideWindow() +{ + // this test verifies that one can resize the window outside the decoration with NoSideBorder + + // first adjust config + kwinApp()->config()->group("org.kde.kdecoration2").writeEntry("BorderSize", QStringLiteral("None")); + kwinApp()->config()->sync(); + workspace()->slotReconfigure(); + + // now create window + QFETCH(Test::ShellSurfaceType, type); + AbstractClient *c = showWindow(type); + QVERIFY(c); + QVERIFY(c->isDecorated()); + QVERIFY(!c->noBorder()); + c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2)); + QVERIFY(c->geometry() != c->inputGeometry()); + QVERIFY(c->inputGeometry().contains(c->geometry())); + QSignalSpy startMoveResizedSpy(c, &AbstractClient::clientStartUserMovedResized); + QVERIFY(startMoveResizedSpy.isValid()); + + // go to border + quint32 timestamp = 1; + QFETCH(Qt::Edge, edge); + switch (edge) { + case Qt::LeftEdge: + MOTION(QPoint(c->geometry().x() -1, c->geometry().center().y())); + break; + case Qt::RightEdge: + MOTION(QPoint(c->geometry().x() + c->geometry().width() +1, c->geometry().center().y())); + break; + case Qt::BottomEdge: + MOTION(QPoint(c->geometry().center().x(), c->geometry().y() + c->geometry().height() + 1)); + break; + default: + break; + } + QVERIFY(!c->geometry().contains(KWin::Cursor::pos())); + + // pressing should trigger resize + PRESS; + QVERIFY(!c->isResize()); + QVERIFY(startMoveResizedSpy.wait()); + QVERIFY(c->isResize()); + + RELEASE; + QVERIFY(!c->isResize()); +} + } WAYLANDTEST_MAIN(KWin::DecorationInputTest) diff --git a/input.cpp b/input.cpp --- a/input.cpp +++ b/input.cpp @@ -1451,7 +1451,7 @@ continue; } } - if (t->geometry().contains(pos) && acceptsInput(t, pos)) { + if (t->inputGeometry().contains(pos) && acceptsInput(t, pos)) { return t; } } while (it != stacking.begin()); diff --git a/toplevel.h b/toplevel.h --- a/toplevel.h +++ b/toplevel.h @@ -219,6 +219,13 @@ **/ virtual quint32 windowId() const; QRect geometry() const; + /** + * The geometry of the Toplevel which accepts input events. This might be larger + * than the actual geometry, e.g. to support resizing outside the window. + * + * Default implementation returns same as geometry. + **/ + virtual QRect inputGeometry() const; QSize size() const; QPoint pos() const; QRect rect() const; diff --git a/toplevel.cpp b/toplevel.cpp --- a/toplevel.cpp +++ b/toplevel.cpp @@ -538,5 +538,10 @@ return window(); } +QRect Toplevel::inputGeometry() const +{ + return geometry(); +} + } // namespace