summaryrefslogtreecommitdiff
path: root/lain/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'lain/widgets')
-rw-r--r--lain/widgets/abase.lua42
-rw-r--r--lain/widgets/alsa.lua68
-rw-r--r--lain/widgets/alsabar.lua174
-rw-r--r--lain/widgets/base.lua40
-rw-r--r--lain/widgets/bat.lua149
-rw-r--r--lain/widgets/borderbox.lua62
-rw-r--r--lain/widgets/calendar.lua131
-rw-r--r--lain/widgets/contrib/ccurr.lua82
-rw-r--r--lain/widgets/contrib/init.lua19
-rw-r--r--lain/widgets/contrib/moc.lua102
-rw-r--r--lain/widgets/contrib/redshift.lua79
-rw-r--r--lain/widgets/contrib/task.lua133
-rw-r--r--lain/widgets/contrib/tpbat/init.lua170
-rw-r--r--lain/widgets/contrib/tpbat/smapi.lua102
-rw-r--r--lain/widgets/cpu.lua77
-rw-r--r--lain/widgets/fs.lua117
-rw-r--r--lain/widgets/imap.lua93
-rw-r--r--lain/widgets/init.lua20
-rw-r--r--lain/widgets/maildir.lua98
-rw-r--r--lain/widgets/mem.lua59
-rw-r--r--lain/widgets/mpd.lua113
-rw-r--r--lain/widgets/net.lua109
-rw-r--r--lain/widgets/sysload.lua45
-rw-r--r--lain/widgets/temp.lua48
-rw-r--r--lain/widgets/weather.lua130
25 files changed, 2262 insertions, 0 deletions
diff --git a/lain/widgets/abase.lua b/lain/widgets/abase.lua
new file mode 100644
index 0000000..8ffdf0e
--- /dev/null
+++ b/lain/widgets/abase.lua
@@ -0,0 +1,42 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2014, Luke Bonham
+
+--]]
+
+local newtimer = require("lain.helpers").newtimer
+local async = require("lain.asyncshell")
+local wibox = require("wibox")
+
+local setmetatable = setmetatable
+
+-- Basic template for custom widgets
+-- Asynchronous version
+-- lain.widgets.abase
+
+local function worker(args)
+ local abase = {}
+ local args = args or {}
+ local timeout = args.timeout or 5
+ local cmd = args.cmd or ""
+ local settings = args.settings or function() end
+
+ abase.widget = wibox.widget.textbox('')
+
+ function abase.update()
+ async.request(cmd, function(f)
+ output = f:read("*a")
+ f:close()
+ widget = abase.widget
+ settings()
+ end)
+ end
+
+ newtimer(cmd, timeout, abase.update)
+
+ return setmetatable(abase, { __index = abase.widget })
+end
+
+return setmetatable({}, { __call = function(_, ...) return worker(...) end })
diff --git a/lain/widgets/alsa.lua b/lain/widgets/alsa.lua
new file mode 100644
index 0000000..a356892
--- /dev/null
+++ b/lain/widgets/alsa.lua
@@ -0,0 +1,68 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luke Bonham
+ * (c) 2010, Adrian C. <anrxc@sysphere.org>
+
+--]]
+
+local newtimer = require("lain.helpers").newtimer
+
+local wibox = require("wibox")
+
+local io = { popen = io.popen }
+local string = { match = string.match,
+ format = string.format }
+
+local setmetatable = setmetatable
+
+-- ALSA volume
+-- lain.widgets.alsa
+local alsa = {}
+
+local function worker(args)
+ local args = args or {}
+ local timeout = args.timeout or 5
+ local settings = args.settings or function() end
+
+ alsa.cmd = args.cmd or "amixer"
+ alsa.channel = args.channel or "Master"
+
+ alsa.widget = wibox.widget.textbox('')
+
+ function alsa.update()
+ local f = assert(io.popen(string.format("%s get %s", alsa.cmd, alsa.channel)))
+ local mixer = f:read("*a")
+ f:close()
+
+ volume_now = {}
+
+ volume_now.level, volume_now.status = string.match(mixer, "([%d]+)%%.*%[([%l]*)")
+
+ if volume_now.level == nil
+ then
+ volume_now.level = "0"
+ volume_now.status = "off"
+ end
+
+ if volume_now.status == ""
+ then
+ if volume_now.level == "0"
+ then
+ volume_now.status = "off"
+ else
+ volume_now.status = "on"
+ end
+ end
+
+ widget = alsa.widget
+ settings()
+ end
+
+ newtimer("alsa", timeout, alsa.update)
+
+ return setmetatable(alsa, { __index = alsa.widget })
+end
+
+return setmetatable(alsa, { __call = function(_, ...) return worker(...) end })
diff --git a/lain/widgets/alsabar.lua b/lain/widgets/alsabar.lua
new file mode 100644
index 0000000..1421975
--- /dev/null
+++ b/lain/widgets/alsabar.lua
@@ -0,0 +1,174 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luke Bonham
+ * (c) 2013, Rman
+
+--]]
+
+local newtimer = require("lain.helpers").newtimer
+
+local awful = require("awful")
+local beautiful = require("beautiful")
+local naughty = require("naughty")
+
+local io = { popen = io.popen }
+local math = { modf = math.modf }
+local string = { format = string.format,
+ match = string.match,
+ rep = string.rep }
+local tonumber = tonumber
+
+local setmetatable = setmetatable
+
+-- ALSA volume bar
+-- lain.widgets.alsabar
+local alsabar = {
+ card = "0",
+ channel = "Master",
+ step = "2%",
+
+ colors = {
+ background = beautiful.bg_normal,
+ mute = "#EB8F8F",
+ unmute = "#A4CE8A"
+ },
+
+ terminal = terminal or "xterm",
+ mixer = terminal .. " -e alsamixer",
+
+ notifications = {
+ font = beautiful.font:sub(beautiful.font:find(""), beautiful.font:find(" ")),
+ font_size = "11",
+ color = beautiful.fg_normal,
+ bar_size = 18,
+ screen = 1
+ },
+
+ _current_level = 0,
+ _muted = false
+}
+
+function alsabar.notify()
+ alsabar.update()
+
+ local preset = {
+ title = "",
+ text = "",
+ timeout = 4,
+ screen = alsabar.notifications.screen,
+ font = alsabar.notifications.font .. " " ..
+ alsabar.notifications.font_size,
+ fg = alsabar.notifications.color
+ }
+
+ if alsabar._muted
+ then
+ preset.title = alsabar.channel .. " - Muted"
+ else
+ preset.title = alsabar.channel .. " - " .. alsabar._current_level .. "%"
+ end
+
+ int = math.modf((alsabar._current_level / 100) * alsabar.notifications.bar_size)
+ preset.text = "["
+ .. string.rep("|", int)
+ .. string.rep(" ", alsabar.notifications.bar_size - int)
+ .. "]"
+
+ if alsabar._notify ~= nil then
+ alsabar._notify = naughty.notify ({
+ replaces_id = alsabar._notify.id,
+ preset = preset,
+ })
+ else
+ alsabar._notify = naughty.notify ({
+ preset = preset,
+ })
+ end
+end
+
+local function worker(args)
+ local args = args or {}
+ local timeout = args.timeout or 4
+ local settings = args.settings or function() end
+ local width = args.width or 63
+ local height = args.heigth or 1
+ local ticks = args.ticks or false
+ local ticks_size = args.ticks_size or 7
+ local vertical = args.vertical or false
+
+ alsabar.cmd = args.cmd or "amixer"
+ alsabar.channel = args.channel or alsabar.channel
+ alsabar.step = args.step or alsabar.step
+ alsabar.colors = args.colors or alsabar.colors
+ alsabar.notifications = args.notifications or alsabar.notifications
+
+ alsabar.bar = awful.widget.progressbar()
+
+ alsabar.bar:set_background_color(alsabar.colors.background)
+ alsabar.bar:set_color(alsabar.colors.unmute)
+ alsabar.tooltip = awful.tooltip({ objects = { alsabar.bar } })
+ alsabar.bar:set_width(width)
+ alsabar.bar:set_height(height)
+ alsabar.bar:set_ticks(ticks)
+ alsabar.bar:set_ticks_size(ticks_size)
+ alsabar.bar:set_vertical(vertical)
+
+ function alsabar.update()
+ -- Get mixer control contents
+ local f = assert(io.popen(string.format("%s get %s", alsabar.cmd, alsabar.channel)))
+ local mixer = f:read("*a")
+ f:close()
+
+ -- Capture mixer control state: [5%] ... ... [on]
+ local volu, mute = string.match(mixer, "([%d]+)%%.*%[([%l]*)")
+
+ if volu == nil then
+ volu = 0
+ mute = "off"
+ end
+
+ alsabar._current_level = tonumber(volu)
+ alsabar.bar:set_value(alsabar._current_level / 100)
+ if not mute and tonumber(volu) == 0 or mute == "off"
+ then
+ alsabar._muted = true
+ alsabar.tooltip:set_text (" [Muted] ")
+ alsabar.bar:set_color(alsabar.colors.mute)
+ else
+ alsabar._muted = false
+ alsabar.tooltip:set_text(string.format(" %s:%s ", alsabar.channel, volu))
+ alsabar.bar:set_color(alsabar.colors.unmute)
+ end
+
+ volume_now = {}
+ volume_now.level = tonumber(volu)
+ volume_now.status = mute
+ settings()
+ end
+
+ newtimer("alsabar", timeout, alsabar.update)
+
+ alsabar.bar:buttons (awful.util.table.join (
+ awful.button ({}, 1, function()
+ awful.util.spawn(alsabar.mixer)
+ end),
+ awful.button ({}, 3, function()
+ awful.util.spawn(string.format("amixer -c %s set %s toggle", alsabar.card, alsabar.channel))
+ alsabar.update()
+ end),
+ awful.button ({}, 4, function()
+ awful.util.spawn(string.format("amixer -c %s set %s %s+", alsabar.card, alsabar.channel, alsabar.step))
+ alsabar.update()
+ end),
+ awful.button ({}, 5, function()
+ awful.util.spawn(string.format("amixer -c %s set %s %s-", alsabar.card, alsabar.channel, alsabar.step))
+ alsabar.update()
+ end)
+ ))
+
+ return alsabar
+end
+
+return setmetatable(alsabar, { __call = function(_, ...) return worker(...) end })
diff --git a/lain/widgets/base.lua b/lain/widgets/base.lua
new file mode 100644
index 0000000..39b0863
--- /dev/null
+++ b/lain/widgets/base.lua
@@ -0,0 +1,40 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2014, Luke Bonham
+
+--]]
+
+local newtimer = require("lain.helpers").newtimer
+local wibox = require("wibox")
+
+local io = { popen = io.popen }
+local setmetatable = setmetatable
+
+-- Basic template for custom widgets
+-- lain.widgets.base
+
+local function worker(args)
+ local base = {}
+ local args = args or {}
+ local timeout = args.timeout or 5
+ local cmd = args.cmd or ""
+ local settings = args.settings or function() end
+
+ base.widget = wibox.widget.textbox('')
+
+ function base.update()
+ local f = assert(io.popen(cmd))
+ output = f:read("*a")
+ f:close()
+ widget = base.widget
+ settings()
+ end
+
+ newtimer(cmd, timeout, base.update)
+
+ return setmetatable(base, { __index = base.widget })
+end
+
+return setmetatable({}, { __call = function(_, ...) return worker(...) end })
diff --git a/lain/widgets/bat.lua b/lain/widgets/bat.lua
new file mode 100644
index 0000000..572d099
--- /dev/null
+++ b/lain/widgets/bat.lua
@@ -0,0 +1,149 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luke Bonham
+ * (c) 2010-2012, Peter Hofmann
+
+--]]
+
+local newtimer = require("lain.helpers").newtimer
+local first_line = require("lain.helpers").first_line
+
+local naughty = require("naughty")
+local wibox = require("wibox")
+
+local math = { floor = math.floor }
+local string = { format = string.format }
+local tonumber = tonumber
+
+local setmetatable = setmetatable
+
+-- Battery infos
+-- lain.widgets.bat
+local bat = {}
+
+local function worker(args)
+ local args = args or {}
+ local timeout = args.timeout or 30
+ local battery = args.battery or "BAT0"
+ local notify = args.notify or "on"
+ local settings = args.settings or function() end
+
+ bat.widget = wibox.widget.textbox('')
+
+ bat_notification_low_preset = {
+ title = "Battery low",
+ text = "Plug the cable!",
+ timeout = 15,
+ fg = "#202020",
+ bg = "#CDCDCD"
+ }
+
+ bat_notification_critical_preset = {
+ title = "Battery exhausted",
+ text = "Shutdown imminent",
+ timeout = 15,
+ fg = "#000000",
+ bg = "#FFFFFF"
+ }
+
+ function update()
+ bat_now = {
+ status = "Not present",
+ perc = "N/A",
+ time = "N/A",
+ watt = "N/A"
+ }
+
+ local bstr = "/sys/class/power_supply/" .. battery
+
+ local present = first_line(bstr .. "/present")
+
+ if present == "1"
+ then
+ local rate = first_line(bstr .. "/power_now") or
+ first_line(bstr .. "/current_now")
+
+ local ratev = first_line(bstr .. "/voltage_now")
+
+ local rem = first_line(bstr .. "/energy_now") or
+ first_line(bstr .. "/charge_now")
+
+ local tot = first_line(bstr .. "/energy_full") or
+ first_line(bstr .. "/charge_full")
+
+ bat_now.status = first_line(bstr .. "/status") or "N/A"
+
+ rate = tonumber(rate) or 1
+ ratev = tonumber(ratev)
+ rem = tonumber(rem)
+ tot = tonumber(tot)
+
+ local time_rat = 0
+ if bat_now.status == "Charging"
+ then
+ time_rat = (tot - rem) / rate
+ elseif bat_now.status == "Discharging"
+ then
+ time_rat = rem / rate
+ end
+
+ local hrs = math.floor(time_rat)
+ if hrs < 0 then hrs = 0 elseif hrs > 23 then hrs = 23 end
+
+ local min = math.floor((time_rat - hrs) * 60)
+ if min < 0 then min = 0 elseif min > 59 then min = 59 end
+
+ bat_now.time = string.format("%02d:%02d", hrs, min)
+
+ bat_now.perc = first_line(bstr .. "/capacity")
+
+ if not bat_now.perc then
+ local perc = (rem / tot) * 100
+ if perc <= 100 then
+ bat_now.perc = string.format("%d", perc)
+ elseif perc > 100 then
+ bat_now.perc = "100"
+ elseif perc < 0 then
+ bat_now.perc = "0"
+ end
+ end
+
+ if rate ~= nil and ratev ~= nil then
+ bat_now.watt = string.format("%.2fW", (rate * ratev) / 1e12)
+ else
+ bat_now.watt = "N/A"
+ end
+
+ end
+
+ widget = bat.widget
+ settings()
+
+ -- notifications for low and critical states
+ if bat_now.status == "Discharging" and notify == "on" and bat_now.perc ~= nil
+ then
+ local nperc = tonumber(bat_now.perc) or 100
+ if nperc <= 5
+ then
+ bat.id = naughty.notify({
+ preset = bat_notification_critical_preset,
+ replaces_id = bat.id,
+ }).id
+ elseif nperc <= 15
+ then
+ bat.id = naughty.notify({
+ preset = bat_notification_low_preset,
+ replaces_id = bat.id,
+ }).id
+ end
+ end
+ end
+
+ newtimer("bat", timeout, update)
+
+ return bat.widget
+end
+
+return setmetatable(bat, { __call = function(_, ...) return worker(...) end })
diff --git a/lain/widgets/borderbox.lua b/lain/widgets/borderbox.lua
new file mode 100644
index 0000000..cce8517
--- /dev/null
+++ b/lain/widgets/borderbox.lua
@@ -0,0 +1,62 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luke Bonham
+ * (c) 2010-2012, Peter Hofmann
+
+--]]
+
+local wibox = require("awful.wibox")
+
+local setmetatable = setmetatable
+
+-- Creates a thin wibox at a position relative to another wibox
+-- lain.widgets.borderbox
+local borderbox = {}
+
+local function worker(relbox, s, args)
+ local where = args.position or 'top'
+ local color = args.color or '#FFFFFF'
+ local size = args.size or 1
+ local box = nil
+ local wiboxarg = {
+ position = nil,
+ bg = color
+ }
+
+ if where == 'top'
+ then
+ wiboxarg.width = relbox.width
+ wiboxarg.height = size
+ box = wibox(wiboxarg)
+ box.x = relbox.x
+ box.y = relbox.y - size
+ elseif where == 'bottom'
+ then
+ wiboxarg.width = relbox.width
+ wiboxarg.height = size
+ box = wibox(wiboxarg)
+ box.x = relbox.x
+ box.y = relbox.y + relbox.height
+ elseif where == 'left'
+ then
+ wiboxarg.width = size
+ wiboxarg.height = relbox.height
+ box = wibox(wiboxarg)
+ box.x = relbox.x - size
+ box.y = relbox.y
+ elseif where == 'right'
+ then
+ wiboxarg.width = size
+ wiboxarg.height = relbox.height
+ box = wibox(wiboxarg)
+ box.x = relbox.x + relbox.width
+ box.y = relbox.y
+ end
+
+ box.screen = s
+ return box
+end
+
+return setmetatable(borderbox, { __call = function(_, ...) return worker(...) end })
diff --git a/lain/widgets/calendar.lua b/lain/widgets/calendar.lua
new file mode 100644
index 0000000..3e65f38
--- /dev/null
+++ b/lain/widgets/calendar.lua
@@ -0,0 +1,131 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luke Bonham
+
+--]]
+
+local icons_dir = require("lain.helpers").icons_dir
+
+local awful = require("awful")
+local beautiful = require("beautiful")
+local naughty = require("naughty")
+
+local io = { popen = io.popen }
+local os = { date = os.date }
+local tonumber = tonumber
+
+local setmetatable = setmetatable
+
+-- Calendar notification
+-- lain.widgets.calendar
+local calendar = {}
+local cal_notification = nil
+
+function calendar:hide()
+ if cal_notification ~= nil then
+ naughty.destroy(cal_notification)
+ cal_notification = nil
+ end
+end
+
+function calendar:show(t_out, inc_offset, scr)
+ calendar:hide()
+
+ local offs = inc_offset or 0
+ local tims = t_out or 0
+ local f, c_text
+ local today = tonumber(os.date('%d'))
+ local init_t = calendar.cal .. ' ' .. calendar.post_cal .. ' ' ..
+ ' | sed -r -e "s/_\\x08//g" | sed -r -e "s/(^| )('
+
+ calendar.offset = calendar.offset + offs
+
+ if offs == 0 or calendar.offset == 0
+ then -- current month showing, today highlighted
+ calendar.offset = 0
+ calendar.notify_icon = calendar.icons .. today .. ".png"
+
+ -- bg and fg inverted to highlight today
+ f = io.popen( init_t .. today ..
+ ')($| )/\\1<b><span foreground=\\"'
+ .. calendar.bg ..
+ '\\" background=\\"'
+ .. calendar.fg ..
+ '\\">\\2<\\/span><\\/b>\\3/"' )
+
+ else -- no current month showing, no day to highlight
+ local month = tonumber(os.date('%m'))
+ local year = tonumber(os.date('%Y'))
+
+ month = month + calendar.offset
+
+ if month > 12 then
+ month = month % 12
+ year = year + 1
+ if month <= 0 then
+ month = 12
+ end
+ elseif month < 1 then
+ month = month + 12
+ year = year - 1
+ if month <= 0 then
+ month = 1
+ end
+ end
+
+ calendar.notify_icon = nil
+
+ f = io.popen(calendar.cal .. ' ' .. month .. ' ' .. year .. ' ' ..
+ calendar.post_cal)
+ end
+
+ c_text = "<tt><span font='" .. calendar.font .. " "
+ .. calendar.font_size .. "'><b>"
+ .. f:read() .. "</b>\n\n"
+ .. f:read() .. "\n"
+ .. f:read("*a"):gsub("\n*$", "")
+ .. "</span></tt>"
+ f:close()
+
+ cal_notification = naughty.notify({
+ text = c_text,
+ icon = calendar.notify_icon,
+ position = calendar.position,
+ fg = calendar.fg,
+ bg = calendar.bg,
+ timeout = tims,
+ screen = scr or 1
+ })
+end
+
+function calendar:attach(widget, args)
+ local args = args or {}
+ calendar.cal = args.cal or "/usr/bin/cal"
+ calendar.post_cal = args.post_cal or ""
+ calendar.icons = args.icons or icons_dir .. "cal/white/"
+ calendar.font = args.font or beautiful.font:sub(beautiful.font:find(""),
+ beautiful.font:find(" "))
+ calendar.font_size = tonumber(args.font_size) or 11
+ calendar.fg = args.fg or beautiful.fg_normal or "#FFFFFF"
+ calendar.bg = args.bg or beautiful.bg_normal or "#FFFFFF"
+ calendar.position = args.position or "top_right"
+ calendar.scr_pos = args.scr_pos or 1
+
+ calendar.offset = 0
+ calendar.notify_icon = nil
+
+ widget:connect_signal("mouse::enter", function () calendar:show(0, 0, scr_pos) end)
+ widget:connect_signal("mouse::leave", function () calendar:hide() end)
+ widget:buttons(awful.util.table.join( awful.button({ }, 1, function ()
+ calendar:show(0, -1, scr_pos) end),
+ awful.button({ }, 3, function ()
+ calendar:show(0, 1, scr_pos) end),
+ awful.button({ }, 4, function ()
+ calendar:show(0, -1, scr_pos) end),
+ awful.button({ }, 5, function ()
+ calendar:show(0, 1, scr_pos) end)))
+end
+
+return setmetatable(calendar, { __call = function(_, ...) return create(...) end })
diff --git a/lain/widgets/contrib/ccurr.lua b/lain/widgets/contrib/ccurr.lua
new file mode 100644
index 0000000..980e19b
--- /dev/null
+++ b/lain/widgets/contrib/ccurr.lua
@@ -0,0 +1,82 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2014, Aaron Lebo
+
+--]]
+
+local newtimer = require("lain.helpers").newtimer
+local json = require("lain.util").dkjson
+
+local wibox = require("wibox")
+
+local string = { format = string.format }
+local tonumber = tonumber
+
+-- Crypto currencies widget
+-- lain.widgets.contrib.ccurr
+local ccurr = {}
+
+-- Currently gets
+-- * BTC/USD
+-- * DOGE/USD
+-- using Coinbase and Cryptsy APIs.
+
+-- requires http://dkolf.de/src/dkjson-lua.fsl/home
+-- based upon http://awesome.naquadah.org/wiki/Bitcoin_Price_Widget
+
+local function get(url)
+ local f = io.popen('curl -m 5 -s "' .. url .. '"')
+ if not f then
+ return 0
+ else
+ local s = f:read("*all")
+ f:close()
+ return s
+ end
+end
+
+local function parse(j)
+ local obj, pos, err = json.decode(j, 1, nil)
+ if err then
+ return nil
+ else
+ return obj
+ end
+end
+
+local function worker(args)
+ local args = args or {}
+ local timeout = args.timeout or 600
+ local btc_url = args.btc_url or "https://coinbase.com/api/v1/prices/buy"
+ local doge_url = args.doge_url or "http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=132"
+ local settings = args.settings or function() end
+
+ ccurr.widget = wibox.widget.textbox('')
+
+ local function update()
+ price_now = {
+ btc = "N/A",
+ doge = "N/A"
+ }
+
+ btc = parse(get(btc_url))
+ doge = parse(get(doge_url))
+
+ if btc and doge then
+ price_now.btc = tonumber(btc["subtotal"]["amount"])
+ price_now.doge = tonumber(doge["return"]["markets"]["DOGE"]["lasttradeprice"])
+ price_now.doge = string.format("%.4f", price_now.btc * price_now.doge)
+ end
+
+ widget = ccurr.widget
+ settings()
+ end
+
+ newtimer("ccurr", timeout, update)
+
+ return ccurr.widget
+end
+
+return setmetatable(ccurr, { __call = function(_, ...) return worker(...) end })
diff --git a/lain/widgets/contrib/init.lua b/lain/widgets/contrib/init.lua
new file mode 100644
index 0000000..ccaed82
--- /dev/null
+++ b/lain/widgets/contrib/init.lua
@@ -0,0 +1,19 @@
+
+--[[
+
+ Lain
+ Layouts, widgets and utilities for Awesome WM
+
+ Users contributed widgets section
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luke Bonham
+
+--]]
+
+local wrequire = require("lain.helpers").wrequire
+local setmetatable = setmetatable
+
+local widgets = { _NAME = "lain.widgets.contrib" }
+
+return setmetatable(widgets, { __index = wrequire })
diff --git a/lain/widgets/contrib/moc.lua b/lain/widgets/contrib/moc.lua
new file mode 100644
index 0000000..cfdbec7
--- /dev/null
+++ b/lain/widgets/contrib/moc.lua
@@ -0,0 +1,102 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2014, anticlockwise <http://github.com/anticlockwise>
+
+--]]
+
+local helpers = require("lain.helpers")
+local async = require("lain.asyncshell")
+
+local escape_f = require("awful.util").escape
+local naughty = require("naughty")
+local wibox = require("wibox")
+
+local io = { popen = io.popen }
+local os = { execute = os.execute,
+ getenv = os.getenv }
+local string = { format = string.format,
+ gmatch = string.gmatch }
+
+local setmetatable = setmetatable
+
+local moc = {}
+
+local function worker(args)
+ local args = args or {}
+ local timeout = args.timeout or 2
+ local music_dir = args.music_dir or os.getenv("HOME") .. "/Music"
+ local cover_size = args.cover_size or 100
+ local default_art = args.default_art or ""
+ local settings = args.settings or function() end
+
+ local mpdcover = helpers.scripts_dir .. "mpdcover"
+
+ moc.widget = wibox.widget.textbox('')
+
+ moc_notification_preset = {
+ title = "Now playing",
+ timeout = 6
+ }
+
+ helpers.set_map("current moc track", nil)
+
+ function moc.update()
+ -- mocp -i will produce output like:
+ -- Artist: Travis
+ -- Album: The Man Who
+ -- etc.
+ async.request("mocp -i", function(f)
+ moc_now = {
+ state = "N/A",
+ file = "N/A",
+ artist = "N/A",
+ title = "N/A",
+ album = "N/A",
+ elapsed = "N/A",
+ total = "N/A"
+ }
+
+ for line in f:lines() do
+ for k, v in string.gmatch(line, "([%w]+):[%s](.*)$") do
+ if k == "State" then moc_now.state = v
+ elseif k == "File" then moc_now.file = v
+ elseif k == "Artist" then moc_now.artist = escape_f(v)
+ elseif k == "SongTitle" then moc_now.title = escape_f(v)
+ elseif k == "Album" then moc_now.album = escape_f(v)
+ elseif k == "CurrentTime" then moc_now.elapsed = escape_f(v)
+ elseif k == "TotalTime" then moc_now.total = escape_f(v)
+ end
+ end
+ end
+
+ moc_notification_preset.text = string.format("%s (%s) - %s\n%s", moc_now.artist,
+ moc_now.album, moc_now.total, moc_now.title)
+ widget = moc.widget
+ settings()
+
+ if moc_now.state == "PLAY" then
+ if moc_now.title ~= helpers.get_map("current moc track") then
+ helpers.set_map("current moc track", moc_now.title)
+ os.execute(string.format("%s %q %q %d %q", mpdcover, "",
+ moc_now.file, cover_size, default_art))
+
+ moc.id = naughty.notify({
+ preset = moc_notification_preset,
+ icon = "/tmp/mpdcover.png",
+ replaces_id = moc.id,
+ }).id
+ end
+ elseif moc_now.state ~= "PAUSE" then
+ helpers.set_map("current moc track", nil)
+ end
+ end)
+ end
+
+ helpers.newtimer("moc", timeout, moc.update)
+
+ return setmetatable(moc, { __index = moc.widget })
+end
+
+return setmetatable(moc, { __call = function(_, ...) return worker(...) end })
diff --git a/lain/widgets/contrib/redshift.lua b/lain/widgets/contrib/redshift.lua
new file mode 100644
index 0000000..5ed9300
--- /dev/null
+++ b/lain/widgets/contrib/redshift.lua
@@ -0,0 +1,79 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2014, blueluke <http://github.com/blueluke>
+
+--]]
+
+local awful = require("awful")
+local os = os
+local spawn = awful.util.spawn_with_shell
+
+local setmetatable = setmetatable
+
+-- Redshift
+-- lain.widgets.contrib.redshift
+local redshift = {}
+
+local attached = false -- true if attached to a widget
+local active = false -- true if redshift is active
+local running = false -- true if redshift was initialized
+local update_fnct = function() end -- Function that is run each time redshift is toggled. See redshift:attach().
+
+
+local function init()
+ -- As there is no way to determine if redshift was previously
+ -- toggled off (i.e Awesome on-the-fly restart), kill redshift to make sure
+ os.execute("pkill redshift")
+ -- Remove existing color adjustment
+ spawn("redshift -x")
+ -- (Re)start redshift
+ spawn("redshift")
+ running = true
+ active = true
+end
+
+function redshift:toggle()
+ if running then
+ -- Sending -USR1 toggles redshift (See project website)
+ os.execute("pkill -USR1 redshift")
+ active = not active
+ else
+ init()
+ end
+ update_fnct()
+end
+
+function redshift:off()
+ if running and active then
+ redshift:toggle()
+ end
+end
+
+function redshift:on()
+ if not active then
+ redshift:toggle()
+ end
+end
+
+function redshift:is_active()
+ return active
+end
+
+-- Attach to a widget
+-- Provides a button which toggles redshift on/off on click
+-- @param widget: Widget to attach to.
+-- @param fnct: Function to be run each time redshift is toggled (optional).
+-- Use it to update widget text or icons on status change.
+function redshift:attach(widget, fnct)
+ update_fnct = fnct or function() end
+ if not attached then
+ init()
+ attached = true
+ update_fnct()
+ end
+ widget:buttons(awful.util.table.join( awful.button({}, 1, function () redshift:toggle() end) ))
+end
+
+return setmetatable(redshift, { _call = function(_, ...) return create(...) end })
diff --git a/lain/widgets/contrib/task.lua b/lain/widgets/contrib/task.lua
new file mode 100644
index 0000000..6425926
--- /dev/null
+++ b/lain/widgets/contrib/task.lua
@@ -0,0 +1,133 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Jan Xie
+
+--]]
+
+local icons_dir = require("lain.helpers").icons_dir
+
+local awful = require("awful")
+local beautiful = require("beautiful")
+local naughty = require("naughty")
+
+local io = io
+local string = { len = string.len }
+local tonumber = tonumber
+
+local setmetatable = setmetatable
+
+-- Taskwarrior notification
+-- lain.widgets.contrib.task
+local task = {}
+
+local task_notification = nil
+
+function task:hide()
+ if task_notification ~= nil then
+ naughty.destroy(task_notification)
+ task_notification = nil
+ end
+end
+
+function task:show()
+ task:hide()
+
+ local f, c_text
+
+ f = io.popen('task')
+ c_text = "<span font='"
+ .. task.font .. " "
+ .. task.font_size .. "'>"
+ .. f:read("*all"):gsub("\n*$", "")
+ .. "</span>"
+ f:close()
+
+ task_notification = naughty.notify({ title = "[task next]",
+ text = c_text,
+ icon = task.notify_icon,
+ position = task.position,
+ fg = task.fg,
+ bg = task.bg,
+ timeout = task.timeout,
+ })
+end
+
+function task:prompt_add()
+ awful.prompt.run({ prompt = "Add task: " },
+ mypromptbox[mouse.screen].widget,
+ function (...)
+ local f = io.popen("task add " .. ...)
+ c_text = "\n<span font='"
+ .. task.font .. " "
+ .. task.font_size .. "'>"
+ .. f:read("*all")
+ .. "</span>"
+ f:close()
+
+ naughty.notify({
+ text = c_text,
+ icon = task.notify_icon,
+ position = task.position,
+ fg = task.fg,
+ bg = task.bg,
+ timeout = task.timeout,
+ })
+ end,
+ nil,
+ awful.util.getdir("cache") .. "/history_task_add")
+end
+
+function task:prompt_search()
+ awful.prompt.run({ prompt = "Search task: " },
+ mypromptbox[mouse.screen].widget,
+ function (...)
+ local f = io.popen("task " .. ...)
+ c_text = f:read("*all"):gsub(" \n*$", "")
+ f:close()
+
+ if string.len(c_text) == 0
+ then
+ c_text = "No results found."
+ else
+ c_text = "<span font='"
+ .. task.font .. " "
+ .. task.font_size .. "'>"
+ .. c_text
+ .. "</span>"
+ end
+
+ naughty.notify({
+ title = "[task next " .. ... .. "]",
+ text = c_text,
+ icon = task.notify_icon,
+ position = task.position,
+ fg = task.fg,
+ bg = task.bg,
+ timeout = task.timeout,
+ })
+ end,
+ nil,
+ awful.util.getdir("cache") .. "/history_task")
+end
+
+function task:attach(widget, args)
+ local args = args or {}
+
+ task.font_size = tonumber(args.font_size) or 12
+ task.font = beautiful.font:sub(beautiful.font:find(""),
+ beautiful.font:find(" "))
+ task.fg = args.fg or beautiful.fg_normal or "#FFFFFF"
+ task.bg = args.bg or beautiful.bg_normal or "#FFFFFF"
+ task.position = args.position or "top_right"
+ task.timeout = args.timeout or 7
+
+ task.notify_icon = icons_dir .. "/taskwarrior/task.png"
+ task.notify_icon_small = icons_dir .. "/taskwarrior/tasksmall.png"
+
+ widget:connect_signal("mouse::enter", function () task:show() end)
+ widget:connect_signal("mouse::leave", function () task:hide() end)
+end
+
+return setmetatable(task, { __call = function(_, ...) return create(...) end })
diff --git a/lain/widgets/contrib/tpbat/init.lua b/lain/widgets/contrib/tpbat/init.lua
new file mode 100644
index 0000000..782bf35
--- /dev/null
+++ b/lain/widgets/contrib/tpbat/init.lua
@@ -0,0 +1,170 @@
+
+--[[
+
+ tpbat.lua
+ Battery status widget for ThinkPad laptops that use SMAPI
+ lain.widgets.contrib.tpbat
+
+ More on tp_smapi: http://www.thinkwiki.org/wiki/Tp_smapi
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Conor Heine
+ * (c) 2013, Luke Bonham
+ * (c) 2010-2012, Peter Hofmann
+
+--]]
+
+local debug = { getinfo = debug.getinfo }
+local newtimer = require("lain.helpers").newtimer
+local first_line = require("lain.helpers").first_line
+local beautiful = require("beautiful")
+local naughty = require("naughty")
+local wibox = require("wibox")
+
+local string = { format = string.format }
+local math = { floor = math.floor }
+local tostring = tostring
+local setmetatable = setmetatable
+
+package.path = debug.getinfo(1,"S").source:match[[^@?(.*[\/])[^\/]-$]] .. "?.lua;" .. package.path
+local smapi = require("smapi")
+
+-- ThinkPad SMAPI-enabled battery info widget
+-- lain.widgets.contrib.tpbat
+local tpbat = { }
+local tpbat_notification = nil
+
+function tpbat:hide()
+ if tpbat_notification ~= nil
+ then
+ naughty.destroy(tpbat_notification)
+ tpbat_notification = nil
+ end
+end
+
+function tpbat:show(t_out)
+ tpbat:hide()
+
+ local bat = self.bat
+ local t_out = t_out or 0
+
+ if bat == nil or not bat:installed() then return end
+
+ local mfgr = bat:get('manufacturer') or "no_mfgr"
+ local model = bat:get('model') or "no_model"
+ local chem = bat:get('chemistry') or "no_chem"
+ local status = bat:get('state') or "nil"
+ local time = bat:remaining_time()
+ local msg = "\t"
+
+ if status ~= "idle" and status ~= "nil"
+ then
+ if time == "N/A"
+ then
+ msg = "...Calculating time remaining..."
+ else
+ msg = time .. (status == "charging" and " until charged" or " remaining")
+ end
+ else
+ msg = "On AC Power"
+ end
+
+ local str = string.format("%s : %s %s (%s)\n", bat.name, mfgr, model, chem)
+ .. string.format("\n%s \t\t\t %s", status:upper(), msg)
+
+ tpbat_notification = naughty.notify({
+ preset = { fg = beautiful.fg_normal },
+ text = str,
+ timeout = t_out,
+ screen = client.focus and client.focus.screen or 1
+ })
+end
+
+function tpbat.register(args)
+ local args = args or {}
+ local timeout = args.timeout or 30
+ local battery = args.battery or "BAT0"
+ local settings = args.settings or function() end
+
+ tpbat.bat = smapi:battery(battery) -- Create a new battery
+ local bat = tpbat.bat
+
+ tpbat.widget = wibox.widget.textbox('')
+
+ bat_notification_low_preset = {
+ title = "Battery low",
+ text = "Plug the cable!",
+ timeout = 15,
+ fg = "#202020",
+ bg = "#CDCDCD"
+ }
+
+ bat_notification_critical_preset = {
+ title = "Battery exhausted",
+ text = "Shutdown imminent",
+ timeout = 15,
+ fg = "#000000",
+ bg = "#FFFFFF"
+ }
+
+ if bat:get('state') == nil
+ then
+ local n = naughty.notify({
+ preset = bat_notification_low_preset,
+ title = "SMAPI Battery Warning: Unable to read battery state!",
+ text = "This widget is intended for ThinkPads. Is tp_smapi installed? Check your configs & paths.",
+ screen = client.focus and client.focus.screen or 1
+ })
+ end
+
+ function update()
+ bat_now = {
+ status = "Not present",
+ perc = "N/A",
+ time = "N/A",
+ watt = "N/A"
+ }
+
+ if bat:installed()
+ then
+ bat_now.status = bat:status() or "N/A"
+ bat_now.perc = bat:percent()
+ bat_now.time = bat:remaining_time()
+ -- bat_now.watt = string.format("%.2fW", (VOLTS * AMPS) / 1e12)
+
+ -- notifications for low and critical states (when discharging)
+ if bat_now.status == "discharging"
+ then
+ if bat_now.perc <= 5
+ then
+ tpbat.id = naughty.notify({
+ preset = bat_notification_critical_preset,
+ replaces_id = tpbat.id,
+ screen = client.focus and client.focus.screen or 1
+ }).id
+ elseif bat_now.perc <= 15
+ then
+ tpbat.id = naughty.notify({
+ preset = bat_notification_low_preset,
+ replaces_id = tpbat.id,
+ screen = client.focus and client.focus.screen or 1
+ }).id
+ end
+ end
+
+ bat_now.perc = tostring(bat_now.perc)
+ end
+
+ widget = tpbat.widget
+ settings()
+ end
+
+ newtimer("tpbat", timeout, update)
+
+ widget:connect_signal('mouse::enter', function () tpbat:show() end)
+ widget:connect_signal('mouse::leave', function () tpbat:hide() end)
+
+ return tpbat.widget
+end
+
+return setmetatable(tpbat, { __call = function(_, ...) return tpbat.register(...) end })
diff --git a/lain/widgets/contrib/tpbat/smapi.lua b/lain/widgets/contrib/tpbat/smapi.lua
new file mode 100644
index 0000000..c7f093e
--- /dev/null
+++ b/lain/widgets/contrib/tpbat/smapi.lua
@@ -0,0 +1,102 @@
+
+--[[
+
+ smapi.lua
+ Interface with thinkpad battery information
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Conor Heine
+
+--]]
+
+local first_line = require("lain.helpers").first_line
+
+local string = { format = string.format }
+local tonumber = tonumber
+local setmetatable = setmetatable
+
+local smapi = {}
+
+local apipath = "/sys/devices/platform/smapi"
+
+-- Most are readable values, but some can be written to (not implemented, yet?)
+local readable = {
+ barcoding = true,
+ charging_max_current = true,
+ charging_max_voltage = true,
+ chemistry = true,
+ current_avg = true,
+ current_now = true,
+ cycle_count = true,
+ design_capacity = true,
+ design_voltage = true,
+ dump = true,
+ first_use_date = true,
+ force_discharge = false,
+ group0_voltage = true,
+ group1_voltage = true,
+ group2_voltage = true,
+ group3_voltage = true,
+ inhibit_charge_minutes = false,
+ installed = true,
+ last_full_capacity = true,
+ manufacture_date = true,
+ manufacturer = true,
+ model = true,
+ power_avg = true,
+ power_now = true,
+ remaining_capacity = true,
+ remaining_charging_time = true,
+ remaining_percent = true,
+ remaining_percent_error = true,
+ remaining_running_time = true,
+ remaining_running_time_now = true,
+ serial = true,
+ start_charge_thresh = false,
+ state = true,
+ stop_charge_thresh = false,
+ temperature = true,
+ voltage = true,
+}
+
+function smapi:battery(name)
+ local bat = {}
+
+ bat.name = name
+ bat.path = apipath .. "/" .. name
+
+ function bat:get(item)
+ return self.path ~= nil and readable[item] and first_line(self.path .. "/" .. item) or nil
+ end
+
+ function bat:installed()
+ return self:get("installed") == "1"
+ end
+
+ function bat:status()
+ return self:get('state')
+ end
+
+ -- Remaining time can either be time until battery dies or time until charging completes
+ function bat:remaining_time()
+ local time_val = bat_now.status == 'discharging' and 'remaining_running_time' or 'remaining_charging_time'
+ local mins_left = self:get(time_val)
+
+ if mins_left:find("^%d+") == nil
+ then
+ return "N/A"
+ end
+
+ local hrs = math.floor(mins_left / 60)
+ local min = mins_left % 60
+ return string.format("%02d:%02d", hrs, min)
+ end
+
+ function bat:percent()
+ return tonumber(self:get("remaining_percent"))
+ end
+
+ return setmetatable(bat, {__metatable = false, __newindex = false})
+end
+
+return smapi
diff --git a/lain/widgets/cpu.lua b/lain/widgets/cpu.lua
new file mode 100644
index 0000000..7c1ecb0
--- /dev/null
+++ b/lain/widgets/cpu.lua
@@ -0,0 +1,77 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luke Bonham
+ * (c) 2010-2012, Peter Hofmann
+
+--]]
+
+local first_line = require("lain.helpers").first_line
+local newtimer = require("lain.helpers").newtimer
+
+local wibox = require("wibox")
+
+local math = { ceil = math.ceil }
+local string = { format = string.format,
+ gmatch = string.gmatch }
+local tostring = tostring
+
+local setmetatable = setmetatable
+
+-- CPU usage
+-- lain.widgets.cpu
+local cpu = {
+ last_total = 0,
+ last_active = 0
+}
+
+local function worker(args)
+ local args = args or {}
+ local timeout = args.timeout or 5
+ local settings = args.settings or function() end
+
+ cpu.widget = wibox.widget.textbox('')
+
+ function update()
+ -- Read the amount of time the CPUs have spent performing
+ -- different kinds of work. Read the first line of /proc/stat
+ -- which is the sum of all CPUs.
+ local times = first_line("/proc/stat")
+ local at = 1
+ local idle = 0
+ local total = 0
+ for field in string.gmatch(times, "[%s]+([^%s]+)")
+ do
+ -- 4 = idle, 5 = ioWait. Essentially, the CPUs have done
+ -- nothing during these times.
+ if at == 4 or at == 5
+ then
+ idle = idle + field
+ end
+ total = total + field
+ at = at + 1
+ end
+ local active = total - idle
+
+ -- Read current data and calculate relative values.
+ local dactive = active - cpu.last_active
+ local dtotal = total - cpu.last_total
+
+ cpu_now = {}
+ cpu_now.usage = tostring(math.ceil((dactive / dtotal) * 100))
+
+ widget = cpu.widget
+ settings()
+
+ -- Save current data for the next run.
+ cpu.last_active = active
+ cpu.last_total = total
+ end
+
+ newtimer("cpu", timeout, update)
+
+ return cpu.widget
+end
+
+return setmetatable(cpu, { __call = function(_, ...) return worker(...) end })
diff --git a/lain/widgets/fs.lua b/lain/widgets/fs.lua
new file mode 100644
index 0000000..8b51178
--- /dev/null
+++ b/lain/widgets/fs.lua
@@ -0,0 +1,117 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luke Bonham
+ * (c) 2010, Adrian C. <anrxc@sysphere.org>
+ * (c) 2009, Lucas de Vries <lucas@glacicle.com>
+
+--]]
+
+local helpers = require("lain.helpers")
+
+local beautiful = require("beautiful")
+local wibox = require("wibox")
+local naughty = require("naughty")
+
+local io = { popen = io.popen }
+local pairs = pairs
+local string = { match = string.match,
+ format = string.format }
+local tonumber = tonumber
+
+local setmetatable = setmetatable
+
+-- File system disk space usage
+-- lain.widgets.fs
+local fs = {}
+
+local notification = nil
+fs_notification_preset = { fg = beautiful.fg_normal }
+
+function fs:hide()
+ if notification ~= nil then
+ naughty.destroy(notification)
+ notification = nil
+ end
+end
+
+function fs:show(t_out)
+ fs:hide()
+
+ local f = io.popen(helpers.scripts_dir .. "dfs")
+ ws = f:read("*a"):gsub("\n*$", "")
+ f:close()
+
+ notification = naughty.notify({
+ preset = fs_notification_preset,
+ text = ws,
+ timeout = t_out,
+ })
+end
+
+-- Unit definitions
+local unit = { ["mb"] = 1024, ["gb"] = 1024^2 }
+
+local function worker(args)
+ local args = args or {}
+ local timeout = args.timeout or 600
+ local partition = args.partition or "/"
+ local settings = args.settings or function() end
+
+ fs.widget = wibox.widget.textbox('')
+
+ helpers.set_map("fs", false)
+
+ function update()
+ fs_info = {}
+ fs_now = {}
+ local f = assert(io.popen("LC_ALL=C df -kP"))
+
+ for line in f:lines() do -- Match: (size) (used)(avail)(use%) (mount)
+ local s = string.match(line, "^.-[%s]([%d]+)")
+ local u,a,p = string.match(line, "([%d]+)[%D]+([%d]+)[%D]+([%d]+)%%")
+ local m = string.match(line, "%%[%s]([%p%w]+)")
+
+ if u and m then -- Handle 1st line and broken regexp
+ fs_info[m .. " size_mb"] = string.format("%.1f", tonumber(s) / unit["mb"])
+ fs_info[m .. " size_gb"] = string.format("%.1f", tonumber(s) / unit["gb"])
+ fs_info[m .. " used_p"] = tonumber(p)
+ fs_info[m .. " avail_p"] = 100 - tonumber(p)
+ end
+ end
+
+ f:close()
+
+ fs_now.used = tonumber(fs_info[partition .. " used_p"]) or 0
+ fs_now.available = tonumber(fs_info[partition .. " avail_p"]) or 0
+ fs_now.size_mb = tonumber(fs_info[partition .. " size_mb"]) or 0
+ fs_now.size_gb = tonumber(fs_info[partition .. " size_gb"]) or 0
+
+ widget = fs.widget
+ settings()
+
+ if fs_now.used >= 99 and not helpers.get_map("fs")
+ then
+ naughty.notify({
+ title = "warning",
+ text = partition .. " ran out!\nmake some room",
+ timeout = 8,
+ fg = "#000000",
+ bg = "#FFFFFF",
+ })
+ helpers.set_map("fs", true)
+ else
+ helpers.set_map("fs", false)
+ end
+ end
+
+ helpers.newtimer(partition, timeout, update)
+
+ widget:connect_signal('mouse::enter', function () fs:show(0) end)
+ widget:connect_signal('mouse::leave', function () fs:hide() end)
+
+ return setmetatable(fs, { __index = fs.widget })
+end
+
+return setmetatable(fs, { __call = function(_, ...) return worker(...) end })
diff --git a/lain/widgets/imap.lua b/lain/widgets/imap.lua
new file mode 100644
index 0000000..1ebbb76
--- /dev/null
+++ b/lain/widgets/imap.lua
@@ -0,0 +1,93 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luke Bonham
+
+--]]
+
+local helpers = require("lain.helpers")
+local async = require("lain.asyncshell")
+
+local naughty = require("naughty")
+local wibox = require("wibox")
+
+local string = { format = string.format,
+ gsub = string.gsub }
+local tonumber = tonumber
+
+local setmetatable = setmetatable
+
+-- Mail IMAP check
+-- lain.widgets.imap
+
+local function worker(args)
+ local imap = {}
+ local args = args or {}
+
+ local server = args.server
+ local mail = args.mail
+ local password = args.password
+
+ local port = args.port or 993
+ local timeout = args.timeout or 60
+ local is_plain = args.is_plain or false
+ local settings = args.settings or function() end
+
+ local head_command = "curl --connect-timeout 3 -fsm 3"
+ local request = "-X 'SEARCH (UNSEEN)'"
+
+ helpers.set_map(mail, 0)
+
+ if not is_plain
+ then
+ local f = io.popen(password)
+ password = f:read("*a"):gsub("\n", "")
+ f:close()
+ end
+
+ imap.widget = wibox.widget.textbox('')
+
+ function update()
+ mail_notification_preset = {
+ icon = helpers.icons_dir .. "mail.png",
+ position = "top_left"
+ }
+
+ curl = string.format("%s --url imaps://%s:%s/INBOX -u %s:%q %s -k",
+ head_command, server, port, mail, password, request)
+
+ async.request(curl, function(f)
+ ws = f:read("*a")
+ f:close()
+
+ _, mailcount = string.gsub(ws, "%d+", "")
+ _ = nil
+
+ widget = imap.widget
+ settings()
+
+ if mailcount >= 1 and mailcount > helpers.get_map(mail)
+ then
+ if mailcount == 1 then
+ nt = mail .. " has one new message"
+ else
+ nt = mail .. " has <b>" .. mailcount .. "</b> new messages"
+ end
+ naughty.notify({
+ preset = mail_notification_preset,
+ text = nt,
+ })
+ end
+
+ helpers.set_map(mail, mailcount)
+ end)
+
+ end
+
+ helpers.newtimer(mail, timeout, update, true)
+
+ return setmetatable(imap, { __index = imap.widget })
+end
+
+return setmetatable({}, { __call = function(_, ...) return worker(...) end })
diff --git a/lain/widgets/init.lua b/lain/widgets/init.lua
new file mode 100644
index 0000000..0e863ba
--- /dev/null
+++ b/lain/widgets/init.lua
@@ -0,0 +1,20 @@
+
+--[[
+
+ Lain
+ Layouts, widgets and utilities for Awesome WM
+
+ Widgets section
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luke Bonham
+ * (c) 2010-2012, Peter Hofmann
+
+--]]
+
+local wrequire = require("lain.helpers").wrequire
+local setmetatable = setmetatable
+
+local widgets = { _NAME = "lain.widgets" }
+
+return setmetatable(widgets, { __index = wrequire })
diff --git a/lain/widgets/maildir.lua b/lain/widgets/maildir.lua
new file mode 100644
index 0000000..246341f
--- /dev/null
+++ b/lain/widgets/maildir.lua
@@ -0,0 +1,98 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luke Bonham
+ * (c) 2010-2012, Peter Hofmann
+
+--]]
+
+local newtimer = require("lain.helpers").newtimer
+
+local wibox = require("wibox")
+
+local util = require("lain.util")
+
+local io = { popen = io.popen }
+local os = { getenv = os.getenv }
+local pairs = pairs
+local string = { len = string.len,
+ match = string.match }
+local table = { sort = table.sort }
+
+local setmetatable = setmetatable
+
+-- Maildir check
+-- lain.widgets.maildir
+local maildir = {}
+
+local function worker(args)
+ local args = args or {}
+ local timeout = args.timeout or 60
+ local mailpath = args.mailpath or os.getenv("HOME") .. "/Mail"
+ local ignore_boxes = args.ignore_boxes or {}
+ local settings = args.settings or function() end
+
+ maildir.widget = wibox.widget.textbox('')
+
+ function update()
+ -- Find pathes to mailboxes.
+ local p = io.popen("find " .. mailpath ..
+ " -mindepth 1 -maxdepth 1 -type d" ..
+ " -not -name .git")
+ local boxes = {}
+ repeat
+ line = p:read("*l")
+ if line ~= nil
+ then
+ -- Find all files in the "new" subdirectory. For each
+ -- file, print a single character (no newline). Don't
+ -- match files that begin with a dot.
+ -- Afterwards the length of this string is the number of
+ -- new mails in that box.
+ local np = io.popen("find " .. line ..
+ "/new -mindepth 1 -type f " ..
+ "-not -name '.*' -printf a")
+ local mailstring = np:read("*a")
+
+ -- Strip off leading mailpath.
+ local box = string.match(line, mailpath .. "/*([^/]+)")
+ local nummails = string.len(mailstring)
+ if nummails > 0
+ then
+ boxes[box] = nummails
+ end
+ end
+ until line == nil
+
+ table.sort(boxes)
+
+ newmail = "no mail"
+ --Count the total number of mails irrespective of where it was found
+ total = 0
+
+ for box, number in pairs(boxes)
+ do
+ -- Add this box only if it's not to be ignored.
+ if not util.element_in_table(box, ignore_boxes)
+ then
+ total = total + number
+ if newmail == "no mail"
+ then
+ newmail = box .. "(" .. number .. ")"
+ else
+ newmail = newmail .. ", " ..
+ box .. "(" .. number .. ")"
+ end
+ end
+ end
+
+ widget = maildir.widget
+ settings()
+ end
+
+ newtimer(mailpath, timeout, update, true)
+ return maildir.widget
+end
+
+return setmetatable(maildir, { __call = function(_, ...) return worker(...) end })
diff --git a/lain/widgets/mem.lua b/lain/widgets/mem.lua
new file mode 100644
index 0000000..46bb5f9
--- /dev/null
+++ b/lain/widgets/mem.lua
@@ -0,0 +1,59 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luke Bonham
+ * (c) 2010-2012, Peter Hofmann
+
+--]]
+
+local newtimer = require("lain.helpers").newtimer
+
+local wibox = require("wibox")
+
+local io = { lines = io.lines }
+local math = { floor = math.floor }
+local string = { gmatch = string.gmatch }
+
+local setmetatable = setmetatable
+
+-- Memory usage (ignoring caches)
+-- lain.widgets.mem
+local mem = {}
+
+local function worker(args)
+ local args = args or {}
+ local timeout = args.timeout or 3
+ local settings = args.settings or function() end
+
+ mem.widget = wibox.widget.textbox('')
+
+ function update()
+ mem_now = {}
+ for line in io.lines("/proc/meminfo")
+ do
+ for k, v in string.gmatch(line, "([%a]+):[%s]+([%d]+).+")
+ do
+ if k == "MemTotal" then mem_now.total = math.floor(v / 1024)
+ elseif k == "MemFree" then mem_now.free = math.floor(v / 1024)
+ elseif k == "Buffers" then mem_now.buf = math.floor(v / 1024)
+ elseif k == "Cached" then mem_now.cache = math.floor(v / 1024)
+ elseif k == "SwapTotal" then mem_now.swap = math.floor(v / 1024)
+ elseif k == "SwapFree" then mem_now.swapf = math.floor(v / 1024)
+ end
+ end
+ end
+
+ mem_now.used = mem_now.total - (mem_now.free + mem_now.buf + mem_now.cache)
+ mem_now.swapused = mem_now.swap - mem_now.swapf
+
+ widget = mem.widget
+ settings()
+ end
+
+ newtimer("mem", timeout, update)
+
+ return mem.widget
+end
+
+return setmetatable(mem, { __call = function(_, ...) return worker(...) end })
diff --git a/lain/widgets/mpd.lua b/lain/widgets/mpd.lua
new file mode 100644
index 0000000..c10eb78
--- /dev/null
+++ b/lain/widgets/mpd.lua
@@ -0,0 +1,113 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luke Bonham
+ * (c) 2010, Adrian C. <anrxc@sysphere.org>
+
+--]]
+
+local helpers = require("lain.helpers")
+local async = require("lain.asyncshell")
+
+local escape_f = require("awful.util").escape
+local naughty = require("naughty")
+local wibox = require("wibox")
+
+local os = { execute = os.execute,
+ getenv = os.getenv }
+local math = { floor = math.floor }
+local string = { format = string.format,
+ match = string.match,
+ gmatch = string.gmatch }
+
+local setmetatable = setmetatable
+
+-- MPD infos
+-- lain.widgets.mpd
+local mpd = {}
+
+local function worker(args)
+ local args = args or {}
+ local timeout = args.timeout or 2
+ local password = args.password or ""
+ local host = args.host or "127.0.0.1"
+ local port = args.port or "6600"
+ local music_dir = args.music_dir or os.getenv("HOME") .. "/Music"
+ local cover_size = args.cover_size or 100
+ local default_art = args.default_art or ""
+ local settings = args.settings or function() end
+
+ local mpdcover = helpers.scripts_dir .. "mpdcover"
+ local mpdh = "telnet://" .. host .. ":" .. port
+ local echo = "echo 'password " .. password .. "\nstatus\ncurrentsong\nclose'"
+
+ mpd.widget = wibox.widget.textbox('')
+
+ mpd_notification_preset = {
+ title = "Now playing",
+ timeout = 6
+ }
+
+ helpers.set_map("current mpd track", nil)
+
+ function mpd.update()
+ async.request(echo .. " | curl --connect-timeout 1 -fsm 3 " .. mpdh, function (f)
+ mpd_now = {
+ state = "N/A",
+ file = "N/A",
+ artist = "N/A",
+ title = "N/A",
+ album = "N/A",
+ date = "N/A",
+ time = "N/A",
+ elapsed = "N/A"
+ }
+
+ for line in f:lines() do
+ for k, v in string.gmatch(line, "([%w]+):[%s](.*)$") do
+ if k == "state" then mpd_now.state = v
+ elseif k == "file" then mpd_now.file = v
+ elseif k == "Artist" then mpd_now.artist = escape_f(v)
+ elseif k == "Title" then mpd_now.title = escape_f(v)
+ elseif k == "Album" then mpd_now.album = escape_f(v)
+ elseif k == "Date" then mpd_now.date = escape_f(v)
+ elseif k == "Time" then mpd_now.time = v
+ elseif k == "elapsed" then mpd_now.elapsed = string.match(v, "%d+")
+ end
+ end
+ end
+
+ mpd_notification_preset.text = string.format("%s (%s) - %s\n%s", mpd_now.artist,
+ mpd_now.album, mpd_now.date, mpd_now.title)
+ widget = mpd.widget
+ settings()
+
+ if mpd_now.state == "play"
+ then
+ if mpd_now.title ~= helpers.get_map("current mpd track")
+ then
+ helpers.set_map("current mpd track", mpd_now.title)
+
+ os.execute(string.format("%s %q %q %d %q", mpdcover, music_dir,
+ mpd_now.file, cover_size, default_art))
+
+ mpd.id = naughty.notify({
+ preset = mpd_notification_preset,
+ icon = "/tmp/mpdcover.png",
+ replaces_id = mpd.id,
+ }).id
+ end
+ elseif mpd_now.state ~= "pause"
+ then
+ helpers.set_map("current mpd track", nil)
+ end
+ end)
+ end
+
+ helpers.newtimer("mpd", timeout, mpd.update)
+
+ return setmetatable(mpd, { __index = mpd.widget })
+end
+
+return setmetatable(mpd, { __call = function(_, ...) return worker(...) end })
diff --git a/lain/widgets/net.lua b/lain/widgets/net.lua
new file mode 100644
index 0000000..2585ad4
--- /dev/null
+++ b/lain/widgets/net.lua
@@ -0,0 +1,109 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luke Bonham
+ * (c) 2010-2012, Peter Hofmann
+
+--]]
+
+local helpers = require("lain.helpers")
+
+local notify_fg = require("beautiful").fg_focus
+local naughty = require("naughty")
+local wibox = require("wibox")
+
+local io = { popen = io.popen }
+local string = { format = string.format,
+ gsub = string.gsub,
+ match = string.match }
+
+local setmetatable = setmetatable
+
+-- Network infos
+-- lain.widgets.net
+local net = {
+ last_t = 0,
+ last_r = 0
+}
+
+function net.get_device()
+ f = io.popen("ip link show | cut -d' ' -f2,9")
+ ws = f:read("*a")
+ f:close()
+ ws = ws:match("%w+: UP") or ws:match("ppp%w+: UNKNOWN")
+ if ws ~= nil then
+ return ws:match("(%w+):")
+ else
+ return "network off"
+ end
+end
+
+local function worker(args)
+ local args = args or {}
+ local timeout = args.timeout or 2
+ local units = args.units or 1024 --kb
+ local notify = args.notify or "on"
+ local screen = args.screen or 1
+ local settings = args.settings or function() end
+
+ iface = args.iface or net.get_device()
+
+ net.widget = wibox.widget.textbox('')
+
+ helpers.set_map(iface, true)
+
+ function update()
+ net_now = {}
+
+ if iface == "" or string.match(iface, "network off")
+ then
+ iface = net.get_device()
+ end
+
+ net_now.carrier = helpers.first_line('/sys/class/net/' .. iface ..
+ '/carrier') or "0"
+ net_now.state = helpers.first_line('/sys/class/net/' .. iface ..
+ '/operstate') or "down"
+ local now_t = helpers.first_line('/sys/class/net/' .. iface ..
+ '/statistics/tx_bytes') or 0
+ local now_r = helpers.first_line('/sys/class/net/' .. iface ..
+ '/statistics/rx_bytes') or 0
+
+ net_now.sent = (now_t - net.last_t) / timeout / units
+ net_now.sent = string.gsub(string.format('%.1f', net_now.sent), ",", ".")
+
+ net_now.received = (now_r - net.last_r) / timeout / units
+ net_now.received = string.gsub(string.format('%.1f', net_now.received), ",", ".")
+
+ widget = net.widget
+ settings()
+
+ net.last_t = now_t
+ net.last_r = now_r
+
+ if net_now.carrier ~= "1" and notify == "on"
+ then
+ if helpers.get_map(iface)
+ then
+ naughty.notify({
+ title = iface,
+ text = "no carrier",
+ timeout = 7,
+ position = "top_left",
+ icon = helpers.icons_dir .. "no_net.png",
+ fg = notify_fg or "#FFFFFF",
+ screen = screen
+ })
+ helpers.set_map(iface, false)
+ end
+ else
+ helpers.set_map(iface, true)
+ end
+ end
+
+ helpers.newtimer(iface, timeout, update)
+ return net.widget
+end
+
+return setmetatable(net, { __call = function(_, ...) return worker(...) end })
diff --git a/lain/widgets/sysload.lua b/lain/widgets/sysload.lua
new file mode 100644
index 0000000..144ad0c
--- /dev/null
+++ b/lain/widgets/sysload.lua
@@ -0,0 +1,45 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luke Bonham
+ * (c) 2010-2012, Peter Hofmann
+
+--]]
+
+local newtimer = require("lain.helpers").newtimer
+
+local wibox = require("wibox")
+
+local io = { open = io.open }
+local string = { match = string.match }
+
+local setmetatable = setmetatable
+
+-- System load
+-- lain.widgets.sysload
+local sysload = {}
+
+local function worker(args)
+ local args = args or {}
+ local timeout = args.timeout or 5
+ local settings = args.settings or function() end
+
+ sysload.widget = wibox.widget.textbox('')
+
+ function update()
+ local f = io.open("/proc/loadavg")
+ local ret = f:read("*a")
+ f:close()
+
+ load_1, load_5, load_15 = string.match(ret, "([^%s]+) ([^%s]+) ([^%s]+)")
+
+ widget = sysload.widget
+ settings()
+ end
+
+ newtimer("sysload", timeout, update)
+ return sysload.widget
+end
+
+return setmetatable(sysload, { __call = function(_, ...) return worker(...) end })
diff --git a/lain/widgets/temp.lua b/lain/widgets/temp.lua
new file mode 100644
index 0000000..5994f59
--- /dev/null
+++ b/lain/widgets/temp.lua
@@ -0,0 +1,48 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luke Bonham
+
+--]]
+
+local newtimer = require("lain.helpers").newtimer
+
+local wibox = require("wibox")
+
+local io = { open = io.open }
+local tonumber = tonumber
+
+local setmetatable = setmetatable
+
+-- coretemp
+-- lain.widgets.temp
+local temp = {}
+
+local function worker(args)
+ local args = args or {}
+ local timeout = args.timeout or 5
+ local tempfile = args.tempfile or "/sys/class/thermal/thermal_zone0/temp"
+ local settings = args.settings or function() end
+
+ temp.widget = wibox.widget.textbox('')
+
+ function update()
+ local f = io.open(tempfile)
+ if f ~= nil
+ then
+ coretemp_now = tonumber(f:read("*a")) / 1000
+ f:close()
+ else
+ coretemp_now = "N/A"
+ end
+
+ widget = temp.widget
+ settings()
+ end
+
+ newtimer("coretemp", timeout, update)
+ return temp.widget
+end
+
+return setmetatable(temp, { __call = function(_, ...) return worker(...) end })
diff --git a/lain/widgets/weather.lua b/lain/widgets/weather.lua
new file mode 100644
index 0000000..d1624fa
--- /dev/null
+++ b/lain/widgets/weather.lua
@@ -0,0 +1,130 @@
+
+--[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2015, Luke Bonham
+
+--]]
+
+local newtimer = require("lain.helpers").newtimer
+local async = require("lain.asyncshell")
+local json = require("lain.util").dkjson
+local lain_icons = require("lain.helpers").icons_dir
+local naughty = require("naughty")
+local wibox = require("wibox")
+
+local math = { floor = math.floor }
+local string = { format = string.format,
+ gsub = string.gsub }
+
+local setmetatable = setmetatable
+
+-- OpenWeatherMap
+-- current weather and X-days forecast
+-- lain.widgets.weather
+
+local function worker(args)
+ local weather = {}
+ local args = args or {}
+ local timeout = args.timeout or 900 -- 15 min
+ local timeout_forecast = args.timeout or 86400 -- 24 hrs
+ local current_call = "curl -s 'http://api.openweathermap.org/data/2.5/weather?id=%s&units=%s&lang=%s'"
+ local forecast_call = "curl -s 'http://api.openweathermap.org/data/2.5/forecast/daily?id=%s&units=%s&lang=%s&cnt=%s'"
+ local city_id = args.city_id or 0 -- placeholder
+ local units = args.units or "metric"
+ local lang = args.lang or "en"
+ local cnt = args.cnt or 7
+ local date_cmd = args.date_cmd or "date -u -d @%d +'%%a %%d'"
+ local icons_path = args.icons_path or lain_icons .. "openweathermap/"
+ local w_notification_preset = args.w_notification_preset or {}
+ local settings = args.settings or function() end
+
+ weather.widget = wibox.widget.textbox('')
+ weather.icon = wibox.widget.imagebox()
+
+ function weather.show(t_out)
+ weather.hide()
+ weather.notification = naughty.notify({
+ text = weather.notification_text,
+ icon = weather.icon_path,
+ timeout = t_out,
+ preset = w_notification_preset
+ })
+ end
+
+ function weather.hide()
+ if weather.notification ~= nil then
+ naughty.destroy(weather.notification)
+ weather.notification = nil
+ end
+ end
+
+ function weather.attach(obj)
+ obj:connect_signal("mouse::enter", function()
+ weather.show(0)
+ end)
+ obj:connect_signal("mouse::leave", function()
+ weather.hide()
+ end)
+ end
+
+ function weather.forecast_update()
+ local cmd = string.format(forecast_call, city_id, units, lang, cnt)
+ async.request(cmd, function(f)
+ j = f:read("*a")
+ f:close()
+ weather_now, pos, err = json.decode(j, 1, nil)
+
+ if not err and weather_now ~= nil and tonumber(weather_now["cod"]) == 200 then
+ weather.notification_text = ''
+ for i = 1, weather_now["cnt"] do
+ local f = assert(io.popen(string.format(date_cmd, weather_now["list"][i]["dt"])))
+ day = string.gsub(f:read("a"), "\n", "")
+ f:close()
+
+ tmin = math.floor(weather_now["list"][i]["temp"]["min"])
+ tmax = math.floor(weather_now["list"][i]["temp"]["max"])
+ desc = weather_now["list"][i]["weather"][1]["description"]
+
+ weather.notification_text = weather.notification_text ..
+ string.format("<b>%s</b>: %s, %d - %d ", day, desc, tmin, tmax)
+
+ if i < weather_now["cnt"] then
+ weather.notification_text = weather.notification_text .. "\n"
+ end
+ end
+ else
+ weather.icon_path = icons_path .. "na.png"
+ weather.notification_text = "API/connection error or bad/not set city ID"
+ end
+ end)
+ end
+
+ function weather.update()
+ local cmd = string.format(current_call, city_id, units, lang)
+ async.request(cmd, function(f)
+ j = f:read("*a")
+ f:close()
+ weather_now, pos, err = json.decode(j, 1, nil)
+
+ if not err and weather_now ~= nil and tonumber(weather_now["cod"]) == 200 then
+ weather.icon_path = icons_path .. weather_now["weather"][1]["icon"] .. ".png"
+ weather.icon:set_image(weather.icon_path)
+ widget = weather.widget
+ settings()
+ else
+ weather.widget._layout.text = " N/A " -- tries to avoid textbox bugs
+ weather.icon:set_image(icons_path .. "na.png")
+ end
+ end)
+ end
+
+ weather.attach(weather.widget)
+
+ newtimer("weather", timeout, weather.update)
+ newtimer("weather_forecast", timeout, weather.forecast_update)
+
+ return setmetatable(weather, { __index = weather.widget })
+end
+
+return setmetatable({}, { __call = function(_, ...) return worker(...) end })