audio module, system stats, fixed activewindow
This commit is contained in:
@@ -48,6 +48,20 @@ Scope {
|
||||
Layout.topMargin: 6
|
||||
Layout.bottomMargin: 6
|
||||
}
|
||||
|
||||
AudioWidget {
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: 6
|
||||
Layout.bottomMargin: 6
|
||||
}
|
||||
|
||||
Resources {
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: 6
|
||||
Layout.bottomMargin: 6
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
|
||||
@@ -5,5 +5,5 @@ import Quickshell.Hyprland
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
property string activeWindow: Hyprland.activeToplevel?.lastIpcObject.class || ""
|
||||
property string activeWindow: Hyprland.activeToplevel?.title || ""
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Pipewire
|
||||
import Quickshell.Widgets
|
||||
import qs.Modules
|
||||
|
||||
Item {
|
||||
id: root
|
||||
implicitWidth: 150
|
||||
implicitHeight: 100
|
||||
|
||||
PwObjectTracker {
|
||||
objects: [ Pipewire.defaultAudioSink ]
|
||||
}
|
||||
|
||||
PwObjectTracker {
|
||||
objects: [ Pipewire.defaultAudioSource ]
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: height / 2
|
||||
color: "#40000000"
|
||||
|
||||
// Background circle
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
radius: width / 2
|
||||
color: "transparent"
|
||||
border.color: "#30ffffff"
|
||||
border.width: 0
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors {
|
||||
fill: parent
|
||||
leftMargin: 10
|
||||
rightMargin: 15
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.family: "Material Symbols Rounded"
|
||||
font.pixelSize: 18
|
||||
text: "\ue050" // volume_up icon
|
||||
color: "#ffffff"
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
|
||||
implicitHeight: 4
|
||||
radius: 20
|
||||
color: "#50ffffff"
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
implicitWidth: parent.width * (Pipewire.defaultAudioSink?.audio.volume ?? 0)
|
||||
radius: parent.radius
|
||||
color: "#ffffff"
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.family: "Material Symbols Rounded"
|
||||
font.pixelSize: 18
|
||||
text: "\ue029"
|
||||
color: (Pipewire.defaultAudioSource?.audio.muted ?? false) ? "#ff4444" : "#ffffff"
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
|
||||
implicitHeight: 4
|
||||
radius: 20
|
||||
color: "#50ffffff"
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
implicitWidth: parent.width * (Pipewire.defaultAudioSource?.audio.volume ?? 0)
|
||||
radius: parent.radius
|
||||
color: (Pipewire.defaultAudioSource?.audio.muted ?? false) ? "#ff4444" : "#ffffff"
|
||||
}
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: pavucontrolProc.running = true
|
||||
}
|
||||
Process {
|
||||
id: pavucontrolProc
|
||||
command: ["pavucontrol"]
|
||||
running: false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import qs.Modules
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
|
||||
Item {
|
||||
id: root
|
||||
required property double percentage
|
||||
property int warningThreshold: 100
|
||||
property bool shown: true
|
||||
clip: true
|
||||
visible: width > 0 && height > 0
|
||||
implicitWidth: resourceRowLayout.x < 0 ? 0 : resourceRowLayout.implicitWidth
|
||||
implicitHeight: 22
|
||||
property bool warning: percentage * 100 >= warningThreshold
|
||||
|
||||
RowLayout {
|
||||
id: resourceRowLayout
|
||||
spacing: 2
|
||||
x: shown ? 0 : -resourceRowLayout.width
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
implicitWidth: 14
|
||||
implicitHeight: root.implicitHeight
|
||||
|
||||
// Background circle
|
||||
Rectangle {
|
||||
id: backgroundCircle
|
||||
anchors.centerIn: parent
|
||||
width: 14
|
||||
height: 14
|
||||
radius: height / 2
|
||||
color: "#40000000"
|
||||
border.color: "#404040"
|
||||
border.width: 1
|
||||
}
|
||||
|
||||
// Pie progress indicator
|
||||
Canvas {
|
||||
id: progressCanvas
|
||||
anchors.fill: backgroundCircle
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onPercentageChanged() {
|
||||
progressCanvas.requestPaint()
|
||||
}
|
||||
function onWarningChanged() {
|
||||
progressCanvas.requestPaint()
|
||||
}
|
||||
}
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.reset();
|
||||
|
||||
var centerX = width / 2;
|
||||
var centerY = height / 2;
|
||||
var radius = width / 2;
|
||||
var startAngle = -Math.PI / 2; // Start at top
|
||||
var endAngle = startAngle + (2 * Math.PI * percentage);
|
||||
|
||||
ctx.fillStyle = warning ? "#ff6b6b" : "#4080ff";
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(centerX, centerY);
|
||||
ctx.arc(centerX, centerY, radius, startAngle, endAngle);
|
||||
ctx.lineTo(centerX, centerY);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
// // Percentage text
|
||||
// Item {
|
||||
// anchors.centerIn: backgroundCircle
|
||||
// implicitWidth: fullPercentageTextMetrics.width
|
||||
// implicitHeight: percentageText.implicitHeight
|
||||
//
|
||||
// TextMetrics {
|
||||
// id: fullPercentageTextMetrics
|
||||
// text: "100"
|
||||
// font.pixelSize: 12
|
||||
// }
|
||||
//
|
||||
// Text {
|
||||
// id: percentageText
|
||||
// anchors.centerIn: parent
|
||||
// color: "#ffffff"
|
||||
// font.pixelSize: 12
|
||||
// font.bold: true
|
||||
// text: `${Math.round(percentage * 100).toString()}`
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
/**
|
||||
* Simple polled resource usage service with RAM, Swap, and CPU usage.
|
||||
*/
|
||||
Singleton {
|
||||
property double memoryTotal: 1
|
||||
property double memoryFree: 1
|
||||
property double memoryUsed: memoryTotal - memoryFree
|
||||
property double memoryUsedPercentage: memoryUsed / memoryTotal
|
||||
property double swapTotal: 1
|
||||
property double swapFree: 1
|
||||
property double swapUsed: swapTotal - swapFree
|
||||
property double swapUsedPercentage: swapTotal > 0 ? (swapUsed / swapTotal) : 0
|
||||
property double cpuUsage: 0
|
||||
property var previousCpuStats
|
||||
property double gpuUsage: 0
|
||||
property double gpuMemUsage: 0
|
||||
|
||||
Timer {
|
||||
interval: 1
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
// Reload files
|
||||
fileMeminfo.reload()
|
||||
fileStat.reload()
|
||||
|
||||
// Parse memory and swap usage
|
||||
const textMeminfo = fileMeminfo.text()
|
||||
memoryTotal = Number(textMeminfo.match(/MemTotal: *(\d+)/)?.[1] ?? 1)
|
||||
memoryFree = Number(textMeminfo.match(/MemAvailable: *(\d+)/)?.[1] ?? 0)
|
||||
swapTotal = Number(textMeminfo.match(/SwapTotal: *(\d+)/)?.[1] ?? 1)
|
||||
swapFree = Number(textMeminfo.match(/SwapFree: *(\d+)/)?.[1] ?? 0)
|
||||
|
||||
// Parse CPU usage
|
||||
const textStat = fileStat.text()
|
||||
const cpuLine = textStat.match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/)
|
||||
if (cpuLine) {
|
||||
const stats = cpuLine.slice(1).map(Number)
|
||||
const total = stats.reduce((a, b) => a + b, 0)
|
||||
const idle = stats[3]
|
||||
|
||||
if (previousCpuStats) {
|
||||
const totalDiff = total - previousCpuStats.total
|
||||
const idleDiff = idle - previousCpuStats.idle
|
||||
cpuUsage = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0
|
||||
}
|
||||
|
||||
previousCpuStats = { total, idle }
|
||||
}
|
||||
processGpu.running = true
|
||||
|
||||
interval = 1000
|
||||
}
|
||||
}
|
||||
|
||||
FileView { id: fileMeminfo; path: "/proc/meminfo" }
|
||||
FileView { id: fileStat; path: "/proc/stat" }
|
||||
|
||||
Process {
|
||||
id: processGpu
|
||||
command: ["nvidia-smi", "--query-gpu=utilization.gpu,utilization.memory", "--format=csv,noheader,nounits"]
|
||||
running: false
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const parts = this.text.trim().split(", ")
|
||||
gpuUsage = Number(parts[0]) / 100
|
||||
gpuMemUsage = Number(parts[1]) / 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import QtQuick.Layouts
|
||||
import qs.Modules
|
||||
|
||||
Item {
|
||||
id: root
|
||||
implicitWidth: rowLayout.implicitWidth + rowLayout.anchors.leftMargin + rowLayout.anchors.rightMargin
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#40000000"
|
||||
radius: height / 2
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
|
||||
spacing: 6
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 5
|
||||
anchors.rightMargin: 5
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.family: "Material Symbols Rounded"
|
||||
font.pixelSize: 18
|
||||
text: "\uf7a3"
|
||||
color: "#ffffff"
|
||||
}
|
||||
|
||||
Resource {
|
||||
percentage: ResourceUsage.memoryUsedPercentage
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.family: "Material Symbols Rounded"
|
||||
font.pixelSize: 18
|
||||
text: "\ue322"
|
||||
color: "#ffffff"
|
||||
}
|
||||
|
||||
Resource {
|
||||
percentage: ResourceUsage.cpuUsage
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.family: "Material Symbols Rounded"
|
||||
font.pixelSize: 18
|
||||
text: "\ue30d"
|
||||
color: "#ffffff"
|
||||
}
|
||||
|
||||
Resource {
|
||||
percentage: ResourceUsage.gpuUsage
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.family: "Material Symbols Rounded"
|
||||
font.pixelSize: 18
|
||||
text: "\ue30d"
|
||||
color: "#ffffff"
|
||||
}
|
||||
|
||||
Resource {
|
||||
percentage: ResourceUsage.gpuMemUsage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ import Quickshell.Hyprland
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
implicitWidth: workspacesRow.implicitWidth + 12
|
||||
implicitHeight: workspacesRow.implicitHeight + 8
|
||||
implicitWidth: workspacesRow.implicitWidth + 10
|
||||
implicitHeight: workspacesRow.implicitHeight + 7
|
||||
|
||||
color: "#40000000"
|
||||
radius: height / 2
|
||||
@@ -24,7 +24,7 @@ Rectangle {
|
||||
id: workspacesRow
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: 6
|
||||
anchors.leftMargin: 5
|
||||
spacing: 8
|
||||
|
||||
Repeater {
|
||||
@@ -33,9 +33,9 @@ Rectangle {
|
||||
Rectangle {
|
||||
required property var modelData
|
||||
|
||||
width: 12
|
||||
height: 12
|
||||
radius: 6
|
||||
width: 14
|
||||
height: 14
|
||||
radius: height / 2
|
||||
|
||||
color: modelData.id === Hyprland.focusedWorkspace.id ? "#4080ff" : "#606060"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user