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
This commit is contained in:
Martin Flöser 2018-12-27 20:22:53 +01:00
parent 0b28abeb01
commit 792d840455
2 changed files with 87 additions and 2 deletions

View file

@ -33,6 +33,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <KWayland/Client/event_queue.h> #include <KWayland/Client/event_queue.h>
#include <KWayland/Client/keyboard.h> #include <KWayland/Client/keyboard.h>
#include <KWayland/Client/registry.h> #include <KWayland/Client/registry.h>
#include <KWayland/Client/plasmashell.h>
#include <KWayland/Client/pointer.h> #include <KWayland/Client/pointer.h>
#include <KWayland/Client/shell.h> #include <KWayland/Client/shell.h>
#include <KWayland/Client/seat.h> #include <KWayland/Client/seat.h>
@ -63,6 +64,7 @@ private Q_SLOTS:
void testDecorationPosition(); void testDecorationPosition();
void testXdgPopup_data(); void testXdgPopup_data();
void testXdgPopup(); void testXdgPopup();
void testXdgPopupWithPanel();
private: private:
AbstractClient *showWlShellWindow(const QSize &size, bool decorated = false, KWayland::Client::Surface *parent = nullptr, const QPoint &offset = QPoint()); 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() void TransientPlacementTest::init()
{ {
QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration)); QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration | Test::AdditionalWaylandInterface::PlasmaShell));
screens()->setCurrent(0); screens()->setCurrent(0);
Cursor::setPos(QPoint(640, 512)); Cursor::setPos(QPoint(640, 512));
@ -378,6 +380,88 @@ void TransientPlacementTest::testXdgPopup()
QTEST(transient->geometry(), "expectedGeometry"); QTEST(transient->geometry(), "expectedGeometry");
} }
void TransientPlacementTest::testXdgPopupWithPanel()
{
using namespace KWayland::Client;
QScopedPointer<Surface> surface{Test::createSurface()};
QVERIFY(!surface.isNull());
QScopedPointer<XdgShellSurface> dockShellSurface{Test::createXdgShellStableSurface(surface.data(), surface.data())};
QVERIFY(!dockShellSurface.isNull());
QScopedPointer<PlasmaShellSurface> 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<quint32>());
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) WAYLANDTEST_MAIN(KWin::TransientPlacementTest)

View file

@ -498,7 +498,8 @@ void Placement::placeOnScreenDisplay(AbstractClient* c, QRect& area)
void Placement::placeTransient(AbstractClient *c) 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(); const QPoint popupPos = c->transientPlacement(screen).topLeft();
c->move(popupPos); c->move(popupPos);