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