Compare commits

...

2 commits

Author SHA1 Message Date
cb2862eca9
wayland/toplevel_management: add ToplevelManager.activeToplevel 2024-07-31 23:10:08 -07:00
9555b201fe
core/clock: fix instability causing timer to fire multiple times
If the signal was fired slightly before the scheduled time, it would
schedule itself again a couple ms in the future.
2024-07-31 23:09:39 -07:00
4 changed files with 111 additions and 31 deletions

View file

@ -8,7 +8,7 @@
#include "util.hpp"
SystemClock::SystemClock(QObject* parent): QObject(parent) {
QObject::connect(&this->timer, &QTimer::timeout, this, &SystemClock::update);
QObject::connect(&this->timer, &QTimer::timeout, this, &SystemClock::onTimeout);
this->update();
}
@ -30,41 +30,63 @@ void SystemClock::setPrecision(SystemClock::Enum precision) {
this->update();
}
void SystemClock::onTimeout() {
this->setTime(this->nextTime);
this->schedule(this->nextTime);
}
void SystemClock::update() {
auto time = QTime::currentTime();
if (this->mEnabled) {
auto secondPrecision = this->mPrecision >= SystemClock::Seconds;
auto secondChanged = this->setSeconds(secondPrecision ? time.second() : 0);
auto minutePrecision = this->mPrecision >= SystemClock::Minutes;
auto minuteChanged = this->setMinutes(minutePrecision ? time.minute() : 0);
auto hourPrecision = this->mPrecision >= SystemClock::Hours;
auto hourChanged = this->setHours(hourPrecision ? time.hour() : 0);
DropEmitter::call(secondChanged, minuteChanged, hourChanged);
auto nextTime = QTime(
hourPrecision ? time.hour() : 0,
minutePrecision ? time.minute() : 0,
secondPrecision ? time.second() : 0
);
if (secondPrecision) nextTime = nextTime.addSecs(1);
else if (minutePrecision) nextTime = nextTime.addSecs(60);
else if (hourPrecision) nextTime = nextTime.addSecs(3600);
auto delay = time.msecsTo(nextTime);
// day rollover
if (delay < 0) delay += 86400000;
this->timer.start(delay);
this->setTime(QTime::currentTime());
this->schedule(QTime::currentTime());
} else {
this->timer.stop();
}
}
void SystemClock::setTime(QTime time) {
auto secondPrecision = this->mPrecision >= SystemClock::Seconds;
auto secondChanged = this->setSeconds(secondPrecision ? time.second() : 0);
auto minutePrecision = this->mPrecision >= SystemClock::Minutes;
auto minuteChanged = this->setMinutes(minutePrecision ? time.minute() : 0);
auto hourPrecision = this->mPrecision >= SystemClock::Hours;
auto hourChanged = this->setHours(hourPrecision ? time.hour() : 0);
DropEmitter::call(secondChanged, minuteChanged, hourChanged);
}
void SystemClock::schedule(QTime floor) {
auto secondPrecision = this->mPrecision >= SystemClock::Seconds;
auto minutePrecision = this->mPrecision >= SystemClock::Minutes;
auto hourPrecision = this->mPrecision >= SystemClock::Hours;
setnext:
auto nextTime = QTime(
hourPrecision ? floor.hour() : 0,
minutePrecision ? floor.minute() : 0,
secondPrecision ? floor.second() : 0
);
if (secondPrecision) nextTime = nextTime.addSecs(1);
else if (minutePrecision) nextTime = nextTime.addSecs(60);
else if (hourPrecision) nextTime = nextTime.addSecs(3600);
auto delay = QTime::currentTime().msecsTo(nextTime);
// If off by more than 2 hours we likely wrapped around midnight.
if (delay < -7200000) delay += 86400000;
else if (delay < 0) {
// Otherwise its just the timer being unstable.
floor = QTime::currentTime();
goto setnext;
}
this->timer.start(delay);
this->nextTime = nextTime;
}
DEFINE_MEMBER_GETSET(SystemClock, hours, setHours);
DEFINE_MEMBER_GETSET(SystemClock, minutes, setMinutes);
DEFINE_MEMBER_GETSET(SystemClock, seconds, setSeconds);

View file

