This repository has no description
0

Configure Feed

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

sway: split eww config into separate module

+535 -525
+1 -525
2configs/desktops/sway/default.nix
··· 8 8 9 9 term = "/run/current-system/sw/bin/alacritty"; 10 10 11 - # eww configuration 12 - ewwYuck = pkgs.writeText "eww.yuck" '' 13 - ; Variables with polling - set initial values to avoid empty string errors 14 - (defpoll time :interval "1s" :initial "00:00" "date '+%H:%M'") 15 - (defpoll date :interval "60s" :initial "0000-00-00" "date '+%Y-%m-%d'") 16 - (defpoll cpu :interval "2s" :initial '{"usage": 0, "load": "0.00", "freq": 0}' "${lib.getExe cpuScript}") 17 - (defpoll memory :interval "2s" :initial 0 "${lib.getExe memScript}") 18 - (defpoll temperature :interval "2s" :initial '{"temp": 0, "fan": 0}' "${lib.getExe tempScript}") 19 - (defpoll battery :interval "10s" :initial '{"capacity": "100", "icon": "󰁹", "watts": "0", "time_remaining": "--", "status": "Full"}' "${lib.getExe batteryScript}") 20 - (defpoll volume :interval "1s" :initial '{"level": "0", "icon": "󰕿"}' "${lib.getExe volumeScript}") 21 - (defpoll network :interval "5s" :initial "󰖪 ..." "${lib.getExe networkScript}") 22 - (defpoll brightness :interval "1s" :initial 100 "${lib.getExe brightnessScript}") 23 - (defpoll idle-inhibited :interval "0.5s" :initial "false" "${lib.getExe idleStatusScript}") 24 - (deflisten workspaces :initial '[]' "${lib.getExe workspacesScript}") 25 - 26 - ; Widget definitions 27 - (defwidget bar [] 28 - (centerbox :orientation "h" 29 - (left) 30 - (center) 31 - (right))) 32 - 33 - (defwidget left [] 34 - (box :class "left" :orientation "h" :space-evenly false :halign "start" 35 - (workspaces-widget))) 36 - 37 - (defwidget center [] 38 - (box :class "center" :orientation "h" :space-evenly false :halign "center" 39 - (label :text "''${time}"))) 40 - 41 - (defwidget right [] 42 - (box :class "right" :orientation "h" :space-evenly false :halign "end" :spacing 8 43 - (network-widget) 44 - (volume-widget) 45 - (graph-widget :value {cpu.usage} :icon "󰍛" :class "cpu" :unit "%" :tooltip "Load: ''${cpu.load} | ''${cpu.freq} MHz") 46 - (graph-widget :value memory :icon "󰘚" :class "memory" :unit "%") 47 - (graph-widget :value {temperature.temp} :icon "󰔏" :class "temp" :unit "°" :tooltip "Fan: ''${temperature.fan} RPM") 48 - (brightness-widget) 49 - (battery-widget) 50 - (idle-inhibitor) 51 - (label :class "date" :text "''${date}") 52 - (systray :class "systray" :icon-size 18 :spacing 4))) 53 - 54 - (defwidget workspaces-widget [] 55 - (box :class "workspaces" :orientation "h" :space-evenly false :spacing 0 56 - (for ws in workspaces 57 - (button :class {ws.urgent ? "workspace urgent" : (ws.focused ? "workspace focused" : "workspace")} 58 - :onclick "swaymsg workspace ''${ws.name}" 59 - {ws.name})))) 60 - 61 - (defwidget graph-widget [value icon class unit ?tooltip] 62 - (box :class "graph-module ''${class}" :orientation "h" :space-evenly false :spacing 4 63 - :tooltip {tooltip ?: ""} 64 - (graph :class "graph" 65 - :value value 66 - :thickness 3 67 - :time-range "60s" 68 - :min 0 69 - :max 100 70 - :dynamic true 71 - :line-style "round" 72 - :width 75) 73 - (label :text "''${value}''${unit} ''${icon}"))) 74 - 75 - (defwidget battery-widget [] 76 - (box :class "battery" :orientation "h" :space-evenly false 77 - :tooltip "''${battery.watts}W | ''${battery.time_remaining} ''${battery.status == 'Charging' ? 'to full' : 'remaining'}" 78 - (label :text "''${battery.capacity}% ''${battery.icon}"))) 79 - 80 - (defwidget volume-widget [] 81 - (box :class "volume" :orientation "h" :space-evenly false 82 - (label :text "''${volume.level}% ''${volume.icon}"))) 83 - 84 - (defwidget network-widget [] 85 - (box :class "network" :orientation "h" :space-evenly false 86 - (label :text "''${network}"))) 87 - 88 - (defwidget brightness-widget [] 89 - (button :class "brightness" :onclick "/run/current-system/sw/bin/switch-theme toggle" 90 - (label :text "''${brightness}% 󰃟"))) 91 - 92 - (defwidget idle-inhibitor [] 93 - (button :class {idle-inhibited == "true" ? "idle-inhibitor active" : "idle-inhibitor"} 94 - :onclick "${lib.getExe idleToggleScript}" 95 - {idle-inhibited == "true" ? "󰅶" : "󰾪"})) 96 - 97 - ; Window definition - screen name passed as argument 98 - (defwindow bar [screen] 99 - :monitor screen 100 - :geometry (geometry :x "0%" 101 - :y "0%" 102 - :width "100%" 103 - :height "36px" 104 - :anchor "top center") 105 - :stacking "fg" 106 - :exclusive true 107 - :focusable false 108 - (bar)) 109 - ''; 110 - 111 - ewwScss = pkgs.writeText "eww.scss" '' 112 - * { 113 - all: unset; 114 - font-family: "Iosevka Nerd Font", monospace; 115 - font-size: 14px; 116 - } 117 - 118 - window { 119 - background-color: #1a1b26; 120 - color: #c0caf5; 121 - } 122 - 123 - .left, .center, .right { 124 - margin: 4px 8px; 125 - } 126 - 127 - .workspaces { 128 - button { 129 - padding: 0 4px; 130 - margin: 0 1px; 131 - border-radius: 2px; 132 - min-width: 24px; 133 - background-color: #24283b; 134 - &.focused { 135 - background-color: #7aa2f7; 136 - color: #1a1b26; 137 - } 138 - &.urgent { 139 - background-color: #f7768e; 140 - color: #1a1b26; 141 - } 142 - &:hover { 143 - background-color: #414868; 144 - } 145 - } 146 - } 147 - 148 - .graph-module { 149 - padding: 0 8px; 150 - margin: 0 2px; 151 - border-radius: 4px; 152 - &.cpu { 153 - background-color: #9ece6a; 154 - color: #1a1b26; 155 - } 156 - &.memory { 157 - background-color: #7dcfff; 158 - color: #1a1b26; 159 - } 160 - &.temp { 161 - background-color: #ff9e64; 162 - color: #1a1b26; 163 - } 164 - } 165 - 166 - .graph { 167 - min-height: 20px; 168 - } 169 - 170 - .battery, .volume, .network, .date, .brightness { 171 - padding: 0 8px; 172 - margin: 0 2px; 173 - border-radius: 4px; 174 - background-color: #24283b; 175 - } 176 - 177 - .idle-inhibitor { 178 - padding: 0 8px; 179 - margin: 0 2px; 180 - border-radius: 4px; 181 - background-color: #24283b; 182 - &.active { 183 - background-color: #bb9af7; 184 - color: #1a1b26; 185 - } 186 - &:hover { 187 - background-color: #414868; 188 - } 189 - } 190 - 191 - .systray { 192 - padding: 0 8px; 193 - margin: 0 2px; 194 - } 195 - 196 - tooltip { 197 - background-color: #1a1b26; 198 - color: #c0caf5; 199 - border: 1px solid #414868; 200 - border-radius: 4px; 201 - padding: 4px 8px; 202 - } 203 - ''; 204 - 205 - cpuScript = pkgs.writeShellApplication { 206 - name = "eww-cpu"; 207 - runtimeInputs = [ pkgs.jq ]; 208 - text = '' 209 - STATE_FILE="/tmp/eww-cpu-state" 210 - read -r _cpu user nice system idle _rest < /proc/stat 211 - total=$((user + nice + system + idle)) 212 - 213 - if [ -f "$STATE_FILE" ]; then 214 - read -r prev_total prev_idle < "$STATE_FILE" 215 - diff_total=$((total - prev_total)) 216 - diff_idle=$((idle - prev_idle)) 217 - if [ "$diff_total" -gt 0 ]; then 218 - usage=$((100 * (diff_total - diff_idle) / diff_total)) 219 - else 220 - usage=0 221 - fi 222 - else 223 - usage=0 224 - fi 225 - echo "$total $idle" > "$STATE_FILE" 226 - 227 - # Get load average (1 min) 228 - load=$(cut -d' ' -f1 /proc/loadavg) 229 - 230 - # Get average CPU frequency in MHz 231 - freq=0 232 - count=0 233 - for f in /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq; do 234 - if [ -f "$f" ]; then 235 - val=$(cat "$f") 236 - freq=$((freq + val)) 237 - count=$((count + 1)) 238 - fi 239 - done 240 - if [ "$count" -gt 0 ]; then 241 - freq=$((freq / count / 1000)) 242 - fi 243 - 244 - jq -n --argjson usage "$usage" --arg load "$load" --argjson freq "$freq" \ 245 - '{usage: $usage, load: $load, freq: $freq}' 246 - ''; 247 - }; 248 - 249 - memScript = pkgs.writeShellApplication { 250 - name = "eww-mem"; 251 - runtimeInputs = [ pkgs.gawk ]; 252 - text = '' 253 - awk '/MemTotal/ {total=$2} /MemAvailable/ {available=$2} END {printf "%d", (total-available)*100/total}' /proc/meminfo 254 - ''; 255 - }; 256 - 257 - tempScript = pkgs.writeShellApplication { 258 - name = "eww-temp"; 259 - runtimeInputs = [ pkgs.jq ]; 260 - text = '' 261 - temp_raw=$(cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null || echo 0) 262 - temp=$((temp_raw / 1000)) 263 - 264 - # Find gpdfan hwmon for fan speed 265 - fan=0 266 - for hwmon in /sys/class/hwmon/hwmon*; do 267 - if [ "$(cat "$hwmon/name" 2>/dev/null)" = "gpdfan" ]; then 268 - fan=$(cat "$hwmon/fan1_input" 2>/dev/null || echo 0) 269 - break 270 - fi 271 - done 272 - 273 - jq -n --argjson temp "$temp" --argjson fan "$fan" '{temp: $temp, fan: $fan}' 274 - ''; 275 - }; 276 - 277 - batteryScript = pkgs.writeShellApplication { 278 - name = "eww-battery"; 279 - runtimeInputs = [ 280 - pkgs.jq 281 - pkgs.bc 282 - ]; 283 - text = '' 284 - # Find battery device dynamically 285 - bat_path="" 286 - for p in /sys/class/power_supply/BAT* /sys/class/power_supply/BATT*; do 287 - if [ -f "$p/capacity" ]; then 288 - bat_path="$p" 289 - break 290 - fi 291 - done 292 - 293 - if [ -z "$bat_path" ]; then 294 - echo '{"capacity": "N/A", "icon": "󰂃", "watts": "0", "time_remaining": "N/A"}' 295 - exit 0 296 - fi 297 - 298 - capacity=$(cat "$bat_path/capacity" 2>/dev/null || echo 100) 299 - status=$(cat "$bat_path/status" 2>/dev/null || echo "Full") 300 - power_now=$(cat "$bat_path/power_now" 2>/dev/null || echo 0) 301 - energy_now=$(cat "$bat_path/energy_now" 2>/dev/null || echo 0) 302 - energy_full=$(cat "$bat_path/energy_full" 2>/dev/null || echo 0) 303 - 304 - # Convert microwatts to watts 305 - watts=$(echo "scale=1; $power_now / 1000000" | bc) 306 - 307 - # Calculate time remaining 308 - if [ "$power_now" -gt 0 ]; then 309 - if [ "$status" = "Charging" ]; then 310 - # Time to full 311 - remaining_energy=$((energy_full - energy_now)) 312 - hours=$(echo "scale=2; $remaining_energy / $power_now" | bc) 313 - else 314 - # Time to empty 315 - hours=$(echo "scale=2; $energy_now / $power_now" | bc) 316 - fi 317 - # Convert to hours:minutes 318 - h=$(echo "$hours" | cut -d. -f1) 319 - m=$(echo "scale=0; ($hours - $h) * 60 / 1" | bc) 320 - time_remaining="''${h}h ''${m}m" 321 - else 322 - time_remaining="--" 323 - fi 324 - 325 - if [ "$status" = "Charging" ]; then 326 - icon="󰂄" 327 - elif [ "$status" = "Full" ]; then 328 - icon="󰁹" 329 - elif [ "$capacity" -gt 80 ]; then 330 - icon="󰂂" 331 - elif [ "$capacity" -gt 60 ]; then 332 - icon="󰂀" 333 - elif [ "$capacity" -gt 40 ]; then 334 - icon="󰁾" 335 - elif [ "$capacity" -gt 20 ]; then 336 - icon="󰁼" 337 - else 338 - icon="󰁺" 339 - fi 340 - jq -n --arg cap "$capacity" --arg icon "$icon" --arg watts "$watts" --arg time "$time_remaining" --arg status "$status" \ 341 - '{capacity: $cap, icon: $icon, watts: $watts, time_remaining: $time, status: $status}' 342 - ''; 343 - }; 344 - 345 - volumeScript = pkgs.writeShellApplication { 346 - name = "eww-volume"; 347 - runtimeInputs = [ 348 - pkgs.pulseaudio 349 - pkgs.jq 350 - pkgs.gnugrep 351 - ]; 352 - text = '' 353 - vol=$(pactl get-sink-volume @DEFAULT_SINK@ | grep -oP '\d+%' | head -1 | tr -d '%') 354 - muted=$(pactl get-sink-mute @DEFAULT_SINK@ | grep -oP '(yes|no)') 355 - if [ "$muted" = "yes" ]; then 356 - icon="󰖁" 357 - elif [ "$vol" -gt 50 ]; then 358 - icon="󰕾" 359 - elif [ "$vol" -gt 0 ]; then 360 - icon="󰖀" 361 - else 362 - icon="󰕿" 363 - fi 364 - jq -n --arg level "$vol" --arg icon "$icon" '{level: $level, icon: $icon}' 365 - ''; 366 - }; 367 - 368 - networkScript = pkgs.writeShellApplication { 369 - name = "eww-network"; 370 - runtimeInputs = [ pkgs.networkmanager ]; 371 - text = '' 372 - connection=$(nmcli -t -f NAME,TYPE,DEVICE connection show --active 2>/dev/null | head -1) 373 - if [ -z "$connection" ]; then 374 - echo "󰖪 Disconnected" 375 - else 376 - name=$(echo "$connection" | cut -d: -f1) 377 - type=$(echo "$connection" | cut -d: -f2) 378 - if [ "$type" = "802-11-wireless" ]; then 379 - echo "󰖩 $name" 380 - else 381 - echo "󰈀 $name" 382 - fi 383 - fi 384 - ''; 385 - }; 386 - 387 - workspacesScript = pkgs.writeShellApplication { 388 - name = "sway-workspaces"; 389 - runtimeInputs = [ 390 - pkgs.sway 391 - pkgs.jq 392 - ]; 393 - text = '' 394 - # Output initial state 395 - swaymsg -t get_workspaces | jq -c '[.[] | {name: .name, focused: .focused, urgent: .urgent}]' 396 - 397 - # Subscribe to workspace events and output updated state 398 - swaymsg -t subscribe -m '["workspace"]' | while read -r _; do 399 - swaymsg -t get_workspaces | jq -c '[.[] | {name: .name, focused: .focused, urgent: .urgent}]' 400 - done 401 - ''; 402 - }; 403 - 404 - brightnessScript = pkgs.writeShellApplication { 405 - name = "eww-brightness"; 406 - runtimeInputs = [ pkgs.brightnessctl ]; 407 - text = '' 408 - brightnessctl -m | cut -d, -f4 | tr -d '%' 409 - ''; 410 - }; 411 - 412 - idleToggleScript = pkgs.writeShellApplication { 413 - name = "eww-idle-toggle"; 414 - runtimeInputs = [ pkgs.systemd ]; 415 - text = '' 416 - if systemctl --user is-active --quiet sway-idle-inhibit.service; then 417 - systemctl --user stop sway-idle-inhibit.service 418 - else 419 - systemctl --user start sway-idle-inhibit.service 420 - fi 421 - ''; 422 - }; 423 - 424 - idleStatusScript = pkgs.writeShellApplication { 425 - name = "eww-idle-status"; 426 - runtimeInputs = [ pkgs.systemd ]; 427 - text = '' 428 - if systemctl --user is-active --quiet sway-idle-inhibit.service; then 429 - echo "true" 430 - else 431 - echo "false" 432 - fi 433 - ''; 434 - }; 435 - 436 - ewwConfigDir = pkgs.runCommand "eww-config" { } '' 437 - mkdir -p $out 438 - cp ${ewwYuck} $out/eww.yuck 439 - cp ${ewwScss} $out/eww.scss 440 - ''; 441 - 442 11 in 443 12 { 444 13 imports = [ 445 14 ../lib/wayland.nix 15 + ./eww.nix 446 16 ]; 447 17 programs.sway = { 448 18 enable = true; ··· 461 31 value = 1; 462 32 } 463 33 ]; 464 - 465 - # eww daemon 466 - systemd.user.services.eww = { 467 - description = "ElKowars Wacky Widgets daemon"; 468 - partOf = [ "sway-session.target" ]; 469 - wantedBy = [ "sway-session.target" ]; 470 - path = [ 471 - pkgs.bash 472 - pkgs.sway 473 - ]; 474 - environment.EWW_CONFIG = "${ewwConfigDir}"; 475 - serviceConfig = { 476 - Type = "simple"; 477 - ExecStart = "${lib.getExe pkgs.eww} daemon --config ${ewwConfigDir} --no-daemonize"; 478 - ExecStop = "${lib.getExe pkgs.eww} --config ${ewwConfigDir} kill"; 479 - Restart = "on-failure"; 480 - RestartSec = 1; 481 - }; 482 - }; 483 - 484 - # Idle inhibit service - toggled by eww button 485 - systemd.user.services.sway-idle-inhibit = { 486 - description = "Inhibit idle/sleep"; 487 - serviceConfig = { 488 - Type = "simple"; 489 - ExecStart = "${pkgs.systemd}/bin/systemd-inhibit --what=idle:sleep --who=eww --why='User requested' --mode=block sleep infinity"; 490 - }; 491 - }; 492 - 493 - # eww bar per output - template unit using output name (e.g. eww-bar@eDP-1.service) 494 - systemd.user.services."eww-bar@" = { 495 - description = "Eww bar on output %i"; 496 - partOf = [ "sway-session.target" ]; 497 - after = [ "eww.service" ]; 498 - requires = [ "eww.service" ]; 499 - serviceConfig = { 500 - Type = "oneshot"; 501 - RemainAfterExit = true; 502 - ExecStart = "${pkgs.eww}/bin/eww --config ${ewwConfigDir} open bar --id bar-%i --arg screen=%i"; 503 - ExecStop = "${pkgs.eww}/bin/eww --config ${ewwConfigDir} close bar-%i"; 504 - }; 505 - }; 506 - 507 - # Watches for output changes and starts/stops eww-bar@ instances 508 - systemd.user.services.eww-output-watcher = { 509 - description = "Watch for output changes and manage eww bars"; 510 - partOf = [ "sway-session.target" ]; 511 - wantedBy = [ "sway-session.target" ]; 512 - after = [ "eww.service" ]; 513 - requires = [ "eww.service" ]; 514 - serviceConfig = { 515 - Type = "simple"; 516 - ExecStart = lib.getExe ( 517 - pkgs.writeShellApplication { 518 - name = "eww-output-watcher"; 519 - runtimeInputs = [ 520 - pkgs.sway 521 - pkgs.jq 522 - pkgs.systemd 523 - pkgs.gawk 524 - pkgs.gnused 525 - pkgs.gnugrep 526 - ]; 527 - text = '' 528 - update_bars() { 529 - # Get current outputs 530 - outputs=$(swaymsg -t get_outputs | jq -r '.[].name') 531 - 532 - # Start bars for connected outputs 533 - for output in $outputs; do 534 - systemctl --user start "eww-bar@$output.service" || true 535 - done 536 - 537 - # Stop bars for disconnected outputs 538 - running=$(systemctl --user list-units --type=service --state=running --plain --no-legend 'eww-bar@*' | awk '{print $1}' | sed 's/eww-bar@\(.*\)\.service/\1/') 539 - for output in $running; do 540 - if ! echo "$outputs" | grep -qx "$output"; then 541 - systemctl --user stop "eww-bar@$output.service" || true 542 - fi 543 - done 544 - } 545 - 546 - update_bars 547 - swaymsg -t subscribe -m '["output"]' | while read -r _; do 548 - sleep 0.5 549 - update_bars 550 - done 551 - ''; 552 - } 553 - ); 554 - Restart = "on-failure"; 555 - RestartSec = 1; 556 - }; 557 - }; 558 34 559 35 systemd.user.services.sway-urgent-rumble = { 560 36 description = "Trigger rumble on urgent windows";
+534
2configs/desktops/sway/eww.nix
··· 1 + { 2 + pkgs, 3 + lib, 4 + ... 5 + }: 6 + let 7 + 8 + # eww configuration 9 + ewwYuck = pkgs.writeText "eww.yuck" '' 10 + ; Variables with polling - set initial values to avoid empty string errors 11 + (defpoll time :interval "1s" :initial "00:00" "date '+%H:%M'") 12 + (defpoll date :interval "60s" :initial "0000-00-00" "date '+%Y-%m-%d'") 13 + (defpoll cpu :interval "2s" :initial '{"usage": 0, "load": "0.00", "freq": 0}' "${lib.getExe cpuScript}") 14 + (defpoll memory :interval "2s" :initial 0 "${lib.getExe memScript}") 15 + (defpoll temperature :interval "2s" :initial '{"temp": 0, "fan": 0}' "${lib.getExe tempScript}") 16 + (defpoll battery :interval "10s" :initial '{"capacity": "100", "icon": "󰁹", "watts": "0", "time_remaining": "--", "status": "Full"}' "${lib.getExe batteryScript}") 17 + (defpoll volume :interval "1s" :initial '{"level": "0", "icon": "󰕿"}' "${lib.getExe volumeScript}") 18 + (defpoll network :interval "5s" :initial "󰖪 ..." "${lib.getExe networkScript}") 19 + (defpoll brightness :interval "1s" :initial 100 "${lib.getExe brightnessScript}") 20 + (defpoll idle-inhibited :interval "0.5s" :initial "false" "${lib.getExe idleStatusScript}") 21 + (deflisten workspaces :initial '[]' "${lib.getExe workspacesScript}") 22 + 23 + ; Widget definitions 24 + (defwidget bar [] 25 + (centerbox :orientation "h" 26 + (left) 27 + (center) 28 + (right))) 29 + 30 + (defwidget left [] 31 + (box :class "left" :orientation "h" :space-evenly false :halign "start" 32 + (workspaces-widget))) 33 + 34 + (defwidget center [] 35 + (box :class "center" :orientation "h" :space-evenly false :halign "center" 36 + (label :text "''${time}"))) 37 + 38 + (defwidget right [] 39 + (box :class "right" :orientation "h" :space-evenly false :halign "end" :spacing 8 40 + (network-widget) 41 + (volume-widget) 42 + (graph-widget :value {cpu.usage} :icon "󰍛" :class "cpu" :unit "%" :tooltip "Load: ''${cpu.load} | ''${cpu.freq} MHz") 43 + (graph-widget :value memory :icon "󰘚" :class "memory" :unit "%") 44 + (graph-widget :value {temperature.temp} :icon "󰔏" :class "temp" :unit "°" :tooltip "Fan: ''${temperature.fan} RPM") 45 + (brightness-widget) 46 + (battery-widget) 47 + (idle-inhibitor) 48 + (label :class "date" :text "''${date}") 49 + (systray :class "systray" :icon-size 18 :spacing 4))) 50 + 51 + (defwidget workspaces-widget [] 52 + (box :class "workspaces" :orientation "h" :space-evenly false :spacing 0 53 + (for ws in workspaces 54 + (button :class {ws.urgent ? "workspace urgent" : (ws.focused ? "workspace focused" : "workspace")} 55 + :onclick "swaymsg workspace ''${ws.name}" 56 + {ws.name})))) 57 + 58 + (defwidget graph-widget [value icon class unit ?tooltip] 59 + (box :class "graph-module ''${class}" :orientation "h" :space-evenly false :spacing 4 60 + :tooltip {tooltip ?: ""} 61 + (graph :class "graph" 62 + :value value 63 + :thickness 3 64 + :time-range "60s" 65 + :min 0 66 + :max 100 67 + :dynamic true 68 + :line-style "round" 69 + :width 75) 70 + (label :text "''${value}''${unit} ''${icon}"))) 71 + 72 + (defwidget battery-widget [] 73 + (box :class "battery" :orientation "h" :space-evenly false 74 + :tooltip "''${battery.watts}W | ''${battery.time_remaining} ''${battery.status == 'Charging' ? 'to full' : 'remaining'}" 75 + (label :text "''${battery.capacity}% ''${battery.icon}"))) 76 + 77 + (defwidget volume-widget [] 78 + (box :class "volume" :orientation "h" :space-evenly false 79 + (label :text "''${volume.level}% ''${volume.icon}"))) 80 + 81 + (defwidget network-widget [] 82 + (box :class "network" :orientation "h" :space-evenly false 83 + (label :text "''${network}"))) 84 + 85 + (defwidget brightness-widget [] 86 + (button :class "brightness" :onclick "/run/current-system/sw/bin/switch-theme toggle" 87 + (label :text "''${brightness}% 󰃟"))) 88 + 89 + (defwidget idle-inhibitor [] 90 + (button :class {idle-inhibited == "true" ? "idle-inhibitor active" : "idle-inhibitor"} 91 + :onclick "${lib.getExe idleToggleScript}" 92 + {idle-inhibited == "true" ? "󰅶" : "󰾪"})) 93 + 94 + ; Window definition - screen name passed as argument 95 + (defwindow bar [screen] 96 + :monitor screen 97 + :geometry (geometry :x "0%" 98 + :y "0%" 99 + :width "100%" 100 + :height "36px" 101 + :anchor "top center") 102 + :stacking "fg" 103 + :exclusive true 104 + :focusable false 105 + (bar)) 106 + ''; 107 + 108 + ewwScss = pkgs.writeText "eww.scss" '' 109 + * { 110 + all: unset; 111 + font-family: "Iosevka Nerd Font", monospace; 112 + font-size: 14px; 113 + } 114 + 115 + window { 116 + background-color: #1a1b26; 117 + color: #c0caf5; 118 + } 119 + 120 + .left, .center, .right { 121 + margin: 4px 8px; 122 + } 123 + 124 + .workspaces { 125 + button { 126 + padding: 0 4px; 127 + margin: 0 1px; 128 + border-radius: 2px; 129 + min-width: 24px; 130 + background-color: #24283b; 131 + &.focused { 132 + background-color: #7aa2f7; 133 + color: #1a1b26; 134 + } 135 + &.urgent { 136 + background-color: #f7768e; 137 + color: #1a1b26; 138 + } 139 + &:hover { 140 + background-color: #414868; 141 + } 142 + } 143 + } 144 + 145 + .graph-module { 146 + padding: 0 8px; 147 + margin: 0 2px; 148 + border-radius: 4px; 149 + &.cpu { 150 + background-color: #9ece6a; 151 + color: #1a1b26; 152 + } 153 + &.memory { 154 + background-color: #7dcfff; 155 + color: #1a1b26; 156 + } 157 + &.temp { 158 + background-color: #ff9e64; 159 + color: #1a1b26; 160 + } 161 + } 162 + 163 + .graph { 164 + min-height: 20px; 165 + } 166 + 167 + .battery, .volume, .network, .date, .brightness { 168 + padding: 0 8px; 169 + margin: 0 2px; 170 + border-radius: 4px; 171 + background-color: #24283b; 172 + } 173 + 174 + .idle-inhibitor { 175 + padding: 0 8px; 176 + margin: 0 2px; 177 + border-radius: 4px; 178 + background-color: #24283b; 179 + &.active { 180 + background-color: #bb9af7; 181 + color: #1a1b26; 182 + } 183 + &:hover { 184 + background-color: #414868; 185 + } 186 + } 187 + 188 + .systray { 189 + padding: 0 8px; 190 + margin: 0 2px; 191 + } 192 + 193 + tooltip { 194 + background-color: #1a1b26; 195 + color: #c0caf5; 196 + border: 1px solid #414868; 197 + border-radius: 4px; 198 + padding: 4px 8px; 199 + } 200 + ''; 201 + 202 + cpuScript = pkgs.writeShellApplication { 203 + name = "eww-cpu"; 204 + runtimeInputs = [ pkgs.jq ]; 205 + text = '' 206 + STATE_FILE="/tmp/eww-cpu-state" 207 + read -r _cpu user nice system idle _rest < /proc/stat 208 + total=$((user + nice + system + idle)) 209 + 210 + if [ -f "$STATE_FILE" ]; then 211 + read -r prev_total prev_idle < "$STATE_FILE" 212 + diff_total=$((total - prev_total)) 213 + diff_idle=$((idle - prev_idle)) 214 + if [ "$diff_total" -gt 0 ]; then 215 + usage=$((100 * (diff_total - diff_idle) / diff_total)) 216 + else 217 + usage=0 218 + fi 219 + else 220 + usage=0 221 + fi 222 + echo "$total $idle" > "$STATE_FILE" 223 + 224 + # Get load average (1 min) 225 + load=$(cut -d' ' -f1 /proc/loadavg) 226 + 227 + # Get average CPU frequency in MHz 228 + freq=0 229 + count=0 230 + for f in /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq; do 231 + if [ -f "$f" ]; then 232 + val=$(cat "$f") 233 + freq=$((freq + val)) 234 + count=$((count + 1)) 235 + fi 236 + done 237 + if [ "$count" -gt 0 ]; then 238 + freq=$((freq / count / 1000)) 239 + fi 240 + 241 + jq -n --argjson usage "$usage" --arg load "$load" --argjson freq "$freq" \ 242 + '{usage: $usage, load: $load, freq: $freq}' 243 + ''; 244 + }; 245 + 246 + memScript = pkgs.writeShellApplication { 247 + name = "eww-mem"; 248 + runtimeInputs = [ pkgs.gawk ]; 249 + text = '' 250 + awk '/MemTotal/ {total=$2} /MemAvailable/ {available=$2} END {printf "%d", (total-available)*100/total}' /proc/meminfo 251 + ''; 252 + }; 253 + 254 + tempScript = pkgs.writeShellApplication { 255 + name = "eww-temp"; 256 + runtimeInputs = [ pkgs.jq ]; 257 + text = '' 258 + temp_raw=$(cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null || echo 0) 259 + temp=$((temp_raw / 1000)) 260 + 261 + # Find gpdfan hwmon for fan speed 262 + fan=0 263 + for hwmon in /sys/class/hwmon/hwmon*; do 264 + if [ "$(cat "$hwmon/name" 2>/dev/null)" = "gpdfan" ]; then 265 + fan=$(cat "$hwmon/fan1_input" 2>/dev/null || echo 0) 266 + break 267 + fi 268 + done 269 + 270 + jq -n --argjson temp "$temp" --argjson fan "$fan" '{temp: $temp, fan: $fan}' 271 + ''; 272 + }; 273 + 274 + batteryScript = pkgs.writeShellApplication { 275 + name = "eww-battery"; 276 + runtimeInputs = [ 277 + pkgs.jq 278 + pkgs.bc 279 + ]; 280 + text = '' 281 + # Find battery device dynamically 282 + bat_path="" 283 + for p in /sys/class/power_supply/BAT* /sys/class/power_supply/BATT*; do 284 + if [ -f "$p/capacity" ]; then 285 + bat_path="$p" 286 + break 287 + fi 288 + done 289 + 290 + if [ -z "$bat_path" ]; then 291 + echo '{"capacity": "N/A", "icon": "󰂃", "watts": "0", "time_remaining": "N/A"}' 292 + exit 0 293 + fi 294 + 295 + capacity=$(cat "$bat_path/capacity" 2>/dev/null || echo 100) 296 + status=$(cat "$bat_path/status" 2>/dev/null || echo "Full") 297 + power_now=$(cat "$bat_path/power_now" 2>/dev/null || echo 0) 298 + energy_now=$(cat "$bat_path/energy_now" 2>/dev/null || echo 0) 299 + energy_full=$(cat "$bat_path/energy_full" 2>/dev/null || echo 0) 300 + 301 + # Convert microwatts to watts 302 + watts=$(echo "scale=1; $power_now / 1000000" | bc) 303 + 304 + # Calculate time remaining 305 + if [ "$power_now" -gt 0 ]; then 306 + if [ "$status" = "Charging" ]; then 307 + # Time to full 308 + remaining_energy=$((energy_full - energy_now)) 309 + hours=$(echo "scale=2; $remaining_energy / $power_now" | bc) 310 + else 311 + # Time to empty 312 + hours=$(echo "scale=2; $energy_now / $power_now" | bc) 313 + fi 314 + # Convert to hours:minutes 315 + h=$(echo "$hours" | cut -d. -f1) 316 + m=$(echo "scale=0; ($hours - $h) * 60 / 1" | bc) 317 + time_remaining="''${h}h ''${m}m" 318 + else 319 + time_remaining="--" 320 + fi 321 + 322 + if [ "$status" = "Charging" ]; then 323 + icon="󰂄" 324 + elif [ "$status" = "Full" ]; then 325 + icon="󰁹" 326 + elif [ "$capacity" -gt 80 ]; then 327 + icon="󰂂" 328 + elif [ "$capacity" -gt 60 ]; then 329 + icon="󰂀" 330 + elif [ "$capacity" -gt 40 ]; then 331 + icon="󰁾" 332 + elif [ "$capacity" -gt 20 ]; then 333 + icon="󰁼" 334 + else 335 + icon="󰁺" 336 + fi 337 + jq -n --arg cap "$capacity" --arg icon "$icon" --arg watts "$watts" --arg time "$time_remaining" --arg status "$status" \ 338 + '{capacity: $cap, icon: $icon, watts: $watts, time_remaining: $time, status: $status}' 339 + ''; 340 + }; 341 + 342 + volumeScript = pkgs.writeShellApplication { 343 + name = "eww-volume"; 344 + runtimeInputs = [ 345 + pkgs.pulseaudio 346 + pkgs.jq 347 + pkgs.gnugrep 348 + ]; 349 + text = '' 350 + vol=$(pactl get-sink-volume @DEFAULT_SINK@ | grep -oP '\d+%' | head -1 | tr -d '%') 351 + muted=$(pactl get-sink-mute @DEFAULT_SINK@ | grep -oP '(yes|no)') 352 + if [ "$muted" = "yes" ]; then 353 + icon="󰖁" 354 + elif [ "$vol" -gt 50 ]; then 355 + icon="󰕾" 356 + elif [ "$vol" -gt 0 ]; then 357 + icon="󰖀" 358 + else 359 + icon="󰕿" 360 + fi 361 + jq -n --arg level "$vol" --arg icon "$icon" '{level: $level, icon: $icon}' 362 + ''; 363 + }; 364 + 365 + networkScript = pkgs.writeShellApplication { 366 + name = "eww-network"; 367 + runtimeInputs = [ pkgs.networkmanager ]; 368 + text = '' 369 + connection=$(nmcli -t -f NAME,TYPE,DEVICE connection show --active 2>/dev/null | head -1) 370 + if [ -z "$connection" ]; then 371 + echo "󰖪 Disconnected" 372 + else 373 + name=$(echo "$connection" | cut -d: -f1) 374 + type=$(echo "$connection" | cut -d: -f2) 375 + if [ "$type" = "802-11-wireless" ]; then 376 + echo "󰖩 $name" 377 + else 378 + echo "󰈀 $name" 379 + fi 380 + fi 381 + ''; 382 + }; 383 + 384 + workspacesScript = pkgs.writeShellApplication { 385 + name = "sway-workspaces"; 386 + runtimeInputs = [ 387 + pkgs.sway 388 + pkgs.jq 389 + ]; 390 + text = '' 391 + # Output initial state 392 + swaymsg -t get_workspaces | jq -c '[.[] | {name: .name, focused: .focused, urgent: .urgent}]' 393 + 394 + # Subscribe to workspace events and output updated state 395 + swaymsg -t subscribe -m '["workspace"]' | while read -r _; do 396 + swaymsg -t get_workspaces | jq -c '[.[] | {name: .name, focused: .focused, urgent: .urgent}]' 397 + done 398 + ''; 399 + }; 400 + 401 + brightnessScript = pkgs.writeShellApplication { 402 + name = "eww-brightness"; 403 + runtimeInputs = [ pkgs.brightnessctl ]; 404 + text = '' 405 + brightnessctl -m | cut -d, -f4 | tr -d '%' 406 + ''; 407 + }; 408 + 409 + idleToggleScript = pkgs.writeShellApplication { 410 + name = "eww-idle-toggle"; 411 + runtimeInputs = [ pkgs.systemd ]; 412 + text = '' 413 + if systemctl --user is-active --quiet sway-idle-inhibit.service; then 414 + systemctl --user stop sway-idle-inhibit.service 415 + else 416 + systemctl --user start sway-idle-inhibit.service 417 + fi 418 + ''; 419 + }; 420 + 421 + idleStatusScript = pkgs.writeShellApplication { 422 + name = "eww-idle-status"; 423 + runtimeInputs = [ pkgs.systemd ]; 424 + text = '' 425 + if systemctl --user is-active --quiet sway-idle-inhibit.service; then 426 + echo "true" 427 + else 428 + echo "false" 429 + fi 430 + ''; 431 + }; 432 + 433 + ewwConfigDir = pkgs.runCommand "eww-config" { } '' 434 + mkdir -p $out 435 + cp ${ewwYuck} $out/eww.yuck 436 + cp ${ewwScss} $out/eww.scss 437 + ''; 438 + 439 + in 440 + { 441 + # eww daemon 442 + systemd.user.services.eww = { 443 + description = "ElKowars Wacky Widgets daemon"; 444 + partOf = [ "sway-session.target" ]; 445 + wantedBy = [ "sway-session.target" ]; 446 + path = [ 447 + pkgs.bash 448 + pkgs.sway 449 + ]; 450 + environment.EWW_CONFIG = "${ewwConfigDir}"; 451 + serviceConfig = { 452 + Type = "simple"; 453 + ExecStart = "${lib.getExe pkgs.eww} daemon --config ${ewwConfigDir} --no-daemonize"; 454 + ExecStop = "${lib.getExe pkgs.eww} --config ${ewwConfigDir} kill"; 455 + Restart = "on-failure"; 456 + RestartSec = 1; 457 + }; 458 + }; 459 + 460 + # Idle inhibit service - toggled by eww button 461 + systemd.user.services.sway-idle-inhibit = { 462 + description = "Inhibit idle/sleep"; 463 + serviceConfig = { 464 + Type = "simple"; 465 + ExecStart = "${pkgs.systemd}/bin/systemd-inhibit --what=idle:sleep --who=eww --why='User requested' --mode=block sleep infinity"; 466 + }; 467 + }; 468 + 469 + # eww bar per output - template unit using output name (e.g. eww-bar@eDP-1.service) 470 + systemd.user.services."eww-bar@" = { 471 + description = "Eww bar on output %i"; 472 + partOf = [ "sway-session.target" ]; 473 + after = [ "eww.service" ]; 474 + requires = [ "eww.service" ]; 475 + serviceConfig = { 476 + Type = "oneshot"; 477 + RemainAfterExit = true; 478 + ExecStart = "${pkgs.eww}/bin/eww --config ${ewwConfigDir} open bar --id bar-%i --arg screen=%i"; 479 + ExecStop = "${pkgs.eww}/bin/eww --config ${ewwConfigDir} close bar-%i"; 480 + }; 481 + }; 482 + 483 + # Watches for output changes and starts/stops eww-bar@ instances 484 + systemd.user.services.eww-output-watcher = { 485 + description = "Watch for output changes and manage eww bars"; 486 + partOf = [ "sway-session.target" ]; 487 + wantedBy = [ "sway-session.target" ]; 488 + after = [ "eww.service" ]; 489 + requires = [ "eww.service" ]; 490 + serviceConfig = { 491 + Type = "simple"; 492 + ExecStart = lib.getExe ( 493 + pkgs.writeShellApplication { 494 + name = "eww-output-watcher"; 495 + runtimeInputs = [ 496 + pkgs.sway 497 + pkgs.jq 498 + pkgs.systemd 499 + pkgs.gawk 500 + pkgs.gnused 501 + pkgs.gnugrep 502 + ]; 503 + text = '' 504 + update_bars() { 505 + # Get current outputs 506 + outputs=$(swaymsg -t get_outputs | jq -r '.[].name') 507 + 508 + # Start bars for connected outputs 509 + for output in $outputs; do 510 + systemctl --user start "eww-bar@$output.service" || true 511 + done 512 + 513 + # Stop bars for disconnected outputs 514 + running=$(systemctl --user list-units --type=service --state=running --plain --no-legend 'eww-bar@*' | awk '{print $1}' | sed 's/eww-bar@\(.*\)\.service/\1/') 515 + for output in $running; do 516 + if ! echo "$outputs" | grep -qx "$output"; then 517 + systemctl --user stop "eww-bar@$output.service" || true 518 + fi 519 + done 520 + } 521 + 522 + update_bars 523 + swaymsg -t subscribe -m '["output"]' | while read -r _; do 524 + sleep 0.5 525 + update_bars 526 + done 527 + ''; 528 + } 529 + ); 530 + Restart = "on-failure"; 531 + RestartSec = 1; 532 + }; 533 + }; 534 + }