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);