From: luke bonham Date: Sat, 7 Sep 2013 14:54:01 +0000 (+0200) Subject: readme updated X-Git-Url: https://git.madduck.net/etc/awesome.git/commitdiff_plain/00a2951166f61cbbfe1d6e042fe0e53427c001cd?hp=32a5f32dc47cf930a72e14962caf8f624fbb9d4c readme updated --- diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 8c97c99..0000000 --- a/LICENSE +++ /dev/null @@ -1,339 +0,0 @@ -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. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..de2ed65 --- /dev/null +++ b/README.rst @@ -0,0 +1,27 @@ +Lain +==== + +--------------------------------------------- +Layouts, widgets and utilities for Awesome WM +--------------------------------------------- + +Author: Luke Bonham +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 diff --git a/helpers.lua b/helpers.lua new file mode 100644 index 0000000..7677768 --- /dev/null +++ b/helpers.lua @@ -0,0 +1,90 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2010-2012, Peter Hofmann + * (c) 2010, Adrian C. + +--]] + +local awful = require("awful") +local debug = require("debug") +local pairs = pairs +local rawget = rawget + +-- Lain helper functions for internal use +-- lain.helpers +local helpers = {} + +helpers.lain_dir = debug.getinfo(1, 'S').source:match[[^@(.*/).*$]] +helpers.icons_dir = helpers.lain_dir .. 'icons/' +helpers.scripts_dir = helpers.lain_dir .. 'scripts/' + +-- {{{ Modules loader + +function helpers.wrequire(table, key) + local module = rawget(table, key) + return module or require(table._NAME .. '.' .. key) +end + +-- }}} + +-- {{{ +-- If lain.terminal is a string, e.g. "xterm", then "xterm -e " .. cmd is +-- run. But if lain.terminal is a function, then terminal(cmd) is run. + +function helpers.run_in_terminal(cmd) + if type(terminal) == "function" + then + terminal(cmd) + elseif type(terminal) == "string" + then + awful.util.spawn(terminal .. ' -e ' .. cmd) + end +end + +-- }}} + +-- {{{ Format units to one decimal point + +function helpers.uformat(array, key, value, unit) + for u, v in pairs(unit) do + array["{"..key.."_"..u.."}"] = string.format("%.1f", value/v) + end + return array +end + +-- }}} + +-- {{{ Read the first line of a file or return nil. + +function helpers.first_line(f) + local fp = io.open(f) + if not fp + then + return nil + end + + local content = fp:read("*l") + fp:close() + return content +end + +-- }}} + +-- {{{ A map utility + +helpers.map_table = {} + +function helpers.set_map(element, value) + helpers.map_table[element] = value +end + +function helpers.get_map(element) + return helpers.map_table[element] +end + +-- }}} + +return helpers diff --git a/icons/cal/white/1.png b/icons/cal/white/1.png new file mode 100644 index 0000000..90b696c Binary files /dev/null and b/icons/cal/white/1.png differ diff --git a/icons/cal/white/10.png b/icons/cal/white/10.png new file mode 100644 index 0000000..b462ffb Binary files /dev/null and b/icons/cal/white/10.png differ diff --git a/icons/cal/white/11.png b/icons/cal/white/11.png new file mode 100644 index 0000000..cf43296 Binary files /dev/null and b/icons/cal/white/11.png differ diff --git a/icons/cal/white/12.png b/icons/cal/white/12.png new file mode 100644 index 0000000..42cf092 Binary files /dev/null and b/icons/cal/white/12.png differ diff --git a/icons/cal/white/13.png b/icons/cal/white/13.png new file mode 100644 index 0000000..37db670 Binary files /dev/null and b/icons/cal/white/13.png differ diff --git a/icons/cal/white/14.png b/icons/cal/white/14.png new file mode 100644 index 0000000..0188504 Binary files /dev/null and b/icons/cal/white/14.png differ diff --git a/icons/cal/white/15.png b/icons/cal/white/15.png new file mode 100644 index 0000000..64418a6 Binary files /dev/null and b/icons/cal/white/15.png differ diff --git a/icons/cal/white/16.png b/icons/cal/white/16.png new file mode 100644 index 0000000..8b86700 Binary files /dev/null and b/icons/cal/white/16.png differ diff --git a/icons/cal/white/17.png b/icons/cal/white/17.png new file mode 100644 index 0000000..033b5ff Binary files /dev/null and b/icons/cal/white/17.png differ diff --git a/icons/cal/white/18.png b/icons/cal/white/18.png new file mode 100644 index 0000000..817c426 Binary files /dev/null and b/icons/cal/white/18.png differ diff --git a/icons/cal/white/19.png b/icons/cal/white/19.png new file mode 100644 index 0000000..0e6dafc Binary files /dev/null and b/icons/cal/white/19.png differ diff --git a/icons/cal/white/2.png b/icons/cal/white/2.png new file mode 100644 index 0000000..b93789a Binary files /dev/null and b/icons/cal/white/2.png differ diff --git a/icons/cal/white/20.png b/icons/cal/white/20.png new file mode 100644 index 0000000..3d8d7c6 Binary files /dev/null and b/icons/cal/white/20.png differ diff --git a/icons/cal/white/21.png b/icons/cal/white/21.png new file mode 100644 index 0000000..79a74f3 Binary files /dev/null and b/icons/cal/white/21.png differ diff --git a/icons/cal/white/22.png b/icons/cal/white/22.png new file mode 100644 index 0000000..e8845ce Binary files /dev/null and b/icons/cal/white/22.png differ diff --git a/icons/cal/white/23.png b/icons/cal/white/23.png new file mode 100644 index 0000000..a8d4dfb Binary files /dev/null and b/icons/cal/white/23.png differ diff --git a/icons/cal/white/24.png b/icons/cal/white/24.png new file mode 100644 index 0000000..1a3b38a Binary files /dev/null and b/icons/cal/white/24.png differ diff --git a/icons/cal/white/25.png b/icons/cal/white/25.png new file mode 100644 index 0000000..c3621b7 Binary files /dev/null and b/icons/cal/white/25.png differ diff --git a/icons/cal/white/26.png b/icons/cal/white/26.png new file mode 100644 index 0000000..f26731b Binary files /dev/null and b/icons/cal/white/26.png differ diff --git a/icons/cal/white/27.png b/icons/cal/white/27.png new file mode 100644 index 0000000..e4dde77 Binary files /dev/null and b/icons/cal/white/27.png differ diff --git a/icons/cal/white/28.png b/icons/cal/white/28.png new file mode 100644 index 0000000..b924c22 Binary files /dev/null and b/icons/cal/white/28.png differ diff --git a/icons/cal/white/29.png b/icons/cal/white/29.png new file mode 100644 index 0000000..e9a74f8 Binary files /dev/null and b/icons/cal/white/29.png differ diff --git a/icons/cal/white/3.png b/icons/cal/white/3.png new file mode 100644 index 0000000..1124271 Binary files /dev/null and b/icons/cal/white/3.png differ diff --git a/icons/cal/white/30.png b/icons/cal/white/30.png new file mode 100644 index 0000000..8147d78 Binary files /dev/null and b/icons/cal/white/30.png differ diff --git a/icons/cal/white/31.png b/icons/cal/white/31.png new file mode 100644 index 0000000..a1be3e8 Binary files /dev/null and b/icons/cal/white/31.png differ diff --git a/icons/cal/white/4.png b/icons/cal/white/4.png new file mode 100644 index 0000000..16713bc Binary files /dev/null and b/icons/cal/white/4.png differ diff --git a/icons/cal/white/5.png b/icons/cal/white/5.png new file mode 100644 index 0000000..466aa71 Binary files /dev/null and b/icons/cal/white/5.png differ diff --git a/icons/cal/white/6.png b/icons/cal/white/6.png new file mode 100644 index 0000000..a1c9798 Binary files /dev/null and b/icons/cal/white/6.png differ diff --git a/icons/cal/white/7.png b/icons/cal/white/7.png new file mode 100644 index 0000000..e971951 Binary files /dev/null and b/icons/cal/white/7.png differ diff --git a/icons/cal/white/8.png b/icons/cal/white/8.png new file mode 100644 index 0000000..909b726 Binary files /dev/null and b/icons/cal/white/8.png differ diff --git a/icons/cal/white/9.png b/icons/cal/white/9.png new file mode 100644 index 0000000..dc636c4 Binary files /dev/null and b/icons/cal/white/9.png differ diff --git a/icons/layout/default/browse.png b/icons/layout/default/browse.png new file mode 100644 index 0000000..c063ca0 Binary files /dev/null and b/icons/layout/default/browse.png differ diff --git a/icons/layout/default/browsew.png b/icons/layout/default/browsew.png new file mode 100644 index 0000000..9b0b2b3 Binary files /dev/null and b/icons/layout/default/browsew.png differ diff --git a/icons/layout/default/cascade.png b/icons/layout/default/cascade.png new file mode 100644 index 0000000..292a057 Binary files /dev/null and b/icons/layout/default/cascade.png differ diff --git a/icons/layout/default/cascadebrowse.png b/icons/layout/default/cascadebrowse.png new file mode 100644 index 0000000..2f12ada Binary files /dev/null and b/icons/layout/default/cascadebrowse.png differ diff --git a/icons/layout/default/cascadebrowsew.png b/icons/layout/default/cascadebrowsew.png new file mode 100644 index 0000000..c46b48b Binary files /dev/null and b/icons/layout/default/cascadebrowsew.png differ diff --git a/icons/layout/default/cascadew.png b/icons/layout/default/cascadew.png new file mode 100644 index 0000000..da64bd6 Binary files /dev/null and b/icons/layout/default/cascadew.png differ diff --git a/icons/layout/default/centerwork.png b/icons/layout/default/centerwork.png new file mode 100644 index 0000000..826b331 Binary files /dev/null and b/icons/layout/default/centerwork.png differ diff --git a/icons/layout/default/centerworkw.png b/icons/layout/default/centerworkw.png new file mode 100644 index 0000000..fcfa7e3 Binary files /dev/null and b/icons/layout/default/centerworkw.png differ diff --git a/icons/layout/default/gimp.png b/icons/layout/default/gimp.png new file mode 100644 index 0000000..207ab40 Binary files /dev/null and b/icons/layout/default/gimp.png differ diff --git a/icons/layout/default/gimpw.png b/icons/layout/default/gimpw.png new file mode 100644 index 0000000..35fd10e Binary files /dev/null and b/icons/layout/default/gimpw.png differ diff --git a/icons/layout/default/termfair.png b/icons/layout/default/termfair.png new file mode 100644 index 0000000..06226c1 Binary files /dev/null and b/icons/layout/default/termfair.png differ diff --git a/icons/layout/default/termfairw.png b/icons/layout/default/termfairw.png new file mode 100644 index 0000000..0a8b576 Binary files /dev/null and b/icons/layout/default/termfairw.png differ diff --git a/icons/layout/zenburn/browse.png b/icons/layout/zenburn/browse.png new file mode 100644 index 0000000..f0bd177 Binary files /dev/null and b/icons/layout/zenburn/browse.png differ diff --git a/icons/layout/zenburn/cascade.png b/icons/layout/zenburn/cascade.png new file mode 100644 index 0000000..532842d Binary files /dev/null and b/icons/layout/zenburn/cascade.png differ diff --git a/icons/layout/zenburn/cascadebrowse.png b/icons/layout/zenburn/cascadebrowse.png new file mode 100644 index 0000000..87be658 Binary files /dev/null and b/icons/layout/zenburn/cascadebrowse.png differ diff --git a/icons/layout/zenburn/centerwork.png b/icons/layout/zenburn/centerwork.png new file mode 100644 index 0000000..6a2cecc Binary files /dev/null and b/icons/layout/zenburn/centerwork.png differ diff --git a/icons/layout/zenburn/gimp.png b/icons/layout/zenburn/gimp.png new file mode 100644 index 0000000..ded8e62 Binary files /dev/null and b/icons/layout/zenburn/gimp.png differ diff --git a/icons/layout/zenburn/termfair.png b/icons/layout/zenburn/termfair.png new file mode 100644 index 0000000..b7d5880 Binary files /dev/null and b/icons/layout/zenburn/termfair.png differ diff --git a/icons/mail.png b/icons/mail.png new file mode 100644 index 0000000..60ba6e0 Binary files /dev/null and b/icons/mail.png differ diff --git a/icons/no_net.png b/icons/no_net.png new file mode 100755 index 0000000..1a3e8a8 Binary files /dev/null and b/icons/no_net.png differ diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..3abf9f6 --- /dev/null +++ b/init.lua @@ -0,0 +1,20 @@ + +--[[ + + Lain + Layouts, widgets and utilities for Awesome WM + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2010-2012, Peter Hofmann + +--]] + +local lain = +{ + layout = require("lain.layout"), + util = require("lain.util"), + widgets = require("lain.widgets") +} + +return lain diff --git a/layout/cascade.lua b/layout/cascade.lua new file mode 100644 index 0000000..cabacef --- /dev/null +++ b/layout/cascade.lua @@ -0,0 +1,65 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2010-2012, Peter Hofmann + +--]] + +local tag = require("awful.tag") + +local cascade = +{ + name = "cascade", + nmaster = 0, + offset_x = 32, + offset_y = 8 +} + +function cascade.arrange(p) + + -- Cascade windows. + + -- Screen. + local wa = p.workarea + local cls = p.clients + + -- Opening a new window will usually force all existing windows to + -- get resized. This wastes a lot of CPU time. So let's set a lower + -- bound to "how_many": This wastes a little screen space but you'll + -- get a much better user experience. + local t = tag.selected(p.screen) + local num_c + if cascade.nmaster > 0 + then + num_c = cascade.nmaster + else + num_c = tag.getnmaster(t) + end + + local how_many = #cls + if how_many < num_c + then + how_many = num_c + end + + local current_offset_x = cascade.offset_x * (how_many - 1) + local current_offset_y = cascade.offset_y * (how_many - 1) + + -- Iterate. + for i = 1,#cls,1 + do + local c = cls[i] + local g = {} + + g.x = wa.x + (how_many - i) * cascade.offset_x + g.y = wa.y + (i - 1) * cascade.offset_y + g.width = wa.width - current_offset_x + g.height = wa.height - current_offset_y + + c:geometry(g) + end +end + +return cascade diff --git a/layout/cascadetile.lua b/layout/cascadetile.lua new file mode 100644 index 0000000..a94bbed --- /dev/null +++ b/layout/cascadetile.lua @@ -0,0 +1,159 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2010-2012, Peter Hofmann + +--]] + +local tag = require("awful.tag") +local beautiful = require("beautiful") +local tonumber = tonumber + +local cascadetile = +{ + name = "cascadetile", + nmaster = 0, + ncol = 0, + mwfact = 0, + offset_x = 5, + offset_y = 32, + extra_padding = 0 +} + +function cascadetile.arrange(p) + + -- Layout with one fixed column meant for a master window. Its + -- width is calculated according to mwfact. Other clients are + -- cascaded or "tabbed" in a slave column on the right. + + -- It's a bit hard to demonstrate the behaviour with ASCII-images... + -- + -- (1) (2) (3) (4) + -- +-----+---+ +-----+---+ +-----+---+ +-----+---+ + -- | | | | | | | | | | | 4 | + -- | | | | | 2 | | | 3 | | | | + -- | 1 | | -> | 1 | | -> | 1 | | -> | 1 +---+ + -- | | | | +---+ | +---+ | | 3 | + -- | | | | | | | | 2 | | |---| + -- | | | | | | | |---| | | 2 | + -- | | | | | | | | | | |---| + -- +-----+---+ +-----+---+ +-----+---+ +-----+---+ + + -- A useless gap (like the dwm patch) can be defined with + -- beautiful.useless_gap_width. + local useless_gap = tonumber(beautiful.useless_gap_width) + if useless_gap == nil + then + useless_gap = 0 + end + + -- Screen. + local wa = p.workarea + local cls = p.clients + + -- Width of main column? + local t = tag.selected(p.screen) + local mwfact + if cascadetile.mwfact > 0 + then + mwfact = cascadetile.mwfact + else + mwfact = tag.getmwfact(t) + end + + -- Make slave windows overlap main window? Do this if ncol is 1. + local overlap_main + if cascadetile.ncol > 0 + then + overlap_main = cascadetile.ncol + else + overlap_main = tag.getncol(t) + end + + -- Minimum space for slave windows? See cascade.lua. + local num_c + if cascadetile.nmaster > 0 + then + num_c = cascadetile.nmaster + else + num_c = tag.getnmaster(t) + end + + local how_many = #cls - 1 + if how_many < num_c + then + how_many = num_c + end + local current_offset_x = cascadetile.offset_x * (how_many - 1) + local current_offset_y = cascadetile.offset_y * (how_many - 1) + + if #cls > 0 + then + -- Main column, fixed width and height. + local c = cls[#cls] + local g = {} + local mainwid = wa.width * mwfact + local slavewid = wa.width - mainwid + + if overlap_main == 1 + then + g.width = wa.width + + -- The size of the main window may be reduced a little bit. + -- This allows you to see if there are any windows below the + -- main window. + -- This only makes sense, though, if the main window is + -- overlapping everything else. + g.width = g.width - cascadetile.extra_padding + else + g.width = mainwid + end + + g.height = wa.height + g.x = wa.x + g.y = wa.y + if useless_gap > 0 + then + -- Reduce width once and move window to the right. Reduce + -- height twice, however. + g.width = g.width - useless_gap + g.height = g.height - 2 * useless_gap + g.x = g.x + useless_gap + g.y = g.y + useless_gap + + -- When there's no window to the right, add an additional + -- gap. + if overlap_main == 1 + then + g.width = g.width - useless_gap + end + end + c:geometry(g) + + -- Remaining clients stacked in slave column, new ones on top. + if #cls > 1 + then + for i = (#cls - 1),1,-1 + do + c = cls[i] + g = {} + g.width = slavewid - current_offset_x + g.height = wa.height - current_offset_y + g.x = wa.x + mainwid + (how_many - i) * cascadetile.offset_x + g.y = wa.y + (i - 1) * cascadetile.offset_y + if useless_gap > 0 + then + g.width = g.width - 2 * useless_gap + g.height = g.height - 2 * useless_gap + g.x = g.x + useless_gap + g.y = g.y + useless_gap + end + c:geometry(g) + end + end + end +end + +return cascadetile diff --git a/layout/centerwork.lua b/layout/centerwork.lua new file mode 100644 index 0000000..2035c65 --- /dev/null +++ b/layout/centerwork.lua @@ -0,0 +1,122 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2010-2012, Peter Hofmann + +--]] + +local awful = require("awful") +local beautiful = require("beautiful") +local tonumber = tonumber +local math = { floor = math.floor } + +local centerwork = +{ + name = "centerwork", + top_left = 0, + top_right = 1, + bottom_left = 2, + bottom_right = 3 +} + +function centerwork.arrange(p) + -- A useless gap (like the dwm patch) can be defined with + -- beautiful.useless_gap_width . + local useless_gap = tonumber(beautiful.useless_gap_width) + if useless_gap == nil + then + useless_gap = 0 + end + + -- Screen. + local wa = p.workarea + local cls = p.clients + + -- Width of main column? + local t = awful.tag.selected(p.screen) + local mwfact = awful.tag.getmwfact(t) + + if #cls > 0 + then + -- Main column, fixed width and height. + local c = cls[#cls] + local g = {} + local mainwid = math.floor(wa.width * mwfact) + local slavewid = wa.width - mainwid + local slaveLwid = math.floor(slavewid / 2) + local slaveRwid = slavewid - slaveLwid + local slaveThei = math.floor(wa.height / 2) + local slaveBhei = wa.height - slaveThei + + g.height = wa.height - 2 * useless_gap + g.width = mainwid + g.x = wa.x + slaveLwid + g.y = wa.y + useless_gap + + c:geometry(g) + + -- Auxiliary windows. + if #cls > 1 + then + local at = 0 + for i = (#cls - 1),1,-1 + do + -- It's all fixed. If there are more than 5 clients, + -- those additional clients will float. This is + -- intentional. + if at == 4 + then + break + end + + c = cls[i] + g = {} + + if at == centerwork.top_left + then + -- top left + g.x = wa.x + useless_gap + g.y = wa.y + useless_gap + g.width = slaveLwid - 2 * useless_gap + g.height = slaveThei - useless_gap + elseif at == centerwork.top_right + then + -- top right + g.x = wa.x + slaveLwid + mainwid + useless_gap + g.y = wa.y + useless_gap + g.width = slaveRwid - 2 * useless_gap + g.height = slaveThei - useless_gap + elseif at == centerwork.bottom_left + then + -- bottom left + g.x = wa.x + useless_gap + g.y = wa.y + slaveThei + useless_gap + g.width = slaveLwid - 2 * useless_gap + g.height = slaveBhei - 2 * useless_gap + elseif at == centerwork.bottom_right + then + -- bottom right + g.x = wa.x + slaveLwid + mainwid + useless_gap + g.y = wa.y + slaveThei + useless_gap + g.width = slaveRwid - 2 * useless_gap + g.height = slaveBhei - 2 * useless_gap + end + + c:geometry(g) + + at = at + 1 + end + + -- Set remaining clients to floating. + for i = (#cls - 1 - 4),1,-1 + do + c = cls[i] + awful.client.floating.set(c, true) + end + end + end +end + +return centerwork diff --git a/layout/init.lua b/layout/init.lua new file mode 100644 index 0000000..d79679a --- /dev/null +++ b/layout/init.lua @@ -0,0 +1,20 @@ + +--[[ + + Lain + Layouts, widgets and utilities for Awesome WM + + Layouts section + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2010-2012, Peter Hofmann + +--]] + +local wrequire = require("lain.helpers").wrequire +local setmetatable = setmetatable + +local layout = { _NAME = "lain.layout" } + +return setmetatable(layout, { __index = wrequire }) diff --git a/layout/termfair.lua b/layout/termfair.lua new file mode 100644 index 0000000..62eef9a --- /dev/null +++ b/layout/termfair.lua @@ -0,0 +1,160 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2010-2012, Peter Hofmann + +--]] + +local tag = require("awful.tag") +local beautiful = require("beautiful") +local math = { ceil = math.ceil, + floor = math.floor, + max = math.max } +local tonumber = tonumber + +local termfair = +{ + name = "termfair", + + -- You can set the number of columns and rows, + -- -- otherwise they are read from awful.tag + nmaster = 0, -- columns + ncol = 0 -- rows +} + +function termfair.arrange(p) + -- Layout with fixed number of vertical columns (read from nmaster). + -- New windows align from left to right. When a row is full, a now + -- one above it is created. Like this: + + -- (1) (2) (3) + -- +---+---+---+ +---+---+---+ +---+---+---+ + -- | | | | | | | | | | | | + -- | 1 | | | -> | 2 | 1 | | -> | 3 | 2 | 1 | -> + -- | | | | | | | | | | | | + -- +---+---+---+ +---+---+---+ +---+---+---+ + + -- (4) (5) (6) + -- +---+---+---+ +---+---+---+ +---+---+---+ + -- | 4 | | | | 5 | 4 | | | 6 | 5 | 4 | + -- +---+---+---+ -> +---+---+---+ -> +---+---+---+ + -- | 3 | 2 | 1 | | 3 | 2 | 1 | | 3 | 2 | 1 | + -- +---+---+---+ +---+---+---+ +---+---+---+ + + -- A useless gap (like the dwm patch) can be defined with + -- beautiful.useless_gap_width. + local useless_gap = tonumber(beautiful.useless_gap_width) + if useless_gap == nil + then + useless_gap = 0 + end + + -- Screen. + local wa = p.workarea + local cls = p.clients + + -- How many vertical columns? + local t = tag.selected(p.screen) + local num_x + if termfair.nmaster ~= 0 + then + num_x = termfair.nmaster + else + num_x = tag.getnmaster(t) + end + + -- Do at least "desired_y" rows. + local desired_y + if termfair.ncol ~= 0 + then + desired_y = termfair.ncol + else + desired_y = tag.getncol(t) + end + + if #cls > 0 + then + local num_y = math.max(math.ceil(#cls / num_x), desired_y) + local cur_num_x = num_x + local at_x = 0 + local at_y = 0 + local remaining_clients = #cls + local width = math.floor(wa.width / num_x) + local height = math.floor(wa.height / num_y) + + -- We start the first row. Left-align by limiting the number of + -- available slots. + if remaining_clients < num_x + then + cur_num_x = remaining_clients + end + + -- Iterate in reversed order. + for i = #cls,1,-1 + do + -- Get x and y position. + local c = cls[i] + local this_x = cur_num_x - at_x - 1 + local this_y = num_y - at_y - 1 + + -- Calc geometry. + local g = {} + if this_x == (num_x - 1) + then + g.width = wa.width - (num_x - 1) * width + else + g.width = width + end + if this_y == (num_y - 1) + then + g.height = wa.height - (num_y - 1) * height + else + g.height = height + end + g.x = wa.x + this_x * width + g.y = wa.y + this_y * height + if useless_gap > 0 + then + -- Top and left clients are shrinked by two steps and + -- get moved away from the border. Other clients just + -- get shrinked in one direction. + if this_x == 0 + then + g.width = g.width - 2 * useless_gap + g.x = g.x + useless_gap + else + g.width = g.width - useless_gap + end + + if this_y == 0 + then + g.height = g.height - 2 * useless_gap + g.y = g.y + useless_gap + else + g.height = g.height - useless_gap + end + end + c:geometry(g) + remaining_clients = remaining_clients - 1 + + -- Next grid position. + at_x = at_x + 1 + if at_x == num_x + then + -- Row full, create a new one above it. + at_x = 0 + at_y = at_y + 1 + + -- We start a new row. Left-align. + if remaining_clients < num_x + then + cur_num_x = remaining_clients + end + end + end + end +end + +return termfair diff --git a/layout/uselessfair.lua b/layout/uselessfair.lua new file mode 100644 index 0000000..92e8d45 --- /dev/null +++ b/layout/uselessfair.lua @@ -0,0 +1,122 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2012, Josh Komoroske + * (c) 2010-2012, Peter Hofmann + +--]] + +local beautiful = require("beautiful") +local ipairs = ipairs +local math = { ceil = math.ceil, sqrt = math.sqrt } +local tonumber = tonumber + +local uselessfair = {} + +local function fair(p, orientation) + -- A useless gap (like the dwm patch) can be defined with + -- beautiful.useless_gap_width. + local useless_gap = tonumber(beautiful.useless_gap_width) + if useless_gap == nil + then + useless_gap = 0 + end + + local wa = p.workarea + local cls = p.clients + + if #cls > 0 then + local cells = math.ceil(math.sqrt(#cls)) + local strips = math.ceil(#cls / cells) + + local cell = 0 + local strip = 0 + for k, c in ipairs(cls) do + local g = {} + -- Save actual grid index for use in the useless_gap + -- routine. + local this_x = 0 + local this_y = 0 + if ( orientation == "east" and #cls > 2 ) + or ( orientation == "south" and #cls <= 2 ) then + if #cls < (strips * cells) and strip == strips - 1 then + g.width = wa.width / (cells - ((strips * cells) - #cls)) + else + g.width = wa.width / cells + end + g.height = wa.height / strips + + this_x = cell + this_y = strip + + g.x = wa.x + cell * g.width + g.y = wa.y + strip * g.height + + else + if #cls < (strips * cells) and strip == strips - 1 then + g.height = wa.height / (cells - ((strips * cells) - #cls)) + else + g.height = wa.height / cells + end + g.width = wa.width / strips + + this_x = strip + this_y = cell + + g.x = wa.x + strip * g.width + g.y = wa.y + cell * g.height + end + + -- Useless gap. + if useless_gap > 0 + then + -- Top and left clients are shrinked by two steps and + -- get moved away from the border. Other clients just + -- get shrinked in one direction. + if this_x == 0 + then + g.width = g.width - 2 * useless_gap + g.x = g.x + useless_gap + else + g.width = g.width - useless_gap + end + + if this_y == 0 + then + g.height = g.height - 2 * useless_gap + g.y = g.y + useless_gap + else + g.height = g.height - useless_gap + end + end + -- End of useless gap. + + c:geometry(g) + + cell = cell + 1 + if cell == cells then + cell = 0 + strip = strip + 1 + end + end + end +end + +--- Horizontal fair layout. +-- @param screen The screen to arrange. +uselessfair.horizontal = {} +uselessfair.horizontal.name = "uselessfairh" +function uselessfair.horizontal.arrange(p) + return fair(p, "east") +end + +-- Vertical fair layout. +-- @param screen The screen to arrange. +uselessfair.name = "uselessfair" +function uselessfair.arrange(p) + return fair(p, "south") +end + +return uselessfair diff --git a/layout/uselesspiral.lua b/layout/uselesspiral.lua new file mode 100644 index 0000000..695728c --- /dev/null +++ b/layout/uselesspiral.lua @@ -0,0 +1,112 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2009 Uli Schlachter + * (c) 2008 Julien Danjolu + +--]] + +local beautiful = require("beautiful") +local ipairs = ipairs +local tonumber = tonumber + +local uselesspiral = {} + +local function spiral(p, spiral) + -- A useless gap (like the dwm patch) can be defined with + -- beautiful.useless_gap_width. + local useless_gap = tonumber(beautiful.useless_gap_width) + if useless_gap == nil + then + useless_gap = 0 + end + + local wa = p.workarea + local cls = p.clients + local n = #cls + + local static_wa = wa + + for k, c in ipairs(cls) do + if k < n then + if k % 2 == 0 then + wa.height = wa.height / 2 + else + wa.width = wa.width / 2 + end + end + + if k % 4 == 0 and spiral then + wa.x = wa.x - wa.width + elseif k % 2 == 0 or + (k % 4 == 3 and k < n and spiral) then + wa.x = wa.x + wa.width + end + + if k % 4 == 1 and k ~= 1 and spiral then + wa.y = wa.y - wa.height + elseif k % 2 == 1 and k ~= 1 or + (k % 4 == 0 and k < n and spiral) then + wa.y = wa.y + wa.height + end + + local wa2 = {} + wa2.x = wa.x + wa2.y = wa.y + wa2.height = wa.height + wa2.width = wa.width + + -- Useless gap. + if useless_gap > 0 + then + -- Top and left clients are shrinked by two steps and + -- get moved away from the border. Other clients just + -- get shrinked in one direction. + + top = false + left = false + + if wa2.y == static_wa.y then + top = true + end + + if wa2.x == static_wa.x then + left = true + end + + if top then + wa2.height = wa2.height - 2 * useless_gap + wa2.y = wa2.y + useless_gap + else + wa2.height = wa2.height - useless_gap + end + + if left then + wa2.width = wa2.width - 2 * useless_gap + wa2.x = wa2.x + useless_gap + else + wa2.width = wa2.width - useless_gap + end + end + -- End of useless gap. + + c:geometry(wa2) + end +end + +--- Dwindle layout +uselesspiral.dwindle = {} +uselesspiral.dwindle.name = "uselessdwindle" +function uselesspiral.dwindle.arrange(p) + return spiral(p, false) +end + +--- Spiral layout +uselesspiral.name = "uselesspiral" +function uselesspiral.arrange(p) + return spiral(p, true) +end + +return uselesspiral diff --git a/layout/uselesstile.lua b/layout/uselesstile.lua new file mode 100644 index 0000000..b82c97e --- /dev/null +++ b/layout/uselesstile.lua @@ -0,0 +1,232 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2009 Donald Ephraim Curtis + * (c) 2008 Julien Danjolu + +--]] + +local tag = require("awful.tag") +local beautiful = require("beautiful") +local ipairs = ipairs +local math = { floor = math.floor, + max = math.max, + min = math.min } +local tonumber = tonumber + +local uselesstile = {} + +local function tile_group(cls, wa, orientation, fact, group) + -- A useless gap (like the dwm patch) can be defined with + -- beautiful.useless_gap_width . + local useless_gap = tonumber(beautiful.useless_gap_width) + if useless_gap == nil + then + useless_gap = 0 + end + + -- get our orientation right + local height = "height" + local width = "width" + local x = "x" + local y = "y" + if orientation == "top" or orientation == "bottom" then + height = "width" + width = "height" + x = "y" + y = "x" + end + + -- make this more generic (not just width) + available = wa[width] - (group.coord - wa[x]) + + -- find our total values + local total_fact = 0 + local min_fact = 1 + local size = group.size + for c = group.first,group.last do + -- determine the width/height based on the size_hint + local i = c - group.first +1 + local size_hints = cls[c].size_hints + local size_hint = size_hints["min_"..width] or size_hints["base_"..width] or 0 + size_hint = size_hint + cls[c].border_width*2 + size = math.max(size_hint, size) + + -- calculate the height + if not fact[i] then + fact[i] = min_fact + else + min_fact = math.min(fact[i],min_fact) + end + total_fact = total_fact + fact[i] + end + size = math.min(size, available) + + local coord = wa[y] + local geom = {} + local used_size = 0 + local unused = wa[height] + local stat_coord = wa[x] + --stat_coord = size + for c = group.first,group.last do + local i = c - group.first +1 + geom[width] = size + geom[height] = math.floor(unused * fact[i] / total_fact) + geom[x] = group.coord + geom[y] = coord + + coord = coord + geom[height] + unused = unused - geom[height] + total_fact = total_fact - fact[i] + used_size = math.max(used_size, geom[width]) + + -- Useless gap + if useless_gap > 0 + then + -- Top and left clients are shrinked by two steps and + -- get moved away from the border. Other clients just + -- get shrinked in one direction. + + top = false + left = false + + if geom[y] == wa[y] then + top = true + end + + if geom[x] == 0 or geom[x] == wa[x] then + left = true + end + + if top then + geom[height] = geom[height] - 2 * useless_gap + geom[y] = geom[y] + useless_gap + else + geom[height] = geom[height] - useless_gap + end + + if left then + geom[width] = geom[width] - 2 * useless_gap + geom[x] = geom[x] + useless_gap + else + geom[width] = geom[width] - useless_gap + end + end + -- End of useless gap. + + geom = cls[c]:geometry(geom) + end + + return used_size +end + +local function tile(param, orientation) + local t = tag.selected(param.screen) + orientation = orientation or "right" + + -- this handles are different orientations + local height = "height" + local width = "width" + local x = "x" + local y = "y" + if orientation == "top" or orientation == "bottom" then + height = "width" + width = "height" + x = "y" + y = "x" + end + + local cls = param.clients + local nmaster = math.min(tag.getnmaster(t), #cls) + local nother = math.max(#cls - nmaster,0) + + local mwfact = tag.getmwfact(t) + local wa = param.workarea + local ncol = tag.getncol(t) + + local data = tag.getdata(t).windowfact + + if not data then + data = {} + tag.getdata(t).windowfact = data + end + + local coord = wa[x] + local place_master = true + if orientation == "left" or orientation == "top" then + -- if we are on the left or top we need to render the other windows first + place_master = false + end + + -- this was easier than writing functions because there is a lot of data we need + for d = 1,2 do + if place_master and nmaster > 0 then + local size = wa[width] + if nother > 0 then + size = math.min(wa[width] * mwfact, wa[width] - (coord - wa[x])) + end + if not data[0] then + data[0] = {} + end + coord = coord + tile_group(cls, wa, orientation, data[0], {first=1, last=nmaster, coord = coord, size = size}) + end + + if not place_master and nother > 0 then + local last = nmaster + + -- we have to modify the work area size to consider left and top views + local wasize = wa[width] + if nmaster > 0 and (orientation == "left" or orientation == "top") then + wasize = wa[width] - wa[width]*mwfact + end + for i = 1,ncol do + -- Try to get equal width among remaining columns + local size = math.min( (wasize - (coord - wa[x])) / (ncol - i + 1) ) + local first = last + 1 + last = last + math.floor((#cls - last)/(ncol - i + 1)) + -- tile the column and update our current x coordinate + if not data[i] then + data[i] = {} + end + coord = coord + tile_group(cls, wa, orientation, data[i], { first = first, last = last, coord = coord, size = size }) + end + end + place_master = not place_master + end + +end + +uselesstile.right = {} +uselesstile.right.name = "uselesstile" +uselesstile.right.arrange = tile + +--- The main tile algo, on left. +-- @param screen The screen number to tile. +uselesstile.left = {} +uselesstile.left.name = "uselesstileleft" +function uselesstile.left.arrange(p) + return tile(p, "left") +end + +--- The main tile algo, on bottom. +-- @param screen The screen number to tile. +uselesstile.bottom = {} +uselesstile.bottom.name = "uselesstilebottom" +function uselesstile.bottom.arrange(p) + return tile(p, "bottom") +end + +--- The main tile algo, on top. +-- @param screen The screen number to tile. +uselesstile.top = {} +uselesstile.top.name = "uselesstiletop" +function uselesstile.top.arrange(p) + return tile(p, "top") +end + +uselesstile.arrange = uselesstile.right.arrange +uselesstile.name = uselesstile.right.name + +return uselesstile diff --git a/scripts/checkmail b/scripts/checkmail new file mode 100755 index 0000000..67c5206 --- /dev/null +++ b/scripts/checkmail @@ -0,0 +1,104 @@ +#!/usr/bin/python + +# Simple email checker +# +# Wrote by copycat-killer on a rainy day of august 2013 +# to be used in Lain. +# +# https://github.com/copycat-killer/lain + +import sys, getopt, locale, imaplib + +def main(argv): + usage = "usage: checkmail -s -u -p [--port ] [--encoding ] [--cut]" + server = "" + user = "" + password = "" + port = 993 + cut = False + encoding = locale.getdefaultlocale()[1] + output = "" + + try: + opts, args = getopt.getopt(argv, "hs:u:p:", ["port=", "encoding=", "cut"]) + except getopt.GetoptError: + print(usage) + sys.exit(2) + + if len(argv) == 0: + print(usage) + sys.exit() + + for opt, arg in opts: + if opt == "-h": + print(usage) + sys.exit() + elif opt == "-s": + server = arg + elif opt == "-u": + user = arg + elif opt == "-p": + password = arg + elif opt == "--port": + port = int(arg) + elif opt == "--cut": + cut = True + elif opt == "--encoding": + encoding = arg + + try: + mail = imaplib.IMAP4_SSL(server, port) + mail.login(user, password) + except imaplib.IMAP4.error: + print("CheckMailError: invalid credentials") + sys.exit(2) + + status, counts = mail.status("Inbox","(MESSAGES UNSEEN)") + + unread = int(counts[0].split()[4][:-1]) + + if status == "OK" and unread: + mail.select("Inbox", readonly = 1) + ret, messages = mail.uid("search", None, "(UNSEEN)") + + if ret == "OK": + latest_email_uid = messages[0].split()[-1] + + ret_header, new_mail_header = mail.uid("fetch", latest_email_uid, + "(BODY.PEEK[HEADER.FIELDS (SUBJECT FROM)])") + ret_text, new_mail_text = mail.uid("fetch", latest_email_uid, "(BODY[TEXT])") + + if ret_header == "OK" and ret_text == "OK": + try: # not all the servers like this, that's why we try + mail.store(latest_email_uid, "-FLAGS", "\\Seen") + except imaplib.IMAP4.error: + # this simply means the server refused to + # toggle Seen flag from mail + print("[+Seen]\n") + + nm_header = new_mail_header[0][1].decode(encoding, "replace").strip() + nm_text = new_mail_text[0][1].decode(encoding, "replace").strip() + + if unread == 1: + print(user, "has 1 new message\n") + else: + print(user, "has", unread, "new messages\n") + nm_header.replace("From:", "Latest from:", 1) + + print(nm_header, "\n") + + if cut: + if len(nm_text) <= 100: + print(nm_text) + else: + print(nm_text[0:100]) + print("[...]") + else: + print(nm_text) + else: + print("No new messages") + + mail.logout() + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/scripts/dfs b/scripts/dfs new file mode 100755 index 0000000..1730b6e --- /dev/null +++ b/scripts/dfs @@ -0,0 +1,385 @@ +#!/bin/bash +# +# Adapted from Eridan's "fs" (cleanup, enhancements and switch to bash/Linux) +# JM, 10/12/2004 +# +# Integrated into Lain in september 2013 +# https://github.com/copycat-killer/lain + +# ------------------------------------------------------------------------- +# Decoding options +# ------------------------------------------------------------------------- +USAGE="Usage: $0 [-h(elp)] | [-n(arrow mode)] | [-w(eb output)]" + +NARROW_MODE=0 +WEB_OUTPUT=0 + +while [ $# -gt 0 ]; do +case "$1" in +"-h" ) +echo $USAGE +exit +;; +"-d" ) +DEBUG=1 +;; +"-n" ) +NARROW_MODE=1 +;; +"-w" ) +WEB_OUTPUT=1 +;; +* ) +echo $USAGE +exit +;; +esac +shift +done + +# ------------------------------------------------------------------------- +# Preparations +# ------------------------------------------------------------------------- +SYSTEM=`uname -s` +PATTERN="/" + +case "$SYSTEM" in +"Linux" ) +DF_COMMAND="/usr/bin/env df -k" +SORT_COMMAND="/usr/bin/env sort -k6" +AWK_COMMAND="/usr/bin/env awk" +;; +* ) +DF_COMMAND="/bin/df -k" +SORT_COMMAND="/usr/bin/sort -k6" +AWK_COMMAND="/usr/bin/env gawk" +;; +esac + +# ------------------------------------------------------------------------- +# Grabbing "df" result +# ------------------------------------------------------------------------- +DF_RESULT=`$DF_COMMAND` +if [ ! -z $DEBUG ]; then +echo "--> DF_RESULT:" +echo "$DF_RESULT" +echo "" +fi + +# ------------------------------------------------------------------------- +# Preprocessing "df" result, to join split logical lines +# ------------------------------------------------------------------------- +PREPROCESSING_RESULT=` \ + echo "$DF_RESULT" | $AWK_COMMAND -v PATTERN=$PATTERN \ + ' + NF == 1 { + printf ("%s", $0) + } + +NF == 5 { + printf ("%s\n", $0) +} + +NF > 6 { +} + +NF == 6 { + printf ("%s\n", $0) +}' +` +if [ ! -z $DEBUG ]; then +echo "--> PREPROCESSING_RESULT:" +echo "$PREPROCESSING_RESULT" +echo "" +fi + +SORTED_FILE_SYSTEMS_INFO=`echo "$PREPROCESSING_RESULT" | $SORT_COMMAND` + +if [ ! -z $DEBUG ]; then +echo "--> SORTED_FILE_SYSTEMS_INFO:" +echo "$SORTED_FILE_SYSTEMS_INFO" +echo "" +fi + +# ------------------------------------------------------------------------- +# Computing mount point max length +# ------------------------------------------------------------------------- +MOUNT_POINT_MAX_LENGTH=` \ + echo $SORTED_FILE_SYSTEMS_INFO | $AWK_COMMAND -v PATTERN=$PATTERN \ + ' + BEGIN { + mount_point_length_max = 15; + } + +END { + printf ("%d", mount_point_length_max); +} + +$0 ~ PATTERN { +# printf ("$6 = %s\n", $6); + + mount_point = $6; +# printf ("mount_point = %s\n", mount_point); + + mount_point_length = length (mount_point); +# printf ("mount_point_length = %d\n", mount_point_length); + + if (mount_point_length > mount_point_length_max) + mount_point_length_max = mount_point_length; +}' +` +if [ ! -z $DEBUG ]; then +echo "MOUNT_POINT_MAX_LENGTH: $MOUNT_POINT_MAX_LENGTH" +fi + +# ------------------------------------------------------------------------- +# Computing mount point data max size +# ------------------------------------------------------------------------- +MOUNT_POINT_MAX_SIZE=` \ + echo "$SORTED_FILE_SYSTEMS_INFO" | $AWK_COMMAND -v PATTERN=$PATTERN \ + ' + BEGIN { + mount_point_size_max = 0; + } + +END { + printf ("%d", mount_point_size_max); +} + +$0 ~ PATTERN { +# df -k shows k_bytes! +# printf ("$2 = %s\n", $2); + + mount_point_size = $2 * 1024; +# printf ("mount_point_size = %d\n", mount_point_size); + + if (mount_point_size > mount_point_size_max) + mount_point_size_max = mount_point_size; +}' +` +if [ ! -z $DEBUG ]; then +echo "MOUNT_POINT_MAX_SIZE: $MOUNT_POINT_MAX_SIZE" +fi + +# ------------------------------------------------------------------------- +# Let's go! +# ------------------------------------------------------------------------- +echo "$SORTED_FILE_SYSTEMS_INFO" | $AWK_COMMAND -v DEBUG=$DEBUG -v PATTERN=$PATTERN -v NARROW_MODE=$NARROW_MODE -v LEFT_COLUMN=$MOUNT_POINT_MAX_LENGTH -v MAX_SIZE=$MOUNT_POINT_MAX_SIZE -v SCALE=$SCALE -v WEB_OUTPUT=$WEB_OUTPUT \ + ' +# {printf ("$0 = %s\n", $0);} +# {printf ("$1 = %s\n", $1);} +# {printf ("PATTERN = %s\n", PATTERN);} +# {printf ("LEFT_COLUMN = %s\n", LEFT_COLUMN);} + + BEGIN { + k_bytes = 1024.0; + m_bytes = 1024.0 * k_bytes; + g_bytes = 1024.0 * m_bytes; + t_bytes = 1024.0 * g_bytes; + + if (WEB_OUTPUT) + { + all_stars = "**************************************************"; + current_date = strftime ("%d-%m-%Y @ %H:%M:%S", localtime (systime ())); + free_threshold = 10; # % + + printf ("\n"); + + printf ( \ + "\n" \ + "

