From 792d8404551d18827b08cf7af709dbb3cfbd4d53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Fl=C3=B6ser?= Date: Thu, 27 Dec 2018 20:22:53 +0100 Subject: [PATCH] Honor struts when placing Wayland transients Summary: So far transients were placed anywhere on the screen. This behavior was inspired from X11 where context menus were able to overlap any other window and use the complete screen area. On X11 context menus and similar windows are override redirect and thus above all windows managed by KWin. On Wayland, though, context menus and similar and windows just like any other window and thus follow stacking constraints like the parent window. A context menu is stacked just above it's parent and is (normally) below any panels. This resulted in problems that context menu are stacked behind the panel with unreachable options. This change changes the placement for transients to use the PlacementArea instead of a screen geometry. Thus the transient does not render behind the panel. Only in case of a fullscreen the struts are ignored. BUG: 389222 FIXED-IN: 5.15 Test Plan: New test case Reviewers: #kwin Subscribers: kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D17826 --- autotests/integration/transient_placement.cpp | 86 ++++++++++++++++++- placement.cpp | 3 +- 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/autotests/integration/transient_placement.cpp b/autotests/integration/transient_placement.cpp index c9621ede98..ad033827e3 100644 --- a/autotests/integration/transient_placement.cpp +++ b/autotests/integration/transient_placement.cpp @@ -33,6 +33,7 @@ along with this program. If not, see . #include #include #include +#include #include #include #include @@ -63,6 +64,7 @@ private Q_SLOTS: void testDecorationPosition(); void testXdgPopup_data(); void testXdgPopup(); + void testXdgPopupWithPanel(); private: AbstractClient *showWlShellWindow(const QSize &size, bool decorated = false, KWayland::Client::Surface *parent = nullptr, const QPoint &offset = QPoint()); @@ -91,7 +93,7 @@ void TransientPlacementTest::initTestCase() void TransientPlacementTest::init() { - QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration | Test::AdditionalWaylandInterface::PlasmaShell)); screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); @@ -378,6 +380,88 @@ void TransientPlacementTest::testXdgPopup() QTEST(transient->geometry(), "expectedGeometry"); } +void TransientPlacementTest::testXdgPopupWithPanel() +{ + using namespace KWayland::Client; + + QScopedPointer surface{Test::createSurface()}; + QVERIFY(!surface.isNull()); + QScopedPointer dockShellSurface{Test::createXdgShellStableSurface(surface.data(), surface.data())}; + QVERIFY(!dockShellSurface.isNull()); + QScopedPointer plasmaSurface(Test::waylandPlasmaShell()->createSurface(surface.data())); + QVERIFY(!plasmaSurface.isNull()); + plasmaSurface->setRole(PlasmaShellSurface::Role::Panel); + plasmaSurface->setPosition(QPoint(0, screens()->geometry(0).height() - 50)); + plasmaSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AlwaysVisible); + + // now render and map the window + QVERIFY(workspace()->clientArea(PlacementArea, 0, 1) == workspace()->clientArea(FullScreenArea, 0, 1)); + auto dock = Test::renderAndWaitForShown(surface.data(), QSize(1280, 50), Qt::blue); + QVERIFY(dock); + QCOMPARE(dock->windowType(), NET::Dock); + QVERIFY(dock->isDock()); + QCOMPARE(dock->geometry(), QRect(0, screens()->geometry(0).height() - 50, 1280, 50)); + QCOMPARE(dock->hasStrut(), true); + QVERIFY(workspace()->clientArea(PlacementArea, 0, 1) != workspace()->clientArea(FullScreenArea, 0, 1)); + + //create parent + Surface *parentSurface = Test::createSurface(Test::waylandCompositor()); + QVERIFY(parentSurface); + auto parentShellSurface = Test::createXdgShellStableSurface(parentSurface, Test::waylandCompositor()); + QVERIFY(parentShellSurface); + auto parent = Test::renderAndWaitForShown(parentSurface, {800, 600}, Qt::blue); + QVERIFY(parent); + + QVERIFY(!parent->isDecorated()); + parent->move({0, screens()->geometry(0).height() - 600}); + parent->keepInArea(workspace()->clientArea(PlacementArea, parent)); + QCOMPARE(parent->geometry(), QRect(0, screens()->geometry(0).height() - 600 - 50, 800, 600)); + + Surface *transientSurface = Test::createSurface(Test::waylandCompositor()); + QVERIFY(transientSurface); + XdgPositioner positioner(QSize(200,200), QRect(50,500, 200,200)); + + auto transientShellSurface = Test::createXdgShellStablePopup(transientSurface, parentShellSurface, positioner, Test::waylandCompositor()); + auto transient = Test::renderAndWaitForShown(transientSurface, positioner.initialSize(), Qt::red); + QVERIFY(transient); + + QVERIFY(!transient->isDecorated()); + QVERIFY(transient->hasTransientPlacementHint()); + + QCOMPARE(transient->geometry(), QRect(50, screens()->geometry(0).height() - 200 - 50, 200, 200)); + + transientShellSurface->deleteLater(); + transientSurface->deleteLater(); + QVERIFY(Test::waitForWindowDestroyed(transient)); + + // now parent to fullscreen - on fullscreen the panel is ignored + QSignalSpy fullscreenSpy{parentShellSurface, &XdgShellSurface::configureRequested}; + QVERIFY(fullscreenSpy.isValid()); + parent->setFullScreen(true); + QVERIFY(fullscreenSpy.wait()); + parentShellSurface->ackConfigure(fullscreenSpy.first().at(2).value()); + QSignalSpy geometryShapeChangedSpy{parent, &ShellClient::geometryShapeChanged}; + QVERIFY(geometryShapeChangedSpy.isValid()); + Test::render(parentSurface, fullscreenSpy.first().at(0).toSize(), Qt::red); + QVERIFY(geometryShapeChangedSpy.wait()); + QCOMPARE(parent->geometry(), screens()->geometry(0)); + QVERIFY(parent->isFullScreen()); + + // another transient, with same hints as before from bottom of window + transientSurface = Test::createSurface(Test::waylandCompositor()); + QVERIFY(transientSurface); + + XdgPositioner positioner2(QSize(200,200), QRect(50,screens()->geometry(0).height()-100, 200,200)); + transientShellSurface = Test::createXdgShellStablePopup(transientSurface, parentShellSurface, positioner2, Test::waylandCompositor()); + transient = Test::renderAndWaitForShown(transientSurface, positioner2.initialSize(), Qt::red); + QVERIFY(transient); + + QVERIFY(!transient->isDecorated()); + QVERIFY(transient->hasTransientPlacementHint()); + + QCOMPARE(transient->geometry(), QRect(50, screens()->geometry(0).height() - 200, 200, 200)); +} + } WAYLANDTEST_MAIN(KWin::TransientPlacementTest) diff --git a/placement.cpp b/placement.cpp index d11c2eb837..c190b9f2c2 100644 --- a/placement.cpp +++ b/placement.cpp @@ -498,7 +498,8 @@ void Placement::placeOnScreenDisplay(AbstractClient* c, QRect& area) void Placement::placeTransient(AbstractClient *c) { - const QRect screen = screens()->geometry(c->transientFor()->screen()); + const auto parent = c->transientFor(); + const QRect screen = Workspace::self()->clientArea(parent->isFullScreen() ? FullScreenArea : PlacementArea, parent); const QPoint popupPos = c->transientPlacement(screen).topLeft(); c->move(popupPos);