From 8c97d465eb6bcc9b068ac2e1e0a57ff0a7748aa1 Mon Sep 17 00:00:00 2001 From: Gregory DAVID Date: Fri, 7 Jun 2024 22:56:26 +0200 Subject: [PATCH] battery2: change battery symbol when discharging - according to its level - popup notification when battery level < threshold - formatting --- battery2/README.md | 32 +++- battery2/battery2 | 201 ++++++++++++++++---------- battery2/i3blocks.conf | 17 +++ battery2/images/unplugged.png | Bin 1178 -> 0 bytes battery2/images/unplugged_25.png | Bin 0 -> 1779 bytes battery2/images/unplugged_50.png | Bin 0 -> 1788 bytes battery2/images/unplugged_75.png | Bin 0 -> 1876 bytes battery2/images/unplugged_empty.png | Bin 0 -> 1747 bytes battery2/images/unplugged_full.png | Bin 0 -> 1860 bytes battery2/images/warning_threshold.png | Bin 0 -> 6312 bytes 10 files changed, 168 insertions(+), 82 deletions(-) delete mode 100644 battery2/images/unplugged.png create mode 100644 battery2/images/unplugged_25.png create mode 100644 battery2/images/unplugged_50.png create mode 100644 battery2/images/unplugged_75.png create mode 100644 battery2/images/unplugged_empty.png create mode 100644 battery2/images/unplugged_full.png create mode 100644 battery2/images/warning_threshold.png diff --git a/battery2/README.md b/battery2/README.md index 6a286b7e..5480e6bb 100644 --- a/battery2/README.md +++ b/battery2/README.md @@ -1,24 +1,46 @@ # battery2 -Show the current status of your battery. +## Show the current status of your battery +### Plugged and full ![](images/full.png) +### Charging ![](images/charging.png) -![](images/unplugged.png) +### Discharging +![](images/unplugged_full.png) +![](images/unplugged_75.png) + +![](images/unplugged_50.png) + +![](images/unplugged_25.png) + +![](images/unplugged_empty.png) + +### Unknown status ![](images/unknown.png) +### No battery is present ![](images/nobattery.png) -# Dependencies +## Show the notification when below threshold + +![](images/warning_threshold.png) + +# Dependencies (Debian like) -fonts-font-awesome, acpi, python3 +- `fonts-font-awesome`, +- `libnotify-bin`, +- `acpi`, +- `python3` # Installation -To use with i3blocks, copy the blocklet configuration in the given `i3blocks.conf` into your i3blocks configuration file, the recommended config is +To use with `i3blocks`, copy the blocklet configuration in the given +`i3blocks.conf` into your i3blocks configuration file, the recommended +config is: ```INI [battery2] diff --git a/battery2/battery2 b/battery2/battery2 index 4898d3ce..f0a298c5 100755 --- a/battery2/battery2 +++ b/battery2/battery2 @@ -1,106 +1,153 @@ #!/usr/bin/env python3 # # Copyright (C) 2016 James Murphy +# 2024 Gregory David # Licensed under the GPL version 2 only # # A battery indicator blocklet script for i3blocks -from subprocess import check_output import os import re +import sys +from subprocess import CalledProcessError, check_output +from typing import Dict -config = dict(os.environ) -status = check_output(['acpi'], universal_newlines=True) - -if not status: - # stands for no battery found - color = config.get("color_10", "red") - fulltext = "\uf00d \uf240".format(color) - percentleft = 100 -else: - # if there is more than one battery in one laptop, the percentage left is - # available for each battery separately, although state and remaining - # time for overall block is shown in the status of the first battery - batteries = status.split("\n") - state_batteries=[] - commasplitstatus_batteries=[] - percentleft_batteries=[] - time = "" - for battery in batteries: - if battery!='': +CONFIG = dict(os.environ) +DISCHARGE_LEVEL = { + 90: CONFIG.get("discharge_90", "\uf240"), + 75: CONFIG.get("discharge_75", "\uf241"), + 50: CONFIG.get("discharge_50", "\uf242"), + 25: CONFIG.get("discharge_25", "\uf243"), + 10: CONFIG.get("discharge_10", "\uf244"), +} +COLOR_LEVEL = { + 90: CONFIG.get("color_90", "#FFFFFF"), + 80: CONFIG.get("color_80", "#FFFF66"), + 70: CONFIG.get("color_70", "#FFFF33"), + 60: CONFIG.get("color_60", "#FFFF00"), + 50: CONFIG.get("color_50", "#FFCC00"), + 40: CONFIG.get("color_40", "#FF9900"), + 30: CONFIG.get("color_30", "#FF6600"), + 20: CONFIG.get("color_20", "#FF3300"), + 10: CONFIG.get("color_10", "#FFFFFF"), +} +WARNING_THRESHOLD = int(CONFIG.get("warning_threshold", 10)) +COLOR_CHARGING = CONFIG.get("color_charging", "yellow") +COLOR_NO_BATTERY = CONFIG.get("color_no_battery", "red") + + +def value_for_level(percent: int, level_mapping: Dict[int, str]): + """Get symbol associated to a level percentage.""" + levels = sorted(level_mapping.items(), reverse=True) + for lvl, sym in levels: + if lvl <= percent: + return sym + return levels[-1][1] + + +def discharge_level(percent: int): + """Get battery symbol for given level percentage.""" + return value_for_level(percent, DISCHARGE_LEVEL) + + +def color_level(percent: int): + """Get color for given level percentage.""" + return value_for_level(percent, COLOR_LEVEL) + + +def battery2(): + """Main entry point.""" + try: + status = check_output(["acpi", "-b"], universal_newlines=True) + except (FileNotFoundError, CalledProcessError): + status = None + + if not status: + percentleft = 100 + content = discharge_level(percentleft) + if status is None: + content = "no ACPI process" + fulltext = f"\uf00d {content}" + shorttext = fulltext + else: + # if there is more than one battery in one laptop, the percentage left is + # available for each battery separately, although state and remaining + # time for overall block is shown in the status of the first battery + batteries = status.split("\n") + state_batteries = [] + commasplitstatus_batteries = [] + percentleft_batteries = [] + time = "" + for battery in batteries: + if not battery: + continue state_batteries.append(battery.split(": ")[1].split(", ")[0]) commasplitstatus = battery.split(", ") if not time: time = commasplitstatus[-1].strip() # check if it matches a time time = re.match(r"(\d+):(\d+)", time) + timeleft = "" if time: time = ":".join(time.groups()) - timeleft = " ({})".format(time) - else: - timeleft = "" + timeleft = f"({time})" p = int(commasplitstatus[1].rstrip("%\n")) - if p>0: + if p > 0: percentleft_batteries.append(p) commasplitstatus_batteries.append(commasplitstatus) - state = state_batteries[0] - commasplitstatus = commasplitstatus_batteries[0] - if percentleft_batteries: - percentleft = int(sum(percentleft_batteries)/len(percentleft_batteries)) - else: + state = state_batteries[0] + commasplitstatus = commasplitstatus_batteries[0] percentleft = 0 + if percentleft_batteries: + percentleft = int(sum(percentleft_batteries) / len(percentleft_batteries)) - # stands for charging - color = config.get("color_charging", "yellow") - FA_LIGHTNING = "\uf0e7".format(color) + fa_plugged_in = "\uf1e6" + fa_battery = f"{discharge_level(percentleft)}" - # stands for plugged in - FA_PLUG = "\uf1e6" + output = [] + if state == "Discharging": + output.append(f"{fa_battery}") + elif state in ["Full", "Not charging"]: + timeleft = "" + output.append(f"{fa_plugged_in}") + elif state == "Charging": + output.append( + f"\uf0e7" + ) + output.append(fa_plugged_in) + else: + # stands for unknown status of battery + timeleft = "" + output.append("\uf128") + output.append(fa_battery) - # stands for using battery - FA_BATTERY = "\uf240" + output.append(f"{percentleft}%") + shorttext = " ".join(output) + output.append(timeleft) + fulltext = " ".join(output) - # stands for unknown status of battery - FA_QUESTION = "\uf128" + print(fulltext) + print(shorttext) + if percentleft < WARNING_THRESHOLD and state != "Charging": + check_output( + [ + "notify-send", + "-u", + "critical", + "-a", + "i3blocks.battery2", + "-t", + "0", + "Warning", + f"Battery level {percentleft}% is below {WARNING_THRESHOLD}%, plug power or suspend system", + ] + ) + # exit code 33 will turn background red + return 33 + return 0 - if state == "Discharging": - fulltext = FA_BATTERY + " " - elif state == "Full": - fulltext = FA_PLUG + " " - timeleft = "" - elif state == "Unknown": - fulltext = FA_QUESTION + " " + FA_BATTERY + " " - timeleft = "" - else: - fulltext = FA_LIGHTNING + " " + FA_PLUG + " " - - def color(percent): - if percent < 10: - # exit code 33 will turn background red - return config.get("color_10", "#FFFFFF") - if percent < 20: - return config.get("color_20", "#FF3300") - if percent < 30: - return config.get("color_30", "#FF6600") - if percent < 40: - return config.get("color_40", "#FF9900") - if percent < 50: - return config.get("color_50", "#FFCC00") - if percent < 60: - return config.get("color_60", "#FFFF00") - if percent < 70: - return config.get("color_70", "#FFFF33") - if percent < 80: - return config.get("color_80", "#FFFF66") - return config.get("color_full", "#FFFFFF") - - form = '{}%' - fulltext += form.format(color(percentleft), percentleft) - fulltext += timeleft - -print(fulltext) -print(fulltext) -if percentleft < 10: - exit(33) + +if __name__ == "__main__": + sys.exit(battery2()) diff --git a/battery2/i3blocks.conf b/battery2/i3blocks.conf index 74aacd43..4562849b 100644 --- a/battery2/i3blocks.conf +++ b/battery2/i3blocks.conf @@ -2,3 +2,20 @@ command=$SCRIPT_DIR/battery2 markup=pango interval=30 +warning_threshold=10 +color_charging=yellow +color_no_battery=red +color_90=#FFFFFF +color_80=#FFFF66 +color_70=#FFFF33 +color_60=#FFFF00 +color_50=#FFCC00 +color_40=#FF9900 +color_30=#FF6600 +color_20=#FF3300 +color_10=#FFFFFF +discharge_90= +discharge_75= +discharge_50= +discharge_25= +discharge_10= diff --git a/battery2/images/unplugged.png b/battery2/images/unplugged.png deleted file mode 100644 index 3b8e02e5d2abb3728795f82655eadf6de246f7b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1178 zcmV;L1ZDe)P);)F7;^pu#=|X+>d) z2vJglh-jdM!=i|qL=B=r(FaK&nHnMGKq7K%k!jL7`B{cel2W5LIQPlH>T36Tc|Pzr za<8*JuJvc_wbm9xrBXqfKPDqBL7Fsa(xmyD3=a>dh$nKnT%*yXf+t$7Rwk2uPs0?^ zkw75$e7^GX@>Fo9yuAGJ@ez;5C$3}sxraz3E-fuV5cK-`dUJC#HZ~Tsp|rHr>-E0B zzXJd|J3Ac?2LwSp9`9SO3j~6Tiwldzf;Ntfj99JKP$*=zT8D;)qW1THN#xDV&CP1H z+VA&2KR;hxT`3fbs6aFtZDV7@;c$dPq07t5uC6Y`Zn0QQCX+xQNand-TU$FgIAAas zI2?}6X6yBOkw_GMnwgn-etteNF@Z$obUHa4PHk;148z~jQK?jl#bUIjr>6&o;f98W z+}zy8#>V^mdp@6!-jJY&m0a(;gP@bD0W!9;besj1;`IQRGWKL`Dc z3<`x3*A=_nPN&n+mebSI{{H^xa$sQK)zc&d%iIEiEm(ySrOkTTM+(3A*x8$G5jP zEEWp@SYBQR0AR7$NF?$b=Ne6KZ?D(uWipunfJh{w(P-FgHV%iQ)9EUe3i%-fJRYB9 z9r1Yl+}xbqZhv`sF`Lb!qoW>=2LNC&7!(RcOjI2m9eTZ9CX*Eu6d<_?K~QUJYa-{G z!C=JXBf6d>n2%qPm6f%yumHnwb#*mj2?PS@f>Tsfgv5owWHRk``(K=UrlzL0x3|$v z6Ag((%EJ`;(C_yv6bcLmgSLysV!d9Egod2!)z#JL)1#v!^jMMv9lN``udlDe z;jq)`+}zxx(P*D;`xq6U&v(1sNY9Du3LcMte0;2^s6ZQ~QmM^m3xz^9o9)y2@uO;A zSz`F{;|Bm>e0&_4Phl8dTwEj&2*_6|D=U3IUv&9@y3YUrWHMPyO8!^qbh_`}3o}#a srawD7nEX>4Tx04R}tkv&MmKpe$iQ?()$2Md1SkfA!+MMWHI6^c+H)C#RSm|XfHG-*gu zTpR`0f`cE6RR;V-yJO0*#vEd>=bb;{*sk16O*>U#SB#pQP7X zTJ#9$+XgPKTbi;5TeSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00hfPL_t(o!|j-VNLyDF$3HQ#z65`eG_WR_E>;nl%qFC3 z$sa;pJ1RWzpZ(LS9T=E{{UsGwizR}z4BASqP}qOs){2r~Tw53aD74r@TM#F1sXtn& zb<4J}bhH+lr8VCD@!Dw6#86h*Mh;va_ndR@_ulV2_uTWI3J_#uOC>8qL5B1U=^1&8 zq&GD+Wti9kdCjvXnS@1fqUBX~6(BI#W&1NGSjndcGhuiH=I#!Y- z#>U35SS&0rFXQoeXl-pph}8uS4i2(+?_R7{D*#%pmK!&2uy5Z!B9Ta1YZOgQP2yD* z6K`W%vMeVZuU4x?QBjcq zgd|Dg&Ye4=wzf8Imt|RaJRY&Iu#hmWy}eyDH#etT+iRYa=JZ%BhRfw*e0-eo@o|nG zJ(}{(Ns>f;eSK1Uco{~XLQKF?3<41_Whb9+1;tmascCamn@7;$!-w(v{j97gMgnchAE-H9AIb>&NqtodC$j!~AwY8Pgr%$iL z9UL6Q<#KK9MS9@C0S+BHl$Nh48=Se|f4ZuhdLL#h;P{SLDdIQy#tI+h!FNyK%N@{D z1nNzQud1rz?%lf^%#r>?G!xw$zi zDk_+nnPJCc`$5ujk^$ix`bYgb-Z0a)k>QE^JC002ovPDHLkV1jCvHA(;g literal 0 HcmV?d00001 diff --git a/battery2/images/unplugged_50.png b/battery2/images/unplugged_50.png new file mode 100644 index 0000000000000000000000000000000000000000..721ce56e1d00405fa380a5acbad88c06d2257b6b GIT binary patch literal 1788 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 literal 0 HcmV?d00001 diff --git a/battery2/images/unplugged_full.png b/battery2/images/unplugged_full.png new file mode 100644 index 0000000000000000000000000000000000000000..6e7d3a9150e3db2607d7b58300694da5fd3dff1f GIT binary patch literal 1860 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