--------------------------------------------
-- Author: Gregor Best --
-- Copyright 2009, 2010, 2011 Gregor Best --
--------------------------------------------
local tostring = tostring
local setmetatable = setmetatable
local io = {
popen = io.popen
}
local capi = {
mouse = mouse
}
local math = {
floor = math.floor
}
local naughty = require("naughty")
local awful = require("awful")
local wibox = require("wibox")
local gears = require("gears")
local widget = wibox.widget.textbox()
local status_text = {
["charged"] = "↯",
["full"] = "↯",
["high"] = "↯",
["discharging"] = "▼",
["not connected"] = "▼",
["charging"] = "▲",
["unknown"] = "⌁"
}
local battery = { mt = {}, status_callback = nil }
-- {{{ upower backend
local tonumber = tonumber
local function defaults_to_key(t)
local function return_key__index(_, key)
return key
end
return setmetatable(t, { __index = return_key__index })
end
local function popen(cmd)
return io.popen('LC_ALL=C LANG=C ' .. cmd .. ' 2>/dev/null')
end
local function match_case(input, ...)
local args = { ... }
for i = 1, #args, 2 do
local pattern = args[i]
local action = args[i + 1]
local matches = { string.match(input, pattern) }
if matches[1] then
action(table.unpack(matches))
break
end
end
end
local upower_backend = { name = 'upower' }
local upower_status_mapping = defaults_to_key {
['fully-charged'] = 'charged',
}
function upower_backend:clone(clone)
return setmetatable(clone or {}, { __index = self })
end
function upower_backend:configure()
local fd, err = popen('upower -e')
if not fd then
return nil, err
end
local battery_filenames = {}
for line in fd:lines() do
if line:match('battery_BAT') then
battery_filenames[#battery_filenames + 1] = line
end
end
fd:close()
if #battery_filenames > 0 then
return upower_backend:clone { filenames = battery_filenames }
end
end
function upower_backend:state()
local results = {}
for i = 1, #self.filenames do
local filename = self.filenames[i]
local rv = {}
local fd, err = popen('upower -i ' .. filename)
if not fd then
return nil, err
end
local function handle_time(time, units)
if time == 'unknown' then
time = nil
elseif units == 'hour' or units == 'hours' then
time = math.floor(time * 60)
elseif units == 'minute' or units == 'minutes' then
time = tonumber(math.floor(time))
else
time = 0
end
rv.time = time
end
for line in fd:lines() do
match_case(line,
'^%s*percentage:%s*(%d+)', function(charge)
rv.charge = tonumber(charge)
end,
'%s*time to empty:%s*(%S+)%s*(%w*)', handle_time,
'%s*time to full:%s*(%S+)%s*(%w*)', handle_time,
'state:%s*(%S+)', function(status)
rv.status = upower_status_mapping[status]
end)
end
fd:close()
results[i] = rv
end
return table.unpack(results)
end
function upower_backend:details()
local results = ''
for i = 1, #self.filenames do
local filename = self.filenames[i]
local pipe, err = popen('upower -i ' .. filename)
if not pipe then
return nil, err
end
local output = pipe:read '*a'
results = results .. '\n' .. output:gsub('^\n', '')
end
return results
end
-- }}}
local backend = upower_backend:configure()
local inverted = false
local show_time = false
local cycle_count = 0
local previous_state
local function update(force)
if force then
cycle_count = 0
end
if cycle_count == 0 then
previous_state = { backend:state() }
end
cycle_count = (cycle_count + 1) % 60
local bats = previous_state
if #bats == 0 then
widget:set_markup("no data")
return
end
local markup = ''
for i = 1, #bats do
local bat = bats[i]
local color
local blinking = false
local charge = bat.charge
if charge == nil then
color = '#900000'
charge = 'Unknown charge'
elseif charge >= 60 then
color = '#009000'
elseif charge > 35 then
color = '#909000'
else
color = '#900000'
blinking = bat.charge <= 20 and (bat.status == 'discharging' or bat.status == 'not connected')
end
local status = status_text[bat.status] or 'unknown'
local battery_status
if battery.status_callback then
battery.status_callback(bat)
end
if blinking and inverted then
battery_status = status .. ' ' .. awful.util.escape(tostring(bat.charge)) .. '%'
else
battery_status = '' .. tostring(status) .. ''.. ' ' .. awful.util.escape(tostring(bat.charge)) .. '%'
end
if bat.time and show_time then
local hours = math.floor(bat.time / 60)
local minutes = bat.time % 60
battery_status = battery_status .. ' ' .. awful.util.escape(string.format('%02d:%02d', hours, minutes))
end
if blinking and inverted then
battery_status = '' .. tostring(battery_status) .. ''
end
inverted = not inverted
if i == 1 then
markup = markup .. battery_status
else
markup = markup .. ' ' .. battery_status
end
end
widget:set_markup(markup)
end
local function detail ()
local details = backend:details()
if not details then
details = 'no details available'
end
naughty.notify({
text = details,
screen = capi.mouse.screen,
})
update(true)
end
function get_data()
local bats = { backend:state() }
if bats then
return bats[1]
end
end
widget:buttons(awful.util.table.join(
awful.button({ }, 1, detail)
))
return setmetatable(battery, { __call = function ()
update()
gears.timer({
timeout=30,
autostart=true,
callback=update,
})
return widget
end })
-- vim:ft=lua:ts=2:sw=2:sts=2:tw=80:et