resources popout refresh + new components & reskinned old components
This commit is contained in:
@@ -1,14 +1,25 @@
|
||||
find_package(Qt6 REQUIRED COMPONENTS ShaderTools Core Qml Gui Quick Concurrent Sql Network DBus)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
find_library(SENSORS_LIBRARY NAMES sensors REQUIRED)
|
||||
find_path(SENSORS_INCLUDE_DIR NAMES sensors/sensors.h REQUIRED)
|
||||
pkg_check_modules(Qalculate IMPORTED_TARGET libqalculate REQUIRED)
|
||||
pkg_check_modules(Pipewire IMPORTED_TARGET libpipewire-0.3 REQUIRED)
|
||||
pkg_check_modules(Aubio IMPORTED_TARGET aubio REQUIRED)
|
||||
pkg_check_modules(Cava IMPORTED_TARGET libcava QUIET)
|
||||
pkg_check_modules(GLIB REQUIRED glib-2.0 gobject-2.0 gio-2.0)
|
||||
|
||||
if(NOT Cava_FOUND)
|
||||
pkg_check_modules(Cava IMPORTED_TARGET cava REQUIRED)
|
||||
endif()
|
||||
|
||||
if(NOT TARGET Sensors::Sensors)
|
||||
add_library(Sensors::Sensors UNKNOWN IMPORTED)
|
||||
set_target_properties(Sensors::Sensors PROPERTIES
|
||||
IMPORTED_LOCATION "${SENSORS_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${SENSORS_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
set(QT_QML_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/qml")
|
||||
qt_standard_project_setup(REQUIRES 6.9)
|
||||
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
qml_module(ZShell-internal
|
||||
URI ZShell.Internal
|
||||
SOURCES
|
||||
hyprextras.hpp hyprextras.cpp
|
||||
hyprdevices.hpp hyprdevices.cpp
|
||||
cachingimagemanager.hpp cachingimagemanager.cpp
|
||||
URI ZShell.Internal
|
||||
SOURCES
|
||||
hyprextras.hpp hyprextras.cpp
|
||||
hyprdevices.hpp hyprdevices.cpp
|
||||
cachingimagemanager.hpp cachingimagemanager.cpp
|
||||
circularindicatormanager.hpp circularindicatormanager.cpp
|
||||
circularbuffer.hpp circularbuffer.cpp
|
||||
sparklineitem.hpp sparklineitem.cpp
|
||||
arcgauge.hpp arcgauge.cpp
|
||||
wallpaperimage.hpp wallpaperimage.cpp
|
||||
lidwatcher.hpp lidwatcher.cpp
|
||||
LIBRARIES
|
||||
Qt::Gui
|
||||
Qt::Quick
|
||||
Qt::Concurrent
|
||||
Qt::Core
|
||||
visualizerbars.hpp visualizerbars.cpp
|
||||
linearindicatormanager.hpp linearindicatormanager.cpp
|
||||
LIBRARIES
|
||||
Qt::Gui
|
||||
Qt::Quick
|
||||
Qt::Concurrent
|
||||
Qt::Core
|
||||
Qt::Network
|
||||
Qt::DBus
|
||||
)
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
#include "linearindicatormanager.hpp"
|
||||
|
||||
#include <qpoint.h>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int TOTAL_DURATION_IN_MS = 1800;
|
||||
constexpr std::array DURATION_TO_MOVE_SEGMENT_ENDS = { 533, 567, 850, 750 };
|
||||
constexpr std::array DELAY_TO_MOVE_SEGMENT_ENDS = { 1267, 1000, 333, 0 };
|
||||
|
||||
QEasingCurve curve(const QPointF& c1, const QPointF& c2) {
|
||||
QEasingCurve curve(QEasingCurve::BezierSpline);
|
||||
curve.addCubicBezierSegment(c1, c2, { 1.0, 1.0 });
|
||||
return curve;
|
||||
}
|
||||
|
||||
qreal getFractionInRange(qreal playtime, int start, int duration) {
|
||||
const auto fraction = static_cast<qreal>(playtime - start) / duration;
|
||||
return std::clamp(fraction, 0.0, 1.0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace ZShell::controls {
|
||||
|
||||
LinearIndicatorSegment::LinearIndicatorSegment(int gap, QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_startFraction(0)
|
||||
, m_endFraction(0)
|
||||
, m_gapSize(gap) {
|
||||
}
|
||||
|
||||
qreal LinearIndicatorSegment::startFraction() const {
|
||||
return m_startFraction;
|
||||
}
|
||||
|
||||
qreal LinearIndicatorSegment::endFraction() const {
|
||||
return m_endFraction;
|
||||
}
|
||||
|
||||
int LinearIndicatorSegment::gapSize() const {
|
||||
return m_gapSize;
|
||||
}
|
||||
|
||||
LinearIndicatorManager::LinearIndicatorManager(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_interpolators({
|
||||
curve({ 0.2, 0.0 }, { 0.8, 1.0 }),
|
||||
curve({ 0.4, 0.0 }, { 1.0, 1.0 }),
|
||||
curve({ 0.0, 0.0 }, { 0.65, 1.0 }),
|
||||
curve({ 0.1, 0.0 }, { 0.45, 1.0 }),
|
||||
})
|
||||
, m_progress(0)
|
||||
, m_completeEndProgress(0)
|
||||
, m_gap(4)
|
||||
, m_activeIndicators({
|
||||
new LinearIndicatorSegment(m_gap, this),
|
||||
new LinearIndicatorSegment(m_gap, this),
|
||||
}) {
|
||||
for (auto el : m_activeIndicators)
|
||||
QObject::connect(this, &LinearIndicatorManager::updated, el, &LinearIndicatorSegment::updated);
|
||||
}
|
||||
|
||||
QList<LinearIndicatorSegment*> LinearIndicatorManager::activeIndicators() const {
|
||||
return { m_activeIndicators.cbegin(), m_activeIndicators.cend() };
|
||||
}
|
||||
|
||||
qreal LinearIndicatorManager::progress() const {
|
||||
return m_progress;
|
||||
}
|
||||
|
||||
qreal LinearIndicatorManager::completeEndProgress() const {
|
||||
return m_completeEndProgress;
|
||||
}
|
||||
|
||||
int LinearIndicatorManager::gap() const {
|
||||
return m_gap;
|
||||
}
|
||||
|
||||
void LinearIndicatorManager::setGap(int gap) {
|
||||
m_gap = gap;
|
||||
for (auto el : m_activeIndicators)
|
||||
el->m_gapSize = m_gap;
|
||||
update(m_progress);
|
||||
}
|
||||
|
||||
int LinearIndicatorManager::duration() const {
|
||||
return TOTAL_DURATION_IN_MS;
|
||||
}
|
||||
|
||||
int LinearIndicatorManager::completeEndDuration() const {
|
||||
return TOTAL_DURATION_IN_MS;
|
||||
}
|
||||
|
||||
void LinearIndicatorManager::update(qreal progress) {
|
||||
const auto playtime = progress * TOTAL_DURATION_IN_MS;
|
||||
for (size_t i = 0; i < SEGMENTS; i++) {
|
||||
const auto di = i * 2;
|
||||
auto* const indicator = m_activeIndicators[i];
|
||||
|
||||
auto fraction = getFractionInRange(playtime, DELAY_TO_MOVE_SEGMENT_ENDS[di], DURATION_TO_MOVE_SEGMENT_ENDS[di]);
|
||||
indicator->m_startFraction = std::clamp(m_interpolators[di].valueForProgress(fraction), 0.0, 1.0);
|
||||
|
||||
fraction =
|
||||
getFractionInRange(playtime, DELAY_TO_MOVE_SEGMENT_ENDS[di + 1], DURATION_TO_MOVE_SEGMENT_ENDS[di + 1]);
|
||||
indicator->m_endFraction = std::clamp(m_interpolators[di + 1].valueForProgress(fraction), 0.0, 1.0);
|
||||
}
|
||||
|
||||
m_progress = progress;
|
||||
emit updated();
|
||||
}
|
||||
|
||||
void LinearIndicatorManager::updateCompleteEndProgress(qreal progress) {
|
||||
m_completeEndProgress = progress;
|
||||
update(m_progress);
|
||||
}
|
||||
|
||||
} // namespace ZShell::controls
|
||||
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
#include <qcolor.h>
|
||||
#include <qeasingcurve.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <qqmlintegration.h>
|
||||
|
||||
namespace ZShell::controls {
|
||||
|
||||
class LinearIndicatorManager;
|
||||
|
||||
class LinearIndicatorSegment : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("LinearIndicatorSegments can only be retrieved from a "
|
||||
"LinearIndicatorManager.")
|
||||
|
||||
Q_PROPERTY(qreal startFraction READ startFraction NOTIFY updated FINAL)
|
||||
Q_PROPERTY(qreal endFraction READ endFraction NOTIFY updated FINAL)
|
||||
Q_PROPERTY(int gapSize READ gapSize NOTIFY updated FINAL)
|
||||
|
||||
public:
|
||||
explicit LinearIndicatorSegment(int gap, QObject* parent = nullptr);
|
||||
|
||||
qreal startFraction() const;
|
||||
qreal endFraction() const;
|
||||
int gapSize() const;
|
||||
|
||||
signals:
|
||||
void updated();
|
||||
|
||||
private:
|
||||
qreal m_startFraction;
|
||||
qreal m_endFraction;
|
||||
int m_gapSize;
|
||||
|
||||
friend LinearIndicatorManager;
|
||||
};
|
||||
|
||||
class LinearIndicatorManager : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(
|
||||
QList<ZShell::controls::LinearIndicatorSegment*> activeIndicators READ activeIndicators CONSTANT FINAL)
|
||||
|
||||
Q_PROPERTY(qreal progress READ progress WRITE update NOTIFY updated FINAL)
|
||||
Q_PROPERTY(qreal completeEndProgress READ completeEndProgress WRITE updateCompleteEndProgress NOTIFY updated FINAL)
|
||||
Q_PROPERTY(int gap READ gap WRITE setGap NOTIFY updated FINAL)
|
||||
|
||||
Q_PROPERTY(qreal duration READ duration CONSTANT FINAL)
|
||||
Q_PROPERTY(qreal completeEndDuration READ completeEndDuration CONSTANT FINAL)
|
||||
|
||||
public:
|
||||
explicit LinearIndicatorManager(QObject* parent = nullptr);
|
||||
|
||||
QList<LinearIndicatorSegment*> activeIndicators() const;
|
||||
|
||||
qreal progress() const;
|
||||
qreal completeEndProgress() const;
|
||||
|
||||
int gap() const;
|
||||
void setGap(int gap);
|
||||
|
||||
int duration() const;
|
||||
int completeEndDuration() const;
|
||||
|
||||
void update(qreal progress);
|
||||
void updateCompleteEndProgress(qreal progress);
|
||||
|
||||
signals:
|
||||
void updated();
|
||||
|
||||
private:
|
||||
static constexpr int SEGMENTS = 2;
|
||||
|
||||
std::array<QEasingCurve, 4> m_interpolators;
|
||||
qreal m_progress;
|
||||
qreal m_completeEndProgress;
|
||||
int m_gap;
|
||||
|
||||
std::array<LinearIndicatorSegment*, SEGMENTS> m_activeIndicators;
|
||||
};
|
||||
|
||||
} // namespace ZShell::controls
|
||||
@@ -0,0 +1,198 @@
|
||||
#include "visualizerbars.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <qbrush.h>
|
||||
#include <qpainter.h>
|
||||
#include <qpainterpath.h>
|
||||
#include <qpen.h>
|
||||
|
||||
namespace ZShell::internal {
|
||||
|
||||
VisualizerBars::VisualizerBars(QQuickItem* parent)
|
||||
: QQuickPaintedItem(parent) {
|
||||
setAntialiasing(true);
|
||||
}
|
||||
|
||||
void VisualizerBars::advance(qreal dt) {
|
||||
if (m_displayValues.isEmpty() || m_settled)
|
||||
return;
|
||||
|
||||
// dt is in seconds (from FrameAnimation.frameTime), convert to ms
|
||||
const qreal dtMs = dt * 1000.0;
|
||||
const qreal tau = m_animationDuration / 3.0;
|
||||
const qreal alpha = 1.0 - std::exp(-dtMs / tau);
|
||||
|
||||
bool allSettled = true;
|
||||
|
||||
for (qsizetype i = 0; i < m_displayValues.size(); ++i) {
|
||||
const double diff = m_targetValues[i] - m_displayValues[i];
|
||||
|
||||
if (std::abs(diff) > 0.001) {
|
||||
m_displayValues[i] += diff * alpha;
|
||||
allSettled = false;
|
||||
} else {
|
||||
m_displayValues[i] = m_targetValues[i];
|
||||
}
|
||||
}
|
||||
|
||||
update();
|
||||
|
||||
if (allSettled && !m_settled) {
|
||||
m_settled = true;
|
||||
emit settledChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void VisualizerBars::paint(QPainter* painter) {
|
||||
if (m_displayValues.isEmpty())
|
||||
return;
|
||||
|
||||
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||
painter->setPen(Qt::NoPen);
|
||||
|
||||
const qreal h = height();
|
||||
const qreal maxBarHeight = h * 0.4;
|
||||
|
||||
QLinearGradient gradient(0, h - maxBarHeight, 0, h);
|
||||
gradient.setColorAt(0, m_primaryColor);
|
||||
gradient.setColorAt(1, m_secondaryColor);
|
||||
painter->setBrush(gradient);
|
||||
|
||||
drawSide(painter, false);
|
||||
drawSide(painter, true);
|
||||
}
|
||||
|
||||
void VisualizerBars::drawSide(QPainter* painter, bool rightSide) {
|
||||
const qreal w = width();
|
||||
const qreal h = height();
|
||||
const auto count = m_displayValues.size();
|
||||
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
const qreal sideWidth = w * 0.4;
|
||||
const qreal slotWidth = sideWidth / static_cast<qreal>(count);
|
||||
const qreal barWidth = slotWidth - m_spacing;
|
||||
|
||||
if (barWidth <= 0)
|
||||
return;
|
||||
|
||||
const qreal sideOffset = rightSide ? w * 0.6 : 0;
|
||||
const qreal maxBarHeight = h * 0.4;
|
||||
|
||||
for (qsizetype i = 0; i < count; ++i) {
|
||||
const qsizetype valueIndex = rightSide ? i : (count - i - 1);
|
||||
const qreal value = std::clamp(m_displayValues[valueIndex], 0.0, 1.0);
|
||||
const qreal barHeight = value * maxBarHeight;
|
||||
|
||||
if (barHeight <= 0)
|
||||
continue;
|
||||
|
||||
const qreal x = static_cast<qreal>(i) * slotWidth + sideOffset;
|
||||
const qreal y = h - barHeight;
|
||||
const qreal r = std::min({ m_rounding, barWidth / 2.0, barHeight });
|
||||
|
||||
QPainterPath path;
|
||||
path.moveTo(x, h);
|
||||
path.lineTo(x, y + r);
|
||||
|
||||
if (r > 0) {
|
||||
path.arcTo(x, y, r * 2, r * 2, 180, -90);
|
||||
path.lineTo(x + barWidth - r, y);
|
||||
path.arcTo(x + barWidth - r * 2, y, r * 2, r * 2, 90, -90);
|
||||
} else {
|
||||
path.lineTo(x, y);
|
||||
path.lineTo(x + barWidth, y);
|
||||
}
|
||||
|
||||
path.lineTo(x + barWidth, h);
|
||||
path.closeSubpath();
|
||||
|
||||
painter->drawPath(path);
|
||||
}
|
||||
}
|
||||
|
||||
QVector<double> VisualizerBars::values() const {
|
||||
return m_targetValues;
|
||||
}
|
||||
|
||||
void VisualizerBars::setValues(const QVector<double>& values) {
|
||||
m_targetValues = values;
|
||||
|
||||
if (m_displayValues.size() != values.size()) {
|
||||
m_displayValues.resize(values.size(), 0.0);
|
||||
}
|
||||
|
||||
if (m_settled) {
|
||||
m_settled = false;
|
||||
emit settledChanged();
|
||||
}
|
||||
|
||||
emit valuesChanged();
|
||||
}
|
||||
|
||||
bool VisualizerBars::settled() const {
|
||||
return m_settled;
|
||||
}
|
||||
|
||||
QColor VisualizerBars::primaryColor() const {
|
||||
return m_primaryColor;
|
||||
}
|
||||
|
||||
void VisualizerBars::setPrimaryColor(const QColor& color) {
|
||||
if (m_primaryColor == color)
|
||||
return;
|
||||
m_primaryColor = color;
|
||||
emit primaryColorChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
QColor VisualizerBars::secondaryColor() const {
|
||||
return m_secondaryColor;
|
||||
}
|
||||
|
||||
void VisualizerBars::setSecondaryColor(const QColor& color) {
|
||||
if (m_secondaryColor == color)
|
||||
return;
|
||||
m_secondaryColor = color;
|
||||
emit secondaryColorChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
qreal VisualizerBars::rounding() const {
|
||||
return m_rounding;
|
||||
}
|
||||
|
||||
void VisualizerBars::setRounding(qreal rounding) {
|
||||
if (qFuzzyCompare(m_rounding, rounding))
|
||||
return;
|
||||
m_rounding = rounding;
|
||||
emit roundingChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
qreal VisualizerBars::spacing() const {
|
||||
return m_spacing;
|
||||
}
|
||||
|
||||
void VisualizerBars::setSpacing(qreal spacing) {
|
||||
if (qFuzzyCompare(m_spacing, spacing))
|
||||
return;
|
||||
m_spacing = spacing;
|
||||
emit spacingChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
int VisualizerBars::animationDuration() const {
|
||||
return m_animationDuration;
|
||||
}
|
||||
|
||||
void VisualizerBars::setAnimationDuration(int duration) {
|
||||
if (m_animationDuration == duration)
|
||||
return;
|
||||
m_animationDuration = duration;
|
||||
emit animationDurationChanged();
|
||||
}
|
||||
|
||||
} // namespace ZShell::internal
|
||||
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <qcolor.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qquickpainteditem.h>
|
||||
#include <qvector.h>
|
||||
|
||||
namespace ZShell::internal {
|
||||
|
||||
class VisualizerBars : public QQuickPaintedItem {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(QVector<double> values READ values WRITE setValues NOTIFY valuesChanged)
|
||||
Q_PROPERTY(QColor primaryColor READ primaryColor WRITE setPrimaryColor NOTIFY primaryColorChanged)
|
||||
Q_PROPERTY(QColor secondaryColor READ secondaryColor WRITE setSecondaryColor NOTIFY secondaryColorChanged)
|
||||
Q_PROPERTY(qreal rounding READ rounding WRITE setRounding NOTIFY roundingChanged)
|
||||
Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged)
|
||||
Q_PROPERTY(int animationDuration READ animationDuration WRITE setAnimationDuration NOTIFY animationDurationChanged)
|
||||
Q_PROPERTY(bool settled READ settled NOTIFY settledChanged)
|
||||
|
||||
public:
|
||||
explicit VisualizerBars(QQuickItem* parent = nullptr);
|
||||
|
||||
void paint(QPainter* painter) override;
|
||||
|
||||
Q_INVOKABLE void advance(qreal dt);
|
||||
|
||||
[[nodiscard]] QVector<double> values() const;
|
||||
void setValues(const QVector<double>& values);
|
||||
|
||||
[[nodiscard]] QColor primaryColor() const;
|
||||
void setPrimaryColor(const QColor& color);
|
||||
|
||||
[[nodiscard]] QColor secondaryColor() const;
|
||||
void setSecondaryColor(const QColor& color);
|
||||
|
||||
[[nodiscard]] qreal rounding() const;
|
||||
void setRounding(qreal rounding);
|
||||
|
||||
[[nodiscard]] qreal spacing() const;
|
||||
void setSpacing(qreal spacing);
|
||||
|
||||
[[nodiscard]] int animationDuration() const;
|
||||
void setAnimationDuration(int duration);
|
||||
|
||||
[[nodiscard]] bool settled() const;
|
||||
|
||||
signals:
|
||||
void valuesChanged();
|
||||
void primaryColorChanged();
|
||||
void secondaryColorChanged();
|
||||
void roundingChanged();
|
||||
void spacingChanged();
|
||||
void animationDurationChanged();
|
||||
void settledChanged();
|
||||
|
||||
private:
|
||||
void drawSide(QPainter* painter, bool rightSide);
|
||||
|
||||
QVector<double> m_targetValues;
|
||||
QVector<double> m_displayValues;
|
||||
QColor m_primaryColor;
|
||||
QColor m_secondaryColor;
|
||||
qreal m_rounding = 0.0;
|
||||
qreal m_spacing = 0.0;
|
||||
int m_animationDuration = 200;
|
||||
bool m_settled = true;
|
||||
};
|
||||
|
||||
} // namespace ZShell::internal
|
||||
@@ -1,19 +1,27 @@
|
||||
qml_module(ZShell-services
|
||||
URI ZShell.Services
|
||||
SOURCES
|
||||
service.hpp service.cpp
|
||||
serviceref.hpp serviceref.cpp
|
||||
beattracker.hpp beattracker.cpp
|
||||
audiocollector.hpp audiocollector.cpp
|
||||
audioprovider.hpp audioprovider.cpp
|
||||
cavaprovider.hpp cavaprovider.cpp
|
||||
SOURCES
|
||||
service.hpp service.cpp
|
||||
serviceref.hpp serviceref.cpp
|
||||
beattracker.hpp beattracker.cpp
|
||||
audiocollector.hpp audiocollector.cpp
|
||||
audioprovider.hpp audioprovider.cpp
|
||||
cavaprovider.hpp cavaprovider.cpp
|
||||
desktopmodel.hpp desktopmodel.cpp
|
||||
desktopstatemanager.hpp desktopstatemanager.cpp
|
||||
hyprsunsetmanager.hpp hyprsunsetmanager.cpp
|
||||
LIBRARIES
|
||||
tickingservice.hpp tickingservice.cpp
|
||||
sensorslib.hpp sensorslib.cpp
|
||||
usagefmt.hpp usagefmt.cpp
|
||||
cpu.hpp cpu.cpp
|
||||
memory.hpp memory.cpp
|
||||
diskinfo.hpp diskinfo.cpp
|
||||
storage.hpp storage.cpp
|
||||
LIBRARIES
|
||||
Qt6::Core
|
||||
Qt6::Qml
|
||||
PkgConfig::Pipewire
|
||||
PkgConfig::Aubio
|
||||
PkgConfig::Cava
|
||||
PkgConfig::Pipewire
|
||||
PkgConfig::Aubio
|
||||
PkgConfig::Cava
|
||||
Sensors::Sensors
|
||||
)
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
#include "cpu.hpp"
|
||||
|
||||
#include "sensorslib.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <qfile.h>
|
||||
#include <qregularexpression.h>
|
||||
|
||||
namespace ZShell::services {
|
||||
|
||||
Cpu::Cpu(QObject* parent)
|
||||
: TickingService(parent) {
|
||||
readNameOnce();
|
||||
}
|
||||
|
||||
QString Cpu::name() const {
|
||||
return m_name;
|
||||
}
|
||||
|
||||
qreal Cpu::percentage() const {
|
||||
return m_percentage;
|
||||
}
|
||||
|
||||
qreal Cpu::temperature() const {
|
||||
return m_temperature;
|
||||
}
|
||||
|
||||
void Cpu::tick() {
|
||||
if (!m_nameLoaded) {
|
||||
readNameOnce();
|
||||
}
|
||||
refreshPercentage();
|
||||
refreshTemperature();
|
||||
}
|
||||
|
||||
void Cpu::readNameOnce() {
|
||||
QFile f(QStringLiteral("/proc/cpuinfo"));
|
||||
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return;
|
||||
}
|
||||
const QByteArray data = f.readAll();
|
||||
f.close();
|
||||
|
||||
static const QRegularExpression re(QStringLiteral("model name\\s*:\\s*(.+)"));
|
||||
const auto match = re.match(QString::fromLatin1(data));
|
||||
if (!match.hasMatch()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString cleaned = cleanName(match.captured(1));
|
||||
m_nameLoaded = true;
|
||||
if (cleaned == m_name) {
|
||||
return;
|
||||
}
|
||||
m_name = cleaned;
|
||||
Q_EMIT nameChanged();
|
||||
}
|
||||
|
||||
void Cpu::refreshPercentage() {
|
||||
QFile f(QStringLiteral("/proc/stat"));
|
||||
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return;
|
||||
}
|
||||
const QByteArray data = f.readAll();
|
||||
f.close();
|
||||
|
||||
static const QRegularExpression re(
|
||||
QStringLiteral("^cpu\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)"));
|
||||
const auto match = re.match(QString::fromLatin1(data));
|
||||
if (!match.hasMatch()) {
|
||||
return;
|
||||
}
|
||||
|
||||
quint64 total = 0;
|
||||
quint64 idle = 0;
|
||||
for (int i = 1; i <= 7; ++i) {
|
||||
const quint64 v = match.captured(i).toULongLong();
|
||||
total += v;
|
||||
if (i == 4 || i == 5) {
|
||||
idle += v;
|
||||
}
|
||||
}
|
||||
|
||||
const quint64 totalDiff = total > m_lastTotal ? total - m_lastTotal : 0;
|
||||
const quint64 idleDiff = idle > m_lastIdle ? idle - m_lastIdle : 0;
|
||||
const qreal newPerc = totalDiff > 0 ? 1.0 - static_cast<qreal>(idleDiff) / static_cast<qreal>(totalDiff) : 0.0;
|
||||
|
||||
m_lastTotal = total;
|
||||
m_lastIdle = idle;
|
||||
|
||||
if (std::abs(newPerc - m_percentage) > 0.0001) {
|
||||
m_percentage = newPerc;
|
||||
Q_EMIT percentageChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void Cpu::refreshTemperature() {
|
||||
const auto t = sensorslib::cpuPackageTemp();
|
||||
const qreal newTemp = t.value_or(0.0);
|
||||
if (std::abs(newTemp - m_temperature) > 0.05) {
|
||||
m_temperature = newTemp;
|
||||
Q_EMIT temperatureChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QString Cpu::cleanName(QString s) {
|
||||
static const QRegularExpression noise(
|
||||
QStringLiteral("\\(R\\)|\\(TM\\)|CPU|\\d+(?:th|nd|rd|st) Gen |Core |Processor"),
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
static const QRegularExpression spaces(QStringLiteral("\\s+"));
|
||||
|
||||
s.replace(noise, QString());
|
||||
s.replace(spaces, QStringLiteral(" "));
|
||||
return s.trimmed();
|
||||
}
|
||||
|
||||
} // namespace ZShell::services
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "tickingservice.hpp"
|
||||
|
||||
#include <qqmlintegration.h>
|
||||
|
||||
namespace ZShell::services {
|
||||
|
||||
class Cpu : public TickingService {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||
Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged)
|
||||
Q_PROPERTY(qreal temperature READ temperature NOTIFY temperatureChanged)
|
||||
|
||||
public:
|
||||
explicit Cpu(QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] QString name() const;
|
||||
[[nodiscard]] qreal percentage() const;
|
||||
[[nodiscard]] qreal temperature() const;
|
||||
|
||||
signals:
|
||||
void nameChanged();
|
||||
void percentageChanged();
|
||||
void temperatureChanged();
|
||||
|
||||
protected:
|
||||
void tick() override;
|
||||
|
||||
private:
|
||||
void readNameOnce();
|
||||
void refreshPercentage();
|
||||
void refreshTemperature();
|
||||
|
||||
[[nodiscard]] static QString cleanName(QString s);
|
||||
|
||||
QString m_name;
|
||||
qreal m_percentage = 0.0;
|
||||
qreal m_temperature = 0.0;
|
||||
quint64 m_lastIdle = 0;
|
||||
quint64 m_lastTotal = 0;
|
||||
bool m_nameLoaded = false;
|
||||
};
|
||||
|
||||
} // namespace ZShell::services
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "diskinfo.hpp"
|
||||
|
||||
namespace ZShell::services {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr qreal kKib = 1024.0;
|
||||
|
||||
} // namespace
|
||||
|
||||
DiskInfo::DiskInfo(QString mount, quint64 usedBytes, quint64 totalBytes, bool hasRoot, QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_mount(std::move(mount))
|
||||
, m_usedBytes(usedBytes)
|
||||
, m_totalBytes(totalBytes)
|
||||
, m_hasRoot(hasRoot) {
|
||||
}
|
||||
|
||||
QString DiskInfo::mount() const {
|
||||
return m_mount;
|
||||
}
|
||||
|
||||
qreal DiskInfo::used() const {
|
||||
return static_cast<qreal>(m_usedBytes) / kKib;
|
||||
}
|
||||
|
||||
qreal DiskInfo::total() const {
|
||||
return static_cast<qreal>(m_totalBytes) / kKib;
|
||||
}
|
||||
|
||||
qreal DiskInfo::free() const {
|
||||
const quint64 freeBytes = m_totalBytes > m_usedBytes ? m_totalBytes - m_usedBytes : 0;
|
||||
return static_cast<qreal>(freeBytes) / kKib;
|
||||
}
|
||||
|
||||
qreal DiskInfo::perc() const {
|
||||
return m_totalBytes > 0 ? static_cast<qreal>(m_usedBytes) / static_cast<qreal>(m_totalBytes) : 0.0;
|
||||
}
|
||||
|
||||
bool DiskInfo::hasRoot() const {
|
||||
return m_hasRoot;
|
||||
}
|
||||
|
||||
void DiskInfo::update(quint64 usedBytes, quint64 totalBytes, bool hasRoot) {
|
||||
const bool usedDiff = usedBytes != m_usedBytes;
|
||||
const bool totalDiff = totalBytes != m_totalBytes;
|
||||
const bool rootDiff = hasRoot != m_hasRoot;
|
||||
|
||||
m_usedBytes = usedBytes;
|
||||
m_totalBytes = totalBytes;
|
||||
m_hasRoot = hasRoot;
|
||||
|
||||
if (usedDiff) {
|
||||
Q_EMIT usedChanged();
|
||||
}
|
||||
if (totalDiff) {
|
||||
Q_EMIT totalChanged();
|
||||
}
|
||||
if (usedDiff || totalDiff) {
|
||||
Q_EMIT freeChanged();
|
||||
Q_EMIT percChanged();
|
||||
}
|
||||
if (rootDiff) {
|
||||
Q_EMIT hasRootChanged();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ZShell::services
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
|
||||
namespace ZShell::services {
|
||||
|
||||
class DiskInfo : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("DiskInfo is created by DiskUsage")
|
||||
|
||||
Q_PROPERTY(QString mount READ mount CONSTANT)
|
||||
Q_PROPERTY(qreal used READ used NOTIFY usedChanged)
|
||||
Q_PROPERTY(qreal total READ total NOTIFY totalChanged)
|
||||
Q_PROPERTY(qreal free READ free NOTIFY freeChanged)
|
||||
Q_PROPERTY(qreal perc READ perc NOTIFY percChanged)
|
||||
Q_PROPERTY(bool hasRoot READ hasRoot NOTIFY hasRootChanged)
|
||||
|
||||
public:
|
||||
DiskInfo(QString mount, quint64 usedBytes, quint64 totalBytes, bool hasRoot, QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] QString mount() const;
|
||||
[[nodiscard]] qreal used() const;
|
||||
[[nodiscard]] qreal total() const;
|
||||
[[nodiscard]] qreal free() const;
|
||||
[[nodiscard]] qreal perc() const;
|
||||
[[nodiscard]] bool hasRoot() const;
|
||||
|
||||
void update(quint64 usedBytes, quint64 totalBytes, bool hasRoot);
|
||||
|
||||
signals:
|
||||
void usedChanged();
|
||||
void totalChanged();
|
||||
void freeChanged();
|
||||
void percChanged();
|
||||
void hasRootChanged();
|
||||
|
||||
private:
|
||||
QString m_mount;
|
||||
quint64 m_usedBytes;
|
||||
quint64 m_totalBytes;
|
||||
bool m_hasRoot;
|
||||
};
|
||||
|
||||
} // namespace ZShell::services
|
||||
@@ -0,0 +1,59 @@
|
||||
#include "memory.hpp"
|
||||
|
||||
#include <qfile.h>
|
||||
#include <qregularexpression.h>
|
||||
|
||||
namespace ZShell::services {
|
||||
|
||||
Memory::Memory(QObject* parent)
|
||||
: TickingService(parent) {
|
||||
}
|
||||
|
||||
qreal Memory::used() const {
|
||||
return m_used;
|
||||
}
|
||||
|
||||
qreal Memory::total() const {
|
||||
return m_total;
|
||||
}
|
||||
|
||||
qreal Memory::percentage() const {
|
||||
return m_total > 0.0 ? m_used / m_total : 0.0;
|
||||
}
|
||||
|
||||
void Memory::tick() {
|
||||
QFile f(QStringLiteral("/proc/meminfo"));
|
||||
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return;
|
||||
}
|
||||
const QByteArray data = f.readAll();
|
||||
f.close();
|
||||
|
||||
static const QRegularExpression reTotal(QStringLiteral("MemTotal: *(\\d+)"));
|
||||
static const QRegularExpression reAvail(QStringLiteral("MemAvailable: *(\\d+)"));
|
||||
const QString text = QString::fromLatin1(data);
|
||||
|
||||
const auto totalMatch = reTotal.match(text);
|
||||
const auto availMatch = reAvail.match(text);
|
||||
if (!totalMatch.hasMatch() || !availMatch.hasMatch()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const quint64 totalKib = totalMatch.captured(1).toULongLong();
|
||||
const quint64 availKib = availMatch.captured(1).toULongLong();
|
||||
if (totalKib == 0) {
|
||||
return;
|
||||
}
|
||||
const quint64 usedKib = totalKib > availKib ? totalKib - availKib : 0;
|
||||
|
||||
if (totalKib == m_lastTotal && usedKib == m_lastUsed) {
|
||||
return;
|
||||
}
|
||||
m_lastTotal = totalKib;
|
||||
m_lastUsed = usedKib;
|
||||
m_total = static_cast<qreal>(totalKib);
|
||||
m_used = static_cast<qreal>(usedKib);
|
||||
Q_EMIT changed();
|
||||
}
|
||||
|
||||
} // namespace ZShell::services
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "tickingservice.hpp"
|
||||
|
||||
#include <qqmlintegration.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
namespace ZShell::services {
|
||||
|
||||
class Memory : public TickingService {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
Q_PROPERTY(qreal used READ used NOTIFY changed)
|
||||
Q_PROPERTY(qreal total READ total NOTIFY changed)
|
||||
Q_PROPERTY(qreal percentage READ percentage NOTIFY changed)
|
||||
|
||||
public:
|
||||
explicit Memory(QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] qreal used() const;
|
||||
[[nodiscard]] qreal total() const;
|
||||
[[nodiscard]] qreal percentage() const;
|
||||
|
||||
signals:
|
||||
void changed();
|
||||
|
||||
protected:
|
||||
void tick() override;
|
||||
|
||||
private:
|
||||
qreal m_used = 0.0;
|
||||
qreal m_total = 1.0;
|
||||
quint64 m_lastUsed = 0;
|
||||
quint64 m_lastTotal = 0;
|
||||
};
|
||||
|
||||
} // namespace ZShell::services
|
||||
@@ -0,0 +1,166 @@
|
||||
#include "sensorslib.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <qloggingcategory.h>
|
||||
#include <sensors/sensors.h>
|
||||
|
||||
Q_LOGGING_CATEGORY(lcSensorsLib, "ZShell.services.sensorslib", QtInfoMsg)
|
||||
|
||||
namespace ZShell::services::sensorslib {
|
||||
|
||||
namespace {
|
||||
|
||||
std::atomic<bool> g_initOk{ false };
|
||||
std::once_flag g_initFlag;
|
||||
|
||||
void doInit() {
|
||||
if (sensors_init(nullptr) != 0) {
|
||||
qCWarning(lcSensorsLib, "sensors_init failed");
|
||||
g_initOk.store(false, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
g_initOk.store(true, std::memory_order_release);
|
||||
std::atexit([] {
|
||||
if (g_initOk.load(std::memory_order_acquire)) {
|
||||
sensors_cleanup();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<double> readTempInput(const sensors_chip_name* chip, const sensors_feature* feat) {
|
||||
const sensors_subfeature* sf = sensors_get_subfeature(chip, feat, SENSORS_SUBFEATURE_TEMP_INPUT);
|
||||
if (!sf) {
|
||||
return std::nullopt;
|
||||
}
|
||||
double value = 0.0;
|
||||
if (sensors_get_value(chip, sf->number, &value) != 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray featureLabel(const sensors_chip_name* chip, const sensors_feature* feat) {
|
||||
char* raw = sensors_get_label(chip, feat);
|
||||
if (!raw) {
|
||||
return {};
|
||||
}
|
||||
QByteArray out(raw);
|
||||
std::free(raw);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool labelEquals(const QByteArray& label, const char* literal) {
|
||||
return label == QByteArrayView(literal);
|
||||
}
|
||||
|
||||
bool labelStartsWith(const QByteArray& label, const char* prefix) {
|
||||
const auto n = std::strlen(prefix);
|
||||
return static_cast<size_t>(label.size()) >= n && std::memcmp(label.constData(), prefix, n) == 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ensureInit() {
|
||||
std::call_once(g_initFlag, doInit);
|
||||
}
|
||||
|
||||
std::optional<double> cpuPackageTemp() {
|
||||
ensureInit();
|
||||
if (!g_initOk.load(std::memory_order_acquire)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<double> primary; // Package id N / Tdie
|
||||
std::optional<double> fallback; // Tctl
|
||||
|
||||
int chipNr = 0;
|
||||
while (const sensors_chip_name* chip = sensors_get_detected_chips(nullptr, &chipNr)) {
|
||||
int featNr = 0;
|
||||
while (const sensors_feature* feat = sensors_get_features(chip, &featNr)) {
|
||||
if (feat->type != SENSORS_FEATURE_TEMP) {
|
||||
continue;
|
||||
}
|
||||
const QByteArray label = featureLabel(chip, feat);
|
||||
if (label.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (labelStartsWith(label, "Package id ") || labelEquals(label, "Tdie")) {
|
||||
if (auto v = readTempInput(chip, feat)) {
|
||||
primary = v;
|
||||
}
|
||||
} else if (labelEquals(label, "Tctl")) {
|
||||
if (auto v = readTempInput(chip, feat)) {
|
||||
fallback = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return primary.has_value() ? primary : fallback;
|
||||
}
|
||||
|
||||
std::optional<double> gpuPciAverageTemp() {
|
||||
ensureInit();
|
||||
if (!g_initOk.load(std::memory_order_acquire)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
double sumPrimary = 0.0;
|
||||
int countPrimary = 0;
|
||||
double sumFallback = 0.0;
|
||||
int countFallback = 0;
|
||||
|
||||
int chipNr = 0;
|
||||
while (const sensors_chip_name* chip = sensors_get_detected_chips(nullptr, &chipNr)) {
|
||||
if (chip->bus.type != SENSORS_BUS_TYPE_PCI) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int featNr = 0;
|
||||
while (const sensors_feature* feat = sensors_get_features(chip, &featNr)) {
|
||||
if (feat->type != SENSORS_FEATURE_TEMP) {
|
||||
continue;
|
||||
}
|
||||
const QByteArray label = featureLabel(chip, feat);
|
||||
if (label.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool tempIndexed = labelStartsWith(label, "temp") && label.size() > 4 &&
|
||||
std::isdigit(static_cast<unsigned char>(label[4]));
|
||||
const bool isPrimary = tempIndexed || labelEquals(label, "GPU core") || labelEquals(label, "edge");
|
||||
const bool isFallback = labelEquals(label, "junction") || labelEquals(label, "mem");
|
||||
|
||||
if (!isPrimary && !isFallback) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto v = readTempInput(chip, feat);
|
||||
if (!v) {
|
||||
continue;
|
||||
}
|
||||
if (isPrimary) {
|
||||
sumPrimary += *v;
|
||||
++countPrimary;
|
||||
} else {
|
||||
sumFallback += *v;
|
||||
++countFallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (countPrimary > 0) {
|
||||
return sumPrimary / countPrimary;
|
||||
}
|
||||
if (countFallback > 0) {
|
||||
return sumFallback / countFallback;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace ZShell::services::sensorslib
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace ZShell::services::sensorslib {
|
||||
|
||||
void ensureInit();
|
||||
|
||||
[[nodiscard]] std::optional<double> cpuPackageTemp();
|
||||
[[nodiscard]] std::optional<double> gpuPciAverageTemp();
|
||||
|
||||
} // namespace ZShell::services::sensorslib
|
||||
@@ -0,0 +1,313 @@
|
||||
#include "storage.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <qdir.h>
|
||||
#include <qfile.h>
|
||||
#include <qfileinfo.h>
|
||||
#include <qhash.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qstorageinfo.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/sysmacros.h>
|
||||
|
||||
Q_LOGGING_CATEGORY(lcStorage, "ZShell.services.storage", QtInfoMsg)
|
||||
|
||||
namespace ZShell::services {
|
||||
|
||||
namespace {
|
||||
|
||||
struct Accum {
|
||||
quint64 usedBytes = 0;
|
||||
quint64 totalBytes = 0;
|
||||
bool hasRoot = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] QString sysfsRealPath(uint major, uint minor) {
|
||||
const QString link = QStringLiteral("/sys/dev/block/%1:%2").arg(major).arg(minor);
|
||||
const QString resolved = QFileInfo(link).canonicalFilePath();
|
||||
return resolved;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool readDevtFromSysfs(const QString& sysfsBlockDir, uint& major, uint& minor) {
|
||||
QFile f(sysfsBlockDir + QStringLiteral("/dev"));
|
||||
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return false;
|
||||
}
|
||||
const QByteArray line = f.readLine().trimmed();
|
||||
f.close();
|
||||
|
||||
const qsizetype colon = line.indexOf(':');
|
||||
if (colon <= 0) {
|
||||
return false;
|
||||
}
|
||||
bool okM = false;
|
||||
bool okN = false;
|
||||
major = line.left(colon).toUInt(&okM);
|
||||
minor = line.mid(colon + 1).toUInt(&okN);
|
||||
return okM && okN;
|
||||
}
|
||||
|
||||
QStringList resolveByDevt(uint major, uint minor, int depth = 0);
|
||||
|
||||
QStringList resolveAtNode(const QString& node, int depth) {
|
||||
if (node.isEmpty() || depth > 8) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const QFileInfo nodeInfo(node);
|
||||
if (!nodeInfo.exists() || !nodeInfo.isDir()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (QFileInfo::exists(node + QStringLiteral("/partition"))) {
|
||||
const QString diskNode = nodeInfo.path();
|
||||
return { QFileInfo(diskNode).fileName() };
|
||||
}
|
||||
|
||||
const QDir slavesDir(node + QStringLiteral("/slaves"));
|
||||
if (slavesDir.exists()) {
|
||||
const QStringList slaves = slavesDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
if (!slaves.isEmpty()) {
|
||||
QStringList out;
|
||||
for (const QString& slave : slaves) {
|
||||
uint sm = 0;
|
||||
uint sn = 0;
|
||||
const QString slaveDir = QStringLiteral("/sys/class/block/") + slave;
|
||||
if (!readDevtFromSysfs(slaveDir, sm, sn)) {
|
||||
continue;
|
||||
}
|
||||
const auto devs = resolveByDevt(sm, sn, depth + 1);
|
||||
for (const QString& d : devs) {
|
||||
if (!out.contains(d)) {
|
||||
out.append(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
return { nodeInfo.fileName() };
|
||||
}
|
||||
|
||||
QStringList resolveByDevt(uint major, uint minor, int depth) {
|
||||
return resolveAtNode(sysfsRealPath(major, minor), depth);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Storage::Storage(QObject* parent)
|
||||
: TickingService(parent) {
|
||||
}
|
||||
|
||||
qreal Storage::percentage() const {
|
||||
qreal totalUsed = 0.0;
|
||||
qreal totalSize = 0.0;
|
||||
for (const DiskInfo* d : m_disks) {
|
||||
totalUsed += d->used();
|
||||
totalSize += d->total();
|
||||
}
|
||||
return totalSize > 0.0 ? totalUsed / totalSize : 0.0;
|
||||
}
|
||||
|
||||
bool Storage::sameOrder(const QList<DiskInfo*>& a, const QList<DiskInfo*>& b) {
|
||||
if (a.size() != b.size()) {
|
||||
return false;
|
||||
}
|
||||
for (qsizetype i = 0; i < a.size(); ++i) {
|
||||
if (a.at(i) != b.at(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QQmlListProperty<DiskInfo> Storage::disksProp() {
|
||||
return QQmlListProperty<DiskInfo>(this, nullptr, &Storage::disksCount, &Storage::disksAt);
|
||||
}
|
||||
|
||||
qsizetype Storage::disksCount(QQmlListProperty<DiskInfo>* prop) {
|
||||
return static_cast<Storage*>(prop->object)->m_disks.size();
|
||||
}
|
||||
|
||||
DiskInfo* Storage::disksAt(QQmlListProperty<DiskInfo>* prop, qsizetype i) {
|
||||
return static_cast<Storage*>(prop->object)->m_disks.at(i);
|
||||
}
|
||||
|
||||
DiskInfo* Storage::manualPrimaryDisk() const {
|
||||
return m_manualPrimaryDisk.data();
|
||||
}
|
||||
|
||||
void Storage::setManualPrimaryDisk(DiskInfo* disk) {
|
||||
if (m_manualPrimaryDisk.data() == disk) {
|
||||
return;
|
||||
}
|
||||
m_manualPrimaryDisk = disk;
|
||||
Q_EMIT manualPrimaryDiskChanged();
|
||||
Q_EMIT primaryDiskChanged();
|
||||
}
|
||||
|
||||
DiskInfo* Storage::primaryDisk() const {
|
||||
if (auto* m = m_manualPrimaryDisk.data()) {
|
||||
return m;
|
||||
}
|
||||
return m_disks.isEmpty() ? nullptr : m_disks.first();
|
||||
}
|
||||
|
||||
bool Storage::isPseudoFs(QByteArrayView fsType) {
|
||||
static constexpr const char* kPseudo[] = {
|
||||
"tmpfs",
|
||||
"devtmpfs",
|
||||
"proc",
|
||||
"sysfs",
|
||||
"cgroup",
|
||||
"cgroup2",
|
||||
"overlay",
|
||||
"squashfs",
|
||||
"devpts",
|
||||
"mqueue",
|
||||
"ramfs",
|
||||
"rpc_pipefs",
|
||||
"autofs",
|
||||
"configfs",
|
||||
"debugfs",
|
||||
"tracefs",
|
||||
"securityfs",
|
||||
"pstore",
|
||||
"bpf",
|
||||
"binfmt_misc",
|
||||
"hugetlbfs",
|
||||
"fusectl",
|
||||
"efivarfs",
|
||||
"selinuxfs",
|
||||
};
|
||||
for (const char* p : kPseudo) {
|
||||
if (fsType == QByteArrayView(p)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return fsType.startsWith(QByteArrayView("fuse."));
|
||||
}
|
||||
|
||||
QStringList Storage::resolveToPhysicalDisks(const QString& devicePath) {
|
||||
if (devicePath.isEmpty() || !devicePath.startsWith(QLatin1Char('/'))) {
|
||||
return {};
|
||||
}
|
||||
struct stat st {};
|
||||
if (::stat(devicePath.toLocal8Bit().constData(), &st) != 0) {
|
||||
return {};
|
||||
}
|
||||
if (!S_ISBLK(st.st_mode)) {
|
||||
return {};
|
||||
}
|
||||
return resolveByDevt(major(st.st_rdev), minor(st.st_rdev));
|
||||
}
|
||||
|
||||
void Storage::tick() {
|
||||
const qreal prevPercentage = percentage();
|
||||
QHash<QString, Accum> byDisk;
|
||||
|
||||
// Multiple mounts can share a single backing filesystem (btrfs subvolumes,
|
||||
// bind mounts, etc.) and each one reports identical bytesTotal/bytesAvailable.
|
||||
// Dedupe by source device so the filesystem only contributes once per disk.
|
||||
struct DeviceEntry {
|
||||
quint64 totalBytes = 0;
|
||||
quint64 usedBytes = 0;
|
||||
bool hasRoot = false;
|
||||
QByteArray device;
|
||||
};
|
||||
|
||||
QHash<QByteArray, DeviceEntry> byDevice;
|
||||
|
||||
const auto mountedVols = QStorageInfo::mountedVolumes();
|
||||
for (const QStorageInfo& v : mountedVols) {
|
||||
if (!v.isReady() || !v.isValid() || v.bytesTotal() <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (isPseudoFs(QByteArrayView(v.fileSystemType()))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QByteArray device = v.device();
|
||||
const auto totalBytes = static_cast<quint64>(v.bytesTotal());
|
||||
const auto availBytes = static_cast<quint64>(v.bytesAvailable());
|
||||
const quint64 usedBytes = totalBytes > availBytes ? totalBytes - availBytes : 0;
|
||||
const bool isRoot = v.rootPath() == QStringLiteral("/");
|
||||
|
||||
DeviceEntry& e = byDevice[device];
|
||||
e.device = device;
|
||||
e.totalBytes = totalBytes;
|
||||
e.usedBytes = usedBytes;
|
||||
e.hasRoot = e.hasRoot || isRoot;
|
||||
}
|
||||
|
||||
for (auto it = byDevice.constBegin(); it != byDevice.constEnd(); ++it) {
|
||||
const DeviceEntry& e = it.value();
|
||||
const QStringList disks = resolveToPhysicalDisks(QString::fromLocal8Bit(e.device));
|
||||
if (disks.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
for (const QString& d : disks) {
|
||||
if (d.startsWith(QStringLiteral("zram"))) {
|
||||
continue;
|
||||
}
|
||||
Accum& a = byDisk[d];
|
||||
a.usedBytes += e.usedBytes;
|
||||
a.totalBytes += e.totalBytes;
|
||||
a.hasRoot = a.hasRoot || e.hasRoot;
|
||||
}
|
||||
}
|
||||
|
||||
QHash<QString, DiskInfo*> existing;
|
||||
existing.reserve(m_disks.size());
|
||||
for (DiskInfo* d : std::as_const(m_disks)) {
|
||||
existing.insert(d->mount(), d);
|
||||
}
|
||||
|
||||
QList<DiskInfo*> next;
|
||||
next.reserve(byDisk.size());
|
||||
for (auto it = byDisk.constBegin(); it != byDisk.constEnd(); ++it) {
|
||||
if (DiskInfo* survivor = existing.take(it.key())) {
|
||||
survivor->update(it.value().usedBytes, it.value().totalBytes, it.value().hasRoot);
|
||||
next.append(survivor);
|
||||
} else {
|
||||
next.append(new DiskInfo(it.key(), it.value().usedBytes, it.value().totalBytes, it.value().hasRoot, this));
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(next.begin(), next.end(), [](const DiskInfo* a, const DiskInfo* b) {
|
||||
if (a->hasRoot() != b->hasRoot()) {
|
||||
return a->hasRoot();
|
||||
}
|
||||
return a->mount() < b->mount();
|
||||
});
|
||||
|
||||
bool manualCleared = false;
|
||||
if (DiskInfo* m = m_manualPrimaryDisk.data(); m && existing.contains(m->mount())) {
|
||||
m_manualPrimaryDisk.clear();
|
||||
manualCleared = true;
|
||||
}
|
||||
for (DiskInfo* stale : std::as_const(existing)) {
|
||||
stale->deleteLater();
|
||||
}
|
||||
|
||||
const bool listChanged = !sameOrder(m_disks, next);
|
||||
DiskInfo* prevPrimary = primaryDisk();
|
||||
m_disks = next;
|
||||
|
||||
if (listChanged) {
|
||||
Q_EMIT disksChanged();
|
||||
}
|
||||
if (std::abs(percentage() - prevPercentage) > 0.0001) {
|
||||
Q_EMIT percentageChanged();
|
||||
}
|
||||
if (manualCleared) {
|
||||
Q_EMIT manualPrimaryDiskChanged();
|
||||
}
|
||||
if (primaryDisk() != prevPrimary) {
|
||||
Q_EMIT primaryDiskChanged();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ZShell::services
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "diskinfo.hpp"
|
||||
#include "tickingservice.hpp"
|
||||
|
||||
#include <qbytearrayview.h>
|
||||
#include <qpointer.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
namespace ZShell::services {
|
||||
|
||||
class Storage : public TickingService {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged)
|
||||
Q_PROPERTY(QQmlListProperty<ZShell::services::DiskInfo> disks READ disksProp NOTIFY disksChanged)
|
||||
Q_PROPERTY(ZShell::services::DiskInfo* manualPrimaryDisk READ manualPrimaryDisk WRITE setManualPrimaryDisk NOTIFY
|
||||
manualPrimaryDiskChanged)
|
||||
Q_PROPERTY(ZShell::services::DiskInfo* primaryDisk READ primaryDisk NOTIFY primaryDiskChanged)
|
||||
|
||||
public:
|
||||
explicit Storage(QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] qreal percentage() const;
|
||||
[[nodiscard]] QQmlListProperty<DiskInfo> disksProp();
|
||||
[[nodiscard]] DiskInfo* manualPrimaryDisk() const;
|
||||
void setManualPrimaryDisk(DiskInfo* disk);
|
||||
[[nodiscard]] DiskInfo* primaryDisk() const;
|
||||
|
||||
signals:
|
||||
void disksChanged();
|
||||
void percentageChanged();
|
||||
void manualPrimaryDiskChanged();
|
||||
void primaryDiskChanged();
|
||||
|
||||
protected:
|
||||
void tick() override;
|
||||
|
||||
private:
|
||||
[[nodiscard]] static QStringList resolveToPhysicalDisks(const QString& devicePath);
|
||||
[[nodiscard]] static bool isPseudoFs(QByteArrayView fsType);
|
||||
[[nodiscard]] static bool sameOrder(const QList<DiskInfo*>& a, const QList<DiskInfo*>& b);
|
||||
|
||||
static qsizetype disksCount(QQmlListProperty<DiskInfo>* prop);
|
||||
static DiskInfo* disksAt(QQmlListProperty<DiskInfo>* prop, qsizetype i);
|
||||
|
||||
QList<DiskInfo*> m_disks;
|
||||
QPointer<DiskInfo> m_manualPrimaryDisk;
|
||||
};
|
||||
|
||||
} // namespace ZShell::services
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "tickingservice.hpp"
|
||||
|
||||
namespace ZShell::services {
|
||||
|
||||
TickingService::TickingService(QObject* parent)
|
||||
: Service(parent)
|
||||
, m_timer(new QTimer(this)) {
|
||||
m_timer->setSingleShot(false);
|
||||
QObject::connect(m_timer, &QTimer::timeout, this, [this] {
|
||||
tick();
|
||||
});
|
||||
}
|
||||
|
||||
int TickingService::updateInterval() const {
|
||||
return m_interval;
|
||||
}
|
||||
|
||||
void TickingService::start() {
|
||||
m_running = true;
|
||||
if (m_interval > 0) {
|
||||
m_timer->start(m_interval);
|
||||
}
|
||||
tick();
|
||||
}
|
||||
|
||||
void TickingService::stop() {
|
||||
m_running = false;
|
||||
m_timer->stop();
|
||||
}
|
||||
|
||||
void TickingService::applyInterval(int ms) {
|
||||
if (ms <= 0 || ms == m_interval) {
|
||||
return;
|
||||
}
|
||||
m_interval = ms;
|
||||
if (m_running) {
|
||||
m_timer->start(m_interval);
|
||||
}
|
||||
Q_EMIT updateIntervalChanged();
|
||||
}
|
||||
|
||||
} // namespace ZShell::services
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "service.hpp"
|
||||
#include <qtimer.h>
|
||||
|
||||
namespace ZShell::services {
|
||||
|
||||
class TickingService : public Service {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(int updateInterval READ updateInterval NOTIFY updateIntervalChanged)
|
||||
|
||||
public:
|
||||
explicit TickingService(QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] int updateInterval() const;
|
||||
|
||||
signals:
|
||||
void updateIntervalChanged();
|
||||
|
||||
protected:
|
||||
void start() final;
|
||||
void stop() final;
|
||||
|
||||
virtual void tick() = 0;
|
||||
|
||||
private:
|
||||
void applyInterval(int ms);
|
||||
|
||||
QTimer* m_timer;
|
||||
int m_interval = 1000;
|
||||
bool m_running = false;
|
||||
};
|
||||
|
||||
} // namespace ZShell::services
|
||||
@@ -0,0 +1,33 @@
|
||||
#include "usagefmt.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr qreal kKib = 1024.0;
|
||||
constexpr qreal kMib = kKib * 1024.0;
|
||||
constexpr qreal kGib = kMib * 1024.0;
|
||||
|
||||
bool finitePositive(qreal v) {
|
||||
return std::isfinite(v) && v >= 0.0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace ZShell::services::usagefmt {
|
||||
|
||||
FormatResult UsageFmt::formatKib(qreal kib, qreal total) const {
|
||||
if (!finitePositive(kib) || !finitePositive(total)) {
|
||||
return { 0.0, 0.0, "KiB" };
|
||||
}
|
||||
if (total >= kGib) {
|
||||
return { kib / kGib, total / kGib, "TiB" };
|
||||
}
|
||||
if (total >= kMib) {
|
||||
return { kib / kMib, total / kMib, "GiB" };
|
||||
}
|
||||
if (total >= kKib) {
|
||||
return { kib / kKib, total / kKib, "MiB" };
|
||||
}
|
||||
return { kib, total, "KiB" };
|
||||
}
|
||||
|
||||
} // namespace ZShell::services::usagefmt
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qobjectdefs.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
namespace ZShell::services::usagefmt {
|
||||
|
||||
struct FormatResult {
|
||||
Q_GADGET
|
||||
QML_ANONYMOUS
|
||||
|
||||
Q_PROPERTY(qreal value MEMBER value CONSTANT)
|
||||
Q_PROPERTY(qreal total MEMBER total CONSTANT)
|
||||
Q_PROPERTY(QString unit MEMBER unit CONSTANT)
|
||||
|
||||
public:
|
||||
qreal value;
|
||||
qreal total;
|
||||
QString unit;
|
||||
};
|
||||
|
||||
class UsageFmt : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
public:
|
||||
Q_INVOKABLE [[nodiscard]] FormatResult formatKib(qreal kib, qreal total) const;
|
||||
};
|
||||
|
||||
} // namespace ZShell::services::usagefmt
|
||||
Reference in New Issue
Block a user