Compare commits
10 Commits
master
...
multi-seat
| Author | SHA1 | Date |
|---|---|---|
|
|
c53b71ac1e | |
|
|
ab3f7cbef8 | |
|
|
33ef9ed72c | |
|
|
2a73458a56 | |
|
|
a9e9ffec43 | |
|
|
e2f7b34b45 | |
|
|
f9d7ee3dda | |
|
|
0655d4787e | |
|
|
0c0ba4d817 | |
|
|
41aaeda3d0 |
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
1484
src/seat/helper.cpp
1484
src/seat/helper.cpp
File diff suppressed because it is too large
Load Diff
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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
|
||||||
|
};
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue