wayland: Fix windows shrinking when output layout changes

When the output layout changes, the Workspace is going to update the
struts and then go through every window and see whether it should be
moved or resized.

On the other hand, the layer shell windows react to output changes on
a timer. Furthermore, it's not synchronized with the workspace rearranging
the managed windows. It means that when Workspace::desktopResized() runs,
the panel struts can be slightly outdated, i.e.

- An output layout change occurs
- Workspace::desktopResized() is called but the struts can be wrong
- some time later, LayerShellV1Integration::rearrange is called, it
  fixes layer shell window geometries and struts
- after the layer shell integration has finished rearranging the
  layer shell windows, it calls Workspace::desktopResized(), but the
  damage had already been caused

With the proposed change, the Workspace and the LayerShellV1Integration
will rearrange the windows in sync.

CCBUG: 482361
This commit is contained in:
Vlad Zahorodnii 2024-03-06 11:29:31 +02:00
parent 9ca738ffec
commit a489bfa12c
6 changed files with 65 additions and 23 deletions

View file

@ -7,6 +7,7 @@
#include "kwin_wayland_test.h"
#include "core/output.h"
#include "core/outputconfiguration.h"
#include "main.h"
#include "pointer_input.h"
#include "screenedge.h"
@ -44,6 +45,7 @@ private Q_SLOTS:
void testChangeLayer();
void testPlacementArea_data();
void testPlacementArea();
void testPlacementAreaAfterOutputLayoutChange();
void testFill_data();
void testFill();
void testStack();
@ -431,6 +433,54 @@ void LayerShellV1WindowTest::testPlacementArea()
QVERIFY(Test::waitForWindowClosed(window));
}
void LayerShellV1WindowTest::testPlacementAreaAfterOutputLayoutChange()
{
// This test verifies that layer shell windows correctly react to output layout changes.
// The output where the layer surface should be placed.
Output *output = workspace()->activeOutput();
// Create a layer surface with an exclusive zone.
std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
std::unique_ptr<Test::LayerSurfaceV1> shellSurface(Test::createLayerSurfaceV1(surface.get(), QStringLiteral("dock"), Test::waylandOutput(output->name())));
shellSurface->set_layer(Test::LayerShellV1::layer_top);
shellSurface->set_anchor(Test::LayerSurfaceV1::anchor_bottom);
shellSurface->set_size(100, 50);
shellSurface->set_exclusive_edge(Test::LayerSurfaceV1::anchor_bottom);
shellSurface->set_exclusive_zone(50);
surface->commit(KWayland::Client::Surface::CommitFlag::None);
// Wait for the compositor to position the layer surface.
QSignalSpy configureRequestedSpy(shellSurface.get(), &Test::LayerSurfaceV1::configureRequested);
QVERIFY(configureRequestedSpy.wait());
shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt());
Window *window = Test::renderAndWaitForShown(surface.get(), configureRequestedSpy.last().at(1).toSize(), Qt::red);
QVERIFY(window);
QCOMPARE(workspace()->clientArea(PlacementArea, window), output->geometry().adjusted(0, 0, 0, -50));
// Move the output 100px down.
OutputConfiguration config1;
{
auto changeSet = config1.changeSet(output);
changeSet->pos = output->geometry().topLeft() + QPoint(0, 100);
}
workspace()->applyOutputConfiguration(config1);
QCOMPARE(workspace()->clientArea(PlacementArea, window), output->geometry().adjusted(0, 0, 0, -50));
// Move the output back to its original position.
OutputConfiguration config2;
{
auto changeSet = config2.changeSet(output);
changeSet->pos = output->geometry().topLeft() - QPoint(0, 100);
}
workspace()->applyOutputConfiguration(config2);
QCOMPARE(workspace()->clientArea(PlacementArea, window), output->geometry().adjusted(0, 0, 0, -50));
// Destroy the window.
shellSurface.reset();
QVERIFY(Test::waitForWindowClosed(window));
}
void LayerShellV1WindowTest::testFill_data()
{
QTest::addColumn<int>("anchor");

View file

@ -13,8 +13,6 @@
#include "wayland_server.h"
#include "workspace.h"
#include <QTimer>
namespace KWin
{
@ -28,9 +26,7 @@ LayerShellV1Integration::LayerShellV1Integration(QObject *parent)
connect(shell, &LayerShellV1Interface::surfaceCreated,
this, &LayerShellV1Integration::createWindow);
m_rearrangeTimer = new QTimer(this);
m_rearrangeTimer->setSingleShot(true);
connect(m_rearrangeTimer, &QTimer::timeout, this, &LayerShellV1Integration::rearrange);
connect(workspace(), &Workspace::aboutToUpdateClientArea, this, &LayerShellV1Integration::rearrange);
}
void LayerShellV1Integration::createWindow(LayerSurfaceV1Interface *shellSurface)
@ -198,21 +194,10 @@ static void rearrangeOutput(Output *output)
void LayerShellV1Integration::rearrange()
{
m_rearrangeTimer->stop();
const QList<Output *> outputs = workspace()->outputs();
for (Output *output : outputs) {
rearrangeOutput(output);
}
if (workspace()) {
workspace()->updateClientArea();
}
}
void LayerShellV1Integration::scheduleRearrange()
{
m_rearrangeTimer->start();
}
} // namespace KWin

