This repository has no description
0

Configure Feed

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

1{ 2 pkgs, 3 lib, 4 config, 5 self, 6 ... 7}: 8let 9 10 # Reference to goto-workspace (defined in default.nix, available at /run/current-system/sw/bin/) 11 gotoWorkspace = "/run/current-system/sw/bin/goto-workspace"; 12 13 # eww configuration 14 ewwYuck = pkgs.writeText "eww.yuck" '' 15 ; Magic variables (built-in, no polling needed) 16 ; EWW_TIME provides current time 17 ; EWW_CPU provides {cores: [...], avg: X} 18 ; EWW_RAM provides {used_mem_perc: X, available_mem: Y, total_mem: Z, ...} 19 20 ; Custom variables with polling 21 (defpoll cpu-freq :interval "2s" :initial 0 "${lib.getExe cpuFreqScript}") 22 (defpoll cpu-load :interval "2s" :initial "0.00" "cut -d' ' -f1 /proc/loadavg") 23 (defpoll temperature :interval "2s" :initial '{"temp": 0, "fan": 0}' "${lib.getExe tempScript}") 24 (defpoll power :interval "2s" :initial '{"watts": 0, "charging": false}' "${lib.getExe powerScript}") 25 (defpoll power-profile :interval "2s" :initial '{"name": "unknown", "tdp": 0}' "${lib.getExe powerProfileStatusScript}") 26 (defpoll battery :interval "10s" :initial '{"capacity": "100", "icon": "󰁹", "watts": "0", "time_remaining": "--", "status": "Full"}' "${lib.getExe batteryScript}") 27 (defpoll volume :interval "1s" :initial '{"level": "0", "icon": "󰕿"}' "${lib.getExe volumeScript}") 28 (defpoll network :interval "5s" :initial "󰖪 ..." "${lib.getExe networkScript}") 29 (defpoll brightness :interval "1s" :initial 100 "${lib.getExe brightnessScript}") 30 (defpoll idle-inhibited :interval "0.5s" :initial "false" "${lib.getExe idleStatusScript}") 31 (deflisten workspaces :initial '[]' "${lib.getExe workspacesScript}") 32 33 ; Widget definitions 34 (defwidget bar [] 35 (centerbox :orientation "h" 36 (left) 37 (center) 38 (right))) 39 40 (defwidget left [] 41 (box :class "left" :orientation "h" :space-evenly false :halign "start" 42 (workspaces-widget))) 43 44 (defwidget center [] 45 (box :class "center" :orientation "h" :space-evenly false :halign "center" 46 (label :text {formattime(EWW_TIME, "%H:%M")}))) 47 48 (defwidget right [] 49 (box :class "right" :orientation "h" :space-evenly false :halign "end" :spacing 8 50 (network-widget) 51 (volume-widget) 52 (graph-widget :value {EWW_CPU.avg} :icon "󰍛" :class "cpu" :unit "%" :tooltip "Load: ''${cpu-load} | ''${cpu-freq} MHz") 53 (graph-widget :value {EWW_RAM.used_mem_perc} :icon "󰘚" :class "memory" :unit "%") 54 (graph-widget :value {temperature.temp} :icon "󰔏" :class "temp" :unit "°" :tooltip "Fan: ''${temperature.fan} RPM") 55 (power-graph-widget) 56 (brightness-widget) 57 (battery-widget) 58 (idle-inhibitor) 59 (label :class "date" :text {formattime(EWW_TIME, "%Y-%m-%d")}) 60 (systray :class "systray" :icon-size 18 :spacing 4))) 61 62 (defwidget workspaces-widget [] 63 (box :class "workspaces" :orientation "h" :space-evenly false :spacing 0 64 (for ws in workspaces 65 (button :class {ws.urgent ? "workspace urgent" : (ws.focused ? "workspace focused" : "workspace")} 66 :onclick "${gotoWorkspace} ''${ws.name}" 67 {ws.name})))) 68 69 (defwidget graph-widget [value icon class unit ?tooltip] 70 (box :class "graph-module ''${class}" :orientation "h" :space-evenly false :spacing 4 71 :tooltip {tooltip ?: ""} 72 (graph :class "graph" 73 :value value 74 :thickness 3 75 :time-range "60s" 76 :min 0 77 :max 100 78 :dynamic true 79 :line-style "round" 80 :width 75) 81 (label :text "''${round(value, 0)}''${unit} ''${icon}"))) 82 83 (defwidget battery-widget [] 84 (box :class "battery" :orientation "h" :space-evenly false 85 :tooltip "''${battery.watts}W | ''${battery.time_remaining} ''${battery.status == 'Charging' ? 'to full' : 'remaining'}" 86 (label :text "''${battery.capacity}% ''${battery.icon}"))) 87 88 (defwidget power-graph-widget [] 89 (eventbox :onclick "${lib.getExe powerProfileCycleScript}" 90 (box :class {power.charging ? "graph-module power charging" : "graph-module power"} :orientation "h" :space-evenly false :spacing 4 91 :tooltip "TDP: ''${power-profile.tdp}W (''${power-profile.name}) | ''${battery.time_remaining} ''${battery.status == 'Charging' ? 'to full' : 'remaining'}" 92 (graph :class "graph" 93 :value {power.watts} 94 :thickness 3 95 :time-range "60s" 96 :min 0 97 :max 45 98 :dynamic false 99 :line-style "round" 100 :width 75) 101 (label :text "''${round(power.watts, 1)}W ''${power.charging ? '󰂄' : '󰁹'}")))) 102 103 (defwidget volume-widget [] 104 (box :class "volume" :orientation "h" :space-evenly false 105 (label :text "''${volume.level}% ''${volume.icon}"))) 106 107 (defwidget network-widget [] 108 (box :class "network" :orientation "h" :space-evenly false 109 (label :text "''${network}"))) 110 111 (defwidget brightness-widget [] 112 (button :class "brightness" :onclick "/run/current-system/sw/bin/switch-theme toggle" 113 (label :text "''${brightness}% 󰃟"))) 114 115 (defwidget idle-inhibitor [] 116 (button :class {idle-inhibited == "true" ? "idle-inhibitor active" : "idle-inhibitor"} 117 :onclick "${lib.getExe idleToggleScript}" 118 {idle-inhibited == "true" ? "󰅶" : "󰾪"})) 119 120 ; Window definition - screen name passed as argument 121 (defwindow bar [screen] 122 :monitor screen 123 :geometry (geometry :x "0%" 124 :y "0%" 125 :width "100%" 126 :height "36px" 127 :anchor "top center") 128 :stacking "fg" 129 :exclusive true 130 :focusable false 131 (bar)) 132 ''; 133 134 ewwScss = pkgs.writeText "eww.scss" '' 135 * { 136 all: unset; 137 font-family: "Iosevka Nerd Font", monospace; 138 font-size: 14px; 139 } 140 141 window { 142 background-color: #1a1b26; 143 color: #c0caf5; 144 } 145 146 .left, .center, .right { 147 margin: 4px 8px; 148 } 149 150 .workspaces { 151 button { 152 padding: 0 4px; 153 margin: 0 1px; 154 border-radius: 2px; 155 min-width: 24px; 156 background-color: #24283b; 157 &.focused { 158 background-color: #7aa2f7; 159 color: #1a1b26; 160 } 161 &.urgent { 162 background-color: #f7768e; 163 color: #1a1b26; 164 } 165 &:hover { 166 background-color: #414868; 167 } 168 } 169 } 170 171 .graph-module { 172 padding: 0 8px; 173 margin: 0 2px; 174 border-radius: 4px; 175 &.cpu { 176 background-color: #9ece6a; 177 color: #1a1b26; 178 } 179 &.memory { 180 background-color: #7dcfff; 181 color: #1a1b26; 182 } 183 &.temp { 184 background-color: #ff9e64; 185 color: #1a1b26; 186 } 187 &.power { 188 background-color: #f7768e; 189 color: #1a1b26; 190 &.charging { 191 background-color: #9ece6a; 192 } 193 } 194 } 195 196 .graph { 197 min-height: 20px; 198 } 199 200 .battery, .volume, .network, .date, .brightness { 201 padding: 0 8px; 202 margin: 0 2px; 203 border-radius: 4px; 204 background-color: #24283b; 205 } 206 207 .idle-inhibitor { 208 padding: 0 8px; 209 margin: 0 2px; 210 border-radius: 4px; 211 background-color: #24283b; 212 &.active { 213 background-color: #bb9af7; 214 color: #1a1b26; 215 } 216 &:hover { 217 background-color: #414868; 218 } 219 } 220 221 .systray { 222 padding: 0 8px; 223 margin: 0 2px; 224 } 225 226 tooltip { 227 background-color: #1a1b26; 228 color: #c0caf5; 229 border: 1px solid #414868; 230 border-radius: 4px; 231 padding: 4px 8px; 232 } 233 ''; 234 235 cpuFreqScript = pkgs.writeShellApplication { 236 name = "eww-cpu-freq"; 237 runtimeInputs = [ ]; 238 text = '' 239 # Get average CPU frequency in MHz 240 freq=0 241 count=0 242 for f in /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq; do 243 if [ -f "$f" ]; then 244 val=$(cat "$f") 245 freq=$((freq + val)) 246 count=$((count + 1)) 247 fi 248 done 249 if [ "$count" -gt 0 ]; then 250 freq=$((freq / count / 1000)) 251 fi 252 echo "$freq" 253 ''; 254 }; 255 256 tempScript = pkgs.writeShellApplication { 257 name = "eww-temp"; 258 runtimeInputs = [ pkgs.jq ]; 259 text = '' 260 temp_raw=$(cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null || echo 0) 261 temp=$((temp_raw / 1000)) 262 263 # Find gpdfan hwmon for fan speed 264 fan=0 265 for hwmon in /sys/class/hwmon/hwmon*; do 266 if [ "$(cat "$hwmon/name" 2>/dev/null)" = "gpdfan" ]; then 267 fan=$(cat "$hwmon/fan1_input" 2>/dev/null || echo 0) 268 break 269 fi 270 done 271 272 jq -n --argjson temp "$temp" --argjson fan "$fan" '{temp: $temp, fan: $fan}' 273 ''; 274 }; 275 276 powerScript = pkgs.writeShellApplication { 277 name = "eww-power"; 278 runtimeInputs = [ 279 pkgs.jq 280 pkgs.bc 281 ]; 282 text = '' 283 # Use RAPL for actual system power consumption (works on Intel and AMD Zen) 284 # Falls back to battery power_now if RAPL unavailable 285 286 state_file="/tmp/eww-power-state" 287 rapl_path="/sys/class/powercap/intel-rapl:0/energy_uj" 288 289 # Check charging status 290 charging="false" 291 for p in /sys/class/power_supply/BAT* /sys/class/power_supply/BATT*; do 292 if [ -f "$p/status" ]; then 293 status=$(cat "$p/status" 2>/dev/null || echo "Unknown") 294 if [ "$status" = "Charging" ]; then 295 charging="true" 296 fi 297 break 298 fi 299 done 300 301 # Try RAPL first (gives actual system power regardless of charging state) 302 if [ -r "$rapl_path" ]; then 303 current_energy=$(cat "$rapl_path") 304 current_time=$(date +%s%N) 305 306 if [ -f "$state_file" ]; then 307 read -r prev_energy prev_time < "$state_file" 308 309 # Calculate power from energy difference 310 energy_diff=$((current_energy - prev_energy)) 311 time_diff=$((current_time - prev_time)) 312 313 # Handle counter wrap-around 314 if [ "$energy_diff" -lt 0 ]; then 315 max_energy=$(cat /sys/class/powercap/intel-rapl:0/max_energy_range_uj 2>/dev/null || echo 0) 316 energy_diff=$((energy_diff + max_energy)) 317 fi 318 319 if [ "$time_diff" -gt 0 ]; then 320 # energy_diff is in microjoules, time_diff is in nanoseconds 321 # watts = microjoules / (nanoseconds / 1e9) / 1e6 = microjoules * 1000 / nanoseconds 322 watts=$(echo "scale=1; $energy_diff * 1000 / $time_diff" | bc) 323 else 324 watts="0" 325 fi 326 else 327 watts="0" 328 fi 329 330 # Save current state 331 echo "$current_energy $current_time" > "$state_file" 332 333 jq -n --argjson watts "$watts" --argjson charging "$charging" '{watts: $watts, charging: $charging}' 334 exit 0 335 fi 336 337 # Fallback: use battery power_now (only accurate when discharging) 338 bat_path="" 339 for p in /sys/class/power_supply/BAT* /sys/class/power_supply/BATT*; do 340 if [ -f "$p/power_now" ]; then 341 bat_path="$p" 342 break 343 fi 344 done 345 346 if [ -z "$bat_path" ]; then 347 echo '{"watts": 0, "charging": false}' 348 exit 0 349 fi 350 351 power_now=$(cat "$bat_path/power_now" 2>/dev/null || echo 0) 352 watts=$(echo "scale=1; $power_now / 1000000" | bc) 353 354 jq -n --argjson watts "$watts" --argjson charging "$charging" '{watts: $watts, charging: $charging}' 355 ''; 356 }; 357 358 batteryScript = pkgs.writeShellApplication { 359 name = "eww-battery"; 360 runtimeInputs = [ 361 pkgs.jq 362 pkgs.bc 363 ]; 364 text = '' 365 # Find battery device dynamically 366 bat_path="" 367 for p in /sys/class/power_supply/BAT* /sys/class/power_supply/BATT*; do 368 if [ -f "$p/capacity" ]; then 369 bat_path="$p" 370 break 371 fi 372 done 373 374 if [ -z "$bat_path" ]; then 375 echo '{"capacity": "N/A", "icon": "󰂃", "watts": "0", "time_remaining": "N/A"}' 376 exit 0 377 fi 378 379 capacity=$(cat "$bat_path/capacity" 2>/dev/null || echo 100) 380 status=$(cat "$bat_path/status" 2>/dev/null || echo "Full") 381 power_now=$(cat "$bat_path/power_now" 2>/dev/null || echo 0) 382 energy_now=$(cat "$bat_path/energy_now" 2>/dev/null || echo 0) 383 energy_full=$(cat "$bat_path/energy_full" 2>/dev/null || echo 0) 384 385 # Convert microwatts to watts 386 watts=$(echo "scale=1; $power_now / 1000000" | bc) 387 388 # Calculate time remaining 389 if [ "$power_now" -gt 0 ]; then 390 if [ "$status" = "Charging" ]; then 391 # Time to full 392 remaining_energy=$((energy_full - energy_now)) 393 hours=$(echo "scale=2; $remaining_energy / $power_now" | bc) 394 else 395 # Time to empty 396 hours=$(echo "scale=2; $energy_now / $power_now" | bc) 397 fi 398 # Convert to hours:minutes 399 # Note: bc outputs ".76" for values < 1, so cut returns empty string 400 h=$(echo "$hours" | cut -d. -f1) 401 h=''${h:-0} # Default to 0 if empty (i.e., less than 1 hour) 402 m=$(echo "scale=0; ($hours - $h) * 60 / 1" | bc) 403 time_remaining="''${h}h ''${m}m" 404 else 405 time_remaining="--" 406 fi 407 408 if [ "$status" = "Charging" ]; then 409 icon="󰂄" 410 elif [ "$status" = "Full" ]; then 411 icon="󰁹" 412 elif [ "$capacity" -gt 80 ]; then 413 icon="󰂂" 414 elif [ "$capacity" -gt 60 ]; then 415 icon="󰂀" 416 elif [ "$capacity" -gt 40 ]; then 417 icon="󰁾" 418 elif [ "$capacity" -gt 20 ]; then 419 icon="󰁼" 420 else 421 icon="󰁺" 422 fi 423 jq -n --arg cap "$capacity" --arg icon "$icon" --arg watts "$watts" --arg time "$time_remaining" --arg status "$status" \ 424 '{capacity: $cap, icon: $icon, watts: $watts, time_remaining: $time, status: $status}' 425 ''; 426 }; 427 428 volumeScript = pkgs.writeShellApplication { 429 name = "eww-volume"; 430 runtimeInputs = [ 431 pkgs.pulseaudio 432 pkgs.jq 433 pkgs.gnugrep 434 ]; 435 text = '' 436 vol=$(pactl get-sink-volume @DEFAULT_SINK@ | grep -oP '\d+%' | head -1 | tr -d '%') 437 muted=$(pactl get-sink-mute @DEFAULT_SINK@ | grep -oP '(yes|no)') 438 if [ "$muted" = "yes" ]; then 439 icon="󰖁" 440 elif [ "$vol" -gt 50 ]; then 441 icon="󰕾" 442 elif [ "$vol" -gt 0 ]; then 443 icon="󰖀" 444 else 445 icon="󰕿" 446 fi 447 jq -n --arg level "$vol" --arg icon "$icon" '{level: $level, icon: $icon}' 448 ''; 449 }; 450 451 networkScript = pkgs.writeShellApplication { 452 name = "eww-network"; 453 runtimeInputs = [ pkgs.networkmanager ]; 454 text = '' 455 connection=$(nmcli -t -f NAME,TYPE,DEVICE connection show --active 2>/dev/null | head -1) 456 if [ -z "$connection" ]; then 457 echo "󰖪 Disconnected" 458 else 459 name=$(echo "$connection" | cut -d: -f1) 460 type=$(echo "$connection" | cut -d: -f2) 461 if [ "$type" = "802-11-wireless" ]; then 462 echo "󰖩 $name" 463 else 464 echo "󰈀 $name" 465 fi 466 fi 467 ''; 468 }; 469 470 workspacesScript = pkgs.writeShellApplication { 471 name = "sway-workspaces"; 472 runtimeInputs = [ 473 pkgs.sway 474 pkgs.jq 475 ]; 476 text = '' 477 # Output initial state 478 swaymsg -t get_workspaces | jq -c '[.[] | {name: .name, focused: .focused, urgent: .urgent}]' 479 480 # Subscribe to workspace events and output updated state 481 swaymsg -t subscribe -m '["workspace"]' | while read -r _; do 482 swaymsg -t get_workspaces | jq -c '[.[] | {name: .name, focused: .focused, urgent: .urgent}]' 483 done 484 ''; 485 }; 486 487 brightnessScript = pkgs.writeShellApplication { 488 name = "eww-brightness"; 489 runtimeInputs = [ pkgs.brightnessctl ]; 490 text = '' 491 brightnessctl -m | cut -d, -f4 | tr -d '%' 492 ''; 493 }; 494 495 idleToggleScript = pkgs.writeShellApplication { 496 name = "eww-idle-toggle"; 497 runtimeInputs = [ pkgs.systemd ]; 498 text = '' 499 if systemctl --user is-active --quiet sway-idle-inhibit.service; then 500 systemctl --user stop sway-idle-inhibit.service 501 else 502 systemctl --user start sway-idle-inhibit.service 503 fi 504 ''; 505 }; 506 507 idleStatusScript = pkgs.writeShellApplication { 508 name = "eww-idle-status"; 509 runtimeInputs = [ pkgs.systemd ]; 510 text = '' 511 if systemctl --user is-active --quiet sway-idle-inhibit.service; then 512 echo "true" 513 else 514 echo "false" 515 fi 516 ''; 517 }; 518 519 # Power profile state file (written by power-profile tool, read by eww) 520 powerProfileStateFile = "/tmp/power-profile-state"; 521 522 powerProfileStatusScript = pkgs.writeShellApplication { 523 name = "eww-power-profile-status"; 524 runtimeInputs = [ pkgs.jq ]; 525 text = '' 526 state_file="${powerProfileStateFile}" 527 if [ -f "$state_file" ]; then 528 read -r name tdp < "$state_file" 529 else 530 name="unknown" 531 tdp="?" 532 fi 533 jq -n --arg name "$name" --arg tdp "$tdp" '{name: $name, tdp: $tdp}' 534 ''; 535 }; 536 537 powerProfileCycleScript = pkgs.writeShellApplication { 538 name = "eww-power-profile-cycle"; 539 runtimeInputs = [ pkgs.libnotify ]; 540 text = '' 541 state_file="${powerProfileStateFile}" 542 543 # Profile names (must match power-profile tool) 544 profiles=("low" "normal" "high") 545 546 # Read current profile from state file 547 current="unknown" 548 if [ -f "$state_file" ]; then 549 read -r current _ < "$state_file" 550 fi 551 552 # Find next profile (cycle through) 553 next_idx=0 554 for i in "''${!profiles[@]}"; do 555 if [ "''${profiles[$i]}" = "$current" ]; then 556 next_idx=$(( (i + 1) % ''${#profiles[@]} )) 557 break 558 fi 559 done 560 561 next="''${profiles[$next_idx]}" 562 563 # Use the power-profile tool (uses sudo internally, writes state file) 564 if /run/current-system/sw/bin/power-profile "$next" >/dev/null 2>&1; then 565 notify-send -t 2000 "Power Profile" "Set to $next" 566 else 567 notify-send -t 2000 "Power Profile" "Failed to set profile" 568 fi 569 ''; 570 }; 571 572 ewwConfigDir = pkgs.runCommand "eww-config" { } '' 573 mkdir -p $out 574 cp ${ewwYuck} $out/eww.yuck 575 cp ${ewwScss} $out/eww.scss 576 ''; 577 578in 579{ 580 # ryzenadj and power-profile for power profile control 581 environment.systemPackages = [ 582 pkgs.ryzenadj 583 self.packages.${pkgs.system}.power-profile 584 ]; 585 586 # Allow user to run ryzenadj without password (for power profile switching) 587 security.sudo.extraRules = [ 588 { 589 users = [ config.users.users.mainUser.name ]; 590 commands = [ 591 { 592 command = "/run/current-system/sw/bin/ryzenadj"; 593 options = [ "NOPASSWD" ]; 594 } 595 ]; 596 } 597 ]; 598 599 # Make RAPL energy counters readable for power monitoring 600 # Works on both Intel and AMD Zen CPUs (via intel_rapl_msr module) 601 services.udev.extraRules = '' 602 SUBSYSTEM=="powercap", ACTION=="add", RUN+="${pkgs.coreutils}/bin/chmod a+r /sys/class/powercap/%k/energy_uj" 603 ''; 604 605 # eww daemon 606 systemd.user.services.eww = { 607 description = "ElKowars Wacky Widgets daemon"; 608 partOf = [ "sway-session.target" ]; 609 wantedBy = [ "sway-session.target" ]; 610 path = [ 611 pkgs.bash 612 pkgs.sway 613 ]; 614 environment.EWW_CONFIG = "${ewwConfigDir}"; 615 serviceConfig = { 616 Type = "simple"; 617 ExecStart = "${lib.getExe pkgs.eww} daemon --config ${ewwConfigDir} --no-daemonize"; 618 ExecStop = "${lib.getExe pkgs.eww} --config ${ewwConfigDir} kill"; 619 Restart = "on-failure"; 620 RestartSec = 1; 621 }; 622 }; 623 624 # Idle inhibit service - toggled by eww button 625 systemd.user.services.sway-idle-inhibit = { 626 description = "Inhibit idle/sleep"; 627 serviceConfig = { 628 Type = "simple"; 629 ExecStart = "${pkgs.systemd}/bin/systemd-inhibit --what=idle:sleep --who=eww --why='User requested' --mode=block sleep infinity"; 630 }; 631 }; 632 633 # eww bar per output - template unit using output name (e.g. eww-bar@eDP-1.service) 634 systemd.user.services."eww-bar@" = { 635 description = "Eww bar on output %i"; 636 partOf = [ "sway-session.target" ]; 637 after = [ "eww.service" ]; 638 requires = [ "eww.service" ]; 639 serviceConfig = { 640 Type = "oneshot"; 641 RemainAfterExit = true; 642 ExecStart = "${pkgs.eww}/bin/eww --config ${ewwConfigDir} open bar --id bar-%i --arg screen=%i"; 643 ExecStop = "${pkgs.eww}/bin/eww --config ${ewwConfigDir} close bar-%i"; 644 }; 645 }; 646 647 # Watches for output changes and starts/stops eww-bar@ instances 648 systemd.user.services.eww-output-watcher = { 649 description = "Watch for output changes and manage eww bars"; 650 partOf = [ "sway-session.target" ]; 651 wantedBy = [ "sway-session.target" ]; 652 after = [ "eww.service" ]; 653 requires = [ "eww.service" ]; 654 serviceConfig = { 655 Type = "simple"; 656 ExecStart = lib.getExe ( 657 pkgs.writeShellApplication { 658 name = "eww-output-watcher"; 659 runtimeInputs = [ 660 pkgs.sway 661 pkgs.jq 662 pkgs.systemd 663 pkgs.gawk 664 pkgs.gnused 665 pkgs.gnugrep 666 ]; 667 text = '' 668 update_bars() { 669 # Get current outputs 670 outputs=$(swaymsg -t get_outputs | jq -r '.[].name') 671 672 # Start bars for connected outputs 673 for output in $outputs; do 674 systemctl --user start "eww-bar@$output.service" || true 675 done 676 677 # Stop bars for disconnected outputs 678 running=$(systemctl --user list-units --type=service --state=running --plain --no-legend 'eww-bar@*' | awk '{print $1}' | sed 's/eww-bar@\(.*\)\.service/\1/') 679 for output in $running; do 680 if ! echo "$outputs" | grep -qx "$output"; then 681 systemctl --user stop "eww-bar@$output.service" || true 682 fi 683 done 684 } 685 686 update_bars 687 swaymsg -t subscribe -m '["output"]' | while read -r _; do 688 sleep 0.5 689 update_bars 690 done 691 ''; 692 } 693 ); 694 Restart = "on-failure"; 695 RestartSec = 1; 696 }; 697 }; 698 699}