+++ /dev/null
-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.
-
- Next generation Vain
- Copyright (C) 2013 Luke Bonham
-
- 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.
--- /dev/null
+Lain
+====
+
+---------------------------------------------
+Layouts, widgets and utilities for Awesome WM
+---------------------------------------------
+
+Author: Luke Bonham <dada [at] archlinux [dot] info>
+Version: 1.0-git
+License: GNU-GPLv2_
+Source: https://github.com/copycat-killer/vain
+
+Based on a port of awesome-vain_, this costantly evolving module provides new layouts, a set of widgets and utility functions in order to improve Awesome usability and
+configurability.
+
+Read the wiki_ for all the info.
+
+Screenshots
+-----------
+
+.. image:: http://i.imgur.com/8D9A7lW.png
+.. image:: http://i.imgur.com/9Iv3OR3.png
+.. image:: http://i.imgur.com/STCPcaJ.png
+
+.. _GNU-GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
+.. _awesome-vain: https://github.com/vain/awesome-vain
+.. _wiki: https://github.com/copycat-killer/lain/wiki
--- /dev/null
+
+--[[
+
+ 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
--- /dev/null
+
+--[[
+
+ 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
--- /dev/null
+
+--[[
+
+ 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
--- /dev/null
+
+--[[
+
+ 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
--- /dev/null
+
+--[[
+
+ 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
--- /dev/null
+
+--[[
+
+ 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 })
--- /dev/null
+
+--[[
+
+ 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
--- /dev/null
+
+--[[
+
+ 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
--- /dev/null
+
+--[[
+
+ 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
--- /dev/null
+
+--[[
+
+ 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
--- /dev/null
+#!/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:])
--- /dev/null
+#!/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 (<SPAN CLASS=\"titleblue\">*</SPAN>)" \
+ " - %% Free (<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
+}'
--- /dev/null
+#!/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
--- /dev/null
+
+--[[
+
+ 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 })
--- /dev/null
+
+--[[
+
+ Licensed under MIT License
+ * (c) 2013, Luke Bonham
+ * (c) 2009, Uli Schlachter
+ * (c) 2009, Majic
+
+--]]
+
+local beautiful = require("beautiful")
+local tostring = tostring
+local setmetatable = setmetatable
+
+-- Lain markup util submodule
+-- lain.util.markup
+local markup = {}
+
+local fg = {}
+local bg = {}
+
+-- Convenience tags.
+function markup.bold(text) return '<b>' .. tostring(text) .. '</b>' end
+function markup.italic(text) return '<i>' .. tostring(text) .. '</i>' end
+function markup.strike(text) return '<s>' .. tostring(text) .. '</s>' end
+function markup.underline(text) return '<u>' .. tostring(text) .. '</u>' end
+function markup.monospace(text) return '<tt>' .. tostring(text) .. '</tt>' end
+function markup.big(text) return '<big>' .. tostring(text) .. '</big>' end
+function markup.small(text) return '<small>' .. tostring(text) .. '</small>' end
+
+-- Set the font.
+function markup.font(font, text)
+ return '<span font="' .. tostring(font) .. '">' .. tostring(text) ..'</span>'
+end
+
+-- Set the foreground.
+function fg.color(color, text)
+ return '<span foreground="' .. tostring(color) .. '">' .. tostring(text) .. '</span>'
+end
+
+-- Set the background.
+function bg.color(color, text)
+ return '<span background="' .. tostring(color) .. '">' .. tostring(text) .. '</span>'
+end
+
+-- Context: focus
+function fg.focus(text) return fg.color(beautiful.fg_focus, text) end
+function bg.focus(text) return bg.color(beautiful.bg_focus, text) end
+function markup.focus(text) return bg.focus(fg.focus(text)) end
+
+-- Context: normal
+function fg.normal(text) return fg.color(beautiful.fg_normal, text) end
+function bg.normal(text) return bg.color(beautiful.bg_normal, text) end
+function markup.normal(text) return bg.normal(fg.normal(text)) end
+
+-- Context: urgent
+function fg.urgent(text) return fg.color(beautiful.fg_urgent, text) end
+function bg.urgent(text) return bg.color(beautiful.bg_urgent, text) end
+function markup.urgent(text) return bg.urgent(fg.urgent(text)) end
+
+markup.fg = fg
+markup.bg = bg
+
+-- link markup.{fg,bg}(...) calls to markup.{fg,bg}.color(...)
+setmetatable(markup.fg, { __call = function(_, ...) return markup.fg.color(...) end })
+setmetatable(markup.bg, { __call = function(_, ...) return markup.bg.color(...) end })
+
+-- link markup(...) calls to markup.fg.color(...)
+return setmetatable(markup, { __call = function(_, ...) return markup.fg.color(...) end })
--- /dev/null
+
+--[[
+
+ 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 })
--- /dev/null
+
+--[[
+
+ 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 })
--- /dev/null
+
+--[[
+
+ 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 })
--- /dev/null
+
+--[[
+
+ 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 })
--- /dev/null
+
+--[[
+
+ 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 })
--- /dev/null
+
+--[[
+
+ 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 })
--- /dev/null
+
+--[[
+
+ 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 })
--- /dev/null
+
+--[[
+
+ 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 })
--- /dev/null
+
+--[[
+
+ 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 })
--- /dev/null
+
+--[[
+
+ 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 })
--- /dev/null
+
+--[[
+
+ 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 })
--- /dev/null
+
+--[[
+
+ 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 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 app = args.app 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 = 6,
+ 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(app)
+ 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 })
--- /dev/null
+
+--[[
+
+ 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 = {}
+}
+
+net.units = {
+ ["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 units = args.units or net.units["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 "#FFFFFF"
+ local color_down = args.color_down or beautiful.fg_focus or "#FFFFFF"
+ 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 / units)
+ net.recv = tostring((now_r - net.last_r[iface]) / delta / units)
+
+ 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 })
--- /dev/null
+
+--[[
+
+ 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 "#FFFFFF"
+ 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 })
--- /dev/null
+
+--[[
+
+ 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 })
--- /dev/null
+=========================================
+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>
--- /dev/null
+DayClear.png
\ No newline at end of file
--- /dev/null
+Rain.png
\ No newline at end of file
--- /dev/null
+Hail.png
\ No newline at end of file
--- /dev/null
+NightClear.png
\ No newline at end of file
--- /dev/null
+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>
--- /dev/null
+SnowShowers.png
\ No newline at end of file
--- /dev/null
+SnowShowers.png
\ No newline at end of file
--- /dev/null
+BlowingSnow.png
\ No newline at end of file
--- /dev/null
+
+--[[
+
+ 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 })
--- /dev/null
+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
--- /dev/null
+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|