]> 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:

first commit
authorluke bonham <dadasignificanulla@gmail.com>
Sat, 7 Sep 2013 10:06:42 +0000 (12:06 +0200)
committercopycat-killer <dada@archlinux.info>
Wed, 5 Aug 2015 11:29:53 +0000 (13:29 +0200)
112 files changed:
LICENSE [new file with mode: 0644]
README.md [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/browse.png [new file with mode: 0644]
icons/layout/default/browsew.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/centerwork.png [new file with mode: 0644]
icons/layout/default/centerworkw.png [new file with mode: 0644]
icons/layout/default/gimp.png [new file with mode: 0644]
icons/layout/default/gimpw.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/browse.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/centerwork.png [new file with mode: 0644]
icons/layout/zenburn/gimp.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]
init.lua [new file with mode: 0644]
layout/cascade.lua [new file with mode: 0644]
layout/cascadetile.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/checkmail [new file with mode: 0755]
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/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/README.rst [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/DayMostlyCloudy.png [new file with mode: 0755]
widgets/yawn/icons/DayPartlyCloudy.png [new file with mode: 0755]
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/HeavySnow.png [new file with mode: 0755]
widgets/yawn/icons/LightSnowShowers.png [new file with mode: 0755]
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/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/SnowShowers.png [new file with mode: 0755]
widgets/yawn/icons/Sunny.png [new file with mode: 0755]
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]

diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..d159169
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..8aa9449
--- /dev/null
+++ b/README.md
@@ -0,0 +1,419 @@
+VAin agaIN
+==========
+
+Author: Luke Bonham <dada [at] archlinux [dot] info>
+
+Source: https://github.com/copycat-killer/vain
+
+Version: 1.9.9
+
+Release version: 2.0
+
+**Please note**: until release version, this documentation will be not updated.
+
+Foreword
+--------
+
+Based on a port of [awesome-vain](https://github.com/vain/awesome-vain), this
+costantly evolving module provides new layouts, a set of widgets and
+utility functions in order to improve Awesome usability and configurability.
+
+This work is licensed under [GNU GPLv2 License](http://www.gnu.org/licenses/gpl-2.0.html).
+Installation
+============
+
+Simply clone this repository into your Awesome directory.
+
+Widgets
+=======
+
+systemload
+----------
+
+Show the current system load in a textbox. Read it directly from
+`/proc/loadavg`.
+
+       mysysload = vain.widgets.systemload()
+
+A click on the widget will call `htop` in your `terminal`.
+
+The function takes a table as an optional argument. That table may
+contain:
+
+* `.refresh_timeout`: Default to 10 seconds.
+* `.show_all`: Show all three values (`true`) or only the first one (`false`). Default to `false`.
+* `.color`: Default to beautiful.bg_normal or "#FFFFFF".
+
+cpu
+--------
+
+Shows the average CPU usage percent for a given amount of time.
+
+       mycpuusage = vain.widgets.cpu()
+
+A click on the widget will call `htop` in your `terminal`.
+
+The function takes a table as optional argument, which can contain:
+
+Variable | Meaning | Type | Default
+--- | --- | --- | ---
+`refresh_timeout` | Refresh timeout seconds | int | 10
+`header` | Text to show before value | string | " Vol "
+`header_color` | Header color | string | `beautiful.fg_normal` or "#FFFFFF"
+`color` | Value color | string | `beautiful.fg_focus` or "#FFFFFF"
+`footer` | Text to show after value | string | "%"
+
+**Note**: `footer` color is `color`.
+
+memusage
+--------
+
+Show used memory and total memory in MiB.
+
+       mymem = vain.widgets.mem()
+
+
+The function takes a table as an optional argument. That table may
+contain:
+
+Variable | Meaning | Type | Default
+--- | --- | --- | ---
+`refresh_timeout` | Refresh timeout seconds | int | 10
+`show_swap` | Show amount of used swap space? | boolean | false
+`show_total` | Show amout of total memory? | boolean | false
+`header` | Text to show before value | string | " Vol "
+`header_color` | Header color | string | `beautiful.fg_normal` or "#FFFFFF"
+`color` | Value color | string | `beautiful.fg_focus` or "#FFFFFF"
+`footer` | Text to show after value | string | "MB"
+
+**Note**: `footer` color is `color`.
+
+mailcheck
+---------
+Checks maildirs and shows the result in a textbox.
+Maildirs are structured as follows:
+
+       ~/Mail
+       .
+       |-- arch
+       |   |-- cur
+       |   |-- new
+       |   `-- tmp
+       |-- gmail
+       |   |-- cur
+       |   |-- new
+       |   `-- tmp
+       .
+       .
+       .
+
+therefore `mailcheck` checks whether there are files in the `new`
+directories. To do so, it calls `find`. If there's new mail, the textbox
+will say something like "mail: bugs(3), system(1)", otherwise it says
+"no mail".
+
+       mymailcheck = vain.widgets.mailcheck("/path/to/my/maildir")
+
+The function takes a table as an optional argument. That table may
+contain:
+
+* `.mailprogram`: Your favourite mail program. Clicking on the widget will
+  spawn it. Default is `mutt`.
+* `.refresh_timeout`: Default to 60 seconds.
+* `.mailpath`: Path to your maildir, default is `~/Mail`.
+* `.ignore_boxes`: Another table which lists boxes (just the last part,
+  like `lists`) to ignore. Default to an empty table.
+* `.initial_update`: Check for mail when starting Awesome (`true`) or
+  wait for the first refresh timeout (`false`)? Default to `false`.
+* `.header_text`: Text to show along with output, default is "Mail".
+* `.header_text_color`: Default to "#9E9E9E".
+* `.color_newmail`: Default to "#D4D4D4".
+* `.color_nomail`: Default to "#9E9E9E".
+* `.shadow`: Hides widget when there are no mails. Default is `false`.
+
+imapcheck
+---------
+
+Check new mails over imap protocol.
+
+Dependencies:
+
+* Python3
+
+Since [luasec](https://github.com/brunoos/luasec/) is still not officially
+supported in lua 5.2, writing a pure lua solution would have meant too many
+hacks and dependencies, resulting in a very big and not efficient-proven submodule.
+
+That's why I chose Python.
+
+Python offers [imaplib](http://docs.python.org/2/library/imaplib.html), a simple yet powerful IMAP4 client library which provides encrypted communication over SSL sockets.
+
+Basically, `imapcheck` calls ``vain/scripts/checkmail`` and parse its output in a widget. New mails are also notified through Naughty, with a popup like this:
+
+       +---------------------------------------------------+
+       | +---+                                             |
+       | |\ /| donald@disney.org has 3 new messages        |
+       | +---+                                             |
+       |       Latest From: Mickey Mouse <boss@disney.org> |
+    |       Subject: Re: Vacation Day                   |
+    |                                                   |
+    |       Not after what you did yesterday.           |
+    |       Daisy told me everything [...]              |
+       |                                                   |
+       +---------------------------------------------------+
+
+Text will be cut if the mail is too long.
+
+       myimapcheck = vain.widgets.mailcheck(args)
+
+The function takes a table as argument. Required table parameters are:
+
+* `.server`: You email server. Example: `imap.gmail.com`.
+* `.mail`: Your email.
+* `.password`: Your email password.
+
+while the optional are:
+
+* `.port`: Imap port. Default is `993`.
+* `.refresh_timeout`: Default to 60 seconds.
+* `.notify_timeout`: Notification timeout. Default to 8 seconds.
+* `.notify_position`: Notification position. Default is "top_left". Check
+  [Naughty position parameter](http://awesome.naquadah.org/doc/api/modules/naughty.html) for a list of other possible values.
+* `.mailprogram`: Your favourite mail program. Clicking on the widget will
+  spawn it. Default is `mutt`.
+* `.mail_encoding`: If you wish to set an encoding. Default is `nil`.
+* `.initial_update`: Check for mail when starting Awesome (`true`) or
+  wait for the first refresh timeout (`false`)? Default to `false`.
+* `.header_text`: Text to show along with output, default is "Mail".
+* `.header_text_color`: Default to "#9E9E9E".
+* `.color_newmail`: Default to "#D4D4D4".
+* `.color_nomail`: Default to "#9E9E9E".
+* `.shadow`: Hides widget when there are no mails. Default is `false`.
+* `.maxlen`: Maximum mail length. If mail is longer, it will be cut. Default is
+  `100`.
+* `.is_plain`: Define whether `.password` field is a plain password (`true`) or a function that retrieves it (`false`). Default to `false`.
+
+Let's focus better on `.is_plain` parameter.
+
+You can just easily set your password like this:
+
+    args.is_plain = false
+    args.password = "mypassword"
+
+and you'll have the same security provided by `~/.netrc`. (In this case, it's
+better to set your `rc.lua` permissions to 700 or 600)
+
+**Or**, you can use a keyring, like gnome's:
+
+    args.password = "gnome-keyring-query get password"
+
+(`gnome-keyring-query` is not in gnome-keyring pkg, you have to download it
+separately)
+
+or the very light [python keyring](https://pypi.python.org/pypi/keyring).
+
+When `.is_plain` is `false`, it *executes* `.password` before using it, so you can also use whatever password fetching solution you want.
+
+You can also define your icon for the naughty notification. Just set `vain_mail_notify` into your ``theme.lua``.
+
+
+
+mpd
+---
+
+Provides a `table` with 2 elements:
+
+* `table["widget"]` is a textbox displaying current song in play.
+
+* `table["force"]` is a function to *force* the widget to update, exactly
+  like `vicious.force()`.
+
+Also, a notification is shown when a new song is playing.
+
+Dependencies:
+
+* libnotify
+* imagemagick
+
+
+    mpdwidget = vain.widgets.mpd()
+    ...
+    right_layout:add(mpdwidget["widget"])
+
+The function takes a table as an optional argument. That table may
+contain:
+
+* `.password`: Mpd password. Default is unset.
+* `.host`: Mpd host. Default is "127.0.0.1" (localhost).
+* `.port`: Mpd port. Default is "6600".
+* `.music_dir`: Your music directory. Default is "~/Music". If you have to
+  change this, be sure to write the absolute path.
+* `.refresh_timeout`: Widget refresh timeout. Default is `1`.
+* `.notify_timeout`: Notification timeout. Default is `5`.
+* `.color_artist`: Artist name color. Default is `#9E9E9E`.
+* `.color_song`: Song name color. Default is `#EBEBFF`.
+* `.musicplr`: Your favourite music player. Clicking on the widget will spawn
+  it. Default is `ncmpcpp`.
+* `.shadow`: Hides widget when no song is playing. Default is `false`.
+
+You can use `table["force"]` to make your mpd keybindings immediate.
+Example usage:
+
+    globalkeys = awful.util.table.join(
+    ...
+        -- Music control
+        awful.key({ altkey, "Control" }, "Up", function ()
+                                                  awful.util.spawn_with_shell( "mpc toggle || ncmpcpp toggle || ncmpc toggle || pms toggle", false )
+                                                  mpdwidget["force"]()
+                                               end),
+        awful.key({ altkey, "Control" }, "Down", function ()
+                                                  awful.util.spawn_with_shell( "mpc stop || ncmpcpp stop || ncmpc stop || pms stop", false )
+                                                  mpdwidget["force"]()
+                                                 end ),
+        awful.key({ altkey, "Control" }, "Left", function ()
+                                                  awful.util.spawn_with_shell( "mpc prev || ncmpcpp prev || ncmpc prev || pms prev", false )
+                                                  mpdwidget["force"]()
+                                                 end ),
+        awful.key({ altkey, "Control" }, "Right", function ()
+                                                  awful.util.spawn_with_shell( "mpc next || ncmpcpp next || ncmpc next || pms next", false )
+                                                  mpdwidget["force"]()
+                                                  end ),
+
+net
+---
+
+Monitors network interfaces and shows current traffic in a textbox. If
+the interface is not present or if there's not enough data yet, you'll
+see `wlan0: -` or similar.  Otherwise, the current traffic is shown in
+kilobytes per second as `eth0: ↑(00,010.2), ↓(01,037.8)` or similar.
+
+       neteth0 = vain.widgets.net()
+
+The function takes a table as an optional argument. That table may
+contain:
+
+* `.iface`: Default to `eth0`.
+* `.refresh_timeout`: Default to 2 seconds.
+* `.color`: Default to beautiful.bg_normal or "#FFFFFF".
+
+gitodo
+------
+
+This is an integration of [gitodo](https://github.com/vain/gitodo) into
+Awesome.
+
+       todolist = vain.widgets.gitodo()
+
+The function takes a table as an optional argument. That table may
+contain:
+
+* `.refresh_timeout`: Default to 120 seconds.
+* `.initial_update`: Check for todo items when starting Awesome (`true`)
+  or wait for the first refresh timeout (`false`)? Default to `true`.
+
+`beautiful.gitodo_normal` is used as the color for non-outdated items,
+`beautiful.gitodo_warning` for those items close to their deadline and
+`beautiful.gitodo_outdated` is the color of outdated items.
+
+
+
+Utility functions
+=================
+
+I'll only explain the more complex functions. See the source code for
+the others.
+
+menu\_clients\_current\_tags
+----------------------------
+
+Similar to `awful.menu.clients()`, but this menu only shows the clients
+of currently visible tags. Use it like this:
+
+       globalkeys = awful.util.table.join(
+           ...
+           awful.key({ "Mod1" }, "Tab", function()
+               awful.menu.menu_keys.down = { "Down", "Alt_L", "Tab", "j" }
+               awful.menu.menu_keys.up = { "Up", "k" }
+               vain.util.menu_clients_current_tags({ width = 350 }, { keygrabber = true })
+           end),
+           ...
+       )
+
+magnify\_client
+---------------
+
+Set a client to floating and resize it in the same way the "magnifier"
+layout does it. Place it on the "current" screen (derived from the mouse
+position). This allows you to magnify any client you wish, regardless of
+the currently used layout. Use it with a client keybinding like this:
+
+       clientkeys = awful.util.table.join(
+               ...
+               awful.key({ modkey, "Control" }, "m", vain.util.magnify_client),
+               ...
+       )
+
+If you want to "de-magnify" it, just reset the clients floating state to
+`false` (hit `Mod4`+`CTRL`+`Space`, for example).
+
+niceborder\_{focus, unfocus}
+----------------------------
+
+By default, your `rc.lua` contains something like this:
+
+       client.connect_signal("focus", function(c) c.border_color = beautiful.border_focus end)
+       client.connect_signal("unfocus", function(c) c.border_color = beautiful.border_normal end)
+
+You can change it to this:
+
+       client.connect_signal("focus", vain.util.niceborder_focus(c))
+       client.connect_signal("unfocus", vain.util.niceborder_unfocus(c))
+
+Now, when a client is focused or unfocused, Awesome will look up its
+nice value in `/proc/<pid>/stat`. If it's less than 0, the client is
+classified as "high priority"; if it's greater than 0, the client is
+classified as "low priority". If it's equal to 0, nothing special
+happens.
+
+This requires to define additional colors in your `theme.lua`. For example:
+
+       theme.border_focus_highprio  = "#FF0000"
+       theme.border_normal_highprio = "#A03333"
+
+       theme.border_focus_lowprio   = "#3333FF"
+       theme.border_normal_lowprio  = "#333366"
+
+tag\_view\_nonempty
+------------------------------
+
+This function lets you jump to the next/previous non-empty tag.
+It takes two arguments:
+
+* `direction`: `1` for next non-empty tag, `-1` for previous.
+* `sc`: Screen in which the taglist is. Default is `mouse.screen` or `1`. This
+  argument is optional.
+
+Usage example:
+
+       globalkeys = awful.util.table.join(
+               ...
+        -- Non-empty tag browsing
+        awful.key({ altkey }, "Left", function () vain.util.tag_view_nonempty(-1)
+    end),
+        awful.key({ altkey }, "Right", function () vain.util.tag_view_nonempty(1) end),
+        ...
+
+prompt\_rename\_tag
+-------------------
+
+This function enables you to dynamically rename the current tag you have
+focused.
+Usage example:
+
+       globalkeys = awful.util.table.join(
+           ..
+        -- Dynamic tag renaming
+               awful.key({ modkey, "Shift" }, "r", function () vain.util.prompt_rename_tag(mypromptbox) end),
+               ...
+
+Credits goes to [minism](https://bbs.archlinux.org/viewtopic.php?pid=1315135#p1315135).
diff --git a/helpers.lua b/helpers.lua
new file mode 100644 (file)
index 0000000..7677768
--- /dev/null
@@ -0,0 +1,90 @@
+
+--[[
+                                                      
+     Licensed under GNU General Public License v2     
+      * (c) 2013,      Luke Bonham                    
+      * (c) 2010-2012, Peter Hofmann                  
+      * (c) 2010,      Adrian C. <anrxc@sysphere.org> 
+                                                      
+--]]
+
+local awful  = require("awful")
+local debug  = require("debug")
+local pairs  = pairs
+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
+
+-- }}}
+
+-- {{{
+-- If lain.terminal is a string, e.g. "xterm", then "xterm -e " .. cmd is
+-- run. But if lain.terminal is a function, then terminal(cmd) is run.
+
+function helpers.run_in_terminal(cmd)
+    if type(terminal) == "function"
+    then
+        terminal(cmd)
+    elseif type(terminal) == "string"
+    then
+        awful.util.spawn(terminal .. ' -e ' .. cmd)
+    end
+end
+
+-- }}}
+
+-- {{{ Format units to one decimal point
+
+function helpers.uformat(array, key, value, unit)
+    for u, v in pairs(unit) do
+        array["{"..key.."_"..u.."}"] = string.format("%.1f", value/v)
+    end
+    return array
+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
+
+-- }}}
+
+-- {{{ 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/browse.png b/icons/layout/default/browse.png
new file mode 100644 (file)
index 0000000..c063ca0
Binary files /dev/null and b/icons/layout/default/browse.png differ
diff --git a/icons/layout/default/browsew.png b/icons/layout/default/browsew.png
new file mode 100644 (file)
index 0000000..9b0b2b3
Binary files /dev/null and b/icons/layout/default/browsew.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/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/gimp.png b/icons/layout/default/gimp.png
new file mode 100644 (file)
index 0000000..207ab40
Binary files /dev/null and b/icons/layout/default/gimp.png differ
diff --git a/icons/layout/default/gimpw.png b/icons/layout/default/gimpw.png
new file mode 100644 (file)
index 0000000..35fd10e
Binary files /dev/null and b/icons/layout/default/gimpw.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/browse.png b/icons/layout/zenburn/browse.png
new file mode 100644 (file)
index 0000000..f0bd177
Binary files /dev/null and b/icons/layout/zenburn/browse.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/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/gimp.png b/icons/layout/zenburn/gimp.png
new file mode 100644 (file)
index 0000000..ded8e62
Binary files /dev/null and b/icons/layout/zenburn/gimp.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/init.lua b/init.lua
new file mode 100644 (file)
index 0000000..3abf9f6
--- /dev/null
+++ b/init.lua
@@ -0,0 +1,20 @@
+
+--[[
+                                                   
+     Lain                                          
+     Layouts, widgets and utilities for Awesome WM 
+                                                   
+     Licensed under GNU General Public License v2  
+      * (c) 2013,      Luke Bonham                 
+      * (c) 2010-2012, Peter Hofmann               
+                                                   
+--]]
+
+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..a94bbed
--- /dev/null
@@ -0,0 +1,159 @@
+
+--[[
+                                                  
+     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)
+    if useless_gap == nil
+    then
+        useless_gap = 0
+    end
+
+    -- 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/centerwork.lua b/layout/centerwork.lua
new file mode 100644 (file)
index 0000000..2035c65
--- /dev/null
@@ -0,0 +1,122 @@
+
+--[[
+                                                  
+     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)
+    if useless_gap == nil
+    then
+        useless_gap = 0
+    end
+
+    -- 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..62eef9a
--- /dev/null
@@ -0,0 +1,160 @@
+
+--[[
+                                                  
+     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",
+
+    -- You can set the number of columns and rows,
+    -- -- otherwise they are read from awful.tag
+    nmaster = 0, -- columns
+    ncol    = 0  -- rows
+}
+
+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)
+    if useless_gap == nil
+    then
+        useless_gap = 0
+    end
+
+    -- Screen.
+    local wa = p.workarea
+    local cls = p.clients
+
+    -- How many vertical columns?
+    local t = tag.selected(p.screen)
+    local num_x
+    if termfair.nmaster ~= 0
+    then
+        num_x = termfair.nmaster
+    else
+        num_x = tag.getnmaster(t)
+    end
+
+    -- Do at least "desired_y" rows.
+    local desired_y
+    if termfair.ncol ~= 0
+    then
+        desired_y = termfair.ncol
+    else
+        desired_y = tag.getncol(t)
+    end
+
+    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.
+                if this_x == 0
+                then
+                    g.width = g.width - 2 * useless_gap
+                    g.x = g.x + useless_gap
+                else
+                    g.width = g.width - useless_gap
+                end
+
+                if this_y == 0
+                then
+                    g.height = g.height - 2 * useless_gap
+                    g.y = g.y + useless_gap
+                else
+                    g.height = g.height - 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..92e8d45
--- /dev/null
@@ -0,0 +1,122 @@
+
+--[[
+                                                  
+     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)
+    if useless_gap == nil
+    then
+        useless_gap = 0
+    end
+
+    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.
+                if this_x == 0
+                then
+                    g.width = g.width - 2 * useless_gap
+                    g.x = g.x + useless_gap
+                else
+                    g.width = g.width - useless_gap
+                end
+
+                if this_y == 0
+                then
+                    g.height = g.height - 2 * useless_gap
+                    g.y = g.y + useless_gap
+                else
+                    g.height = g.height - 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..695728c
--- /dev/null
@@ -0,0 +1,112 @@
+
+--[[
+                                                  
+     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)
+    if useless_gap == nil
+    then
+        useless_gap = 0
+    end
+
+    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
+
+            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 * useless_gap
+                  wa2.y = wa2.y + useless_gap
+            else
+                 wa2.height = wa2.height - useless_gap
+           end
+
+           if left then
+                         wa2.width = wa2.width - 2 * useless_gap
+                  wa2.x = wa2.x + useless_gap
+           else
+                 wa2.width = wa2.width - 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..b82c97e
--- /dev/null
@@ -0,0 +1,232 @@
+
+--[[
+                                                  
+     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)
+    if useless_gap == nil
+    then
+        useless_gap = 0
+    end
+
+    -- 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
+
+            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 * useless_gap
+                geom[y] = geom[y] + useless_gap
+            else
+                geom[height] = geom[height] - useless_gap
+            end
+
+            if left then
+                geom[width] = geom[width] - 2 * useless_gap
+                geom[x] = geom[x] + useless_gap
+            else
+                geom[width] = geom[width] - 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/checkmail b/scripts/checkmail
new file mode 100755 (executable)
index 0000000..67c5206
--- /dev/null
@@ -0,0 +1,104 @@
+#!/usr/bin/python
+
+# Simple email checker
+#
+# Wrote by copycat-killer on a rainy day of august 2013
+# to be used in Lain. 
+#
+# https://github.com/copycat-killer/lain
+
+import sys, getopt, locale, imaplib
+
+def main(argv):
+   usage    = "usage: checkmail -s <imapserver> -u <usermail> -p <password> [--port <port>] [--encoding <encoding>] [--cut]"
+   server   = ""
+   user     = ""
+   password = ""
+   port     = 993
+   cut      = False
+   encoding = locale.getdefaultlocale()[1]
+   output   = ""
+
+   try:
+       opts, args = getopt.getopt(argv, "hs:u:p:", ["port=", "encoding=", "cut"])
+   except getopt.GetoptError:
+      print(usage)
+      sys.exit(2)
+
+   if len(argv) == 0:
+      print(usage)
+      sys.exit()
+
+   for opt, arg in opts:
+      if opt == "-h":
+         print(usage)
+         sys.exit()
+      elif opt == "-s":
+         server = arg
+      elif opt == "-u":
+         user = arg
+      elif opt == "-p":
+         password = arg
+      elif opt == "--port":
+         port = int(arg)
+      elif opt == "--cut":
+         cut = True
+      elif opt == "--encoding":
+         encoding = arg
+
+   try:
+      mail = imaplib.IMAP4_SSL(server, port)
+      mail.login(user, password)
+   except imaplib.IMAP4.error:
+      print("CheckMailError: invalid credentials")
+      sys.exit(2)
+
+   status, counts = mail.status("Inbox","(MESSAGES UNSEEN)")
+
+   unread = int(counts[0].split()[4][:-1])
+
+   if status == "OK" and unread:
+      mail.select("Inbox", readonly = 1)
+      ret, messages = mail.uid("search", None, "(UNSEEN)")
+
+      if ret == "OK":
+          latest_email_uid = messages[0].split()[-1]
+
+          ret_header, new_mail_header = mail.uid("fetch", latest_email_uid,
+                                                 "(BODY.PEEK[HEADER.FIELDS (SUBJECT FROM)])")
+          ret_text, new_mail_text = mail.uid("fetch", latest_email_uid, "(BODY[TEXT])")
+
+          if ret_header == "OK" and ret_text == "OK":
+              try: # not all the servers like this, that's why we try
+                  mail.store(latest_email_uid, "-FLAGS", "\\Seen")
+              except imaplib.IMAP4.error:
+                  # this simply means the server refused to
+                  # toggle Seen flag from mail
+                  print("[+Seen]\n")
+
+              nm_header = new_mail_header[0][1].decode(encoding, "replace").strip()
+              nm_text = new_mail_text[0][1].decode(encoding, "replace").strip()
+
+              if unread == 1:
+                  print(user, "has 1 new message\n")
+              else:
+                  print(user, "has", unread, "new messages\n")
+                  nm_header.replace("From:", "Latest from:", 1)
+
+              print(nm_header, "\n")
+
+              if cut:
+                  if len(nm_text) <= 100:
+                      print(nm_text)
+                  else:
+                      print(nm_text[0:100])
+                      print("[...]")
+              else:
+                  print(nm_text)
+   else:
+      print("No new messages")
+
+   mail.logout()
+
+if __name__ == "__main__":
+   main(sys.argv[1:])
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..38b43e9
--- /dev/null
@@ -0,0 +1,64 @@
+#!/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> <default_art>
+
+# Configuration-------------------------------------------------------
+
+# Music directory
+MUSIC_DIR=$1
+
+# Song file
+file=$2
+
+# The default cover to use (optional)
+DEFAULT_ART=$3
+
+# Regex expression used for image search
+IMG_REG="(front|cover|art|Folder|folder)\.(jpg|jpeg|png|gif)$"
+
+# Path of temporary resized cover
+TEMP_PATH="/tmp/mpdcover.png"
+
+# Resize cover
+COVER_RESIZE="100x100"
+
+# 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..a44d52c
--- /dev/null
@@ -0,0 +1,174 @@
+
+--[[
+                                                  
+     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       = string
+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)
+    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)
+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
+
+-- Dynamically rename the current tag you have focused.
+function util.prompt_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
+
+return setmetatable(util, { __index = wrequire })
diff --git a/util/markup.lua b/util/markup.lua
new file mode 100644 (file)
index 0000000..e7baec0
--- /dev/null
@@ -0,0 +1,102 @@
+
+--[[
+                                              
+     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 = {}
+
+--[[ clean this as soon as you document it
+
+  +-- markup
+  |
+  |`-- bold()        Set bold.
+  |`-- italic()      Set italicized text.
+  |`-- strike()      Set strikethrough text.
+  |`-- underline()   Set underlined text.
+  |`-- monospace()   Set monospaced text.
+  |`-- big()         Set bigger text.
+  |`-- small()       Set smaller text.
+  |`-- font()        Set the font of the text.
+  |
+  |`--+ bg
+  |   |
+  |   |`-- color()   Set background color.
+  |   |`-- focus()   Set focus  background color.
+  |   |`-- normal()  Set normal background color.
+  |    `-- urgent()  Set urgent background color.
+  |
+  |`--+ fg
+  |   |
+  |   |`-- color()   Set foreground color.
+  |   |`-- focus()   Set focus  foreground color.
+  |   |`-- normal()  Set normal foreground color.
+  |    `-- urgent()  Set urgent foreground color.
+  |
+  |`-- focus()       Set both foreground and background focus  colors.
+  |`-- normal()      Set both foreground and background normal colors.
+   `-- urgent()      Set both foreground and background urgent colors.
+
+]]
+
+-- 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..7c26908
--- /dev/null
@@ -0,0 +1,103 @@
+
+--[[
+                                                      
+     Licensed under GNU General Public License v2     
+      * (c) 2013,      Luke Bonham                    
+      * (c) 2010-2012, Peter Hofmann                  
+      * (c) 2010,      Adrian C. <anrxc@sysphere.org> 
+                                                      
+--]]
+
+local markup          = require("lain.util.markup")
+local run_in_terminal = require("lain.helpers").run_in_terminal
+
+local awful           = require("awful")
+local beautiful       = require("beautiful")
+local wibox           = require("wibox")
+
+local io              = io
+local string          = { format = string.format,
+                          match  = string.match }
+
+local setmetatable    = setmetatable
+
+-- ALSA volume infos
+-- nain.widgets.alsa
+local alsa = {
+    volume = 0,
+    mute = false,
+}
+
+function worker(args)
+    local args = args or {}
+    local channel = args.channel or "Master"
+    local step = args.step or "1%"
+    local header = args.header or " Vol "
+    local header_color = args.header_color or beautiful.fg_normal or "#FFFFFF"
+    local color = args.color or beautiful.fg_focus or "#FFFFFF"
+
+    local myvolume = wibox.widget.textbox()
+    local myvolumeupdate = function()
+        local f = io.popen('amixer get ' .. channel)
+        local mixer = f:read("*all")
+        f:close()
+
+        local volume, mute = string.match(mixer, "([%d]+)%%.*%[([%l]*)")
+
+        if volume == nil
+        then
+            alsa.volume = 0
+        else
+            alsa.volume = volume
+        end
+
+        if mute == nil or mute == 'on'
+        then
+            alsa.mute = true
+            mute = ''
+        else
+            alsa.mute = false
+            mute = 'M'
+        end
+
+        local ret = markup(color, string.format("%d%s", volume, mute))
+        myvolume:set_markup(markup(header_color, header) .. ret .. " ")
+    end
+
+    local myvolumetimer = timer({ timeout = 5 })
+    myvolumetimer:connect_signal("timeout", myvolumeupdate)
+    myvolumetimer:start()
+    myvolumetimer:emit_signal("timeout")
+
+    myvolume:buttons(awful.util.table.join(
+        awful.button({}, 1,
+            function()
+                run_in_terminal('alsamixer')
+             end),
+        awful.button({}, 3,
+            function()
+                awful.util.spawn('amixer sset ' .. channel ' toggle')
+            end),
+
+        awful.button({}, 4,
+            function()
+                awful.util.spawn('amixer sset ' .. channel .. ' ' .. step '+')
+                myvolumeupdate()
+            end),
+
+        awful.button({}, 5,
+            function()
+                awful.util.spawn('amixer sset ' .. channel .. ' ' .. step '-')
+                myvolumeupdate()
+            end)
+    ))
+
+    alsa.widget = myvolume
+    alsa.channel = channel
+    alsa.step = step
+    alsa.notify = myvolumeupdate
+
+    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..0421f5c
--- /dev/null
@@ -0,0 +1,164 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013, Luke Bonham                     
+      * (c) 2013, Rman                            
+--]]
+
+local awful        = require("awful")
+local beautiful    = require("beautiful")
+local naughty      = require("naughty")
+
+local io           = io
+local math         = { modf  = math.modf }
+local string       = { 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"
+  },
+
+  mixer = terminal .. " -e alsamixer",
+
+  notifications =
+  {
+     font = beautiful.font:sub(beautiful.font:find(""), beautiful.font:find(" ")),
+     font_size = "11",
+     bar_size = 18 -- Awesome default
+  },
+
+  _current_level = 0,
+  _muted = false
+}
+
+function alsabar:notify()
+       local preset =
+       {
+      title = "", text = "",
+      timeout = 3,
+      font = alsabar.notifications.font .. " " .. alsabar.notifications.font_size,
+      fg = beautiful.fg_focus
+       }
+
+       if alsabar._muted then
+               preset.title = alsabar.channel .. " - Muted"
+       else
+               preset.title = alsabar.channel .. " - " .. alsabar._current_level * 100 .. "%"
+       end
+
+  local 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
+
+function worker(args)
+    local args = args or {}
+    local width = args.width or 63
+    local height = args.heigth or 1
+    local ticks = args.ticks or true
+    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)
+
+    if vertical then
+        alsabar.bar:set_vertical(true)
+    end
+
+    local myvolumebarupdate = function()
+        -- 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]*)")
+        -- Handle mixers without data
+        if volu == nil then
+           volu = 0
+           mute = "off"
+        end
+
+        alsabar._current_level = tonumber(volu) / 100
+        alsabar.bar:set_value(alsabar._current_level)
+
+        if mute == "" and 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(" " .. alsabar.channel .. ": " .. volu .. "% ")
+            alsabar.bar:set_color(alsabar.colors.unmute)
+        end
+    end
+
+    local myvolumebartimer = timer({ timeout = 5 })
+    myvolumebartimer:connect_signal("timeout", myvolumebarupdate)
+    myvolumebartimer:start()
+    myvolumebartimer:emit_signal("timeout")
+
+    alsabar.bar:buttons (awful.util.table.join (
+          awful.button ({}, 1, function()
+            awful.util.spawn(alsabar.mixer)
+          end),
+          awful.button ({}, 3, function()
+            awful.util.spawn("amixer sset " .. alsabar.channel .. " toggle")
+            myvolumebarupdate()
+          end),
+          awful.button ({}, 4, function()
+            awful.util.spawn("amixer sset " .. alsabar.channel .. " "
+                              .. alsabar.step .. "+")
+            myvolumebarupdate()
+          end),
+          awful.button ({}, 5, function()
+            awful.util.spawn("amixer sset " .. alsabar.channel .. " "
+                              .. alsabar.step .. "-")
+            myvolumebarupdate()
+          end)
+    ))
+
+    return { widget = alsabar.bar,
+             channel = alsabar.channel, 
+             step = alsabar.step,
+             notify = function() 
+                         myvolumebarupdate()
+                         alsabar.notify()
+                       end
+           }
+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..0461607
--- /dev/null
@@ -0,0 +1,147 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2010-2012, Peter Hofmann              
+                                                  
+--]]
+
+local markup       = require("lain.util.markup")
+local first_line   = require("lain.helpers").first_line
+
+local beautiful    = require("beautiful")
+local naughty      = require("naughty")
+local wibox        = require("wibox")
+
+local math         = { floor  = math.floor }
+local string       = { format = string.format }
+
+local setmetatable = setmetatable
+
+-- Battery infos
+-- lain.widgets.bat
+local bat = {
+    status = "not present",
+    perc   = "N/A",
+    time   = "N/A",
+}
+
+function worker(args)
+    local args = args or {}
+    local battery = args.battery or "BAT0"
+    local show_all = args.show_all or false
+    local refresh_timeout = args.refresh_timeout or 30
+    local header = args.header or " Bat "
+    local header_color = args.header_color or beautiful.fg_normal or "#FFFFFF"
+    local color = args.color or beautiful.fg_focus or "#FFFFFF"
+    local shadow = args.shadow or false
+
+    local mybattery = wibox.widget.textbox()
+
+    local mybatteryupdate = function()
+        local present = first_line("/sys/class/power_supply/"
+                                   .. battery
+                                   .. "/present")
+
+        if present == "1"
+        then
+            local rate = first_line("/sys/class/power_supply/"
+                                    .. battery ..
+                                    "/power_now")
+            local ratev = first_line("/sys/class/power_supply/"
+                                    .. battery ..
+                                     "/voltage_now")
+            local rem = first_line("/sys/class/power_supply/"
+                                    .. battery ..
+                                   "/energy_now")
+            local tot = first_line("/sys/class/power_supply/"
+                                    .. battery ..
+                                   "/energy_full")
+            bat.status = first_line("/sys/class/power_supply/"
+                                    .. battery ..
+                                   "/status")
+
+            local time_rat = 0
+            if bat.status == "Charging"
+            then
+                status = "(+)"
+                time_rat = (tot - rem) / rate
+            elseif bat.status == "Discharging"
+            then
+                status = "(-)"
+                time_rat = rem / rate
+            else
+                status = "(.)"
+            end
+
+            local hrs = math.floor(time_rat)
+            local min = (time_rat - hrs) * 60
+            bat.time = string.format("%02d:%02d", hrs, min)
+
+            local amount = (rem / tot) * 100
+
+            if shadow
+            then
+                bat.perc = string.format("%d", amount)
+            else
+                bat.perc = string.format("%d%%", amount)
+            end
+
+            local watt = string.format("%.2fW", (rate * ratev) / 1e12)
+
+            if show_all
+            then
+                text = watt .. " " .. bat.perc .. " " .. bat.time .. " " .. bat.status
+            else
+                text = bat.perc
+            end
+
+            -- notifications for low and critical states
+            if amount <= 5
+            then
+                naughty.notify{
+                    text = "shutdown imminent",
+                    title = "battery nearly exhausted",
+                    position = "top_right",
+                    timeout = 15,
+                    fg="#000000",
+                    bg="#ffffff",
+                    ontop = true
+                }
+            elseif amount <= 15
+            then
+                old_id = naughty.notify{
+                    text = "plug the cable",
+                    title = "battery low",
+                    position = "top_right",
+                    timeout = 5,
+                    fg="#202020",
+                    bg="#cdcdcd",
+                    ontop = true
+                }
+            end
+        else
+            text = "none"
+        end
+
+        if shadow
+        then
+            mybattery:set_text('')
+        else
+            mybattery:set_markup(markup(header_color, header)
+                                 .. markup(color, text) .. " ")
+        end
+    end
+
+    local mybatterytimer = timer({ timeout = refresh_timeout })
+    mybatterytimer:connect_signal("timeout", mybatteryupdate)
+    mybatterytimer:start()
+    mybatterytimer:emit_signal("timeout")
+
+    bat.widget = mybattery
+
+    return setmetatable(bat, { __index = 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..150c1c3
--- /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 'above'
+    local color = args.color or '#FFFFFF'
+    local size = args.size or 1
+    local box = nil
+    local wiboxarg = {
+        position = nil,
+        bg = color
+    }
+
+    if where == 'above'
+    then
+        wiboxarg.width = relbox.width
+        wiboxarg.height = size
+        box = wibox(wiboxarg)
+        box.x = relbox.x
+        box.y = relbox.y - size
+    elseif where == 'below'
+    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..4b6d469
--- /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 notification = nil
+
+local function create(background, foreground)
+    calendar.offset = 0
+    calendar.icons_dir = icons_dir .. "cal/white/" -- default
+    calendar.notify_icon = nil
+    calendar.font_size = 12
+    calendar.bg = background or beautiful.bg_normal or "#FFFFFF"
+    calendar.fg = foreground or beautiful.fg_normal or "#FFFFFF"
+end
+
+function calendar:hide()
+    if notification ~= nil then
+        naughty.destroy(notification)
+        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_dir .. 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()
+
+    notification = naughty.notify({ text = c_text,
+                                    icon = calendar.notify_icon,
+                                    fg = calendar.fg,
+                                    bg = calendar.bg,
+                                    timeout = tims })
+end
+
+function calendar:attach(widget, background, foreground)
+    create(background, foreground)
+    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/cpu.lua b/widgets/cpu.lua
new file mode 100644 (file)
index 0000000..cf0b76c
--- /dev/null
@@ -0,0 +1,80 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2010-2012, Peter Hofmann              
+                                                  
+--]]
+
+local markup       = require("lain.util.markup")
+local first_line   = require("lain.helpers").first_line
+
+local beautiful    = require("beautiful")
+local wibox        = require("wibox")
+
+local math         = { ceil   = math.ceil }
+local string       = { format = string.format,
+                       gmatch = string.gmatch }
+
+local setmetatable = setmetatable
+
+-- CPU usage
+-- lain.widgets.cpu
+local cpu = {
+    last_total = 0,
+    last_active = 0
+}
+
+function worker(args)
+    local args = args or {}
+    local refresh_timeout = args.refresh_timeout or 5
+    local header = args.header or " Cpu "
+    local header_color = args.header or beautiful.fg_normal or "#FFFFFF"
+    local color = args.color or beautiful.fg_focus or "#FFFFFF"
+    local footer = args.footer or "%"
+
+    local w = wibox.widget.textbox()
+
+    local cpuusageupdate = function()
+        -- 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
+        local dta = math.ceil((dactive / dtotal) * 100)
+
+        w:set_markup(markup(header_color, header) .. markup(color, dta .. footer) .. " ")
+
+        -- Save current data for the next run.
+        cpu.last_active = active
+        cpu.last_total = total
+    end
+
+    local cpuusagetimer = timer({ timeout = refresh_timeout })
+    cpuusagetimer:connect_signal("timeout", cpuusageupdate)
+    cpuusagetimer:start()
+    cpuusagetimer:emit_signal("timeout")
+
+    return w
+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..9611617
--- /dev/null
@@ -0,0 +1,134 @@
+
+--[[
+                                                      
+     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 markup       = require("lain.util.markup")
+local helpers      = require("lain.helpers")
+
+local beautiful    = require("beautiful")
+local wibox        = require("wibox")
+local naughty      = require("naughty")
+
+local io           = io
+local string       = { match = string.match }
+local tonumber     = tonumber
+
+local setmetatable = setmetatable
+
+-- File system disk space usage
+-- lain.widgets.fs
+local fs = {}
+local notification = nil
+
+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({
+        text = ws,
+       timeout = t_out,
+        fg = beautiful.fg_focus,
+    })
+end
+
+-- Variable definitions
+local unit = { ["mb"] = 1024, ["gb"] = 1024^2 }
+
+local function worker(args)
+    local args = args or {}
+    local partition = args.partition or "/"
+    local refresh_timeout = args.refresh_timeout or 600
+    local header = args.header or " Hdd "
+    local header_color = args.header_color or beautiful.fg_normal or "#FFFFFF"
+    local color = args.color or beautiful.fg_focus or "#FFFFFF"
+    local footer = args.header or ""
+    local shadow = args.shadow or false
+
+    local myfs = wibox.widget.textbox()
+
+    helpers.set_map("fs", false)
+
+    local fsupdate = function()
+        local fs_info = {} -- Get data from df
+        local f = io.popen("LC_ALL=C df -kP")
+
+        local function set_text()
+            local info = fs_info['{' .. partition .. ' used_p}']
+            myfs:set_markup(markup(header_color, header)
+                            .. markup(color, info .. footer) .. " ")
+        end
+
+        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
+                helpers.uformat(fs_info, m .. " used",  u, unit)
+                fs_info["{" .. m .. " used_p}"]  = tonumber(p)
+            end
+        end
+
+        f:close()
+
+        if shadow
+        then
+            myfs:set_text('')
+        else
+            set_text()
+        end
+
+        local part = fs_info['{' .. partition .. ' used_p}']
+
+        if part >= 90  then
+            if part >= 99 and not helpers.get_map("fs") then
+                naughty.notify({ title = "warning",
+                                 text = partition .. " ran out!\n"
+                                        .. "make some room",
+                                 timeout = 8,
+                                 position = "top_right",
+                                 fg = beautiful.fg_urgent,
+                                 bg = beautiful.bg_urgent })
+                helpers.set_map("fs", true)
+            end
+            if shadow then set_text() end
+        end
+    end
+
+    local fstimer = timer({ timeout = refresh_timeout })
+    fstimer:connect_signal("timeout", fsupdate)
+    fstimer:start()
+    fstimer:emit_signal("timeout")
+
+    myfs:connect_signal('mouse::enter', function () fs:show(0) end)
+    myfs:connect_signal('mouse::leave', function () fs:hide() end)
+
+    local fs_out =
+    {
+        widget = myfs,
+        show = function(t_out)
+                   fsupdate()
+                   fs:show(t_out)
+               end
+    }
+
+    return setmetatable(fs_out, { __index = fs_out.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..94652b6
--- /dev/null
@@ -0,0 +1,166 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013, Luke Bonham                     
+                                                  
+--]]
+
+local markup       = require("lain.util.markup")
+local helpers      = require("lain.helpers")
+
+local awful        = require("awful")
+local beautiful    = require("beautiful")
+local naughty      = require("naughty")
+local wibox        = require("wibox")
+
+local io           = io
+local tonumber     = tonumber
+local string       = string
+
+local setmetatable = setmetatable
+
+-- Mail imap check
+-- lain.widgets.imap
+local imap = {} 
+
+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 refresh_timeout = args.refresh_timeout or 60
+    local header = args.header or " Mail "
+    local header_color = args.header_color or beautiful.fg_normal or "#FFFFFF"
+    local color_newmail = args.color_newmail or beautiful.fg_focus or "#FFFFFF"
+    local color_nomail = args.color_nomail or beautiful.fg_normal or "#FFFFFF"
+    local mail_encoding = args.mail_encoding or nil
+    local maxlen = args.maxlen or 200
+    local app = args.app or "mutt"
+    local is_plain = args.is_plain or false
+    local shadow = args.shadow or false
+
+    helpers.set_map(mail, true)
+    helpers.set_map(mail .. " count", "0")
+
+    local checkmail = helpers.scripts_dir .. "checkmail"
+
+    if not is_plain
+    then
+        local f = io.popen(password)
+        password = f:read("*all"):gsub("\n", ""):gsub("\r", "")
+        f:close()
+    end
+
+    local myimapcheck = wibox.widget.textbox()
+
+    local myimapcheckupdate = function()
+        function set_nomail()
+            if shadow
+            then
+                myimapcheck:set_text('')
+            else
+                myimapcheck:set_markup(markup(color_nomail, " no mail "))
+            end
+        end
+
+        conn = io.popen("ip link show")
+        check_conn = conn:read("*all") 
+        conn:close()
+
+        if not check_conn:find("state UP") then
+               set_nomail()
+               return
+        end
+
+        to_execute = checkmail .. ' -s ' .. server ..
+                     ' -u ' .. mail .. ' -p ' .. password
+                     .. ' --port ' .. port
+
+        if mail_encoding ~= nil
+        then
+            to_execute = to_execute .. ' --encoding '
+                         .. mail_encoding
+        end
+
+        f = io.popen(to_execute)
+        ws = f:read("*all")
+        f:close()
+
+        if ws:find("No new messages") ~= nil
+        then
+            helpers.set_map(mail, true)
+            set_nomail()
+        elseif ws:find("CheckMailError: invalid credentials") ~= nil
+        then
+            helpers.set_map(mail, true)
+            myimapcheck.set_markup(markup(header_color, header) ..
+                                   markup(color_newmail, "invalid credentials "))
+        else
+            mailcount = ws:match("%d") or "?"
+
+            if helpers.get_map(mail .. " count") ~= mailcount and mailcount ~= "?"
+            then
+                helpers.set_map(mail, true)
+                helpers.set_map(mail .. " count", mailcount)
+            end
+
+            myimapcheck:set_markup(markup(header_color, header) ..
+                                   markup(color_newmail, mailcount) .. " ")
+
+            if helpers.get_map(mail)
+            then
+                if mailcount == "?"
+                -- May happens sometimes using keyrings or other password fetchers.
+                -- Since this should be automatically fixed in short times, we threat
+                -- this exception delaying the update to the next timeout.
+                then
+                    set_nomail()
+                    return
+                elseif tonumber(mailcount) >= 1
+                then
+                    notify_title = ws:match(mail .. " has %d new message.?")
+                    ws = ws:gsub(notify_title, "", 1):gsub("\n", "", 2)
+
+                    ws = ws:gsub("--Content.%S+.-\n", "")
+                    ws = ws:gsub("--%d+.-\n", "")
+
+                    if string.len(ws) > maxlen
+                    then
+                        ws = ws:sub(1, maxlen) .. "[...]"
+                    end
+
+                    notify_title = notify_title:gsub("\n", "")
+                end
+
+                naughty.notify({ title = notify_title,
+                                 fg = color_newmail,
+                                 text = ws,
+                                 icon = beautiful.lain_mail_notify or
+                                        helpers.icons_dir .. "mail.png",
+                                 timeout = 8,
+                                 position = "top_left" })
+
+                helpers.set_map(mail, false)
+            end
+        end
+    end
+
+    local myimapchecktimer = timer({ timeout = refresh_timeout })
+    myimapchecktimer:connect_signal("timeout", myimapcheckupdate)
+    myimapchecktimer:start()
+    myimapcheck:buttons(awful.util.table.join(
+        awful.button({}, 0,
+
+            function()
+                helpers.run_in_terminal(app)
+            end)
+    ))
+
+    return myimapcheck
+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..78cac9b
--- /dev/null
@@ -0,0 +1,24 @@
+
+--[[
+                                                   
+     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",
+    terminal = "xterm" -- X default
+}
+
+return setmetatable(widgets, { __index = wrequire })
diff --git a/widgets/maildir.lua b/widgets/maildir.lua
new file mode 100644 (file)
index 0000000..b5437bd
--- /dev/null
@@ -0,0 +1,129 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2010-2012, Peter Hofmann              
+                                                  
+--]]
+
+local markup          = require("lain.util.markup")
+local run_in_terminal = require("lain.helpers").run_in_terminal
+
+local awful           = require("awful")
+local beautiful       = require("beautiful")
+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 = {}
+
+function worker(args)
+    local args = args or {}
+    local mailpath = args.mailpath or os.getenv("HOME") .. "/Mail"
+    local ignore_boxes = args.ignore_boxes or {}
+    local refresh_timeout = args.refresh_timeout or 60
+    local header = args.header or " Mail "
+    local header_color = args.header_color or beautiful.fg_normal or "#FFFFFF"
+    local color_newmail = args.color_newmail or beautiful.fg_focus or "#FFFFFF"
+    local color_nomail = args.color_nomail or beautiful.fg_normal or "#FFFFFF"
+    local app = args.app or "mutt"
+    local shadow = args.shadow or false
+
+    local mymailcheck = wibox.widget.textbox()
+    local mymailcheckupdate = function()
+        -- Find pathes to mailboxes.
+        local p = io.popen("find " .. mailpath ..
+                           " -mindepth 1 -maxdepth 1 -type d" ..
+                           " -not -name .git")
+        local boxes = {}
+        local line = ""
+        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)
+
+        local newmail = ""
+        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
+
+        if count == 1 then
+            -- it will be only executed once
+            for box, number in pairs(boxes)
+            do  -- it's useless to show only INBOX(x)
+                if box == "INBOX" then newmail = number end
+            end
+        end
+
+        if newmail == ""
+        then
+            if shadow
+            then
+                mymailcheck:set_text('')
+            else
+                myimapcheck:set_markup(markup(color_nomail, " no mail "))
+            end
+        else
+            myimapcheck:set_markup(markup(header_color, header) ..
+                                   markup(color_newmail, newmail) .. " ")
+        end
+    end
+
+    local mymailchecktimer = timer({ timeout = refresh_timeout })
+    mymailchecktimer:connect_signal("timeout", mymailcheckupdate)
+    mymailchecktimer:start()
+    mymailcheck:buttons(awful.util.table.join(
+        awful.button({}, 0,
+            function()
+                run_in_terminal(app)
+            end)
+    ))
+
+    return mymailcheck
+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..09be00f
--- /dev/null
@@ -0,0 +1,87 @@
+
+--[[
+                                                           
+     Licensed under GNU General Public License v2          
+      * (c) 2013,      Luke Bonham                         
+      * (c) 2010-2012, Peter Hofmann                       
+      * (c) 2010,      Adrian C.      <anrxc@sysphere.org> 
+      * (c) 2009,      Lucas de Vries <lucas@glacicle.com> 
+                                                           
+--]]
+
+local markup          = require("lain.util.markup")
+local run_in_terminal = require("lain.helpers").run_in_terminal
+
+local beautiful       = require("beautiful")
+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 = {}
+
+function worker(args)
+    local args = args or {}
+    local refresh_timeout = args.refresh_timeout or 10
+    local show_swap = args.show_swap or false
+    local show_total = args.show_total or false
+    local header = args.header or " Mem "
+    local header_color = args.header or beautiful.fg_normal or "#FFFFFF"
+    local color = args.color or beautiful.fg_focus or "#FFFFFF"
+    local footer = args.footer or "MB"
+
+    local widg = wibox.widget.textbox()
+
+    local upd = function()
+        local mem = {}
+        for line in io.lines("/proc/meminfo")
+        do
+            for k, v in string.gmatch(line, "([%a]+):[%s]+([%d]+).+")
+            do
+                if     k == "MemTotal"  then mem.total = math.floor(v / 1024)
+                elseif k == "MemFree"   then mem.free  = math.floor(v / 1024)
+                elseif k == "Buffers"   then mem.buf   = math.floor(v / 1024)
+                elseif k == "Cached"    then mem.cache = math.floor(v / 1024)
+                elseif k == "SwapTotal" then mem.swap  = math.floor(v / 1024)
+                elseif k == "SwapFree"  then mem.swapf = math.floor(v / 1024)
+                end
+            end
+        end
+
+        used = mem.total - (mem.free + mem.buf + mem.cache)
+        swapused = mem.swap - mem.swapf
+
+        if show_total
+        then
+            local fmt = "%" .. string.len(mem.total) .. ".0f/%.0f"
+            widg:set_markup(markup(header_color, header) ..
+                            markup(color, string.format(fmt, used, mem.total) .. footer .. " "))
+        else
+            widg:set_markup(markup(header_color, header) ..
+                            markup(color, used .. footer .. " "))
+        end
+
+        if show_swap
+        then
+            widg:set_markup(widg._layout.text .. ' ('
+                            .. string.format('%.0f '.. footer, swapused)
+                            .. ') ')
+        end
+    end
+
+    local tmr = timer({ timeout = refresh_timeout })
+    tmr:connect_signal("timeout", upd)
+    tmr:start()
+    tmr:emit_signal("timeout")
+
+    return widg
+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..dcb7101
--- /dev/null
@@ -0,0 +1,138 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013, Luke Bonham                     
+      * (c) 2010, Adrian C. <anrxc@sysphere.org>  
+                                                  
+--]]
+
+local markup       = require("lain.util.markup")
+local helpers      = require("lain.helpers")
+
+local awful        = require("awful")
+local beautiful    = require("beautiful")
+local naughty      = require("naughty")
+local wibox        = require("wibox")
+
+local io           = io
+local os           = { execute  = os.execute,
+                       getenv   = os.getenv }
+local string       = { gmatch   = string.gmatch }
+
+local setmetatable = setmetatable
+
+-- MPD infos
+-- lain.widgets.mpd
+local mpd = { id = nil }
+
+function worker(args)
+    local args = args or {}
+    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 refresh_timeout = args.refresh_timeout or 1
+    local notify_timeout = args.notify_timeout or 5
+    local color_artist = args.color_artist or beautiful.fg_normal or "#FFFFFF"
+    local color_song = args.color_song or beautiful.fg_focus or "#FFFFFF"
+    local spr = args.spr or ""
+    local musicplr = args.musicplr or "ncmpcpp"
+    local shadow = args.shadow or false
+
+    local mpdcover = helpers.scripts_dir .. "mpdcover"
+    local mpdh = "telnet://"..host..":"..port
+    local echo = "echo 'password "..password.."\nstatus\ncurrentsong\nclose'"
+
+    local mympd = wibox.widget.textbox()
+
+    helpers.set_map("current mpd track", nil)
+
+    local mympdupdate = function()
+        local function set_nompd()
+            if shadow
+            then
+                mympd:set_text('')
+            else
+                mympd:set_markup(markup(color_artist, " mpd "), markup(color_song , "off "))
+            end
+        end
+
+        local mpd_state  = {
+            ["{state}"]  = "N/A",
+            ["{file}"]   = "N/A",
+            ["{Artist}"] = "N/A",
+            ["{Title}"]  = "N/A",
+            ["{Album}"]  = "N/A",
+            ["{Date}"]   = "N/A"
+        }
+
+        -- Get data from MPD server
+        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_state["{"..k.."}"] = v
+                elseif k == "file"   then mpd_state["{"..k.."}"] = v
+                elseif k == "Artist" then mpd_state["{"..k.."}"] = awful.util.escape(v)
+                elseif k == "Title"  then mpd_state["{"..k.."}"] = awful.util.escape(v)
+                elseif k == "Album"  then mpd_state["{"..k.."}"] = awful.util.escape(v)
+                elseif k == "Date"   then mpd_state["{"..k.."}"] = awful.util.escape(v)
+                end
+            end
+        end
+
+        f:close()
+
+        if mpd_state["{state}"] == "play"
+        then
+            if mpd_state["{Title}"] ~= helpers.get_map("current mpd track")
+            then
+                helpers.set_map("current mpd track", mpd_state["{Title}"])
+                os.execute(mpdcover .. " '" .. music_dir .. "' '"
+                           .. mpd_state["{file}"] .. "'")
+                mpd.id = naughty.notify({
+                    title = "Now playing",
+                    text = mpd_state["{Artist}"] .. " ("   ..
+                           mpd_state["{Album}"]  .. ") - " ..
+                           mpd_state["{Date}"]   .. "\n"   ..
+                           mpd_state["{Title}"],
+                    icon = "/tmp/mpdcover.png",
+                    fg = beautiful.fg_focus or "#FFFFFF",
+                    bg = beautiful.bg_normal or "#000000" ,
+                    timeout = notify_timeout,
+                    replaces_id = mpd.id
+                }).id
+            end
+            mympd:set_markup(markup(color_artist, " " .. mpd_state["{Artist}"])
+                             .. spr ..
+                             markup(color_song, " " .. mpd_state["{Title}"] .. " "))
+        elseif mpd_state["{state}"] == "pause"
+        then
+            mympd:set_markup(markup(color_artist, " mpd")
+                             .. spr ..
+                             markup(color_song, " paused "))
+        else
+            helpers.set_map("current mpd track", nil)
+                       set_nompd()
+             end
+    end
+
+    local mympdtimer = timer({ timeout = refresh_timeout })
+    mympdtimer:connect_signal("timeout", mympdupdate)
+    mympdtimer:start()
+    mympdtimer:emit_signal("timeout")
+
+    mympd:buttons(awful.util.table.join(
+        awful.button({}, 0,
+            function()
+                helpers.run_in_terminal(musicplr)
+            end)
+    ))
+
+    local mpd_out = { widget = mympd, notify = mympdupdate }
+
+    return setmetatable(mpd_out, { __index = mpd_out.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..f361146
--- /dev/null
@@ -0,0 +1,153 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2010-2012, Peter Hofmann              
+                                                  
+--]]
+
+local markup       = require("lain.util.markup")
+local helpers      = require("lain.helpers")
+
+local awful        = require("awful")
+local beautiful    = require("beautiful")
+local wibox        = require("wibox")
+
+local io           = io
+local tostring     = tostring
+local string       = { format = string.format }
+
+local setmetatable = setmetatable
+
+-- Network infos
+-- lain.widgets.net
+local net = {
+    send = "0",
+    recv = "0",
+    last_t = {},
+    last_r = {}
+}
+
+local unit = {
+    ["b"] = 1,
+    ["kb"] = 1024,
+    ["mb"] = 1024^2,
+    ["gb"] = 1024^3
+}
+
+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
+
+function worker(args)
+    local args = args or {}
+    local iface = args.iface or net.get_device()
+    local delta = args.refresh_timeout or 2
+    local unit = args.unit or unit["kb"]
+    local spr = args.spr or " "
+    local header = args.header or iface
+    local header_color = args.header_color or beautiful.fg_normal or "#FFFFFF"
+    local color_up = args.color_up or beautiful.fg_focus or header_color
+    local color_down = args.color_down or beautiful.fg_focus or header_color
+    local app = args.app or "sudo wifi-menu"
+
+    helpers.set_map(iface, true)
+    helpers.set_map("carrier", 0)
+
+    local mynet = wibox.widget.textbox()
+
+    local mynetupdate = function()
+        if iface == "" then
+            iface = net.get_device()
+            header = iface
+        end
+
+        local carrier = helpers.first_line('/sys/class/net/' .. iface ..
+                                           '/carrier') or ""
+        local state = helpers.first_line('/sys/class/net/' .. iface ..
+                                           '/operstate')
+        local now_t = helpers.first_line('/sys/class/net/' .. iface ..
+                                           '/statistics/tx_bytes')
+        local now_r = helpers.first_line('/sys/class/net/' .. iface ..
+                                           '/statistics/rx_bytes')
+        local text = '<span color="' .. header_color .. '">' .. header .. '</span> '
+
+        if carrier ~= "1"
+        then
+            if helpers.get_map(iface)
+            then
+                n_title = iface
+                if n_title == "" then
+                    n_title = "network"
+                    header = "Net"
+                end
+                naughty.notify({ title = n_title, text = "no carrier",
+                                 timeout = 7,
+                                 position = "top_left",
+                                 icon = beautiful.lain_no_net_notify or
+                                        helpers.icons_dir .. "no_net.png",
+                                 fg = beautiful.fg_focus or "#FFFFFF" })
+
+                mynet:set_markup(markup(header_color, header) .. markup(color_up, " Off"))
+                helpers.set_map(iface, false)
+            end
+            return
+        else
+            helpers.set_map(iface, true)
+        end
+
+        if state == 'down' or not now_t or not now_r
+        then
+            mynet:set_markup(' ' .. text .. '-' .. ' ')
+            return
+        end
+
+        if net.last_t[iface] and net.last_t[iface]
+        then
+            net.send = tostring((now_t - net.last_t[iface]) / delta / unit)
+            net.recv = tostring((now_r - net.last_r[iface]) / delta / unit)
+
+            text = text
+                   .. '<span color="' .. color_up .. '">'
+                   .. string.format('%.1f', net.send)
+                   .. '</span>'
+                   ..  spr
+                   .. '<span color="' .. color_down .. '">'
+                   .. string.format('%.1f', net.recv)
+                   .. '</span>'
+
+            mynet:set_markup(' ' .. text .. ' ')
+        else
+            mynet:set_markup(' ' .. text .. '-' .. ' ')
+        end
+
+        net.last_t[iface] = now_t
+        net.last_r[iface] = now_r
+    end
+
+    local mynettimer = timer({ timeout = delta })
+    mynettimer:connect_signal("timeout", mynetupdate)
+    mynettimer:start()
+    mynettimer:emit_signal("timeout")
+
+    mynet:buttons(awful.util.table.join(
+            awful.button({}, 0, function()
+                helpers.run_in_terminal(app)
+                mynetupdate()
+            end)))
+
+    net.widget = mynet
+
+    return setmetatable(net, { __index = 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..8583ccb
--- /dev/null
@@ -0,0 +1,70 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013,      Luke Bonham                
+      * (c) 2010-2012, Peter Hofmann              
+                                                  
+--]]
+
+local markup       = require("lain.util.markup")
+local helpers      = require("lain.helpers")
+
+local awful        = require("awful")
+local beautiful    = require("beautiful")
+local wibox        = require("wibox")
+
+local io           = io
+local string       = { format = string.format,
+                       match  = string.match }
+
+local setmetatable = setmetatable
+
+-- System load
+-- lain.widgets.sysload
+local sysload = {}
+
+function worker(args)
+    local args = args or {}
+    local refresh_timeout = args.refresh_timeout or 5
+    local show_all = args.show_all or false
+    local header = args.header or " Load "
+    local header_color = args.header_color or beautiful.fg_normal or "#FFFFFF"
+    local color = args.color or beautiful.fg_focus or header_color
+    local app = args.app or "top"
+
+    local mysysload = wibox.widget.textbox()
+
+    local mysysloadupdate = function()
+        local f = io.open("/proc/loadavg")
+        local ret = f:read("*all")
+        f:close()
+
+        if show_all
+        then
+            local a, b, c = string.match(ret, "([^%s]+) ([^%s]+) ([^%s]+)")
+            mysysload:set_text(string.format("%s %s %s", a, b, c))
+        else
+            local a = string.match(ret, "([^%s]+) ")
+            mysysload:set_text(string.format("%s", a))
+        end
+        mysysload:set_markup(markup(header_color, header) ..
+                             markup(color, mysysload._layout.text .. " "))
+    end
+
+    local mysysloadtimer = timer({ timeout = refresh_timeout })
+    mysysloadtimer:connect_signal("timeout", mysysloadupdate)
+    mysysloadtimer:start()
+    mysysloadtimer:emit_signal("timeout")
+
+    mysysload:buttons(awful.util.table.join(
+        awful.button({}, 0,
+            function()
+                helpers.run_in_terminal(app)
+            end)
+    ))
+
+    return mysysload
+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..301bc1c
--- /dev/null
@@ -0,0 +1,52 @@
+
+--[[
+                                                  
+     Licensed under GNU General Public License v2 
+      * (c) 2013, Luke Bonham                     
+                                                  
+--]]
+
+local markup       = require("lain.util.markup")
+
+local beautiful    = require("beautiful")
+local wibox        = require("wibox")
+
+local io           = io
+local tonumber     = tonumber
+
+local setmetatable = setmetatable
+
+-- coretemp
+-- lain.widgets.temp
+local temp = {}
+
+function worker(args)
+    local args = args or {}
+    local refresh_timeout = args.refresh_timeout or 5
+    local header = args.header or " Temp "
+    local header_color = args.header_color or beautiful.fg_normal or "#FFFFFF"
+    local color = args.color or beautiful.fg_focus or header_color
+    local footer = args.footer or "C "
+
+    local mytemp = wibox.widget.textbox()
+
+    local mytempupdate = function()
+        local f = io.open("/sys/class/thermal/thermal_zone0/temp")
+        local ret = f:read("*all")
+        f:close()
+
+        ret = tonumber(ret) / 1000
+
+        mytemp:set_markup(markup(header_color, header) ..
+                          markup(color, ret .. footer))
+    end
+
+    local mytemptimer = timer({ timeout = refresh_timeout })
+    mytemptimer:connect_signal("timeout", mytempupdate)
+    mytemptimer:start()
+    mytemptimer:emit_signal("timeout")
+
+    return mytemp
+end
+
+return setmetatable(temp, { __call = function(_, ...) return worker(...) end })
diff --git a/widgets/yawn/README.rst b/widgets/yawn/README.rst
new file mode 100644 (file)
index 0000000..d067db3
--- /dev/null
@@ -0,0 +1,133 @@
+=========================================
+Yahoo's Awesome (WM) Weather Notification
+=========================================
+
+----------------
+Lain integration
+----------------
+
+:Author: Luke Bonham <dada [at] archlinux [dot] info>
+:License: WTFPLv2_
+:Version: 2.0-git
+
+Description
+-----------
+
+Yawn is a module for Awesome WM providing brief and compact
+weather notification via Naughty and Yahoo! Weather API.
+
+Originally a port of perceptive_, it became a completely new module after various improvements and style changes.
+
+-----
+Usage
+-----
+
+You can ``register`` Yawn to get a set of widgets, or ``attach`` it to
+an existent widget.
+
+register
+^^^^^^^^
+
+Call: ::
+
+    lain.widgets.yawn(id, args)
+
+Arguments:
+
+``id``
+    An integer that defines the WOEID code of your city.
+    To obtain it you can google 'yahoo weather %CITYNAME%' and follow the first link.
+    It will look like::
+
+        http://weather.yahoo.com/united-states/california/san-diego-2487889/
+
+    and the last number in that link will be the ID you need.
+``args``
+    An optional table which can contain the following settings:
+        ``u``
+            Units. Type: string. Possible values: "c" (Celsius), "f" (Fahrenheit). Default: "c".
+
+        ``toshow``
+            What to show. Type: string. Possible values: "units", "forecast", "both".
+            Default: "forecast".
+
+        ``units_color``
+            Color of units text. Type: string. Possible values: hexadecimal color
+            codes.
+
+        ``forecast_color``
+            Color of forecast text. Type: string. Possible values: hexadecimal color
+            codes.
+
+        ``notification_color``
+            Color of notification text. Type: string. Possible values: hexadecimal color
+            codes.
+
+        ``spr``
+            A separator. Type: string. You can define it when ``toshow`` is set to "both".
+
+        ``footer``
+            A footer. Type: string. You can define it when ``toshow`` is set to
+            "both".
+
+The function creates an imagebox icon and a textbox widget. Add them to you wibox like this: ::
+
+    right_layout:add(lain.widgets.yawn.icon)
+    right_layout:add(lain.widgets.yawn.widget)
+
+Hovering over ``yawn.icon`` will display the notification.
+
+attach
+^^^^^^
+
+Call: ::
+
+    lain.widgets.yawn.attach(widget, id, args)
+
+Arguments:
+
+``widget``
+    The widget which you want to attach yawn to.
+``id``
+    same as in ``register``
+``args``
+    same as in ``register``
+
+Hovering over ``widget`` will display the notification.
+
+--------------
+Popup shortcut
+--------------
+
+You can also create a keybinding for the weather popup like this: ::
+
+    globalkeys = awful.util.table.join(
+        ...
+        awful.key( { "Mod1" }, "w", function () lain.widgets.yawn.show(5) end )
+        ...
+
+where ``show`` argument is an integer defining timeout seconds.
+
+------------
+Localization
+------------
+
+Default language is English, but Yawn can be localized.
+Move to ``localizations`` subdirectory and fill ``localization_template``.
+
+Once you're done, rename it like your locale id. In my case: ::
+
+    $ lua
+    Lua 5.2.2  Copyright (C) 1994-2013 Lua.org, PUC-Rio
+    > print(os.getenv("LANG"):match("(%S*$*)[.]"))
+    it_IT
+    >
+
+hence I named my file "it_IT" (Italian localization).
+
+**NOTE:** If you create a localization, feel free to send me! I will add it.
+
+.. _WTFPLv2: http://www.wtfpl.net
+.. _perceptive: https://github.com/ioga/perceptive
+.. _Tamsyn: http://www.fial.com/~scott/tamsyn-font/
+.. _Rainbow: https://github.com/copycat-killer/awesome-copycats>
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/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/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/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/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/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/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/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/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..f248e25
--- /dev/null
@@ -0,0 +1,227 @@
+
+--[[
+                                               
+     Yahoo's Awesome (WM) Weather Notification 
+                                               
+     Licensed under WTFPL v2                   
+      * (c) 2013, Luke Bonham                  
+                                               
+--]]
+
+local markup       = require("lain.util.markup")
+
+local beautiful    = require("beautiful")
+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
+
+-- yawn integration
+-- https://github.com/copycat-killer/yawn
+-- lain.widgets.yawn
+local yawn =
+{
+    units    = "",
+    forecast = "",
+    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           = {}
+local update_timer       = nil
+
+local function fetch_weather(args)
+    local toshow = args.toshow or "forecast"
+    local spr = args.spr or " "
+    local footer = args.footer or ""
+
+    local url = api_url .. units_set .. city_id
+    local f = io.popen("curl --connect-timeout 1 -fsm 2 '"
+                       .. url .. "'" )
+    local text = f:read("*all")
+    io.close(f)
+
+    -- In case of no connection or invalid city ID
+    -- widgets won't display
+    if text == "" or text:match("City not found")
+    then
+        sky = icon_path .. "na.png"
+        if text == "" then
+            weather_data = "Service not available at the moment."
+            return "N/A"
+        else
+            weather_data = "City not found!\n" ..
+                           "Are you sure " .. city_id ..
+                           " is your Yahoo city ID?"
+            return "?"
+        end
+    end
+
+    -- Processing raw data
+    weather_data = text:gsub("<.->", "")
+    weather_data = weather_data:match("Current Conditions:.-Full")
+    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)
+    local forecast = now:sub(1, now:find(",")-1)
+    local 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
+    f = io.popen(sky)
+    if f == nil then
+        sky = icon_path .. "na.png"
+    else
+        io.close(f)
+    end
+
+    -- Localization
+    local f = io.open(localizations_path .. language, "r")
+    if language:find("en_") == nil and f ~= nil
+    then
+        io.close(f)
+        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
+    forecast = weather_data:match(": %S+"):gsub(": ", ""):gsub(",", "")
+    yawn.forecast = markup(yawn.forecast_color, markup.font(beautiful.font, forecast))
+    yawn.units = markup(yawn.units_color, markup.font(beautiful.font, units))
+    yawn.icon:set_image(sky)
+
+    if toshow == "forecast" then
+        return yawn.forecast
+    elseif toshow == "units" then
+        return yawn.units
+    else -- "both"
+        return yawn.forecast .. spr
+               .. yawn.units .. footer
+    end
+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 == "?"
+    then
+        if update_timer ~= nil
+        then
+            update_timer:emit_signal("timeout")
+        else
+            fetch_weather(settings)
+        end
+    end
+
+    yawn.hide()
+
+    notification = naughty.notify({
+        text = weather_data,
+        icon = sky,
+        timeout = t_out,
+        fg = yawn.notification_color
+    })
+end
+
+function yawn.register(id, args)
+    local args = args or {}
+
+    settings = { args.toshow, args.spr, args.footer }
+
+    yawn.units_color        = args.units_color or
+                              beautiful.fg_normal or "#FFFFFF"
+    yawn.forecast_color     = args.forecast_color or
+                              yawn.units_color
+    yawn.notification_color = args.notification_color or
+                              beautiful.fg_focus or "#FFFFFF"
+
+    if args.u == "f" then units_set = '?u=f&w=' end
+
+    city_id = id
+
+    update_timer = timer({ timeout = 600 }) -- 10 mins
+    update_timer:connect_signal("timeout", function()
+        yawn.widget:set_markup(fetch_weather(settings))
+    end)
+    update_timer:start()
+    update_timer:emit_signal("timeout")
+
+    yawn.icon:connect_signal("mouse::enter", function()
+        yawn.show(0)
+    end)
+    yawn.icon:connect_signal("mouse::leave", function()
+        yawn.hide()
+    end)
+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..0b74b60
--- /dev/null
@@ -0,0 +1,57 @@
+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|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
+Drizzle|Pioggerella
+Freezing Drizzle|Pioggerella Congelantesi
+Hail|Grandine
+Foggy|Nebbia
+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..98d527d
--- /dev/null
@@ -0,0 +1,57 @@
+Now:|
+Sun:|
+Mon:|
+Tue:|
+Wed:|
+Thu:|
+Fri:|
+Sat:|
+Mostly Sunny|
+Sunny|
+Sun|
+Rain/Thunder|
+Isolated Thunderstorms|
+Scattered Thunderstorms|
+Thundershowers|
+Thunderstorms|
+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|
+Drizzle|
+Freezing Drizzle|
+Hail|
+Foggy|
+Haze|
+Light|