303 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
import os
import sys
import re
from powerline.lib.shell import run_cmd
def _fetch_battery_info(pl):
try:
import dbus
except ImportError:
pl.debug('Not using DBUS+UPower as dbus is not available')
else:
try:
bus = dbus.SystemBus()
except Exception as e:
pl.exception('Failed to connect to system bus: {0}', str(e))
else:
interface = 'org.freedesktop.UPower'
try:
up = bus.get_object(interface, '/org/freedesktop/UPower')
except dbus.exceptions.DBusException as e:
if getattr(e, '_dbus_error_name', '').endswith('ServiceUnknown'):
pl.debug('Not using DBUS+UPower as UPower is not available via dbus')
else:
pl.exception('Failed to get UPower service with dbus: {0}', str(e))
else:
devinterface = 'org.freedesktop.DBus.Properties'
devtype_name = interface + '.Device'
devices = []
for devpath in up.EnumerateDevices(dbus_interface=interface):
dev = bus.get_object(interface, devpath)
devget = lambda what: dev.Get(
devtype_name,
what,
dbus_interface=devinterface
)
if int(devget('Type')) != 2:
pl.debug('Not using DBUS+UPower with {0}: invalid type', devpath)
continue
if not bool(devget('IsPresent')):
pl.debug('Not using DBUS+UPower with {0}: not present', devpath)
continue
if not bool(devget('PowerSupply')):
pl.debug('Not using DBUS+UPower with {0}: not a power supply', devpath)
continue
devices.append(devpath)
pl.debug('Using DBUS+UPower with {0}', devpath)
if devices:
def _flatten_battery(pl):
energy = 0.0
energy_full = 0.0
state = True
for devpath in devices:
dev = bus.get_object(interface, devpath)
energy_full += float(
dbus.Interface(dev, dbus_interface=devinterface).Get(
devtype_name,
'EnergyFull'
),
)
energy += float(
dbus.Interface(dev, dbus_interface=devinterface).Get(
devtype_name,
'Energy'
),
)
state &= dbus.Interface(dev, dbus_interface=devinterface).Get(
devtype_name,
'State'
) != 2
if energy_full > 0:
return (energy * 100.0 / energy_full), state
else:
return 0.0, state
return _flatten_battery
pl.debug('Not using DBUS+UPower as no batteries were found')
if os.path.isdir('/sys/class/power_supply'):
# ENERGY_* attributes represents capacity in µWh only.
# CHARGE_* attributes represents capacity in µAh only.
linux_capacity_units = ('energy', 'charge')
linux_energy_full_fmt = '/sys/class/power_supply/{0}/{1}_full'
linux_energy_fmt = '/sys/class/power_supply/{0}/{1}_now'
linux_status_fmt = '/sys/class/power_supply/{0}/status'
devices = []
for linux_supplier in os.listdir('/sys/class/power_supply'):
for unit in linux_capacity_units:
energy_path = linux_energy_fmt.format(linux_supplier, unit)
if not os.path.exists(energy_path):
continue
pl.debug('Using /sys/class/power_supply with battery {0} and unit {1}',
linux_supplier, unit)
devices.append((linux_supplier, unit))
break # energy or charge, not both
if devices:
def _get_battery_status(pl):
energy = 0.0
energy_full = 0.0
state = True
for device, unit in devices:
with open(linux_energy_full_fmt.format(device, unit), 'r') as f:
energy_full += int(float(f.readline().split()[0]))
with open(linux_energy_fmt.format(device, unit), 'r') as f:
energy += int(float(f.readline().split()[0]))
try:
with open(linux_status_fmt.format(device), 'r') as f:
state &= (f.readline().strip() != 'Discharging')
except IOError:
state = None
return (energy * 100.0 / energy_full), state
return _get_battery_status
pl.debug('Not using /sys/class/power_supply as no batteries were found')
else:
pl.debug("Checking for first capacity battery percentage")
for batt in os.listdir('/sys/class/power_supply'):
if os.path.exists('/sys/class/power_supply/{0}/capacity'.format(batt)):
def _get_battery_perc(pl):
state = True
with open('/sys/class/power_supply/{0}/capacity'.format(batt), 'r') as f:
perc = int(f.readline().split()[0])
try:
with open(linux_status_fmt.format(batt), 'r') as f:
state &= (f.readline().strip() != 'Discharging')
except IOError:
state = None
return perc, state
return _get_battery_perc
else:
pl.debug('Not using /sys/class/power_supply: no directory')
try:
from shutil import which # Python-3.3 and later
except ImportError:
pl.info('Using dumb “which” which only checks for file in /usr/bin')
which = lambda f: (lambda fp: os.path.exists(fp) and fp)(os.path.join('/usr/bin', f))
if which('pmset'):
pl.debug('Using pmset')
BATTERY_PERCENT_RE = re.compile(r'(\d+)%')
def _get_battery_status(pl):
battery_summary = run_cmd(pl, ['pmset', '-g', 'batt'])
battery_percent = BATTERY_PERCENT_RE.search(battery_summary).group(1)
ac_charging = 'AC' in battery_summary
return int(battery_percent), ac_charging
return _get_battery_status
else:
pl.debug('Not using pmset: executable not found')
if sys.platform.startswith('win') or sys.platform == 'cygwin':
# From http://stackoverflow.com/a/21083571/273566, reworked
try:
from win32com.client import GetObject
except ImportError:
pl.debug('Not using win32com.client as it is not available')
else:
try:
wmi = GetObject('winmgmts:')
except Exception as e:
pl.exception('Failed to run GetObject from win32com.client: {0}', str(e))
else:
for battery in wmi.InstancesOf('Win32_Battery'):
pl.debug('Using win32com.client with Win32_Battery')
def _get_battery_status(pl):
# http://msdn.microsoft.com/en-us/library/aa394074(v=vs.85).aspx
return battery.EstimatedChargeRemaining, battery.BatteryStatus == 6
return _get_battery_status
pl.debug('Not using win32com.client as no batteries were found')
from ctypes import Structure, c_byte, c_ulong, byref
if sys.platform == 'cygwin':
pl.debug('Using cdll to communicate with kernel32 (Cygwin)')
from ctypes import cdll
library_loader = cdll
else:
pl.debug('Using windll to communicate with kernel32 (Windows)')
from ctypes import windll
library_loader = windll
class PowerClass(Structure):
_fields_ = [
('ACLineStatus', c_byte),
('BatteryFlag', c_byte),
('BatteryLifePercent', c_byte),
('Reserved1', c_byte),
('BatteryLifeTime', c_ulong),
('BatteryFullLifeTime', c_ulong)
]
def _get_battery_status(pl):
powerclass = PowerClass()
result = library_loader.kernel32.GetSystemPowerStatus(byref(powerclass))
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa372693(v=vs.85).aspx
if result:
return None
return powerclass.BatteryLifePercent, powerclass.ACLineStatus == 1
if _get_battery_status() is None:
pl.debug('Not using GetSystemPowerStatus because it failed')
else:
pl.debug('Using GetSystemPowerStatus')
return _get_battery_status
raise NotImplementedError
def _get_battery_status(pl):
global _get_battery_status
def _failing_get_status(pl):
raise NotImplementedError
try:
_get_battery_status = _fetch_battery_info(pl)
except NotImplementedError:
_get_battery_status = _failing_get_status
except Exception as e:
pl.exception('Exception while obtaining battery status: {0}', str(e))
_get_battery_status = _failing_get_status
return _get_battery_status(pl)
def battery(pl, format='{ac_state} {capacity:3.0%}', steps=5, gamify=False, full_heart='O', empty_heart='O', online='C', offline=' '):
'''Return battery charge status.
:param str format:
Percent format in case gamify is False. Format arguments: ``ac_state``
which is equal to either ``online`` or ``offline`` string arguments and
``capacity`` which is equal to current battery capacity in interval [0,
100].
:param int steps:
Number of discrete steps to show between 0% and 100% capacity if gamify
is True.
:param bool gamify:
Measure in hearts (♥) instead of percentages. For full hearts
``battery_full`` highlighting group is preferred, for empty hearts there
is ``battery_empty``. ``battery_online`` or ``battery_offline`` group
will be used for leading segment containing ``online`` or ``offline``
argument contents.
:param str full_heart:
Heart displayed for “full” part of battery.
:param str empty_heart:
Heart displayed for “used” part of battery. It is also displayed using
another gradient level and highlighting group, so it is OK for it to be
the same as full_heart as long as necessary highlighting groups are
defined.
:param str online:
Symbol used if computer is connected to a power supply.
:param str offline:
Symbol used if computer is not connected to a power supply.
``battery_gradient`` and ``battery`` groups are used in any case, first is
preferred.
Highlight groups used: ``battery_full`` or ``battery_gradient`` (gradient) or ``battery``, ``battery_empty`` or ``battery_gradient`` (gradient) or ``battery``, ``battery_online`` or ``battery_ac_state`` or ``battery_gradient`` (gradient) or ``battery``, ``battery_offline`` or ``battery_ac_state`` or ``battery_gradient`` (gradient) or ``battery``.
'''
try:
capacity, ac_powered = _get_battery_status(pl)
except NotImplementedError:
pl.info('Unable to get battery status.')
return None
ret = []
if gamify:
denom = int(steps)
numer = int(denom * capacity / 100)
ret.append({
'contents': online if ac_powered else offline,
'draw_inner_divider': False,
'highlight_groups': ['battery_online' if ac_powered else 'battery_offline', 'battery_ac_state', 'battery_gradient', 'battery'],
'gradient_level': 0,
})
ret.append({
'contents': full_heart * numer,
'draw_inner_divider': False,
'highlight_groups': ['battery_full', 'battery_gradient', 'battery'],
# Using zero as “nothing to worry about”: it is least alert color.
'gradient_level': 0,
})
ret.append({
'contents': empty_heart * (denom - numer),
'draw_inner_divider': False,
'highlight_groups': ['battery_empty', 'battery_gradient', 'battery'],
# Using a hundred as it is most alert color.
'gradient_level': 100,
})
else:
ret.append({
'contents': format.format(ac_state=(online if ac_powered else offline), capacity=(capacity / 100.0)),
'highlight_groups': ['battery_gradient', 'battery'],
# Gradients are “least alert most alert” by default, capacity has
# the opposite semantics.
'gradient_level': 100 - capacity,
})
return ret