Krolyxon
Commit eec80fe

Branches

Refs →

auroraeec80fe

switch to aurora shell

its time to get rid of waybar, swaync
Author
krolyxon
Date
2026-10-12 10:56 +0530
Parent
0355e4e
Commit
eec80fe22479c9023ff74b4392fe598b32d8b17f

+1328 −154

35 files changed

  1. M .config/hypr/hyprland.lua +2 −2
  2. M .config/hypr/source/keybinds.lua +6 −3
  3. R .config/quickshell/wal-control/AudioPage.qml → .config/quickshell/aurora/AudioPage.qml +0 −0
  4. A .config/quickshell/aurora/Bar.qml +153 −0
  5. A .config/quickshell/aurora/BarPill.qml +57 −0
  6. R .config/quickshell/wal-control/BarSlider.qml → .config/quickshell/aurora/BarSlider.qml +0 −0
  7. R .config/quickshell/wal-control/Bright.qml → .config/quickshell/aurora/Bright.qml +0 −0
  8. R .config/quickshell/wal-control/BtPage.qml → .config/quickshell/aurora/BtPage.qml +0 −0
  9. A .config/quickshell/aurora/CatLoop.qml +41 −0
  10. A .config/quickshell/aurora/ControlPanel.qml +115 −0
  11. A .config/quickshell/aurora/Gpu.qml +46 −0
  12. R .config/quickshell/wal-control/HomePage.qml → .config/quickshell/aurora/HomePage.qml +46 −9
  13. A .config/quickshell/aurora/Hypr.qml +19 −0
  14. R .config/quickshell/wal-control/IconBtn.qml → .config/quickshell/aurora/IconBtn.qml +0 −0
  15. R .config/quickshell/wal-control/ListRow.qml → .config/quickshell/aurora/ListRow.qml +0 −0
  16. R .config/quickshell/wal-control/MediaCard.qml → .config/quickshell/aurora/MediaCard.qml +0 −0
  17. R .config/quickshell/wal-control/Net.qml → .config/quickshell/aurora/Net.qml +15 −5
  18. R .config/quickshell/wal-control/NetPage.qml → .config/quickshell/aurora/NetPage.qml +0 −0
  19. A .config/quickshell/aurora/NotifCard.qml +112 −0
  20. A .config/quickshell/aurora/NotifPage.qml +65 −0
  21. A .config/quickshell/aurora/NotifPopups.qml +34 −0
  22. A .config/quickshell/aurora/Notifs.qml +59 −0
  23. R .config/quickshell/wal-control/SectionLabel.qml → .config/quickshell/aurora/SectionLabel.qml +0 −0
  24. R .config/quickshell/wal-control/SliderRow.qml → .config/quickshell/aurora/SliderRow.qml +0 −0
  25. A .config/quickshell/aurora/Spark.qml +33 −0
  26. A .config/quickshell/aurora/StatBar.qml +38 −0
  27. A .config/quickshell/aurora/SysPage.qml +179 −0
  28. A .config/quickshell/aurora/SysStats.qml +69 −0
  29. R .config/quickshell/wal-control/Theme.qml → .config/quickshell/aurora/Theme.qml +49 −3
  30. A .config/quickshell/aurora/Tray.qml +59 −0
  31. A .config/quickshell/aurora/Workspaces.qml +67 −0
  32. A .config/quickshell/aurora/shell.qml +62 −0
  33. D .config/quickshell/wal-control/shell.qml +0 −118
  34. R .config/quickshell/wal-control/wal-control.service → .config/systemd/user/aurora.service +2 −2
  35. D .config/systemd/user/wal-control.service +0 −12
M .config/hypr/hyprland.lua View
@@ -20,8 +20,8 @@ hl.on("hyprland.start", function()
2020 hl.exec_cmd("wl-paste --type image --watch cliphist store")
2121 hl.exec_cmd("hyprpm reload -n")
2222 hl.exec_cmd("kanshi")
23− hl.exec_cmd("waybar")
24− hl.exec_cmd("swaync")
23+ -- hl.exec_cmd("waybar")
24+ -- hl.exec_cmd("swaync")
2525 hl.exec_cmd("gammastep")
2626 hl.exec_cmd("hypridle")
2727 -- hl.exec_cmd("blueman-applet")
M .config/hypr/source/keybinds.lua View
@@ -27,10 +27,15 @@ hl.bind(
2727 )
2828
2929
30+------------------
31+-- Aurora Shell
32+-----------------
33+hl.bind(mainMod .. " + c", hl.dsp.exec_cmd("qs -c aurora ipc call shell toggle"))
34+hl.bind(mainMod .. " + b", hl.dsp.exec_cmd("systemctl --user restart aurora"))
35+
3036 hl.bind(mainMod .. " + R", hl.dsp.exec_cmd(menu))
3137 -- bind = $mainMod SHIFT, P, pseudo, # dwindle
3238 hl.bind(mainMod .. " + Tab", hl.dsp.window.cycle_next({ next = false }), { repeating = true })
33−hl.bind(mainMod .. " + b", hl.dsp.exec_cmd("killall waybar || waybar"))
3439 hl.bind(mainMod .. " + grave", hl.dsp.exec_cmd("fuzzelunicode"))
3540 hl.bind(mainMod .. " + escape", hl.dsp.exec_cmd("sysact"))
3641
@@ -145,5 +150,3 @@ hl.bind("XF86AudioPause", hl.dsp.exec_cmd("playerctl play-pause"), { locked = tr
145150 hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true })
146151 hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { locked = true })
147152
148−-- Quichshell
149−hl.bind(mainMod .. "+ C", hl.dsp.exec_cmd("qs -p ~/.config/quickshell/wal-control ipc call control toggle"))
R .config/quickshell/wal-control/AudioPage.qml → .config/quickshell/aurora/AudioPage.qml View

[No content changes]

