tiling: Evacuate tiled windows from custom & quick tiling on output removal

Context: If a display is removed, the corresponding TileManager is removed with it. This in turn removes every one of its Tiles with it, and when a Tile's destructor is called, it attempts to find a new replacement tile for any windows it was previously managing.

However, if the Tile is removed because its corresponding TileManager has been removed, this has the potential to cause a segfault in KWin, causing it to crash (I suspect a possible race condition? but not sure).

This MR correctly evacuates custom tiled windows & migrates quick tiled windows upon output removal.

BUG: 465522
This commit is contained in:
Dominique Hummel 2023-02-14 12:03:40 +00:00 committed by Vlad Zahorodnii
parent fbed3d2d69
commit 0ca7b40da0
3 changed files with 55 additions and 3 deletions

View file

@ -372,6 +372,14 @@ Tile *Tile::parentTile() const
return m_parentTile;
}
void Tile::visitDescendants(std::function<void(const Tile *child)> callback) const
{
callback(this);
for (const Tile *child : m_children) {
child->visitDescendants(callback);
}
}
TileManager *Tile::manager() const
{
return m_tiling;

View file

@ -98,6 +98,11 @@ public:
*/
QList<Tile *> descendants() const;
/**
* Visit all tiles descendant of this tile, recursive
*/
void visitDescendants(std::function<void(const Tile *child)> callback) const;
void resizeFromGravity(Gravity gravity, int x_root, int y_root);
Q_INVOKABLE void resizeByPixels(qreal delta, Qt::Edge edge);

View file

@ -1595,12 +1595,51 @@ void Workspace::updateOutputs(const QVector<Output *> &outputOrder)
Q_EMIT outputAdded(output);
}
desktopResized();
const auto removed = oldOutputsSet - outputsSet;
for (Output *output : removed) {
m_tileManagers.erase(output);
Q_EMIT outputRemoved(output);
auto tileManager = std::move(m_tileManagers[output]);
m_tileManagers.erase(output);
// Evacuate windows from the defunct custom tile tree.
tileManager->rootTile()->visitDescendants([](const Tile *child) {
const QList<Window *> windows = child->windows();
for (Window *window : windows) {
window->setTile(nullptr);
}
});
// Migrate windows from the defunct quick tile to a quick tile tree on another output.
static constexpr QuickTileMode quickTileModes[] = {
QuickTileFlag::Left,
QuickTileFlag::Right,
QuickTileFlag::Top,
QuickTileFlag::Bottom,
QuickTileFlag::Top | QuickTileFlag::Left,
QuickTileFlag::Top | QuickTileFlag::Right,
QuickTileFlag::Bottom | QuickTileFlag::Left,
QuickTileFlag::Bottom | QuickTileFlag::Right,
};
for (const QuickTileMode &quickTileMode : quickTileModes) {
Tile *quickTile = tileManager->quickTile(quickTileMode);
const QList<Window *> windows = quickTile->windows();
if (windows.isEmpty()) {
continue;
}
Output *bestOutput = outputAt(output->geometry().center());
Tile *bestTile = m_tileManagers[bestOutput]->quickTile(quickTileMode);
for (Window *window : windows) {
window->setTile(bestTile);
}
}
}
desktopResized();
for (Output *output : removed) {
output->unref();
}