Fix the strut handling for wayland clients

Summary:
The implementation was broken as it transformed the QRects into QRegions,
subtracted the geometries and took the bounding rect again. In several
setups this could result in the strut getting ignored.

This change improves the calculation of the struts by creating a QMargin
which describes the area which needs to be subtracted from a screen rect.
The QMargin is only adjusted for the edge the window borders. We can
assume that a window with a strut needs to border a screen on Wayland.

With this change we are also able to support panels between screens.
On Wayland a panel placed on the right of a left screen affects the
maximization area of the left screen, but does not affect the overall
workarea.

CCBUG: 167852

Reviewers: #plasma_on_wayland, #kwin

Subscribers: plasma-devel, kwin

Tags: #plasma_on_wayland, #kwin

Differential Revision: https://phabricator.kde.org/D1803
This commit is contained in:
Martin Gräßlin 2016-06-09 10:01:05 +02:00
parent 935f8875fe
commit 58db477796
2 changed files with 152 additions and 5 deletions

View file

@ -32,6 +32,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <KWayland/Client/compositor.h>
#include <KWayland/Client/event_queue.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/plasmashell.h>
#include <KWayland/Client/pointer.h>
#include <KWayland/Client/server_decoration.h>
#include <KWayland/Client/shell.h>
@ -56,6 +57,8 @@ private Q_SLOTS:
void initTestCase();
void init();
void cleanup();
void testWaylandStruts_data();
void testWaylandStruts();
void testX11Struts_data();
void testX11Struts();
void test363804();
@ -68,6 +71,7 @@ private:
KWayland::Client::ShmPool *m_shm = nullptr;
KWayland::Client::Shell *m_shell = nullptr;
KWayland::Client::EventQueue *m_queue = nullptr;
KWayland::Client::PlasmaShell *m_plasmaShell = nullptr;
QThread *m_thread = nullptr;
};
@ -145,6 +149,10 @@ void StrutsTest::init()
QVERIFY(m_seat->isValid());
m_deco = registry.createServerSideDecorationManager(decorationSpy.first().first().value<quint32>(), decorationSpy.first().last().value<quint32>());
QVERIFY(m_deco->isValid());
m_plasmaShell = registry.createPlasmaShell(registry.interface(Registry::Interface::PlasmaShell).name,
registry.interface(Registry::Interface::PlasmaShell).version,
this);
QVERIFY(m_plasmaShell);
QSignalSpy hasPointerSpy(m_seat, &Seat::hasPointerChanged);
QVERIFY(hasPointerSpy.isValid());
QVERIFY(hasPointerSpy.wait());
@ -165,6 +173,8 @@ void StrutsTest::cleanup()
m_shm = nullptr;
delete m_shell;
m_shell = nullptr;
delete m_plasmaShell;
m_plasmaShell = nullptr;
delete m_queue;
m_queue = nullptr;
if (m_thread) {
@ -175,6 +185,118 @@ void StrutsTest::cleanup()
m_thread = nullptr;
m_connection = nullptr;
}
while (!waylandServer()->clients().isEmpty()) {
QCoreApplication::instance()->processEvents(QEventLoop::WaitForMoreEvents);
}
QVERIFY(waylandServer()->clients().isEmpty());
}
void StrutsTest::testWaylandStruts_data()
{
QTest::addColumn<QVector<QRect>>("windowGeometries");
QTest::addColumn<QRect>("screen0Maximized");
QTest::addColumn<QRect>("screen1Maximized");
QTest::addColumn<QRect>("workArea");
QTest::newRow("bottom/0") << QVector<QRect>{QRect(0, 992, 1280, 32)} << QRect(0, 0, 1280, 992) << QRect(1280, 0, 1280, 1024) << QRect(0, 0, 2560, 992);
QTest::newRow("bottom/1") << QVector<QRect>{QRect(1280, 992, 1280, 32)} << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 992) << QRect(0, 0, 2560, 992);
QTest::newRow("top/0") << QVector<QRect>{QRect(0, 0, 1280, 32)} << QRect(0, 32, 1280, 992) << QRect(1280, 0, 1280, 1024) << QRect(0, 32, 2560, 992);
QTest::newRow("top/1") << QVector<QRect>{QRect(1280, 0, 1280, 32)} << QRect(0, 0, 1280, 1024) << QRect(1280, 32, 1280, 992) << QRect(0, 32, 2560, 992);
QTest::newRow("left/0") << QVector<QRect>{QRect(0, 0, 32, 1024)} << QRect(32, 0, 1248, 1024) << QRect(1280, 0, 1280, 1024) << QRect(32, 0, 2528, 1024);
QTest::newRow("left/1") << QVector<QRect>{QRect(1280, 0, 32, 1024)} << QRect(0, 0, 1280, 1024) << QRect(1312, 0, 1248, 1024) << QRect(0, 0, 2560, 1024);
QTest::newRow("right/0") << QVector<QRect>{QRect(1248, 0, 32, 1024)} << QRect(0, 0, 1248, 1024) << QRect(1280, 0, 1280, 1024) << QRect(0, 0, 2560, 1024);
QTest::newRow("right/1") << QVector<QRect>{QRect(2528, 0, 32, 1024)} << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1248, 1024) << QRect(0, 0, 2528, 1024);
// same with partial panels not covering the whole area
QTest::newRow("part bottom/0") << QVector<QRect>{QRect(100, 992, 1080, 32)} << QRect(0, 0, 1280, 992) << QRect(1280, 0, 1280, 1024) << QRect(0, 0, 2560, 992);
QTest::newRow("part bottom/1") << QVector<QRect>{QRect(1380, 992, 1080, 32)} << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 992) << QRect(0, 0, 2560, 992);
QTest::newRow("part top/0") << QVector<QRect>{QRect(100, 0, 1080, 32)} << QRect(0, 32, 1280, 992) << QRect(1280, 0, 1280, 1024) << QRect(0, 32, 2560, 992);
QTest::newRow("part top/1") << QVector<QRect>{QRect(1380, 0, 1080, 32)} << QRect(0, 0, 1280, 1024) << QRect(1280, 32, 1280, 992) << QRect(0, 32, 2560, 992);
QTest::newRow("part left/0") << QVector<QRect>{QRect(0, 100, 32, 824)} << QRect(32, 0, 1248, 1024) << QRect(1280, 0, 1280, 1024) << QRect(32, 0, 2528, 1024);
QTest::newRow("part left/1") << QVector<QRect>{QRect(1280, 100, 32, 824)} << QRect(0, 0, 1280, 1024) << QRect(1312, 0, 1248, 1024) << QRect(0, 0, 2560, 1024);
QTest::newRow("part right/0") << QVector<QRect>{QRect(1248, 100, 32, 824)} << QRect(0, 0, 1248, 1024) << QRect(1280, 0, 1280, 1024) << QRect(0, 0, 2560, 1024);
QTest::newRow("part right/1") << QVector<QRect>{QRect(2528, 100, 32, 824)} << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1248, 1024) << QRect(0, 0, 2528, 1024);
// multiple panels
QTest::newRow("two bottom panels") << QVector<QRect>{QRect(100, 992, 1080, 32), QRect(1380, 984, 1080, 40)} << QRect(0, 0, 1280, 992) << QRect(1280, 0, 1280, 984) << QRect(0, 0, 2560, 984);
QTest::newRow("two left panels") << QVector<QRect>{QRect(0, 10, 32, 390), QRect(0, 450, 40, 100)} << QRect(40, 0, 1240, 1024) << QRect(1280, 0, 1280, 1024) << QRect(40, 0, 2520, 1024);
}
void StrutsTest::testWaylandStruts()
{
// this test verifies that struts on Wayland panels are handled correctly
using namespace KWayland::Client;
// no, struts yet
QVERIFY(waylandServer()->clients().isEmpty());
// first screen
QCOMPARE(workspace()->clientArea(PlacementArea, 0, 1), QRect(0, 0, 1280, 1024));
QCOMPARE(workspace()->clientArea(MovementArea, 0, 1), QRect(0, 0, 1280, 1024));
QCOMPARE(workspace()->clientArea(MaximizeArea, 0, 1), QRect(0, 0, 1280, 1024));
QCOMPARE(workspace()->clientArea(MaximizeFullArea, 0, 1), QRect(0, 0, 1280, 1024));
QCOMPARE(workspace()->clientArea(FullScreenArea, 0, 1), QRect(0, 0, 1280, 1024));
QCOMPARE(workspace()->clientArea(ScreenArea, 0, 1), QRect(0, 0, 1280, 1024));
// second screen
QCOMPARE(workspace()->clientArea(PlacementArea, 1, 1), QRect(1280, 0, 1280, 1024));
QCOMPARE(workspace()->clientArea(MovementArea, 1, 1), QRect(1280, 0, 1280, 1024));
QCOMPARE(workspace()->clientArea(MaximizeArea, 1, 1), QRect(1280, 0, 1280, 1024));
QCOMPARE(workspace()->clientArea(MaximizeFullArea, 1, 1), QRect(1280, 0, 1280, 1024));
QCOMPARE(workspace()->clientArea(FullScreenArea, 1, 1), QRect(1280, 0, 1280, 1024));
QCOMPARE(workspace()->clientArea(ScreenArea, 1, 1), QRect(1280, 0, 1280, 1024));
// combined
QCOMPARE(workspace()->clientArea(WorkArea, 0, 1), QRect(0, 0, 2560, 1024));
QCOMPARE(workspace()->clientArea(FullArea, 0, 1), QRect(0, 0, 2560, 1024));
QFETCH(QVector<QRect>, windowGeometries);
// create the panels
QSignalSpy windowCreatedSpy(waylandServer(), &WaylandServer::shellClientAdded);
QVERIFY(windowCreatedSpy.isValid());
for (auto it = windowGeometries.constBegin(), end = windowGeometries.constEnd(); it != end; it++) {
const QRect windowGeometry = *it;
Surface *surface = m_compositor->createSurface(m_compositor);
ShellSurface *shellSurface = m_shell->createSurface(surface, surface);
Q_UNUSED(shellSurface)
PlasmaShellSurface *plasmaSurface = m_plasmaShell->createSurface(surface, surface);
plasmaSurface->setPosition(windowGeometry.topLeft());
plasmaSurface->setRole(PlasmaShellSurface::Role::Panel);
// map the window
QImage img(windowGeometry.size(), QImage::Format_RGB32);
img.fill(Qt::red);
surface->attachBuffer(m_shm->createBuffer(img));
surface->damage(QRect(QPoint(0, 0), windowGeometry.size()));
surface->commit(Surface::CommitFlag::None);
QVERIFY(windowCreatedSpy.wait());
QCOMPARE(windowCreatedSpy.count(), 1);
auto c = windowCreatedSpy.first().first().value<ShellClient*>();
QVERIFY(c);
QVERIFY(!c->isActive());
QCOMPARE(c->geometry(), windowGeometry);
QVERIFY(c->isDock());
QVERIFY(c->hasStrut());
windowCreatedSpy.clear();
}
// some props are independent of struts - those first
// screen 0
QCOMPARE(workspace()->clientArea(MovementArea, 0, 1), QRect(0, 0, 1280, 1024));
QCOMPARE(workspace()->clientArea(MaximizeFullArea, 0, 1), QRect(0, 0, 1280, 1024));
QCOMPARE(workspace()->clientArea(FullScreenArea, 0, 1), QRect(0, 0, 1280, 1024));
QCOMPARE(workspace()->clientArea(ScreenArea, 0, 1), QRect(0, 0, 1280, 1024));
// screen 1
QCOMPARE(workspace()->clientArea(MovementArea, 1, 1), QRect(1280, 0, 1280, 1024));
QCOMPARE(workspace()->clientArea(MaximizeFullArea, 1, 1), QRect(1280, 0, 1280, 1024));
QCOMPARE(workspace()->clientArea(FullScreenArea, 1, 1), QRect(1280, 0, 1280, 1024));
QCOMPARE(workspace()->clientArea(ScreenArea, 1, 1), QRect(1280, 0, 1280, 1024));
// combined
QCOMPARE(workspace()->clientArea(FullArea, 0, 1), QRect(0, 0, 2560, 1024));
// now verify the actual updated client areas
QTEST(workspace()->clientArea(PlacementArea, 0, 1), "screen0Maximized");
QTEST(workspace()->clientArea(MaximizeArea, 0, 1), "screen0Maximized");
QTEST(workspace()->clientArea(PlacementArea, 1, 1), "screen1Maximized");
QTEST(workspace()->clientArea(MaximizeArea, 1, 1), "screen1Maximized");
QTEST(workspace()->clientArea(WorkArea, 0, 1), "workArea");
}
void StrutsTest::testX11Struts_data()