A .config/quickshell/aurora/Bar.qml View
@@ -0,0 +1,153 @@
1+import QtQuick
2+import Quickshell
3+import Quickshell.Wayland
4+import Quickshell.Bluetooth
5+import Quickshell.Services.Pipewire
6+import Quickshell.Services.UPower
7+
8+PanelWindow {
9+ id: bar
10+ required property var modelData
11+ screen: modelData
12+ signal togglePanel(string name)
13+
14+ anchors { top: true; left: true; right: true }
15+ implicitHeight: Theme.barHeight
16+ color: Theme.barBg
17+ WlrLayershell.namespace: "aurora-bar"
18+
19+ SystemClock { id: clock; precision: SystemClock.Minutes }
20+
21+ // ---- state ----
22+ readonly property var audio: Pipewire.defaultAudioSink ? Pipewire.defaultAudioSink.audio : null
23+ readonly property var mic: Pipewire.defaultAudioSource ? Pipewire.defaultAudioSource.audio : null
24+ readonly property var bat: UPower.displayDevice
25+ readonly property bool hasBat: bat !== null && bat.isPresent
26+ readonly property real batPct: hasBat ? (bat.percentage > 1 ? bat.percentage / 100 : bat.percentage) : 0
27+ readonly property bool charging: hasBat && bat.state === UPowerDeviceState.Charging
28+ readonly property bool plugged: hasBat && bat.state === UPowerDeviceState.FullyCharged
29+ readonly property bool batLow: hasBat && batPct <= 0.15 && !charging
30+ readonly property var adapter: Bluetooth.defaultAdapter
31+
32+ function findHeadset() {
33+ var l = Bluetooth.devices.values
34+ for (var i = 0; i < l.length; i++) {
35+ var d = l[i]
36+ if (!d.connected || !d.batteryAvailable) continue
37+ var ic = String(d.icon)
38+ if (ic.indexOf("head") >= 0 || ic.indexOf("audio") >= 0) return d
39+ }
40+ return null
41+ }
42+ function btConnected() {
43+ var l = Bluetooth.devices.values
44+ for (var i = 0; i < l.length; i++) if (l[i].connected) return true
45+ return false
46+ }
47+ readonly property var headset: findHeadset()
48+ readonly property bool btOn: adapter !== null && adapter.enabled
49+ readonly property bool btLink: btOn && btConnected()
50+
51+ Item {
52+ anchors.fill: parent
53+
54+ // ---------------- left ----------------
55+ Row {
56+ anchors.left: parent.left
57+ height: parent.height
58+ spacing: 10
59+ Workspaces { height: parent.height }
60+ CatLoop { height: parent.height }
61+ }
62+
63+ // ---------------- center ----------------
64+ Text {
65+ anchors.centerIn: parent
66+ width: Math.min(implicitWidth, parent.width * 0.32)
67+ elide: Text.ElideRight
68+ horizontalAlignment: Text.AlignHCenter
69+ readonly property string title: ToplevelManager.activeToplevel ? ToplevelManager.activeToplevel.title : ""
70+ visible: title !== ""
71+ text: Theme.iArch + " " + title
72+ color: Theme.fg
73+ font.family: Theme.font
74+ font.pixelSize: Theme.fontSize
75+ font.bold: true
76+ }
77+
78+ // ---------------- right ----------------
79+ Row {
80+ anchors.right: parent.right
81+ height: parent.height
82+ spacing: 10
83+
84+ Tray {}
85+
86+ BarPill {
87+ visible: bar.headset !== null
88+ icon: Theme.iHeadphones
89+ text: bar.headset ? Math.round(bar.headset.battery * 100) + "%" : ""
90+ onActivated: bar.togglePanel("bt")
91+ }
92+
93+ BarPill {
94+ visible: bar.audio !== null
95+ icon: Theme.volIcon(bar.audio ? bar.audio.volume : 0, bar.audio ? bar.audio.muted : true)
96+ text: (bar.audio ? Math.round(bar.audio.volume * 100) : 0) + "%"
97+ + (bar.mic && bar.mic.muted ? " " + Theme.iMicOff : "")
98+ onActivated: button => {
99+ if (button === Qt.RightButton && bar.audio) bar.audio.muted = !bar.audio.muted
100+ else if (button === Qt.MiddleButton && bar.mic) bar.mic.muted = !bar.mic.muted
101+ else bar.togglePanel("audio")
102+ }
103+ onScrolled: dir => {
104+ if (bar.audio) bar.audio.volume = Math.max(0, Math.min(1, bar.audio.volume + dir * 0.02))
105+ }
106+ }
107+
108+ BarPill {
109+ icon: Net.wired && !(Net.wifiEnabled && Net.connected !== "") ? Theme.iEthernet
110+ : (Net.wifiEnabled ? Theme.wifiIcon(Net.connected !== "" ? Net.activeSignal : 0) : Theme.iWifiOff)
111+ tint: (Net.wired || (Net.wifiEnabled && Net.connected !== "")) ? Theme.fg : Theme.dim
112+ onActivated: bar.togglePanel("net")
113+ }
114+
115+ BarPill {
116+ visible: bar.adapter !== null
117+ icon: !bar.btOn ? Theme.iBtOff : (bar.btLink ? Theme.iBtConn : Theme.iBt)
118+ tint: bar.btLink ? Theme.fg : Theme.dim
119+ onActivated: bar.togglePanel("bt")
120+ }
121+
122+ BarPill {
123+ visible: bar.hasBat
124+ icon: Theme.batIcon(bar.batPct, bar.charging, bar.plugged)
125+ text: Math.round(bar.batPct * 100) + "%"
126+ tint: bar.batLow ? Theme.err : Theme.fg
127+ underline: bar.batLow ? Theme.err : Theme.accent
128+ onActivated: bar.togglePanel("sys")
129+ }
130+
131+ BarPill {
132+ icon: Notifs.dnd ? Theme.iBellOff : Theme.iBell
133+ text: Notifs.count > 0 ? String(Notifs.count) : ""
134+ tint: Notifs.dnd ? Theme.dim : Theme.fg
135+ onActivated: button => {
136+ if (button === Qt.RightButton) Notifs.dnd = !Notifs.dnd
137+ else bar.togglePanel("notif")
138+ }
139+ }
140+
141+ BarPill {
142+ icon: Theme.iCal
143+ text: Qt.formatDateTime(clock.date, "dd MMM yyyy")
144+ onActivated: bar.togglePanel("home")
145+ }
146+ BarPill {
147+ icon: Theme.iClock
148+ text: Qt.formatDateTime(clock.date, "HH:mm")
149+ onActivated: bar.togglePanel("home")
150+ }
151+ }
152+ }
153+}
A .config/quickshell/aurora/BarPill.qml View
@@ -0,0 +1,57 @@
1+import QtQuick
2+
3+// A waybar-style block: flat, 12px padding, 1px accent underline.
4+Item {
5+ id: p
6+ property string icon: ""
7+ property string text: ""
8+ property color tint: Theme.fg
9+ property color underline: Theme.accent
10+ signal activated(int button)
11+ signal scrolled(int dir)
12+
13+ implicitWidth: row.implicitWidth + 24
14+ implicitHeight: parent ? parent.height : Theme.barHeight
15+
16+ Rectangle {
17+ anchors.fill: parent
18+ color: m.containsMouse ? Theme.surface : "transparent"
19+ }
20+ Rectangle {
21+ anchors.left: parent.left
22+ anchors.right: parent.right
23+ anchors.bottom: parent.bottom
24+ height: 1
25+ color: p.underline
26+ }
27+ Row {
28+ id: row
29+ anchors.centerIn: parent
30+ spacing: 6
31+ Text {
32+ visible: p.icon !== ""
33+ text: p.icon
34+ color: p.tint
35+ font.family: Theme.font
36+ font.pixelSize: Theme.fontSize + 1
37+ anchors.verticalCenter: parent.verticalCenter
38+ }
39+ Text {
40+ visible: p.text !== ""
41+ text: p.text
42+ color: p.tint
43+ font.family: Theme.font
44+ font.pixelSize: Theme.fontSize
45+ font.bold: true
46+ anchors.verticalCenter: parent.verticalCenter
47+ }
48+ }
49+ MouseArea {
50+ id: m
51+ anchors.fill: parent
52+ hoverEnabled: true
53+ acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
54+ onClicked: mouse => p.activated(mouse.button)
55+ onWheel: w => p.scrolled(w.angleDelta.y > 0 ? 1 : -1)
56+ }
57+}
R .config/quickshell/wal-control/BarSlider.qml → .config/quickshell/aurora/BarSlider.qml View

[No content changes]

R .config/quickshell/wal-control/Bright.qml → .config/quickshell/aurora/Bright.qml View

[No content changes]

R .config/quickshell/wal-control/BtPage.qml → .config/quickshell/aurora/BtPage.qml View

[No content changes]

