Compare commits

...

10 Commits

Author SHA1 Message Date
Lu YaNing c53b71ac1e fix : build error
add render enable interface.

cherry-pick form #516
2025-08-28 11:50:58 +08:00
Lu YaNing ab3f7cbef8 fix: resolve brief black screen during standby
1. Move D-Bus includes to fix potential header conflicts
2. Add new D-Bus signal handler for systemd-logind's PrepareForSleep
3. Implement onPrepareForSleep slot to manage rendering during hibernate
4. Improve session management with proper signal connections
5. Add logging for better debugging of power management events

The changes address the brief black screen issue that occurs during
system standby/hibernate transitions by properly managing the rendering
state through systemd-logind's PrepareForSleep signal. When entering
hibernate, we now explicitly disable rendering to prevent visual
artifacts, and re-enable it after waking up. The D-Bus signal handlers
have been reorganized for better maintainability and proper header
inclusion order.

fix: 解决待机时短暂黑屏问题

1. 移动 D-Bus 头文件包含位置以修复潜在的头文件冲突
2. 添加对 systemd-logind PrepareForSleep 信号的新 D-Bus 处理器
3. 实现 onPrepareForSleep 槽函数来管理休眠期间的渲染状态
4. 通过改进的信号连接提升会话管理功能
5. 添加日志记录以便更好地调试电源管理事件

cherry-pick from #524
2025-08-28 10:27:27 +08:00
Lu YaNing 33ef9ed72c chore(debian): update version to 0.5.21.2-test-multi-seat
Log: update version
2025-08-26 21:06:21 +08:00
Lu YaNing 2a73458a56 feat: Add FPS display
support 60/90/120/144Hz displays.
2025-08-26 20:49:42 +08:00
Lu YaNing a9e9ffec43 fix: dock unable to activate minimized windows in multi-seat environment
Treat minimize requests on already minimized windows as activation requests.

Qt issue in multi-seat mode. event->minimized is not updated when minimized.
2025-08-25 09:41:13 +08:00
Lu YaNing e2f7b34b45 fix(multiseat): resolve window resize and XWayland focus issues
- Add per-seat resize state management to prevent conflicts
- Fix XWayland transient dialog focus loss (browser About dialogs)
- Use oldGeometry anchoring to eliminate resize jitter
- Add recursion protection in filterSurfaceGeometryChanged

Bug: PMS-329615
Log: 修复多座位窗口resize冲突和XWayland对话框焦点丢失问题
2025-08-22 10:54:11 +08:00
Lu YaNing f9d7ee3dda fix: prevent XWayland dialog parent deactivation
Skip deactivating parent surface when activating XWayland dialogs
without Focus capability to prevent menu closure issues.

Bug: PMS-329607
2025-08-22 10:54:11 +08:00
Lu YaNing 0655d4787e fix: Resolve placeSmartCascaded crash in multi-seat scenarios
Multi-seat functionality increased the frequency of pushActivedSurface calls,
causing unstable/unmapped surfaces to enter the active window history. When
placeSmartCascaded accessed these half-invalidated surfaces for cascade
positioning, it crashed on null shellSurface dereferences.

Bug: PMS-327661
2025-08-12 10:52:39 +08:00
Lu YaNing 0c0ba4d817 chore(debian): update version to 0.5.21.1
Log: update version
2025-07-25 09:30:07 +08:00
Lu YaNing 41aaeda3d0 feat: Add multi-seat support
Add comprehensive multi-seat functionality to improve multi-user experience:
- Implement seat management and device assignment system
- Add seat-specific window activation and focus handling
- Support per-seat window movement and resizing
- Handle seat-specific surface interactions and events

This enables multiple users to independently interact with the system using
different input devices simultaneously.
2025-07-24 21:19:31 +08:00
11 changed files with 1913 additions and 260 deletions

12
debian/changelog vendored
View File

@ -1,3 +1,15 @@
treeland (0.5.21.2-test-multi-seat) unstable; urgency=medium
* feat: Add FPS display.
-- Lu YaNing <luyaning@uniontech.com> Tue, 26 Aug 2025 20:52:43 +0800
treeland (0.5.21.1) unstable; urgency=medium
* feat: Add multi-seat support.
-- Lu YaNing <luyaning@uniontech.com> Fri, 25 Jul 2025 08:34:28 +0800
treeland (0.5.21) unstable; urgency=medium treeland (0.5.21) unstable; urgency=medium
* fix: inputted password and capsIndicator button are covered * fix: inputted password and capsIndicator button are covered

View File

