me like nix
1import Quickshell
2import Quickshell.Wayland
3import Quickshell.Io
4import QtQuick
5import QtQuick.Layouts
6
7ShellRoot {
8 PanelWindow {
9 id: root
10
11 // Catppuccin Frappe palette
12 readonly property color colBase: "#303446"
13 readonly property color colMantle: "#292c3c"
14 readonly property color colSurface0: "#414559"
15 readonly property color colText: "#c6d0f5"
16 readonly property color colSubtext0: "#a5adce"
17 readonly property color colOverlay0: "#737994"
18 readonly property color colBlue: "#8caaee"
19 readonly property color colGreen: "#a6d189"
20 readonly property color colPeach: "#ef9f76"
21 readonly property color colMauve: "#ca9ee6"
22 readonly property color colRed: "#e78284"
23 readonly property color colYellow: "#e5c890"
24 readonly property color colMaroon: "#ea999c"
25 readonly property color colLavender: "#babbf1"
26 readonly property color colSky: "#99d1db"
27 readonly property color colSapphire: "#85c1dc"
28
29 readonly property string fontFamily: "BerkeleyMono Nerd Font"
30 readonly property int fontSize: 14
31
32 // Nerd Font icons (using Unicode escapes)
33 // Volume
34 readonly property string iconVolHigh: "\uf028"
35 readonly property string iconVolLow: "\uf027"
36 readonly property string iconVolMute: "\uf6a9"
37 // Network
38 readonly property string iconWifi: "\uf1eb"
39 // Power profiles
40 readonly property string iconBolt: "\uf0e7"
41 readonly property string iconBalance: "\uf24e"
42 readonly property string iconLeaf: "\uf06c"
43 // CPU
44 readonly property string iconCpu: "\uf2db"
45 // Memory
46 readonly property string iconMem: "\uefc5"
47 // Temperature
48 readonly property string iconTempLow: "\uf2cb"
49 readonly property string iconTempMed: "\uf2c9"
50 readonly property string iconTempHigh: "\uf2c7"
51 // Backlight
52 readonly property string iconSun: "\uf185"
53 // Battery
54 readonly property string iconBatEmpty: "\uf244"
55 readonly property string iconBatQuarter: "\uf243"
56 readonly property string iconBatHalf: "\uf242"
57 readonly property string iconBatThreeQ: "\uf241"
58 readonly property string iconBatFull: "\uf240"
59 readonly property string iconBatCharge: "\uf0e7"
60 // Power
61 readonly property string iconPower: "\u23fb"
62
63 // System data
64 property int cpuUsage: 0
65 property int memUsage: 0
66 property int temperature: 0
67 property var lastCpuIdle: 0
68 property var lastCpuTotal: 0
69 property string networkName: ""
70 property int networkStrength: 0
71 property bool networkConnected: false
72 property int volume: 0
73 property bool volumeMuted: false
74 property int brightness: 0
75 property int batteryPercent: 0
76 property string batteryStatus: ""
77 property string powerProfile: ""
78
79 anchors {
80 top: true
81 left: true
82 right: true
83 }
84 margins.top: 0
85 margins.bottom: 0
86 margins.left: 0
87 margins.right: 0
88 implicitHeight: 30
89 color: Qt.rgba(0.188, 0.204, 0.275, 0.85)
90
91 // CPU process
92 Process {
93 id: cpuProc
94 command: ["sh", "-c", "head -1 /proc/stat"]
95 stdout: SplitParser {
96 onRead: data => {
97 if (!data) return
98 var p = data.trim().split(/\s+/)
99 var idle = parseInt(p[4]) + parseInt(p[5])
100 var total = p.slice(1, 8).reduce((a, b) => a + parseInt(b), 0)
101 if (root.lastCpuTotal > 0) {
102 root.cpuUsage = Math.round(100 * (1 - (idle - root.lastCpuIdle) / (total - root.lastCpuTotal)))
103 }
104 root.lastCpuTotal = total
105 root.lastCpuIdle = idle
106 }
107 }
108 Component.onCompleted: running = true
109 }
110
111 // Memory process
112 Process {
113 id: memProc
114 command: ["sh", "-c", "free | grep Mem"]
115 stdout: SplitParser {
116 onRead: data => {
117 if (!data) return
118 var parts = data.trim().split(/\s+/)
119 var total = parseInt(parts[1]) || 1
120 var used = parseInt(parts[2]) || 0
121 root.memUsage = Math.round(100 * used / total)
122 }
123 }
124 Component.onCompleted: running = true
125 }
126
127 // Temperature
128 Process {
129 id: tempProc
130 command: ["sh", "-c", "cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null || echo 0"]
131 stdout: SplitParser {
132 onRead: data => {
133 if (!data) return
134 root.temperature = Math.round(parseInt(data.trim()) / 1000)
135 }
136 }
137 Component.onCompleted: running = true
138 }
139
140 // Network
141 Process {
142 id: netProc
143 command: ["sh", "-c", "nmcli -t -f ACTIVE,SSID,SIGNAL dev wifi | grep '^yes' | head -1"]
144 stdout: SplitParser {
145 onRead: data => {
146 if (!data || data.trim() === "") {
147 root.networkConnected = false
148 root.networkName = "Disconnected"
149 root.networkStrength = 0
150 return
151 }
152 var parts = data.trim().split(":")
153 root.networkConnected = true
154 root.networkName = parts[1] || ""
155 root.networkStrength = parseInt(parts[2]) || 0
156 }
157 }
158 Component.onCompleted: running = true
159 }
160
161 // Volume
162 Process {
163 id: volProc
164 command: ["sh", "-c", "wpctl get-volume @DEFAULT_AUDIO_SINK@"]
165 stdout: SplitParser {
166 onRead: data => {
167 if (!data) return
168 root.volumeMuted = data.indexOf("[MUTED]") !== -1
169 var match = data.match(/Volume:\s+([\d.]+)/)
170 if (match) {
171 root.volume = Math.round(parseFloat(match[1]) * 100)
172 }
173 }
174 }
175 Component.onCompleted: running = true
176 }
177
178 // Brightness
179 Process {
180 id: brightProc
181 command: ["sh", "-c", "brightnessctl -m | cut -d, -f4 | tr -d '%'"]
182 stdout: SplitParser {
183 onRead: data => {
184 if (!data) return
185 root.brightness = parseInt(data.trim()) || 0
186 }
187 }
188 Component.onCompleted: running = true
189 }
190
191 // Battery
192 Process {
193 id: batProc
194 command: ["sh", "-c", "cat /sys/class/power_supply/BAT1/capacity 2>/dev/null || echo 0"]
195 stdout: SplitParser {
196 onRead: data => {
197 if (!data) return
198 root.batteryPercent = parseInt(data.trim()) || 0
199 }
200 }
201 Component.onCompleted: running = true
202 }
203
204 Process {
205 id: batStatusProc
206 command: ["sh", "-c", "cat /sys/class/power_supply/BAT1/status 2>/dev/null || echo Unknown"]
207 stdout: SplitParser {
208 onRead: data => {
209 if (!data) return
210 root.batteryStatus = data.trim()
211 }
212 }
213 Component.onCompleted: running = true
214 }
215
216 // Power profile
217 Process {
218 id: powerProc
219 command: ["sh", "-c", "powerprofilesctl get 2>/dev/null || echo unknown"]
220 stdout: SplitParser {
221 onRead: data => {
222 if (!data) return
223 root.powerProfile = data.trim()
224 }
225 }
226 Component.onCompleted: running = true
227 }
228
229 // Update timer
230 Timer {
231 interval: 2000
232 running: true
233 repeat: true
234 onTriggered: {
235 cpuProc.running = true
236 memProc.running = true
237 tempProc.running = true
238 netProc.running = true
239 volProc.running = true
240 brightProc.running = true
241 batProc.running = true
242 batStatusProc.running = true
243 powerProc.running = true
244 }
245 }
246
247 RowLayout {
248 anchors.fill: parent
249 anchors.leftMargin: 12
250 anchors.rightMargin: 12
251 spacing: 0
252
253 // Clock
254 Text {
255 id: clockText
256 color: root.colBlue
257 font { family: root.fontFamily; pixelSize: root.fontSize }
258 text: Qt.formatDateTime(new Date(), "dd-MM-yyyy HH:mm")
259 Timer {
260 interval: 1000
261 running: true
262 repeat: true
263 onTriggered: clockText.text = Qt.formatDateTime(new Date(), "dd-MM-yyyy HH:mm")
264 }
265 }
266
267 Item { Layout.fillWidth: true }
268
269 // Right section
270 RowLayout {
271 spacing: 12
272
273 // Volume
274 Text {
275 color: root.colMaroon
276 font { family: root.fontFamily; pixelSize: root.fontSize }
277 text: root.volume + "% " + (root.volumeMuted ? root.iconVolMute : (root.volume > 50 ? root.iconVolHigh : root.iconVolLow))
278 MouseArea {
279 anchors.fill: parent
280 cursorShape: Qt.PointingHandCursor
281 onClicked: volClickProc.running = true
282 }
283 }
284
285 Process {
286 id: volClickProc
287 command: ["pavucontrol"]
288 }
289
290 Rectangle { width: 1; height: 16; color: root.colOverlay0 }
291
292 // Network
293 Text {
294 color: root.colGreen
295 font { family: root.fontFamily; pixelSize: root.fontSize }
296 text: root.networkConnected ? root.networkName + " (" + root.networkStrength + "%) " + root.iconWifi : "Disconnected \u26a0"
297 }
298
299 Rectangle { width: 1; height: 16; color: root.colOverlay0 }
300
301 // Power Profile
302 Text {
303 color: root.colText
304 font { family: root.fontFamily; pixelSize: root.fontSize }
305 text: {
306 if (root.powerProfile === "performance") return root.iconBolt
307 if (root.powerProfile === "balanced") return root.iconBalance
308 if (root.powerProfile === "power-saver") return root.iconLeaf
309 return root.iconBalance
310 }
311 }
312
313 Rectangle { width: 1; height: 16; color: root.colOverlay0 }
314
315 // CPU
316 Text {
317 color: root.colPeach
318 font { family: root.fontFamily; pixelSize: root.fontSize }
319 text: root.cpuUsage + "% " + root.iconCpu
320 }
321
322 Rectangle { width: 1; height: 16; color: root.colOverlay0 }
323
324 // Memory
325 Text {
326 color: root.colMauve
327 font { family: root.fontFamily; pixelSize: root.fontSize }
328 text: root.memUsage + "% " + root.iconMem
329 }
330
331 Rectangle { width: 1; height: 16; color: root.colOverlay0 }
332
333 // Temperature
334 Text {
335 color: root.colRed
336 font { family: root.fontFamily; pixelSize: root.fontSize }
337 text: {
338 var icon = root.temperature >= 80 ? root.iconTempHigh : (root.temperature >= 50 ? root.iconTempMed : root.iconTempLow)
339 return root.temperature + "\u00b0C " + icon
340 }
341 }
342
343 Rectangle { width: 1; height: 16; color: root.colOverlay0 }
344
345 // Backlight
346 Text {
347 color: root.colYellow
348 font { family: root.fontFamily; pixelSize: root.fontSize }
349 text: root.brightness + "% " + root.iconSun
350 }
351
352 Rectangle { width: 1; height: 16; color: root.colOverlay0 }
353
354 // Battery
355 Text {
356 color: {
357 if (root.batteryStatus === "Charging") return root.colGreen
358 if (root.batteryPercent <= 15) return root.colRed
359 if (root.batteryPercent <= 30) return root.colRed
360 return root.colGreen
361 }
362 font { family: root.fontFamily; pixelSize: root.fontSize }
363 text: {
364 var icon
365 if (root.batteryStatus === "Charging") {
366 icon = root.iconBatCharge
367 } else {
368 var icons = [root.iconBatEmpty, root.iconBatQuarter, root.iconBatHalf, root.iconBatThreeQ, root.iconBatFull]
369 var idx = Math.min(Math.floor(root.batteryPercent / 25), 4)
370 icon = icons[idx]
371 }
372 return root.batteryPercent + "% " + icon
373 }
374 }
375
376 Rectangle { width: 1; height: 16; color: root.colOverlay0 }
377
378 // Power button
379 Text {
380 color: root.colRed
381 font { family: root.fontFamily; pixelSize: root.fontSize }
382 text: root.iconPower
383 }
384 }
385 }
386 }
387}