A .config/quickshell/aurora/CatLoop.qml View
@@ -0,0 +1,41 @@
1+import QtQuick
2+
3+// catloop.sh, natively: frames A-E awake, G-N asleep. Faster with more CPU load,
4+// falls asleep after 4 idle cycles. Needs the "Waycat" font you already use.
5+Item {
6+ id: cat
7+ implicitWidth: 40
8+
9+ property int idx: 0
10+ property bool sleeping: false
11+ property int low: 0
12+ readonly property var awake: ["A", "B", "C", "D", "E"]
13+ readonly property var asleep: ["G", "H", "I", "J", "K", "L", "M", "N"]
14+ readonly property var frames: sleeping ? asleep : awake
15+
16+ Text {
17+ anchors.centerIn: parent
18+ text: cat.frames[cat.idx] || ""
19+ color: Theme.fg
20+ font.family: "Waycat"
21+ font.pixelSize: 14
22+ font.bold: true
23+ }
24+
25+ Timer {
26+ id: tick
27+ running: true
28+ repeat: true
29+ interval: 250
30+ onTriggered: {
31+ var next = cat.idx + 1
32+ if (next >= cat.frames.length) {
33+ next = 0
34+ cat.low = SysStats.cpu < 0.02 ? cat.low + 1 : 0
35+ cat.sleeping = cat.low >= 4
36+ }
37+ cat.idx = next
38+ tick.interval = Math.round(Math.max(0.03, 1 / (4 + SysStats.cpu * 100)) * 1000)
39+ }
40+ }
41+}
A .config/quickshell/aurora/ControlPanel.qml View
@@ -0,0 +1,115 @@
1+import QtQuick
2+import QtQuick.Layouts
3+import Quickshell
4+import Quickshell.Wayland
5+import Quickshell.Hyprland
6+
7+PanelWindow {
8+ id: win
9+ property bool opened: false
10+ property int tab: 0
11+ signal tabRequested(int index)
12+ signal closeRequested()
13+
14+ visible: opened
15+ anchors { top: true; right: true }
16+ margins { top: 6; right: 8 } // sits just under the bar (bar's exclusive zone is respected)
17+ implicitWidth: 440
18+ implicitHeight: 610
19+ exclusiveZone: 0
20+ color: "transparent"
21+ WlrLayershell.layer: WlrLayer.Overlay
22+ WlrLayershell.namespace: "aurora-panel"
23+ WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
24+
25+ // click anywhere outside to close
26+ HyprlandFocusGrab {
27+ windows: [win]
28+ active: win.opened
29+ onCleared: win.closeRequested()
30+ }
31+
32+ readonly property var tabs: [
33+ { icon: Theme.iHome, name: "home" },
34+ { icon: Theme.iVolHigh, name: "audio" },
35+ { icon: Theme.iWifi, name: "net" },
36+ { icon: Theme.iBt, name: "bt" },
37+ { icon: Theme.iChip, name: "system" },
38+ { icon: Theme.iBell, name: "notifs" }
39+ ]
40+
41+ Rectangle {
42+ anchors.fill: parent
43+ color: Theme.bgA
44+ border.width: 2
45+ border.color: Theme.accent
46+ focus: true
47+ Keys.onEscapePressed: win.closeRequested()
48+
49+ ColumnLayout {
50+ anchors.fill: parent
51+ anchors.margins: 12
52+ spacing: 10
53+
54+ RowLayout {
55+ Layout.fillWidth: true
56+ spacing: 2
57+
58+ Repeater {
59+ model: win.tabs
60+ delegate: Rectangle {
61+ id: tabBtn
62+ required property var modelData
63+ required property int index
64+ readonly property bool current: win.tab === index
65+ implicitWidth: lbl.implicitWidth + 22
66+ implicitHeight: 28
67+ color: current ? Theme.accent : (tm.containsMouse ? Theme.surfaceHi : "transparent")
68+
69+ Text {
70+ id: lbl
71+ anchors.centerIn: parent
72+ text: tabBtn.current ? tabBtn.modelData.icon + " " + tabBtn.modelData.name : tabBtn.modelData.icon
73+ color: tabBtn.current ? Theme.bg : Theme.fg
74+ font.family: Theme.font
75+ font.pixelSize: Theme.fontSize
76+ font.bold: true
77+ }
78+ Rectangle { // unread badge on the notifs tab
79+ visible: tabBtn.modelData.name === "notifs" && Notifs.count > 0 && !tabBtn.current
80+ width: 6; height: 6
81+ anchors.top: parent.top
82+ anchors.right: parent.right
83+ anchors.margins: 3
84+ color: Theme.accent
85+ }
86+ MouseArea {
87+ id: tm
88+ anchors.fill: parent
89+ hoverEnabled: true
90+ cursorShape: Qt.PointingHandCursor
91+ onClicked: win.tabRequested(tabBtn.index)
92+ }
93+ }
94+ }
95+ Item { Layout.fillWidth: true }
96+ IconBtn { icon: Theme.iClose; implicitHeight: 28; onClicked: win.closeRequested() }
97+ }
98+
99+ Rectangle { Layout.fillWidth: true; Layout.preferredHeight: 1; color: Theme.line }
100+
101+ StackLayout {
102+ Layout.fillWidth: true
103+ Layout.fillHeight: true
104+ currentIndex: win.tab
105+
106+ HomePage { onCloseRequested: win.closeRequested() }
107+ AudioPage {}
108+ NetPage { active: win.opened && win.tab === 2 }
109+ BtPage { active: win.opened && win.tab === 3 }
110+ SysPage {}
111+ NotifPage {}
112+ }
113+ }
114+ }
115+}
A .config/quickshell/aurora/Gpu.qml View
@@ -0,0 +1,46 @@
1+pragma Singleton
2+import QtQuick
3+import Quickshell
4+import Quickshell.Io
5+
6+// Mirrors your gpu_mode.sh (envycontrol -q) and can switch modes via pkexec.
7+Singleton {
8+ id: root
9+ property string mode: "" // "" = envycontrol not available
10+ property string pending: "" // mode queued, applies after reboot
11+ property string message: ""
12+ property bool busy: false
13+ property string target: ""
14+
15+ function refresh() { query.running = true }
16+ function set(m) {
17+ if (busy) return
18+ busy = true
19+ message = ""
20+ target = m
21+ sw.command = ["pkexec", "envycontrol", "-s", m]
22+ sw.running = true
23+ }
24+
25+ Process {
26+ id: query
27+ command: ["envycontrol", "-q"]
28+ running: true
29+ stdout: StdioCollector {
30+ onStreamFinished: {
31+ var w = text.trim().split(/\s+/)
32+ root.mode = w.length && w[0] !== "" ? w[w.length - 1] : ""
33+ }
34+ }
35+ }
36+ Process {
37+ id: sw
38+ stderr: StdioCollector { id: swErr }
39+ onExited: (code, status) => {
40+ root.busy = false
41+ if (code === 0) root.pending = root.target
42+ else root.message = "switch failed or cancelled"
43+ root.refresh()
44+ }
45+ }
46+}
R .config/quickshell/wal-control/HomePage.qml → .config/quickshell/aurora/HomePage.qml View
@@ -1,6 +1,7 @@
11 import QtQuick
22 import QtQuick.Layouts
33 import Quickshell
4+import Quickshell.Bluetooth
45 import Quickshell.Services.Pipewire
56
67 ColumnLayout {
@@ -11,10 +12,10 @@ ColumnLayout {
1112 property string armed: ""
1213 Timer { id: disarm; interval: 3000; onTriggered: page.armed = "" }
1314
14− function act(name, cmd, confirm) {
15+ function act(name, fn, confirm) {
1516 if (confirm && armed !== name) { armed = name; disarm.restart(); return }
1617 armed = ""
17− Quickshell.execDetached(cmd)
18+ fn()
1819 closeRequested()
1920 }
2021
@@ -38,7 +39,6 @@ ColumnLayout {
3839 onMoved: v => { if (audio) audio.volume = v }
3940 onIconClicked: if (audio) audio.muted = !audio.muted
4041 }
41−
4242 SliderRow {
4343 Layout.fillWidth: true
4444 readonly property var audio: Pipewire.defaultAudioSource ? Pipewire.defaultAudioSource.audio : null
@@ -48,7 +48,6 @@ ColumnLayout {
4848 onMoved: v => { if (audio) audio.volume = v }
4949 onIconClicked: if (audio) audio.muted = !audio.muted
5050 }
51−
5251 SliderRow {
5352 Layout.fillWidth: true
5453 visible: Bright.available
@@ -58,6 +57,44 @@ ColumnLayout {
5857 onMoved: v => Bright.setPercent(Math.max(0.01, v) * 100)
5958 }
6059
60+ SectionLabel { text: "quick toggles" }
61+
62+ RowLayout {
63+ Layout.fillWidth: true
64+ spacing: 6
65+ IconBtn {
66+ Layout.fillWidth: true
67+ icon: Net.wifiEnabled ? Theme.iWifi : Theme.iWifiOff
68+ label: "wifi"
69+ active: Net.wifiEnabled
70+ onClicked: Net.setWifi(!Net.wifiEnabled)
71+ }
72+ IconBtn {
73+ Layout.fillWidth: true
74+ readonly property var ad: Bluetooth.defaultAdapter
75+ visible: ad !== null
76+ icon: ad && ad.enabled ? Theme.iBt : Theme.iBtOff
77+ label: "bt"
78+ active: ad ? ad.enabled : false
79+ onClicked: if (ad) ad.enabled = !ad.enabled
80+ }
81+ IconBtn {
82+ Layout.fillWidth: true
83+ icon: Notifs.dnd ? Theme.iBellOff : Theme.iBell
84+ label: "dnd"
85+ active: Notifs.dnd
86+ onClicked: Notifs.dnd = !Notifs.dnd
87+ }
88+ IconBtn {
89+ Layout.fillWidth: true
90+ readonly property var audio: Pipewire.defaultAudioSource ? Pipewire.defaultAudioSource.audio : null
91+ icon: audio && audio.muted ? Theme.iMicOff : Theme.iMic
92+ label: "mic"
93+ active: audio ? !audio.muted : false
94+ onClicked: if (audio) audio.muted = !audio.muted
95+ }
96+ }
97+
6198 Item { Layout.fillHeight: true }
6299
63100 SectionLabel { text: "session" }
@@ -68,33 +105,33 @@ ColumnLayout {
68105 IconBtn {
69106 Layout.fillWidth: true
70107 icon: Theme.iLock
71− onClicked: page.act("lock", ["loginctl", "lock-session"], false)
108+ onClicked: page.act("lock", function () { Quickshell.execDetached(["loginctl", "lock-session"]) }, false)
72109 }
73110 IconBtn {
74111 Layout.fillWidth: true
75112 icon: Theme.iSleep
76− onClicked: page.act("sleep", ["systemctl", "suspend"], false)
113+ onClicked: page.act("sleep", function () { Quickshell.execDetached(["systemctl", "suspend"]) }, false)
77114 }
78115 IconBtn {
79116 Layout.fillWidth: true
80117 icon: Theme.iLogout
81118 label: page.armed === "logout" ? "sure?" : ""
82119 active: page.armed === "logout"
83− onClicked: page.act("logout", ["hyprctl", "dispatch", "exit"], true)
120+ onClicked: page.act("logout", function () { Hypr.logout() }, true)
84121 }
85122 IconBtn {
86123 Layout.fillWidth: true
87124 icon: Theme.iReboot
88125 label: page.armed === "reboot" ? "sure?" : ""
89126 active: page.armed === "reboot"
90− onClicked: page.act("reboot", ["systemctl", "reboot"], true)
127+ onClicked: page.act("reboot", function () { Quickshell.execDetached(["systemctl", "reboot"]) }, true)
91128 }
92129 IconBtn {
93130 Layout.fillWidth: true
94131 icon: Theme.iPower
95132 label: page.armed === "off" ? "sure?" : ""
96133 active: page.armed === "off"
97− onClicked: page.act("off", ["systemctl", "poweroff"], true)
134+ onClicked: page.act("off", function () { Quickshell.execDetached(["systemctl", "poweroff"]) }, true)
98135 }
99136 }
100137 }
A .config/quickshell/aurora/Hypr.qml View
@@ -0,0 +1,19 @@
1+pragma Singleton
2+import QtQuick
3+import Quickshell
4+
5+// Compositor actions. Your waybar config uses the new Lua-style dispatchers
6+// (hl.dsp.focus{...}), so that is the default; flip luaSyntax for older Hyprland.
7+Singleton {
8+ property bool luaSyntax: true
9+
10+ function workspace(arg) {
11+ if (luaSyntax)
12+ Quickshell.execDetached(["hyprctl", "dispatch", 'hl.dsp.focus({workspace="' + arg + '"})'])
13+ else
14+ Quickshell.execDetached(["hyprctl", "dispatch", "workspace", String(arg)])
15+ }
16+
17+ // compositor-agnostic logout
18+ function logout() { Quickshell.execDetached(["sh", "-c", 'loginctl terminate-user "$USER"']) }
19+}
R .config/quickshell/wal-control/IconBtn.qml → .config/quickshell/aurora/IconBtn.qml View

[No content changes]

R .config/quickshell/wal-control/ListRow.qml → .config/quickshell/aurora/ListRow.qml View

[No content changes]

R .config/quickshell/wal-control/MediaCard.qml → .config/quickshell/aurora/MediaCard.qml View

[No content changes]

R .config/quickshell/wal-control/Net.qml → .config/quickshell/aurora/Net.qml View
@@ -7,7 +7,8 @@ import Quickshell.Io
77 Singleton {
88 id: root
99
10− property bool active: false // NetPage sets this while visible
10+ property bool active: false // NetPage sets this while visible (faster polling + rescan)
11+ property bool wired: false
1112 property bool wifiEnabled: true
1213 property string wifiDevice: ""
1314 property var raw: [] // [{inUse, ssid, signal, security}]
@@ -21,6 +22,10 @@ Singleton {
2122 property bool lastWasNew: false
2223
2324 readonly property var networks: build(raw, savedNames)
25+ readonly property int activeSignal: {
26+ for (var i = 0; i < raw.length; i++) if (raw[i].inUse) return raw[i].signal
27+ return 0
28+ }
2429 readonly property string connected: {
2530 for (var i = 0; i < raw.length; i++) if (raw[i].inUse) return raw[i].ssid
2631 return ""
@@ -123,10 +128,12 @@ Singleton {
123128 function setWifi(on) { run(["radio", "wifi", on ? "on" : "off"]) }
124129
125130 // ---- processes ----------------------------------------------------
131+ // always on (the bar shows wifi state); quicker while the Net tab is open
126132 Timer {
127− interval: 10000
128− running: root.active
133+ interval: root.active ? 10000 : 30000
134+ running: true
129135 repeat: true
136+ triggeredOnStart: true
130137 onTriggered: root.refresh()
131138 }
132139
@@ -152,11 +159,14 @@ Singleton {
152159 command: ["nmcli", "-t", "-f", "DEVICE,TYPE,STATE", "device"]
153160 stdout: StdioCollector {
154161 onStreamFinished: {
155− var lines = text.split("\n")
162+ var lines = text.split("\n"), wired = false
156163 for (var i = 0; i < lines.length; i++) {
157164 var f = root.parseTerse(lines[i])
158− if (f.length >= 2 && f[1] === "wifi") { root.wifiDevice = f[0]; return }
165+ if (f.length < 3) continue
166+ if (f[1] === "wifi") root.wifiDevice = f[0]
167+ if (f[1] === "ethernet" && f[2] === "connected") wired = true
159168 }
169+ root.wired = wired
160170 }
161171 }
162172 }
R .config/quickshell/wal-control/NetPage.qml → .config/quickshell/aurora/NetPage.qml View

[No content changes]

A .config/quickshell/aurora/NotifCard.qml View
@@ -0,0 +1,112 @@
1+import QtQuick
2+import QtQuick.Layouts
3+import Quickshell
4+import Quickshell.Services.Notifications
5+
6+Rectangle {
7+ id: card
8+ required property var n // Notification
9+ property bool popup: false
10+
11+ Layout.fillWidth: true
12+ implicitHeight: body.implicitHeight + 20
13+ color: popup ? Theme.bg : Theme.surface
14+ border.width: popup ? 2 : 1
15+ border.color: n.urgency === NotificationUrgency.Critical ? Theme.err : (popup ? Theme.accent : Theme.line)
16+
17+ readonly property string imgSource: {
18+ if (n.image && n.image !== "") return n.image
19+ if (n.appIcon && n.appIcon !== "") return Quickshell.iconPath(n.appIcon, true)
20+ return ""
21+ }
22+
23+ function close() {
24+ if (popup) Notifs.dropPopup(n)
25+ else n.dismiss()
26+ }
27+
28+ MouseArea {
29+ anchors.fill: parent
30+ onClicked: if (card.popup) Notifs.dropPopup(card.n)
31+ }
32+
33+ RowLayout {
34+ id: body
35+ anchors.fill: parent
36+ anchors.margins: 10
37+ spacing: 10
38+
39+ Image {
40+ visible: card.imgSource !== ""
41+ source: card.imgSource
42+ Layout.preferredWidth: 36
43+ Layout.preferredHeight: 36
44+ Layout.alignment: Qt.AlignTop
45+ sourceSize: Qt.size(72, 72)
46+ fillMode: Image.PreserveAspectFit
47+ asynchronous: true
48+ }
49+
50+ ColumnLayout {
51+ Layout.fillWidth: true
52+ spacing: 2
53+
54+ RowLayout {
55+ Layout.fillWidth: true
56+ Text {
57+ Layout.fillWidth: true
58+ elide: Text.ElideRight
59+ text: card.n.appName
60+ color: Theme.accent
61+ font.family: Theme.font
62+ font.pixelSize: Theme.fontSize - 3
63+ font.letterSpacing: 1
64+ }
65+ IconBtn {
66+ icon: Theme.iClose
67+ iconSize: 12
68+ implicitWidth: 22
69+ implicitHeight: 22
70+ onClicked: card.close()
71+ }
72+ }
73+ Text {
74+ Layout.fillWidth: true
75+ text: card.n.summary
76+ wrapMode: Text.Wrap
77+ maximumLineCount: 2
78+ elide: Text.ElideRight
79+ color: Theme.fg
80+ font.family: Theme.font
81+ font.pixelSize: Theme.fontSize
82+ font.bold: true
83+ }
84+ Text {
85+ visible: card.n.body !== ""
86+ Layout.fillWidth: true
87+ text: card.n.body
88+ textFormat: Text.StyledText
89+ wrapMode: Text.Wrap
90+ maximumLineCount: card.popup ? 4 : 6
91+ elide: Text.ElideRight
92+ color: Theme.dim
93+ font.family: Theme.font
94+ font.pixelSize: Theme.fontSize - 1
95+ }
96+ Flow {
97+ visible: card.n.actions.length > 0
98+ Layout.fillWidth: true
99+ spacing: 6
100+ Repeater {
101+ model: card.n.actions
102+ delegate: IconBtn {
103+ required property var modelData
104+ label: modelData.text
105+ implicitHeight: 24
106+ onClicked: { modelData.invoke(); if (card.popup) Notifs.dropPopup(card.n) }
107+ }
108+ }
109+ }
110+ }
111+ }
112+}
A .config/quickshell/aurora/NotifPage.qml View
@@ -0,0 +1,65 @@
1+import QtQuick
2+import QtQuick.Layouts
3+
4+ColumnLayout {
5+ id: page
6+ spacing: 8
7+
8+ RowLayout {
9+ Layout.fillWidth: true
10+ spacing: 6
11+ Text {
12+ Layout.fillWidth: true
13+ text: Notifs.count + (Notifs.count === 1 ? " notification" : " notifications")
14+ color: Notifs.count > 0 ? Theme.accent : Theme.dim
15+ font.family: Theme.font
16+ font.pixelSize: Theme.fontSize
17+ }
18+ IconBtn {
19+ icon: Notifs.dnd ? Theme.iBellOff : Theme.iBell
20+ label: "dnd"
21+ active: Notifs.dnd
22+ onClicked: Notifs.dnd = !Notifs.dnd
23+ }
24+ IconBtn {
25+ icon: Theme.iTrash
26+ label: "clear"
27+ onClicked: Notifs.clearAll()
28+ }
29+ }
30+
31+ SectionLabel { text: "history" }
32+
33+ Text {
34+ visible: Notifs.count === 0
35+ Layout.alignment: Qt.AlignHCenter
36+ Layout.topMargin: 40
37+ text: Theme.iBell + " all quiet"
38+ color: Theme.dim
39+ font.family: Theme.font
40+ font.pixelSize: Theme.fontSize + 1
41+ }
42+
43+ Flickable {
44+ Layout.fillWidth: true
45+ Layout.fillHeight: true
46+ clip: true
47+ contentWidth: width
48+ contentHeight: list.implicitHeight
49+ boundsBehavior: Flickable.StopAtBounds
50+ id: flick
51+
52+ ColumnLayout {
53+ id: list
54+ width: flick.width
55+ spacing: 6
56+ Repeater {
57+ model: { Notifs.count; return Notifs.history() }
58+ delegate: NotifCard {
59+ required property var modelData
60+ n: modelData
61+ }
62+ }
63+ }
64+ }
65+}
A .config/quickshell/aurora/NotifPopups.qml View
@@ -0,0 +1,34 @@
1+import QtQuick
2+import QtQuick.Layouts
3+import Quickshell
4+import Quickshell.Wayland
5+
6+PanelWindow {
7+ id: win
8+ property bool suppressed: false // hidden while the control panel is open
9+
10+ visible: Notifs.popups.length > 0 && !suppressed
11+ anchors { top: true; right: true }
12+ margins { top: 6; right: 8 }
13+ implicitWidth: 360
14+ implicitHeight: col.implicitHeight
15+ exclusiveZone: 0
16+ color: "transparent"
17+ WlrLayershell.layer: WlrLayer.Overlay
18+ WlrLayershell.namespace: "aurora-notifs"
19+ WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
20+
21+ ColumnLayout {
22+ id: col
23+ width: 360
24+ spacing: 6
25+ Repeater {
26+ model: Notifs.popups
27+ delegate: NotifCard {
28+ required property var modelData
29+ n: modelData.n
30+ popup: true
31+ }
32+ }
33+ }
34+}
A .config/quickshell/aurora/Notifs.qml View
@@ -0,0 +1,59 @@
1+pragma Singleton
2+import QtQuick
3+import Quickshell
4+import Quickshell.Services.Notifications
5+
6+// Native notification daemon: toasts + history + do-not-disturb.
7+// Stop dunst/mako first, only one daemon can own org.freedesktop.Notifications.
8+Singleton {
9+ id: root
10+ property bool dnd: false
11+ property var popups: [] // [{n, until}]
12+ readonly property var model: server.trackedNotifications
13+ readonly property int count: server.trackedNotifications.values.length
14+
15+ function history() { // newest first
16+ var l = server.trackedNotifications.values, out = []
17+ for (var i = l.length - 1; i >= 0; i--) out.push(l[i])
18+ return out
19+ }
20+ function clearAll() {
21+ var l = server.trackedNotifications.values, all = []
22+ for (var i = 0; i < l.length; i++) all.push(l[i])
23+ for (var j = 0; j < all.length; j++) all[j].dismiss()
24+ popups = []
25+ }
26+ function dropPopup(n) {
27+ popups = popups.filter(function (p) { return p.n !== n })
28+ }
29+ function push(n) {
30+ var ms = n.urgency === NotificationUrgency.Critical ? 12000
31+ : (n.expireTimeout > 0 ? n.expireTimeout * 1000 : 5000)
32+ var next = popups.concat([{ n: n, until: Date.now() + ms }])
33+ popups = next.slice(-4)
34+ n.closed.connect(function () { root.dropPopup(n) })
35+ }
36+
37+ NotificationServer {
38+ id: server
39+ keepOnReload: true
40+ actionsSupported: true
41+ bodyMarkupSupported: true
42+ imageSupported: true
43+ onNotification: n => {
44+ n.tracked = true
45+ if (!root.dnd || n.urgency === NotificationUrgency.Critical) root.push(n)
46+ }
47+ }
48+
49+ Timer {
50+ interval: 500
51+ running: root.popups.length > 0
52+ repeat: true
53+ onTriggered: {
54+ var now = Date.now()
55+ var keep = root.popups.filter(function (p) { return p.until > now })
56+ if (keep.length !== root.popups.length) root.popups = keep
57+ }
58+ }
59+}
R .config/quickshell/wal-control/SectionLabel.qml → .config/quickshell/aurora/SectionLabel.qml View

[No content changes]

R .config/quickshell/wal-control/SliderRow.qml → .config/quickshell/aurora/SliderRow.qml View

[No content changes]

A .config/quickshell/aurora/Spark.qml View
@@ -0,0 +1,33 @@
1+import QtQuick
2+
3+// Bar-graph history, same flavour as the visualizer bars in your screenshot.
4+Item {
5+ id: s
6+ property var values: []
7+ property int slots: 40
8+ implicitHeight: 44
9+
10+ Row {
11+ anchors.fill: parent
12+ spacing: 2
13+ Repeater {
14+ model: s.slots
15+ delegate: Item {
16+ required property int index
17+ readonly property int vi: s.values.length - s.slots + index
18+ readonly property real v: vi >= 0 && vi < s.values.length ? s.values[vi] : 0
19+ width: (s.width - (s.slots - 1) * 2) / s.slots
20+ height: s.height
21+ Rectangle {
22+ anchors.bottom: parent.bottom
23+ width: parent.width
24+ height: Math.max(2, parent.height * parent.v)
25+ gradient: Gradient {
26+ GradientStop { position: 0; color: Theme.accent }
27+ GradientStop { position: 1; color: Theme.accentDim }
28+ }
29+ }
30+ }
31+ }
32+ }
33+}
A .config/quickshell/aurora/StatBar.qml View
@@ -0,0 +1,38 @@
1+import QtQuick
2+import QtQuick.Layouts
3+
4+RowLayout {
5+ id: r
6+ property string label: ""
7+ property real frac: 0
8+ property string valueText: ""
9+ property color fill: Theme.accent
10+ Layout.fillWidth: true
11+ spacing: 10
12+
13+ Text {
14+ text: r.label
15+ Layout.preferredWidth: 64
16+ color: Theme.dim
17+ font.family: Theme.font
18+ font.pixelSize: Theme.fontSize - 1
19+ }
20+ Rectangle {
21+ Layout.fillWidth: true
22+ Layout.preferredHeight: 6
23+ color: Theme.surfaceHi
24+ Rectangle {
25+ width: parent.width * Math.max(0, Math.min(1, r.frac))
26+ height: parent.height
27+ color: r.fill
28+ }
29+ }
30+ Text {
31+ text: r.valueText
32+ Layout.preferredWidth: 96
33+ horizontalAlignment: Text.AlignRight
34+ color: Theme.fg
35+ font.family: Theme.font
36+ font.pixelSize: Theme.fontSize - 1
37+ }
38+}
A .config/quickshell/aurora/SysPage.qml View
@@ -0,0 +1,179 @@
1+import QtQuick
2+import QtQuick.Layouts
3+import Quickshell.Services.UPower
4+
5+Flickable {
6+ id: flick
7+ clip: true
8+ contentWidth: width
9+ contentHeight: col.implicitHeight
10+ boundsBehavior: Flickable.StopAtBounds
11+
12+ readonly property var bat: UPower.displayDevice
13+ readonly property bool hasBat: bat !== null && bat.isPresent
14+ readonly property real batPct: hasBat ? (bat.percentage > 1 ? bat.percentage / 100 : bat.percentage) : 0
15+ readonly property bool charging: hasBat && bat.state === UPowerDeviceState.Charging
16+
17+ property string armedGpu: ""
18+ Timer { id: disarm; interval: 3000; onTriggered: flick.armedGpu = "" }
19+
20+ function gpuClick(m) {
21+ if (Gpu.mode === m || Gpu.busy) return
22+ if (armedGpu !== m) { armedGpu = m; disarm.restart(); return }
23+ armedGpu = ""
24+ Gpu.set(m)
25+ }
26+ function stateText() {
27+ if (!hasBat) return ""
28+ switch (bat.state) {
29+ case UPowerDeviceState.Charging: return "charging"
30+ case UPowerDeviceState.Discharging: return "discharging"
31+ case UPowerDeviceState.FullyCharged: return "full"
32+ default: return "idle"
33+ }
34+ }
35+ function g(v) { return v.toFixed(1) + "G" }
36+
37+ ColumnLayout {
38+ id: col
39+ width: flick.width
40+ spacing: 8
41+
42+ SectionLabel { text: "cpu" }
43+ RowLayout {
44+ Layout.fillWidth: true
45+ spacing: 10
46+ Text {
47+ text: Theme.iChip + " " + Math.round(SysStats.cpu * 100) + "%"
48+ Layout.preferredWidth: 80
49+ color: Theme.fg
50+ font.family: Theme.font
51+ font.pixelSize: Theme.fontSize + 2
52+ font.bold: true
53+ }
54+ Spark { Layout.fillWidth: true; values: SysStats.history }
55+ }
56+
57+ SectionLabel { text: "memory & disk"; Layout.topMargin: 6 }
58+ StatBar {
59+ label: "memory"
60+ frac: SysStats.memTotal > 0 ? SysStats.memUsed / SysStats.memTotal : 0
61+ valueText: flick.g(SysStats.memUsed) + " / " + flick.g(SysStats.memTotal)
62+ }
63+ StatBar {
64+ label: "disk /"
65+ frac: SysStats.diskTotal > 0 ? SysStats.diskUsed / SysStats.diskTotal : 0
66+ valueText: Math.round(SysStats.diskUsed) + "G / " + Math.round(SysStats.diskTotal) + "G"
67+ }
68+
69+ ColumnLayout {
70+ visible: flick.hasBat
71+ Layout.fillWidth: true
72+ Layout.topMargin: 6
73+ spacing: 8
74+ SectionLabel { text: "battery" }
75+ StatBar {
76+ label: "charge"
77+ frac: flick.batPct
78+ fill: flick.batPct <= 0.15 && !flick.charging ? Theme.err : Theme.accent
79+ valueText: Math.round(flick.batPct * 100) + "% " + flick.stateText()
80+ }
81+ Text {
82+ Layout.fillWidth: true
83+ wrapMode: Text.Wrap
84+ color: Theme.dim
85+ font.family: Theme.font
86+ font.pixelSize: Theme.fontSize - 1
87+ lineHeight: 1.3
88+ text: {
89+ if (!flick.hasBat) return ""
90+ var lines = []
91+ var t = flick.charging ? flick.bat.timeToFull : flick.bat.timeToEmpty
92+ lines.push((flick.charging ? "time to full " : "time left ") + Theme.fmtTime(t))
93+ if (flick.bat.changeRate !== undefined)
94+ lines.push("power draw " + Math.abs(flick.bat.changeRate).toFixed(1) + " W")
95+ if (flick.bat.healthSupported)
96+ lines.push("health " + Math.round(flick.bat.healthPercentage) + "%")
97+ return lines.join("\n")
98+ }
99+ }
100+ }
101+
102+ ColumnLayout {
103+ visible: PowerProfiles.hasPerformanceProfile !== undefined
104+ Layout.fillWidth: true
105+ Layout.topMargin: 6
106+ spacing: 8
107+ SectionLabel { text: "power profile" }
108+ RowLayout {
109+ Layout.fillWidth: true
110+ spacing: 6
111+ IconBtn {
112+ Layout.fillWidth: true
113+ icon: Theme.iLeaf
114+ label: "saver"
115+ active: PowerProfiles.profile === PowerProfile.PowerSaver
116+ onClicked: PowerProfiles.profile = PowerProfile.PowerSaver
117+ }
118+ IconBtn {
119+ Layout.fillWidth: true
120+ icon: Theme.iBalance
121+ label: "balanced"
122+ active: PowerProfiles.profile === PowerProfile.Balanced
123+ onClicked: PowerProfiles.profile = PowerProfile.Balanced
124+ }
125+ IconBtn {
126+ Layout.fillWidth: true
127+ visible: PowerProfiles.hasPerformanceProfile
128+ icon: Theme.iSpeed
129+ label: "perf"
130+ active: PowerProfiles.profile === PowerProfile.Performance
131+ onClicked: PowerProfiles.profile = PowerProfile.Performance
132+ }
133+ }
134+ }
135+
136+ ColumnLayout {
137+ visible: Gpu.mode !== ""
138+ Layout.fillWidth: true
139+ Layout.topMargin: 6
140+ spacing: 8
141+ SectionLabel { text: "graphics · envycontrol" }
142+ RowLayout {
143+ Layout.fillWidth: true
144+ spacing: 6
145+ Repeater {
146+ model: ["integrated", "hybrid", "nvidia"]
147+ delegate: IconBtn {
148+ required property string modelData
149+ Layout.fillWidth: true
150+ icon: Theme.iGpu
151+ label: flick.armedGpu === modelData ? "sure?" : modelData
152+ active: Gpu.mode === modelData
153+ opacity: Gpu.busy ? 0.5 : 1
154+ onClicked: flick.gpuClick(modelData)
155+ }
156+ }
157+ }
158+ Text {
159+ visible: Gpu.pending !== "" || Gpu.message !== "" || Gpu.busy
160+ Layout.fillWidth: true
161+ wrapMode: Text.Wrap
162+ color: Gpu.message !== "" ? Theme.err : Theme.accent
163+ font.family: Theme.font
164+ font.pixelSize: Theme.fontSize - 2
165+ text: Gpu.busy ? "switching…" : (Gpu.message !== "" ? Gpu.message : "queued: " + Gpu.pending + " · reboot to apply")
166+ }
167+ }
168+
169+ Text {
170+ Layout.topMargin: 10
171+ Layout.alignment: Qt.AlignRight
172+ text: "up " + Theme.fmtTime(SysStats.uptime)
173+ color: Theme.dim
174+ font.family: Theme.font
175+ font.pixelSize: Theme.fontSize - 2
176+ }
177+ Item { Layout.preferredHeight: 6 }
178+ }
179+}
A .config/quickshell/aurora/SysStats.qml View
@@ -0,0 +1,69 @@
1+pragma Singleton
2+import QtQuick
3+import Quickshell
4+import Quickshell.Io
5+
6+// cpu / memory / disk / uptime. Shared by the cat, the bar and the System tab.
7+Singleton {
8+ id: root
9+ property real cpu: 0 // 0..1
10+ property real memUsed: 0 // GiB
11+ property real memTotal: 0
12+ property real diskUsed: 0
13+ property real diskTotal: 0
14+ property real uptime: 0 // seconds
15+ property var history: [] // last 40 cpu samples
16+ property real prevIdle: -1
17+ property real prevTotal: 0
18+
19+ FileView { id: statFile; path: "/proc/stat"; blockLoading: true }
20+ FileView { id: memFile; path: "/proc/meminfo"; blockLoading: true }
21+ FileView { id: upFile; path: "/proc/uptime"; blockLoading: true }
22+
23+ function sample() {
24+ statFile.reload()
25+ var m = statFile.text().match(/^cpu\s+(.*)$/m)
26+ if (m) {
27+ var f = m[1].trim().split(/\s+/).map(Number)
28+ var idle = f[3] + (f[4] || 0)
29+ var total = 0
30+ for (var i = 0; i < Math.min(8, f.length); i++) total += f[i]
31+ if (prevIdle >= 0 && total > prevTotal)
32+ cpu = Math.max(0, Math.min(1, 1 - (idle - prevIdle) / (total - prevTotal)))
33+ prevIdle = idle
34+ prevTotal = total
35+ var h = history.slice()
36+ h.push(cpu)
37+ if (h.length > 40) h.shift()
38+ history = h
39+ }
40+
41+ memFile.reload()
42+ var mt = memFile.text()
43+ var t = mt.match(/MemTotal:\s+(\d+)/), a = mt.match(/MemAvailable:\s+(\d+)/)
44+ if (t && a) {
45+ memTotal = parseInt(t[1]) / 1048576
46+ memUsed = (parseInt(t[1]) - parseInt(a[1])) / 1048576
47+ }
48+
49+ upFile.reload()
50+ uptime = parseFloat(upFile.text().split(" ")[0]) || 0
51+ }
52+
53+ Timer { interval: 1000; running: true; repeat: true; triggeredOnStart: true; onTriggered: root.sample() }
54+
55+ Process {
56+ id: dfProc
57+ command: ["df", "-B1", "--output=used,size", "/"]
58+ stdout: StdioCollector {
59+ onStreamFinished: {
60+ var l = text.trim().split("\n")
61+ if (l.length < 2) return
62+ var p = l[1].trim().split(/\s+/)
63+ root.diskUsed = parseFloat(p[0]) / 1073741824
64+ root.diskTotal = parseFloat(p[1]) / 1073741824
65+ }
66+ }
67+ }
68+ Timer { interval: 30000; running: true; repeat: true; triggeredOnStart: true; onTriggered: dfProc.running = true }
69+}
R .config/quickshell/wal-control/Theme.qml → .config/quickshell/aurora/Theme.qml View
@@ -3,7 +3,7 @@ import QtQuick
33 import Quickshell
44 import Quickshell.Io
55
6−// Reads ~/.cache/wal/colors.json (pywal16) and hot-reloads whenever `wal` runs.
6+// pywal16 colours (~/.cache/wal/colors.json), hot-reloaded whenever `wal` runs.
77 Singleton {
88 id: root
99
@@ -11,8 +11,11 @@ Singleton {
1111 property string font: "JetBrainsMono Nerd Font" // any Nerd Font mono
1212 property int fontSize: 12
1313 property real panelAlpha: 0.88
14− // -1 = auto-pick the most vivid of color1..color6, or force 0..15
15− property int accentIndex: -1
14+ // your waybar uses @color14 for underlines/active workspace, so that is the default.
15+ // set -1 to auto-pick the most vivid of color1..color6 instead
16+ property int accentIndex: 14
17+ property int barHeight: 18
18+ property real barAlpha: 1.0
1619
1720 readonly property string walFile: Quickshell.env("HOME") + "/.cache/wal/colors.json"
1821
@@ -79,6 +82,8 @@ Singleton {
7982 readonly property color line: mix(bg, accent, 0.35)
8083 readonly property color dim: mix(bg, fg, 0.55)
8184 readonly property color err: Qt.color(pal[1])
85+ readonly property color barBg: Qt.rgba(bg.r, bg.g, bg.b, barAlpha)
86+ readonly property color cursorC: wal.special.cursor
8287 readonly property color bgA: Qt.rgba(bg.r, bg.g, bg.b, panelAlpha)
8388
8489 // ---- Nerd Font (Material Design) glyphs ---------------------------
@@ -121,6 +126,47 @@ Singleton {
121126 readonly property string iGamepad: cp(0xF0297)
122127 readonly property string iSearch: cp(0xF0349)
123128
129+ readonly property string iHeadset: cp(0xF02CE)
130+ readonly property string iBell: cp(0xF009A)
131+ readonly property string iBellOff: cp(0xF009B)
132+ readonly property string iChip: cp(0xF061A)
133+ readonly property string iMem: cp(0xF035B)
134+ readonly property string iDisk: cp(0xF02CA)
135+ readonly property string iGpu: cp(0xF08AE)
136+ readonly property string iLeaf: cp(0xF032A)
137+ readonly property string iBalance: cp(0xF05D1)
138+ readonly property string iSpeed: cp(0xF04C5)
139+ readonly property string iCal: cp(0xF00ED)
140+ readonly property string iClock: cp(0xF0954)
141+ readonly property string iArch: cp(0xF08C7)
142+ readonly property string iEthernet: cp(0xF0200)
143+ readonly property string iPlug: cp(0xF06A5)
144+ readonly property string iBatCharge: cp(0xF0084)
145+
146+ // workspace icons: the exact Font Awesome glyphs from your waybar config.
147+ // 1 browser, 2 code, 3 book, 4 cog, 5 chat, 6 file, 7 folder, 8 terminal, 9 flask, 10 image
148+ // 11-20 repeat the same ten, like your waybar format-icons did
149+ readonly property var wsIcons: [
150+ String.fromCharCode(0xf269), String.fromCharCode(0xf121), String.fromCharCode(0xf02d),
151+ String.fromCharCode(0xf085), String.fromCharCode(0xf086), String.fromCharCode(0xf1c9),
152+ String.fromCharCode(0xf07c), String.fromCharCode(0xf120), String.fromCharCode(0xf0c3),
153+ String.fromCharCode(0xf03e)
154+ ]
155+ function wsIcon(id) { return (id >= 1 && id <= 20) ? wsIcons[(id - 1) % 10] : String(id) }
156+
157+ readonly property var batLevels: [0xF007A, 0xF007B, 0xF007C, 0xF007D, 0xF007E,
158+ 0xF007F, 0xF0080, 0xF0081, 0xF0082, 0xF0079]
159+ function batIcon(p, charging, plugged) {
160+ if (charging) return iBatCharge
161+ if (plugged) return iPlug
162+ return cp(batLevels[Math.max(0, Math.min(9, Math.floor(p * 10)))])
163+ }
164+ function fmtTime(s) {
165+ if (!s || s <= 0) return "-"
166+ var h = Math.floor(s / 3600), m = Math.floor((s % 3600) / 60)
167+ return h > 0 ? h + "h " + m + "m" : m + "m"
168+ }
169+
124170 function volIcon(v, muted) {
125171 if (muted || v <= 0) return iVolOff
126172 return v < 0.34 ? iVolLow : (v < 0.67 ? iVolMed : iVolHigh)
A .config/quickshell/aurora/Tray.qml View
@@ -0,0 +1,59 @@
1+import QtQuick
2+import Quickshell
3+import Quickshell.Services.SystemTray
4+
5+Item {
6+ id: tray
7+ visible: SystemTray.items.values.length > 0
8+ implicitWidth: row.implicitWidth + 24
9+ implicitHeight: parent ? parent.height : Theme.barHeight
10+
11+ Rectangle {
12+ anchors.left: parent.left
13+ anchors.right: parent.right
14+ anchors.bottom: parent.bottom
15+ height: 1
16+ color: Theme.dim
17+ }
18+ Row {
19+ id: row
20+ anchors.centerIn: parent
21+ spacing: 10
22+ Repeater {
23+ model: SystemTray.items
24+ delegate: Item {
25+ id: it
26+ required property var modelData
27+ width: 14
28+ height: 14
29+ anchors.verticalCenter: parent.verticalCenter
30+
31+ function openMenu() {
32+ var p = it.mapToItem(null, 0, it.height + 4)
33+ modelData.display(QsWindow.window, p.x, p.y)
34+ }
35+ Image {
36+ anchors.fill: parent
37+ source: it.modelData.icon
38+ sourceSize: Qt.size(28, 28)
39+ smooth: true
40+ }
41+ MouseArea {
42+ anchors.fill: parent
43+ acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
44+ cursorShape: Qt.PointingHandCursor
45+ onClicked: mouse => {
46+ if (mouse.button === Qt.LeftButton) {
47+ if (it.modelData.onlyMenu) it.openMenu()
48+ else it.modelData.activate()
49+ } else if (mouse.button === Qt.MiddleButton) {
50+ it.modelData.secondaryActivate()
51+ } else {
52+ it.openMenu()
53+ }
54+ }
55+ }
56+ }
57+ }
58+ }
59+}
A .config/quickshell/aurora/Workspaces.qml View
@@ -0,0 +1,67 @@
1+import QtQuick
2+import Quickshell.Hyprland
3+
4+// Workspaces 1-10 always shown (like a persistent waybar setup), extras appear when used.
5+Row {
6+ id: ws
7+ spacing: 0
8+
9+ function find(id) {
10+ var l = Hyprland.workspaces.values
11+ for (var i = 0; i < l.length; i++) if (l[i].id === id) return l[i]
12+ return null
13+ }
14+ function ids() {
15+ var out = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
16+ var l = Hyprland.workspaces.values
17+ for (var i = 0; i < l.length; i++) if (l[i].id > 10) out.push(l[i].id)
18+ out.sort(function (a, b) { return a - b })
19+ return out
20+ }
21+ readonly property var list: ids()
22+
23+ Repeater {
24+ model: ws.list
25+ delegate: Item {
26+ id: b
27+ required property var modelData
28+ readonly property var obj: ws.find(modelData)
29+ readonly property bool active: Hyprland.focusedWorkspace !== null && Hyprland.focusedWorkspace.id === modelData
30+ readonly property bool occupied: obj !== null && obj.toplevels.values.length > 0
31+ readonly property bool urgent: obj !== null && obj.urgent
32+ readonly property bool lit: active || m.containsMouse
33+
34+ width: 30
35+ height: ws.height
36+
37+ Rectangle {
38+ anchors.fill: parent
39+ color: b.lit ? Theme.accent : "transparent"
40+ }
41+ Rectangle { // occupied marker, like the inset underline in your css
42+ visible: b.occupied && !b.lit
43+ anchors.left: parent.left
44+ anchors.right: parent.right
45+ anchors.bottom: parent.bottom
46+ height: 1
47+ color: Theme.cursorC
48+ }
49+ Text {
50+ anchors.centerIn: parent
51+ text: Theme.wsIcon(b.modelData)
52+ color: b.lit ? Theme.bg : (b.urgent ? Theme.err : Theme.fg)
53+ opacity: (b.occupied || b.lit || b.urgent) ? 1 : 0.4
54+ font.family: Theme.font
55+ font.pixelSize: Theme.fontSize + 1
56+ }
57+ MouseArea {
58+ id: m
59+ anchors.fill: parent
60+ hoverEnabled: true
61+ cursorShape: Qt.PointingHandCursor
62+ onClicked: Hypr.workspace(b.modelData)
63+ onWheel: w => Hypr.workspace(w.angleDelta.y > 0 ? "e-1" : "e+1")
64+ }
65+ }
66+ }
67+}
A .config/quickshell/aurora/shell.qml View
@@ -0,0 +1,62 @@
1+import QtQuick
2+import Quickshell
3+import Quickshell.Io
4+import Quickshell.Services.Pipewire
5+
6+// aurora: bar + control panel + notifications, themed from pywal16.
7+ShellRoot {
8+ id: root
9+
10+ property bool opened: false
11+ property int tab: 0
12+ property double closedAt: 0
13+ property string lastTab: ""
14+ readonly property var tabNames: ["home", "audio", "net", "bt", "sys", "notif"]
15+
16+ function close() {
17+ if (!opened) return
18+ opened = false
19+ closedAt = Date.now()
20+ lastTab = tabNames[tab]
21+ }
22+ function openTab(name) {
23+ var i = tabNames.indexOf(name)
24+ if (i >= 0) tab = i
25+ opened = true
26+ }
27+ // bar buttons: click once to open that tab, again to close.
28+ // (the focus grab already closed the panel on the press, so guard against instant re-open)
29+ function togglePanel(name) {
30+ var i = tabNames.indexOf(name)
31+ if (opened && tab === i) { close(); return }
32+ if (!opened && lastTab === name && Date.now() - closedAt < 250) return
33+ tab = i
34+ opened = true
35+ }
36+
37+ // keep every pipewire node bound so volume / mute / properties are readable
38+ PwObjectTracker { objects: Pipewire.nodes.values }
39+
40+ Variants {
41+ model: Quickshell.screens
42+ Bar { onTogglePanel: name => root.togglePanel(name) }
43+ }
44+
45+ ControlPanel {
46+ opened: root.opened
47+ tab: root.tab
48+ onTabRequested: i => root.tab = i
49+ onCloseRequested: root.close()
50+ }
51+
52+ NotifPopups { suppressed: root.opened }
53+
54+ IpcHandler {
55+ target: "shell"
56+ function toggle(): void { if (root.opened) root.close(); else root.opened = true }
57+ function open(): void { root.opened = true }
58+ function close(): void { root.close() }
59+ function show(name: string): void { root.openTab(name) }
60+ function dnd(): void { Notifs.dnd = !Notifs.dnd }
61+ }
62+}
D .config/quickshell/wal-control/shell.qml View
@@ -1,118 +0,0 @@
1−import QtQuick
2−import QtQuick.Layouts
3−import Quickshell
4−import Quickshell.Io
5−import Quickshell.Wayland
6−import Quickshell.Hyprland
7−import Quickshell.Services.Pipewire
8−
9−ShellRoot {
10− id: root
11− property bool opened: false
12− property int tab: 0
13− readonly property var tabNames: ["home", "audio", "net", "bt"]
14−
15− function openTab(name) {
16− var i = tabNames.indexOf(name)
17− if (i >= 0) tab = i
18− opened = true
19− }
20−
21− // keep every pipewire node bound so volume / mute / properties are readable
22− PwObjectTracker { objects: Pipewire.nodes.values }
23−
24− PanelWindow {
25− id: panel
26− visible: root.opened
27− anchors { top: true; right: true }
28− margins { top: 2 ; right: 2 }
29− implicitWidth: 430
30− implicitHeight: 590
31− exclusiveZone: 0
32− color: "transparent"
33− WlrLayershell.layer: WlrLayer.Overlay
34− WlrLayershell.namespace: "wal-control"
35− WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
36−
37− Rectangle {
38− anchors.fill: parent
39− color: Theme.bgA
40− border.width: 2
41− border.color: Theme.accent
42− focus: true
43− Keys.onEscapePressed: root.opened = false
44−
45− ColumnLayout {
46− anchors.fill: parent
47− anchors.margins: 12
48− spacing: 10
49−
50− RowLayout {
51− Layout.fillWidth: true
52− spacing: 2
53− Repeater {
54− model: [
55− { icon: Theme.iHome, name: "home" },
56− { icon: Theme.iVolHigh, name: "audio" },
57− { icon: Theme.iWifi, name: "net" },
58− { icon: Theme.iBt, name: "bt" }
59− ]
60− delegate: Rectangle {
61− id: tabBtn
62− required property var modelData
63− required property int index
64− implicitWidth: lbl.implicitWidth + 22
65− implicitHeight: 28
66− color: root.tab === index ? Theme.accent : (tm.containsMouse ? Theme.surfaceHi : "transparent")
67− Text {
68− id: lbl
69− anchors.centerIn: parent
70− text: tabBtn.modelData.icon + " " + tabBtn.modelData.name
71− color: root.tab === tabBtn.index ? Theme.bg : Theme.fg
72− font.family: Theme.font
73− font.pixelSize: Theme.fontSize
74− }
75− MouseArea {
76− id: tm
77− anchors.fill: parent
78− hoverEnabled: true
79− cursorShape: Qt.PointingHandCursor
80− onClicked: root.tab = tabBtn.index
81− }
82− }
83− }
84− Item { Layout.fillWidth: true }
85− IconBtn { icon: Theme.iClose; height: 28; onClicked: root.opened = false }
86− }
87−
88− Rectangle { Layout.fillWidth: true; Layout.preferredHeight: 1; color: Theme.line }
89−
90− StackLayout {
91− Layout.fillWidth: true
92− Layout.fillHeight: true
93− currentIndex: root.tab
94−
95− HomePage { onCloseRequested: root.opened = false }
96− AudioPage {}
97− NetPage { active: root.opened && root.tab === 2 }
98− BtPage { active: root.opened && root.tab === 3 }
99− }
100− }
101− }
102− }
103−
104− // clicking anywhere outside the panel closes it
105− HyprlandFocusGrab {
106− windows: [panel]
107− active: root.opened
108− onCleared: root.opened = false
109− }
110−
111− IpcHandler {
112− target: "control"
113− function toggle(): void { root.opened = !root.opened }
114− function open(): void { root.opened = true }
115− function close(): void { root.opened = false }
116− function show(name: string): void { root.openTab(name) }
117− }
118−}
R .config/quickshell/wal-control/wal-control.service → .config/systemd/user/aurora.service View
@@ -1,10 +1,10 @@
11 [Unit]
2−Description=pywal-themed Quickshell control center
2+Description=aurora: Quickshell bar, control panel and notifications
33 After=graphical-session.target
44 PartOf=graphical-session.target
55
66 [Service]
7−ExecStart=/usr/bin/quickshell -p %h/.config/quickshell/wal-control
7+ExecStart=/usr/bin/quickshell -p %h/.config/quickshell/aurora
88 Restart=on-failure
99 RestartSec=2
1010
D .config/systemd/user/wal-control.service View
@@ -1,12 +0,0 @@
1−[Unit]
2−Description=pywal-themed Quickshell control center
3−After=graphical-session.target
4−PartOf=graphical-session.target
5−
6−[Service]
7−ExecStart=/usr/bin/quickshell -p %h/.config/quickshell/wal-control
8−Restart=on-failure
9−RestartSec=2
10−
11−[Install]
12−WantedBy=graphical-session.target