[submodule "lain.wiki"]
path = wiki
- url = https://github.com/copycat-killer/lain.wiki.git
+ url = https://github.com/lcpz/lain.wiki.git
- # If you have a question
+ # Please, read me!
- Take the following steps:
-
- 1. [Google it](https://encrypted.google.com)
- 2. Search [Awesome doc](https://awesomewm.org/doc)
- 3. Ask [community](https://awesomewm.org/community)
-
- and, if you still don't have an answer, you can ask here.
-
- **Please be warned:** if your question is __unrelated__ to this repository, a reply is only an act of kindness.
+ So that I can help you quickly and without having to redirect you here.
# If you have an issue
- **Please read the [wiki](https://github.com/copycat-killer/lain/wiki) and search the [Issues section](https://github.com/copycat-killer/lain/issues) first.**
+ **Please read the [wiki](https://github.com/lcpz/lain/wiki) and search the [Issues section](https://github.com/lcpz/lain/issues) first.**
If you can't find a solution there, then go ahead and provide:
* output of `awesome -v` and `lua -v`
* expected behavior and actual behavior
* steps to reproduce the problem
+ * X error log
+
+ # How to provide X error log
+
+ There are two ways:
+
+ * (Physically) Restart X like this:
+ ```shell
+ startx -- -keeptty -nolisten tcp > $HOME/.xorg.log 2>&1
+ ```
+ the error log will be output into `$HOME/.xorg.log`.
+
+ * (Virtually) Use [Xephyr](https://wikipedia.org/wiki/Xephyr):
+ ```shell
+ # set screen size as you like
+ Xephyr :1 -screen 1280x800 2> stdout.txt & DISPLAY=:1 awesome
+ ```
+ the error log will be output in the file `stdout.txt`.
+
+ Before reporting, read the log and see if you can solve it yourself.
Layouts, widgets and utilities for Awesome WM 4.x
-------------------------------------------------
- :Author: Luke Bonham <dada [at] archlinux [dot] info>
+ :Author: Luca CPZ
:Version: git
:License: GNU-GPL2_
- :Source: https://github.com/copycat-killer/lain
-
- Warning
- -------
-
- If you still have to use branch 3.5.x, you can refer to the commit 301faf5_, but be aware that it's no longer supported.
+ :Source: https://github.com/lcpz/lain
Description
-----------
- Successor of awesome-vain_, this module provides alternative layouts, asynchronous widgets and utility functions for Awesome_ WM.
-
- Read the wiki_ for all the info.
+ Successor of awesome-vain_, this module provides alternative layouts, asynchronous widgets and utility functions for Awesome_.
Contributions
-------------
- Any contribution is welcome! Feel free to make a pull request.
+ Constructive criticism and suggestions are welcome.
- Just make sure that:
+ If you want to create a pull request, make sure that:
- Your code fits with the general style of the module. In particular, you should use the same indentation pattern that the code uses, and also avoid adding space at the ends of lines.
- - Your code its easy to understand, maintainable, and modularized. You should also avoid code duplication wherever possible by adding functions or using lain.helpers_. If something is unclear, and you can't write it in such a way that it will be clear, explain it with a comment.
+ - Your code its easy to understand, maintainable, and modularized. You should also avoid code duplication wherever possible by adding functions to or using lain.helpers_. If something is unclear, or you can not write it in such a way that it will be clear, explain it with a comment.
- - You test your changes before submitting to make sure that not only your code works, but did not break other parts of the module too!
+ - You test your changes before submitting to make sure that you code works and does not break other parts of the module.
- - You eventually update ``wiki`` submodule with a thorough section.
+ - You update ``wiki`` submodule with a thorough section, if necessary.
Contributed widgets have to be put in ``widget/contrib``.
- Screenshots
- -----------
-
- .. image:: http://i.imgur.com/8D9A7lW.png
- .. image:: http://i.imgur.com/9Iv3OR3.png
- .. image:: http://i.imgur.com/STCPcaJ.png
-
.. _GNU-GPL2: http://www.gnu.org/licenses/gpl-2.0.html
- .. _301faf5: https://github.com/copycat-killer/lain/tree/301faf5370d045e94c9c344acb0fdac84a2f25a6
.. _awesome-vain: https://github.com/vain/awesome-vain
.. _Awesome: https://github.com/awesomeWM/awesome
- .. _wiki: https://github.com/copycat-killer/lain/wiki
- .. _lain.helpers: https://github.com/copycat-killer/lain/blob/master/helpers.lua
+ .. _lain.helpers: https://github.com/lcpz/lain/blob/master/helpers.lua
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2013, Luke Bonham
-
- --]]
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luca CPZ
+
+ --]]
- local easy_async = require("awful.spawn").easy_async
+ local spawn = require("awful.spawn")
local timer = require("gears.timer")
local debug = require("debug")
local io = { lines = io.lines,
open = io.open }
+ local pairs = pairs
local rawget = rawget
- local table = { sort = table.sort }
+ local table = { sort = table.sort, unpack = table.unpack }
+ local unpack = unpack or table.unpack -- lua 5.1 retro-compatibility
-- Lain helper functions for internal use
-- lain.helpers
-- {{{ File operations
- -- see if the file exists and is readable
- function helpers.file_exists(file)
- local f = io.open(file)
- if f then
- local s = f:read()
- f:close()
- f = s
- end
- return f ~= nil
- end
-
- -- get all lines from a file, returns an empty
- -- list/table if the file does not exist
- function helpers.lines_from(file)
- if not helpers.file_exists(file) then return {} end
- local lines = {}
- for line in io.lines(file) do
- lines[#lines + 1] = line
- end
- return lines
- end
-
- -- match all lines from a file, returns an empty
- -- list/table if the file or match does not exist
- function helpers.lines_match(regexp, file)
- local lines = {}
- for index,line in pairs(helpers.lines_from(file)) do
- if string.match(line, regexp) then
- lines[index] = line
- end
- end
- return lines
- end
-
- -- get first line of a file, return nil if
- -- the file does not exist
- function helpers.first_line(file)
- return helpers.lines_from(file)[1]
- end
-
- -- get first non empty line from a file,
- -- returns nil otherwise
- function helpers.first_nonempty_line(file)
- for k,v in pairs(helpers.lines_from(file)) do
- if #v then return v end
- end
- return nil
+ -- check if the file exists and is readable
+ function helpers.file_exists(path)
+ local file = io.open(path, "rb")
+ if file then file:close() end
+ return file ~= nil
+ end
+
+ -- get a table with all lines from a file
+ function helpers.lines_from(path)
+ local lines = {}
+ for line in io.lines(path) do
+ lines[#lines + 1] = line
+ end
+ return lines
+ end
+
+ -- get a table with all lines from a file matching regexp
+ function helpers.lines_match(regexp, path)
+ local lines = {}
+ for line in io.lines(path) do
+ if string.match(line, regexp) then
+ lines[#lines + 1] = line
+ end
+ end
+ return lines
+ end
+
+ -- get first line of a file
+ function helpers.first_line(path)
+ local file, first = io.open(path, "rb"), nil
+ if file then
+ first = file:read("*l")
+ file:close()
+ end
+ return first
+ end
+
+ -- get first non empty line from a file
+ function helpers.first_nonempty_line(path)
+ for line in io.lines(path) do
+ if #line then return line end
+ end
+ return nil
end
-- }}}
-- @param callback function to execute on cmd output
-- @return cmd PID
function helpers.async(cmd, callback)
- return easy_async(cmd,
+ return spawn.easy_async(cmd,
+ function (stdout, stderr, reason, exit_code)
+ callback(stdout, exit_code)
+ end)
+ end
+
+ -- like above, but call spawn.easy_async with a shell
+ function helpers.async_with_shell(cmd, callback)
+ return spawn.easy_async_with_shell(cmd,
function (stdout, stderr, reason, exit_code)
- callback(stdout)
+ callback(stdout, exit_code)
end)
end
+ -- run a command and execute a function on its output line by line
+ function helpers.line_callback(cmd, callback)
+ return spawn.with_line_callback(cmd, {
+ stdout = function (line)
+ callback(line)
+ end,
+ })
+ end
+
-- }}}
-- {{{ A map utility
end
end
+ -- create the partition of singletons of a given set
+ -- example: the trivial partition set of {a, b, c}, is {{a}, {b}, {c}}
+ function helpers.trivial_partition_set(set)
+ local ss = {}
+ for _,e in pairs(set) do
+ ss[#ss+1] = {e}
+ end
+ return ss
+ end
+
+ -- create the powerset of a given set
+ function helpers.powerset(s)
+ if not s then return {} end
+ local t = {{}}
+ for i = 1, #s do
+ for j = 1, #t do
+ t[#t+1] = {s[i],unpack(t[j])}
+ end
+ end
+ return t
+ end
+
-- }}}
return helpers
-
--[[
-
- Lain
- Layouts, widgets and utilities for Awesome WM
-
- Licensed under GNU General Public License v2
- * (c) 2013, Luke Bonham
-
+
+ Lain
+ Layouts, widgets and utilities for Awesome WM
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luca CPZ
+
--]]
return {
--- /dev/null
+ package = "lain"
+ version = "scm-1"
+ source = {
+ url = "https://github.com/lcpz/lain",
+ tag = "scm-1`"
+ }
+ description = {
+ summary = "Layout, widgets and utilities for Awesome WM",
+ detailed = [[
+ Successor of awesome-vain, this module provides alternative layouts, asynchronous widgets and utility functions for Awesome WM.
+
+ Dependencies: curl (for IMAP, MPD and weather widgets); Glib >= 2.54 (for filesystems widget).
+ ]],
+ homepage = "https://github.com/lcpz/lain",
+ license = "GPL-2.0"
+ }
+ dependencies = {
+ "lua >= 5.1",
+ "awesome >= 4.0",
+ "Glib >= 2.54",
+ "curl"
+ }
+ supported_platforms = { "linux" }
+ build = {
+ type = "builtin",
+ modules = { lain = "init.lua" }
+ }
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2014, projektile
- * (c) 2013, Luke Bonham
- * (c) 2010-2012, Peter Hofmann
-
+
+ Licensed under GNU General Public License v2
+ * (c) 2014, projektile
+ * (c) 2013, Luca CPZ
+ * (c) 2010-2012, Peter Hofmann
+
--]]
local floor = math.floor
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2016, Henrik Antonsson
- * (c) 2015, Joerg Jaspert
- * (c) 2014, projektile
- * (c) 2013, Luke Bonham
- * (c) 2010-2012, Peter Hofmann
-
+
+ Licensed under GNU General Public License v2
+ * (c) 2018, Eugene Pakhomov
+ * (c) 2016, Henrik Antonsson
+ * (c) 2015, Joerg Jaspert
+ * (c) 2014, projektile
+ * (c) 2013, Luca CPZ
+ * (c) 2010-2012, Peter Hofmann
+
--]]
- local floor = math.floor
- local screen = screen
+ local floor, max, mouse, mousegrabber, screen = math.floor, math.max, mouse, mousegrabber, screen
local centerwork = {
- name = "centerwork",
- horizontal = { name = "centerworkh" }
+ name = "centerwork",
+ horizontal = { name = "centerworkh" }
}
- local function do_centerwork(p, orientation)
- local t = p.tag or screen[p.screen].selected_tag
- local wa = p.workarea
+ local function arrange(p, layout)
+ local t = p.tag or screen[p.screen].selected_tag
+ local wa = p.workarea
local cls = p.clients
if #cls == 0 then return end
- local c = cls[1]
- local g = {}
+ local c, g = cls[1], {}
- -- Main column, fixed width and height.
+ -- Main column, fixed width and height
local mwfact = t.master_width_factor
local mainhei = floor(wa.height * mwfact)
local mainwid = floor(wa.width * mwfact)
local slaveFirstDim, slaveSecondDim = 0, 0
- if orientation == "vertical" then
+ if layout.name == "centerwork" then -- vertical
if nbrFirstSlaves > 0 then slaveFirstDim = floor(wa.height / nbrFirstSlaves) end
if nbrSecondSlaves > 0 then slaveSecondDim = floor(wa.height / nbrSecondSlaves) end
g.x = wa.x + slaveLwid
g.y = wa.y
- else
+ else -- horizontal
if nbrFirstSlaves > 0 then slaveFirstDim = floor(wa.width / nbrFirstSlaves) end
if nbrSecondSlaves > 0 then slaveSecondDim = floor(wa.width / nbrSecondSlaves) end
g.y = wa.y + slaveThei
end
- if g.width < 1 then g.width = 1 end
- if g.height < 1 then g.height = 1 end
+ g.width = max(g.width, 1)
+ g.height = max(g.height, 1)
p.geometries[c] = g
- -- Auxiliary windows.
+ -- Auxiliary clients
if #cls <= 1 then return end
- for i = 2,#cls do
- local c = cls[i]
- local g = {}
+ for i = 2, #cls do
+ local c, g = cls[i], {}
+ local idxChecker, dimToAssign
local rowIndex = floor(i/2)
- if orientation == "vertical" then
- if i % 2 == 0 then
- -- left slave
- g.x = wa.x
- g.y = wa.y + (rowIndex-1)*slaveFirstDim
-
+ if layout.name == "centerwork" then
+ if i % 2 == 0 then -- left slave
+ g.x = wa.x
+ g.y = wa.y + (rowIndex - 1) * slaveFirstDim
g.width = slaveLwid
- -- if last slave in left row use remaining space for that slave
- if rowIndex == nbrFirstSlaves then
- g.height = wa.y + wa.height - g.y
- else
- g.height = slaveFirstDim
- end
- else
- -- right slave
- g.x = wa.x + slaveLwid + mainwid
- g.y = wa.y + (rowIndex-1)*slaveSecondDim
-
+ idxChecker, dimToAssign = nbrFirstSlaves, slaveFirstDim
+ else -- right slave
+ g.x = wa.x + slaveLwid + mainwid
+ g.y = wa.y + (rowIndex - 1) * slaveSecondDim
g.width = slaveRwid
- -- if last slave in right row use remaining space for that slave
- if rowIndex == nbrSecondSlaves then
- g.height = wa.y + wa.height - g.y
- else
- g.height = slaveSecondDim
- end
+ idxChecker, dimToAssign = nbrSecondSlaves, slaveSecondDim
end
- else
- if i % 2 == 0 then
- -- top slave
- g.x = wa.x + (rowIndex-1)*slaveFirstDim
- g.y = wa.y
-
- g.height = slaveThei
- -- if last slave in top row use remaining space for that slave
- if rowIndex == nbrFirstSlaves then
- g.width = wa.x + wa.width - g.x
- else
- g.width = slaveFirstDim
- end
+ -- if last slave in row, use remaining space for it
+ if rowIndex == idxChecker then
+ g.height = wa.y + wa.height - g.y
else
- -- bottom slave
- g.x = wa.x + (rowIndex-1)*slaveSecondDim
- g.y = wa.y + slaveThei + mainhei
+ g.height = dimToAssign
+ end
+ else
+ if i % 2 == 0 then -- top slave
+ g.x = wa.x + (rowIndex - 1) * slaveFirstDim
+ g.y = wa.y
+ g.height = slaveThei
+ idxChecker, dimToAssign = nbrFirstSlaves, slaveFirstDim
+ else -- bottom slave
+ g.x = wa.x + (rowIndex - 1) * slaveSecondDim
+ g.y = wa.y + slaveThei + mainhei
g.height = slaveBhei
- -- if last slave in bottom row use remaining space for that slave
- if rowIndex == nbrSecondSlaves then
- g.width = wa.x + wa.width - g.x
- else
- g.width = slaveSecondDim
- end
+ idxChecker, dimToAssign = nbrSecondSlaves, slaveSecondDim
+ end
+ -- if last slave in row, use remaining space for it
+ if rowIndex == idxChecker then
+ g.width = wa.x + wa.width - g.x
+ else
+ g.width = dimToAssign
end
end
- if g.width < 1 then g.width = 1 end
- if g.height < 1 then g.height = 1 end
+ g.width = max(g.width, 1)
+ g.height = max(g.height, 1)
p.geometries[c] = g
end
end
+ local function mouse_resize_handler(c, corner, x, y, orientation)
+ local wa = c.screen.workarea
+ local mwfact = c.screen.selected_tag.master_width_factor
+ local g = c:geometry()
+ local offset = 0
+ local cursor = "cross"
+
+ local corner_coords
+
+ if orientation == 'vertical' then
+ if g.height + 15 >= wa.height then
+ offset = g.height * .5
+ cursor = "sb_h_double_arrow"
+ elseif not (g.y + g.height + 15 > wa.y + wa.height) then
+ offset = g.height
+ end
+ corner_coords = { x = wa.x + wa.width * (1 - mwfact) / 2, y = g.y + offset }
+ else
+ if g.width + 15 >= wa.width then
+ offset = g.width * .5
+ cursor = "sb_v_double_arrow"
+ elseif not (g.x + g.width + 15 > wa.x + wa.width) then
+ offset = g.width
+ end
+ corner_coords = { y = wa.y + wa.height * (1 - mwfact) / 2, x = g.x + offset }
+ end
+
+ mouse.coords(corner_coords)
- function centerwork.horizontal.arrange(p)
- return do_centerwork(p, "horizontal")
+ local prev_coords = {}
+
+ mousegrabber.run(function(_mouse)
+ if not c.valid then return false end
+ for _, v in ipairs(_mouse.buttons) do
+ if v then
+ prev_coords = { x = _mouse.x, y = _mouse.y }
+ local new_mwfact
+ if orientation == 'vertical' then
+ new_mwfact = 1 - (_mouse.x - wa.x) / wa.width * 2
+ else
+ new_mwfact = 1 - (_mouse.y - wa.y) / wa.height * 2
+ end
+ c.screen.selected_tag.master_width_factor = math.min(math.max(new_mwfact, 0.01), 0.99)
+ return true
+ end
+ end
+ return prev_coords.x == _mouse.x and prev_coords.y == _mouse.y
+ end, cursor)
end
function centerwork.arrange(p)
- return do_centerwork(p, "vertical")
+ return arrange(p, centerwork)
+ end
+
+ function centerwork.horizontal.arrange(p)
+ return arrange(p, centerwork.horizontal)
+ end
+
+ function centerwork.mouse_resize_handler(c, corner, x, y)
+ return mouse_resize_handler(c, corner, x, y, 'vertical')
+ end
+
+ function centerwork.horizontal.mouse_resize_handler(c, corner, x, y)
+ return mouse_resize_handler(c, corner, x, y, 'horizontal')
end
return centerwork
-
--[[
-
- Lain
- Layouts, widgets and utilities for Awesome WM
-
- Layouts section
-
- Licensed under GNU General Public License v2
- * (c) 2013, Luke Bonham
- * (c) 2010-2012, Peter Hofmann
-
+
+ Lain
+ Layouts, widgets and utilities for Awesome WM
+
+ Layouts section
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luca CPZ
+ * (c) 2010-2012, Peter Hofmann
+
--]]
local wrequire = require("lain.helpers").wrequire
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2014, projektile
- * (c) 2013, Luke Bonham
- * (c) 2010, Nicolas Estibals
- * (c) 2010-2012, Peter Hofmann
-
+
+ Licensed under GNU General Public License v2
+ * (c) 2014, projektile
+ * (c) 2013, Luca CPZ
+ * (c) 2010, Nicolas Estibals
+ * (c) 2010-2012, Peter Hofmann
+
--]]
- local math = { ceil = math.ceil,
- floor = math.floor,
- max = math.max }
+ local math = math
local screen = screen
local tonumber = tonumber
-
--[[
-
- Lain
- Layouts, widgets and utilities for Awesome WM
-
- Utilities section
-
- Licensed under GNU General Public License v2
- * (c) 2013, Luke Bonham
- * (c) 2010-2012, Peter Hofmann
-
+
+ Lain
+ Layouts, widgets and utilities for Awesome WM
+
+ Utilities section
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luca CPZ
+ * (c) 2010-2012, Peter Hofmann
+
--]]
local awful = require("awful")
end
end
- -- https://github.com/copycat-killer/lain/issues/195
+ -- https://github.com/lcpz/lain/issues/195
function util.mc(c, width_f, height_f)
c = c or util.magnified_client
if not c then return end
textbox = awful.screen.focused().mypromptbox.widget,
exe_callback = function(name)
if not name or #name == 0 then return end
- awful.tag.add(name, { screen = awful.screen.focused(), layout = layout or awful.layout.layouts[0] }):view_only()
+ awful.tag.add(name, { screen = awful.screen.focused(), layout = layout or awful.layout.suit.tile }):view_only()
end
}
end
-- }}}
-- On the fly useless gaps change
- function util.useless_gaps_resize(thatmuch)
- local scr = awful.screen.focused()
- scr.selected_tag.gap = scr.selected_tag.gap + tonumber(thatmuch)
+ function util.useless_gaps_resize(thatmuch, s, t)
+ local scr = s or awful.screen.focused()
+ local tag = t or scr.selected_tag
+ tag.gap = tag.gap + tonumber(thatmuch)
awful.layout.arrange(scr)
end
-
--[[
-
- Licensed under MIT License
- * (c) 2013, Luke Bonham
- * (c) 2009, Uli Schlachter
- * (c) 2009, Majic
-
+
+ Licensed under MIT License
+ * (c) 2013, Luca CPZ
+ * (c) 2009, Uli Schlachter
+ * (c) 2009, Majic
+
--]]
- local string = { format = string.format }
+ local format = string.format
local setmetatable = setmetatable
-- Lain markup util submodule
-- lain.util.markup
local markup = { fg = {}, bg = {} }
- -- Convenience tags.
- function markup.bold(text) return '<b>' .. text .. '</b>' end
- function markup.italic(text) return '<i>' .. text .. '</i>' end
- function markup.strike(text) return '<s>' .. text .. '</s>' end
- function markup.underline(text) return '<u>' .. text .. '</u>' end
- function markup.monospace(text) return '<tt>' .. text .. '</tt>' end
- function markup.big(text) return '<big>' .. text .. '</big>' end
- function markup.small(text) return '<small>' .. text .. '</small>' end
+ -- Convenience tags
+ function markup.bold(text) return format("<b>%s</b>", text) end
+ function markup.italic(text) return format("<i>%s</i>", text) end
+ function markup.strike(text) return format("<s>%s</s>", text) end
+ function markup.underline(text) return format("<u>%s</u>", text) end
+ function markup.monospace(text) return format("<tt>%s</tt>", text) end
+ function markup.big(text) return format("<big>%s</big>", text) end
+ function markup.small(text) return format("<small>%s</small>", text) end
- -- Set the font.
+ -- Set the font
function markup.font(font, text)
- return '<span font="' .. font .. '">' .. text ..'</span>'
+ return format("<span font='%s'>%s</span>", font, text)
end
- -- Set the foreground.
+ -- Set the foreground
function markup.fg.color(color, text)
- return '<span foreground="' .. color .. '">' .. text .. '</span>'
+ return format("<span foreground='%s'>%s</span>", color, text)
end
- -- Set the background.
+ -- Set the background
function markup.bg.color(color, text)
- return '<span background="' .. color .. '">' .. text .. '</span>'
+ return format("<span background='%s'>%s</span>", color, text)
end
- -- Set foreground and background.
+ -- Set foreground and background
function markup.color(fg, bg, text)
- return string.format('<span foreground="%s" background="%s">%s</span>', fg, bg, text)
+ return format("<span foreground='%s' background='%s'>%s</span>", fg, bg, text)
end
- -- Set font and foreground.
+ -- Set font and foreground
function markup.fontfg(font, fg, text)
- return string.format('<span font="%s" foreground="%s">%s</span>', font, fg, text)
+ return format("<span font='%s' foreground='%s'>%s</span>", font, fg, text)
end
- -- Set font and background.
+ -- Set font and background
function markup.fontbg(font, bg, text)
- return string.format('<span font="%s" background="%s">%s</span>', font, bg, text)
+ return format("<span font='%s' background='%s'>%s</span>", font, bg, text)
end
- -- Set font, foreground and background.
+ -- Set font, foreground and background
function markup.fontcolor(font, fg, bg, text)
- return string.format('<span font="%s" foreground="%s" background="%s">%s</span>', font, fg, bg, text)
+ return format("<span font='%s' foreground='%s' background='%s'>%s</span>", font, fg, bg, text)
end
-- link markup.{fg,bg}(...) calls to markup.{fg,bg}.color(...)
--- /dev/null
+ --[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2017, Simon Désaulniers <sim.desaulniers@gmail.com>
+ * (c) 2017, Uli Schlachter
+ * (c) 2017, Jeferson Siqueira <jefersonlsiq@gmail.com>
+
+ --]]
+
+ -- Menu iterator with Naughty notifications
+ -- lain.util.menu_iterator
+
+ local naughty = require("naughty")
+ local helpers = require("lain.helpers")
+ local util = require("lain.util")
+ local atable = require("awful.util").table
+ local assert = assert
+ local pairs = pairs
+ local tconcat = table.concat
+ local unpack = unpack or table.unpack -- lua 5.1 retro-compatibility
+
+ local state = { cid = nil }
+
+ local function naughty_destroy_callback(reason)
+ local closed = naughty.notificationClosedReason
+ if reason == closed.expired or reason == closed.dismissedByUser then
+ local actions = state.index and state.menu[state.index - 1][2]
+ if actions then
+ for _,action in pairs(actions) do
+ -- don't try to call nil callbacks
+ if action then action() end
+ end
+ state.index = nil
+ end
+ end
+ end
+
+ -- Iterates over a menu.
+ -- After the timeout, callbacks associated to the last visited choice are
+ -- executed. Inputs:
+ -- * menu: a list of {label, {callbacks}} pairs
+ -- * timeout: time to wait before confirming the menu selection
+ -- * icon: icon to display in the notification of the chosen label
+ local function iterate(menu, timeout, icon)
+ local timeout = timeout or 4 -- default timeout for each menu entry
+ local icon = icon or nil -- icon to display on the menu
+
+ -- Build the list of choices
+ if not state.index then
+ state.menu = menu
+ state.index = 1
+ end
+
+ -- Select one and display the appropriate notification
+ local label
+ local next = state.menu[state.index]
+ state.index = state.index + 1
+
+ if not next then
+ label = "Cancel"
+ state.index = nil
+ else
+ label, _ = unpack(next)
+ end
+
+ state.cid = naughty.notify({
+ text = label,
+ icon = icon,
+ timeout = timeout,
+ screen = mouse.screen,
+ replaces_id = state.cid,
+ destroy = naughty_destroy_callback
+ }).id
+ end
+
+ -- Generates a menu compatible with the first argument of `iterate` function and
+ -- suitable for the following cases:
+ -- * all possible choices individually (partition of singletons);
+ -- * all possible subsets of the set of choices (powerset).
+ --
+ -- Inputs:
+ -- * args: an array containing the following members:
+ -- * choices: Array of choices (string) on which the menu will be
+ -- generated.
+ -- * name: Displayed name of the menu (in the form "name: choices").
+ -- * selected_cb: Callback to execute for each selected choice. Takes
+ -- the choice as a string argument. Can be `nil` (no action
+ -- to execute).
+ -- * rejected_cb: Callback to execute for each rejected choice (possible
+ -- choices which are not selected). Takes the choice as a
+ -- string argument. Can be `nil` (no action to execute).
+ -- * extra_choices: An array of extra { choice_str, callback_fun } pairs to be
+ -- added to the menu. Each callback_fun can be `nil`.
+ -- * combination: The combination of choices to generate. Possible values:
+ -- "powerset" and "single" (default).
+ -- Output:
+ -- * m: menu to be iterated over.
+ local function menu(args)
+ local choices = assert(args.choices or args[1])
+ local name = assert(args.name or args[2])
+ local selected_cb = args.selected_cb
+ local rejected_cb = args.rejected_cb
+ local extra_choices = args.extra_choices or {}
+
+ local ch_combinations = args.combination == "powerset" and helpers.powerset(choices) or helpers.trivial_partition_set(choices)
+
+ for _,c in pairs(extra_choices) do
+ ch_combinations = atable.join(ch_combinations, {{c[1]}})
+ end
+
+ local m = {} -- the menu
+
+ for _,c in pairs(ch_combinations) do
+ if #c > 0 then
+ local cbs = {}
+
+ -- selected choices
+ for _,ch in pairs(c) do
+ if atable.hasitem(choices, ch) then
+ cbs[#cbs + 1] = selected_cb and function() selected_cb(ch) end or nil
+ end
+ end
+
+ -- rejected choices
+ for _,ch in pairs(choices) do
+ if not atable.hasitem(c, ch) and atable.hasitem(choices, ch) then
+ cbs[#cbs + 1] = rejected_cb and function() rejected_cb(ch) end or nil
+ end
+ end
+
+ -- add user extra choices (like the choice "None" for example)
+ for _,x in pairs(extra_choices) do
+ if x[1] == c[1] then
+ cbs[#cbs + 1] = x[2]
+ end
+ end
+
+ m[#m + 1] = { name .. ": " .. tconcat(c, " + "), cbs }
+ end
+ end
+
+ return m
+ end
+
+ return { iterate = iterate, menu = menu }
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2016, Luke Bonham
- * (c) 2015, unknown
-
+
+ Licensed under GNU General Public License v2
+ * (c) 2016, Luca CPZ
+ * (c) 2015, unknown
+
--]]
local awful = require("awful")
local capi = { client = client }
-
- local math = { floor = math.floor }
- local string = { format = string.format }
-
+ local math = math
+ local string = string
local pairs = pairs
local screen = screen
-
local setmetatable = setmetatable
-- Quake-like Dropdown application spawn
for c in awful.client.iterate(function (c)
-- c.name may be changed!
return c.instance == self.name
- end, nil, self.screen)
+ end)
do
i = i + 1
if i == 1 then
client.floating = true
client.border_width = self.border
client.size_hints_honor = false
- client:geometry(self:compute_size())
+ client:geometry(self.geometry[self.screen.index] or self:compute_size())
-- Set not sticky and on top
client.sticky = false
function quake:compute_size()
-- skip if we already have a geometry for this screen
- if not self.geometry[self.screen] then
+ if not self.geometry[self.screen.index] then
local geom
if not self.overlap then
- geom = screen[self.screen].workarea
+ geom = screen[self.screen.index].workarea
else
- geom = screen[self.screen].geometry
+ geom = screen[self.screen.index].geometry
end
local width, height = self.width, self.height
if width <= 1 then width = math.floor(geom.width * width) - 2 * self.border end
if self.vert == "top" then y = geom.y
elseif self.vert == "bottom" then y = geom.height + geom.y - height
else y = geom.y + (geom.height - height)/2 end
- self.geometry[self.screen] = { x = x, y = y, width = width, height = height }
+ self.geometry[self.screen.index] = { x = x, y = y, width = width, height = height }
end
- return self.geometry[self.screen]
+ return self.geometry[self.screen.index]
end
function quake:new(config)
if self.followtag then self.screen = awful.screen.focused() end
local current_tag = self.screen.selected_tag
if current_tag and self.last_tag ~= current_tag and self.visible then
- self:display():move_to_tag(current_tag)
+ local c=self:display()
+ if c then
+ c:move_to_tag(current_tag)
+ end
else
self.visible = not self.visible
self:display()
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2015, Luke Bonham
- * (c) 2015, plotnikovanton
-
+
+ Licensed under GNU General Public License v2
+ * (c) 2015, Luca CPZ
+ * (c) 2015, plotnikovanton
+
--]]
- local wibox = require("wibox")
- local gears = require("gears")
+ local wibox = require("wibox")
+ local gears = require("gears")
-- Lain Cairo separators util submodule
-- lain.util.separators
-- Right
function separators.arrow_right(col1, col2)
local widget = wibox.widget.base.make_widget()
+ widget.col1 = col1
+ widget.col2 = col2
widget.fit = function(m, w, h)
return separators.width, separators.height
end
+ widget.update = function(col1, col2)
+ widget.col1 = col1
+ widget.col2 = col2
+ widget:emit_signal("widget::redraw_needed")
+ end
+
widget.draw = function(mycross, wibox, cr, width, height)
- if col2 ~= "alpha" then
- cr:set_source_rgb(gears.color.parse_color(col2))
+ if widget.col2 ~= "alpha" then
+ cr:set_source_rgb(gears.color.parse_color(widget.col2))
cr:new_path()
cr:move_to(0, 0)
cr:line_to(width, height/2)
cr:fill()
end
- if col1 ~= "alpha" then
- cr:set_source_rgb(gears.color.parse_color(col1))
+ if widget.col1 ~= "alpha" then
+ cr:set_source_rgb(gears.color.parse_color(widget.col1))
cr:new_path()
cr:move_to(0, 0)
cr:line_to(width, height/2)
-- Left
function separators.arrow_left(col1, col2)
local widget = wibox.widget.base.make_widget()
+ widget.col1 = col1
+ widget.col2 = col2
widget.fit = function(m, w, h)
return separators.width, separators.height
end
+ widget.update = function(col1, col2)
+ widget.col1 = col1
+ widget.col2 = col2
+ widget:emit_signal("widget::redraw_needed")
+ end
+
widget.draw = function(mycross, wibox, cr, width, height)
- if col1 ~= "alpha" then
- cr:set_source_rgb(gears.color.parse_color(col1))
+ if widget.col1 ~= "alpha" then
+ cr:set_source_rgb(gears.color.parse_color(widget.col1))
cr:new_path()
cr:move_to(width, 0)
cr:line_to(0, height/2)
cr:fill()
end
- if col2 ~= "alpha" then
+ if widget.col2 ~= "alpha" then
cr:new_path()
cr:move_to(width, 0)
cr:line_to(0, height/2)
cr:line_to(width, height)
cr:close_path()
- cr:set_source_rgb(gears.color.parse_color(col2))
+ cr:set_source_rgb(gears.color.parse_color(widget.col2))
cr:fill()
end
end
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2013, Luke Bonham
- * (c) 2010, Adrian C. <anrxc@sysphere.org>
-
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luca CPZ
+ * (c) 2010, Adrian C. <anrxc@sysphere.org>
+
--]]
local helpers = require("lain.helpers")
local shell = require("awful.util").shell
local wibox = require("wibox")
- local string = { match = string.match,
- format = string.format }
+ local string = string
-- ALSA volume
-- lain.widget.alsa
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2013, Luke Bonham
- * (c) 2013, Rman
-
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luca CPZ
+ * (c) 2013, Rman
+
--]]
- local helpers = require("lain.helpers")
- local awful = require("awful")
- local naughty = require("naughty")
- local wibox = require("wibox")
- local math = { modf = math.modf }
- local string = { format = string.format,
- match = string.match,
- rep = string.rep }
- local type, tonumber = type, tonumber
+ local helpers = require("lain.helpers")
+ local awful = require("awful")
+ local naughty = require("naughty")
+ local wibox = require("wibox")
+ local math = math
+ local string = string
+ local type = type
+ local tonumber = tonumber
-- ALSA volume bar
-- lain.widget.alsabar
},
_current_level = 0,
- _muted = false
+ _playback = "off"
}
local args = args or {}
local settings = args.settings or function() end
local width = args.width or 63
local height = args.height or 1
+ local margins = args.margins or 1
+ local paddings = args.paddings or 1
local ticks = args.ticks or false
local ticks_size = args.ticks_size or 7
+ local tick = args.tick or "|"
+ local tick_pre = args.tick_pre or "["
+ local tick_post = args.tick_post or "]"
+ local tick_none = args.tick_none or " "
alsabar.cmd = args.cmd or "amixer"
alsabar.channel = args.channel or "Master"
alsabar.notification_preset = args.notification_preset
if not alsabar.notification_preset then
- alsabar.notification_preset = {}
- alsabar.notification_preset.font = "Monospace 10"
+ alsabar.notification_preset = { font = "Monospace 10" }
end
local format_cmd = string.format("%s get %s", alsabar.cmd, alsabar.channel)
end
alsabar.bar = wibox.widget {
- forced_height = height,
- forced_width = width,
color = alsabar.colors.unmute,
background_color = alsabar.colors.background,
- margins = 1,
- paddings = 1,
+ forced_height = height,
+ forced_width = width,
+ margins = margins,
+ paddings = margins,
ticks = ticks,
ticks_size = ticks_size,
widget = wibox.widget.progressbar
function alsabar.update(callback)
helpers.async(format_cmd, function(mixer)
- local volu,mute = string.match(mixer, "([%d]+)%%.*%[([%l]*)")
- if (volu and tonumber(volu) ~= alsabar._current_level) or (mute and string.match(mute, "on") ~= alsabar._muted) then
- alsabar._current_level = tonumber(volu) or alsabar._current_level
+ local vol, playback = string.match(mixer, "([%d]+)%%.*%[([%l]*)")
+
+ if not vol or not playback then return end
+
+ if vol ~= alsabar._current_level or playback ~= alsabar._playback then
+ alsabar._current_level = tonumber(vol)
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]")
+ if alsabar._current_level == 0 or playback == "off" then
+ alsabar._playback = playback
+ alsabar.tooltip:set_text("[Muted]")
alsabar.bar.color = alsabar.colors.mute
else
- alsabar._muted = false
- alsabar.tooltip:set_text(string.format("%s: %s", alsabar.channel, volu))
+ alsabar._playback = "on"
+ alsabar.tooltip:set_text(string.format("%s: %s", alsabar.channel, vol))
alsabar.bar.color = alsabar.colors.unmute
end
- volume_now = {}
- volume_now.level = tonumber(volu)
- volume_now.status = mute
+ volume_now = {
+ level = alsabar._current_level,
+ status = alsabar._playback
+ }
settings()
alsabar.update(function()
local preset = alsabar.notification_preset
- if alsabar._muted then
- preset.title = string.format("%s - Muted", alsabar.channel)
- else
- preset.title = string.format("%s - %s%%", alsabar.channel, alsabar._current_level)
+ preset.title = string.format("%s - %s%%", alsabar.channel, alsabar._current_level)
+
+ if alsabar._playback == "off" then
+ preset.title = preset.title .. " Muted"
+ end
+
+ -- tot is the maximum number of ticks to display in the notification
+ local tot = alsabar.notification_preset.max_ticks
+
+ if not tot then
+ local wib = awful.screen.focused().mywibox
+ -- if we can grab mywibox, tot is defined as its height if
+ -- horizontal, or width otherwise
+ if wib then
+ if wib.position == "left" or wib.position == "right" then
+ tot = wib.width
+ else
+ tot = wib.height
+ end
+ -- fallback: default horizontal wibox height
+ else
+ tot = 20
+ end
end
- int = math.modf((alsabar._current_level / 100) * awful.screen.focused().mywibox.height)
- preset.text = string.format("[%s%s]", string.rep("|", int),
- string.rep(" ", awful.screen.focused().mywibox.height - int))
+ int = math.modf((alsabar._current_level / 100) * tot)
+ preset.text = string.format(
+ "%s%s%s%s",
+ tick_pre,
+ string.rep(tick, int),
+ string.rep(tick_none, tot - int),
+ tick_post
+ )
if alsabar.followtag then preset.screen = awful.screen.focused() end
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2013, Luke Bonham
- * (c) 2010-2012, Peter Hofmann
-
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luca CPZ
+ * (c) 2010-2012, Peter Hofmann
+
--]]
- local first_line = require("lain.helpers").first_line
- local newtimer = require("lain.helpers").newtimer
- local naughty = require("naughty")
- local wibox = require("wibox")
- local math = { abs = math.abs,
- floor = math.floor,
- log10 = math.log10,
- min = math.min }
- local string = { format = string.format }
- local ipairs = ipairs
- local tonumber = tonumber
+ local helpers = require("lain.helpers")
+ local fs = require("gears.filesystem")
+ local naughty = require("naughty")
+ local wibox = require("wibox")
+ local math = math
+ local string = string
+ local ipairs = ipairs
+ local tonumber = tonumber
-- Battery infos
-- lain.widget.bat
local function factory(args)
- local bat = { widget = wibox.widget.textbox() }
- local args = args or {}
- local timeout = args.timeout or 30
- local batteries = args.batteries or (args.battery and {args.battery}) or {"BAT0"}
- local ac = args.ac or "AC0"
- local notify = args.notify or "on"
- local n_perc = args.n_perc or { 5, 15 }
- local settings = args.settings or function() end
+ local pspath = args.pspath or "/sys/class/power_supply/"
+
+ if not fs.is_dir(pspath) then
+ naughty.notify { text = "lain.widget.bat: invalid power supply path", timeout = 0 }
+ return
+ end
+
+ local bat = { widget = wibox.widget.textbox() }
+ local args = args or {}
+ local timeout = args.timeout or 30
+ local notify = args.notify or "on"
+ local full_notify = args.full_notify or notify
+ local n_perc = args.n_perc or { 5, 15 }
+ local batteries = args.batteries or (args.battery and {args.battery}) or {}
+ local ac = args.ac or "AC0"
+ local settings = args.settings or function() end
+
+ function bat.get_batteries()
+ helpers.line_callback("ls -1 " .. pspath, function(line)
+ local bstr = string.match(line, "BAT%w+")
+ if bstr then
+ batteries[#batteries + 1] = bstr
+ else
+ ac = string.match(line, "A%w+") or "AC0"
+ end
+ end)
+ end
+
+ if #batteries == 0 then bat.get_batteries() end
bat_notification_critical_preset = {
title = "Battery exhausted",
bg = "#CDCDCD"
}
+ bat_notification_charged_preset = {
+ title = "Battery full",
+ text = "You can unplug the cable",
+ timeout = 15,
+ fg = "#202020",
+ bg = "#CDCDCD"
+ }
+
bat_now = {
status = "N/A",
ac_status = "N/A",
bat_now.n_perc[i] = 0
end
+ -- used to notify full charge only once before discharging
+ local fullnotification = false
+
function bat.update()
local sum_rate_current = 0
local sum_rate_voltage = 0
local sum_rate_energy = 0
local sum_energy_now = 0
local sum_energy_full = 0
- local pspath = "/sys/class/power_supply/"
for i, battery in ipairs(batteries) do
local bstr = pspath .. battery
- local present = first_line(bstr .. "/present")
+ local present = helpers.first_line(bstr .. "/present")
if tonumber(present) == 1 then
-- current_now(I)[uA], voltage_now(U)[uV], power_now(P)[uW]
- local rate_current = tonumber(first_line(bstr .. "/current_now"))
- local rate_voltage = tonumber(first_line(bstr .. "/voltage_now"))
- local rate_power = tonumber(first_line(bstr .. "/power_now"))
+ local rate_current = tonumber(helpers.first_line(bstr .. "/current_now"))
+ local rate_voltage = tonumber(helpers.first_line(bstr .. "/voltage_now"))
+ local rate_power = tonumber(helpers.first_line(bstr .. "/power_now"))
-- energy_now(P)[uWh], charge_now(I)[uAh]
- local energy_now = tonumber(first_line(bstr .. "/energy_now") or
- first_line(bstr .. "/charge_now"))
+ local energy_now = tonumber(helpers.first_line(bstr .. "/energy_now") or
+ helpers.first_line(bstr .. "/charge_now"))
-- energy_full(P)[uWh], charge_full(I)[uAh]
- local energy_full = tonumber(first_line(bstr .. "/energy_full") or
- first_line(bstr .. "/charge_full"))
+ local energy_full = tonumber(helpers.first_line(bstr .. "/energy_full") or
+ helpers.first_line(bstr .. "/charge_full"))
- local energy_percentage = tonumber(first_line(bstr .. "/capacity")) or
+ local energy_percentage = tonumber(helpers.first_line(bstr .. "/capacity")) or
math.floor((energy_now / energy_full) * 100)
- bat_now.n_status[i] = first_line(bstr .. "/status") or "N/A"
+ bat_now.n_status[i] = helpers.first_line(bstr .. "/status") or "N/A"
bat_now.n_perc[i] = energy_percentage or bat_now.n_perc[i]
sum_rate_current = sum_rate_current + (rate_current or 0)
bat_now.status = status
end
end
- bat_now.ac_status = tonumber(first_line(string.format("%s%s/online", pspath, ac))) or "N/A"
+ bat_now.ac_status = tonumber(helpers.first_line(string.format("%s%s/online", pspath, ac))) or "N/A"
if bat_now.status ~= "N/A" then
if bat_now.status ~= "Full" and sum_rate_power == 0 and bat_now.ac_status == 1 then
widget = bat.widget
settings()
- -- notifications for critical and low levels
- if notify == "on" and bat_now.status == "Discharging" then
- if tonumber(bat_now.perc) <= n_perc[1] then
- bat.id = naughty.notify({
- preset = bat_notification_critical_preset,
- replaces_id = bat.id
- }).id
- elseif tonumber(bat_now.perc) <= n_perc[2] then
+ -- notifications for critical, low, and full levels
+ if notify == "on" then
+ if bat_now.status == "Discharging" then
+ if tonumber(bat_now.perc) <= n_perc[1] then
+ bat.id = naughty.notify({
+ preset = bat_notification_critical_preset,
+ replaces_id = bat.id
+ }).id
+ elseif tonumber(bat_now.perc) <= n_perc[2] then
+ bat.id = naughty.notify({
+ preset = bat_notification_low_preset,
+ replaces_id = bat.id
+ }).id
+ end
+ fullnotification = false
+ elseif bat_now.status == "Full" and full_notify == "on" and not fullnotification then
bat.id = naughty.notify({
- preset = bat_notification_low_preset,
+ preset = bat_notification_charged_preset,
replaces_id = bat.id
}).id
+ fullnotification = true
end
end
end
- newtimer("batteries", timeout, bat.update)
+ helpers.newtimer("batteries", timeout, bat.update)
return bat
end
--- /dev/null
+ --[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2018, Luca CPZ
+
+ --]]
+
+ local helpers = require("lain.helpers")
+ local markup = require("lain.util.markup")
+ local awful = require("awful")
+ local naughty = require("naughty")
+ local floor = math.floor
+ local os = os
+ local pairs = pairs
+ local string = string
+ local tconcat = table.concat
+ local type = type
+ local tonumber = tonumber
+ local tostring = tostring
+
+ -- Calendar notification
+ -- lain.widget.cal
+
+ local function factory(args)
+ args = args or {}
+ local cal = {
+ attach_to = args.attach_to or {},
+ week_start = args.week_start or 2,
+ three = args.three or false,
+ followtag = args.followtag or false,
+ week_number = args.week_number or "none",
+ week_number_format = args.week_number_format or args.week_number == "left" and "%3d | " or "| %-3d",
+ icons = args.icons or helpers.icons_dir .. "cal/white/",
+ notification_preset = args.notification_preset or {
+ font = "Monospace 10", fg = "#FFFFFF", bg = "#000000"
+ }
+ }
+
+ function cal.get_week_number(m, st_day, x)
+ return string.format(cal.week_number_format, os.date("%V", m) + (x ~= 0 and floor((x + st_day) / 7) - 1 or 0))
+ end
+
+ function cal.sum_week_days(x, y)
+ return (x + y) % 7
+ end
+
+ function cal.build(month, year)
+ local current_month, current_year = tonumber(os.date("%m")), tonumber(os.date("%Y"))
+ local is_current_month = (not month or not year) or (month == current_month and year == current_year)
+ local today = is_current_month and tonumber(os.date("%d")) -- otherwise nil and not highlighted
+ local t = os.time { year = year or current_year, month = month and month+1 or current_month+1, day = 0 }
+ local d = os.date("*t", t)
+ local mth_days, st_day, this_month = d.day, (d.wday-d.day-cal.week_start+1)%7, os.date("%B %Y", t)
+ local notifytable = { [1] = string.format("%s%s\n", string.rep(" ", floor((28 - this_month:len())/2)), markup.bold(this_month)) }
+ for x = 0,6 do notifytable[#notifytable+1] = os.date("%a", os.time { year=2006, month=1, day=x+cal.week_start }):sub(1, 3) .. " " end
+ notifytable[#notifytable] = string.format("%s\n%s", notifytable[#notifytable]:sub(1, -2), string.rep(" ", st_day*4))
+ local strx
+ for x = 1,mth_days do
+ strx = x
+ if x == today then
+ if x < 10 then x = " " .. x end
+ strx = markup.bold(markup.color(cal.notification_preset.bg, cal.notification_preset.fg, x) .. " ")
+ end
+ strx = string.format("%s%s", string.rep(" ", 3 - tostring(x):len()), strx)
+ notifytable[#notifytable+1] = string.format("%-4s%s", strx, (x+st_day)%7==0 and x ~= mth_days and "\n" or "")
+ end
+ if string.len(cal.icons or "") > 0 and today then cal.icon = cal.icons .. today .. ".png" end
+ cal.month, cal.year = d.month, d.year
+
+ if cal.week_number ~= "none" then
+ local m = os.time { year = year or current_year, month = month and month or current_month, day = 0 }
+ local head_prepend = string.rep(" ", tostring(string.format(cal.week_number_format, 0)):len())
+
+ if cal.week_number == "left" then
+ notifytable[1] = head_prepend .. notifytable[1] -- month-year row
+ notifytable[2] = head_prepend .. notifytable[2] -- weekdays row
+ notifytable[8] = notifytable[8]:gsub("\n", "\n" .. cal.get_week_number(m, st_day, 0)) -- first week of the month
+
+ for x = 10,#notifytable do
+ if cal.sum_week_days(st_day, x) == 2 then
+ notifytable[x] = cal.get_week_number(m, st_day, x) .. notifytable[x]
+ end
+ end
+ elseif cal.week_number == "right" then
+ notifytable[8] = notifytable[8]:gsub("\n", head_prepend .. "\n") -- weekdays row
+ for x = 9,#notifytable do
+ if cal.sum_week_days(st_day, x) == 1 then
+ notifytable[x] = notifytable[x]:gsub("\n", cal.get_week_number(m, st_day, x - 7) .. "\n")
+ end
+ end
+ -- last week of the month
+ local end_days = cal.sum_week_days(st_day, mth_days)
+ if end_days ~= 0 then end_days = 7 - end_days end
+ notifytable[#notifytable] = notifytable[#notifytable] .. string.rep(" ", 4 * end_days) .. cal.get_week_number(m, st_day, mth_days + end_days)
+ end
+ end
+
+ return notifytable
+ end
+
+ function cal.getdate(month, year, offset)
+ if not month or not year then
+ month = tonumber(os.date("%m"))
+ year = tonumber(os.date("%Y"))
+ end
+
+ month = month + offset
+
+ while month > 12 do
+ month = month - 12
+ year = year + 1
+ end
+
+ while month < 1 do
+ month = month + 12
+ year = year - 1
+ end
+
+ return month, year
+ end
+
+ function cal.hide()
+ if not cal.notification then return end
+ naughty.destroy(cal.notification)
+ cal.notification = nil
+ end
+
+ function cal.show(seconds, month, year, scr)
+ cal.notification_preset.text = tconcat(cal.build(month, year))
+
+ if cal.three then
+ local current_month, current_year = cal.month, cal.year
+ local prev_month, prev_year = cal.getdate(cal.month, cal.year, -1)
+ local next_month, next_year = cal.getdate(cal.month, cal.year, 1)
+ cal.notification_preset.text = string.format("%s\n\n%s\n\n%s",
+ tconcat(cal.build(prev_month, prev_year)), cal.notification_preset.text,
+ tconcat(cal.build(next_month, next_year)))
+ cal.month, cal.year = current_month, current_year
+ end
+
+ cal.hide()
+ cal.notification = naughty.notify {
+ preset = cal.notification_preset,
+ screen = cal.followtag and awful.screen.focused() or scr or 1,
+ icon = cal.icon,
+ timeout = type(seconds) == "number" and seconds or cal.notification_preset.timeout or 5
+ }
+ end
+
+ function cal.hover_on() cal.show(0) end
+ function cal.move(offset)
+ local offset = offset or 0
+ cal.month, cal.year = cal.getdate(cal.month, cal.year, offset)
+ cal.show(0, cal.month, cal.year)
+ end
+ function cal.prev() cal.move(-1) end
+ function cal.next() cal.move( 1) end
+
+ function cal.attach(widget)
+ widget:connect_signal("mouse::enter", cal.hover_on)
+ widget:connect_signal("mouse::leave", cal.hide)
+ widget:buttons(awful.util.table.join(
+ awful.button({}, 1, cal.prev),
+ awful.button({}, 3, cal.next),
+ awful.button({}, 2, cal.hover_on),
+ awful.button({}, 5, cal.prev),
+ awful.button({}, 4, cal.next)))
+ end
+
+ for _, widget in pairs(cal.attach_to) do cal.attach(widget) end
+
+ return cal
+ end
+
+ return factory
-
--[[
-
- Lain
- Layouts, widgets and utilities for Awesome WM
-
- Users contributed widgets section
-
- Licensed under GNU General Public License v2
- * (c) 2013, Luke Bonham
-
+
+ Lain
+ Layouts, widgets and utilities for Awesome WM
+
+ Users contributed widgets section
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luca CPZ
+
--]]
local wrequire = require("lain.helpers").wrequire
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2014, anticlockwise <http://github.com/anticlockwise>
-
+
+ Licensed under GNU General Public License v2
+ * (c) 2014, anticlockwise <http://github.com/anticlockwise>
+
--]]
local helpers = require("lain.helpers")
local escape_f = require("awful.util").escape
local naughty = require("naughty")
local wibox = require("wibox")
- local os = { getenv = os.getenv }
- local string = { format = string.format,
- gmatch = string.gmatch }
+ local os = os
+ local string = string
-- MOC audio player
-- lain.widget.contrib.moc
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2014, blueluke <http://github.com/blueluke>
-
+
+ Licensed under GNU General Public License v2
+ * (c) 2017, Luca CPZ
+ * (c) 2014, blueluke <http://github.com/blueluke>
+
--]]
local async = require("lain.helpers").async
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2013, Jan Xie
-
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Jan Xie
+
--]]
local helpers = require("lain.helpers")
local markup = require("lain.util").markup
local awful = require("awful")
local naughty = require("naughty")
- local string = { format = string.format, gsub = string.gsub }
+ local mouse = mouse
+ local string = string
-- Taskwarrior notification
-- lain.widget.contrib.task
end
function task.show(scr)
- task.hide()
+ task.notification_preset.screen = task.followtag and awful.screen.focused() or scr or 1
- if task.followtag then
- task.notification_preset.screen = awful.screen.focused()
- elseif scr then
- task.notification_preset.screen = scr
- end
+ helpers.async({ awful.util.shell, "-c", task.show_cmd }, function(f)
+ local widget_focused = true
+
+ if mouse.current_widgets then
+ widget_focused = false
+ for _,v in ipairs(mouse.current_widgets) do
+ if task.widget == v then
+ widget_focused = true
+ break
+ end
+ end
+ end
- helpers.async(task.show_cmd, function(f)
- task.notification = naughty.notify({
- preset = task.notification_preset,
- title = task.show_cmd,
- text = markup.font(task.notification_preset.font,
- awful.util.escape(f:gsub("\n*$", "")))
- })
+ if widget_focused then
+ task.hide()
+ task.notification = naughty.notify {
+ preset = task.notification_preset,
+ title = "task next",
+ text = markup.font(task.notification_preset.font,
+ awful.util.escape(f:gsub("\n*$", "")))
+ }
+ end
end)
end
helpers.async(t, function(f)
naughty.notify {
preset = task.notification_preset,
- title = t,
- text = markup.font(task.notification_preset.font,
- awful.util.escape(f:gsub("\n*$", "")))
+ title = t,
+ text = markup.font(task.notification_preset.font,
+ awful.util.escape(f:gsub("\n*$", "")))
}
end)
end,
task.prompt_text = args.prompt_text or "Enter task command: "
task.followtag = args.followtag or false
task.notification_preset = args.notification_preset
+ task.widget = widget
if not task.notification_preset then
task.notification_preset = {
--- /dev/null
+ --[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2018, Luca CPZ
+ * (c) 2013, Conor Heine
+
+ --]]
+
+ local helpers = require("lain.helpers")
+ local focused = require("awful.screen").focused
+ local gears = require("gears")
+ local naughty = require("naughty")
+ local wibox = require("wibox")
+ local string = string
+ local type = type
+
+ -- ThinkPad battery infos and widget creator
+ -- http://www.thinkwiki.org/wiki/Tp_smapi
+ -- lain.widget.contrib.tp_smapi
+
+ local function factory(apipath)
+ local tp_smapi = {
+ path = apipath or "/sys/devices/platform/smapi"
+ }
+
+ function tp_smapi.get(batid, feature)
+ return helpers.first_line(string.format("%s/%s/%s", tp_smapi.path, batid or "BAT0", feature or ""))
+ end
+
+ function tp_smapi.installed(batid)
+ return tp_smapi.get(batid, "installed") == "1"
+ end
+
+ function tp_smapi.status(batid)
+ return tp_smapi.get(batid, "state")
+ end
+
+ function tp_smapi.percentage(batid)
+ return tp_smapi.get(batid, "remaining_percent")
+ end
+
+ -- either running or charging time
+ function tp_smapi.time(batid)
+ local status = tp_smapi.status(batid)
+ local mins_left = tp_smapi.get(batid, string.match(string.lower(status), "discharging") and "remaining_running_time" or "remaining_charging_time")
+ if not string.find(mins_left, "^%d+") then return "N/A" end
+ return string.format("%02d:%02d", math.floor(mins_left / 60), mins_left % 60) -- HH:mm
+ end
+
+ function tp_smapi.hide()
+ if not tp_smapi.notification then return end
+ naughty.destroy(tp_smapi.notification)
+ tp_smapi.notification = nil
+ end
+
+ function tp_smapi.show(batid, seconds, scr)
+ if not tp_smapi.installed(batid) then return end
+
+ local mfgr = tp_smapi.get(batid, "manufacturer") or "no_mfgr"
+ local model = tp_smapi.get(batid, "model") or "no_model"
+ local chem = tp_smapi.get(batid, "chemistry") or "no_chem"
+ local status = tp_smapi.get(batid, "state")
+ local time = tp_smapi.time(batid)
+ local msg = ""
+
+ if status and status ~= "idle" then
+ msg = string.format("[%s] %s %s", status, time ~= "N/A" and time or "unknown remaining time",
+ string.lower(status):gsub(" ", ""):gsub("\n", "") == "charging" and " until charged" or " remaining")
+ else
+ msg = "On AC power"
+ end
+
+ tp_smapi.hide()
+ tp_smapi.notification = naughty.notify {
+ title = string.format("%s: %s %s (%s)", batid, mfgr, model, chem),
+ text = msg,
+ timeout = type(seconds) == "number" and seconds or 0,
+ screen = scr or focused()
+ }
+ end
+
+ function tp_smapi.create_widget(args)
+ local args = args or {}
+ local pspath = args.pspath or "/sys/class/power_supply/"
+ local batteries = args.batteries or (args.battery and {args.battery}) or {}
+ local timeout = args.timeout or 30
+ local settings = args.settings or function() end
+
+ if #batteries == 0 then
+ helpers.line_callback("ls -1 " .. pspath, function(line)
+ local bstr = string.match(line, "BAT%w+")
+ if bstr then batteries[#batteries + 1] = bstr end
+ end)
+ end
+
+ local all_batteries_installed = true
+
+ for i, battery in ipairs(batteries) do
+ if not tp_smapi.installed(battery) then
+ naughty.notify {
+ preset = naughty.config.critical,
+ title = "tp_smapi: error while creating widget",
+ text = string.format("battery %s is not installed", battery)
+ }
+ all_batteries_installed = false
+ break
+ end
+ end
+
+ if not all_batteries_installed then return end
+
+ tpbat = {
+ batteries = batteries,
+ widget = args.widget or wibox.widget.textbox()
+ }
+
+ function tpbat.update()
+ tpbat_now = {
+ n_status = {},
+ n_perc = {},
+ n_time = {},
+ status = "N/A"
+ }
+
+ for i = 1, #batteries do
+ tpbat_now.n_status[i] = tp_smapi.status(batteries[i]) or "N/A"
+ tpbat_now.n_perc[i] = tp_smapi.percentage(batteries[i])
+ tpbat_now.n_time[i] = tp_smapi.time(batteries[i]) or "N/A"
+
+ if not tpbat_now.n_status[i]:lower():match("full") then
+ tpbat_now.status = tpbat_now.n_status[i]
+ end
+ end
+
+ widget = tpbat.widget -- backwards compatibility
+ settings()
+ end
+
+ helpers.newtimer("thinkpad-batteries", timeout, tpbat.update)
+
+ return tpbat
+ end
+
+ return tp_smapi
+ end
+
+ return factory
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2013, Luke Bonham
- * (c) 2010-2012, Peter Hofmann
-
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luca CPZ
+ * (c) 2010-2012, Peter Hofmann
+
--]]
local helpers = require("lain.helpers")
local wibox = require("wibox")
- local math = { ceil = math.ceil }
- local string = { format = string.format,
- gmatch = string.gmatch }
+ local math = math
+ local string = string
local tostring = tostring
-- CPU usage
-- 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 = helpers.lines_match("cpu","/proc/stat")
-
- for index,time in pairs(times) do
+ for index,time in pairs(helpers.lines_match("cpu","/proc/stat")) do
local coreid = index - 1
local core = cpu.core[coreid] or
{ last_active = 0 , last_total = 0, usage = 0 }
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2013, Luke Bonham
-
- --]]
- local helpers = require("lain.helpers")
- local shell = require("awful.util").shell
- local focused = require("awful.screen").focused
- local wibox = require("wibox")
- local naughty = require("naughty")
- local string = string
- local tonumber = tonumber
+ Licensed under GNU General Public License v2
+ * (c) 2018, Uli Schlacter
+ * (c) 2018, Otto Modinos
+ * (c) 2013, Luca CPZ
+
+ --]]
- -- File system disk space usage
+ local helpers = require("lain.helpers")
+ local Gio = require("lgi").Gio
+ local focused = require("awful.screen").focused
+ local wibox = require("wibox")
+ local naughty = require("naughty")
+ local math = math
+ local string = string
+ local tconcat = table.concat
+ local type = type
+ local tonumber = tonumber
+ local query_size = Gio.FILE_ATTRIBUTE_FILESYSTEM_SIZE
+ local query_free = Gio.FILE_ATTRIBUTE_FILESYSTEM_FREE
+ local query_used = Gio.FILE_ATTRIBUTE_FILESYSTEM_USED
+ local query = query_size .. "," .. query_free .. "," .. query_used
+
+ -- File systems info
-- lain.widget.fs
local function factory(args)
- local fs = { unit = { ["mb"] = 1024, ["gb"] = 1024^2 }, widget = wibox.widget.textbox() }
+ local fs = {
+ widget = wibox.widget.textbox(),
+ units = {
+ [1] = "Kb", [2] = "Mb", [3] = "Gb",
+ [4] = "Tb", [5] = "Pb", [6] = "Eb",
+ [7] = "Zb", [8] = "Yb"
+ }
+ }
function fs.hide()
if not fs.notification then return end
end
function fs.show(seconds, scr)
- fs.update()
- fs.hide()
-
- if fs.followtag then
- fs.notification_preset.screen = focused()
- else
- fs.notification_preset.screen = scr or 1
- end
-
- fs.notification = naughty.notify({
+ fs.hide(); fs.update()
+ fs.notification_preset.screen = fs.followtag and focused() or scr or 1
+ fs.notification = naughty.notify {
preset = fs.notification_preset,
- timeout = seconds or 5
- })
+ timeout = type(seconds) == "number" and seconds or 5
+ }
end
- local args = args or {}
- local timeout = args.timeout or 600
- local partition = args.partition or "/"
- local showpopup = args.showpopup or "on"
- local notify = args.notify or "on"
- local settings = args.settings or function() end
+ local args = args or {}
+ local timeout = args.timeout or 600
+ local partition = args.partition
+ local threshold = args.threshold or 99
+ local showpopup = args.showpopup or "on"
+ local settings = args.settings or function() end
- fs.options = args.options
fs.followtag = args.followtag or false
fs.notification_preset = args.notification_preset
}
end
- helpers.set_map(partition, false)
-
function fs.update()
- fs_info, fs_now = {}, {}
- helpers.async({ shell, "-c", "/usr/bin/env LC_ALL=C df -k --output=target,size,used,avail,pcent" }, function(f)
- for line in string.gmatch(f, "\n[^\n]+") do
- local m,s,u,a,p = string.match(line, "(/.-%s).-(%d+).-(%d+).-(%d+).-([%d]+)%%")
- m = m:gsub(" ", "") -- clean target from any whitespace
-
- fs_info[m .. " size_mb"] = string.format("%.1f", tonumber(s) / fs.unit["mb"])
- fs_info[m .. " size_gb"] = string.format("%.1f", tonumber(s) / fs.unit["gb"])
- fs_info[m .. " used_mb"] = string.format("%.1f", tonumber(u) / fs.unit["mb"])
- fs_info[m .. " used_gb"] = string.format("%.1f", tonumber(u) / fs.unit["gb"])
- fs_info[m .. " used_p"] = p
- fs_info[m .. " avail_mb"] = string.format("%.1f", tonumber(a) / fs.unit["mb"])
- fs_info[m .. " avail_gb"] = string.format("%.1f", tonumber(a) / fs.unit["gb"])
- fs_info[m .. " avail_p"] = string.format("%d", 100 - tonumber(p))
+ local notifytable = { [1] = string.format("%-10s %4s\t%6s\t%6s\t\n", "path", "used", "free", "size") }
+ local pathlen = 10
+ local maxpathidx = 1
+ fs_now = {}
+
+ for _, mount in ipairs(Gio.unix_mounts_get()) do
+ local path = Gio.unix_mount_get_mount_path(mount)
+ local root = Gio.File.new_for_path(path)
+ local info = root:query_filesystem_info(query)
+
+ if info then
+ local size = info:get_attribute_uint64(query_size)
+ local used = info:get_attribute_uint64(query_used)
+ local free = info:get_attribute_uint64(query_free)
+
+ if size > 0 then
+ local units = math.floor(math.log(size)/math.log(1024))
+
+ fs_now[path] = {
+ units = fs.units[units],
+ percentage = math.floor(100 * used / size), -- used percentage
+ size = size / math.pow(1024, math.floor(units)),
+ used = used / math.pow(1024, math.floor(units)),
+ free = free / math.pow(1024, math.floor(units))
+ }
+
+ if fs_now[path].percentage > 0 then -- don't notify unused file systems
+ notifytable[#notifytable+1] = string.format("\n%-10s %3s%%\t%6.2f\t%6.2f\t%s", path,
+ math.floor(fs_now[path].percentage), fs_now[path].free, fs_now[path].size,
+ fs_now[path].units)
+
+ if #path > pathlen then
+ pathlen = #path
+ maxpathidx = #notifytable
+ end
+ end
+ end
end
+ end
+
+ widget = fs.widget
+ settings()
- fs_now.size_mb = fs_info[partition .. " size_mb"] or "N/A"
- fs_now.size_gb = fs_info[partition .. " size_gb"] or "N/A"
- fs_now.used = fs_info[partition .. " used_p"] or "N/A"
- fs_now.used_mb = fs_info[partition .. " used_mb"] or "N/A"
- fs_now.used_gb = fs_info[partition .. " used_gb"] or "N/A"
- fs_now.available = fs_info[partition .. " avail_p"] or "N/A"
- fs_now.available_mb = fs_info[partition .. " avail_mb"] or "N/A"
- fs_now.available_gb = fs_info[partition .. " avail_gb"] or "N/A"
-
- notification_preset = fs.notification_preset
- widget = fs.widget
- settings()
-
- if notify == "on" and #fs_now.used > 0 and tonumber(fs_now.used) >= 99 and not helpers.get_map(partition) then
- naughty.notify({
+ if partition and fs_now[partition] and fs_now[partition].percentage >= threshold then
+ if not helpers.get_map(partition) then
+ naughty.notify {
preset = naughty.config.presets.critical,
title = "Warning",
- text = partition .. " is full",
- })
+ text = string.format("%s is above %d%% (%d%%)", partition, threshold, fs_now[partition].percentage)
+ }
helpers.set_map(partition, true)
else
helpers.set_map(partition, false)
end
- end)
+ end
+
+ if pathlen > 10 then -- if are there paths longer than 10 chars, reformat first column accordingly
+ local pathspaces
+ for i = 1, #notifytable do
+ pathspaces = notifytable[i]:match("[ ]+")
+ if i ~= maxpathidx and pathspaces then
+ notifytable[i] = notifytable[i]:gsub(pathspaces, pathspaces .. string.rep(" ", pathlen - 10))
+ end
+ end
+ end
- local notifycmd = (fs.options and string.format("dfs %s", fs.options)) or "dfs"
- helpers.async(helpers.scripts_dir .. notifycmd, function(ws)
- fs.notification_preset.text = ws:gsub("\n*$", "")
- end)
+ fs.notification_preset.text = tconcat(notifytable)
end
if showpopup == "on" then
fs.widget:connect_signal('mouse::leave', function () fs.hide() end)
end
- helpers.newtimer(partition, timeout, fs.update)
+ helpers.newtimer(partition or "fs", timeout, fs.update)
return fs
end
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2013, Luke Bonham
-
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luca CPZ
+
--]]
local helpers = require("lain.helpers")
local naughty = require("naughty")
local wibox = require("wibox")
- local string = { format = string.format,
- gsub = string.gsub }
+ local awful = require("awful")
+ local string = string
local type = type
local tonumber = tonumber
-- lain.widget.imap
local function factory(args)
- local imap = { widget = wibox.widget.textbox() }
- 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 followtag = args.followtag or false
- local settings = args.settings or function() end
+ local imap = { widget = wibox.widget.textbox() }
+ 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 pwdtimeout = args.pwdtimeout or 10
+ local is_plain = args.is_plain or false
+ local followtag = args.followtag or false
+ local notify = args.notify or "on"
+ local settings = args.settings or function() end
local head_command = "curl --connect-timeout 3 -fsm 3"
- local request = "-X 'SEARCH (UNSEEN)'"
+ local request = "-X 'STATUS INBOX (MESSAGES RECENT UNSEEN)'"
if not server or not mail or not password then return end
+ mail_notification_preset = {
+ icon = helpers.icons_dir .. "mail.png",
+ position = "top_left"
+ }
+
helpers.set_map(mail, 0)
if not is_plain then
if type(password) == "string" or type(password) == "table" then
helpers.async(password, function(f) password = f:gsub("\n", "") end)
elseif type(password) == "function" then
- local p = password()
+ imap.pwdtimer = helpers.newtimer(mail .. "-password", pwdtimeout, function()
+ local retrieved_password, try_again = password()
+ if not try_again then
+ imap.pwdtimer:stop() -- stop trying to retrieve
+ password = retrieved_password or "" -- failsafe
+ end
+ end, true, true)
end
end
- function update()
- mail_notification_preset = {
- icon = helpers.icons_dir .. "mail.png",
- position = "top_left"
- }
+ function imap.update()
+ -- do not update if the password has not been retrieved yet
+ if type(password) ~= "string" then return end
- if followtag then
- mail_notification_preset.screen = awful.screen.focused()
- end
-
- curl = string.format("%s --url imaps://%s:%s/INBOX -u %s:%q %s -k",
- head_command, server, port, mail, password, request)
+ local curl = string.format("%s --url imaps://%s:%s/INBOX -u %s:'%s' %s -k",
+ head_command, server, port, mail, password, request)
helpers.async(curl, function(f)
- _, mailcount = string.gsub(f, "%d+", "")
- _ = nil
+ imap_now = { ["MESSAGES"] = 0, ["RECENT"] = 0, ["UNSEEN"] = 0 }
+ for s,d in f:gmatch("(%w+)%s+(%d+)") do imap_now[s] = tonumber(d) end
+ mailcount = imap_now["UNSEEN"] -- backwards compatibility
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 })
+ if notify == "on" and mailcount and mailcount >= 1 and mailcount > helpers.get_map(mail) then
+ if followtag then mail_notification_preset.screen = awful.screen.focused() end
+ naughty.notify {
+ preset = mail_notification_preset,
+ text = string.format("%s has <b>%d</b> new message%s", mail, mailcount, mailcount == 1 and "" or "s")
+ }
end
- helpers.set_map(mail, mailcount)
+ helpers.set_map(mail, imap_now["UNSEEN"])
end)
end
- imap.timer = helpers.newtimer(mail, timeout, update, true, true)
+ imap.timer = helpers.newtimer(mail, timeout, imap.update, true, true)
return imap
end
-
--[[
-
- 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
-
+
+ Lain
+ Layouts, widgets and utilities for Awesome WM
+
+ Widgets section
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luca CPZ
+ * (c) 2010-2012, Peter Hofmann
+
--]]
local wrequire = require("lain.helpers").wrequire
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2013, Luke Bonham
- * (c) 2010-2012, Peter Hofmann
-
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luca CPZ
+ * (c) 2010-2012, Peter Hofmann
+
--]]
local helpers = require("lain.helpers")
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2013, Luke Bonham
- * (c) 2010, Adrian C. <anrxc@sysphere.org>
-
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luca CPZ
+ * (c) 2010, Adrian C. <anrxc@sysphere.org>
+
--]]
- local helpers = require("lain.helpers")
- local shell = require("awful.util").shell
- local escape_f = require("awful.util").escape
- local focused = require("awful.screen").focused
- local naughty = require("naughty")
- local wibox = require("wibox")
- local os = { getenv = os.getenv }
- local string = { format = string.format,
- gmatch = string.gmatch,
- match = string.match }
+ local helpers = require("lain.helpers")
+ local shell = require("awful.util").shell
+ local escape_f = require("awful.util").escape
+ local focused = require("awful.screen").focused
+ local naughty = require("naughty")
+ local wibox = require("wibox")
+ local os = os
+ local string = string
-- MPD infos
-- lain.widget.mpd
local args = args or {}
local timeout = args.timeout or 2
local password = (args.password and #args.password > 0 and string.format("password %s\\n", args.password)) or ""
- local host = args.host or "127.0.0.1"
- local port = args.port or "6600"
+ local host = args.host or os.getenv("MPD_HOST") or "127.0.0.1"
+ local port = args.port or os.getenv("MPD_PORT") or "6600"
local music_dir = args.music_dir or os.getenv("HOME") .. "/Music"
local cover_pattern = args.cover_pattern or "*\\.(jpg|jpeg|png|gif)$"
local cover_size = args.cover_size or 100
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2013, Luke Bonham
- * (c) 2010-2012, Peter Hofmann
-
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luca CPZ
+ * (c) 2010-2012, Peter Hofmann
+
--]]
local helpers = require("lain.helpers")
local naughty = require("naughty")
local wibox = require("wibox")
- local string = { format = string.format, match = string.match }
+ local string = string
-- Network infos
-- lain.widget.net
local function factory(args)
- local net = { widget = wibox.widget.textbox(), devices = {} }
- 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
+ local net = { widget = wibox.widget.textbox(), devices = {} }
+ local args = args or {}
+ local timeout = args.timeout or 2
+ local units = args.units or 1024 -- KB
+ local notify = args.notify or "on"
+ local wifi_state = args.wifi_state or "off"
+ local eth_state = args.eth_state or "off"
+ local screen = args.screen or 1
+ local settings = args.settings or function() end
-- Compatibility with old API where iface was a string corresponding to 1 interface
net.iface = (args.iface and (type(args.iface) == "string" and {args.iface}) or
(type(args.iface) == "table" and args.iface)) or {}
function net.get_device()
- helpers.async(string.format("ip link show", device_cmd), function(ws)
- ws = ws:match("(%w+): <BROADCAST,MULTICAST,.-UP,LOWER_UP>")
- net.iface = ws and { ws } or {}
+ helpers.line_callback("ip link", function(line)
+ net.iface[#net.iface + 1] = not string.match(line, "LOOPBACK") and string.match(line, "(%w+): <") or nil
end)
end
received = 0
}
- for i, dev in ipairs(net.iface) do
+ for _, dev in ipairs(net.iface) do
local dev_now = {}
local dev_before = net.devices[dev] or { last_t = 0, last_r = 0 }
local now_t = tonumber(helpers.first_line(string.format("/sys/class/net/%s/statistics/tx_bytes", dev)) or 0)
dev_now.last_t = now_t
dev_now.last_r = now_r
+ if wifi_state == "on" and helpers.first_line(string.format("/sys/class/net/%s/uevent", dev)) == "DEVTYPE=wlan" and string.match(dev_now.carrier, "1") then
+ dev_now.wifi = true
+ dev_now.signal = tonumber(string.match(helpers.lines_from("/proc/net/wireless")[3], "(%-%d+%.)")) or nil
+ end
+
+ if eth_state == "on" and helpers.first_line(string.format("/sys/class/net/%s/uevent", dev)) ~= "DEVTYPE=wlan" and string.match(dev_now.carrier, "1") then
+ dev_now.ethernet = true
+ end
+
net.devices[dev] = dev_now
- -- Notify only once when connection is loss
+ -- Notify only once when connection is lost
if string.match(dev_now.carrier, "0") and notify == "on" and helpers.get_map(dev) then
naughty.notify {
title = dev,
net_now.carrier = dev_now.carrier
net_now.state = dev_now.state
net_now.devices[dev] = dev_now
- -- new_now.sent and net_now.received will be
+ -- net_now.sent and net_now.received will be
-- the totals across all specified devices
end
--- /dev/null
+ --[[
+
+ Licensed under GNU General Public License v2
+ * (c) 2016, Luca CPZ
+
+ --]]
+
+ local helpers = require("lain.helpers")
+ local shell = require("awful.util").shell
+ local wibox = require("wibox")
+ local string = string
+ local type = type
+
+ -- PulseAudio volume
+ -- lain.widget.pulse
+
+ local function factory(args)
+ local pulse = { widget = wibox.widget.textbox(), device = "N/A" }
+ local args = args or {}
+ local timeout = args.timeout or 5
+ local settings = args.settings or function() end
+
+ pulse.devicetype = args.devicetype or "sink"
+ pulse.cmd = args.cmd or "pacmd list-" .. pulse.devicetype .. "s | sed -n -e '/*/,$!d' -e '/index/p' -e '/base volume/d' -e '/volume:/p' -e '/muted:/p' -e '/device\\.string/p'"
+
+ function pulse.update()
+ helpers.async({ shell, "-c", type(pulse.cmd) == "string" and pulse.cmd or pulse.cmd() },
+ function(s)
+ volume_now = {
+ index = string.match(s, "index: (%S+)") or "N/A",
+ device = string.match(s, "device.string = \"(%S+)\"") or "N/A",
+ muted = string.match(s, "muted: (%S+)") or "N/A"
+ }
+
+ pulse.device = volume_now.index
+
+ local ch = 1
+ volume_now.channel = {}
+ for v in string.gmatch(s, ":.-(%d+)%%") do
+ volume_now.channel[ch] = v
+ ch = ch + 1
+ end
+
+ volume_now.left = volume_now.channel[1] or "N/A"
+ volume_now.right = volume_now.channel[2] or "N/A"
+
+ widget = pulse.widget
+ settings()
+ end)
+ end
+
+ helpers.newtimer("pulse", timeout, pulse.update)
+
+ return pulse
+ end
+
+ return factory
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2013, Luke Bonham
- * (c) 2013, Rman
-
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luca CPZ
+ * (c) 2013, Rman
+
--]]
- local helpers = require("lain.helpers")
- local awful = require("awful")
- local naughty = require("naughty")
- local wibox = require("wibox")
- local math = { modf = math.modf }
- local string = { format = string.format,
- match = string.match,
- gmatch = string.gmatch,
- rep = string.rep }
- local type, tonumber = type, tonumber
-
- -- Pulseaudio volume bar
+ local helpers = require("lain.helpers")
+ local awful = require("awful")
+ local naughty = require("naughty")
+ local wibox = require("wibox")
+ local math = math
+ local string = string
+ local type = type
+ local tonumber = tonumber
+
+ -- PulseAudio volume bar
-- lain.widget.pulsebar
local function factory(args)
},
_current_level = 0,
- _muted = false
+ _mute = "no",
+ device = "N/A"
}
local args = args or {}
local timeout = args.timeout or 5
local settings = args.settings or function() end
local width = args.width or 63
- local height = args.heigth or 1
+ local height = args.height or 1
+ local margins = args.margins or 1
+ local paddings = args.paddings or 1
local ticks = args.ticks or false
local ticks_size = args.ticks_size or 7
- local scallback = args.scallback
+ local tick = args.tick or "|"
+ local tick_pre = args.tick_pre or "["
+ local tick_post = args.tick_post or "]"
+ local tick_none = args.tick_none or " "
- pulsebar.cmd = args.cmd or "pacmd list-sinks | sed -n -e '0,/*/d' -e '/base volume/d' -e '/volume:/p' -e '/muted:/p' -e '/device\\.string/p'"
- pulsebar.sink = args.sink or 0
- pulsebar.colors = args.colors or pulsebar.colors
- pulsebar.followtag = args.followtag or false
- pulsebar.notifications = args.notification_preset
- pulsebar.device = "N/A"
+ pulsebar.colors = args.colors or pulsebar.colors
+ pulsebar.followtag = args.followtag or false
+ pulsebar.notification_preset = args.notification_preset
+ pulsebar.devicetype = args.devicetype or "sink"
+ pulsebar.cmd = args.cmd or "pacmd list-" .. pulsebar.devicetype .. "s | sed -n -e '/*/,$!d' -e '/index/p' -e '/base volume/d' -e '/volume:/p' -e '/muted:/p' -e '/device\\.string/p'"
if not pulsebar.notification_preset then
- pulsebar.notification_preset = {}
- pulsebar.notification_preset.font = "Monospace 10"
+ pulsebar.notification_preset = {
+ font = "Monospace 10"
+ }
end
pulsebar.bar = wibox.widget {
- forced_height = height,
- forced_width = width,
color = pulsebar.colors.unmute,
background_color = pulsebar.colors.background,
- margins = 1,
- paddings = 1,
+ forced_height = height,
+ forced_width = width,
+ margins = margins,
+ paddings = paddings,
ticks = ticks,
ticks_size = ticks_size,
widget = wibox.widget.progressbar,
pulsebar.tooltip = awful.tooltip({ objects = { pulsebar.bar } })
function pulsebar.update(callback)
- if scallback then pulsebar.cmd = scallback() end
-
- helpers.async({ awful.util.shell, "-c", pulsebar.cmd }, function(s)
+ helpers.async({ awful.util.shell, "-c", type(pulsebar.cmd) == "string" and pulsebar.cmd or pulsebar.cmd() },
+ function(s)
volume_now = {
- index = string.match(s, "index: (%S+)") or "N/A",
- sink = string.match(s, "device.string = \"(%S+)\"") or "N/A",
- muted = string.match(s, "muted: (%S+)") or "N/A"
+ index = string.match(s, "index: (%S+)") or "N/A",
+ device = string.match(s, "device.string = \"(%S+)\"") or "N/A",
+ muted = string.match(s, "muted: (%S+)") or "N/A"
}
pulsebar.device = volume_now.index
local volu = volume_now.left
local mute = volume_now.muted
- if (volu and volu ~= pulsebar._current_level) or (mute and mute ~= pulsebar._muted) then
- pulsebar._current_level = volu
+ if volu:match("N/A") or mute:match("N/A") then return end
+
+ if volu ~= pulsebar._current_level or mute ~= pulsebar._mute then
+ pulsebar._current_level = tonumber(volu)
pulsebar.bar:set_value(pulsebar._current_level / 100)
- if (not mute and volu == 0) or mute == "yes" then
- pulsebar._muted = true
- pulsebar.tooltip:set_text ("[Muted]")
+ if pulsebar._current_level == 0 or mute == "yes" then
+ pulsebar._mute = mute
+ pulsebar.tooltip:set_text ("[muted]")
pulsebar.bar.color = pulsebar.colors.mute
else
- pulsebar._muted = false
- pulsebar.tooltip:set_text(string.format("%s: %s", pulsebar.sink, volu))
+ pulsebar._mute = "no"
+ pulsebar.tooltip:set_text(string.format("%s %s: %s", pulsebar.devicetype, pulsebar.device, volu))
pulsebar.bar.color = pulsebar.colors.unmute
end
pulsebar.update(function()
local preset = pulsebar.notification_preset
- if pulsebar._muted then
- preset.title = string.format("Sink %s - Muted", pulsebar.sink)
- else
- preset.title = string.format("%s - %s%%", pulsebar.sink, pulsebar._current_level)
+ preset.title = string.format("%s %s - %s%%", pulsebar.devicetype, pulsebar.device, pulsebar._current_level)
+
+ if pulsebar._mute == "yes" then
+ preset.title = preset.title .. " muted"
+ end
+
+ -- tot is the maximum number of ticks to display in the notification
+ -- fallback: default horizontal wibox height
+ local wib, tot = awful.screen.focused().mywibox, 20
+
+ -- if we can grab mywibox, tot is defined as its height if
+ -- horizontal, or width otherwise
+ if wib then
+ if wib.position == "left" or wib.position == "right" then
+ tot = wib.width
+ else
+ tot = wib.height
+ end
end
- int = math.modf((pulsebar._current_level / 100) * awful.screen.focused().mywibox.height)
- preset.text = string.format("[%s%s]", string.rep("|", int),
- string.rep(" ", awful.screen.focused().mywibox.height - int))
+ int = math.modf((pulsebar._current_level / 100) * tot)
+ preset.text = string.format(
+ "%s%s%s%s",
+ tick_pre,
+ string.rep(tick, int),
+ string.rep(tick_none, tot - int),
+ tick_post
+ )
if pulsebar.followtag then preset.screen = awful.screen.focused() end
end)
end
- helpers.newtimer(string.format("pulsebar-%s", pulsebar.sink), timeout, pulsebar.update)
+ helpers.newtimer(string.format("pulsebar-%s-%s", pulsebar.devicetype, pulsebar.device), timeout, pulsebar.update)
return pulsebar
end
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2013, Luke Bonham
- * (c) 2010-2012, Peter Hofmann
-
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luca CPZ
+ * (c) 2010-2012, Peter Hofmann
+
--]]
local helpers = require("lain.helpers")
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2013, Luke Bonham
-
+
+ Licensed under GNU General Public License v2
+ * (c) 2013, Luca CPZ
+
--]]
local helpers = require("lain.helpers")
local wibox = require("wibox")
- local open = io.open
local tonumber = tonumber
- -- coretemp
+ -- {thermal,core} temperature info
-- lain.widget.temp
local function factory(args)
local temp = { widget = wibox.widget.textbox() }
local args = args or {}
- local timeout = args.timeout or 2
- local tempfile = args.tempfile or "/sys/class/thermal/thermal_zone0/temp"
+ local timeout = args.timeout or 30
+ local tempfile = args.tempfile or "/sys/devices/virtual/thermal/thermal_zone0/temp"
local settings = args.settings or function() end
function temp.update()
- local f = open(tempfile)
- if f then
- coretemp_now = tonumber(f:read("*all")) / 1000
- f:close()
- else
- coretemp_now = "N/A"
- end
-
- widget = temp.widget
- settings()
+ helpers.async({"find", "/sys/devices", "-type", "f", "-name", "*temp*"}, function(f)
+ temp_now = {}
+ local temp_fl, temp_value
+ for t in f:gmatch("[^\n]+") do
+ temp_fl = helpers.first_line(t)
+ if temp_fl then
+ temp_value = tonumber(temp_fl)
+ temp_now[t] = temp_value and temp_value/1e3 or temp_fl
+ end
+ end
+ coretemp_now = temp_now[tempfile] or "N/A"
+ widget = temp.widget
+ settings()
+ end)
end
- helpers.newtimer("coretemp", timeout, temp.update)
+ helpers.newtimer("thermal", timeout, temp.update)
return temp
end
-
--[[
-
- Licensed under GNU General Public License v2
- * (c) 2015, Luke Bonham
-
+
+ Licensed under GNU General Public License v2
+ * (c) 2015, Luca CPZ
+
--]]
local helpers = require("lain.helpers")
local focused = require("awful.screen").focused
local naughty = require("naughty")
local wibox = require("wibox")
- local math = { floor = math.floor }
- local os = { time = os.time,
- date = os.date,
- difftime = os.difftime }
- local string = { format = string.format,
- gsub = string.gsub }
+ local math = math
+ local os = os
+ local string = string
+ local type = type
local tonumber = tonumber
-- OpenWeatherMap
local function factory(args)
local weather = { widget = wibox.widget.textbox() }
local args = args or {}
- local APPID = args.APPID or "3e321f9414eaedbfab34983bda77a66e" -- lain default
- local timeout = args.timeout or 900 -- 15 min
- local timeout_forecast = args.timeout or 86400 -- 24 hrs
+ local APPID = args.APPID or "3e321f9414eaedbfab34983bda77a66e" -- lain's default
+ local timeout = args.timeout or 60 * 15 -- 15 min
+ local timeout_forecast = args.timeout or 60 * 60 * 24 -- 24 hrs
local current_call = args.current_call or "curl -s 'http://api.openweathermap.org/data/2.5/weather?id=%s&units=%s&lang=%s&APPID=%s'"
local forecast_call = args.forecast_call or "curl -s 'http://api.openweathermap.org/data/2.5/forecast/daily?id=%s&units=%s&lang=%s&cnt=%s&APPID=%s'"
local city_id = args.city_id or 0 -- placeholder
- local utc_offset = args.utc_offset or
- function ()
- local now = os.time()
- return os.difftime(now, os.time(os.date("!*t", now))) + ((os.date("*t").isdst and 1 or 0) * 3600)
- end
local units = args.units or "metric"
local lang = args.lang or "en"
local cnt = args.cnt or 5
end
local weather_na_markup = args.weather_na_markup or " N/A "
local followtag = args.followtag or false
+ local showpopup = args.showpopup or "on"
local settings = args.settings or function() end
weather.widget:set_markup(weather_na_markup)
weather.icon_path = icons_path .. "na.png"
weather.icon = wibox.widget.imagebox(weather.icon_path)
- function weather.show(t_out)
+ function weather.show(seconds)
weather.hide()
if followtag then
weather.forecast_update()
end
- weather.notification = naughty.notify({
+ weather.notification = naughty.notify {
+ preset = notification_preset,
text = weather.notification_text,
icon = weather.icon_path,
- timeout = t_out,
- preset = notification_preset
- })
+ timeout = type(seconds == "number") and seconds or notification_preset.timeout
+ }
end
function weather.hide()
weather_now, pos, err = json.decode(f, 1, nil)
if not err and type(weather_now) == "table" and tonumber(weather_now["cod"]) == 200 then
- weather.notification_text = ''
+ weather.notification_text = ""
for i = 1, weather_now["cnt"] do
weather.notification_text = weather.notification_text ..
notification_text_fun(weather_now["list"][i])
-
if i < weather_now["cnt"] then
weather.notification_text = weather.notification_text .. "\n"
end
weather_now, pos, err = json.decode(f, 1, nil)
if not err and type(weather_now) == "table" and tonumber(weather_now["cod"]) == 200 then
- -- weather icon based on localtime
- local now = os.time()
local sunrise = tonumber(weather_now["sys"]["sunrise"])
local sunset = tonumber(weather_now["sys"]["sunset"])
local icon = weather_now["weather"][1]["icon"]
- local loc_m = os.time { year = os.date("%Y"), month = os.date("%m"), day = os.date("%d"), hour = 0 }
- local offset = utc_offset()
- local utc_m = loc_m - offset
-
- if offset > 0 and (now - utc_m)>=86400 then
- utc_m = utc_m + 86400
- elseif offset < 0 and (utc_m - now)>=86400 then
- utc_m = utc_m - 86400
- end
-
- -- if we are 1 day after the GMT, return 1 day back, and viceversa
- if offset > 0 and loc_m >= utc_m then
- now = now - 86400
- elseif offset < 0 and loc_m <= utc_m then
- now = now + 86400
- end
+ local loc_now = os.time()
- if sunrise <= now and now <= sunset then
+ if sunrise <= loc_now and loc_now <= sunset then
icon = string.gsub(icon, "n", "d")
else
icon = string.gsub(icon, "d", "n")
end)
end
- weather.attach(weather.widget)
+ if showpopup == "on" then weather.attach(weather.widget) end
weather.timer = helpers.newtimer("weather-" .. city_id, timeout, weather.update, false, true)
weather.timer_forecast = helpers.newtimer("weather_forecast-" .. city_id, timeout, weather.forecast_update, false, true)
- Subproject commit d6cf027a4c2535c179a8112137d065a5bc740fea
+ Subproject commit 2899629c445cb12efb72cc538e36cfc2ec812201