View file

@ -188,19 +188,44 @@ void Workspace::updateClientArea(bool force)
if (!c->hasStrut()) {
return;
}
auto margins = [c] (const QRect &geometry) {
QMargins margins;
if (!geometry.intersects(c->geometry())) {
return margins;
}
// figure out which areas of the overall screen setup it borders
const bool left = c->geometry().left() == geometry.left();
const bool right = c->geometry().right() == geometry.right();
const bool top = c->geometry().top() == geometry.top();
const bool bottom = c->geometry().bottom() == geometry.bottom();
const bool horizontal = c->geometry().width() >= c->geometry().height();
if (left && ((!top && !bottom) || !horizontal)) {
margins.setLeft(c->geometry().width());
}
if (right && ((!top && !bottom) || !horizontal)) {
margins.setRight(c->geometry().width());
}
if (top && ((!left && !right) || horizontal)) {
margins.setTop(c->geometry().height());
}
if (bottom && ((!left && !right) || horizontal)) {
margins.setBottom(c->geometry().height());
}
return margins;
};
// TODO: implement restrictedMoveArea adjustments
QRegion r = QRegion(desktopArea).subtracted(c->geometry());
QRect r = desktopArea - margins(KWin::screens()->geometry());
if (c->isOnAllDesktops()) {
for (int i = 1; i <= numberOfDesktops; ++i) {
new_wareas[ i ] = new_wareas[ i ].intersected(r.boundingRect());
new_wareas[ i ] = new_wareas[ i ].intersected(r);
for (int iS = 0; iS < nscreens; ++iS) {
new_sareas[ i ][ iS ] = new_sareas[ i ][ iS ].intersected(QRegion(screens[iS]).subtracted(c->geometry()).boundingRect());
new_sareas[ i ][ iS ] = new_sareas[ i ][ iS ].intersected(screens[iS] - margins(screens[iS]));
}
}
} else {
new_wareas[c->desktop()] = new_wareas[c->desktop()].intersected(r.boundingRect());
new_wareas[c->desktop()] = new_wareas[c->desktop()].intersected(r);
for (int iS = 0; iS < nscreens; iS++) {
new_sareas[c->desktop()][ iS ] = new_sareas[c->desktop()][ iS ].intersected(QRegion(screens[iS]).subtracted(c->geometry()).boundingRect());
new_sareas[c->desktop()][ iS ] = new_sareas[c->desktop()][ iS ].intersected(screens[iS] - margins(screens[iS]));
}
}
};