diff options
author | ache <ache@ache.one> | 2017-03-13 23:17:19 +0100 |
---|---|---|
committer | ache <ache@ache.one> | 2017-03-13 23:17:19 +0100 |
commit | 22d656903563f75678f3634964731ccf93355dfd (patch) | |
tree | e3cb6279d95c9764093072d5e946566ea6533799 /lain/widgets |
Init commit
Diffstat (limited to 'lain/widgets')
-rw-r--r-- | lain/widgets/abase.lua | 42 | ||||
-rw-r--r-- | lain/widgets/alsa.lua | 68 | ||||
-rw-r--r-- | lain/widgets/alsabar.lua | 174 | ||||
-rw-r--r-- | lain/widgets/base.lua | 40 | ||||
-rw-r--r-- | lain/widgets/bat.lua | 149 | ||||
-rw-r--r-- | lain/widgets/borderbox.lua | 62 | ||||
-rw-r--r-- | lain/widgets/calendar.lua | 131 | ||||
-rw-r--r-- | lain/widgets/contrib/ccurr.lua | 82 | ||||
-rw-r--r-- | lain/widgets/contrib/init.lua | 19 | ||||
-rw-r--r-- | lain/widgets/contrib/moc.lua | 102 | ||||
-rw-r--r-- | lain/widgets/contrib/redshift.lua | 79 | ||||
-rw-r--r-- | lain/widgets/contrib/task.lua | 133 | ||||
-rw-r--r-- | lain/widgets/contrib/tpbat/init.lua | 170 | ||||
-rw-r--r-- | lain/widgets/contrib/tpbat/smapi.lua | 102 | ||||
-rw-r--r-- | lain/widgets/cpu.lua | 77 | ||||
-rw-r--r-- | lain/widgets/fs.lua | 117 | ||||
-rw-r--r-- | lain/widgets/imap.lua | 93 | ||||
-rw-r--r-- | lain/widgets/init.lua | 20 | ||||
-rw-r--r-- | lain/widgets/maildir.lua | 98 | ||||
-rw-r--r-- | lain/widgets/mem.lua | 59 | ||||
-rw-r--r-- | lain/widgets/mpd.lua | 113 | ||||
-rw-r--r-- | lain/widgets/net.lua | 109 | ||||
-rw-r--r-- | lain/widgets/sysload.lua | 45 | ||||
-rw-r--r-- | lain/widgets/temp.lua | 48 | ||||
-rw-r--r-- | lain/widgets/weather.lua | 130 |
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 }) |