resources popout refresh + new components & reskinned old components
This commit is contained in:
@@ -17,8 +17,8 @@ BusyIndicator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
property int animState
|
property int animState
|
||||||
property color bgColour: DynamicColors.palette.m3secondaryContainer
|
property color bgColor: DynamicColors.palette.m3secondaryContainer
|
||||||
property color fgColour: DynamicColors.palette.m3primary
|
property color fgColor: DynamicColors.palette.m3primary
|
||||||
property real implicitSize: Appearance.font.size.normal * 3
|
property real implicitSize: Appearance.font.size.normal * 3
|
||||||
property real internalStrokeWidth: strokeWidth
|
property real internalStrokeWidth: strokeWidth
|
||||||
readonly property alias progress: manager.progress
|
readonly property alias progress: manager.progress
|
||||||
@@ -31,8 +31,8 @@ BusyIndicator {
|
|||||||
|
|
||||||
contentItem: CircularProgress {
|
contentItem: CircularProgress {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
bgColour: root.bgColour
|
bgColor: root.bgColor
|
||||||
fgColour: root.fgColour
|
fgColor: root.fgColor
|
||||||
padding: root.padding
|
padding: root.padding
|
||||||
rotation: manager.rotation
|
rotation: manager.rotation
|
||||||
startAngle: manager.startFraction * 360
|
startAngle: manager.startFraction * 360
|
||||||
@@ -73,7 +73,6 @@ BusyIndicator {
|
|||||||
|
|
||||||
CircularIndicatorManager {
|
CircularIndicatorManager {
|
||||||
id: manager
|
id: manager
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
|
|||||||
@@ -1,66 +1,116 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Shapes
|
import QtQuick.Shapes
|
||||||
|
import ZShell.Components
|
||||||
import qs.Config
|
import qs.Config
|
||||||
|
|
||||||
Shape {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property real arcRadius: (size - padding - strokeWidth) / 2
|
readonly property real arcRadius: (size - padding - strokeWidth * (1 + waveAmplitude * 2)) / 2
|
||||||
property color bgColour: DynamicColors.palette.m3secondaryContainer
|
property color bgColor: DynamicColors.palette.m3secondaryContainer
|
||||||
property color fgColour: DynamicColors.palette.m3primary
|
property real clampedVal: Math.max(1 / 360, Math.min(1, isNaN(value) ? 0 : value))
|
||||||
|
readonly property real dotAngleRad: (startAngle + sweepAngle - gapAngle * (sweepAngle < 360 ? 0 : 1)) * Math.PI / 180
|
||||||
|
property color fgColor: DynamicColors.palette.m3primary
|
||||||
readonly property real gapAngle: ((spacing + strokeWidth) / (arcRadius || 1)) * (180 / Math.PI)
|
readonly property real gapAngle: ((spacing + strokeWidth) / (arcRadius || 1)) * (180 / Math.PI)
|
||||||
|
property alias hasEndIndicator: dot.active
|
||||||
|
property real implicitSize
|
||||||
property int padding: 0
|
property int padding: 0
|
||||||
readonly property real size: Math.min(width, height)
|
readonly property real size: Math.min(width, height)
|
||||||
property int spacing: Appearance.spacing.small
|
property int spacing: Appearance.spacing.small
|
||||||
property int startAngle: -90
|
property int startAngle: -90
|
||||||
property int strokeWidth: Appearance.padding.smaller
|
property int strokeWidth: Appearance.padding.small
|
||||||
readonly property real vValue: value || 1 / 360
|
property int sweepAngle: 360
|
||||||
|
readonly property real thickness: strokeWidth * (1 + waveAmplitude) * 2
|
||||||
property real value
|
property real value
|
||||||
|
property alias waveAmplitude: wave.amplitudeMultiplier
|
||||||
|
property alias waveDuration: waveProgAnim.duration
|
||||||
|
property alias waveFrequency: wave.frequency
|
||||||
|
property bool wavePaused
|
||||||
|
property bool wavy: false
|
||||||
|
|
||||||
asynchronous: true
|
implicitHeight: implicitSize
|
||||||
preferredRendererType: Shape.CurveRenderer
|
implicitWidth: implicitSize
|
||||||
|
|
||||||
ShapePath {
|
Shape {
|
||||||
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
|
asynchronous: true
|
||||||
fillColor: "transparent"
|
opacity: Math.min(1, remainingArc.sweepAngle)
|
||||||
strokeColor: root.bgColour
|
preferredRendererType: Shape.CurveRenderer
|
||||||
strokeWidth: root.strokeWidth
|
|
||||||
|
|
||||||
Behavior on strokeColor {
|
ShapePath {
|
||||||
CAnim {
|
capStyle: ShapePath.RoundCap
|
||||||
duration: Appearance.anim.durations.large
|
fillColor: "transparent"
|
||||||
|
strokeColor: root.bgColor
|
||||||
|
strokeWidth: Math.min(1, remainingArc.sweepAngle) * root.strokeWidth
|
||||||
|
|
||||||
|
Behavior on strokeColor {
|
||||||
|
CAnim {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
PathAngleArc {
|
PathAngleArc {
|
||||||
centerX: root.size / 2
|
id: remainingArc
|
||||||
centerY: root.size / 2
|
|
||||||
radiusX: root.arcRadius
|
centerX: root.size / 2
|
||||||
radiusY: root.arcRadius
|
centerY: root.size / 2
|
||||||
startAngle: root.startAngle + 360 * root.vValue + root.gapAngle
|
radiusX: root.arcRadius
|
||||||
sweepAngle: Math.max(-root.gapAngle, 360 * (1 - root.vValue) - root.gapAngle * 2)
|
radiusY: root.arcRadius
|
||||||
|
startAngle: root.startAngle + root.clampedVal * root.sweepAngle + root.gapAngle
|
||||||
|
sweepAngle: Math.max(1 / 360, root.sweepAngle * (1 - root.clampedVal) - root.gapAngle * (root.sweepAngle < 360 ? 1 : 2))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ShapePath {
|
WavyLine {
|
||||||
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
|
id: wave
|
||||||
fillColor: "transparent"
|
|
||||||
strokeColor: root.fgColour
|
|
||||||
strokeWidth: root.strokeWidth
|
|
||||||
|
|
||||||
Behavior on strokeColor {
|
amplitudeMultiplier: root.wavy ? 0.5 : 0
|
||||||
CAnim {
|
anchors.fill: parent
|
||||||
duration: Appearance.anim.durations.large
|
anchors.margins: -lineWidth * amplitudeMultiplier
|
||||||
|
color: root.fgColor
|
||||||
|
frequency: 8
|
||||||
|
fullAngle: root.sweepAngle
|
||||||
|
lineWidth: root.strokeWidth
|
||||||
|
pathType: WavyLine.Arc
|
||||||
|
radius: root.arcRadius
|
||||||
|
startAngle: root.startAngle
|
||||||
|
value: root.clampedVal
|
||||||
|
|
||||||
|
Behavior on amplitudeMultiplier {
|
||||||
|
Anim {
|
||||||
|
type: Anim.DefaultEffects
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Behavior on color {
|
||||||
|
CAnim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Anim on waveProgress {
|
||||||
|
id: waveProgAnim
|
||||||
|
|
||||||
PathAngleArc {
|
duration: 2000
|
||||||
centerX: root.size / 2
|
easing.type: Easing.Linear
|
||||||
centerY: root.size / 2
|
from: 0
|
||||||
radiusX: root.arcRadius
|
loops: Animation.Infinite
|
||||||
radiusY: root.arcRadius
|
paused: root.wavePaused || wave.amplitudeMultiplier === 0
|
||||||
startAngle: root.startAngle
|
running: true
|
||||||
sweepAngle: 360 * root.vValue
|
to: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: dot
|
||||||
|
|
||||||
|
x: root.size / 2 + root.arcRadius * Math.cos(root.dotAngleRad) - width / 2
|
||||||
|
y: root.size / 2 + root.arcRadius * Math.sin(root.dotAngleRad) - height / 2
|
||||||
|
|
||||||
|
sourceComponent: CustomRect {
|
||||||
|
color: root.fgColor
|
||||||
|
implicitHeight: Math.min(1, remainingArc.sweepAngle) * Math.min(4, root.strokeWidth)
|
||||||
|
implicitWidth: Math.min(1, remainingArc.sweepAngle) * Math.min(4, root.strokeWidth)
|
||||||
|
opacity: Math.min(1, remainingArc.sweepAngle)
|
||||||
|
radius: Appearance.rounding.full
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,237 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Templates
|
||||||
|
import ZShell
|
||||||
|
import ZShell.Components
|
||||||
|
import ZShell.Internal
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
ProgressBar {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
enum IndeterminateAnimState {
|
||||||
|
Running,
|
||||||
|
Completing,
|
||||||
|
Stopped
|
||||||
|
}
|
||||||
|
|
||||||
|
property color bgColor: DynamicColors.palette.m3secondaryContainer
|
||||||
|
property color fgColor: DynamicColors.palette.m3primary
|
||||||
|
property int indeterminateAnimState: CustomProgressBar.Stopped
|
||||||
|
property real waveAmplitude: 0.5
|
||||||
|
property int waveDuration: 1000
|
||||||
|
property int waveFrequency: 6
|
||||||
|
property bool wavePaused
|
||||||
|
property bool wavy
|
||||||
|
|
||||||
|
function toBounds(startFrac: real, endFrac: real, gapSize: real): point {
|
||||||
|
startFrac = ZUtils.clamp(startFrac, 0, 1);
|
||||||
|
endFrac = ZUtils.clamp(endFrac, 0, 1);
|
||||||
|
|
||||||
|
// Ramp down gap size
|
||||||
|
const GAP_RAMP_DOWN_THRESHOLD = 0.01;
|
||||||
|
gapSize += height / 2;
|
||||||
|
const startGapSize = (gapSize * ZUtils.clamp(startFrac, 0, GAP_RAMP_DOWN_THRESHOLD) / GAP_RAMP_DOWN_THRESHOLD);
|
||||||
|
const endGapSize = (gapSize * (1 - ZUtils.clamp(endFrac, 1 - GAP_RAMP_DOWN_THRESHOLD, 1)) / GAP_RAMP_DOWN_THRESHOLD);
|
||||||
|
const start = width * startFrac + startGapSize;
|
||||||
|
const end = width * endFrac - endGapSize;
|
||||||
|
|
||||||
|
return start >= end ? Qt.point(0, 0) : Qt.point(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateIAnimState(): void {
|
||||||
|
if (indeterminate) {
|
||||||
|
manager.completeEndProgress = 0;
|
||||||
|
indeterminateAnimState = CustomProgressBar.Running;
|
||||||
|
} else if (indeterminateAnimState === CustomProgressBar.Running) {
|
||||||
|
indeterminateAnimState = CustomProgressBar.Completing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitHeight: 4
|
||||||
|
implicitWidth: 200
|
||||||
|
|
||||||
|
contentItem: Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
asynchronous: true
|
||||||
|
sourceComponent: root.indeterminate || root.indeterminateAnimState !== CustomProgressBar.Stopped ? indeterminateComp : determinateComp
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: updateIAnimState()
|
||||||
|
onIndeterminateChanged: updateIAnimState()
|
||||||
|
|
||||||
|
LinearIndicatorManager {
|
||||||
|
id: manager
|
||||||
|
|
||||||
|
gap: Appearance.spacing.extraSmall
|
||||||
|
|
||||||
|
Anim on completeEndProgress {
|
||||||
|
duration: manager.completeEndDuration
|
||||||
|
running: root.indeterminateAnimState === CustomProgressBar.Completing
|
||||||
|
to: 1
|
||||||
|
|
||||||
|
onFinished: {
|
||||||
|
if (root.indeterminateAnimState === CustomProgressBar.Completing)
|
||||||
|
root.indeterminateAnimState = CustomProgressBar.Stopped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Anim on progress {
|
||||||
|
duration: manager.duration
|
||||||
|
easing.type: Easing.Linear
|
||||||
|
from: 0
|
||||||
|
loops: Animation.Infinite
|
||||||
|
running: root.indeterminateAnimState !== CustomProgressBar.Stopped
|
||||||
|
to: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: determinateComp
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Line {
|
||||||
|
id: remaining
|
||||||
|
|
||||||
|
anchors.right: parent.right
|
||||||
|
implicitWidth: parent.width - wave.implicitWidth - Appearance.spacing.extraSmall
|
||||||
|
}
|
||||||
|
|
||||||
|
Line {
|
||||||
|
property real implicitSize
|
||||||
|
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: (parent.height - implicitHeight) / 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: root.fgColor
|
||||||
|
implicitHeight: implicitSize
|
||||||
|
implicitWidth: implicitSize
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
|
||||||
|
Behavior on implicitSize {
|
||||||
|
Anim {
|
||||||
|
type: Anim.FastSpatial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: implicitSize = Qt.binding(() => parent.width - wave.implicitWidth < parent.height ? parent.height : 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
Wave {
|
||||||
|
id: wave
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
|
||||||
|
Behavior on implicitWidth {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: implicitWidth = Qt.binding(() => parent.width * root.visualPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: indeterminateComp
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: content
|
||||||
|
|
||||||
|
Line {
|
||||||
|
bounds: {
|
||||||
|
const i = manager.activeIndicators[0]; // qmllint disable unresolved-type
|
||||||
|
return i ? root.toBounds(0, i.startFraction, i.gapSize / 2) : Qt.point(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Line {
|
||||||
|
bounds: {
|
||||||
|
const i = manager.activeIndicators[manager.activeIndicators.length - 1]; // qmllint disable unresolved-type
|
||||||
|
return i ? root.toBounds(i.endFraction, 1, i.gapSize / 2) : Qt.point(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Instantiator {
|
||||||
|
model: Math.max(manager.activeIndicators.length, 1) - 1 // qmllint disable unresolved-type
|
||||||
|
|
||||||
|
delegate: Line {
|
||||||
|
readonly property LinearIndicatorSegment cur: manager.activeIndicators[index] // qmllint disable unresolved-type
|
||||||
|
required property int index
|
||||||
|
readonly property LinearIndicatorSegment next: manager.activeIndicators[index + 1 % manager.activeIndicators.length] // qmllint disable unresolved-type
|
||||||
|
|
||||||
|
bounds: root.toBounds(cur.endFraction, next.startFraction, cur.gapSize / 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
onObjectAdded: (_, obj) => content.data.push(obj)
|
||||||
|
onObjectRemoved: (_, obj) => {
|
||||||
|
const idx = content.data.indexOf(obj);
|
||||||
|
if (idx !== -1)
|
||||||
|
content.data.splice(idx, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Instantiator {
|
||||||
|
model: manager.activeIndicators // qmllint disable unresolved-type
|
||||||
|
|
||||||
|
delegate: Wave {
|
||||||
|
readonly property point bounds: root.toBounds(modelData.startFraction, modelData.endFraction, modelData.gapSize / 2)
|
||||||
|
required property LinearIndicatorSegment modelData
|
||||||
|
|
||||||
|
color: root.fgColor
|
||||||
|
implicitWidth: bounds.y - bounds.x
|
||||||
|
x: bounds.x
|
||||||
|
}
|
||||||
|
|
||||||
|
onObjectAdded: (_, obj) => content.data.push(obj)
|
||||||
|
onObjectRemoved: (_, obj) => {
|
||||||
|
const idx = content.data.indexOf(obj);
|
||||||
|
if (idx !== -1)
|
||||||
|
content.data.splice(idx, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component Line: CustomRect {
|
||||||
|
property point bounds
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: root.bgColor
|
||||||
|
implicitHeight: parent.height
|
||||||
|
implicitWidth: bounds.y - bounds.x
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
x: bounds.x
|
||||||
|
}
|
||||||
|
component Wave: WavyLine {
|
||||||
|
id: wave
|
||||||
|
|
||||||
|
amplitudeMultiplier: root.wavy ? root.waveAmplitude : 0
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: root.fgColor
|
||||||
|
frequency: root.waveFrequency
|
||||||
|
fullLength: parent.width
|
||||||
|
implicitHeight: lineWidth * amplitudeMultiplier * 2 + lineWidth
|
||||||
|
lineWidth: parent.height
|
||||||
|
startX: x
|
||||||
|
|
||||||
|
Behavior on amplitudeMultiplier {
|
||||||
|
Anim {
|
||||||
|
type: Anim.DefaultEffects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on color {
|
||||||
|
CAnim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Anim on waveProgress {
|
||||||
|
duration: root.waveDuration
|
||||||
|
easing.type: Easing.Linear
|
||||||
|
from: 0
|
||||||
|
loops: Animation.Infinite
|
||||||
|
paused: wave.amplitudeMultiplier === 0 || root.wavePaused
|
||||||
|
running: true
|
||||||
|
to: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,9 +77,11 @@ JsonObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
component Padding: JsonObject {
|
component Padding: JsonObject {
|
||||||
|
property int extraLarge: 28 * scale
|
||||||
property int extraLargeIncreased: 32 * scale
|
property int extraLargeIncreased: 32 * scale
|
||||||
property int extraSmall: 4 * scale
|
property int extraSmall: 4 * scale
|
||||||
property int large: 16 * scale
|
property int large: 16 * scale
|
||||||
|
property int largeIncreased: 20 * scale
|
||||||
property int larger: 12 * scale
|
property int larger: 12 * scale
|
||||||
property int normal: 8 * scale
|
property int normal: 8 * scale
|
||||||
property real scale: 1
|
property real scale: 1
|
||||||
|
|||||||
+1
-1
@@ -271,7 +271,7 @@ CustomWindow {
|
|||||||
implicitHeight: panels.resources.height
|
implicitHeight: panels.resources.height
|
||||||
implicitWidth: panels.resources.width
|
implicitWidth: panels.resources.width
|
||||||
panel: panels.resourcesWrapper
|
panel: panels.resourcesWrapper
|
||||||
radius: Appearance.rounding.normal
|
radius: Appearance.rounding.large
|
||||||
x: panels.resourcesWrapper.x + panels.resources.x + Config.barConfig.border
|
x: panels.resourcesWrapper.x + panels.resources.x + Config.barConfig.border
|
||||||
y: panels.resourcesWrapper.y + panels.resources.y + bar.implicitHeight
|
y: panels.resourcesWrapper.y + panels.resources.y + bar.implicitHeight
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,33 +9,13 @@ Singleton {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string autoGpuType: "NONE"
|
property string autoGpuType: "NONE"
|
||||||
property string cpuName: ""
|
|
||||||
property real cpuPerc
|
|
||||||
property real cpuTemp
|
|
||||||
|
|
||||||
// Individual disks: Array of { mount, used, total, free, perc }
|
|
||||||
property var disks: []
|
|
||||||
property real gpuMemTotal: 0
|
property real gpuMemTotal: 0
|
||||||
property real gpuMemUsed
|
property real gpuMemUsed
|
||||||
property string gpuName
|
property string gpuName
|
||||||
property real gpuPerc
|
property real gpuPerc
|
||||||
property real gpuTemp
|
property real gpuTemp
|
||||||
readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType
|
readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType
|
||||||
property real lastCpuIdle
|
|
||||||
property real lastCpuTotal
|
|
||||||
readonly property real memPerc: memTotal > 0 ? memUsed / memTotal : 0
|
|
||||||
property real memTotal
|
|
||||||
property real memUsed
|
|
||||||
property int refCount
|
property int refCount
|
||||||
readonly property real storagePerc: {
|
|
||||||
let totalUsed = 0;
|
|
||||||
let totalSize = 0;
|
|
||||||
for (const disk of disks) {
|
|
||||||
totalUsed += disk.used;
|
|
||||||
totalSize += disk.total;
|
|
||||||
}
|
|
||||||
return totalSize > 0 ? totalUsed / totalSize : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanCpuName(name: string): string {
|
function cleanCpuName(name: string): string {
|
||||||
return name.replace(/\(R\)/gi, "").replace(/\(TM\)/gi, "").replace(/CPU/gi, "").replace(/\d+th Gen /gi, "").replace(/\d+nd Gen /gi, "").replace(/\d+rd Gen /gi, "").replace(/\d+st Gen /gi, "").replace(/Core /gi, "").replace(/Processor/gi, "").replace(/\s+/g, " ").trim();
|
return name.replace(/\(R\)/gi, "").replace(/\(TM\)/gi, "").replace(/CPU/gi, "").replace(/\d+th Gen /gi, "").replace(/\d+nd Gen /gi, "").replace(/\d+rd Gen /gi, "").replace(/\d+st Gen /gi, "").replace(/Core /gi, "").replace(/Processor/gi, "").replace(/\s+/g, " ").trim();
|
||||||
@@ -78,8 +58,6 @@ Singleton {
|
|||||||
triggeredOnStart: true
|
triggeredOnStart: true
|
||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
stat.reload();
|
|
||||||
meminfo.reload();
|
|
||||||
if (root.gpuType === "GENERIC")
|
if (root.gpuType === "GENERIC")
|
||||||
gpuUsage.running = true;
|
gpuUsage.running = true;
|
||||||
|
|
||||||
@@ -88,170 +66,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
|
||||||
interval: 60000 * 120
|
|
||||||
repeat: true
|
|
||||||
running: true
|
|
||||||
triggeredOnStart: true
|
|
||||||
|
|
||||||
onTriggered: {
|
|
||||||
storage.running = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
interval: Config.dashboard.resourceUpdateInterval * 5
|
|
||||||
repeat: true
|
|
||||||
running: root.refCount > 0
|
|
||||||
triggeredOnStart: true
|
|
||||||
|
|
||||||
onTriggered: {
|
|
||||||
sensors.running = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: cpuinfoInit
|
|
||||||
|
|
||||||
path: "/proc/cpuinfo"
|
|
||||||
|
|
||||||
onLoaded: {
|
|
||||||
const nameMatch = text().match(/model name\s*:\s*(.+)/);
|
|
||||||
if (nameMatch)
|
|
||||||
root.cpuName = root.cleanCpuName(nameMatch[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: stat
|
|
||||||
|
|
||||||
path: "/proc/stat"
|
|
||||||
|
|
||||||
onLoaded: {
|
|
||||||
const data = text().match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/);
|
|
||||||
if (data) {
|
|
||||||
const stats = data.slice(1).map(n => parseInt(n, 10));
|
|
||||||
const total = stats.reduce((a, b) => a + b, 0);
|
|
||||||
const idle = stats[3] + (stats[4] ?? 0);
|
|
||||||
|
|
||||||
const totalDiff = total - root.lastCpuTotal;
|
|
||||||
const idleDiff = idle - root.lastCpuIdle;
|
|
||||||
const newCpuPerc = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0;
|
|
||||||
|
|
||||||
root.lastCpuTotal = total;
|
|
||||||
root.lastCpuIdle = idle;
|
|
||||||
|
|
||||||
if (Math.abs(newCpuPerc - root.cpuPerc) >= 0.01)
|
|
||||||
root.cpuPerc = newCpuPerc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: meminfo
|
|
||||||
|
|
||||||
path: "/proc/meminfo"
|
|
||||||
|
|
||||||
onLoaded: {
|
|
||||||
const data = text();
|
|
||||||
const total = parseInt(data.match(/MemTotal: *(\d+)/)[1], 10) || 1;
|
|
||||||
const used = (root.memTotal - parseInt(data.match(/MemAvailable: *(\d+)/)[1], 10)) || 0;
|
|
||||||
|
|
||||||
if (root.memTotal !== total)
|
|
||||||
root.memTotal = total;
|
|
||||||
|
|
||||||
if (Math.abs(used - root.memUsed) >= 16384)
|
|
||||||
root.memUsed = used;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: storage
|
|
||||||
|
|
||||||
command: ["lsblk", "-b", "-o", "NAME,SIZE,TYPE,FSUSED,FSSIZE", "-P"]
|
|
||||||
|
|
||||||
stdout: StdioCollector {
|
|
||||||
onStreamFinished: {
|
|
||||||
const diskMap = {}; // Map disk name -> { name, totalSize, used, fsTotal }
|
|
||||||
const lines = text.trim().split("\n");
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.trim() === "")
|
|
||||||
continue;
|
|
||||||
const nameMatch = line.match(/NAME="([^"]+)"/);
|
|
||||||
const sizeMatch = line.match(/SIZE="([^"]+)"/);
|
|
||||||
const typeMatch = line.match(/TYPE="([^"]+)"/);
|
|
||||||
const fsusedMatch = line.match(/FSUSED="([^"]*)"/);
|
|
||||||
const fssizeMatch = line.match(/FSSIZE="([^"]*)"/);
|
|
||||||
|
|
||||||
if (!nameMatch || !typeMatch)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const name = nameMatch[1];
|
|
||||||
const type = typeMatch[1];
|
|
||||||
const size = parseInt(sizeMatch?.[1] || "0", 10);
|
|
||||||
const fsused = parseInt(fsusedMatch?.[1] || "0", 10);
|
|
||||||
const fssize = parseInt(fssizeMatch?.[1] || "0", 10);
|
|
||||||
|
|
||||||
if (type === "disk") {
|
|
||||||
// Skip zram (swap) devices
|
|
||||||
if (name.startsWith("zram"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Initialize disk entry
|
|
||||||
if (!diskMap[name]) {
|
|
||||||
diskMap[name] = {
|
|
||||||
name: name,
|
|
||||||
totalSize: size,
|
|
||||||
used: 0,
|
|
||||||
fsTotal: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else if (type === "part") {
|
|
||||||
// Find parent disk (remove trailing numbers/p+numbers)
|
|
||||||
let parentDisk = name.replace(/p?\d+$/, "");
|
|
||||||
// For nvme devices like nvme0n1p1, parent is nvme0n1
|
|
||||||
if (name.match(/nvme\d+n\d+p\d+/))
|
|
||||||
parentDisk = name.replace(/p\d+$/, "");
|
|
||||||
|
|
||||||
// Aggregate partition usage to parent disk
|
|
||||||
if (diskMap[parentDisk]) {
|
|
||||||
diskMap[parentDisk].used += fsused;
|
|
||||||
diskMap[parentDisk].fsTotal += fssize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const diskList = [];
|
|
||||||
let totalUsed = 0;
|
|
||||||
let totalSize = 0;
|
|
||||||
|
|
||||||
for (const diskName of Object.keys(diskMap).sort()) {
|
|
||||||
const disk = diskMap[diskName];
|
|
||||||
// Use filesystem total if available, otherwise use disk size
|
|
||||||
const total = disk.fsTotal > 0 ? disk.fsTotal : disk.totalSize;
|
|
||||||
const used = disk.used;
|
|
||||||
const perc = total > 0 ? used / total : 0;
|
|
||||||
|
|
||||||
// Convert bytes to KiB for consistency with formatKib
|
|
||||||
diskList.push({
|
|
||||||
mount: disk.name // Using 'mount' property for compatibility
|
|
||||||
,
|
|
||||||
used: used / 1024,
|
|
||||||
total: total / 1024,
|
|
||||||
free: (total - used) / 1024,
|
|
||||||
perc: perc
|
|
||||||
});
|
|
||||||
|
|
||||||
totalUsed += used;
|
|
||||||
totalSize += total;
|
|
||||||
}
|
|
||||||
|
|
||||||
root.disks = diskList;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: gpuNameDetect
|
id: gpuNameDetect
|
||||||
|
|
||||||
@@ -412,53 +226,4 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
|
||||||
id: sensors
|
|
||||||
|
|
||||||
command: ["sensors"]
|
|
||||||
environment: ({
|
|
||||||
LANG: "C.UTF-8",
|
|
||||||
LC_ALL: "C.UTF-8"
|
|
||||||
})
|
|
||||||
|
|
||||||
stdout: StdioCollector {
|
|
||||||
onStreamFinished: {
|
|
||||||
let cpuTemp = text.match(/(?:Package id [0-9]+|Tdie):\s+((\+|-)[0-9.]+)(°| )C/);
|
|
||||||
if (!cpuTemp)
|
|
||||||
// If AMD Tdie pattern failed, try fallback on Tctl
|
|
||||||
cpuTemp = text.match(/Tctl:\s+((\+|-)[0-9.]+)(°| )C/);
|
|
||||||
|
|
||||||
if (cpuTemp && Math.abs(parseFloat(cpuTemp[1]) - root.cpuTemp) >= 0.5)
|
|
||||||
root.cpuTemp = parseFloat(cpuTemp[1]);
|
|
||||||
|
|
||||||
if (root.gpuType !== "GENERIC")
|
|
||||||
return;
|
|
||||||
|
|
||||||
let eligible = false;
|
|
||||||
let sum = 0;
|
|
||||||
let count = 0;
|
|
||||||
|
|
||||||
for (const line of text.trim().split("\n")) {
|
|
||||||
if (line === "Adapter: PCI adapter")
|
|
||||||
eligible = true;
|
|
||||||
else if (line === "")
|
|
||||||
eligible = false;
|
|
||||||
else if (eligible) {
|
|
||||||
let match = line.match(/^(temp[0-9]+|GPU core|edge)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
|
|
||||||
if (!match)
|
|
||||||
// Fall back to junction/mem if GPU doesn't have edge temp (for AMD GPUs)
|
|
||||||
match = line.match(/^(junction|mem)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
|
|
||||||
|
|
||||||
if (match) {
|
|
||||||
sum += parseFloat(match[2]);
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
root.gpuTemp = count > 0 ? sum / count : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,92 +1,89 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import ZShell.Services
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Helpers
|
import qs.Helpers
|
||||||
import qs.Config
|
import qs.Config
|
||||||
|
|
||||||
Row {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
padding: Appearance.padding.large
|
implicitWidth: layout.implicitWidth + layout.anchors.margins * 4
|
||||||
spacing: Appearance.spacing.large
|
|
||||||
|
ServiceRef {
|
||||||
|
service: Storage
|
||||||
|
}
|
||||||
|
|
||||||
|
ServiceRef {
|
||||||
|
service: Memory
|
||||||
|
}
|
||||||
|
|
||||||
|
ServiceRef {
|
||||||
|
service: Cpu
|
||||||
|
}
|
||||||
|
|
||||||
Ref {
|
Ref {
|
||||||
service: SystemUsage
|
service: SystemUsage
|
||||||
}
|
}
|
||||||
|
|
||||||
Resource {
|
ColumnLayout {
|
||||||
color: DynamicColors.palette.m3primary
|
id: layout
|
||||||
icon: "memory"
|
|
||||||
value: SystemUsage.cpuPerc
|
|
||||||
}
|
|
||||||
|
|
||||||
Resource {
|
|
||||||
color: DynamicColors.palette.m3secondary
|
|
||||||
icon: "memory_alt"
|
|
||||||
value: SystemUsage.memPerc
|
|
||||||
}
|
|
||||||
|
|
||||||
Resource {
|
|
||||||
color: DynamicColors.palette.m3tertiary
|
|
||||||
icon: "gamepad"
|
|
||||||
value: SystemUsage.gpuPerc
|
|
||||||
}
|
|
||||||
|
|
||||||
Resource {
|
|
||||||
color: DynamicColors.palette.m3primary
|
|
||||||
icon: "host"
|
|
||||||
value: SystemUsage.gpuMemUsed
|
|
||||||
}
|
|
||||||
|
|
||||||
Resource {
|
|
||||||
color: DynamicColors.palette.m3secondary
|
|
||||||
icon: "hard_disk"
|
|
||||||
value: SystemUsage.storagePerc
|
|
||||||
}
|
|
||||||
|
|
||||||
component Resource: Item {
|
|
||||||
id: res
|
|
||||||
|
|
||||||
required property color color
|
|
||||||
required property string icon
|
|
||||||
required property real value
|
|
||||||
|
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.margins: Appearance.padding.large
|
anchors.margins: Appearance.padding.large
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
implicitWidth: icon.implicitWidth
|
spacing: Appearance.spacing.normal
|
||||||
|
|
||||||
Behavior on value {
|
Resource {
|
||||||
Anim {
|
fgColor: DynamicColors.palette.m3primary
|
||||||
duration: Appearance.anim.durations.large
|
icon: "memory"
|
||||||
}
|
value: Cpu.percentage
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomRect {
|
Resource {
|
||||||
anchors.bottom: icon.top
|
fgColor: DynamicColors.palette.m3secondary
|
||||||
anchors.bottomMargin: Appearance.spacing.small
|
icon: "memory_alt"
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
value: Memory.percentage
|
||||||
anchors.top: parent.top
|
}
|
||||||
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
|
|
||||||
implicitWidth: Config.dashboard.sizes.resourceProgessThickness
|
|
||||||
radius: Appearance.rounding.full
|
|
||||||
|
|
||||||
CustomRect {
|
Resource {
|
||||||
anchors.bottom: parent.bottom
|
fgColor: DynamicColors.palette.m3tertiary
|
||||||
anchors.left: parent.left
|
icon: "gamepad"
|
||||||
anchors.right: parent.right
|
value: SystemUsage.gpuPerc
|
||||||
color: res.color
|
}
|
||||||
implicitHeight: res.value * parent.height
|
|
||||||
radius: Appearance.rounding.full
|
Resource {
|
||||||
|
fgColor: DynamicColors.palette.m3primary
|
||||||
|
icon: "host"
|
||||||
|
value: SystemUsage.gpuMemUsed
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource {
|
||||||
|
fgColor: DynamicColors.palette.m3secondary
|
||||||
|
icon: "hard_disk"
|
||||||
|
value: Storage.percentage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component Resource: CircularProgress {
|
||||||
|
id: res
|
||||||
|
|
||||||
|
required property string icon
|
||||||
|
|
||||||
|
Layout.fillHeight: true
|
||||||
|
implicitSize: height
|
||||||
|
|
||||||
|
Behavior on clampedVal {
|
||||||
|
Anim {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
MaterialIcon {
|
||||||
id: icon
|
anchors.centerIn: parent
|
||||||
|
color: res.fgColor
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
color: res.color
|
|
||||||
text: res.icon
|
text: res.icon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ GridLayout {
|
|||||||
Resource {
|
Resource {
|
||||||
Layout.bottomMargin: Appearance.padding.large
|
Layout.bottomMargin: Appearance.padding.large
|
||||||
Layout.topMargin: Appearance.padding.large
|
Layout.topMargin: Appearance.padding.large
|
||||||
colour: DynamicColors.palette.m3primary
|
color: DynamicColors.palette.m3primary
|
||||||
icon: "memory"
|
icon: "memory"
|
||||||
value: SystemUsage.cpuPerc
|
value: SystemUsage.cpuPerc
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ GridLayout {
|
|||||||
Resource {
|
Resource {
|
||||||
Layout.bottomMargin: Appearance.padding.large
|
Layout.bottomMargin: Appearance.padding.large
|
||||||
Layout.topMargin: Appearance.padding.large
|
Layout.topMargin: Appearance.padding.large
|
||||||
colour: DynamicColors.palette.m3secondary
|
color: DynamicColors.palette.m3secondary
|
||||||
icon: "memory_alt"
|
icon: "memory_alt"
|
||||||
value: SystemUsage.memPerc
|
value: SystemUsage.memPerc
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ GridLayout {
|
|||||||
component Resource: CustomRect {
|
component Resource: CustomRect {
|
||||||
id: res
|
id: res
|
||||||
|
|
||||||
required property color colour
|
required property color color
|
||||||
required property string icon
|
required property string icon
|
||||||
required property real value
|
required property real value
|
||||||
|
|
||||||
@@ -58,8 +58,8 @@ GridLayout {
|
|||||||
id: circ
|
id: circ
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
bgColour: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 3)
|
bgColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 3)
|
||||||
fgColour: res.colour
|
fgColor: res.color
|
||||||
padding: Appearance.padding.large * 3
|
padding: Appearance.padding.large * 3
|
||||||
strokeWidth: width < 200 ? Appearance.padding.smaller : Appearance.padding.normal
|
strokeWidth: width < 200 ? Appearance.padding.smaller : Appearance.padding.normal
|
||||||
value: res.value
|
value: res.value
|
||||||
@@ -69,7 +69,7 @@ GridLayout {
|
|||||||
id: icon
|
id: icon
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
color: res.colour
|
color: res.color
|
||||||
font.pointSize: (circ.arcRadius * 0.7) || 1
|
font.pointSize: (circ.arcRadius * 0.7) || 1
|
||||||
font.weight: 600
|
font.weight: 600
|
||||||
text: res.icon
|
text: res.icon
|
||||||
|
|||||||
+17
-7
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import ZShell.Services
|
||||||
import qs.Helpers
|
import qs.Helpers
|
||||||
import qs.Modules
|
import qs.Modules
|
||||||
import qs.Config
|
import qs.Config
|
||||||
@@ -16,7 +17,7 @@ CustomRect {
|
|||||||
clip: true
|
clip: true
|
||||||
color: visibilities.resources ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
|
color: visibilities.resources ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
|
||||||
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
|
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
|
||||||
implicitWidth: rowLayout.implicitWidth + Appearance.padding.normal * 2
|
implicitWidth: rowLayout.implicitWidth + Appearance.padding.larger * 2
|
||||||
radius: Appearance.rounding.full
|
radius: Appearance.rounding.full
|
||||||
|
|
||||||
StateLayer {
|
StateLayer {
|
||||||
@@ -29,6 +30,7 @@ CustomRect {
|
|||||||
id: rowLayout
|
id: rowLayout
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
anchors.horizontalCenterOffset: -2
|
||||||
implicitHeight: root.implicitHeight
|
implicitHeight: root.implicitHeight
|
||||||
spacing: Appearance.spacing.smaller
|
spacing: Appearance.spacing.smaller
|
||||||
|
|
||||||
@@ -36,13 +38,21 @@ CustomRect {
|
|||||||
service: SystemUsage
|
service: SystemUsage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ServiceRef {
|
||||||
|
service: Cpu
|
||||||
|
}
|
||||||
|
|
||||||
|
ServiceRef {
|
||||||
|
service: Memory
|
||||||
|
}
|
||||||
|
|
||||||
Resource {
|
Resource {
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
icon: "memory"
|
icon: "memory"
|
||||||
iconColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
|
iconColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
|
||||||
mainColor: root.visibilities.resources ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3primary
|
mainColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3primary
|
||||||
percentage: SystemUsage.cpuPerc
|
percentage: Cpu.percentage
|
||||||
warningThreshold: 95
|
warningThreshold: 95
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,8 +60,8 @@ CustomRect {
|
|||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
icon: "memory_alt"
|
icon: "memory_alt"
|
||||||
iconColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
|
iconColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
|
||||||
mainColor: root.visibilities.resources ? DynamicColors.palette.m3secondaryContainer : DynamicColors.palette.m3secondary
|
mainColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3secondary
|
||||||
percentage: SystemUsage.memPerc
|
percentage: Memory.percentage
|
||||||
warningThreshold: 80
|
warningThreshold: 80
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +69,7 @@ CustomRect {
|
|||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
icon: "gamepad"
|
icon: "gamepad"
|
||||||
iconColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
|
iconColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
|
||||||
mainColor: root.visibilities.resources ? DynamicColors.palette.m3tertiaryContainer : DynamicColors.palette.m3tertiary
|
mainColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3tertiary
|
||||||
percentage: SystemUsage.gpuPerc
|
percentage: SystemUsage.gpuPerc
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +77,7 @@ CustomRect {
|
|||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
icon: "developer_board"
|
icon: "developer_board"
|
||||||
iconColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
|
iconColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
|
||||||
mainColor: root.visibilities.resources ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3primary
|
mainColor: root.visibilities.resources ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3primary
|
||||||
percentage: SystemUsage.gpuMemUsed
|
percentage: SystemUsage.gpuMemUsed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell.Services.UPower
|
||||||
|
import qs.Components
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
CustomClippingRect {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property real animPerc: UPower.displayDevice.percentage
|
||||||
|
|
||||||
|
color: DynamicColors.palette.m3secondaryContainer
|
||||||
|
implicitWidth: 120
|
||||||
|
radius: Appearance.rounding.large
|
||||||
|
|
||||||
|
Behavior on animPerc {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Contents {
|
||||||
|
id: layout
|
||||||
|
|
||||||
|
accentColor: DynamicColors.palette.m3primary
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Appearance.padding.larger
|
||||||
|
subTextColor: DynamicColors.palette.m3onSurfaceVariant
|
||||||
|
textColor: DynamicColors.palette.m3onSurface
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
clip: true
|
||||||
|
color: DynamicColors.palette.m3secondary
|
||||||
|
implicitHeight: parent.height * root.animPerc
|
||||||
|
radius: Appearance.rounding.extraSmall
|
||||||
|
|
||||||
|
Contents {
|
||||||
|
accentColor: DynamicColors.palette.m3primaryContainer
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.margins: layout.anchors.margins
|
||||||
|
anchors.right: parent.right
|
||||||
|
height: layout.height
|
||||||
|
subTextColor: DynamicColors.palette.m3secondaryContainer
|
||||||
|
textColor: DynamicColors.palette.m3onSecondary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component Contents: ColumnLayout {
|
||||||
|
id: contents
|
||||||
|
|
||||||
|
required property color accentColor
|
||||||
|
readonly property bool charging: [UPowerDeviceState.Charging, UPowerDeviceState.FullyCharged, UPowerDeviceState.PendingCharge].includes(UPower.displayDevice.state)
|
||||||
|
required property color subTextColor
|
||||||
|
required property color textColor
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.leftMargin: -Appearance.padding.extraSmall
|
||||||
|
color: contents.accentColor
|
||||||
|
text: "battery_full"
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
color: contents.textColor
|
||||||
|
text: qsTr("Battery")
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
animate: true
|
||||||
|
color: contents.subTextColor
|
||||||
|
text: {
|
||||||
|
if (UPower.displayDevice.state === UPowerDeviceState.FullyCharged)
|
||||||
|
return qsTr("Full");
|
||||||
|
|
||||||
|
if (contents.charging)
|
||||||
|
return qsTr("Charging");
|
||||||
|
|
||||||
|
const s = UPower.displayDevice.timeToEmpty;
|
||||||
|
if (s === 0)
|
||||||
|
return qsTr("...");
|
||||||
|
|
||||||
|
const hr = Math.floor(s / 3600);
|
||||||
|
const min = Math.floor((s % 3600) / 60);
|
||||||
|
if (hr > 0)
|
||||||
|
return `${hr}h ${min}m`;
|
||||||
|
|
||||||
|
return `${min}m`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
Layout.bottomMargin: -Appearance.padding.small
|
||||||
|
Layout.rightMargin: -Appearance.padding.extraSmall
|
||||||
|
Layout.topMargin: -Appearance.padding.extraSmall
|
||||||
|
spacing: Appearance.spacing.extraSmall
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
color: contents.accentColor
|
||||||
|
fill: 1
|
||||||
|
opacity: contents.charging ? 1 : 0
|
||||||
|
scale: contents.charging ? 1 : 0
|
||||||
|
text: "bolt"
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {
|
||||||
|
type: Anim.FastEffects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on scale {
|
||||||
|
Anim {
|
||||||
|
type: Anim.FastSpatial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
color: contents.accentColor
|
||||||
|
text: `${Math.round(UPower.displayDevice.percentage * 100)}%`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Components
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
CustomClippingRect {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property color accent
|
||||||
|
required property string icon
|
||||||
|
required property string label
|
||||||
|
required property string subLabel
|
||||||
|
required property real temperature
|
||||||
|
required property real usage
|
||||||
|
|
||||||
|
color: DynamicColors.tPalette.m3surfaceContainer
|
||||||
|
implicitHeight: Math.max(tempProg.implicitHeight + detailsRow.implicitHeight + Appearance.spacing.large, usageColumn.implicitHeight + usageLabel.implicitHeight) + Appearance.padding.large * 2
|
||||||
|
implicitWidth: 450
|
||||||
|
radius: Appearance.rounding.large - Appearance.padding.normal
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.top: parent.top
|
||||||
|
color: Qt.alpha(root.accent, 0.05)
|
||||||
|
implicitWidth: parent.width * root.usage
|
||||||
|
|
||||||
|
Behavior on implicitWidth {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CircularProgress {
|
||||||
|
id: tempProg
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.margins: Appearance.padding.large
|
||||||
|
anchors.top: parent.top
|
||||||
|
fgColor: root.accent
|
||||||
|
implicitSize: Math.max(icon.implicitWidth, icon.implicitHeight) + Appearance.padding.larger * 2
|
||||||
|
spacing: Appearance.spacing.extraSmall
|
||||||
|
strokeWidth: Appearance.padding.extraSmall
|
||||||
|
value: root.usage
|
||||||
|
|
||||||
|
Behavior on clampedVal {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
id: icon
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
color: root.accent
|
||||||
|
text: root.icon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.left: tempProg.right
|
||||||
|
anchors.margins: Appearance.spacing.large
|
||||||
|
anchors.right: usageColumn.left
|
||||||
|
anchors.verticalCenter: tempProg.verticalCenter
|
||||||
|
spacing: Appearance.spacing.extraSmall
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
color: root.accent
|
||||||
|
text: root.label
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
|
elide: Text.ElideRight
|
||||||
|
text: root.subLabel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: detailsRow
|
||||||
|
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.margins: Appearance.padding.largeIncreased
|
||||||
|
spacing: Appearance.spacing.extraSmall
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.leftMargin: -Appearance.padding.extraSmall
|
||||||
|
spacing: Appearance.spacing.extraSmall
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.topMargin: Math.round(fontInfo.pointSize * 0.08)
|
||||||
|
color: root.temperature > 90 ? DynamicColors.palette.m3error : root.accent
|
||||||
|
fill: 1
|
||||||
|
text: root.temperature > 90 ? "thermometer_alert" : "thermometer"
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
text: `${Math.ceil(root.temperature)}°${"C"}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomProgressBar {
|
||||||
|
fgColor: root.accent
|
||||||
|
implicitHeight: Appearance.padding.small
|
||||||
|
indeterminate: isNaN(root.usage) || isNaN(root.temperature)
|
||||||
|
value: root.temperature / 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: usageColumn
|
||||||
|
|
||||||
|
anchors.margins: Appearance.padding.large
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 32
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
id: usageLabel
|
||||||
|
|
||||||
|
anchors.right: parent.right
|
||||||
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
|
font.pointSize: Appearance.font.size.normal
|
||||||
|
text: qsTr("Usage")
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
anchors.right: parent.right
|
||||||
|
color: root.accent
|
||||||
|
font.pointSize: Appearance.font.size.extraLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
text: isNaN(root.usage) ? "...%" : Math.round(root.usage * 100) + "%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import ZShell.Services
|
||||||
|
import qs.Components
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property color accent: DynamicColors.palette.m3tertiary
|
||||||
|
|
||||||
|
color: DynamicColors.tPalette.m3surfaceContainer
|
||||||
|
implicitHeight: layout.implicitHeight + Appearance.padding.large * 2
|
||||||
|
implicitWidth: layout.implicitWidth + Appearance.padding.extraLargeIncreased * 2
|
||||||
|
radius: Appearance.rounding.medium
|
||||||
|
|
||||||
|
ServiceRef {
|
||||||
|
service: Memory
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: layout
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Appearance.spacing.extraSmall
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.leftMargin: -Appearance.padding.extraSmall
|
||||||
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
color: root.accent
|
||||||
|
fill: 1
|
||||||
|
text: "memory_alt"
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
text: qsTr("Memory")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CircularProgress {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.topMargin: Appearance.spacing.large
|
||||||
|
fgColor: root.accent
|
||||||
|
implicitSize: usageColumn.implicitHeight + thickness + Appearance.padding.largeIncreased * 2
|
||||||
|
startAngle: -225
|
||||||
|
sweepAngle: 270
|
||||||
|
value: Memory.percentage
|
||||||
|
|
||||||
|
Behavior on clampedVal {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: usageColumn
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
anchors.verticalCenterOffset: Appearance.padding.extraSmall
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
color: root.accent
|
||||||
|
text: Math.round(Memory.percentage * 100) + "%"
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
|
text: qsTr("Used")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: {
|
||||||
|
const fmt = UsageFmt.formatKib(Memory.used, Memory.total);
|
||||||
|
return `${fmt.value.toFixed(1)} / ${Math.floor(fmt.total)} ${fmt.unit}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import ZShell.Internal
|
||||||
|
import qs.Helpers
|
||||||
|
import qs.Components
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
color: DynamicColors.tPalette.m3surfaceContainer
|
||||||
|
implicitHeight: 220
|
||||||
|
implicitWidth: 300
|
||||||
|
radius: Appearance.rounding.large - Appearance.padding.normal
|
||||||
|
|
||||||
|
Ref {
|
||||||
|
service: NetworkUsage
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: layout
|
||||||
|
|
||||||
|
anchors.bottomMargin: Appearance.padding.larger
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Appearance.padding.large
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
color: DynamicColors.palette.m3primary
|
||||||
|
text: "swap_vert"
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
text: qsTr("Network")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.bottomMargin: Appearance.spacing.small
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: Appearance.spacing.larger
|
||||||
|
|
||||||
|
SparklineItem {
|
||||||
|
id: sparkline
|
||||||
|
|
||||||
|
property real smoothMax: targetMax
|
||||||
|
property real targetMax: 1024
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
historyLength: NetworkUsage.historyLength
|
||||||
|
line1: NetworkUsage.uploadBuffer // qmllint disable missing-type
|
||||||
|
line1Color: DynamicColors.palette.m3secondary
|
||||||
|
line1FillAlpha: 0.15
|
||||||
|
line2: NetworkUsage.downloadBuffer // qmllint disable missing-type
|
||||||
|
line2Color: DynamicColors.palette.m3tertiary
|
||||||
|
line2FillAlpha: 0.2
|
||||||
|
maxValue: smoothMax
|
||||||
|
|
||||||
|
Behavior on smoothMax {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onValuesChanged(): void {
|
||||||
|
sparkline.targetMax = Math.max(NetworkUsage.downloadBuffer.maximum, NetworkUsage.uploadBuffer.maximum, 1024);
|
||||||
|
slideAnim.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
target: NetworkUsage.downloadBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
id: slideAnim
|
||||||
|
|
||||||
|
duration: Config.dashboard.resourceUpdateInterval
|
||||||
|
easing.type: Easing.Linear
|
||||||
|
from: 0
|
||||||
|
property: "slideProgress"
|
||||||
|
target: sparkline
|
||||||
|
to: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Collecting data" placeholder
|
||||||
|
CustomText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
color: DynamicColors.palette.m3outline
|
||||||
|
text: qsTr("Collecting data...")
|
||||||
|
visible: NetworkUsage.downloadBuffer.count < 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download row
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
color: DynamicColors.palette.m3tertiary
|
||||||
|
text: "download"
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
|
text: qsTr("Download")
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
color: DynamicColors.palette.m3tertiary
|
||||||
|
text: {
|
||||||
|
const fmt = NetworkUsage.formatBytes(NetworkUsage.downloadSpeed ?? 0);
|
||||||
|
return fmt ? `${fmt.value.toFixed(1)} ${fmt.unit}` : "0.0 B/s";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload row
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
color: DynamicColors.palette.m3secondary
|
||||||
|
text: "upload"
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
|
text: qsTr("Upload")
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
color: DynamicColors.palette.m3secondary
|
||||||
|
text: {
|
||||||
|
const fmt = NetworkUsage.formatBytes(NetworkUsage.uploadSpeed ?? 0);
|
||||||
|
return fmt ? `${fmt.value.toFixed(1)} ${fmt.unit}` : "0.0 B/s";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session totals
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
|
text: "history"
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
|
text: qsTr("Total")
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
|
text: {
|
||||||
|
const down = NetworkUsage.formatBytesTotal(NetworkUsage.downloadTotal ?? 0);
|
||||||
|
const up = NetworkUsage.formatBytesTotal(NetworkUsage.uploadTotal ?? 0);
|
||||||
|
return (down && up) ? `↓${down.value.toFixed(1)}${down.unit} ↑${up.value.toFixed(1)}${up.unit}` : "↓0.0B ↑0.0B";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import ZShell.Services
|
||||||
|
import qs.Components
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property color accent: DynamicColors.palette.m3secondary
|
||||||
|
readonly property real percentage: Storage.primaryDisk?.perc ?? 0
|
||||||
|
|
||||||
|
color: DynamicColors.tPalette.m3surfaceContainer
|
||||||
|
implicitHeight: layout.implicitHeight + Appearance.padding.large * 2
|
||||||
|
implicitWidth: layout.implicitWidth + layout.anchors.margins * 2
|
||||||
|
radius: Appearance.rounding.large - Appearance.padding.normal
|
||||||
|
|
||||||
|
ServiceRef {
|
||||||
|
service: Storage
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: layout
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.margins: Appearance.padding.small
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: row
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
spacing: Appearance.spacing.large
|
||||||
|
|
||||||
|
CircularProgress {
|
||||||
|
fgColor: root.accent
|
||||||
|
implicitSize: usageColumn.implicitHeight + thickness + Appearance.padding.large * 2
|
||||||
|
startAngle: -225
|
||||||
|
sweepAngle: 270
|
||||||
|
value: root.percentage
|
||||||
|
|
||||||
|
Behavior on clampedVal {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: usageColumn
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
color: root.accent
|
||||||
|
font.pointSize: Appearance.font.size.medium
|
||||||
|
text: "hard_drive"
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
color: root.accent
|
||||||
|
text: Math.round(root.percentage * 100) + "%"
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
|
text: qsTr("Used")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: Appearance.spacing.extraSmall
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
text: qsTr("Storage")
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
color: root.accent
|
||||||
|
text: {
|
||||||
|
if (!Storage.primaryDisk)
|
||||||
|
return qsTr("No disks detected");
|
||||||
|
|
||||||
|
const fmt = UsageFmt.formatKib(Storage.primaryDisk.used, Storage.primaryDisk.total);
|
||||||
|
return `${fmt.value.toFixed(1)} / ${Math.floor(fmt.total)} ${fmt.unit}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomSplitButton {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
active: menuItems.find(m => m.modelData === Storage.primaryDisk) ?? menuItems[0] ?? null
|
||||||
|
enabled: Storage.disks.length
|
||||||
|
fallbackIcon: "storage"
|
||||||
|
fallbackText: qsTr("No disks")
|
||||||
|
menuItems: disks.instances
|
||||||
|
minLeftWidth: row.implicitWidth * 0.6
|
||||||
|
type: CustomSplitButton.Tonal
|
||||||
|
|
||||||
|
menu.onItemSelected: item => Storage.manualPrimaryDisk = (item as DiskItem).modelData
|
||||||
|
|
||||||
|
Variants {
|
||||||
|
id: disks
|
||||||
|
|
||||||
|
model: Storage.disks
|
||||||
|
|
||||||
|
DiskItem {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component DiskItem: MenuItem {
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
activeIcon: "storage"
|
||||||
|
icon: modelData === Storage.primaryDisk ? "check" : ""
|
||||||
|
text: modelData.mount
|
||||||
|
}
|
||||||
|
}
|
||||||
+71
-743
@@ -1,792 +1,120 @@
|
|||||||
import Quickshell
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell.Services.UPower
|
import ZShell.Services
|
||||||
import ZShell.Internal
|
import qs.Modules.Resources.Cards
|
||||||
import qs.Components
|
|
||||||
import qs.Helpers
|
import qs.Helpers
|
||||||
|
import qs.Components
|
||||||
import qs.Config
|
import qs.Config
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property int minWidth: 400 + 400 + Appearance.spacing.normal + 120 + Appearance.padding.large * 2
|
implicitHeight: content.implicitHeight + Appearance.padding.normal * 2
|
||||||
readonly property real nonAnimHeight: content.implicitHeight + Appearance.padding.normal * 2
|
implicitWidth: content.implicitWidth
|
||||||
readonly property real nonAnimWidth: Math.max(minWidth, content.implicitWidth) + Appearance.padding.normal * 2
|
|
||||||
required property real padding
|
|
||||||
required property PersistentProperties visibilities
|
|
||||||
|
|
||||||
function displayTemp(temp: real): string {
|
|
||||||
return `${Math.ceil(temp)}°C`;
|
|
||||||
}
|
|
||||||
|
|
||||||
implicitHeight: nonAnimHeight
|
|
||||||
implicitWidth: nonAnimWidth
|
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: content
|
id: content
|
||||||
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: root.padding
|
anchors.margins: Appearance.padding.normal
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: root.padding
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: Appearance.spacing.normal
|
spacing: Appearance.spacing.larger
|
||||||
|
|
||||||
Ref {
|
|
||||||
service: SystemUsage
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: mainColumn
|
id: mainColumn
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: Appearance.spacing.normal
|
spacing: Appearance.spacing.larger
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
spacing: Appearance.spacing.larger
|
||||||
spacing: Appearance.spacing.normal
|
visible: cpuCard.active || gpuCard.active
|
||||||
visible: Config.dashboard.performance.showCpu || (Config.dashboard.performance.showGpu && SystemUsage.gpuType !== "NONE")
|
|
||||||
|
|
||||||
HeroCard {
|
WrappedLoader {
|
||||||
Layout.fillWidth: true
|
id: cpuCard
|
||||||
Layout.minimumWidth: 400
|
|
||||||
Layout.preferredHeight: 150
|
|
||||||
accentColor: DynamicColors.palette.m3primary
|
|
||||||
icon: "memory"
|
|
||||||
mainLabel: qsTr("Usage")
|
|
||||||
mainValue: `${Math.round(SystemUsage.cpuPerc * 100)}%`
|
|
||||||
secondaryLabel: qsTr("Temp")
|
|
||||||
secondaryValue: root.displayTemp(SystemUsage.cpuTemp)
|
|
||||||
temperature: SystemUsage.cpuTemp
|
|
||||||
title: SystemUsage.cpuName ? `CPU - ${SystemUsage.cpuName}` : qsTr("CPU")
|
|
||||||
usage: SystemUsage.cpuPerc
|
|
||||||
visible: Config.dashboard.performance.showCpu
|
|
||||||
}
|
|
||||||
|
|
||||||
HeroCard {
|
active: Config.dashboard.performance.showCpu
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.minimumWidth: 400
|
|
||||||
Layout.preferredHeight: 150
|
|
||||||
accentColor: DynamicColors.palette.m3secondary
|
|
||||||
icon: "desktop_windows"
|
|
||||||
mainLabel: qsTr("Usage")
|
|
||||||
mainValue: `${Math.round(SystemUsage.gpuPerc * 100)}%`
|
|
||||||
secondaryLabel: qsTr("Temp")
|
|
||||||
secondaryValue: root.displayTemp(SystemUsage.gpuTemp)
|
|
||||||
temperature: SystemUsage.gpuTemp
|
|
||||||
title: SystemUsage.gpuName ? `GPU - ${SystemUsage.gpuName}` : qsTr("GPU")
|
|
||||||
usage: SystemUsage.gpuPerc
|
|
||||||
visible: Config.dashboard.performance.showGpu && SystemUsage.gpuType !== "NONE"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
sourceComponent: HeroCard {
|
||||||
Layout.fillWidth: true
|
accent: DynamicColors.palette.m3primary
|
||||||
spacing: Appearance.spacing.normal
|
icon: "memory"
|
||||||
visible: Config.dashboard.performance.showMemory || Config.dashboard.performance.showStorage || Config.dashboard.performance.showNetwork
|
label: qsTr("CPU")
|
||||||
|
subLabel: Cpu.name
|
||||||
|
temperature: Cpu.temperature
|
||||||
|
usage: Cpu.percentage
|
||||||
|
|
||||||
GaugeCard {
|
ServiceRef {
|
||||||
Layout.fillWidth: !Config.dashboard.performance.showStorage && !Config.dashboard.performance.showNetwork
|
service: Cpu
|
||||||
Layout.minimumWidth: 250
|
|
||||||
Layout.preferredHeight: 220
|
|
||||||
accentColor: DynamicColors.palette.m3tertiary
|
|
||||||
icon: "memory_alt"
|
|
||||||
percentage: SystemUsage.memPerc
|
|
||||||
subtitle: {
|
|
||||||
const usedFmt = SystemUsage.formatKib(SystemUsage.memUsed);
|
|
||||||
const totalFmt = SystemUsage.formatKib(SystemUsage.memTotal);
|
|
||||||
return `${usedFmt.value.toFixed(1)} / ${Math.floor(totalFmt.value)} ${totalFmt.unit}`;
|
|
||||||
}
|
|
||||||
title: qsTr("Memory")
|
|
||||||
visible: Config.dashboard.performance.showMemory
|
|
||||||
}
|
|
||||||
|
|
||||||
StorageGaugeCard {
|
|
||||||
Layout.fillWidth: !Config.dashboard.performance.showNetwork
|
|
||||||
Layout.minimumWidth: 250
|
|
||||||
Layout.preferredHeight: 220
|
|
||||||
visible: Config.dashboard.performance.showStorage
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkCard {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.minimumWidth: 200
|
|
||||||
Layout.preferredHeight: 220
|
|
||||||
visible: Config.dashboard.performance.showNetwork
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BatteryTank {
|
|
||||||
Layout.preferredHeight: mainColumn.implicitHeight
|
|
||||||
Layout.preferredWidth: 120
|
|
||||||
visible: UPower.displayDevice.isLaptopBattery && Config.dashboard.performance.showBattery
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
component BatteryTank: CustomClippingRect {
|
|
||||||
id: batteryTank
|
|
||||||
|
|
||||||
property color accentColor: DynamicColors.palette.m3primary
|
|
||||||
property real animatedPercentage: 0
|
|
||||||
property bool isCharging: UPower.displayDevice.state === UPowerDeviceState.Charging
|
|
||||||
property real percentage: UPower.displayDevice.percentage
|
|
||||||
|
|
||||||
color: DynamicColors.tPalette.m3surfaceContainer
|
|
||||||
radius: Appearance.rounding.large - Appearance.padding.normal
|
|
||||||
|
|
||||||
Behavior on animatedPercentage {
|
|
||||||
Anim {
|
|
||||||
duration: Appearance.anim.durations.large
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: animatedPercentage = percentage
|
|
||||||
onPercentageChanged: animatedPercentage = percentage
|
|
||||||
|
|
||||||
// Background Fill
|
|
||||||
CustomRect {
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
color: Qt.alpha(batteryTank.accentColor, 0.15)
|
|
||||||
height: parent.height * batteryTank.animatedPercentage
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Appearance.padding.large
|
|
||||||
spacing: Appearance.spacing.small
|
|
||||||
|
|
||||||
// Header Section
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Appearance.spacing.small
|
|
||||||
|
|
||||||
MaterialIcon {
|
|
||||||
color: batteryTank.accentColor
|
|
||||||
font.pointSize: Appearance.font.size.large
|
|
||||||
text: {
|
|
||||||
if (!UPower.displayDevice.isLaptopBattery) {
|
|
||||||
if (PowerProfiles.profile === PowerProfile.PowerSaver)
|
|
||||||
return "energy_savings_leaf";
|
|
||||||
|
|
||||||
if (PowerProfiles.profile === PowerProfile.Performance)
|
|
||||||
return "rocket_launch";
|
|
||||||
|
|
||||||
return "balance";
|
|
||||||
}
|
}
|
||||||
if (UPower.displayDevice.state === UPowerDeviceState.FullyCharged)
|
|
||||||
return "battery_full";
|
|
||||||
|
|
||||||
const perc = UPower.displayDevice.percentage;
|
|
||||||
const charging = [UPowerDeviceState.Charging, UPowerDeviceState.PendingCharge].includes(UPower.displayDevice.state);
|
|
||||||
if (perc >= 0.99)
|
|
||||||
return "battery_full";
|
|
||||||
|
|
||||||
let level = Math.floor(perc * 7);
|
|
||||||
if (charging && (level === 4 || level === 1))
|
|
||||||
level--;
|
|
||||||
|
|
||||||
return charging ? `battery_charging_${(level + 3) * 10}` : `battery_${level}_bar`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomText {
|
WrappedLoader {
|
||||||
Layout.fillWidth: true
|
id: gpuCard
|
||||||
color: DynamicColors.palette.m3onSurface
|
|
||||||
font.pointSize: Appearance.font.size.normal
|
active: Config.dashboard.performance.showGpu && SystemUsage.gpuType !== ""
|
||||||
text: qsTr("Battery")
|
|
||||||
|
sourceComponent: HeroCard {
|
||||||
|
accent: DynamicColors.palette.m3secondary
|
||||||
|
icon: "desktop_windows"
|
||||||
|
label: qsTr("GPU")
|
||||||
|
subLabel: SystemUsage.gpuName
|
||||||
|
temperature: SystemUsage.gpuTemp
|
||||||
|
usage: SystemUsage.gpuPerc
|
||||||
|
|
||||||
|
Ref {
|
||||||
|
service: SystemUsage
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
RowLayout {
|
||||||
Layout.fillHeight: true
|
spacing: Appearance.spacing.larger
|
||||||
}
|
visible: storageCard.active || networkCard.active || memoryCard.active
|
||||||
|
|
||||||
// Bottom Info Section
|
WrappedLoader {
|
||||||
ColumnLayout {
|
id: storageCard
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: -4
|
|
||||||
|
|
||||||
CustomText {
|
active: Config.dashboard.performance.showStorage
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
color: batteryTank.accentColor
|
sourceComponent: StorageCard {
|
||||||
font.pointSize: Appearance.font.size.extraLarge
|
}
|
||||||
font.weight: Font.Medium
|
|
||||||
text: `${Math.round(batteryTank.percentage * 100)}%`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomText {
|
WrappedLoader {
|
||||||
Layout.alignment: Qt.AlignRight
|
id: memoryCard
|
||||||
color: DynamicColors.palette.m3onSurfaceVariant
|
|
||||||
font.pointSize: Appearance.font.size.smaller
|
|
||||||
text: {
|
|
||||||
if (UPower.displayDevice.state === UPowerDeviceState.FullyCharged)
|
|
||||||
return qsTr("Full");
|
|
||||||
|
|
||||||
if (batteryTank.isCharging)
|
active: Config.dashboard.performance.showMemory
|
||||||
return qsTr("Charging");
|
|
||||||
|
|
||||||
const s = UPower.displayDevice.timeToEmpty;
|
sourceComponent: MemoryCard {
|
||||||
if (s === 0)
|
}
|
||||||
return qsTr("...");
|
}
|
||||||
|
|
||||||
const hr = Math.floor(s / 3600);
|
WrappedLoader {
|
||||||
const min = Math.floor((s % 3600) / 60);
|
id: networkCard
|
||||||
if (hr > 0)
|
|
||||||
return `${hr}h ${min}m`;
|
|
||||||
|
|
||||||
return `${min}m`;
|
active: Config.dashboard.performance.showNetwork
|
||||||
|
|
||||||
|
sourceComponent: NetworkCard {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
component CardHeader: RowLayout {
|
|
||||||
property color accentColor: DynamicColors.palette.m3primary
|
|
||||||
property string icon
|
|
||||||
property string title
|
|
||||||
|
|
||||||
|
WrappedLoader {
|
||||||
|
Layout.fillWidth: false
|
||||||
|
active: Battery.isLaptop && Config.dashboard.performance.showBattery
|
||||||
|
|
||||||
|
sourceComponent: BatteryTank {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component WrappedLoader: Loader {
|
||||||
|
Layout.fillHeight: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: Appearance.spacing.small
|
visible: active
|
||||||
|
|
||||||
MaterialIcon {
|
|
||||||
color: parent.accentColor
|
|
||||||
fill: 1
|
|
||||||
font.pointSize: Appearance.spacing.large
|
|
||||||
text: parent.icon
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
elide: Text.ElideRight
|
|
||||||
font.pointSize: Appearance.font.size.normal
|
|
||||||
text: parent.title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
component GaugeCard: CustomRect {
|
|
||||||
id: gaugeCard
|
|
||||||
|
|
||||||
property color accentColor: DynamicColors.palette.m3primary
|
|
||||||
property real animatedPercentage: 0
|
|
||||||
readonly property real arcStartAngle: 0.75 * Math.PI
|
|
||||||
readonly property real arcSweep: 1.5 * Math.PI
|
|
||||||
property string icon
|
|
||||||
property real percentage: 0
|
|
||||||
property string subtitle
|
|
||||||
property string title
|
|
||||||
|
|
||||||
clip: true
|
|
||||||
color: DynamicColors.tPalette.m3surfaceContainer
|
|
||||||
radius: Appearance.rounding.large - Appearance.padding.normal
|
|
||||||
|
|
||||||
Behavior on animatedPercentage {
|
|
||||||
Anim {
|
|
||||||
duration: Appearance.anim.durations.large
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: animatedPercentage = percentage
|
|
||||||
onPercentageChanged: animatedPercentage = percentage
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Appearance.padding.large
|
|
||||||
spacing: Appearance.spacing.smaller
|
|
||||||
|
|
||||||
CardHeader {
|
|
||||||
accentColor: gaugeCard.accentColor
|
|
||||||
icon: gaugeCard.icon
|
|
||||||
title: gaugeCard.title
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
ArcGauge {
|
|
||||||
accentColor: gaugeCard.accentColor
|
|
||||||
anchors.centerIn: parent
|
|
||||||
height: width
|
|
||||||
percentage: gaugeCard.animatedPercentage
|
|
||||||
startAngle: gaugeCard.arcStartAngle
|
|
||||||
sweepAngle: gaugeCard.arcSweep
|
|
||||||
trackColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
|
|
||||||
width: Math.min(parent.width, parent.height)
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
color: gaugeCard.accentColor
|
|
||||||
font.pointSize: Appearance.font.size.extraLarge
|
|
||||||
font.weight: Font.Medium
|
|
||||||
text: `${Math.round(gaugeCard.percentage * 100)}%`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
color: DynamicColors.palette.m3onSurfaceVariant
|
|
||||||
font.pointSize: Appearance.font.size.smaller
|
|
||||||
text: gaugeCard.subtitle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
component HeroCard: CustomClippingRect {
|
|
||||||
id: heroCard
|
|
||||||
|
|
||||||
property color accentColor: DynamicColors.palette.m3primary
|
|
||||||
property real animatedTemp: 0
|
|
||||||
property real animatedUsage: 0
|
|
||||||
property string icon
|
|
||||||
property string mainLabel
|
|
||||||
property string mainValue
|
|
||||||
readonly property real maxTemp: 100
|
|
||||||
property string secondaryLabel
|
|
||||||
property string secondaryValue
|
|
||||||
readonly property real tempProgress: Math.min(1, Math.max(0, temperature / maxTemp))
|
|
||||||
property real temperature: 0
|
|
||||||
property string title
|
|
||||||
property real usage: 0
|
|
||||||
|
|
||||||
color: DynamicColors.tPalette.m3surfaceContainer
|
|
||||||
radius: Appearance.rounding.large - Appearance.padding.normal
|
|
||||||
|
|
||||||
Behavior on animatedTemp {
|
|
||||||
Anim {
|
|
||||||
duration: Appearance.anim.durations.large
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Behavior on animatedUsage {
|
|
||||||
Anim {
|
|
||||||
duration: Appearance.anim.durations.large
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
animatedUsage = usage;
|
|
||||||
animatedTemp = tempProgress;
|
|
||||||
}
|
|
||||||
onTempProgressChanged: animatedTemp = tempProgress
|
|
||||||
onUsageChanged: animatedUsage = usage
|
|
||||||
|
|
||||||
CustomRect {
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.top: parent.top
|
|
||||||
color: Qt.alpha(heroCard.accentColor, 0.15)
|
|
||||||
implicitWidth: parent.width * heroCard.animatedUsage
|
|
||||||
}
|
|
||||||
|
|
||||||
CardHeader {
|
|
||||||
accentColor: heroCard.accentColor
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Appearance.padding.large
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.topMargin: Math.round(Appearance.padding.large * 1.2)
|
|
||||||
icon: heroCard.icon
|
|
||||||
title: heroCard.title
|
|
||||||
width: parent.width - anchors.leftMargin - usageColumn.anchors.rightMargin - usageLabel.width - Appearance.spacing.normal
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.bottomMargin: Math.round(Appearance.padding.large * 1.3)
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.margins: Math.round(Appearance.padding.large * 1.2)
|
|
||||||
anchors.right: parent.right
|
|
||||||
spacing: Appearance.spacing.small
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Appearance.spacing.small
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
font.pointSize: Appearance.font.size.normal
|
|
||||||
font.weight: Font.Medium
|
|
||||||
text: heroCard.secondaryValue
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
anchors.baseline: parent.children[0].baseline
|
|
||||||
color: DynamicColors.palette.m3onSurfaceVariant
|
|
||||||
font.pointSize: Appearance.font.size.small
|
|
||||||
text: heroCard.secondaryLabel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ProgressBar {
|
|
||||||
bgColor: Qt.alpha(heroCard.accentColor, 0.2)
|
|
||||||
fgColor: heroCard.accentColor
|
|
||||||
implicitHeight: 6
|
|
||||||
implicitWidth: parent.width * 0.5
|
|
||||||
value: heroCard.tempProgress
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: usageColumn
|
|
||||||
|
|
||||||
anchors.margins: Appearance.padding.large
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 32
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
id: usageLabel
|
|
||||||
|
|
||||||
anchors.right: parent.right
|
|
||||||
color: DynamicColors.palette.m3onSurfaceVariant
|
|
||||||
font.pointSize: Appearance.font.size.normal
|
|
||||||
text: heroCard.mainLabel
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
anchors.right: parent.right
|
|
||||||
color: heroCard.accentColor
|
|
||||||
font.pointSize: Appearance.font.size.extraLarge
|
|
||||||
font.weight: Font.Medium
|
|
||||||
text: heroCard.mainValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
component NetworkCard: CustomRect {
|
|
||||||
id: networkCard
|
|
||||||
|
|
||||||
property color accentColor: DynamicColors.palette.m3primary
|
|
||||||
|
|
||||||
clip: true
|
|
||||||
color: DynamicColors.tPalette.m3surfaceContainer
|
|
||||||
radius: Appearance.rounding.large - Appearance.padding.normal
|
|
||||||
|
|
||||||
Ref {
|
|
||||||
service: NetworkUsage
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Appearance.padding.large
|
|
||||||
spacing: Appearance.spacing.small
|
|
||||||
|
|
||||||
CardHeader {
|
|
||||||
accentColor: networkCard.accentColor
|
|
||||||
icon: "swap_vert"
|
|
||||||
title: qsTr("Network")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sparkline graph
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
SparklineItem {
|
|
||||||
id: sparkline
|
|
||||||
|
|
||||||
property real smoothMax: targetMax
|
|
||||||
property real targetMax: 1024
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
historyLength: NetworkUsage.historyLength
|
|
||||||
line1: NetworkUsage.uploadBuffer // qmllint disable missing-type
|
|
||||||
line1Color: DynamicColors.palette.m3secondary
|
|
||||||
line1FillAlpha: 0.15
|
|
||||||
line2: NetworkUsage.downloadBuffer // qmllint disable missing-type
|
|
||||||
line2Color: DynamicColors.palette.m3tertiary
|
|
||||||
line2FillAlpha: 0.2
|
|
||||||
maxValue: smoothMax
|
|
||||||
|
|
||||||
Behavior on smoothMax {
|
|
||||||
Anim {
|
|
||||||
duration: Appearance.anim.durations.large
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
function onValuesChanged(): void {
|
|
||||||
sparkline.targetMax = Math.max(NetworkUsage.downloadBuffer.maximum, NetworkUsage.uploadBuffer.maximum, 1024);
|
|
||||||
slideAnim.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
target: NetworkUsage.downloadBuffer
|
|
||||||
}
|
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
id: slideAnim
|
|
||||||
|
|
||||||
duration: Config.dashboard.resourceUpdateInterval
|
|
||||||
from: 0
|
|
||||||
property: "slideProgress"
|
|
||||||
target: sparkline
|
|
||||||
to: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// "No data" placeholder
|
|
||||||
CustomText {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
color: DynamicColors.palette.m3onSurfaceVariant
|
|
||||||
font.pointSize: Appearance.font.size.small
|
|
||||||
opacity: 0.6
|
|
||||||
text: qsTr("Collecting data...")
|
|
||||||
visible: NetworkUsage.downloadBuffer.count < 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download row
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Appearance.spacing.normal
|
|
||||||
|
|
||||||
MaterialIcon {
|
|
||||||
color: DynamicColors.palette.m3tertiary
|
|
||||||
font.pointSize: Appearance.font.size.normal
|
|
||||||
text: "download"
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
color: DynamicColors.palette.m3onSurfaceVariant
|
|
||||||
font.pointSize: Appearance.font.size.small
|
|
||||||
text: qsTr("Download")
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
color: DynamicColors.palette.m3tertiary
|
|
||||||
font.pointSize: Appearance.font.size.normal
|
|
||||||
font.weight: Font.Medium
|
|
||||||
text: {
|
|
||||||
const fmt = NetworkUsage.formatBytes(NetworkUsage.downloadSpeed ?? 0);
|
|
||||||
return fmt ? `${fmt.value.toFixed(1)} ${fmt.unit}` : "0.0 B/s";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload row
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Appearance.spacing.normal
|
|
||||||
|
|
||||||
MaterialIcon {
|
|
||||||
color: DynamicColors.palette.m3secondary
|
|
||||||
font.pointSize: Appearance.font.size.normal
|
|
||||||
text: "upload"
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
color: DynamicColors.palette.m3onSurfaceVariant
|
|
||||||
font.pointSize: Appearance.font.size.small
|
|
||||||
text: qsTr("Upload")
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
color: DynamicColors.palette.m3secondary
|
|
||||||
font.pointSize: Appearance.font.size.normal
|
|
||||||
font.weight: Font.Medium
|
|
||||||
text: {
|
|
||||||
const fmt = NetworkUsage.formatBytes(NetworkUsage.uploadSpeed ?? 0);
|
|
||||||
return fmt ? `${fmt.value.toFixed(1)} ${fmt.unit}` : "0.0 B/s";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Session totals
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Appearance.spacing.normal
|
|
||||||
|
|
||||||
MaterialIcon {
|
|
||||||
color: DynamicColors.palette.m3onSurfaceVariant
|
|
||||||
font.pointSize: Appearance.font.size.normal
|
|
||||||
text: "history"
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
color: DynamicColors.palette.m3onSurfaceVariant
|
|
||||||
font.pointSize: Appearance.font.size.small
|
|
||||||
text: qsTr("Total")
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
color: DynamicColors.palette.m3onSurfaceVariant
|
|
||||||
font.pointSize: Appearance.font.size.small
|
|
||||||
text: {
|
|
||||||
const down = NetworkUsage.formatBytesTotal(NetworkUsage.downloadTotal ?? 0);
|
|
||||||
const up = NetworkUsage.formatBytesTotal(NetworkUsage.uploadTotal ?? 0);
|
|
||||||
return (down && up) ? `↓${down.value.toFixed(1)}${down.unit} ↑${up.value.toFixed(1)}${up.unit}` : "↓0.0B ↑0.0B";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
component ProgressBar: CustomRect {
|
|
||||||
id: progressBar
|
|
||||||
|
|
||||||
property real animatedValue: 0
|
|
||||||
property color bgColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
|
|
||||||
property color fgColor: DynamicColors.palette.m3primary
|
|
||||||
property real value: 0
|
|
||||||
|
|
||||||
color: bgColor
|
|
||||||
radius: Appearance.rounding.full
|
|
||||||
|
|
||||||
Behavior on animatedValue {
|
|
||||||
Anim {
|
|
||||||
duration: Appearance.anim.durations.large
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: animatedValue = value
|
|
||||||
onValueChanged: animatedValue = value
|
|
||||||
|
|
||||||
CustomRect {
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.top: parent.top
|
|
||||||
color: progressBar.fgColor
|
|
||||||
radius: Appearance.rounding.full
|
|
||||||
width: parent.width * progressBar.animatedValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
component StorageGaugeCard: CustomRect {
|
|
||||||
id: storageGaugeCard
|
|
||||||
|
|
||||||
property color accentColor: DynamicColors.palette.m3secondary
|
|
||||||
property real animatedPercentage: 0
|
|
||||||
readonly property real arcStartAngle: 0.75 * Math.PI
|
|
||||||
readonly property real arcSweep: 1.5 * Math.PI
|
|
||||||
readonly property var currentDisk: SystemUsage.disks.length > 0 ? SystemUsage.disks[currentDiskIndex] : null
|
|
||||||
property int currentDiskIndex: 0
|
|
||||||
property int diskCount: 0
|
|
||||||
|
|
||||||
clip: true
|
|
||||||
color: DynamicColors.tPalette.m3surfaceContainer
|
|
||||||
radius: Appearance.rounding.large - Appearance.padding.normal
|
|
||||||
|
|
||||||
Behavior on animatedPercentage {
|
|
||||||
Anim {
|
|
||||||
duration: Appearance.anim.durations.large
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
diskCount = SystemUsage.disks.length;
|
|
||||||
if (currentDisk)
|
|
||||||
animatedPercentage = currentDisk.perc;
|
|
||||||
}
|
|
||||||
onCurrentDiskChanged: {
|
|
||||||
if (currentDisk)
|
|
||||||
animatedPercentage = currentDisk.perc;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update diskCount and animatedPercentage when disks data changes
|
|
||||||
Connections {
|
|
||||||
function onDisksChanged() {
|
|
||||||
if (SystemUsage.disks.length !== storageGaugeCard.diskCount)
|
|
||||||
storageGaugeCard.diskCount = SystemUsage.disks.length;
|
|
||||||
|
|
||||||
// Update animated percentage when disk data refreshes
|
|
||||||
if (storageGaugeCard.currentDisk)
|
|
||||||
storageGaugeCard.animatedPercentage = storageGaugeCard.currentDisk.perc;
|
|
||||||
}
|
|
||||||
|
|
||||||
target: SystemUsage
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
onWheel: wheel => {
|
|
||||||
if (wheel.angleDelta.y > 0)
|
|
||||||
storageGaugeCard.currentDiskIndex = (storageGaugeCard.currentDiskIndex - 1 + storageGaugeCard.diskCount) % storageGaugeCard.diskCount;
|
|
||||||
else if (wheel.angleDelta.y < 0)
|
|
||||||
storageGaugeCard.currentDiskIndex = (storageGaugeCard.currentDiskIndex + 1) % storageGaugeCard.diskCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Appearance.padding.large
|
|
||||||
spacing: Appearance.spacing.smaller
|
|
||||||
|
|
||||||
CardHeader {
|
|
||||||
accentColor: storageGaugeCard.accentColor
|
|
||||||
icon: "hard_disk"
|
|
||||||
title: {
|
|
||||||
const base = qsTr("Storage");
|
|
||||||
if (!storageGaugeCard.currentDisk)
|
|
||||||
return base;
|
|
||||||
|
|
||||||
return `${base} - ${storageGaugeCard.currentDisk.mount}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scroll hint icon
|
|
||||||
MaterialIcon {
|
|
||||||
ToolTip.delay: 500
|
|
||||||
ToolTip.text: qsTr("Scroll to switch disks")
|
|
||||||
ToolTip.visible: hintHover.hovered
|
|
||||||
color: DynamicColors.palette.m3onSurfaceVariant
|
|
||||||
font.pointSize: Appearance.font.size.normal
|
|
||||||
opacity: 0.7
|
|
||||||
text: "unfold_more"
|
|
||||||
visible: storageGaugeCard.diskCount > 1
|
|
||||||
|
|
||||||
HoverHandler {
|
|
||||||
id: hintHover
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
ArcGauge {
|
|
||||||
accentColor: storageGaugeCard.accentColor
|
|
||||||
anchors.centerIn: parent
|
|
||||||
height: width
|
|
||||||
percentage: storageGaugeCard.animatedPercentage
|
|
||||||
startAngle: storageGaugeCard.arcStartAngle
|
|
||||||
sweepAngle: storageGaugeCard.arcSweep
|
|
||||||
trackColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
|
|
||||||
width: Math.min(parent.width, parent.height)
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
color: storageGaugeCard.accentColor
|
|
||||||
font.pointSize: Appearance.font.size.extraLarge
|
|
||||||
font.weight: Font.Medium
|
|
||||||
text: storageGaugeCard.currentDisk ? `${Math.round(storageGaugeCard.currentDisk.perc * 100)}%` : "—"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
color: DynamicColors.palette.m3onSurfaceVariant
|
|
||||||
font.pointSize: Appearance.font.size.smaller
|
|
||||||
text: {
|
|
||||||
if (!storageGaugeCard.currentDisk)
|
|
||||||
return "—";
|
|
||||||
|
|
||||||
const usedFmt = SystemUsage.formatKib(storageGaugeCard.currentDisk.used);
|
|
||||||
const totalFmt = SystemUsage.formatKib(storageGaugeCard.currentDisk.total);
|
|
||||||
return `${usedFmt.value.toFixed(1)} / ${Math.floor(totalFmt.value)} ${totalFmt.unit}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ Item {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
||||||
sourceComponent: Content {
|
sourceComponent: Content {
|
||||||
padding: Appearance.padding.normal
|
|
||||||
visibilities: root.visibilities
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,25 @@
|
|||||||
find_package(Qt6 REQUIRED COMPONENTS ShaderTools Core Qml Gui Quick Concurrent Sql Network DBus)
|
find_package(Qt6 REQUIRED COMPONENTS ShaderTools Core Qml Gui Quick Concurrent Sql Network DBus)
|
||||||
find_package(PkgConfig REQUIRED)
|
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(Qalculate IMPORTED_TARGET libqalculate REQUIRED)
|
||||||
pkg_check_modules(Pipewire IMPORTED_TARGET libpipewire-0.3 REQUIRED)
|
pkg_check_modules(Pipewire IMPORTED_TARGET libpipewire-0.3 REQUIRED)
|
||||||
pkg_check_modules(Aubio IMPORTED_TARGET aubio REQUIRED)
|
pkg_check_modules(Aubio IMPORTED_TARGET aubio REQUIRED)
|
||||||
pkg_check_modules(Cava IMPORTED_TARGET libcava QUIET)
|
pkg_check_modules(Cava IMPORTED_TARGET libcava QUIET)
|
||||||
pkg_check_modules(GLIB REQUIRED glib-2.0 gobject-2.0 gio-2.0)
|
pkg_check_modules(GLIB REQUIRED glib-2.0 gobject-2.0 gio-2.0)
|
||||||
|
|
||||||
if(NOT Cava_FOUND)
|
if(NOT Cava_FOUND)
|
||||||
pkg_check_modules(Cava IMPORTED_TARGET cava REQUIRED)
|
pkg_check_modules(Cava IMPORTED_TARGET cava REQUIRED)
|
||||||
endif()
|
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")
|
set(QT_QML_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/qml")
|
||||||
qt_standard_project_setup(REQUIRES 6.9)
|
qt_standard_project_setup(REQUIRES 6.9)
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
qml_module(ZShell-internal
|
qml_module(ZShell-internal
|
||||||
URI ZShell.Internal
|
URI ZShell.Internal
|
||||||
SOURCES
|
SOURCES
|
||||||
hyprextras.hpp hyprextras.cpp
|
hyprextras.hpp hyprextras.cpp
|
||||||
hyprdevices.hpp hyprdevices.cpp
|
hyprdevices.hpp hyprdevices.cpp
|
||||||
cachingimagemanager.hpp cachingimagemanager.cpp
|
cachingimagemanager.hpp cachingimagemanager.cpp
|
||||||
circularindicatormanager.hpp circularindicatormanager.cpp
|
circularindicatormanager.hpp circularindicatormanager.cpp
|
||||||
circularbuffer.hpp circularbuffer.cpp
|
circularbuffer.hpp circularbuffer.cpp
|
||||||
sparklineitem.hpp sparklineitem.cpp
|
sparklineitem.hpp sparklineitem.cpp
|
||||||
arcgauge.hpp arcgauge.cpp
|
arcgauge.hpp arcgauge.cpp
|
||||||
wallpaperimage.hpp wallpaperimage.cpp
|
wallpaperimage.hpp wallpaperimage.cpp
|
||||||
lidwatcher.hpp lidwatcher.cpp
|
lidwatcher.hpp lidwatcher.cpp
|
||||||
LIBRARIES
|
visualizerbars.hpp visualizerbars.cpp
|
||||||
Qt::Gui
|
linearindicatormanager.hpp linearindicatormanager.cpp
|
||||||
Qt::Quick
|
LIBRARIES
|
||||||
Qt::Concurrent
|
Qt::Gui
|
||||||
Qt::Core
|
Qt::Quick
|
||||||
|
Qt::Concurrent
|
||||||
|
Qt::Core
|
||||||
Qt::Network
|
Qt::Network
|
||||||
Qt::DBus
|
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
|
qml_module(ZShell-services
|
||||||
URI ZShell.Services
|
URI ZShell.Services
|
||||||
SOURCES
|
SOURCES
|
||||||
service.hpp service.cpp
|
service.hpp service.cpp
|
||||||
serviceref.hpp serviceref.cpp
|
serviceref.hpp serviceref.cpp
|
||||||
beattracker.hpp beattracker.cpp
|
beattracker.hpp beattracker.cpp
|
||||||
audiocollector.hpp audiocollector.cpp
|
audiocollector.hpp audiocollector.cpp
|
||||||
audioprovider.hpp audioprovider.cpp
|
audioprovider.hpp audioprovider.cpp
|
||||||
cavaprovider.hpp cavaprovider.cpp
|
cavaprovider.hpp cavaprovider.cpp
|
||||||
desktopmodel.hpp desktopmodel.cpp
|
desktopmodel.hpp desktopmodel.cpp
|
||||||
desktopstatemanager.hpp desktopstatemanager.cpp
|
desktopstatemanager.hpp desktopstatemanager.cpp
|
||||||
hyprsunsetmanager.hpp hyprsunsetmanager.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::Core
|
||||||
Qt6::Qml
|
Qt6::Qml
|
||||||
PkgConfig::Pipewire
|
PkgConfig::Pipewire
|
||||||
PkgConfig::Aubio
|
PkgConfig::Aubio
|
||||||
PkgConfig::Cava
|
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