@ -1,5 +1,6 @@
#pragma once
#include <qdatetime.h>
#include <qobject.h>
#include <qqmlintegration.h>
#include <qtimer.h>
@ -49,7 +50,7 @@ signals:
void secondsChanged();
private slots:
void update();
void onTimeout();
private:
bool mEnabled = true;
@ -58,6 +59,11 @@ private:
quint32 mMinutes = 0;
quint32 mSeconds = 0;
QTimer timer;
QTime nextTime;
void update();
void setTime(QTime time);
void schedule(QTime floor);
DECLARE_PRIVATE_MEMBER(SystemClock, hours, setHours, mHours, hoursChanged);
DECLARE_PRIVATE_MEMBER(SystemClock, minutes, setMinutes, mMinutes, minutesChanged);

View file

@ -3,6 +3,7 @@
#include <qobject.h>
#include <qtmetamacros.h>
#include "../../core/util.hpp"
#include "../../core/model.hpp"
#include "../../core/proxywindow.hpp"
#include "../../core/qmlscreen.hpp"
@ -132,22 +133,49 @@ ObjectModel<Toplevel>* ToplevelManager::toplevels() { return &this->mToplevels;
void ToplevelManager::onToplevelReady(impl::ToplevelHandle* handle) {
auto* toplevel = new Toplevel(handle, this);
// clang-format off
QObject::connect(toplevel, &Toplevel::closed, this, &ToplevelManager::onToplevelClosed);
QObject::connect(toplevel, &Toplevel::activatedChanged, this, &ToplevelManager::onToplevelActiveChanged);
// clang-format on
if (toplevel->activated()) this->setActiveToplevel(toplevel);
this->mToplevels.insertObject(toplevel);
}
void ToplevelManager::onToplevelActiveChanged() {
auto* toplevel = qobject_cast<Toplevel*>(this->sender());
if (toplevel->activated()) this->setActiveToplevel(toplevel);
}
void ToplevelManager::onToplevelClosed() {
auto* toplevel = qobject_cast<Toplevel*>(this->sender());
if (toplevel == this->mActiveToplevel) this->setActiveToplevel(nullptr);
this->mToplevels.removeObject(toplevel);
}
DEFINE_MEMBER_GETSET(ToplevelManager, activeToplevel, setActiveToplevel);
ToplevelManager* ToplevelManager::instance() {
static auto* instance = new ToplevelManager(); // NOLINT
return instance;
}
ToplevelManagerQml::ToplevelManagerQml(QObject* parent): QObject(parent) {
QObject::connect(
ToplevelManager::instance(),
&ToplevelManager::activeToplevelChanged,
this,
&ToplevelManagerQml::activeToplevelChanged
);
}
ObjectModel<Toplevel>* ToplevelManagerQml::toplevels() {
return ToplevelManager::instance()->toplevels();
}
Toplevel* ToplevelManagerQml::activeToplevel() {
return ToplevelManager::instance()->activeToplevel();
}
} // namespace qs::wayland::toplevel_management

View file

@ -7,6 +7,7 @@
#include "../../core/model.hpp"
#include "../../core/proxywindow.hpp"
#include "../../core/qmlscreen.hpp"
#include "../../core/util.hpp"
namespace qs::wayland::toplevel_management {
@ -111,14 +112,27 @@ public:
static ToplevelManager* instance();
signals:
void activeToplevelChanged();
private slots:
void onToplevelReady(impl::ToplevelHandle* handle);
void onToplevelActiveChanged();
void onToplevelClosed();
private:
explicit ToplevelManager();
ObjectModel<Toplevel> mToplevels {this};
Toplevel* mActiveToplevel = nullptr;
DECLARE_PRIVATE_MEMBER(
ToplevelManager,
activeToplevel,
setActiveToplevel,
mActiveToplevel,
activeToplevelChanged
);
};
///! Exposes a list of Toplevels.
@ -127,14 +141,24 @@ private:
/// wayland protocol.
class ToplevelManagerQml: public QObject {
Q_OBJECT;
/// All toplevel windows exposed by the compositor.
Q_PROPERTY(ObjectModel<Toplevel>* toplevels READ toplevels CONSTANT);
/// Active toplevel or null.
///
/// > [!INFO] If multiple are active, this will be the most recently activated one.
/// > Usually compositors will not report more than one toplevel as active at a time.
Q_PROPERTY(Toplevel* activeToplevel READ activeToplevel NOTIFY activeToplevelChanged);
QML_NAMED_ELEMENT(ToplevelManager);
QML_SINGLETON;
public:
explicit ToplevelManagerQml(QObject* parent = nullptr): QObject(parent) {}
explicit ToplevelManagerQml(QObject* parent = nullptr);
[[nodiscard]] static ObjectModel<Toplevel>* toplevels();
[[nodiscard]] static Toplevel* activeToplevel();
signals:
void activeToplevelChanged();
};
} // namespace qs::wayland::toplevel_management