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