%s -- STATUS OF ALCOR FILE SYSTEMS


\n", + current_date ) + + printf ("\n"); + + printf ( \ + "\n" \ + "\n" \ + "\n" \ + "\n" \ + "\n" \ + "\n" \ + "\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 ("
Mount point%% Usato (*)" \ + " - %% Free (*)%% UsatoSpazio liberoSpazio totale
\n"); + + printf ("\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 ("\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 ( \ + "%s%s\n", + substr (mount_point, 1, posGroup - 1), + class, + substr (mount_point, posGroup) ); + } + else + { + printf ("%s\n", mount_point); + } + + printf ( \ + "%s%s\n", + substr (all_stars, 1, stars_number), substr (all_stars, stars_number + 1, 49) ); + + if (percentage_free < free_threshold) + { + color_beginning = ""; + color_end = "" + } + else + { + color_beginning = ""; + color_end = "" + } + + if (total_size > 1 * t_bytes) + printf ( \ + "%s%3d%%%s%5.1f Tb%5.1f Tb\n", \ + color_beginning, percentage_occupied, color_end, free_size / t_bytes, total_size / t_bytes \ + ); + else if (total_size > 1 * g_bytes) + printf ( \ + "%s%3d%%%s%5.1f Gb%5.1f Gb\n", \ + color_beginning, percentage_occupied, color_end, free_size / g_bytes, total_size / g_bytes \ + ); + else if (total_size > 1 * m_byptes) + printf ( \ + "%s%3d%%%s%5.1f Mb%5.1f Mb\n", \ + color_beginning, percentage_occupied, color_end, free_size / m_bytes, total_size / m_bytes \ + ); + else + printf ( \ + "%s%3d%%%s%5.1f Kb%5.1f Kb\n", \ + color_beginning, percentage_occupied, color_end, free_size / k_bytes, total_size / k_bytes \ + ); + + printf ("\n"); + } + + else + { +# printf ("percentage_occupied = %d\n", percentage_occupied); +# printf ("percentage_free = %d\n", percentage_free); + + printf ("%-*s", LEFT_COLUMN + 2, mount_point); + if (NARROW_MODE) + printf ("\n%s", narrow_margin); + +# printf ("stars_number = %d\n", stars_number); + + printf ("|"); + for (i = 1; i <= stars_number; i++) + { + printf ("%s", "*"); + } + for (i = stars_number + 1; i <= 49; i++) + { + printf ("%s", "-"); + } + + + if (total_size > 1 * t_bytes) + printf ( \ + "| %3d%% %5.1f %5.1f Tb\n", \ + percentage_occupied, free_size / t_bytes, total_size / t_bytes \ + ); + else if (total_size > 1 * g_bytes) + printf ( \ + "| %3d%% %5.1f %5.1f Gb\n", \ + percentage_occupied, free_size / g_bytes, total_size / g_bytes \ + ); + else if (total_size > 1 * m_byptes) + printf ( \ + "| %3d%% %5.1f %5.1f Mb\n", \ + percentage_occupied, free_size / m_bytes, total_size / m_bytes \ + ); + else + printf ( \ + "| %3d%% %5.1f %5.1f Kb\n", \ + percentage_occupied, free_size / k_bytes, total_size / k_bytes \ + ); + } + } # if +}' diff --git a/scripts/mpdcover b/scripts/mpdcover new file mode 100755 index 0000000..38b43e9 --- /dev/null +++ b/scripts/mpdcover @@ -0,0 +1,64 @@ +#!/bin/bash +# +# A simple cover fetcher script for current playing song on mpd. +# +# Author : Wolfgang Mueller +# +# Adapted for Lain internal use. +# https://github.com/copycat-killer/lain +# +# You can use, edit and redistribute this script in any way you like. +# +# Dependencies: imagemagick. +# +# Usage: mpdcover + +# Configuration------------------------------------------------------- + +# Music directory +MUSIC_DIR=$1 + +# Song file +file=$2 + +# The default cover to use (optional) +DEFAULT_ART=$3 + +# Regex expression used for image search +IMG_REG="(front|cover|art|Folder|folder)\.(jpg|jpeg|png|gif)$" + +# Path of temporary resized cover +TEMP_PATH="/tmp/mpdcover.png" + +# Resize cover +COVER_RESIZE="100x100" + +# Thumbnail background (transparent) +COVER_BACKGROUND="none" + +#-------------------------------------------------------------------- + +# check if anything is playing at all +[[ -z $file ]] && exit 1 + +# Art directory +art="$MUSIC_DIR/${file%/*}" + +# find every file that matches IMG_REG set the first matching file to be the +# cover. +cover="$(find "$art/" -maxdepth 1 -type f | egrep -i -m1 "$IMG_REG")" + +# when no cover is found, use DEFAULT_ART as cover +cover="${cover:=$DEFAULT_ART}" + +# check if art is available +if [[ -n $cover ]]; then + if [[ -n $COVER_RESIZE ]]; then + convert "$cover" -thumbnail $COVER_RESIZE -gravity center -background "$COVER_BACKGROUND" -extent $COVER_RESIZE "$TEMP_PATH" + cover="$TEMP_PATH" + fi +else + rm $TEMP_PATH +fi + +exit 0 diff --git a/util/init.lua b/util/init.lua new file mode 100644 index 0000000..a44d52c --- /dev/null +++ b/util/init.lua @@ -0,0 +1,174 @@ + +--[[ + + Lain + Layouts, widgets and utilities for Awesome WM + + Utilities section + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2010-2012, Peter Hofmann + +--]] + +local awful = require("awful") +local beautiful = require("beautiful") +local math = { sqrt = math.sqrt } +local mouse = mouse +local pairs = pairs +local string = string +local client = client +local screen = screen +local tonumber = tonumber + +local wrequire = require("lain.helpers").wrequire +local setmetatable = setmetatable + +-- Lain utilities submodule +-- lain.util +local util = { _NAME = "lain.util" } + +-- Like awful.menu.clients, but only show clients of currently selected +-- tags. +function util.menu_clients_current_tags(menu, args) + -- List of currently selected tags. + local cls_tags = awful.tag.selectedlist(mouse.screen) + + -- Final list of menu items. + local cls_t = {} + + if cls_tags == nil + then + return nil + end + + -- For each selected tag get all clients of that tag and add them to + -- the menu. A click on a menu item will raise that client. + for i = 1,#cls_tags + do + local t = cls_tags[i] + local cls = t:clients() + + for k, c in pairs(cls) + do + cls_t[#cls_t + 1] = { awful.util.escape(c.name) or "", + function () + c.minimized = false + client.focus = c + c:raise() + end, + c.icon } + end + end + + -- No clients? Then quit. + if #cls_t <= 0 + then + return nil + end + + -- menu may contain some predefined values, otherwise start with a + -- fresh menu. + if not menu + then + menu = {} + end + + -- Set the list of items and show the menu. + menu.items = cls_t + local m = awful.menu.new(menu) + m:show(args) + return m +end + +-- Magnify a client: Set it to "float" and resize it. +function util.magnify_client(c) + awful.client.floating.set(c, true) + + local mg = screen[mouse.screen].geometry + local tag = awful.tag.selected(mouse.screen) + local mwfact = awful.tag.getmwfact(tag) + local g = {} + g.width = math.sqrt(mwfact) * mg.width + g.height = math.sqrt(mwfact) * mg.height + g.x = mg.x + (mg.width - g.width) / 2 + g.y = mg.y + (mg.height - g.height) / 2 + c:geometry(g) +end + +-- Read the nice value of pid from /proc. +local function get_nice_value(pid) + local n = first_line('/proc/' .. pid .. '/stat') + if n == nil + then + -- This should not happen. But I don't want to crash, either. + return 0 + end + + -- Remove pid and tcomm. This is necessary because tcomm may contain + -- nasty stuff such as whitespace or additional parentheses... + n = string.gsub(n, '.*%) ', '') + + -- Field number 17 now is the nice value. + fields = split(n, ' ') + return tonumber(fields[17]) +end + +-- To be used as a signal handler for "focus" +-- This requires beautiful.border_focus{,_highprio,_lowprio}. +function util.niceborder_focus(c) + local n = get_nice_value(c.pid) + if n == 0 + then + c.border_color = beautiful.border_focus + elseif n < 0 + then + c.border_color = beautiful.border_focus_highprio + else + c.border_color = beautiful.border_focus_lowprio + end +end + +-- To be used as a signal handler for "unfocus" +-- This requires beautiful.border_normal{,_highprio,_lowprio}. +function util.niceborder_unfocus(c) + local n = get_nice_value(c.pid) + if n == 0 + then + c.border_color = beautiful.border_normal + elseif n < 0 + then + c.border_color = beautiful.border_normal_highprio + else + c.border_color = beautiful.border_normal_lowprio + end +end + +-- Non-empty tag browsing +-- direction in {-1, 1} <-> {previous, next} non-empty tag +function util.tag_view_nonempty(direction, sc) + local s = sc or mouse.screen or 1 + local scr = screen[s] + + for i = 1, #tags[s] do + awful.tag.viewidx(direction,s) + if #awful.client.visible(s) > 0 then + return + end + end +end + +-- Dynamically rename the current tag you have focused. +function util.prompt_rename_tag(mypromptbox) + local tag = awful.tag.selected(mouse.screen) + awful.prompt.run({prompt="Rename tag: "}, mypromptbox[mouse.screen].widget, + function(text) + if text:len() > 0 then + tag.name = text + tag:emit_signal("property::name") + end + end) +end + +return setmetatable(util, { __index = wrequire }) diff --git a/util/markup.lua b/util/markup.lua new file mode 100644 index 0000000..dbb8529 --- /dev/null +++ b/util/markup.lua @@ -0,0 +1,69 @@ + +--[[ + + Licensed under MIT License + * (c) 2013, Luke Bonham + * (c) 2009, Uli Schlachter + * (c) 2009, Majic + +--]] + +local beautiful = require("beautiful") +local tostring = tostring +local setmetatable = setmetatable + +-- Lain markup util submodule +-- lain.util.markup +local markup = {} + +local fg = {} +local bg = {} + +-- Convenience tags. +function markup.bold(text) return '' .. tostring(text) .. '' end +function markup.italic(text) return '' .. tostring(text) .. '' end +function markup.strike(text) return '' .. tostring(text) .. '' end +function markup.underline(text) return '' .. tostring(text) .. '' end +function markup.monospace(text) return '' .. tostring(text) .. '' end +function markup.big(text) return '' .. tostring(text) .. '' end +function markup.small(text) return '' .. tostring(text) .. '' end + +-- Set the font. +function markup.font(font, text) + return '' .. tostring(text) ..'' +end + +-- Set the foreground. +function fg.color(color, text) + return '' .. tostring(text) .. '' +end + +-- Set the background. +function bg.color(color, text) + return '' .. tostring(text) .. '' +end + +-- Context: focus +function fg.focus(text) return fg.color(beautiful.fg_focus, text) end +function bg.focus(text) return bg.color(beautiful.bg_focus, text) end +function markup.focus(text) return bg.focus(fg.focus(text)) end + +-- Context: normal +function fg.normal(text) return fg.color(beautiful.fg_normal, text) end +function bg.normal(text) return bg.color(beautiful.bg_normal, text) end +function markup.normal(text) return bg.normal(fg.normal(text)) end + +-- Context: urgent +function fg.urgent(text) return fg.color(beautiful.fg_urgent, text) end +function bg.urgent(text) return bg.color(beautiful.bg_urgent, text) end +function markup.urgent(text) return bg.urgent(fg.urgent(text)) end + +markup.fg = fg +markup.bg = bg + +-- link markup.{fg,bg}(...) calls to markup.{fg,bg}.color(...) +setmetatable(markup.fg, { __call = function(_, ...) return markup.fg.color(...) end }) +setmetatable(markup.bg, { __call = function(_, ...) return markup.bg.color(...) end }) + +-- link markup(...) calls to markup.fg.color(...) +return setmetatable(markup, { __call = function(_, ...) return markup.fg.color(...) end }) diff --git a/widgets/alsa.lua b/widgets/alsa.lua new file mode 100644 index 0000000..7c26908 --- /dev/null +++ b/widgets/alsa.lua @@ -0,0 +1,103 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2010-2012, Peter Hofmann + * (c) 2010, Adrian C. + +--]] + +local markup = require("lain.util.markup") +local run_in_terminal = require("lain.helpers").run_in_terminal + +local awful = require("awful") +local beautiful = require("beautiful") +local wibox = require("wibox") + +local io = io +local string = { format = string.format, + match = string.match } + +local setmetatable = setmetatable + +-- ALSA volume infos +-- nain.widgets.alsa +local alsa = { + volume = 0, + mute = false, +} + +function worker(args) + local args = args or {} + local channel = args.channel or "Master" + local step = args.step or "1%" + local header = args.header or " Vol " + local header_color = args.header_color or beautiful.fg_normal or "#FFFFFF" + local color = args.color or beautiful.fg_focus or "#FFFFFF" + + local myvolume = wibox.widget.textbox() + local myvolumeupdate = function() + local f = io.popen('amixer get ' .. channel) + local mixer = f:read("*all") + f:close() + + local volume, mute = string.match(mixer, "([%d]+)%%.*%[([%l]*)") + + if volume == nil + then + alsa.volume = 0 + else + alsa.volume = volume + end + + if mute == nil or mute == 'on' + then + alsa.mute = true + mute = '' + else + alsa.mute = false + mute = 'M' + end + + local ret = markup(color, string.format("%d%s", volume, mute)) + myvolume:set_markup(markup(header_color, header) .. ret .. " ") + end + + local myvolumetimer = timer({ timeout = 5 }) + myvolumetimer:connect_signal("timeout", myvolumeupdate) + myvolumetimer:start() + myvolumetimer:emit_signal("timeout") + + myvolume:buttons(awful.util.table.join( + awful.button({}, 1, + function() + run_in_terminal('alsamixer') + end), + awful.button({}, 3, + function() + awful.util.spawn('amixer sset ' .. channel ' toggle') + end), + + awful.button({}, 4, + function() + awful.util.spawn('amixer sset ' .. channel .. ' ' .. step '+') + myvolumeupdate() + end), + + awful.button({}, 5, + function() + awful.util.spawn('amixer sset ' .. channel .. ' ' .. step '-') + myvolumeupdate() + end) + )) + + alsa.widget = myvolume + alsa.channel = channel + alsa.step = step + alsa.notify = myvolumeupdate + + return setmetatable(alsa, { __index = alsa.widget }) +end + +return setmetatable(alsa, { __call = function(_, ...) return worker(...) end }) diff --git a/widgets/alsabar.lua b/widgets/alsabar.lua new file mode 100644 index 0000000..0421f5c --- /dev/null +++ b/widgets/alsabar.lua @@ -0,0 +1,164 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2013, Rman +--]] + +local awful = require("awful") +local beautiful = require("beautiful") +local naughty = require("naughty") + +local io = io +local math = { modf = math.modf } +local string = { match = string.match, + rep = string.rep } +local tonumber = tonumber + +local setmetatable = setmetatable + +-- ALSA volume bar +-- lain.widgets.alsabar +local alsabar = +{ + channel = "Master", + step = "5%", + + colors = + { + background = beautiful.bg_normal, + mute = "#EB8F8F", + unmute = "#A4CE8A" + }, + + mixer = terminal .. " -e alsamixer", + + notifications = + { + font = beautiful.font:sub(beautiful.font:find(""), beautiful.font:find(" ")), + font_size = "11", + bar_size = 18 -- Awesome default + }, + + _current_level = 0, + _muted = false +} + +function alsabar:notify() + local preset = + { + title = "", text = "", + timeout = 3, + font = alsabar.notifications.font .. " " .. alsabar.notifications.font_size, + fg = beautiful.fg_focus + } + + if alsabar._muted then + preset.title = alsabar.channel .. " - Muted" + else + preset.title = alsabar.channel .. " - " .. alsabar._current_level * 100 .. "%" + end + + local int = math.modf(alsabar._current_level * alsabar.notifications.bar_size) + preset.text = "[" .. string.rep("|", int) + .. string.rep(" ", alsabar.notifications.bar_size - int) .. "]" + + if alsabar._notify ~= nil then + alsabar._notify = naughty.notify ({ replaces_id = alsabar._notify.id, + preset = preset }) + else + alsabar._notify = naughty.notify ({ preset = preset }) + end +end + +function worker(args) + local args = args or {} + local width = args.width or 63 + local height = args.heigth or 1 + local ticks = args.ticks or true + local ticks_size = args.ticks_size or 7 + local vertical = args.vertical or false + alsabar.channel = args.channel or alsabar.channel + alsabar.step = args.step or alsabar.step + alsabar.colors = args.colors or alsabar.colors + alsabar.notifications = args.notifications or alsabar.notifications + + alsabar.bar = awful.widget.progressbar() + alsabar.bar:set_background_color(alsabar.colors.background) + alsabar.bar:set_color(alsabar.colors.unmute) + alsabar.tooltip = awful.tooltip({ objects = { alsabar.bar } }) + alsabar.bar:set_width(width) + alsabar.bar:set_height(height) + alsabar.bar:set_ticks(ticks) + alsabar.bar:set_ticks_size(ticks_size) + + if vertical then + alsabar.bar:set_vertical(true) + end + + local myvolumebarupdate = function() + -- Get mixer control contents + local f = io.popen("amixer get " .. alsabar.channel) + local mixer = f:read("*all") + f:close() + + -- Capture mixer control state: [5%] ... ... [on] + local volu, mute = string.match(mixer, "([%d]+)%%.*%[([%l]*)") + -- Handle mixers without data + if volu == nil then + volu = 0 + mute = "off" + end + + alsabar._current_level = tonumber(volu) / 100 + alsabar.bar:set_value(alsabar._current_level) + + if mute == "" and volu == "0" or mute == "off" + then + alsabar._muted = true + alsabar.tooltip:set_text (" [Muted] ") + alsabar.bar:set_color(alsabar.colors.mute) + else + alsabar._muted = false + alsabar.tooltip:set_text(" " .. alsabar.channel .. ": " .. volu .. "% ") + alsabar.bar:set_color(alsabar.colors.unmute) + end + end + + local myvolumebartimer = timer({ timeout = 5 }) + myvolumebartimer:connect_signal("timeout", myvolumebarupdate) + myvolumebartimer:start() + myvolumebartimer:emit_signal("timeout") + + alsabar.bar:buttons (awful.util.table.join ( + awful.button ({}, 1, function() + awful.util.spawn(alsabar.mixer) + end), + awful.button ({}, 3, function() + awful.util.spawn("amixer sset " .. alsabar.channel .. " toggle") + myvolumebarupdate() + end), + awful.button ({}, 4, function() + awful.util.spawn("amixer sset " .. alsabar.channel .. " " + .. alsabar.step .. "+") + myvolumebarupdate() + end), + awful.button ({}, 5, function() + awful.util.spawn("amixer sset " .. alsabar.channel .. " " + .. alsabar.step .. "-") + myvolumebarupdate() + end) + )) + + return { widget = alsabar.bar, + channel = alsabar.channel, + step = alsabar.step, + notify = function() + myvolumebarupdate() + alsabar.notify() + end + } +end + +return setmetatable(alsabar, { __call = function(_, ...) return worker(...) end }) diff --git a/widgets/bat.lua b/widgets/bat.lua new file mode 100644 index 0000000..0461607 --- /dev/null +++ b/widgets/bat.lua @@ -0,0 +1,147 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2010-2012, Peter Hofmann + +--]] + +local markup = require("lain.util.markup") +local first_line = require("lain.helpers").first_line + +local beautiful = require("beautiful") +local naughty = require("naughty") +local wibox = require("wibox") + +local math = { floor = math.floor } +local string = { format = string.format } + +local setmetatable = setmetatable + +-- Battery infos +-- lain.widgets.bat +local bat = { + status = "not present", + perc = "N/A", + time = "N/A", +} + +function worker(args) + local args = args or {} + local battery = args.battery or "BAT0" + local show_all = args.show_all or false + local refresh_timeout = args.refresh_timeout or 30 + local header = args.header or " Bat " + local header_color = args.header_color or beautiful.fg_normal or "#FFFFFF" + local color = args.color or beautiful.fg_focus or "#FFFFFF" + local shadow = args.shadow or false + + local mybattery = wibox.widget.textbox() + + local mybatteryupdate = function() + local present = first_line("/sys/class/power_supply/" + .. battery + .. "/present") + + if present == "1" + then + local rate = first_line("/sys/class/power_supply/" + .. battery .. + "/power_now") + local ratev = first_line("/sys/class/power_supply/" + .. battery .. + "/voltage_now") + local rem = first_line("/sys/class/power_supply/" + .. battery .. + "/energy_now") + local tot = first_line("/sys/class/power_supply/" + .. battery .. + "/energy_full") + bat.status = first_line("/sys/class/power_supply/" + .. battery .. + "/status") + + local time_rat = 0 + if bat.status == "Charging" + then + status = "(+)" + time_rat = (tot - rem) / rate + elseif bat.status == "Discharging" + then + status = "(-)" + time_rat = rem / rate + else + status = "(.)" + end + + local hrs = math.floor(time_rat) + local min = (time_rat - hrs) * 60 + bat.time = string.format("%02d:%02d", hrs, min) + + local amount = (rem / tot) * 100 + + if shadow + then + bat.perc = string.format("%d", amount) + else + bat.perc = string.format("%d%%", amount) + end + + local watt = string.format("%.2fW", (rate * ratev) / 1e12) + + if show_all + then + text = watt .. " " .. bat.perc .. " " .. bat.time .. " " .. bat.status + else + text = bat.perc + end + + -- notifications for low and critical states + if amount <= 5 + then + naughty.notify{ + text = "shutdown imminent", + title = "battery nearly exhausted", + position = "top_right", + timeout = 15, + fg="#000000", + bg="#ffffff", + ontop = true + } + elseif amount <= 15 + then + old_id = naughty.notify{ + text = "plug the cable", + title = "battery low", + position = "top_right", + timeout = 5, + fg="#202020", + bg="#cdcdcd", + ontop = true + } + end + else + text = "none" + end + + if shadow + then + mybattery:set_text('') + else + mybattery:set_markup(markup(header_color, header) + .. markup(color, text) .. " ") + end + end + + local mybatterytimer = timer({ timeout = refresh_timeout }) + mybatterytimer:connect_signal("timeout", mybatteryupdate) + mybatterytimer:start() + mybatterytimer:emit_signal("timeout") + + bat.widget = mybattery + + return setmetatable(bat, { __index = bat.widget }) +end + +return setmetatable(bat, { __call = function(_, ...) return worker(...) end }) diff --git a/widgets/borderbox.lua b/widgets/borderbox.lua new file mode 100644 index 0000000..150c1c3 --- /dev/null +++ b/widgets/borderbox.lua @@ -0,0 +1,61 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2010-2012, Peter Hofmann + +--]] + +local wibox = require("awful.wibox") +local setmetatable = setmetatable + +-- Creates a thin wibox at a position relative to another wibox +-- lain.widgets.borderbox +local borderbox = {} + +local function worker(relbox, s, args) + local where = args.position or 'above' + local color = args.color or '#FFFFFF' + local size = args.size or 1 + local box = nil + local wiboxarg = { + position = nil, + bg = color + } + + if where == 'above' + then + wiboxarg.width = relbox.width + wiboxarg.height = size + box = wibox(wiboxarg) + box.x = relbox.x + box.y = relbox.y - size + elseif where == 'below' + then + wiboxarg.width = relbox.width + wiboxarg.height = size + box = wibox(wiboxarg) + box.x = relbox.x + box.y = relbox.y + relbox.height + elseif where == 'left' + then + wiboxarg.width = size + wiboxarg.height = relbox.height + box = wibox(wiboxarg) + box.x = relbox.x - size + box.y = relbox.y + elseif where == 'right' + then + wiboxarg.width = size + wiboxarg.height = relbox.height + box = wibox(wiboxarg) + box.x = relbox.x + relbox.width + box.y = relbox.y + end + + box.screen = s + return box +end + +return setmetatable(borderbox, { __call = function(_, ...) return worker(...) end }) diff --git a/widgets/calendar.lua b/widgets/calendar.lua new file mode 100644 index 0000000..4b6d469 --- /dev/null +++ b/widgets/calendar.lua @@ -0,0 +1,124 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + +--]] + +local icons_dir = require("lain.helpers").icons_dir + +local awful = require("awful") +local beautiful = require("beautiful") +local naughty = require("naughty") + +local io = io +local os = { date = os.date } +local tonumber = tonumber + +local setmetatable = setmetatable + +-- Calendar notification +-- lain.widgets.calendar +local calendar = {} +local notification = nil + +local function create(background, foreground) + calendar.offset = 0 + calendar.icons_dir = icons_dir .. "cal/white/" -- default + calendar.notify_icon = nil + calendar.font_size = 12 + calendar.bg = background or beautiful.bg_normal or "#FFFFFF" + calendar.fg = foreground or beautiful.fg_normal or "#FFFFFF" +end + +function calendar:hide() + if notification ~= nil then + naughty.destroy(notification) + notification = nil + end +end + +function calendar:show(t_out, inc_offset) + calendar:hide() + + local offs = inc_offset or 0 + local tims = t_out or 0 + local f, c_text + local today = tonumber(os.date('%d')) + local init_t = '/usr/bin/cal | sed -r -e "s/(^| )( ' + -- let's take font only, font size is set in calendar table + local font = beautiful.font:sub(beautiful.font:find(""), + beautiful.font:find(" ")) + + if offs == 0 + then -- current month showing, today highlighted + if today >= 10 + then + init_t = '/usr/bin/cal | sed -r -e "s/(^| )(' + end + + calendar.offset = 0 + calendar.notify_icon = calendar.icons_dir .. today .. ".png" + + -- bg and fg inverted to highlight today + f = io.popen( init_t .. today .. + ')($| )/\\1\\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 = "" + .. f:read() .. "\n\n" + .. f:read() .. "\n" + .. f:read("*all"):gsub("\n*$", "") + .. "" + f:close() + + notification = naughty.notify({ text = c_text, + icon = calendar.notify_icon, + fg = calendar.fg, + bg = calendar.bg, + timeout = tims }) +end + +function calendar:attach(widget, background, foreground) + create(background, foreground) + widget:connect_signal("mouse::enter", function () calendar:show() end) + widget:connect_signal("mouse::leave", function () calendar:hide() end) + widget:buttons(awful.util.table.join( awful.button({ }, 1, function () + calendar:show(0, -1) end), + awful.button({ }, 3, function () + calendar:show(0, 1) end) )) +end + +return setmetatable(calendar, { __call = function(_, ...) return create(...) end }) diff --git a/widgets/cpu.lua b/widgets/cpu.lua new file mode 100644 index 0000000..cf0b76c --- /dev/null +++ b/widgets/cpu.lua @@ -0,0 +1,80 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2010-2012, Peter Hofmann + +--]] + +local markup = require("lain.util.markup") +local first_line = require("lain.helpers").first_line + +local beautiful = require("beautiful") +local wibox = require("wibox") + +local math = { ceil = math.ceil } +local string = { format = string.format, + gmatch = string.gmatch } + +local setmetatable = setmetatable + +-- CPU usage +-- lain.widgets.cpu +local cpu = { + last_total = 0, + last_active = 0 +} + +function worker(args) + local args = args or {} + local refresh_timeout = args.refresh_timeout or 5 + local header = args.header or " Cpu " + local header_color = args.header or beautiful.fg_normal or "#FFFFFF" + local color = args.color or beautiful.fg_focus or "#FFFFFF" + local footer = args.footer or "%" + + local w = wibox.widget.textbox() + + local cpuusageupdate = function() + -- Read the amount of time the CPUs have spent performing + -- different kinds of work. Read the first line of /proc/stat + -- which is the sum of all CPUs. + local times = first_line("/proc/stat") + local at = 1 + local idle = 0 + local total = 0 + for field in string.gmatch(times, "[%s]+([^%s]+)") + do + -- 3 = idle, 4 = ioWait. Essentially, the CPUs have done + -- nothing during these times. + if at == 3 or at == 4 + then + idle = idle + field + end + total = total + field + at = at + 1 + end + local active = total - idle + + -- Read current data and calculate relative values. + local dactive = active - cpu.last_active + local dtotal = total - cpu.last_total + local dta = math.ceil((dactive / dtotal) * 100) + + w:set_markup(markup(header_color, header) .. markup(color, dta .. footer) .. " ") + + -- Save current data for the next run. + cpu.last_active = active + cpu.last_total = total + end + + local cpuusagetimer = timer({ timeout = refresh_timeout }) + cpuusagetimer:connect_signal("timeout", cpuusageupdate) + cpuusagetimer:start() + cpuusagetimer:emit_signal("timeout") + + return w +end + +return setmetatable(cpu, { __call = function(_, ...) return worker(...) end }) diff --git a/widgets/fs.lua b/widgets/fs.lua new file mode 100644 index 0000000..9611617 --- /dev/null +++ b/widgets/fs.lua @@ -0,0 +1,134 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2010, Adrian C. + * (c) 2009, Lucas de Vries + +--]] + +local markup = require("lain.util.markup") +local helpers = require("lain.helpers") + +local beautiful = require("beautiful") +local wibox = require("wibox") +local naughty = require("naughty") + +local io = io +local string = { match = string.match } +local tonumber = tonumber + +local setmetatable = setmetatable + +-- File system disk space usage +-- lain.widgets.fs +local fs = {} +local notification = nil + +function fs:hide() + if notification ~= nil then + naughty.destroy(notification) + notification = nil + end +end + +function fs:show(t_out) + fs:hide() + + local f = io.popen(helpers.scripts_dir .. "dfs") + ws = f:read("*all"):gsub("\n*$", "") + f:close() + + notification = naughty.notify({ + text = ws, + timeout = t_out, + fg = beautiful.fg_focus, + }) +end + +-- Variable definitions +local unit = { ["mb"] = 1024, ["gb"] = 1024^2 } + +local function worker(args) + local args = args or {} + local partition = args.partition or "/" + local refresh_timeout = args.refresh_timeout or 600 + local header = args.header or " Hdd " + local header_color = args.header_color or beautiful.fg_normal or "#FFFFFF" + local color = args.color or beautiful.fg_focus or "#FFFFFF" + local footer = args.header or "" + local shadow = args.shadow or false + + local myfs = wibox.widget.textbox() + + helpers.set_map("fs", false) + + local fsupdate = function() + local fs_info = {} -- Get data from df + local f = io.popen("LC_ALL=C df -kP") + + local function set_text() + local info = fs_info['{' .. partition .. ' used_p}'] + myfs:set_markup(markup(header_color, header) + .. markup(color, info .. footer) .. " ") + end + + for line in f:lines() do -- Match: (size) (used)(avail)(use%) (mount) + local s = string.match(line, "^.-[%s]([%d]+)") + local u,a,p = string.match(line, "([%d]+)[%D]+([%d]+)[%D]+([%d]+)%%") + local m = string.match(line, "%%[%s]([%p%w]+)") + + if u and m then -- Handle 1st line and broken regexp + helpers.uformat(fs_info, m .. " used", u, unit) + fs_info["{" .. m .. " used_p}"] = tonumber(p) + end + end + + f:close() + + if shadow + then + myfs:set_text('') + else + set_text() + end + + local part = fs_info['{' .. partition .. ' used_p}'] + + if part >= 90 then + if part >= 99 and not helpers.get_map("fs") then + naughty.notify({ title = "warning", + text = partition .. " ran out!\n" + .. "make some room", + timeout = 8, + position = "top_right", + fg = beautiful.fg_urgent, + bg = beautiful.bg_urgent }) + helpers.set_map("fs", true) + end + if shadow then set_text() end + end + end + + local fstimer = timer({ timeout = refresh_timeout }) + fstimer:connect_signal("timeout", fsupdate) + fstimer:start() + fstimer:emit_signal("timeout") + + myfs:connect_signal('mouse::enter', function () fs:show(0) end) + myfs:connect_signal('mouse::leave', function () fs:hide() end) + + local fs_out = + { + widget = myfs, + show = function(t_out) + fsupdate() + fs:show(t_out) + end + } + + return setmetatable(fs_out, { __index = fs_out.widget }) +end + +return setmetatable(fs, { __call = function(_, ...) return worker(...) end }) diff --git a/widgets/imap.lua b/widgets/imap.lua new file mode 100644 index 0000000..94652b6 --- /dev/null +++ b/widgets/imap.lua @@ -0,0 +1,166 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + +--]] + +local markup = require("lain.util.markup") +local helpers = require("lain.helpers") + +local awful = require("awful") +local beautiful = require("beautiful") +local naughty = require("naughty") +local wibox = require("wibox") + +local io = io +local tonumber = tonumber +local string = string + +local setmetatable = setmetatable + +-- Mail imap check +-- lain.widgets.imap +local imap = {} + +function worker(args) + local args = args or {} + + local server = args.server + local mail = args.mail + local password = args.password + + local port = args.port or "993" + local refresh_timeout = args.refresh_timeout or 60 + local header = args.header or " Mail " + local header_color = args.header_color or beautiful.fg_normal or "#FFFFFF" + local color_newmail = args.color_newmail or beautiful.fg_focus or "#FFFFFF" + local color_nomail = args.color_nomail or beautiful.fg_normal or "#FFFFFF" + local mail_encoding = args.mail_encoding or nil + local maxlen = args.maxlen or 200 + local app = args.app or "mutt" + local is_plain = args.is_plain or false + local shadow = args.shadow or false + + helpers.set_map(mail, true) + helpers.set_map(mail .. " count", "0") + + local checkmail = helpers.scripts_dir .. "checkmail" + + if not is_plain + then + local f = io.popen(password) + password = f:read("*all"):gsub("\n", ""):gsub("\r", "") + f:close() + end + + local myimapcheck = wibox.widget.textbox() + + local myimapcheckupdate = function() + function set_nomail() + if shadow + then + myimapcheck:set_text('') + else + myimapcheck:set_markup(markup(color_nomail, " no mail ")) + end + end + + conn = io.popen("ip link show") + check_conn = conn:read("*all") + conn:close() + + if not check_conn:find("state UP") then + set_nomail() + return + end + + to_execute = checkmail .. ' -s ' .. server .. + ' -u ' .. mail .. ' -p ' .. password + .. ' --port ' .. port + + if mail_encoding ~= nil + then + to_execute = to_execute .. ' --encoding ' + .. mail_encoding + end + + f = io.popen(to_execute) + ws = f:read("*all") + f:close() + + if ws:find("No new messages") ~= nil + then + helpers.set_map(mail, true) + set_nomail() + elseif ws:find("CheckMailError: invalid credentials") ~= nil + then + helpers.set_map(mail, true) + myimapcheck.set_markup(markup(header_color, header) .. + markup(color_newmail, "invalid credentials ")) + else + mailcount = ws:match("%d") or "?" + + if helpers.get_map(mail .. " count") ~= mailcount and mailcount ~= "?" + then + helpers.set_map(mail, true) + helpers.set_map(mail .. " count", mailcount) + end + + myimapcheck:set_markup(markup(header_color, header) .. + markup(color_newmail, mailcount) .. " ") + + if helpers.get_map(mail) + then + if mailcount == "?" + -- May happens sometimes using keyrings or other password fetchers. + -- Since this should be automatically fixed in short times, we threat + -- this exception delaying the update to the next timeout. + then + set_nomail() + return + elseif tonumber(mailcount) >= 1 + then + notify_title = ws:match(mail .. " has %d new message.?") + ws = ws:gsub(notify_title, "", 1):gsub("\n", "", 2) + + ws = ws:gsub("--Content.%S+.-\n", "") + ws = ws:gsub("--%d+.-\n", "") + + if string.len(ws) > maxlen + then + ws = ws:sub(1, maxlen) .. "[...]" + end + + notify_title = notify_title:gsub("\n", "") + end + + naughty.notify({ title = notify_title, + fg = color_newmail, + text = ws, + icon = beautiful.lain_mail_notify or + helpers.icons_dir .. "mail.png", + timeout = 8, + position = "top_left" }) + + helpers.set_map(mail, false) + end + end + end + + local myimapchecktimer = timer({ timeout = refresh_timeout }) + myimapchecktimer:connect_signal("timeout", myimapcheckupdate) + myimapchecktimer:start() + myimapcheck:buttons(awful.util.table.join( + awful.button({}, 0, + + function() + helpers.run_in_terminal(app) + end) + )) + + return myimapcheck +end + +return setmetatable(imap, { __call = function(_, ...) return worker(...) end }) diff --git a/widgets/init.lua b/widgets/init.lua new file mode 100644 index 0000000..78cac9b --- /dev/null +++ b/widgets/init.lua @@ -0,0 +1,24 @@ + +--[[ + + Lain + Layouts, widgets and utilities for Awesome WM + + Widgets section + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2010-2012, Peter Hofmann + +--]] + +local wrequire = require("lain.helpers").wrequire +local setmetatable = setmetatable + +local widgets = +{ + _NAME = "lain.widgets", + terminal = "xterm" -- X default +} + +return setmetatable(widgets, { __index = wrequire }) diff --git a/widgets/maildir.lua b/widgets/maildir.lua new file mode 100644 index 0000000..b5437bd --- /dev/null +++ b/widgets/maildir.lua @@ -0,0 +1,129 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2010-2012, Peter Hofmann + +--]] + +local markup = require("lain.util.markup") +local run_in_terminal = require("lain.helpers").run_in_terminal + +local awful = require("awful") +local beautiful = require("beautiful") +local wibox = require("wibox") + +local io = io +local os = { getenv = os.getenv } +local pairs = pairs +local string = { len = string.len, + match = string.match } +local table = { sort = table.sort } + +local setmetatable = setmetatable + +-- Maildir check +-- lain.widgets.maildir +local maildir = {} + +function worker(args) + local args = args or {} + local mailpath = args.mailpath or os.getenv("HOME") .. "/Mail" + local ignore_boxes = args.ignore_boxes or {} + local refresh_timeout = args.refresh_timeout or 60 + local header = args.header or " Mail " + local header_color = args.header_color or beautiful.fg_normal or "#FFFFFF" + local color_newmail = args.color_newmail or beautiful.fg_focus or "#FFFFFF" + local color_nomail = args.color_nomail or beautiful.fg_normal or "#FFFFFF" + local app = args.app or "mutt" + local shadow = args.shadow or false + + local mymailcheck = wibox.widget.textbox() + local mymailcheckupdate = function() + -- Find pathes to mailboxes. + local p = io.popen("find " .. mailpath .. + " -mindepth 1 -maxdepth 1 -type d" .. + " -not -name .git") + local boxes = {} + local line = "" + repeat + line = p:read("*l") + if line ~= nil + then + -- Find all files in the "new" subdirectory. For each + -- file, print a single character (no newline). Don't + -- match files that begin with a dot. + -- Afterwards the length of this string is the number of + -- new mails in that box. + local np = io.popen("find " .. line .. + "/new -mindepth 1 -type f " .. + "-not -name '.*' -printf a") + local mailstring = np:read("*all") + + -- Strip off leading mailpath. + local box = string.match(line, mailpath .. "/*([^/]+)") + local nummails = string.len(mailstring) + if nummails > 0 + then + boxes[box] = nummails + end + end + until line == nil + + table.sort(boxes) + + local newmail = "" + local count = 0 + for box, number in pairs(boxes) + do + count = count + 1 + -- Add this box only if it's not to be ignored. + if not util.element_in_table(box, ignore_boxes) + then + if newmail == "" + then + newmail = box .. "(" .. number .. ")" + else + newmail = newmail .. ", " .. + box .. "(" .. number .. ")" + end + end + end + + if count == 1 then + -- it will be only executed once + for box, number in pairs(boxes) + do -- it's useless to show only INBOX(x) + if box == "INBOX" then newmail = number end + end + end + + if newmail == "" + then + if shadow + then + mymailcheck:set_text('') + else + myimapcheck:set_markup(markup(color_nomail, " no mail ")) + end + else + myimapcheck:set_markup(markup(header_color, header) .. + markup(color_newmail, newmail) .. " ") + end + end + + local mymailchecktimer = timer({ timeout = refresh_timeout }) + mymailchecktimer:connect_signal("timeout", mymailcheckupdate) + mymailchecktimer:start() + mymailcheck:buttons(awful.util.table.join( + awful.button({}, 0, + function() + run_in_terminal(app) + end) + )) + + return mymailcheck +end + +return setmetatable(maildir, { __call = function(_, ...) return worker(...) end }) diff --git a/widgets/mem.lua b/widgets/mem.lua new file mode 100644 index 0000000..09be00f --- /dev/null +++ b/widgets/mem.lua @@ -0,0 +1,87 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2010-2012, Peter Hofmann + * (c) 2010, Adrian C. + * (c) 2009, Lucas de Vries + +--]] + +local markup = require("lain.util.markup") +local run_in_terminal = require("lain.helpers").run_in_terminal + +local beautiful = require("beautiful") +local wibox = require("wibox") + +local io = { lines = io.lines } +local math = { floor = math.floor } +local string = { format = string.format, + gmatch = string.gmatch, + len = string.len } + +local setmetatable = setmetatable + +-- Memory usage (ignoring caches) +-- lain.widgets.mem +local mem = {} + +function worker(args) + local args = args or {} + local refresh_timeout = args.refresh_timeout or 10 + local show_swap = args.show_swap or false + local show_total = args.show_total or false + local header = args.header or " Mem " + local header_color = args.header or beautiful.fg_normal or "#FFFFFF" + local color = args.color or beautiful.fg_focus or "#FFFFFF" + local footer = args.footer or "MB" + + local widg = wibox.widget.textbox() + + local upd = function() + local mem = {} + for line in io.lines("/proc/meminfo") + do + for k, v in string.gmatch(line, "([%a]+):[%s]+([%d]+).+") + do + if k == "MemTotal" then mem.total = math.floor(v / 1024) + elseif k == "MemFree" then mem.free = math.floor(v / 1024) + elseif k == "Buffers" then mem.buf = math.floor(v / 1024) + elseif k == "Cached" then mem.cache = math.floor(v / 1024) + elseif k == "SwapTotal" then mem.swap = math.floor(v / 1024) + elseif k == "SwapFree" then mem.swapf = math.floor(v / 1024) + end + end + end + + used = mem.total - (mem.free + mem.buf + mem.cache) + swapused = mem.swap - mem.swapf + + if show_total + then + local fmt = "%" .. string.len(mem.total) .. ".0f/%.0f" + widg:set_markup(markup(header_color, header) .. + markup(color, string.format(fmt, used, mem.total) .. footer .. " ")) + else + widg:set_markup(markup(header_color, header) .. + markup(color, used .. footer .. " ")) + end + + if show_swap + then + widg:set_markup(widg._layout.text .. ' (' + .. string.format('%.0f '.. footer, swapused) + .. ') ') + end + end + + local tmr = timer({ timeout = refresh_timeout }) + tmr:connect_signal("timeout", upd) + tmr:start() + tmr:emit_signal("timeout") + + return widg +end + +return setmetatable(mem, { __call = function(_, ...) return worker(...) end }) diff --git a/widgets/mpd.lua b/widgets/mpd.lua new file mode 100644 index 0000000..0b9a4c9 --- /dev/null +++ b/widgets/mpd.lua @@ -0,0 +1,137 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2010, Adrian C. + +--]] + +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 }) diff --git a/widgets/net.lua b/widgets/net.lua new file mode 100644 index 0000000..18727f1 --- /dev/null +++ b/widgets/net.lua @@ -0,0 +1,153 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2010-2012, Peter Hofmann + +--]] + +local markup = require("lain.util.markup") +local helpers = require("lain.helpers") + +local awful = require("awful") +local beautiful = require("beautiful") +local wibox = require("wibox") + +local io = io +local tostring = tostring +local string = { format = string.format } + +local setmetatable = setmetatable + +-- Network infos +-- lain.widgets.net +local net = { + send = "0", + recv = "0", + last_t = {}, + last_r = {} +} + +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 = '' .. header .. ' ' + + 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 + .. '' + .. string.format('%.1f', net.send) + .. '' + .. spr + .. '' + .. string.format('%.1f', net.recv) + .. '' + + mynet:set_markup(' ' .. text .. ' ') + else + mynet:set_markup(' ' .. text .. '-' .. ' ') + end + + net.last_t[iface] = now_t + net.last_r[iface] = now_r + end + + local mynettimer = timer({ timeout = delta }) + mynettimer:connect_signal("timeout", mynetupdate) + mynettimer:start() + mynettimer:emit_signal("timeout") + + mynet:buttons(awful.util.table.join( + awful.button({}, 0, function() + helpers.run_in_terminal(app) + mynetupdate() + end))) + + net.widget = mynet + + return setmetatable(net, { __index = net.widget }) +end + +return setmetatable(net, { __call = function(_, ...) return worker(...) end }) diff --git a/widgets/sysload.lua b/widgets/sysload.lua new file mode 100644 index 0000000..eb06828 --- /dev/null +++ b/widgets/sysload.lua @@ -0,0 +1,70 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + * (c) 2010-2012, Peter Hofmann + +--]] + +local markup = require("lain.util.markup") +local helpers = require("lain.helpers") + +local awful = require("awful") +local beautiful = require("beautiful") +local wibox = require("wibox") + +local io = io +local string = { format = string.format, + match = string.match } + +local setmetatable = setmetatable + +-- System load +-- lain.widgets.sysload +local sysload = {} + +function worker(args) + local args = args or {} + local refresh_timeout = args.refresh_timeout or 5 + local show_all = args.show_all or false + local header = args.header or " Load " + local header_color = args.header_color or beautiful.fg_normal or "#FFFFFF" + local color = args.color or beautiful.fg_focus or "#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 }) diff --git a/widgets/temp.lua b/widgets/temp.lua new file mode 100644 index 0000000..301bc1c --- /dev/null +++ b/widgets/temp.lua @@ -0,0 +1,52 @@ + +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luke Bonham + +--]] + +local markup = require("lain.util.markup") + +local beautiful = require("beautiful") +local wibox = require("wibox") + +local io = io +local tonumber = tonumber + +local setmetatable = setmetatable + +-- coretemp +-- lain.widgets.temp +local temp = {} + +function worker(args) + local args = args or {} + local refresh_timeout = args.refresh_timeout or 5 + local header = args.header or " Temp " + local header_color = args.header_color or beautiful.fg_normal or "#FFFFFF" + local color = args.color or beautiful.fg_focus or header_color + local footer = args.footer or "C " + + local mytemp = wibox.widget.textbox() + + local mytempupdate = function() + local f = io.open("/sys/class/thermal/thermal_zone0/temp") + local ret = f:read("*all") + f:close() + + ret = tonumber(ret) / 1000 + + mytemp:set_markup(markup(header_color, header) .. + markup(color, ret .. footer)) + end + + local mytemptimer = timer({ timeout = refresh_timeout }) + mytemptimer:connect_signal("timeout", mytempupdate) + mytemptimer:start() + mytemptimer:emit_signal("timeout") + + return mytemp +end + +return setmetatable(temp, { __call = function(_, ...) return worker(...) end }) diff --git a/widgets/yawn/README.rst b/widgets/yawn/README.rst new file mode 100644 index 0000000..d067db3 --- /dev/null +++ b/widgets/yawn/README.rst @@ -0,0 +1,133 @@ +========================================= +Yahoo's Awesome (WM) Weather Notification +========================================= + +---------------- +Lain integration +---------------- + +:Author: Luke Bonham +:License: WTFPLv2_ +:Version: 2.0-git + +Description +----------- + +Yawn is a module for Awesome WM providing brief and compact +weather notification via Naughty and Yahoo! Weather API. + +Originally a port of perceptive_, it became a completely new module after various improvements and style changes. + +----- +Usage +----- + +You can ``register`` Yawn to get a set of widgets, or ``attach`` it to +an existent widget. + +register +^^^^^^^^ + +Call: :: + + lain.widgets.yawn(id, args) + +Arguments: + +``id`` + An integer that defines the WOEID code of your city. + To obtain it you can google 'yahoo weather %CITYNAME%' and follow the first link. + It will look like:: + + http://weather.yahoo.com/united-states/california/san-diego-2487889/ + + and the last number in that link will be the ID you need. +``args`` + An optional table which can contain the following settings: + ``u`` + Units. Type: string. Possible values: "c" (Celsius), "f" (Fahrenheit). Default: "c". + + ``toshow`` + What to show. Type: string. Possible values: "units", "forecast", "both". + Default: "forecast". + + ``units_color`` + Color of units text. Type: string. Possible values: hexadecimal color + codes. + + ``forecast_color`` + Color of forecast text. Type: string. Possible values: hexadecimal color + codes. + + ``notification_color`` + Color of notification text. Type: string. Possible values: hexadecimal color + codes. + + ``spr`` + A separator. Type: string. You can define it when ``toshow`` is set to "both". + + ``footer`` + A footer. Type: string. You can define it when ``toshow`` is set to + "both". + +The function creates an imagebox icon and a textbox widget. Add them to you wibox like this: :: + + right_layout:add(lain.widgets.yawn.icon) + right_layout:add(lain.widgets.yawn.widget) + +Hovering over ``yawn.icon`` will display the notification. + +attach +^^^^^^ + +Call: :: + + lain.widgets.yawn.attach(widget, id, args) + +Arguments: + +``widget`` + The widget which you want to attach yawn to. +``id`` + same as in ``register`` +``args`` + same as in ``register`` + +Hovering over ``widget`` will display the notification. + +-------------- +Popup shortcut +-------------- + +You can also create a keybinding for the weather popup like this: :: + + globalkeys = awful.util.table.join( + ... + awful.key( { "Mod1" }, "w", function () lain.widgets.yawn.show(5) end ) + ... + +where ``show`` argument is an integer defining timeout seconds. + +------------ +Localization +------------ + +Default language is English, but Yawn can be localized. +Move to ``localizations`` subdirectory and fill ``localization_template``. + +Once you're done, rename it like your locale id. In my case: :: + + $ lua + Lua 5.2.2 Copyright (C) 1994-2013 Lua.org, PUC-Rio + > print(os.getenv("LANG"):match("(%S*$*)[.]")) + it_IT + > + +hence I named my file "it_IT" (Italian localization). + +**NOTE:** If you create a localization, feel free to send me! I will add it. + +.. _WTFPLv2: http://www.wtfpl.net +.. _perceptive: https://github.com/ioga/perceptive +.. _Tamsyn: http://www.fial.com/~scott/tamsyn-font/ +.. _Rainbow: https://github.com/copycat-killer/awesome-copycats> diff --git a/widgets/yawn/icons/BlowingSnow.png b/widgets/yawn/icons/BlowingSnow.png new file mode 100755 index 0000000..6223f8f Binary files /dev/null and b/widgets/yawn/icons/BlowingSnow.png differ diff --git a/widgets/yawn/icons/Cloudy.png b/widgets/yawn/icons/Cloudy.png new file mode 100755 index 0000000..bac1e7e Binary files /dev/null and b/widgets/yawn/icons/Cloudy.png differ diff --git a/widgets/yawn/icons/DayClear.png b/widgets/yawn/icons/DayClear.png new file mode 100755 index 0000000..d9e2745 Binary files /dev/null and b/widgets/yawn/icons/DayClear.png differ diff --git a/widgets/yawn/icons/DayFair.png b/widgets/yawn/icons/DayFair.png new file mode 120000 index 0000000..8ee94d1 --- /dev/null +++ b/widgets/yawn/icons/DayFair.png @@ -0,0 +1 @@ +DayClear.png \ No newline at end of file diff --git a/widgets/yawn/icons/DayMostlyCloudy.png b/widgets/yawn/icons/DayMostlyCloudy.png new file mode 100755 index 0000000..22b929c Binary files /dev/null and b/widgets/yawn/icons/DayMostlyCloudy.png differ diff --git a/widgets/yawn/icons/DayPartlyCloudy.png b/widgets/yawn/icons/DayPartlyCloudy.png new file mode 100755 index 0000000..8fd0a5b Binary files /dev/null and b/widgets/yawn/icons/DayPartlyCloudy.png differ diff --git a/widgets/yawn/icons/Drizzle.png b/widgets/yawn/icons/Drizzle.png new file mode 120000 index 0000000..df34463 --- /dev/null +++ b/widgets/yawn/icons/Drizzle.png @@ -0,0 +1 @@ +Rain.png \ No newline at end of file diff --git a/widgets/yawn/icons/Foggy.png b/widgets/yawn/icons/Foggy.png new file mode 100755 index 0000000..009039f Binary files /dev/null and b/widgets/yawn/icons/Foggy.png differ diff --git a/widgets/yawn/icons/FreezingDrizzle.png b/widgets/yawn/icons/FreezingDrizzle.png new file mode 100755 index 0000000..6a66140 Binary files /dev/null and b/widgets/yawn/icons/FreezingDrizzle.png differ diff --git a/widgets/yawn/icons/FreezingRain.png b/widgets/yawn/icons/FreezingRain.png new file mode 100755 index 0000000..c924fac Binary files /dev/null and b/widgets/yawn/icons/FreezingRain.png differ diff --git a/widgets/yawn/icons/Hail.png b/widgets/yawn/icons/Hail.png new file mode 100755 index 0000000..009039f Binary files /dev/null and b/widgets/yawn/icons/Hail.png differ diff --git a/widgets/yawn/icons/Haze.png b/widgets/yawn/icons/Haze.png new file mode 120000 index 0000000..0874a83 --- /dev/null +++ b/widgets/yawn/icons/Haze.png @@ -0,0 +1 @@ +Hail.png \ No newline at end of file diff --git a/widgets/yawn/icons/HeavySnow.png b/widgets/yawn/icons/HeavySnow.png new file mode 100755 index 0000000..ddcb8f3 Binary files /dev/null and b/widgets/yawn/icons/HeavySnow.png differ diff --git a/widgets/yawn/icons/LightSnowShowers.png b/widgets/yawn/icons/LightSnowShowers.png new file mode 100755 index 0000000..d797ee9 Binary files /dev/null and b/widgets/yawn/icons/LightSnowShowers.png differ diff --git a/widgets/yawn/icons/MixedRainAndHail.png b/widgets/yawn/icons/MixedRainAndHail.png new file mode 100755 index 0000000..758b01e Binary files /dev/null and b/widgets/yawn/icons/MixedRainAndHail.png differ diff --git a/widgets/yawn/icons/MixedRainAndSleet.png b/widgets/yawn/icons/MixedRainAndSleet.png new file mode 100755 index 0000000..7f0d252 Binary files /dev/null and b/widgets/yawn/icons/MixedRainAndSleet.png differ diff --git a/widgets/yawn/icons/MixedRainAndSnow.png b/widgets/yawn/icons/MixedRainAndSnow.png new file mode 100755 index 0000000..0a07b7b Binary files /dev/null and b/widgets/yawn/icons/MixedRainAndSnow.png differ diff --git a/widgets/yawn/icons/NightClear.png b/widgets/yawn/icons/NightClear.png new file mode 100755 index 0000000..84ea140 Binary files /dev/null and b/widgets/yawn/icons/NightClear.png differ diff --git a/widgets/yawn/icons/NightFair.png b/widgets/yawn/icons/NightFair.png new file mode 120000 index 0000000..23df45a --- /dev/null +++ b/widgets/yawn/icons/NightFair.png @@ -0,0 +1 @@ +NightClear.png \ No newline at end of file diff --git a/widgets/yawn/icons/NightMostlyCloudy.png b/widgets/yawn/icons/NightMostlyCloudy.png new file mode 100755 index 0000000..d8b3673 Binary files /dev/null and b/widgets/yawn/icons/NightMostlyCloudy.png differ diff --git a/widgets/yawn/icons/NightPartlyCloudy.png b/widgets/yawn/icons/NightPartlyCloudy.png new file mode 100755 index 0000000..9e4404d Binary files /dev/null and b/widgets/yawn/icons/NightPartlyCloudy.png differ diff --git a/widgets/yawn/icons/README.md b/widgets/yawn/icons/README.md new file mode 100644 index 0000000..e4dc111 --- /dev/null +++ b/widgets/yawn/icons/README.md @@ -0,0 +1,6 @@ +Yawn icons +========== + +These are [Plain Weather Icons](http://merlinthered.deviantart.com/art/plain-weather-icons-157162192), created by [MerlinTheRed](http://merlinthered.deviantart.com/). + + diff --git a/widgets/yawn/icons/Rain.png b/widgets/yawn/icons/Rain.png new file mode 100755 index 0000000..d00552a Binary files /dev/null and b/widgets/yawn/icons/Rain.png differ diff --git a/widgets/yawn/icons/RainThunder.png b/widgets/yawn/icons/RainThunder.png new file mode 100755 index 0000000..d30e120 Binary files /dev/null and b/widgets/yawn/icons/RainThunder.png differ diff --git a/widgets/yawn/icons/Showers.png b/widgets/yawn/icons/Showers.png new file mode 100755 index 0000000..3cc6665 Binary files /dev/null and b/widgets/yawn/icons/Showers.png differ diff --git a/widgets/yawn/icons/Sleet.png b/widgets/yawn/icons/Sleet.png new file mode 120000 index 0000000..f8f9693 --- /dev/null +++ b/widgets/yawn/icons/Sleet.png @@ -0,0 +1 @@ +SnowShowers.png \ No newline at end of file diff --git a/widgets/yawn/icons/Snow.png b/widgets/yawn/icons/Snow.png new file mode 120000 index 0000000..f8f9693 --- /dev/null +++ b/widgets/yawn/icons/Snow.png @@ -0,0 +1 @@ +SnowShowers.png \ No newline at end of file diff --git a/widgets/yawn/icons/SnowFlurries.png b/widgets/yawn/icons/SnowFlurries.png new file mode 120000 index 0000000..2e090cd --- /dev/null +++ b/widgets/yawn/icons/SnowFlurries.png @@ -0,0 +1 @@ +BlowingSnow.png \ No newline at end of file diff --git a/widgets/yawn/icons/SnowShowers.png b/widgets/yawn/icons/SnowShowers.png new file mode 100755 index 0000000..30534a2 Binary files /dev/null and b/widgets/yawn/icons/SnowShowers.png differ diff --git a/widgets/yawn/icons/Sunny.png b/widgets/yawn/icons/Sunny.png new file mode 100755 index 0000000..cf08c5c Binary files /dev/null and b/widgets/yawn/icons/Sunny.png differ diff --git a/widgets/yawn/icons/Wind.png b/widgets/yawn/icons/Wind.png new file mode 100755 index 0000000..5dc1356 Binary files /dev/null and b/widgets/yawn/icons/Wind.png differ diff --git a/widgets/yawn/icons/na.png b/widgets/yawn/icons/na.png new file mode 100755 index 0000000..62a5350 Binary files /dev/null and b/widgets/yawn/icons/na.png differ diff --git a/widgets/yawn/init.lua b/widgets/yawn/init.lua new file mode 100644 index 0000000..f248e25 --- /dev/null +++ b/widgets/yawn/init.lua @@ -0,0 +1,227 @@ + +--[[ + + Yahoo's Awesome (WM) Weather Notification + + Licensed under WTFPL v2 + * (c) 2013, Luke Bonham + +--]] + +local markup = require("lain.util.markup") + +local beautiful = require("beautiful") +local naughty = require("naughty") +local wibox = require("wibox") + +local debug = { getinfo = debug.getinfo } +local io = io +local os = { date = os.date, + getenv = os.getenv } +local string = { find = string.find, + match = string.match, + gsub = string.gsub, + sub = string.sub } +local tonumber = tonumber + +local setmetatable = setmetatable + +-- yawn integration +-- https://github.com/copycat-killer/yawn +-- lain.widgets.yawn +local yawn = +{ + units = "", + forecast = "", + icon = wibox.widget.imagebox(), + widget = wibox.widget.textbox() +} + +local project_path = debug.getinfo(1, 'S').source:match[[^@(.*/).*$]] +local localizations_path = project_path .. 'localizations/' +local icon_path = project_path .. 'icons/' +local api_url = 'http://weather.yahooapis.com/forecastrss' +local units_set = '?u=c&w=' -- Default is Celsius +local language = string.match(os.getenv("LANG"), "(%S*$*)[.]") +local weather_data = nil +local notification = nil +local city_id = nil +local sky = nil +local settings = {} +local update_timer = nil + +local function fetch_weather(args) + local toshow = args.toshow or "forecast" + local spr = args.spr or " " + local footer = args.footer or "" + + local url = api_url .. units_set .. city_id + local f = io.popen("curl --connect-timeout 1 -fsm 2 '" + .. url .. "'" ) + local text = f:read("*all") + io.close(f) + + -- In case of no connection or invalid city ID + -- widgets won't display + if text == "" or text:match("City not found") + then + sky = icon_path .. "na.png" + if text == "" then + weather_data = "Service not available at the moment." + return "N/A" + else + weather_data = "City not found!\n" .. + "Are you sure " .. city_id .. + " is your Yahoo city ID?" + return "?" + end + end + + -- Processing raw data + weather_data = text:gsub("<.->", "") + weather_data = weather_data:match("Current Conditions:.-Full") + weather_data = weather_data:gsub("Current Conditions:.-\n", "Now: ") + weather_data = weather_data:gsub("Forecast:.-\n", "") + weather_data = weather_data:gsub("\nFull", "") + weather_data = weather_data:gsub("[\n]$", "") + weather_data = weather_data:gsub(" [-] " , ": ") + weather_data = weather_data:gsub("[.]", ",") + weather_data = weather_data:gsub("High: ", "") + weather_data = weather_data:gsub(" Low: ", " - ") + + -- Getting info for text widget + local now = weather_data:sub(weather_data:find("Now:")+5, + weather_data:find("\n")-1) + local forecast = now:sub(1, now:find(",")-1) + local units = now:sub(now:find(",")+2, -2) + + -- Day/Night icon change + local hour = tonumber(os.date("%H")) + sky = icon_path + + if forecast == "Clear" or + forecast == "Fair" or + forecast == "Partly Cloudy" or + forecast == "Mostly Cloudy" + then + if hour >= 6 and hour <= 18 + then + sky = sky .. "Day" + else + sky = sky .. "Night" + end + end + + sky = sky .. forecast:gsub(" ", ""):gsub("/", "") .. ".png" + + -- In case there's no defined icon for current forecast + f = io.popen(sky) + if f == nil then + sky = icon_path .. "na.png" + else + io.close(f) + end + + -- Localization + local f = io.open(localizations_path .. language, "r") + if language:find("en_") == nil and f ~= nil + then + io.close(f) + for line in io.lines(localizations_path .. language) + do + word = string.sub(line, 1, line:find("|")-1) + translation = string.sub(line, line:find("|")+1) + weather_data = string.gsub(weather_data, word, translation) + end + end + + -- Finally setting infos + forecast = weather_data:match(": %S+"):gsub(": ", ""):gsub(",", "") + yawn.forecast = markup(yawn.forecast_color, markup.font(beautiful.font, forecast)) + yawn.units = markup(yawn.units_color, markup.font(beautiful.font, units)) + yawn.icon:set_image(sky) + + if toshow == "forecast" then + return yawn.forecast + elseif toshow == "units" then + return yawn.units + else -- "both" + return yawn.forecast .. spr + .. yawn.units .. footer + end +end + +function yawn.hide() + if notification ~= nil then + naughty.destroy(notification) + notification = nil + end +end + +function yawn.show(t_out) + if yawn.widget._layout.text == "?" + then + if update_timer ~= nil + then + update_timer:emit_signal("timeout") + else + fetch_weather(settings) + end + end + + yawn.hide() + + notification = naughty.notify({ + text = weather_data, + icon = sky, + timeout = t_out, + fg = yawn.notification_color + }) +end + +function yawn.register(id, args) + local args = args or {} + + settings = { args.toshow, args.spr, args.footer } + + yawn.units_color = args.units_color or + beautiful.fg_normal or "#FFFFFF" + yawn.forecast_color = args.forecast_color or + yawn.units_color + yawn.notification_color = args.notification_color or + beautiful.fg_focus or "#FFFFFF" + + if args.u == "f" then units_set = '?u=f&w=' end + + city_id = id + + update_timer = timer({ timeout = 600 }) -- 10 mins + update_timer:connect_signal("timeout", function() + yawn.widget:set_markup(fetch_weather(settings)) + end) + update_timer:start() + update_timer:emit_signal("timeout") + + yawn.icon:connect_signal("mouse::enter", function() + yawn.show(0) + end) + yawn.icon:connect_signal("mouse::leave", function() + yawn.hide() + end) +end + +function yawn.attach(widget, id, args) + yawn.register(id, args) + + widget:connect_signal("mouse::enter", function() + yawn.show(0) + end) + + widget:connect_signal("mouse::leave", function() + yawn.hide() + end) +end + +-- }}} + +return setmetatable(yawn, { __call = function(_, ...) return yawn.register(...) end }) diff --git a/widgets/yawn/localizations/it_IT b/widgets/yawn/localizations/it_IT new file mode 100644 index 0000000..0b74b60 --- /dev/null +++ b/widgets/yawn/localizations/it_IT @@ -0,0 +1,57 @@ +Now:|Ora: +Sun:|Dom: +Mon:|Lun: +Tue:|Mar: +Wed:|Mer: +Thu:|Gio: +Fri:|Ven: +Sat:|Sab: +Mostly Sunny|Abbastanza Soleggiato +Sunny|Soleggiato +Sun|Soleggiato +Rain/Thunder|Temporali +Isolated Thunderstorms|Temporali Isolati +Scattered Thunderstorms|Temporali Sparsi +Thundershowers|Rovesci Temporaleschi +Thunderstorms|Temporali +Thunder|Temporale +AM|In Mattinata +PM|Nel Pomeriggio +Early|In Mattinata +Late|In Serata +Few|Sporadiche +Severe|Forti +Clear|Sereno +Fair|Sereno +Partly|Parzialmente +Mostly|Molto +Cloudy|Nuvoloso +Clouds|Nuvoloso +Scattered Showers|Temporali Sparsi +Light Snow Showers|Nevicate Leggere +Snow Showers|Nevicate +aeavy Snow|Forti Nevicate +Scattered Snow Showers|Nevicate Sparse +Mixed Rain And Snow|Pioggia E Neve +Mixed Rain And Sleet|Pioggia E Nevischio +Mixed Snow And Sleet|Neve E Nevischio +Mixed Rain And Hail|Pioggia E Grandine +Snow Flurries|Folate Di Neve +Blowing Snow|Neve Battente +Blowing Rain|Pioggia Battente +Heavy Rain|Forti Piogge +Freezing Rain|Pioggia Congelantesi +Showers|Piogge +Light Rain|Pioggia Leggera +Heavy|Forti +Rain|Piovoso +Windy|Ventoso +Wind|Ventoso +Snow|Neve +Sleet|Nevischio +Drizzle|Pioggerella +Freezing Drizzle|Pioggerella Congelantesi +Hail|Grandine +Foggy|Nebbia +Haze|Nebbia +Light|Leggere diff --git a/widgets/yawn/localizations/localization_template b/widgets/yawn/localizations/localization_template new file mode 100644 index 0000000..98d527d --- /dev/null +++ b/widgets/yawn/localizations/localization_template @@ -0,0 +1,57 @@ +Now:| +Sun:| +Mon:| +Tue:| +Wed:| +Thu:| +Fri:| +Sat:| +Mostly Sunny| +Sunny| +Sun| +Rain/Thunder| +Isolated Thunderstorms| +Scattered Thunderstorms| +Thundershowers| +Thunderstorms| +Thunder| +AM| +PM| +Early| +Late| +Few| +Severe| +Clear| +Fair| +Partly| +Mostly| +Cloudy| +Clouds| +Scattered Showers| +Light Snow Showers| +Snow Showers| +Heavy Snow| +Scattered Snow Showers| +Mixed Rain And Snow| +Mixed Rain And Sleet| +Mixed Snow And Sleet| +Mixed Rain And Hail| +Snow Flurries| +Blowing Snow| +Blowing Rain| +Heavy Rain| +Freezing Rain| +Showers| +Light Rain| +Heavy| +Rain| +Windy| +Wind| +Snow| +Sleet| +Drizzle| +Freezing Drizzle| +Hail| +Foggy| +Haze| +Light|