-------------------------------------------- -- 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