Source code for powerline.segments.common.bat

import os
import sys
import re

from powerline.lib.shell import run_cmd

# XXX Warning: module name must not be equal to the segment name as long as this
# segment is imported into powerline.segments.common module.

show_original=False
capacity_full_design=-1
base_dir = '/sys/class/power_supply'

def _file_exists(path, arg):
    return os.path.exists(path.format(arg))

def _get_batteries(file_names):
    import functools
    batteries = []
    for linux_bat in os.listdir('/sys/class/power_supply'):
        if linux_bat.startswith('BAT'):
            pos = [functools.reduce(lambda x, y: x and y, a, True) for a in
                    [[_file_exists(base_dir + '/{0}/' + b, linux_bat) for b in file_names[i]]
                    for i in range(0, len(file_names))]]
            for i in range(0, len(pos)):
                if pos[i]:
                    batteries += [(linux_bat, i)]
                    break
    return batteries

def _get_paths(file_names, batteries, battery):
    return [(base_dir + '/BAT' + str(battery) + '/' + fn)
            for fn in file_names[
                [bat[1] for bat in batteries
                    if bat[0] == ('BAT' + str(battery))][0]]]

def _read(file_names):
    res = []
    for p in file_names:
        with open(p, 'r') as f:
            res += [f.readline().split()[0]]
    return res

def _get_battery(pl):
    if os.path.isdir(base_dir):
        file_names = [['charge_now', 'charge_full', 'charge_full_design'],
                ['energy_now', 'energy_full', 'energy_full_design']]
        batteries = _get_batteries(file_names)

        def _get_capacity(pl, battery):
            current = 0
            full = 1

            paths = _get_paths(file_names, batteries, battery)
            vals = _read(paths)

            current = int(float(vals[0]))
            if not show_original:
                full = int(float(vals[1]))
            elif capacity_full_design == -1:
                full = int(float(vals[2]))
            else:
                full = capacity_full_design
            return (current * 100/full)

        return _get_capacity
    else:
        pl.debug('Not using /sys/class/power_supply: no directory')

    raise NotImplementedError

def _get_battery_status(pl):
    if os.path.isdir(base_dir):
        status_paths = [['status']]
        batteries = _get_batteries(status_paths)

        def _get_status(pl, battery):
            path = _get_paths(status_paths, batteries, battery)
            stat = _read(path)[0]
            return stat
        return _get_status
    else:
        pl.debug('Not using /sys/class/power_supply: no directory')

    raise NotImplementedError

def _get_battery_rem_time(pl, battery):
    if os.path.isdir(base_dir):
        rem_time_paths = [['energy_now', 'energy_full', 'power_now', 'status'],
                ['charge_now', 'charge_full', 'current_now', 'status']]
        batteries = _get_batteries(rem_time_paths)

        def _get_rem_time(pl, battery):
            paths = _get_paths(rem_time_paths, batteries, battery)
            vals = _read(paths)
            if vals[2] == '0' or vals[3] == 'Unknown':
                return 0

            curr = int(float(vals[2]))
            charge = int(vals[0])
            full = int(vals[1])
            if vals[3] == 'Charging':
                return (full - charge) / curr
            elif vals[3] == 'Discharging':
                return charge / curr
            else:
                return 0
        return _get_rem_time
    else:
        pl.debug('Not using /sys/class/power_supply: no directory')

    raise NotImplementedError

def _get_capacity(pl, battery):
    global _get_capacity

    def _failing_get_capacity(pl, battery):
        raise NotImplementedError

    try:
        _get_capacity = _get_battery(pl)
    except NotImplementedError:
        _get_capacity = _failing_get_capacity
    except Exception as e:
        pl.exception('Exception while obtaining battery capacity getter: {0}', str(e))
        _get_capacity = _failing_get_capacity
    return _get_capacity(pl, battery)

def _get_status(pl, battery):
    global _get_status

    def _failing_get_status(pl, battery):
        raise NotImplementedError

    try:
        _get_status = _get_battery_status(pl)
    except NotImplementedError:
        _get_status = _failing_get_status
    except Exception as e:
        pl.exception('Exception while obtaining battery capacity getter: {0}', str(e))
        _get_status = _failing_get_status
    return _get_status(pl, battery)

def _get_rem_time(pl, battery):
    global _get_rem_time

    def _failing_get_rem_time(pl, battery):
        raise NotImplementedError

    try:
        _get_rem_time = _get_battery_rem_time(pl, battery)
    except NotImplementedError:
        _get_rem_time = _failing_get_rem_time
    except Exception as e:
        pl.exception('Exception while obtaining battery capacity getter: {0}', str(e))
        _get_rem_time = _failing_get_rem_time
    return _get_rem_time(pl, battery)

