Add ac-charging indication for battery segment

This commit is contained in:
Fumihiro Xue 2015-07-01 19:59:52 +08:00
parent 090cd1397c
commit f098ed2fe0
8 changed files with 113 additions and 42 deletions

View File

@ -51,7 +51,9 @@
"powerline.segments.common.bat.battery": { "powerline.segments.common.bat.battery": {
"args": { "args": {
"full_heart": "O", "full_heart": "O",
"empty_heart": "O" "empty_heart": "O",
"online": "C",
"offline": " "
} }
}, },
"powerline.segments.common.sys.uptime": { "powerline.segments.common.sys.uptime": {

View File

@ -50,7 +50,9 @@
"powerline.segments.common.bat.battery": { "powerline.segments.common.bat.battery": {
"args": { "args": {
"full_heart": "♥", "full_heart": "♥",
"empty_heart": "♥" "empty_heart": "♥",
"online": "⚡︎",
"offline": " "
} }
}, },
"powerline.segments.common.sys.uptime": { "powerline.segments.common.sys.uptime": {

View File

@ -50,7 +50,9 @@
"powerline.segments.common.bat.battery": { "powerline.segments.common.bat.battery": {
"args": { "args": {
"full_heart": "💙", "full_heart": "💙",
"empty_heart": "💛" "empty_heart": "💛",
"online": "⚡️",
"offline": " "
} }
}, },
"powerline.segments.common.sys.uptime": { "powerline.segments.common.sys.uptime": {

View File

@ -50,7 +50,9 @@
"powerline.segments.common.bat.battery": { "powerline.segments.common.bat.battery": {
"args": { "args": {
"full_heart": "♥", "full_heart": "♥",
"empty_heart": "♥" "empty_heart": "♥",
"online": "⚡︎",
"offline": " "
} }
}, },
"powerline.segments.common.sys.uptime": { "powerline.segments.common.sys.uptime": {

View File

@ -50,7 +50,9 @@
"powerline.segments.common.bat.battery": { "powerline.segments.common.bat.battery": {
"args": { "args": {
"full_heart": "♥", "full_heart": "♥",
"empty_heart": "♥" "empty_heart": "♥",
"online": "⚡︎",
"offline": " "
} }
}, },
"powerline.segments.common.sys.uptime": { "powerline.segments.common.sys.uptime": {

View File

@ -51,7 +51,9 @@
"powerline.segments.common.bat.battery": { "powerline.segments.common.bat.battery": {
"args": { "args": {
"full_heart": "♥", "full_heart": "♥",
"empty_heart": "♥" "empty_heart": "♥",
"online": "⚡︎",
"offline": " "
} }
}, },
"powerline.segments.common.sys.uptime": { "powerline.segments.common.sys.uptime": {

View File

@ -12,7 +12,7 @@ from powerline.lib.shell import run_cmd
# segment is imported into powerline.segments.common module. # segment is imported into powerline.segments.common module.
def _get_battery(pl): def _fetch_battery_info(pl):
try: try:
import dbus import dbus
except ImportError: except ImportError:
@ -55,23 +55,31 @@ def _get_battery(pl):
dbus.Interface(dev, dbus_interface=devinterface).Get( dbus.Interface(dev, dbus_interface=devinterface).Get(
devtype_name, devtype_name,
'Percentage' 'Percentage'
) ),
dbus.Interface(dev, dbus_interface=devinterface).Get(
devtype_name,
'State'
) == 1
) )
pl.debug('Not using DBUS+UPower as no batteries were found') pl.debug('Not using DBUS+UPower as no batteries were found')
if os.path.isdir('/sys/class/power_supply'): if os.path.isdir('/sys/class/power_supply'):
linux_bat_fmt = '/sys/class/power_supply/{0}/capacity' linux_bat_fmt = '/sys/class/power_supply/{0}/capacity'
linux_ac_fmt = '/sys/class/power_supply/{0}/online'
for linux_bat in os.listdir('/sys/class/power_supply'): for linux_bat in os.listdir('/sys/class/power_supply'):
cap_path = linux_bat_fmt.format(linux_bat) cap_path = linux_bat_fmt.format(linux_bat)
online_path = linux_ac_fmt.format(linux_bat)
if linux_bat.startswith('BAT') and os.path.exists(cap_path): if linux_bat.startswith('BAT') and os.path.exists(cap_path):
pl.debug('Using /sys/class/power_supply with battery {0}', linux_bat) pl.debug('Using /sys/class/power_supply with battery {0}', linux_bat)
def _get_capacity(pl): def _get_battery_status(pl):
with open(cap_path, 'r') as f: with open(cap_path, 'r') as f:
return int(float(f.readline().split()[0])) _capacity = int(float(f.readline().split()[0]))
with open(online_path, 'r') as f:
return _get_capacity _ac_powered = f.readline() == 1
pl.debug('Not using /sys/class/power_supply as no batteries were found') return _capacity, _ac_powered
return _get_battery_status
pl.debug('Not using /sys/class/power_supply as no batteries were found')
else: else:
pl.debug('Not using /sys/class/power_supply: no directory') pl.debug('Not using /sys/class/power_supply: no directory')
@ -86,12 +94,12 @@ def _get_battery(pl):
BATTERY_PERCENT_RE = re.compile(r'(\d+)%') BATTERY_PERCENT_RE = re.compile(r'(\d+)%')
def _get_capacity(pl): def _get_battery_status(pl):
battery_summary = run_cmd(pl, ['pmset', '-g', 'batt']) battery_summary = run_cmd(pl, ['pmset', '-g', 'batt'])
battery_percent = BATTERY_PERCENT_RE.search(battery_summary).group(1) battery_percent = BATTERY_PERCENT_RE.search(battery_summary).group(1)
return int(battery_percent) ac_charging = 'AC' in battery_summary
return int(battery_percent), ac_charging
return _get_capacity return _get_battery_status
else: else:
pl.debug('Not using pmset: executable not found') pl.debug('Not using pmset: executable not found')
@ -110,11 +118,11 @@ def _get_battery(pl):
for battery in wmi.InstancesOf('Win32_Battery'): for battery in wmi.InstancesOf('Win32_Battery'):
pl.debug('Using win32com.client with Win32_Battery') pl.debug('Using win32com.client with Win32_Battery')
def _get_capacity(pl): def _get_battery_status(pl):
# http://msdn.microsoft.com/en-us/library/aa394074(v=vs.85).aspx # http://msdn.microsoft.com/en-us/library/aa394074(v=vs.85).aspx
return battery.EstimatedChargeRemaining return battery.EstimatedChargeRemaining, battery.BatteryStatus == 6
return _get_capacity return _get_battery_status
pl.debug('Not using win32com.client as no batteries were found') pl.debug('Not using win32com.client as no batteries were found')
from ctypes import Structure, c_byte, c_ulong, byref from ctypes import Structure, c_byte, c_ulong, byref
if sys.platform == 'cygwin': if sys.platform == 'cygwin':
@ -136,41 +144,41 @@ def _get_battery(pl):
('BatteryFullLifeTime', c_ulong) ('BatteryFullLifeTime', c_ulong)
] ]
def _get_capacity(pl): def _get_battery_status(pl):
powerclass = PowerClass() powerclass = PowerClass()
result = library_loader.kernel32.GetSystemPowerStatus(byref(powerclass)) result = library_loader.kernel32.GetSystemPowerStatus(byref(powerclass))
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa372693(v=vs.85).aspx # http://msdn.microsoft.com/en-us/library/windows/desktop/aa372693(v=vs.85).aspx
if result: if result:
return None return None
return powerclass.BatteryLifePercent return powerclass.BatteryLifePercent, powerclass.ACLineStatus == 1
if _get_capacity() is None: if _get_battery_status() is None:
pl.debug('Not using GetSystemPowerStatus because it failed') pl.debug('Not using GetSystemPowerStatus because it failed')
else: else:
pl.debug('Using GetSystemPowerStatus') pl.debug('Using GetSystemPowerStatus')
return _get_capacity return _get_battery_status
raise NotImplementedError raise NotImplementedError
def _get_capacity(pl): def _get_battery_status(pl):
global _get_capacity global _get_battery_status
def _failing_get_capacity(pl): def _failing_get_status(pl):
raise NotImplementedError raise NotImplementedError
try: try:
_get_capacity = _get_battery(pl) _get_battery_status = _fetch_battery_info(pl)
except NotImplementedError: except NotImplementedError:
_get_capacity = _failing_get_capacity _get_battery_status = _failing_get_status
except Exception as e: except Exception as e:
pl.exception('Exception while obtaining battery capacity getter: {0}', str(e)) pl.exception('Exception while obtaining battery status: {0}', str(e))
_get_capacity = _failing_get_capacity _get_battery_status = _failing_get_status
return _get_capacity(pl) return _get_battery_status(pl)
def battery(pl, format='{capacity:3.0%}', steps=5, gamify=False, full_heart='O', empty_heart='O'): 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. '''Return battery charge status.
:param str format: :param str format:
@ -189,21 +197,32 @@ def battery(pl, format='{capacity:3.0%}', steps=5, gamify=False, full_heart='O',
another gradient level and highlighting group, so it is OK for it to be 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 the same as full_heart as long as necessary highlighting groups are
defined. defined.
:param str online:
If computer is connected to a power supply this symbol is prepended to the segment.
:param str offline:
If computer is NOT connected to a power supply this symbol is prepended to the segment.
``battery_gradient`` and ``battery`` groups are used in any case, first is ``battery_gradient`` and ``battery`` groups are used in any case, first is
preferred. preferred.
Highlight groups used: ``battery_full`` or ``battery_gradient`` (gradient) or ``battery``, ``battery_empty`` or ``battery_gradient`` (gradient) or ``battery``. 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: try:
capacity = _get_capacity(pl) capacity, ac_powered = _get_battery_status(pl)
except NotImplementedError: except NotImplementedError:
pl.info('Unable to get battery capacity.') pl.info('Unable to get battery status.')
return None return None
ret = [] ret = []
if gamify: if gamify:
denom = int(steps) denom = int(steps)
numer = int(denom * capacity / 100) 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({ ret.append({
'contents': full_heart * numer, 'contents': full_heart * numer,
'draw_inner_divider': False, 'draw_inner_divider': False,
@ -220,7 +239,7 @@ def battery(pl, format='{capacity:3.0%}', steps=5, gamify=False, full_heart='O',
}) })
else: else:
ret.append({ ret.append({
'contents': format.format(capacity=(capacity / 100.0)), 'contents': format.format(ac_state=(online if ac_powered else offline), capacity=(capacity / 100.0)),
'highlight_groups': ['battery_gradient', 'battery'], 'highlight_groups': ['battery_gradient', 'battery'],
# Gradients are “least alert most alert” by default, capacity has # Gradients are “least alert most alert” by default, capacity has
# the opposite semantics. # the opposite semantics.

View File

@ -881,12 +881,12 @@ class TestBat(TestCommon):
def test_battery(self): def test_battery(self):
pl = Pl() pl = Pl()
def _get_capacity(pl): def _get_battery_status(pl):
return 86 return 86, False
with replace_attr(self.module, '_get_capacity', _get_capacity): with replace_attr(self.module, '_get_battery_status', _get_battery_status):
self.assertEqual(self.module.battery(pl=pl), [{ self.assertEqual(self.module.battery(pl=pl), [{
'contents': '86%', 'contents': ' 86%',
'highlight_groups': ['battery_gradient', 'battery'], 'highlight_groups': ['battery_gradient', 'battery'],
'gradient_level': 14, 'gradient_level': 14,
}]) }])
@ -896,11 +896,17 @@ class TestBat(TestCommon):
'gradient_level': 14, 'gradient_level': 14,
}]) }])
self.assertEqual(self.module.battery(pl=pl, steps=7), [{ self.assertEqual(self.module.battery(pl=pl, steps=7), [{
'contents': '86%', 'contents': ' 86%',
'highlight_groups': ['battery_gradient', 'battery'], 'highlight_groups': ['battery_gradient', 'battery'],
'gradient_level': 14, 'gradient_level': 14,
}]) }])
self.assertEqual(self.module.battery(pl=pl, gamify=True), [ self.assertEqual(self.module.battery(pl=pl, gamify=True), [
{
'contents': ' ',
'draw_inner_divider': False,
'highlight_groups': ['battery_offline', 'battery_ac_state', 'battery_gradient', 'battery'],
'gradient_level': 0
},
{ {
'contents': 'OOOO', 'contents': 'OOOO',
'draw_inner_divider': False, 'draw_inner_divider': False,
@ -915,6 +921,12 @@ class TestBat(TestCommon):
} }
]) ])
self.assertEqual(self.module.battery(pl=pl, gamify=True, full_heart='+', empty_heart='-', steps='10'), [ self.assertEqual(self.module.battery(pl=pl, gamify=True, full_heart='+', empty_heart='-', steps='10'), [
{
'contents': ' ',
'draw_inner_divider': False,
'highlight_groups': ['battery_offline', 'battery_ac_state', 'battery_gradient', 'battery'],
'gradient_level': 0
},
{ {
'contents': '++++++++', 'contents': '++++++++',
'draw_inner_divider': False, 'draw_inner_divider': False,
@ -929,6 +941,34 @@ class TestBat(TestCommon):
} }
]) ])
def test_battery_with_ac_online(self):
pl = Pl()
def _get_battery_status(pl):
return 86, True
with replace_attr(self.module, '_get_battery_status', _get_battery_status):
self.assertEqual(self.module.battery(pl=pl, online='C', offline=' '), [
{
'contents': 'C 86%',
'highlight_groups': ['battery_gradient', 'battery'],
'gradient_level': 14,
}])
def test_battery_with_ac_offline(self):
pl = Pl()
def _get_battery_status(pl):
return 86, False
with replace_attr(self.module, '_get_battery_status', _get_battery_status):
self.assertEqual(self.module.battery(pl=pl, online='C', offline=' '), [
{
'contents': ' 86%',
'highlight_groups': ['battery_gradient', 'battery'],
'gradient_level': 14,
}])
class TestVim(TestCase): class TestVim(TestCase):
def test_mode(self): def test_mode(self):