]> git.madduck.net Git - etc/awesome.git/commitdiff

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Merge commit '33c0e0c2360a04fcc6f51bccb0ad2a7a9e9c07b3'
authormartin f. krafft <madduck@madduck.net>
Sat, 2 Nov 2019 09:41:23 +0000 (22:41 +1300)
committermartin f. krafft <madduck@madduck.net>
Sat, 2 Nov 2019 09:41:23 +0000 (22:41 +1300)
122 files changed:
1  2 
.config/awesome/lain/.gitmodules
.config/awesome/lain/ISSUE_TEMPLATE.md
.config/awesome/lain/README.rst
.config/awesome/lain/helpers.lua
.config/awesome/lain/icons/cal/black/1.png
.config/awesome/lain/icons/cal/black/10.png
.config/awesome/lain/icons/cal/black/11.png
.config/awesome/lain/icons/cal/black/12.png
.config/awesome/lain/icons/cal/black/13.png
.config/awesome/lain/icons/cal/black/14.png
.config/awesome/lain/icons/cal/black/15.png
.config/awesome/lain/icons/cal/black/16.png
.config/awesome/lain/icons/cal/black/17.png
.config/awesome/lain/icons/cal/black/18.png
.config/awesome/lain/icons/cal/black/19.png
.config/awesome/lain/icons/cal/black/2.png
.config/awesome/lain/icons/cal/black/20.png
.config/awesome/lain/icons/cal/black/21.png
.config/awesome/lain/icons/cal/black/22.png
.config/awesome/lain/icons/cal/black/23.png
.config/awesome/lain/icons/cal/black/24.png
.config/awesome/lain/icons/cal/black/25.png
.config/awesome/lain/icons/cal/black/26.png
.config/awesome/lain/icons/cal/black/27.png
.config/awesome/lain/icons/cal/black/28.png
.config/awesome/lain/icons/cal/black/29.png
.config/awesome/lain/icons/cal/black/3.png
.config/awesome/lain/icons/cal/black/30.png
.config/awesome/lain/icons/cal/black/31.png
.config/awesome/lain/icons/cal/black/4.png
.config/awesome/lain/icons/cal/black/5.png
.config/awesome/lain/icons/cal/black/6.png
.config/awesome/lain/icons/cal/black/7.png
.config/awesome/lain/icons/cal/black/8.png
.config/awesome/lain/icons/cal/black/9.png
.config/awesome/lain/icons/cal/white/1.png
.config/awesome/lain/icons/cal/white/10.png
.config/awesome/lain/icons/cal/white/11.png
.config/awesome/lain/icons/cal/white/12.png
.config/awesome/lain/icons/cal/white/13.png
.config/awesome/lain/icons/cal/white/14.png
.config/awesome/lain/icons/cal/white/18.png
.config/awesome/lain/icons/cal/white/19.png
.config/awesome/lain/icons/cal/white/2.png
.config/awesome/lain/icons/cal/white/20.png
.config/awesome/lain/icons/cal/white/21.png
.config/awesome/lain/icons/cal/white/22.png
.config/awesome/lain/icons/cal/white/23.png
.config/awesome/lain/icons/cal/white/24.png
.config/awesome/lain/icons/cal/white/25.png
.config/awesome/lain/icons/cal/white/26.png
.config/awesome/lain/icons/cal/white/27.png
.config/awesome/lain/icons/cal/white/28.png
.config/awesome/lain/icons/cal/white/29.png
.config/awesome/lain/icons/cal/white/3.png
.config/awesome/lain/icons/cal/white/30.png
.config/awesome/lain/icons/cal/white/31.png
.config/awesome/lain/icons/cal/white/4.png
.config/awesome/lain/icons/cal/white/6.png
.config/awesome/lain/icons/cal/white/8.png
.config/awesome/lain/icons/cal/white/9.png
.config/awesome/lain/icons/layout/default/cascadetile.png
.config/awesome/lain/icons/layout/default/cascadetilew.png
.config/awesome/lain/icons/layout/default/centerwork.png
.config/awesome/lain/icons/layout/default/centerworkh.png
.config/awesome/lain/icons/layout/default/centerworkhw.png
.config/awesome/lain/icons/layout/default/centerworkw.png
.config/awesome/lain/icons/layout/zenburn/cascade.png
.config/awesome/lain/icons/layout/zenburn/cascadetile.png
.config/awesome/lain/icons/layout/zenburn/centerfair.png
.config/awesome/lain/icons/layout/zenburn/centerwork.png
.config/awesome/lain/icons/layout/zenburn/centerworkh.png
.config/awesome/lain/icons/layout/zenburn/termfair.png
.config/awesome/lain/icons/mail.png
.config/awesome/lain/icons/no_net.png
.config/awesome/lain/icons/openweathermap/01d.png
.config/awesome/lain/icons/openweathermap/01n.png
.config/awesome/lain/icons/openweathermap/02d.png
.config/awesome/lain/icons/openweathermap/02n.png
.config/awesome/lain/icons/openweathermap/03d.png
.config/awesome/lain/icons/openweathermap/03n.png
.config/awesome/lain/icons/openweathermap/04d.png
.config/awesome/lain/icons/openweathermap/09d.png
.config/awesome/lain/icons/openweathermap/10d.png
.config/awesome/lain/icons/openweathermap/11d.png
.config/awesome/lain/icons/openweathermap/13d.png
.config/awesome/lain/icons/openweathermap/50d.png
.config/awesome/lain/icons/openweathermap/na.png
.config/awesome/lain/icons/taskwarrior.png
.config/awesome/lain/init.lua
.config/awesome/lain/lain-scm-1.rockspec
.config/awesome/lain/layout/cascade.lua
.config/awesome/lain/layout/centerwork.lua
.config/awesome/lain/layout/init.lua
.config/awesome/lain/layout/termfair.lua
.config/awesome/lain/util/init.lua
.config/awesome/lain/util/markup.lua
.config/awesome/lain/util/menu_iterator.lua
.config/awesome/lain/util/quake.lua
.config/awesome/lain/util/separators.lua
.config/awesome/lain/widget/alsa.lua
.config/awesome/lain/widget/alsabar.lua
.config/awesome/lain/widget/bat.lua
.config/awesome/lain/widget/cal.lua
.config/awesome/lain/widget/contrib/init.lua
.config/awesome/lain/widget/contrib/moc.lua
.config/awesome/lain/widget/contrib/redshift.lua
.config/awesome/lain/widget/contrib/task.lua
.config/awesome/lain/widget/contrib/tp_smapi.lua
.config/awesome/lain/widget/cpu.lua
.config/awesome/lain/widget/fs.lua
.config/awesome/lain/widget/imap.lua
.config/awesome/lain/widget/init.lua
.config/awesome/lain/widget/mem.lua
.config/awesome/lain/widget/mpd.lua
.config/awesome/lain/widget/net.lua
.config/awesome/lain/widget/pulse.lua
.config/awesome/lain/widget/pulsebar.lua
.config/awesome/lain/widget/sysload.lua
.config/awesome/lain/widget/temp.lua
.config/awesome/lain/widget/weather.lua
.config/awesome/lain/wiki