[docs]def battery(pl, name='capacity', icons={'online':'CHR', 'offline':'BAT', 'full':''}, format='{capacity:3.0%}', rem_time_format='%H:%M', gamify_steps=5, bat=0, original_health=False, full_design=-1): '''Return batteries' charge status. :param str name: Determines the information displayed. Valid values: ======== ======================================================================== Name Description ======== ======================================================================== capacity The remaining capacity of the battery as a float btw 0 and 1. gamify Rem. cap. encoded in a string of gamify_steps chars from icons. status Current adapter status (Charging, Discharging or Full). icon Icon depicting current battery status/capacity. rem_time Remaining time till the battery is full or empty. ======== ======================================================================== :param dict icons: Icons used to display the adapter status. Possible entries are ``online``, ``offline`` and ``full`` for statuses, ``0``, ``25``, ``50``, ``75`` and ``100`` for use with gamify. If ``online``, ``offline`` or ``full`` are absent, icon will try to use appropriate icons from gamify. :param string format: Format used to display the capacity. :param string rem_time_format: Format used to display the remaining time (as a strftime format string) :param int gamify_steps: Number of discrete steps to show between 0% and 100% capacity if gamify occurs in format. The single one step that is neither completely full nor completely empty will use the icon corresponding to the percentage that part is empty. :param int bat: Specifies the battery to display information for. :param bool original_health: Use the original battery health as base value. (Experimental) :param int full_design: Specifies the design capacity of the battery. You will need this only if this value happens to read wrong. (Experimental) ``battery_gradient`` and ``battery`` groups are used in any case, first is preferred. Highlight groups used: ``battery`` or ``battery_gradient`` (gradient) or ``battery:100`` or ``battery:50`` or ``battery:0`` or ``battery:full`` or ``battery:online`` or ``battery:offline``. Click values supplied: ``capacity`` (int), ``rem_time`` (string), ``status`` (string). ''' capacity = 0 try: global show_original global capacity_full_design show_original = original_health capacity_full_design = full_design capacity = min(100, _get_capacity(pl, bat)) except NotImplementedError: pl.info('Unable to get battery capacity.') capacity = None status = None try: status = _get_status(pl, bat) except NotImplementedError: pl.info('Unable to get battery status.') status = None rem_time = 0 try: rem_time = _get_rem_time(pl, bat) except NotImplementedError: pl.info('Unable to get remaining time.') rem_time = None except OSError: pl.info('Your BIOS is screwed.') rem_time = None if rem_time: from datetime import time rem_sec = int(rem_time * 3600) rem_hours = int(rem_sec / 3600) rem_sec -= rem_hours * 3600 rem_minutes = int(rem_sec / 60) rem_sec -= rem_minutes * 60 rem_time = time(hour=rem_hours, minute=rem_minutes, second=rem_sec).strftime(rem_time_format) else: rem_time = None click_values = {'status': status, 'rem_time': rem_time, 'capacity': capacity} def get_icon(percentage): for p in range(100, -1, -1): if percentage >= p and str(p) in icons: return icons[str(p)] return '' def get_status_icon(status): if status in icons: return icons[status] else: return get_icon(capacity) def translate_status(status): return {'Charging':'online', 'Discharging':'offline', 'Full':'full'}.get(status, 'unknown') if name == 'gamify': segment_size = 100 // gamify_steps full = int(capacity + .5) // segment_size half = (int(capacity) % segment_size) * 100 // segment_size empty = gamify_steps - full - 1 ret = [] ret.append({ 'contents': get_icon(100) * full, 'draw_inner_divider': False, 'highlight_groups': ['battery:100', 'battery_gradient', 'battery'], # Using zero as “nothing to worry about”: it is least alert color. 'gradient_level': 0, 'click_values': click_values }) ret.append({ 'contents': get_icon(half), 'draw_inner_divider': False, 'highlight_groups': ['battery_gamify_gradient', 'battery_gradient', 'battery'], 'gradient_level': segment_size - half, 'click_values': click_values }) ret.append({ 'contents': get_icon(0) * empty, 'highlight_groups': ['battery:0', 'battery_gradient', 'battery'], # Using a hundred as it is most alert color. 'gradient_level': 100, 'click_values': click_values }) return ret elif name == 'icon': return [{ 'contents': get_status_icon(translate_status(status)), 'highlight_groups': ['battery:' + translate_status(status), 'battery_gradient', 'battery'], 'gradient_level': 100 - capacity, 'click_values': click_values }] elif name == 'status': if not status: return None return [{ 'contents': status, 'highlight_groups': ['battery:' + translate_status(status), 'battery_gradient', 'battery'], 'gradient_level': 100 - capacity, 'click_values': click_values }] elif name == 'capacity': return [{ 'contents': format.format(capacity=capacity / 100.0, status=status, rem_time=rem_time), 'highlight_groups': ['battery_gradient', 'battery'], # Gradients are “least alert – most alert” by default, capacity has # the opposite semantics. 'gradient_level': 100 - capacity, 'click_values': click_values }] elif name == 'rem_time': if not rem_time: return None return [{ 'contents': rem_time, 'highlight_groups': ['battery_gradient', 'battery'], # Gradients are “least alert – most alert” by default, capacity has # the opposite semantics. 'gradient_level': 100 - capacity, 'click_values': click_values }] else: return None