aboutsummaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
authorAdrian C. (anrxc) <anrxc@sysphere.org>2010-08-29 00:49:57 +0200
committerAdrian C. (anrxc) <anrxc@sysphere.org>2010-08-29 00:49:57 +0200
commit26b0395ba988948a7cc8b79d08faf6a57058886a (patch)
treeae475623e8b249aa8b8d29b57878737f09c165f5 /contrib
parent0d6333ed61a305c8a18fd4eb42ad33a353627bc9 (diff)
downloadvicious-legacy-26b0395ba988948a7cc8b79d08faf6a57058886a.tar.xz
contrib: imported contrib widgets
Diffstat (limited to 'contrib')
-rw-r--r--contrib/batacpi.lua51
-rw-r--r--contrib/batpmu.lua78
-rw-r--r--contrib/batproc.lua85
-rw-r--r--contrib/init.lua22
-rw-r--r--contrib/mpc.lua47
-rw-r--r--contrib/net.lua138
-rw-r--r--contrib/netcfg.lua34
-rw-r--r--contrib/ossvol.lua53
-rw-r--r--contrib/pulse.lua105
-rw-r--r--contrib/rss.lua67
-rw-r--r--contrib/sensors.lua68
11 files changed, 748 insertions, 0 deletions
diff --git a/contrib/batacpi.lua b/contrib/batacpi.lua
new file mode 100644
index 0000000..62156fd
--- /dev/null
+++ b/contrib/batacpi.lua
@@ -0,0 +1,51 @@
+---------------------------------------------------
+-- Licensed under the GNU General Public License v2
+-- * (c) 2010, Adrian C. <anrxc@sysphere.org>
+---------------------------------------------------
+
+-- {{{ Grab environment
+local tonumber = tonumber
+local io = { popen = io.popen }
+local setmetatable = setmetatable
+local table = { insert = table.insert }
+local string = { match = string.match }
+-- }}}
+
+
+-- Batacpi: provides state, charge, and remaining time for all batteries using acpitool
+module("vicious.contrib.batacpi")
+
+
+-- {{{ Battery widget type
+local function worker(format)
+ local battery_info = {}
+ local battery_state = {
+ ["full"] = "↯",
+ ["unknown"] = "⌁",
+ ["charged"] = "↯",
+ ["charging"] = "+",
+ ["discharging"] = "-"
+ }
+
+ -- Get data from acpitool
+ local f = io.popen("acpitool -b")
+
+ for line in f:lines() do
+ -- Check if the battery is present
+ if string.match(line, "^[%s]+Battery.*") then
+ -- Store state and charge information
+ table.insert(battery_info, (battery_state[string.match(line, "([%a]*),") or "unknown"]))
+ table.insert(battery_info, (tonumber(string.match(line, "([%d]?[%d]?[%d])%.")) or 0))
+ -- Store remaining time information
+ table.insert(battery_info, (string.match(line, "%%,%s(.*)") or "N/A"))
+ else
+ return {battery_state["unknown"], 0, "N/A"}
+ end
+ end
+ f:close()
+
+ return battery_info
+end
+-- }}}
+
+setmetatable(_M, { __call = function(_, ...) return worker(...) end })
diff --git a/contrib/batpmu.lua b/contrib/batpmu.lua
new file mode 100644
index 0000000..e84295e
--- /dev/null
+++ b/contrib/batpmu.lua
@@ -0,0 +1,78 @@
+---------------------------------------------------
+-- Licensed under the GNU General Public License v2
+-- * (c) 2010, Adrian C. <anrxc@sysphere.org>
+---------------------------------------------------
+
+-- {{{ Grab environment
+local tonumber = tonumber
+local io = { open = io.open }
+local setmetatable = setmetatable
+local math = {
+ min = math.min,
+ floor = math.floor
+}
+local string = {
+ find = string.find,
+ match = string.match,
+ format = string.format
+}
+-- }}}
+
+
+-- Batpmu: provides state, charge and remaining time for a requested battery using PMU
+module("vicious.contrib.batpmu")
+
+
+-- {{{ Battery widget type
+local function worker(format, batid)
+ local battery_state = {
+ ["full"] = "↯",
+ ["unknown"] = "⌁",
+ ["00000013"] = "+",
+ ["00000011"] = "-"
+ }
+
+ -- Get /proc/pmu/battery* state
+ local f = io.open("/proc/pmu/" .. batid)
+ -- Handler for incompetent users
+ if not f then return {battery_state["unknown"], 0, "N/A"} end
+ local statefile = f:read("*all")
+ f:close()
+
+ -- Get /proc/pmu/info data
+ local f = io.open("/proc/pmu/info")
+ local infofile = f:read("*all")
+ f:close()
+
+ -- Check if the battery is present
+ if infofile == nil or string.find(infofile, "Battery count[%s]+:[%s]0") then
+ return {battery_state["unknown"], 0, "N/A"}
+ end
+
+
+ -- Get capacity and charge information
+ local capacity = string.match(statefile, "max_charge[%s]+:[%s]([%d]+).*")
+ local remaining = string.match(statefile, "charge[%s]+:[%s]([%d]+).*")
+
+ -- Calculate percentage
+ local percent = math.min(math.floor(remaining / capacity * 100), 100)
+
+
+ -- Get timer information
+ local timer = string.match(statefile, "time rem%.[%s]+:[%s]([%d]+).*")
+ if timer == "0" then return {battery_state["full"], percent, "N/A"} end
+
+ -- Get state information
+ local state = string.match(statefile, "flags[%s]+:[%s]([%d]+).*")
+ local state = battery_state[state] or battery_state["unknown"]
+
+ -- Calculate remaining (charging or discharging) time
+ local hoursleft = math.floor(tonumber(timer) / 3600)
+ local minutesleft = math.floor((tonumber(timer) / 60) % 60)
+ local time = string.format("%02d:%02d", hoursleft, minutesleft)
+
+ return {state, percent, time}
+end
+-- }}}
+
+setmetatable(_M, { __call = function(_, ...) return worker(...) end })
diff --git a/contrib/batproc.lua b/contrib/batproc.lua
new file mode 100644
index 0000000..dac4d44
--- /dev/null
+++ b/contrib/batproc.lua
@@ -0,0 +1,85 @@
+---------------------------------------------------
+-- Licensed under the GNU General Public License v2
+-- * (c) 2010, Adrian C. <anrxc@sysphere.org>
+---------------------------------------------------
+
+-- {{{ Grab environment
+local tonumber = tonumber
+local io = { open = io.open }
+local setmetatable = setmetatable
+local math = {
+ min = math.min,
+ floor = math.floor
+}
+local string = {
+ find = string.find,
+ match = string.match,
+ format = string.format
+}
+-- }}}
+
+
+-- Batproc: provides state, charge, and remaining time for a requested battery using procfs
+module("vicious.contrib.batproc")
+
+
+-- {{{ Battery widget type
+local function worker(format, batid)
+ local battery_state = {
+ ["full"] = "↯",
+ ["unknown"] = "⌁",
+ ["charged"] = "↯",
+ ["charging"] = "+",
+ ["discharging"] = "-"
+ }
+
+ -- Get /proc/acpi/battery info
+ local f = io.open("/proc/acpi/battery/"..batid.."/info")
+ -- Handler for incompetent users
+ if not f then return {battery_state["unknown"], 0, "N/A"} end
+ local infofile = f:read("*all")
+ f:close()
+
+ -- Check if the battery is present
+ if infofile == nil or string.find(infofile, "present:[%s]+no") then
+ return {battery_state["unknown"], 0, "N/A"}
+ end
+
+ -- Get capacity information
+ local capacity = string.match(infofile, "last full capacity:[%s]+([%d]+).*")
+
+
+ -- Get /proc/acpi/battery state
+ local f = io.open("/proc/acpi/battery/"..batid.."/state")
+ local statefile = f:read("*all")
+ f:close()
+
+ -- Get state information
+ local state = string.match(statefile, "charging state:[%s]+([%a]+).*")
+ local state = battery_state[state] or battery_state["unknown"]
+
+ -- Get charge information
+ local rate = string.match(statefile, "present rate:[%s]+([%d]+).*")
+ local remaining = string.match(statefile, "remaining capacity:[%s]+([%d]+).*")
+
+
+ -- Calculate percentage (but work around broken BAT/ACPI implementations)
+ local percent = math.min(math.floor(remaining / capacity * 100), 100)
+
+ -- Calculate remaining (charging or discharging) time
+ if state == "+" then
+ timeleft = (tonumber(capacity) - tonumber(remaining)) / tonumber(rate)
+ elseif state == "-" then
+ timeleft = tonumber(remaining) / tonumber(rate)
+ else
+ return {state, percent, "N/A"}
+ end
+ local hoursleft = math.floor(timeleft)
+ local minutesleft = math.floor((timeleft - hoursleft) * 60 )
+ local time = string.format("%02d:%02d", hoursleft, minutesleft)
+
+ return {state, percent, time}
+end
+-- }}}
+
+setmetatable(_M, { __call = function(_, ...) return worker(...) end })
diff --git a/contrib/init.lua b/contrib/init.lua
new file mode 100644
index 0000000..8df46b5
--- /dev/null
+++ b/contrib/init.lua
@@ -0,0 +1,22 @@
+---------------------------------------------------
+-- Vicious widgets for the awesome window manager
+---------------------------------------------------
+-- Licensed under the GNU General Public License v2
+-- * (c) 2010, Adrian C. <anrxc@sysphere.org>
+---------------------------------------------------
+
+-- {{{ Configure widgets
+require("vicious.contrib.batacpi")
+require("vicious.contrib.batpmu")
+require("vicious.contrib.batproc")
+require("vicious.contrib.mpc")
+require("vicious.contrib.netcfg")
+require("vicious.contrib.net")
+require("vicious.contrib.ossvol")
+require("vicious.contrib.pulse")
+require("vicious.contrib.rss")
+require("vicious.contrib.sensors")
+-- }}}
+
+-- Vicious: widgets for the awesome window manager
+module("vicious.contrib")
diff --git a/contrib/mpc.lua b/contrib/mpc.lua
new file mode 100644
index 0000000..8f1f0a9
--- /dev/null
+++ b/contrib/mpc.lua
@@ -0,0 +1,47 @@
+---------------------------------------------------
+-- Licensed under the GNU General Public License v2
+-- * (c) 2010, Adrian C. <anrxc@sysphere.org>
+-- * (c) 2009, Lucas de Vries <lucas@glacicle.com>
+---------------------------------------------------
+
+-- {{{ Grab environment
+local type = type
+local io = { popen = io.popen }
+local setmetatable = setmetatable
+local string = { find = string.find }
+local helpers = require("vicious.helpers")
+-- }}}
+
+
+-- Mpc: provides the currently playing song in MPD
+module("vicious.contrib.mpc")
+
+
+-- {{{ MPC widget type
+local function worker(format, warg)
+ -- Get data from mpd
+ local f = io.popen("mpc")
+ local np = f:read("*line")
+ f:close()
+
+ -- Not installed,
+ if np == nil or -- off or stoppped.
+ (string.find(np, "MPD_HOST") or string.find(np, "volume:"))
+ then
+ return {"Stopped"}
+ end
+
+ -- Check if we should scroll, or maybe truncate
+ if warg then
+ if type(warg) == "table" then
+ np = helpers.scroll(np, warg[1], warg[2])
+ else
+ np = helpers.truncate(np, warg)
+ end
+ end
+
+ return {helpers.escape(np)}
+end
+-- }}}
+
+setmetatable(_M, { __call = function(_, ...) return worker(...) end })
diff --git a/contrib/net.lua b/contrib/net.lua
new file mode 100644
index 0000000..8f18604
--- /dev/null
+++ b/contrib/net.lua
@@ -0,0 +1,138 @@
+---------------------------------------------------
+-- Licensed under the GNU General Public License v2
+-- * (c) 2010, Adrian C. <anrxc@sysphere.org>
+-- * (c) 2009, Henning Glawe <glaweh@debian.org>
+-- * (c) 2009, Lucas de Vries <lucas@glacicle.com>
+---------------------------------------------------
+
+-- {{{ Grab environment
+local pairs = pairs
+local tonumber = tonumber
+local os = { time = os.time }
+local io = { lines = io.lines }
+local setmetatable = setmetatable
+local string = { match = string.match }
+local helpers = require("vicious.helpers")
+-- }}}
+
+
+-- Net: provides usage statistics for all network interfaces
+module("vicious.contrib.net")
+
+
+-- Initialise function tables
+local nets = {}
+-- Variable definitions
+local unit = { ["b"] = 1, ["kb"] = 1024,
+ ["mb"] = 1024^2, ["gb"] = 1024^3
+}
+
+-- {{{ Net widget type
+local function worker(format, tignorelist)
+ local args = {}
+ local tignore = {}
+ local total_rx = 0
+ local total_tx = 0
+ local any_up = 0
+
+ if not tignorelist then
+ tignorelist = {"lo", "wmaster0"}
+ end
+ for k, i in pairs(tignorelist) do
+ tignore[i] = true
+ end
+
+ -- Get NET stats
+ for line in io.lines("/proc/net/dev") do
+ -- Match wmaster0 as well as rt0 (multiple leading spaces)
+ local name = string.match(line, "^[%s]?[%s]?[%s]?[%s]?([%w]+):")
+ if name ~= nil then
+ -- Received bytes, first value after the name
+ local recv = tonumber(string.match(line, ":[%s]*([%d]+)"))
+ -- Transmited bytes, 7 fields from end of the line
+ local send = tonumber(string.match(line,
+ "([%d]+)%s+%d+%s+%d+%s+%d+%s+%d+%s+%d+%s+%d+%s+%d$"))
+
+ if not tignore[name] then
+ total_rx = total_rx + recv
+ total_tx = total_tx + send
+ end
+
+ helpers.uformat(args, name .. " rx", recv, unit)
+ helpers.uformat(args, name .. " tx", send, unit)
+
+ if nets[name] == nil then
+ -- Default values on the first run
+ nets[name] = {}
+
+ helpers.uformat(args, name .. " down", 0, unit)
+ helpers.uformat(args, name .. " up", 0, unit)
+ args["{"..name.." carrier}"] = 0
+
+ nets[name].time = os.time()
+ else -- Net stats are absolute, substract our last reading
+ local interval = os.time() - nets[name].time > 0 and
+ os.time() - nets[name].time or 1
+ nets[name].time = os.time()
+
+ local down = (recv - nets[name][1]) / interval
+ local up = (send - nets[name][2]) / interval
+
+ helpers.uformat(args, name .. " down", down, unit)
+ helpers.uformat(args, name .. " up", up, unit)
+
+ -- Carrier detection
+ sysnet = helpers.pathtotable("/sys/class/net/" .. name)
+
+ if sysnet.carrier then
+ ccarrier = tonumber(sysnet.carrier)
+
+ args["{"..name.." carrier}"] = ccarrier
+ if ccarrier ~= 0 and not tignore[name] then
+ any_up = 1
+ end
+ else
+ args["{"..name.." carrier}"] = 0
+ end
+ end
+
+ -- Store totals
+ nets[name][1] = recv
+ nets[name][2] = send
+ end
+ end
+
+ helpers.uformat(args, "total rx", total_rx, unit)
+ helpers.uformat(args, "total tx", total_tx, unit)
+
+ if nets["total"] == nil then
+ -- Default values on the first run
+ nets["total"] = {}
+
+ helpers.uformat(args, "total down", 0, unit)
+ helpers.uformat(args, "total up", 0, unit)
+ args["{total carrier}"] = 0
+
+ nets["total"].time = os.time()
+ else -- Net stats are absolute, substract our last reading
+ local interval = os.time() - nets["total"].time > 0 and
+ os.time() - nets["total"].time or 1
+ nets["total"].time = os.time()
+
+ local down = (total_rx - nets["total"][1]) / interval
+ local up = (total_tx - nets["total"][2]) / interval
+
+ helpers.uformat(args, "total down", down, unit)
+ helpers.uformat(args, "total up", up, unit)
+ args["{total carrier}"] = any_up
+ end
+
+ -- Store totals
+ nets["total"][1] = total_rx
+ nets["total"][2] = total_tx
+
+ return args
+end
+-- }}}
+
+setmetatable(_M, { __call = function(_, ...) return worker(...) end })
diff --git a/contrib/netcfg.lua b/contrib/netcfg.lua
new file mode 100644
index 0000000..fc22e8d
--- /dev/null
+++ b/contrib/netcfg.lua
@@ -0,0 +1,34 @@
+---------------------------------------------------
+-- Licensed under the GNU General Public License v2
+-- * (c) 2010, Radu A. <admiral0@tuxfamily.org>
+---------------------------------------------------
+
+-- {{{ Grab environment
+local io = { popen = io.popen }
+local setmetatable = setmetatable
+local table = { insert = table.insert }
+-- }}}
+
+
+-- Netcfg: provides active netcfg network profiles
+module("vicious.contrib.netcfg")
+
+
+-- {{{ Netcfg widget type
+local function worker(format)
+ -- Initialize counters
+ local profiles = {}
+
+ local f = io.popen("ls -1 /var/run/network/profiles")
+ for line in f:lines() do
+ if line ~= nil then
+ table.insert(profiles, line)
+ end
+ end
+ f:close()
+
+ return profiles
+end
+-- }}}
+
+setmetatable(_M, { __call = function(_, ...) return worker(...) end })
diff --git a/contrib/ossvol.lua b/contrib/ossvol.lua
new file mode 100644
index 0000000..eb14cb8
--- /dev/null
+++ b/contrib/ossvol.lua
@@ -0,0 +1,53 @@
+---------------------------------------------------
+-- Licensed under the GNU General Public License v2
+-- * (c) 2010, Adrian C. <anrxc@sysphere.org>
+---------------------------------------------------
+
+-- {{{ Grab environment
+local tonumber = tonumber
+local io = { popen = io.popen }
+local setmetatable = setmetatable
+local string = { match = string.match }
+-- }}}
+
+
+-- Ossvol: provides volume levels of requested OSS mixers
+module("vicious.contrib.ossvol")
+
+
+-- {{{ Volume widget type
+local function worker(format, warg)
+ if not warg then return end
+
+ local mixer_state = {
+ ["on"] = "♫", -- "",
+ ["off"] = "♩" -- "M"
+ }
+
+ -- Get mixer control contents
+ local f = io.popen("ossmix -c")
+ local mixer = f:read("*all")
+ f:close()
+
+ -- Capture mixer control state
+ local volu = tonumber(string.match(mixer, warg .. "[%s]([%d%.]+)"))/0.25
+ local mute = string.match(mixer, "vol%.mute[%s]([%a]+)")
+ -- Handle mixers without data
+ if volu == nil then
+ return {0, mixer_state["off"]}
+ end
+
+ -- Handle mixers without mute
+ if mute == "OFF" and volu == "0"
+ -- Handle mixers that are muted
+ or mute == "ON" then
+ mute = mixer_state["off"]
+ else
+ mute = mixer_state["on"]
+ end
+
+ return {volu, mute}
+end
+-- }}}
+
+setmetatable(_M, { __call = function(_, ...) return worker(...) end })
diff --git a/contrib/pulse.lua b/contrib/pulse.lua
new file mode 100644
index 0000000..5986265
--- /dev/null
+++ b/contrib/pulse.lua
@@ -0,0 +1,105 @@
+---------------------------------------------------
+-- Licensed under the GNU General Public License v2
+-- * (c) 2010, MrMagne <MrMagne@cerv.fr>
+---------------------------------------------------
+-- Usage example
+--
+-- -- Register widget
+-- vicious.register(vol, vicious.contrib.pulse, " $1%", 2, "alsa_output.pci-0000_00_1b.0.analog-stereo")
+-- -- Register buttons
+-- vol:buttons(awful.util.table.join(
+-- awful.button({ }, 1, function () awful.util.spawn("pavucontrol") end),
+-- awful.button({ }, 4, function () vicious.contrib.pulse.add(5,"alsa_output.pci-0000_00_1b.0.analog-stereo") end),
+-- awful.button({ }, 5, function () vicious.contrib.pulse.add(-5,"alsa_output.pci-0000_00_1b.0.analog-stereo") end)
+-- ))
+---------------------------------------------------
+
+-- {{{ Grab environment
+local type = type
+local tonumber = tonumber
+local io = { popen = io.popen }
+local setmetatable = setmetatable
+local os = { execute = os.execute }
+local table = { insert = table.insert }
+local string = {
+ find = string.find,
+ match = string.match,
+ format = string.format,
+ gmatch = string.gmatch
+}
+-- }}}
+
+
+-- Pulse: provides volume levels of requested pulseaudio sinks
+module("vicious.contrib.pulse")
+
+
+-- {{{ Helper function
+local function get_sink_name(sink)
+ -- If no sink is specified take the first one
+ if sink == nil then
+ local f = io.popen("pacmd list-sinks | grep name:")
+ local line = f:read("*all")
+ f:close()
+
+ sink = string.match(line, "<(.*)>")
+ -- If sink is an index, retrieve its name
+ elseif type(sink) == "number" then
+ local f = io.popen("pacmd list-sinks | grep name:")
+ local line = f:read("*all")
+ f:close()
+
+ local sinks = {}
+ for s in string.gmatch(line, "<(.*)>") do
+ table.insert(sinks, s)
+ end
+
+ sink = sinks[sink]
+ end
+
+ return sink
+end
+-- }}}
+
+-- {{{ Pulseaudio widget type
+local function worker(format, sink)
+ sink = get_sink_name(sink)
+ if sink == nil then return {0} end
+
+ -- Get sink data
+ local f = io.popen("pacmd dump | grep '\\(set-sink-volume " .. sink.."\\)\\|\\(set-sink-mute "..sink.."\\)'")
+ local data = f:read("*all")
+ f:close()
+
+ -- If mute return 0 (not "Mute") so we don't break progressbars
+ if string.match(data," (yes)\n$") then
+ return {0}
+ end
+
+ local vol = tonumber(string.match(data, "(0x[%x]+)"))
+ if vol == nil then vol = 0 end
+
+ return { vol/0x10000*100 }
+end
+-- }}}
+
+-- {{{ Volume control helper
+function add(percent, sink)
+ sink = get_sink_name(sink)
+ if sink == nil then return end
+
+ local f = io.popen("pacmd dump | grep 'set-sink-volume " .. sink.."'")
+ local data = f:read("*all")
+ f:close()
+
+ local initial_vol = tonumber(string.match(data, "(0x[%x]+)"))
+ local vol = initial_vol + percent/100*0x10000
+ if vol > 0x10000 then vol = 0x10000 end
+ if vol < 0 then vol = 0 end
+
+ local cmd = "pacmd set-sink-volume "..sink..string.format(" 0x%x", vol).." >/dev/null"
+ os.execute(cmd)
+end
+-- }}}
+
+setmetatable(_M, { __call = function(_, ...) return worker(...) end })
diff --git a/contrib/rss.lua b/contrib/rss.lua
new file mode 100644
index 0000000..bba1bf2
--- /dev/null
+++ b/contrib/rss.lua
@@ -0,0 +1,67 @@
+---------------------------------------------------
+-- Licensed under the GNU General Public License v2
+-- * (c) 2009, olcc
+--
+-- This is now a standalone RSS reader for awesome:
+-- * http://github.com/olcc/aware
+---------------------------------------------------
+
+-- {{{ Grab environment
+local pairs = pairs
+local io = { popen = io.popen }
+local setmetatable = setmetatable
+-- }}}
+
+
+-- RSS: provides latest world news
+module("vicious.contrib.rss")
+
+
+-- {{{ RSS widget type
+local function worker(format, input)
+ -- input: * feed - feed url
+ -- * object - entity to look for (typically: 'item')
+ -- * fields - fields to read (example: 'link', 'title', 'description')
+ -- output: * count - number of entities found
+ -- * one table for each field, containing wanted values
+ local feed = input.feed
+ local object = input.object
+ local fields = input.fields
+
+ -- Initialise tables
+ local out = {}
+
+ for _, v in pairs(fields) do
+ out[v] = {}
+ end
+
+ -- Initialise variables
+ local ob = nil
+ local i,j,k = 1, 1, 0
+ local curl = "curl -A 'Mozilla/4.0' -fsm 5 --connect-timeout 3 "
+
+ -- Get the feed
+ local f = io.popen(curl .. '"' .. feed .. '"')
+ local feed = f:read("*all")
+ f:close()
+
+ while true do
+ i, j, ob = feed.find(feed, "<" .. object .. ">(.-)</" .. object .. ">", i)
+ if not ob then break end
+
+ for _, v in pairs(fields) do
+ out[v][k] = ob:match("<" .. v .. ">(.*)</" .. v .. ">")
+ end
+
+ k = k+1
+ i = j+1
+ end
+
+ -- Update the entity count
+ out.count = k
+
+ return out
+end
+-- }}}
+
+setmetatable(_M, { __call = function(_, ...) return worker(...) end })
diff --git a/contrib/sensors.lua b/contrib/sensors.lua
new file mode 100644
index 0000000..45c7d9a
--- /dev/null
+++ b/contrib/sensors.lua
@@ -0,0 +1,68 @@
+---------------------------------------------------
+-- Licensed under the GNU General Public License v2
+-- * (c) 2010, Greg D. <jabbas@jabbas.pl>
+---------------------------------------------------
+
+-- {{{ Grab environment
+local tonumber = tonumber
+local io = { popen = io.popen }
+local setmetatable = setmetatable
+local table = { insert = table.insert }
+local string = {
+ gsub = string.gsub,
+ match = string.match
+}
+-- }}}
+
+
+-- Sensors: provides access to lm_sensors data
+module("vicious.contrib.sensors")
+
+
+-- {{{ Split helper function
+local function datasplit(str)
+ -- Splitting strings into associative array
+ -- with some magic to get the values right.
+ str = string.gsub(str, "\n", ":")
+
+ local tbl = {}
+ string.gsub(str, "([^:]*)", function (v)
+ if string.match(v, ".") then
+ table.insert(tbl, v)
+ end
+ end)
+
+ local assoc = {}
+ for c = 1, #tbl, 2 do
+ local k = string.gsub(tbl[c], ".*_", "")
+ local v = tonumber(string.match(tbl[c+1], "[%d]+"))
+ assoc[k] = v
+ end
+
+ return assoc
+end
+-- }}}
+
+-- {{{ Sensors widget type
+local function worker(format, warg)
+ -- Get data from all sensors
+ local f = io.popen("LANG=C sensors -uA")
+ local lm_sensors = f:read("*all")
+ f:close()
+
+ local sensor_data = string.gsub(
+ string.match(lm_sensors, warg..":\n(%s%s.-)\n[^ ]"), " ", "")
+
+ -- One of: crit, max
+ local divisor = "crit"
+ local s_data = datasplit(sensor_data)
+
+ if s_data[divisor] and s_data[divisor] > 0 then
+ s_data.percent = s_data.input / s_data[divisor] * 100
+ end
+
+ return {s_data.input, tonumber(s_data.percent)}
+end
+-- }}}
+
+setmetatable(_M, { __call = function(_, ...) return worker(...) end })