Created
March 4, 2016 23:50
-
-
Save arxanas/24b5003cd341e3adbbf8 to your computer and use it in GitHub Desktop.
Battery text display script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# -*- coding: utf-8 -*- | |
import json | |
import os | |
import pickle | |
import subprocess | |
import time | |
BATTERY_CMD = ["/usr/sbin/ioreg", "-r", "-n", "AppleSmartBattery"] | |
GREP_CMD = ["/usr/bin/egrep", "Capacity|ExternalConnected"] | |
ENABLED = True | |
CACHE_FILE = "~/.local/tmp/battery-text-cache" | |
FULL_BATTERY_CUTOFF = 95 | |
class StatusCache(object): | |
"""Running the battery command all the time can cause CPU spikes. Instead, | |
we can cache the textual result and use that instead. | |
""" | |
CACHE_TIME = 10 | |
"""The time to cache the status, in seconds.""" | |
CACHE_FILE = os.path.expanduser(CACHE_FILE) | |
"""The file to cache to. This is a pickle file.""" | |
@property | |
def is_cached(self): | |
"""Whether or not the status is cached.""" | |
# If the cache file doesn't exist, it's obviously not cached. | |
if not os.path.exists(self.CACHE_FILE): | |
return False | |
# The status is cached if the cache file was modified recently, and not | |
# cached if the script file was modified recently. | |
current_time = time.time() | |
def recently_modified(file_path): | |
return current_time - os.path.getmtime(file_path) < self.CACHE_TIME | |
return ( | |
recently_modified(self.CACHE_FILE) and | |
not recently_modified(os.path.realpath(__file__)) | |
) | |
@property | |
def cached(self): | |
"""Gets the cached status, or returns None if it's not cached.""" | |
if not self.is_cached: | |
return None | |
try: | |
with open(self.CACHE_FILE, "rb") as cache_file: | |
return pickle.load(cache_file) | |
except IOError: | |
return None | |
@cached.setter | |
def cached(self, status): | |
"""Caches a status.""" | |
pickle.dump(status, open(self.CACHE_FILE, "wb")) | |
def main(): | |
"""Prints the status of the battery for use in the statusline.""" | |
if not ENABLED: | |
return | |
cache = StatusCache() | |
cached_status = cache.cached | |
if cached_status: | |
print(cached_status) | |
return | |
num_blocks = 8 | |
try: | |
battery_charge, is_charging = get_battery_status() | |
# Floor battery charge | |
battery_charge = int(battery_charge) | |
# The battery doesn't seem to ever exceed 98%, so we just pretend | |
# that's 100%. However, it's unlikely it's actually at full capacity | |
# if it's not being charged. | |
if battery_charge >= FULL_BATTERY_CUTOFF and is_charging: | |
battery_charge = 100 | |
battery_icon = make_charge_string(battery_charge, num_blocks) | |
except RuntimeError: | |
battery_charge = "?" | |
battery_icon = " " * num_blocks | |
# text = "▕{battery_icon}▏[{charge_symbol} {charge_text:>3}%]".format( | |
# charge_text=battery_charge, | |
# battery_icon=battery_icon, | |
# charge_symbol=charge_symbol | |
# ) | |
text = "▕{battery_icon}▏,{is_charging},{charge_text}".format( | |
battery_icon=battery_icon, | |
is_charging=(1 if is_charging else 0), | |
charge_text=battery_charge | |
) | |
cache.cached = text | |
print(text) | |
def make_charge_string(battery_charge, num_blocks=4): | |
"""Returns the charge string, like "▊▊▊▍". | |
num_blocks: The number of cells in the icon. | |
""" | |
blocks = { | |
0: " ", | |
12.5: "▏", | |
25: "▎", | |
32.5: "▍", | |
50: "▌", | |
67.5: "▋", | |
87.5: "▊", | |
100: "█", | |
} | |
def find_max_charge(charge): | |
"""Finds the key for the maximum category in the `blocks` dict above. | |
charge: The charge, from 0 to 100. | |
""" | |
return max( | |
i | |
for i | |
in blocks.keys() | |
if charge >= i | |
) | |
ret = "" | |
remaining_charge = battery_charge * num_blocks | |
for i in range(num_blocks): | |
ret += blocks[find_max_charge(remaining_charge)] | |
# Each block depletes 100, since we already scaled to have each block | |
# be worth 100. | |
remaining_charge -= 100 | |
if remaining_charge < 0: | |
remaining_charge = 0 | |
return ret | |
def ioreg_to_dict(ioreg_output): | |
"""Converts the output of an `ioreg` call to a dict.""" | |
# Convert the output to JSON format, then feed to `json.loads`. | |
replacements = { | |
"Yes": "true", | |
"No": "false", | |
"=": ":", | |
} | |
for i, j in replacements.iteritems(): | |
ioreg_output = ioreg_output.replace(i, j) | |
ioreg_output = ",".join(ioreg_output.strip().split("\n")) | |
ioreg_output = "{" + ioreg_output + "}" | |
return json.loads(ioreg_output) | |
def get_battery_status(): | |
"""Returns a tuple of (battery_charge, is_charging), or raises | |
RuntimeError if something unexpected happened. `battery_charge` is a float | |
between 1 and 100. | |
Originally from | |
http://treknologies.blogspot.com/2011/03/osx-login-screens-and-battery.html | |
""" | |
process = subprocess.Popen( | |
BATTERY_CMD, | |
stdout=subprocess.PIPE, | |
stderr=subprocess.PIPE | |
) | |
grep = subprocess.Popen( | |
GREP_CMD, | |
stdin=process.stdout, | |
stdout=subprocess.PIPE, | |
stderr=subprocess.PIPE | |
) | |
grep.wait() | |
output = grep.communicate()[0] | |
battery_status = ioreg_to_dict(output) | |
is_charging = battery_status["ExternalConnected"] | |
max_charge = float(battery_status["MaxCapacity"]) | |
current_charge = float(battery_status["CurrentCapacity"]) | |
battery_charge = 100 * (current_charge / max_charge) | |
return battery_charge, is_charging | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment