Skip to content
Draft
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
3 changes: 2 additions & 1 deletion src/common/treelandlogging.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// 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 "common/treelandlogging.h"

Check warning on line 4 in src/common/treelandlogging.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "common/treelandlogging.h" not found.
#include <qlogging.h>

Check warning on line 5 in src/common/treelandlogging.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <qlogging.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.

// TreeLand logging category definitions
// Naming convention: treeland.module_name.submodule_name
Expand All @@ -23,7 +24,7 @@
Q_LOGGING_CATEGORY(treelandOutput, "treeland.output")

// Window management
Q_LOGGING_CATEGORY(treelandSurface, "treeland.surface")
Q_LOGGING_CATEGORY(treelandSurface, "treeland.surface", QtDebugMsg)

// Protocol module
Q_LOGGING_CATEGORY(treelandProtocol, "treeland.protocol")
Expand Down
5 changes: 3 additions & 2 deletions src/core/qml/Animations/GeometryAnimation.qml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Item {
readonly property real yScale: root.height / surface.height

live: true
sourceItem: surface
sourceItem: surface.surfaceItem
hideSource: true
sourceRect: surface.boundingRect
width: sourceRect.width * xScale
Expand All @@ -60,6 +60,7 @@ Item {

live: false
sourceItem: surface
hideSource: true
width: sourceRect.width * xScale
height: sourceRect.height * yScale
x: sourceRect.x * xScale
Expand Down Expand Up @@ -114,7 +115,7 @@ Item {
OpacityAnimator {
target: frontEffect
duration: root.duration / 2
easing.type: Easing.OutCubic
easing.type: Easing.Linear
from: 1.0
to: 0.0
}
Expand Down
10 changes: 0 additions & 10 deletions src/core/qml/PrelaunchSplash.qml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ Item {
required property var iconBuffer
required property color backgroundColor
readonly property bool isLightBackground: backgroundColor.hslLightness >= 0.5
signal destroyRequested

// Fill the entire parent (SurfaceWrapper)
anchors.fill: parent
Expand Down Expand Up @@ -68,13 +67,4 @@ Item {
}
}
}

function hideAndDestroy() {
if (!splash.visible) {
console.warn("PrelaunchSplash: Already hidden, ignoring hideAndDestroy call.");
return;
}
splash.visible = false
splash.destroyRequested();
}
}
22 changes: 11 additions & 11 deletions src/core/windowconfigstore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@

void WindowConfigStore::saveLastSize(const QString &appId, const QSize &size)
{
return; // TO Debug
if (appId.isEmpty() || !size.isValid()) {

Check warning on line 36 in src/core/windowconfigstore.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Statements following 'return' will never be executed.
qCWarning(treelandCore) << "WindowConfigStore: saveLastSize invalid parameters for" << appId
<< size;
return;
Expand All @@ -49,14 +50,13 @@
config->setLastWindowHeight(size.height());
}

void WindowConfigStore::withSplashConfigFor(
const QString &appId,
QObject *context,
std::function<void(const QSize &size,
const QString &darkPalette,
const QString &lightPalette,
qlonglong splashThemeType)> callback,
std::function<void()> skipCallback) const
void WindowConfigStore::withSplashConfigFor(const QString &appId,
QObject *context,
std::function<void(const QSize &size,
const QString &darkPalette,
const QString &lightPalette,
qlonglong splashThemeType)> callback,
std::function<void()> skipCallback) const
{
Q_ASSERT_X(callback, Q_FUNC_INFO, "callback must be provided");
Q_ASSERT_X(skipCallback, Q_FUNC_INFO, "skipCallback must be provided");
Expand All @@ -81,9 +81,9 @@
static_cast<int>(config->lastWindowHeight()));
const QSize validatedSize = size.isValid() ? size : QSize();
callback(validatedSize,
config->splashDarkPalette(),
config->splashLightPalette(),
config->splashThemeType());
config->splashDarkPalette(),
config->splashLightPalette(),
config->splashThemeType());
return;
}

