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