diff --git a/CHANGES b/CHANGES index 4e33af5..ce6b144 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,27 @@ Full changelog is available online: http://git.sysphere.org/vicious/log/?showmsg=1 --------------------------------------------------- +6e16a41 bat: fix broken battery remaining time (was always N/A) +bfbc1bd volume: get the normalized volume like alsamixer and DE's indicator +3ef0f11 Next release, tag 2.1.2 +946271c bat: expose information on battery wear and tear +7626989 README: write a list of major contributors to the project +c51e13c init: stop allocating reg table functions with properties +52cbb64 Revert "init: emit timeout instead of forced update" +7961ca1 weather: add support for dew point +dc556e5 bat: Use a real minus sign for the charging status +f36997b README: explain wifi abbreviations linp and sign +71ea0f3 README: enable caching in vicious.widgets.cpu example +211d450 init: share timers when possible +75cd103 Revert "mpd: workaround command termination bug in mpd server v0.18" +a6a73f4 mpd: workaround command termination bug in mpd server v0.18 +c795642 README: update awesome usage examples for v3.5 +563cb6f pkg: revert to pacman as default Arch method, checkupdates optional +01b2302 pkg: use checkupdates on Arch Linux. +2641bf8 contrib: add Open Weather and ATi graphics widget types +0fd4fc5 mboxc: Fixed typo in setmetatable after ported to lua 5.2 +052d19e README: explain vicious is a generic WM widget library +bb891d6 Next release, tag 2.1.1 fac688e wifi: add support for /usr/bin binary path f7fdd90 README: usage examples are for awesome version 3.4 d63343e contrib: add buildbot monitoring widget diff --git a/README b/README index 79c40bd..4f1d0f6 100644 --- a/README +++ b/README @@ -159,11 +159,12 @@ vicious.widgets.uptime for 5 minutes and 6th for 15 minutes vicious.widgets.bat - - provides state, charge, and remaining time for a requested battery + - provides state, charge, remaining time and wear for a requested + battery - takes battery ID as an argument, i.e. "BAT0" - returns 1st value as state of requested battery, 2nd as charge - level in percent and 3rd as remaining (charging or discharging) - time + level in percent, 3rd as remaining (charging or discharging) + time and 4th as the wear level in percent vicious.widgets.mem - provides RAM and Swap usage statistics @@ -218,13 +219,15 @@ vicious.widgets.wifi - provides wireless information for a requested interface - takes the network interface as an argument, i.e. "wlan0" - returns a table with string keys: {ssid}, {mode}, {chan}, {rate}, - {link}, {linp} and {sign} + {link}, {linp} (link quality in percent) and {sign} (signal level) vicious.widgets.mbox - provides the subject of last e-mail in a mbox file - takes the full path to the mbox as an argument, or a table with - 1st field as path, 2nd as maximum lenght and 3rd (optional) as - widget name - if 3rd field is present scrolling will be used + 1st field as path, 2nd as maximum length and 3rd (optional) as + widget name - if 3rd field is present scrolling will be used (note: the + path will be escaped so special variables like ~ will not work, use + os.getenv("HOME").."mail" instead to access environment variables) - returns 1st value as the subject of the last e-mail vicious.widgets.mboxc @@ -243,11 +246,14 @@ vicious.widgets.mdir vicious.widgets.gmail - provides count of new and subject of last e-mail on Gmail - takes an (optional) argument, if it's a number subject will be - truncated, if a table, with 1st field as maximum lenght and 2nd + truncated, if a table, with 1st field as maximum length and 2nd the widget name (i.e. "gmailwidget"), scrolling will be used - keeps login information in the ~/.netrc file, example: machine mail.google.com login user password pass - returns a table with string keys: {count} and {subject} + - to be able to use this widget, make sure in your Gmail account + you disabled "two step verification" (https://support.google.com/accounts/answer/1064203) + and then "allowed access to your account for less secure apps" (https://www.google.com/settings/security/lesssecureapps) vicious.widgets.org - provides agenda statistics for Emacs org-mode @@ -282,7 +288,8 @@ vicious.widgets.weather - provides weather information for a requested station - takes the ICAO station code as an argument, i.e. "LDRI" - returns a table with string keys: {city}, {wind}, {windmph}, - {windkmh}, {sky}, {weather}, {tempf}, {tempc}, {humid}, {press} + {windkmh}, {sky}, {weather}, {tempf}, {tempc}, {humid}, {dewf}, + {dewc}, {press} vicious.widgets.date - provides access to os.date, with optional time formatting provided @@ -308,10 +315,10 @@ holds widget types that were obsoleted or rewritten. Contrib widgets will not be imported by init unless you explicitly enable it, or load them in your rc.lua. -Richard Kolkovich, a FreeBSD user, published his vicious-fbsd -branch. If you are also a BSD user you can find his work here: +Rudi Siegel, a FreeBSD user, published his FreeBSD branch. If you are +also a BSD user you can find his work here: - - http://git.sigil.org/vicious-fbsd/ + - https://bitbucket.org/mutluyum/vicious_bsd/ Some users would like to avoid writing new modules. For them Vicious kept the old Wicked functionality, possibility to register their own @@ -445,10 +452,12 @@ CPU usage widget cpuwidget:set_background_color("#494B4F") cpuwidget:set_color({ type = "linear", from = { 0, 0 }, to = { 50, 0 }, stops = { { 0, "#FF5656" }, { 0.5, "#88A175" }, { 1, "#AECF96" }}) + vicious.cache(vicious.widgets.cpu) vicious.register(cpuwidget, vicious.widgets.cpu, "$1", 3) - updated every 3 seconds, feeds the graph with total usage - percentage of all CPUs/cores + percentage of all CPUs/cores and enable caching of this widget + type Format functions @@ -565,9 +574,7 @@ Wicked written by: Vicious written by: - Adrian C. (anrxc) -Vicious contributors: - - Michael Unterkalmsteiner - - Martin Striz +Vicious major contributors: - Benedikt Sauer - Greg D. - Henning Glawe @@ -576,3 +583,6 @@ Vicious contributors: - Hagen Schink - Jörg Thalheim - Arvydas Sidorenko + - Dodo The Last + - ... + - Consult git log for a complete list of contributors diff --git a/contrib/README b/contrib/README index 15998c4..74dda00 100644 --- a/contrib/README +++ b/contrib/README @@ -76,6 +76,11 @@ vicious.contrib.openweather - returns a table with string keys: {city}, {wind deg}, {wind aim}, {wind kmh}, {wind mps}, {sky}, {weather}, {temp c}, {humid}, {press} +vicious.contrib.nvsmi + - provides (very basic) information about Nvidia GPU status from SMI + - takes optional card ID as an argument, i.e. "1", or defaults to ID 0 + - returns 1st value as temperature of requested graphics device + vicious.contrib.ossvol - diff --git a/contrib/nvsmi.lua b/contrib/nvsmi.lua new file mode 100644 index 0000000..e68d4a1 --- /dev/null +++ b/contrib/nvsmi.lua @@ -0,0 +1,42 @@ +--------------------------------------------------- +-- Licensed under the GNU General Public License v2 +-- * (c) 2014, Adrian C. +--------------------------------------------------- + +-- {{{ Grab environment +local tonumber = tonumber +local io = { popen = io.popen } +local setmetatable = setmetatable +local string = { match = string.match } +-- }}} + + +-- nvsmi: provides GPU information from nvidia SMI +-- vicious.contrib.nvsmi +local nvsmi = {} + + +-- {{{ GPU Information widget type +local function worker(format, warg) + -- Fallback to querying first device + if not warg then warg = "0" end + + -- Get data from smi + -- * Todo: support more; MEMORY,UTILIZATION,ECC,POWER,CLOCK,COMPUTE,PIDS,PERFORMANCE + local f = io.popen("nvidia-smi -q -d TEMPERATURE -i " .. warg) + local smi = f:read("*all") + f:close() + + -- Not installed + if smi == nil then return {0} end + + -- Get temperature information + local _thermal = string.match(smi, "Gpu[%s]+:[%s]([%d]+)[%s]C") + -- Handle devices without data + if _thermal == nil then return {0} end + + return {tonumber(_thermal)} +end +-- }}} + +return setmetatable(nvsmi, { __call = function(_, ...) return worker(...) end }) diff --git a/contrib/pulse.lua b/contrib/pulse.lua index 8b3298b..663a6ab 100644 --- a/contrib/pulse.lua +++ b/contrib/pulse.lua @@ -18,7 +18,8 @@ local string = { gmatch = string.gmatch } local math = { - floor = math.floor + floor = math.floor, + ceil = math.ceil } -- }}} @@ -29,15 +30,19 @@ local pulse = {} -- {{{ Helper function local function pacmd(args) - local f = io.popen("pacmd "..args) - local line = f:read("*all") - f:close() - return line + local f = io.popen("pacmd "..args) + if f == nil then + return nil + else + local line = f:read("*all") + f:close() + return line + end end local function escape(text) - local special_chars = { ["."] = "%.", ["-"] = "%-" } - return text:gsub("[%.%-]", special_chars) + local special_chars = { ["."] = "%.", ["-"] = "%-" } + return text:gsub("[%.%-]", special_chars) end local cached_sinks = {} @@ -47,10 +52,11 @@ local function get_sink_name(sink) local key = sink or 1 -- Cache requests if not cached_sinks[key] then - local line = pacmd("list-sinks") - for s in string.gmatch(line, "name: <(.-)>") do - table.insert(cached_sinks, s) - end + local line = pacmd("list-sinks") + if line == nil then return nil end + for s in string.gmatch(line, "name: <(.-)>") do + table.insert(cached_sinks, s) + end end return cached_sinks[key] @@ -66,10 +72,11 @@ local function worker(format, sink) -- Get sink data local data = pacmd("dump") + if sink == nil then return {0, "unknown"} end -- If mute return 0 (not "Mute") so we don't break progressbars if string.find(data,"set%-sink%-mute "..escape(sink).." yes") then - return {0, "off"} + return {0, "off"} end local vol = tonumber(string.match(data, "set%-sink%-volume "..escape(sink).." (0x[%x]+)")) @@ -93,6 +100,8 @@ function pulse.add(percent, sink) if vol > 0x10000 then vol = 0x10000 end if vol < 0 then vol = 0 end + vol = math.ceil(vol) + local cmd = string.format("pacmd set-sink-volume %s 0x%x >/dev/null", sink, vol) return os.execute(cmd) end diff --git a/helpers.lua b/helpers.lua index 7c01cf6..1f04fdd 100644 --- a/helpers.lua +++ b/helpers.lua @@ -96,6 +96,15 @@ function helpers.escape(text) end -- }}} +-- {{{ Escape a string for save usage on the command line +function helpers.shellquote(s) + if s == nil then return "" end + -- use single quotes, and put single quotes into double quotes + -- the string $'b is then quoted as '$'"'"'b'"'"' + return "'" .. s:gsub("'", "'\"'\"'") .. "'" +end +-- }}} + -- {{{ Capitalize a string function helpers.capitalize(text) return text and text:gsub("([%w])([%w]*)", function(c, s) diff --git a/init.lua b/init.lua index f567546..62b6980 100644 --- a/init.lua +++ b/init.lua @@ -10,7 +10,7 @@ local type = type local pairs = pairs local tonumber = tonumber -local capi = { timer = timer } +local timer = (type(timer) == 'table' and timer or require("gears.timer")) local os = { time = os.time } local table = { insert = table.insert, @@ -116,7 +116,7 @@ local function regregister(reg) -- Start the timer if reg.timer > 0 then local tm = timers[reg.timer] and timers[reg.timer].timer - tm = tm or capi.timer({ timeout = reg.timer }) + tm = tm or timer({ timeout = reg.timer }) if tm.connect_signal then tm:connect_signal("timeout", reg.update) else @@ -131,7 +131,7 @@ local function regregister(reg) tm:start() end -- Initial update - tm:emit_signal("timeout") + reg.update() end reg.running = true end @@ -151,12 +151,11 @@ function vicious.register(widget, wtype, format, timer, warg) timer = timer, warg = warg, widget = widget, - - -- Update function - update = function () - update(widget, reg) - end, } + -- Set functions + reg.update = function () + update(widget, reg) + end -- Default to 2s timer if reg.timer == nil then diff --git a/widgets/bat.lua b/widgets/bat.lua index 34d1124..1f491d9 100644 --- a/widgets/bat.lua +++ b/widgets/bat.lua @@ -1,6 +1,7 @@ --------------------------------------------------- -- Licensed under the GNU General Public License v2 -- * (c) 2010, Adrian C. +-- * (c) 2013, NormalRa --------------------------------------------------- -- {{{ Grab environment @@ -15,7 +16,7 @@ local math = { -- }}} --- Bat: provides state, charge, and remaining time for a requested battery +-- Bat: provides state, charge, remaining time, and wear for a requested battery -- vicious.widgets.bat local bat = {} @@ -30,12 +31,12 @@ local function worker(format, warg) ["Unknown\n"] = "⌁", ["Charged\n"] = "↯", ["Charging\n"] = "+", - ["Discharging\n"] = "-" + ["Discharging\n"] = "−" } -- Check if the battery is present if battery.present ~= "1\n" then - return {battery_state["Unknown\n"], 0, "N/A"} + return {battery_state["Unknown\n"], 0, "N/A", 0} end @@ -45,14 +46,17 @@ local function worker(format, warg) -- Get capacity information if battery.charge_now then remaining, capacity = battery.charge_now, battery.charge_full + capacity_design = battery.charge_full_design or capacity elseif battery.energy_now then remaining, capacity = battery.energy_now, battery.energy_full + capacity_design = battery.energy_full_design or capacity else - return {battery_state["Unknown\n"], 0, "N/A"} + return {battery_state["Unknown\n"], 0, "N/A", 0} end - -- Calculate percentage (but work around broken BAT/ACPI implementations) + -- Calculate capacity and wear percentage (but work around broken BAT/ACPI implementations) local percent = math.min(math.floor(remaining / capacity * 100), 100) + local wear = math.floor(100 - capacity / capacity_design * 100) -- Get charge information @@ -61,7 +65,7 @@ local function worker(format, warg) elseif battery.power_now then rate = tonumber(battery.power_now) else - return {state, percent, "N/A"} + return {state, percent, "N/A", wear} end -- Calculate remaining (charging or discharging) time @@ -70,10 +74,10 @@ local function worker(format, warg) if rate ~= nil and rate ~= 0 then if state == "+" then timeleft = (tonumber(capacity) - tonumber(remaining)) / tonumber(rate) - elseif state == "-" then + elseif state == "−" then timeleft = tonumber(remaining) / tonumber(rate) else - return {state, percent, time} + return {state, percent, time, wear} end -- Calculate time @@ -83,7 +87,7 @@ local function worker(format, warg) time = string.format("%02d:%02d", hoursleft, minutesleft) end - return {state, percent, time} + return {state, percent, time, wear} end -- }}} diff --git a/widgets/fs.lua b/widgets/fs.lua index c0b7f16..4b889dc 100644 --- a/widgets/fs.lua +++ b/widgets/fs.lua @@ -27,12 +27,12 @@ local function worker(format, warg) if warg then warg = "" else warg = "-l" end local fs_info = {} -- Get data from df - local f = io.popen("LC_ALL=C df -kP " .. warg) + local f = io.popen("LC_ALL=C df -kP " .. helpers.shellquote(warg)) 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]+)") + local m = string.match(line, "%%[%s]+([%p%w]+)") if u and m then -- Handle 1st line and broken regexp helpers.uformat(fs_info, m .. " size", s, unit) diff --git a/widgets/gmail.lua b/widgets/gmail.lua index ba8e731..84f2125 100644 --- a/widgets/gmail.lua +++ b/widgets/gmail.lua @@ -10,7 +10,6 @@ local io = { popen = io.popen } local setmetatable = setmetatable local helpers = require("vicious.helpers") local string = { - find = string.find, match = string.match } -- }}} @@ -23,18 +22,9 @@ local gmail = {} -- {{{ Variable definitions local rss = { - inbox = { - "https://mail.google.com/mail/feed/atom", - "Gmail %- Inbox" - }, - unread = { - "https://mail.google.com/mail/feed/atom/unread", - "Gmail %- Label" - }, - --labelname = { - -- "https://mail.google.com/mail/feed/atom/labelname", - -- "Gmail %- Label" - --}, + inbox = "https://mail.google.com/mail/feed/atom", + unread = "https://mail.google.com/mail/feed/atom/unread", + --labelname = "https://mail.google.com/mail/feed/atom/labelname", } -- Default is just Inbox @@ -49,31 +39,35 @@ local mail = { -- {{{ Gmail widget type local function worker(format, warg) -- Get info from the Gmail atom feed - local f = io.popen("curl --connect-timeout 1 -m 3 -fsn " .. feed[1]) + local f = io.popen("curl --connect-timeout 1 -m 3 -fsn " .. feed) -- Could be huge don't read it all at once, info we are after is at the top - for line in f:lines() do - mail["{count}"] = -- Count comes before messages and matches at least 0 - tonumber(string.match(line, "([%d]+)")) or mail["{count}"] + local xml = f:read(2000) - -- Find subject tags - local title = string.match(line, "(.*)") - -- If the subject changed then break out of the loop - if title ~= nil and not string.find(title, feed[2]) then - -- Check if we should scroll, or maybe truncate - if warg then - if type(warg) == "table" then - title = helpers.scroll(title, warg[1], warg[2]) - else - title = helpers.truncate(title, warg) - end - end - - -- Spam sanitize the subject and store - mail["{subject}"] = helpers.escape(title) - break - end + if xml ~= nil then + return mail end + + mail["{count}"] = -- Count comes before messages and matches at least 0 + tonumber(string.match(xml, "([%d]+)")) or mail["{count}"] + + -- Find subject tag + local title = string.match(xml, ".-(.-)") + + if title ~= nil then + -- Check if we should scroll, or maybe truncate + if warg then + if type(warg) == "table" then + title = helpers.scroll(title, warg[1], warg[2]) + else + title = helpers.truncate(title, warg) + end + end + + -- Spam sanitize the subject and store + mail["{subject}"] = helpers.escape(title) + end + f:close() return mail diff --git a/widgets/hddtemp.lua b/widgets/hddtemp.lua index 85ee767..5c2b1a6 100644 --- a/widgets/hddtemp.lua +++ b/widgets/hddtemp.lua @@ -8,6 +8,7 @@ local tonumber = tonumber local io = { popen = io.popen } local setmetatable = setmetatable local string = { gmatch = string.gmatch } +local helpers = require("vicious.helpers") -- }}} @@ -22,7 +23,8 @@ local function worker(format, warg) if warg == nil then warg = 7634 end local hdd_temp = {} -- Get info from the hddtemp daemon - local f = io.popen("echo | curl --connect-timeout 1 -fsm 3 telnet://127.0.0.1:"..warg) + local quoted = helpers.shellquote(warg) + local f = io.popen("echo | curl --connect-timeout 1 -fsm 3 telnet://127.0.0.1:"..quoted) for line in f:lines() do for d, t in string.gmatch(line, "|([%/%a%d]+)|.-|([%d]+)|[CF]+|") do diff --git a/widgets/mdir.lua b/widgets/mdir.lua index d7c6f3d..bea9088 100644 --- a/widgets/mdir.lua +++ b/widgets/mdir.lua @@ -7,6 +7,7 @@ -- {{{ Grab environment local io = { popen = io.popen } local setmetatable = setmetatable +local helpers = require("vicious.helpers") -- }}} @@ -23,13 +24,14 @@ local function worker(format, warg) local count = { new = 0, cur = 0 } for i=1, #warg do + quoted_path = helpers.shellquote(warg[i]) -- Recursively find new messages - local f = io.popen("find '"..warg[i].."' -type f -wholename '*/new/*'") + local f = io.popen("find "..quoted_path.." -type f -wholename '*/new/*'") for line in f:lines() do count.new = count.new + 1 end f:close() -- Recursively find "old" messages lacking the Seen flag - local f = io.popen("find '"..warg[i].."' -type f -regex '.*/cur/.*2,[^S]*$'") + local f = io.popen("find "..quoted_path.." -type f -regex '.*/cur/.*2,[^S]*$'") for line in f:lines() do count.cur = count.cur + 1 end f:close() end diff --git a/widgets/volume.lua b/widgets/volume.lua index 3baa7e6..57970a3 100644 --- a/widgets/volume.lua +++ b/widgets/volume.lua @@ -8,6 +8,7 @@ local tonumber = tonumber local io = { popen = io.popen } local setmetatable = setmetatable local string = { match = string.match } +local helpers = require("vicious.helpers") -- }}} @@ -26,7 +27,7 @@ local function worker(format, warg) } -- Get mixer control contents - local f = io.popen("amixer get " .. warg) + local f = io.popen("amixer -M get " .. helpers.shellquote(warg)) local mixer = f:read("*all") f:close() diff --git a/widgets/weather.lua b/widgets/weather.lua index 356f32f..632d5b9 100644 --- a/widgets/weather.lua +++ b/widgets/weather.lua @@ -28,6 +28,8 @@ local _weather = { ["{weather}"] = "N/A", ["{tempf}"] = "N/A", ["{tempc}"] = "N/A", + ["{dewf}"] = "N/A", + ["{dewc}"] = "N/A", ["{humid}"] = "N/A", ["{press}"] = "N/A" } @@ -38,8 +40,8 @@ local function worker(format, warg) -- Get weather forceast by the station ICAO code, from: -- * US National Oceanic and Atmospheric Administration - local noaa = "http://weather.noaa.gov/pub/data/observations/metar/decoded/" - local f = io.popen("curl --connect-timeout 1 -fsm 3 "..noaa..warg..".TXT") + local url = "http://weather.noaa.gov/pub/data/observations/metar/decoded/"..warg + local f = io.popen("curl --connect-timeout 1 -fsm 3 "..helpers.shellquote(url)..".TXT") local ws = f:read("*all") f:close() @@ -58,6 +60,8 @@ local function worker(format, warg) string.match(ws, "Weather:[%s](.-)[%c]") or _weather["{weather}"] _weather["{tempf}"] = -- Temperature in fahrenheit string.match(ws, "Temperature:[%s]([%-]?[%d%.]+).*[%c]") or _weather["{tempf}"] + _weather["{dewf}"] = -- Dew Point in fahrenheit + string.match(ws, "Dew[%s]Point:[%s]([%-]?[%d%.]+).*[%c]") or _weather["{dewf}"] _weather["{humid}"] = -- Relative humidity in percent string.match(ws, "Relative[%s]Humidity:[%s]([%d]+)%%") or _weather["{humid}"] _weather["{press}"] = -- Pressure in hPa @@ -71,6 +75,10 @@ local function worker(format, warg) if _weather["{tempf}"] ~= "N/A" then _weather["{tempf}"] = tonumber(_weather["{tempf}"]) _weather["{tempc}"] = math.ceil((_weather["{tempf}"] - 32) * 5/9) + end -- Dew Point in °C if °F was available + if _weather["{dewf}"] ~= "N/A" then + _weather["{dewf}"] = tonumber(_weather["{dewf}"]) + _weather["{dewc}"] = math.ceil((_weather["{dewf}"] - 32) * 5/9) end -- Capitalize some stats so they don't look so out of place if _weather["{sky}"] ~= "N/A" then _weather["{sky}"] = helpers.capitalize(_weather["{sky}"]) diff --git a/widgets/wifi.lua b/widgets/wifi.lua index 15666e5..973f109 100644 --- a/widgets/wifi.lua +++ b/widgets/wifi.lua @@ -58,7 +58,7 @@ local function worker(format, warg) end -- Get data from iwconfig where available - local f = io.popen(iwconfig .." ".. warg .. " 2>&1") + local f = io.popen(iwconfig .." ".. helpers.shellquote(warg) .. " 2>&1") local iw = f:read("*all") f:close()