Expand Down
172 changes: 148 additions & 24 deletions src/surface/surfacewrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,17 +165,12 @@ SurfaceWrapper::SurfaceWrapper(QmlEngine *qmlEngine,
if (initialSize.isValid() && initialSize.width() > 0 && initialSize.height() > 0) {
// Also set implicit size to keep QML layout consistent
setImplicitSize(initialSize.width(), initialSize.height());
qCDebug(treelandSurface) << "Prelaunch Splash: set initial size to" << initialSize;
qCWarning(treelandSurface) << "Prelaunch Splash: set initial size to" << initialSize;
} else {
setImplicitSize(800, 600);
}
m_prelaunchSplash =
m_engine->createPrelaunchSplash(this, radius(), iconBuffer, backgroundColor);
// Connect to QML signal so C++ can destroy the QML item when requested
connect(m_prelaunchSplash,
SIGNAL(destroyRequested()),
this,
SLOT(onPrelaunchSplashDestroyRequested()));

setNoDecoration(false);
updateHasActiveCapability(ActiveControlState::MappedOrSplash, true); // Splash is true
Expand Down Expand Up @@ -309,7 +304,7 @@ void SurfaceWrapper::setup()
});
}

if (!m_prelaunchSplash || !m_prelaunchSplash->isVisible()) {
if (!m_prelaunchSplash) {
setImplicitSize(m_surfaceItem->implicitWidth(), m_surfaceItem->implicitHeight());
connect(m_surfaceItem, &WSurfaceItem::implicitWidthChanged, this, [this] {
setImplicitWidth(m_surfaceItem->implicitWidth());
Expand All @@ -332,9 +327,7 @@ void SurfaceWrapper::setup()
}

if (!m_shellSurface->hasCapability(WToplevelSurface::Capability::Focus)) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
m_surfaceItem->setFocusPolicy(Qt::NoFocus);
#endif
}

if (m_type == Type::XdgToplevel && !m_isProxy) // x11 will set later
Expand Down Expand Up @@ -423,19 +416,21 @@ void SurfaceWrapper::convertToNormalSurface(WToplevelSurface *shellSurface, Type

// Call setup() to initialize surfaceItem related features
setup();
m_surfaceItem->setVisible(false);

// setNoDecoration not called updateTitleBar when type is SplashScreen
updateTitleBar();
updateDecoration();
if (surface()->mapped()) {
qCWarning(treelandSurface) << "[prelaunch-debug] convertToNormalSurface: already mapped"
<< "hasSplash=" << static_cast<bool>(m_prelaunchSplash)
<< "implicit=" << QSizeF(implicitWidth(), implicitHeight());
// Apply pending outputs only after mapped to ensure wl_surface resource is ready.
if (!m_prelaunchOutputs.isEmpty()) {
setOutputs(m_prelaunchOutputs);
m_prelaunchOutputs.clear();
}
updateActiveState();
Q_ASSERT(m_prelaunchSplash);
QMetaObject::invokeMethod(m_prelaunchSplash, "hideAndDestroy", Qt::QueuedConnection);
// Start prelaunch hidden sequence: check if size transition is needed
startPrelaunchSplashHideSequence();
} else {
// Defer output assignment/updateActiveState until surface becomes mapped
connect(
Expand Down Expand Up @@ -516,10 +511,48 @@ void SurfaceWrapper::setFocus(bool focus, Qt::FocusReason reason)
m_surfaceItem->setFocus(false, reason);
}

void SurfaceWrapper::onPrelaunchSplashDestroyRequested()
void SurfaceWrapper::startPrelaunchSplashHideSequence()
{
if (m_surfaceItem) {
setImplicitSize(m_surfaceItem->implicitWidth(), m_surfaceItem->implicitHeight());
Q_ASSERT(m_surfaceItem);

QSizeF targetImplicitSize;
if (auto surf = surface()) {
const QSize surfaceSize = surf->size();
targetImplicitSize = QSizeF(surfaceSize.width(), surfaceSize.height());
}
if (targetImplicitSize.width() <= 0 || targetImplicitSize.height() <= 0) {
targetImplicitSize = QSizeF(m_surfaceItem->implicitWidth(),
m_surfaceItem->implicitHeight());
}
if (targetImplicitSize.width() <= 0 || targetImplicitSize.height() <= 0) {
const QSizeF fallbackSize(qMax(implicitWidth(), 1.0), qMax(implicitHeight(), 1.0));
qCWarning(treelandSurface)
<< "[prelaunch-debug] invalid target implicit size, fallback to current"
<< "targetImplicit=" << targetImplicitSize << "fallback=" << fallbackSize;
targetImplicitSize = fallbackSize;
}

const bool needImplicitSizeTransition =
!qFuzzyCompare(implicitWidth() + 1.0, targetImplicitSize.width() + 1.0)
|| !qFuzzyCompare(implicitHeight() + 1.0, targetImplicitSize.height() + 1.0);

qCWarning(treelandSurface) << "[prelaunch-debug] startHideSequence"
<< "needTransition=" << needImplicitSizeTransition
<< "hasGeometryAnim=" << static_cast<bool>(m_geometryAnimation)
<< "hasContainer=" << static_cast<bool>(container())
<< "currentImplicit=" << QSizeF(implicitWidth(), implicitHeight())
<< "targetImplicit=" << targetImplicitSize;

// This sequence can be triggered from multiple paths (e.g. convert + mappedChanged).
// If geometry animation is already running, keep it running and avoid fallback finalize.
if (needImplicitSizeTransition && m_geometryAnimation) {
qCWarning(treelandSurface)
<< "[prelaunch-debug] startHideSequence: geometry animation already running, skip";
return;
}

auto applySurfaceImplicitBinding = [this, targetImplicitSize]() {
setImplicitSize(targetImplicitSize.width(), targetImplicitSize.height());
connect(m_surfaceItem, &WSurfaceItem::implicitWidthChanged, this, [this] {
setImplicitWidth(m_surfaceItem->implicitWidth());
});
Expand All @@ -532,17 +565,108 @@ void SurfaceWrapper::onPrelaunchSplashDestroyRequested()
&SurfaceWrapper::updateBoundingRect);
if (m_decoration)
m_decoration->stackBefore(m_surfaceItem);
};

auto prepareSurfaceForReveal = [this]() {
// setNoDecoration not called updateTitleBar when type is SplashScreen
updateTitleBar();
updateDecoration();
if (m_surfaceItem)
m_surfaceItem->setVisible(true);
};

if (needImplicitSizeTransition && !m_geometryAnimation && container()) {
qCWarning(treelandSurface) << "[prelaunch-debug] startHideSequence: create/start geometry animation";
m_pendingPrelaunchImplicitSize = targetImplicitSize;
const QRectF fromGeometry(position(), size());
// XWayland clients manage their own position; respect it and don't shift.
// For all other types, keep the center fixed so the window expands from center.
const QPointF toTopLeft = (m_type == Type::XWayland) ? fromGeometry.topLeft()
: fromGeometry.center()
- QPointF(targetImplicitSize.width() / 2.0, targetImplicitSize.height() / 2.0);
m_pendingPrelaunchPosition = toTopLeft;
const QRectF toGeometry(toTopLeft, targetImplicitSize);
m_geometryAnimation =
m_engine->createGeometryAnimation(this, fromGeometry, toGeometry, container());

m_geometryAnimation->setOpacity(0.8);
bool ok = connect(m_geometryAnimation,
SIGNAL(ready()),
this,
SLOT(onPrelaunchGeometryAnimationReady()));
Q_ASSERT(ok);
ok = connect(m_geometryAnimation,
SIGNAL(finished()),
this,
SLOT(onPrelaunchGeometryAnimationFinished()));
Q_ASSERT(ok);
ok = QMetaObject::invokeMethod(m_geometryAnimation, "start");
Q_ASSERT(ok);
qCWarning(treelandSurface) << "[prelaunch-debug] startHideSequence: geometry animation started"
<< "from=" << fromGeometry << "to=" << toGeometry;
} else {
qCWarning(treelandSurface) << "[prelaunch-debug] startHideSequence: no geometry animation path, finalize now";
applySurfaceImplicitBinding();
prepareSurfaceForReveal();
finalizePrelaunchSplash();
}
}

updateVisible();
void SurfaceWrapper::onPrelaunchGeometryAnimationReady()
{

qCWarning(treelandSurface) << "[prelaunch-debug] geometryAnimation ready"
<< "targetPos=" << m_pendingPrelaunchPosition
<< "targetImplicit=" << m_pendingPrelaunchImplicitSize;

// Move the wrapper to the animation's final position before revealing the real surface,
// so the window appears exactly where the animation ended (no position jump).
const QPointF targetPos = m_pendingPrelaunchPosition;
setPosition(targetPos);
// Keep normalGeometry in sync so subsequent state transitions use the correct position.
setNormalGeometry(QRectF(targetPos, m_pendingPrelaunchImplicitSize));

setImplicitSize(m_pendingPrelaunchImplicitSize.width(),
m_pendingPrelaunchImplicitSize.height());
connect(m_surfaceItem, &WSurfaceItem::implicitWidthChanged, this, [this] {
setImplicitWidth(m_surfaceItem->implicitWidth());
});
connect(m_surfaceItem, &WSurfaceItem::implicitHeightChanged, this, [this] {
setImplicitHeight(m_surfaceItem->implicitHeight());
});
connect(m_surfaceItem,
&WSurfaceItem::boundingRectChanged,
this,
&SurfaceWrapper::updateBoundingRect);
// setNoDecoration not called updateTitleBar when type is SplashScreen
updateTitleBar();
//updateDecoration();
//if (m_decoration)
// m_decoration->stackBefore(m_surfaceItem);
finalizePrelaunchSplash();
m_surfaceItem->setVisible(true);
}

void SurfaceWrapper::onPrelaunchGeometryAnimationFinished()
{
Q_ASSERT(m_geometryAnimation);
m_geometryAnimation->disconnect(this);
m_geometryAnimation->deleteLater();
m_geometryAnimation = nullptr;
}

void SurfaceWrapper::finalizePrelaunchSplash()
{
Q_ASSERT (m_prelaunchSplash);

Q_ASSERT(m_prelaunchSplash);
m_prelaunchSplash->setProperty("visible", false);
m_prelaunchSplash->deleteLater();
m_prelaunchSplash = nullptr;
Q_EMIT prelaunchSplashChanged();

updateHasActiveCapability(ActiveControlState::MappedOrSplash, surface() && surface()->mapped());
if (m_shellSurface)
updateActiveState();
Q_EMIT prelaunchSplashChanged();
}

WSurface *SurfaceWrapper::surface() const
Expand Down Expand Up @@ -1059,9 +1183,6 @@ void SurfaceWrapper::updateBoundingRect()

void SurfaceWrapper::updateVisible()
{
if (m_prelaunchSplash && m_prelaunchSplash->isVisible())
return;

setVisible(!m_hideByWorkspace && !isMinimized() && (surface() && surface()->mapped())
&& m_socketEnabled && m_hideByshowDesk && !m_confirmHideByLockScreen
&& Helper::instance()->surfaceBelongsToCurrentSession(this));
Expand Down Expand Up @@ -1346,6 +1467,9 @@ void SurfaceWrapper::onMappedChanged()

Q_ASSERT(surface());
bool mapped = surface()->mapped() && !m_hideByLockScreen;
qCWarning(treelandSurface) << "[prelaunch-debug] onMappedChanged"
<< "mapped=" << mapped
<< "hasSplash=" << static_cast<bool>(m_prelaunchSplash);

if (!m_isProxy) {
if (mapped) {
Expand All @@ -1364,8 +1488,8 @@ void SurfaceWrapper::onMappedChanged()
}

if (m_prelaunchSplash) {
// The QML part will check for duplicate calls.
QMetaObject::invokeMethod(m_prelaunchSplash, "hideAndDestroy", Qt::QueuedConnection);
qCWarning(treelandSurface) << "[prelaunch-debug] onMappedChanged: trigger hide sequence";
startPrelaunchSplashHideSequence();
}

// Splash can't call onMappedChanged, just use mapped state to update
Expand Down
7 changes: 6 additions & 1 deletion src/surface/surfacewrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,9 @@ public Q_SLOTS:
void doSetSurfaceState(State newSurfaceState);
Q_SLOT void onAnimationReady();
Q_SLOT void onAnimationFinished();
Q_SLOT void onPrelaunchSplashDestroyRequested();
void startPrelaunchSplashHideSequence();
Q_SLOT void onPrelaunchGeometryAnimationReady();
Q_SLOT void onPrelaunchGeometryAnimationFinished();
bool startStateChangeAnimation(SurfaceWrapper::State targetState, const QRectF &targetGeometry);
void onWindowAnimationFinished();
Q_SLOT void onShowAnimationFinished();
Expand All @@ -386,6 +388,7 @@ public Q_SLOTS:
void startShowDesktopAnimation(bool show);
Q_SLOT void onShowDesktopAnimationFinished();
void updateHasActiveCapability(ActiveControlState state, bool value);
void finalizePrelaunchSplash();

// wayland set by treeland-dde-shell, x11 set by bypassManager/windowTypes
void setSkipDockPreView(bool skip);
Expand All @@ -405,6 +408,8 @@ public Q_SLOTS:
QPointer<QQuickItem> m_coverContent;
QPointer<QQuickItem> m_prelaunchSplash; // Pre-launch splash item
QList<WOutput *> m_prelaunchOutputs; // Outputs for pre-launch splash
QSizeF m_pendingPrelaunchImplicitSize;
QPointF m_pendingPrelaunchPosition;
QRectF m_boundedRect;
QRectF m_normalGeometry;
QRectF m_maximizedGeometry;
Expand Down
Loading