2022-03-08 01:42:46 +00:00
|
|
|
|
{
|
|
|
|
|
lib,
|
|
|
|
|
pkgs,
|
|
|
|
|
config,
|
|
|
|
|
...
|
|
|
|
|
}: let
|
2022-08-11 09:50:05 +00:00
|
|
|
|
battery-watch =
|
|
|
|
|
pkgs.writeHaskellScript
|
|
|
|
|
{
|
|
|
|
|
name = "battery-watch";
|
|
|
|
|
bins = [pkgs.acpi];
|
|
|
|
|
imports = [
|
|
|
|
|
"DBus.Notify"
|
|
|
|
|
"Control.Concurrent"
|
|
|
|
|
"Text.Megaparsec"
|
|
|
|
|
"Text.Megaparsec.Char"
|
|
|
|
|
"Text.Megaparsec.Char.Lexer"
|
|
|
|
|
"Replace.Megaparsec"
|
|
|
|
|
"Data.Maybe"
|
|
|
|
|
];
|
|
|
|
|
} ''
|
|
|
|
|
moderateLevel = 50 -- percent
|
|
|
|
|
lowLevel = 20 -- percent
|
|
|
|
|
criticalLevel = 8 -- percent
|
|
|
|
|
minute = 60 * 1000 * 1000 -- threadDelay takes microseconds
|
2020-01-08 00:14:05 +00:00
|
|
|
|
|
2022-08-11 09:50:05 +00:00
|
|
|
|
main = do
|
|
|
|
|
client <- connectSession
|
|
|
|
|
let loop = \lastState handleMay -> do
|
|
|
|
|
newState <- getState
|
|
|
|
|
let noteMay = chooseAction lastState newState
|
|
|
|
|
handle <- if | Just note <- noteMay -> Just <$> maybe (notify client note) (flip (replace client) note) handleMay
|
|
|
|
|
| otherwise -> pure handleMay
|
|
|
|
|
threadDelay $ minute `div` 4
|
|
|
|
|
loop newState handle
|
|
|
|
|
loop (BatState True 100) Nothing
|
2020-01-08 00:14:05 +00:00
|
|
|
|
|
2022-08-11 09:50:05 +00:00
|
|
|
|
data BatState = BatState { charging :: Bool, level :: Int }
|
2020-01-13 20:12:38 +00:00
|
|
|
|
|
2022-08-11 09:50:05 +00:00
|
|
|
|
getState = do
|
|
|
|
|
batteryStateText <- decodeUtf8 <$> (acpi "-a" |> captureTrim)
|
|
|
|
|
batteryLevelText <- decodeUtf8 <$> (acpi "-b" |> captureTrim)
|
|
|
|
|
chargerOnline <- maybe (fail "Couldn‘t get charging state") pure $ parseMaybe onlineParser batteryStateText
|
|
|
|
|
batteryLevel <- maybe (fail "Couldn‘t get battery level") pure $ parseMaybe levelParser batteryLevelText
|
|
|
|
|
pure $ BatState chargerOnline batteryLevel
|
2020-01-08 00:14:05 +00:00
|
|
|
|
|
2022-08-11 09:50:05 +00:00
|
|
|
|
type Parser = Parsec Text LText
|
2020-01-08 00:14:05 +00:00
|
|
|
|
|
2022-08-11 09:50:05 +00:00
|
|
|
|
onlineParser :: Parser Bool
|
|
|
|
|
onlineParser = not . null . rights <$> sepCap (string "on-line")
|
2020-01-08 00:14:05 +00:00
|
|
|
|
|
2022-08-11 09:50:05 +00:00
|
|
|
|
levelParser :: Parser Int
|
|
|
|
|
levelParser = (maybe (fail "No Number found") pure . listToMaybe . rights) =<< sepCap (decimal <* "%")
|
2020-01-08 00:14:05 +00:00
|
|
|
|
|
2022-08-11 09:50:05 +00:00
|
|
|
|
chooseAction :: BatState -> BatState -> Maybe Note
|
|
|
|
|
chooseAction (BatState wasCharging lastLevel) (BatState isCharging currentLevel)
|
|
|
|
|
| wasCharging && isCharging = Nothing
|
|
|
|
|
| wasCharging && not isCharging = Just $ myNote{summary = "Charger disconnected." }
|
|
|
|
|
| not wasCharging && isCharging = Just $ myNote{summary = "Charger connected.", expiry = Milliseconds 5000 }
|
|
|
|
|
| currentLevel <= criticalLevel = Just $ myNote{summary = "Battery is very low!" }
|
|
|
|
|
| currentLevel <= lowLevel && currentLevel < lastLevel = Just $ myNote{summary = "Battery is low!"}
|
|
|
|
|
| ((currentLevel `mod` 5 == 0 && currentLevel <= moderateLevel) || (currentLevel `mod` 10 == 0)) && currentLevel < lastLevel = Just $ myNote{summary = "Battery is discharging."}
|
|
|
|
|
| otherwise = Nothing
|
|
|
|
|
where
|
|
|
|
|
myNote = blankNote { body = Just $ Text [i|#{currentLevel}% remaining.|]}
|
|
|
|
|
'';
|
2022-03-08 01:42:46 +00:00
|
|
|
|
in {
|
2018-05-26 20:48:33 +00:00
|
|
|
|
systemd.user = {
|
|
|
|
|
services.battery = {
|
2020-12-17 01:26:47 +00:00
|
|
|
|
Unit.Description = "Watch battery state and warn user";
|
2020-01-08 00:14:05 +00:00
|
|
|
|
Service = {
|
|
|
|
|
ExecStart = "${battery-watch}/bin/battery-watch";
|
|
|
|
|
Restart = "always";
|
2021-09-19 18:49:53 +00:00
|
|
|
|
RestartSec = 60;
|
2020-01-08 00:14:05 +00:00
|
|
|
|
};
|
2022-03-08 01:42:46 +00:00
|
|
|
|
Install.WantedBy = ["default.target"];
|
2018-05-26 20:48:33 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
}
|