From e2762a9442e5b971b847ce3c75e9884181ceaf4f Mon Sep 17 00:00:00 2001 From: deepin-wm Date: Thu, 18 Jun 2026 13:31:31 +0000 Subject: [PATCH] fix: ext-mode primary migration and window binding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Fix onScreenRemoved primary migration condition: use isCurrentPrimary || wasPrimaryBeforeRemoval instead of !isCurrentPrimary && !wasPrimaryBeforeRemoval to correctly migrate windows when primary output is removed 2. Add window-output binding record and restore mechanism: SurfaceBinding stores surfaceState, getAllOutputSurfaces collects surfaces across all workspaces, use primaryOutput() directly in onScreenRemoved to avoid conflict with removeOutput's primary selection, overwrite strategy for duplicate recordSurfaceBinding calls, skip restore for user-moved windows (positionAutomatic), qBound boundary constraint in restoreSurfaceBindings Log: Fixed windows not migrating to remaining screen when primary output is unplugged in dual-screen extension mode, and windows not returning to original screen when reconnected Influence: 1. Test unplugging primary screen in extension mode, verify windows migrate to remaining screen 2. Test unplugging secondary screen, verify windows migrate to primary screen 3. Test reconnecting screens, verify windows return to original positions 4. Test maximized and fullscreen window migration 5. Test minimized window restoration after screen reconnect fix: 扩展模式主屏迁移和窗口-输出绑定 1. 修复 onScreenRemoved 主屏迁移条件:使用 isCurrentPrimary || wasPrimaryBeforeRemoval 替代 !isCurrentPrimary && !wasPrimaryBeforeRemoval, 正确处理主屏移除时窗口迁移 2. 新增窗口-输出绑定记录与恢复机制: SurfaceBinding 存储 surfaceState, getAllOutputSurfaces 收集所有工作区的 surface, 在 onScreenRemoved 中直接使用 primaryOutput() 避免与 removeOutput 的主屏选择冲突, 覆盖策略处理重复 recordSurfaceBinding 调用, 跳过用户手动移动的窗口(positionAutomatic), restoreSurfaceBindings 中使用 qBound 边界约束 Log: 修复扩展模式拔掉主屏后窗口未迁移到剩余屏幕, 以及重新连接后窗口未回到原屏幕的问题 Influence: 1. 测试扩展模式拔掉主屏,验证窗口迁移到剩余屏幕 2. 测试拔掉副屏,验证窗口迁移到主屏 3. 测试重新连接屏幕,验证窗口回到原位置 4. 测试最大化和全屏窗口的迁移 5. 测试最小化窗口在重新连接屏幕后的恢复 --- src/output/outputconfigstate.cpp | 42 ++++++++- src/output/outputconfigstate.h | 24 ++++- src/output/outputlifecyclemanager.cpp | 125 +++++++++++++++++++++++++- src/output/outputlifecyclemanager.h | 10 ++- src/seat/helper.cpp | 25 +++++- src/seat/helper.h | 1 + 6 files changed, 216 insertions(+), 11 deletions(-) diff --git a/src/output/outputconfigstate.cpp b/src/output/outputconfigstate.cpp index 3456f1dad..967aa6d58 100644 --- a/src/output/outputconfigstate.cpp +++ b/src/output/outputconfigstate.cpp @@ -1,7 +1,8 @@ -// 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 #include "outputconfigstate.h" +#include "surface/surfacewrapper.h" void OutputConfigState::markScreenAsPrimary(const QString &outputName) { @@ -39,3 +40,42 @@ void OutputConfigState::clearCopyModeIntent() { m_copyModeExited = false; } + +void OutputConfigState::recordSurfaceBinding(SurfaceWrapper *surface, + const QString &originalOutputName, + const QPointF &relativePosition, + const QSizeF &originalOutputSize, + int surfaceState) +{ + if (!surface) + return; + + auto &bindings = m_surfaceBindings[originalOutputName]; + for (int i = 0; i < bindings.size(); ++i) { + if (bindings[i].surface == surface) { + bindings[i].relativePosition = relativePosition; + bindings[i].originalOutputSize = originalOutputSize; + bindings[i].surfaceState = surfaceState; + return; + } + } + + SurfaceBinding binding; + binding.surface = surface; + binding.relativePosition = relativePosition; + binding.originalOutputSize = originalOutputSize; + binding.surfaceState = surfaceState; + + bindings.append(binding); +} + +QList OutputConfigState::takeSurfaceBindings(const QString &outputName) +{ + return m_surfaceBindings.take(outputName); +} + +bool OutputConfigState::hasSurfaceBindings(const QString &outputName) const +{ + auto it = m_surfaceBindings.constFind(outputName); + return it != m_surfaceBindings.constEnd() && !it->isEmpty(); +} diff --git a/src/output/outputconfigstate.h b/src/output/outputconfigstate.h index 9fbdf4355..530549515 100644 --- a/src/output/outputconfigstate.h +++ b/src/output/outputconfigstate.h @@ -1,16 +1,29 @@ -// 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 +#include +#include #include #include +#include +#include #include struct OutputPrimaryState { bool wasPrimary = false; }; +class SurfaceWrapper; + +struct SurfaceBinding { + QPointer surface; + QPointF relativePosition; + QSizeF originalOutputSize; + int surfaceState = 0; +}; + class OutputConfigState : public QObject { Q_OBJECT public: @@ -24,8 +37,15 @@ class OutputConfigState : public QObject { bool shouldRestoreCopyMode() const; void clearCopyModeIntent(); + void recordSurfaceBinding(SurfaceWrapper *surface, const QString &originalOutputName, + const QPointF &relativePosition, const QSizeF &originalOutputSize, + int surfaceState); + QList takeSurfaceBindings(const QString &outputName); + bool hasSurfaceBindings(const QString &outputName) const; + private: QMap m_states; - bool m_copyModeExited = false; // Flag indicating Copy Mode was exited and should be restored + bool m_copyModeExited = false; + QMap> m_surfaceBindings; }; diff --git a/src/output/outputlifecyclemanager.cpp b/src/output/outputlifecyclemanager.cpp index 3652021af..0f4533a41 100644 --- a/src/output/outputlifecyclemanager.cpp +++ b/src/output/outputlifecyclemanager.cpp @@ -7,6 +7,7 @@ #include "core/rootsurfacecontainer.h" #include "output.h" #include "outputconfigstate.h" +#include "surface/surfacewrapper.h" OutputLifecycleManager::OutputLifecycleManager(RootSurfaceContainer *rootContainer, OutputConfigState *configState, @@ -59,6 +60,101 @@ void OutputLifecycleManager::switchPrimaryOutput(Output *from, m_rootContainer->moveSurfacesToOutput(surfaces, to, from); } +void OutputLifecycleManager::recordSurfaceBindings(const QList &surfaces, + Output *sourceOutput) +{ + if (!m_configState || !sourceOutput) + return; + + QString outputName = sourceOutput->output()->name(); + QRectF outputGeometry = sourceOutput->geometry(); + QPointF outputCenter = outputGeometry.center(); + QSizeF outputSize = outputGeometry.size(); + + for (auto *surface : surfaces) { + if (!surface) + continue; + + auto surfaceType = surface->type(); + if (surfaceType != SurfaceWrapper::Type::XdgToplevel + && surfaceType != SurfaceWrapper::Type::XWayland) + continue; + + QPointF relativePos = surface->position() - outputCenter; + int state = static_cast(surface->surfaceState()); + m_configState->recordSurfaceBinding(surface, outputName, relativePos, outputSize, state); + } +} + +void OutputLifecycleManager::restoreSurfaceBindings(Output *targetOutput) +{ + if (!m_configState || !targetOutput || !m_rootContainer) + return; + + QString outputName = targetOutput->output()->name(); + if (!m_configState->hasSurfaceBindings(outputName)) + return; + + QList bindings = m_configState->takeSurfaceBindings(outputName); + + QList validBindings; + for (const auto &binding : bindings) { + if (!binding.surface) + continue; + + SurfaceWrapper *surface = binding.surface; + auto surfaceType = surface->type(); + if (surfaceType != SurfaceWrapper::Type::XdgToplevel + && surfaceType != SurfaceWrapper::Type::XWayland) + continue; + + if (surface->ownsOutput() == targetOutput) + continue; + + if (!surface->positionAutomatic()) + continue; + + validBindings.append(binding); + } + + if (validBindings.isEmpty()) + return; + + QRectF targetGeometry = targetOutput->geometry(); + for (const auto &binding : validBindings) { + SurfaceWrapper *surface = binding.surface; + if (!surface) + continue; + + SurfaceWrapper::State savedState = static_cast(binding.surfaceState); + + if (savedState == SurfaceWrapper::State::Maximized + || savedState == SurfaceWrapper::State::Fullscreen) { + surface->setOwnsOutput(targetOutput); + surface->setSurfaceState(savedState); + } else if (savedState == SurfaceWrapper::State::Minimized) { + surface->setOwnsOutput(targetOutput); + } else { + qreal scaleX = binding.originalOutputSize.width() > 0 + ? targetGeometry.width() / binding.originalOutputSize.width() : 1.0; + qreal scaleY = binding.originalOutputSize.height() > 0 + ? targetGeometry.height() / binding.originalOutputSize.height() : 1.0; + + QPointF newPos(targetGeometry.center().x() + binding.relativePosition.x() * scaleX, + targetGeometry.center().y() + binding.relativePosition.y() * scaleY); + + const QSizeF size = surface->size(); + newPos.setX( + qBound(targetGeometry.left(), newPos.x(), targetGeometry.right() - size.width())); + newPos.setY( + qBound(targetGeometry.top(), newPos.y(), targetGeometry.bottom() - size.height())); + + surface->setOwnsOutput(targetOutput); + surface->setPosition(newPos); + } + } +} + void OutputLifecycleManager::onScreenAdded(Output *output) { if (!output || !m_configState) @@ -74,11 +170,14 @@ void OutputLifecycleManager::onScreenAdded(Output *output) restoreScreenAsPrimary(output); } + restoreSurfaceBindings(output); + m_configState->clearOutputState(outputName); } void OutputLifecycleManager::onScreenRemoved(Output *output, - const QList &surfaces) + const QList &surfaces, + const QList &allWorkspacesSurfaces) { if (!output || !m_rootContainer || !m_configState) return; @@ -91,7 +190,20 @@ void OutputLifecycleManager::onScreenRemoved(Output *output, markScreenAsPrimaryIntent(output); } - if (!isCurrentPrimary && !wasPrimaryBeforeRemoval) { + recordSurfaceBindings(allWorkspacesSurfaces, output); + + if (isCurrentPrimary || wasPrimaryBeforeRemoval) { + auto newPrimary = m_rootContainer->primaryOutput(); + if (newPrimary && newPrimary != output) { + m_rootContainer->moveSurfacesToOutput(surfaces, newPrimary, output); + } else if (!m_rootContainer->outputs().isEmpty()) { + Output *nextPrimary = findFirstAvailableOutput(output); + if (nextPrimary) { + m_rootContainer->setPrimaryOutput(nextPrimary); + m_rootContainer->moveSurfacesToOutput(surfaces, nextPrimary, output); + } + } + } else { auto primaryOutput = m_rootContainer->primaryOutput(); if (primaryOutput) { m_rootContainer->moveSurfacesToOutput(surfaces, primaryOutput, output); @@ -100,7 +212,8 @@ void OutputLifecycleManager::onScreenRemoved(Output *output, } void OutputLifecycleManager::onScreenDisabled(Output *output, - const QList &surfaces) + const QList &surfaces, + const QList &allWorkspacesSurfaces) { if (!output || !m_rootContainer) return; @@ -114,6 +227,10 @@ void OutputLifecycleManager::onScreenDisabled(Output *output, markScreenAsPrimaryIntent(output); } + if (m_configState) { + recordSurfaceBindings(allWorkspacesSurfaces, output); + } + if (isCurrentPrimary && !m_rootContainer->outputs().isEmpty()) { Output *nextPrimary = findFirstAvailableOutput(output); if (nextPrimary) { @@ -145,5 +262,7 @@ void OutputLifecycleManager::onScreenEnabled(Output *output) restoreScreenAsPrimary(output); } + restoreSurfaceBindings(output); + m_configState->clearOutputState(outputName); } diff --git a/src/output/outputlifecyclemanager.h b/src/output/outputlifecyclemanager.h index 9eac0d683..024b06960 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 @@ -29,8 +29,10 @@ class OutputLifecycleManager : public QObject { ~OutputLifecycleManager() = default; void onScreenAdded(Output *output); - void onScreenRemoved(Output *output, const QList &surfaces); - void onScreenDisabled(Output *output, const QList &surfaces); + void onScreenRemoved(Output *output, const QList &surfaces, + const QList &allWorkspacesSurfaces); + void onScreenDisabled(Output *output, const QList &surfaces, + const QList &allWorkspacesSurfaces); void onScreenEnabled(Output *output); void setMode(Mode mode) { m_mode = mode; @@ -56,4 +58,6 @@ class OutputLifecycleManager : public QObject { void markScreenAsPrimaryIntent(Output *output); void restoreScreenAsPrimary(Output *output); void switchPrimaryOutput(Output *from, Output *to, const QList &surfaces); + void recordSurfaceBindings(const QList &surfaces, Output *sourceOutput); + void restoreSurfaceBindings(Output *targetOutput); }; diff --git a/src/seat/helper.cpp b/src/seat/helper.cpp index 369aed990..0e6e0ef10 100644 --- a/src/seat/helper.cpp +++ b/src/seat/helper.cpp @@ -591,7 +591,8 @@ void Helper::onOutputRemoved(WOutput *output) m_rootSurfaceContainer->removeOutput(o); if (m_outputLifecycleManager) { m_outputLifecycleManager->setMode(OutputLifecycleManager::Mode::Extension); - m_outputLifecycleManager->onScreenRemoved(o, surfaces); + const auto &allSurfaces = getAllOutputSurfaces(o); + m_outputLifecycleManager->onScreenRemoved(o, surfaces, allSurfaces); } } @@ -747,7 +748,8 @@ void Helper::onOutputTestOrApply(qw_output_configuration_v1 *config, bool onlyTe if (!state.enabled && state.output->isEnabled()) { const auto &surfaces = getWorkspaceSurfaces(outputObj); - m_outputLifecycleManager->onScreenDisabled(outputObj, surfaces); + const auto &allSurfaces = getAllOutputSurfaces(outputObj); + m_outputLifecycleManager->onScreenDisabled(outputObj, surfaces, allSurfaces); } else if (state.enabled && !state.output->isEnabled()) { m_outputLifecycleManager->onScreenEnabled(outputObj); if (m_outputLifecycleManager->takeCopyModeRestoreIntent()) { @@ -2195,6 +2197,25 @@ QList Helper::getWorkspaceSurfaces(Output *filterOutput) return surfaces; } +QList Helper::getAllOutputSurfaces(Output *filterOutput) +{ + QList surfaces; + WOutputRenderWindow::paintOrderItemList( + Helper::instance()->workspace(), + [&surfaces, filterOutput](QQuickItem *item) -> bool { + SurfaceWrapper *surfaceWrapper = qobject_cast(item); + if (surfaceWrapper + && (!filterOutput || surfaceWrapper->ownsOutput() == filterOutput)) { + surfaces.append(surfaceWrapper); + return true; + } else { + return false; + } + }); + + return surfaces; +} + void Helper::moveSurfacesToOutput(const QList &surfaces, Output *targetOutput, Output *sourceOutput) diff --git a/src/seat/helper.h b/src/seat/helper.h index d2eb4b071..cc1ee2b4f 100644 --- a/src/seat/helper.h +++ b/src/seat/helper.h @@ -344,6 +344,7 @@ private Q_SLOTS: Output *createCopyOutput(WOutput *output, Output *proxy); WOutputViewport *getOwnOutputViewport(WOutput *output); QList getWorkspaceSurfaces(Output *filterOutput = nullptr); + QList getAllOutputSurfaces(Output *filterOutput); void moveSurfacesToOutput(const QList &surfaces, Output *targetOutput, Output *sourceOutput);