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