]> 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 wiki
authorluke bonham <dadasignificanulla@gmail.com>
Tue, 21 Jan 2014 15:01:44 +0000 (16:01 +0100)
committerluke bonham <dadasignificanulla@gmail.com>
Tue, 21 Jan 2014 15:01:44 +0000 (16:01 +0100)
129 files changed:
.gitmodules [new file with mode: 0644]
README.rst [new file with mode: 0644]
helpers.lua [new file with mode: 0644]
icons/cal/white/1.png [new file with mode: 0644]
icons/cal/white/10.png [new file with mode: 0644]
icons/cal/white/11.png [new file with mode: 0644]
icons/cal/white/12.png [new file with mode: 0644]
icons/cal/white/13.png [new file with mode: 0644]
icons/cal/white/14.png [new file with mode: 0644]
icons/cal/white/15.png [new file with mode: 0644]
icons/cal/white/16.png [new file with mode: 0644]
icons/cal/white/17.png [new file with mode: 0644]
icons/cal/white/18.png [new file with mode: 0644]
icons/cal/white/19.png [new file with mode: 0644]
icons/cal/white/2.png [new file with mode: 0644]
icons/cal/white/20.png [new file with mode: 0644]
icons/cal/white/21.png [new file with mode: 0644]
icons/cal/white/22.png [new file with mode: 0644]
icons/cal/white/23.png [new file with mode: 0644]
icons/cal/white/24.png [new file with mode: 0644]
icons/cal/white/25.png [new file with mode: 0644]
icons/cal/white/26.png [new file with mode: 0644]
icons/cal/white/27.png [new file with mode: 0644]
icons/cal/white/28.png [new file with mode: 0644]
icons/cal/white/29.png [new file with mode: 0644]
icons/cal/white/3.png [new file with mode: 0644]
icons/cal/white/30.png [new file with mode: 0644]
icons/cal/white/31.png [new file with mode: 0644]
icons/cal/white/4.png [new file with mode: 0644]
icons/cal/white/5.png [new file with mode: 0644]
icons/cal/white/6.png [new file with mode: 0644]
icons/cal/white/7.png [new file with mode: 0644]
icons/cal/white/8.png [new file with mode: 0644]
icons/cal/white/9.png [new file with mode: 0644]
icons/layout/default/cascade.png [new file with mode: 0644]
icons/layout/default/cascadebrowse.png [new file with mode: 0644]
icons/layout/default/cascadebrowsew.png [new file with mode: 0644]
icons/layout/default/cascadew.png [new file with mode: 0644]
icons/layout/default/centerfair.png [new file with mode: 0644]
icons/layout/default/centerfairw.png [new file with mode: 0644]
icons/layout/default/centerwork.png [new file with mode: 0644]
icons/layout/default/centerworkw.png [new file with mode: 0644]
icons/layout/default/termfair.png [new file with mode: 0644]
icons/layout/default/termfairw.png [new file with mode: 0644]
icons/layout/zenburn/cascade.png [new file with mode: 0644]
icons/layout/zenburn/cascadebrowse.png [new file with mode: 0644]
icons/layout/zenburn/centerfair.png [new file with mode: 0644]
icons/layout/zenburn/centerwork.png [new file with mode: 0644]
icons/layout/zenburn/termfair.png [new file with mode: 0644]
icons/mail.png [new file with mode: 0644]
icons/no_net.png [new file with mode: 0755]
icons/taskwarrior/task.png [new file with mode: 0644]
icons/taskwarrior/tasksmall.png [new file with mode: 0755]
init.lua [new file with mode: 0644]
layout/cascade.lua [new file with mode: 0644]
layout/cascadetile.lua [new file with mode: 0644]
layout/centerfair.lua [new file with mode: 0644]
layout/centerwork.lua [new file with mode: 0644]
layout/init.lua [new file with mode: 0644]
layout/termfair.lua [new file with mode: 0644]
layout/uselessfair.lua [new file with mode: 0644]
layout/uselesspiral.lua [new file with mode: 0644]
layout/uselesstile.lua [new file with mode: 0644]
scripts/dfs [new file with mode: 0755]
scripts/mpdcover [new file with mode: 0755]
util/init.lua [new file with mode: 0644]
util/markup.lua [new file with mode: 0644]
widgets/alsa.lua [new file with mode: 0644]
widgets/alsabar.lua [new file with mode: 0644]
widgets/bat.lua [new file with mode: 0644]
widgets/borderbox.lua [new file with mode: 0644]
widgets/calendar.lua [new file with mode: 0644]
widgets/contrib/brightness.lua [new file with mode: 0644]
widgets/contrib/init.lua [new file with mode: 0644]
widgets/contrib/task.lua [new file with mode: 0644]
widgets/contrib/tpbat/init.lua [new file with mode: 0644]
widgets/contrib/tpbat/smapi.lua [new file with mode: 0644]
widgets/cpu.lua [new file with mode: 0644]
widgets/fs.lua [new file with mode: 0644]
widgets/imap.lua [new file with mode: 0644]
widgets/init.lua [new file with mode: 0644]
widgets/maildir.lua [new file with mode: 0644]
widgets/mem.lua [new file with mode: 0644]
widgets/mpd.lua [new file with mode: 0644]
widgets/net.lua [new file with mode: 0644]
widgets/sysload.lua [new file with mode: 0644]
widgets/temp.lua [new file with mode: 0644]
widgets/yawn/icons/BlowingSnow.png [new file with mode: 0755]
widgets/yawn/icons/Cloudy.png [new file with mode: 0755]
widgets/yawn/icons/DayClear.png [new file with mode: 0755]
widgets/yawn/icons/DayFair.png [new symlink]
widgets/yawn/icons/DayMostlyCloudy.png [new file with mode: 0755]
widgets/yawn/icons/DayPartlyCloudy.png [new file with mode: 0755]
widgets/yawn/icons/Drizzle.png [new symlink]
widgets/yawn/icons/Fog.png [new symlink]
widgets/yawn/icons/Foggy.png [new file with mode: 0755]
widgets/yawn/icons/FreezingDrizzle.png [new file with mode: 0755]
widgets/yawn/icons/FreezingRain.png [new file with mode: 0755]
widgets/yawn/icons/Hail.png [new file with mode: 0755]
widgets/yawn/icons/Haze.png [new symlink]
widgets/yawn/icons/HeavyRain.png [new symlink]
widgets/yawn/icons/HeavySnow.png [new file with mode: 0755]
widgets/yawn/icons/LightRain.png [new symlink]
widgets/yawn/icons/LightSnow.png [new symlink]
widgets/yawn/icons/LightSnowShowers.png [new file with mode: 0755]
widgets/yawn/icons/Mist.png [new symlink]
widgets/yawn/icons/MixedRainAndHail.png [new file with mode: 0755]
widgets/yawn/icons/MixedRainAndSleet.png [new file with mode: 0755]
widgets/yawn/icons/MixedRainAndSnow.png [new file with mode: 0755]
widgets/yawn/icons/NightClear.png [new file with mode: 0755]
widgets/yawn/icons/NightFair.png [new symlink]
widgets/yawn/icons/NightMostlyCloudy.png [new file with mode: 0755]
widgets/yawn/icons/NightPartlyCloudy.png [new file with mode: 0755]
widgets/yawn/icons/README.md [new file with mode: 0644]
widgets/yawn/icons/Rain.png [new file with mode: 0755]
widgets/yawn/icons/RainThunder.png [new file with mode: 0755]
widgets/yawn/icons/Showers.png [new file with mode: 0755]
widgets/yawn/icons/Sleet.png [new symlink]
widgets/yawn/icons/Snow.png [new symlink]
widgets/yawn/icons/SnowFlurries.png [new symlink]
widgets/yawn/icons/SnowShowers.png [new file with mode: 0755]
widgets/yawn/icons/Sunny.png [new file with mode: 0755]
widgets/yawn/icons/ThunderintheVicinity.png [new symlink]
widgets/yawn/icons/Wind.png [new file with mode: 0755]
widgets/yawn/icons/na.png [new file with mode: 0755]
widgets/yawn/init.lua [new file with mode: 0644]
widgets/yawn/localizations/it_IT [new file with mode: 0644]
widgets/yawn/localizations/localization_template [new file with mode: 0644]
wiki [new submodule]

