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