From 9960e41dbc008cd2b8c765a41485fb207e376ed8 Mon Sep 17 00:00:00 2001 From: deepin-wm Date: Thu, 18 Jun 2026 20:46:34 +0800 Subject: [PATCH] fix: migrate windows and layer surfaces on primary hot-unplug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Fix onScreenRemoved missing primary migration when removeOutput already switched primary output before onScreenRemoved is called 2. Extract migrateSurfacesToNewPrimary as a public method to eliminate duplicated migration logic in onScreenRemoved and onScreenDisabled 3. Add position correction for remaining output surfaces after output layout change, with normalGeometry sync for XdgToplevel 4. Migrate layer surfaces to remaining output instead of closing them when output is removed, and trigger arrangeLayerSurfaces for final layout Log: Fix windows and layer surfaces not displaying on remaining screen after primary output hot-unplug in extension mode Influence: 1. Test hot-unplug primary output in dual-screen extension mode 2. Verify windows on secondary screen move correctly to remaining screen 3. Verify layer surfaces (panel/dock) migrate correctly after unplug 4. Test maximize/restore window after output removal 5. Verify Copy mode is not affected by the changes fix: 主屏热插拔时正确迁移窗口和层表面 1. 修复 onScreenRemoved 缺失主屏迁移逻辑,当 removeOutput 在 onScreenRemoved 之前已切换主屏输出时 2. 提取 migrateSurfacesToNewPrimary 为公共方法,消除 onScreenRemoved 和 onScreenDisabled 中的重复迁移逻辑 3. 添加输出布局变化后剩余输出上表面位置校正,同步更新 XdgToplevel 的 normalGeometry 4. 输出移除时将层表面迁移到剩余输出而非关闭,并触发 arrangeLayerSurfaces 进行最终布局 Log: 修复扩展模式下拔掉主屏后窗口和面板不显示在剩余屏幕的问题 Influence: 1. 测试双屏扩展模式下拔掉主屏 2. 验证副屏上的窗口正确移动到剩余屏幕 3. 验证层表面(面板/任务栏)在拔屏后正确迁移 4. 测试拔屏后最大化/还原窗口功能 5. 验证复制模式不受此变更影响 Fixes: linuxdeepin/treeland#1 --- src/core/layersurfacecontainer.cpp | 21 ++++++++++++------- src/core/rootsurfacecontainer.cpp | 29 +++++++++++++++++++++++++++ src/output/output.h | 2 +- src/output/outputlifecyclemanager.cpp | 29 ++++++++++++++++++--------- src/output/outputlifecyclemanager.h | 3 ++- 5 files changed, 66 insertions(+), 18 deletions(-) diff --git a/src/core/layersurfacecontainer.cpp b/src/core/layersurfacecontainer.cpp index bab103117..5571e6b9c 100644 --- a/src/core/layersurfacecontainer.cpp +++ b/src/core/layersurfacecontainer.cpp @@ -64,19 +64,26 @@ void LayerSurfaceContainer::removeOutput(Output *output) Q_ASSERT(container); m_surfaceContainers.removeOne(container); + OutputLayerSurfaceContainer *targetContainer = m_surfaceContainers.isEmpty() + ? nullptr + : m_surfaceContainers.first(); + const auto surfaces = container->surfaces(); for (SurfaceWrapper *surface : surfaces) { - auto layerSurface = qobject_cast(surface->shellSurface()); - Q_ASSERT(layerSurface); - // Needs to be moved to the new primary output - if (!layerSurface->output() && rootContainer()->primaryOutput()) { - container->removeSurface(surface); - addSurfaceToContainer(surface); + container->removeSurface(surface); + if (targetContainer) { + targetContainer->addSurface(surface); } else { - layerSurface->closed(); + auto layerSurface = qobject_cast(surface->shellSurface()); + if (layerSurface) + layerSurface->closed(); } } + if (targetContainer) { + targetContainer->output()->arrangeLayerSurfaces(); + } + container->deleteLater(); } diff --git a/src/core/rootsurfacecontainer.cpp b/src/core/rootsurfacecontainer.cpp index 7aaf942cd..8b63fba52 100644 --- a/src/core/rootsurfacecontainer.cpp +++ b/src/core/rootsurfacecontainer.cpp @@ -149,6 +149,13 @@ void RootSurfaceContainer::removeOutput(Output *output) } } + // Save positions of remaining outputs before layout removal + QMap oldPositions; + for (auto o : outputs()) { + if (o != output) + oldPositions[o] = o->outputItem()->position(); + } + m_outputLayout->remove(output->output()); if (m_primaryOutput == output) { const auto outputs = m_outputLayout->outputs(); @@ -158,6 +165,28 @@ void RootSurfaceContainer::removeOutput(Output *output) } } + // Correct positions of surfaces on remaining outputs after layout change + for (auto o : outputs()) { + if (o == output) + continue; + auto it = oldPositions.find(o); + if (it == oldPositions.end()) + continue; + QPointF delta = o->outputItem()->position() - it.value(); + if (!delta.isNull()) { + for (auto *surface : surfaces()) { + if (surface->type() != SurfaceWrapper::Type::Layer && surface->ownsOutput() == o) { + surface->setPosition(surface->position() + delta); + if (surface->type() == SurfaceWrapper::Type::XdgToplevel) { + auto normalGeo = surface->normalGeometry(); + normalGeo.moveTopLeft(normalGeo.topLeft() + delta); + surface->moveNormalGeometryInOutput(normalGeo.topLeft()); + } + } + } + } + } + // ensure cursor within output const auto outputPos = output->outputItem()->position(); if (output->geometry().contains(m_cursor->position()) && m_primaryOutput) { diff --git a/src/output/output.h b/src/output/output.h index b0bfa5fb2..08939e8f7 100644 --- a/src/output/output.h +++ b/src/output/output.h @@ -90,6 +90,7 @@ class Output : public SurfaceListModel void adjustToOutputBounds(QPointF &pos, const QRectF &normalGeo, const QRectF &outputRect) const; + void arrangeLayerSurfaces(); Q_SIGNALS: void exclusiveZoneChanged(); @@ -110,7 +111,6 @@ public Q_SLOTS: void setExclusiveZone(Qt::Edge edge, QObject *object, int value); bool removeExclusiveZone(QObject *object); void arrangeLayerSurface(SurfaceWrapper *surface); - void arrangeLayerSurfaces(); void arrangeNonLayerSurface(SurfaceWrapper *surface, const QSizeF &sizeDiff); void arrangePopupSurface(SurfaceWrapper *surface); void arrangeNonLayerSurfaces(); diff --git a/src/output/outputlifecyclemanager.cpp b/src/output/outputlifecyclemanager.cpp index 3652021af..6f02b7aa5 100644 --- a/src/output/outputlifecyclemanager.cpp +++ b/src/output/outputlifecyclemanager.cpp @@ -59,6 +59,23 @@ void OutputLifecycleManager::switchPrimaryOutput(Output *from, m_rootContainer->moveSurfacesToOutput(surfaces, to, from); } +void OutputLifecycleManager::migrateSurfacesToNewPrimary(Output *removedOutput, + const QList &surfaces) +{ + // NOTE: This must be called while removedOutput's Output item still has valid position + // data. The call chain is: Helper::onOutputRemoved -> removeOutput (switches primary) + // -> onScreenRemoved -> migrateSurfacesToNewPrimary. At this point the removed output + // has been removed from the layout, but the Output object (and its item position) still + // exists because `delete o` happens after onScreenRemoved returns. + if (!m_rootContainer || surfaces.isEmpty()) + return; + + auto newPrimary = m_rootContainer->primaryOutput(); + if (newPrimary) { + m_rootContainer->moveSurfacesToOutput(surfaces, newPrimary, removedOutput); + } +} + void OutputLifecycleManager::onScreenAdded(Output *output) { if (!output || !m_configState) @@ -91,11 +108,8 @@ void OutputLifecycleManager::onScreenRemoved(Output *output, markScreenAsPrimaryIntent(output); } - if (!isCurrentPrimary && !wasPrimaryBeforeRemoval) { - auto primaryOutput = m_rootContainer->primaryOutput(); - if (primaryOutput) { - m_rootContainer->moveSurfacesToOutput(surfaces, primaryOutput, output); - } + if (!isCurrentPrimary) { + migrateSurfacesToNewPrimary(output, surfaces); } } @@ -120,10 +134,7 @@ void OutputLifecycleManager::onScreenDisabled(Output *output, switchPrimaryOutput(output, nextPrimary, surfaces); } } else if (!isCurrentPrimary) { - auto primaryOutput = m_rootContainer->primaryOutput(); - if (primaryOutput) { - m_rootContainer->moveSurfacesToOutput(surfaces, primaryOutput, output); - } + migrateSurfacesToNewPrimary(output, surfaces); } } diff --git a/src/output/outputlifecyclemanager.h b/src/output/outputlifecyclemanager.h index 9eac0d683..1ab5a05cc 100644 --- a/src/output/outputlifecyclemanager.h +++ b/src/output/outputlifecyclemanager.h @@ -1,4 +1,4 @@ -// Copyright (C) 2025 UnionTech Software Technology Co., Ltd. +// Copyright (C) 2025-2026 UnionTech Software Technology Co., Ltd. // SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #pragma once @@ -32,6 +32,7 @@ class OutputLifecycleManager : public QObject { void onScreenRemoved(Output *output, const QList &surfaces); void onScreenDisabled(Output *output, const QList &surfaces); void onScreenEnabled(Output *output); + void migrateSurfacesToNewPrimary(Output *removedOutput, const QList &surfaces); void setMode(Mode mode) { m_mode = mode; }