diff --git a/.gitmodules b/.gitmodules
new file mode 100644 (file)
index 0000000..350c0f8
--- /dev/null
@@ -0,0 +1,3 @@
+[submodule "wiki"]
+       path = wiki
+       url = https://github.com/copycat-killer/lain.wiki.git
diff --git a/README.rst b/README.rst
new file mode 100644 (file)
index 0000000..f59c04d
--- /dev/null
@@ -0,0 +1,47 @@
+Lain
+====
+
+---------------------------------------------
+Layouts, widgets and utilities for Awesome WM
+---------------------------------------------
+
+:Author: Luke Bonham <dada [at] archlinux [dot] info>
+:Version: git
+:License: GNU-GPLv2_
+:Source: https://github.com/copycat-killer/lain
+
+Description
+-----------
+
+Successor of awesome-vain_, this module provides new layouts, a set of widgets and utility functions, in order to improve Awesome_ usability and configurability.
+
+Read the wiki_ for all the info.
+
+Contributions
+-------------
+
+Any contribution is welcome! Feel free to make a pull request.
+
+Just 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.
+
+- 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 eventually update ``wiki`` submodule with a thorough section.
+
+Contributed widgets have to be put in ``lain/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-GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
+.. _awesome-vain: https://github.com/vain/awesome-vain
+.. _Awesome: http://awesome.naquadah.org/
+.. _wiki: https://github.com/copycat-killer/lain/wiki
diff --git a/helpers.lua b/helpers.lua
new file mode 100644 (file)
index 0000000..c42ce61
--- /dev/null
@@ -0,0 +1,78 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2010-2012, Peter Hofmann              
+                                                  
+--]]
+
+local debug  = require("debug")
+
+local capi   = { timer = timer }
+local io     = { open = io.open }
+local rawget = rawget
+
+-- Lain helper functions for internal use
+-- lain.helpers
+local helpers = {}
+
+helpers.lain_dir    = debug.getinfo(1, 'S').source:match[[^@(.*/).*$]]
+helpers.icons_dir   = helpers.lain_dir .. 'icons/'
+helpers.scripts_dir = helpers.lain_dir .. 'scripts/'
+
+-- {{{ Modules loader
+
+function helpers.wrequire(table, key)
+    local module = rawget(table, key)
+    return module or require(table._NAME .. '.' .. key)
+end
+
+-- }}}
+
+-- {{{ Read the first line of a file or return nil
+
+function helpers.first_line(f)
+    local fp = io.open(f)
+    if not fp
+    then
+        return nil
+    end
+
+    local content = fp:read("*l")
+    fp:close()
+    return content
+end
+
+-- }}}
+
+-- {{{ Timer maker
+
+helpers.timer_table = {}
+
+function helpers.newtimer(name, timeout, fun, nostart)
+    helpers.timer_table[name] = capi.timer({ timeout = timeout })
+    helpers.timer_table[name]:connect_signal("timeout", fun)
+    helpers.timer_table[name]:start()
+    if not nostart then
+        helpers.timer_table[name]:emit_signal("timeout")
+    end
+end
+
+-- }}}
+
+-- {{{ A map utility
+
+helpers.map_table = {}
+
+function helpers.set_map(element, value)
+    helpers.map_table[element] = value
+end
+
+function helpers.get_map(element)
+    return helpers.map_table[element]
+end
+
+-- }}}
+
+return helpers
diff --git a/icons/cal/white/1.png b/icons/cal/white/1.png
new file mode 100644 (file)
index 0000000..90b696c
Binary files /dev/null and b/icons/cal/white/1.png differ
diff --git a/icons/cal/white/10.png b/icons/cal/white/10.png
new file mode 100644 (file)
index 0000000..b462ffb
Binary files /dev/null and b/icons/cal/white/10.png differ
diff --git a/icons/cal/white/11.png b/icons/cal/white/11.png
new file mode 100644 (file)
index 0000000..cf43296
Binary files /dev/null and b/icons/cal/white/11.png differ
diff --git a/icons/cal/white/12.png b/icons/cal/white/12.png
new file mode 100644 (file)
index 0000000..42cf092
Binary files /dev/null and b/icons/cal/white/12.png differ
diff --git a/icons/cal/white/13.png b/icons/cal/white/13.png
new file mode 100644 (file)
index 0000000..37db670
Binary files /dev/null and b/icons/cal/white/13.png differ
diff --git a/icons/cal/white/14.png b/icons/cal/white/14.png
new file mode 100644 (file)
index 0000000..0188504
Binary files /dev/null and b/icons/cal/white/14.png differ
diff --git a/icons/cal/white/15.png b/icons/cal/white/15.png
new file mode 100644 (file)
index 0000000..64418a6
Binary files /dev/null and b/icons/cal/white/15.png differ
diff --git a/icons/cal/white/16.png b/icons/cal/white/16.png
new file mode 100644 (file)
index 0000000..8b86700
Binary files /dev/null and b/icons/cal/white/16.png differ
diff --git a/icons/cal/white/17.png b/icons/cal/white/17.png
new file mode 100644 (file)
index 0000000..033b5ff
Binary files /dev/null and b/icons/cal/white/17.png differ
diff --git a/icons/cal/white/18.png b/icons/cal/white/18.png
new file mode 100644 (file)
index 0000000..817c426
Binary files /dev/null and b/icons/cal/white/18.png differ
diff --git a/icons/cal/white/19.png b/icons/cal/white/19.png
new file mode 100644 (file)
index 0000000..0e6dafc
Binary files /dev/null and b/icons/cal/white/19.png differ
diff --git a/icons/cal/white/2.png b/icons/cal/white/2.png
new file mode 100644 (file)
index 0000000..b93789a
Binary files /dev/null and b/icons/cal/white/2.png differ
diff --git a/icons/cal/white/20.png b/icons/cal/white/20.png
new file mode 100644 (file)
index 0000000..3d8d7c6
Binary files /dev/null and b/icons/cal/white/20.png differ
diff --git a/icons/cal/white/21.png b/icons/cal/white/21.png
new file mode 100644 (file)
index 0000000..79a74f3
Binary files /dev/null and b/icons/cal/white/21.png differ
diff --git a/icons/cal/white/22.png b/icons/cal/white/22.png
new file mode 100644 (file)
index 0000000..e8845ce
Binary files /dev/null and b/icons/cal/white/22.png differ
diff --git a/icons/cal/white/23.png b/icons/cal/white/23.png
new file mode 100644 (file)
index 0000000..a8d4dfb
Binary files /dev/null and b/icons/cal/white/23.png differ
diff --git a/icons/cal/white/24.png b/icons/cal/white/24.png
new file mode 100644 (file)
index 0000000..1a3b38a
Binary files /dev/null and b/icons/cal/white/24.png differ
diff --git a/icons/cal/white/25.png b/icons/cal/white/25.png
new file mode 100644 (file)
index 0000000..c3621b7
Binary files /dev/null and b/icons/cal/white/25.png differ
diff --git a/icons/cal/white/26.png b/icons/cal/white/26.png
new file mode 100644 (file)
index 0000000..f26731b
Binary files /dev/null and b/icons/cal/white/26.png differ
diff --git a/icons/cal/white/27.png b/icons/cal/white/27.png
new file mode 100644 (file)
index 0000000..e4dde77
Binary files /dev/null and b/icons/cal/white/27.png differ
diff --git a/icons/cal/white/28.png b/icons/cal/white/28.png
new file mode 100644 (file)
index 0000000..b924c22
Binary files /dev/null and b/icons/cal/white/28.png differ
diff --git a/icons/cal/white/29.png b/icons/cal/white/29.png
new file mode 100644 (file)
index 0000000..e9a74f8
Binary files /dev/null and b/icons/cal/white/29.png differ
diff --git a/icons/cal/white/3.png b/icons/cal/white/3.png
new file mode 100644 (file)
index 0000000..1124271
Binary files /dev/null and b/icons/cal/white/3.png differ
diff --git a/icons/cal/white/30.png b/icons/cal/white/30.png
new file mode 100644 (file)
index 0000000..8147d78
Binary files /dev/null and b/icons/cal/white/30.png differ
diff --git a/icons/cal/white/31.png b/icons/cal/white/31.png
new file mode 100644 (file)
index 0000000..a1be3e8
Binary files /dev/null and b/icons/cal/white/31.png differ
diff --git a/icons/cal/white/4.png b/icons/cal/white/4.png
new file mode 100644 (file)
index 0000000..16713bc
Binary files /dev/null and b/icons/cal/white/4.png differ
diff --git a/icons/cal/white/5.png b/icons/cal/white/5.png
new file mode 100644 (file)
index 0000000..466aa71
Binary files /dev/null and b/icons/cal/white/5.png differ
diff --git a/icons/cal/white/6.png b/icons/cal/white/6.png
new file mode 100644 (file)
index 0000000..a1c9798
Binary files /dev/null and b/icons/cal/white/6.png differ
diff --git a/icons/cal/white/7.png b/icons/cal/white/7.png
new file mode 100644 (file)
index 0000000..e971951
Binary files /dev/null and b/icons/cal/white/7.png differ
diff --git a/icons/cal/white/8.png b/icons/cal/white/8.png
new file mode 100644 (file)
index 0000000..909b726
Binary files /dev/null and b/icons/cal/white/8.png differ
diff --git a/icons/cal/white/9.png b/icons/cal/white/9.png
new file mode 100644 (file)
index 0000000..dc636c4
Binary files /dev/null and b/icons/cal/white/9.png differ
diff --git a/icons/layout/default/cascade.png b/icons/layout/default/cascade.png
new file mode 100644 (file)
index 0000000..292a057
Binary files /dev/null and b/icons/layout/default/cascade.png differ
diff --git a/icons/layout/default/cascadebrowse.png b/icons/layout/default/cascadebrowse.png
new file mode 100644 (file)
index 0000000..2f12ada
Binary files /dev/null and b/icons/layout/default/cascadebrowse.png differ
diff --git a/icons/layout/default/cascadebrowsew.png b/icons/layout/default/cascadebrowsew.png
new file mode 100644 (file)
index 0000000..c46b48b
Binary files /dev/null and b/icons/layout/default/cascadebrowsew.png differ
diff --git a/icons/layout/default/cascadew.png b/icons/layout/default/cascadew.png
new file mode 100644 (file)
index 0000000..da64bd6
Binary files /dev/null and b/icons/layout/default/cascadew.png differ
diff --git a/icons/layout/default/centerfair.png b/icons/layout/default/centerfair.png
new file mode 100644 (file)
index 0000000..188c243
Binary files /dev/null and b/icons/layout/default/centerfair.png differ
diff --git a/icons/layout/default/centerfairw.png b/icons/layout/default/centerfairw.png
new file mode 100644 (file)
index 0000000..ed4bcf5
Binary files /dev/null and b/icons/layout/default/centerfairw.png differ
diff --git a/icons/layout/default/centerwork.png b/icons/layout/default/centerwork.png
new file mode 100644 (file)
index 0000000..826b331
Binary files /dev/null and b/icons/layout/default/centerwork.png differ
diff --git a/icons/layout/default/centerworkw.png b/icons/layout/default/centerworkw.png
new file mode 100644 (file)
index 0000000..fcfa7e3
Binary files /dev/null and b/icons/layout/default/centerworkw.png differ
diff --git a/icons/layout/default/termfair.png b/icons/layout/default/termfair.png
new file mode 100644 (file)
index 0000000..06226c1
Binary files /dev/null and b/icons/layout/default/termfair.png differ
diff --git a/icons/layout/default/termfairw.png b/icons/layout/default/termfairw.png
new file mode 100644 (file)
index 0000000..0a8b576
Binary files /dev/null and b/icons/layout/default/termfairw.png differ
diff --git a/icons/layout/zenburn/cascade.png b/icons/layout/zenburn/cascade.png
new file mode 100644 (file)
index 0000000..532842d
Binary files /dev/null and b/icons/layout/zenburn/cascade.png differ
diff --git a/icons/layout/zenburn/cascadebrowse.png b/icons/layout/zenburn/cascadebrowse.png
new file mode 100644 (file)
index 0000000..87be658
Binary files /dev/null and b/icons/layout/zenburn/cascadebrowse.png differ
diff --git a/icons/layout/zenburn/centerfair.png b/icons/layout/zenburn/centerfair.png
new file mode 100644 (file)
index 0000000..01cda8e
Binary files /dev/null and b/icons/layout/zenburn/centerfair.png differ
diff --git a/icons/layout/zenburn/centerwork.png b/icons/layout/zenburn/centerwork.png
new file mode 100644 (file)
index 0000000..6a2cecc
Binary files /dev/null and b/icons/layout/zenburn/centerwork.png differ
diff --git a/icons/layout/zenburn/termfair.png b/icons/layout/zenburn/termfair.png
new file mode 100644 (file)
index 0000000..b7d5880
Binary files /dev/null and b/icons/layout/zenburn/termfair.png differ
diff --git a/icons/mail.png b/icons/mail.png
new file mode 100644 (file)
index 0000000..60ba6e0
Binary files /dev/null and b/icons/mail.png differ
diff --git a/icons/no_net.png b/icons/no_net.png
new file mode 100755 (executable)
index 0000000..1a3e8a8
Binary files /dev/null and b/icons/no_net.png differ
diff --git a/icons/taskwarrior/task.png b/icons/taskwarrior/task.png
new file mode 100644 (file)
index 0000000..859ca29
Binary files /dev/null and b/icons/taskwarrior/task.png differ
diff --git a/icons/taskwarrior/tasksmall.png b/icons/taskwarrior/tasksmall.png
new file mode 100755 (executable)
index 0000000..2de6946
Binary files /dev/null and b/icons/taskwarrior/tasksmall.png differ
diff --git a/init.lua b/init.lua
new file mode 100644 (file)
index 0000000..5086435
--- /dev/null
+++ b/init.lua
@@ -0,0 +1,21 @@
+
+--[[
+                                                   
+     Lain                                          
+     Layouts, widgets and utilities for Awesome WM 
+                                                   
+     Licensed under GNU General Public License v2  
+      * (c) 2013, Luke Bonham                      
+                                                   
+--]]
+
+package.loaded.lain = nil
+
+local lain =
+{
+    layout  = require("lain.layout"),
+    util    = require("lain.util"),
+    widgets = require("lain.widgets")
+}
+
+return lain
diff --git a/layout/cascade.lua b/layout/cascade.lua
new file mode 100644 (file)
index 0000000..cabacef
--- /dev/null
@@ -0,0 +1,65 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2010-2012, Peter Hofmann              
+                                                  
+--]]
+
+local tag = require("awful.tag")
+
+local cascade =
+{
+    name     = "cascade",
+    nmaster  = 0,
+    offset_x = 32,
+    offset_y = 8
+}
+
+function cascade.arrange(p)
+
+    -- Cascade windows.
+
+    -- Screen.
+    local wa = p.workarea
+    local cls = p.clients
+
+    -- Opening a new window will usually force all existing windows to
+    -- get resized. This wastes a lot of CPU time. So let's set a lower
+    -- bound to "how_many": This wastes a little screen space but you'll
+    -- get a much better user experience.
+    local t = tag.selected(p.screen)
+    local num_c
+    if cascade.nmaster > 0
+    then
+        num_c = cascade.nmaster
+    else
+        num_c = tag.getnmaster(t)
+    end
+
+    local how_many = #cls
+    if how_many < num_c
+    then
+        how_many = num_c
+    end
+
+    local current_offset_x = cascade.offset_x * (how_many - 1)
+    local current_offset_y = cascade.offset_y * (how_many - 1)
+
+    -- Iterate.
+    for i = 1,#cls,1
+    do
+        local c = cls[i]
+        local g = {}
+
+        g.x = wa.x + (how_many - i) * cascade.offset_x
+        g.y = wa.y + (i - 1) * cascade.offset_y
+        g.width = wa.width - current_offset_x
+        g.height = wa.height - current_offset_y
+
+        c:geometry(g)
+    end
+end
+
+return cascade
diff --git a/layout/cascadetile.lua b/layout/cascadetile.lua
new file mode 100644 (file)
index 0000000..98821e3
--- /dev/null
@@ -0,0 +1,155 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2010-2012, Peter Hofmann              
+                                                  
+--]]
+
+local tag       = require("awful.tag")
+local beautiful = require("beautiful")
+local tonumber  = tonumber
+
+local cascadetile =
+{
+    name          = "cascadetile",
+    nmaster       = 0,
+    ncol          = 0,
+    mwfact        = 0,
+    offset_x      = 5,
+    offset_y      = 32,
+    extra_padding = 0
+}
+
+function cascadetile.arrange(p)
+
+    -- Layout with one fixed column meant for a master window. Its
+    -- width is calculated according to mwfact. Other clients are
+    -- cascaded or "tabbed" in a slave column on the right.
+
+    -- It's a bit hard to demonstrate the behaviour with ASCII-images...
+    --
+    --       (1)              (2)              (3)              (4)
+    --   +-----+---+      +-----+---+      +-----+---+      +-----+---+
+    --   |     |   |      |     |   |      |     |   |      |     | 4 |
+    --   |     |   |      |     | 2 |      |     | 3 |      |     |   |
+    --   |  1  |   |  ->  |  1  |   |  ->  |  1  |   |  ->  |  1  +---+
+    --   |     |   |      |     +---+      |     +---+      |     | 3 |
+    --   |     |   |      |     |   |      |     | 2 |      |     |---|
+    --   |     |   |      |     |   |      |     |---|      |     | 2 |
+    --   |     |   |      |     |   |      |     |   |      |     |---|
+    --   +-----+---+      +-----+---+      +-----+---+      +-----+---+
+
+    -- A useless gap (like the dwm patch) can be defined with
+    -- beautiful.useless_gap_width.
+    local useless_gap = tonumber(beautiful.useless_gap_width) or 0
+
+    -- Screen.
+    local wa = p.workarea
+    local cls = p.clients
+
+    -- Width of main column?
+    local t = tag.selected(p.screen)
+    local mwfact
+    if cascadetile.mwfact > 0
+    then
+        mwfact = cascadetile.mwfact
+    else
+        mwfact = tag.getmwfact(t)
+    end
+
+    -- Make slave windows overlap main window? Do this if ncol is 1.
+    local overlap_main
+    if cascadetile.ncol > 0
+    then
+        overlap_main = cascadetile.ncol
+    else
+        overlap_main = tag.getncol(t)
+    end
+
+    -- Minimum space for slave windows? See cascade.lua.
+    local num_c
+    if cascadetile.nmaster > 0
+    then
+        num_c = cascadetile.nmaster
+    else
+        num_c = tag.getnmaster(t)
+    end
+
+    local how_many = #cls - 1
+    if how_many < num_c
+    then
+        how_many = num_c
+    end
+    local current_offset_x = cascadetile.offset_x * (how_many - 1)
+    local current_offset_y = cascadetile.offset_y * (how_many - 1)
+
+    if #cls > 0
+    then
+        -- Main column, fixed width and height.
+        local c = cls[#cls]
+        local g = {}
+        local mainwid = wa.width * mwfact
+        local slavewid = wa.width - mainwid
+
+        if overlap_main == 1
+        then
+            g.width = wa.width
+
+            -- The size of the main window may be reduced a little bit.
+            -- This allows you to see if there are any windows below the
+            -- main window.
+            -- This only makes sense, though, if the main window is
+            -- overlapping everything else.
+            g.width = g.width - cascadetile.extra_padding
+        else
+            g.width = mainwid
+        end
+
+        g.height = wa.height
+        g.x = wa.x
+        g.y = wa.y
+        if useless_gap > 0
+        then
+            -- Reduce width once and move window to the right. Reduce
+            -- height twice, however.
+            g.width = g.width - useless_gap
+            g.height = g.height - 2 * useless_gap
+            g.x = g.x + useless_gap
+            g.y = g.y + useless_gap
+
+            -- When there's no window to the right, add an additional
+            -- gap.
+            if overlap_main == 1
+            then
+                g.width = g.width - useless_gap
+            end
+        end
+        c:geometry(g)
+
+        -- Remaining clients stacked in slave column, new ones on top.
+        if #cls > 1
+        then
+            for i = (#cls - 1),1,-1
+            do
+                c = cls[i]
+                g = {}
+                g.width = slavewid - current_offset_x
+                g.height = wa.height - current_offset_y
+                g.x = wa.x + mainwid + (how_many - i) * cascadetile.offset_x
+                g.y = wa.y + (i - 1) * cascadetile.offset_y
+                if useless_gap > 0
+                then
+                    g.width = g.width - 2 * useless_gap
+                    g.height = g.height - 2 * useless_gap
+                    g.x = g.x + useless_gap
+                    g.y = g.y + useless_gap
+                end
+                c:geometry(g)
+            end
+        end
+    end
+end
+
+return cascadetile
diff --git a/layout/centerfair.lua b/layout/centerfair.lua
new file mode 100644 (file)
index 0000000..49b4a14
--- /dev/null
@@ -0,0 +1,147 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2010,      Nicolas Estibals           
+      * (c) 2010-2012, Peter Hofmann              
+                                                  
+--]]
+
+local tag       = require("awful.tag")
+local beautiful = require("beautiful")
+local math      = { ceil  = math.ceil,
+                    floor = math.floor,
+                    max   = math.max }
+local tonumber  = tonumber
+
+local centerfair  = { name = "centerfair" }
+
+function centerfair.arrange(p)
+    -- Layout with fixed number of vertical columns (read from nmaster).
+    -- Cols are centerded until there is nmaster columns, then windows
+    -- are stacked in the slave columns, with at most ncol clients per
+    -- column if possible.
+
+    -- with nmaster=3 and ncol=1 you'll have
+    --        (1)                (2)                (3)
+    --   +---+---+---+      +-+---+---+-+      +---+---+---+
+    --   |   |   |   |      | |   |   | |      |   |   |   |
+    --   |   | 1 |   |  ->  | | 1 | 2 | | ->   | 1 | 2 | 3 |  ->
+    --   |   |   |   |      | |   |   | |      |   |   |   |
+    --   +---+---+---+      +-+---+---+-+      +---+---+---+
+
+    --        (4)                (5)
+    --   +---+---+---+      +---+---+---+
+    --   |   |   | 3 |      |   | 2 | 4 |
+    --   + 1 + 2 +---+  ->  + 1 +---+---+
+    --   |   |   | 4 |      |   | 3 | 5 |
+    --   +---+---+---+      +---+---+---+
+
+    -- A useless gap (like the dwm patch) can be defined with
+    -- beautiful.useless_gap_width .
+    local useless_gap = tonumber(beautiful.useless_gap_width) or 0
+
+    -- Screen.
+    local wa = p.workarea
+    local cls = p.clients
+
+    -- How many vertical columns? Read from nmaster on the tag.
+    local t = tag.selected(p.screen)
+    local num_x = centerfair.nmaster or tag.getnmaster(t)
+    local ncol = centerfair.ncol or tag.getncol(t)
+
+    local width = math.floor((wa.width-(num_x+1)*useless_gap) / num_x)
+
+    local offset_y = wa.y + useless_gap
+    if #cls < num_x
+    then
+        -- Less clients than the number of columns, let's center it!
+        local offset_x = wa.x + useless_gap + (wa.width - #cls*width - (#cls+1)*useless_gap) / 2
+        local g = {}
+        g.width = width
+        g.height = wa.height - 2*useless_gap - 2
+        g.y = offset_y
+        for i = 1, #cls do
+            g.x = offset_x + (i - 1) * (width + useless_gap + 2)
+            cls[i]:geometry(g)
+        end
+    else
+        -- More clients than the number of columns, let's arrange it!
+        local offset_x = wa.x 
+        if useless_gap > 0 then
+           offset_x = offset_x 
+        end
+
+        -- Master client deserves a special treatement
+        local g = {}
+        g.width = wa.width - (num_x-1)*width -num_x*useless_gap - 2
+        g.height = wa.height - 2*useless_gap - 2
+        g.x = offset_x + useless_gap
+        g.y = offset_y
+        cls[1]:geometry(g)
+
+        -- Treat the other clients
+
+        -- Compute distribution of clients among columns
+        local num_y ={}
+        do
+            local remaining_clients = #cls-1
+            local ncol_min = math.ceil(remaining_clients/(num_x-1))
+            if ncol >= ncol_min
+            then
+                for i = (num_x-1), 1, -1 do
+                    if (remaining_clients-i+1) < ncol
+                    then
+                        num_y[i] = remaining_clients-i + 1
+                    else
+                        num_y[i] = ncol
+                    end
+                    remaining_clients = remaining_clients - num_y[i]
+                end
+            else
+                local rem = remaining_clients % (num_x-1)
+                if rem ==0
+                then
+                    for i = 1, num_x-1 do
+                        num_y[i] = ncol_min
+                    end
+                else
+                    for i = 1, num_x-1 do
+                        num_y[i] = ncol_min - 1
+                    end
+                    for i = 0, rem-1 do
+                        num_y[num_x-1-i] = num_y[num_x-1-i] + 1
+                    end
+                end
+            end
+        end
+
+        -- Compute geometry of the other clients
+        local nclient = 2
+        g.x = g.x + g.width+useless_gap + 2
+        g.width = width
+
+        if useless_gap > 0 then
+            g.width = g.width - useless_gap/2 - 2
+        end
+
+        for i = 1, (num_x-1) do
+            to_remove = 2
+            g.height = math.floor((wa.height-useless_gap)/num_y[i]) 
+            g.y = offset_y
+            for j = 0, (num_y[i]-2) do
+                cls[nclient]:geometry(g)
+                nclient = nclient + 1
+                g.y = g.y + g.height+useless_gap + 2
+                to_remove = to_remove + 2
+            end
+            g.height = wa.height - num_y[i]*useless_gap - (num_y[i]-1)*g.height - useless_gap - to_remove
+            cls[nclient]:geometry(g)
+            nclient = nclient + 1
+            g.x = g.x+g.width+useless_gap + 2
+        end
+    end
+end
+
+return centerfair
diff --git a/layout/centerwork.lua b/layout/centerwork.lua
new file mode 100644 (file)
index 0000000..b8175ea
--- /dev/null
@@ -0,0 +1,118 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2010-2012, Peter Hofmann              
+                                                  
+--]]
+
+local awful     = require("awful")
+local beautiful = require("beautiful")
+local tonumber  = tonumber
+local math      = { floor = math.floor }
+
+local centerwork =
+{
+    name         = "centerwork",
+    top_left     = 0,
+    top_right    = 1,
+    bottom_left  = 2,
+    bottom_right = 3
+}
+
+function centerwork.arrange(p)
+    -- A useless gap (like the dwm patch) can be defined with
+    -- beautiful.useless_gap_width .
+    local useless_gap = tonumber(beautiful.useless_gap_width) or 0
+
+    -- Screen.
+    local wa = p.workarea
+    local cls = p.clients
+
+    -- Width of main column?
+    local t = awful.tag.selected(p.screen)
+    local mwfact = awful.tag.getmwfact(t)
+
+    if #cls > 0
+    then
+        -- Main column, fixed width and height.
+        local c = cls[#cls]
+        local g = {}
+        local mainwid = math.floor(wa.width * mwfact)
+        local slavewid = wa.width - mainwid
+        local slaveLwid = math.floor(slavewid / 2)
+        local slaveRwid = slavewid - slaveLwid
+        local slaveThei = math.floor(wa.height / 2)
+        local slaveBhei = wa.height - slaveThei
+
+        g.height = wa.height - 2 * useless_gap
+        g.width = mainwid
+        g.x = wa.x + slaveLwid
+        g.y = wa.y + useless_gap
+
+        c:geometry(g)
+
+        -- Auxiliary windows.
+        if #cls > 1
+        then
+            local at = 0
+            for i = (#cls - 1),1,-1
+            do
+                -- It's all fixed. If there are more than 5 clients,
+                -- those additional clients will float. This is
+                -- intentional.
+                if at == 4
+                then
+                    break
+                end
+
+                c = cls[i]
+                g = {}
+
+                if at == centerwork.top_left
+                then
+                    -- top left
+                    g.x = wa.x + useless_gap
+                    g.y = wa.y + useless_gap
+                    g.width = slaveLwid - 2 * useless_gap
+                    g.height = slaveThei - useless_gap
+                elseif at == centerwork.top_right
+                then
+                    -- top right
+                    g.x = wa.x + slaveLwid + mainwid + useless_gap
+                    g.y = wa.y + useless_gap
+                    g.width = slaveRwid - 2 * useless_gap
+                    g.height = slaveThei - useless_gap
+                elseif at == centerwork.bottom_left
+                then
+                    -- bottom left
+                    g.x = wa.x + useless_gap
+                    g.y = wa.y + slaveThei + useless_gap
+                    g.width = slaveLwid - 2 * useless_gap
+                    g.height = slaveBhei - 2 * useless_gap
+                elseif at == centerwork.bottom_right
+                then
+                    -- bottom right
+                    g.x = wa.x + slaveLwid + mainwid + useless_gap
+                    g.y = wa.y + slaveThei + useless_gap
+                    g.width = slaveRwid - 2 * useless_gap
+                    g.height = slaveBhei - 2 * useless_gap
+                end
+
+                c:geometry(g)
+
+                at = at + 1
+            end
+
+            -- Set remaining clients to floating.
+            for i = (#cls - 1 - 4),1,-1
+            do
+                c = cls[i]
+                awful.client.floating.set(c, true)
+            end
+        end
+    end
+end
+
+return centerwork
diff --git a/layout/init.lua b/layout/init.lua
new file mode 100644 (file)
index 0000000..d79679a
--- /dev/null
@@ -0,0 +1,20 @@
+
+--[[
+                                                   
+     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               
+                                                   
+--]]
+
+local wrequire     = require("lain.helpers").wrequire
+local setmetatable = setmetatable
+
+local layout       = { _NAME = "lain.layout" }
+
+return setmetatable(layout, { __index = wrequire })
diff --git a/layout/termfair.lua b/layout/termfair.lua
new file mode 100644 (file)
index 0000000..89a44bb
--- /dev/null
@@ -0,0 +1,141 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2010-2012, Peter Hofmann              
+                                                  
+--]]
+
+local tag       = require("awful.tag")
+local beautiful = require("beautiful")
+local math      = { ceil  = math.ceil,
+                    floor = math.floor,
+                    max   = math.max }
+local tonumber  = tonumber
+
+local termfair  = { name = "termfair" }
+
+function termfair.arrange(p)
+    -- Layout with fixed number of vertical columns (read from nmaster).
+    -- New windows align from left to right. When a row is full, a now
+    -- one above it is created. Like this:
+
+    --        (1)                (2)                (3)
+    --   +---+---+---+      +---+---+---+      +---+---+---+
+    --   |   |   |   |      |   |   |   |      |   |   |   |
+    --   | 1 |   |   |  ->  | 2 | 1 |   |  ->  | 3 | 2 | 1 |  ->
+    --   |   |   |   |      |   |   |   |      |   |   |   |
+    --   +---+---+---+      +---+---+---+      +---+---+---+
+
+    --        (4)                (5)                (6)
+    --   +---+---+---+      +---+---+---+      +---+---+---+
+    --   | 4 |   |   |      | 5 | 4 |   |      | 6 | 5 | 4 |
+    --   +---+---+---+  ->  +---+---+---+  ->  +---+---+---+
+    --   | 3 | 2 | 1 |      | 3 | 2 | 1 |      | 3 | 2 | 1 |
+    --   +---+---+---+      +---+---+---+      +---+---+---+
+
+    -- A useless gap (like the dwm patch) can be defined with
+    -- beautiful.useless_gap_width.
+    local useless_gap = tonumber(beautiful.useless_gap_width) or 0
+
+    -- Screen.
+    local wa = p.workarea
+    local cls = p.clients
+
+    -- How many vertical columns?
+    local t = tag.selected(p.screen)
+    local num_x = termfair.nmaster or tag.getnmaster(t)
+
+    -- Do at least "desired_y" rows.
+    local desired_y = termfair.ncol or tag.getncol(t)
+
+    if #cls > 0
+    then
+        local num_y = math.max(math.ceil(#cls / num_x), desired_y)
+        local cur_num_x = num_x
+        local at_x = 0
+        local at_y = 0
+        local remaining_clients = #cls
+        local width = math.floor(wa.width / num_x)
+        local height = math.floor(wa.height / num_y)
+
+        -- We start the first row. Left-align by limiting the number of
+        -- available slots.
+        if remaining_clients < num_x
+        then
+            cur_num_x = remaining_clients
+        end
+
+        -- Iterate in reversed order.
+        for i = #cls,1,-1
+        do
+            -- Get x and y position.
+            local c = cls[i]
+            local this_x = cur_num_x - at_x - 1
+            local this_y = num_y - at_y - 1
+
+            -- Calc geometry.
+            local g = {}
+            if this_x == (num_x - 1)
+            then
+                g.width = wa.width - (num_x - 1) * width
+            else
+                g.width = width
+            end
+            if this_y == (num_y - 1)
+            then
+                g.height = wa.height - (num_y - 1) * height
+            else
+                g.height = height
+            end
+
+            g.x = wa.x + this_x * width
+            g.y = wa.y + this_y * height
+
+            if useless_gap > 0
+            then
+                -- Top and left clients are shrinked by two steps and
+                -- get moved away from the border. Other clients just
+                -- get shrinked in one direction.
+                
+                gap_factor = (useless_gap / 100) * 2
+
+                if this_x == 0
+                then
+                    g.width = g.width - (2 + gap_factor) * useless_gap
+                    g.x = g.x + useless_gap
+                else
+                    g.width = g.width - (1 + gap_factor) * useless_gap
+                end
+
+                if this_y == 0
+                then
+                    g.height = g.height - (2 + gap_factor) * useless_gap
+                    g.y = g.y + useless_gap
+                else
+                    g.height = g.height - (1 + gap_factor) * useless_gap
+                end
+            end
+            c:geometry(g)
+            remaining_clients = remaining_clients - 1
+
+            -- Next grid position.
+            at_x = at_x + 1
+            if at_x == num_x
+            then
+                -- Row full, create a new one above it.
+                at_x = 0
+                at_y = at_y + 1
+
+                -- We start a new row. Left-align.
+                if remaining_clients < num_x
+                then
+                    cur_num_x = remaining_clients
+                end
+            end
+        end
+    end
+end
+
+return termfair
diff --git a/layout/uselessfair.lua b/layout/uselessfair.lua
new file mode 100644 (file)
index 0000000..6aa6666
--- /dev/null
@@ -0,0 +1,121 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2012,      Josh Komoroske             
+      * (c) 2010-2012, Peter Hofmann              
+                                                  
+--]]
+
+local beautiful = require("beautiful")
+local ipairs    = ipairs
+local math      = { ceil = math.ceil, sqrt = math.sqrt }
+local tonumber  = tonumber
+
+local uselessfair = {}
+
+local function fair(p, orientation)
+    -- A useless gap (like the dwm patch) can be defined with
+    -- beautiful.useless_gap_width.
+    local useless_gap = tonumber(beautiful.useless_gap_width) or 0
+
+    local wa = p.workarea
+    local cls = p.clients
+
+    if #cls > 0 then
+        local cells = math.ceil(math.sqrt(#cls))
+        local strips = math.ceil(#cls / cells)
+
+        local cell = 0
+        local strip = 0
+        for k, c in ipairs(cls) do
+            local g = {}
+            -- Save actual grid index for use in the useless_gap
+            -- routine.
+            local this_x = 0
+            local this_y = 0
+            if ( orientation == "east" and #cls > 2 )
+            or ( orientation == "south" and #cls <= 2 ) then
+                if #cls < (strips * cells) and strip == strips - 1 then
+                    g.width = wa.width / (cells - ((strips * cells) - #cls))
+                else
+                    g.width = wa.width / cells
+                end
+                g.height = wa.height / strips
+
+                this_x = cell
+                this_y = strip
+
+                g.x = wa.x + cell * g.width
+                g.y = wa.y + strip * g.height
+
+            else
+                if #cls < (strips * cells) and strip == strips - 1 then
+                    g.height = wa.height / (cells - ((strips * cells) - #cls))
+                else
+                    g.height = wa.height / cells
+                end
+                g.width = wa.width / strips
+
+                this_x = strip
+                this_y = cell
+
+                g.x = wa.x + strip * g.width
+                g.y = wa.y + cell * g.height
+            end
+
+            -- Useless gap.
+            if useless_gap > 0
+            then
+                -- Top and left clients are shrinked by two steps and
+                -- get moved away from the border. Other clients just
+                -- get shrinked in one direction.
+
+                gap_factor = (useless_gap / 100) * 2
+
+                if this_x == 0
+                then
+                    g.width = g.width - (2 + gap_factor) * useless_gap
+                    g.x = g.x + useless_gap
+                else
+                    g.width = g.width - (1 + gap_factor) * useless_gap
+                end
+
+                if this_y == 0
+                then
+                    g.height = g.height - (2 + gap_factor) * useless_gap
+                    g.y = g.y + useless_gap
+                else
+                    g.height = g.height - (1 + gap_factor) * useless_gap
+                end
+            end
+            -- End of useless gap.
+
+            c:geometry(g)
+
+            cell = cell + 1
+            if cell == cells then
+                cell = 0
+                strip = strip + 1
+            end
+        end
+    end
+end
+
+--- Horizontal fair layout.
+-- @param screen The screen to arrange.
+uselessfair.horizontal = {}
+uselessfair.horizontal.name = "uselessfairh"
+function uselessfair.horizontal.arrange(p)
+    return fair(p, "east")
+end
+
+-- Vertical fair layout.
+-- @param screen The screen to arrange.
+uselessfair.name = "uselessfair"
+function uselessfair.arrange(p)
+    return fair(p, "south")
+end
+
+return uselessfair
diff --git a/layout/uselesspiral.lua b/layout/uselesspiral.lua
new file mode 100644 (file)
index 0000000..3164c75
--- /dev/null
@@ -0,0 +1,110 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2009       Uli Schlachter             
+      * (c) 2008       Julien Danjolu             
+                                                  
+--]]
+
+local beautiful = require("beautiful")
+local ipairs    = ipairs
+local tonumber  = tonumber
+
+local uselesspiral = {}
+
+local function spiral(p, spiral)
+    -- A useless gap (like the dwm patch) can be defined with
+    -- beautiful.useless_gap_width.
+    local useless_gap = tonumber(beautiful.useless_gap_width) or 0
+
+    local wa = p.workarea
+    local cls = p.clients
+    local n = #cls
+
+    local static_wa = wa
+
+    for k, c in ipairs(cls) do
+        if k < n then
+            if k % 2 == 0 then
+                wa.height = wa.height / 2
+            else
+                wa.width = wa.width / 2
+            end
+        end
+
+        if k % 4 == 0 and spiral then
+            wa.x = wa.x - wa.width
+        elseif k % 2 == 0 or
+            (k % 4 == 3 and k < n and spiral) then
+            wa.x = wa.x + wa.width
+        end
+
+        if k % 4 == 1 and k ~= 1 and spiral then
+            wa.y = wa.y - wa.height
+        elseif k % 2 == 1 and k ~= 1 or
+            (k % 4 == 0 and k < n and spiral) then
+            wa.y = wa.y + wa.height
+        end
+
+            local wa2 = {}
+            wa2.x = wa.x
+            wa2.y = wa.y
+            wa2.height = wa.height
+            wa2.width = wa.width
+
+        -- Useless gap.
+        if useless_gap > 0
+        then
+            -- Top and left clients are shrinked by two steps and
+            -- get moved away from the border. Other clients just
+            -- get shrinked in one direction.
+
+            top = false
+            left = false
+
+            gap_factor = (useless_gap / 100) * 2
+
+            if wa2.y == static_wa.y then
+               top = true
+            end
+
+            if wa2.x == static_wa.x then
+               left = true
+            end
+
+            if top then
+                wa2.height = wa2.height - (2 + gap_factor) * useless_gap
+                wa2.y = wa2.y + useless_gap
+            else
+                wa2.height = wa2.height - (1 + gap_factor) * useless_gap
+            end
+
+            if left then
+                wa2.width = wa2.width - (2 + gap_factor) * useless_gap
+                wa2.x = wa2.x + useless_gap
+            else
+                wa2.width = wa2.width - (1 + gap_factor) * useless_gap
+            end
+        end
+        -- End of useless gap.
+
+        c:geometry(wa2)
+    end
+end
+
+--- Dwindle layout
+uselesspiral.dwindle = {}
+uselesspiral.dwindle.name = "uselessdwindle"
+function uselesspiral.dwindle.arrange(p)
+    return spiral(p, false)
+end
+
+--- Spiral layout
+uselesspiral.name = "uselesspiral"
+function uselesspiral.arrange(p)
+    return spiral(p, true)
+end
+
+return uselesspiral
diff --git a/layout/uselesstile.lua b/layout/uselesstile.lua
new file mode 100644 (file)
index 0000000..e496500
--- /dev/null
@@ -0,0 +1,230 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2009       Donald Ephraim Curtis      
+      * (c) 2008       Julien Danjolu             
+                                                  
+--]]
+
+local tag       = require("awful.tag")
+local beautiful = require("beautiful")
+local ipairs    = ipairs
+local math      = { floor = math.floor,
+                    max   = math.max,
+                    min   = math.min }
+local tonumber  = tonumber
+
+local uselesstile = {}
+
+local function tile_group(cls, wa, orientation, fact, group)
+    -- A useless gap (like the dwm patch) can be defined with
+    -- beautiful.useless_gap_width .
+    local useless_gap = tonumber(beautiful.useless_gap_width) or 0
+
+    -- get our orientation right
+    local height = "height"
+    local width = "width"
+    local x = "x"
+    local y = "y"
+    if orientation == "top" or orientation == "bottom" then
+        height = "width"
+        width = "height"
+        x = "y"
+        y = "x"
+    end
+
+    -- make this more generic (not just width)
+    available = wa[width] - (group.coord - wa[x])
+
+    -- find our total values
+    local total_fact = 0
+    local min_fact = 1
+    local size = group.size
+    for c = group.first,group.last do
+        -- determine the width/height based on the size_hint
+        local i = c - group.first +1
+        local size_hints = cls[c].size_hints
+        local size_hint = size_hints["min_"..width] or size_hints["base_"..width] or 0
+        size_hint = size_hint + cls[c].border_width*2
+        size = math.max(size_hint, size)
+
+        -- calculate the height
+        if not fact[i] then
+            fact[i] = min_fact
+        else
+            min_fact = math.min(fact[i],min_fact)
+        end
+        total_fact = total_fact + fact[i]
+    end
+    size = math.min(size, available)
+
+    local coord = wa[y]
+    local geom = {}
+    local used_size = 0
+    local unused = wa[height]
+    local stat_coord = wa[x]
+    --stat_coord = size
+    for c = group.first,group.last do
+        local i = c - group.first +1
+        geom[width] = size
+        geom[height] = math.floor(unused * fact[i] / total_fact)
+        geom[x] = group.coord
+        geom[y] = coord
+
+        coord = coord + geom[height]
+        unused = unused - geom[height]
+        total_fact = total_fact - fact[i]
+        used_size = math.max(used_size, geom[width])
+
+        -- Useless gap
+        if useless_gap > 0
+        then
+            -- Top and left clients are shrinked by two steps and
+            -- get moved away from the border. Other clients just
+            -- get shrinked in one direction.
+
+            top = false
+            left = false
+
+            gap_factor = (useless_gap / 100) * 2
+
+            if geom[y] == wa[y] then
+                top = true
+            end
+
+            if geom[x] == 0 or geom[x] == wa[x] then
+                left = true
+            end
+
+            if top then
+                geom[height] = geom[height] - (2 + gap_factor) * useless_gap
+                geom[y] = geom[y] + useless_gap
+            else
+                geom[height] = geom[height] - (1 + gap_factor) * useless_gap 
+            end
+
+            if left then
+                geom[width] = geom[width] - (2 + gap_factor) * useless_gap
+                geom[x] = geom[x] + useless_gap
+            else
+                geom[width] = geom[width] - (1 + gap_factor) * useless_gap
+            end
+        end
+        -- End of useless gap.
+
+        geom = cls[c]:geometry(geom)
+    end
+
+    return used_size
+end
+
+local function tile(param, orientation)
+    local t = tag.selected(param.screen)
+    orientation = orientation or "right"
+
+    -- this handles are different orientations
+    local height = "height"
+    local width = "width"
+    local x = "x"
+    local y = "y"
+    if orientation == "top" or orientation == "bottom" then
+        height = "width"
+        width = "height"
+        x = "y"
+        y = "x"
+    end
+
+    local cls = param.clients
+    local nmaster = math.min(tag.getnmaster(t), #cls)
+    local nother = math.max(#cls - nmaster,0)
+
+    local mwfact = tag.getmwfact(t)
+    local wa = param.workarea
+    local ncol = tag.getncol(t)
+
+    local data = tag.getdata(t).windowfact
+
+    if not data then
+        data = {}
+        tag.getdata(t).windowfact = data
+    end
+
+    local coord = wa[x]
+    local place_master = true
+    if orientation == "left" or orientation == "top" then
+        -- if we are on the left or top we need to render the other windows first
+        place_master = false
+    end
+
+    -- this was easier than writing functions because there is a lot of data we need
+    for d = 1,2 do
+        if place_master and nmaster > 0 then
+            local size = wa[width]
+            if nother > 0 then
+                size = math.min(wa[width] * mwfact, wa[width] - (coord - wa[x]))
+            end
+            if not data[0] then
+                data[0] = {}
+            end
+            coord = coord + tile_group(cls, wa, orientation, data[0], {first=1, last=nmaster, coord = coord, size = size})
+        end
+
+        if not place_master and nother > 0 then
+            local last = nmaster
+
+            -- we have to modify the work area size to consider left and top views
+            local wasize = wa[width]
+            if nmaster > 0 and (orientation == "left" or orientation == "top") then
+                wasize = wa[width] - wa[width]*mwfact
+            end
+            for i = 1,ncol do
+                -- Try to get equal width among remaining columns
+                local size = math.min( (wasize - (coord - wa[x])) / (ncol - i + 1) )
+                local first = last + 1
+                last = last + math.floor((#cls - last)/(ncol - i + 1))
+                -- tile the column and update our current x coordinate
+                if not data[i] then
+                    data[i] = {}
+                end
+                coord = coord + tile_group(cls, wa, orientation, data[i], { first = first, last = last, coord = coord, size = size })
+            end
+        end
+        place_master = not place_master
+    end
+
+end
+
+uselesstile.right = {}
+uselesstile.right.name = "uselesstile"
+uselesstile.right.arrange = tile
+
+--- The main tile algo, on left.
+-- @param screen The screen number to tile.
+uselesstile.left = {}
+uselesstile.left.name = "uselesstileleft"
+function uselesstile.left.arrange(p)
+    return tile(p, "left")
+end
+
+--- The main tile algo, on bottom.
+-- @param screen The screen number to tile.
+uselesstile.bottom = {}
+uselesstile.bottom.name = "uselesstilebottom"
+function uselesstile.bottom.arrange(p)
+    return tile(p, "bottom")
+end
+
+--- The main tile algo, on top.
+-- @param screen The screen number to tile.
+uselesstile.top = {}
+uselesstile.top.name = "uselesstiletop"
+function uselesstile.top.arrange(p)
+    return tile(p, "top")
+end
+
+uselesstile.arrange = uselesstile.right.arrange
+uselesstile.name = uselesstile.right.name
+
+return uselesstile
diff --git a/scripts/dfs b/scripts/dfs
new file mode 100755 (executable)
index 0000000..1730b6e
--- /dev/null
@@ -0,0 +1,385 @@
+#!/bin/bash
+#
+#   Adapted from Eridan's "fs" (cleanup, enhancements and switch to bash/Linux)
+#   JM,  10/12/2004
+#
+#   Integrated into Lain in september 2013
+#   https://github.com/copycat-killer/lain
+
+# -------------------------------------------------------------------------
+#   Decoding options
+# -------------------------------------------------------------------------
+USAGE="Usage: $0 [-h(elp)] | [-n(arrow mode)] | [-w(eb output)]"
+
+NARROW_MODE=0
+WEB_OUTPUT=0
+
+while [ $# -gt 0 ]; do
+case "$1" in
+"-h" )
+echo $USAGE
+exit
+;;
+"-d" )
+DEBUG=1
+;;
+"-n" )
+NARROW_MODE=1
+;;
+"-w" )
+WEB_OUTPUT=1
+;;
+* )
+echo $USAGE
+exit
+;;
+esac
+shift
+done
+
+# -------------------------------------------------------------------------
+#   Preparations
+# -------------------------------------------------------------------------
+SYSTEM=`uname -s`
+PATTERN="/"
+
+case "$SYSTEM" in
+"Linux" )
+DF_COMMAND="/usr/bin/env df -k"
+SORT_COMMAND="/usr/bin/env sort -k6"
+AWK_COMMAND="/usr/bin/env awk"
+;;
+* )
+DF_COMMAND="/bin/df -k"
+SORT_COMMAND="/usr/bin/sort -k6"
+AWK_COMMAND="/usr/bin/env gawk"
+;;
+esac
+
+# -------------------------------------------------------------------------
+#   Grabbing "df" result
+# -------------------------------------------------------------------------
+DF_RESULT=`$DF_COMMAND`
+if [ ! -z $DEBUG ]; then
+echo "--> DF_RESULT:"
+echo "$DF_RESULT"
+echo ""
+fi
+
+# -------------------------------------------------------------------------
+#   Preprocessing "df" result, to join split logical lines
+# -------------------------------------------------------------------------
+PREPROCESSING_RESULT=` \
+                                                                                echo "$DF_RESULT" | $AWK_COMMAND -v PATTERN=$PATTERN \
+                                                                                '
+                                                                                NF == 1 {
+                                                                                        printf ("%s", $0)
+                                                                                }
+
+NF == 5 {
+       printf ("%s\n", $0)
+}
+
+NF > 6  {
+}
+
+NF == 6 {
+       printf ("%s\n", $0)
+}'
+`
+if [ ! -z $DEBUG ]; then
+echo "--> PREPROCESSING_RESULT:"
+echo "$PREPROCESSING_RESULT"
+echo ""
+fi
+
+SORTED_FILE_SYSTEMS_INFO=`echo "$PREPROCESSING_RESULT" | $SORT_COMMAND`
+
+if [ ! -z $DEBUG ]; then
+echo "--> SORTED_FILE_SYSTEMS_INFO:"
+echo "$SORTED_FILE_SYSTEMS_INFO"
+echo ""
+fi
+
+# -------------------------------------------------------------------------
+#   Computing mount point max length
+# -------------------------------------------------------------------------
+MOUNT_POINT_MAX_LENGTH=` \
+                                                                                        echo $SORTED_FILE_SYSTEMS_INFO | $AWK_COMMAND -v PATTERN=$PATTERN \
+                                                                                        '
+                                                                                        BEGIN       {
+                                                                                                mount_point_length_max = 15;
+                                                                                        }
+
+END     {
+       printf ("%d", mount_point_length_max);
+}
+
+$0 ~ PATTERN    {
+#       printf ("$6 = %s\n", $6);
+
+       mount_point = $6;
+#       printf ("mount_point = %s\n", mount_point);
+
+       mount_point_length = length (mount_point);
+#       printf ("mount_point_length = %d\n", mount_point_length);
+
+       if (mount_point_length > mount_point_length_max)
+               mount_point_length_max = mount_point_length;
+}'
+`
+if [ ! -z $DEBUG ]; then
+echo "MOUNT_POINT_MAX_LENGTH:      $MOUNT_POINT_MAX_LENGTH"
+fi
+
+# -------------------------------------------------------------------------
+#   Computing mount point data max size
+# -------------------------------------------------------------------------
+MOUNT_POINT_MAX_SIZE=` \
+                                                                                echo "$SORTED_FILE_SYSTEMS_INFO" | $AWK_COMMAND -v PATTERN=$PATTERN \
+                                                                                '
+                                                                                BEGIN       {
+                                                                                        mount_point_size_max = 0;
+                                                                                }
+
+END     {
+       printf ("%d", mount_point_size_max);
+}
+
+$0 ~ PATTERN    {
+#       df -k shows k_bytes!
+#       printf ("$2 = %s\n", $2);
+
+       mount_point_size = $2 * 1024;
+#       printf ("mount_point_size = %d\n", mount_point_size);
+
+       if (mount_point_size > mount_point_size_max)
+               mount_point_size_max = mount_point_size;
+}'
+`
+if [ ! -z $DEBUG ]; then
+echo "MOUNT_POINT_MAX_SIZE:      $MOUNT_POINT_MAX_SIZE"
+fi
+
+# -------------------------------------------------------------------------
+#   Let's go!
+# -------------------------------------------------------------------------
+echo "$SORTED_FILE_SYSTEMS_INFO" | $AWK_COMMAND -v DEBUG=$DEBUG -v PATTERN=$PATTERN -v NARROW_MODE=$NARROW_MODE -v LEFT_COLUMN=$MOUNT_POINT_MAX_LENGTH -v MAX_SIZE=$MOUNT_POINT_MAX_SIZE -v SCALE=$SCALE -v WEB_OUTPUT=$WEB_OUTPUT \
+                        '
+#   {printf ("$0 = %s\n", $0);}
+#   {printf ("$1 = %s\n", $1);}
+#   {printf ("PATTERN = %s\n", PATTERN);}
+#   {printf ("LEFT_COLUMN = %s\n", LEFT_COLUMN);}
+
+                        BEGIN       {
+                                k_bytes = 1024.0;
+                                m_bytes = 1024.0 * k_bytes;
+                                g_bytes = 1024.0 * m_bytes;
+                                t_bytes = 1024.0 * g_bytes;
+
+                                if (WEB_OUTPUT)
+                                {
+                                        all_stars = "**************************************************";
+                                        current_date = strftime ("%d-%m-%Y @ %H:%M:%S", localtime (systime ()));
+                                        free_threshold = 10; # %
+
+                                                printf ("<!-- DEBUT CONTENU -->\n");
+
+                                        printf ( \
+                                                        "<A NAME=\"top\"></A>\n" \
+                                                        "<P ALIGN=CENTER><SPAN CLASS=\"titleblue\">%s</SPAN><SPAN CLASS=\"textbold\">  --  STATUS OF <SPAN CLASS=\"titlered\">ALCOR</SPAN> FILE SYSTEMS</SPAN></P><BR>\n",
+                                                        current_date )
+
+                                                printf ("<TABLE WIDTH=\"100%%\" BORDER=1>\n");
+
+                                        printf ( \
+                                                        "<TR>\n" \
+                                                        "<TD ALIGN=LEFT><STRONG>Mount point</STRONG></TD>\n" \
+                                                        "<TD ALIGN=CENTER><STRONG>%% Usato&nbsp;(<SPAN CLASS=\"titleblue\">*</SPAN>)" \
+                                                        "&nbsp;-&nbsp;%% Free&nbsp;(<SPAN CLASS=\"titlegreen\">*</SPAN>)</STRONG></TD>\n" \
+                                                        "<TD ALIGN=CENTER><STRONG>%% Usato</STRONG></TD>\n" \
+                                                        "<TD ALIGN=CENTER><STRONG>Spazio libero</STRONG></TD>\n" \
+                                                        "<TD ALIGN=CENTER><STRONG>Spazio totale</STRONG></TD>\n" \
+                                                        "</TR>\n" );
+                                }
+                                else
+                                {
+                                        narrow_margin = "       ";
+#           printf ("%-*s", LEFT_COLUMN + 2, "Mount point");
+                                                if (NARROW_MODE)
+                                                        printf ("\n%s", narrow_margin);
+                                                else
+                                                        printf ("%-*s", LEFT_COLUMN + 2, "");
+                                        print "                                                     Used    Free     Total ";
+                                        if (! NARROW_MODE)
+                                                print "";
+                                }
+                        }
+
+END     {
+       if (WEB_OUTPUT)
+       {
+               printf ("</TABLE>\n");
+
+               printf ("<!-- FIN CONTENU -->\n");
+       }
+       else
+       {
+               if (NARROW_MODE)
+                       printf ("%s", narrow_margin);
+               else
+                       printf ("%-*s", LEFT_COLUMN + 2, "");
+               print "|----|----|----|----|----|----|----|----|----|----|"
+                       if (NARROW_MODE)
+                               printf ("\n%s", narrow_margin);
+                       else
+                               printf ("%-*s", LEFT_COLUMN + 2, "");
+               print "0   10   20   30   40   50   60   70   80   90  100";
+               print "";
+       }
+}
+
+$0 ~ PATTERN    {
+
+       if (index ($0, "members") == 0 && index ($0, "Download") == 0 && index ($0, "admin") == 0)
+       {
+#       df -k shows k_bytes!
+
+               total_size = $2 * k_bytes;
+               free_size = $4 * k_bytes;
+               percentage_occupied = substr($5, 0, 3);
+               mount_point = $6;
+
+               percentage_free = int (100 - percentage_occupied);
+
+#       reduction_factor: 2
+               stars_number = int (percentage_occupied / 2);
+
+               if (WEB_OUTPUT)
+               {
+                       posGroup = index (mount_point, "scratch");
+                       if (posGroup == 0)
+                               posGroup = index (mount_point, "u1");
+                       if (posGroup == 0)
+                               posGroup = index (mount_point, "u2");
+                       if (posGroup == 0)
+                               posGroup = index (mount_point, "u4");
+                       if (posGroup == 0)
+                               posGroup = index (mount_point, "u5");
+
+                       printf ("<TR>\n");
+
+                       if (posGroup > 0 || percentage_free < free_threshold)
+                       {
+                               if (percentage_free < free_threshold)
+                               {
+                                       class = "titlered";
+                                       if (posGroup == 0)
+                                               posGroup = 1;   # to display the whole mount_point in this color anyway
+                               }
+                               else if ((index (mount_point, "scratch") != 0) || (index (mount_point, "u1") != 0) || (index (mount_point, "u2") != 0))
+                               {
+                                       class = "titleorange";
+                                       posGroup = 1;   # to display the whole mount_point in this color
+                               }
+                               else if ((index (mount_point, "u4") != 0) || (index (mount_point, "u5") != 0))
+                               {
+                                       class = "titlebrown";
+                                       posGroup = 1;   # to display the whole mount_point in this color
+                               }
+
+                               printf ( \
+                                               "<TD ALIGN=LEFT>%s<SPAN CLASS=\"%s\">%s</SPAN></TD>\n",
+                                               substr (mount_point, 1, posGroup - 1),
+                                               class,
+                                               substr (mount_point, posGroup) );
+                       }
+                       else
+                       {
+                               printf ("<TD ALIGN=LEFT>%s</TD>\n", mount_point);
+                       }
+
+                       printf ( \
+                                       "<TD ALIGN=CENTER><SPAN CLASS=\"titleblue\">%s</SPAN><SPAN CLASS=\"titlegreen\">%s</SPAN></TD>\n",
+                                       substr (all_stars, 1, stars_number), substr (all_stars, stars_number + 1, 49) );
+
+                       if (percentage_free < free_threshold)
+                       {
+                               color_beginning = "<SPAN CLASS=\"titlered\">";
+                               color_end = "</SPAN>"
+                       }
+                       else
+                       {
+                               color_beginning = "";
+                               color_end = ""
+                       }
+
+                       if (total_size > 1 * t_bytes)
+                               printf ( \
+                                               "<TD ALIGN=RIGHT>%s%3d%%%s</TD><TD ALIGN=RIGHT>%5.1f Tb</TD><TD ALIGN=RIGHT>%5.1f Tb</TD>\n", \
+                                               color_beginning, percentage_occupied, color_end, free_size / t_bytes, total_size / t_bytes \
+                                               );
+                       else if (total_size > 1 * g_bytes)
+                               printf ( \
+                                               "<TD ALIGN=RIGHT>%s%3d%%%s</TD><TD ALIGN=RIGHT>%5.1f Gb</TD><TD ALIGN=RIGHT>%5.1f Gb</TD>\n", \
+                                               color_beginning, percentage_occupied, color_end, free_size / g_bytes, total_size / g_bytes \
+                                               );
+                       else if (total_size > 1 * m_byptes)
+                               printf ( \
+                                               "<TD ALIGN=RIGHT>%s%3d%%%s</TD><TD ALIGN=RIGHT>%5.1f Mb</TD><TD ALIGN=RIGHT>%5.1f Mb</TD>\n", \
+                                               color_beginning, percentage_occupied, color_end, free_size / m_bytes, total_size / m_bytes \
+                                               );
+                       else
+                               printf ( \
+                                               "<TD ALIGN=RIGHT>%s%3d%%%s</TD><TD ALIGN=RIGHT>%5.1f Kb</TD><TD ALIGN=RIGHT>%5.1f Kb</TD>\n", \
+                                               color_beginning, percentage_occupied, color_end, free_size / k_bytes, total_size / k_bytes \
+                                               );
+
+                       printf ("</TR>\n");
+               }
+
+               else
+               {
+#           printf ("percentage_occupied = %d\n", percentage_occupied);
+#           printf ("percentage_free = %d\n", percentage_free);
+
+                       printf ("%-*s", LEFT_COLUMN + 2, mount_point);
+                       if (NARROW_MODE)
+                               printf ("\n%s", narrow_margin);
+
+#           printf ("stars_number = %d\n", stars_number);
+
+                       printf ("|");
+                       for (i = 1; i <= stars_number; i++)
+                       {
+                               printf ("%s", "*");
+                       }
+                       for (i = stars_number + 1; i <= 49; i++)
+                       {
+                               printf ("%s", "-");
+                       }
+
+
+                       if (total_size > 1 * t_bytes)
+                               printf ( \
+                                               "| %3d%%    %5.1f    %5.1f Tb\n", \
+                                               percentage_occupied, free_size / t_bytes, total_size / t_bytes \
+                                               );
+                       else if (total_size > 1 * g_bytes)
+                               printf ( \
+                                               "| %3d%%    %5.1f    %5.1f Gb\n", \
+                                               percentage_occupied, free_size / g_bytes, total_size / g_bytes \
+                                               );
+                       else if (total_size > 1 * m_byptes)
+                               printf ( \
+                                               "| %3d%%    %5.1f    %5.1f Mb\n", \
+                                               percentage_occupied, free_size / m_bytes, total_size / m_bytes \
+                                               );
+                       else
+                               printf ( \
+                                               "| %3d%%    %5.1f    %5.1f Kb\n", \
+                                               percentage_occupied, free_size / k_bytes, total_size / k_bytes \
+                                               );
+               }
+       }   # if
+}'
diff --git a/scripts/mpdcover b/scripts/mpdcover
new file mode 100755 (executable)
index 0000000..f6cf0d6
--- /dev/null
@@ -0,0 +1,68 @@
+#!/bin/bash
+#
+# A simple cover fetcher script for current playing song on mpd.
+#
+# Author : Wolfgang Mueller
+#
+# Adapted for Lain internal use.
+# https://github.com/copycat-killer/lain
+#
+# You can use, edit and redistribute this script in any way you like.
+#
+# Dependencies: imagemagick.
+#
+# Usage: mpdcover <music_directory> <song_file> <cover_resize> <default_art>
+
+# Configuration-------------------------------------------------------
+
+# Music directory
+MUSIC_DIR=$1
+
+# Song file
+file=$2
+
+# Regex expression used for image search
+IMG_REG="(Front|front|Cover|cover|Art|art|Folder|folder)\.(jpg|jpeg|png|gif)$"
+
+# Path of temporary resized cover
+TEMP_PATH="/tmp/mpdcover.png"
+
+# Resize cover
+COVER_RESIZE="$3x$3"
+
+if [ $COVER_RESIZE == "x" ]; then
+    COVER_RESIZE="100x100"
+fi
+
+# The default cover to use (optional)
+DEFAULT_ART=$4
+
+# Thumbnail background (transparent)
+COVER_BACKGROUND="none"
+
+#--------------------------------------------------------------------
+
+# check if anything is playing at all
+[[ -z $file ]] && exit 1
+
+# Art directory
+art="$MUSIC_DIR/${file%/*}"
+
+# find every file that matches IMG_REG set the first matching file to be the
+# cover.
+cover="$(find "$art/" -maxdepth 1 -type f | egrep -i -m1 "$IMG_REG")"
+
+# when no cover is found, use DEFAULT_ART as cover
+cover="${cover:=$DEFAULT_ART}"
+
+# check if art is available
+if [[ -n $cover ]]; then
+   if [[ -n $COVER_RESIZE ]]; then
+        convert "$cover" -thumbnail $COVER_RESIZE -gravity "center" -background "$COVER_BACKGROUND" -extent $COVER_RESIZE "$TEMP_PATH"
+        cover="$TEMP_PATH"
+   fi
+else
+   rm $TEMP_PATH
+fi
+
+exit 0
diff --git a/util/init.lua b/util/init.lua
new file mode 100644 (file)
index 0000000..1dfff65
--- /dev/null
@@ -0,0 +1,213 @@
+
+--[[
+                                                   
+     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               
+                                                   
+--]]
+
+local awful        = require("awful")
+local beautiful    = require("beautiful")
+local math         = { sqrt = math.sqrt }
+local mouse        = mouse
+local pairs        = pairs
+local string       = { gsub = string.gsub }
+local client       = client
+local screen       = screen
+local tonumber     = tonumber
+
+local wrequire     = require("lain.helpers").wrequire
+local setmetatable = setmetatable
+
+-- Lain utilities submodule
+-- lain.util
+local util = { _NAME = "lain.util" }
+
+-- Like awful.menu.clients, but only show clients of currently selected
+-- tags.
+function util.menu_clients_current_tags(menu, args)
+    -- List of currently selected tags.
+    local cls_tags = awful.tag.selectedlist(mouse.screen)
+
+    -- Final list of menu items.
+    local cls_t = {}
+
+    if cls_tags == nil then return nil end
+
+    -- For each selected tag get all clients of that tag and add them to
+    -- the menu. A click on a menu item will raise that client.
+    for i = 1,#cls_tags
+    do
+        local t = cls_tags[i]
+        local cls = t:clients()
+
+        for k, c in pairs(cls)
+        do
+            cls_t[#cls_t + 1] = { awful.util.escape(c.name) or "",
+                                  function ()
+                                      c.minimized = false
+                                      client.focus = c
+                                      c:raise()
+                                  end,
+                                  c.icon }
+        end
+    end
+
+    -- No clients? Then quit.
+    if #cls_t <= 0 then return nil end
+
+    -- menu may contain some predefined values, otherwise start with a
+    -- fresh menu.
+    if not menu then menu = {} end
+
+    -- Set the list of items and show the menu.
+    menu.items = cls_t
+    local m = awful.menu.new(menu)
+    m:show(args)
+    return m
+end
+
+-- Magnify a client: Set it to "float" and resize it.
+function util.magnify_client(c)
+    if not awful.client.floating.get(c) then
+        awful.client.floating.set(c, true)
+
+        local mg = screen[mouse.screen].geometry
+        local tag = awful.tag.selected(mouse.screen)
+        local mwfact = awful.tag.getmwfact(tag)
+        local g = {}
+        g.width = math.sqrt(mwfact) * mg.width
+        g.height = math.sqrt(mwfact) * mg.height
+        g.x = mg.x + (mg.width - g.width) / 2
+        g.y = mg.y + (mg.height - g.height) / 2
+        c:geometry(g)
+    else
+        awful.client.floating.set(c, false)
+    end
+end
+
+-- Read the nice value of pid from /proc.
+local function get_nice_value(pid)
+    local n = first_line('/proc/' .. pid .. '/stat')
+    if n == nil
+    then
+        -- This should not happen. But I don't want to crash, either.
+        return 0
+    end
+
+    -- Remove pid and tcomm. This is necessary because tcomm may contain
+    -- nasty stuff such as whitespace or additional parentheses...
+    n = string.gsub(n, '.*%) ', '')
+
+    -- Field number 17 now is the nice value.
+    fields = split(n, ' ')
+    return tonumber(fields[17])
+end
+
+-- To be used as a signal handler for "focus"
+-- This requires beautiful.border_focus{,_highprio,_lowprio}.
+function util.niceborder_focus(c)
+    local n = get_nice_value(c.pid)
+    if n == 0
+    then
+        c.border_color = beautiful.border_focus
+    elseif n < 0
+    then
+        c.border_color = beautiful.border_focus_highprio
+    else
+        c.border_color = beautiful.border_focus_lowprio
+    end
+end
+
+-- To be used as a signal handler for "unfocus"
+-- This requires beautiful.border_normal{,_highprio,_lowprio}.
+function util.niceborder_unfocus(c)
+    local n = get_nice_value(c.pid)
+    if n == 0
+    then
+        c.border_color = beautiful.border_normal
+    elseif n < 0
+    then
+        c.border_color = beautiful.border_normal_highprio
+    else
+        c.border_color = beautiful.border_normal_lowprio
+    end
+end
+
+-- Non-empty tag browsing
+-- direction in {-1, 1} <-> {previous, next} non-empty tag
+function util.tag_view_nonempty(direction, sc)
+   local s = sc or mouse.screen or 1
+   local scr = screen[s]
+
+   for i = 1, #tags[s] do
+       awful.tag.viewidx(direction,s)
+       if #awful.client.visible(s) > 0 then
+           return
+       end
+   end
+end
+
+-- {{{ Dynamic tagging
+--
+-- Add a new tag
+function util.add_tag(mypromptbox)
+    awful.prompt.run({prompt="New tag name: "}, mypromptbox[mouse.screen].widget,
+    function(text)
+        if text:len() > 0 then
+            props = { selected = true }
+            tag = awful.tag.add(new_name, props)
+            tag.name = text
+            tag:emit_signal("property::name")
+        end
+    end)
+end
+
+-- Rename current tag
+-- @author: minism
+function util.rename_tag(mypromptbox)
+    local tag = awful.tag.selected(mouse.screen)
+    awful.prompt.run({prompt="Rename tag: "}, mypromptbox[mouse.screen].widget,
+    function(text)
+        if text:len() > 0 then
+            tag.name = text
+            tag:emit_signal("property::name")
+        end
+    end)
+end
+
+-- Move current tag
+-- pos in {-1, 1} <-> {previous, next} tag position
+function util.move_tag(pos)
+    local tag = awful.tag.selected(mouse.screen)
+    local idx = awful.tag.getidx(tag)
+    if tonumber(pos) <= -1 then
+        awful.tag.move(idx - 1, tag)
+    else
+        awful.tag.move(idx + 1, tag)
+    end
+end
+
+-- Remove current tag (if empty)
+-- Any rule set on the tag shall be broken
+function util.remove_tag()
+    local tag = awful.tag.selected(mouse.screen)
+    local prevtag = awful.tag.gettags(mouse.screen)[awful.tag.getidx(tag) - 1]
+    awful.tag.delete(tag, prevtag)
+end
+--
+-- }}}
+
+-- On the fly useless gaps change
+function util.useless_gaps_resize(thatmuch)
+    beautiful.useless_gap_width = tonumber(beautiful.useless_gap_width) + thatmuch
+    awful.layout.arrange(mouse.screen)
+end
+
+return setmetatable(util, { __index = wrequire })
diff --git a/util/markup.lua b/util/markup.lua
new file mode 100644 (file)
index 0000000..d367bca
--- /dev/null
@@ -0,0 +1,69 @@
+
+--[[
+                                 
+     Licensed under MIT License  
+      * (c) 2013, Luke Bonham    
+      * (c) 2009, Uli Schlachter 
+      * (c) 2009, Majic          
+                                 
+--]]
+
+local beautiful    = require("beautiful")
+local tostring     = tostring
+local setmetatable = setmetatable
+
+-- Lain markup util submodule
+-- lain.util.markup
+local markup = {}
+
+local fg = {}
+local bg = {}
+
+-- Convenience tags.
+function markup.bold(text)      return '<b>'     .. tostring(text) .. '</b>'     end
+function markup.italic(text)    return '<i>'     .. tostring(text) .. '</i>'     end
+function markup.strike(text)    return '<s>'     .. tostring(text) .. '</s>'     end
+function markup.underline(text) return '<u>'     .. tostring(text) .. '</u>'     end
+function markup.monospace(text) return '<tt>'    .. tostring(text) .. '</tt>'    end
+function markup.big(text)       return '<big>'   .. tostring(text) .. '</big>'   end
+function markup.small(text)     return '<small>' .. tostring(text) .. '</small>' end
+
+-- Set the font.
+function markup.font(font, text)
+  return '<span font="'  .. tostring(font)  .. '">' .. tostring(text) ..'</span>'
+end
+
+-- Set the foreground.
+function fg.color(color, text)
+  return '<span foreground="' .. tostring(color) .. '">' .. tostring(text) .. '</span>'
+end
+
+-- Set the background.
+function bg.color(color, text)
+  return '<span background="' .. tostring(color) .. '">' .. tostring(text) .. '</span>'
+end
+
+-- Context: focus
+function fg.focus(text) return fg.color(beautiful.fg_focus, text) end
+function bg.focus(text) return bg.color(beautiful.bg_focus, text) end
+function markup.focus(text) return bg.focus(fg.focus(text)) end
+
+-- Context: normal
+function fg.normal(text) return fg.color(beautiful.fg_normal, text) end
+function bg.normal(text) return bg.color(beautiful.bg_normal, text) end
+function markup.normal(text) return bg.normal(fg.normal(text)) end
+
+-- Context: urgent
+function fg.urgent(text) return fg.color(beautiful.fg_urgent, text) end
+function bg.urgent(text) return bg.color(beautiful.bg_urgent, text) end
+function markup.urgent(text) return bg.urgent(fg.urgent(text)) end
+
+markup.fg = fg
+markup.bg = bg
+
+-- link markup.{fg,bg}(...) calls to markup.{fg,bg}.color(...)
+setmetatable(markup.fg, { __call = function(_, ...) return markup.fg.color(...) end })
+setmetatable(markup.bg, { __call = function(_, ...) return markup.bg.color(...) end })
+
+-- link markup(...) calls to markup.fg.color(...)
+return setmetatable(markup, { __call = function(_, ...) return markup.fg.color(...) end })
diff --git a/widgets/alsa.lua b/widgets/alsa.lua
new file mode 100644 (file)
index 0000000..28bb05c
--- /dev/null
@@ -0,0 +1,65 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013, Luke Bonham                     
+      * (c) 2010, Adrian C. <anrxc@sysphere.org>  
+                                                  
+--]]
+
+local newtimer        = require("lain.helpers").newtimer
+
+local wibox           = require("wibox")
+
+local io              = { popen  = io.popen }
+local string          = { match  = string.match }
+
+local setmetatable    = setmetatable
+
+-- ALSA volume
+-- lain.widgets.alsa
+local alsa = {}
+
+local function worker(args)
+    local args     = args or {}
+    local timeout  = args.timeout or 5
+    local channel  = args.channel or "Master"
+    local settings = args.settings or function() end
+
+    alsa.widget = wibox.widget.textbox('')
+
+    function alsa.update()
+        local f = assert(io.popen('amixer get ' .. channel))
+        local mixer = f:read("*all")
+        f:close()
+
+        volume_now = {}
+
+        volume_now.level, volume_now.status = string.match(mixer, "([%d]+)%%.*%[([%l]*)")
+
+        if volume_now.level == nil
+        then
+            volume_now.level  = "0"
+            volume_now.status = "off"
+        end
+
+        if volume_now.status == ""
+        then
+            if volume_now.level == "0"
+            then
+                volume_now.status = "off"
+            else
+                volume_now.status = "on"
+            end
+        end
+
+        widget = alsa.widget
+        settings()
+    end
+
+    newtimer("alsa", timeout, alsa.update)
+
+    return setmetatable(alsa, { __index = alsa.widget })
+end
+
+return setmetatable(alsa, { __call = function(_, ...) return worker(...) end })
diff --git a/widgets/alsabar.lua b/widgets/alsabar.lua
new file mode 100644 (file)
index 0000000..365ac2d
--- /dev/null
@@ -0,0 +1,175 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013, Luke Bonham                     
+      * (c) 2013, Rman                            
+                                                  
+--]]
+
+local newtimer     = require("lain.helpers").newtimer
+
+local awful        = require("awful")
+local beautiful    = require("beautiful")
+local naughty      = require("naughty")
+
+local io           = { popen  = io.popen }
+local math         = { modf   = math.modf }
+local string       = { format = string.format,
+                       match  = string.match,
+                       rep    = string.rep }
+local tonumber     = tonumber
+
+local setmetatable = setmetatable
+
+-- ALSA volume bar
+-- lain.widgets.alsabar
+local alsabar =
+{
+  channel = "Master",
+  step    = "5%",
+
+  colors =
+  {
+     background = beautiful.bg_normal,
+     mute       = "#EB8F8F",
+     unmute     = "#A4CE8A"
+  },
+
+  terminal = terminal or "xterm",
+  mixer    = terminal .. " -e alsamixer",
+
+  notifications =
+  {
+     font      = beautiful.font:sub(beautiful.font:find(""), beautiful.font:find(" ")),
+     font_size = "11",
+     color     = beautiful.fg_normal,
+     bar_size  = 18
+  },
+
+  _current_level = 0,
+  _muted         = false
+}
+
+function alsabar.notify()
+  alsabar.update()
+
+       local preset =
+       {
+      title   = "",
+      text    = "",
+      timeout = 4,
+      font    = alsabar.notifications.font .. " " ..
+                alsabar.notifications.font_size,
+      fg      = alsabar.notifications.color
+       }
+
+       if alsabar._muted
+  then
+               preset.title = alsabar.channel .. " - Muted"
+       else
+               preset.title = alsabar.channel .. " - " .. alsabar._current_level * 100 .. "%"
+       end
+
+  int = math.modf(alsabar._current_level * alsabar.notifications.bar_size)
+  preset.text = "["
+                .. string.rep("|", int)
+                .. string.rep(" ", alsabar.notifications.bar_size - int)
+                .. "]"
+
+  if alsabar._notify ~= nil then
+               alsabar._notify = naughty.notify ({
+        replaces_id = alsabar._notify.id,
+                         preset      = preset
+    })
+       else
+               alsabar._notify = naughty.notify ({
+        preset = preset
+    })
+       end
+end
+
+local function worker(args)
+    local args = args or {}
+    local timeout = args.timeout or 4
+    local settings = args.settings or function() end
+    local width = args.width or 63
+    local height = args.heigth or 1
+    local ticks = args.ticks or false
+    local ticks_size = args.ticks_size or 7
+    local vertical = args.vertical or false
+
+    alsabar.channel = args.channel or alsabar.channel
+    alsabar.step = args.step or alsabar.step
+    alsabar.colors = args.colors or alsabar.colors
+    alsabar.notifications = args.notifications or alsabar.notifications
+
+    alsabar.bar = awful.widget.progressbar()
+
+    alsabar.bar:set_background_color(alsabar.colors.background)
+    alsabar.bar:set_color(alsabar.colors.unmute)
+    alsabar.tooltip = awful.tooltip({ objects = { alsabar.bar } })
+    alsabar.bar:set_width(width)
+    alsabar.bar:set_height(height)
+    alsabar.bar:set_ticks(ticks)
+    alsabar.bar:set_ticks_size(ticks_size)
+    alsabar.bar:set_vertical(vertical)
+
+    function alsabar.update()
+        -- Get mixer control contents
+        local f = io.popen("amixer get " .. alsabar.channel)
+        local mixer = f:read("*all")
+        f:close()
+
+        -- Capture mixer control state:          [5%] ... ... [on]
+        local volu, mute = string.match(mixer, "([%d]+)%%.*%[([%l]*)")
+
+        if volu == nil then
+            volu = 0
+            mute = "off"
+        end
+
+        alsabar._current_level = tonumber(volu) / 100
+        alsabar.bar:set_value(alsabar._current_level)
+
+        if not mute and tonumber(volu) == 0 or mute == "off"
+        then
+            alsabar._muted = true
+            alsabar.tooltip:set_text (" [Muted] ")
+            alsabar.bar:set_color(alsabar.colors.mute)
+        else
+            alsabar._muted = false
+            alsabar.tooltip:set_text(string.format(" %s:%s ", alsabar.channel, volu))
+            alsabar.bar:set_color(alsabar.colors.unmute)
+        end
+
+        volume_now = {}
+        volume_now.level = tonumber(volu)
+        volume_now.status = mute
+        settings()
+    end
+
+    newtimer("alsabar", timeout, alsabar.update)
+
+    alsabar.bar:buttons (awful.util.table.join (
+          awful.button ({}, 1, function()
+            awful.util.spawn(alsabar.mixer)
+          end),
+          awful.button ({}, 3, function()
+            awful.util.spawn(string.format("amixer set %s toggle", alsabar.channel))
+            alsabar.update()
+          end),
+          awful.button ({}, 4, function()
+            awful.util.spawn(string.format("amixer set %s %s+", alsabar.channel, alsabar.step))
+            alsabar.update()
+          end),
+          awful.button ({}, 5, function()
+            awful.util.spawn(string.format("amixer set %s %s-", alsabar.channel, alsabar.step))
+            alsabar.update()
+          end)
+    ))
+
+    return alsabar
+end
+
+return setmetatable(alsabar, { __call = function(_, ...) return worker(...) end })
diff --git a/widgets/bat.lua b/widgets/bat.lua
new file mode 100644 (file)
index 0000000..48a14fb
--- /dev/null
@@ -0,0 +1,139 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2010-2012, Peter Hofmann              
+                                                  
+--]]
+
+local newtimer     = require("lain.helpers").newtimer
+local first_line   = require("lain.helpers").first_line
+
+local naughty      = require("naughty")
+local wibox        = require("wibox")
+
+local math         = { floor  = math.floor }
+local string       = { format = string.format }
+local tonumber     = tonumber
+
+local setmetatable = setmetatable
+
+-- Battery infos
+-- lain.widgets.bat
+local bat = {}
+
+local function worker(args)
+    local args = args or {}
+    local timeout = args.timeout or 30
+    local battery = args.battery or "BAT0"
+    local notify = args.notify or "on"
+    local settings = args.settings or function() end
+
+    bat.widget = wibox.widget.textbox('')
+
+    function update()
+        bat_now = {
+            status = "Not present",
+            perc   = "N/A",
+            time   = "N/A",
+            watt   = "N/A"
+        }
+
+        local bstr  = "/sys/class/power_supply/" .. battery
+
+        local present = first_line(bstr .. "/present")
+
+        if present == "1"
+        then
+            local rate  = first_line(bstr .. "/power_now") or
+                          first_line(bstr .. "/current_now")
+
+            local ratev = first_line(bstr .. "/voltage_now")
+
+            local rem   = first_line(bstr .. "/energy_now") or
+                          first_line(bstr .. "/charge_now")
+
+            local tot   = first_line(bstr .. "/energy_full") or
+                          first_line(bstr .. "/charge_full")
+
+            bat_now.status = first_line(bstr .. "/status") or "N/A"
+
+            rate  = tonumber(rate)
+            ratev = tonumber(ratev)
+            rem   = tonumber(rem)
+            tot   = tonumber(tot)
+
+            local time_rat = 0
+            if bat_now.status == "Charging"
+            then
+                time_rat = (tot - rem) / rate
+            elseif bat_now.status == "Discharging"
+            then
+                time_rat = rem / rate
+            end
+
+            local hrs = math.floor(time_rat)
+            if hrs < 0 then hrs = 0 elseif hrs > 23 then hrs = 23 end
+
+            local min = math.floor((time_rat - hrs) * 60)
+            if min < 0 then min = 0 elseif min > 59 then min = 59 end
+
+            bat_now.time = string.format("%02d:%02d", hrs, min)
+
+            local perc = (rem / tot) * 100
+            if perc <= 100 then
+                bat_now.perc = string.format("%d", perc)
+            elseif perc > 100 then
+                bat_now.perc = "100"
+            elseif perc < 0 then
+                bat_now.perc = "0"
+            end
+
+            if rate ~= nil and ratev ~= nil then
+                bat_now.watt = string.format("%.2fW", (rate * ratev) / 1e12)
+            else
+                bat_now.watt = "N/A"
+            end
+
+            -- notifications for low and critical states
+            if bat_now.status == "Discharging" and notify == "on"
+            then
+                if tonumber(bat_now.perc) <= 5
+                then
+                    bat.id = naughty.notify({
+                        text = "shutdown imminent",
+                        title = "battery nearly exhausted",
+                        position = "top_right",
+                        timeout = 15,
+                        fg="#000000",
+                        bg="#ffffff",
+                        ontop = true,
+                        replaces_id = bat.id
+                    }).id
+                elseif tonumber(bat_now.perc) <= 15
+                then
+                    bat.id = naughty.notify({
+                        text = "plug the cable",
+                        title = "battery low",
+                        position = "top_right",
+                        timeout = 15,
+                        fg="#202020",
+                        bg="#cdcdcd",
+                        ontop = true,
+                        replaces_id = bat.id
+                    }).id
+                end
+            end
+        end
+
+        widget = bat.widget
+        settings()
+    end
+
+    newtimer("bat", timeout, update)
+
+    return bat.widget
+end
+
+return setmetatable(bat, { __call = function(_, ...) return worker(...) end })
diff --git a/widgets/borderbox.lua b/widgets/borderbox.lua
new file mode 100644 (file)
index 0000000..c251ea8
--- /dev/null
@@ -0,0 +1,61 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2010-2012, Peter Hofmann              
+                                                  
+--]]
+
+local wibox        = require("awful.wibox")
+local setmetatable = setmetatable
+
+-- Creates a thin wibox at a position relative to another wibox
+-- lain.widgets.borderbox
+local borderbox = {}
+
+local function worker(relbox, s, args)
+    local where = args.position or 'top'
+    local color = args.color or '#FFFFFF'
+    local size = args.size or 1
+    local box = nil
+    local wiboxarg = {
+        position = nil,
+        bg = color
+    }
+
+    if where == 'top'
+    then
+        wiboxarg.width = relbox.width
+        wiboxarg.height = size
+        box = wibox(wiboxarg)
+        box.x = relbox.x
+        box.y = relbox.y - size
+    elseif where == 'bottom'
+    then
+        wiboxarg.width = relbox.width
+        wiboxarg.height = size
+        box = wibox(wiboxarg)
+        box.x = relbox.x
+        box.y = relbox.y + relbox.height
+    elseif where == 'left'
+    then
+        wiboxarg.width = size
+        wiboxarg.height = relbox.height
+        box = wibox(wiboxarg)
+        box.x = relbox.x - size
+        box.y = relbox.y
+    elseif where == 'right'
+    then
+        wiboxarg.width = size
+        wiboxarg.height = relbox.height
+        box = wibox(wiboxarg)
+        box.x = relbox.x + relbox.width
+        box.y = relbox.y
+    end
+
+    box.screen = s
+    return box
+end
+
+return setmetatable(borderbox, { __call = function(_, ...) return worker(...) end })
diff --git a/widgets/calendar.lua b/widgets/calendar.lua
new file mode 100644 (file)
index 0000000..f684fd7
--- /dev/null
@@ -0,0 +1,124 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013, Luke Bonham                     
+                                                  
+--]]
+
+local icons_dir    = require("lain.helpers").icons_dir
+
+local awful        = require("awful")
+local beautiful    = require("beautiful")
+local naughty      = require("naughty")
+
+local io           = io
+local os           = { date = os.date }
+local tonumber     = tonumber
+
+local setmetatable = setmetatable
+
+-- Calendar notification
+-- lain.widgets.calendar
+local calendar = {}
+local cal_notification = nil
+
+function calendar:hide()
+    if cal_notification ~= nil then
+        naughty.destroy(cal_notification)
+        cal_notification = nil
+    end
+end
+
+function calendar:show(t_out, inc_offset)
+    calendar:hide()
+
+    local offs = inc_offset or 0
+    local tims = t_out or 0
+    local f, c_text
+    local today = tonumber(os.date('%d'))
+    local init_t = '/usr/bin/cal | sed -r -e "s/(^| )( '
+    -- let's take font only, font size is set in calendar table
+    local font = beautiful.font:sub(beautiful.font:find(""),
+                 beautiful.font:find(" "))
+
+    if offs == 0
+    then -- current month showing, today highlighted
+        if today >= 10
+        then
+           init_t = '/usr/bin/cal | sed -r -e "s/(^| )('
+        end
+
+        calendar.offset = 0
+        calendar.notify_icon = calendar.icons .. today .. ".png"
+
+        -- bg and fg inverted to highlight today
+        f = io.popen( init_t .. today ..
+                      ')($| )/\\1<b><span foreground=\\"'
+                      .. calendar.bg ..
+                      '\\" background=\\"'
+                      .. calendar.fg ..
+                      '\\">\\2<\\/span><\\/b>\\3/"' )
+
+    else -- no current month showing, no day to highlight
+       local month = tonumber(os.date('%m'))
+       local year = tonumber(os.date('%Y'))
+
+       calendar.offset = calendar.offset + offs
+       month = month + calendar.offset
+
+       if month > 12 then
+           month = month % 12
+           year = year + 1
+           if month <= 0 then
+               month = 12
+           end
+       elseif month < 1 then
+           month = month + 12
+           year = year - 1
+           if month <= 0 then
+               month = 1
+           end
+       end
+
+       calendar.notify_icon = nil
+
+       f = io.popen('/usr/bin/cal ' .. month .. ' ' .. year)
+    end
+
+    c_text = "<tt><span font='" .. font .. " "
+             .. calendar.font_size .. "'><b>"
+             .. f:read() .. "</b>\n\n"
+             .. f:read() .. "\n"
+             .. f:read("*all"):gsub("\n*$", "")
+             .. "</span></tt>"
+    f:close()
+
+    cal_notification = naughty.notify({ text = c_text,
+                                        icon = calendar.notify_icon,
+                                        position = calendar.position,
+                                        fg = calendar.fg,
+                                        bg = calendar.bg,
+                                        timeout = tims })
+end
+
+function calendar:attach(widget, args)
+    local args = args or {}
+    calendar.icons = args.icons or icons_dir .. "cal/white/"
+    calendar.font_size = tonumber(args.font_size) or 11
+    calendar.fg = args.fg or beautiful.fg_normal or "#FFFFFF"
+    calendar.bg = args.bg or beautiful.bg_normal or "#FFFFFF"
+    calendar.position = args.position or "top_right"
+
+    calendar.offset = 0
+    calendar.notify_icon = nil
+
+    widget:connect_signal("mouse::enter", function () calendar:show() end)
+    widget:connect_signal("mouse::leave", function () calendar:hide() end)
+    widget:buttons(awful.util.table.join( awful.button({ }, 1, function ()
+                                              calendar:show(0, -1) end),
+                                          awful.button({ }, 3, function ()
+                                              calendar:show(0, 1) end) ))
+end
+
+return setmetatable(calendar, { __call = function(_, ...) return create(...) end })
diff --git a/widgets/contrib/brightness.lua b/widgets/contrib/brightness.lua
new file mode 100644 (file)
index 0000000..04b8d2b
--- /dev/null
@@ -0,0 +1,42 @@
+
+--[[
+                                                   
+     Licensed under GNU General Public License v2  
+      * (c) 2013, yawnt <yawn.localhost@gmail.com> 
+                                                   
+--]]
+
+local newtimer        = require("lain.helpers").newtimer
+
+local wibox           = require("wibox")
+local io              = { popen  = io.popen }
+
+local setmetatable    = setmetatable
+
+-- Brightness level
+-- lain.widgets.contrib.brightness
+local brightness = {}
+
+local function worker(args)
+    local args      = args or {}
+    local backlight = args.backlight or "acpi_video0"
+    local timeout   = args.timeout or 5
+    local settings  = args.settings or function() end
+
+    brightness.widget = wibox.widget.textbox('')
+
+    function brightness.update()
+        local f = assert(io.popen('cat /sys/class/backlight/' .. backlight .. "/brightness"))
+        brightness_now = f:read("*a")
+        f:close()
+
+        widget = brightness.widget
+        settings()
+    end
+
+    newtimer("brightness", timeout, brightness.update)
+
+    return setmetatable(brightness, { __index = brightness.widget })
+end
+
+return setmetatable(brightness, { __call = function(_, ...) return worker(...) end })
diff --git a/widgets/contrib/init.lua b/widgets/contrib/init.lua
new file mode 100644 (file)
index 0000000..9a9fa63
--- /dev/null
@@ -0,0 +1,20 @@
+
+--[[
+                                                   
+     Lain                                          
+     Layouts, widgets and utilities for Awesome WM 
+                                                   
+     Users contributed widgets section             
+                                                   
+     Licensed under GNU General Public License v2  
+      * (c) 2013,      Luke Bonham                 
+      * (c) 2010-2012, Peter Hofmann               
+                                                   
+--]]
+
+local wrequire     = require("lain.helpers").wrequire
+local setmetatable = setmetatable
+
+local widgets = { _NAME = "lain.widgets.contrib" }
+
+return setmetatable(widgets, { __index = wrequire })
diff --git a/widgets/contrib/task.lua b/widgets/contrib/task.lua
new file mode 100644 (file)
index 0000000..a6c9f31
--- /dev/null
@@ -0,0 +1,133 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013, Luke Bonham                     
+      * (c) 2013, Jan Xie                         
+                                                  
+--]]
+
+local icons_dir    = require("lain.helpers").icons_dir
+
+local awful        = require("awful")
+local beautiful    = require("beautiful")
+local naughty      = require("naughty")
+
+local io           = io
+local string       = { len = string.len }
+local tonumber     = tonumber
+
+local setmetatable = setmetatable
+
+-- Taskwarrior notification
+-- lain.widgets.task
+local task = {}
+
+local task_notification = nil
+
+function task:hide()
+    if task_notification ~= nil then
+        naughty.destroy(task_notification)
+        task_notification = nil
+    end
+end
+
+function task:show()
+    task:hide()
+
+    local f, c_text
+
+    f = io.popen('task')
+    c_text = "<span font='"
+             .. task.font .. " "
+             .. task.font_size .. "'>"
+             .. f:read("*all"):gsub("\n*$", "")
+             .. "</span>"
+    f:close()
+
+    task_notification = naughty.notify({ title = "[task next]",
+                                         text = c_text,
+                                         icon = task.notify_icon,
+                                         position = task.position,
+                                         fg = task.fg,
+                                         bg = task.bg,
+                                         timeout = task.timeout })
+end
+
+function task:prompt_add()
+  awful.prompt.run({ prompt = "Add task: " },
+      mypromptbox[mouse.screen].widget,
+      function (...)
+          local f = io.popen("task add " .. ...)
+          c_text = "\n<span font='" 
+                   .. task.font .. " "
+                   .. task.font_size .. "'>"
+                   .. f:read("*all")
+                   .. "</span>"
+          f:close()
+
+          naughty.notify({
+              text     = c_text,
+              icon     = task.notify_icon,
+              position = task.position,
+              fg       = task.fg,
+              bg       = task.bg,
+              timeout  = task.timeout
+          })
+      end,
+      nil,
+      awful.util.getdir("cache") .. "/history_task_add")
+end
+
+function task:prompt_search()
+  awful.prompt.run({ prompt = "Search task: " },
+      mypromptbox[mouse.screen].widget,
+      function (...)
+          local f = io.popen("task " .. ...)
+          c_text = f:read("*all"):gsub(" \n*$", "")
+          f:close()
+
+          if string.len(c_text) == 0
+          then
+              c_text = "No results found."
+          else
+              c_text = "<span font='"
+                       .. task.font .. " "
+                       .. task.font_size .. "'>"
+                       .. c_text 
+                       .. "</span>"
+          end
+
+          naughty.notify({
+              title    = "[task next " .. ... .. "]",
+              text     = c_text,
+              icon     = task.notify_icon,
+              position = task.position,
+              fg       = task.fg,
+              bg       = task.bg,
+              timeout  = task.timeout
+          })
+      end,
+      nil,
+      awful.util.getdir("cache") .. "/history_task")
+end
+
+function task:attach(widget, args)
+    local args     = args or {}
+
+    task.font_size = tonumber(args.font_size) or 12
+    task.font      = beautiful.font:sub(beautiful.font:find(""),
+                     beautiful.font:find(" "))
+    task.fg        = args.fg or beautiful.fg_normal or "#FFFFFF"
+    task.bg        = args.bg or beautiful.bg_normal or "#FFFFFF"
+    task.position  = args.position or "top_right"
+    task.timeout   = args.timeout or 7
+
+    task.notify_icon = icons_dir .. "/taskwarrior/task.png"
+    task.notify_icon_small = icons_dir .. "/taskwarrior/tasksmall.png"
+
+    widget:connect_signal("mouse::enter", function () task:show() end)
+    widget:connect_signal("mouse::leave", function () task:hide() end)
+end
+
+return setmetatable(task, { __call = function(_, ...) return create(...) end })
diff --git a/widgets/contrib/tpbat/init.lua b/widgets/contrib/tpbat/init.lua
new file mode 100644 (file)
index 0000000..dbf90aa
--- /dev/null
@@ -0,0 +1,166 @@
+
+--[[
+                                                               
+     tpbat.lua                                                 
+     Battery status widget for ThinkPad laptops that use SMAPI 
+     lain.widgets.contrib.tpbat                                
+                                                               
+     More on tp_smapi: http://www.thinkwiki.org/wiki/Tp_smapi  
+                                                               
+     Licensed under GNU General Public License v2              
+      * (c) 2013,      Conor Heine                             
+      * (c) 2013,      Luke Bonham                             
+      * (c) 2010-2012, Peter Hofmann                           
+                                                               
+--]]
+
+local debug        = { getinfo = debug.getinfo }
+local newtimer     = require("lain.helpers").newtimer
+local first_line   = require("lain.helpers").first_line
+local beautiful    = require("beautiful")
+local naughty      = require("naughty")
+local wibox        = require("wibox")
+
+local string       = { format = string.format }
+local math         = { floor = math.floor }
+local tostring     = tostring
+local setmetatable = setmetatable
+
+package.path       = debug.getinfo(1,"S").source:match[[^@?(.*[\/])[^\/]-$]] .. "?.lua;" .. package.path
+local smapi        = require("smapi")
+
+-- ThinkPad SMAPI-enabled battery info widget
+-- lain.widgets.contrib.tpbat
+local tpbat = { }
+local tpbat_notification = nil
+
+function tpbat:hide()
+    if tpbat_notification ~= nil
+    then
+        naughty.destroy(tpbat_notification)
+        tpbat_notification = nil
+    end
+end
+
+function tpbat:show(t_out)
+    tpbat:hide()
+
+    local bat   = self.bat
+    local t_out = t_out or 0
+
+    if bat == nil or not bat:installed() then return end
+
+    local mfgr   = bat:get('manufacturer') or "no_mfgr"
+    local model  = bat:get('model') or "no_model"
+    local chem   = bat:get('chemistry') or "no_chem"
+    local status = bat:get('state') or "nil"
+    local time   = bat:remaining_time()
+    local msg    = "\t"
+
+    if status ~= "idle" and status ~= "nil"
+    then
+        if time == "N/A"
+        then
+            msg = "...Calculating time remaining..."
+        else
+            msg = time .. (status == "charging" and " until charged" or " remaining")
+        end
+    else
+        msg = "On AC Power"
+    end
+
+    local str = string.format("%s : %s %s (%s)\n", bat.name, mfgr, model, chem)
+                .. string.format("\n%s \t\t\t %s", status:upper(), msg)
+
+    tpbat_notification = naughty.notify({
+        preset = { fg = beautiful.fg_normal },
+        text = str,
+        timeout = t_out
+    })
+end
+
+function tpbat.register(args)
+    local args = args or {}
+    local timeout = args.timeout or 30
+    local battery = args.battery or "BAT0"
+    local settings = args.settings or function() end
+
+    tpbat.bat = smapi:battery(battery) -- Create a new battery
+    local bat = tpbat.bat
+
+    tpbat.widget = wibox.widget.textbox('')
+
+    if bat:get('state') == nil
+    then
+        local n = naughty.notify({
+            title = "SMAPI Battery Warning: Unable to read battery state!",
+            text = "This widget is intended for ThinkPads. Is tp_smapi installed? Check your configs & paths.",
+            position = "top_right",
+            timeout = 15,
+            fg="#202020",
+            bg="#cdcdcd",
+            ontop = true
+        })
+    end
+
+    function update()
+        bat_now = {
+            status = "Not present",
+            perc   = "N/A",
+            time   = "N/A",
+            watt   = "N/A"
+        }
+
+        if bat:installed()
+        then
+            bat_now.status = bat:status() or "N/A"
+            bat_now.perc   = bat:percent()
+            bat_now.time   = bat:remaining_time()
+            -- bat_now.watt = string.format("%.2fW", (VOLTS * AMPS) / 1e12)
+
+            -- notifications for low and critical states (when discharging)
+            if bat_now.status == "discharging"
+            then
+                if bat_now.perc <= 5
+                then
+                    tpbat.id = naughty.notify({
+                        text = "shutdown imminent",
+                        title = "battery nearly exhausted",
+                        position = "top_right",
+                        timeout = 15,
+                        fg="#000000",
+                        bg="#ffffff",
+                        ontop = true,
+                        replaces_id = tpbat.id
+                    }).id
+                elseif bat_now.perc <= 15
+                then
+                    tpbat.id = naughty.notify({
+                        text = "plug the cable",
+                        title = "battery low",
+                        position = "top_right",
+                        timeout = 15,
+                        fg="#202020",
+                        bg="#cdcdcd",
+                        ontop = true,
+                        replaces_id = tpbat.id
+                    }).id
+                end
+            end
+
+            bat_now.perc = tostring(bat_now.perc)
+        end
+
+        widget = tpbat.widget
+        settings()
+    end
+
+    newtimer("tpbat", timeout, update)
+
+    widget:connect_signal('mouse::enter', function () tpbat:show() end)
+    widget:connect_signal('mouse::leave', function () tpbat:hide() end)
+
+    return tpbat.widget
+end
+
+return setmetatable(tpbat, { __call = function(_, ...) return tpbat.register(...) end })
diff --git a/widgets/contrib/tpbat/smapi.lua b/widgets/contrib/tpbat/smapi.lua
new file mode 100644 (file)
index 0000000..862d4cd
--- /dev/null
@@ -0,0 +1,102 @@
+
+--[[
+                                                  
+     smapi.lua                                    
+     Interface with thinkpad battery information  
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013, Conor Heine                     
+                                                  
+--]]
+
+local first_line   = require("lain.helpers").first_line
+
+local string       = { format = string.format }
+local tonumber     = tonumber
+local setmetatable = setmetatable
+
+local smapi = {}
+
+local apipath = "/sys/devices/platform/smapi"
+
+-- Most are readable values, but some can be written to (not implemented, yet?)
+local readable = {
+    barcoding                  = true,
+    charging_max_current       = true,
+    charging_max_voltage       = true,
+    chemistry                  = true,
+    current_avg                = true,
+    current_now                = true,
+    cycle_count                = true,
+    design_capacity            = true,
+    design_voltage             = true,
+    dump                       = true,
+    first_use_date             = true,
+    force_discharge            = false,
+    group0_voltage             = true,
+    group1_voltage             = true,
+    group2_voltage             = true,
+    group3_voltage             = true,
+    inhibit_charge_minutes     = false,
+    installed                  = true,
+    last_full_capacity         = true,
+    manufacture_date           = true,
+    manufacturer               = true,
+    model                      = true,
+    power_avg                  = true,
+    power_now                  = true,
+    remaining_capacity         = true,
+    remaining_charging_time    = true,
+    remaining_percent          = true,
+    remaining_percent_error    = true,
+    remaining_running_time     = true,
+    remaining_running_time_now = true,
+    serial                     = true,
+    start_charge_thresh        = false,
+    state                      = true,
+    stop_charge_thresh         = false,
+    temperature                = true,
+    voltage                    = true,
+}
+
+function smapi:battery(name)
+    local bat = {}
+
+    bat.name = name
+    bat.path = apipath .. "/" .. name
+
+    function bat:get(item)
+        return self.path ~= nil and readable[item] and first_line(self.path .. "/" .. item) or nil
+    end
+
+    function bat:installed()
+        return self:get("installed") == "1"
+    end
+
+    function bat:status()
+        return self:get('state')
+    end
+
+    -- Remaining time can either be time until battery dies or time until charging completes
+    function bat:remaining_time()
+        local time_val = bat_now.status == 'discharging' and 'remaining_running_time' or 'remaining_charging_time'
+        local mins_left = self:get(time_val)
+
+        if mins_left:find("^%d+") == nil
+        then
+            return "N/A"
+        end
+
+        local hrs = mins_left / 60
+        local min = mins_left % 60
+        return string.format("%02d:%02d", hrs, min)
+    end
+
+    function bat:percent()
+        return tonumber(self:get("remaining_percent"))
+    end
+
+    return setmetatable(bat, {__metatable = false, __newindex = false})
+end
+
+return smapi
diff --git a/widgets/cpu.lua b/widgets/cpu.lua
new file mode 100644 (file)
index 0000000..0b21edc
--- /dev/null
@@ -0,0 +1,77 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2010-2012, Peter Hofmann              
+                                                  
+--]]
+
+local first_line   = require("lain.helpers").first_line
+local newtimer     = require("lain.helpers").newtimer
+
+local wibox        = require("wibox")
+
+local math         = { ceil   = math.ceil }
+local string       = { format = string.format,
+                       gmatch = string.gmatch }
+local tostring     = tostring
+
+local setmetatable = setmetatable
+
+-- CPU usage
+-- lain.widgets.cpu
+local cpu = {
+    last_total = 0,
+    last_active = 0
+}
+
+local function worker(args)
+    local args     = args or {}
+    local timeout  = args.timeout or 5
+    local settings = args.settings or function() end
+
+    cpu.widget = wibox.widget.textbox('')
+
+    function update()
+        -- Read the amount of time the CPUs have spent performing
+        -- different kinds of work. Read the first line of /proc/stat
+        -- which is the sum of all CPUs.
+        local times = first_line("/proc/stat")
+        local at = 1
+        local idle = 0
+        local total = 0
+        for field in string.gmatch(times, "[%s]+([^%s]+)")
+        do
+            -- 3 = idle, 4 = ioWait. Essentially, the CPUs have done
+            -- nothing during these times.
+            if at == 3 or at == 4
+            then
+                idle = idle + field
+            end
+            total = total + field
+            at = at + 1
+        end
+        local active = total - idle
+
+        -- Read current data and calculate relative values.
+        local dactive = active - cpu.last_active
+        local dtotal = total - cpu.last_total
+
+        cpu_now = {}
+        cpu_now.usage = tostring(math.ceil((dactive / dtotal) * 100))
+
+        widget = cpu.widget
+        settings()
+
+        -- Save current data for the next run.
+        cpu.last_active = active
+        cpu.last_total = total
+    end
+
+    newtimer("cpu", timeout, update)
+
+    return cpu.widget
+end
+
+return setmetatable(cpu, { __call = function(_, ...) return worker(...) end })
diff --git a/widgets/fs.lua b/widgets/fs.lua
new file mode 100644 (file)
index 0000000..7406e05
--- /dev/null
@@ -0,0 +1,120 @@
+
+--[[
+                                                      
+     Licensed under GNU General Public License v2     
+      * (c) 2013, Luke Bonham                         
+      * (c) 2010, Adrian C.      <anrxc@sysphere.org> 
+      * (c) 2009, Lucas de Vries <lucas@glacicle.com> 
+                                                      
+--]]
+
+local helpers      = require("lain.helpers")
+
+local beautiful    = require("beautiful")
+local wibox        = require("wibox")
+local naughty      = require("naughty")
+
+local io           = io
+local pairs        = pairs
+local string       = { match  = string.match,
+                       format = string.format }
+local tonumber     = tonumber
+
+local setmetatable = setmetatable
+
+-- File system disk space usage
+-- lain.widgets.fs
+local fs = {}
+
+local notification  = nil
+fs_notification_preset = { fg = beautiful.fg_normal }
+
+function fs:hide()
+    if notification ~= nil then
+        naughty.destroy(notification)
+        notification = nil
+    end
+end
+
+function fs:show(t_out)
+    fs:hide()
+
+    local f = io.popen(helpers.scripts_dir .. "dfs")
+    ws = f:read("*all"):gsub("\n*$", "")
+    f:close()
+
+    notification = naughty.notify({
+        preset = fs_notification_preset,
+        text = ws,
+       timeout = t_out
+    })
+end
+
+-- Units definitions
+local unit = { ["mb"] = 1024, ["gb"] = 1024^2 }
+
+local function worker(args)
+    local args      = args or {}
+    local timeout   = args.timeout or 600
+    local partition = args.partition or "/"
+    local settings  = args.settings or function() end
+
+    fs.widget = wibox.widget.textbox('')
+
+    helpers.set_map("fs", false)
+
+    function update()
+        fs_info = {}
+        fs_now  = {}
+
+        local f = io.popen("LC_ALL=C df -kP")
+
+        for line in f:lines() do -- Match: (size) (used)(avail)(use%) (mount)
+            local s     = string.match(line, "^.-[%s]([%d]+)")
+            local u,a,p = string.match(line, "([%d]+)[%D]+([%d]+)[%D]+([%d]+)%%")
+            local m     = string.match(line, "%%[%s]([%p%w]+)")
+
+            if u and m then -- Handle 1st line and broken regexp
+                fs_info[m .. " size_mb"]  = string.format("%.1f", tonumber(s) / unit["mb"])
+                fs_info[m .. " size_gb"]  = string.format("%.1f", tonumber(s) / unit["gb"])
+                fs_info[m .. " used_p"]   = tonumber(p)
+                fs_info[m .. " avail_p"]  = 100 - tonumber(p)
+            end
+        end
+
+        f:close()
+
+        -- chosen partition easy stuff
+        -- you can however check whatever partition else
+        fs_now.used      = tonumber(fs_info[partition .. " used_p"])  or 0
+        fs_now.available = tonumber(fs_info[partition .. " avail_p"]) or 0
+        fs_now.size_mb   = tonumber(fs_info[partition .. " size_mb"]) or 0
+        fs_now.size_gb   = tonumber(fs_info[partition .. " size_gb"]) or 0
+
+        widget = fs.widget
+        settings()
+
+        if fs_now.used >= 99 and not helpers.get_map("fs")
+        then
+            naughty.notify({
+                title = "warning",
+                text = partition .. " ran out!\nmake some room",
+                timeout = 8,
+                fg = "#000000",
+                bg = "#FFFFFF"
+            })
+            helpers.set_map("fs", true)
+        else
+            helpers.set_map("fs", false)
+        end
+    end
+
+    helpers.newtimer(partition, timeout, update)
+
+    widget:connect_signal('mouse::enter', function () fs:show(0) end)
+    widget:connect_signal('mouse::leave', function () fs:hide() end)
+
+    return setmetatable(fs, { __index = fs.widget })
+end
+
+return setmetatable(fs, { __call = function(_, ...) return worker(...) end })
diff --git a/widgets/imap.lua b/widgets/imap.lua
new file mode 100644 (file)
index 0000000..39518bd
--- /dev/null
@@ -0,0 +1,87 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013, Luke Bonham                     
+                                                  
+--]]
+
+local helpers      = require("lain.helpers")
+
+local naughty      = require("naughty")
+local wibox        = require("wibox")
+
+local io           = { popen  = io.popen }
+local string       = { format = string.format,
+                       gsub   = string.gsub }
+local tonumber     = tonumber
+
+local setmetatable = setmetatable
+
+-- Mail IMAP check
+-- lain.widgets.imap
+local imap = {}
+
+local function worker(args)
+    local args     = args or {}
+
+    local server   = args.server
+    local mail     = args.mail
+    local password = args.password
+
+    local port     = args.port or 993
+    local timeout  = args.timeout or 60
+    local is_plain = args.is_plain or false
+    local settings = args.settings or function() end
+
+    local head_command  = "curl --connect-timeout 1 -fsm 3"
+    local request = "-X 'SEARCH (UNSEEN)'"
+
+    helpers.set_map(mail, 0)
+
+    if not is_plain
+    then
+        local f = io.popen(password)
+        password = f:read("*all"):gsub("\n", "")
+        f:close()
+    end
+
+    imap.widget = wibox.widget.textbox('')
+
+    function update()
+        mail_notification_preset = {
+            icon     = helpers.icons_dir .. "mail.png",
+            position = "top_left"
+        }
+
+        curl = string.format("%s --url imaps://%s:%s/INBOX -u %s:%s %s -k",
+               head_command, server, port, mail, password, request)
+
+        f = io.popen(curl)
+        ws = f:read("*all")
+        f:close()
+
+        _, mailcount = string.gsub(ws, "%d+", "")
+        _ = nil
+
+        widget = imap.widget
+        settings()
+
+        if mailcount > helpers.get_map(mail) and mailcount >= 1
+        then
+            if mailcount == 1 then
+                nt = mail .. " has one new message"
+            else
+                nt = mail .. " has <b>" .. mailcount .. "</b> new messages"
+            end
+            naughty.notify({ preset = mail_notification_preset, text = nt })
+        end
+
+        helpers.set_map(mail, mailcount)
+    end
+
+    helpers.newtimer(mail, timeout, update, true)
+    return imap.widget
+end
+
+return setmetatable(imap, { __call = function(_, ...) return worker(...) end })
diff --git a/widgets/init.lua b/widgets/init.lua
new file mode 100644 (file)
index 0000000..0e863ba
--- /dev/null
@@ -0,0 +1,20 @@
+
+--[[
+                                                   
+     Lain                                          
+     Layouts, widgets and utilities for Awesome WM 
+                                                   
+     Widgets section                               
+                                                   
+     Licensed under GNU General Public License v2  
+      * (c) 2013,      Luke Bonham                 
+      * (c) 2010-2012, Peter Hofmann               
+                                                   
+--]]
+
+local wrequire     = require("lain.helpers").wrequire
+local setmetatable = setmetatable
+
+local widgets = { _NAME = "lain.widgets" }
+
+return setmetatable(widgets, { __index = wrequire })
diff --git a/widgets/maildir.lua b/widgets/maildir.lua
new file mode 100644 (file)
index 0000000..5cb6840
--- /dev/null
@@ -0,0 +1,94 @@
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2010-2012, Peter Hofmann              
+                                                  
+--]]
+
+local newtimer        = require("lain.helpers").newtimer
+
+local wibox           = require("wibox")
+
+local io              = io
+local os              = { getenv = os.getenv }
+local pairs           = pairs
+local string          = { len    = string.len,
+                          match  = string.match }
+local table           = { sort   = table.sort }
+
+local setmetatable    = setmetatable
+
+-- Maildir check
+-- lain.widgets.maildir
+local maildir = {}
+
+local function worker(args)
+    local args         = args or {}
+    local timeout      = args.timeout or 60
+    local mailpath     = args.mailpath or os.getenv("HOME") .. "/Mail"
+    local ignore_boxes = args.ignore_boxes or {}
+    local settings     = args.settings or function() end
+
+    maildir.widget = wibox.widget.textbox('')
+
+    function update()
+        -- Find pathes to mailboxes.
+        local p = io.popen("find " .. mailpath ..
+                           " -mindepth 1 -maxdepth 1 -type d" ..
+                           " -not -name .git")
+        local boxes = {}
+        repeat
+            line = p:read("*l")
+            if line ~= nil
+            then
+                -- Find all files in the "new" subdirectory. For each
+                -- file, print a single character (no newline). Don't
+                -- match files that begin with a dot.
+                -- Afterwards the length of this string is the number of
+                -- new mails in that box.
+                local np = io.popen("find " .. line ..
+                                    "/new -mindepth 1 -type f " ..
+                                    "-not -name '.*' -printf a")
+                local mailstring = np:read("*all")
+
+                -- Strip off leading mailpath.
+                local box = string.match(line, mailpath .. "/*([^/]+)")
+                local nummails = string.len(mailstring)
+                if nummails > 0
+                then
+                    boxes[box] = nummails
+                end
+            end
+        until line == nil
+
+        table.sort(boxes)
+
+        newmail = "no mail"
+
+        local count = 0
+        for box, number in pairs(boxes)
+        do
+            count = count + 1
+            -- Add this box only if it's not to be ignored.
+            if not util.element_in_table(box, ignore_boxes)
+            then
+                if newmail == ""
+                then
+                    newmail = box .. "(" .. number .. ")"
+                else
+                    newmail = newmail .. ", " ..
+                              box .. "(" .. number .. ")"
+                end
+            end
+        end
+
+        widget = maildir.widget
+        settings()
+    end
+
+    newtimer(mailpath, timeout, update, true)
+    return maildir.widget
+end
+
+return setmetatable(maildir, { __call = function(_, ...) return worker(...) end })
diff --git a/widgets/mem.lua b/widgets/mem.lua
new file mode 100644 (file)
index 0000000..986fa76
--- /dev/null
@@ -0,0 +1,61 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2010-2012, Peter Hofmann              
+                                                  
+--]]
+
+local newtimer        = require("lain.helpers").newtimer
+
+local wibox           = require("wibox")
+
+local io              = { lines  = io.lines }
+local math            = { floor  = math.floor }
+local string          = { format = string.format,
+                          gmatch = string.gmatch,
+                          len    = string.len }
+
+local setmetatable    = setmetatable
+
+-- Memory usage (ignoring caches)
+-- lain.widgets.mem
+local mem = {}
+
+local function worker(args)
+    local args     = args or {}
+    local timeout  = args.timeout or 3
+    local settings = args.settings or function() end
+
+    mem.widget = wibox.widget.textbox('')
+
+    function update()
+        mem_now = {}
+        for line in io.lines("/proc/meminfo")
+        do
+            for k, v in string.gmatch(line, "([%a]+):[%s]+([%d]+).+")
+            do
+                if     k == "MemTotal"  then mem_now.total = math.floor(v / 1024)
+                elseif k == "MemFree"   then mem_now.free  = math.floor(v / 1024)
+                elseif k == "Buffers"   then mem_now.buf   = math.floor(v / 1024)
+                elseif k == "Cached"    then mem_now.cache = math.floor(v / 1024)
+                elseif k == "SwapTotal" then mem_now.swap  = math.floor(v / 1024)
+                elseif k == "SwapFree"  then mem_now.swapf = math.floor(v / 1024)
+                end
+            end
+        end
+
+        mem_now.used = mem_now.total - (mem_now.free + mem_now.buf + mem_now.cache)
+        mem_now.swapused = mem_now.swap - mem_now.swapf
+
+        widget = mem.widget
+        settings()
+    end
+
+    newtimer("mem", timeout, update)
+
+    return mem.widget
+end
+
+return setmetatable(mem, { __call = function(_, ...) return worker(...) end })
diff --git a/widgets/mpd.lua b/widgets/mpd.lua
new file mode 100644 (file)
index 0000000..1749b57
--- /dev/null
@@ -0,0 +1,109 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013, Luke Bonham                     
+      * (c) 2010, Adrian C. <anrxc@sysphere.org>  
+                                                  
+--]]
+
+local helpers      = require("lain.helpers")
+
+local escape_f     = require("awful.util").escape
+local naughty      = require("naughty")
+local wibox        = require("wibox")
+
+local io           = { popen    = io.popen }
+local os           = { execute  = os.execute,
+                       getenv   = os.getenv }
+local string       = { format   = string.format,
+                       gmatch   = string.gmatch }
+
+local setmetatable = setmetatable
+
+-- MPD infos
+-- lain.widgets.mpd
+local mpd = {}
+
+local function worker(args)
+    local args        = args or {}
+    local timeout     = args.timeout or 2
+    local password    = args.password or ""
+    local host        = args.host or "127.0.0.1"
+    local port        = args.port or "6600"
+    local music_dir   = args.music_dir or os.getenv("HOME") .. "/Music"
+    local cover_size  = args.cover_size or 100
+    local default_art = args.default_art or ""
+    local settings    = args.settings or function() end
+
+    local mpdcover = helpers.scripts_dir .. "mpdcover"
+    local mpdh = "telnet://" .. host .. ":" .. port
+    local echo = "echo 'password " .. password .. "\nstatus\ncurrentsong\nclose'"
+
+    mpd.widget = wibox.widget.textbox('')
+
+    mpd_notification_preset = {
+        title   = "Now playing",
+        timeout = 6
+    }
+
+    helpers.set_map("current mpd track", nil)
+
+    function mpd.update()
+        mpd_now = {
+            state  = "N/A",
+            file   = "N/A",
+            artist = "N/A",
+            title  = "N/A",
+            album  = "N/A",
+            date   = "N/A"
+        }
+
+        local f = io.popen(echo .. " | curl --connect-timeout 1 -fsm 3 " .. mpdh)
+
+        for line in f:lines() do
+            for k, v in string.gmatch(line, "([%w]+):[%s](.*)$") do
+                if     k == "state"  then mpd_now.state  = v
+                elseif k == "file"   then mpd_now.file   = v
+                elseif k == "Artist" then mpd_now.artist = escape_f(v)
+                elseif k == "Title"  then mpd_now.title  = escape_f(v)
+                elseif k == "Album"  then mpd_now.album  = escape_f(v)
+                elseif k == "Date"   then mpd_now.date   = escape_f(v)
+                end
+            end
+        end
+
+        f:close()
+
+        mpd_notification_preset.text = string.format("%s (%s) - %s\n%s", mpd_now.artist,
+                                       mpd_now.album, mpd_now.date, mpd_now.title)
+        widget = mpd.widget
+        settings()
+
+        if mpd_now.state == "play"
+        then
+            if mpd_now.title ~= helpers.get_map("current mpd track")
+            then
+                helpers.set_map("current mpd track", mpd_now.title)
+
+                os.execute(string.format("%s %q %q %d %q", mpdcover, music_dir,
+                           mpd_now.file, cover_size, default_art))
+
+                mpd.id = naughty.notify({
+                    preset = mpd_notification_preset,
+                    icon = "/tmp/mpdcover.png",
+                    replaces_id = mpd.id
+                }).id
+            end
+        elseif mpd_now.state ~= "pause"
+        then
+            helpers.set_map("current mpd track", nil)
+             end
+    end
+
+    helpers.newtimer("mpd", timeout, mpd.update)
+
+    return setmetatable(mpd, { __index = mpd.widget })
+end
+
+return setmetatable(mpd, { __call = function(_, ...) return worker(...) end })
diff --git a/widgets/net.lua b/widgets/net.lua
new file mode 100644 (file)
index 0000000..d79e117
--- /dev/null
@@ -0,0 +1,104 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2010-2012, Peter Hofmann              
+                                                  
+--]]
+
+local helpers      = require("lain.helpers")
+
+local notify_fg    = require("beautiful").fg_focus
+local naughty      = require("naughty")
+local wibox        = require("wibox")
+
+local io           = io
+local tostring     = tostring
+local string       = { format = string.format,
+                       gsub   = string.gsub }
+
+local setmetatable = setmetatable
+
+-- Network infos
+-- lain.widgets.net
+local net = {
+    last_t = 0,
+    last_r = 0
+}
+
+function net.get_device()
+    f = io.popen("ip link show | cut -d' ' -f2,9")
+    ws = f:read("*all")
+    f:close()
+    ws = ws:match("%w+: UP")
+    if ws ~= nil then
+        return ws:gsub(": UP", "")
+    else
+        return ""
+    end
+end
+
+local function worker(args)
+    local args = args or {}
+    local timeout = args.timeout or 2
+    local iface = args.iface or net.get_device()
+    local units = args.units or 1024 --kb
+    local settings = args.settings or function() end
+
+    net.widget = wibox.widget.textbox('')
+
+    helpers.set_map(iface, true)
+
+    function update()
+        net_now = {}
+
+        if iface == "" then iface = net.get_device() end
+
+        net_now.carrier = helpers.first_line('/sys/class/net/' .. iface ..
+                                           '/carrier') or "0"
+        net_now.state = helpers.first_line('/sys/class/net/' .. iface ..
+                                           '/operstate') or "down"
+        local now_t = helpers.first_line('/sys/class/net/' .. iface ..
+                                           '/statistics/tx_bytes') or 0
+        local now_r = helpers.first_line('/sys/class/net/' .. iface ..
+                                           '/statistics/rx_bytes') or 0
+
+        net_now.sent = tostring((now_t - net.last_t) / timeout / units)
+        net_now.sent = string.gsub(string.format('%.1f', net_now.sent), ",", ".")
+
+        net_now.received = tostring((now_r - net.last_r) / timeout / units)
+        net_now.received = string.gsub(string.format('%.1f', net_now.received), ",", ".")
+
+        widget = net.widget
+        settings()
+
+        net.last_t = now_t
+        net.last_r = now_r
+
+        if net_now.carrier ~= "1"
+        then
+            if helpers.get_map(iface)
+            then
+                n_title = iface
+                if n_title == "" then n_title = "network" end
+                naughty.notify({
+                    title    = n_title,
+                    text     = "no carrier",
+                    timeout  = 7,
+                    position = "top_left",
+                    icon     = helpers.icons_dir .. "no_net.png",
+                    fg       = notify_fg or "#FFFFFF"
+                })
+                helpers.set_map(iface, false)
+            end
+        else
+            helpers.set_map(iface, true)
+        end
+    end
+
+    helpers.newtimer(iface, timeout, update)
+    return net.widget
+end
+
+return setmetatable(net, { __call = function(_, ...) return worker(...) end })
diff --git a/widgets/sysload.lua b/widgets/sysload.lua
new file mode 100644 (file)
index 0000000..2abac33
--- /dev/null
@@ -0,0 +1,46 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2010-2012, Peter Hofmann              
+                                                  
+--]]
+
+local newtimer     = require("lain.helpers").newtimer
+
+local wibox        = require("wibox")
+
+local io           = { open = io.open }
+local string       = { format = string.format,
+                       match  = string.match }
+
+local setmetatable = setmetatable
+
+-- System load
+-- lain.widgets.sysload
+local sysload = {}
+
+local function worker(args)
+    local args = args or {}
+    local timeout = args.timeout or 5
+    local settings = args.settings or function() end
+
+    sysload.widget = wibox.widget.textbox('')
+
+    function update()
+        local f = io.open("/proc/loadavg")
+        local ret = f:read("*all")
+        f:close()
+
+        load_1, load_5, load_15 = string.match(ret, "([^%s]+) ([^%s]+) ([^%s]+)")
+
+        widget = sysload.widget
+        settings()
+    end
+
+    newtimer("sysload", timeout, update)
+    return sysload.widget
+end
+
+return setmetatable(sysload, { __call = function(_, ...) return worker(...) end })
diff --git a/widgets/temp.lua b/widgets/temp.lua
new file mode 100644 (file)
index 0000000..61a9aa5
--- /dev/null
@@ -0,0 +1,48 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013, Luke Bonham                     
+                                                  
+--]]
+
+local newtimer     = require("lain.helpers").newtimer
+
+local wibox        = require("wibox")
+
+local io           = io
+local tonumber     = tonumber
+
+local setmetatable = setmetatable
+
+-- coretemp
+-- lain.widgets.temp
+local temp = {}
+
+local function worker(args)
+    local args     = args or {}
+    local timeout  = args.timeout or 5
+    local tempfile = args.tempfile or "/sys/class/thermal/thermal_zone0/temp"
+    local settings = args.settings or function() end
+
+    temp.widget = wibox.widget.textbox('')
+
+    function update()
+        local f = io.open(tempfile)
+        if f ~= nil
+        then
+            coretemp_now = tonumber(f:read("*all")) / 1000
+            f:close()
+        else
+            coretemp_now = "N/A"
+        end
+
+        widget = temp.widget
+        settings()
+    end
+
+    newtimer("coretemp", timeout, update)
+    return temp.widget
+end
+
+return setmetatable(temp, { __call = function(_, ...) return worker(...) end })
diff --git a/widgets/yawn/icons/BlowingSnow.png b/widgets/yawn/icons/BlowingSnow.png
new file mode 100755 (executable)
index 0000000..6223f8f
Binary files /dev/null and b/widgets/yawn/icons/BlowingSnow.png differ
diff --git a/widgets/yawn/icons/Cloudy.png b/widgets/yawn/icons/Cloudy.png
new file mode 100755 (executable)
index 0000000..bac1e7e
Binary files /dev/null and b/widgets/yawn/icons/Cloudy.png differ
diff --git a/widgets/yawn/icons/DayClear.png b/widgets/yawn/icons/DayClear.png
new file mode 100755 (executable)
index 0000000..d9e2745
Binary files /dev/null and b/widgets/yawn/icons/DayClear.png differ
diff --git a/widgets/yawn/icons/DayFair.png b/widgets/yawn/icons/DayFair.png
new file mode 120000 (symlink)
index 0000000..8ee94d1
--- /dev/null
@@ -0,0 +1 @@
+DayClear.png
\ No newline at end of file
diff --git a/widgets/yawn/icons/DayMostlyCloudy.png b/widgets/yawn/icons/DayMostlyCloudy.png
new file mode 100755 (executable)
index 0000000..22b929c
Binary files /dev/null and b/widgets/yawn/icons/DayMostlyCloudy.png differ
diff --git a/widgets/yawn/icons/DayPartlyCloudy.png b/widgets/yawn/icons/DayPartlyCloudy.png
new file mode 100755 (executable)
index 0000000..8fd0a5b
Binary files /dev/null and b/widgets/yawn/icons/DayPartlyCloudy.png differ
diff --git a/widgets/yawn/icons/Drizzle.png b/widgets/yawn/icons/Drizzle.png
new file mode 120000 (symlink)
index 0000000..df34463
--- /dev/null
@@ -0,0 +1 @@
+Rain.png
\ No newline at end of file
diff --git a/widgets/yawn/icons/Fog.png b/widgets/yawn/icons/Fog.png
new file mode 120000 (symlink)
index 0000000..b615645
--- /dev/null
@@ -0,0 +1 @@
+Foggy.png
\ No newline at end of file
diff --git a/widgets/yawn/icons/Foggy.png b/widgets/yawn/icons/Foggy.png
new file mode 100755 (executable)
index 0000000..009039f
Binary files /dev/null and b/widgets/yawn/icons/Foggy.png differ
diff --git a/widgets/yawn/icons/FreezingDrizzle.png b/widgets/yawn/icons/FreezingDrizzle.png
new file mode 100755 (executable)
index 0000000..6a66140
Binary files /dev/null and b/widgets/yawn/icons/FreezingDrizzle.png differ
diff --git a/widgets/yawn/icons/FreezingRain.png b/widgets/yawn/icons/FreezingRain.png
new file mode 100755 (executable)
index 0000000..c924fac
Binary files /dev/null and b/widgets/yawn/icons/FreezingRain.png differ
diff --git a/widgets/yawn/icons/Hail.png b/widgets/yawn/icons/Hail.png
new file mode 100755 (executable)
index 0000000..009039f
Binary files /dev/null and b/widgets/yawn/icons/Hail.png differ
diff --git a/widgets/yawn/icons/Haze.png b/widgets/yawn/icons/Haze.png
new file mode 120000 (symlink)
index 0000000..0874a83
--- /dev/null
@@ -0,0 +1 @@
+Hail.png
\ No newline at end of file
diff --git a/widgets/yawn/icons/HeavyRain.png b/widgets/yawn/icons/HeavyRain.png
new file mode 120000 (symlink)
index 0000000..ace2a94
--- /dev/null
@@ -0,0 +1 @@
+Showers.png
\ No newline at end of file
diff --git a/widgets/yawn/icons/HeavySnow.png b/widgets/yawn/icons/HeavySnow.png
new file mode 100755 (executable)
index 0000000..ddcb8f3
Binary files /dev/null and b/widgets/yawn/icons/HeavySnow.png differ
diff --git a/widgets/yawn/icons/LightRain.png b/widgets/yawn/icons/LightRain.png
new file mode 120000 (symlink)
index 0000000..df34463
--- /dev/null
@@ -0,0 +1 @@
+Rain.png
\ No newline at end of file
diff --git a/widgets/yawn/icons/LightSnow.png b/widgets/yawn/icons/LightSnow.png
new file mode 120000 (symlink)
index 0000000..aa8b28e
--- /dev/null
@@ -0,0 +1 @@
+LightSnowShowers.png
\ No newline at end of file
diff --git a/widgets/yawn/icons/LightSnowShowers.png b/widgets/yawn/icons/LightSnowShowers.png
new file mode 100755 (executable)
index 0000000..d797ee9
Binary files /dev/null and b/widgets/yawn/icons/LightSnowShowers.png differ
diff --git a/widgets/yawn/icons/Mist.png b/widgets/yawn/icons/Mist.png
new file mode 120000 (symlink)
index 0000000..b615645
--- /dev/null
@@ -0,0 +1 @@
+Foggy.png
\ No newline at end of file
diff --git a/widgets/yawn/icons/MixedRainAndHail.png b/widgets/yawn/icons/MixedRainAndHail.png
new file mode 100755 (executable)
index 0000000..758b01e
Binary files /dev/null and b/widgets/yawn/icons/MixedRainAndHail.png differ
diff --git a/widgets/yawn/icons/MixedRainAndSleet.png b/widgets/yawn/icons/MixedRainAndSleet.png
new file mode 100755 (executable)
index 0000000..7f0d252
Binary files /dev/null and b/widgets/yawn/icons/MixedRainAndSleet.png differ
diff --git a/widgets/yawn/icons/MixedRainAndSnow.png b/widgets/yawn/icons/MixedRainAndSnow.png
new file mode 100755 (executable)
index 0000000..0a07b7b
Binary files /dev/null and b/widgets/yawn/icons/MixedRainAndSnow.png differ
diff --git a/widgets/yawn/icons/NightClear.png b/widgets/yawn/icons/NightClear.png
new file mode 100755 (executable)
index 0000000..84ea140
Binary files /dev/null and b/widgets/yawn/icons/NightClear.png differ
diff --git a/widgets/yawn/icons/NightFair.png b/widgets/yawn/icons/NightFair.png
new file mode 120000 (symlink)
index 0000000..23df45a
--- /dev/null
@@ -0,0 +1 @@
+NightClear.png
\ No newline at end of file
diff --git a/widgets/yawn/icons/NightMostlyCloudy.png b/widgets/yawn/icons/NightMostlyCloudy.png
new file mode 100755 (executable)
index 0000000..d8b3673
Binary files /dev/null and b/widgets/yawn/icons/NightMostlyCloudy.png differ
diff --git a/widgets/yawn/icons/NightPartlyCloudy.png b/widgets/yawn/icons/NightPartlyCloudy.png
new file mode 100755 (executable)
index 0000000..9e4404d
Binary files /dev/null and b/widgets/yawn/icons/NightPartlyCloudy.png differ
diff --git a/widgets/yawn/icons/README.md b/widgets/yawn/icons/README.md
new file mode 100644 (file)
index 0000000..e4dc111
--- /dev/null
@@ -0,0 +1,6 @@
+Yawn icons
+==========
+
+These are [Plain Weather Icons](http://merlinthered.deviantart.com/art/plain-weather-icons-157162192), created by [MerlinTheRed](http://merlinthered.deviantart.com/).
+
+<a href="http://creativecommons.org/licenses/by-nc-sa/2.5/"><img src="http://i.creativecommons.org/l/by-nc-sa/2.5/80x15.png" align="right"></a>
diff --git a/widgets/yawn/icons/Rain.png b/widgets/yawn/icons/Rain.png
new file mode 100755 (executable)
index 0000000..d00552a
Binary files /dev/null and b/widgets/yawn/icons/Rain.png differ
diff --git a/widgets/yawn/icons/RainThunder.png b/widgets/yawn/icons/RainThunder.png
new file mode 100755 (executable)
index 0000000..d30e120
Binary files /dev/null and b/widgets/yawn/icons/RainThunder.png differ
diff --git a/widgets/yawn/icons/Showers.png b/widgets/yawn/icons/Showers.png
new file mode 100755 (executable)
index 0000000..3cc6665
Binary files /dev/null and b/widgets/yawn/icons/Showers.png differ
diff --git a/widgets/yawn/icons/Sleet.png b/widgets/yawn/icons/Sleet.png
new file mode 120000 (symlink)
index 0000000..f8f9693
--- /dev/null
@@ -0,0 +1 @@
+SnowShowers.png
\ No newline at end of file
diff --git a/widgets/yawn/icons/Snow.png b/widgets/yawn/icons/Snow.png
new file mode 120000 (symlink)
index 0000000..f8f9693
--- /dev/null
@@ -0,0 +1 @@
+SnowShowers.png
\ No newline at end of file
diff --git a/widgets/yawn/icons/SnowFlurries.png b/widgets/yawn/icons/SnowFlurries.png
new file mode 120000 (symlink)
index 0000000..2e090cd
--- /dev/null
@@ -0,0 +1 @@
+BlowingSnow.png
\ No newline at end of file
diff --git a/widgets/yawn/icons/SnowShowers.png b/widgets/yawn/icons/SnowShowers.png
new file mode 100755 (executable)
index 0000000..30534a2
Binary files /dev/null and b/widgets/yawn/icons/SnowShowers.png differ
diff --git a/widgets/yawn/icons/Sunny.png b/widgets/yawn/icons/Sunny.png
new file mode 100755 (executable)
index 0000000..cf08c5c
Binary files /dev/null and b/widgets/yawn/icons/Sunny.png differ
diff --git a/widgets/yawn/icons/ThunderintheVicinity.png b/widgets/yawn/icons/ThunderintheVicinity.png
new file mode 120000 (symlink)
index 0000000..1fb3b9c
--- /dev/null
@@ -0,0 +1 @@
+Cloudy.png
\ No newline at end of file
diff --git a/widgets/yawn/icons/Wind.png b/widgets/yawn/icons/Wind.png
new file mode 100755 (executable)
index 0000000..5dc1356
Binary files /dev/null and b/widgets/yawn/icons/Wind.png differ
diff --git a/widgets/yawn/icons/na.png b/widgets/yawn/icons/na.png
new file mode 100755 (executable)
index 0000000..62a5350
Binary files /dev/null and b/widgets/yawn/icons/na.png differ
diff --git a/widgets/yawn/init.lua b/widgets/yawn/init.lua
new file mode 100644 (file)
index 0000000..36cdf54
--- /dev/null
@@ -0,0 +1,199 @@
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013, Luke Bonham                     
+                                                  
+--]]
+
+local newtimer     = require("lain.helpers").newtimer
+
+local naughty      = require("naughty")
+local wibox        = require("wibox")
+
+local debug        = { getinfo = debug.getinfo }
+local io           = io
+local os           = { date    = os.date,
+                       getenv  = os.getenv }
+local string       = { find    = string.find,
+                       match   = string.match,
+                       gsub    = string.gsub,
+                       sub     = string.sub }
+local tonumber     = tonumber
+
+local setmetatable = setmetatable
+
+-- YAhoo! Weather Notification
+-- lain.widgets.yawn
+local yawn =
+{
+    icon   = wibox.widget.imagebox(),
+    widget = wibox.widget.textbox('')
+}
+
+local project_path        = debug.getinfo(1, 'S').source:match[[^@(.*/).*$]]
+local localizations_path  = project_path .. 'localizations/'
+local icon_path           = project_path .. 'icons/'
+local api_url             = 'http://weather.yahooapis.com/forecastrss'
+local units_set           = '?u=c&w=' -- Default is Celsius
+local language            = string.match(os.getenv("LANG"), "(%S*$*)[.]")
+local weather_data        = nil
+local notification        = nil
+local city_id             = nil
+local sky                 = nil
+local settings            = function() end
+
+yawn_notification_preset = {}
+
+local function fetch_weather()
+    local url = api_url .. units_set .. city_id
+    local f = io.popen("curl --connect-timeout 1 -fsm 3 '" .. url .. "'" )
+    local text = f:read("*all")
+    f:close()
+
+    -- In case of no connection or invalid city ID
+    -- widgets won't display
+    if text == "" or text:match("City not found")
+    then
+        yawn.icon:set_image(icon_path .. "na.png")
+        if text == "" then
+            weather_data = "Service not available at the moment."
+            yawn.widget:set_text(" N/A")
+        else
+            weather_data = "City not found!\n" ..
+                           "Are you sure " .. city_id ..
+                           " is your Yahoo city ID?"
+            yawn.widget:set_text(" ?")
+        end
+        return
+    end
+
+    -- Processing raw data
+    weather_data = text:gsub("<.->", "")
+    weather_data = weather_data:match("Current Conditions:.-Full") or ""
+
+    -- may still happens in case of bad connectivity
+    if weather_data == "" then
+        yawn.icon:set_image(icon_path .. "na.png")
+        yawn.widget:set_text(" ?")
+        return
+    end
+
+    weather_data = weather_data:gsub("Current Conditions:.-\n", "Now: ")
+    weather_data = weather_data:gsub("Forecast:.-\n", "")
+    weather_data = weather_data:gsub("\nFull", "")
+    weather_data = weather_data:gsub("[\n]$", "")
+    weather_data = weather_data:gsub(" [-] " , ": ")
+    weather_data = weather_data:gsub("[.]", ",")
+    weather_data = weather_data:gsub("High: ", "")
+    weather_data = weather_data:gsub(" Low: ", " - ")
+
+    -- Getting info for text widget
+    local now      = weather_data:sub(weather_data:find("Now:")+5,
+                     weather_data:find("\n")-1)
+    forecast       = now:sub(1, now:find(",")-1)
+    units          = now:sub(now:find(",")+2, -2)
+
+    -- Day/Night icon change
+    local hour = tonumber(os.date("%H"))
+    sky = icon_path
+
+    if forecast == "Clear"         or
+       forecast == "Fair"          or
+       forecast == "Partly Cloudy" or
+       forecast == "Mostly Cloudy"
+       then
+           if hour >= 6 and hour <= 18
+           then
+               sky = sky .. "Day"
+           else
+               sky = sky .. "Night"
+           end
+    end
+
+    sky = sky  .. forecast:gsub(" ", ""):gsub("/", "") .. ".png"
+
+    -- In case there's no defined icon for current forecast
+    if io.open(sky) == nil then
+        sky = icon_path .. "na.png"
+    end
+
+    -- Localization
+    local f = io.open(localizations_path .. language, "r")
+    if language:find("en_") == nil and f ~= nil
+    then
+        f:close()
+        for line in io.lines(localizations_path .. language)
+        do
+            word = string.sub(line, 1, line:find("|")-1)
+            translation = string.sub(line, line:find("|")+1)
+            weather_data = string.gsub(weather_data, word, translation)
+        end
+    end
+
+    -- Finally setting infos
+    yawn.icon:set_image(sky)
+    widget = yawn.widget
+
+    forecast = weather_data:match(": %S.-,"):gsub(": ", ""):gsub(",", "")
+    units = units:gsub(" ", "")
+
+    settings()
+end
+
+function yawn.hide()
+    if notification ~= nil then
+        naughty.destroy(notification)
+        notification = nil
+    end
+end
+
+function yawn.show(t_out)
+    if yawn.widget._layout.text:match("?")
+    then
+        fetch_weather(settings)
+    end
+
+    yawn.hide()
+
+    notification = naughty.notify({
+        preset = yawn_notification_preset,
+        text = weather_data,
+        icon = sky,
+        timeout = t_out
+    })
+end
+
+function yawn.register(id, args)
+    local args     = args or {}
+    local timeout  = args.timeout or 600
+    settings       = args.settings or function() end
+
+    if args.u == "f" then units_set = '?u=f&w=' end
+
+    city_id = id
+
+    newtimer("yawn", timeout, fetch_weather)
+
+    yawn.icon:connect_signal("mouse::enter", function()
+        yawn.show(0)
+    end)
+    yawn.icon:connect_signal("mouse::leave", function()
+        yawn.hide()
+    end)
+
+    return yawn
+end
+
+function yawn.attach(widget, id, args)
+    yawn.register(id, args)
+
+    widget:connect_signal("mouse::enter", function()
+        yawn.show(0)
+    end)
+
+    widget:connect_signal("mouse::leave", function()
+        yawn.hide()
+    end)
+end
+
+return setmetatable(yawn, { __call = function(_, ...) return yawn.register(...) end })
diff --git a/widgets/yawn/localizations/it_IT b/widgets/yawn/localizations/it_IT
new file mode 100644 (file)
index 0000000..70b0eef
--- /dev/null
@@ -0,0 +1,60 @@
+Now:|Ora:
+Sun:|Dom:
+Mon:|Lun:
+Tue:|Mar:
+Wed:|Mer:
+Thu:|Gio:
+Fri:|Ven:
+Sat:|Sab:
+Mostly Sunny|Abbastanza Soleggiato
+Sunny|Soleggiato
+Sun|Soleggiato
+Rain/Thunder|Temporali
+Isolated Thunderstorms|Temporali Isolati
+Scattered Thunderstorms|Temporali Sparsi
+Thundershowers|Rovesci Temporaleschi
+Thunderstorms|Temporali
+Thunder in the Vicinity|Tuoni in prossimità
+Thunder|Temporale
+AM|In Mattinata
+PM|Nel Pomeriggio
+Early|In Mattinata
+Late|In Serata
+Few|Sporadiche
+Severe|Forti
+Clear|Sereno
+Fair|Sereno
+Partly|Parzialmente
+Mostly|Molto
+Cloudy|Nuvoloso
+Clouds|Nuvoloso
+Scattered Showers|Temporali Sparsi
+Light Snow Showers|Nevicate Leggere
+Snow Showers|Nevicate
+aeavy Snow|Forti Nevicate
+Scattered Snow Showers|Nevicate Sparse
+Mixed Rain And Snow|Pioggia E Neve
+Mixed Rain And Sleet|Pioggia E Nevischio
+Mixed Snow And Sleet|Neve E Nevischio
+Mixed Rain And Hail|Pioggia E Grandine
+Snow Flurries|Folate Di Neve
+Blowing Snow|Neve Battente
+Blowing Rain|Pioggia Battente
+Heavy Rain|Forti Piogge
+Freezing Rain|Pioggia Congelantesi
+Showers|Piogge
+Light Rain|Pioggia Leggera
+Heavy|Forti
+Rain|Piovoso
+Windy|Ventoso
+Wind|Ventoso
+Snow|Neve
+Sleet|Nevischio
+Light Drizzle|Pioggia Leggera
+Drizzle|Pioggia Leggera
+Freezing Drizzle|Pioggerella Congelantesi
+Hail|Grandine
+Fog|Nebbia
+Foggy|Nebbioso
+Haze|Nebbia
+Light|Leggere
diff --git a/widgets/yawn/localizations/localization_template b/widgets/yawn/localizations/localization_template
new file mode 100644 (file)
index 0000000..453807e
--- /dev/null
@@ -0,0 +1,60 @@
+Now:|
+Sun:|
+Mon:|
+Tue:|
+Wed:|
+Thu:|
+Fri:|
+Sat:|
+Mostly Sunny|
+Sunny|
+Sun|
+Rain/Thunder|
+Isolated Thunderstorms|
+Scattered Thunderstorms|
+Thundershowers|
+Thunderstorms|
+Thunder in the Vicinity|
+Thunder|
+AM|
+PM|
+Early|
+Late|
+Few|
+Severe|
+Clear|
+Fair|
+Partly|
+Mostly|
+Cloudy|
+Clouds|
+Scattered Showers|
+Light Snow Showers|
+Snow Showers|
+Heavy Snow|
+Scattered Snow Showers|
+Mixed Rain And Snow|
+Mixed Rain And Sleet|
+Mixed Snow And Sleet|
+Mixed Rain And Hail|
+Snow Flurries|
+Blowing Snow|
+Blowing Rain|
+Heavy Rain|
+Freezing Rain|
+Showers|
+Light Rain|
+Heavy|
+Rain|
+Windy|
+Wind|
+Snow|
+Sleet|
+Freezing Drizzle|
+Light Drizzle|
+Drizzle|
+Hail|
+Fog|
+Foggy|
+Haze|
+Light|
diff --git a/wiki b/wiki
new file mode 160000 (submodule)
index 0000000..f996cdb
--- /dev/null
+++ b/wiki
@@ -0,0 +1 @@
+Subproject commit f996cdb74e7583534256d82706ef63d2b9811b42