Krolyxon
Commit 4ad2e15

Branches

Refs →

aurora4ad2e15

aurora: add desktop layer (clock, stats, now playing, cava visualizer)

Author
krolyxon
Date
2026-10-17 15:02 +0530
Parent
35754e3
Commit
4ad2e157920474009c63c5bf2bca30aad7c582ce

+161 −0

3 files changed

  1. A .config/quickshell/aurora/Cava.qml +53 −0
  2. A .config/quickshell/aurora/Desktop.qml +103 −0
  3. M .config/quickshell/aurora/shell.qml +5 −0
A .config/quickshell/aurora/Cava.qml View
@@ -0,0 +1,53 @@
1+pragma Singleton
2+import QtQuick
3+import Quickshell
4+import Quickshell.Io
5+import Quickshell.Services.Mpris
6+
7+// Audio spectrum from cava (raw ascii output on stdout). Only runs while an MPRIS player is
8+// playing and something wants it (`wanted`), so it costs nothing when idle. Hidden if cava is missing.
9+Singleton {
10+ id: root
11+ property int bars: 64
12+ property bool wanted: true // desktop visualizer enabled
13+ property bool available: false
14+ property var values: [] // 0..1 per bar
15+
16+ function anyPlaying() {
17+ var l = Mpris.players.values
18+ for (var i = 0; i < l.length; i++) if (l[i].isPlaying) return true
19+ return false
20+ }
21+ readonly property bool playing: anyPlaying()
22+ readonly property bool active: available && wanted && playing
23+ onActiveChanged: if (!active) idle.restart()
24+
25+ readonly property string conf:
26+ "[general]\nbars = " + bars + "\nframerate = 60\nsleep_timer = 3\n" +
27+ "[input]\nmethod = pipewire\nsource = auto\n" +
28+ "[output]\nmethod = raw\nraw_target = /dev/stdout\nchannels = mono\n" +
29+ "data_format = ascii\nascii_max_range = 1000\nbar_delimiter = 59\nframe_delimiter = 10\n" +
30+ "[smoothing]\nnoise_reduction = 70\n"
31+
32+ Process {
33+ command: ["sh", "-c", "command -v cava"]
34+ running: true
35+ onExited: code => root.available = code === 0
36+ }
37+
38+ Process {
39+ id: proc
40+ running: root.active
41+ command: ["sh", "-c", 'mkdir -p "${2%/*}" && printf "%s" "$1" > "$2" && exec cava -p "$2"',
42+ "sh", root.conf, Quickshell.env("HOME") + "/.cache/aurora/cava.conf"]
43+ stdout: SplitParser {
44+ onRead: line => {
45+ var p = line.split(";"), out = []
46+ for (var i = 0; i < p.length; i++) if (p[i] !== "") out.push(parseInt(p[i]) / 1000)
47+ root.values = out
48+ }
49+ }
50+ }
51+ // let the bars fall to zero once cava stops instead of freezing mid-frame
52+ Timer { id: idle; interval: 50; onTriggered: root.values = [] }
53+}
A .config/quickshell/aurora/Desktop.qml View
@@ -0,0 +1,103 @@
1+import QtQuick
2+import Quickshell
3+import Quickshell.Wayland
4+import Quickshell.Services.Mpris
5+
6+// Desktop layer (per monitor): clock, date, system line, now playing, cava visualizer along the bottom.
7+// Sits on the Bottom layer: above hyprpaper, below windows, never takes input.
8+PanelWindow {
9+ id: win
10+ required property var modelData
11+ screen: modelData
12+ property bool showClock: true
13+ property bool showViz: true
14+
15+ anchors { top: true; bottom: true; left: true; right: true }
16+ exclusionMode: ExclusionMode.Ignore
17+ color: "transparent"
18+ mask: Region {}
19+ WlrLayershell.layer: WlrLayer.Bottom
20+ WlrLayershell.namespace: "aurora-desktop"
21+ WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
22+
23+ SystemClock { id: clock; precision: SystemClock.Minutes }
24+
25+ function pick() {
26+ var l = Mpris.players.values, first = null
27+ for (var i = 0; i < l.length; i++) {
28+ if (l[i].isPlaying) return l[i]
29+ if (first === null) first = l[i]
30+ }
31+ return first
32+ }
33+ readonly property var player: pick()
34+
35+ Column {
36+ visible: win.showClock
37+ x: 56
38+ y: Theme.barHeight + 48
39+ spacing: 2
40+
41+ Text {
42+ text: Qt.formatDateTime(clock.date, "HH:mm")
43+ color: Theme.fg
44+ font.family: Theme.font
45+ font.pixelSize: 96
46+ font.bold: true
47+ }
48+ Row {
49+ spacing: 10
50+ Rectangle { width: 4; height: 22; color: Theme.accent; anchors.verticalCenter: parent.verticalCenter }
51+ Text {
52+ text: Qt.formatDateTime(clock.date, "dddd, d MMMM").toUpperCase()
53+ color: Theme.accent
54+ font.family: Theme.font
55+ font.pixelSize: 18
56+ font.bold: true
57+ font.letterSpacing: 2
58+ }
59+ }
60+ Item { width: 1; height: 10 }
61+ Text {
62+ text: Theme.iChip + " " + Math.round(SysStats.cpu * 100) + "% "
63+ + Theme.iMem + " " + SysStats.memUsed.toFixed(1) + "/" + SysStats.memTotal.toFixed(1) + "G "
64+ + Theme.iClock + " up " + Theme.fmtTime(SysStats.uptime)
65+ color: Theme.dim
66+ font.family: Theme.font
67+ font.pixelSize: Theme.fontSize + 1
68+ }
69+ Text {
70+ visible: win.player !== null && (win.player.trackTitle || "") !== ""
71+ text: win.player ? (win.player.isPlaying ? Theme.iMusic : Theme.iPause) + " " + win.player.trackTitle
72+ + (win.player.trackArtist ? " · " + win.player.trackArtist : "") : ""
73+ color: win.player && win.player.isPlaying ? Theme.fg : Theme.dim
74+ font.family: Theme.font
75+ font.pixelSize: Theme.fontSize + 1
76+ }
77+ }
78+
79+ // visualizer: bottom-up bars across the whole width
80+ Row {
81+ visible: win.showViz && Cava.available
82+ anchors.bottom: parent.bottom
83+ anchors.left: parent.left
84+ anchors.right: parent.right
85+ height: 180
86+ spacing: 3
87+ Repeater {
88+ model: Cava.bars
89+ delegate: Rectangle {
90+ required property int index
91+ readonly property real v: index < Cava.values.length ? Cava.values[index] : 0
92+ anchors.bottom: parent.bottom
93+ width: (win.width - (Cava.bars - 1) * 3) / Cava.bars
94+ height: Math.max(0, v * parent.height)
95+ opacity: 0.85
96+ gradient: Gradient {
97+ GradientStop { position: 0; color: Theme.accent }
98+ GradientStop { position: 1; color: Theme.accentDim }
99+ }
100+ }
101+ }
102+ }
103+}
M .config/quickshell/aurora/shell.qml View
@@ -72,6 +72,11 @@ ShellRoot {
7272 // keep every pipewire node bound so volume / mute / properties are readable
7373 PwObjectTracker { objects: Pipewire.nodes.values }
7474
75+ Variants {
76+ model: Quickshell.screens
77+ Desktop {}
78+ }
79+
7580 Variants {
7681 model: Quickshell.screens
7782 Bar {