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 3b8e02e5..00000000 Binary files a/battery2/images/unplugged.png and /dev/null differ diff --git a/battery2/images/unplugged_25.png b/battery2/images/unplugged_25.png new file mode 100644 index 00000000..1da22d89 Binary files /dev/null and b/battery2/images/unplugged_25.png differ diff --git a/battery2/images/unplugged_50.png b/battery2/images/unplugged_50.png new file mode 100644 index 00000000..721ce56e Binary files /dev/null and b/battery2/images/unplugged_50.png differ diff --git a/battery2/images/unplugged_75.png b/battery2/images/unplugged_75.png new file mode 100644 index 00000000..cc934f51 Binary files /dev/null and b/battery2/images/unplugged_75.png differ diff --git a/battery2/images/unplugged_empty.png b/battery2/images/unplugged_empty.png new file mode 100644 index 00000000..1198378b Binary files /dev/null and b/battery2/images/unplugged_empty.png differ diff --git a/battery2/images/unplugged_full.png b/battery2/images/unplugged_full.png new file mode 100644 index 00000000..6e7d3a91 Binary files /dev/null and b/battery2/images/unplugged_full.png differ diff --git a/battery2/images/warning_threshold.png b/battery2/images/warning_threshold.png new file mode 100644 index 00000000..ca9cb3f7 Binary files /dev/null and b/battery2/images/warning_threshold.png differ