@ -135,6 +135,8 @@ qt_add_qml_module(libtreeland
utils/propertymonitor.h utils/propertymonitor.h
utils/loginddbustypes.h utils/loginddbustypes.h
utils/loginddbustypes.cpp utils/loginddbustypes.cpp
utils/fpsdisplaymanager.cpp
utils/fpsdisplaymanager.h
wallpaper/wallpapercontroller.cpp wallpaper/wallpapercontroller.cpp
wallpaper/wallpapercontroller.h wallpaper/wallpapercontroller.h
wallpaper/wallpaperimage.cpp wallpaper/wallpaperimage.cpp

View File

@ -297,29 +297,56 @@ bool RootSurfaceContainer::filterSurfaceGeometryChanged(SurfaceWrapper *surface,
QRectF &newGeometry, QRectF &newGeometry,
const QRectF &oldGeometry) const QRectF &oldGeometry)
{ {
if (surface != moveResizeState.surface) if (surface == moveResizeState.surface) {
if (moveResizeState.setSurfacePositionForAnchorEdgets) {
Q_ASSERT(newGeometry.size() == oldGeometry.size());
return true;
}
if (moveResizeState.resizeEdges != 0) {
QRectF geometry = newGeometry;
if (moveResizeState.resizeEdges & Qt::RightEdge)
geometry.moveLeft(oldGeometry.left());
if (moveResizeState.resizeEdges & Qt::BottomEdge)
geometry.moveTop(oldGeometry.top());
if (moveResizeState.resizeEdges & Qt::LeftEdge)
geometry.moveRight(oldGeometry.right());
if (moveResizeState.resizeEdges & Qt::TopEdge)
geometry.moveBottom(oldGeometry.bottom());
if (geometry.topLeft() != newGeometry.topLeft()) {
newGeometry = geometry;
moveResizeState.setSurfacePositionForAnchorEdgets = true;
surface->setPosition(geometry.topLeft());
moveResizeState.setSurfacePositionForAnchorEdgets = false;
}
}
return false; return false;
if (moveResizeState.setSurfacePositionForAnchorEdgets) {
Q_ASSERT(newGeometry.size() == oldGeometry.size());
return true;
} }
if (moveResizeState.resizeEdges != 0) { Qt::Edges seatEdges;
QRectF geometry = newGeometry; QRectF seatStartGeometry;
if (moveResizeState.resizeEdges & Qt::RightEdge) if (Helper::getSeatMoveResizeInfo(surface, seatEdges, seatStartGeometry)) {
geometry.moveLeft(oldGeometry.left()); if (Helper::isSettingSeatPosition()) {
if (moveResizeState.resizeEdges & Qt::BottomEdge) return false;
geometry.moveTop(oldGeometry.top()); }
if (moveResizeState.resizeEdges & Qt::LeftEdge) if (seatEdges != 0) {
geometry.moveRight(oldGeometry.right()); QRectF geometry = newGeometry;
if (moveResizeState.resizeEdges & Qt::TopEdge) if (seatEdges & Qt::RightEdge)
geometry.moveBottom(oldGeometry.bottom()); geometry.moveLeft(oldGeometry.left());
if (seatEdges & Qt::BottomEdge)
geometry.moveTop(oldGeometry.top());
if (seatEdges & Qt::LeftEdge)
geometry.moveRight(oldGeometry.right());
if (seatEdges & Qt::TopEdge)
geometry.moveBottom(oldGeometry.bottom());
if (geometry.topLeft() != newGeometry.topLeft()) { if (geometry.topLeft() != newGeometry.topLeft()) {
newGeometry = geometry; newGeometry = geometry;
moveResizeState.setSurfacePositionForAnchorEdgets = true; Helper::setSeatPositionFlag(true);
surface->setPosition(geometry.topLeft()); surface->setPosition(geometry.topLeft());
moveResizeState.setSurfacePositionForAnchorEdgets = false; Helper::setSeatPositionFlag(false);
}
} }
} }

View File

@ -354,6 +354,16 @@ InputDevice *InputDevice::instance()
bool InputDevice::initTouchPad(WInputDevice *device) bool InputDevice::initTouchPad(WInputDevice *device)
{ {
if (!device) {
qWarning() << "Cannot initialize touchpad for null device";
return false;
}
if (!device->qtDevice()) {
qWarning() << "Cannot initialize touchpad: device has no qtDevice";
return false;
}
if (device->handle()->is_libinput() if (device->handle()->is_libinput()
&& device->qtDevice()->type() == QInputDevice::DeviceType::TouchPad) { && device->qtDevice()->type() == QInputDevice::DeviceType::TouchPad) {
configTapEnabled(libinput_device_handle(device->handle()), LIBINPUT_CONFIG_TAP_ENABLED); configTapEnabled(libinput_device_handle(device->handle()), LIBINPUT_CONFIG_TAP_ENABLED);

View File

@ -121,7 +121,10 @@ void ForeignToplevelV1::addSurface(SurfaceWrapper *wrapper)
Helper::instance()->forceActivateSurface(wrapper); Helper::instance()->forceActivateSurface(wrapper);
return; return;
} }
if (event->minimized && wrapper->isMinimized()) {
Helper::instance()->forceActivateSurface(wrapper);
return;
}
if (event->minimized) if (event->minimized)
wrapper->requestMinimize(); wrapper->requestMinimize();
else else

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,7 @@
#include <wqmlcreator.h> #include <wqmlcreator.h>
#include <wseat.h> #include <wseat.h>
#include <wxdgdecorationmanager.h> #include <wxdgdecorationmanager.h>
#include <wseatmanager.h>
Q_MOC_INCLUDE(<wtoplevelsurface.h>) Q_MOC_INCLUDE(<wtoplevelsurface.h>)
Q_MOC_INCLUDE(<wxdgsurface.h>) Q_MOC_INCLUDE(<wxdgsurface.h>)
@ -50,6 +51,7 @@ class WSurfaceItem;
class WForeignToplevel; class WForeignToplevel;
class WOutputManagerV1; class WOutputManagerV1;
class WLayerSurface; class WLayerSurface;
class WSeatManager;
WAYLIB_SERVER_END_NAMESPACE WAYLIB_SERVER_END_NAMESPACE
QW_BEGIN_NAMESPACE QW_BEGIN_NAMESPACE
@ -88,6 +90,7 @@ class IMultitaskView;
class LockScreenInterface; class LockScreenInterface;
class ILockScreen; class ILockScreen;
class UserModel; class UserModel;
class FpsDisplayManager;
struct wlr_idle_inhibitor_v1; struct wlr_idle_inhibitor_v1;
struct wlr_output_power_v1_set_mode_event; struct wlr_output_power_v1_set_mode_event;
@ -130,6 +133,10 @@ public:
static Helper *instance(); static Helper *instance();
static bool getSeatMoveResizeInfo(SurfaceWrapper *surface, Qt::Edges &edges, QRectF &startGeometry);
static bool isSettingSeatPosition();
static void setSeatPositionFlag(bool setting);
QmlEngine *qmlEngine() const; QmlEngine *qmlEngine() const;
WOutputRenderWindow *window() const; WOutputRenderWindow *window() const;
ShellHandler *shellHandler() const; ShellHandler *shellHandler() const;
@ -196,6 +203,25 @@ public:
void showLockScreen(); void showLockScreen();
Output* getOutputAtCursor() const; Output* getOutputAtCursor() const;
WSeatManager *seatManager() const;
void initMultiSeat();
void loadSeatConfig();
void saveSeatConfig();
bool validateSeatConfig(const QJsonObject &config) const;
void checkAndFixSeatDevices();
void assignDeviceToSeat(WInputDevice *device);
WSeat *getSeatForDevice(WInputDevice *device) const;
WSeat *getSeatForEvent(QInputEvent *event) const;
WSeat *findSeatForSurface(SurfaceWrapper *wrapper) const;
void setActivatedSurfaceForSeat(WSeat *seat, SurfaceWrapper *surface);
SurfaceWrapper *getActivatedSurfaceForSeat(WSeat *seat) const;
void toggleFpsDisplay();
void enableRender();
void disableRender();
public Q_SLOTS: public Q_SLOTS:
void activateSurface(SurfaceWrapper *wrapper, Qt::FocusReason reason = Qt::OtherFocusReason); void activateSurface(SurfaceWrapper *wrapper, Qt::FocusReason reason = Qt::OtherFocusReason);
void forceActivateSurface(SurfaceWrapper *wrapper, void forceActivateSurface(SurfaceWrapper *wrapper,
@ -219,6 +245,8 @@ Q_SIGNALS:
private Q_SLOTS: private Q_SLOTS:
void onShowDesktop(); void onShowDesktop();
void deleteTaskSwitch(); void deleteTaskSwitch();
void onPrepareForSleep(bool sleep);
void onSessionNew(const QString &sessionId, const QDBusObjectPath &objectPath);
private: private:
void onOutputAdded(WOutput *output); void onOutputAdded(WOutput *output);
@ -242,11 +270,9 @@ private:
void onSurfaceWrapperAboutToRemove(SurfaceWrapper *wrapper); void onSurfaceWrapperAboutToRemove(SurfaceWrapper *wrapper);
void handleRequestDrag([[maybe_unused]] WSurface *surface); void handleRequestDrag([[maybe_unused]] WSurface *surface);
void handleLockScreen(LockScreenInterface *lockScreen); void handleLockScreen(LockScreenInterface *lockScreen);
void onSessionNew(const QString &sessionId, const QDBusObjectPath &sessionPath);
void onSessionLock(); void onSessionLock();
void onSessionUnlock(); void onSessionUnlock();
private:
void allowNonDrmOutputAutoChangeMode(WOutput *output); void allowNonDrmOutputAutoChangeMode(WOutput *output);
int indexOfOutput(WOutput *output) const; int indexOfOutput(WOutput *output) const;
@ -254,19 +280,17 @@ private:
void setOutputProxy(Output *output); void setOutputProxy(Output *output);
SurfaceWrapper *keyboardFocusSurface() const; SurfaceWrapper *keyboardFocusSurface() const;
void requestKeyboardFocusForSurface(SurfaceWrapper *newActivateSurface, Qt::FocusReason reason); void requestKeyboardFocusForSurfaceForSeat(WSeat *seat, SurfaceWrapper *newActivate, Qt::FocusReason reason);
SurfaceWrapper *getKeyboardFocusSurfaceForSeat(WSeat *seat) const;
SurfaceWrapper *activatedSurface() const; SurfaceWrapper *activatedSurface() const;
void setActivatedSurface(SurfaceWrapper *newActivateSurface); void setActivatedSurface(SurfaceWrapper *newActivateSurface);
void setCursorPosition(const QPointF &position); void setCursorPosition(const QPointF &position);
bool beforeDisposeEvent(WSeat *seat, QWindow *watched, QInputEvent *event) override; bool beforeDisposeEvent(WSeat *seat, QWindow *window, QInputEvent *event) override;
bool afterHandleEvent([[maybe_unused]] WSeat *seat, bool afterHandleEvent(WSeat *seat, WSurface *watched, QObject *shellObject,
WSurface *watched, QObject *eventObject, QInputEvent *event) override;
QObject *surfaceItem, bool unacceptedEvent(WSeat *seat, QWindow *window, QInputEvent *event) override;
QObject *,
QInputEvent *event) override;
bool unacceptedEvent(WSeat *, QWindow *, QInputEvent *event) override;
void handleLeftButtonStateChanged(const QInputEvent *event); void handleLeftButtonStateChanged(const QInputEvent *event);
void handleWhellValueChanged(const QInputEvent *event); void handleWhellValueChanged(const QInputEvent *event);
@ -282,6 +306,26 @@ private:
void restoreFromShowDesktop(SurfaceWrapper *activeSurface = nullptr); void restoreFromShowDesktop(SurfaceWrapper *activeSurface = nullptr);
void updateIdleInhibitor(); void updateIdleInhibitor();
void setupSeatsConfiguration();
void connectDeviceSignals();
void assignExistingDevices();
void beginMoveResizeForSeat(WSeat *seat, SurfaceWrapper *surface, Qt::Edges edges);
void endMoveResizeForSeat(WSeat *seat);
SurfaceWrapper *getMoveResizeSurfaceForSeat(WSeat *seat) const;
void doMoveResizeForSeat(WSeat *seat, const QPointF &delta);
WSeat *getLastInteractingSeat(SurfaceWrapper *surface) const;
void updateSurfaceSeatInteraction(SurfaceWrapper *surface, WSeat *seat);
void switchWorkspaceForSeat(WSeat *seat, int index);
void handleRequestDragForSeat(WSeat *seat, WSurface *surface);
bool getSingleMetaKeyPendingPressed(WSeat *seat) const;
void setSingleMetaKeyPendingPressed(WSeat *seat, bool pressed);
WSeat *m_currentEventSeat = nullptr;
static Helper *m_instance; static Helper *m_instance;
CurrentMode m_currentMode{ CurrentMode::Normal }; CurrentMode m_currentMode{ CurrentMode::Normal };
@ -345,6 +389,22 @@ private:
IMultitaskView *m_multitaskView{ nullptr }; IMultitaskView *m_multitaskView{ nullptr };
UserModel *m_userModel{ nullptr }; UserModel *m_userModel{ nullptr };
FpsDisplayManager *m_fpsDisplayManager{ nullptr };
quint32 m_atomDeepinNoTitlebar; quint32 m_atomDeepinNoTitlebar;
WSeatManager *m_seatManager = nullptr;
QString m_seatConfigPath;
QMap<WSeat*, SurfaceWrapper*> m_seatActivatedSurfaces;
struct SeatMoveResizeState {
SurfaceWrapper *surface = nullptr;
Qt::Edges edges;
QRectF startGeometry;
QPointF initialPosition;
};
QMap<WSeat*, SeatMoveResizeState> m_seatMoveResizeStates;
QMap<WSeat*, QPointF> m_seatLastPressedPositions;
QMap<WSeat*, bool> m_seatMetaKeyStates;
QMap<WSeat*, SurfaceWrapper*> m_seatKeyboardFocusSurfaces;
}; };

View File

@ -890,6 +890,10 @@ void SurfaceWrapper::doSetSurfaceState(State newSurfaceState)
m_surfaceState.notify(); m_surfaceState.notify();
updateTitleBar(); updateTitleBar();
updateVisible(); updateVisible();
if (surfaceItem() && surfaceItem()->eventItem()) {
surfaceItem()->eventItem()->forceActiveFocus();
}
} }
void SurfaceWrapper::onAnimationReady() void SurfaceWrapper::onAnimationReady()

View File

@ -0,0 +1,444 @@
// Copyright (C) 2025 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 "fpsdisplaymanager.h"
#include <QQuickWindow>
#include <QQuickItem>
#include <QLoggingCategory>
#include <QPainter>
#include <QQuickPaintedItem>
#include <QScreen>
#include <QFontDatabase>
Q_LOGGING_CATEGORY(qLcFpsDisplay, "treeland.fpsdisplay")
FpsDisplayManager::FpsDisplayManager(QObject *parent)
: QObject(parent)
, m_updateTimer(this)
, m_vsyncTimer(this)
{
m_timer.start();
m_updateTimer.setInterval(UPDATE_INTERVAL_MS);
m_updateTimer.setSingleShot(false);
connect(&m_updateTimer, &QTimer::timeout, this, &FpsDisplayManager::updateFps);
detectDisplayRefreshRate();
double preciseInterval = 1000.0 / m_displayRefreshRate;
int vsyncInterval = qRound(preciseInterval);
m_preciseVSyncInterval = preciseInterval;
// Optimize timer intervals for common refresh rates
if (m_displayRefreshRate == 60) {
vsyncInterval = 17; // 17ms for QTimer (closest to 16.67ms)
m_preciseVSyncInterval = 16.666667; // Exact 60Hz interval (1000/60)
} else if (m_displayRefreshRate == 90) {
vsyncInterval = 11; // 11ms for QTimer (closest to 11.11ms)
m_preciseVSyncInterval = 11.111111; // Exact 90Hz interval (1000/90)
} else if (m_displayRefreshRate == 120) {
vsyncInterval = 8; // 8ms for QTimer (closest to 8.33ms)
m_preciseVSyncInterval = 8.333333; // Exact 120Hz interval (1000/120)
} else if (m_displayRefreshRate == 144) {
vsyncInterval = 7; // 7ms for QTimer (closest to 6.94ms)
m_preciseVSyncInterval = 6.944444; // Exact 144Hz interval (1000/144)
}
m_vsyncTimer.setInterval(vsyncInterval);
m_vsyncTimer.setSingleShot(false);
connect(&m_vsyncTimer, &QTimer::timeout, this, &FpsDisplayManager::onVSyncTimer);
}
FpsDisplayManager::~FpsDisplayManager()
{
destroyDisplayItem();
}
void FpsDisplayManager::setTargetWindow(QQuickWindow *window)
{
if (m_targetWindow == window)
return;
destroyDisplayItem();
m_targetWindow = window;
if (m_targetWindow) {
detectDisplayRefreshRate();
double preciseInterval = 1000.0 / m_displayRefreshRate;
int vsyncInterval = qRound(preciseInterval);
m_preciseVSyncInterval = preciseInterval;
// Optimize timer intervals for common refresh rates
if (m_displayRefreshRate == 60) {
vsyncInterval = 17; // 17ms for QTimer (closest to 16.67ms)
m_preciseVSyncInterval = 16.666667; // Exact 60Hz interval (1000/60)
} else if (m_displayRefreshRate == 90) {
vsyncInterval = 11; // 11ms for QTimer (closest to 11.11ms)
m_preciseVSyncInterval = 11.111111; // Exact 90Hz interval (1000/90)
} else if (m_displayRefreshRate == 120) {
vsyncInterval = 8; // 8ms for QTimer (closest to 8.33ms)
m_preciseVSyncInterval = 8.333333; // Exact 120Hz interval (1000/120)
} else if (m_displayRefreshRate == 144) {
vsyncInterval = 7; // 7ms for QTimer (closest to 6.94ms)
m_preciseVSyncInterval = 6.944444; // Exact 144Hz interval (1000/144)
}
m_vsyncTimer.setInterval(vsyncInterval);
}
if (m_visible && m_targetWindow) {
createDisplayItem();
}
}
void FpsDisplayManager::show()
{
if (m_visible && m_displayItem)
return;
m_visible = true;
if (m_targetWindow && !m_displayItem) {
createDisplayItem();
m_updateTimer.start();
m_vsyncTimer.start();
reset();
}
}
void FpsDisplayManager::hide()
{
if (!m_visible)
return;
m_visible = false;
m_updateTimer.stop();
m_vsyncTimer.stop();
destroyDisplayItem();
}
void FpsDisplayManager::toggle()
{
if (m_visible) {
hide();
} else {
show();
}
}
void FpsDisplayManager::onVSyncTimer()
{
if (!m_visible)
return;
qint64 currentTime = m_timer.elapsed();
if (m_lastVSyncTime_precise > 0) {
qint64 actualInterval = currentTime - m_lastVSyncTime_precise;
double expectedInterval = m_preciseVSyncInterval;
if (actualInterval > expectedInterval * 1.5 || actualInterval < expectedInterval * 0.5) { // 50% tolerance for timer precision
m_lastVSyncTime_precise = currentTime;
return;
}
}
m_vSyncTimes.enqueue(currentTime);
m_lastVSyncTime_precise = currentTime;
while (m_vSyncTimes.size() > MAX_SAMPLES) { // Keep last 120 samples (2 seconds at 60Hz)
m_vSyncTimes.dequeue();
}
}
void FpsDisplayManager::reset()
{
m_currentFps = m_displayRefreshRate;
m_averageFps = m_displayRefreshRate;
m_maximumFps = m_displayRefreshRate;
m_lastUpdateTime = m_timer.elapsed();
m_vSyncTimes.clear();
m_lastVSyncTime_precise = 0;
if (m_displayItem) {
updateFpsText();
}
}
void FpsDisplayManager::updateFps()
{
if (!m_visible || m_vSyncTimes.isEmpty())
return;
qint64 currentTime = m_timer.elapsed();
qint64 timeDiff = currentTime - m_lastUpdateTime;
if (timeDiff < UPDATE_INTERVAL_MS) // Update UI every 500ms for responsiveness
return;
qreal currentFps = 0.0;
qreal averageFps = 0.0;
if (m_vSyncTimes.size() >= 2) {
qint64 totalTimeSpan = m_vSyncTimes.last() - m_vSyncTimes.first();
if (totalTimeSpan > 0) {
averageFps = ((m_vSyncTimes.size() - 1) * 1000.0) / totalTimeSpan;
}
if (m_vSyncTimes.size() >= 3) { // Need minimum 3 samples for instantaneous FPS
qint64 recentTimeSpan = m_vSyncTimes.last() - m_vSyncTimes[m_vSyncTimes.size() - 3];
if (recentTimeSpan > 0) {
currentFps = (2 * 1000.0) / recentTimeSpan;
}
} else {
currentFps = averageFps;
}
} else if (m_vSyncTimes.size() == 1) {
currentFps = averageFps = m_displayRefreshRate;
}
if (currentFps > 0 || averageFps > 0) {
const qreal smoothingFactor = 0.3; // 30% new value, 70% previous for smooth transitions
if (m_currentFps == 0.0) {
m_currentFps = currentFps;
m_averageFps = averageFps;
} else {
m_currentFps = m_currentFps * (1.0 - smoothingFactor) + currentFps * smoothingFactor;
m_averageFps = m_averageFps * (1.0 - smoothingFactor) + averageFps * smoothingFactor;
}
double maxFps = m_displayRefreshRate + 1.0; // Allow 1 FPS tolerance above refresh rate
m_currentFps = qBound(1.0, m_currentFps, maxFps);
m_averageFps = qBound(1.0, m_averageFps, maxFps);
if (m_currentFps > m_maximumFps) {
m_maximumFps = m_currentFps;
}
}
m_lastUpdateTime = currentTime;
updateFpsText();
}
void FpsDisplayManager::detectDisplayRefreshRate()
{
m_displayRefreshRate = 60; // Default 60Hz fallback for most displays
if (m_targetWindow) {
if (auto screen = m_targetWindow->screen()) {
qreal refreshRate = screen->refreshRate();
if (refreshRate > 0) {
m_displayRefreshRate = qRound(refreshRate);
return;
}
}
}
// Environment variable override for testing/debugging
QString envRefreshRate = qgetenv("TREELAND_REFRESH_RATE");
if (!envRefreshRate.isEmpty()) {
bool ok;
int rate = envRefreshRate.toInt(&ok);
if (ok && rate > 0 && rate <= 240) { // Support up to 240Hz displays
m_displayRefreshRate = rate;
}
}
}
class FpsDisplayItem : public QQuickPaintedItem
{
Q_OBJECT
public:
explicit FpsDisplayItem(QQuickItem *parent = nullptr)
: QQuickPaintedItem(parent)
{
setFlag(QQuickItem::ItemAcceptsInputMethod, false);
setFlag(QQuickItem::ItemAcceptsDrops, false);
setFlag(QQuickItem::ItemIsFocusScope, false);
setFlag(QQuickItem::ItemHasContents, true);
setAcceptedMouseButtons(Qt::NoButton);
setAcceptHoverEvents(false);
setAcceptTouchEvents(false);
setActiveFocusOnTab(false);
setFocus(false);
setEnabled(false);
setVisible(false);
setWidth(130);
setHeight(45);
}
void paint(QPainter *painter) override
{
painter->setRenderHint(QPainter::Antialiasing);
painter->setRenderHint(QPainter::TextAntialiasing);
QFont font;
qreal scaleFactor = 1.0;
if (width() > 150) {
scaleFactor = width() / 130.0;
}
int fontSize = qRound(14 * scaleFactor);
font.setPixelSize(fontSize);
font.setWeight(QFont::Normal);
font.setHintingPreference(QFont::PreferFullHinting);
painter->setFont(font);
qreal lineHeight = height() / 2.2;
qreal lineSpacing = height() / 2.0;
painter->setPen(QColor(255, 255, 255, 150));
painter->drawText(QRectF(1, 1, width(), lineHeight), Qt::AlignLeft | Qt::AlignVCenter, m_fpsText);
painter->drawText(QRectF(1, lineSpacing + 1, width(), lineHeight), Qt::AlignLeft | Qt::AlignVCenter, m_avgFpsText);
painter->setPen(QColor(0, 0, 0));
painter->drawText(QRectF(0, 0, width(), lineHeight), Qt::AlignLeft | Qt::AlignVCenter, m_fpsText);
painter->drawText(QRectF(0, lineSpacing, width(), lineHeight), Qt::AlignLeft | Qt::AlignVCenter, m_avgFpsText);
}
void setFpsText(const QString &text, const QColor &color)
{
if (m_fpsText != text || m_fpsColor != color) {
m_fpsText = text;
m_fpsColor = color;
update();
}
}
void setAvgFpsText(const QString &text)
{
if (m_avgFpsText != text) {
m_avgFpsText = text;
update();
}
}
protected:
// Override all event handlers to ensure complete input isolation
bool event(QEvent *event) override
{
// Only handle paint-related events, reject all input and focus events
switch (event->type()) {
case QEvent::Paint:
case QEvent::UpdateRequest:
case QEvent::Polish:
case QEvent::PolishRequest:
return QQuickPaintedItem::event(event);
case QEvent::FocusIn:
case QEvent::FocusOut:
case QEvent::FocusAboutToChange:
case QEvent::KeyPress:
case QEvent::KeyRelease:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseMove:
case QEvent::Enter:
case QEvent::Leave:
case QEvent::HoverEnter:
case QEvent::HoverLeave:
case QEvent::HoverMove:
case QEvent::TouchBegin:
case QEvent::TouchUpdate:
case QEvent::TouchEnd:
case QEvent::InputMethod:
case QEvent::InputMethodQuery:
event->ignore();
return false;
default:
if (event->type() >= QEvent::User) {
event->ignore();
return false;
}
return QQuickPaintedItem::event(event);
}
}
void mousePressEvent(QMouseEvent *event) override { event->ignore(); }
void mouseReleaseEvent(QMouseEvent *event) override { event->ignore(); }
void mouseMoveEvent(QMouseEvent *event) override { event->ignore(); }
void keyPressEvent(QKeyEvent *event) override { event->ignore(); }
void keyReleaseEvent(QKeyEvent *event) override { event->ignore(); }
void focusInEvent(QFocusEvent *event) override { event->ignore(); }
void focusOutEvent(QFocusEvent *event) override { event->ignore(); }
void hoverEnterEvent(QHoverEvent *event) override { event->ignore(); }
void hoverLeaveEvent(QHoverEvent *event) override { event->ignore(); }
void hoverMoveEvent(QHoverEvent *event) override { event->ignore(); }
void touchEvent(QTouchEvent *event) override { event->ignore(); }
private:
QString m_fpsText = "Current FPS: 0";
QString m_avgFpsText = "Maximum FPS: 0";
QColor m_fpsColor = QColor(255, 255, 255);
};
void FpsDisplayManager::createDisplayItem()
{
if (!m_targetWindow || m_displayItem)
return;
auto displayItem = new FpsDisplayItem(nullptr);
displayItem->setParent(m_targetWindow);
displayItem->setParentItem(m_targetWindow->contentItem());
qreal windowWidth = m_targetWindow->contentItem()->width();
qreal scaleFactor = 1.0;
if (windowWidth > 2560) {
scaleFactor = 1.5; // 4K+ displays need 50% larger UI
} else if (windowWidth > 1920) {
scaleFactor = 1.2; // 2K displays need 20% larger UI
}
qreal displayWidth = 130 * scaleFactor;
qreal displayHeight = 45 * scaleFactor;
qreal margin = 20 * scaleFactor;
qreal topOffset = 40 * scaleFactor;
displayItem->setWidth(displayWidth);
displayItem->setHeight(displayHeight);
displayItem->setX(windowWidth - displayWidth - margin);
displayItem->setY(topOffset);
displayItem->setZ(99999); // Ensure FPS display is always on top
m_displayItem = displayItem;
updateFpsText();
QMetaObject::invokeMethod(this, [this]() {
if (m_displayItem) {
m_displayItem->setVisible(true);
m_displayItem->setEnabled(false);
m_displayItem->setFocus(false);
m_displayItem->setActiveFocusOnTab(false);
if (m_targetWindow && m_targetWindow->contentItem()) {
auto contentItem = m_targetWindow->contentItem();
if (!contentItem->hasActiveFocus() && !contentItem->hasFocus()) {
contentItem->setFocus(true);
}
}
}
}, Qt::QueuedConnection);
}
void FpsDisplayManager::destroyDisplayItem()
{
if (m_displayItem) {
m_displayItem->deleteLater();
m_displayItem = nullptr;
}
}
void FpsDisplayManager::updateFpsText()
{
auto displayItem = qobject_cast<FpsDisplayItem*>(m_displayItem);
if (!displayItem)
return;
QString currentFpsText = QString("Current FPS: %1").arg(qRound(m_currentFps));
QString maximumFpsText = QString("Maximum FPS: %1").arg(qRound(m_maximumFps));
QColor fpsColor = QColor(255, 255, 255);
displayItem->setFpsText(currentFpsText, fpsColor);
displayItem->setAvgFpsText(maximumFpsText);
}
#include "fpsdisplaymanager.moc"

View File

@ -0,0 +1,62 @@
// Copyright (C) 2025 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 <QObject>
#include <QTimer>
#include <QElapsedTimer>
#include <QQueue>
#include <QPointer>
class QQuickItem;
class QQuickWindow;
class FpsDisplayManager : public QObject
{
Q_OBJECT
public:
explicit FpsDisplayManager(QObject *parent = nullptr);
~FpsDisplayManager();
void setTargetWindow(QQuickWindow *window);
void show();
void hide();
void toggle();
void reset();
bool isVisible() const { return m_visible; }
private slots:
void updateFps();
void onVSyncTimer();
private:
void createDisplayItem();
void destroyDisplayItem();
void updateFpsText();
QPointer<QQuickWindow> m_targetWindow;
QPointer<QQuickItem> m_displayItem;
QElapsedTimer m_timer;
QTimer m_updateTimer;
QTimer m_vsyncTimer;
QQueue<qint64> m_vSyncTimes;
qreal m_currentFps = 0.0;
qreal m_averageFps = 0.0;
qreal m_maximumFps = 0.0;
qint64 m_lastUpdateTime = 0;
qint64 m_lastVSyncTime_precise = 0;
int m_displayRefreshRate = 60;
double m_preciseVSyncInterval = 16.67;
bool m_visible = false;
void detectDisplayRefreshRate();
static constexpr int MAX_SAMPLES = 120; // 2 seconds of samples at 60Hz
static constexpr int UPDATE_INTERVAL_MS = 500; // UI update frequency in milliseconds
};

View File

@ -376,7 +376,10 @@ void Workspace::removeActivedSurface(SurfaceWrapper *surface)
wpModle->removeActivedSurface(surface); wpModle->removeActivedSurface(surface);
m_showOnAllWorkspaceModel->removeActivedSurface(surface); m_showOnAllWorkspaceModel->removeActivedSurface(surface);
} else { } else {
auto wpModle = modelFromId(surface->workspaceId()); int workspaceId = surface->workspaceId();
if (workspaceId == -1)
return;
auto wpModle = modelFromId(workspaceId);
Q_ASSERT(wpModle); Q_ASSERT(wpModle);
wpModle->removeActivedSurface(surface); wpModle->removeActivedSurface(surface);
} }