|
1 | 1 | #!/usr/bin/env python3
|
2 | 2 | #
|
3 | 3 | # Copyright (C) 2016 James Murphy
|
| 4 | +# 2024 Gregory David |
4 | 5 | # Licensed under the GPL version 2 only
|
5 | 6 | #
|
6 | 7 | # A battery indicator blocklet script for i3blocks
|
7 | 8 |
|
8 |
| -from subprocess import check_output |
9 | 9 | import os
|
10 | 10 | import re
|
| 11 | +import sys |
| 12 | +from subprocess import CalledProcessError, check_output |
| 13 | +from typing import Dict |
11 | 14 |
|
12 |
| -config = dict(os.environ) |
13 |
| -status = check_output(['acpi'], universal_newlines=True) |
14 |
| - |
15 |
| -if not status: |
16 |
| - # stands for no battery found |
17 |
| - color = config.get("color_10", "red") |
18 |
| - fulltext = "<span color='{}'><span font='FontAwesome'>\uf00d \uf240</span></span>".format(color) |
19 |
| - percentleft = 100 |
20 |
| -else: |
21 |
| - # if there is more than one battery in one laptop, the percentage left is |
22 |
| - # available for each battery separately, although state and remaining |
23 |
| - # time for overall block is shown in the status of the first battery |
24 |
| - batteries = status.split("\n") |
25 |
| - state_batteries=[] |
26 |
| - commasplitstatus_batteries=[] |
27 |
| - percentleft_batteries=[] |
28 |
| - time = "" |
29 |
| - for battery in batteries: |
30 |
| - if battery!='': |
| 15 | +CONFIG = dict(os.environ) |
| 16 | +DISCHARGE_LEVEL = { |
| 17 | + 90: CONFIG.get("discharge_90", "\uf240"), |
| 18 | + 75: CONFIG.get("discharge_75", "\uf241"), |
| 19 | + 50: CONFIG.get("discharge_50", "\uf242"), |
| 20 | + 25: CONFIG.get("discharge_25", "\uf243"), |
| 21 | + 10: CONFIG.get("discharge_10", "\uf244"), |
| 22 | +} |
| 23 | +COLOR_LEVEL = { |
| 24 | + 90: CONFIG.get("color_90", "#FFFFFF"), |
| 25 | + 80: CONFIG.get("color_80", "#FFFF66"), |
| 26 | + 70: CONFIG.get("color_70", "#FFFF33"), |
| 27 | + 60: CONFIG.get("color_60", "#FFFF00"), |
| 28 | + 50: CONFIG.get("color_50", "#FFCC00"), |
| 29 | + 40: CONFIG.get("color_40", "#FF9900"), |
| 30 | + 30: CONFIG.get("color_30", "#FF6600"), |
| 31 | + 20: CONFIG.get("color_20", "#FF3300"), |
| 32 | + 10: CONFIG.get("color_10", "#FFFFFF"), |
| 33 | +} |
| 34 | +WARNING_THRESHOLD = int(CONFIG.get("warning_threshold", 10)) |
| 35 | +COLOR_CHARGING = CONFIG.get("color_charging", "yellow") |
| 36 | +COLOR_NO_BATTERY = CONFIG.get("color_no_battery", "red") |
| 37 | + |
| 38 | + |
| 39 | +def value_for_level(percent: int, level_mapping: Dict[int, str]): |
| 40 | + """Get symbol associated to a level percentage.""" |
| 41 | + levels = sorted(level_mapping.items(), reverse=True) |
| 42 | + for lvl, sym in levels: |
| 43 | + if lvl <= percent: |
| 44 | + return sym |
| 45 | + return levels[-1][1] |
| 46 | + |
| 47 | + |
| 48 | +def discharge_level(percent: int): |
| 49 | + """Get battery symbol for given level percentage.""" |
| 50 | + return value_for_level(percent, DISCHARGE_LEVEL) |
| 51 | + |
| 52 | + |
| 53 | +def color_level(percent: int): |
| 54 | + """Get color for given level percentage.""" |
| 55 | + return value_for_level(percent, COLOR_LEVEL) |
| 56 | + |
| 57 | + |
| 58 | +def battery2(): |
| 59 | + """Main entry point.""" |
| 60 | + try: |
| 61 | + status = check_output(["acpi", "-b"], universal_newlines=True) |
| 62 | + except (FileNotFoundError, CalledProcessError): |
| 63 | + status = None |
| 64 | + |
| 65 | + if not status: |
| 66 | + percentleft = 100 |
| 67 | + content = discharge_level(percentleft) |
| 68 | + if status is None: |
| 69 | + content = "no ACPI process" |
| 70 | + fulltext = f"<span color='{COLOR_NO_BATTERY}' font='FontAwesome'>\uf00d {content}</span>" |
| 71 | + shorttext = fulltext |
| 72 | + else: |
| 73 | + # if there is more than one battery in one laptop, the percentage left is |
| 74 | + # available for each battery separately, although state and remaining |
| 75 | + # time for overall block is shown in the status of the first battery |
| 76 | + batteries = status.split("\n") |
| 77 | + state_batteries = [] |
| 78 | + commasplitstatus_batteries = [] |
| 79 | + percentleft_batteries = [] |
| 80 | + time = "" |
| 81 | + for battery in batteries: |
| 82 | + if not battery: |
| 83 | + continue |
31 | 84 | state_batteries.append(battery.split(": ")[1].split(", ")[0])
|
32 | 85 | commasplitstatus = battery.split(", ")
|
33 | 86 | if not time:
|
34 | 87 | time = commasplitstatus[-1].strip()
|
35 | 88 | # check if it matches a time
|
36 | 89 | time = re.match(r"(\d+):(\d+)", time)
|
| 90 | + timeleft = "" |
37 | 91 | if time:
|
38 | 92 | time = ":".join(time.groups())
|
39 |
| - timeleft = " ({})".format(time) |
40 |
| - else: |
41 |
| - timeleft = "" |
| 93 | + timeleft = f"({time})" |
42 | 94 |
|
43 | 95 | p = int(commasplitstatus[1].rstrip("%\n"))
|
44 |
| - if p>0: |
| 96 | + if p > 0: |
45 | 97 | percentleft_batteries.append(p)
|
46 | 98 | commasplitstatus_batteries.append(commasplitstatus)
|
47 |
| - state = state_batteries[0] |
48 |
| - commasplitstatus = commasplitstatus_batteries[0] |
49 |
| - if percentleft_batteries: |
50 |
| - percentleft = int(sum(percentleft_batteries)/len(percentleft_batteries)) |
51 |
| - else: |
| 99 | + state = state_batteries[0] |
| 100 | + commasplitstatus = commasplitstatus_batteries[0] |
52 | 101 | percentleft = 0
|
| 102 | + if percentleft_batteries: |
| 103 | + percentleft = int(sum(percentleft_batteries) / len(percentleft_batteries)) |
53 | 104 |
|
54 |
| - # stands for charging |
55 |
| - color = config.get("color_charging", "yellow") |
56 |
| - FA_LIGHTNING = "<span color='{}'><span font='FontAwesome'>\uf0e7</span></span>".format(color) |
| 105 | + fa_plugged_in = "<span font='FontAwesome'>\uf1e6</span>" |
| 106 | + fa_battery = f"<span font='FontAwesome'>{discharge_level(percentleft)}</span>" |
57 | 107 |
|
58 |
| - # stands for plugged in |
59 |
| - FA_PLUG = "<span font='FontAwesome'>\uf1e6</span>" |
| 108 | + output = [] |
| 109 | + if state == "Discharging": |
| 110 | + output.append(f"{fa_battery}") |
| 111 | + elif state in ["Full", "Not charging"]: |
| 112 | + timeleft = "" |
| 113 | + output.append(f"{fa_plugged_in}") |
| 114 | + elif state == "Charging": |
| 115 | + output.append( |
| 116 | + f"<span color='{COLOR_CHARGING}' font='FontAwesome'>\uf0e7</span>" |
| 117 | + ) |
| 118 | + output.append(fa_plugged_in) |
| 119 | + else: |
| 120 | + # stands for unknown status of battery |
| 121 | + timeleft = "" |
| 122 | + output.append("<span font='FontAwesome'>\uf128</span>") |
| 123 | + output.append(fa_battery) |
60 | 124 |
|
61 |
| - # stands for using battery |
62 |
| - FA_BATTERY = "<span font='FontAwesome'>\uf240</span>" |
| 125 | + output.append(f"<span color='{color_level(percentleft)}'>{percentleft}%</span>") |
| 126 | + shorttext = " ".join(output) |
| 127 | + output.append(timeleft) |
| 128 | + fulltext = " ".join(output) |
63 | 129 |
|
64 |
| - # stands for unknown status of battery |
65 |
| - FA_QUESTION = "<span font='FontAwesome'>\uf128</span>" |
| 130 | + print(fulltext) |
| 131 | + print(shorttext) |
66 | 132 |
|
| 133 | + if percentleft < WARNING_THRESHOLD and state != "Charging": |
| 134 | + check_output( |
| 135 | + [ |
| 136 | + "notify-send", |
| 137 | + "-u", |
| 138 | + "critical", |
| 139 | + "-a", |
| 140 | + "i3blocks.battery2", |
| 141 | + "-t", |
| 142 | + "0", |
| 143 | + "Warning", |
| 144 | + f"Battery level <b>{percentleft}%</b> is below <b>{WARNING_THRESHOLD}%</b>, plug power or suspend system", |
| 145 | + ] |
| 146 | + ) |
| 147 | + # exit code 33 will turn background red |
| 148 | + return 33 |
| 149 | + return 0 |
67 | 150 |
|
68 |
| - if state == "Discharging": |
69 |
| - fulltext = FA_BATTERY + " " |
70 |
| - elif state == "Full": |
71 |
| - fulltext = FA_PLUG + " " |
72 |
| - timeleft = "" |
73 |
| - elif state == "Unknown": |
74 |
| - fulltext = FA_QUESTION + " " + FA_BATTERY + " " |
75 |
| - timeleft = "" |
76 |
| - else: |
77 |
| - fulltext = FA_LIGHTNING + " " + FA_PLUG + " " |
78 |
| - |
79 |
| - def color(percent): |
80 |
| - if percent < 10: |
81 |
| - # exit code 33 will turn background red |
82 |
| - return config.get("color_10", "#FFFFFF") |
83 |
| - if percent < 20: |
84 |
| - return config.get("color_20", "#FF3300") |
85 |
| - if percent < 30: |
86 |
| - return config.get("color_30", "#FF6600") |
87 |
| - if percent < 40: |
88 |
| - return config.get("color_40", "#FF9900") |
89 |
| - if percent < 50: |
90 |
| - return config.get("color_50", "#FFCC00") |
91 |
| - if percent < 60: |
92 |
| - return config.get("color_60", "#FFFF00") |
93 |
| - if percent < 70: |
94 |
| - return config.get("color_70", "#FFFF33") |
95 |
| - if percent < 80: |
96 |
| - return config.get("color_80", "#FFFF66") |
97 |
| - return config.get("color_full", "#FFFFFF") |
98 |
| - |
99 |
| - form = '<span color="{}">{}%</span>' |
100 |
| - fulltext += form.format(color(percentleft), percentleft) |
101 |
| - fulltext += timeleft |
102 |
| - |
103 |
| -print(fulltext) |
104 |
| -print(fulltext) |
105 |
| -if percentleft < 10: |
106 |
| - exit(33) |
| 151 | + |
| 152 | +if __name__ == "__main__": |
| 153 | + sys.exit(battery2()) |
0 commit comments