Skip to content

Instantly share code, notes, and snippets.

@arxanas
Created March 4, 2016 23:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save arxanas/24b5003cd341e3adbbf8 to your computer and use it in GitHub Desktop.
Save arxanas/24b5003cd341e3adbbf8 to your computer and use it in GitHub Desktop.
Battery text display script
#!/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