Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion src/output/outputconfigstate.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// 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"

Check warning on line 5 in src/output/outputconfigstate.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "surface/surfacewrapper.h" not found.

void OutputConfigState::markScreenAsPrimary(const QString &outputName)

Check warning on line 7 in src/output/outputconfigstate.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

The function 'markScreenAsPrimary' is never used.
{
if (m_states.contains(outputName)) {
m_states[outputName].wasPrimary = true;
Expand Down Expand Up @@ -39,3 +40,42 @@
{
m_copyModeExited = false;
}

void OutputConfigState::recordSurfaceBinding(SurfaceWrapper *surface,

Check warning on line 44 in src/output/outputconfigstate.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

The function 'recordSurfaceBinding' is never used.
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<SurfaceBinding> OutputConfigState::takeSurfaceBindings(const QString &outputName)

Check warning on line 72 in src/output/outputconfigstate.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

The function 'takeSurfaceBindings' is never used.
{
return m_surfaceBindings.take(outputName);
}

bool OutputConfigState::hasSurfaceBindings(const QString &outputName) const

Check warning on line 77 in src/output/outputconfigstate.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

The function 'hasSurfaceBindings' is never used.
{
auto it = m_surfaceBindings.constFind(outputName);
return it != m_surfaceBindings.constEnd() && !it->isEmpty();
}
24 changes: 22 additions & 2 deletions src/output/outputconfigstate.h
Original file line number Diff line number Diff line change
@@ -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 <QPointF>

Check warning on line 6 in src/output/outputconfigstate.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QPointF> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QSizeF>

Check warning on line 7 in src/output/outputconfigstate.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QSizeF> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QString>

Check warning on line 8 in src/output/outputconfigstate.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QString> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QMap>

Check warning on line 9 in src/output/outputconfigstate.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QMap> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QList>

Check warning on line 10 in src/output/outputconfigstate.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QList> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QPointer>
#include <QObject>

struct OutputPrimaryState {
bool wasPrimary = false;
};

class SurfaceWrapper;

struct SurfaceBinding {
QPointer<SurfaceWrapper> surface;
QPointF relativePosition;
QSizeF originalOutputSize;
int surfaceState = 0;
};

class OutputConfigState : public QObject {
Q_OBJECT
public:
Expand All @@ -24,8 +37,15 @@
bool shouldRestoreCopyMode() const;
void clearCopyModeIntent();

void recordSurfaceBinding(SurfaceWrapper *surface, const QString &originalOutputName,
const QPointF &relativePosition, const QSizeF &originalOutputSize,
int surfaceState);
QList<SurfaceBinding> takeSurfaceBindings(const QString &outputName);
bool hasSurfaceBindings(const QString &outputName) const;

private:
QMap<QString, OutputPrimaryState> m_states;
bool m_copyModeExited = false; // Flag indicating Copy Mode was exited and should be restored
bool m_copyModeExited = false;
QMap<QString, QList<SurfaceBinding>> m_surfaceBindings;
};

125 changes: 122 additions & 3 deletions src/output/outputlifecyclemanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "core/rootsurfacecontainer.h"
#include "output.h"
#include "outputconfigstate.h"
#include "surface/surfacewrapper.h"

OutputLifecycleManager::OutputLifecycleManager(RootSurfaceContainer *rootContainer,
OutputConfigState *configState,
Expand Down Expand Up @@ -59,6 +60,101 @@ void OutputLifecycleManager::switchPrimaryOutput(Output *from,
m_rootContainer->moveSurfacesToOutput(surfaces, to, from);
}

void OutputLifecycleManager::recordSurfaceBindings(const QList<SurfaceWrapper *> &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<int>(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<SurfaceBinding> bindings = m_configState->takeSurfaceBindings(outputName);

QList<SurfaceBinding> 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<SurfaceWrapper::State>(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)
Expand All @@ -74,11 +170,14 @@ void OutputLifecycleManager::onScreenAdded(Output *output)
restoreScreenAsPrimary(output);
}

restoreSurfaceBindings(output);

m_configState->clearOutputState(outputName);
}

void OutputLifecycleManager::onScreenRemoved(Output *output,
const QList<SurfaceWrapper *> &surfaces)
const QList<SurfaceWrapper *> &surfaces,
const QList<SurfaceWrapper *> &allWorkspacesSurfaces)
{
if (!output || !m_rootContainer || !m_configState)
return;
Expand All @@ -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);
Expand All @@ -100,7 +212,8 @@ void OutputLifecycleManager::onScreenRemoved(Output *output,
}

void OutputLifecycleManager::onScreenDisabled(Output *output,
const QList<SurfaceWrapper *> &surfaces)
const QList<SurfaceWrapper *> &surfaces,
const QList<SurfaceWrapper *> &allWorkspacesSurfaces)
{
if (!output || !m_rootContainer)
return;
Expand All @@ -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) {
Expand Down Expand Up @@ -145,5 +262,7 @@ void OutputLifecycleManager::onScreenEnabled(Output *output)
restoreScreenAsPrimary(output);
}

restoreSurfaceBindings(output);

m_configState->clearOutputState(outputName);
}
10 changes: 7 additions & 3 deletions src/output/outputlifecyclemanager.h
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -29,8 +29,10 @@ class OutputLifecycleManager : public QObject {
~OutputLifecycleManager() = default;

void onScreenAdded(Output *output);
void onScreenRemoved(Output *output, const QList<SurfaceWrapper *> &surfaces);
void onScreenDisabled(Output *output, const QList<SurfaceWrapper *> &surfaces);
void onScreenRemoved(Output *output, const QList<SurfaceWrapper *> &surfaces,
const QList<SurfaceWrapper *> &allWorkspacesSurfaces);
void onScreenDisabled(Output *output, const QList<SurfaceWrapper *> &surfaces,
const QList<SurfaceWrapper *> &allWorkspacesSurfaces);
void onScreenEnabled(Output *output);
void setMode(Mode mode) {
m_mode = mode;
Expand All @@ -56,4 +58,6 @@ class OutputLifecycleManager : public QObject {
void markScreenAsPrimaryIntent(Output *output);
void restoreScreenAsPrimary(Output *output);
void switchPrimaryOutput(Output *from, Output *to, const QList<SurfaceWrapper *> &surfaces);
void recordSurfaceBindings(const QList<SurfaceWrapper *> &surfaces, Output *sourceOutput);
void restoreSurfaceBindings(Output *targetOutput);
};
25 changes: 23 additions & 2 deletions src/seat/helper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -2195,6 +2197,25 @@ QList<SurfaceWrapper *> Helper::getWorkspaceSurfaces(Output *filterOutput)
return surfaces;
}

QList<SurfaceWrapper *> Helper::getAllOutputSurfaces(Output *filterOutput)
{
QList<SurfaceWrapper *> surfaces;
WOutputRenderWindow::paintOrderItemList(
Helper::instance()->workspace(),
[&surfaces, filterOutput](QQuickItem *item) -> bool {
SurfaceWrapper *surfaceWrapper = qobject_cast<SurfaceWrapper *>(item);
if (surfaceWrapper
&& (!filterOutput || surfaceWrapper->ownsOutput() == filterOutput)) {
surfaces.append(surfaceWrapper);
return true;
} else {
return false;
}
});

return surfaces;
}

void Helper::moveSurfacesToOutput(const QList<SurfaceWrapper *> &surfaces,
Output *targetOutput,
Output *sourceOutput)
Expand Down
1 change: 1 addition & 0 deletions src/seat/helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ private Q_SLOTS:
Output *createCopyOutput(WOutput *output, Output *proxy);
WOutputViewport *getOwnOutputViewport(WOutput *output);
QList<SurfaceWrapper *> getWorkspaceSurfaces(Output *filterOutput = nullptr);
QList<SurfaceWrapper *> getAllOutputSurfaces(Output *filterOutput);
void moveSurfacesToOutput(const QList<SurfaceWrapper *> &surfaces,
Output *targetOutput,
Output *sourceOutput);
Expand Down
Loading