me like nix
0

Configure Feed

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

replace waybar with quickshell

author
Sean Aye
date (Mar 22, 2026, 8:03 PM -0400) commit a17d0da5 parent b2c51c20 change-id pwpsqxrp
+404 -219
+6 -219
hosts/common/home.nix
··· 176 176 { command = [ "xwayland-satellite" ]; } 177 177 { command = [ "swww-daemon" ]; } 178 178 { command = [ "mako" ]; } 179 - { command = [ "waybar" ]; } 179 + { command = [ "quickshell" "-p" "${./quickshell/shell.qml}" ]; } 180 180 { command = [ "wl-paste --watch cliphist store" ]; } 181 181 ]; 182 182 environment = { ··· 192 192 "libsoup-2.74.3" 193 193 ]; 194 194 195 - programs.waybar = { 196 - enable = true; 197 - settings = { 198 - mainBar = { 199 - height = 8; 200 - spacing = 0; 201 - 202 - modules-left = [ 203 - "custom/media" 204 - ]; 205 - 206 - modules-center = [ 207 - "clock" 208 - ]; 209 - 210 - modules-right = [ 211 - "mpd" 212 - "pulseaudio" 213 - "network" 214 - "power-profiles-daemon" 215 - "cpu" 216 - "memory" 217 - "temperature" 218 - "backlight" 219 - "sway/language" 220 - "battery" 221 - "battery#bat2" 222 - "custom/power" 223 - ]; 224 - 225 - "keyboard-state" = { 226 - numlock = true; 227 - capslock = true; 228 - format = "{name} {icon}"; 229 - format-icons = { 230 - locked = " "; 231 - unlocked = " "; 232 - }; 233 - }; 234 - 235 - mpd = { 236 - format = "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ {volume}% "; 237 - format-disconnected = "Disconnected "; 238 - format-stopped = "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped "; 239 - unknown-tag = "N/A"; 240 - interval = 5; 241 - consume-icons = { 242 - on = " "; 243 - }; 244 - random-icons = { 245 - off = "<span color=\"#f53c3c\"></span> "; 246 - on = " "; 247 - }; 248 - repeat-icons = { 249 - on = " "; 250 - }; 251 - single-icons = { 252 - on = "1 "; 253 - }; 254 - state-icons = { 255 - paused = ""; 256 - playing = ""; 257 - }; 258 - tooltip-format = "MPD (connected)"; 259 - tooltip-format-disconnected = "MPD (disconnected)"; 260 - }; 261 - 262 - idle_inhibitor = { 263 - format = "{icon}"; 264 - format-icons = { 265 - activated = " "; 266 - deactivated = " "; 267 - }; 268 - }; 269 - 270 - tray = { 271 - spacing = 10; 272 - }; 273 - 274 - clock = { 275 - format = "{:%d-%m-%Y %H:%M}"; 276 - tooltip-format = "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>"; 277 - format-alt = "{:%Y-%m-%d %H:%M:%S}"; 278 - }; 279 - 280 - cpu = { 281 - format = "{usage}%  "; 282 - tooltip = false; 283 - }; 284 - 285 - memory = { 286 - format = "{}%  "; 287 - }; 288 - 289 - temperature = { 290 - critical-threshold = 80; 291 - format = "{temperatureC}°C {icon}"; 292 - format-icons = [ 293 - "" 294 - "" 295 - "" 296 - ]; 297 - }; 298 - 299 - backlight = { 300 - format = "{percent}% {icon}"; 301 - format-icons = [ 302 - "" 303 - "" 304 - "" 305 - "" 306 - "" 307 - "" 308 - "" 309 - "" 310 - "" 311 - ]; 312 - }; 313 - 314 - battery = { 315 - states = { 316 - warning = 30; 317 - critical = 15; 318 - }; 319 - format = "{capacity}% {icon}"; 320 - format-full = "{capacity}% {icon}"; 321 - format-charging = "{capacity}% "; 322 - format-plugged = "{capacity}% "; 323 - format-alt = "{time} {icon}"; 324 - format-icons = [ 325 - "" 326 - "" 327 - "" 328 - "" 329 - "" 330 - ]; 331 - }; 332 - 333 - "battery#bat2" = { 334 - bat = "BAT2"; 335 - }; 336 - 337 - "power-profiles-daemon" = { 338 - format = "{icon}"; 339 - tooltip-format = "Power profile: {profile}\nDriver: {driver}"; 340 - tooltip = true; 341 - format-icons = { 342 - default = ""; 343 - performance = ""; 344 - balanced = " "; 345 - power-saver = " "; 346 - }; 347 - }; 348 - 349 - network = { 350 - format-wifi = "{essid} ({signalStrength}%)  "; 351 - format-ethernet = "{ipaddr}/{cidr} "; 352 - tooltip-format = "{ifname} via {gwaddr} "; 353 - format-linked = "{ifname} (No IP) "; 354 - format-disconnected = "Disconnected ⚠"; 355 - format-alt = "{ifname}: {ipaddr}/{cidr}"; 356 - }; 357 - 358 - pulseaudio = { 359 - format = "{volume}% {icon} {format_source}"; 360 - format-bluetooth = "{volume}% {icon} {format_source}"; 361 - format-bluetooth-muted = " {icon} {format_source}"; 362 - format-muted = " {format_source}"; 363 - format-source = "{volume}% "; 364 - format-source-muted = " "; 365 - format-icons = { 366 - headphone = ""; 367 - hands-free = ""; 368 - headset = ""; 369 - phone = ""; 370 - portable = ""; 371 - car = ""; 372 - default = [ 373 - "" 374 - " " 375 - " " 376 - ]; 377 - }; 378 - on-click = "pavucontrol"; 379 - }; 380 - 381 - "custom/media" = { 382 - format = "{icon} {text}"; 383 - return-type = "json"; 384 - max-length = 40; 385 - format-icons = { 386 - spotify = " "; 387 - default = "🎜 "; 388 - }; 389 - escape = true; 390 - exec = "$HOME/.config/waybar/mediaplayer.py 2> /dev/null"; 391 - }; 392 - 393 - "custom/power" = { 394 - format = "⏻ "; 395 - tooltip = false; 396 - menu = "on-click"; 397 - menu-file = "${./waybar/power_menu.xml}"; 398 - menu-actions = { 399 - shutdown = "shutdown"; 400 - reboot = "reboot"; 401 - suspend = "systemctl suspend"; 402 - hibernate = "systemctl hibernate"; 403 - }; 404 - }; 405 - }; 406 - }; 407 - style = builtins.readFile ./waybar/style.css; 408 - }; 195 + # Quickshell status bar (QML config in ./quickshell/shell.qml) 409 196 410 197 programs.ssh = { 411 198 enable = true; ··· 439 226 fuzzel # Application launcher 440 227 bemoji # emoji picker 441 228 networkmanager_dmenu # network picker for fuzzel 442 - waybar # Status bar (highly recommended) 229 + quickshell # Status bar (QML-based) 443 230 mako # Notification daemon 444 231 swww # For setting wallpapers 445 232 cliphist # Clipboard history manager ··· 478 265 atuin 479 266 480 267 # --- FONTS ARE IMPORTANT --- 481 - # Berkeley Mono is the main system font, keeping JetBrains and Font Awesome for waybar 482 - font-awesome # For icons in waybar 268 + # Berkeley Mono is the main system font, keeping JetBrains and Font Awesome for icons 269 + font-awesome 483 270 noto-fonts 484 271 noto-fonts-cjk-sans 485 272 noto-fonts-color-emoji 486 273 nerd-fonts.jetbrains-mono 487 - nerd-fonts.symbols-only # Additional Nerd Font symbols for waybar 274 + nerd-fonts.symbols-only 488 275 # --- POLKIT AGENT (for 1Password GUI, etc.) --- 489 276 lxqt.lxqt-policykit # Lightweight polkit agent 490 277 ];
+398
hosts/common/quickshell/shell.qml
··· 1 + import Quickshell 2 + import Quickshell.Wayland 3 + import Quickshell.Io 4 + import QtQuick 5 + import QtQuick.Layouts 6 + 7 + ShellRoot { 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 + implicitHeight: 38 85 + color: "transparent" 86 + 87 + // CPU process 88 + Process { 89 + id: cpuProc 90 + command: ["sh", "-c", "head -1 /proc/stat"] 91 + stdout: SplitParser { 92 + onRead: data => { 93 + if (!data) return 94 + var p = data.trim().split(/\s+/) 95 + var idle = parseInt(p[4]) + parseInt(p[5]) 96 + var total = p.slice(1, 8).reduce((a, b) => a + parseInt(b), 0) 97 + if (root.lastCpuTotal > 0) { 98 + root.cpuUsage = Math.round(100 * (1 - (idle - root.lastCpuIdle) / (total - root.lastCpuTotal))) 99 + } 100 + root.lastCpuTotal = total 101 + root.lastCpuIdle = idle 102 + } 103 + } 104 + Component.onCompleted: running = true 105 + } 106 + 107 + // Memory process 108 + Process { 109 + id: memProc 110 + command: ["sh", "-c", "free | grep Mem"] 111 + stdout: SplitParser { 112 + onRead: data => { 113 + if (!data) return 114 + var parts = data.trim().split(/\s+/) 115 + var total = parseInt(parts[1]) || 1 116 + var used = parseInt(parts[2]) || 0 117 + root.memUsage = Math.round(100 * used / total) 118 + } 119 + } 120 + Component.onCompleted: running = true 121 + } 122 + 123 + // Temperature 124 + Process { 125 + id: tempProc 126 + command: ["sh", "-c", "cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null || echo 0"] 127 + stdout: SplitParser { 128 + onRead: data => { 129 + if (!data) return 130 + root.temperature = Math.round(parseInt(data.trim()) / 1000) 131 + } 132 + } 133 + Component.onCompleted: running = true 134 + } 135 + 136 + // Network 137 + Process { 138 + id: netProc 139 + command: ["sh", "-c", "nmcli -t -f ACTIVE,SSID,SIGNAL dev wifi | grep '^yes' | head -1"] 140 + stdout: SplitParser { 141 + onRead: data => { 142 + if (!data || data.trim() === "") { 143 + root.networkConnected = false 144 + root.networkName = "Disconnected" 145 + root.networkStrength = 0 146 + return 147 + } 148 + var parts = data.trim().split(":") 149 + root.networkConnected = true 150 + root.networkName = parts[1] || "" 151 + root.networkStrength = parseInt(parts[2]) || 0 152 + } 153 + } 154 + Component.onCompleted: running = true 155 + } 156 + 157 + // Volume 158 + Process { 159 + id: volProc 160 + command: ["sh", "-c", "wpctl get-volume @DEFAULT_AUDIO_SINK@"] 161 + stdout: SplitParser { 162 + onRead: data => { 163 + if (!data) return 164 + root.volumeMuted = data.indexOf("[MUTED]") !== -1 165 + var match = data.match(/Volume:\s+([\d.]+)/) 166 + if (match) { 167 + root.volume = Math.round(parseFloat(match[1]) * 100) 168 + } 169 + } 170 + } 171 + Component.onCompleted: running = true 172 + } 173 + 174 + // Brightness 175 + Process { 176 + id: brightProc 177 + command: ["sh", "-c", "brightnessctl -m | cut -d, -f4 | tr -d '%'"] 178 + stdout: SplitParser { 179 + onRead: data => { 180 + if (!data) return 181 + root.brightness = parseInt(data.trim()) || 0 182 + } 183 + } 184 + Component.onCompleted: running = true 185 + } 186 + 187 + // Battery 188 + Process { 189 + id: batProc 190 + command: ["sh", "-c", "cat /sys/class/power_supply/BAT1/capacity 2>/dev/null || echo 0"] 191 + stdout: SplitParser { 192 + onRead: data => { 193 + if (!data) return 194 + root.batteryPercent = parseInt(data.trim()) || 0 195 + } 196 + } 197 + Component.onCompleted: running = true 198 + } 199 + 200 + Process { 201 + id: batStatusProc 202 + command: ["sh", "-c", "cat /sys/class/power_supply/BAT1/status 2>/dev/null || echo Unknown"] 203 + stdout: SplitParser { 204 + onRead: data => { 205 + if (!data) return 206 + root.batteryStatus = data.trim() 207 + } 208 + } 209 + Component.onCompleted: running = true 210 + } 211 + 212 + // Power profile 213 + Process { 214 + id: powerProc 215 + command: ["sh", "-c", "powerprofilesctl get 2>/dev/null || echo unknown"] 216 + stdout: SplitParser { 217 + onRead: data => { 218 + if (!data) return 219 + root.powerProfile = data.trim() 220 + } 221 + } 222 + Component.onCompleted: running = true 223 + } 224 + 225 + // Update timer 226 + Timer { 227 + interval: 2000 228 + running: true 229 + repeat: true 230 + onTriggered: { 231 + cpuProc.running = true 232 + memProc.running = true 233 + tempProc.running = true 234 + netProc.running = true 235 + volProc.running = true 236 + brightProc.running = true 237 + batProc.running = true 238 + batStatusProc.running = true 239 + powerProc.running = true 240 + } 241 + } 242 + 243 + RowLayout { 244 + anchors.fill: parent 245 + anchors.leftMargin: 12 246 + anchors.rightMargin: 12 247 + spacing: 0 248 + 249 + // Left section 250 + RowLayout { 251 + spacing: 0 252 + 253 + Rectangle { 254 + Layout.preferredHeight: 30 255 + Layout.preferredWidth: clockText.implicitWidth + 24 256 + radius: 15 257 + color: root.colSurface0 258 + 259 + Text { 260 + id: clockText 261 + anchors.centerIn: parent 262 + color: root.colBlue 263 + font { family: root.fontFamily; pixelSize: root.fontSize } 264 + text: Qt.formatDateTime(new Date(), "dd-MM-yyyy HH:mm") 265 + Timer { 266 + interval: 1000 267 + running: true 268 + repeat: true 269 + onTriggered: clockText.text = Qt.formatDateTime(new Date(), "dd-MM-yyyy HH:mm") 270 + } 271 + } 272 + } 273 + } 274 + 275 + Item { Layout.fillWidth: true } 276 + 277 + // Right section 278 + RowLayout { 279 + spacing: 8 280 + 281 + Rectangle { 282 + Layout.preferredHeight: 30 283 + Layout.preferredWidth: rightRow.implicitWidth + 24 284 + radius: 15 285 + color: root.colSurface0 286 + 287 + RowLayout { 288 + id: rightRow 289 + anchors.centerIn: parent 290 + spacing: 12 291 + 292 + // Volume 293 + Text { 294 + color: root.colMaroon 295 + font { family: root.fontFamily; pixelSize: root.fontSize } 296 + text: root.volume + "% " + (root.volumeMuted ? root.iconVolMute : (root.volume > 50 ? root.iconVolHigh : root.iconVolLow)) 297 + } 298 + 299 + Rectangle { width: 1; height: 16; color: root.colOverlay0 } 300 + 301 + // Network 302 + Text { 303 + color: root.colGreen 304 + font { family: root.fontFamily; pixelSize: root.fontSize } 305 + text: root.networkConnected ? root.networkName + " (" + root.networkStrength + "%) " + root.iconWifi : "Disconnected \u26a0" 306 + } 307 + 308 + Rectangle { width: 1; height: 16; color: root.colOverlay0 } 309 + 310 + // Power Profile 311 + Text { 312 + color: root.colText 313 + font { family: root.fontFamily; pixelSize: root.fontSize } 314 + text: { 315 + if (root.powerProfile === "performance") return root.iconBolt 316 + if (root.powerProfile === "balanced") return root.iconBalance 317 + if (root.powerProfile === "power-saver") return root.iconLeaf 318 + return root.iconBalance 319 + } 320 + } 321 + 322 + Rectangle { width: 1; height: 16; color: root.colOverlay0 } 323 + 324 + // CPU 325 + Text { 326 + color: root.colPeach 327 + font { family: root.fontFamily; pixelSize: root.fontSize } 328 + text: root.cpuUsage + "% " + root.iconCpu 329 + } 330 + 331 + Rectangle { width: 1; height: 16; color: root.colOverlay0 } 332 + 333 + // Memory 334 + Text { 335 + color: root.colMauve 336 + font { family: root.fontFamily; pixelSize: root.fontSize } 337 + text: root.memUsage + "% " + root.iconMem 338 + } 339 + 340 + Rectangle { width: 1; height: 16; color: root.colOverlay0 } 341 + 342 + // Temperature 343 + Text { 344 + color: root.colRed 345 + font { family: root.fontFamily; pixelSize: root.fontSize } 346 + text: { 347 + var icon = root.temperature >= 80 ? root.iconTempHigh : (root.temperature >= 50 ? root.iconTempMed : root.iconTempLow) 348 + return root.temperature + "\u00b0C " + icon 349 + } 350 + } 351 + 352 + Rectangle { width: 1; height: 16; color: root.colOverlay0 } 353 + 354 + // Backlight 355 + Text { 356 + color: root.colYellow 357 + font { family: root.fontFamily; pixelSize: root.fontSize } 358 + text: root.brightness + "% " + root.iconSun 359 + } 360 + 361 + Rectangle { width: 1; height: 16; color: root.colOverlay0 } 362 + 363 + // Battery 364 + Text { 365 + color: { 366 + if (root.batteryStatus === "Charging") return root.colGreen 367 + if (root.batteryPercent <= 15) return root.colRed 368 + if (root.batteryPercent <= 30) return root.colRed 369 + return root.colGreen 370 + } 371 + font { family: root.fontFamily; pixelSize: root.fontSize } 372 + text: { 373 + var icon 374 + if (root.batteryStatus === "Charging") { 375 + icon = root.iconBatCharge 376 + } else { 377 + var icons = [root.iconBatEmpty, root.iconBatQuarter, root.iconBatHalf, root.iconBatThreeQ, root.iconBatFull] 378 + var idx = Math.min(Math.floor(root.batteryPercent / 25), 4) 379 + icon = icons[idx] 380 + } 381 + return root.batteryPercent + "% " + icon 382 + } 383 + } 384 + 385 + Rectangle { width: 1; height: 16; color: root.colOverlay0 } 386 + 387 + // Power button 388 + Text { 389 + color: root.colRed 390 + font { family: root.fontFamily; pixelSize: root.fontSize } 391 + text: root.iconPower 392 + } 393 + } 394 + } 395 + } 396 + } 397 + } 398 + }