index a50818fd507d0ed5c2283eba482ad16842ebc072,d95bcb57322f746bfd5e79c4fa9edff5cafdd045..d95bcb57322f746bfd5e79c4fa9edff5cafdd045
@@@ -1,3 -1,3 +1,3 @@@
  [submodule "lain.wiki"]
        path = wiki
-       url = https://github.com/copycat-killer/lain.wiki.git
+       url = https://github.com/lcpz/lain.wiki.git
index 37c2141da70793b0ff81182bc269551997123543,e9dcc0bc84153ab625241f7907997231c71b43a6..e9dcc0bc84153ab625241f7907997231c71b43a6
@@@ -1,21 -1,33 +1,33 @@@
- # 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.
index 4515be7bfbea3c29d8d83e7723ff8ae2f4346258,2b77f9a7b5a13c87173d2bec5cfc64ad9d7c3950..2b77f9a7b5a13c87173d2bec5cfc64ad9d7c3950
@@@ -5,50 -5,34 +5,34 @@@ Lai
  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
index 4e5ce1facc505c5a78617fa40181120b4f1f5826,d6f6b3c1fcf861c283fff9134a1b9587a40998cd..d6f6b3c1fcf861c283fff9134a1b9587a40998cd
@@@ -1,19 -1,19 +1,19 @@@
  --[[
-                                                   
-      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
@@@ -34,53 -34,49 +34,49 @@@ en
  
  -- {{{ 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
  
  -- }}}
@@@ -112,12 -108,29 +108,29 @@@ en
  -- @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
@@@ -164,6 -177,28 +177,28 @@@ function helpers.spairs(t
      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
index 0000000000000000000000000000000000000000,d2fb62efed0b3d3217156b2d6e6dc100d3de505b..d2fb62efed0b3d3217156b2d6e6dc100d3de505b
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,507b0795a285c1bfd69f28227ae6016da6f468cb..507b0795a285c1bfd69f28227ae6016da6f468cb
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,336141b1d75423b3a575a6585eefaa92abebcc45..336141b1d75423b3a575a6585eefaa92abebcc45
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,c58972948eb35130f67b04edcc3dc96ab0f16e45..c58972948eb35130f67b04edcc3dc96ab0f16e45
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,377518bb4a72f73e51397a056d784c24c43218cc..377518bb4a72f73e51397a056d784c24c43218cc
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,6f4a9fe89a15285f7529ccc8c2561b58198b6295..6f4a9fe89a15285f7529ccc8c2561b58198b6295
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,1a271c1798c6b5763f237308554a307868e7a01c..1a271c1798c6b5763f237308554a307868e7a01c
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,5e65835dc6f2492241b874cd512f3bb6230e475c..5e65835dc6f2492241b874cd512f3bb6230e475c
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,f3fa0a997e607ce485c4d5a9cfce4a799f965f32..f3fa0a997e607ce485c4d5a9cfce4a799f965f32
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,7acb37a874d2d992acbce5058c25e7241df3dfbb..7acb37a874d2d992acbce5058c25e7241df3dfbb
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,a55795731b54a9cbd599a7d1261ba100311f494b..a55795731b54a9cbd599a7d1261ba100311f494b
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,17b33e01699cff6f09928e6d15fa9364d5e4b19d..17b33e01699cff6f09928e6d15fa9364d5e4b19d
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,558d111017d89d6e0094b381a4a790a581ff9ac0..558d111017d89d6e0094b381a4a790a581ff9ac0
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,0bbedc85ece0eda14690ec6aaa6df156577aa7ac..0bbedc85ece0eda14690ec6aaa6df156577aa7ac
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,762d2628c170c70e98e4f54c5c95ec3fddcb0f11..762d2628c170c70e98e4f54c5c95ec3fddcb0f11
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,a39dceef2ce63719cd018ac3d143cc51ca4e2aa3..a39dceef2ce63719cd018ac3d143cc51ca4e2aa3
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,c00dbca8ace5a63284fbc7a9ef1eb5af4741d5c5..c00dbca8ace5a63284fbc7a9ef1eb5af4741d5c5
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,dc9243cf44efff936d27e22f33b68f31223eab67..dc9243cf44efff936d27e22f33b68f31223eab67
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,50bb182a54e87ccae7a7205983fe59e71764b5cc..50bb182a54e87ccae7a7205983fe59e71764b5cc
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,0fbf9fcf424b0e018ef5108c035754751ecee0b2..0fbf9fcf424b0e018ef5108c035754751ecee0b2
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,def6ab29ffde309e908afdd34e3d5fee238c1464..def6ab29ffde309e908afdd34e3d5fee238c1464
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,531923cb1e114b38aa58cb4a8a25e1a401adaea0..531923cb1e114b38aa58cb4a8a25e1a401adaea0
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,98b552d39053d7edc7857ef2505ff28d049482b3..98b552d39053d7edc7857ef2505ff28d049482b3
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,ca58151deb777a3117989b290d85bdf843bb0aee..ca58151deb777a3117989b290d85bdf843bb0aee
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,6e8da212b6e3e1d5105841f89350435acc26faf0..6e8da212b6e3e1d5105841f89350435acc26faf0
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,43359797a5eb03268e0b0bd92782de42f21a2ee1..43359797a5eb03268e0b0bd92782de42f21a2ee1
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,576ec119f8c730a82d4aefe35d53a76da9e368fc..576ec119f8c730a82d4aefe35d53a76da9e368fc
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,56fa8abea64f43b5d209c15f3851623765407498..56fa8abea64f43b5d209c15f3851623765407498
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,7c90b3a8bd982ff6122d09619b6485e1c3b28c91..7c90b3a8bd982ff6122d09619b6485e1c3b28c91
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,9d1f28e6f2d0c1b0a94e1fac58f520ad84146797..9d1f28e6f2d0c1b0a94e1fac58f520ad84146797
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,00d0933c34f51f6ed6934c46ab958cbf89b2c333..00d0933c34f51f6ed6934c46ab958cbf89b2c333
mode 000000,100644..100644
Binary files differ
index 90b696ca0820164118bea3af0df0f41b0b2ba4bb,a0faa20c7992d5894fff6667a78af0bc53891c11..a0faa20c7992d5894fff6667a78af0bc53891c11
Binary files differ
index b462ffbf0d60bc57c236bc3aef3f388dcce152f9,7d9343bd4030e8bea49f994ad003730a0512cff5..7d9343bd4030e8bea49f994ad003730a0512cff5
Binary files differ
index cf43296120793006ccdeb2b08da06fba5bc629a2,7af5e993e620ffc6402001b0f43e779cd81b7105..7af5e993e620ffc6402001b0f43e779cd81b7105
Binary files differ
index 42cf092c707630612ecda89455794e1cecd2579f,b164f858f81b057490c770f5ed84c52cf21f73b1..b164f858f81b057490c770f5ed84c52cf21f73b1
Binary files differ
index 37db670cb71fecb2f2932981b09dae765adee205,fef74f36e7c2a65ddff7ce9876e1c8c78fbc61de..fef74f36e7c2a65ddff7ce9876e1c8c78fbc61de
Binary files differ
index 01885040cb3d6920da2bf0c591ed3006e3e42709,d747a6bd1714fff027ea81b23d7e0f27dec67b38..d747a6bd1714fff027ea81b23d7e0f27dec67b38
Binary files differ
index 817c4267d2d8d6fe36ae370c388aeae0f79c86d2,0cf1c2430217fb553f0a3994d5bf34e52afcbeaf..0cf1c2430217fb553f0a3994d5bf34e52afcbeaf
Binary files differ
index 0e6dafcc9ff661f86836c6746265b9d2bb34f20e,bfd3530c249c7a3956a2ba1d693292696b9bebec..bfd3530c249c7a3956a2ba1d693292696b9bebec
Binary files differ
index b93789a59ad776ad87b50024d765abfe4b9258ee,e7f3fa49510883b734e09eb895a3960aff0f94e5..e7f3fa49510883b734e09eb895a3960aff0f94e5
Binary files differ
index 3d8d7c618fcf7b0bd4446be1fd26a9dd9e5647f1,9a5a1fb3c99ee20cc5d6f509955ea46d45611270..9a5a1fb3c99ee20cc5d6f509955ea46d45611270
Binary files differ
index 79a74f3d2ba82322a3ae7fb7be95ac6805f77747,266ab9f389a943587ea3a9b151d8d1e7b5fc6b78..266ab9f389a943587ea3a9b151d8d1e7b5fc6b78
Binary files differ
index e8845ce2e8950e7a773d7fd9dad893f3f490882c,f4862892d4ac2f84484005e3af8377fdc7bca606..f4862892d4ac2f84484005e3af8377fdc7bca606
Binary files differ
index a8d4dfb6dc75657f4b5b9cd07704bafc0b779340,244dceb8af83e9c769659a6f06b7b63f60a9f666..244dceb8af83e9c769659a6f06b7b63f60a9f666
Binary files differ
index 1a3b38ad29f89a08702f67ddb9cc2d37126673f4,0ce1c75530ec0afe555333ad26a6149084f53317..0ce1c75530ec0afe555333ad26a6149084f53317
Binary files differ
index c3621b7b6ea8705107ef54198389dde2c9325224,48d279c4a2ef0e55259633daf7b6c77b55959109..48d279c4a2ef0e55259633daf7b6c77b55959109
Binary files differ
index f26731bbf41bb4fcebae8d075310abc25128c299,7535855dacaf32dd47f3caf28b5ce61208830c92..7535855dacaf32dd47f3caf28b5ce61208830c92
Binary files differ
index e4dde7700dd2c86fed2c94ec070f3fb751ced45b,2aa9074280bf67089da3ee7c8048ae982d888a94..2aa9074280bf67089da3ee7c8048ae982d888a94
Binary files differ
index b924c22a861495d22203257b4de5f3e71ee8b208,0201976903380d771849f3c8c109d53e35568fe2..0201976903380d771849f3c8c109d53e35568fe2
Binary files differ
index e9a74f8be9e55a8e7db753e3689354dea3f9028f,9305b9bd0411669c859a66c3c34789cc4f32232a..9305b9bd0411669c859a66c3c34789cc4f32232a
Binary files differ
index 11242710cb04449563e2e6c56440d9f19c8afbe1,f1eb5de62db5f243352fd5aa91c0b82a86428177..f1eb5de62db5f243352fd5aa91c0b82a86428177
Binary files differ
index 8147d78f042bc2918a7cb6b8ff0f482c91a8f363,1ba61aacfa9adbf89acb2d084b42ab65411ae5c0..1ba61aacfa9adbf89acb2d084b42ab65411ae5c0
Binary files differ
index a1be3e840049e04738875cebdb5f6f7d75ddcb40,e9a873bc290f1b488ae1bb8238de4e45fccf05c6..e9a873bc290f1b488ae1bb8238de4e45fccf05c6
Binary files differ
index 16713bcd6729a74a11856b7199365bffe6fd3689,ee1ed6a8829e464e0c591377cc0871ffc34bc996..ee1ed6a8829e464e0c591377cc0871ffc34bc996
Binary files differ
index a1c9798bd0c4cf8f03ebdb6524f723c6f8e415ec,0a7bf4d704b2d10c3895cbda295627fd2ace3bab..0a7bf4d704b2d10c3895cbda295627fd2ace3bab
Binary files differ
index 909b72606b000c6c2642db09f2a3ae7ce71104d0,cb03d0b40d4204f869f9d90cdffb2335c51249dd..cb03d0b40d4204f869f9d90cdffb2335c51249dd
Binary files differ
index dc636c48820ade07a45c24431101a4cff6f296e7,fca554a5bb00ceef5006b5ece6a208282abeb169..fca554a5bb00ceef5006b5ece6a208282abeb169
Binary files differ
index 2f12ada6300e54c16b5b2337824c7651c5909f29,ba30f43e0bc7adbd02e91e11c9033d591ebca574..ba30f43e0bc7adbd02e91e11c9033d591ebca574
Binary files differ
index c46b48be2b4132ca68f77250765f4dc7bc6bb4be,d15eb70be374033aa153aa119bfd2821f7bad580..d15eb70be374033aa153aa119bfd2821f7bad580
Binary files differ
index 826b331b48679d671048f232a9c2c254ba5e7af4,51e06bc7e806dfe62b860b62fc1b94c78a93ce5c..51e06bc7e806dfe62b860b62fc1b94c78a93ce5c
Binary files differ
index 4fb4fdd38e9e30d30537434d963a192b30ba154d,c59092f01d9dc94c48162ef26e1b343554a1a8f9..c59092f01d9dc94c48162ef26e1b343554a1a8f9
Binary files differ
index fd27766ca2b2b7ded2f59c82901f4ce9defecde5,7820f8cb4a27f74ef55dbb3a5160ddc291de8291..7820f8cb4a27f74ef55dbb3a5160ddc291de8291
Binary files differ
index fcfa7e317d0c3a093c18568bf16418fc9010260c,85e699646d84d1832b9266bb663fd1c563da8257..85e699646d84d1832b9266bb663fd1c563da8257
Binary files differ
index 532842ddcf2ebdf34f50f8450df45a288713510d,fbe4fac509426ab8c4cb17cbc24eea2514175e11..fbe4fac509426ab8c4cb17cbc24eea2514175e11
Binary files differ
index 87be6581f6e9137f15b74c8f12db2c41eabe68f8,2e03a8072eba9f7b1337c575476424c57fbd626b..2e03a8072eba9f7b1337c575476424c57fbd626b
Binary files differ
index 01cda8e89c14980825199778ed2c5defea611305,75dc993bed99b27d88637902db444ea5b292a57f..75dc993bed99b27d88637902db444ea5b292a57f
Binary files differ
index 6a2ceccee81eee4fea2cd2fb05fc5890e6c66b85,af7a863d1f2d017866c8917d4eb5dd7f1b90ba31..af7a863d1f2d017866c8917d4eb5dd7f1b90ba31
Binary files differ
index 00beeb50c49d50ade2e964925de8e14afe6db535,88019b309eada2ba102b9c09a6333e1d5e86bfbd..88019b309eada2ba102b9c09a6333e1d5e86bfbd
Binary files differ
index b7d5880c64fb32af4906b58bae4a347e8f8db4f5,f7640b555f7b291ff8d69724fe6174bffae3313f..f7640b555f7b291ff8d69724fe6174bffae3313f
Binary files differ
index 60ba6e03c83096ece4c7b3e819fbf465a57c33db,9c0c7a39096084c198c0fc73334cdc90628a72ea..9c0c7a39096084c198c0fc73334cdc90628a72ea
Binary files differ
index 1a3e8a8ab82472adbce12200207d3af55956e9c7,3613372f8615ffa50877d02646b01379a643a46a..3613372f8615ffa50877d02646b01379a643a46a
mode 100755,100644..100644
Binary files differ
index d9e2745cee4f88db9a855ed58d9bc3f2c34ddb12,569965e62fc1e20eff0c287cda8ee743f838d836..569965e62fc1e20eff0c287cda8ee743f838d836
mode 100755,100644..100644
Binary files differ
index 84ea1408ba782bc48e03bbbd79f862412d3cb48d,ce5b135466d886ab7f9befb5ec3085eeb79701db..ce5b135466d886ab7f9befb5ec3085eeb79701db
mode 100755,100644..100644
Binary files differ
index 8fd0a5b04d686db4e7dea16981db46d5d1f94de2,2ba979984f2bcd4fc97125d8f5515b73dc526e37..2ba979984f2bcd4fc97125d8f5515b73dc526e37
mode 100755,100644..100644
Binary files differ
index 9e4404d7511aa84a563fbc2e4933ee032a1ce88e,12e4283df110569a13f354d6a110d21f8194281c..12e4283df110569a13f354d6a110d21f8194281c
mode 100755,100644..100644
Binary files differ
index 22b929c612e7d04a2408bc71625114908f255769,1cf0e9d2cd8f89eea3dbad0734a0b778404d4c03..1cf0e9d2cd8f89eea3dbad0734a0b778404d4c03
mode 100755,100644..100644
Binary files differ
index d8b3673492f457ab354bf4276b2a2e49cc4c6dee,89a42b8b91d596e07fb3c33842e5f6d13aa4fdb7..89a42b8b91d596e07fb3c33842e5f6d13aa4fdb7
mode 100755,100644..100644
Binary files differ
index bac1e7ebb84426f6bf1704f4c8a3c70486713cd1,e7fb67ff6a4a7e1a27d9ee04c964c3791d8948aa..e7fb67ff6a4a7e1a27d9ee04c964c3791d8948aa
mode 100755,100644..100644
Binary files differ
index d00552a5fd8d96d08d0e5915d51a7963d40cc7ae,cfa066a27ca1a99425a8a0b7b87d012a31c30388..cfa066a27ca1a99425a8a0b7b87d012a31c30388
mode 100755,100644..100644
Binary files differ
index 3cc6665dc0e98c2be584079add634d9744b459a5,712d0c88dd71f9d44cfa856f2520ab9ab1e59ff9..712d0c88dd71f9d44cfa856f2520ab9ab1e59ff9
mode 100755,100644..100644
Binary files differ
index d30e120582fe1084bbfc6f33f67dfa4ef4e66539,3b62f7c7756f59cc369c2c4b52e190f1447bfcec..3b62f7c7756f59cc369c2c4b52e190f1447bfcec
mode 100755,100644..100644
Binary files differ
index ddcb8f38c76c583648000c9bc8846a04676815d2,e265b012c7445a69f75fdba1edf75eb62f08674a..e265b012c7445a69f75fdba1edf75eb62f08674a
mode 100755,100644..100644
Binary files differ
index 009039f8dd2433627a9499003f6c86bfad4b0c80,905ace335f506df75a88ffc5fe1beaf60cf850f5..905ace335f506df75a88ffc5fe1beaf60cf850f5
mode 100755,100644..100644
Binary files differ
index 62a5350d878dccc626e9e7be17e728aab0737b87,1cc513211171edcb389a8a80c91f42ddcd6c28d6..1cc513211171edcb389a8a80c91f42ddcd6c28d6
mode 100755,100644..100644
Binary files differ
index 859ca29120f197ef2f46a76baefd7b5cd81c58c1,c64fe8623044f41f204857ccbb3394b2c5450e79..c64fe8623044f41f204857ccbb3394b2c5450e79
Binary files differ
index 46ab825e370fd50a52dcf70a0481a56c35b0689f,b59d5dd93bb9568c83e80874f6a45a719d3ef54e..b59d5dd93bb9568c83e80874f6a45a719d3ef54e
@@@ -1,12 -1,11 +1,11 @@@
  --[[
-                                                    
-      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 {
index 0000000000000000000000000000000000000000,fb1eaaf1255db7240d0c2ddb32a6b6c629d444e0..fb1eaaf1255db7240d0c2ddb32a6b6c629d444e0
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,27 +1,27 @@@
+ 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" }
+ }
index 204ce4017bc211f532c750ae5f2e6c3979590486,cbc3877b0fb992ffb1570c898fb93049264fb9c8..cbc3877b0fb992ffb1570c898fb93049264fb9c8
@@@ -1,11 -1,10 +1,10 @@@
  --[[
-                                                   
-      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
index eafab78ce5c5054f1834e616b97e555305f6294c,2b38a69f0378ff5e095e45266b0d1fd4183a49c1..2b38a69f0378ff5e095e45266b0d1fd4183a49c1
@@@ -1,34 -1,32 +1,32 @@@
  --[[
-                                                   
-      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)
@@@ -43,7 -41,7 +41,7 @@@
  
      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
  
@@@ -52,7 -50,7 +50,7 @@@
  
          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
index d79679a5f17620b736284afb077d10d38bc57ad7,6478b06052460446729a9f950fe12e136706da3c..6478b06052460446729a9f950fe12e136706da3c
@@@ -1,15 -1,14 +1,14 @@@
  --[[
-                                                    
-      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
index 33b7ffc7489634761565caab3248c336b494a975,e33894e11777ad85031ed6d9c1e21577869cc063..e33894e11777ad85031ed6d9c1e21577869cc063
@@@ -1,17 -1,14 +1,14 @@@
  --[[
-                                                   
-      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
  
index 1fc5805be81c0d1a2293e4f98d32c3b03cf54b7a,55bfa26a557d7676331b7472a304bae0204372e2..55bfa26a557d7676331b7472a304bae0204372e2
@@@ -1,15 -1,14 +1,14 @@@
  --[[
-                                                    
-      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")
@@@ -77,7 -76,7 +76,7 @@@ function util.magnify_client(c, width_f
      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
@@@ -117,7 -116,7 +116,7 @@@ function util.add_tag(layout
          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
@@@ -159,9 -158,10 +158,10 @@@ en
  -- }}}
  
  -- 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
  
index 0d3b17a1b1da7b8655a3ceda569871c34e7e8e5e,63f94864da231fed330a7f6cdb268df1076f205c..63f94864da231fed330a7f6cdb268df1076f205c
@@@ -1,62 -1,61 +1,61 @@@
  --[[
-                                  
-      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(...)
index 0000000000000000000000000000000000000000,9959b2589d4b40f3fedfa2453d6b87a8ece6dda9..9959b2589d4b40f3fedfa2453d6b87a8ece6dda9
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,145 +1,145 @@@
+ --[[
+      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 }
index 4c428039df23ec9c6d41732280219c5ec1b6fa7d,01891b037aba4ea34ab4d48b7aba365b8aa48568..01891b037aba4ea34ab4d48b7aba365b8aa48568
@@@ -1,21 -1,17 +1,17 @@@
  --[[
-                                                    
-      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
@@@ -34,7 -30,7 +30,7 @@@ function quake:display(
      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
@@@ -64,7 -60,7 +60,7 @@@
      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
@@@ -96,12 -92,12 +92,12 @@@ en
  
  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)
@@@ -159,7 -155,10 +155,10 @@@ function quake:toggle(
       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()
index abf57c4b9c0e75892127147dc2df135e5e7761f7,465132d56193d6de9d7098c6ec66f8a948b2c040..465132d56193d6de9d7098c6ec66f8a948b2c040
@@@ -1,14 -1,13 +1,13 @@@
  --[[
-                                                    
-      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
@@@ -19,14 -18,22 +18,22 @@@ local separators = { height = 0, width 
  -- 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)
@@@ -42,8 -49,8 +49,8 @@@
              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)
@@@ -59,14 -66,22 +66,22 @@@ en
  -- 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
index 36ccc719b3b38ac18d02b546b084bf39baf677f8,3b6c6d6d2e87ce69234e02a571e6c4bd9193a833..3b6c6d6d2e87ce69234e02a571e6c4bd9193a833
@@@ -1,17 -1,15 +1,15 @@@
  --[[
-                                                   
-      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
index 2ad021048a82880e6e79717ce38f2384ff05aab4,b2f7b44ec6e84987d5d7216136abb5522254ada7..b2f7b44ec6e84987d5d7216136abb5522254ada7
@@@ -1,21 -1,19 +1,19 @@@
  --[[
-                                                   
-      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
@@@ -29,7 -27,7 +27,7 @@@ local function factory(args
          },
  
          _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"
@@@ -48,8 -52,7 +52,7 @@@
      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
  
index e901842f27fc0492d5fe75952b217d895177d91e,3cb801c8ef93eeda994dedcfe960171979e513dc..3cb801c8ef93eeda994dedcfe960171979e513dc
@@@ -1,36 -1,53 +1,53 @@@
  --[[
-                                                                                                                       
-        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",
@@@ -63,6 -88,9 +88,9 @@@
          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
index 0000000000000000000000000000000000000000,928a7bd91f15119e582584b8ac6412a41e6a0930..928a7bd91f15119e582584b8ac6412a41e6a0930
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,175 +1,175 @@@
+ --[[
+      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
index d7f130ebfe1a970dfcee8b2434876e7d28f11acf,9e863a528473cbb2e406d2bbf8ceaa19f209799e..9e863a528473cbb2e406d2bbf8ceaa19f209799e
@@@ -1,14 -1,13 +1,13 @@@
  --[[
-                                                    
-      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
index 83d8aaf86870ef3dda47468fbc3468af992cd487,f429c7782fb277d2a4978fb0dd06678b5d5176a6..f429c7782fb277d2a4978fb0dd06678b5d5176a6
@@@ -1,9 -1,8 +1,8 @@@
  --[[
-                                                                   
-      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")
@@@ -12,9 -11,8 +11,8 @@@ local focused      = require("awful.scr
  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
index 0babb3abe5227a1267292c88ab65ae5b5efd4864,d0e5eed77f47d01d7eb623c36f16fde8fce5bb6b..d0e5eed77f47d01d7eb623c36f16fde8fce5bb6b
@@@ -1,9 -1,9 +1,9 @@@
  --[[
-                                                         
-      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
index ba795538647f1b711e32056e5e0fd88cc5e6383a,536e0063087abd4b5152516dbbc218be735fd7ed..536e0063087abd4b5152516dbbc218be735fd7ed
@@@ -1,16 -1,16 +1,16 @@@
  --[[
-                                                   
-      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
@@@ -23,21 -23,30 +23,30 @@@ function task.hide(
  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
  
@@@ -49,9 -58,9 +58,9 @@@ function task.prompt(
              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,
@@@ -65,6 -74,7 +74,7 @@@ function task.attach(widget, args
      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 = {
index 0000000000000000000000000000000000000000,b8acbe2cdedf0bc033ccbc122da943eba38be619..b8acbe2cdedf0bc033ccbc122da943eba38be619
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,147 +1,147 @@@
+ --[[
+      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
index 9a5a9648f01e679c5cdf7992fd5cb45eb03b3c3e,f4cce734763b5238302c73879c1d37284bd89047..f4cce734763b5238302c73879c1d37284bd89047
@@@ -1,17 -1,15 +1,15 @@@
  --[[
-                                                   
-      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
@@@ -27,9 -25,7 +25,7 @@@ local function factory(args
          -- 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 }
index 473bd33fa77554274b31581f01ea56bf94d3479b,58fbf93ffbc574bcfebe8a7910a89c5e6fc7b1c1..58fbf93ffbc574bcfebe8a7910a89c5e6fc7b1c1
@@@ -1,24 -1,39 +1,39 @@@
  --[[
-                                                   
-      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
index 0f7fde56ca1ea384a2fa4c12a76047c3be42a88f,b3d9dc7d0d65965a1eea3bcb841cedefa763c2cd..b3d9dc7d0d65965a1eea3bcb841cedefa763c2cd
@@@ -1,16 -1,15 +1,15 @@@
  --[[
-                                                   
-      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
index f77f872314a01f855adb1e79e3552cab9582b201,57b86bb986b3c905e70a005bdb1dc30e82195765..57b86bb986b3c905e70a005bdb1dc30e82195765
@@@ -1,15 -1,14 +1,14 @@@
  --[[
-                                                    
-      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
index 50fff3bb7d62f78cd4bbfd6c5cd84987223d1786,3dcae2b7d9366dddfabf8da718ffe76eff85e79d..3dcae2b7d9366dddfabf8da718ffe76eff85e79d
@@@ -1,10 -1,9 +1,9 @@@
  --[[
-                                                   
-      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")
index d0b37d7db0daaef3f3a97fa4ab83912bdd4c52e0,01f28e633e271c0fc617e7af454d2ab4c6732ad7..01f28e633e271c0fc617e7af454d2ab4c6732ad7
@@@ -1,22 -1,19 +1,19 @@@
  --[[
-                                                   
-      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
@@@ -26,8 -23,8 +23,8 @@@ local function factory(args
      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
index f42ec25d01a52a0f37f24b495a1993f08fce4753,805b5778f1199caad96390e9cf7f4ccc8f548a01..805b5778f1199caad96390e9cf7f4ccc8f548a01
@@@ -1,37 -1,37 +1,37 @@@
  --[[
-                                                   
-      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
  
@@@ -46,7 -46,7 +46,7 @@@
              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,
@@@ -85,7 -94,7 +94,7 @@@
              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
  
index 0000000000000000000000000000000000000000,f63fe55c3b146b548a348176b9d05d44112a1796..f63fe55c3b146b548a348176b9d05d44112a1796
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,57 +1,57 @@@
+ --[[
+      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
index 41a8ce36b608525ff3e164f8c006f25309fa2d47,51290f8cd9f84ef01213cd8b7f7d7b6d563d0326..51290f8cd9f84ef01213cd8b7f7d7b6d563d0326
@@@ -1,24 -1,21 +1,21 @@@
  --[[
-                                                   
-      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
index d35868753135b02b9ad58e41de46d98363757c2f,adf3e035ba4cf5930021e561939fa3cc42761c38..adf3e035ba4cf5930021e561939fa3cc42761c38
@@@ -1,10 -1,9 +1,9 @@@
  --[[
-                                                   
-      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")
index efe2ab931dd971af9f39b44889b07ed3b7003030,e909b322f14a197b1d51f4448dc845ab18df1069..e909b322f14a197b1d51f4448dc845ab18df1069
@@@ -1,40 -1,42 +1,42 @@@
  --[[
-                                                   
-      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
index 10981466179f600900ca27c56c04d931a780c1dd,9c1e797fe8bb69eabc47b10f0b22f53395310ab3..9c1e797fe8bb69eabc47b10f0b22f53395310ab3
@@@ -1,9 -1,8 +1,8 @@@
  --[[
-                                                   
-      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")
@@@ -11,12 -10,10 +10,10 @@@ local json     = require("lain.util").d
  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)
index d6cf027a4c2535c179a8112137d065a5bc740fea,2899629c445cb12efb72cc538e36cfc2ec812201..2899629c445cb12efb72cc538e36cfc2ec812201
--- 2/wiki
@@@ -1,1 -1,1 +1,1 @@@
- Subproject commit d6cf027a4c2535c179a8112137d065a5bc740fea
+ Subproject commit 2899629c445cb12efb72cc538e36cfc2ec812201