#!/usr/bin/python -OO ''' tray icon battery indicator adapted from http://ubuntuforums.org/showthread.php?t=1153951 and http://code.activestate.com/recipes/475155-dynamic-system-tray-icon-wxpython/ any original code Copyright (c) 2013 jc.unternet.net licensing GPL3 or later ''' import sys, os, wx, subprocess, re, time ACPI_PATTERN = '^Battery 0:\s+(\w+), (\d+)%' COLORS = { 'Full': 'green', 'Charging': 'green', 'Discharging': 'yellow', } UPDATE_TIME = 5 # seconds MILLISECONDS = 1000 # multiplier for seconds UPDATER = wx.NewEventType() # returns unique (to this app) ID WIDTH, HEIGHT = 16, 16 # must be multiples of 5 + 1; only 16 works right BORDER_WIDTH = BORDER_HEIGHT = 1 # black border -- width and height the same PADDING_WIDTH = 3 # transparent pixels BATTERY_WIDTH = WIDTH - (PADDING_WIDTH * 2) - (BORDER_WIDTH * 2) TRANSPARENT = 'white' WARNING_LEVEL = 3 # show red when this many lines class Icon(wx.Frame): def __init__(self, parent, ID, title): wx.Frame.__init__(self, parent, ID, title, wx.DefaultPosition, wx.DefaultSize) self.updateIcon() # bind frame onQuit to window close self.Bind(wx.EVT_CLOSE, self.onQuit) # bind frame onUp to mouseup on taskbar icon self.icon.Bind(wx.EVT_TASKBAR_LEFT_UP, self.onUp) width, height = self.GetSize() debug('frame size: (%d, %d)' % (width, height)) self.timer = wx.Timer(self, UPDATER) self.timer.Start(UPDATE_TIME * MILLISECONDS) wx.EVT_TIMER(self, UPDATER, self.updateIcon) debug('initialization complete') def onQuit(self, event): self.timer.Stop() # maybe not necessary but can't hurt self.icon.Destroy() self.Destroy() def onUp(self, event): iconized = self.IsIconized() if not iconized: self.Iconize() self.Hide() else: self.Restore() self.Show() def updateIcon(self, event = None): acpistring = acpi() debug('acpi: %s' % acpistring) status = re.compile(ACPI_PATTERN).match(acpistring) debug('status: %s' % status) color, charge = icon_data(*status.groups()) if status else ('red', 5) pixels = build_image(color, charge) image = wx.EmptyImage(WIDTH, HEIGHT) image.SetData(pixels) bitmap = image.ConvertToBitmap() bitmap.SetMask(wx.Mask(bitmap, color_tuple(TRANSPARENT))) icon_image = wx.EmptyIcon() icon_image.CopyFromBitmap(bitmap) if not hasattr(self, 'icon'): self.icon = wx.TaskBarIcon() self.icon.SetIcon(icon = icon_image, tooltip = "Battery 0 State") # put text of acpi string in frame if not hasattr(self, 'statusline'): self.statusline = wx.StaticText(self, label = acpistring) else: self.statusline.SetLabel(acpistring) class IconApp(wx.App): def OnInit(self): frame = Icon(None, -1, "Battery 0 State") frame.Show() # shows just momentarily before we iconize it self.SetTopWindow(frame) frame.Iconize() return True def build_image(color, charge): battery_height = HEIGHT - (2 * BORDER_HEIGHT) charge_height = int((float(charge) / 100) * battery_height) if color == 'yellow' and charge_height <= WARNING_LEVEL: color = 'red' padding_left = (pixel(TRANSPARENT) * PADDING_WIDTH) + pixel('black') padding_right = pixel('black') + (pixel(TRANSPARENT) * PADDING_WIDTH) pixels = padding_left + (pixel('black') * BATTERY_WIDTH) + padding_right for i in range(battery_height): rowcolor = color if (battery_height - 1 - i) > charge_height: rowcolor = TRANSPARENT pixels += padding_left + (pixel(rowcolor) * BATTERY_WIDTH) + padding_right pixels += padding_left + (pixel('black') * BATTERY_WIDTH) + padding_right return pixels def color_tuple(color): if type(color) == str: return wx.NamedColour(color).asTuple() else: return tuple(color) def pixel(color): return ''.join(map(chr, color_tuple(color))) def debug(message = None): if __debug__: if message: print >>sys.stderr, message return True def acpi(): output = subprocess.Popen(['acpi'], stdout = subprocess.PIPE).communicate()[0] return output def icon_data(word, number): return COLORS.get(word, 'yellow'), int(number) if __name__ == '__main__': IconApp(0).MainLoop()