View file

@ -21,14 +21,10 @@ public:
explicit LayerShellV1Integration(QObject *parent = nullptr);
void rearrange();
void scheduleRearrange();
void createWindow(LayerSurfaceV1Interface *shellSurface);
void recreateWindow(LayerSurfaceV1Interface *shellSurface);
void destroyWindow(LayerSurfaceV1Interface *shellSurface);
private:
QTimer *m_rearrangeTimer;
};
} // namespace KWin

View file

@ -52,8 +52,6 @@ LayerShellV1Window::LayerShellV1Window(LayerSurfaceV1Interface *shellSurface,
connect(shellSurface->surface(), &SurfaceInterface::aboutToBeDestroyed,
this, &LayerShellV1Window::destroyWindow);
connect(output, &Output::geometryChanged,
this, &LayerShellV1Window::scheduleRearrange);
connect(output, &Output::enabledChanged,
this, &LayerShellV1Window::handleOutputEnabledChanged);
@ -90,7 +88,7 @@ Output *LayerShellV1Window::desiredOutput() const
void LayerShellV1Window::scheduleRearrange()
{
m_integration->scheduleRearrange();
workspace()->scheduleUpdateClientArea();
}
WindowType LayerShellV1Window::windowType() const

View file

@ -205,9 +205,11 @@ void Workspace::init()
vds->setCurrent(m_initialDesktop);
reconfigureTimer.setSingleShot(true);
m_updateClientAreaTimer.setSingleShot(true);
updateToolWindowsTimer.setSingleShot(true);
connect(&reconfigureTimer, &QTimer::timeout, this, &Workspace::slotReconfigure);
connect(&m_updateClientAreaTimer, &QTimer::timeout, this, &Workspace::updateClientArea);
connect(&updateToolWindowsTimer, &QTimer::timeout, this, &Workspace::slotUpdateToolWindows);
// TODO: do we really need to reconfigure everything when fonts change?
@ -2347,6 +2349,11 @@ QRectF Workspace::adjustClientArea(Window *window, const QRectF &area) const
return adjustedArea;
}
void Workspace::scheduleUpdateClientArea()
{
m_updateClientAreaTimer.start(0);
}
/**
* Updates the current client areas according to the current windows.
*
@ -2358,6 +2365,9 @@ QRectF Workspace::adjustClientArea(Window *window, const QRectF &area) const
*/
void Workspace::updateClientArea()
{
Q_EMIT aboutToUpdateClientArea();
m_updateClientAreaTimer.stop();
const QList<VirtualDesktop *> desktops = VirtualDesktopManager::self()->desktops();
QHash<const VirtualDesktop *, QRectF> workAreas;

View file

@ -530,6 +530,7 @@ public Q_SLOTS:
void setupWindowShortcutDone(bool);
void updateClientArea();
void scheduleUpdateClientArea();
private Q_SLOTS:
void desktopResized();
@ -580,6 +581,7 @@ Q_SIGNALS:
* or lowered
*/
void stackingOrderChanged();
void aboutToUpdateClientArea();
private:
void init();
@ -706,6 +708,7 @@ private:
// Timer to collect requests for 'reconfigure'
QTimer reconfigureTimer;
QTimer m_updateClientAreaTimer;
QTimer updateToolWindowsTimer;