···
19
19
(defpoll cpu-freq :interval "2s" :initial 0 "${lib.getExe cpuFreqScript}")
20
20
(defpoll cpu-load :interval "2s" :initial "0.00" "cut -d' ' -f1 /proc/loadavg")
21
21
(defpoll temperature :interval "2s" :initial '{"temp": 0, "fan": 0}' "${lib.getExe tempScript}")
22
22
+
(defpoll power :interval "2s" :initial '{"watts": 0, "charging": false}' "${lib.getExe powerScript}")
22
23
(defpoll battery :interval "10s" :initial '{"capacity": "100", "icon": "", "watts": "0", "time_remaining": "--", "status": "Full"}' "${lib.getExe batteryScript}")
23
24
(defpoll volume :interval "1s" :initial '{"level": "0", "icon": ""}' "${lib.getExe volumeScript}")
24
25
(defpoll network :interval "5s" :initial " ..." "${lib.getExe networkScript}")
···
48
49
(graph-widget :value {EWW_CPU.avg} :icon "" :class "cpu" :unit "%" :tooltip "Load: ''${cpu-load} | ''${cpu-freq} MHz")
49
50
(graph-widget :value {EWW_RAM.used_mem_perc} :icon "" :class "memory" :unit "%")
50
51
(graph-widget :value {temperature.temp} :icon "" :class "temp" :unit "°" :tooltip "Fan: ''${temperature.fan} RPM")
52
52
+
(power-graph-widget)
51
53
(brightness-widget)
52
54
(battery-widget)
53
55
(idle-inhibitor)
···
79
81
(box :class "battery" :orientation "h" :space-evenly false
80
82
:tooltip "''${battery.watts}W | ''${battery.time_remaining} ''${battery.status == 'Charging' ? 'to full' : 'remaining'}"
81
83
(label :text "''${battery.capacity}% ''${battery.icon}")))
84
84
+
85
85
+
(defwidget power-graph-widget []
86
86
+
(box :class {power.charging ? "graph-module power charging" : "graph-module power"} :orientation "h" :space-evenly false :spacing 4
87
87
+
:tooltip "''${battery.time_remaining} ''${battery.status == 'Charging' ? 'to full' : 'remaining'}"
88
88
+
(graph :class "graph"
89
89
+
:value {power.watts}
90
90
+
:thickness 3
91
91
+
:time-range "60s"
92
92
+
:min 0
93
93
+
:max 45
94
94
+
:dynamic false
95
95
+
:line-style "round"
96
96
+
:width 75)
97
97
+
(label :text "''${round(power.watts, 1)}W ''${power.charging ? '' : ''}")))
82
98
83
99
(defwidget volume-widget []
84
100
(box :class "volume" :orientation "h" :space-evenly false
···
164
180
background-color: #ff9e64;
165
181
color: #1a1b26;
166
182
}
183
183
+
&.power {
184
184
+
background-color: #f7768e;
185
185
+
color: #1a1b26;
186
186
+
&.charging {
187
187
+
background-color: #9ece6a;
188
188
+
}
189
189
+
}
167
190
}
168
191
169
192
.graph {
···
246
269
'';
247
270
};
248
271
272
272
+
powerScript = pkgs.writeShellApplication {
273
273
+
name = "eww-power";
274
274
+
runtimeInputs = [
275
275
+
pkgs.jq
276
276
+
pkgs.bc
277
277
+
];
278
278
+
text = ''
279
279
+
# Use RAPL for actual system power consumption (works on Intel and AMD Zen)
280
280
+
# Falls back to battery power_now if RAPL unavailable
281
281
+
282
282
+
state_file="/tmp/eww-power-state"
283
283
+
rapl_path="/sys/class/powercap/intel-rapl:0/energy_uj"
284
284
+
285
285
+
# Check charging status
286
286
+
charging="false"
287
287
+
for p in /sys/class/power_supply/BAT* /sys/class/power_supply/BATT*; do
288
288
+
if [ -f "$p/status" ]; then
289
289
+
status=$(cat "$p/status" 2>/dev/null || echo "Unknown")
290
290
+
if [ "$status" = "Charging" ]; then
291
291
+
charging="true"
292
292
+
fi
293
293
+
break
294
294
+
fi
295
295
+
done
296
296
+
297
297
+
# Try RAPL first (gives actual system power regardless of charging state)
298
298
+
if [ -r "$rapl_path" ]; then
299
299
+
current_energy=$(cat "$rapl_path")
300
300
+
current_time=$(date +%s%N)
301
301
+
302
302
+
if [ -f "$state_file" ]; then
303
303
+
read -r prev_energy prev_time < "$state_file"
304
304
+
305
305
+
# Calculate power from energy difference
306
306
+
energy_diff=$((current_energy - prev_energy))
307
307
+
time_diff=$((current_time - prev_time))
308
308
+
309
309
+
# Handle counter wrap-around
310
310
+
if [ "$energy_diff" -lt 0 ]; then
311
311
+
max_energy=$(cat /sys/class/powercap/intel-rapl:0/max_energy_range_uj 2>/dev/null || echo 0)
312
312
+
energy_diff=$((energy_diff + max_energy))
313
313
+
fi
314
314
+
315
315
+
if [ "$time_diff" -gt 0 ]; then
316
316
+
# energy_diff is in microjoules, time_diff is in nanoseconds
317
317
+
# watts = microjoules / (nanoseconds / 1e9) / 1e6 = microjoules * 1000 / nanoseconds
318
318
+
watts=$(echo "scale=1; $energy_diff * 1000 / $time_diff" | bc)
319
319
+
else
320
320
+
watts="0"
321
321
+
fi
322
322
+
else
323
323
+
watts="0"
324
324
+
fi
325
325
+
326
326
+
# Save current state
327
327
+
echo "$current_energy $current_time" > "$state_file"
328
328
+
329
329
+
jq -n --argjson watts "$watts" --argjson charging "$charging" '{watts: $watts, charging: $charging}'
330
330
+
exit 0
331
331
+
fi
332
332
+
333
333
+
# Fallback: use battery power_now (only accurate when discharging)
334
334
+
bat_path=""
335
335
+
for p in /sys/class/power_supply/BAT* /sys/class/power_supply/BATT*; do
336
336
+
if [ -f "$p/power_now" ]; then
337
337
+
bat_path="$p"
338
338
+
break
339
339
+
fi
340
340
+
done
341
341
+
342
342
+
if [ -z "$bat_path" ]; then
343
343
+
echo '{"watts": 0, "charging": false}'
344
344
+
exit 0
345
345
+
fi
346
346
+
347
347
+
power_now=$(cat "$bat_path/power_now" 2>/dev/null || echo 0)
348
348
+
watts=$(echo "scale=1; $power_now / 1000000" | bc)
349
349
+
350
350
+
jq -n --argjson watts "$watts" --argjson charging "$charging" '{watts: $watts, charging: $charging}'
351
351
+
'';
352
352
+
};
353
353
+
249
354
batteryScript = pkgs.writeShellApplication {
250
355
name = "eww-battery";
251
356
runtimeInputs = [
···
287
392
hours=$(echo "scale=2; $energy_now / $power_now" | bc)
288
393
fi
289
394
# Convert to hours:minutes
395
395
+
# Note: bc outputs ".76" for values < 1, so cut returns empty string
290
396
h=$(echo "$hours" | cut -d. -f1)
397
397
+
h=''${h:-0} # Default to 0 if empty (i.e., less than 1 hour)
291
398
m=$(echo "scale=0; ($hours - $h) * 60 / 1" | bc)
292
399
time_remaining="''${h}h ''${m}m"
293
400
else
···
413
520
414
521
in
415
522
{
523
523
+
# Make RAPL energy counters readable for power monitoring
524
524
+
# Works on both Intel and AMD Zen CPUs (via intel_rapl_msr module)
525
525
+
services.udev.extraRules = ''
526
526
+
SUBSYSTEM=="powercap", ACTION=="add", RUN+="${pkgs.coreutils}/bin/chmod a+r /sys/class/powercap/%k/energy_uj"
527
527
+
'';
528
528
+
416
529
# eww daemon
417
530
systemd.user.services.eww = {
418
531
description = "ElKowars Wacky Widgets daemon";