Files

520 lines
16 KiB
Nix

{
self,
inputs,
...
}: {
flake.nixosModules.battery-laptop = {pkgs, ...}:
# https://discourse.nixos.org/t/what-is-the-best-option-for-power-management/63406/2
# we just activate tlp unconditonally
#let
# cfg = config.custom;
#hasBattery =
# lib.any (x: lib.strings.hasPrefix "BAT" x)
# (builtins.attrNames (builtins.readDir "/sys/class/power_supply"));
#in
{
# options.custom = {
# battery.enable = lib.mkOption {
# default = hasBattery;
# description = "Enable better battery support";
# type = lib.types.bool;
# };
# };
# config = lib.mkIf cfg.battery.enable {
/*
powerManagement.powertop.enable = true; # enable powertop auto tuning on startup.
# Acording to https://community.frame.work/t/solved-keys-stick-and-repeat-after-being-released/51153/12
# Some powertop bug is responsable for the problem of the random keypress being stuck
# fix: from https://git.gabbie.blue/blue/nixconf/src/commit/2d1bc6dad4684c019b6b3e894408e76e2734806c/hosts/gabbielaptop/configuration.nix#L68
powerManagement.powertop.postStart = ''
${lib.getExe' config.systemd.package "udevadm"} trigger -c bind -s usb -a idVendor=32ac -a idProduct=0018
# Retrigger macropad udev rules
${lib.getExe' config.systemd.package "udevadm"} trigger -c bind -s usb -a idVendor=32ac -a idProduct=0013
'';
*/
services.system76-scheduler.settings.cfsProfiles.enable = true; # Better scheduling for CPU cycles - thanks System76!!!
services.thermald.enable = false; # Enable thermald, the temperature management daemon. (only necessary if on Intel CPUs)
services.power-profiles-daemon.enable = true; # ppd is recommended over tlp for framework 16
/*
services.tlp = {
enable = true; # Enable TLP (better than gnomes internal power manager)
settings = {
CPU_BOOST_ON_AC = 1;
CPU_BOOST_ON_BAT = 1;
CPU_HWP_DYN_BOOST_ON_AC = 1;
CPU_HWP_DYN_BOOST_ON_BAT = 1;
CPU_SCALING_GOVERNOR_ON_AC = "balanced";
CPU_SCALING_GOVERNOR_ON_BAT = "powersave";
CPU_ENERGY_PERF_POLICY_ON_AC = "balanced";
CPU_ENERGY_PERF_POLICY_ON_BAT = "powersave";
PLATFORM_PROFILE_ON_AC = "balanced";
PLATFORM_PROFILE_ON_BAT = "powersave";
STOP_CHARGE_THRESH_BAT1 = 80;
};
};
*/
environment.systemPackages = [
self.packages.${pkgs.system}.custom-performance
];
};
perSystem = {pkgs, ...}: let
NotifySound = ../../assets/battery_notify.mp3;
in {
packages.custom-batterynotify = pkgs.writeShellApplication {
# taken and adapted https://github.com/miniMinn24/Battery_Notify with MIT License
name = "custom-batterynotify";
runtimeInputs = with pkgs; [
mplayer
brightnessctl
];
text = ''
# Delay for startup
sleep 5
# Change your sound path here
playsound() {
mplayer ${NotifySound}
}
adjustBrightness() {
local adjustment="$1"
brightnessctl set "$adjustment"
}
sendNotification() {
local title="$1"
local message="$2"
notify-send -h string:x-canonical-private-synchronous:sys-notify -e -u low "$title" "$message"
}
# Runs full time in background
x=0
while true; do
Battery_Status="$(\cat /sys/class/power_supply/BAT*/status)"
Battery_Capacity=$(\cat /sys/class/power_supply/BAT*/capacity)
case "$Battery_Status" in
"Discharging")
if [ $x -eq 1 ]; then
sendNotification "Discharging" "Remains: <b>''${Battery_Capacity}%</b>"
adjustBrightness "15%-"
playsound
x=0
fi
;;
"Charging")
if [ $x -eq 0 ]; then
sendNotification "Charging" "Current: <b>''${Battery_Capacity}%</b>"
adjustBrightness "15%+"
playsound
x=1
fi
;;
"Full")
if [ $x -eq 0 ]; then
sendNotification "Fully Charged" "Noice: <b>''${Battery_Capacity}%</b>"
playsound
x=1
fi
;;
esac
sleep 1
done
'';
};
packages.custom-batterywarning = pkgs.writeShellApplication {
# taken and adapted https://github.com/miniMinn24/Battery_Notify with MIT License
name = "custom-batterywarning";
runtimeInputs = with pkgs; [
mplayer
brightnessctl
];
text = ''
# Delay for startup
sleep 5
playsound() {
mplayer ${NotifySound}
}
while true; do
Battery_Status="$(cat /sys/class/power_supply/BAT*/status)"
Battery_Capacity="$(cat /sys/class/power_supply/BAT*/capacity)"
if [ "$Battery_Status" == "Discharging" ]; then
if [ "$Battery_Capacity" -le 20 ] && [ "$Battery_Capacity" -ge 10 ]; then
notify-send -e -u critical "Battery Low!" "Power running out: <b>''${Battery_Capacity}%</b>"
playsound
sleep 180
elif [ "$Battery_Capacity" -lt 10 ]; then
notify-send -e -u critical "Battery Low!" "<b>Save your works</b> before immediate shutdown."
playsound
sleep 180
fi
else
sleep 120
fi
done
'';
};
# because cbatticon, batticonplus and other things didn't work.
# chatGpt did this.
# https://chatgpt.com/c/69da11b3-ba24-8331-b7b0-a58a72e69d42
# at your risk and perils
# click on the icon to get a notification with state, percentage and wattage
packages.qtbatticon = pkgs.stdenv.mkDerivation {
pname = "qtbatticon";
version = "1.0";
src = pkgs.writeTextFile {
name = "qtbatticon.cpp";
text = ''
#include <QApplication>
#include <QSystemTrayIcon>
#include <QTimer>
#include <QFile>
#include <QTextStream>
#include <QIcon>
#include <QString>
#include <QDebug>
#include <cstdlib>
QString readFile(const QString &path) {
QFile f(path);
if (!f.open(QIODevice::ReadOnly)) return "";
QTextStream in(&f);
return in.readAll().trimmed();
}
int readInt(const QString &path) {
return readFile(path).toInt();
}
double readDouble(const QString &path) {
QFile f(path);
if (!f.open(QIODevice::ReadOnly)) return 0;
QTextStream in(&f);
return in.readAll().trimmed().toDouble();
}
int toStep(int capacity) {
return (capacity / 10) * 10;
}
QString pad3(int n) {
return QString("%1").arg(n, 3, 10, QChar('0'));
}
double getPowerW(const QString &batPath) {
double current_uA = readDouble(batPath + "/current_now");
double voltage_uV = readDouble(batPath + "/voltage_now");
if (current_uA > 0 && voltage_uV > 0) {
return (current_uA * voltage_uV) / 1e12;
}
return -1;
}
QIcon batteryIcon(int capacity, bool charging) {
int step = toStep(capacity);
QString num = pad3(step);
QString base = "/run/current-system/sw/share/icons/Papirus/24x24/panel/";
QString file;
if (charging) {
file = base + "battery-" + num + "-charging.svg";
} else {
file = base + "battery-" + num + ".svg";
}
QIcon icon(file);
if (icon.isNull()) {
qDebug() << "FAILED icon:" << file;
icon = QIcon(base + "battery-100.svg");
}
return icon;
}
void sendNotification(const QString &title, const QString &body) {
QString cmd = "notify-send \"" + title + "\" \"" + body + "\"";
system(cmd.toUtf8().constData());
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QString batPath = "/sys/class/power_supply/BAT1";
QSystemTrayIcon tray;
auto updateIcon = [&]() {
int capacity = readInt(batPath + "/capacity");
QString status = readFile(batPath + "/status");
bool charging = status.contains("Charging");
tray.setIcon(batteryIcon(capacity, charging));
};
auto notify = [&]() {
int capacity = readInt(batPath + "/capacity");
QString status = readFile(batPath + "/status");
bool charging = status.contains("Charging");
bool discharging = status.contains("Discharging");
double power = getPowerW(batPath);
QString state;
if (charging) state = "Charging";
else if (discharging) state = "Discharging";
else state = status;
QString powerStr = (power > 0.01)
? QString::number(power, 'f', 2) + " W"
: "N/A";
QString msg =
QString("%1\n%2%\n%3")
.arg(state)
.arg(capacity)
.arg(powerStr);
sendNotification("Battery status", msg);
};
QObject::connect(&tray, &QSystemTrayIcon::activated,
[&](QSystemTrayIcon::ActivationReason r) {
if (r == QSystemTrayIcon::Trigger) {
notify();
}
});
updateIcon();
tray.setVisible(true);
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, updateIcon);
timer.start(5000);
return app.exec();
}
'';
};
nativeBuildInputs = with pkgs; [
pkg-config
qt6.wrapQtAppsHook
];
buildInputs = with pkgs; [
qt6.qtbase
qt6.qtsvg
papirus-icon-theme
];
dontUnpack = true;
buildPhase = ''
g++ $src -o qtbatticon \
$(pkg-config --cflags --libs Qt6Widgets)
'';
installPhase = ''
mkdir -p $out/bin
install -m755 qtbatticon $out/bin/
'';
};
# This was replaced by caelestia shell's Game Mode
packages.custom-performance = pkgs.writeShellApplication {
name = "custom-performance";
runtimeInputs = with pkgs; [
hyprland
brightnessctl
swaynotificationcenter
self.packages.${pkgs.system}.custom-wallpaper
libnotify
inputs.caelestia-shell.packages.${pkgs.system}.default
];
text = ''
STATE="$HOME/.cache/hypr-battery-saver"
enable() {
mkdir -p "$(dirname "$STATE")"
echo "Enabling battery saver mode..."
# Save brightness
brightnessctl get > "''${STATE}.brightness" 2>/dev/null || true
# Kill heavy UI components
pkill -f caelestia-shell || true
pkill -f caelestia || true
pkill shell || true
pkill -f custom-wallpaper || true
# Start lightweight notifications
pkill swaync || true
swaync & disown
# Start lightweight waybar
pkill waybar || true
waybar & disown
# Lower refresh rate (BIGGEST win)
hyprctl keyword monitor "eDP-1,2560x1600@60,0x0,1"
# Core performance toggles
hyprctl --batch "\
keyword animations:enabled 0;\
keyword animation borderangle,0;\
keyword decoration:shadow:enabled 0;\
keyword decoration:blur:enabled 0;\
keyword decoration:rounding 0;\
keyword decoration:fullscreen_opacity 1;\
keyword general:gaps_in 0;\
keyword general:gaps_out 0;\
keyword general:border_size 1;\
keyword decoration:active_opacity 1;\
keyword decoration:inactive_opacity 1;\
keyword misc:vfr true"
# Lower brightness
brightnessctl set 5% 2>/dev/null || true
touch "$STATE"
notify-send "Battery Saver" "Enabled"
}
disable() {
echo "Disabling battery saver mode..."
# Restore refresh rate
hyprctl keyword monitor "eDP-1,2560x1600@165,0x0,1"
# Restore Caelestia
pkill swaync || true
pkill waybar || true
caelestia shell -d >/dev/null 2>&1 &
disown
# Restart wallpaper
custom-wallpaper >/dev/null 2>&1 &
disown
# Restore full UI via reload (safe here on exit)
hyprctl reload
# Restore brightness
if [[ -f "''${STATE}.brightness" ]]; then
brightnessctl set "$(cat "''${STATE}.brightness")" 2>/dev/null || true
fi
rm -f "$STATE" "''${STATE}.brightness"
notify-send "Battery Saver" "Disabled"
}
if [[ -f "$STATE" ]]; then
disable
else
enable
fi
'';
};
# Old script that was used for the battery. Might not work.
packages.custom-olddeprecatedbatterynotify = pkgs.writeShellApplication {
name = "custom-olddeprecatedbatterynotify";
runtimeInputs = with pkgs; [
libnotify # provides notify-send
systemd # provides systemctl
];
text = ''
set -euo pipefail
LOW="''${BATTERY_LOW_THRESHOLD:-20}"
CRITICAL="''${BATTERY_CRITICAL_THRESHOLD:-5}"
FULL="''${BATTERY_FULL_THRESHOLD:-95}"
INTERVAL="''${BATTERY_INTERVAL:-30}"
ACTION="''${BATTERY_CRITICAL_ACTION:-systemctl suspend}"
notify() {
notify-send -a "Battery" -u "$1" "$2" "$3"
}
get_info() {
total=0
count=0
for bat in /sys/class/power_supply/BAT*; do
[ -f "$bat/capacity" ] || continue
capacity=$(<"$bat/capacity")
status=$(<"$bat/status")
total=$((total + capacity))
count=$((count + 1))
done
[ "$count" -gt 0 ] || exit 0
percentage=$((total / count))
}
last_state=""
while true; do
get_info
if [[ "$status" == "Discharging" && "$percentage" -le "$CRITICAL" ]]; then
notify critical "Battery Critically Low" "$percentage% — suspending."
$ACTION
elif [[ "$status" == "Discharging" && "$percentage" -le "$LOW" ]]; then
if [[ "$last_state" != "low" ]]; then
notify normal "Battery Low" "$percentage% remaining."
last_state="low"
fi
elif [[ "$status" != "Discharging" && "$percentage" -ge "$FULL" ]]; then
if [[ "$last_state" != "full" ]]; then
notify normal "Battery Full" "$percentage% — unplug charger."
last_state="full"
fi
else
last_state=""
fi
sleep "$INTERVAL"
done
'';
};
};
flake.homeModules.cbatticon = _: {
# don't works. some GTK error. I let it here if someday, someone is interest in this shit.
# ChatGPT half baked a replacement that works on my system.
# see qtbatticon
services.cbatticon = {
enable = true;
lowLevelPercent = 20;
criticalLevelPercent = 5;
batteryId = "BAT1";
hideNotification = true; # we use another script for battery notifications
updateIntervalSeconds = 10;
};
};
}