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
|
||||
|
||||
* fix: inputted password and capsIndicator button are covered
|
||||
|
|
|
|||
|
|
@ -135,6 +135,8 @@ qt_add_qml_module(libtreeland
|
|||
utils/propertymonitor.h
|
||||
utils/loginddbustypes.h
|
||||
utils/loginddbustypes.cpp
|
||||
utils/fpsdisplaymanager.cpp
|
||||
utils/fpsdisplaymanager.h
|
||||
wallpaper/wallpapercontroller.cpp
|
||||
wallpaper/wallpapercontroller.h
|
||||
wallpaper/wallpaperimage.cpp
|
||||
|
|
|
|||
|
|
@ -297,29 +297,56 @@ bool RootSurfaceContainer::filterSurfaceGeometryChanged(SurfaceWrapper *surface,
|
|||
QRectF &newGeometry,
|
||||
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;
|
||||
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());
|
||||
Qt::Edges seatEdges;
|
||||
QRectF seatStartGeometry;
|
||||
if (Helper::getSeatMoveResizeInfo(surface, seatEdges, seatStartGeometry)) {
|
||||
if (Helper::isSettingSeatPosition()) {
|
||||
return false;
|
||||
}
|
||||
if (seatEdges != 0) {
|
||||
QRectF geometry = newGeometry;
|
||||
if (seatEdges & Qt::RightEdge)
|
||||
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()) {
|
||||
newGeometry = geometry;
|
||||
moveResizeState.setSurfacePositionForAnchorEdgets = true;
|
||||
surface->setPosition(geometry.topLeft());
|
||||
moveResizeState.setSurfacePositionForAnchorEdgets = false;
|
||||
if (geometry.topLeft() != newGeometry.topLeft()) {
|
||||
newGeometry = geometry;
|
||||
Helper::setSeatPositionFlag(true);
|
||||
surface->setPosition(geometry.topLeft());
|
||||
Helper::setSeatPositionFlag(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -354,6 +354,16 @@ InputDevice *InputDevice::instance()
|
|||
|
||||
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()
|
||||
&& device->qtDevice()->type() == QInputDevice::DeviceType::TouchPad) {
|
||||
configTapEnabled(libinput_device_handle(device->handle()), LIBINPUT_CONFIG_TAP_ENABLED);
|
||||
|
|
|
|||
|
|
@ -121,7 +121,10 @@ void ForeignToplevelV1::addSurface(SurfaceWrapper *wrapper)
|
|||
Helper::instance()->forceActivateSurface(wrapper);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->minimized && wrapper->isMinimized()) {
|
||||
Helper::instance()->forceActivateSurface(wrapper);
|
||||
return;
|
||||
}
|
||||
if (event->minimized)
|
||||
wrapper->requestMinimize();
|
||||
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 <wseat.h>
|
||||
#include <wxdgdecorationmanager.h>
|
||||
#include <wseatmanager.h>
|
||||
|
||||
Q_MOC_INCLUDE(<wtoplevelsurface.h>)
|
||||
Q_MOC_INCLUDE(<wxdgsurface.h>)
|
||||
|
|
@ -50,6 +51,7 @@ class WSurfaceItem;
|
|||
class WForeignToplevel;
|
||||
class WOutputManagerV1;
|
||||
class WLayerSurface;
|
||||
class WSeatManager;
|
||||
WAYLIB_SERVER_END_NAMESPACE
|
||||
|
||||
QW_BEGIN_NAMESPACE
|
||||
|
|
@ -88,6 +90,7 @@ class IMultitaskView;
|
|||
class LockScreenInterface;
|
||||
class ILockScreen;
|
||||
class UserModel;
|
||||
class FpsDisplayManager;
|
||||
struct wlr_idle_inhibitor_v1;
|
||||
struct wlr_output_power_v1_set_mode_event;
|
||||
|
||||
|
|
@ -130,6 +133,10 @@ public:
|
|||
|
||||
static Helper *instance();
|
||||
|
||||
static bool getSeatMoveResizeInfo(SurfaceWrapper *surface, Qt::Edges &edges, QRectF &startGeometry);
|
||||
static bool isSettingSeatPosition();
|
||||
static void setSeatPositionFlag(bool setting);
|
||||
|
||||
QmlEngine *qmlEngine() const;
|
||||
WOutputRenderWindow *window() const;
|
||||
ShellHandler *shellHandler() const;
|
||||
|
|
@ -196,6 +203,25 @@ public:
|
|||
void showLockScreen();
|
||||
|
||||
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:
|
||||
void activateSurface(SurfaceWrapper *wrapper, Qt::FocusReason reason = Qt::OtherFocusReason);
|
||||
void forceActivateSurface(SurfaceWrapper *wrapper,
|
||||
|
|
@ -219,6 +245,8 @@ Q_SIGNALS:
|
|||
private Q_SLOTS:
|
||||
void onShowDesktop();
|
||||
void deleteTaskSwitch();
|
||||
void onPrepareForSleep(bool sleep);
|
||||
void onSessionNew(const QString &sessionId, const QDBusObjectPath &objectPath);
|
||||
|
||||
private:
|
||||
void onOutputAdded(WOutput *output);
|
||||
|
|
@ -242,11 +270,9 @@ private:
|
|||
void onSurfaceWrapperAboutToRemove(SurfaceWrapper *wrapper);
|
||||
void handleRequestDrag([[maybe_unused]] WSurface *surface);
|
||||
void handleLockScreen(LockScreenInterface *lockScreen);
|
||||
void onSessionNew(const QString &sessionId, const QDBusObjectPath &sessionPath);
|
||||
void onSessionLock();
|
||||
void onSessionUnlock();
|
||||
|
||||
private:
|
||||
void allowNonDrmOutputAutoChangeMode(WOutput *output);
|
||||
|
||||
int indexOfOutput(WOutput *output) const;
|
||||
|
|
@ -254,19 +280,17 @@ private:
|
|||
void setOutputProxy(Output *output);
|
||||
|
||||
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;
|
||||
void setActivatedSurface(SurfaceWrapper *newActivateSurface);
|
||||
|
||||
void setCursorPosition(const QPointF &position);
|
||||
|
||||
bool beforeDisposeEvent(WSeat *seat, QWindow *watched, QInputEvent *event) override;
|
||||
bool afterHandleEvent([[maybe_unused]] WSeat *seat,
|
||||
WSurface *watched,
|
||||
QObject *surfaceItem,
|
||||
QObject *,
|
||||
QInputEvent *event) override;
|
||||
bool unacceptedEvent(WSeat *, QWindow *, QInputEvent *event) override;
|
||||
bool beforeDisposeEvent(WSeat *seat, QWindow *window, QInputEvent *event) override;
|
||||
bool afterHandleEvent(WSeat *seat, WSurface *watched, QObject *shellObject,
|
||||
QObject *eventObject, QInputEvent *event) override;
|
||||
bool unacceptedEvent(WSeat *seat, QWindow *window, QInputEvent *event) override;
|
||||
|
||||
void handleLeftButtonStateChanged(const QInputEvent *event);
|
||||
void handleWhellValueChanged(const QInputEvent *event);
|
||||
|
|
@ -282,6 +306,26 @@ private:
|
|||
void restoreFromShowDesktop(SurfaceWrapper *activeSurface = nullptr);
|
||||
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;
|
||||
|
||||
CurrentMode m_currentMode{ CurrentMode::Normal };
|
||||
|
|
@ -345,6 +389,22 @@ private:
|
|||
|
||||
IMultitaskView *m_multitaskView{ nullptr };
|
||||
UserModel *m_userModel{ nullptr };
|
||||
FpsDisplayManager *m_fpsDisplayManager{ nullptr };
|
||||
|
||||
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();
|
||||
updateTitleBar();
|
||||
updateVisible();
|
||||
|
||||
if (surfaceItem() && surfaceItem()->eventItem()) {
|
||||
surfaceItem()->eventItem()->forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
m_showOnAllWorkspaceModel->removeActivedSurface(surface);
|
||||
} else {
|
||||
auto wpModle = modelFromId(surface->workspaceId());
|
||||
int workspaceId = surface->workspaceId();
|
||||
if (workspaceId == -1)
|
||||
return;
|
||||
auto wpModle = modelFromId(workspaceId);
|
||||
Q_ASSERT(wpModle);
|
||||
wpModle->removeActivedSurface(surface);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue