me like nix
0

Configure Feed

Select the types of activity you want to include in your feed.

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 // GitHub 68 readonly property string iconGithub: "\uf09b" 69 70 // System data 71 property int cpuUsage: 0 72 property int memUsage: 0 73 property int temperature: 0 74 property var lastCpuIdle: 0 75 property var lastCpuTotal: 0 76 property string networkName: "" 77 property int networkStrength: 0 78 property bool networkConnected: false 79 property bool networkIsEthernet: false 80 property int volume: 0 81 property bool volumeMuted: false 82 property int brightness: 0 83 property int batteryPercent: 0 84 property string batteryStatus: "" 85 property string powerProfile: "" 86 property string weather: "" 87 property int ghNotifications: 0 88 property int ghLastCount: 0 89 90 anchors { 91 top: true 92 left: true 93 right: true 94 } 95 margins.top: 0 96 margins.bottom: 0 97 margins.left: 0 98 margins.right: 0 99 implicitHeight: 30 100 color: Qt.rgba(0.188, 0.204, 0.275, 0.85) 101 102 // CPU process 103 Process { 104 id: cpuProc 105 command: ["sh", "-c", "head -1 /proc/stat"] 106 stdout: SplitParser { 107 onRead: data => { 108 if (!data) return 109 var p = data.trim().split(/\s+/) 110 var idle = parseInt(p[4]) + parseInt(p[5]) 111 var total = p.slice(1, 8).reduce((a, b) => a + parseInt(b), 0) 112 if (root.lastCpuTotal > 0) { 113 root.cpuUsage = Math.round(100 * (1 - (idle - root.lastCpuIdle) / (total - root.lastCpuTotal))) 114 } 115 root.lastCpuTotal = total 116 root.lastCpuIdle = idle 117 } 118 } 119 Component.onCompleted: running = true 120 } 121 122 // Memory process 123 Process { 124 id: memProc 125 command: ["sh", "-c", "free | grep Mem"] 126 stdout: SplitParser { 127 onRead: data => { 128 if (!data) return 129 var parts = data.trim().split(/\s+/) 130 var total = parseInt(parts[1]) || 1 131 var used = parseInt(parts[2]) || 0 132 root.memUsage = Math.round(100 * used / total) 133 } 134 } 135 Component.onCompleted: running = true 136 } 137 138 // Temperature 139 Process { 140 id: tempProc 141 command: ["sh", "-c", "cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null || echo 0"] 142 stdout: SplitParser { 143 onRead: data => { 144 if (!data) return 145 root.temperature = Math.round(parseInt(data.trim()) / 1000) 146 } 147 } 148 Component.onCompleted: running = true 149 } 150 151 // Network - check ethernet first, then wifi 152 Process { 153 id: netProc 154 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"] 155 stdout: SplitParser { 156 onRead: data => { 157 if (!data || data.trim() === "") { 158 root.networkConnected = false 159 root.networkIsEthernet = false 160 root.networkName = "Disconnected" 161 root.networkStrength = 0 162 return 163 } 164 var trimmed = data.trim() 165 if (trimmed.startsWith("ethernet:")) { 166 root.networkConnected = true 167 root.networkIsEthernet = true 168 root.networkName = trimmed.split(":")[1] || "Ethernet" 169 root.networkStrength = 100 170 } else { 171 var parts = trimmed.split(":") 172 root.networkConnected = true 173 root.networkIsEthernet = false 174 root.networkName = parts[1] || "" 175 root.networkStrength = parseInt(parts[2]) || 0 176 } 177 } 178 } 179 Component.onCompleted: running = true 180 } 181 182 // Volume 183 Process { 184 id: volProc 185 command: ["sh", "-c", "wpctl get-volume @DEFAULT_AUDIO_SINK@"] 186 stdout: SplitParser { 187 onRead: data => { 188 if (!data) return 189 root.volumeMuted = data.indexOf("[MUTED]") !== -1 190 var match = data.match(/Volume:\s+([\d.]+)/) 191 if (match) { 192 root.volume = Math.round(parseFloat(match[1]) * 100) 193 } 194 } 195 } 196 Component.onCompleted: running = true 197 } 198 199 // Brightness 200 Process { 201 id: brightProc 202 command: ["sh", "-c", "brightnessctl -m | cut -d, -f4 | tr -d '%'"] 203 stdout: SplitParser { 204 onRead: data => { 205 if (!data) return 206 root.brightness = parseInt(data.trim()) || 0 207 } 208 } 209 Component.onCompleted: running = true 210 } 211 212 // Battery 213 Process { 214 id: batProc 215 command: ["sh", "-c", "cat /sys/class/power_supply/BAT1/capacity 2>/dev/null || echo 0"] 216 stdout: SplitParser { 217 onRead: data => { 218 if (!data) return 219 root.batteryPercent = parseInt(data.trim()) || 0 220 } 221 } 222 Component.onCompleted: running = true 223 } 224 225 Process { 226 id: batStatusProc 227 command: ["sh", "-c", "cat /sys/class/power_supply/BAT1/status 2>/dev/null || echo Unknown"] 228 stdout: SplitParser { 229 onRead: data => { 230 if (!data) return 231 root.batteryStatus = data.trim() 232 } 233 } 234 Component.onCompleted: running = true 235 } 236 237 // Power profile 238 Process { 239 id: powerProc 240 command: ["sh", "-c", "powerprofilesctl get 2>/dev/null || echo unknown"] 241 stdout: SplitParser { 242 onRead: data => { 243 if (!data) return 244 root.powerProfile = data.trim() 245 } 246 } 247 Component.onCompleted: running = true 248 } 249 250 // Weather 251 Process { 252 id: weatherProc 253 command: ["sh", "-c", "curl -sf 'wttr.in/?format=%c+%t' | tr -d '+'"] 254 stdout: SplitParser { 255 onRead: data => { 256 if (!data) return 257 root.weather = data.trim() 258 } 259 } 260 Component.onCompleted: running = true 261 } 262 263 // GitHub notifications 264 Process { 265 id: ghProc 266 command: ["sh", "-c", "gh api notifications --jq '[.[] | select(.unread)] | length' 2>/dev/null || echo 0"] 267 stdout: SplitParser { 268 onRead: data => { 269 if (!data) return 270 var count = parseInt(data.trim()) || 0 271 root.ghNotifications = count 272 // Send desktop notification if count increased 273 if (count > root.ghLastCount && count > 0) { 274 ghNotifyProc.running = true 275 } 276 root.ghLastCount = count 277 } 278 } 279 Component.onCompleted: running = true 280 } 281 282 // Desktop notification for new GitHub notifications 283 Process { 284 id: ghNotifyProc 285 command: ["notify-send", "-a", "GitHub", "GitHub", "You have new notifications"] 286 } 287 288 // GitHub timer (refresh every 2 minutes) 289 Timer { 290 interval: 120000 291 running: true 292 repeat: true 293 onTriggered: ghProc.running = true 294 } 295 296 // Weather timer (refresh every 15 minutes) 297 Timer { 298 interval: 900000 299 running: true 300 repeat: true 301 onTriggered: weatherProc.running = true 302 } 303 304 // Update timer 305 Timer { 306 interval: 2000 307 running: true 308 repeat: true 309 onTriggered: { 310 cpuProc.running = true 311 memProc.running = true 312 tempProc.running = true 313 netProc.running = true 314 volProc.running = true 315 brightProc.running = true 316 batProc.running = true 317 batStatusProc.running = true 318 powerProc.running = true 319 } 320 } 321 322 // Left section - time, weather, github 323 RowLayout { 324 anchors.left: parent.left 325 anchors.leftMargin: 12 326 anchors.verticalCenter: parent.verticalCenter 327 spacing: 12 328 329 Text { 330 id: clockText 331 color: root.colBlue 332 font { family: root.fontFamily; pixelSize: root.fontSize } 333 text: Qt.formatDateTime(new Date(), "dd-MM-yyyy HH:mm") 334 Timer { 335 interval: 1000 336 running: true 337 repeat: true 338 onTriggered: clockText.text = Qt.formatDateTime(new Date(), "dd-MM-yyyy HH:mm") 339 } 340 } 341 342 Rectangle { width: 1; height: 16; color: root.colOverlay0; visible: root.weather !== "" } 343 344 Text { 345 color: root.colText 346 font { family: root.fontFamily; pixelSize: root.fontSize } 347 text: root.weather 348 visible: root.weather !== "" 349 } 350 351 Rectangle { width: 1; height: 16; color: root.colOverlay0 } 352 353 Text { 354 color: root.ghNotifications > 0 ? root.colBlue : root.colOverlay0 355 font { family: root.fontFamily; pixelSize: root.fontSize } 356 text: root.iconGithub + (root.ghNotifications > 0 ? " " + root.ghNotifications : "") 357 MouseArea { 358 anchors.fill: parent 359 cursorShape: Qt.PointingHandCursor 360 onClicked: ghOpenProc.running = true 361 } 362 } 363 364 Process { 365 id: ghOpenProc 366 command: ["xdg-open", "https://github.com/notifications"] 367 } 368 } 369 370 // Right section - anchored to right edge 371 RowLayout { 372 anchors.right: parent.right 373 anchors.rightMargin: 12 374 anchors.verticalCenter: parent.verticalCenter 375 spacing: 12 376 377 // Volume 378 Text { 379 color: root.colMaroon 380 font { family: root.fontFamily; pixelSize: root.fontSize } 381 text: root.volume + "% " + (root.volumeMuted ? root.iconVolMute : (root.volume > 50 ? root.iconVolHigh : root.iconVolLow)) 382 MouseArea { 383 anchors.fill: parent 384 cursorShape: Qt.PointingHandCursor 385 onClicked: volClickProc.running = true 386 } 387 } 388 389 Process { 390 id: volClickProc 391 command: ["pavucontrol"] 392 } 393 394 Rectangle { width: 1; height: 16; color: root.colOverlay0 } 395 396 // Network 397 Text { 398 color: root.colGreen 399 font { family: root.fontFamily; pixelSize: root.fontSize } 400 text: { 401 if (!root.networkConnected) return "Disconnected \u26a0" 402 if (root.networkIsEthernet) return root.networkName + " " + root.iconEthernet 403 return root.networkName + " (" + root.networkStrength + "%) " + root.iconWifi 404 } 405 } 406 407 Rectangle { width: 1; height: 16; color: root.colOverlay0 } 408 409 // Power Profile 410 Text { 411 color: root.colText 412 font { family: root.fontFamily; pixelSize: root.fontSize } 413 text: { 414 if (root.powerProfile === "performance") return root.iconBolt 415 if (root.powerProfile === "balanced") return root.iconBalance 416 if (root.powerProfile === "power-saver") return root.iconLeaf 417 return root.iconBalance 418 } 419 } 420 421 Rectangle { width: 1; height: 16; color: root.colOverlay0 } 422 423 // CPU 424 Text { 425 color: root.colPeach 426 font { family: root.fontFamily; pixelSize: root.fontSize } 427 text: root.cpuUsage + "% " + root.iconCpu 428 } 429 430 Rectangle { width: 1; height: 16; color: root.colOverlay0 } 431 432 // Memory 433 Text { 434 color: root.colMauve 435 font { family: root.fontFamily; pixelSize: root.fontSize } 436 text: root.memUsage + "% " + root.iconMem 437 } 438 439 Rectangle { width: 1; height: 16; color: root.colOverlay0 } 440 441 // Temperature 442 Text { 443 color: root.colRed 444 font { family: root.fontFamily; pixelSize: root.fontSize } 445 text: { 446 var icon = root.temperature >= 80 ? root.iconTempHigh : (root.temperature >= 50 ? root.iconTempMed : root.iconTempLow) 447 return root.temperature + "\u00b0C " + icon 448 } 449 } 450 451 Rectangle { width: 1; height: 16; color: root.colOverlay0 } 452 453 // Backlight 454 Text { 455 color: root.colYellow 456 font { family: root.fontFamily; pixelSize: root.fontSize } 457 text: root.brightness + "% " + root.iconSun 458 } 459 460 Rectangle { width: 1; height: 16; color: root.colOverlay0 } 461 462 // Battery 463 Text { 464 color: { 465 if (root.batteryStatus === "Charging") return root.colGreen 466 if (root.batteryPercent <= 15) return root.colRed 467 if (root.batteryPercent <= 30) return root.colRed 468 return root.colGreen 469 } 470 font { family: root.fontFamily; pixelSize: root.fontSize } 471 text: { 472 var icon 473 if (root.batteryStatus === "Charging") { 474 icon = root.iconBatCharge 475 } else { 476 var icons = [root.iconBatEmpty, root.iconBatQuarter, root.iconBatHalf, root.iconBatThreeQ, root.iconBatFull] 477 var idx = Math.min(Math.floor(root.batteryPercent / 25), 4) 478 icon = icons[idx] 479 } 480 return root.batteryPercent + "% " + icon 481 } 482 } 483 484 Rectangle { width: 1; height: 16; color: root.colOverlay0 } 485 486 // Power button 487 Text { 488 color: root.colRed 489 font { family: root.fontFamily; pixelSize: root.fontSize } 490 text: root.iconPower 491 } 492 } 493 } 494 } 495} 496