pax_global_header00006660000000000000000000000064131611756400014516gustar00rootroot0000000000000052 comment=241aa9c197bb34fb6511cc62709b2ee464deb04f Nagstamon/000077500000000000000000000000001316117564000130115ustar00rootroot00000000000000Nagstamon/.gitignore000066400000000000000000000001121316117564000147730ustar00rootroot00000000000000*.pyc *~ .idea/ .__pycache__/ *.swp nagstamon.conf/ .directory .metadata/ Nagstamon/COPYRIGHT000066400000000000000000000000661316117564000143060ustar00rootroot00000000000000Copyright (C) 2009 Henri Wahl Nagstamon/ChangeLog000066400000000000000000000337211316117564000145710ustar00rootroot00000000000000nagstamon (3.0.2) stable; urgency=low * New upstream - fixed Retina problem for macOS -- Henri Wahl Fri, 22 Sep 2017 14:00:00 +0200 nagstamon (3.0.1) stable; urgency=low * New upstream - fixes for Zabbix - fixed filters for Op5 - fixes for update detection and self-signed certificates -- Henri Wahl Tue, 19 Sep 2017 16:00:00 +0200 nagstamon (3.0) stable; urgency=low * New upstream - added Kerberos authentification - added increased security by taking TLS seriously - added window mode - added large OK label for fullscreen and window mode - added click-somewhere-to-close-statuswindow mode - added custom views to Check_MK - added monitor type Monitos3 - added monitor type SNAG-View 3 - added monitor type Sensu - fixes statuswindow garbage when changing mode - fixed filename bug - fixed check for .Xauthority file - fixes for Centreon - fixes for Check_MK 1.4.0 - fixes for Zabbix - switched to pyinstaller -- Henri Wahl Wed, 13 Sep 2017 16:00:00 +0200 nagstamon (2.0.1) stable; urgency=low * New upstream - Major Centreon bug making it useless - Icinga version check fix - Thruk login fix - EWMH initialization change - Systrayicon left mouse click without context menu - DBus crash workaround -- Henri Wahl Thu, 20 Oct 2016 08:00:00 +0200 nagstamon (2.0) stable; urgency=low * New upstream - Based on Qt 5 it now comes with a better integrated look-and-feel – especially remarkable on MacOS - Partly simplified design - Less clutter in setting dialogs - Runs on latest Windows and MacOS - Uses QT 5 multimedia which means native sound on Linux and MacOS - Uses only SVG graphics – allows changing colors even in systray icon - Customizable font and font size - Adjust to dark or light desktop theme - Action allowing to copy host/service information to clipboard - Added ‘Archive Event’ action for Check_MK monitors - Additionally supports IcingaWeb2 - Updated Opsview and Centreon to support latest monitor server versions - Experimental support for Livestatus and Zenoss - New build script based on cx-Freeze for Windows and MacOS - Native 64 bit version for Windows - No or less memory leaks, especially in Windows - Make sure only one instance per config is running -- Henri Wahl Mon, 29 Aug 2016 16:00:00 +0200 nagstamon (1.0.1) stable; urgency=low * New upstream - added option to disable system keyring storage to prevent crashes - reverted default sorting order to "Descending" - fixed too narrow fullscreen display - fixed vanishing Nagstamon submenu in Ubuntu Appindicator -- Henri Wahl Mon, 22 Sep 2014 9:00:00 +0200 nagstamon (1.0) stable; urgency=low * New upstream - added custom event notification with custom commands - added highlighting of new events - added storage of passwords in OS keyring - added optional tooltip for full status information - added support for applying custom actions to specific monitor only - added copy buttons for servers and actions dialogs - added stopping notification if event already vanished - added support for Op5Monitor 6.3 instead of Ninja - added experimental Zabbix support - added automatic refreshing after acknowledging - added permanent hamburger menu - unified layout of dialogs - various Check_MK improvements - fixed old regression not-staying-on-top-bug - fixed Check_MK-Recheck-DOS-bug - fixed pop window size calculation on multiple screens - fixed following popup window on multiple screens - fixed hiding dialogs in MacOSX - fixed ugly statusbar font in MacOSX - fixed use of changed colors - fixed non-ascending default sort order - fixed Opsview downtime dialog - fixed sometimes not working context menu - fixed some GUI glitches - fixed password saving bug - fixed Centreon language inconsistencies - fixed regression Umlaut bug -- Henri Wahl Mon, 28 Jul 2014 09:30:00 +0200 nagstamon (1.0rc2) unstable; urgency=low * New upstream - added automatic refreshing after acknowledging - added permanent hamburger menu - unified layout of dialogs - fixed some GUI glitches - fixed password saving bug - fixed Centreon language inconsistencies - fixed regression Umlaut bug -- Henri Wahl Tue, 08 Jul 2014 11:00:00 +0200 nagstamon (1.0rc1) unstable; urgency=low * New upstream - added custom event notification with custom commands - added highlighting of new events - added storage of passwords in OS keyring - added optional tooltip for full status information - added support for applying custom actions to specific monitor only - added copy buttons for servers and actions dialogs - added stopping notification if event already vanished - added support for Op5Monitor 6.3 instead of Ninja - added experimental Zabbix support - fixed old regression not-staying-on-top-bug - fixed Check_MK-Recheck-DOS-bug - fixed pop window size calculation on multiple screens - fixed following popup window on multiple screens - fixed hiding dialogs in MacOSX - fixed ugly statusbar font in MacOSX - fixed use of changed colors - fixed non-ascending default sort order - fixed Opsview downtime dialog - fixed sometimes not working context menu - various Check_MK improvements -- Henri Wahl Tue, 24 Jun 2014 11:00:00 +0200 nagstamon (0.9.11) stable; urgency=low * New upstream - added Ubuntu AppIndicator support - added libnotify desktop notification support - added Centreon criticality support - fixed broken authentication dialog - fixed wrong OK state for Nagios and Icinga - fixed Correct-Statusbar-Position-O-Matic - fixed some Thruk issues - fixed popup resizing artefact - fixed some server edit dialog bugs - fixed missing auth field in Icinga when credentials are wrong - fixed quoting URLs for browser actions -- Henri Wahl Wed, 11 Sep 2013 09:00:00 +0200 nagstamon (0.9.11rc1) unstable; urgency=low * New upstream - added Ubuntu AppIndicator support - added libnotify desktop notification support - added Centreon criticality support - fixed broken authentication dialog - fixed wrong OK state for Nagios and Icinga - fixed Correct-Statusbar-Position-O-Matic - fixed some Thruk issues - fixed popup resizing artefact - fixed some server edit dialog bugs - fixed missing auth field in Icinga when credentials are wrong -- Henri Wahl Mon, 29 Jul 2013 10:35:00 +0200 nagstamon (0.9.10) stable; urgency=low * New upstream - added fullscreen option - added Thruk support - added Check_MK cookie-based auth - added new Centreon autologin option - added configurable default sort order - added filter for hosts in hard/soft state for Nagios, Icinga, Opsview and Centreon - added $STATUS-INFO$ variable for custom actions - added audio alarms also in fullscreen mode - improved update interval set in seconds instead minutes - improved Icinga JSON support - improved Centreon 2.4 xml/broker support - improved Nagios 3.4 pagination support - improved nicer GTK theme Murrine on MacOSX - fixed security bug - fixed some memory leaks - fixed superfluous passive icon for Check_MK - fixed blocking of shutdown/reboot on MacOSX - fixed saving converted pre 0.9.9 config immediately - fixed statusbar position when offscreen - fixed some GUI issues - fixed update detection -- Henri Wahl Wed, 11 Jul 2013 11:07:13 +0200 nagstamon (0.9.10rc2) unstable; urgency=low * New upstream - audio alarms also in fullscreen mode - adjust x0 y0 position of statusbar when offscreen - save converted pre 0.9.9 config immediately -- Henri Wahl Tue, 09 Jul 2013 14:25:00 +0200 nagstamon (0.9.10rc1) unstable; urgency=low * New upstream - added fullscreen option - added Thruk support - added Check_MK cookie-based auth - added new Centreon autologin option - added configurable default sort order - added filter for hosts in hard/soft state for Nagios, Icinga, Opsview and Centreon - added $STATUS-INFO$ variable for custom actions - update interval set in seconds instead minutes - improved Icinga JSON support - improved Centreon 2.4 xml/broker support - improved Nagios 3.4 pagination support - uses nicer GTK theme Murrine on MacOSX - fixed some memory leaks - fixed superfluous passive icon for Check_MK - fixed blocking of shutdown/reboot on MacOSX - fixed some GUI issues - fixed update detection -- Henri Wahl Wed, 03 Jul 2013 10:25:00 +0200 nagstamon (0.9.9.1-1) stable; urgency=low * New upstream - added custom actions in context menu - added reauthentication in case of authenticaton problems - changed configuration file to configuration directory (default: ~/.nagstamon) - added filter for flapping hosts and services - added history button for monitors - added shortcut to filter settings in popup window - improved keyboard usage in acknowledge/downtime/submit dialogs - fixed bug in Icinga acknowledgement - fixed bug in Check_MK Multisite sorting - fixed some Check_MK Multisite UTF trouble - fixed some GUI artefacts when resizing popup window -- Henri Wahl Fri, 13 Apr 2012 11:25:00 +0200 nagstamon (0.9.8.1-1) stable; urgency=low * New upstream - added customizable acknowledge/downtime/submit-result defaults - added regexp filter for status information column - added option to connect to hosts via its monitor hostname without HTTP overhead - added ability to keep status detail popup open despite hovering away - added option to change offset between popup window and systray icon to avoid partly hidden popup - fixed some popup artefacts - fixed various bugs with acknowledgement flags (persistent/sticky/notification), now they are actually working - fixed some issues when running on MacOS X -- Henri Wahl Wed, 10 Oct 2011 14:49:00 +0200 nagstamon (0.9.7.1-1) stable; urgency=low * New upstream - hot fix for broken Centreon support - sf.net bug 3309166 -- Henri Wahl Fri, 30 May 2011 12:01:00 +0200 nagstamon (0.9.7-1) stable; urgency=low * New upstream - on some servers now context menu allows submitting check results for hosts and services - added filter for services on acknowledged hosts - added icons for "passiveonly" and "flapping" hosts and services - fix for uneditable text entry fields in settings dialog - sf.net bug 3300873 - fix for not working filter "services on hosts in maintenance" - sf.net bug 3299790 - fix for soft state detection in Centreon - sf.net bug 3303861 - fix for not filtered services which should have been filtered - sf.net bug 3308008 -- Henri Wahl Fri, 27 May 2011 16:01:00 +0200 nagstamon (0.9.6.1-1) stable; urgency=low * fix for sf.net bug 3298321 - displaying error when all is OK -- Henri Wahl Fri, 06 May 2011 16:01:00 +0200 nagstamon (0.9.6-1) stable; urgency=low * New upstreeam release - improved, full Ninja support - rewritten filtering mechanism allows new features - displaying icons in status overview popup indicating states "acknowledged" and "scheduled downtime" - added option to play notification sounds more than once - small UI improvements - uses BeautifulSoup instead of lxml - uses GTK UI Builder instead of glade - as always: bugfixes -- Henri Wahl Fri, 06 May 2011 16:01:00 +0200 nagstamon (0.9.5-1) stable; urgency=low * New upstream release - added op5 Ninja support - added Check_MK Multisite support - improved Icinga support (compatibility with Icinga 1.3) - improved Centreon support (compatible with Centreon 2.1) - added sortable columns in status overview - added customizable colors - better debugging and error messages - password must not be stored in config file - major memory leak closed, various bugs fixed -- Henri Wahl Tue, 05 Apr 2011 13:23:00 +0200 nagstamon (0.9.4-1) stable; urgency=low * New upstream release (Closes: #582977) * removed debian/manpages * renamed debian/nagstamon.install to debian/install * debian/patches - removed settings_glade - removed setup_patch -- Carl Chenet Sat, 19 Jun 2010 12:46:42 +0200 nagstamon (0.9.3-2) stable; urgency=low * debian/patches/default_search - disable the default search for newer versions (Closes: #585928) * debian/patches/series - added default_search -- Carl Chenet Tue, 15 Jun 2010 00:41:22 +0200 nagstamon (0.9.3-1) stable; urgency=low * New upstream release * Switching to 3.0 source format * debian/patches - added settings_glade patch to close #2998035 upstream bug - added setup_patch to remove an absolute link for the upstream manpage * debian/control - added quilt in Build-Depends - added the support of Opsview servers in the long description * debian/nagstamon.manpages - switched to the file provided by the upstream * debian/nagstamon.desktop - commenting the OnlyShowIn directive -- Carl Chenet Sun, 23 May 2010 12:47:11 +0200 nagstamon (0.9.2-2) stable; urgency=low * debian/control - Added a mandatory runtime missing dependency python-pkg-resources. - Fixed a typo in the long message. -- Carl Chenet Wed, 24 Mar 2010 23:18:21 +0100 nagstamon (0.9.2-1) stable; urgency=low * Initial release. (Closes: #534842) -- Carl Chenet Mon, 22 Feb 2010 14:16:44 +0100 Nagstamon/LICENSE000066400000000000000000001323301316117564000140200ustar00rootroot00000000000000Nagstamon is licensed under the following GPL2: 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. Copyright (C) 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. , 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. Nagstamon uses BeautifulSoup under the following license: Copyright (c) 2004-2010, Leonard Richardson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the the Beautiful Soup Consortium and All Night Kosher Bakery nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE, DAMMIT. Nagstamon's experimental Zabbix support is based on zabbix_api.py, which is licensed under LGPL 2.1: GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 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. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, 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 and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, 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 library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete 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 distribute a copy of this License along with the Library. 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 Library or any portion of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, 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 Library, 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 Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you 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. If distribution of 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 satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be 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. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library 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. 9. 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 Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library 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 with this License. 11. 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 Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library 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 Library. 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. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library 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. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser 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 Library 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 Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, 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 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. 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 LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. 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. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; 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. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it!Nagstamon/Nagstamon/000077500000000000000000000000001316117564000147405ustar00rootroot00000000000000Nagstamon/Nagstamon/Config.py000066400000000000000000001350601316117564000165240ustar00rootroot00000000000000#!/usr/bin/env python3 # encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2016 Henri Wahl et al. # # 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 import os import platform import sys import argparse import configparser import base64 import zlib import datetime import keyring from collections import OrderedDict # avoid build error because of debug_queue unknown to setup.py # if anybody knows a more elegant way (which surely exists) let me know # it had to work quickly! # ##if not 'setup.py' in sys.argv[0] and not 'build.py' in sys.argv[0]: # ## # get debug queue from nagstamon.py # ## debug_queue = sys.modules['__main__'].debug_queue # queue.Queue() needs threading module which might be not such a good idea to be used # because QThread is already in use # maybe not the most logical place here to be defined but at least all # modules access Config.py so it can be distributed from here debug_queue = list() # temporary dict for string-to-bool-conversion # the bool:bool relations are thought to make things easier in Dialog_Settings.ok() BOOLPOOL = {'False': False, 'True': True, False: False, True: True} # config settings which should always be strings, never converted to integer or bool CONFIG_STRINGS = ['custom_browser', 'debug_file', 'notification_custom_sound_warning', 'notification_custom_sound_critical', 'notification_custom_sound_down', 'notification_action_warning_string', 'notification_action_critical_string', 'notification_action_down_string', 'notification_action_ok_string', 'notification_custom_action_string', 'notification_custom_action_separator', 're_host_pattern', 're_service_pattern', 're_status_information_pattern', 're_criticality_pattern', 'font', 'defaults_acknowledge_comment', 'defaults_submit_check_result_comment', 'defaults_downtime_comment', 'name', 'monitor_url', 'monitor_cgi_url', 'username', 'password', 'proxy_address', 'proxy_username', 'proxy_password', 'autologin_key', 'custom_cert_ca_file', 'monitor_site' ] # needed when OS-specific decisions have to be made, mostly Linux/non-Linux NON_LINUX = ('Darwin', 'Windows') # invalid characters in path names of config files have to be replaced INVALID_CHARACTERS = { '\\': '_backslash_', '/': '_slash_', ':': '_colon_', '*': '_aterisk_', '?': '_question_mark_', '"': '_double_quotes_', '<': '_less_than_', '>': '_greater_than_'} class AppInfo(object): """ contains app information previously located in GUI.py """ NAME = 'Nagstamon' VERSION = '3.0.2' WEBSITE = 'https://nagstamon.ifw-dresden.de' COPYRIGHT = '©2008-2017 Henri Wahl et al.' COMMENTS = 'Nagios status monitor for your desktop' # version URL depends on version string if 'alpha' in VERSION.lower() or\ 'beta' in VERSION.lower() or\ 'rc' in VERSION.lower() or\ '-' in VERSION.lower(): VERSION_URL = WEBSITE + '/version/unstable' else: VERSION_URL = WEBSITE + '/version/stable' class Config(object): """ The place for central configuration. """ def __init__(self): """ read config file and set the appropriate attributes supposed to be sensible defaults """ # move from minute interval to seconds self.update_interval_seconds = 60 self.short_display = False self.long_display = True self.show_tooltips = True self.show_grid = True self.grid_use_custom_intensity = False self.grid_alternation_intensity = 10 self.highlight_new_events = True self.default_sort_field = 'status' self.default_sort_order = 'descending' self.filter_all_down_hosts = False self.filter_all_unreachable_hosts = False self.filter_all_flapping_hosts = False self.filter_all_unknown_services = False self.filter_all_information_services = False self.filter_all_warning_services = False self.filter_all_average_services = False self.filter_all_high_services = False self.filter_all_critical_services = False self.filter_all_disaster_services = False self.filter_all_flapping_services = False self.filter_acknowledged_hosts_services = False self.filter_hosts_services_disabled_notifications = False self.filter_hosts_services_disabled_checks = False self.filter_hosts_services_maintenance = False self.filter_services_on_acknowledged_hosts = False self.filter_services_on_down_hosts = False self.filter_services_on_hosts_in_maintenance = False self.filter_services_on_unreachable_hosts = False self.filter_hosts_in_soft_state = False self.filter_services_in_soft_state = False self.position_x = 30 self.position_y = 30 self.position_width = 640 self.position_height = 480 self.popup_details_hover = True self.popup_details_clicking = False self.close_details_hover = True self.close_details_clicking = False self.close_details_clicking_somewhere = False self.connect_by_host = True self.connect_by_dns = False self.connect_by_ip = False self.use_default_browser = True self.use_custom_browser = False self.custom_browser = '' self.debug_mode = False self.debug_to_file = False self.debug_file = os.path.expanduser('~') + os.sep + "nagstamon.log" self.check_for_new_version = True self.notification = True self.notification_flashing = True self.notification_desktop = False self.notification_actions = False self.notification_sound = True self.notification_sound_repeat = False self.notification_default_sound = True self.notification_custom_sound = False self.notification_custom_sound_warning = '' self.notification_custom_sound_critical = '' self.notification_custom_sound_down = '' self.notification_action_warning = False self.notification_action_warning_string = '' self.notification_action_critical = False self.notification_action_critical_string = '' self.notification_action_down = False self.notification_action_down_string = '' self.notification_action_ok = False self.notification_action_ok_string = '' self.notification_custom_action = False self.notification_custom_action_string = '' self.notification_custom_action_separator = '' self.notification_custom_action_single = False self.notify_if_up = False self.notify_if_information = True self.notify_if_warning = True self.notify_if_average = True self.notify_if_high = True self.notify_if_critical = True self.notify_if_disaster = True self.notify_if_unknown = True self.notify_if_unreachable = True self.notify_if_down = True # Regular expression filters self.re_host_enabled = False self.re_host_pattern = '' self.re_host_reverse = False self.re_service_enabled = False self.re_service_pattern = '' self.re_service_reverse = False self.re_status_information_enabled = False self.re_status_information_pattern = '' self.re_status_information_reverse = False self.color_ok_text = self.default_color_ok_text = '#FFFFFF' self.color_ok_background = self.default_color_ok_background = '#006400' self.color_information_text = self.default_color_information_text = "#000000" self.color_information_background = self.default_color_information_background = "#7499FF" self.color_warning_text = self.default_color_warning_text = "#000000" self.color_warning_background = self.default_color_warning_background = '#FFFF00' self.color_average_text = self.default_color_average_text = "#000000" self.color_average_background = self.default_color_average_background = "#FFA059" self.color_high_text = self.default_color_high_text = "#000000" self.color_high_background = self.default_color_high_background = "#E97659" self.color_unknown_text = self.default_color_unknown_text = '#000000' self.color_unknown_background = self.default_color_unknown_background = '#FFA500' self.color_critical_text = self.default_color_critical_text = '#FFFFFF' self.color_critical_background = self.default_color_critical_background = '#FF0000' self.color_disaster_text = self.default_color_disaster_text = "#FFFFFF" self.color_disaster_background = self.default_color_disaster_background = "#660000" self.color_unreachable_text = self.default_color_unreachable_text = '#FFFFFF' self.color_unreachable_background = self.default_color_unreachable_background = '#8B0000' self.color_down_text = self.default_color_down_text = '#FFFFFF' self.color_down_background = self.default_color_down_background = '#000000' self.color_error_text = self.default_color_error_text = '#000000' self.color_error_background = self.default_color_error_background = '#D3D3D3' self.statusbar_floating = True self.icon_in_systray = False self.windowed = False self.fullscreen = False self.fullscreen_display = 0 self.systray_offset_use = False self.systray_offset = 10 self.font = '' self.defaults_acknowledge_sticky = False self.defaults_acknowledge_send_notification = False self.defaults_acknowledge_persistent_comment = False self.defaults_acknowledge_all_services = False self.defaults_acknowledge_comment = 'acknowledged' self.defaults_submit_check_result_comment = 'check result submitted' self.defaults_downtime_duration_hours = 2 self.defaults_downtime_duration_minutes = 0 self.defaults_downtime_comment = 'scheduled downtime' self.defaults_downtime_type_fixed = True self.defaults_downtime_type_flexible = False # internal flag to determine if keyring is available at all - defaults to False # use_system_keyring is checked and defined some lines later after config file was read self.keyring_available = False # setting for keyring usage self.use_system_keyring = False # Special FX # Centreon self.re_criticality_enabled = False self.re_criticality_pattern = '' self.re_criticality_reverse = False # the app is unconfigured by default and will stay so if it # would not find a config file self.unconfigured = True # adding cli args variable self.cli_args = {} # Parse the command line parser = argparse.ArgumentParser(description='Nagstamon for your CLI') # separate NagstaCLI from if len(sys.argv) > 2 or (len(sys.argv) > 1 and sys.argv[1] in ['--help', '-h']): parser.add_argument('--servername', type=str, help="name of the (Nagios)server. Look in nagstamon config") parser.add_argument('--hostname', type=str) parser.add_argument('--comment', type=str, default="") parser.add_argument('--service', type=str, default="", help="specify service, if needed. Mostly the whole host goes to downstate") parser.add_argument('--fixed', type=str, choices=['y', 'n'], default="y", help="fixed=n means wait for service/host to go down, then start the downtime") parser.add_argument('--start_time', type=str, help="start time for downtime") parser.add_argument('--hours', type=int, help="amount of hours for downtime") parser.add_argument('--minutes', type=int, help="amount of minutes for downtime") parser.add_argument('--config', type=str, help="Path for configuration folder") parser.add_argument('--output', type=str, choices=['y', 'n'], default="y", help="lists given parameter (for debugging)") else: parser.add_argument('config', nargs='?', help='Path for configuration folder') self.cli_args, unknown = parser.parse_known_args() # try to use a given config file - there must be one given if len(sys.argv) < 3 and self.cli_args.config: # allow to give a config file self.configdir = self.cli_args.config # otherwise if there exits a configdir in current working directory it should be used elif os.path.exists(os.getcwd() + os.sep + 'nagstamon.config'): self.configdir = os.getcwd() + os.sep + 'nagstamon.config' else: # ~/.nagstamon/nagstamon.conf is the user conf file # os.path.expanduser('~') finds out the user HOME dir where # nagstamon expects its conf file to be self.configdir = os.path.expanduser('~') + os.sep + '.nagstamon' self.configfile = self.configdir + os.sep + 'nagstamon.conf' # make path fit for actual os, normcase for letters and normpath for path self.configfile = os.path.normpath(os.path.normcase(self.configfile)) # because the name of the configdir is also stored in the configfile # there may be situations where the name gets overwritten by a # wrong name so it will be stored here temporarily configdir_temp = self.configdir # default settings dicts self.servers = dict() self.actions = dict() if os.path.exists(self.configfile): # instantiate a configparser to parse the conf file # SF.net bug #3304423 could be fixed with allow_no_value argument which # is only available since Python 2.7 # since Python 3 '%' will be interpolated by default which crashes # with some URLs config = configparser.ConfigParser(allow_no_value=True, interpolation=None) config.read(self.configfile) # go through all sections of the conf file for section in config.sections(): # go through all items of each sections (in fact there is only on # section which has to be there to comply to the .INI file standard for i in config.items(section): # omit config file info as it makes no sense to store its path if not i[0] in ('configfile', 'configdir'): # create a key of every config item with its appropriate value # check first if it is a bool value and convert string if it is if i[1] in BOOLPOOL: object.__setattr__(self, i[0], BOOLPOOL[i[1]]) # in case there are numbers intify them to avoid later conversions # treat negative value specially as .isdecimal() will not detect it elif i[1].isdecimal() or \ (i[1].startswith('-') and i[1].split('-')[1].isdecimal()): object.__setattr__(self, i[0], int(i[1])) else: object.__setattr__(self, i[0], i[1]) # because the switch from Nagstamon 1.0 to 1.0.1 brings the use_system_keyring property # and all the thousands 1.0 installations do not know it yet it will be more comfortable # for most of the Windows users if it is only defined as False after it was checked # from config file if 'use_system_keyring' not in self.__dict__.keys(): if self.unconfigured is True: # an unconfigured system should start with no keyring to prevent crashes self.use_system_keyring = False else: # a configured system seemed to be able to run and thus use system keyring if platform.system() in NON_LINUX: self.use_system_keyring = True else: self.use_system_keyring = self.KeyringAvailable() # reset self.configdir to temporarily saved value in case it differs from # the one read from configfile and so it would fail to save next time self.configdir = configdir_temp # Servers configuration... self.servers = self._LoadServersMultipleConfig() # ... and actions self.actions = self.LoadMultipleConfig("actions", "action", "Action") # seems like there is a config file so the app is not unconfigured anymore self.unconfigured = False # Load actions if Nagstamon is not unconfigured, otherwise load defaults if self.unconfigured is True: self.actions = self._DefaultActions() # do some conversion stuff needed because of config changes and code cleanup self._LegacyAdjustments() def _LoadServersMultipleConfig(self): """ load servers config - special treatment because of obfuscated passwords """ self.keyring_available = self.KeyringAvailable() servers = self.LoadMultipleConfig('servers', 'server', 'Server') # deobfuscate username + password inside a try-except loop # if entries have not been obfuscated yet this action should raise an error # and old values (from nagstamon < 0.9.0) stay and will be converted when next # time saving config try: for server in servers: # usernames for monitor server and proxy servers[server].username = self.DeObfuscate(servers[server].username) servers[server].proxy_username = self.DeObfuscate(servers[server].proxy_username) # passwords for monitor server and proxy if servers[server].save_password == 'False': servers[server].password = "" elif self.keyring_available and self.use_system_keyring: password = keyring.get_password('Nagstamon', '@'.join((servers[server].username, servers[server].monitor_url))) or "" if password == "": if servers[server].password != "": servers[server].password = self.DeObfuscate(servers[server].password) else: servers[server].password = password elif servers[server].password != "": servers[server].password = self.DeObfuscate(servers[server].password) # proxy password if self.keyring_available and self.use_system_keyring: proxy_password = keyring.get_password('Nagstamon', '@'.join(('proxy', servers[server].proxy_username, servers[server].proxy_address))) or "" if proxy_password == "": if servers[server].proxy_password != "": servers[server].proxy_password = self.DeObfuscate(servers[server].proxy_password) else: servers[server].proxy_password = proxy_password elif servers[server].proxy_password != "": servers[server].proxy_password = self.DeObfuscate(servers[server].proxy_password) # do only deobfuscating if any autologin_key is set - will be only Centreon if 'autologin_key' in servers[server].__dict__.keys(): if len(servers[server].__dict__['autologin_key']) > 0: servers[server].autologin_key = self.DeObfuscate(servers[server].autologin_key) # only needed for those who used Icinga2 before it became IcingaWeb2 if servers[server].type == 'Icinga2': servers[server].type = 'IcingaWeb2' except Exception: import traceback traceback.print_exc(file=sys.stdout) return servers def LoadMultipleConfig(self, settingsdir, setting, configobj): """ load generic config into settings dict and return to central config """ # defaults as empty dict in case settings dir/files could not be found settings = OrderedDict() try: if os.path.exists(self.configdir + os.sep + settingsdir): for f in sorted(os.listdir(self.configdir + os.sep + settingsdir)): if f.startswith(setting + '_') and f.endswith('.conf'): config = configparser.ConfigParser(allow_no_value=True, interpolation=None) config.read(self.configdir + os.sep + settingsdir + os.sep + f) # create object for every setting name = config.sections()[0].replace(setting + '_', '', 1) settings[name] = globals()[configobj]() # go through all items of the server for i in config.items(setting + '_' + name): # create a key of every config item with its appropriate value if i[1] in BOOLPOOL: value = BOOLPOOL[i[1]] # in case there are numbers intify them to avoid later conversions # treat negative value specially as .isdecimal() will not detect it elif i[1].isdecimal() or \ (i[1].startswith('-') and i[1].split('-')[1].isdecimal()): value = int(i[1]) else: value = i[1] settings[name].__setattr__(i[0], value) except Exception: import traceback traceback.print_exc(file=sys.stdout) return settings def SaveConfig(self): """ save config file """ try: # Make sure .nagstamon is created if not os.path.exists(self.configdir): os.makedirs(self.configdir) # save config file with configparser config = configparser.ConfigParser(allow_no_value=True, interpolation=None) # general section for Nagstamon config.add_section('Nagstamon') for option in self.__dict__: if option not in ['servers', 'actions', 'configfile', 'configdir', 'cli_args']: config.set('Nagstamon', option, str(self.__dict__[option])) # because the switch from Nagstamon 1.0 to 1.0.1 brings the use_system_keyring property # and all the thousands 1.0 installations do not know it yet it will be more comfortable # for most of the Windows users if it is only defined as False after it was checked # from config file if 'use_system_keyring' not in self.__dict__.keys(): if self.unconfigured is True: # an unconfigured system should start with no keyring to prevent crashes self.use_system_keyring = False else: # a configured system seemed to be able to run and thus use system keyring if platform.system() in NON_LINUX: self.use_system_keyring = True else: self.use_system_keyring = self.KeyringAvailable() # save actions dict self.SaveMultipleConfig('servers', 'server') # save actions dict self.SaveMultipleConfig('actions', 'action') # open, save and close config file f = open(os.path.normpath(self.configfile), "w") config.write(f) f.close() # debug if self.debug_mode: debug_queue.append('DEBUG: {0} Saving configuration to file {1}'.format(str(datetime.datetime.now()), self.configfile)) except Exception as err: import traceback traceback.print_exc(file=sys.stdout) # debug if self.debug_mode: debug_queue.append('ERROR: {0} {1} while saving configuration to file {2}'.format(str(datetime.datetime.now()), err, self.configfile)) def SaveMultipleConfig(self, settingsdir, setting): """ saves conf files for settings like actions in extra directories "multiple" means that multiple confs for actions or servers are loaded, not just one like for e.g. sound file """ # only import keyring lib if configured to do so - to avoid Windows crashes # like https://github.com/HenriWahl/Nagstamon/issues/97 if self.use_system_keyring is True: self.keyring_available = self.KeyringAvailable() # one section for each setting for s in self.__dict__[settingsdir]: config = configparser.ConfigParser(allow_no_value=True, interpolation=None) config.add_section(setting + '_' + s) for option in self.__dict__[settingsdir][s].__dict__: # obfuscate certain entries in config file - special arrangement for servers if settingsdir == 'servers': if option in ['username', 'password', 'proxy_username', 'proxy_password', 'autologin_key']: value = self.Obfuscate(self.__dict__[settingsdir][s].__dict__[option]) if option == 'password': if self.__dict__[settingsdir][s].save_password is False: value = '' elif self.keyring_available and self.use_system_keyring: if self.__dict__[settingsdir][s].password != '': # provoke crash if password saving does not work - this is the case # on newer Ubuntu releases try: keyring.set_password('Nagstamon', '@'.join((self.__dict__[settingsdir][s].username, self.__dict__[settingsdir][s].monitor_url)), self.__dict__[settingsdir][s].password) except Exception: import traceback traceback.print_exc(file=sys.stdout) sys.exit(1) value = '' if option == 'proxy_password': if self.keyring_available and self.use_system_keyring: if self.__dict__[settingsdir][s].proxy_password != '': # provoke crash if password saving does not work - this is the case # on newer Ubuntu releases try: keyring.set_password('Nagstamon', '@'.join(('proxy', self.__dict__[settingsdir][s].proxy_username, self.__dict__[settingsdir][s].proxy_address)), self.__dict__[settingsdir][s].proxy_password) except Exception: import traceback traceback.print_exc(file=sys.stdout) sys.exit(1) value = '' config.set(setting + '_' + s, option, str(value)) else: config.set(setting + '_' + s, option, str(self.__dict__[settingsdir][s].__dict__[option])) else: config.set(setting + '_' + s, option, str(self.__dict__[settingsdir][s].__dict__[option])) # open, save and close config_server file if not os.path.exists(self.configdir + os.sep + settingsdir): os.makedirs(self.configdir + os.sep + settingsdir) # replace invalid characters by their literal replacements s_clean = s for c in INVALID_CHARACTERS: s_clean = s_clean.replace(c, INVALID_CHARACTERS[c]) f = open(os.path.normpath(self.configdir + os.sep + settingsdir + os.sep + setting + "_" + s_clean + ".conf"), "w") config.write(f) f.close() # ### clean up old deleted/renamed config files # ##if os.path.exists(self.configdir + os.sep + settingsdir): # ## for f in os.listdir(self.configdir + os.sep + settingsdir): # ## if not f.split(setting + "_")[1].split(".conf")[0] in self.__dict__[settingsdir]: # ## os.unlink(self.configdir + os.sep + settingsdir + os.sep + f) def KeyringAvailable(self): """ determine if keyring module and an implementation is available for secure password storage """ try: # Linux systems should use keyring only if it comes with the distro, otherwise chances are small # that keyring works at all if platform.system() in NON_LINUX: # safety first - if not yet available disable it if 'use_system_keyring' not in self.__dict__.keys(): self.use_system_keyring = False # only import keyring lib if configured to do so # necessary to avoid Windows crashes like https://github.com/HenriWahl/Nagstamon/issues/97 if self.use_system_keyring is True: # hint for packaging: nagstamon.spec always have to match module path # keyring has to be bound to object to be used later import keyring return not (keyring.get_keyring() is None) else: return False else: # keyring and secretstorage have to be importable import keyring # import secretstorage module as dependency of keyring - # if not available keyring won't work import secretstorage if ("SecretService") in dir(keyring.backends) and not (keyring.get_keyring() is None): return True except Exception: import traceback traceback.print_exc(file=sys.stdout) return False def Obfuscate(self, string, count=5): """ Obfuscate a given string to store passwords etc. """ string = string.encode() for i in range(count): string = base64.b64encode(string).decode() string = list(string) string.reverse() string = "".join(string) string = string.encode() string = zlib.compress(string) # make unicode of bytes string string = base64.b64encode(string).decode() return string def DeObfuscate(self, string, count=5): """ Deobfucate previously obfuscated string """ string = base64.b64decode(string) for i in range(count): string = zlib.decompress(string) string = string.decode() string = list(string) string.reverse() string = "".join(string) string = base64.b64decode(string) # make unicode of bytes coming from base64 operations string = string.decode() return string def _DefaultActions(self): """ create some default actions like SSH and so on """ if platform.system() == "Windows": defaultactions = {"RDP": Action(name="RDP", description="Connect via RDP.", type="command", string="C:\windows\system32\mstsc.exe /v:$ADDRESS$"), "VNC": Action(name="VNC", description="Connect via VNC.", type="command", string="C:\Program Files\TightVNC\vncviewer.exe $ADDRESS$"), "Telnet": Action(name="Telnet", description="Connect via Telnet.", type="command", string="C:\Windows\System32\Telnet.exe root@$ADDRESS$"), "SSH": Action(name="SSH", description="Connect via SSH.", type="command", string="C:\Program Files\PuTTY\putty.exe -l root $ADDRESS$")} elif platform.system() == "Darwin": defaultactions = {"RDP": Action(name="RDP", description="Connect via RDP.", type="command", string="open rdp://$ADDRESS$"), "VNC": Action(name="VNC", description="Connect via VNC.", type="command", string="open vnc://$ADDRESS$"), "SSH": Action(name="SSH", description="Connect via SSH.", type="command", string="open ssh://root@$ADDRESS$"), "Telnet": Action(name="Telnet", description="Connect via Telnet.", type="command", string="open telnet://root@$ADDRESS$")} else: # the Linux settings defaultactions = {"RDP": Action(name="RDP", description="Connect via RDP.", type="command", string="/usr/bin/rdesktop -g 1024x768 $ADDRESS$"), "VNC": Action(name="VNC", description="Connect via VNC.", type="command", string="/usr/bin/vncviewer $ADDRESS$"), "SSH": Action(name="SSH", description="Connect via SSH.", type="command", string="/usr/bin/gnome-terminal -x ssh root@$ADDRESS$"), "Telnet": Action(name="Telnet", description="Connect via Telnet.", type="command", string="/usr/bin/gnome-terminal -x telnet root@$ADDRESS$"), "Update-Linux": Action(name="Update-Linux", description="Run remote update script.", type="command", string="/usr/bin/terminator -x ssh root@$HOST$ update.sh", enabled=False)} # OS agnostic actions as examples defaultactions["Nagios-1-Click-Acknowledge-Host"] = Action(name="Nagios-1-Click-Acknowledge-Host", type="url", description="Acknowledges a host with one click.", filter_target_service=False, enabled=False, string="$MONITOR-CGI$/cmd.cgi?cmd_typ=33&cmd_mod=2&host=$HOST$\ &com_author=$USERNAME$&com_data=acknowledged&btnSubmit=Commit") defaultactions["Nagios-1-Click-Acknowledge-Service"] = Action(name="Nagios-1-Click-Acknowledge-Service", type="url", description="Acknowledges a service with one click.", filter_target_host=False, enabled=False, string="$MONITOR-CGI$/cmd.cgi?cmd_typ=34&cmd_mod=2&host=$HOST$\ &service=$SERVICE$&com_author=$USERNAME$&com_data=acknowledged&btnSubmit=Commit") defaultactions["Opsview-Graph-Service"] = Action(name="Opsview-Graph-Service", type="browser", description="Show graph in browser.", filter_target_host=False, string="$MONITOR$/graph?service=$SERVICE$&host=$HOST$", enabled=False) defaultactions["Opsview-History-Host"] = Action(name="Opsview-Host-Service", type="browser", description="Show host in browser.", filter_target_host=True, string="$MONITOR$/event?host=$HOST$", enabled=False) defaultactions["Opsview-History-Service"] = Action(name="Opsview-History-Service", type="browser", description="Show history in browser.", filter_target_host=True, string="$MONITOR$/event?host=$HOST$&service=$SERVICE$", enabled=False) defaultactions["Check_MK-1-Click-Acknowledge-Host"] = Action(name="Check_MK-1-Click-Acknowledge-Host", type="url", description="Acknowledges a host with one click.", filter_target_service=False, enabled=False, string="$MONITOR$/view.py?_transid=$TRANSID$&_do_actions=yes&_do_confirm=Yes!&output_format=python&view_name=hoststatus&host=$HOST$&_ack_comment=$COMMENT-ACK$&_acknowledge=Acknowledge") defaultactions["Check_MK-1-Click-Acknowledge-Service"] = Action(name="Check_MK-1-Click-Acknowledge-Service", type="url", description="Acknowledges a host with one click.", filter_target_host=False, enabled=False, string="$MONITOR$/view.py?_transid=$TRANSID$&_do_actions=yes&_do_confirm=Yes!&output_format=python&view_name=service&host=$HOST$&_ack_comment=$COMMENT-ACK$&_acknowledge=Acknowledge&service=$SERVICE$") defaultactions["Check_MK Edit host in WATO"] = Action(name="Check_MK Edit host in WATO", enabled=False, monitor_type="Check_MK Multisite", description="Edit host in WATO.", string="$MONITOR$index.py?start_url=%2Fmonitor%2Fcheck_mk%2Fwato.py%3Fhost%3D$HOST$%26mode%3Dedit_host") defaultactions["Email"] = Action(name="Email", enabled=False, description="Send email to someone.", type="browser", string="mailto:servicedesk@my.org?subject=Monitor alert: $HOST$ - $SERVICE$ - $STATUS-INFO$&body=Please help!.%0d%0aBest regards from Nagstamon") return defaultactions def _LegacyAdjustments(self): # mere cosmetics but might be more clear for future additions - changing any "nagios"-setting to "monitor" for s in self.servers.values(): if 'nagios_url' in s.__dict__.keys(): s.monitor_url = s.nagios_url if 'nagios_cgi_url' in s.__dict__.keys(): s.monitor_cgi_url = s.nagios_cgi_url # to reduce complexity in Centreon there is also only one URL necessary if s.type == "Centreon": s.monitor_url = s.monitor_cgi_url # switch to update interval in seconds not minutes if 'update_interval' in self.__dict__.keys(): self.update_interval_seconds = int(self.update_interval) * 60 self.__dict__.pop('update_interval') # remove support for GNOME2-trayicon-egg-stuff if 'statusbar_systray' in self.__dict__.keys(): if self.statusbar_systray is True: self.icon_in_systray = True self.__dict__.pop('statusbar_systray') # some legacy action settings might need a little fix for action in self.actions.values(): if not action.type.lower() in ('browser', 'command', 'url'): # set browser as default to make user notice something is wrong action.type = 'browser' def GetNumberOfEnabledMonitors(self): """ returns the number of enabled monitors - in case all are disabled there is no need to display the popwin """ # to be returned number = 0 for server in self.servers.values(): # ##if str(server.enabled) == "True": if server.enabled is True: number += 1 return number def delete_file(self, settings_dir, settings_file): """ delete specified .conf file if setting is deleted in GUI """ # clean up old deleted/renamed config file file = os.path.abspath('{1}{0}{2}{0}{3}.conf'.format(os.sep, self.configdir, settings_dir, settings_file)) if os.path.exists(file) and (os.path.isfile(file) or os.path.islink(file)): try: os.unlink(file) except Exception: import traceback traceback.print_exc(file=sys.stdout) class Server(object): """ one Server realized as object for config info """ def __init__(self): self.enabled = True self.type = 'Nagios' self.name = 'Monitor server' self.monitor_url = 'https://monitor-server' self.monitor_cgi_url = 'https://monitor-server/monitor/cgi-bin' self.username = 'username' self.password = 'password' self.save_password = False self.use_proxy = False self.use_proxy_from_os = False self.proxy_address = 'http://proxyserver:port/' self.proxy_username = 'proxyusername' self.proxy_password = 'proxypassword' # defaults to 'basic', other possible values are 'digest' and 'kerberos' self.authentication = 'basic' self.timeout = 10 # just GUI-wise deciding if more options are shown in server dialog self.show_options = False # SSL/TLS certificate verification self.ignore_cert = False self.custom_cert_use = False self.custom_cert_ca_file = '' # special FX # Centreon autologin self.use_autologin = False self.autologin_key = '' # Icinga "host_display_name" instead of "host" self.use_display_name_host = False self.use_display_name_service = False # IcingaWeb2 might authenticate without cookies too - default is WITH cookies self.no_cookie_auth = False # Check_MK Multisite # Force Check_MK livestatus code to set AuthUser header for users who # are permitted to see all objects. self.force_authuser = False self.check_mk_view_hosts = 'nagstamon_hosts' self.check_mk_view_services = 'nagstamon_svc' # OP5 api filters self.host_filter = 'state !=0' self.service_filter = 'state !=0 or host.state != 0' # Sensu/Uchiwa/??? Datacenter/Site config self.monitor_site = 'Site 1' class Action(object): """ class for custom actions, which whill be thrown into one config dictionary like the servers """ def __init__(self, **kwds): # to be or not to be enabled... self.enabled = True # monitor type self.monitor_type = "" # one of those: browser, url or command self.type = "browser" # thy name is... self.name = "Custom action" # OS of host where Nagstamon runs - especially commands are mostly not platform agnostic self.os = "" # description self.description = "Starts a custom action." # might be URL in case of type browser/url and a commandline for commands self.string = "" # version - maybe in future this might be more sophisticated self.version = "1" # kind of Nagios item this action is targeted to - maybe also usable for states self.filter_target_host = True self.filter_target_service = True # action applies only to certain hosts or services self.re_host_enabled = False self.re_host_pattern = "" self.re_host_reverse = False self.re_service_enabled = False self.re_service_pattern = "" self.re_service_reverse = False self.re_status_information_enabled = False self.re_status_information_pattern = "" self.re_status_information_reverse = False # close powin or not, depends on personal preference self.close_popwin = True self.leave_popwin_open = False # do an immediate recheck after action was applied self.recheck = False # special FX # Centreon criticality and autologin self.re_criticality_enabled = False self.re_criticality_pattern = "" self.re_criticality_reverse = False # add and/or all keywords to object for k in kwds: self.__dict__[k] = kwds[k] # Initialize configuration to be accessed globally conf = Config() # try to get resources path if nagstamon got be installed by setup.py RESOURCES = "" try: # first try to find local resources directory in case Nagstamon was frozen with cx-Freeze for OSX or Windows executable_dir = os.path.join(os.sep.join(sys.executable.split(os.sep)[:-1])) if os.path.exists(os.path.normcase(os.sep.join((executable_dir, "resources")))): RESOURCES = os.path.normcase(os.sep.join((executable_dir, "resources"))) else: import pkg_resources RESOURCES = pkg_resources.resource_filename("Nagstamon", "resources") except Exception as err: # get resources directory from current directory - only if not being set before by pkg_resources # try-excepts necessary for platforms like Windows .EXE paths_to_check = [os.path.normcase(os.path.join(os.getcwd(), "Nagstamon", "resources")), os.path.normcase(os.path.join(os.getcwd(), "resources"))] try: # if resources dir is not available in CWD, try the # libs dir (site-packages) for the current Python from distutils.sysconfig import get_python_lib paths_to_check.append(os.path.normcase(os.path.join(get_python_lib(), "Nagstamon", "resources"))) except Exception: pass # if we're still out of luck, maybe this was a user scheme install try: import site site.getusersitepackages() # make sure USER_SITE is set paths_to_check.append(os.path.normcase(os.path.join(site.USER_SITE, "Nagstamon", "resources"))) except Exception: pass # add directory nagstamon.py where nagstamon.py resides for cases like 0install without installed pkg-resources paths_to_check.append(os.sep.join(sys.argv[0].split(os.sep)[:-1] + ["Nagstamon", "resources"])) for path in paths_to_check: if os.path.exists(path): RESOURCES = path break Nagstamon/Nagstamon/Helpers.py000066400000000000000000000275671316117564000167350ustar00rootroot00000000000000# encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2014 Henri Wahl et al. # # 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 import datetime # import subprocess # not used import re import sys import traceback import os import psutil import getpass import webbrowser # import md5 for centreon url autologin encoding from hashlib import md5 from Nagstamon.Config import conf # queue.Queue() needs threading module which might be not such a good idea to be used # because QThread is already in use # get debug queue from nagstamon.py # ##debug_queue = sys.modules['__main__'].debug_queue # states needed for gravity comparison for notification and Generic.py # STATES = ['UP', 'UNKNOWN', 'WARNING', 'CRITICAL', 'UNREACHABLE', 'DOWN'] STATES = ['UP', 'UNKNOWN', 'INFORMATION', 'WARNING', 'AVERAGE', 'HIGH', 'CRITICAL', 'DISASTER', 'UNREACHABLE', 'DOWN'] # sound at the moment is only available for these states STATES_SOUND = ['WARNING', 'CRITICAL', 'DOWN'] def not_empty(x): ''' tiny helper function for BeautifulSoup in server Generic.py to filter text elements ''' return bool(x.replace(' ', '').strip()) def is_found_by_re(string, pattern, reverse): """ helper for context menu actions in context menu - hosts and services might be filtered out also useful for services and hosts and status information """ pattern = re.compile(pattern) if len(pattern.findall(string)) > 0: if str(reverse) == "True": return False else: return True else: if str(reverse) == "True": return True else: return False def host_is_filtered_out_by_re(host, conf=None): """ helper for applying RE filters in Generic.GetStatus() """ try: if conf.re_host_enabled is True: return is_found_by_re(host, conf.re_host_pattern, conf.re_host_reverse) # if RE are disabled return True because host is not filtered return False except Exception: traceback.print_exc(file=sys.stdout) def ServiceIsFilteredOutByRE(service, conf=None): """ helper for applying RE filters in Generic.GetStatus() """ try: if conf.re_service_enabled is True: return is_found_by_re(service, conf.re_service_pattern, conf.re_service_reverse) # if RE are disabled return True because host is not filtered return False except Exception: traceback.print_exc(file=sys.stdout) def StatusInformationIsFilteredOutByRE(status_information, conf=None): """ helper for applying RE filters in Generic.GetStatus() """ try: if conf.re_status_information_enabled is True: return is_found_by_re(status_information, conf.re_status_information_pattern, conf.re_status_information_reverse) # if RE are disabled return True because host is not filtered return False except Exception: traceback.print_exc(file=sys.stdout) def CriticalityIsFilteredOutByRE(criticality, conf=None): """ helper for applying RE filters in Generic.GetStatus() """ try: if conf.re_criticality_enabled is True: return is_found_by_re(criticality, conf.re_criticality_pattern, conf.re_criticality_reverse) # if RE are disabled return True because host is not filtered return False except Exception: traceback.print_exc(file=sys.stdout) def HumanReadableDurationFromSeconds(seconds): """ convert seconds given by Opsview to the form Nagios gives them like 70d 3h 34m 34s """ timedelta = str(datetime.timedelta(seconds=int(seconds))) try: if timedelta.find("day") == -1: hms = timedelta.split(":") if len(hms) == 1: return "%02ds" % (hms[0]) elif len(hms) == 2: return "%02dm %02ds" % (hms[0], hms[1]) else: return "%sh %02dm %02ds" % (hms[0], hms[1], hms[2]) else: # waste is waste - does anyone need it? days, waste, hms = str(timedelta).split(" ") hms = hms.split(":") return "%sd %sh %02dm %02ds" % (days, hms[0], hms[1], hms[2]) except Exception: # in case of any error return seconds we got return seconds def HumanReadableDurationFromTimestamp(timestamp): """ Thruk server supplies timestamp of latest state change which has to be subtracted from .now() """ try: td = datetime.datetime.now() - datetime.datetime.fromtimestamp(int(timestamp)) h = int(td.seconds / 3600) m = int(td.seconds % 3600 / 60) s = int(td.seconds % 60) if td.days > 0: return "%sd %sh %02dm %02ds" % (td.days, h, m, s) elif h > 0: return "%sh %02dm %02ds" % (h, m, s) elif m > 0: return "%02dm %02ds" % (m, s) else: return "%02ds" % (s) except Exception: traceback.print_exc(file=sys.stdout) # unified machine readable date might go back to module Actions def MachineSortableDate(raw): """ Try to compute machine readable date for all types of monitor servers """ # dictionary for duration date string components d = {'M': 0, 'w': 0, 'd': 0, 'h': 0, 'm': 0, 's': 0} # if for some reason the value is empty/none make it compatible: 0s if raw is None: raw = '0s' # Check_MK style - added new variants in 1.4.x, based on abbreviations with spaces :-( if ('-' in raw and ':' in raw) or\ ('sec' in raw or 'min' in raw or 'hrs' in raw or 'days' in raw or\ ' s' in raw or ' m' in raw or ' h' in raw or ' d' in raw): # check_mk has different formats - if duration takes too long it changes its scheme if '-' in raw and ':' in raw: datepart, timepart = raw.split(' ') # need to convert years into months for later comparison Y, M, D = datepart.split('-') d['M'] = int(Y) * 12 + int(M) d['d'] = int(D) # time does not need to be changed h, m, s = timepart.split(':') d['h'], d['m'], d['s'] = int(h), int(m), int(s) del datepart, timepart, Y, M, D, h, m, s else: # recalculate a timedelta of the given value if 'sec' in raw or ' s' in raw: d['s'] = raw.split(' ')[0].split('.')[0] delta = datetime.datetime.now() - datetime.timedelta(seconds=int(d['s'])) elif 'min' in raw or ' m' in raw: d['m'] = raw.split(' ')[0].split('.')[0] delta = datetime.datetime.now() - datetime.timedelta(minutes=int(d['m'])) elif 'hrs' in raw or ' h' in raw: d['h'] = raw.split(' ')[0] delta = datetime.datetime.now() - datetime.timedelta(hours=int(d['h'])) elif 'days' in raw or ' d' in raw: d['d'] = raw.split(' ')[0] delta = datetime.datetime.now() - datetime.timedelta(days=int(d['d'])) else: delta = datetime.datetime.now() Y, M, d['d'], d['h'], d['m'], d['s'] = delta.strftime('%Y %m %d %H %M %S').split(' ') # need to convert years into months for later comparison d['M'] = int(Y) * 12 + int(M) # int-ify d for i in d: # workaround to make values negative to fix Check_MK's different order d[i] = -int(d[i]) else: # strip and replace necessary for Nagios duration values, # split components of duration into dictionary for c in raw.strip().replace(' ', ' ').split(' '): number, period = c[0:-1], c[-1] # attempt to be more robust in case of https://github.com/HenriWahl/Nagstamon/issues/405 try: d[period] = int(number) except: d[period] = 0 del number, period # convert collected duration data components into seconds for being comparable return(16934400 * d['M'] + 604800 * d['w'] + 86400 * d['d'] + 3600 * d['h'] + 60 * d['m'] + d['s']) def MD5ify(string): """ makes something md5y of a given username or password for Centreon web interface access """ return md5(string).hexdigest() def lock_config_folder(folder): ''' Locks the config folder by writing a PID file into it. The lock is relative to user name and system's boot time. Returns True on success, False when lock failed Return True too if there is any locking error - if no locking ins possible it might run as well This is also the case if some setup uses the nagstamon.config directory which most probably will be read-only ''' pidFilePath = os.path.join(folder, 'nagstamon.pid') try: # Open the file for rw or create a new one if missing if os.path.exists(pidFilePath): mode = 'r+t' else: mode = 'wt' with open(pidFilePath, mode, newline=None) as pidFile: curPid = os.getpid() curBootTime = int(psutil.boot_time()) curUserName = getpass.getuser().replace('@', '_').strip() pid = None bootTime = None userName = None if mode.startswith('r'): try: procInfo = pidFile.readline().strip().split('@') pid = int(procInfo[0]) bootTime = int(procInfo[1]) userName = procInfo[2].strip() except(ValueError, IndexError): pass if pid is not None and bootTime is not None and userName is not None: # Found a pid stored in the pid file, check if its still running if bootTime == curBootTime and userName == curUserName and psutil.pid_exists(pid): return False pidFile.seek(0) pidFile.truncate() pidFile.write('{}@{}@{}'.format(curPid, curBootTime, curUserName)) except Exception as err: print(err) return True # the following functions are used for sorted() in sort_data_array() def compare_host(item): return(item.lower()) def compare_service(item): return(item.lower()) def compare_status(item): return(STATES.index(item)) def compare_last_check(item): return(MachineSortableDate(item)) def compare_duration(item): return(MachineSortableDate(item)) def compare_attempt(item): return(item) def compare_status_information(item): return(item.lower()) def webbrowser_open(url): """ decide if default or custom browser is used for various tasks used by almost all """ if conf.use_default_browser: webbrowser.open(url) else: webbrowser.get('{0} %s &'.format(conf.custom_browser)).open(url) # depending on column different functions have to be used # 0 + 1 are column "Hosts", 1 + 2 are column "Service" due to extra font flag pictograms SORT_COLUMNS_FUNCTIONS = {0: compare_host, 1: compare_host, 2: compare_service, 3: compare_service, 4: compare_status, 5: compare_last_check, 6: compare_duration, 7: compare_attempt, 8: compare_status_information, 9: compare_status_information} Nagstamon/Nagstamon/Objects.py000066400000000000000000000115301316117564000167030ustar00rootroot00000000000000# encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2014 Henri Wahl et al. # # 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 # for python2 and upcomping python3 compatiblity from __future__ import print_function, absolute_import, unicode_literals STATES = ['WARNING', 'UNKNOWN', 'CRITICAL', 'UNREACHABLE', 'DOWN', 'INFORMATION', 'AVERAGE', 'HIGH', 'DISASTER'] class GenericObject(object): """ template for hosts and services """ def __init__(self): self.name = '' self.status = '' self.status_information = '' # default state is soft, to be changed by status_type check self.status_type = '' self.last_check = '' self.duration = '' self.attempt = '' self.passiveonly = False self.acknowledged = False self.notifications_disabled = False self.flapping = False self.scheduled_downtime = False # compress all flags like acknowledged and flapping into one string self.host_flags = '' self.service_flags = '' self.visible = True # Check_MK also has site info self.site = '' # server to be added to hash self.server = '' # might help in Qt self.host = '' self.service = '' self.dummy_column = '' def is_passive_only(self): return bool(self.passiveonly) def is_flapping(self): return bool(self.flapping) def has_notifications_disabled(self): return bool(self.notifications) def is_acknowledged(self): return bool(self.acknowledged) def is_in_scheduled_downtime(self): return bool(self.scheduled_downtime) def is_visible(self): return bool(self.visible) def get_name(self): """ return stringified name """ return str(self.name) def get_host_name(self): """ Extracts host name from status item. Presentation purpose. """ return '' def get_service_name(self): """ Extracts service name from status item. Presentation purpose. """ return '' def get_hash(self): """ returns hash of event status information - different for host and service thus empty here """ return '' def get_columns(self, columns_wanted): """ Yield host/service status information for treeview table columns """ for c in columns_wanted: yield str(self.__dict__[c]) class GenericHost(GenericObject): """ one host which is monitored by a Nagios server, gets populated with services """ def __init__(self): GenericObject.__init__(self) # take all the faulty services on host self.services = dict() def get_host_name(self): return str(self.name) def is_host(self): """ decides where to put acknowledged/downtime pixbufs in Liststore for Treeview in Popwin """ return True def get_hash(self): """ return hash for event history tracking """ return " ".join((self.server, self.site, self.name, self.status)) class GenericService(GenericObject): """ one service which runs on a host """ def __init__(self): GenericObject.__init__(self) def get_host_name(self): return str(self.host) def get_service_name(self): return str(self.name) def is_host(self): """ decides where to put acknowledged/downtime pixbufs in Liststore for Treeview in Popwin """ return False def get_hash(self): """ return hash for event history tracking """ return " ".join((self.server, self.site, self.host, self.name, self.status)) class Result(object): """ multi purpose result object, used in Servers.Generic.FetchURL() """ result = '' error = '' status_code = 0 def __init__(self, **kwds): # add all keywords to object, every mode searchs inside for its favorite arguments/keywords for k in kwds: self.__dict__[k] = kwds[k] Nagstamon/Nagstamon/QUI/000077500000000000000000000000001316117564000153765ustar00rootroot00000000000000Nagstamon/Nagstamon/QUI/__init__.py000066400000000000000000010245451316117564000175220ustar00rootroot00000000000000# encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2016 Henri Wahl et al. # # 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 import sys """Module QUI""" from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtSvg import * from PyQt5.QtMultimedia import * # global application instance APP = QApplication(sys.argv) import os import os.path import urllib.parse import subprocess import platform import time import random import copy import base64 import datetime import traceback from collections import OrderedDict from copy import deepcopy from Nagstamon.Config import (conf, Server, Action, RESOURCES, BOOLPOOL, CONFIG_STRINGS, NON_LINUX, AppInfo, debug_queue) from Nagstamon.Servers import (SERVER_TYPES, servers, create_server, get_enabled_servers, get_worst_status, get_status_count, get_errors) from Nagstamon.Helpers import (is_found_by_re, webbrowser_open, STATES, STATES_SOUND, SORT_COLUMNS_FUNCTIONS) from Nagstamon.Objects import Result # dialogs from Nagstamon.QUI.settings_main import Ui_settings_main from Nagstamon.QUI.settings_server import Ui_settings_server from Nagstamon.QUI.settings_action import Ui_settings_action from Nagstamon.QUI.dialog_acknowledge import Ui_dialog_acknowledge from Nagstamon.QUI.dialog_downtime import Ui_dialog_downtime from Nagstamon.QUI.dialog_submit import Ui_dialog_submit from Nagstamon.QUI.dialog_authentication import Ui_dialog_authentication from Nagstamon.QUI.dialog_server_missing import Ui_dialog_server_missing from Nagstamon.QUI.dialog_about import Ui_dialog_about # instead of calling platform.system() every now and then just do it once here OS = platform.system() # only on X11/Linux thirdparty path should be added because it contains the Xlib module # needed to tell window manager via EWMH to keep Nagstamon window on all virtual desktops # TODO: test if X11 or Wayland is used if not OS in NON_LINUX: # extract thirdparty path from resources path - make submodules accessible by thirdparty modules THIRDPARTY = os.sep.join(RESOURCES.split(os.sep)[0:-1] + ['thirdparty']) sys.path.insert(0, THIRDPARTY) # Xlib for EWMH needs the file ~/.Xauthority and crashes if it does not exist if not os.path.exists(os.path.expanduser('~') + os.sep + '.Xauthority'): open(os.path.expanduser('~') + os.sep + '.Xauthority', 'a').close() from Nagstamon.thirdparty.ewmh import EWMH # DBus only interesting for Linux too try: from dbus import (Interface, SessionBus) from dbus.mainloop.pyqt5 import DBusQtMainLoop # flag to check later if DBus is available DBUS_AVAILABLE = True except ImportError: print('No DBus for desktop notification available.') DBUS_AVAILABLE = False # fixed shortened and lowered color names for cells, also used by statusbar label snippets COLORS = OrderedDict([('DOWN', 'color_down_'), ('UNREACHABLE', 'color_unreachable_'), ('DISASTER', 'color_disaster_'), ('CRITICAL', 'color_critical_'), ('UNKNOWN', 'color_unknown_'), ('HIGH', 'color_high_'), ('AVERAGE', 'color_average_'), ('WARNING', 'color_warning_'), ('INFORMATION', 'color_information_')]) # states to be used in statusbar if long version is used COLOR_STATE_NAMES = {'DOWN': {True: 'DOWN', False: ''}, 'UNREACHABLE': {True: 'UNREACHABLE', False: ''}, 'DISASTER': {True: 'DISASTER', False: ''}, 'CRITICAL': {True: 'CRITICAL', False: ''}, 'HIGH': {True: 'HIGH', False: ''}, 'AVERAGE': {True: 'AVERAGE', False: ''}, 'WARNING': {True: 'WARNING', False: ''}, 'INFORMATION': {True: 'INFORMATION', False: ''}, 'UNKNOWN': {True: 'UNKNOWN', False: ''}} # colors for server status label in ServerVBox COLOR_STATUS_LABEL = {'critical': 'lightsalmon', 'error': 'orange', 'unknown': 'gray'} # QBrushes made of QColors for treeview model data() method # 2 flavours for alternating backgrounds # filled by create_brushes() QBRUSHES = {0: {}, 1: {}} # dummy QVariant as empty return value for model data() DUMMY_QVARIANT = QVariant() # headers for tablewidgets HEADERS = OrderedDict([('host', {'header': 'Host', 'column': 0}), ('host_flags', {'header': '', 'column': 0}), ('service', {'header': 'Service', 'column': 2}), ('service_flags', {'header': '', 'column': 2}), ('status', {'header': 'Status', 'column': 4}), ('last_check', {'header': 'Last Check', 'column': 5}), ('duration', {'header': 'Duration', 'column': 6}), ('attempt', {'header': 'Attempt', 'column': 7}), ('status_information', {'header': 'Status Information', 'column': 8}), ('dummy_column', {'header': '', 'column': 8})]) # various headers-key-columns variations needed in different parts HEADERS_HEADERS = list() for item in HEADERS.values(): HEADERS_HEADERS.append(item['header']) HEADERS_HEADERS_COLUMNS = dict() for item in HEADERS.values(): HEADERS_HEADERS_COLUMNS[item['header']] = item['column'] HEADERS_HEADERS_KEYS = dict() for item in HEADERS.keys(): HEADERS_HEADERS_KEYS[HEADERS[item]['header']] = item HEADERS_KEYS_COLUMNS = dict() for item in HEADERS.keys(): HEADERS_KEYS_COLUMNS[item] = HEADERS[item]['column'] HEADERS_KEYS_HEADERS = dict() for item in HEADERS.keys(): HEADERS_KEYS_HEADERS[item] = HEADERS[item]['header'] # sorting order for tablewidgets SORT_ORDER = {'descending': 1, 'ascending': 0, 0: True, 1: False} # bend columns 1 and 3 to 0 and 2 to avoid sorting the extra flag icons of hosts and services SORT_COLUMNS_INDEX = {0: 0, 1: 0, 2: 2, 3: 2, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 8} # space used in LayoutBoxes SPACE = 10 # save default font to be able to reset to it DEFAULT_FONT = APP.font() # take global FONT from conf if it exists if conf.font != '': FONT = QFont() FONT.fromString(conf.font) else: FONT = DEFAULT_FONT # add nagstamon.ttf with icons to fonts FONTDATABASE = QFontDatabase() FONTDATABASE.addApplicationFont('{0}{1}nagstamon.ttf'.format(RESOURCES, os.sep)) # always stay in normal weight without any italic ICONS_FONT = QFont('Nagstamon', FONT.pointSize() + 2, QFont.Normal, False) # completely silly but no other rescue for Windows-hides-statusbar-after-display-mode-change problem NUMBER_OF_DISPLAY_CHANGES = 0 # Flags for statusbar - experiment with Qt.ToolTip for Windows because # statusbar permanently seems to vanish at some users desktops # see https://github.com/HenriWahl/Nagstamon/issues/222 # WINDOW_FLAGS = Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint | Qt.ToolTip # ##if OS == 'Windows': # ## # WINDOW_FLAGS = Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint | Qt.ToolTip # ## WINDOW_FLAGS = Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint | Qt.Tool # ## # WINDOW_FLAGS = Qt.FramelessWindowHint | Qt.Tool # ## # WINDOW_FLAGS = Qt.FramelessWindowHint | Qt.ToolTip # ## # WINDOW_FLAGS = Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool | Qt.BypassWindowManagerHint # ##else: # ## WINDOW_FLAGS = Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint | Qt.Tool WINDOW_FLAGS = Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint | Qt.Tool # icon for dialogs ICON = QIcon('{0}{1}nagstamon.ico'.format(RESOURCES, os.sep)) # set style for tooltips globally - to sad not all properties can be set here APP.setStyleSheet('''QToolTip { margin: 3px; }''') class HBoxLayout(QHBoxLayout): """ Apparently necessary to get a HBox which is able to hide its children """ def __init__(self, spacing=None, parent=None): QHBoxLayout.__init__(self, parent) if spacing is None: self.setSpacing(0) else: self.setSpacing(spacing) self.setContentsMargins(0, 0, 0, 0) # no margin def hide_items(self): """ cruise through all child widgets and hide them self,count()-1 is needed because the last item is None """ for item in range(self.count() - 1): self.itemAt(item).widget().hide() def show_items(self): """ cruise through all child widgets and show them self,count()-1 is needed because the last item is None """ for item in range(self.count() - 1): self.itemAt(item).widget().show() class SystemTrayIcon(QSystemTrayIcon): """ Icon in system tray, works at least in Windows and OSX Several Linux desktop environments have different problems For some dark, very dark reason systray menu does NOT work in Windows if run on commandline as nagstamon.py - the binary .exe works """ show_menu = pyqtSignal() show_popwin = pyqtSignal() hide_popwin = pyqtSignal() # flag for displaying error icon in case of error error_shown = False def __init__(self): debug_queue.append('DEBUG: Initializing SystemTrayIcon') QSystemTrayIcon.__init__(self) # icons are in dictionary self.icons = {} self.create_icons() # empty icon for flashing notification self.icons['EMPTY'] = QIcon('{0}{1}nagstamon_systrayicon_empty.svg'.format(RESOURCES, os.sep)) # little workaround to match statuswindow.worker_notification.worst_notification_status self.icons['UP'] = self.icons['OK'] # default icon is OK self.setIcon(self.icons['OK']) debug_queue.append('DEBUG: SystemTrayIcon initial icon: {}'.format(self.currentIconName())) # store icon for flashing self.current_icon = None # no menu at first self.menu = None # timer for singleshots for flashing self.timer = QTimer() # when there are new settings/colors recreate icons dialogs.settings.changed.connect(self.create_icons) # treat clicks self.activated.connect(self.icon_clicked) def currentIconName(self): """ internal function useful for debugging, returns the name of the current icon """ curIcon = self.icon() if curIcon is None: return '' return str(curIcon) @pyqtSlot(QMenu) def set_menu(self, menu): """ create current menu for right clicks """ # store menu for future use, especially for MacOSX self.menu = menu # MacOSX does not distinguish between left and right click so menu will go to upper menu bar # update: apparently not, but own context menu will be shown when icon is clicked an all is OK = green if OS != 'Darwin': self.setContextMenu(self.menu) @pyqtSlot() def create_icons(self): """ create icons from template, applying colors """ # get template from file svg_template_file = open('{0}{1}nagstamon_systrayicon_template.svg'.format(RESOURCES, os.sep)) svg_template_xml = svg_template_file.readlines() # create icons for all states for state in ['OK', 'INFORMATION', 'UNKNOWN', 'WARNING', 'AVERAGE', 'HIGH', 'CRITICAL', 'DISASTER', 'UNREACHABLE', 'DOWN', 'ERROR']: # current SVG XML for state icon, derived from svg_template_cml svg_state_xml = list() # replace dummy text and background colors with configured ones for line in svg_template_xml: line = line.replace('fill:#ff00ff', 'fill:' + conf.__dict__['color_' + state.lower() + '_text']) line = line.replace('fill:#00ff00', 'fill:' + conf.__dict__['color_' + state.lower() + '_background']) svg_state_xml.append(line) # create XML stream of SVG svg_xml_stream = QXmlStreamReader(''.join(svg_state_xml)) # create renderer for SVG and put SVG XML into renderer svg_renderer = QSvgRenderer(svg_xml_stream) # pixmap to be painted on - arbitrarily choosen 128x128 px svg_pixmap = QPixmap(128, 128) # fill transparent backgound svg_pixmap.fill(Qt.transparent) # initiate painter which paints onto paintdevice pixmap svg_painter = QPainter(svg_pixmap) # render svg to pixmap svg_renderer.render(svg_painter) # close painting svg_painter.end() # put pixmap into icon self.icons[state] = QIcon(svg_pixmap) debug_queue.append('DEBUG: SystemTrayIcon created icon {} for state "{}"'.format(self.icons[state], state)) @pyqtSlot(QSystemTrayIcon.ActivationReason) def icon_clicked(self, reason): """ evaluate mouse click """ # some obscure Windows problem again if reason == QSystemTrayIcon.Context and OS == 'Windows': self.show_menu.emit() # only react on left mouse click on OSX elif reason == (QSystemTrayIcon.Trigger or QSystemTrayIcon.DoubleClick): # when green icon is displayed and no popwin is about to po up show at least menu if get_worst_status() == 'UP' and OS == 'Darwin': self.menu.show_at_cursor() else: # show status window if there is something to tell if statuswindow.is_shown: self.hide_popwin.emit() else: self.show_popwin.emit() @pyqtSlot() def show_state(self): """ get worst status and display it in systray """ if self.error_shown is False: worst_status = get_worst_status() self.setIcon(self.icons[worst_status]) # set current icon for flashing self.current_icon = self.icons[worst_status] del(worst_status) else: self.setIcon(self.icons['ERROR']) @pyqtSlot() def flash(self): """ send color inversion signal to labels """ # only if currently a notification is necessary if statuswindow.worker_notification.is_notifying: # store current icon to get it reset back if self.current_icon is None: if self.error_shown is False: self.current_icon = self.icons[statuswindow.worker_notification.worst_notification_status] else: self.current_icon = self.icons['ERROR'] # use empty SVG icon to display emptiness self.setIcon(self.icons['EMPTY']) # fire up a singleshot to reset color soon self.timer.singleShot(500, self.reset) @pyqtSlot() def reset(self): """ tell labels to set original colors """ # only if currently a notification is necessary if statuswindow.worker_notification.is_notifying: try: # set curent status icon self.setIcon(self.current_icon) # even later call itself to invert colors as flash self.timer.singleShot(500, self.flash) except: traceback.print_exc(file=sys.stdout) else: if self.current_icon is not None: self.setIcon(self.current_icon) self.current_icon = None @pyqtSlot() def set_error(self): self.error_shown = True @pyqtSlot() def reset_error(self): self.error_shown = False class MenuAtCursor(QMenu): """ open menu at position of mouse pointer - normal .exec() shows menu at (0, 0) """ # flag to avoid too fast popping up menus available = True def __init__(self, parent=None): QMenu.__init__(self, parent=parent) @pyqtSlot() def show_at_cursor(self): """ pop up at mouse pointer position, lock itself to avoid permamently popping menus on Windows """ # get cursor coordinates and decrease them to show menu under mouse pointer x = QCursor.pos().x() - 10 y = QCursor.pos().y() - 10 self.exec_(QPoint(x, y)) # noqa del(x, y) class MenuContext(MenuAtCursor): """ class for universal context menu, used at systray icon and hamburger menu """ menu_ready = pyqtSignal(QMenu) def __init__(self, parent=None): MenuAtCursor.__init__(self, parent=parent) # connect all relevant widgets which should show the context menu for widget in [statuswindow.toparea.button_hamburger_menu, statuswindow.toparea.label_version, statuswindow.toparea.label_empty_space, statuswindow.toparea.logo, statuswindow.statusbar.logo, statuswindow.statusbar.label_message]: self.menu_ready.connect(widget.set_menu) for color_label in statuswindow.statusbar.color_labels.values(): self.menu_ready.connect(color_label.set_menu) dialogs.settings.changed.connect(self.initialize) self.initialize() @pyqtSlot() def initialize(self): """ add actions and servers to menu """ # first clear to get rid of old servers self.clear() self.action_refresh = QAction('Refresh', self) self.action_refresh.triggered.connect(statuswindow.refresh) self.addAction(self.action_refresh) self.action_recheck = QAction('Recheck all', self) self.action_recheck.triggered.connect(statuswindow.recheck_all) self.addAction(self.action_recheck) self.addSeparator() # dict to hold all servers - more flexible this way self.action_servers = dict() # connect every server to its monitoring webpage for server in servers: self.action_servers[server] = QAction(server, self) self.action_servers[server].triggered.connect(servers[server].open_monitor_webpage) self.addAction(self.action_servers[server]) self.addSeparator() self.action_settings = QAction('Settings...', self) self.action_settings.triggered.connect(statuswindow.hide_window) self.action_settings.triggered.connect(dialogs.settings.show) self.addAction(self.action_settings) if conf.statusbar_floating: self.action_save_position = QAction('Save position', self) self.action_save_position.triggered.connect(self.save_position) self.addAction(self.action_save_position) self.addSeparator() self.action_about = QAction('About...', self) self.action_about.triggered.connect(statuswindow.hide_window) self.action_about.triggered.connect(dialogs.about.show) self.addAction(self.action_about) self.action_exit = QAction('Exit', self) self.action_exit.triggered.connect(exit) self.addAction(self.action_exit) # tell all widgets to use the new menu self.menu_ready.emit(self) def save_position(self): """ save position from window into config """ statuswindow.store_position_to_conf() conf.SaveConfig() class MenuContextSystrayicon(MenuContext): """ Necessary for Ubuntu 16.04 new Qt5-Systray-AppIndicator meltdown Maybe in general a good idea to offer status window popup here """ def __init__(self, parent=None): """ clone of normal MenuContext which serves well in all other places but no need of signal/slots initialization """ QMenu.__init__(self, parent=parent) # initialize as default + extra self.initialize() self.menu_ready.connect(systrayicon.set_menu) self.menu_ready.emit(self) # change menu if there are changes in settings/servers dialogs.settings.changed.connect(self.initialize) def initialize(self): """ initialize as herited + a popup menu entry mostly useful in Ubuntu Unity """ MenuContext.initialize(self) # makes even less sense on OSX if OS != 'Darwin': self.action_status = QAction('Show status window', self) self.action_status.triggered.connect(statuswindow.show_window) self.insertAction(self.action_refresh, self.action_status) self.insertSeparator(self.action_refresh) class FlatButton(QToolButton): """ QToolButton acting as push button """ def __init__(self, text='', parent=None, server=None, url_type=''): QToolButton.__init__(self, parent=parent) self.setAutoRaise(True) self.setStyleSheet('''padding: 3px;''') self.setText(text) # OSX does not support flat QToolButtons so keep the neat default ones if OS == 'Darwin': Button = QPushButton CSS_CLOSE_BUTTON = '''QPushButton {border-width: 0px; border-style: none; margin-right: 5px;} QPushButton:hover {background-color: white; border-radius: 4px;}''' CSS_HAMBURGER_MENU = '''QPushButton {border-width: 0px; border-style: none;} QPushButton::menu-indicator{image:url(none.jpg)}; QPushButton:hover {background-color: white; border-radius: 4px;}''' else: Button = FlatButton CSS_CLOSE_BUTTON = '''margin-right: 5px;''' CSS_HAMBURGER_MENU = '''FlatButton::menu-indicator{image:url(none.jpg);}''' class PushButton_Hamburger(Button): """ Pushbutton with menu for hamburger """ pressed = pyqtSignal() def __init__(self): # ##QPushButton.__init__(self) Button.__init__(self) self.setStyleSheet(CSS_HAMBURGER_MENU) def mousePressEvent(self, event): self.pressed.emit() self.showMenu() @pyqtSlot(QMenu) def set_menu(self, menu): self.setMenu(menu) # ##class PushButton_BrowserURL(QPushButton): class PushButton_BrowserURL(Button): """ QPushButton for ServerVBox which opens certain URL if clicked """ def __init__(self, text='', parent=None, server=None, url_type=''): Button.__init__(self, text, parent=parent) self.server = server self.url_type = url_type @pyqtSlot() def open_url(self): """ open URL from BROWSER_URLS in webbrowser """ # BROWSER_URLS come with $MONITOR$ instead of real monitor url - heritage from actions url = self.server.BROWSER_URLS[self.url_type] url = url.replace('$MONITOR$', self.server.monitor_url) url = url.replace('$MONITOR-CGI$', self.server.monitor_cgi_url) if conf.debug_mode: self.server.Debug(server=self.server.get_name(), debug='Open {0} web page {1}'.format(self.url_type, url)) # use Python method to open browser webbrowser_open(url) # hide statuswindow to get screen space for browser if not conf.fullscreen and not conf.windowed: statuswindow.hide_window() class ComboBox_Servers(QComboBox): """ combobox which does lock statuswindow so it does not close when opening combobox """ monitor_opened = pyqtSignal() # flag to avoid silly focusOutEvent freshly_opened = False def __init__(self, parent=None): QComboBox.__init__(self, parent=parent) # react to clicked monitor self.activated.connect(self.response) def mousePressEvent(self, event): # first click opens combobox popup self.freshly_opened = True # tell status window that there is no combobox anymore self.showPopup() def fill(self): """ fill default order fields combobox with server names """ self.clear() self.addItem('Go to monitor...') self.addItems(sorted(conf.servers.keys(), key=str.lower)) @pyqtSlot() def response(self): """ respnose to activated item in servers combobox """ if self.currentText() in servers: # open webbrowser with server URL webbrowser_open(servers[self.currentText()].monitor_url) # hide window to make room for webbrowser self.monitor_opened.emit() self.setCurrentIndex(0) class DraggableWidget(QWidget): """ Used to give various toparea and statusbar widgets draggability """ # yell if statusbar is moved window_moved = pyqtSignal() # needed for popup after hover mouse_entered = pyqtSignal() # needed for popup after click mouse_pressed = pyqtSignal() mouse_released = pyqtSignal() # keep state of right button pressed to avoid dragging and # unwanted repositioning of statuswindow right_mouse_button_pressed = False # Maybe due to the later mixin usage, but somehow the pyqtSlot decorator is ignored here when used by NagstamonLogo # and DraggableLabel #@pyqtSlot(QMenu) def set_menu(self, menu): self.menu = menu def save_position(self): """ save position from window into config """ statuswindow.store_position_to_conf() conf.SaveConfig() def mousePressEvent(self, event): """ react differently to mouse button presses: 1 - left button, move window 2 - right button, popup menu """ if event.button() == Qt.LeftButton: self.mouse_pressed.emit() if event.button() == Qt.RightButton: self.right_mouse_button_pressed = True # keep x and y relative to statusbar # if not set calculate relative position if not statuswindow.relative_x and\ not statuswindow.relative_y: statuswindow.relative_x = event.globalX() - statuswindow.x() statuswindow.relative_y = event.globalY() - statuswindow.y() def mouseReleaseEvent(self, event): """ decide if moving or menu should be treated after mouse button was released """ if event.button() == Qt.LeftButton: # if popup window should be closed by clicking do it now if statuswindow.is_shown and\ (conf.close_details_clicking or conf.close_details_clicking_somewhere) and\ not conf.fullscreen and not conf.windowed: statuswindow.is_hiding_timestamp = time.time() statuswindow.hide_window() elif not statuswindow.is_shown: self.mouse_released.emit() # reset all helper values statuswindow.relative_x = False statuswindow.relative_y = False statuswindow.moving = False if event.button() == Qt.RightButton: self.right_mouse_button_pressed = False self.menu.show_at_cursor() def mouseMoveEvent(self, event): """ do the moving action """ # if window should close when being clicked it might be problematic if it # will be moved unintendedly so try to filter this events out by waiting 0.5 seconds if not(conf.close_details_clicking and statuswindow.is_shown and statuswindow.is_shown_timestamp + 0.5 < time.time()): if not conf.fullscreen and not conf.windowed and not self.right_mouse_button_pressed: # lock window as moving # if not set calculate relative position if not statuswindow.relative_x and not statuswindow.relative_y: statuswindow.relative_x = event.globalX() - statuswindow.x() statuswindow.relative_y = event.globalY() - statuswindow.y() statuswindow.moving = True statuswindow.move(event.globalX() - statuswindow.relative_x, event.globalY() - statuswindow.relative_y) # needed for OSX - otherwise statusbar stays blank while moving statuswindow.update() self.window_moved.emit() def enterEvent(self, event): """ tell the world that mouse entered the widget - interesting for hover popup and only if toparea hasn't been clickend a moment ago """ if statuswindow.is_shown is False and\ statuswindow.is_hiding_timestamp + 0.2 < time.time(): self.mouse_entered.emit() class DraggableLabel(QLabel, DraggableWidget): """ label with dragging capabilities used by toparea """ # yell if statusbar is moved window_moved = pyqtSignal() # needed for popup after hover mouse_entered = pyqtSignal() # needed for popup after click mouse_pressed = pyqtSignal() mouse_released = pyqtSignal() def __init__(self, text='', parent=None): QLabel.__init__(self, text, parent=parent) class ClosingLabel(QLabel): """ modified QLabel which might close the statuswindow if leftclicked """ def __init__(self, text='', parent=None): QLabel.__init__(self, text, parent=parent) def mouseReleaseEvent(self, event): """ left click and configured close-if-clicking-somewhere makes statuswindow close """ if event.button() == Qt.LeftButton and conf.close_details_clicking_somewhere: # if popup window should be closed by clicking do it now if statuswindow.is_shown and\ not conf.fullscreen and\ not conf.windowed: statuswindow.is_hiding_timestamp = time.time() statuswindow.hide_window() class AllOKLabel(QLabel): """ Label which is shown in fullscreen and windowed mode when all is OK - pretty seldomly """ def __init__(self, text='', parent=None): QLabel.__init__(self, text='OK', parent=parent) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.setAlignment(Qt.AlignCenter) self.set_color() dialogs.settings.changed.connect(self.set_color) @pyqtSlot() def set_color(self): self.setStyleSheet('''padding-left: 1px; padding-right: 1px; color: %s; background-color: %s; font-size: 92px; font-weight: bold;''' % (conf.__dict__['color_ok_text'], conf.__dict__['color_ok_background'])) class StatusWindow(QWidget): """ Consists of statusbar, toparea and scrolling area. Either statusbar is shown or (toparea + scrolling area) """ # sent by .resize_window() resizing = pyqtSignal() # send when windows opens, e.g. for stopping notifications showing = pyqtSignal() # send when window shrinks down to statusbar or closes hiding = pyqtSignal() # signal to be sent to all server workers to recheck all recheck = pyqtSignal() # signal to be sent to all treeview workers to clear server event history # after 'Refresh'-button has been pressed clear_event_history = pyqtSignal() def __init__(self): """ Status window combined from status bar and popup window """ # attempt with desktop as parent for window Qt.Tool QWidget.__init__(self, parent=APP.desktop()) # immediately hide to avoid flicker on Windows and OSX self.hide() # ewmh.py in thirdparty directory needed to keep floating statusbar on all desktops in Linux if not OS in NON_LINUX: self.ewmh = EWMH() # avoid quitting when using Qt.Tool flag and closing settings dialog APP.setQuitOnLastWindowClosed(False) # show tooltips even if popup window has no focus self.setAttribute(Qt.WA_AlwaysShowToolTips) if OS == 'Darwin': # avoid hiding window if it has no focus - necessary on OSX if using flag Qt.Tool self.setAttribute(Qt.WA_MacAlwaysShowToolWindow) self.setWindowTitle(AppInfo.NAME) self.setWindowIcon(QIcon('%s%snagstamon.svg' % (RESOURCES, os.sep))) self.vbox = QVBoxLayout(self) # global VBox self.vbox.setSpacing(0) # no spacing self.vbox.setContentsMargins(0, 0, 0, 0) # no margin self.statusbar = StatusBar(parent=self) # statusbar HBox self.toparea = TopArea(parent=self) # toparea HBox # no need to be seen first self.toparea.hide() self.servers_scrollarea = QScrollArea(self) # scrollable area for server vboxes # avoid horizontal scrollbars self.servers_scrollarea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.servers_scrollarea_widget = QWidget(self.servers_scrollarea) # necessary widget to contain vbox for servers self.servers_scrollarea.hide() self.vbox.addWidget(self.statusbar) self.vbox.addWidget(self.toparea) self.vbox.addWidget(self.servers_scrollarea) self.servers_vbox = QVBoxLayout(self.servers_scrollarea) # VBox full of servers self.servers_vbox.setSpacing(0) self.servers_vbox.setContentsMargins(0, 0, 0, 0) self.label_all_ok = AllOKLabel(parent=self) self.label_all_ok.hide() self.servers_vbox.addWidget(self.label_all_ok) # test with OSX top menubar if OS == 'Darwin': self.menubar = QMenuBar() action_exit = QAction('exit', self.menubar) action_settings = QAction('settings', self.menubar) self.menubar.addAction(action_settings) self.menubar.addAction(action_exit) # connect logo of statusbar self.statusbar.logo.window_moved.connect(self.store_position) self.statusbar.logo.window_moved.connect(self.hide_window) self.statusbar.logo.window_moved.connect(self.correct_moving_position) self.statusbar.logo.mouse_pressed.connect(self.store_position) # after status summarization check if window has to be resized self.statusbar.resize.connect(self.adjust_size) # statusbar label has been entered by mouse -> show for label in self.statusbar.color_labels.values(): label.mouse_entered.connect(self.show_window_after_checking_for_hover) label.mouse_released.connect(self.show_window_after_checking_for_clicking) # connect message label to hover self.statusbar.label_message.mouse_entered.connect(self.show_window_after_checking_for_hover) self.statusbar.label_message.mouse_released.connect(self.show_window_after_checking_for_clicking) # when logo in toparea was pressed hurry up to save the position so the statusbar will not jump self.toparea.logo.window_moved.connect(self.store_position) self.toparea.logo.window_moved.connect(self.hide_window) self.toparea.logo.window_moved.connect(self.correct_moving_position) self.toparea.logo.mouse_pressed.connect(self.store_position) # when version label in toparea was pressed hurry up to save the position so the statusbar will not jump self.toparea.label_version.window_moved.connect(self.store_position) self.toparea.label_version.window_moved.connect(self.hide_window) self.toparea.label_version.window_moved.connect(self.correct_moving_position) self.toparea.label_version.mouse_pressed.connect(self.store_position) # when empty space in toparea was pressed hurry up to save the position so the statusbar will not jump self.toparea.label_empty_space.window_moved.connect(self.store_position) self.toparea.label_empty_space.window_moved.connect(self.hide_window) self.toparea.label_empty_space.window_moved.connect(self.correct_moving_position) self.toparea.label_empty_space.mouse_pressed.connect(self.store_position) # buttons in toparea self.toparea.button_filters.clicked.connect(dialogs.settings.show_filters) self.toparea.button_recheck_all.clicked.connect(self.recheck_all) self.toparea.button_refresh.clicked.connect(self.refresh) self.toparea.button_settings.clicked.connect(self.hide_window) self.toparea.button_settings.clicked.connect(dialogs.settings.show) self.toparea.button_close.clicked.connect(self.hide_window) # if monitor was selected in combobox its monitor window is opened self.toparea.combobox_servers.monitor_opened.connect(self.hide_window) # hide if settings dialog pops up dialogs.settings.show_dialog.connect(self.hide_window) # refresh all information after changed settings dialogs.settings.changed.connect(self.refresh) dialogs.settings.changed.connect(self.toparea.combobox_servers.fill) # show status popup when systray icon was clicked systrayicon.show_popwin.connect(self.show_window_systrayicon) systrayicon.hide_popwin.connect(self.hide_window) # hide status window if version check finished check_version.version_info_retrieved.connect(self.hide_window) # worker and thread duo needed for notifications self.worker_notification_thread = QThread(self) self.worker_notification = self.Worker_Notification() # flashing statusbar self.worker_notification.start_flash.connect(self.statusbar.flash) self.worker_notification.stop_flash.connect(self.statusbar.reset) # flashing statusicon self.worker_notification.start_flash.connect(systrayicon.flash) self.worker_notification.stop_flash.connect(systrayicon.reset) # desktop notification self.worker_notification.desktop_notification.connect(self.desktop_notification) # react to open button in notification bubble dbus_connection.open_statuswindow.connect(self.show_window_from_notification_bubble) # stop notification if window gets shown or hidden self.hiding.connect(self.worker_notification.stop) self.worker_notification.moveToThread(self.worker_notification_thread) # start with priority 0 = lowest self.worker_notification_thread.start(0) self.create_ServerVBoxes() self.servers_scrollarea_widget.setLayout(self.servers_vbox) self.servers_scrollarea.setWidget(self.servers_scrollarea_widget) self.servers_scrollarea.setWidgetResizable(True) # create brushes for treeview create_brushes() # needed for moving the statuswindow self.moving = False self.relative_x = False self.relative_y = False # helper values for QTimer.singleShot move attempt self.move_to_x = self.move_to_y = 0 # stored x y values for systemtray icon self.icon_x = 0 self.icon_y = 0 # flag to mark if window is shown or not if conf.windowed: self.is_shown = True else: self.is_shown = False # store show_window timestamp to avoid flickering window in KDE5 with systray self.is_shown_timestamp = time.time() # store timestamp to avoid reappearing window shortly after clicking onto toparea self.is_hiding_timestamp = time.time() # if status_ok is true no server_vboxes are needed self.status_ok = True # timer for waiting to set is_shown flag self.timer = QTimer(self) # a thread + worker is necessary to do actions thread-safe in background # like debugging self.worker_thread = QThread(self) self.worker = self.Worker() self.worker.moveToThread(self.worker_thread) # start thread and debugging loop if debugging is enabled if conf.debug_mode: self.worker_thread.started.connect(self.worker.debug_loop) # start debug loop by signal # ##self.worker.start_debug_loop.connect(self.worker.debug_loop) dialogs.settings.start_debug_loop.connect(self.worker.debug_loop) # start with priority 0 = lowest self.worker_thread.start(0) # finally show up self.set_mode() def set_mode(self): """ apply presentation mode """ if conf.statusbar_floating: # no need for systray systrayicon.hide() self.hide_window() self.statusbar.show() # show statusbar/statuswindow on last saved position # when coordinates are inside known screens if get_screen(conf.position_x, conf.position_y) is not None: self.move(conf.position_x, conf.position_y) else: # get available desktop specs available_x = desktop.availableGeometry(self).x() available_y = desktop.availableGeometry(self).y() self.move(available_x, available_y) # proud winner of the-dirty-workaround-of-the-year-award # stay on top flag seems to have a problem on Windows if some other window # gets in a race condition race the focus or is topmost instead of Nagstamon # so the floating statusbar moves silently into a quiet corner of the desktop # and raises itself serveral times to be the topmost to make the flags stick if OS == 'Windows': self.move(-32768, -32768) # just a guess - 10 times seem to be enough for counter in range(100): self.setWindowFlags(Qt.FramelessWindowHint) self.show() self.setWindowFlags(WINDOW_FLAGS) self.hide() self.show() self.raise_() else: # statusbar and detail window should be frameless and stay on top # tool flag helps to be invisible in taskbar self.setWindowFlags(WINDOW_FLAGS) # show statusbar without being active, just floating self.setAttribute(Qt.WA_ShowWithoutActivating) # necessary to be shown before Linux EWMH-mantra can be applied self.show() # X11/Linux needs some special treatment to get the statusbar floating on all virtual desktops if not OS in NON_LINUX: # get all windows... winid = self.winId().__int__() self.ewmh.setWmDesktop(winid, 0xffffffff) self.ewmh.display.flush() # show statusbar/statuswindow on last saved position # when coordinates are inside known screens if get_screen(conf.position_x, conf.position_y) is not None: self.move(conf.position_x, conf.position_y) else: # get available desktop specs available_x = desktop.availableGeometry(self).x() available_y = desktop.availableGeometry(self).y() self.move(available_x, available_y) # need a close button self.toparea.button_close.show() elif conf.icon_in_systray: # statusbar and detail window should be frameless and stay on top # tool flag helps to be invisible in taskbar self.setWindowFlags(WINDOW_FLAGS) # show statusbar without being active, just floating self.setAttribute(Qt.WA_ShowWithoutActivating) # yeah! systray! systrayicon.show() # need a close button self.toparea.button_close.show() # no need for window and its parts self.statusbar.hide() self.hide_window() elif conf.fullscreen: self.statusbar.hide() self.toparea.show() self.servers_scrollarea.show() # get screen geometry to get right screen to position window on screen_geometry = get_screen_geometry(conf.fullscreen_display) self.move(screen_geometry.x(), screen_geometry.y()) # keep window entry in taskbar and thus no Qt.Tool self.setWindowFlags(Qt.Widget | Qt.FramelessWindowHint) # show statusbar actively self.setAttribute(Qt.WA_ShowWithoutActivating, False) self.show_window() # fullscreen mode is rather buggy on everything other than OSX so just use a maximized window if OS == 'Darwin': self.showFullScreen() else: self.show() self.showMaximized() # no need for close button self.toparea.button_close.hide() elif conf.windowed: systrayicon.hide() self.statusbar.hide() # no need for close button self.toparea.button_close.hide() self.toparea.show() self.servers_scrollarea.show() # keep window entry in taskbar and thus no Qt.Tool self.setWindowFlags(Qt.Widget) # show statusbar actively self.setAttribute(Qt.WA_ShowWithoutActivating, False) # some maybe sensible default self.setMinimumSize(500, 300) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) # default maximum size self.setMaximumSize(16777215, 16777215) self.move(conf.position_x, conf.position_y) self.resize(conf.position_width, conf.position_height) # make sure window is shown self.show() self.show_window() # make sure windows comes up self.raise_() # store position for showing/hiding statuswindow self.stored_x = self.x() self.stored_y = self.y() self.stored_width = self.width() def create_ServerVBox(self, server): """ internally used to create enabled servers to be displayed """ # create server vboxed from current running servers if server.enabled: # display authentication dialog if password is not known if not conf.servers[server.name].save_password and\ not conf.servers[server.name].use_autologin and\ conf.servers[server.name].password == '' and\ not conf.servers[server.name].authentication == 'kerberos': dialogs.authentication.show_auth_dialog(server.name) # without parent there is some flickering when starting server_vbox = ServerVBox(server, parent=self) # important to set correct server to worker, especially after server changes server_vbox.table.worker.server = server # connect to global resize signal server_vbox.table.ready_to_resize.connect(self.adjust_size) # tell statusbar to summarize after table was refreshed server_vbox.table.worker.new_status.connect(self.statusbar.summarize_states) server_vbox.table.worker.new_status.connect(self.raise_window_on_all_desktops) server_vbox.table.worker.new_status.connect(systrayicon.show_state) # if problems go themselves there is no need to notify user anymore server_vbox.table.worker.problems_vanished.connect(self.worker_notification.stop) # show error message in statusbar server_vbox.table.worker.show_error.connect(self.statusbar.set_error) server_vbox.table.worker.hide_error.connect(self.statusbar.reset_error) # show error icon in systray server_vbox.table.worker.show_error.connect(systrayicon.set_error) server_vbox.table.worker.hide_error.connect(systrayicon.reset_error) # tell notification worker to do something AFTER the table was updated server_vbox.table.status_changed.connect(self.worker_notification.start) # and to update status window server_vbox.table.refreshed.connect(self.update_window) # hide statuswindow if authentication dialog is to be shown server_vbox.button_authenticate.clicked.connect(self.hide_window) # tell table it should remove freshness of formerly new items when window closes # because apparently the new events have been seen now self.hiding.connect(server_vbox.table.worker.unfresh_event_history) # stop notifcation if statuswindow pops up self.showing.connect(self.worker_notification.stop) # tell server worker to recheck all hosts and services self.recheck.connect(server_vbox.table.worker.recheck_all) # refresh table after changed settings dialogs.settings.changed.connect(server_vbox.table.refresh) # listen if statuswindow cries for event history clearance self.clear_event_history.connect(server_vbox.table.worker.unfresh_event_history) return server_vbox else: return None def sort_ServerVBoxes(self): """ sort ServerVBoxes alphabetically """ # shortly after applying changes a QObject might hang around in the children list which should # be filtered out this way vboxes_dict = dict() for child in self.servers_vbox.children(): if 'server' in child.__dict__.keys(): vboxes_dict[child.server.name] = child # freshly set servers_scrollarea_widget and its layout servers_vbox servers_vbox_new = QVBoxLayout() # VBox full of servers servers_vbox_new.setContentsMargins(0, 0, 0, 0) servers_vbox_new.setSpacing(0) # sort server vboxes for vbox in sorted(vboxes_dict): vboxes_dict[vbox].setParent(None) servers_vbox_new.addLayout(vboxes_dict[vbox]) # add expanding stretching item at the end for fullscreen beauty servers_vbox_new.addSpacerItem(QSpacerItem(0, desktop.availableGeometry(self).height(), QSizePolicy.Minimum, QSizePolicy.Expanding)) # switch to new servers_vbox self.servers_vbox = servers_vbox_new self.servers_scrollarea_widget = QWidget() # necessary widget to contain vbox for servers self.servers_scrollarea_widget.setLayout(self.servers_vbox) self.servers_scrollarea.setWidget(self.servers_scrollarea_widget) del(vboxes_dict) def create_ServerVBoxes(self): # create vbox for each enabled server for server in servers.values(): if server.enabled: self.servers_vbox.addLayout(self.create_ServerVBox(server)) self.sort_ServerVBoxes() @pyqtSlot() def show_window_after_checking_for_clicking(self): """ being called after clicking statusbar - check if window should be showed """ if conf.popup_details_clicking: self.show_window() @pyqtSlot() def show_window_after_checking_for_hover(self): """ being called after hovering over statusbar - check if window should be showed """ if conf.popup_details_hover: self.show_window() @pyqtSlot() def show_window_from_notification_bubble(self): """ show status window after button being clicked in notification bubble """ if conf.statusbar_floating: self.show_window() elif conf.icon_in_systray: self.show_window_systrayicon() @pyqtSlot() def show_window_systrayicon(self): """ handle clicks onto systray icon """ if not self.is_shown: # where is the pointer which clicked onto systray icon icon_x = systrayicon.geometry().x() icon_y = systrayicon.geometry().y() # strangely enough on KDE the systray icon geometry gives back 0, 0 as coordinates if icon_x == 0: icon_x = QCursor.pos().x() if icon_y == 0: icon_y = QCursor.pos().y() # get available desktop specs available_width = desktop.availableGeometry(self).width() available_height = desktop.availableGeometry(self).height() available_x = desktop.availableGeometry(self).x() available_y = desktop.availableGeometry(self).y() y = 0 if icon_x > (available_width + available_x) / 2: x = available_x + available_width - self.statusbar.width() else: x = available_x + self.statusbar.width() if icon_y > (available_height - available_y) / 2: y = available_height - available_y self.move(x, y) # under unfortunate circumstances statusbar might have the the moving flag true # fix it here because it makes no sense but might cause non-appearing statuswindow self.moving = False # already show here because was closed before in hide_window() self.show() self.show_window() else: self.hide_window() @pyqtSlot() def show_window(self, event=None): """ used to show status window when its appearance is triggered, also adjusts geometry """ # do not show up when being dragged around if not self.moving: # check if really all is OK for vbox in self.servers_vbox.children(): if vbox.server.all_ok and\ vbox.server.status == '' and\ not vbox.server.refresh_authentication and\ not vbox.server.tls_error: self.status_ok = True else: self.status_ok = False break # here we should check if scroll_area should be shown at all if not self.status_ok: if not conf.fullscreen and not conf.windowed: # attempt to avoid flickering on MacOSX - already hide statusbar here self.statusbar.hide() # show the other status window components self.toparea.show() self.servers_scrollarea.show() else: self.label_all_ok.hide() for vbox in self.servers_vbox.children(): if not vbox.server.all_ok: vbox.show_all() # show at least server vbox header to notify about connection or other errors elif vbox.server.status != '' or vbox.server.refresh_authentication or vbox.server.tls_error: vbox.show_only_header() elif vbox.server.all_ok and vbox.server.status == '': vbox.hide_all() # depending on authentication state show reauthentication button if vbox.server.refresh_authentication: vbox.button_authenticate.show() else: vbox.button_authenticate.hide() # depending on TLS error show fix-TLS-button if vbox.server.tls_error: vbox.button_fix_tls_error.show() else: vbox.button_fix_tls_error.hide() if not conf.fullscreen and not conf.windowed: # theory... width, height, x, y = self.calculate_size() # ...and practice self.resize_window(width, height, x, y) # switch on if OS == 'Darwin': # delayed because of flickering window in OSX self.timer.singleShot(200, self.set_shown) else: self.set_shown() # avoid horizontally scrollable tables self.adjust_dummy_columns() self.show() # Using the EWMH protocol to move the window to the active desktop. # Seemed to be a problem on XFCE # https://github.com/HenriWahl/Nagstamon/pull/199 if not OS in NON_LINUX and conf.icon_in_systray: try: winid = self.winId().__int__() deskid = self.ewmh.getCurrentDesktop() self.ewmh.setWmDesktop(winid, deskid) self.ewmh.display.flush() # makes the window manager switch to the desktop where this widget has appeared self.raise_() except Exception: # workaround for https://github.com/HenriWahl/Nagstamon/issues/246#issuecomment-220478066 pass # store timestamp to avoid flickering as in https://github.com/HenriWahl/Nagstamon/issues/184 self.is_shown_timestamp = time.time() # tell others like notification that statuswindow shows up now self.showing.emit() else: # hide vboxes in fullscreen and whole window in any other case if all is OK for vbox in self.servers_vbox.children(): vbox.hide_all() if conf.fullscreen or conf.windowed: self.label_all_ok.show() if conf.icon_in_systray: self.hide_window() @pyqtSlot() def update_window(self): """ redraw window content, to be effective only when window is shown """ if self.is_shown or conf.fullscreen or ( conf.windowed and self.is_shown): self.show_window() @pyqtSlot() def hide_window(self): """ hide window if not needed """ if not conf.fullscreen and not conf.windowed: # only hide if shown and not locked or if not yet hidden if moving if self.is_shown is True or\ self.is_shown is True and\ self.moving is True: # only hide if shown at least a fraction of a second # or has not been hidden a too short time ago if self.is_shown_timestamp + 0.5 < time.time() or \ self.is_hiding_timestamp + 0.2 < time.time(): if conf.statusbar_floating: self.statusbar.show() #self.statusbar.adjustSize() self.toparea.hide() self.servers_scrollarea.hide() self.setMinimumSize(1, 1) self.adjustSize() if conf.icon_in_systray: self.close() # switch off self.is_shown = False # flag to reflect top-ness of window/statusbar self.top = False # reset icon x y self.icon_x = 0 self.icon_y = 0 # tell the world that window goes down self.hiding.emit() if conf.windowed: self.hide() # store time of hiding self.is_hiding_timestamp = time.time() self.move(self.stored_x, self.stored_y) @pyqtSlot() def correct_moving_position(self): """ correct position if moving and cursor started outside statusbar """ if self.moving: mouse_x = QCursor.pos().x() mouse_y = QCursor.pos().y() # when cursor is outside moved window correct the coordinates of statusbar/statuswindow if not statuswindow.geometry().contains(mouse_x, mouse_y): rect = statuswindow.geometry() corrected_x = int(mouse_x - rect.width() / 2) corrected_y = int(mouse_y - rect.height() / 2) # calculate new relative values self.relative_x = mouse_x - corrected_x self.relative_y = mouse_y - corrected_y statuswindow.move(corrected_x, corrected_y) del(mouse_x, mouse_y, corrected_x, corrected_y) def calculate_size(self): """ get size of popup window """ # screen number or widget object needed for desktop.availableGeometry if conf.statusbar_floating: screen_or_widget = self elif conf.windowed: screen_or_widget = self elif conf.icon_in_systray: # where is the pointer which clicked onto systray icon icon_x = systrayicon.geometry().x() icon_y = systrayicon.geometry().y() if OS in NON_LINUX: if self.icon_x == 0: self.icon_x = QCursor.pos().x() elif icon_x != 0: self.icon_x = icon_x else: # strangely enough on KDE the systray icon geometry gives back 0, 0 as coordinates # also at Ubuntu Unity 16.04 if icon_x == 0 and self.icon_x == 0: self.icon_x = QCursor.pos().x() else: self.icon_x = icon_x if icon_y == 0 and self.icon_y == 0: self.icon_y = QCursor.pos().y() if OS in NON_LINUX: if self.icon_y == 0: self.icon_y = QCursor.pos().y() elif icon_y != 0: self.icon_y = icon_y screen_or_widget = get_screen(self.icon_x, self.icon_y) # only consider offset if it is configured if conf.systray_offset_use and conf.icon_in_systray: available_height = desktop.availableGeometry(screen_or_widget).height() - conf.systray_offset else: available_height = desktop.availableGeometry(screen_or_widget).height() available_width = desktop.availableGeometry(screen_or_widget).width() available_x = desktop.availableGeometry(screen_or_widget).x() available_y = desktop.availableGeometry(screen_or_widget).y() del(screen_or_widget) # take whole screen height into account when deciding about upper/lower-ness # add available_y because it might vary on differently setup screens # calculate top-ness only if window is closed if conf.statusbar_floating: if self.is_shown is False: if self.y() < desktop.screenGeometry(self).height() / 2 + available_y: self.top = True else: self.top = False # always take the stored position of the statusbar x = self.stored_x elif conf.icon_in_systray or conf.windowed: if self.icon_y < desktop.screenGeometry(self).height() / 2 + available_y: self.top = True else: self.top = False # take systray icon position as reference # assuming that a left oriented systray as in GNOME3 will need x = 0 #if self.icon_x < desktop.screenGeometry(self).width() / 2 + available_x and OS not in NON_LINUX: # x = 0 #else: # x = self.icon_x x = self.icon_x # get height from tablewidgets real_height = self.get_real_height() # width simply will be the current screen maximal width - less hassle! if self.get_real_width() > available_width: width = available_width x = available_x else: width = self.get_real_width() if width < self.toparea.sizeHint().width(): width = self.toparea.sizeHint().width() # always take the stored width of the statusbar into account x = x - int(width / 2) + int(self.stored_width / 2) # check left and right limits of x if x < available_x: x = available_x if x + width > available_x + available_width: x = available_x + available_width - width if conf.statusbar_floating: # when statusbar resides in uppermost part of current screen extend from top to bottom if self.top is True: y = self.y() if self.y() + real_height < available_height + available_y: height = real_height else: height = available_height - self.y() + available_y # when statusbar hangs around in lowermost part of current screen extend from bottom to top else: # when height is to large for current screen cut it if self.y() + self.height() - real_height < available_y: height = desktop.screenGeometry().height() - available_y - (desktop.screenGeometry().height() - (self.y() + self.height())) y = available_y else: height = real_height y = self.y() + self.height() - height elif conf.icon_in_systray or conf.windowed: # when systrayicon resides in uppermost part of current screen extend from top to bottom if self.top is True: # when being top y is of course the available one y = available_y if self.y() + real_height < available_height + available_y: height = real_height else: # if bigger than screen shrink to maximal real_height height = available_height - available_y # when statusbar hangs around in lowermost part of current screen extend from bottom to top else: # when height is to large for current screen cut it if self.y() + self.height() - real_height < available_y: # simply take the available max height if there is no more screen real estate # possible because systrayicon resides aside from available space, in fact cutting it height = available_height y = available_height - height else: if available_height < real_height: y = available_y height = available_height else: y = available_height - real_height height = real_height return width, height, x, y def resize_window(self, width, height, x, y): """ resize status window according to its new size """ # store position for restoring it when hiding - only if not shown of course if self.is_shown is False: self.stored_x = self.x() self.stored_y = self.y() self.stored_width = self.width() if OS == 'Windows': # absolutely strange, but no other solution available # - Only on Windows the statusbar is moving FIRST before resizing - no matter which # order was used # - Dirty workaround: # - store x and y in .move_to_* # - start helper move_timer by timer singleshot to give statusbar some time to hide self.move_to_x, self.move_to_y = x, y self.timer.singleShot(10, self.move_timer) else: self.move(x, y) self.setMaximumSize(width, height) self.setMinimumSize(width, height) self.adjustSize() return True @pyqtSlot() def move_timer(self): """ helper for move by QTimer.singleShot - attempt to avoid flickering on Windows """ self.move(self.move_to_x, self.move_to_y) @pyqtSlot() def adjust_size(self): """ resize window if shown and needed """ # avoid race condition when waiting for password dialog if 'is_shown' in self.__dict__: if not conf.fullscreen and not conf.windowed: self.adjusting_size_lock = True # fully displayed statuswindow if self.is_shown is True: width, height, x, y = self.calculate_size() self.adjust_dummy_columns() else: # statusbar only hint = self.sizeHint() # on MacOSX and Windows statusbar will not shrink automatically, so this workaround hopefully helps width = hint.width() height = hint.height() x = self.x() y = self.y() self.setMaximumSize(hint) self.setMinimumSize(hint) del(hint) self.resize_window(width, height, x, y) del(width, height, x, y) else: self.adjust_dummy_columns() @pyqtSlot() def adjust_dummy_columns(self): """ calculate widest width of all server tables to hide dummy column at the widest one """ max_width = 0 max_width_table = None for server in self.servers_vbox.children(): # if table is wider than current max_width take its width as max_width if server.table.get_real_width() > max_width: max_width = server.table.get_real_width() max_width_table = server.table # widest table does not need the dummy column #9 for server in self.servers_vbox.children(): if max_width_table == server.table and max_width == server.table.width(): # hide dummy column as here is the most stretched table server.table.setColumnHidden(9, True) server.table.header().setStretchLastSection(False) else: # show dummy column because some other table is wider server.table.setColumnHidden(9, False) server.table.header().setStretchLastSection(True) del(max_width, max_width_table) return True @pyqtSlot() def store_position(self): """ store position for restoring it when hiding """ if not self.is_shown: self.stored_x = self.x() self.stored_y = self.y() self.stored_width = self.width() def leaveEvent(self, event): """ check if popup has to be hidden depending on mouse position """ # check first if popup has to be shown by hovering or clicking if conf.close_details_hover and not conf.fullscreen and not conf.windowed: # only hide window if cursor is outside of it mouse_x = QCursor.pos().x() mouse_y = QCursor.pos().y() # <= and >= necessary because sometimes mouse_* is the same as self.*() if mouse_x <= self.x() or mouse_x >= self.x() + self.width() or\ mouse_y <= self.y() or mouse_y >= self.y() + self.height(): self.hide_window() del(mouse_x, mouse_y) def closeEvent(self, event): """ window close """ # check first if popup has to be shown by hovering or clicking if conf.windowed: exit() def get_real_width(self): """ calculate widest width of all server tables """ width = 0 for server in self.servers_vbox.children(): # if table is wider than window adjust with to table if server.table.isVisible() and server.table.get_real_width() > width: width = server.table.get_real_width() # if header in server vbox is wider than width adjust the latter if server.header.sizeHint().width() > width: width = server.header.sizeHint().width() return width def get_real_height(self): """ calculate summary of all heights of all server tables plus height of toparea """ height = 0 for vbox in self.servers_vbox.children(): height += vbox.get_real_height() # add size of toparea and 2 times the MARGIN (top and bottom) height += self.toparea.sizeHint().height() + 2 return height def set_shown(self): """ might help to avoid flickering on MacOSX, in cooperation with QTimer """ self.is_shown = True def store_position_to_conf(self): """ store position of statuswindow/statusbar """ # only useful if statusbar is floating if conf.statusbar_floating: # minimize window to statusbar only to get real position self.hide_window() conf.position_x = self.x() conf.position_y = self.y() if conf.windowed: conf.position_x = self.x() conf.position_y = self.y() conf.position_width = self.width() conf.position_height = self.height() @pyqtSlot(str, str) def show_message(self, msg_type, message): """ show message from other thread like MediaPlayer """ title = " ".join((AppInfo.NAME, msg_type)) if msg_type == 'warning': return(QMessageBox.warning(statuswindow, title, message)) elif msg_type == 'information': return(QMessageBox.information(statuswindow, title, message)) @pyqtSlot() def recheck_all(self): """ tell servers to recheck all hosts and services """ self.recheck.emit() @pyqtSlot() def refresh(self): """ tell all enabled servers to refresh their information """ # unfresh event history of servers self.clear_event_history.emit() for server in get_enabled_servers(): if conf.debug_mode: server.Debug(server=server.name, debug='Refreshing all hosts and services') # manipulate server thread counter so get_status loop will refresh when next looking # at thread counter server.thread_counter = conf.update_interval_seconds @pyqtSlot(dict) def desktop_notification(self, current_status_count): """ show desktop notification - must be called from same thread as DBus intialization """ # compile message from status counts message = '' for state in ['DOWN', 'UNREACHABLE', 'DISASTER', 'CRITICAL', 'HIGH', 'AVERAGE', 'WARNING', 'INFORMATION', 'UNKNOWN']: if current_status_count[state] > 0: message += '{0} {1} '.format(str(current_status_count[state]), state) # due to mysterious DBus-Crashes # see https://github.com/HenriWahl/Nagstamon/issues/320 try: dbus_connection.show(AppInfo.NAME, message) except Exception: traceback.print_exc(file=sys.stdout) @pyqtSlot() def raise_window_on_all_desktops(self): """ experimental workaround for floating-statusbar-only-on-one-virtual-desktop-after-a-while bug see https://github.com/HenriWahl/Nagstamon/issues/217 """ if conf.windowed: return # X11/Linux needs some special treatment to get the statusbar floating on all virtual desktops if not OS in NON_LINUX: # get all windows... winid = self.winId().__int__() self.ewmh.setWmDesktop(winid, 0xffffffff) self.ewmh.display.flush() # apparently sometime the floating statusbsr vanishes in the background # lets try here to keep it on top - only if not fullscreen if not conf.fullscreen and not conf.windowed and not platform.system == 'Windows': self.setWindowFlags(WINDOW_FLAGS) # again and again try to keep that statuswindow on top! if OS == 'Windows' and not conf.fullscreen and not conf.windowed: # find out if no context menu is shown and thus would be # overlapped by statuswindow for vbox in self.servers_vbox.children(): # jump out here if any action_menu is shown if not vbox.table.action_menu.available: return self.raise_() class Worker(QObject): """ run a thread for example for debugging """ def __init__(self): QObject.__init__(self) # flag to decide if thread has to run or to be stopped self.running = True # flag if debug_loop is looping self.debug_loop_looping = False # default debug dile does not exist self.debug_file = None def open_debug_file(self): # open file and truncate self.debug_file = open(conf.debug_file, "w") def close_debug_file(self): # close and reset file self.debug_file.close() self.debug_file = None @pyqtSlot() def debug_loop(self): """ if debugging is enabled, poll debug_queue list and print/write its contents """ if conf.debug_mode: self.debug_loop_looping = True # as long thread is supposed to run while self.running and self.debug_loop_looping: # only log something if there is something to tell while len(debug_queue) > 0: # always get oldest item of queue list - FIFO debug_line = (debug_queue.pop(0)) # output to console print(debug_line) if conf.debug_to_file: # if there is no file handle available get it if self.debug_file is None: self.open_debug_file() # log line per line self.debug_file.write(debug_line + "\n") # wait second until next poll time.sleep(1) # unset looping self.debug_mode_looping = False # close file if any if self.debug_file is not None: self.close_debug_file() class Worker_Notification(QObject): """ run a thread for doing all notification stuff """ # tell statusbar labels to flash start_flash = pyqtSignal() stop_flash = pyqtSignal() # tell mediaplayer to load and play sound file load_sound = pyqtSignal(str) play_sound = pyqtSignal() # tell statuswindow to use desktop notification desktop_notification = pyqtSignal(dict) # flag about current notification state is_notifying = False # only one enabled server should have the right to send play_sound signal notifying_server = '' # current worst state worth a notification worst_notification_status = 'UP' # desktop notification needs to store count of states status_count = dict() def __init__(self): QObject.__init__(self) @pyqtSlot(str, str) def start(self, server_name, worst_status_diff): """ start notification """ if conf.notification: # only if not notifying yet or the current state is worse than the prior AND # only when the current state is configured to be honking about if (STATES.index(worst_status_diff) > STATES.index(self.worst_notification_status) or self.is_notifying is False) and\ conf.__dict__['notify_if_{0}'.format(worst_status_diff.lower())] is True: # keep last worst state worth a notification for comparison 3 lines above self.worst_notification_status = worst_status_diff # set flag to avoid innecessary notification self.is_notifying = True if self.notifying_server == '': self.notifying_server = server_name # flashing statusbar if conf.notification_flashing: self.start_flash.emit() # Play default sounds via mediaplayer if conf.notification_sound: sound_file = '' # at the moment there are only sounds for down, critical and warning # only honk if notifications are wanted for this state if worst_status_diff in STATES_SOUND: if conf.notification_default_sound: # default .wav sound files are in resources folder sound_file = '{0}{1}{2}.wav'.format(RESOURCES, os.sep, worst_status_diff.lower()) elif conf.notification_custom_sound: sound_file = conf.__dict__['notification_custom_sound_{0}'.format(worst_status_diff.lower())] # once loaded file will be played by every server, even if it is # not the self.notifying_server that loaded it self.load_sound.emit(sound_file) # only one enabled server should access the mediaplayer if self.notifying_server == server_name: self.play_sound.emit() # Notification actions if conf.notification_actions: if conf.notification_action_warning is True and worst_status_diff == 'WARNING': self.execute_action(server_name, conf.notification_action_warning_string) if conf.notification_action_critical is True and worst_status_diff == 'CRITICAL': self.execute_action(server_name, conf.notification_action_critical_string) if conf.notification_action_down is True and worst_status_diff == 'DOWN': self.execute_action(server_name, conf.notification_action_down_string) # Custom event notification - valid vor ALL events, thus without status comparison if conf.notification_actions is True and conf.notification_custom_action is True: # temporarily used to collect executed events events_list = [] events_string = '' # if no single notifications should be used (default) put all events into one string, separated by separator if conf.notification_custom_action_single is False: for server in get_enabled_servers(): # list comprehension only considers events which are new, ergo True events_list += [k for k, v in server.events_notification.items() if v is True] # create string for no-single-event-notification of events separated by separator events_string = conf.notification_custom_action_separator.join(events_list) # clear already notified events setting them to False for server in get_enabled_servers(): for event in [k for k, v in server.events_notification.items() if v is True]: server.events_notification[event] = False else: for server in get_enabled_servers(): for event in [k for k, v in server.events_notification.items() if v is True]: custom_action_string = conf.notification_custom_action_string.replace('$EVENTS$', event) # execute action self.execute_action(server_name, custom_action_string) # clear already notified events setting them to False server.events_notification[event] = False # if events got filled display them now if events_string != '': # in case a single action per event has to be executed custom_action_string = conf.notification_custom_action_string.replace('$EVENT$', '$EVENTS$') # insert real event(s) custom_action_string = custom_action_string.replace('$EVENTS$', events_string) # execute action self.execute_action(server_name, custom_action_string) else: # set all events to False to ignore them in the future for event in servers[server_name].events_notification: servers[server_name].events_notification[event] = False # repeated sound # only let one enabled server play sound to avoid a larger cacophony if self.is_notifying and\ conf.notification_sound_repeat and\ self.notifying_server == server_name: self.play_sound.emit() # desktop notification if conf.notification_desktop: # get status count from servers current_status_count = get_status_count() if current_status_count != self.status_count: self.desktop_notification.emit(current_status_count) # store status count for next comparison self.status_count = current_status_count del(current_status_count) @pyqtSlot() def stop(self): """ stop notification if there is no need anymore """ if self.is_notifying: self.worst_notification_status = 'UP' self.is_notifying = False # no more flashing statusbar and systray self.stop_flash.emit() # reset notifying server, waiting for next notification self.notifying_server = '' def execute_action(self, server_name, custom_action_string): """ execute custom action """ if conf.debug_mode: servers[server_name].Debug(debug='NOTIFICATION: ' + custom_action_string) subprocess.Popen(custom_action_string, shell=True) class NagstamonLogo(QSvgWidget, DraggableWidget): """ SVG based logo, used for statusbar and toparea logos """ # yell if statusbar is moved window_moved = pyqtSignal() # needed for popup after hover mouse_entered = pyqtSignal() # needed for popup after click mouse_pressed = pyqtSignal() mouse_released = pyqtSignal() def __init__(self, file, width=None, height=None, parent=None): QSvgWidget.__init__(self, parent=parent) # either filepath or QByteArray for toparea logo self.load(file) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) # size needed for small Nagstamon logo in statusbar if width is not None and height is not None: self.setMinimumSize(width, height) self.setMaximumSize(width, height) def adjust_size(self, height=None, width=None): if width is not None and height is not None: self.setMinimumSize(width, height) self.setMaximumSize(width, height) class StatusBar(QWidget): """ status bar for short display of problems """ # send signal to statuswindow resize = pyqtSignal() # needed to maintain flashing labels labels_invert = pyqtSignal() labels_reset = pyqtSignal() def __init__(self, parent=None): QWidget.__init__(self, parent=parent) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.hbox = HBoxLayout(spacing=0, parent=parent) self.setLayout(self.hbox) # define labels first to get their size for svg logo dimensions self.color_labels = OrderedDict() self.color_labels['OK'] = StatusBarLabel('OK', parent=parent) for state in COLORS: self.color_labels[state] = StatusBarLabel(state, parent=parent) self.labels_invert.connect(self.color_labels[state].invert) self.labels_reset.connect(self.color_labels[state].reset) # label for error message(s) self.label_message = StatusBarLabel('error', parent=parent) self.labels_invert.connect(self.label_message.invert) self.labels_reset.connect(self.label_message.reset) # derive logo dimensions from status label self.logo = NagstamonLogo('%s%snagstamon_logo_bar.svg' % (RESOURCES, os.sep), self.color_labels['OK'].fontMetrics().height(), self.color_labels['OK'].fontMetrics().height(), parent=parent) # add logo self.hbox.addWidget(self.logo) # label for error messages self.hbox.addWidget(self.label_message) self.label_message.hide() # add state labels self.hbox.addWidget(self.color_labels['OK']) for state in COLORS: self.hbox.addWidget(self.color_labels[state]) # when there are new settings/colors refresh labels dialogs.settings.changed.connect(self.reset) # when new setings are applied adjust font size dialogs.settings.changed.connect(self.adjust_size) # timer for singleshots for flashing self.timer = QTimer() self.adjust_size() @pyqtSlot() def summarize_states(self): """ display summaries of states in statusbar """ # initial zeros for label in self.color_labels.values(): label.number = 0 # only count numbers of enabled monitor servers for server in (filter(lambda s: s.enabled, servers.values())): for state in COLORS: self.color_labels[state].number += server.__dict__[state.lower()] # summarize all numbers - if all_numbers keeps 0 everything seems to be OK all_numbers = 0 # repaint colored labels or hide them if necessary for label in self.color_labels.values(): if label.number == 0: label.hide() else: label.setText(' '.join((str(label.number), COLOR_STATE_NAMES[label.state][conf.long_display]))) label.show() label.adjustSize() all_numbers += label.number if all_numbers == 0 and not get_errors() and not self.label_message.isVisible(): self.color_labels['OK'].show() self.color_labels['OK'].adjustSize() else: self.color_labels['OK'].hide() # fix size after refresh - better done here to avoid ugly artefacts hint = self.sizeHint() self.setMaximumSize(hint) self.setMinimumSize(hint) del hint # tell statuswindow its size might be adjusted self.resize.emit() @pyqtSlot() def flash(self): """ send color inversion signal to labels """ # only if currently a notification is necessary if statuswindow.worker_notification.is_notifying: self.labels_invert.emit() # fire up a singleshot to reset color soon self.timer.singleShot(500, self.reset) @pyqtSlot() def reset(self): """ tell labels to set original colors """ self.labels_reset.emit() # only if currently a notification is necessary if statuswindow.worker_notification.is_notifying: # even later call itself to invert colors as flash self.timer.singleShot(500, self.flash) @pyqtSlot() def adjust_size(self): """ apply new size of widgets, especially Nagstamon logo run through all labels to the the max height in case not all labels are shown at the same time - which is very likely the case """ # take height for logo height = 0 # run through labels to set font and get height for logo for label in self.color_labels.values(): label.setFont(FONT) if label.fontMetrics().height() > height: height = label.fontMetrics().height() self.label_message.setFont(FONT) # absolutely silly but no other cure in sight # strange miscalculation of nagstamon logo on MacOSX if OS == 'Darwin' and 18 <= height <= 24: height += 1 # adjust logo size to fit to label size self.logo.adjust_size(height, height) # avoid flickerung/artefact by updating immediately self.summarize_states() @pyqtSlot(str) def set_error(self, message): """ display error message if any error exists """ self.label_message.setText(message) self.label_message.show() @pyqtSlot() def reset_error(self): """ delete error message if there is no error """ if not get_errors(): self.label_message.setText('') self.label_message.hide() class StatusBarLabel(DraggableLabel): """ one piece of the status bar labels for one state """ # yell if statusbar is moved window_moved = pyqtSignal() # needed for popup after hover mouse_entered = pyqtSignal() # needed for popup after click mouse_pressed = pyqtSignal() mouse_released = pyqtSignal() def __init__(self, state, parent=None): DraggableLabel.__init__(self, parent=parent) self.setStyleSheet('''padding-left: 1px; padding-right: 1px; color: %s; background-color: %s;''' % (conf.__dict__['color_%s_text' % (state.lower())], conf.__dict__['color_%s_background' % (state.lower())])) # just let labels grow as much as they need self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) # hidden per default self.hide() # default text - only useful in case of OK Label self.setText(state) # number of hosts/services of this state self.number = 0 # store state of label to access long state names in .summarize_states() self.state = state @pyqtSlot() def invert(self): self.setStyleSheet('''padding-left: 1px; padding-right: 1px; color: %s; background-color: %s;''' % (conf.__dict__['color_%s_background' % (self.state.lower())], conf.__dict__['color_%s_text' % (self.state.lower())])) @pyqtSlot() def reset(self): self.setStyleSheet('''padding-left: 1px; padding-right: 1px; color: %s; background-color: %s;''' % (conf.__dict__['color_%s_text' % (self.state.lower())], conf.__dict__['color_%s_background' % (self.state.lower())])) class TopArea(QWidget): """ Top area of status window """ mouse_entered = pyqtSignal() def __init__(self, parent=None): QWidget.__init__(self) self.hbox = HBoxLayout(spacing=SPACE, parent=self) # top HBox containing buttons self.icons = dict() self.create_icons() # top button box self.logo = NagstamonLogo(self.icons['nagstamon_logo_toparea'], width=150, height=42, parent=self) self.label_version = DraggableLabel(text=AppInfo.VERSION, parent=self) self.label_empty_space = DraggableLabel(text='', parent=self) self.label_empty_space.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored) self.combobox_servers = ComboBox_Servers(parent=self) self.button_filters = Button("Filters", parent=self) self.button_recheck_all = Button("Recheck all", parent=self) self.button_refresh = Button("Refresh", parent=self) self.button_settings = Button("Settings", parent=self) # fill default order fields combobox with server names self.combobox_servers.fill() # hambuger menu self.button_hamburger_menu = PushButton_Hamburger() self.button_hamburger_menu.setIcon(self.icons['menu']) self.hamburger_menu = MenuAtCursor() action_exit = QAction("Exit", self) action_exit.triggered.connect(exit) self.hamburger_menu.addAction(action_exit) self.button_hamburger_menu.setMenu(self.hamburger_menu) # X self.button_close = Button() self.button_close.setIcon(self.icons['close']) self.button_close.setStyleSheet(CSS_CLOSE_BUTTON) self.hbox.addWidget(self.logo) self.hbox.addWidget(self.label_version) self.hbox.addWidget(self.label_empty_space) self.hbox.addWidget(self.combobox_servers) self.hbox.addWidget(self.button_filters) self.hbox.addWidget(self.button_recheck_all) self.hbox.addWidget(self.button_refresh) self.hbox.addWidget(self.button_settings) self.hbox.addWidget(self.button_hamburger_menu) self.hbox.addWidget(self.button_close) self.setLayout(self.hbox) def enterEvent(self, event): # unlock statuswindow if pointer touches statusbar self.mouse_entered.emit() @pyqtSlot() def create_icons(self): """ create icons from template, applying colors """ # get rgb values of current foreground color to be used for SVG icons (menu) r, g, b, a = APP.palette().color(QPalette.Foreground).getRgb() for icon in 'nagstamon_logo_toparea', 'close', 'menu': # get template from file svg_template_file = open('{0}{1}{2}_template.svg'.format(RESOURCES, os.sep, icon)) svg_template_xml = svg_template_file.readlines() # current SVG XML for state icon, derived from svg_template_cml svg_icon_xml = list() # replace dummy text and background colors with configured ones for line in svg_template_xml: line = line.replace('fill:#ff00ff', 'fill:#{0:x}{1:x}{2:x}'.format(r, g, b)) svg_icon_xml.append(line) # create XML stream of SVG svg_xml_stream = QXmlStreamReader(''.join(svg_icon_xml)) # create renderer for SVG and put SVG XML into renderer svg_renderer = QSvgRenderer(svg_xml_stream) # pixmap to be painted on - arbitrarily choosen 128x128 px svg_pixmap = QPixmap(128, 128) # fill transparent backgound svg_pixmap.fill(Qt.transparent) # initiate painter which paints onto paintdevice pixmap svg_painter = QPainter(svg_pixmap) # render svg to pixmap svg_renderer.render(svg_painter) # close painting svg_painter.end() # two ways... if icon == 'nagstamon_logo_toparea': # first get a base64 version of the SVG svg_base64 = base64.b64encode(bytes(''.join(svg_icon_xml), 'utf8')) # create a QByteArray for NagstamonLogo aka QSvgWidget svg_bytes = QByteArray.fromBase64(svg_base64) self.icons[icon] = svg_bytes else: # put pixmap into icon self.icons[icon] = QIcon(svg_pixmap) class ServerStatusLabel(ClosingLabel): """ label for ServerVBox to show server connection state extra class to apply simple slots for changing text or color """ # storage for label text if it needs to be restored text_old = '' def __init__(self, parent=None): QLabel.__init__(self, parent=parent) @pyqtSlot(str, str) def change(self, text, style=''): # store old text and stylesheet in case it needs to be reused self.text_old = self.text() self.stylesheet_old = self.styleSheet() # set stylesheet depending on submitted style if style in COLOR_STATUS_LABEL: if OS == 'Darwin': self.setStyleSheet('''background: {0}; border-radius: 3px; '''.format(COLOR_STATUS_LABEL[style])) else: self.setStyleSheet('''background: {0}; margin-top: 8px; margin-bottom: 8px; border-radius: 6px; '''.format(COLOR_STATUS_LABEL[style])) elif style == '': self.setStyleSheet('') # in case of unknown errors try to avoid freaking out status window with too # big status label if style != 'unknown': # set new text with some space self.setText(' {0} '.format(text)) self.setToolTip('') else: # set new text to first word of text, delegate full text to tooltip self.setText(text.split(' ')[0]) self.setToolTip(text) @pyqtSlot() def reset(self): self.setStyleSheet(self.stylesheet_old) self.setText('') @pyqtSlot() def restore(self): # restore text, used by recheck_all of tablewidget worker self.setStyleSheet(self.stylesheet_old) self.setText(self.text_old) class ServerVBox(QVBoxLayout): """ one VBox per server containing buttons and hosts/services listview """ # used to update status label text like 'Connected-' change_label_status = pyqtSignal(str, str) # signal to submit server to authentication dialog authenticate = pyqtSignal(str) button_fix_tls_error_show = pyqtSignal() button_fix_tls_error_hide = pyqtSignal() def __init__(self, server, parent=None): QVBoxLayout.__init__(self, parent) # no space around self.setSpacing(0) self.setContentsMargins(0, 0, 0, 0) # server the vbox belongs to self.server = server # header containing monitor name, buttons and status self.header = HBoxLayout(spacing=SPACE, parent=parent) self.addLayout(self.header) # top and bottom should be kept by padding self.header.setContentsMargins(0, 0, SPACE, 0) #self.label = QLabel(parent=parent) self.label = ClosingLabel(parent=parent) self.update_label() self.button_monitor = PushButton_BrowserURL(text='Monitor', parent=parent, server=self.server, url_type='monitor') self.button_hosts = PushButton_BrowserURL(text='Hosts', parent=parent, server=self.server, url_type='hosts') self.button_services = PushButton_BrowserURL(text='Services', parent=parent, server=self.server, url_type='services') self.button_history = PushButton_BrowserURL(text='History', parent=parent, server=self.server, url_type='history') self.button_edit = Button('Edit', parent=parent) # use label instead of spacer to be clickable self.label_stretcher = ClosingLabel('', parent=parent) self.label_stretcher.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Expanding) self.label_status = ServerStatusLabel(parent=parent) self.label_status.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) self.button_authenticate = QPushButton('Authenticate', parent=parent) self.button_fix_tls_error = QPushButton('Fix error', parent=parent) self.button_monitor.clicked.connect(self.button_monitor.open_url) self.button_hosts.clicked.connect(self.button_hosts.open_url) self.button_services.clicked.connect(self.button_services.open_url) self.button_history.clicked.connect(self.button_history.open_url) self.button_edit.clicked.connect(self.edit_server) self.header.addWidget(self.label) self.header.addWidget(self.button_monitor) self.header.addWidget(self.button_hosts) self.header.addWidget(self.button_services) self.header.addWidget(self.button_history) self.header.addWidget(self.button_edit) self.header.addWidget(self.label_stretcher) self.header.addWidget(self.label_status) self.header.addWidget(self.button_authenticate) self.header.addWidget(self.button_fix_tls_error) # attempt to get header strings try: # when stored as simple lowercase keys sort_column = HEADERS_KEYS_COLUMNS[conf.default_sort_field] except Exception: # when as legacy stored as presetation string sort_column = HEADERS_HEADERS_COLUMNS[conf.default_sort_field] # convert sort order to number as used in Qt.SortOrder sort_order = SORT_ORDER[conf.default_sort_order.lower()] self.table = TreeView(len(HEADERS) + 1, 0, sort_column, sort_order, self.server, parent=parent) # delete vbox if thread quits self.table.worker_thread.finished.connect(self.delete) # connect worker to status label to reflect connectivity self.table.worker.change_label_status.connect(self.label_status.change) self.table.worker.restore_label_status.connect(self.label_status.restore) # connect reauthentication button to authentication state ###self.table.worker.button_authenticate_show.connect(self.button_authenticate.show) ###self.table.worker.button_authenticate_hide.connect(self.button_authenticate.hide) # connect ignore invalid certificate button to TLS state ###self.table.worker.button_fix_tls_error_show.connect(self.button_fix_tls_error.show) ###self.table.worker.button_fix_tls_error_hide.connect(self.button_fix_tls_error.hide) # care about authentications self.button_authenticate.clicked.connect(self.authenticate_server) self.authenticate.connect(dialogs.authentication.show_auth_dialog) dialogs.authentication.update.connect(self.update_label) # start ignoring TLS trouble when button clicked self.button_fix_tls_error.clicked.connect(self.fix_tls_error) self.addWidget(self.table, 1) # as default do not show anything self.show_only_header() def get_real_height(self): """ return summarized real height of hbox items and table """ height = self.table.get_real_height() if self.label.isVisible() and self.button_monitor.isVisible(): # compare item heights, decide to take the largest and add 2 time the MARGIN (top and bottom) if self.label.sizeHint().height() > self.button_monitor.sizeHint().height(): height += self.label.sizeHint().height() + 2 else: height += self.button_monitor.sizeHint().height() + 2 return height @pyqtSlot() def show_all(self): """ show all items in server vbox except the table - not needed if empty """ self.label.show() self.button_monitor.show() self.button_hosts.show() self.button_services.show() self.button_history.show() self.button_edit.show() self.label_status.show() self.label_stretcher.show() self.button_authenticate.hide() self.button_fix_tls_error.hide() # special table treatment self.table.show() self.table.is_shown = True @pyqtSlot() def show_only_header(self): """ show all items in server vbox except the table - not needed if empty or major connection problem """ self.label.show() self.button_monitor.show() self.button_hosts.show() self.button_services.show() self.button_history.show() self.button_edit.show() self.label_status.show() self.label_stretcher.show() self.button_authenticate.hide() self.button_fix_tls_error.hide() # special table treatment self.table.hide() self.table.is_shown = False @pyqtSlot() def hide_all(self): """ hide all items in server vbox """ self.label.hide() self.button_monitor.hide() self.button_hosts.hide() self.button_services.hide() self.button_history.hide() self.button_edit.hide() self.label_status.hide() self.label_stretcher.hide() self.button_authenticate.hide() self.button_fix_tls_error.hide() # special table treatment self.table.hide() self.table.is_shown = False @pyqtSlot() def delete(self): """ delete VBox and its children """ for widget in (self.label, self.button_monitor, self.button_hosts, self.button_services, self.button_history, self.button_edit, self.label_status, self.label_stretcher, self.button_authenticate, self.button_fix_tls_error): widget.hide() widget.deleteLater() self.removeItem(self.header) self.header.deleteLater() self.table.hide() self.table.deleteLater() self.deleteLater() def edit_server(self): """ call dialogs.server.edit() with server name """ if not conf.fullscreen and not conf.windowed: statuswindow.hide_window() dialogs.server.edit(server_name=self.server.name) def authenticate_server(self): """ send signal to open authentication dialog with self.server.name """ self.authenticate.emit(self.server.name) @pyqtSlot() def update_label(self): self.label.setText(' {0}@{1}'.format(self.server.username, self.server.name)) # let label padding keep top and bottom space - apparently not necessary on OSX if OS != 'Darwin': self.label.setStyleSheet('''padding-top: {0}px; padding-bottom: {0}px;'''.format(SPACE)) @pyqtSlot() def fix_tls_error(self): """ call dialogs.server.edit() with server name and showing extra options """ if not conf.fullscreen and not conf.windowed: statuswindow.hide_window() dialogs.server.edit(server_name=self.server.name, show_options=True) class Model(QAbstractTableModel): """ Model for storing status data to be presented in Treeview-table """ data_array_filled = pyqtSignal() # list of lists for storage of status data data_array = list() # cache row and column count row_count = 0 column_count = len(HEADERS_HEADERS) # do not need to create everytime a new QVariant() object dummy_return_qvariant = QVariant() # dummy QModelIndex for dataChanged signal dummy_qmodelindex = QModelIndex() # tell treeview if flags columns should be hidden or not hosts_flags_column_needed = pyqtSignal(bool) services_flags_column_needed = pyqtSignal(bool) def __init__(self, server, parent=None): QAbstractTableModel.__init__(self, parent=parent) self.server = server def rowCount(self, parent): """ overridden method to get number of rows """ # return(len(self.data_array)) return(self.row_count) def columnCount(self, parent): """ overridden method to get number of columns """ return(self.column_count) def headerData(self, column, orientation, role): """ overridden method to get headers of columns """ if role == Qt.DisplayRole: return(HEADERS_HEADERS[column]) @pyqtSlot(list, dict) # @pyqtSlot(list) def fill_data_array(self, data_array, info): """ fill data_array for model """ # tell treeview that model is about to change - necessary because # otherwise new number of rows would not be applied self.beginResetModel() # first empty the data storage del(self.data_array[:]) # use delivered data array self.data_array = data_array # cache row_count self.row_count = len(self.data_array) # tell treeview if flags columns are needed self.hosts_flags_column_needed.emit(info['hosts_flags_column_needed']) self.services_flags_column_needed.emit(info['services_flags_column_needed']) self.data_array_filled.emit() # new model applied self.endResetModel() def data(self, index, role): """ overridden method for data delivery for treeview """ if role == Qt.DisplayRole: return(self.data_array[index.row()][index.column()]) # return(self.server.data[index.row()][index.column()]) elif role == Qt.ForegroundRole: # return(self.data_array[index.row()][COLOR_INDEX['text'][index.column()]]) return(self.data_array[index.row()][10]) elif role == Qt.BackgroundRole: # return(self.data_array[index.row()][COLOR_INDEX['background'][index.column()]]) return(self.data_array[index.row()][11]) elif role == Qt.FontRole: if index.column() == 1: return(ICONS_FONT) elif index.column() == 3: return(ICONS_FONT) else: return(DUMMY_QVARIANT) # provide icons via Qt.UserRole elif role == Qt.UserRole: # depending on host or service column return host or service icon list return(self.data_array[index.row()][7 + index.column()]) elif role == Qt.ToolTipRole: # only if tooltips are wanted show them, combining host + service + status_info if conf.show_tooltips: return('''
{0}: {1}
{2}'''.format(self.data_array[index.row()][0], self.data_array[index.row()][2], self.data_array[index.row()][8])) else: return(DUMMY_QVARIANT) class TreeView(QTreeView): """ attempt to get a less resource-hungry table/tree """ # tell global window that it should be resized ready_to_resize = pyqtSignal() # sent by refresh() for statusbar refreshed = pyqtSignal() # tell worker to get status after a recheck has been solicited recheck = pyqtSignal(dict) # tell notification that status of server has changed status_changed = pyqtSignal(str, str) # action to be executed by worker # 2 values: action and host/service info request_action = pyqtSignal(dict, dict) # tell worker it should sort columns after someone pressed the column header sort_data_array_for_columns = pyqtSignal(int, int, bool) def __init__(self, columncount, rowcount, sort_column, sort_order, server, parent=None): QTreeView.__init__(self, parent=parent) self.sort_column = sort_column self.sort_order = sort_order self.server = server # no handling of selection by treeview self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSelectionMode(QAbstractItemView.NoSelection) # disable space at the left side self.setRootIsDecorated(False) self.setIndentation(0) self.setUniformRowHeights(True) # no scrollbars at tables because they will be scrollable by the global vertical scrollbar self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setAutoScroll(False) self.setSortingEnabled(True) self.sortByColumn(0, Qt.AscendingOrder) self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Expanding) self.header().setSectionResizeMode(QHeaderView.ResizeToContents) self.header().setDefaultAlignment(Qt.AlignLeft) self.header().setSortIndicatorShown(True) self.header().setStretchLastSection(True) try: self.header().setSortIndicator(sort_column, SORT_ORDER[self.sort_order]) except Exception: self.header().setSortIndicator(sort_column, SORT_ORDER[self.sort_order]) # small method needed to tell worker which column and sort order to use self.header().sortIndicatorChanged.connect(self.sort_columns) # set overall margin and hover colors - to be refined self.setStyleSheet('''QTreeView::item {margin: 5px;} QTreeView::item:hover {margin: 0px; color: white; background-color: dimgrey;} ''') # set application font self.set_font() # change font if it has been changed by settings dialogs.settings.changed.connect(self.set_font) # action context menu self.action_menu = MenuAtCursor(parent=self) # flag to avoid popping up menus when clicking somehwere self.action_menu.available = True # signalmapper for getting triggered actions self.signalmapper_action_menu = QSignalMapper() # connect menu to responder self.signalmapper_action_menu.mapped[str].connect(self.action_menu_custom_response) # clipboard actions self.clipboard_menu = QMenu('Copy to clipboard', self) self.clipboard_action_host = QAction('Host', self) self.clipboard_action_host.triggered.connect(self.action_clipboard_action_host) self.clipboard_menu.addAction(self.clipboard_action_host) self.clipboard_action_statusinformation = QAction('Status information', self) self.clipboard_action_statusinformation.triggered.connect(self.action_clipboard_action_statusinformation) self.clipboard_menu.addAction(self.clipboard_action_statusinformation) self.clipboard_action_all = QAction('All information', self) self.clipboard_action_all.triggered.connect(self.action_clipboard_action_all) self.clipboard_menu.addAction(self.clipboard_action_all) self.treeview_model = Model(server=self.server, parent=self) self.setModel(self.treeview_model) self.model().data_array_filled.connect(self.adjust_table) self.model().hosts_flags_column_needed.connect(self.show_hosts_flags_column) self.model().services_flags_column_needed.connect(self.show_services_flags_column) # a thread + worker is necessary to get new monitor server data in the background and # to refresh the table cell by cell after new data is available self.worker_thread = QThread(self) self.worker = self.Worker(server=server, sort_column=self.sort_column, sort_order=self.sort_order) self.worker.moveToThread(self.worker_thread) # if worker got new status data from monitor server get_status # the treeview model has to be updated self.worker.data_array_filled.connect(self.model().fill_data_array) # fill array again if data has been sorted after a header column click self.worker.data_array_sorted.connect(self.model().fill_data_array) # tell worker to sort data_array depending on sort_column and sort_order self.sort_data_array_for_columns.connect(self.worker.sort_data_array) # if worker got new status data from monitor server get_status the table should be refreshed self.worker.new_status.connect(self.refresh) # quit thread if worker has finished self.worker.finish.connect(self.finish_worker_thread) # get status if started self.worker_thread.started.connect(self.worker.get_status) # start with priority 0 = lowest self.worker_thread.start() # connect signal for acknowledge dialogs.acknowledge.acknowledge.connect(self.worker.acknowledge) # connect signal to get start end time for downtime from worker dialogs.downtime.get_start_end.connect(self.worker.get_start_end) self.worker.set_start_end.connect(dialogs.downtime.set_start_end) # connect signal for downtime dialogs.downtime.downtime.connect(self.worker.downtime) # connect signal for submit check result dialogs.submit.submit.connect(self.worker.submit) # connect signal for recheck action self.recheck.connect(self.worker.recheck) # execute action by worker self.request_action.connect(self.worker.execute_action) # display mode - all or only header to display error self.is_shown = False @pyqtSlot() def set_font(self): """ change font if it has been changed by settings """ self.setFont(FONT) @pyqtSlot(bool) def show_hosts_flags_column(self, value): """ show hosts flags column if needed 'value' is True if there is a need so it has to be converted """ self.setColumnHidden(1, not value) @pyqtSlot(bool) def show_services_flags_column(self, value): """ show service flags column if needed 'value' is True if there is a need so it has to be converted """ self.setColumnHidden(3, not value) def get_real_height(self): """ calculate real table height as there is no method included """ height = 0 # only count if there is anything to display - there is no use of the headers only if self.model().rowCount(self) > 0: # height summary starts with headers' height # apparently height works better/without scrollbar if some pixels are added height = self.header().sizeHint().height() + 2 # maybe simply take nagitems_filtered_count? height += self.indexRowSizeHint(self.model().index(0, 0)) * self.model().rowCount(self) return(height) def get_real_width(self): width = 0 # avoid the last dummy column to be counted for column in range(len(HEADERS) - 1): width += self.columnWidth(column) return(width) @pyqtSlot() def adjust_table(self): """ adjust table dimensions after filling it """ # force table to its maximal height, calculated by .get_real_height() self.setMinimumHeight(self.get_real_height()) self.setMaximumHeight(self.get_real_height()) self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Maximum) # after setting table whole window can be repainted self.ready_to_resize.emit() def mouseReleaseEvent(self, event): """ forward clicked cell info from event """ if conf.close_details_clicking_somewhere and event.button() == Qt.LeftButton: statuswindow.hide_window() else: index = self.indexAt(QPoint(event.x(), event.y())) self.cell_clicked(index) def wheelEvent(self, event): """ avoid scrollable single treeview in Linux and GNOME3 by simply do nothing when getting a wheel event """ event.ignore() @pyqtSlot() def cell_clicked(self, index): """ Windows reacts differently to clicks into table cells than Linux and MacOSX Therefore the .available flag is necessary """ if self.action_menu.available or OS != 'Windows': # set flag for Windows self.action_menu.available = False # take data from model data_array self.miserable_host = self.model().data_array[index.row()][0] self.miserable_service = self.model().data_array[index.row()][2] self.miserable_status_info = self.model().data_array[index.row()][8] # empty the menu self.action_menu.clear() # clear signal mappings self.signalmapper_action_menu.removeMappings(self.signalmapper_action_menu) # add custom actions actions_list = list(conf.actions) actions_list.sort(key=str.lower) for a in actions_list: # shortcut for next lines action = conf.actions[a] # check if current monitor server type is in action # second scheck for server type is legacy-compatible with older settions if action.enabled is True and (action.monitor_type in ['', self.server.TYPE] or action.monitor_type not in SERVER_TYPES): # menu item visibility flag item_visible = False # check if clicked line is a service or host # if it is check if the action is targeted on hosts or services if self.miserable_service: if action.filter_target_service is True: # only check if there is some to check if action.re_host_enabled is True: if is_found_by_re(self.miserable_host, action.re_host_pattern, action.re_host_reverse): item_visible = True # dito if action.re_service_enabled is True: if is_found_by_re(self.miserable_service, action.re_service_pattern, action.re_service_reverse): item_visible = True # dito if action.re_status_information_enabled is True: if is_found_by_re(self.miserable_service, action.re_status_information_pattern, action.re_status_information_reverse): item_visible = True # fallback if no regexp is selected if action.re_host_enabled == action.re_service_enabled == \ action.re_status_information_enabled is False: item_visible = True else: # hosts should only care about host specific actions, no services if action.filter_target_host is True: if action.re_host_enabled is True: if is_found_by_re(self.miserable_host, action.re_host_pattern, action.re_host_reverse): item_visible = True else: # a non specific action will be displayed per default item_visible = True else: item_visible = False # populate context menu with service actions if item_visible is True: # create action action_menuentry = QAction(a, self) # add action self.action_menu.addAction(action_menuentry) # action to signalmapper self.signalmapper_action_menu.setMapping(action_menuentry, a) action_menuentry.triggered.connect(self.signalmapper_action_menu.map) del action, item_visible # create adn add default actions action_edit_actions = QAction('Edit actions...', self) action_edit_actions.triggered.connect(self.action_edit_actions) if 'Monitor' in self.server.MENU_ACTIONS: action_monitor = QAction('Monitor', self) action_monitor.triggered.connect(self.action_monitor) if 'Recheck' in self.server.MENU_ACTIONS: action_recheck = QAction('Recheck', self) action_recheck.triggered.connect(self.action_recheck) if 'Acknowledge' in self.server.MENU_ACTIONS: action_acknowledge = QAction('Acknowledge', self) action_acknowledge.triggered.connect(self.action_acknowledge) if 'Downtime' in self.server.MENU_ACTIONS: action_downtime = QAction('Downtime', self) action_downtime.triggered.connect(self.action_downtime) # put actions into menu after separator self.action_menu.addAction(action_edit_actions) self.action_menu.addSeparator() if 'Monitor' in self.server.MENU_ACTIONS: self.action_menu.addAction(action_monitor) if 'Recheck' in self.server.MENU_ACTIONS: self.action_menu.addAction(action_recheck) if 'Acknowledge' in self.server.MENU_ACTIONS: self.action_menu.addAction(action_acknowledge) if 'Downtime' in self.server.MENU_ACTIONS: self.action_menu.addAction(action_downtime) # special menu entry for Check_MK for archiving events if self.server.type == 'Check_MK Multisite': if self.miserable_service == 'Events': action_archive_event = QAction('Archive event', self) action_archive_event.triggered.connect(self.action_archive_event) self.action_menu.addAction(action_archive_event) # not all servers allow to submit fake check results if 'Submit check result' in self.server.MENU_ACTIONS: action_submit = QAction('Submit check result', self) action_submit.triggered.connect(self.action_submit) self.action_menu.addAction(action_submit) # experimental clipboard submenu self.action_menu.addMenu(self.clipboard_menu) # show menu self.action_menu.show_at_cursor() else: self.action_menu.available = True @pyqtSlot(str) def action_menu_custom_response(self, action): # avoid blocked context menu self.action_menu.available = True # send dict with action info and dict with host/service info self.request_action.emit(conf.actions[action].__dict__, {'server': self.server.get_name(), 'host': self.miserable_host, 'service': self.miserable_service, 'status-info': self.miserable_status_info, 'address': self.server.GetHost(self.miserable_host).result, 'monitor': self.server.monitor_url, 'monitor-cgi': self.server.monitor_cgi_url, 'username': self.server.username, 'password': self.server.password, 'comment-ack': conf.defaults_acknowledge_comment, 'comment-down': conf.defaults_downtime_comment, 'comment-submit': conf.defaults_submit_check_result_comment }) # if action wants a closed status window it should be closed now if conf.actions[action].close_popwin and not conf.fullscreen and not conf.windowed: statuswindow.hide_window() @pyqtSlot() def action_response_decorator(method): """ decorate repeatedly called stuff """ def decoration_function(self): # avoid blocked context menu self.action_menu.available = True # run decorated method method(self) # default actions need closed statuswindow to display own dialogs if not conf.fullscreen and not conf.windowed and\ not method.__name__ == 'action_recheck' and\ not method.__name__ == 'action_archive_event': statuswindow.hide_window() return(decoration_function) @action_response_decorator def action_edit_actions(self): # buttons in toparee if not conf.fullscreen and not conf.windowed: statuswindow.hide_window() # open actions tab (#3) of settings dialog dialogs.settings.show(tab=3) @action_response_decorator def action_monitor(self): # open host/service monitor in browser self.server.open_monitor(self.miserable_host, self.miserable_service) @action_response_decorator def action_recheck(self): # send signal to worker recheck slot self.recheck.emit({'host': self.miserable_host, 'service': self.miserable_service}) @action_response_decorator def action_acknowledge(self): # running worker method is left to OK button of dialog dialogs.acknowledge.show() dialogs.acknowledge.initialize(server=self.server, host=self.miserable_host, service=self.miserable_service) @action_response_decorator def action_downtime(self): # running worker method is left to OK button of dialog dialogs.downtime.show() dialogs.downtime.initialize(server=self.server, host=self.miserable_host, service=self.miserable_service) @action_response_decorator def action_archive_event(self): """ archive events in Check_MK Event Console """ # fill action and info dict for thread-safe action request action = {'string': '$MONITOR$/view.py?_transid=$TRANSID$&_do_actions=yes&_do_confirm=Yes!&output_format=python&view_name=ec_events_of_monhost&host=$HOST$&_mkeventd_comment=archived&_mkeventd_acknowledge=on&_mkeventd_state=2&_delete_event=Archive Event&event_first_from=&event_first_until=&event_last_from=&event_last_until=', 'type': 'url', 'recheck': True} info = {'server': self.server.get_name(), 'host': self.miserable_host, 'service': self.miserable_service, 'status-info': self.miserable_status_info, 'address': self.server.GetHost(self.miserable_host).result, 'monitor': self.server.monitor_url, 'monitor-cgi': self.server.monitor_cgi_url, 'username': self.server.username, 'password': self.server.password, 'comment-ack': conf.defaults_acknowledge_comment, 'comment-down': conf.defaults_downtime_comment, 'comment-submit': conf.defaults_submit_check_result_comment} # tell worker to do the action self.request_action.emit(action, info) @action_response_decorator def action_submit(self): # running worker method is left to OK button of dialog dialogs.submit.show() dialogs.submit.initialize(server=self.server, host=self.miserable_host, service=self.miserable_service) @pyqtSlot() def action_clipboard_action_host(self): """ copy host name to clipboard """ clipboard.setText(self.miserable_host) @pyqtSlot() def action_clipboard_action_statusinformation(self): """ copy status information to clipboard """ clipboard.setText(self.miserable_status_info) @pyqtSlot() def action_clipboard_action_all(self): """ """ # item to access all properties of host/service object # defaults to host item = self.server.hosts[self.miserable_host] text = 'Host: {0}\n'.format(self.miserable_host) # if it is a service switch to service object if self.miserable_service != '': item = item.services[self.miserable_service] text += 'Service: {0}\n'.format(self.miserable_service) # the other properties belong to both hosts and services text += 'Status: {0}\n'.format(item.status) text += 'Last check: {0}\n'.format(item.last_check) text += 'Duration: {0}\n'.format(item.duration) text += 'Attempt: {0}\n'.format(item.attempt) text += 'Status information: {0}\n'.format(item.status_information) # copy text to clipboard clipboard.setText(text) @pyqtSlot() def refresh(self): """ refresh status display """ # avoid race condition when waiting for password dialog if statuswindow is not None: # do nothing if window is moving to avoid lagging movement if not statuswindow.moving: # get_status table cells with new data by thread if len(self.model().data_array) > 0: self.is_shown = True else: self.is_shown = False # pre-calculate dimensions # height = self.get_real_height() # never been used # tell statusbar it should update self.refreshed.emit() # check if status changed and notification is necessary # send signal because there are unseen events if self.server.get_events_history_count() > 0: self.status_changed.emit(self.server.name, self.server.worst_status_diff) @pyqtSlot(int, Qt.SortOrder) def sort_columns(self, sort_column, sort_order): """ forward sorting task to worker """ # better int() the Qt.* values because they partly seem to be # intransmissible self.sort_data_array_for_columns.emit(int(sort_column), int(sort_order), True) @pyqtSlot() def finish_worker_thread(self): """ attempt to shutdown thread cleanly """ # tell thread to quit self.worker_thread.quit() # wait until thread is really stopped self.worker_thread.wait(2000) class Worker(QObject): """ attempt to run a server status update thread - only needed by table so it is defined here inside table """ # send signal if monitor server has new status data new_status = pyqtSignal() # send signal if next cell can be filled next_cell = pyqtSignal(int, int, str, str, str, list, str) # send signal if all cells are filled and table can be adjusted table_ready = pyqtSignal() # send signal if ready to stop finish = pyqtSignal() # send start and end of downtime set_start_end = pyqtSignal(str, str) # try to stop thread by evaluating this flag running = True # signal to be sent to slot "change" of ServerStatusLabel change_label_status = pyqtSignal(str, str) # signal to be sent to slot "restore" of ServerStatusLabel restore_label_status = pyqtSignal() # send notification a stop message if problems vanished without being noticed problems_vanished = pyqtSignal() # flag to keep recheck_all from being started more than once rechecking_all = False # signals to show/hide reauthentication ###button_authenticate_show = pyqtSignal() ###button_authenticate_hide = pyqtSignal() # show button to ignore invalid certificate in server vbox ###button_fix_tls_error_show = pyqtSignal() ###button_fix_tls_error_hide = pyqtSignal() # signals to control error message in statusbar show_error = pyqtSignal(str) hide_error = pyqtSignal() # sent to treeview with new data_array data_array_filled = pyqtSignal(list, dict) # sendt to treeview if data has been sorted by click on column header data_array_sorted = pyqtSignal(list, dict) # keep track of last sorting column and order to pre-sort by it # start with sorting by host last_sort_column_cached = 0 last_sort_column_real = 0 last_sort_order = 0 def __init__(self, parent=None, server=None, sort_column=0, sort_order=0): QObject.__init__(self) self.server = server # needed for update interval self.timer = QTimer(self) self.server.init_config() self.sort_column = sort_column self.sort_order = sort_order @pyqtSlot() def get_status(self): """ check every second if thread still has to run if interval time is reached get status """ # if counter is at least update interval get status if self.server.thread_counter >= conf.update_interval_seconds: # reflect status retrieval attempt on server vbox label self.change_label_status.emit('Refreshing...', '') # get status from server instance if connection was already possible and no TLS error if not self.server.tls_error: status = self.server.GetStatus() else: # dummy status result status = Result() # all is OK if no error info came back if self.server.status_description == '' and\ self.server.status_code < 400 and\ not self.server.refresh_authentication and\ not self.server.tls_error: # show last update time self.change_label_status.emit('Last updated at {0}'.format(datetime.datetime.now().strftime('%X')), '') # reset server error flag, needed for error label in statusbar self.server.has_error = False # tell statusbar there is no error self.hide_error.emit() else: # try to display some more user friendly error description if self.server.status_code == 404: self.change_label_status.emit('Monitor URL not valid', 'critical') elif status.error.startswith('requests.exceptions.ConnectTimeout'): self.change_label_status.emit('Connection timeout', 'error') elif status.error.startswith('requests.exceptions.ConnectionError'): self.change_label_status.emit('Connection error', 'error') elif status.error.startswith('requests.exceptions.ReadTimeout'): self.change_label_status.emit('Connection timeout', 'error') elif self.server.tls_error: self.change_label_status.emit('SSL/TLS problem', 'critical') elif self.server.status_code in self.server.STATUS_CODES_NO_AUTH or\ self.server.refresh_authentication: self.change_label_status.emit('Authentication problem', 'critical') elif self.server.status_code == 503: self.change_label_status.emit('Service unavailable', 'error') else: # kick out line breaks to avoid broken status window if self.server.status_description == '': self.server.status_description = 'Unknown error' self.change_label_status.emit(self.server.status_description.replace('\n', ''), 'error') # set server error flag, needed for error label in statusbar self.server.has_error = True # tell statusbar there is some error to display self.show_error.emit('ERROR') # reset counter for this thread self.server.thread_counter = 0 # if failures have gone and nobody took notice switch notification off again if len([k for k, v in self.server.events_history.items() if v is True]) == 0 and\ statuswindow and \ statuswindow.worker_notification.is_notifying is True and\ statuswindow.worker_notification.notifying_server == self.server.name: # tell notification that unnoticed problems are gone self.problems_vanished.emit() # stuff data into array and sort it self.fill_data_array(self.sort_column, self.sort_order) # depending on authentication state show reauthentication button #if self.server.refresh_authentication: # self.button_authenticate_show.emit() #else: # self.button_authenticate_hide.emit() # special treatment for ignore-invalid-certificate button #if self.server.tls_error: # self.button_fix_tls_error_show.emit() #else: # self.button_fix_tls_error_hide.emit() # tell news about new status available self.new_status.emit() # increase thread counter self.server.thread_counter += 1 # if running flag is still set call myself after 1 second if self.running is True: self.timer.singleShot(1000, self.get_status) pass else: # tell treeview to finish worker_thread self.finish.emit() @pyqtSlot(int, int) def fill_data_array(self, sort_column, sort_order): """ let worker do the dirty job of filling the array """ # data_array to be evaluated in data() of model # first 9 items per row come from current status information self.data_array = list() # dictionary containing extra info about data_array self.info = {'hosts_flags_column_needed': False, 'services_flags_column_needed': False, } # avoid race condition when waiting for password dialog if len(QBRUSHES[0]) > 0: # cruising the whole nagitems structure for category in ('hosts', 'services'): for state in self.server.nagitems_filtered[category].values(): for item in state: self.data_array.append(list(item.get_columns(HEADERS))) # hash for freshness comparison hash = item.get_hash() if item.is_host(): if hash in self.server.events_history and\ self.server.events_history[hash] is True: # second item in las data_array line is host flags self.data_array[-1][1] += 'N' else: if hash in self.server.events_history and\ self.server.events_history[hash] is True: # fourth item in las data_array line is service flags self.data_array[-1][3] += 'N' # add text color as QBrush from status self.data_array[-1].append(QBRUSHES[len(self.data_array) % 2][COLORS[item.status] + 'text']) # add background color as QBrush from status self.data_array[-1].append(QBRUSHES[len(self.data_array) % 2][COLORS[item.status] + 'background']) # add text color name for sorting data self.data_array[-1].append(COLORS[item.status] + 'text') # add background color name for sorting data self.data_array[-1].append(COLORS[item.status] + 'background') # check if hosts and services flags should be shown if self.data_array[-1][1] != '': self.info['hosts_flags_column_needed'] = True if self.data_array[-1][3] != '': self.info['services_flags_column_needed'] = True self.data_array[-1].append('X') # sort data before it gets transmitted to treeview model self.sort_data_array(self.sort_column, self.sort_order, False) # give sorted data to model self.data_array_filled.emit(self.data_array, self.info) @pyqtSlot(int, int, bool) def sort_data_array(self, sort_column, sort_order, header_clicked=False): """ sort list of lists in data_array depending on sort criteria used from fill_data_array() and when clicked on table headers """ # store current sort_column and sort_data for next sort actions self.sort_column = sort_column self.sort_order = sort_order # to keep GTK Treeview sort behaviour first by hosts first_sort = sorted(self.data_array, key=lambda row: SORT_COLUMNS_FUNCTIONS[self.last_sort_column_real](row[SORT_COLUMNS_INDEX[self.last_sort_column_real]]), reverse=self.last_sort_order) # use SORT_COLUMNS from Helpers to sort column accordingly self.data_array = sorted(first_sort, key=lambda row: SORT_COLUMNS_FUNCTIONS[self.sort_column](row[SORT_COLUMNS_INDEX[self.sort_column]]), reverse=self.sort_order) # fix alternating colors for count, row in enumerate(self.data_array): # change text color of sorted rows row[10] = QBRUSHES[count % 2][row[12]] # change background color of sorted rows row[11] = QBRUSHES[count % 2][row[13]] # if header was clicked tell model to use new data_array if header_clicked: self.data_array_sorted.emit(self.data_array, self.info) del(first_sort) # store last sorting column for next sorting only if header was clicked if header_clicked: # last sorting column needs to be cached to avoid losing it # effective last column is self.last_sort_column_real if self.last_sort_column_cached != self.sort_column: self.last_sort_column_real = self.last_sort_column_cached self.last_sort_order = self.sort_order self.last_sort_column_cached = self.sort_column @pyqtSlot(dict) def acknowledge(self, info_dict): """ slot waiting for 'acknowledge' signal from ok button from acknowledge dialog all information about target server, host, service and flags is contained in dictionary 'info_dict' """ # because all monitors are connected to this slot we must check which one sent the signal, # otherwise there are several calls and not only one as wanted if self.server == info_dict['server']: # pass dictionary to server's acknowledge machinery self.server.set_acknowledge(info_dict) @pyqtSlot(dict) def downtime(self, info_dict): """ slot waiting for 'downtime' signal from ok button from downtime dialog all information about target server, host, service and flags is contained in dictionary 'info_dict' """ # because all monitors are connected to this slot we must check which one sent the signal, # otherwise there are several calls and not only one as wanted if self.server == info_dict['server']: # pass dictionary to server's downtime machinery self.server.set_downtime(info_dict) @pyqtSlot(dict) def submit(self, info_dict): """ slot waiting for 'submit' signal from ok button from submit dialog all information about target server, host, service and flags is contained in dictionary 'info_dict' """ # because all monitors are connected to this slot we must check which one sent the signal, # otherwise there are several calls and not only one as wanted if self.server == info_dict['server']: # pass dictionary to server's downtime machinery self.server.set_submit_check_result(info_dict) @pyqtSlot(dict) def recheck(self, info_dict): """ Slot to start server recheck method, getting signal from TableWidget context menu """ if conf.debug_mode: # host if info_dict['service'] == '': self.server.Debug(server=self.server.name, debug='Rechecking host {0}'.format(info_dict['host'])) else: self.server.Debug(server=self.server.name, debug='Rechecking service {0} on host {1}'.format(info_dict['service'], info_dict['host'])) # call server recheck method self.server.set_recheck(info_dict) @pyqtSlot() def recheck_all(self): """ call server.set_recheck for every single host/service """ # only if no already rechecking if self.rechecking_all is False: # block rechecking self.rechecking_all = True # change label of server vbox self.change_label_status.emit('Rechecking all...', '') if conf.debug_mode: self.server.Debug(server=self.server.name, debug='Start rechecking all') # special treatment for Check_MK Multisite because there is only one URL call necessary if self.server.type != 'Check_MK Multisite': # make a copy to preserve hosts/service to recheck - just in case something changes meanwhile nagitems_filtered = deepcopy(self.server.nagitems_filtered) for status in nagitems_filtered['hosts'].items(): for host in status[1]: if conf.debug_mode: self.server.Debug(server=self.server.name, debug='Rechecking host {0}'.format(host.name)) # call server recheck method self.server.set_recheck({'host': host.name, 'service': ''}) for status in nagitems_filtered['services'].items(): for service in status[1]: if conf.debug_mode: self.server.Debug(server=self.server.name, debug='Rechecking service {0} on host {1}'.format(service.name, service.host)) # call server recheck method self.server.set_recheck({'host': service.host, 'service': service.name}) del(nagitems_filtered, status) else: # Check_MK Multisite does it its own way self.server.recheck_all() # release rechecking lock self.rechecking_all = False # restore server status label self.restore_label_status.emit() else: if conf.debug_mode: self.server.Debug(server=self.server.name, debug='Already rechecking all') @pyqtSlot(str, str) def get_start_end(self, server_name, host): """ Investigates start and end time of a downtime asynchronously """ # because every server listens to this signal the name has to be filtered if server_name == self.server.name: start, end = self.server.get_start_end(host) # send start/end time to slot self.set_start_end.emit(start, end) @pyqtSlot(dict, dict) def execute_action(self, action, info): """ runs action, may it be custom or included like the Check_MK actions """ # first replace placeholder variables in string with actual values # # Possible values for variables: # $HOST$ - host as in monitor # $SERVICE$ - service as in monitor # $MONITOR$ - monitor address - not yet clear what exactly for # $MONITOR-CGI$ - monitor CGI address - not yet clear what exactly for # $ADDRESS$ - address of host, investigated by Server.GetHost() # $STATUS-INFO$ - status information # $USERNAME$ - username on monitor # $PASSWORD$ - username's password on monitor - whatever for # $COMMENT-ACK$ - default acknowledge comment # $COMMENT-DOWN$ - default downtime comment # $COMMENT-SUBMIT$ - default submit check result comment try: # used for POST request if 'cgi_data' in action: cgi_data = action['cgi_data'] else: cgi_data = '' # mapping of variables and values mapping = {'$HOST$': info['host'], '$SERVICE$': info['service'], '$ADDRESS$': info['address'], '$MONITOR$': info['monitor'], '$MONITOR-CGI$': info['monitor-cgi'], '$STATUS-INFO$': info['status-info'], '$USERNAME$': info['username'], '$PASSWORD$': info['password'], '$COMMENT-ACK$': info['comment-ack'], '$COMMENT-DOWN$': info['comment-down'], '$COMMENT-SUBMIT$': info['comment-submit']} # take string form action string = action['string'] # mapping mapping for i in mapping: string = string.replace(i, mapping[i]) # see what action to take if action['type'] == 'browser': # debug if conf.debug_mode is True: self.server.Debug(server=self.server.name, host=info['host'], service=info['service'], debug='ACTION: BROWSER ' + string) webbrowser_open(string) elif action['type'] == 'command': # debug if conf.debug_mode is True: self.server.Debug(server=self.server.name, host=info['host'], service=info['service'], debug='ACTION: COMMAND ' + string) subprocess.Popen(string, shell=True) elif action['type'] == 'url': # Check_MK uses transids - if this occurs in URL its very likely that a Check_MK-URL is called if '$TRANSID$' in string: transid = servers[info['server']]._get_transid(info['host'], info['service']) string = string.replace('$TRANSID$', transid).replace(' ', '+') else: # make string ready for URL string = self._URLify(string) # debug if conf.debug_mode is True: self.server.Debug(server=self.server.name, host=info['host'], service=info['service'], debug='ACTION: URL in background ' + string) servers[info['server']].FetchURL(string) # used for example by Op5Monitor.py elif action['type'] == 'url-post': # make string ready for URL string = self._URLify(string) # debug if conf.debug_mode is True: self.server.Debug(server=self.server.name, host=info['host'], service=info['service'], debug='ACTION: URL-POST in background ' + string) servers[info['server']].FetchURL(string, cgi_data=cgi_data, multipart=True) if action['recheck']: self.recheck(info) except Exception: traceback.print_exc(file=sys.stdout) def _URLify(self, string): """ return a string that fulfills requirements for URLs exclude several chars """ return urllib.parse.quote(string, ":/=?&@+") @pyqtSlot() def unfresh_event_history(self): # set all flagged-as-fresh-events to un-fresh for event in self.server.events_history.keys(): self.server.events_history[event] = False class Dialogs(object): """ class for accessing all dialogs """ def __init__(self): # settings main dialog self.settings = Dialog_Settings(Ui_settings_main) self.settings.initialize() # server settings dialog self.server = Dialog_Server(Ui_settings_server) self.server.initialize() # action settings dialog self.action = Dialog_Action(Ui_settings_action) self.action.initialize() # acknowledge dialog for miserable item context menu self.acknowledge = Dialog_Acknowledge(Ui_dialog_acknowledge) self.acknowledge.initialize() # downtime dialog for miserable item context menu self.downtime = Dialog_Downtime(Ui_dialog_downtime) self.downtime.initialize() # open defaults settings on button click self.downtime.ui.button_change_defaults_downtime.clicked.connect(self.settings.show_defaults) self.downtime.ui.button_change_defaults_downtime.clicked.connect(self.downtime.window.close) self.acknowledge.ui.button_change_defaults_acknowledge.clicked.connect(self.settings.show_defaults) self.acknowledge.ui.button_change_defaults_acknowledge.clicked.connect(self.acknowledge.window.close) # downtime dialog for miserable item context menu self.submit = Dialog_Submit(Ui_dialog_submit) self.submit.initialize() # authentication dialog for username/password self.authentication = Dialog_Authentication(Ui_dialog_authentication) self.authentication.initialize() # dialog for asking about disabled or not configured servers self.server_missing = Dialog_Server_missing(Ui_dialog_server_missing) self.server_missing.initialize() # open server creation dialog self.server_missing.ui.button_create_server.clicked.connect(self.settings.show_new_server) self.server_missing.ui.button_enable_server.clicked.connect(self.settings.show) # about dialog self.about = Dialog_About(Ui_dialog_about) # file chooser Dialog self.file_chooser = QFileDialog() class Dialog(QObject): """ one single dialog """ # send signal e.g. to statuswindow if dialog pops up show_dialog = pyqtSignal() # dummy toggle dependencies TOGGLE_DEPS = {} # auxiliary list of checkboxes which HIDE some other widgets if triggered - for example proxy OS settings TOGGLE_DEPS_INVERTED = [] # widgets that might be enabled/disebled depending on monitor server type VOLATILE_WIDGETS = {} # names of widgets and their defaults WIDGET_NAMES = {} # style stuff used by settings dialog for servers/actions listwidget GRAY = QBrush(Qt.gray) def __init__(self, dialog): QObject.__init__(self) self.window = QDialog() self.ui = dialog() self.ui.setupUi(self.window) # explicitly set window flags to avoid '?' button on Windows self.window.setWindowFlags(Qt.WindowCloseButtonHint) # set small titlebar icon self.window.setWindowIcon(ICON) # treat dialog content after pressing OK button if 'button_box' in dir(self.ui): self.ui.button_box.accepted.connect(self.ok) self.ui.button_box.rejected.connect(self.cancel) # QSignalMapper needed to connect all toggle-needing-checkboxes/radiobuttons to one .toggle()-method which # decides which sender to use as key in self.TOGGLE_DEPS self.signalmapper_toggles = QSignalMapper() # try to get and keep focus self.window.setWindowModality(Qt.ApplicationModal) def initialize(self): """ dummy initialize method """ pass def show(self, tab=0): """ simple how method, to be enriched """ # tell the world that dialog pops up self.show_dialog.emit() # reset window if only needs smaller screen estate self.window.adjustSize() self.window.show() def toggle_visibility(self, checkbox, widgets=[]): """ state of checkbox toggles visibility of widgets some checkboxes might trigger an inverted behaviour - thus the 'inverted' value """ if checkbox in self.TOGGLE_DEPS_INVERTED: if checkbox.isChecked(): for widget in widgets: widget.hide() else: for widget in widgets: widget.show() # normal case - click on checkbox activates more options else: if checkbox.isChecked(): for widget in widgets: widget.show() else: for widget in widgets: widget.hide() @pyqtSlot(str) def toggle(self, checkbox): """ change state of depending widgets, slot for signals from checkboxes in UI """ # Due to older Qt5 in Ubuntu 14.04 signalmapper has to use strings self.toggle_visibility(self.ui.__dict__[checkbox], self.TOGGLE_DEPS[self.ui.__dict__[checkbox]]) # adjust dialog window size after UI changes self.window.adjustSize() def toggle_toggles(self): # apply toggle-dependencies between checkboxes as certain widgets for checkbox, widgets in self.TOGGLE_DEPS.items(): # toggle visibility self.toggle_visibility(checkbox, widgets) # multiplex slot .toggle() by signal-mapping # Due to older Qt5 in Ubuntu 14.04 signalmapper has to use strings self.signalmapper_toggles.setMapping(checkbox, checkbox.objectName()) checkbox.toggled.connect(self.signalmapper_toggles.map) # finally map signals with .sender() - [QWidget] is important! self.signalmapper_toggles.mapped[str].connect(self.toggle) def fill_list(self, listwidget, config): """ fill listwidget with items from config """ for configitem in sorted(config, key=str.lower): listitem = QListWidgetItem(configitem) if config[configitem].enabled is False: listitem.setForeground(self.GRAY) listwidget.addItem(listitem) @pyqtSlot() def ok(self): # dummy OK treatment pass @pyqtSlot() def cancel(self): """ as default closes dialog - might be refined, for example by settings dialog """ self.window.close() class Dialog_Settings(Dialog): """ class for settings dialog """ # signal to be fired if OK button was clicked and new setting are applied changed = pyqtSignal() # send signal if check for new version is wanted check_for_new_version = pyqtSignal(bool, QWidget) # used to tell debug loop it should start start_debug_loop = pyqtSignal() def __init__(self, dialog): Dialog.__init__(self, dialog) # define checkbox-to-widgets dependencies which apply at initialization # which widgets have to be hidden because of irrelevance # dictionary holds checkbox/radiobutton as key and relevant widgets in list self.TOGGLE_DEPS = { # debug mode self.ui.input_checkbox_debug_mode: [self.ui.input_checkbox_debug_to_file, self.ui.input_lineedit_debug_file], # regular expressions for filtering hosts self.ui.input_checkbox_re_host_enabled: [self.ui.input_lineedit_re_host_pattern, self.ui.input_checkbox_re_host_reverse], # regular expressions for filtering services self.ui.input_checkbox_re_service_enabled: [self.ui.input_lineedit_re_service_pattern, self.ui.input_checkbox_re_service_reverse], # regular expressions for filtering status information self.ui.input_checkbox_re_status_information_enabled: [self.ui.input_lineedit_re_status_information_pattern, self.ui.input_checkbox_re_status_information_reverse], # offset for statuswindow when using systray self.ui.input_radiobutton_icon_in_systray: [self.ui.input_checkbox_systray_offset_use], self.ui.input_checkbox_systray_offset_use: [self.ui.input_spinbox_systray_offset, self.ui.label_offset_statuswindow], # display to use in fullscreen mode self.ui.input_radiobutton_fullscreen: [self.ui.label_fullscreen_display, self.ui.input_combobox_fullscreen_display], # notifications in general self.ui.input_checkbox_notification: [self.ui.notification_groupbox], # sound at all self.ui.input_checkbox_notification_sound: [self.ui.notification_sounds_groupbox], # custom sounds self.ui.input_radiobutton_notification_custom_sound: [self.ui.notification_custom_sounds_groupbox], # notification actions self.ui.input_checkbox_notification_actions: [self.ui.notification_actions_groupbox], # several notification actions depending on status self.ui.input_checkbox_notification_action_warning: [self.ui.input_lineedit_notification_action_warning_string], self.ui.input_checkbox_notification_action_critical: [self.ui.input_lineedit_notification_action_critical_string], self.ui.input_checkbox_notification_action_down: [self.ui.input_lineedit_notification_action_down_string], self.ui.input_checkbox_notification_action_ok: [self.ui.input_lineedit_notification_action_ok_string], # single custom notification action self.ui.input_checkbox_notification_custom_action: [self.ui.notification_custom_action_groupbox], # customized color alternation self.ui.input_checkbox_show_grid: [self.ui.input_checkbox_grid_use_custom_intensity], self.ui.input_checkbox_grid_use_custom_intensity: [ self.ui.input_slider_grid_alternation_intensity, self.ui.label_intensity_information_0, self.ui.label_intensity_information_1, self.ui.label_intensity_warning_0, self.ui.label_intensity_warning_1, self.ui.label_intensity_average_0, self.ui.label_intensity_average_1, self.ui.label_intensity_high_0, self.ui.label_intensity_high_1, self.ui.label_intensity_critical_0, self.ui.label_intensity_critical_1, self.ui.label_intensity_disaster_0, self.ui.label_intensity_disaster_1, self.ui.label_intensity_down_0, self.ui.label_intensity_down_1, self.ui.label_intensity_unreachable_0, self.ui.label_intensity_unreachable_1, self.ui.label_intensity_unknown_0, self.ui.label_intensity_unknown_1], self.ui.input_radiobutton_use_custom_browser: [self.ui.groupbox_custom_browser, self.ui.input_lineedit_custom_browser, self.ui.button_choose_browser]} # set title to current version self.window.setWindowTitle(' '.join((AppInfo.NAME, AppInfo.VERSION))) # connect server buttons to server dialog self.ui.button_new_server.clicked.connect(self.new_server) self.ui.button_edit_server.clicked.connect(self.edit_server) self.ui.button_copy_server.clicked.connect(self.copy_server) self.ui.button_delete_server.clicked.connect(self.delete_server) # connect check-for-updates button to update check # self.ui.button_check_for_new_version_now.clicked.connect(check_version.check) self.ui.button_check_for_new_version_now.clicked.connect(self.button_check_for_new_version_clicked) self.check_for_new_version.connect(check_version.check) # avoid offset spinbox if offest is not enabled self.ui.input_radiobutton_fullscreen.clicked.connect(self.toggle_systray_icon_offset) self.ui.input_radiobutton_icon_in_systray.clicked.connect(self.toggle_systray_icon_offset) self.ui.input_radiobutton_statusbar_floating.clicked.connect(self.toggle_systray_icon_offset) # connect font chooser button to font choosing dialog self.ui.button_fontchooser.clicked.connect(self.font_chooser) # connect revert-to-default-font button self.ui.button_default_font.clicked.connect(self.font_default) # store font as default self.font = FONT # show current font in label_font self.ui.label_font.setFont(FONT) # connect action buttons to action dialog self.ui.button_new_action.clicked.connect(self.new_action) self.ui.button_edit_action.clicked.connect(self.edit_action) self.ui.button_copy_action.clicked.connect(self.copy_action) self.ui.button_delete_action.clicked.connect(self.delete_action) # connect custom sound file buttons self.ui.button_choose_warning.clicked.connect(self.choose_sound_file_warning) self.ui.button_choose_critical.clicked.connect(self.choose_sound_file_critical) self.ui.button_choose_down.clicked.connect(self.choose_sound_file_down) # connect custom sound file buttons self.ui.button_play_warning.clicked.connect(self.play_sound_file_warning) self.ui.button_play_critical.clicked.connect(self.play_sound_file_critical) self.ui.button_play_down.clicked.connect(self.play_sound_file_down) # only show desktop notification on systems that support it if not dbus_connection.connected: self.ui.input_checkbox_notification_desktop.hide() # set folder and play symbols to choose and play buttons self.ui.button_choose_warning.setText('') self.ui.button_choose_warning.setIcon(self.ui.button_play_warning.style().standardIcon(QStyle.SP_DirIcon)) self.ui.button_play_warning.setText('') self.ui.button_play_warning.setIcon(self.ui.button_play_warning.style().standardIcon(QStyle.SP_MediaPlay)) self.ui.button_choose_critical.setText('') self.ui.button_choose_critical.setIcon(self.ui.button_play_warning.style().standardIcon(QStyle.SP_DirIcon)) self.ui.button_play_critical.setText('') self.ui.button_play_critical.setIcon(self.ui.button_play_warning.style().standardIcon(QStyle.SP_MediaPlay)) self.ui.button_choose_down.setText('') self.ui.button_choose_down.setIcon(self.ui.button_play_warning.style().standardIcon(QStyle.SP_DirIcon)) self.ui.button_play_down.setText('') self.ui.button_play_down.setIcon(self.ui.button_play_warning.style().standardIcon(QStyle.SP_MediaPlay)) # set browser file chooser icon and current custom browser path self.ui.button_choose_browser.setText('') self.ui.button_choose_browser.setIcon(self.ui.button_play_warning.style().standardIcon(QStyle.SP_DirIcon)) self.ui.input_lineedit_custom_browser.setText(conf.custom_browser) # connect choose browser button with file dialog self.ui.button_choose_browser.clicked.connect(self.choose_browser_executable) # QSignalMapper needed to connect all color buttons to color dialogs self.signalmapper_colors = QSignalMapper() # connect color buttons with color dialog for widget in [x for x in self.ui.__dict__ if x.startswith('input_button_color_')]: button = self.ui.__dict__[widget] item = widget.split('input_button_color_')[1] # multiplex slot for open color dialog by signal-mapping self.signalmapper_colors.setMapping(button, item) button.clicked.connect(self.signalmapper_colors.map) # connect reset and defaults buttons self.ui.button_colors_reset.clicked.connect(self.paint_colors) self.ui.button_colors_reset.clicked.connect(self.paint_color_alternation) self.ui.button_colors_reset.clicked.connect(self.change_color_alternation_by_value) self.ui.button_colors_defaults.clicked.connect(self.colors_defaults) self.ui.button_colors_defaults.clicked.connect(self.paint_color_alternation) self.ui.button_colors_defaults.clicked.connect(self.change_color_alternation_by_value) # paint alternating colors when example is wanted for customized intensity self.ui.input_checkbox_grid_use_custom_intensity.clicked.connect(self.paint_color_alternation) self.ui.input_checkbox_grid_use_custom_intensity.clicked.connect(self.change_color_alternation_by_value) self.ui.input_checkbox_grid_use_custom_intensity.clicked.connect(self.toggle_zabbix_widgets) # finally map signals with .sender() - [] is important! self.signalmapper_colors.mapped[str].connect(self.color_chooser) # connect slider to alternating colors self.ui.input_slider_grid_alternation_intensity.valueChanged.connect(self.change_color_alternation) # apply toggle-dependencies between checkboxes as certain widgets self.toggle_toggles() # workaround to avoid gigantic settings dialog # list of Zabbix-related widgets, only to be shown if there is a Zabbix monitor server configured self.ZABBIX_WIDGETS = [self.ui.input_checkbox_filter_all_average_services, self.ui.input_checkbox_filter_all_disaster_services, self.ui.input_checkbox_filter_all_high_services, self.ui.input_checkbox_filter_all_information_services, self.ui.input_checkbox_notify_if_average, self.ui.input_checkbox_notify_if_disaster, self.ui.input_checkbox_notify_if_high, self.ui.input_checkbox_notify_if_information, self.ui.label_color_average, self.ui.label_color_disaster, self.ui.label_color_high, self.ui.label_color_information, self.ui.input_button_color_average_text, self.ui.input_button_color_average_background, self.ui.input_button_color_disaster_text, self.ui.input_button_color_disaster_background, self.ui.input_button_color_high_text, self.ui.input_button_color_high_background, self.ui.input_button_color_information_text, self.ui.input_button_color_information_background, self.ui.label_intensity_average_0, self.ui.label_intensity_average_1, self.ui.label_intensity_disaster_0, self.ui.label_intensity_disaster_1, self.ui.label_intensity_high_0, self.ui.label_intensity_high_1, self.ui.label_intensity_information_0, self.ui.label_intensity_information_1] def initialize(self): # apply configuration values # start with servers tab self.ui.tabs.setCurrentIndex(0) for widget in dir(self.ui): if widget.startswith('input_'): if widget.startswith('input_checkbox_'): if conf.__dict__[widget.split('input_checkbox_')[1]] is True: self.ui.__dict__[widget].toggle() elif widget.startswith('input_radiobutton_'): if conf.__dict__[widget.split('input_radiobutton_')[1]] is True: self.ui.__dict__[widget].toggle() elif widget.startswith('input_lineedit_'): # older versions of Nagstamon have a bool value for custom_action_separator # which leads to a crash here - thus str() to solve this self.ui.__dict__[widget].setText(str(conf.__dict__[widget.split('input_lineedit_')[1]])) elif widget.startswith('input_spinbox_'): self.ui.__dict__[widget].setValue(int(conf.__dict__[widget.split('input_spinbox_')[1]])) elif widget.startswith('input_slider_'): self.ui.__dict__[widget].setValue(int(conf.__dict__[widget.split('input_slider_')[1]])) # bruteforce size smallification, lazy try/except variant try: self.ui.__dict__[widget].adjustSize() except: pass # fill default order fields combobox with s names # kick out empty headers for hosts and services flags sort_fields = copy.copy(HEADERS_HEADERS) while '' in sort_fields: sort_fields.remove('') self.ui.input_combobox_default_sort_field.addItems(sort_fields) # catch exception which will occur when older settings are used which have real header names as values try: self.ui.input_combobox_default_sort_field.setCurrentText(HEADERS_KEYS_HEADERS[conf.default_sort_field]) except Exception: self.ui.input_combobox_default_sort_field.setCurrentText(conf.default_sort_field) # fill default sort order combobox self.ui.input_combobox_default_sort_order.addItems(['Ascending', 'Descending']) # .title() to get upper first letter self.ui.input_combobox_default_sort_order.setCurrentText(conf.default_sort_order.title()) # fill combobox with screens for fullscreen for display in range(desktop.screenCount()): self.ui.input_combobox_fullscreen_display.addItem(str(display)) self.ui.input_combobox_fullscreen_display.setCurrentText(str(conf.fullscreen_display)) # fill servers listwidget with servers self.fill_list(self.ui.list_servers, conf.servers) # select first item self.ui.list_servers.setCurrentRow(0) # fill actions listwidget with actions self.fill_list(self.ui.list_actions, conf.actions) # select first item self.ui.list_actions.setCurrentRow(0) # paint colors onto color selection buttons and alternation example self.paint_colors() self.paint_color_alternation() self.change_color_alternation(conf.grid_alternation_intensity) # important final size adjustment self.window.adjustSize() def show(self, tab=0): # hide them and thus be able to fix size if no extra Zabbix widgets are shown self.toggle_zabbix_widgets() # small workaround for timestamp trick to avoid flickering # if the 'Settings' button was clicked too fast the timestamp difference # is too short and the statuswindow will keep open # modifying the timestamp could help statuswindow.is_shown_timestamp -= 1 # tell the world that dialog pops up self.show_dialog.emit() # jump to requested tab in settings dialog self.ui.tabs.setCurrentIndex(tab) # reset window if only needs smaller screen estate self.window.adjustSize() self.window.exec_() @pyqtSlot() def show_new_server(self): """ opens settings and new server dialogs - used by dialogs.server_missing """ # emulate button click self.ui.button_new_server.clicked.emit() @pyqtSlot() def show_filters(self): """ opens filters settings after clicking button_filters in toparea """ self.show(tab=2) @pyqtSlot() def show_defaults(self): """ opens default settings after clicking button in acknowledge/downtime dialog """ self.show(tab=6) def ok(self): """ what to do if OK was pressed """ global FONT, ICONS_FONT, statuswindow, menu, NUMBER_OF_DISPLAY_CHANGES # store position of statuswindow/statusbar only if statusbar is floating if conf.statusbar_floating: statuswindow.store_position_to_conf() # store hash of all display settings as display_mode to decide if statuswindow has to be recreated display_mode = str(conf.statusbar_floating) + \ str(conf.icon_in_systray) + \ str(conf.fullscreen) + \ str(conf.fullscreen_display) + \ str(conf.windowed) # do all stuff necessary after OK button was clicked # put widget values into conf for widget in self.ui.__dict__.values(): if widget.objectName().startswith('input_checkbox_'): conf.__dict__[widget.objectName().split('input_checkbox_')[1]] = widget.isChecked() elif widget.objectName().startswith('input_radiobutton_'): conf.__dict__[widget.objectName().split('input_radiobutton_')[1]] = widget.isChecked() elif widget.objectName().startswith("input_lineedit_"): conf.__dict__[widget.objectName().split('input_lineedit_')[1]] = widget.text() elif widget.objectName().startswith('input_spinbox_'): conf.__dict__[widget.objectName().split('input_spinbox_')[1]] = str(widget.value()) elif widget.objectName().startswith('input_slider_'): conf.__dict__[widget.objectName().split('input_slider_')[1]] = str(widget.value()) elif widget.objectName().startswith('input_combobox_'): conf.__dict__[widget.objectName().split('input_combobox_')[1]] = widget.currentText() elif widget.objectName().startswith('input_button_color_'): # get color value from color button stylesheet color = self.ui.__dict__[widget.objectName()].styleSheet() color = color.split(':')[1].strip().split(';')[0] conf.__dict__[widget.objectName().split('input_button_')[1]] = color # convert some strings to integers and bools for item in conf.__dict__: if type(conf.__dict__[item]) == str: # when item is not one of those which always have to be strings then it might be OK to convert it if not item in CONFIG_STRINGS: if conf.__dict__[item] in BOOLPOOL: conf.__dict__[item] = BOOLPOOL[conf.__dict__[item]] elif conf.__dict__[item].isdecimal(): conf.__dict__[item] = int(conf.__dict__[item]) # start debug loop if debugging is enabled if conf.debug_mode: # only start debugging loop if it not already loops if statuswindow.worker.debug_loop_looping is False: self.start_debug_loop.emit() else: # set flag to tell debug loop it should stop please statuswindow.worker.debug_loop_looping = False # convert sorting fields to simple keys - maybe one day translated conf.default_sort_field = HEADERS_HEADERS_KEYS[conf.default_sort_field] # apply font conf.font = self.font.toString() # update global font and icons font FONT = self.font ICONS_FONT = QFont('Nagstamon', FONT.pointSize() + 2, QFont.Normal, False) # update brushes for treeview create_brushes() # store configuration conf.SaveConfig() # save configuration conf.SaveConfig() # when display mode was changed its the easiest to destroy the old status window and create a new one # store display_mode to decide if statuswindow has to be recreated if display_mode != str(conf.statusbar_floating) + \ str(conf.icon_in_systray) + \ str(conf.fullscreen) + \ str(conf.fullscreen_display) + \ str(conf.windowed): # increase number of display changes for silly Windows-hides-statusbar-after-display-mode-change problem NUMBER_OF_DISPLAY_CHANGES += 1 # stop statuswindow worker #statuswindow.worker.running = False #statuswindow.worker_notification.running = False # hide window to avoid laggy GUI - better none than laggy statuswindow.hide() # tell all treeview threads to stop for server_vbox in statuswindow.servers_vbox.children(): server_vbox.table.worker.finish.emit() # wait until all threads are stopped for server_vbox in statuswindow.servers_vbox.children(): server_vbox.table.worker_thread.wait(1000) # wait until statuswindow worker has finished #statuswindow.worker_thread.wait(1000) # wait until statuswindow notification worker has finished #statuswindow.worker_notification_thread.wait(1000) # kick out ol' statuswindow #statuswindow.destroy(True, True) # create new global one #statuswindow = StatusWindow() #statuswindow.__init__() statuswindow.create_ServerVBoxes() statuswindow.set_mode() # context menu for systray and statuswindow menu = MenuContext() # tell statuswindow to refresh due to new settings self.changed.emit() # see if there are any servers created and enabled check_servers() @pyqtSlot() def cancel(self): """ check if there are any usable servers configured """ self.window.close() check_servers() @pyqtSlot() def new_server(self): """ create new server """ dialogs.server.new() @pyqtSlot() def edit_server(self): """ edit existing server """ dialogs.server.edit() @pyqtSlot() def copy_server(self): """ copy existing server """ dialogs.server.copy() @pyqtSlot() def delete_server(self): """ delete server, stop its thread, remove from config and list """ # server to delete from current row in servers list server = conf.servers[self.ui.list_servers.currentItem().text()] reply = QMessageBox.question(self.window, 'Nagstamon', 'Do you really want to delete monitor server %s?' % (server.name), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: # in case server is enabled delete its vbox if server.enabled: for vbox in statuswindow.servers_vbox.children(): if vbox.server.name == server.name: # stop thread by falsificate running flag vbox.table.worker.running = False vbox.table.worker.finish.emit() break # kick server out of server instances servers.pop(server.name) # dito from config items conf.servers.pop(server.name) # refresh list # row index 0 to x row = self.ui.list_servers.currentRow() # count real number, 1 to x count = self.ui.list_servers.count() # if deleted row was the last line the new current row has to be the new last line, accidently the same as count if row == count - 1: # use the penultimate item as the new current one row = count - 2 else: # go down one row row = row + 1 # refresh list and mark new current row self.refresh_list(list_widget=self.ui.list_servers, list_conf=conf.servers, current=self.ui.list_servers.item(row).text()) del(row, count) # delete server config file from disk conf.delete_file('servers', 'server_{0}'.format(server.name)) del(server) def refresh_list(self, list_widget, list_conf, current=''): """ refresh given 'list_widget' from given 'list_conf' and mark 'current' as current """ # clear list of servers list_widget.clear() # fill servers listwidget with servers self.fill_list(list_widget, list_conf) # select current edited item # activate currently created/edited server monitor item by first searching it in the list list_widget.setCurrentItem(list_widget.findItems(current, Qt.MatchExactly)[0]) @pyqtSlot() def new_action(self): """ create new action """ dialogs.action.new() @pyqtSlot() def edit_action(self): """ edit existing action """ dialogs.action.edit() @pyqtSlot() def copy_action(self): """ copy existing action and edit it """ dialogs.action.copy() @pyqtSlot() def delete_action(self): """ delete action remove from config and list """ # action to delete from current row in actions list action = conf.actions[self.ui.list_actions.currentItem().text()] reply = QMessageBox.question(self.window, 'Nagstamon', 'Do you really want to delete action %s?' % (action.name), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: # kick action out of config items conf.actions.pop(action.name) # refresh list # row index 0 to x row = self.ui.list_actions.currentRow() # count real number, 1 to x count = self.ui.list_actions.count() # if deleted row was the last line the new current row has to be the new last line, accidently the same as count if row == count - 1: # use the penultimate item as the new current one row = count - 2 else: # go down one row row = row + 1 # refresh list and mark new current row self.refresh_list(list_widget=self.ui.list_actions, list_conf=conf.actions, current=self.ui.list_actions.item(row).text()) del(row, count) # delete action config file from disk conf.delete_file('actions', 'action_{0}'.format(action.name)) del(action) def choose_sound_file_decoration(method): """ try to decorate sound file dialog """ def decoration_function(self): # execute decorated function method(self) # shortcut for widget to fill and revaluate widget = self.ui.__dict__['input_lineedit_notification_custom_sound_%s' % self.sound_file_type] # use 2 filters, sound files and all files file = dialogs.file_chooser.getOpenFileName(self.window, filter='Sound files (*.mp3 *.MP3 *.mp4 *.MP4 ' '*.wav *.WAV *.ogg *.OGG);;' 'All files (*)')[0] # only take filename if QFileDialog gave something useful back if file != '': widget.setText(file) return(decoration_function) @choose_sound_file_decoration @pyqtSlot() def choose_sound_file_warning(self): self.sound_file_type = 'warning' @choose_sound_file_decoration @pyqtSlot() def choose_sound_file_critical(self): self.sound_file_type = 'critical' @choose_sound_file_decoration @pyqtSlot() def choose_sound_file_down(self): self.sound_file_type = 'down' def play_sound_file_decoration(method): """ try to decorate sound file dialog """ def decoration_function(self): # execute decorated function method(self) # shortcut for widget to fill and revaluate widget = self.ui.__dict__['input_lineedit_notification_custom_sound_%s' % self.sound_file_type] # get file path from widget file = widget.text() # tell mediaplayer to play file only if it exists if mediaplayer.set_media(file) is True: mediaplayer.play() return(decoration_function) @play_sound_file_decoration @pyqtSlot() def play_sound_file_warning(self): self.sound_file_type = 'warning' @play_sound_file_decoration @pyqtSlot() def play_sound_file_critical(self): self.sound_file_type = 'critical' @play_sound_file_decoration @pyqtSlot() def play_sound_file_down(self): self.sound_file_type = 'down' def paint_colors(self): """ fill color selection buttons with appropriate colors """ # color buttons for color in [x for x in conf.__dict__ if x.startswith('color_')]: self.ui.__dict__['input_button_%s' % (color)].setStyleSheet('''background-color: %s; border-width: 1px; border-color: black; border-style: solid;''' % conf.__dict__[color]) # example color labels for label in [x for x in self.ui.__dict__ if x.startswith('label_color_')]: status = label.split('label_color_')[1] self.ui.__dict__[label].setStyleSheet('color: %s; background: %s' % (conf.__dict__['color_%s_text' % (status)], (conf.__dict__['color_%s_background' % (status)]))) @pyqtSlot() def colors_defaults(self): """ apply default colors to buttons """ # color buttons for default_color in [x for x in conf.__dict__ if x.startswith('default_color_')]: # cut 'default_' off to get color color = default_color.split('default_')[1] self.ui.__dict__['input_button_%s' % (color)].setStyleSheet('''background-color: %s; border-width: 1px; border-color: black; border-style: solid;''' % conf.__dict__[default_color]) # example color labels for label in [x for x in self.ui.__dict__ if x.startswith('label_color_')]: status = label.split('label_color_')[1] # get color values from color button stylesheets color_text = self.ui.__dict__['input_button_color_' + status + '_text'].styleSheet() color_text = color_text.split(':')[1].strip().split(';')[0] color_background = self.ui.__dict__['input_button_color_' + status + '_background'].styleSheet() color_background = color_background.split(':')[1].strip().split(';')[0] # apply color values from stylesheet to label self.ui.__dict__[label].setStyleSheet('color: %s; background: %s' % (color_text, color_background)) @pyqtSlot(str) def color_chooser(self, item): """ open QColorDialog to choose a color and change it in settings dialog """ color = conf.__dict__['color_%s' % (item)] new_color = QColorDialog.getColor(QColor(color), parent=self.window) # if canceled the color is invalid if new_color.isValid(): self.ui.__dict__['input_button_color_%s' % (item)].setStyleSheet('''background-color: %s; border-width: 1px; border-color: black; border-style: solid;''' % new_color.name()) status = item.split('_')[0] # get color value from stylesheet to paint example text = self.ui.__dict__['input_button_color_%s_text' % (status)].styleSheet() text = text.split(':')[1].strip().split(';')[0] background = self.ui.__dict__['input_button_color_%s_background' % (status)].styleSheet() background = background.split(':')[1].strip().split(';')[0] # set example color self.ui.__dict__['label_color_%s' % (status)].setStyleSheet('''color: {0}; background: {1} '''.format(text, background)) # update alternation colors self.paint_color_alternation() self.change_color_alternation(self.ui.input_slider_grid_alternation_intensity.value()) def paint_color_alternation(self): """ paint the intensity example color labels taking actual colors from color chooser buttons this labels have the color of alteration level 0 aka default """ for state in COLORS: # get text color from button CSS text = self.ui.__dict__['input_button_color_{0}_text' .format(state.lower())]\ .styleSheet()\ .split(';\n')[0].split(': ')[1] # get background color from button CSS background = self.ui.__dict__['input_button_color_{0}_background' .format(state.lower())]\ .styleSheet()\ .split(';\n')[0].split(': ')[1] # set CSS self.ui.__dict__['label_intensity_{0}_0'.format(state.lower())]\ .setStyleSheet('''color: {0}; background-color: {1}; padding-top: 3px; padding-bottom: 3px; '''.format(text, background)) @pyqtSlot(int) def change_color_alternation(self, value): """ fill alternation level 1 labels with altered color derived from level 0 labels aka default """ for state in COLORS: # only evaluate colors if there is any stylesheet if len(self.ui.__dict__['input_button_color_{0}_text' .format(state.lower())]\ .styleSheet()) > 0: # access both labels label_0 = self.ui.__dict__['label_intensity_{0}_0'.format(state.lower())] label_1 = self.ui.__dict__['label_intensity_{0}_1'.format(state.lower())] # get text color from text color chooser button text = self.ui.__dict__['input_button_color_{0}_text' .format(state.lower())]\ .styleSheet()\ .split(';\n')[0].split(': ')[1] # get background of level 0 label background = label_0.palette().color(QPalette.Window) r, g, b, a = background.getRgb() # if label background is too dark lighten the color instead of darken it mor if background.lightness() < 30: if value > 5: r += 30 g += 30 b += 30 r = round(r / 100 * (100 + value)) g = round(g / 100 * (100 + value)) b = round(b / 100 * (100 + value)) else: r = round(r / 100 * (100 - value)) g = round(g / 100 * (100 - value)) b = round(b / 100 * (100 - value)) # finally apply new background color # easier with style sheets than with QPalette/QColor label_1.setStyleSheet('''color: {0}; background-color: rgb({1}, {2}, {3}); padding-top: 3px; padding-bottom: 3px; '''.format(text, r, g, b)) @pyqtSlot() def change_color_alternation_by_value(self): """ to be fired up when colors are reset """ self.change_color_alternation(self.ui.input_slider_grid_alternation_intensity.value()) @pyqtSlot() def font_chooser(self): """ use font dialog to choose a font """ self.font = QFontDialog.getFont(self.font, parent=self.window)[0] self.ui.label_font.setFont(self.font) @pyqtSlot() def font_default(self): """ reset font to default font which was valid when Nagstamon was launched """ self.ui.label_font.setFont(DEFAULT_FONT) self.font = DEFAULT_FONT @pyqtSlot() def button_check_for_new_version_clicked(self): """ at this point start_mode for version check is definitively False """ self.check_for_new_version.emit(False, self.window) @pyqtSlot() def choose_browser_executable(self): """ show dialog for selection of non-default browser """ # present dialog with OS-specific sensible defaults if OS == 'Windows': filter = 'Executables (*.exe *.EXE);; All files (*)' directory = os.environ['ProgramFiles'] elif OS == 'Darwin': filter = '' directory = '/Applications' else: filter = '' directory = '/usr/bin' file = dialogs.file_chooser.getOpenFileName(self.window, directory=directory, filter=filter)[0] # only take filename if QFileDialog gave something useful back if file != '': self.ui.input_lineedit_custom_browser.setText(file) @pyqtSlot() def toggle_zabbix_widgets(self): """ Depending on the existence of an enabled Zabbix monitor the Zabbix widgets are shown or hidden """ use_zabbix_widgets = False for server in servers.values(): if server.enabled: if server.type == 'Zabbix': use_zabbix_widgets = True break if use_zabbix_widgets: for widget in self.ZABBIX_WIDGETS: widget.show() else: for widget in self.ZABBIX_WIDGETS: widget.hide() @pyqtSlot() def toggle_systray_icon_offset(self): """ Only show offset spinbox when offset is enabled """ if self.ui.input_checkbox_systray_offset_use.isVisible(): if self.ui.input_checkbox_systray_offset_use.isChecked(): self.ui.input_spinbox_systray_offset.show() self.ui.label_offset_statuswindow.show() else: self.ui.input_spinbox_systray_offset.hide() self.ui.label_offset_statuswindow.hide() else: self.ui.input_spinbox_systray_offset.hide() self.ui.label_offset_statuswindow.hide() class Dialog_Server(Dialog): """ Dialog used to setup one single server """ def __init__(self, dialog): Dialog.__init__(self, dialog) # define checkbox-to-widgets dependencies which apply at initialization # which widgets have to be hidden because of irrelevance # dictionary holds checkbox/radiobutton as key and relevant widgets in list self.TOGGLE_DEPS = { self.ui.input_checkbox_use_autologin: [self.ui.label_autologin_key, self.ui.input_lineedit_autologin_key], self.ui.input_checkbox_use_proxy: [self.ui.groupbox_proxy], self.ui.input_checkbox_use_proxy_from_os: [self.ui.label_proxy_address, self.ui.input_lineedit_proxy_address, self.ui.label_proxy_username, self.ui.input_lineedit_proxy_username, self.ui.label_proxy_password, self.ui.input_lineedit_proxy_password], self.ui.input_checkbox_show_options: [self.ui.groupbox_options], self.ui.input_checkbox_custom_cert_use: [self.ui.label_custom_ca_file, self.ui.input_lineedit_custom_cert_ca_file, self.ui.button_choose_custom_cert_ca_file]} self.TOGGLE_DEPS_INVERTED = [self.ui.input_checkbox_use_proxy_from_os] # these widgets are shown or hidden depending on server type properties # the servers listed at each widget do need them self.VOLATILE_WIDGETS = { self.ui.label_monitor_cgi_url: ['Nagios', 'Icinga', 'Thruk', 'Sensu'], self.ui.input_lineedit_monitor_cgi_url: ['Nagios', 'Icinga', 'Thruk', 'Sensu'], self.ui.input_checkbox_use_autologin: ['Centreon'], self.ui.input_lineedit_autologin_key: ['Centreon'], self.ui.label_autologin_key: ['Centreon'], self.ui.input_checkbox_no_cookie_auth: ['IcingaWeb2', 'Sensu'], self.ui.input_checkbox_use_display_name_host: ['Icinga', 'IcingaWeb2'], self.ui.input_checkbox_use_display_name_service: ['Icinga', 'IcingaWeb2'], self.ui.input_checkbox_force_authuser: ['Check_MK Multisite'], self.ui.groupbox_check_mk_views: ['Check_MK Multisite'], self.ui.input_lineedit_host_filter: ['op5Monitor'], self.ui.input_lineedit_service_filter: ['op5Monitor'], self.ui.label_service_filter: ['op5Monitor'], self.ui.label_host_filter: ['op5Monitor'], self.ui.label_monitor_site: ['Sensu'], self.ui.input_lineedit_monitor_site: ['Sensu']} # to be used when selecting authentication method Kerberos self.AUTHENTICATION_WIDGETS = [ self.ui.label_username, self.ui.input_lineedit_username, self.ui.label_password, self.ui.input_lineedit_password, self.ui.input_checkbox_save_password] # fill default order fields combobox with monitor server types self.ui.input_combobox_type.addItems(sorted(SERVER_TYPES.keys(), key=str.lower)) # default to Nagios as it is the mostly used monitor server self.ui.input_combobox_type.setCurrentText('Nagios') # set folder and play symbols to choose and play buttons self.ui.button_choose_custom_cert_ca_file.setText('') self.ui.button_choose_custom_cert_ca_file.setIcon(self.ui.button_choose_custom_cert_ca_file.style().standardIcon(QStyle.SP_DirIcon)) # connect choose custom cert CA file button with file dialog self.ui.button_choose_custom_cert_ca_file.clicked.connect(self.choose_custom_cert_ca_file) # fill authentication combobox self.ui.input_combobox_authentication.addItems(['Basic', 'Digest', 'Kerberos']) # detect change of server type which leads to certain options shown or hidden self.ui.input_combobox_type.activated.connect(self.toggle_type) # when authentication is changed to Kerberos then disable username/password as the are now useless self.ui.input_combobox_authentication.activated.connect(self.toggle_authentication) # reset Check_MK views self.ui.button_check_mk_view_hosts_reset.clicked.connect(self.check_mk_view_hosts_reset) self.ui.button_check_mk_view_services_reset.clicked.connect(self.check_mk_view_services_reset) # mode needed for evaluate dialog after ok button pressed - defaults to 'new' self.mode = 'new' @pyqtSlot(int) def toggle_type(self, server_type_index=0): # server_type_index is not needed - we get the server type from .currentText() # check if server type is listed in volatile widgets to decide if it has to be shown or hidden for widget, server_types in self.VOLATILE_WIDGETS.items(): if self.ui.input_combobox_type.currentText() in server_types: widget.show() else: widget.hide() @pyqtSlot() def toggle_authentication(self): """ when authentication is changed to Kerberos then disable username/password as the are now useless """ if self.ui.input_combobox_authentication.currentText() == 'Kerberos': for widget in self.AUTHENTICATION_WIDGETS: widget.hide() else: for widget in self.AUTHENTICATION_WIDGETS: widget.show() # after hiding authentication widgets dialog might shrink self.window.adjustSize() def dialog_decoration(method): """ try with a decorator instead of repeated calls """ # function which decorates method def decoration_function(self, **kwargs): """ self.server_conf has to be set by decorated method """ # previous server conf only useful when editing - defaults to None self.previous_server_conf = None # call decorated method method(self, **kwargs) # run through all input widgets and and apply defaults from config for widget in self.ui.__dict__: if widget.startswith('input_'): if widget.startswith('input_checkbox_'): setting = widget.split('input_checkbox_')[1] self.ui.__dict__[widget].setChecked(self.server_conf.__dict__[setting]) elif widget.startswith('input_radiobutton_'): setting = widget.split('input_radiobutton_')[1] self.ui.__dict__[widget].setChecked(self.server_conf.__dict__[setting]) elif widget.startswith('input_combobox_'): setting = widget.split('input_combobox_')[1] self.ui.__dict__[widget].setCurrentText(self.server_conf.__dict__[setting]) elif widget.startswith('input_lineedit_'): setting = widget.split('input_lineedit_')[1] self.ui.__dict__[widget].setText(self.server_conf.__dict__[setting]) elif widget.startswith('input_spinbox_'): setting = widget.split('input_spinbox_')[1] self.ui.__dict__[widget].setValue(self.server_conf.__dict__[setting]) # set current authentication type by using capitalized first letter via .title() self.ui.input_combobox_authentication.setCurrentText(self.server_conf.authentication.title()) # initially hide not needed widgets self.toggle_type() # disable unneeded authentication widgets if Kerberos is used self.toggle_authentication() # apply toggle-dependencies between checkboxes and certain widgets self.toggle_toggles() # open extra options if wanted e.g. by button_fix_tls_error if 'show_options' in self.__dict__: if self.show_options: self.ui.input_checkbox_show_options.setChecked(True) # important final size adjustment self.window.adjustSize() # self.window.show() self.window.exec_() # give back decorated function return(decoration_function) @dialog_decoration def new(self): """ create new server, set default values """ self.mode = 'new' # create new server config object self.server_conf = Server() # window title might be pretty simple self.window.setWindowTitle('New server') @dialog_decoration def edit(self, server_name=None, show_options=False): """ edit existing server when called by Edit button in ServerVBox use given server_name to get server config """ self.mode = 'edit' # shorter server conf if server_name is None: self.server_conf = conf.servers[dialogs.settings.ui.list_servers.currentItem().text()] else: self.server_conf = conf.servers[server_name] # store monitor name in case it will be changed self.previous_server_conf = deepcopy(self.server_conf) # set window title self.window.setWindowTitle('Edit %s' % (self.server_conf.name)) # set self.shot_optios to give value to decorator self.show_options = show_options @dialog_decoration def copy(self): """ copy existing server """ self.mode = 'copy' # shorter server conf self.server_conf = deepcopy(conf.servers[dialogs.settings.ui.list_servers.currentItem().text()]) # set window title before name change to reflect copy self.window.setWindowTitle('Copy %s' % (self.server_conf.name)) # indicate copy of other server self.server_conf.name = 'Copy of ' + self.server_conf.name def ok(self): """ evaluate state of widgets to get new configuration """ # global statement necessary because of reordering of servers OrderedDict global servers # check that no duplicate name exists if self.ui.input_lineedit_name.text() in conf.servers and \ (self.mode in ['new', 'copy'] or self.mode == 'edit' and self.server_conf != conf.servers[self.ui.input_lineedit_name.text()]): # cry if duplicate name exists QMessageBox.critical(self.window, 'Nagstamon', 'The monitor server name %s is already used.' % (self.ui.input_lineedit_name.text()), QMessageBox.Ok) else: # get configuration from UI for widget in self.ui.__dict__: if widget.startswith('input_'): if widget.startswith('input_checkbox_'): setting = widget.split('input_checkbox_')[1] self.server_conf.__dict__[setting] = self.ui.__dict__[widget].isChecked() elif widget.startswith('input_radiobutton_'): setting = widget.split('input_radiobutton_')[1] self.server_conf.__dict__[setting] = self.ui.__dict__[widget].isChecked() elif widget.startswith('input_combobox_'): setting = widget.split('input_combobox_')[1] self.server_conf.__dict__[setting] = self.ui.__dict__[widget].currentText() elif widget.startswith('input_lineedit_'): setting = widget.split('input_lineedit_')[1] self.server_conf.__dict__[setting] = self.ui.__dict__[widget].text() elif widget.startswith('input_spinbox_'): setting = widget.split('input_spinbox_')[1] self.server_conf.__dict__[setting] = self.ui.__dict__[widget].value() # URLs should not end with / - clean it self.server_conf.monitor_url = self.server_conf.monitor_url.rstrip('/') self.server_conf.monitor_cgi_url = self.server_conf.monitor_cgi_url.rstrip('/') # convert some strings to integers and bools for item in self.server_conf.__dict__: if type(self.server_conf.__dict__[item]) == str: # when item is not one of those which always have to be strings then it might be OK to convert it if not item in CONFIG_STRINGS: if self.server_conf.__dict__[item] in BOOLPOOL: self.server_conf.__dict__[item] = BOOLPOOL[self.server_conf.__dict__[item]] elif self.server_conf.__dict__[item].isdecimal(): self.server_conf.__dict__[item] = int(self.server_conf.__dict__[item]) # store lowered authentication type self.server_conf.authentication = self.server_conf.authentication.lower() # edited servers will be deleted and recreated with new configuration if self.mode == 'edit': # remove old server vbox from status window if still running for vbox in statuswindow.servers_vbox.children(): if vbox.server.name == self.previous_server_conf.name: # disable server vbox.server.enabled = False # stop thread by falsificate running flag vbox.table.worker.running = False vbox.table.worker.finish.emit() # nothing more to do break # delete previous name conf.servers.pop(self.previous_server_conf.name) # delete edited and now not needed server instance - if it exists if self.previous_server_conf.name in servers.keys(): servers.pop(self.previous_server_conf.name) # some monitor servers do not need cgi-url - reuse self.VOLATILE_WIDGETS to find out which one if self.server_conf.type not in self.VOLATILE_WIDGETS[self.ui.input_lineedit_monitor_cgi_url]: self.server_conf.monitor_cgi_url = self.server_conf.monitor_url # add new server configuration in every case conf.servers[self.server_conf.name] = self.server_conf # add new server instance to global servers dict servers[self.server_conf.name] = create_server(self.server_conf) if self.server_conf.enabled is True: servers[self.server_conf.name].enabled = True # create vbox statuswindow.servers_vbox.addLayout(statuswindow.create_ServerVBox(servers[self.server_conf.name])) # renew list of server vboxes in status window statuswindow.sort_ServerVBoxes() # reorder servers in dict to reflect changes servers_freshly_sorted = sorted(servers.items()) servers.clear() servers.update(servers_freshly_sorted) del(servers_freshly_sorted) # refresh list of servers, give call the current server name to highlight it dialogs.settings.refresh_list(list_widget=dialogs.settings.ui.list_servers, list_conf=conf.servers, current=self.server_conf.name) self.window.close() # delete old server .conf file to reflect name changes # new one will be written soon if self.previous_server_conf is not None: conf.delete_file('servers', 'server_{0}'.format(self.previous_server_conf.name)) # store server settings conf.SaveMultipleConfig('servers', 'server') @pyqtSlot() def choose_custom_cert_ca_file(self): """ show dialog for selection of non-default browser """ filter = 'All files (*)' file = dialogs.file_chooser.getOpenFileName(self.window, directory=os.path.expanduser('~'), filter=filter)[0] # only take filename if QFileDialog gave something useful back if file != '': self.ui.input_lineedit_custom_cert_ca_file.setText(file) @pyqtSlot() def check_mk_view_hosts_reset(self): self.ui.input_lineedit_check_mk_view_hosts.setText('nagstamon_hosts') @pyqtSlot() def check_mk_view_services_reset(self): self.ui.input_lineedit_check_mk_view_services.setText('nagstamon_svc') class Dialog_Action(Dialog): """ Dialog used to setup one single action """ # mapping between action types and combobox content ACTION_TYPES = {'browser': 'Browser', 'command': 'Command', 'url': 'URL'} def __init__(self, dialog): Dialog.__init__(self, dialog) # define checkbox-to-widgets dependencies which apply at initialization # which widgets have to be hidden because of irrelevance # dictionary holds checkbox/radiobutton as key and relevant widgets in list self.TOGGLE_DEPS = { self.ui.input_checkbox_re_host_enabled: [self.ui.input_lineedit_re_host_pattern, self.ui.input_checkbox_re_host_reverse], self.ui.input_checkbox_re_service_enabled: [self.ui.input_lineedit_re_service_pattern, self.ui.input_checkbox_re_service_reverse], self.ui.input_checkbox_re_status_information_enabled: [self.ui.input_lineedit_re_status_information_pattern, self.ui.input_checkbox_re_status_information_reverse]} # fill action types into combobox self.ui.input_combobox_type.addItems(sorted(self.ACTION_TYPES.values())) # fill default order fields combobox with monitor server types self.ui.input_combobox_monitor_type.addItem("All monitor servers") self.ui.input_combobox_monitor_type.addItems(sorted(SERVER_TYPES.keys(), key=str.lower)) # default to Nagios as it is the mostly used monitor server self.ui.input_combobox_monitor_type.setCurrentIndex(0) def dialog_decoration(method): """ try with a decorator instead of repeated calls """ # function which decorates method def decoration_function(self): """ self.server_conf has to be set by decorated method """ # previous action conf only useful when editing - defaults to None self.previous_action_conf = None # call decorated method method(self) # run through all input widgets and and apply defaults from config for widget in self.ui.__dict__: if widget.startswith('input_'): if widget.startswith('input_checkbox_'): setting = widget.split('input_checkbox_')[1] self.ui.__dict__[widget].setChecked(self.action_conf.__dict__[setting]) elif widget.startswith('input_radiobutton_'): setting = widget.split('input_radiobutton_')[1] self.ui.__dict__[widget].setChecked(self.action_conf.__dict__[setting]) elif widget.startswith('input_lineedit_'): setting = widget.split('input_lineedit_')[1] self.ui.__dict__[widget].setText(self.action_conf.__dict__[setting]) elif widget.startswith('input_textedit_'): setting = widget.split('input_textedit_')[1] self.ui.__dict__[widget].setText(self.action_conf.__dict__[setting]) # set comboboxes self.ui.input_combobox_type.setCurrentText(self.ACTION_TYPES[self.action_conf.type.lower()]) self.ui.input_combobox_monitor_type.setCurrentText(self.action_conf.monitor_type) # apply toggle-dependencies between checkboxes and certain widgets self.toggle_toggles() # important final size adjustment self.window.adjustSize() # self.window.show() self.window.exec_() # give back decorated function return(decoration_function) @dialog_decoration def new(self): """ create new server """ self.mode = 'new' # create new server config object self.action_conf = Action() # window title might be pretty simple self.window.setWindowTitle('New action') @dialog_decoration def edit(self): """ edit existing action """ self.mode = 'edit' # shorter action conf self.action_conf = conf.actions[dialogs.settings.ui.list_actions.currentItem().text()] # store action name in case it will be changed self.previous_action_conf = deepcopy(self.action_conf) # set window title self.window.setWindowTitle('Edit %s' % (self.action_conf.name)) @dialog_decoration def copy(self): """ copy existing action """ self.mode = 'copy' # shorter action conf self.action_conf = deepcopy(conf.actions[dialogs.settings.ui.list_actions.currentItem().text()]) # set window title before name change to reflect copy self.window.setWindowTitle('Copy %s' % (self.action_conf.name)) # indicate copy of other action self.action_conf.name = 'Copy of ' + self.action_conf.name def ok(self): """ evaluate state of widgets to get new configuration """ # check that no duplicate name exists if self.ui.input_lineedit_name.text() in conf.actions and \ (self.mode in ['new', 'copy'] or self.mode == 'edit' and self.action_conf != conf.actions[self.ui.input_lineedit_name.text()]): # cry if duplicate name exists QMessageBox.critical(self.window, 'Nagstamon', 'The action name %s is already used.' % (self.ui.input_lineedit_name.text()), QMessageBox.Ok) else: # get configuration from UI for widget in self.ui.__dict__: if widget.startswith('input_'): if widget.startswith('input_checkbox_'): setting = widget.split('input_checkbox_')[1] self.action_conf.__dict__[setting] = self.ui.__dict__[widget].isChecked() if widget.startswith('input_radiobutton_'): setting = widget.split('input_radiobutton_')[1] self.action_conf.__dict__[setting] = self.ui.__dict__[widget].isChecked() elif widget.startswith('input_combobox_'): setting = widget.split('input_combobox_')[1] self.action_conf.__dict__[setting] = self.ui.__dict__[widget].currentText() elif widget.startswith('input_lineedit_'): setting = widget.split('input_lineedit_')[1] self.action_conf.__dict__[setting] = self.ui.__dict__[widget].text() elif widget.startswith('input_textedit_'): setting = widget.split('input_textedit_')[1] self.action_conf.__dict__[setting] = self.ui.__dict__[widget].toPlainText() # edited action will be deleted and recreated with new configuration if self.mode == 'edit': # delete previous name conf.actions.pop(self.previous_action_conf.name) # Avoid wrong monitor type which blocks display of action if self.action_conf.monitor_type not in SERVER_TYPES: self.action_conf.monitor_type = '' # lower type to recognize action type on monitor self.action_conf.type = self.action_conf.type.lower() # add edited or new/copied action conf.actions[self.action_conf.name] = self.action_conf # refresh list of actions, give call the current action name to highlight it dialogs.settings.refresh_list(list_widget=dialogs.settings.ui.list_actions, list_conf=conf.actions, current=self.action_conf.name) # delete old action .conf file to reflect name changes # new one will be written soon if self.previous_action_conf is not None: conf.delete_file('actions', 'action_{0}'.format(self.previous_action_conf.name)) # store server settings conf.SaveMultipleConfig("actions", "action") class Dialog_Acknowledge(Dialog): """ Dialog for acknowledging host/service problems """ # store host and service to be used for OK button evaluation server = None host = service = '' # tell worker to acknowledge some troublesome item acknowledge = pyqtSignal(dict) def __init__(self, dialog): Dialog.__init__(self, dialog) def initialize(self, server=None, host='', service=''): # store server, host and service to be used for OK button evaluation self.server = server self.host = host self.service = service # if service is "" it must be a host if service == "": # set label for acknowledging a host self.window.setWindowTitle('Acknowledge host') self.ui.input_label_description.setText('Host %s' % (host)) else: # set label for acknowledging a service on host self.window.setWindowTitle('Acknowledge service') self.ui.input_label_description.setText('Service %s on host %s' % (service, host)) # default flags of monitor acknowledgement self.ui.input_checkbox_sticky_acknowledgement.setChecked(conf.defaults_acknowledge_sticky) self.ui.input_checkbox_send_notification.setChecked(conf.defaults_acknowledge_send_notification) self.ui.input_checkbox_persistent_comment.setChecked(conf.defaults_acknowledge_persistent_comment) self.ui.input_checkbox_acknowledge_all_services.setChecked(conf.defaults_acknowledge_all_services) # default author + comment self.ui.input_lineedit_comment.setText(conf.defaults_acknowledge_comment) self.ui.input_lineedit_comment.setFocus() def ok(self): """ acknowledge miserable host/service """ # create a list of all service of selected host to acknowledge them all all_services = list() acknowledge_all_services = self.ui.input_checkbox_acknowledge_all_services.isChecked() if acknowledge_all_services is True: for i in self.server.nagitems_filtered["services"].values(): for s in i: if s.host == self.host: all_services.append(s.name) # send signal to tablewidget worker to care about acknowledging with supplied information self.acknowledge.emit({'server': self.server, 'host': self.host, 'service': self.service, 'author': self.server.username, 'comment': self.ui.input_lineedit_comment.text(), 'sticky': self.ui.input_checkbox_sticky_acknowledgement.isChecked(), 'notify': self.ui.input_checkbox_send_notification.isChecked(), 'persistent': self.ui.input_checkbox_persistent_comment.isChecked(), 'acknowledge_all_services': acknowledge_all_services, 'all_services': all_services}) class Dialog_Downtime(Dialog): """ Dialog for putting hosts/services into downtime """ # send signal to get start and end of a downtime asynchronously get_start_end = pyqtSignal(str, str) # signal to tell worker to commit downtime downtime = pyqtSignal(dict) # store host and service to be used for OK button evaluation server = None host = service = '' def __init__(self, dialog): Dialog.__init__(self, dialog) def initialize(self, server=None, host='', service=''): # store server, host and service to be used for OK button evaluation self.server = server self.host = host self.service = service # if service is "" it must be a host if service == "": # set label for acknowledging a host self.window.setWindowTitle('Downtime for host') self.ui.input_label_description.setText('Host %s' % (host)) else: # set label for acknowledging a service on host self.window.setWindowTitle('Downtime for service') self.ui.input_label_description.setText('Service %s on host %s' % (service, host)) # default flags of monitor acknowledgement self.ui.input_spinbox_duration_hours.setValue(int(conf.defaults_downtime_duration_hours)) self.ui.input_spinbox_duration_minutes.setValue(int(conf.defaults_downtime_duration_minutes)) self.ui.input_radiobutton_type_fixed.setChecked(conf.defaults_downtime_type_fixed) self.ui.input_radiobutton_type_flexible.setChecked(conf.defaults_downtime_type_flexible) # hide/show downtime settings according to typw self.ui.input_radiobutton_type_fixed.clicked.connect(self.set_type_fixed) self.ui.input_radiobutton_type_flexible.clicked.connect(self.set_type_flexible) # show or hide widgets for time settings if self.ui.input_radiobutton_type_fixed.isChecked(): self.set_type_fixed() else: self.set_type_flexible() # empty times at start, will be filled by set_start_end self.ui.input_lineedit_start_time.setText('n/a') self.ui.input_lineedit_end_time.setText('n/a') # default author + comment self.ui.input_lineedit_comment.setText(conf.defaults_downtime_comment) self.ui.input_lineedit_comment.setFocus() if self.server is not None: # at first initialization server is still None self.get_start_end.emit(self.server.name, self.host) def ok(self): """ schedule downtime for miserable host/service """ # type of downtime - fixed or flexible if self.ui.input_radiobutton_type_fixed.isChecked() is True: fixed = 1 else: fixed = 0 self.downtime.emit({'server': self.server, 'host': self.host, 'service': self.service, 'author': self.server.username, 'comment': self.ui.input_lineedit_comment.text(), 'fixed': fixed, 'start_time': self.ui.input_lineedit_start_time.text(), 'end_time': self.ui.input_lineedit_end_time.text(), 'hours': int(self.ui.input_spinbox_duration_hours.value()), 'minutes': int(self.ui.input_spinbox_duration_minutes.value())}) pyqtSlot(str, str) def set_start_end(self, start, end): """ put values sent by worker into start and end fields """ self.ui.input_lineedit_start_time.setText(start) self.ui.input_lineedit_end_time.setText(end) pyqtSlot() def set_type_fixed(self): """ enable/disable appropriate widgets if type is "Fixed" """ # self.ui.label_start_time.show() # self.ui.label_end_time.show() # self.ui.input_lineedit_start_time.show() # self.ui.input_lineedit_end_time.show() self.ui.label_duration.hide() self.ui.label_duration_hours.hide() self.ui.label_duration_minutes.hide() self.ui.input_spinbox_duration_hours.hide() self.ui.input_spinbox_duration_minutes.hide() pyqtSlot() def set_type_flexible(self): """ enable/disable appropriate widgets if type is "Flexible" """ # self.ui.label_start_time.hide() # self.ui.label_end_time.hide() # self.ui.input_lineedit_start_time.hide() # self.ui.input_lineedit_end_time.hide() self.ui.label_duration.show() self.ui.label_duration_hours.show() self.ui.label_duration_minutes.show() self.ui.input_spinbox_duration_hours.show() self.ui.input_spinbox_duration_minutes.show() class Dialog_Submit(Dialog): """ Dialog for submitting arbitrarily chosen results """ # store host and service to be used for OK button evaluation server = None host = service = '' submit = pyqtSignal(dict) def __init__(self, dialog): Dialog.__init__(self, dialog) def initialize(self, server=None, host='', service=''): # store server, host and service to be used for OK button evaluation self.server = server self.host = host self.service = service # if service is "" it must be a host if service == "": # set label for acknowledging a host self.window.setWindowTitle('Submit check result for host') self.ui.input_label_description.setText('Host %s' % (host)) # services do not need all states self.ui.input_radiobutton_result_up.show() self.ui.input_radiobutton_result_ok.hide() self.ui.input_radiobutton_result_warning.hide() self.ui.input_radiobutton_result_critical.hide() self.ui.input_radiobutton_result_unknown.show() self.ui.input_radiobutton_result_unreachable.show() self.ui.input_radiobutton_result_down.show() # activate first radiobutton self.ui.input_radiobutton_result_up.setChecked(True) else: # set label for acknowledging a service on host self.window.setWindowTitle('Submit check result for service') self.ui.input_label_description.setText('Service %s on host %s' % (service, host)) # hosts do not need all states self.ui.input_radiobutton_result_up.hide() self.ui.input_radiobutton_result_ok.show() self.ui.input_radiobutton_result_warning.show() self.ui.input_radiobutton_result_critical.show() self.ui.input_radiobutton_result_unknown.show() self.ui.input_radiobutton_result_unreachable.hide() self.ui.input_radiobutton_result_down.hide() # activate first radiobutton self.ui.input_radiobutton_result_ok.setChecked(True) # clear text fields self.ui.input_lineedit_check_output.setText('') self.ui.input_lineedit_performance_data.setText('') self.ui.input_lineedit_comment.setText(conf.defaults_submit_check_result_comment) self.ui.input_lineedit_check_output.setFocus() def ok(self): """ submit arbitrary check result """ # default state state = "ok" for button in ["ok", "up", "warning", "critical", "unreachable", "unknown", "down"]: if self.ui.__dict__['input_radiobutton_result_' + button].isChecked(): state = button break # tell worker to submit self.submit.emit({'server': self.server, 'host': self.host, 'service': self.service, 'state': state, 'comment': self.ui.input_lineedit_comment.text(), 'check_output': self.ui.input_lineedit_check_output.text(), 'performance_data': self.ui.input_lineedit_performance_data.text()}) class Dialog_Authentication(Dialog): """ Dialog for authentication """ # store server server = None # signal for telling server_vbox label to update update = pyqtSignal(str) def __init__(self, dialog): Dialog.__init__(self, dialog) def initialize(self): """ setup dialog fitting to server """ if self.server is not None: self.window.setWindowTitle('Authenticate {0}'.format(self.server.name)) if self.server.type == 'Centreon': self.ui.input_checkbox_use_autologin.show() self.ui.input_lineedit_autologin_key.show() self.ui.input_lineedit_autologin_key.show() self.ui.label_autologin_key.show() # enable switching autologin key and password self.ui.input_checkbox_use_autologin.clicked.connect(self.toggle_autologin) self.ui.input_checkbox_use_autologin.setChecked(self.server.use_autologin) self.ui.input_lineedit_autologin_key.setText(self.server.autologin_key) # initialize autologin self.toggle_autologin() else: self.ui.input_checkbox_use_autologin.hide() self.ui.input_lineedit_autologin_key.hide() self.ui.label_autologin_key.hide() # set existing values self.ui.input_lineedit_username.setText(self.server.username) self.ui.input_lineedit_password.setText(self.server.password) self.ui.input_checkbox_save_password.setChecked(conf.servers[self.server.name].save_password) @pyqtSlot(str) def show_auth_dialog(self, server): """ initialize and show authentication dialog """ self.server = servers[server] self.initialize() # workaround instead of sent signal if not statuswindow == None: statuswindow.hide_window() self.window.adjustSize() self.window.exec_() def ok(self): """ Take username and password """ # close window fist to avoid lagging UI self.window.close() self.server.username = self.ui.input_lineedit_username.text() self.server.password = self.ui.input_lineedit_password.text() # store password if it should be saved if self.ui.input_checkbox_save_password.isChecked(): conf.servers[self.server.name].username = self.server.username conf.servers[self.server.name].password = self.server.password conf.servers[self.server.name].save_password = self.ui.input_checkbox_save_password.isChecked() # store server settings conf.SaveMultipleConfig('servers', 'server') # Centreon if self.server.type == 'Centreon': if self.ui.input_checkbox_use_autologin: conf.servers[self.server.name].use_autologin = self.ui.input_checkbox_use_autologin.isChecked() conf.servers[self.server.name].autologin_key = self.ui.input_lineedit_autologin_key.text() # store server settings conf.SaveMultipleConfig('servers', 'server') # reset server connection self.server.reset_HTTP() # force server to recheck right now self.server.thread_counter = conf.update_interval_seconds # update server_vbox label self.update.emit(self.server.name) @pyqtSlot() def toggle_autologin(self): """ toolge autologin option for Centreon """ if self.ui.input_checkbox_use_autologin.isChecked(): self.ui.label_username.hide() self.ui.label_password.hide() self.ui.input_lineedit_username.hide() self.ui.input_lineedit_password.hide() self.ui.input_checkbox_save_password.hide() self.ui.label_autologin_key.show() self.ui.input_lineedit_autologin_key.show() else: self.ui.label_username.show() self.ui.label_password.show() self.ui.input_lineedit_username.show() self.ui.input_lineedit_password.show() self.ui.input_checkbox_save_password.show() self.ui.label_autologin_key.hide() self.ui.input_lineedit_autologin_key.hide() # adjust dialog window size after UI changes self.window.adjustSize() class Dialog_Server_missing(Dialog): """ small dialog to ask about disabled ot not configured servers """ def __init__(self, dialog): Dialog.__init__(self, dialog) # hide dialog when server is to be created or enabled self.ui.button_create_server.clicked.connect(self.window.hide) self.ui.button_enable_server.clicked.connect(self.window.hide) # simply hide window if ignore button chosen self.ui.button_ignore.clicked.connect(self.window.hide) # byebye if exit button was pressed self.ui.button_exit.clicked.connect(self.window.hide) self.ui.button_exit.clicked.connect(exit) def initialize(self, mode='no_server'): """ use dialog for missing and not enabled servers, depending on mode """ if mode == 'no_server': self.ui.label_no_server_configured.show() self.ui.label_no_server_enabled.hide() self.ui.button_enable_server.hide() self.ui.button_create_server.show() else: self.ui.label_no_server_configured.hide() self.ui.label_no_server_enabled.show() self.ui.button_enable_server.show() self.ui.button_create_server.hide() class Dialog_About(Dialog): """ About information dialog """ def __init__(self, dialog): Dialog.__init__(self, dialog) # first add the logo on top - no idea how to achive in Qt Designer logo = QSvgWidget('{0}{1}nagstamon.svg'.format(RESOURCES, os.sep)) logo.setFixedSize(100, 100) self.ui.vbox_about.insertWidget(1, logo, 0, Qt.AlignHCenter) # update version information self.ui.label_nagstamon.setText('

{0} {1}

'.format(AppInfo.NAME, AppInfo.VERSION)) self.ui.label_nagstamon_long.setText('

Nagios¹ status monitor for your desktop') self.ui.label_copyright.setText(AppInfo.COPYRIGHT) self.ui.label_website.setText('{0}'.format(AppInfo.WEBSITE)) self.ui.label_website.setOpenExternalLinks(True) self.ui.label_footnote.setText('¹ plus Check_MK, Op5, Icinga, Centreon and more') # fill in license information license_file = open('{0}{1}LICENSE'.format(RESOURCES, os.sep)) license = license_file.read() license_file.close() self.ui.textedit_license.setPlainText(license) self.ui.textedit_license.setReadOnly(True) # fill in credits information credits_file = open('{0}{1}CREDITS'.format(RESOURCES, os.sep), encoding='utf-8') credits = credits_file.read() credits_file.close() self.ui.textedit_credits.setText(credits) self.ui.textedit_credits.setOpenExternalLinks(True) self.ui.textedit_credits.setReadOnly(True) self.ui.tabs.setCurrentIndex(0) def show(self): self.window.exec_() class MediaPlayer(QObject): """ play media files for notification """ # needed to show error in a thread-safe way send_message = pyqtSignal(str, str) def __init__(self): QObject.__init__(self) self.player = QMediaPlayer(parent=self) self.player.setVolume(100) self.playlist = QMediaPlaylist() self.player.setPlaylist(self.playlist) # let statuswindow show message self.send_message.connect(statuswindow.show_message) # connect with statuswindow notification worker statuswindow.worker_notification.load_sound.connect(self.set_media) statuswindow.worker_notification.play_sound.connect(self.play) @pyqtSlot(str) def set_media(self, file): # only existing file can be played if os.path.exists(file): url = QUrl.fromLocalFile(file) mediacontent = QMediaContent(url) self.player.setMedia(mediacontent) del url, mediacontent return True else: # cry and tell no file was found self.send_message.emit('warning', 'Sound file \'{0}\' not found for playback.'.format(file)) return False @pyqtSlot() def play(self): # just play sound self.player.play() class CheckVersion(QObject): """ checking for updates """ is_checking = False version_info_retrieved = pyqtSignal() @pyqtSlot(bool, QWidget) def check(self, start_mode=False, parent=None): if self.is_checking is False: # lock checking thread self.is_checking = True # list of enabled servers which connections outside should be used to check self.enabled_servers = get_enabled_servers() # set mode to be evaluated by worker self.start_mode = start_mode # store caller of dialog window - not if at start because this will disturb EWMH if start_mode is True: self.parent = None else: self.parent = parent # thread for worker to avoid self.worker_thread = QThread(self) self.worker = self.Worker() # if update check is ready it sends the message to GUI thread self.worker.ready.connect(self.show_message) # stop thread if worker has finished self.worker.finished.connect(self.worker_thread.quit) # reset checking lock if finished self.worker.finished.connect(self.reset_checking) self.worker.moveToThread(self.worker_thread) # run check when thread starts self.worker_thread.started.connect(self.worker.check) self.worker_thread.start(0) @pyqtSlot() def reset_checking(self): """ reset checking flag to avoid QThread crashes """ self.is_checking = False @pyqtSlot(str) def show_message(self, message): """ message dialog must be shown from GUI thread """ self.version_info_retrieved.emit() # QMessageBox.information(self.parent, # 'Nagstamon version check', # message, # QMessageBox.Ok) # attempt to solve https://github.com/HenriWahl/Nagstamon/issues/303 # might be working this time if statuswindow.is_shown: parent = statuswindow else: parent = self.parent messagebox = QMessageBox(QMessageBox.Information, 'Nagstamon version check', message, QMessageBox.Ok, parent, Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint) messagebox.setAttribute(Qt.WA_DeleteOnClose) messagebox.setWindowModality(Qt.NonModal) messagebox.exec() class Worker(QObject): """ check for new version in background """ # send signal if some version information is available ready = pyqtSignal(str) finished = pyqtSignal() def __init__(self): QObject.__init__(self) def check(self): """ check for update using server connection """ # get servers to be used for checking version enabled_servers = get_enabled_servers() # default latest version is 'unavailable' latest_version = 'unavailable' message = 'Cannot reach version check at {0}.'.format(AppInfo.VERSION_URL) # find at least one server which allows to get version information for server in enabled_servers: # retrieve VERSION_URL without auth information response = server.FetchURL(AppInfo.VERSION_URL, giveback='raw', no_auth=True) # stop searching if some valid information has been found if response.error == "" and not response.result.startswith('<'): latest_version = response.result.strip() break # compose message according to version information if latest_version != 'unavailable': if latest_version == AppInfo.VERSION: message = 'You are using the latest version Nagstamon {0}.'.format(AppInfo.VERSION) elif latest_version != AppInfo.VERSION: message = 'The new version Nagstamon {0} is available.

' \ 'Get it at {1}.'.format(latest_version, AppInfo.WEBSITE + '/download') else: message = '' # check if there is anything to tell if message != '': # if run from startup do not cry if any error occured or nothing new is available if check_version.start_mode is False or\ (check_version.start_mode is True and latest_version not in ('unavailable', AppInfo.VERSION)): self.ready.emit(message) # tell thread to finish self.finished.emit() class DBus(QObject): """ Create connection to DBus for desktop notification for Linux/Unix """ open_statuswindow = pyqtSignal() # random ID needed because otherwise all instances of Nagstamon # will get commands by clicking on notification bubble via DBUS random_id = str(int(random.random() * 100000)) def __init__(self): QObject.__init__(self) # get DBUS availability - still possible it does not work due to missing # .sevice file on certain distributions global DBUS_AVAILABLE self.id = 0 self.actions = [('open' + self.random_id), 'Open status window'] self.timeout = 0 # use icon from resources in hints, not the package icon - doesn't work neither self.icon = '' # use Nagstamon image if icon is not available from system # see https://developer.gnome.org/notification-spec/#icons-and-images self.hints = {'image-path': '%s%snagstamon.svg' % (RESOURCES, os.sep)} if not OS in NON_LINUX and DBUS_AVAILABLE: if 'dbus' in sys.modules: # try/except needed because of partly occuring problems with DBUS # see https://github.com/HenriWahl/Nagstamon/issues/320 try: # import dbus # never used dbus_mainloop = DBusQtMainLoop(set_as_default=True) dbus_sessionbus = SessionBus(dbus_mainloop) dbus_object = dbus_sessionbus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications') self.dbus_interface = Interface(dbus_object, dbus_interface='org.freedesktop.Notifications') # connect button to action self.dbus_interface.connect_to_signal('ActionInvoked', self.action_callback) self.connected = True except Exception: traceback.print_exc(file=sys.stdout) self.connected = False else: self.connected = False def show(self, summary, message): """ simply show message """ if self.connected: notification_id = self.dbus_interface.Notify(AppInfo.NAME, self.id, self.icon, summary, message, self.actions, self.hints, self.timeout) # reuse ID self.id = int(notification_id) def action_callback(self, dummy, action): """ react to clicked action button in notification bubble """ if action == 'open' + self.random_id: self.open_statuswindow.emit() def create_brushes(): """ fill static brushes with current colors for treeview """ # if not customized usse default intensity if conf.grid_use_custom_intensity: intensity = 100 + conf.grid_alternation_intensity else: intensity = 115 # every state has 2 labels in both alteration levels 0 and 1 for state in STATES[1:]: for role in ('text', 'background'): QBRUSHES[0][COLORS[state] + role] = QColor(conf.__dict__[COLORS[state] + role]) # if background is too dark to be litten split it into RGB values # and increase them sepeartely # light/darkness spans from 0 to 255 - 30 is just a guess if role == 'background' and conf.show_grid: if QBRUSHES[0][COLORS[state] + role].lightness() < 30: r, g, b, a = (QBRUSHES[0][COLORS[state] + role].getRgb()) r += 30 g += 30 b += 30 QBRUSHES[1][COLORS[state] + role] = QColor(r, g, b).lighter(intensity) else: # otherwise just make it a little bit darker QBRUSHES[1][COLORS[state] + role] = QColor(conf.__dict__[COLORS[state] + role]).darker(intensity) else: # only make background darker; text should stay as it is QBRUSHES[1][COLORS[state] + role] = QBRUSHES[0][COLORS[state] + role] def get_screen(x, y): """ find out which screen the given coordinates belong to gives back 'None' if coordinates are out of any known screen """ # integerify these values as they *might* be strings x = int(x) y = int(y) number_of_screens = desktop.screenCount() for screen in range(number_of_screens + 1): # if coordinates are inside screen just break and return screen if (desktop.screenGeometry(screen).contains(x, y)): break del(x, y) # when 'screen' reached number_of_screens no screen was found, thus return None if screen == number_of_screens: return None else: return screen def get_screen_geometry(screen_number): """ set screen for fullscreen """ number_of_screens = desktop.screenCount() for screen in range(number_of_screens + 1): if screen == screen_number: return desktop.screenGeometry(screen) # if not enough displays available reset to display 0 return desktop.screenGeometry(0) @pyqtSlot() def exit(): """ stop all child threads before quitting instance """ # store position of statuswindow/statusbar statuswindow.store_position_to_conf() # hide statuswindow first ro avoid lag when waiting for finished threads statuswindow.hide() # stop statuswindow worker statuswindow.worker.running = False # save configuration conf.SaveConfig() # tell all treeview threads to stop for server_vbox in statuswindow.servers_vbox.children(): server_vbox.table.worker.finish.emit() # delete all windows for dialog in dialogs.__dict__.values(): try: dialog.window().destroy() except Exception: dialog.window.destroy() statuswindow.destroy() # bye bye APP.instance().quit() def check_servers(): """ check if there are any servers configured and enabled """ # no server is configured if len(servers) == 0: dialogs.server_missing.show() dialogs.server_missing.initialize('no_server') # no server is enabled elif len([x for x in conf.servers.values() if x.enabled is True]) == 0: dialogs.server_missing.show() dialogs.server_missing.initialize('no_server_enabled') # if OS == 'Darwin': # Button = QPushButton # else: # Button = FlatButton # check for updates check_version = CheckVersion() # access to various desktop parameters desktop = APP.desktop() # access to clipboard clipboard = APP.clipboard() # DBus initialization dbus_connection = DBus() # access dialogs dialogs = Dialogs() # system tray icon systrayicon = SystemTrayIcon() # combined statusbar/status window # set to none here due to race condition statuswindow = None statuswindow = StatusWindow() # context menu for statuswindow etc. menu = MenuContext() # necessary extra menu due to Qt5-Unity-integration menu_systray = MenuContextSystrayicon() # versatile mediaplayer mediaplayer = MediaPlayer() Nagstamon/Nagstamon/QUI/dialog_about.py000066400000000000000000000124551316117564000204100ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'dialog_about.ui' # # Created by: PyQt5 UI code generator 5.5.1 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_dialog_about(object): def setupUi(self, dialog_about): dialog_about.setObjectName("dialog_about") dialog_about.resize(500, 300) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(dialog_about.sizePolicy().hasHeightForWidth()) dialog_about.setSizePolicy(sizePolicy) dialog_about.setSizeGripEnabled(True) dialog_about.setModal(True) self.gridLayout = QtWidgets.QGridLayout(dialog_about) self.gridLayout.setObjectName("gridLayout") self.tabs = QtWidgets.QTabWidget(dialog_about) self.tabs.setTabShape(QtWidgets.QTabWidget.Rounded) self.tabs.setObjectName("tabs") self.tab_about = QtWidgets.QWidget() self.tab_about.setObjectName("tab_about") self.vbox_about = QtWidgets.QVBoxLayout(self.tab_about) self.vbox_about.setObjectName("vbox_about") spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.vbox_about.addItem(spacerItem) self.label_nagstamon = QtWidgets.QLabel(self.tab_about) self.label_nagstamon.setAlignment(QtCore.Qt.AlignCenter) self.label_nagstamon.setObjectName("label_nagstamon") self.vbox_about.addWidget(self.label_nagstamon) self.label_nagstamon_long = QtWidgets.QLabel(self.tab_about) self.label_nagstamon_long.setAlignment(QtCore.Qt.AlignCenter) self.label_nagstamon_long.setObjectName("label_nagstamon_long") self.vbox_about.addWidget(self.label_nagstamon_long) self.label_copyright = QtWidgets.QLabel(self.tab_about) self.label_copyright.setAlignment(QtCore.Qt.AlignCenter) self.label_copyright.setObjectName("label_copyright") self.vbox_about.addWidget(self.label_copyright) self.label_website = QtWidgets.QLabel(self.tab_about) self.label_website.setAlignment(QtCore.Qt.AlignCenter) self.label_website.setObjectName("label_website") self.vbox_about.addWidget(self.label_website) spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.vbox_about.addItem(spacerItem1) self.label_footnote = QtWidgets.QLabel(self.tab_about) self.label_footnote.setAlignment(QtCore.Qt.AlignCenter) self.label_footnote.setObjectName("label_footnote") self.vbox_about.addWidget(self.label_footnote) self.tabs.addTab(self.tab_about, "") self.tab_credits = QtWidgets.QWidget() self.tab_credits.setObjectName("tab_credits") self.verticalLayout = QtWidgets.QVBoxLayout(self.tab_credits) self.verticalLayout.setObjectName("verticalLayout") self.textedit_credits = QtWidgets.QTextBrowser(self.tab_credits) self.textedit_credits.setObjectName("textedit_credits") self.verticalLayout.addWidget(self.textedit_credits) self.tabs.addTab(self.tab_credits, "") self.tab_license = QtWidgets.QWidget() self.tab_license.setObjectName("tab_license") self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.tab_license) self.verticalLayout_3.setObjectName("verticalLayout_3") self.textedit_license = QtWidgets.QTextEdit(self.tab_license) self.textedit_license.setObjectName("textedit_license") self.verticalLayout_3.addWidget(self.textedit_license) self.tabs.addTab(self.tab_license, "") self.gridLayout.addWidget(self.tabs, 0, 0, 1, 1) self.buttonBox = QtWidgets.QDialogButtonBox(dialog_about) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Ok) self.buttonBox.setObjectName("buttonBox") self.gridLayout.addWidget(self.buttonBox, 1, 0, 1, 1) self.retranslateUi(dialog_about) self.tabs.setCurrentIndex(0) self.buttonBox.accepted.connect(dialog_about.accept) self.buttonBox.rejected.connect(dialog_about.reject) QtCore.QMetaObject.connectSlotsByName(dialog_about) def retranslateUi(self, dialog_about): _translate = QtCore.QCoreApplication.translate dialog_about.setWindowTitle(_translate("dialog_about", "About Nagstamon")) self.label_nagstamon.setText(_translate("dialog_about", "Nagstamon x")) self.label_nagstamon_long.setText(_translate("dialog_about", "Nagios status monitor")) self.label_copyright.setText(_translate("dialog_about", "©2008-2016 Henri Wahl et al.")) self.label_website.setText(_translate("dialog_about", "https://nagstamon.ifw-dresden.de")) self.label_footnote.setText(_translate("dialog_about", "Footnote")) self.tabs.setTabText(self.tabs.indexOf(self.tab_about), _translate("dialog_about", "About")) self.tabs.setTabText(self.tabs.indexOf(self.tab_credits), _translate("dialog_about", "Credits")) self.tabs.setTabText(self.tabs.indexOf(self.tab_license), _translate("dialog_about", "License")) Nagstamon/Nagstamon/QUI/dialog_about.ui000066400000000000000000000116321316117564000203710ustar00rootroot00000000000000 dialog_about 0 0 500 300 0 0 About Nagstamon true true QTabWidget::Rounded 0 About Qt::Vertical 20 40 Nagstamon x Qt::AlignCenter Nagios status monitor Qt::AlignCenter ©2008-2016 Henri Wahl et al. Qt::AlignCenter https://nagstamon.ifw-dresden.de Qt::AlignCenter Qt::Vertical 20 40 Footnote Qt::AlignCenter Credits License Qt::Horizontal QDialogButtonBox::Ok buttonBox accepted() dialog_about accept() 248 254 157 274 buttonBox rejected() dialog_about reject() 316 260 286 274 Nagstamon/Nagstamon/QUI/dialog_acknowledge.py000066400000000000000000000126511316117564000215570ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'dialog_acknowledge.ui' # # Created by: PyQt5 UI code generator 5.5.1 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_dialog_acknowledge(object): def setupUi(self, dialog_acknowledge): dialog_acknowledge.setObjectName("dialog_acknowledge") dialog_acknowledge.resize(465, 274) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(dialog_acknowledge.sizePolicy().hasHeightForWidth()) dialog_acknowledge.setSizePolicy(sizePolicy) dialog_acknowledge.setSizeGripEnabled(True) dialog_acknowledge.setModal(True) self.gridLayout = QtWidgets.QGridLayout(dialog_acknowledge) self.gridLayout.setObjectName("gridLayout") self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.button_change_defaults_acknowledge = QtWidgets.QPushButton(dialog_acknowledge) self.button_change_defaults_acknowledge.setObjectName("button_change_defaults_acknowledge") self.horizontalLayout.addWidget(self.button_change_defaults_acknowledge) self.button_box = QtWidgets.QDialogButtonBox(dialog_acknowledge) self.button_box.setOrientation(QtCore.Qt.Horizontal) self.button_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) self.button_box.setObjectName("button_box") self.horizontalLayout.addWidget(self.button_box) self.gridLayout.addLayout(self.horizontalLayout, 7, 0, 1, 2) self.options_groupbox = QtWidgets.QGroupBox(dialog_acknowledge) self.options_groupbox.setObjectName("options_groupbox") self.verticalLayout = QtWidgets.QVBoxLayout(self.options_groupbox) self.verticalLayout.setObjectName("verticalLayout") self.input_checkbox_sticky_acknowledgement = QtWidgets.QCheckBox(self.options_groupbox) self.input_checkbox_sticky_acknowledgement.setObjectName("input_checkbox_sticky_acknowledgement") self.verticalLayout.addWidget(self.input_checkbox_sticky_acknowledgement) self.input_checkbox_send_notification = QtWidgets.QCheckBox(self.options_groupbox) self.input_checkbox_send_notification.setObjectName("input_checkbox_send_notification") self.verticalLayout.addWidget(self.input_checkbox_send_notification) self.input_checkbox_persistent_comment = QtWidgets.QCheckBox(self.options_groupbox) self.input_checkbox_persistent_comment.setObjectName("input_checkbox_persistent_comment") self.verticalLayout.addWidget(self.input_checkbox_persistent_comment) self.input_checkbox_acknowledge_all_services = QtWidgets.QCheckBox(self.options_groupbox) self.input_checkbox_acknowledge_all_services.setObjectName("input_checkbox_acknowledge_all_services") self.verticalLayout.addWidget(self.input_checkbox_acknowledge_all_services) self.gridLayout.addWidget(self.options_groupbox, 5, 0, 1, 2) self.input_label_description = QtWidgets.QLabel(dialog_acknowledge) self.input_label_description.setObjectName("input_label_description") self.gridLayout.addWidget(self.input_label_description, 0, 0, 1, 2) self.input_lineedit_comment = QtWidgets.QLineEdit(dialog_acknowledge) self.input_lineedit_comment.setObjectName("input_lineedit_comment") self.gridLayout.addWidget(self.input_lineedit_comment, 4, 0, 1, 1) self.retranslateUi(dialog_acknowledge) self.button_box.accepted.connect(dialog_acknowledge.accept) self.button_box.rejected.connect(dialog_acknowledge.reject) QtCore.QMetaObject.connectSlotsByName(dialog_acknowledge) dialog_acknowledge.setTabOrder(self.input_lineedit_comment, self.input_checkbox_sticky_acknowledgement) dialog_acknowledge.setTabOrder(self.input_checkbox_sticky_acknowledgement, self.input_checkbox_send_notification) dialog_acknowledge.setTabOrder(self.input_checkbox_send_notification, self.input_checkbox_persistent_comment) dialog_acknowledge.setTabOrder(self.input_checkbox_persistent_comment, self.input_checkbox_acknowledge_all_services) dialog_acknowledge.setTabOrder(self.input_checkbox_acknowledge_all_services, self.button_change_defaults_acknowledge) def retranslateUi(self, dialog_acknowledge): _translate = QtCore.QCoreApplication.translate dialog_acknowledge.setWindowTitle(_translate("dialog_acknowledge", "Acknowledge")) self.button_change_defaults_acknowledge.setText(_translate("dialog_acknowledge", "Change acknowledgement defaults...")) self.options_groupbox.setTitle(_translate("dialog_acknowledge", "Options")) self.input_checkbox_sticky_acknowledgement.setText(_translate("dialog_acknowledge", "Sticky acknowledgement")) self.input_checkbox_send_notification.setText(_translate("dialog_acknowledge", "Send notification")) self.input_checkbox_persistent_comment.setText(_translate("dialog_acknowledge", "Persistent comment")) self.input_checkbox_acknowledge_all_services.setText(_translate("dialog_acknowledge", "Acknowledge all services on host")) self.input_label_description.setText(_translate("dialog_acknowledge", "description - set by QUI.py")) Nagstamon/Nagstamon/QUI/dialog_acknowledge.ui000066400000000000000000000076001316117564000215420ustar00rootroot00000000000000 dialog_acknowledge 0 0 465 274 0 0 Acknowledge true true Change acknowledgement defaults... Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok Options Sticky acknowledgement Send notification Persistent comment Acknowledge all services on host description - set by QUI.py input_lineedit_comment input_checkbox_sticky_acknowledgement input_checkbox_send_notification input_checkbox_persistent_comment input_checkbox_acknowledge_all_services button_change_defaults_acknowledge button_box accepted() dialog_acknowledge accept() 248 254 157 274 button_box rejected() dialog_acknowledge reject() 316 260 286 274 Nagstamon/Nagstamon/QUI/dialog_authentication.py000066400000000000000000000106101316117564000223040ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'dialog_authentication.ui' # # Created by: PyQt5 UI code generator 5.5.1 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_dialog_authentication(object): def setupUi(self, dialog_authentication): dialog_authentication.setObjectName("dialog_authentication") dialog_authentication.resize(350, 226) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(dialog_authentication.sizePolicy().hasHeightForWidth()) dialog_authentication.setSizePolicy(sizePolicy) dialog_authentication.setMinimumSize(QtCore.QSize(350, 0)) dialog_authentication.setModal(True) self.gridLayout = QtWidgets.QGridLayout(dialog_authentication) self.gridLayout.setObjectName("gridLayout") self.label_username = QtWidgets.QLabel(dialog_authentication) self.label_username.setObjectName("label_username") self.gridLayout.addWidget(self.label_username, 0, 0, 1, 1) self.label_autologin_key = QtWidgets.QLabel(dialog_authentication) self.label_autologin_key.setObjectName("label_autologin_key") self.gridLayout.addWidget(self.label_autologin_key, 6, 0, 1, 1) self.label_password = QtWidgets.QLabel(dialog_authentication) self.label_password.setObjectName("label_password") self.gridLayout.addWidget(self.label_password, 1, 0, 1, 1) self.input_lineedit_autologin_key = QtWidgets.QLineEdit(dialog_authentication) self.input_lineedit_autologin_key.setObjectName("input_lineedit_autologin_key") self.gridLayout.addWidget(self.input_lineedit_autologin_key, 6, 1, 1, 1) self.input_lineedit_username = QtWidgets.QLineEdit(dialog_authentication) self.input_lineedit_username.setFrame(True) self.input_lineedit_username.setObjectName("input_lineedit_username") self.gridLayout.addWidget(self.input_lineedit_username, 0, 1, 1, 1) self.input_lineedit_password = QtWidgets.QLineEdit(dialog_authentication) self.input_lineedit_password.setMinimumSize(QtCore.QSize(200, 0)) self.input_lineedit_password.setBaseSize(QtCore.QSize(0, 0)) self.input_lineedit_password.setEchoMode(QtWidgets.QLineEdit.Password) self.input_lineedit_password.setObjectName("input_lineedit_password") self.gridLayout.addWidget(self.input_lineedit_password, 1, 1, 1, 1) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.button_box = QtWidgets.QDialogButtonBox(dialog_authentication) self.button_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) self.button_box.setCenterButtons(False) self.button_box.setObjectName("button_box") self.horizontalLayout.addWidget(self.button_box) self.gridLayout.addLayout(self.horizontalLayout, 7, 0, 1, 2) self.input_checkbox_use_autologin = QtWidgets.QCheckBox(dialog_authentication) self.input_checkbox_use_autologin.setObjectName("input_checkbox_use_autologin") self.gridLayout.addWidget(self.input_checkbox_use_autologin, 4, 0, 1, 2) self.input_checkbox_save_password = QtWidgets.QCheckBox(dialog_authentication) self.input_checkbox_save_password.setObjectName("input_checkbox_save_password") self.gridLayout.addWidget(self.input_checkbox_save_password, 3, 0, 1, 2) self.retranslateUi(dialog_authentication) QtCore.QMetaObject.connectSlotsByName(dialog_authentication) def retranslateUi(self, dialog_authentication): _translate = QtCore.QCoreApplication.translate dialog_authentication.setWindowTitle(_translate("dialog_authentication", "Authentication")) self.label_username.setText(_translate("dialog_authentication", "Username:")) self.label_autologin_key.setText(_translate("dialog_authentication", "Autologin key:")) self.label_password.setText(_translate("dialog_authentication", "Password:")) self.input_checkbox_use_autologin.setText(_translate("dialog_authentication", "Use autologin")) self.input_checkbox_save_password.setText(_translate("dialog_authentication", "Save password")) Nagstamon/Nagstamon/QUI/dialog_authentication.ui000066400000000000000000000056751316117564000223100ustar00rootroot00000000000000 dialog_authentication 0 0 350 226 0 0 350 0 Authentication true Username: Autologin key: Password: true 200 0 0 0 QLineEdit::Password QDialogButtonBox::Cancel|QDialogButtonBox::Ok false Use autologin Save password Nagstamon/Nagstamon/QUI/dialog_downtime.py000066400000000000000000000147311316117564000211230ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'dialog_downtime.ui' # # Created by: PyQt5 UI code generator 5.5.1 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_dialog_downtime(object): def setupUi(self, dialog_downtime): dialog_downtime.setObjectName("dialog_downtime") dialog_downtime.resize(409, 294) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(dialog_downtime.sizePolicy().hasHeightForWidth()) dialog_downtime.setSizePolicy(sizePolicy) dialog_downtime.setSizeGripEnabled(True) dialog_downtime.setModal(True) self.gridLayout = QtWidgets.QGridLayout(dialog_downtime) self.gridLayout.setObjectName("gridLayout") self.label_start_time = QtWidgets.QLabel(dialog_downtime) self.label_start_time.setObjectName("label_start_time") self.gridLayout.addWidget(self.label_start_time, 2, 0, 1, 1) spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.gridLayout.addItem(spacerItem, 13, 2, 1, 1) self.input_label_description = QtWidgets.QLabel(dialog_downtime) self.input_label_description.setObjectName("input_label_description") self.gridLayout.addWidget(self.input_label_description, 0, 0, 1, 5) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.button_change_defaults_downtime = QtWidgets.QPushButton(dialog_downtime) self.button_change_defaults_downtime.setObjectName("button_change_defaults_downtime") self.horizontalLayout.addWidget(self.button_change_defaults_downtime) self.button_box = QtWidgets.QDialogButtonBox(dialog_downtime) self.button_box.setOrientation(QtCore.Qt.Horizontal) self.button_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) self.button_box.setObjectName("button_box") self.horizontalLayout.addWidget(self.button_box) self.gridLayout.addLayout(self.horizontalLayout, 18, 0, 2, 5) self.label_duration_hours = QtWidgets.QLabel(dialog_downtime) self.label_duration_hours.setObjectName("label_duration_hours") self.gridLayout.addWidget(self.label_duration_hours, 12, 2, 1, 1) self.input_spinbox_duration_minutes = QtWidgets.QSpinBox(dialog_downtime) self.input_spinbox_duration_minutes.setObjectName("input_spinbox_duration_minutes") self.gridLayout.addWidget(self.input_spinbox_duration_minutes, 12, 3, 1, 1) self.input_lineedit_comment = QtWidgets.QLineEdit(dialog_downtime) self.input_lineedit_comment.setObjectName("input_lineedit_comment") self.gridLayout.addWidget(self.input_lineedit_comment, 1, 0, 1, 5) self.label_duration_minutes = QtWidgets.QLabel(dialog_downtime) self.label_duration_minutes.setObjectName("label_duration_minutes") self.gridLayout.addWidget(self.label_duration_minutes, 12, 4, 1, 1) self.input_spinbox_duration_hours = QtWidgets.QSpinBox(dialog_downtime) self.input_spinbox_duration_hours.setObjectName("input_spinbox_duration_hours") self.gridLayout.addWidget(self.input_spinbox_duration_hours, 12, 1, 1, 1) self.label_duration = QtWidgets.QLabel(dialog_downtime) self.label_duration.setObjectName("label_duration") self.gridLayout.addWidget(self.label_duration, 12, 0, 1, 1) self.label_end_time = QtWidgets.QLabel(dialog_downtime) self.label_end_time.setObjectName("label_end_time") self.gridLayout.addWidget(self.label_end_time, 3, 0, 1, 1) self.input_lineedit_end_time = QtWidgets.QLineEdit(dialog_downtime) self.input_lineedit_end_time.setObjectName("input_lineedit_end_time") self.gridLayout.addWidget(self.input_lineedit_end_time, 3, 1, 1, 4) self.input_lineedit_start_time = QtWidgets.QLineEdit(dialog_downtime) self.input_lineedit_start_time.setObjectName("input_lineedit_start_time") self.gridLayout.addWidget(self.input_lineedit_start_time, 2, 1, 1, 4) self.input_radiobutton_type_flexible = QtWidgets.QRadioButton(dialog_downtime) self.input_radiobutton_type_flexible.setObjectName("input_radiobutton_type_flexible") self.gridLayout.addWidget(self.input_radiobutton_type_flexible, 6, 0, 1, 1) self.input_radiobutton_type_fixed = QtWidgets.QRadioButton(dialog_downtime) self.input_radiobutton_type_fixed.setObjectName("input_radiobutton_type_fixed") self.gridLayout.addWidget(self.input_radiobutton_type_fixed, 5, 0, 1, 1) self.retranslateUi(dialog_downtime) self.button_box.accepted.connect(dialog_downtime.accept) self.button_box.rejected.connect(dialog_downtime.reject) QtCore.QMetaObject.connectSlotsByName(dialog_downtime) dialog_downtime.setTabOrder(self.input_lineedit_comment, self.input_spinbox_duration_hours) dialog_downtime.setTabOrder(self.input_spinbox_duration_hours, self.input_spinbox_duration_minutes) dialog_downtime.setTabOrder(self.input_spinbox_duration_minutes, self.button_change_defaults_downtime) def retranslateUi(self, dialog_downtime): _translate = QtCore.QCoreApplication.translate dialog_downtime.setWindowTitle(_translate("dialog_downtime", "Downtime")) self.label_start_time.setText(_translate("dialog_downtime", "Start time:")) self.input_label_description.setText(_translate("dialog_downtime", "description - set by QUI.py")) self.button_change_defaults_downtime.setText(_translate("dialog_downtime", "Change downtime defaults...")) self.label_duration_hours.setText(_translate("dialog_downtime", "hours")) self.label_duration_minutes.setText(_translate("dialog_downtime", "minutes")) self.label_duration.setText(_translate("dialog_downtime", "Duration:")) self.label_end_time.setText(_translate("dialog_downtime", "End time:")) self.input_lineedit_end_time.setText(_translate("dialog_downtime", "n/a")) self.input_lineedit_start_time.setText(_translate("dialog_downtime", "n/a")) self.input_radiobutton_type_flexible.setText(_translate("dialog_downtime", "Flexible")) self.input_radiobutton_type_fixed.setText(_translate("dialog_downtime", "Fixed")) Nagstamon/Nagstamon/QUI/dialog_downtime.ui000066400000000000000000000115171316117564000211070ustar00rootroot00000000000000 dialog_downtime 0 0 409 294 0 0 Downtime true true Start time: Qt::Vertical 20 40 description - set by QUI.py Change downtime defaults... Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok hours minutes Duration: End time: n/a n/a Flexible Fixed input_lineedit_comment input_spinbox_duration_hours input_spinbox_duration_minutes button_change_defaults_downtime button_box accepted() dialog_downtime accept() 248 254 157 274 button_box rejected() dialog_downtime reject() 316 260 286 274 Nagstamon/Nagstamon/QUI/dialog_server_missing.py000066400000000000000000000070461316117564000223350ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'dialog_server_missing.ui' # # Created by: PyQt5 UI code generator 5.5.1 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_dialog_server_missing(object): def setupUi(self, dialog_server_missing): dialog_server_missing.setObjectName("dialog_server_missing") dialog_server_missing.resize(813, 263) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(dialog_server_missing.sizePolicy().hasHeightForWidth()) dialog_server_missing.setSizePolicy(sizePolicy) dialog_server_missing.setMinimumSize(QtCore.QSize(350, 0)) dialog_server_missing.setModal(True) self.verticalLayout = QtWidgets.QVBoxLayout(dialog_server_missing) self.verticalLayout.setObjectName("verticalLayout") self.label_no_server_configured = QtWidgets.QLabel(dialog_server_missing) self.label_no_server_configured.setWordWrap(True) self.label_no_server_configured.setObjectName("label_no_server_configured") self.verticalLayout.addWidget(self.label_no_server_configured) self.label_no_server_enabled = QtWidgets.QLabel(dialog_server_missing) self.label_no_server_enabled.setWordWrap(True) self.label_no_server_enabled.setObjectName("label_no_server_enabled") self.verticalLayout.addWidget(self.label_no_server_enabled) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.button_enable_server = QtWidgets.QPushButton(dialog_server_missing) self.button_enable_server.setObjectName("button_enable_server") self.horizontalLayout.addWidget(self.button_enable_server) self.button_create_server = QtWidgets.QPushButton(dialog_server_missing) self.button_create_server.setObjectName("button_create_server") self.horizontalLayout.addWidget(self.button_create_server) self.button_ignore = QtWidgets.QPushButton(dialog_server_missing) self.button_ignore.setObjectName("button_ignore") self.horizontalLayout.addWidget(self.button_ignore) self.button_exit = QtWidgets.QPushButton(dialog_server_missing) self.button_exit.setObjectName("button_exit") self.horizontalLayout.addWidget(self.button_exit) self.verticalLayout.addLayout(self.horizontalLayout) self.retranslateUi(dialog_server_missing) QtCore.QMetaObject.connectSlotsByName(dialog_server_missing) def retranslateUi(self, dialog_server_missing): _translate = QtCore.QCoreApplication.translate dialog_server_missing.setWindowTitle(_translate("dialog_server_missing", "Nagstamon")) self.label_no_server_configured.setText(_translate("dialog_server_missing", "

There are no configured servers yet.

")) self.label_no_server_enabled.setText(_translate("dialog_server_missing", "

There are no servers enabled.

")) self.button_enable_server.setText(_translate("dialog_server_missing", "Enable server")) self.button_create_server.setText(_translate("dialog_server_missing", "Create new server")) self.button_ignore.setText(_translate("dialog_server_missing", "Ignore")) self.button_exit.setText(_translate("dialog_server_missing", "Exit")) Nagstamon/Nagstamon/QUI/dialog_server_missing.ui000066400000000000000000000045251316117564000223210ustar00rootroot00000000000000 dialog_server_missing 0 0 813 263 0 0 350 0 Nagstamon true <html><head/><body><p>There are no configured servers yet.<br/></p></body></html> true <html><head/><body><p>There are no servers enabled.<br/></p></body></html> true Enable server Create new server Ignore Exit Nagstamon/Nagstamon/QUI/dialog_submit.py000066400000000000000000000266421316117564000206040ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'dialog_submit.ui' # # Created by: PyQt5 UI code generator 5.5.1 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_dialog_submit(object): def setupUi(self, dialog_submit): dialog_submit.setObjectName("dialog_submit") dialog_submit.resize(473, 449) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(dialog_submit.sizePolicy().hasHeightForWidth()) dialog_submit.setSizePolicy(sizePolicy) dialog_submit.setMaximumSize(QtCore.QSize(16777215, 16777215)) dialog_submit.setSizeGripEnabled(True) dialog_submit.setModal(True) self.gridLayout = QtWidgets.QGridLayout(dialog_submit) self.gridLayout.setObjectName("gridLayout") self.input_lineedit_performance_data = QtWidgets.QLineEdit(dialog_submit) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.input_lineedit_performance_data.sizePolicy().hasHeightForWidth()) self.input_lineedit_performance_data.setSizePolicy(sizePolicy) self.input_lineedit_performance_data.setObjectName("input_lineedit_performance_data") self.gridLayout.addWidget(self.input_lineedit_performance_data, 5, 2, 1, 1) self.label_check_output = QtWidgets.QLabel(dialog_submit) self.label_check_output.setObjectName("label_check_output") self.gridLayout.addWidget(self.label_check_output, 3, 0, 1, 1, QtCore.Qt.AlignVCenter) self.input_lineedit_check_output = QtWidgets.QLineEdit(dialog_submit) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.input_lineedit_check_output.sizePolicy().hasHeightForWidth()) self.input_lineedit_check_output.setSizePolicy(sizePolicy) self.input_lineedit_check_output.setObjectName("input_lineedit_check_output") self.gridLayout.addWidget(self.input_lineedit_check_output, 3, 2, 1, 1) self.label_performance_data = QtWidgets.QLabel(dialog_submit) self.label_performance_data.setObjectName("label_performance_data") self.gridLayout.addWidget(self.label_performance_data, 5, 0, 1, 1, QtCore.Qt.AlignVCenter) self.check_result_groupbox = QtWidgets.QGroupBox(dialog_submit) self.check_result_groupbox.setObjectName("check_result_groupbox") self.verticalLayout = QtWidgets.QVBoxLayout(self.check_result_groupbox) self.verticalLayout.setObjectName("verticalLayout") self.input_radiobutton_result_ok = QtWidgets.QRadioButton(self.check_result_groupbox) self.input_radiobutton_result_ok.setChecked(True) self.input_radiobutton_result_ok.setObjectName("input_radiobutton_result_ok") self.verticalLayout.addWidget(self.input_radiobutton_result_ok) self.input_radiobutton_result_up = QtWidgets.QRadioButton(self.check_result_groupbox) self.input_radiobutton_result_up.setObjectName("input_radiobutton_result_up") self.verticalLayout.addWidget(self.input_radiobutton_result_up) self.input_radiobutton_result_information = QtWidgets.QRadioButton(self.check_result_groupbox) self.input_radiobutton_result_information.setObjectName("input_radiobutton_result_information") self.verticalLayout.addWidget(self.input_radiobutton_result_information) self.input_radiobutton_result_warning = QtWidgets.QRadioButton(self.check_result_groupbox) self.input_radiobutton_result_warning.setObjectName("input_radiobutton_result_warning") self.verticalLayout.addWidget(self.input_radiobutton_result_warning) self.input_radiobutton_result_average = QtWidgets.QRadioButton(self.check_result_groupbox) self.input_radiobutton_result_average.setObjectName("input_radiobutton_result_average") self.verticalLayout.addWidget(self.input_radiobutton_result_average) self.input_radiobutton_result_high = QtWidgets.QRadioButton(self.check_result_groupbox) self.input_radiobutton_result_high.setObjectName("input_radiobutton_result_high") self.verticalLayout.addWidget(self.input_radiobutton_result_high) self.input_radiobutton_result_critical = QtWidgets.QRadioButton(self.check_result_groupbox) self.input_radiobutton_result_critical.setObjectName("input_radiobutton_result_critical") self.verticalLayout.addWidget(self.input_radiobutton_result_critical) self.input_radiobutton_result_disaster = QtWidgets.QRadioButton(self.check_result_groupbox) self.input_radiobutton_result_disaster.setObjectName("input_radiobutton_result_disaster") self.verticalLayout.addWidget(self.input_radiobutton_result_disaster) self.input_radiobutton_result_unreachable = QtWidgets.QRadioButton(self.check_result_groupbox) self.input_radiobutton_result_unreachable.setObjectName("input_radiobutton_result_unreachable") self.verticalLayout.addWidget(self.input_radiobutton_result_unreachable) self.input_radiobutton_result_unknown = QtWidgets.QRadioButton(self.check_result_groupbox) self.input_radiobutton_result_unknown.setObjectName("input_radiobutton_result_unknown") self.verticalLayout.addWidget(self.input_radiobutton_result_unknown) self.input_radiobutton_result_down = QtWidgets.QRadioButton(self.check_result_groupbox) self.input_radiobutton_result_down.setObjectName("input_radiobutton_result_down") self.verticalLayout.addWidget(self.input_radiobutton_result_down) self.gridLayout.addWidget(self.check_result_groupbox, 1, 0, 1, 4) self.label_comment = QtWidgets.QLabel(dialog_submit) self.label_comment.setObjectName("label_comment") self.gridLayout.addWidget(self.label_comment, 7, 0, 1, 1, QtCore.Qt.AlignVCenter) self.input_label_description = QtWidgets.QLabel(dialog_submit) self.input_label_description.setObjectName("input_label_description") self.gridLayout.addWidget(self.input_label_description, 0, 0, 1, 4) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.button_change_defaults_submit_check_result = QtWidgets.QPushButton(dialog_submit) self.button_change_defaults_submit_check_result.setObjectName("button_change_defaults_submit_check_result") self.horizontalLayout.addWidget(self.button_change_defaults_submit_check_result) self.button_box = QtWidgets.QDialogButtonBox(dialog_submit) self.button_box.setOrientation(QtCore.Qt.Horizontal) self.button_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok) self.button_box.setObjectName("button_box") self.horizontalLayout.addWidget(self.button_box) self.gridLayout.addLayout(self.horizontalLayout, 11, 0, 2, 4) self.input_lineedit_comment = QtWidgets.QLineEdit(dialog_submit) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.input_lineedit_comment.sizePolicy().hasHeightForWidth()) self.input_lineedit_comment.setSizePolicy(sizePolicy) self.input_lineedit_comment.setObjectName("input_lineedit_comment") self.gridLayout.addWidget(self.input_lineedit_comment, 7, 2, 1, 1) spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.gridLayout.addItem(spacerItem, 10, 0, 1, 1) self.retranslateUi(dialog_submit) self.button_box.accepted.connect(dialog_submit.accept) self.button_box.rejected.connect(dialog_submit.reject) QtCore.QMetaObject.connectSlotsByName(dialog_submit) dialog_submit.setTabOrder(self.input_radiobutton_result_ok, self.input_radiobutton_result_up) dialog_submit.setTabOrder(self.input_radiobutton_result_ok, self.input_radiobutton_result_information) dialog_submit.setTabOrder(self.input_radiobutton_result_information, self.input_radiobutton_result_warning) dialog_submit.setTabOrder(self.input_radiobutton_result_warning, self.input_radiobutton_result_average) dialog_submit.setTabOrder(self.input_radiobutton_result_average, self.input_radiobutton_result_high) dialog_submit.setTabOrder(self.input_radiobutton_result_high, self.input_radiobutton_result_critical) dialog_submit.setTabOrder(self.input_radiobutton_result_critical, self.input_radiobutton_result_disaster) dialog_submit.setTabOrder(self.input_radiobutton_result_disaster, self.input_radiobutton_result_unreachable) dialog_submit.setTabOrder(self.input_radiobutton_result_unreachable, self.input_radiobutton_result_unknown) dialog_submit.setTabOrder(self.input_radiobutton_result_unknown, self.input_radiobutton_result_down) dialog_submit.setTabOrder(self.input_radiobutton_result_down, self.input_lineedit_check_output) dialog_submit.setTabOrder(self.input_lineedit_check_output, self.input_lineedit_performance_data) dialog_submit.setTabOrder(self.input_lineedit_performance_data, self.input_lineedit_comment) dialog_submit.setTabOrder(self.input_lineedit_comment, self.button_change_defaults_submit_check_result) def retranslateUi(self, dialog_submit): _translate = QtCore.QCoreApplication.translate dialog_submit.setWindowTitle(_translate("dialog_submit", "Submit check result")) self.label_check_output.setText(_translate("dialog_submit", "Check output:")) self.label_performance_data.setText(_translate("dialog_submit", "Performance data:")) self.check_result_groupbox.setTitle(_translate("dialog_submit", "Check result")) self.input_radiobutton_result_ok.setText(_translate("dialog_submit", "OK")) self.input_radiobutton_result_up.setText(_translate("dialog_submit", "UP")) self.input_radiobutton_result_information.setText(_translate("dialog_submit", "INFORMATION")) self.input_radiobutton_result_warning.setText(_translate("dialog_submit", "WARNING")) self.input_radiobutton_result_average.setText(_translate("dialog_submit", "AVERAGE")) self.input_radiobutton_result_high.setText(_translate("dialog_submit", "HIGH")) self.input_radiobutton_result_critical.setText(_translate("dialog_submit", "CRITICAL")) self.input_radiobutton_result_disaster.setText(_translate("dialog_submit", "DISASTER")) self.input_radiobutton_result_unreachable.setText(_translate("dialog_submit", "UNREACHABLE")) self.input_radiobutton_result_unknown.setText(_translate("dialog_submit", "UNKNOWN")) self.input_radiobutton_result_down.setText(_translate("dialog_submit", "DOWN")) self.label_comment.setText(_translate("dialog_submit", "Comment:")) self.input_label_description.setText(_translate("dialog_submit", "description - set by QUI.py")) self.button_change_defaults_submit_check_result.setText(_translate("dialog_submit", "Change submit check result defaults...")) Nagstamon/Nagstamon/QUI/dialog_submit.ui000066400000000000000000000167311316117564000205670ustar00rootroot00000000000000 dialog_submit 0 0 473 449 0 0 16777215 16777215 Submit check result true true 0 0 Check output: 0 0 Performance data: Check result OK true UP INFORMATION WARNING AVERAGE HIGH CRITICAL DISASTER UNREACHABLE UNKNOWN DOWN Comment: description - set by QUI.py Change submit check result defaults... Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok 0 0 Qt::Vertical 20 40 input_radiobutton_result_ok input_radiobutton_result_up input_radiobutton_result_warning input_radiobutton_result_critical input_radiobutton_result_unreachable input_radiobutton_result_unknown input_radiobutton_result_down input_lineedit_check_output input_lineedit_performance_data input_lineedit_comment button_change_defaults_submit_check_result button_box accepted() dialog_submit accept() 248 254 157 274 button_box rejected() dialog_submit reject() 316 260 286 274 Nagstamon/Nagstamon/QUI/settings_action.py000066400000000000000000000376501316117564000211600ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'settings_action.ui' # # Created by: PyQt5 UI code generator 5.8.1 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_settings_action(object): def setupUi(self, settings_action): settings_action.setObjectName("settings_action") settings_action.resize(555, 849) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(settings_action.sizePolicy().hasHeightForWidth()) settings_action.setSizePolicy(sizePolicy) settings_action.setModal(True) self.gridLayout = QtWidgets.QGridLayout(settings_action) self.gridLayout.setObjectName("gridLayout") self.input_radiobutton_close_popwin = QtWidgets.QRadioButton(settings_action) self.input_radiobutton_close_popwin.setObjectName("input_radiobutton_close_popwin") self.gridLayout.addWidget(self.input_radiobutton_close_popwin, 31, 0, 1, 6) self.label_monitor_type = QtWidgets.QLabel(settings_action) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_monitor_type.sizePolicy().hasHeightForWidth()) self.label_monitor_type.setSizePolicy(sizePolicy) self.label_monitor_type.setObjectName("label_monitor_type") self.gridLayout.addWidget(self.label_monitor_type, 3, 0, 1, 1) self.label_target = QtWidgets.QLabel(settings_action) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_target.sizePolicy().hasHeightForWidth()) self.label_target.setSizePolicy(sizePolicy) self.label_target.setObjectName("label_target") self.gridLayout.addWidget(self.label_target, 13, 0, 1, 1) self.input_checkbox_re_host_reverse = QtWidgets.QCheckBox(settings_action) self.input_checkbox_re_host_reverse.setObjectName("input_checkbox_re_host_reverse") self.gridLayout.addWidget(self.input_checkbox_re_host_reverse, 22, 5, 1, 1) self.label_action_type = QtWidgets.QLabel(settings_action) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_action_type.sizePolicy().hasHeightForWidth()) self.label_action_type.setSizePolicy(sizePolicy) self.label_action_type.setObjectName("label_action_type") self.gridLayout.addWidget(self.label_action_type, 1, 0, 1, 1) self.input_checkbox_re_status_information_reverse = QtWidgets.QCheckBox(settings_action) self.input_checkbox_re_status_information_reverse.setObjectName("input_checkbox_re_status_information_reverse") self.gridLayout.addWidget(self.input_checkbox_re_status_information_reverse, 26, 5, 1, 1) self.label_name = QtWidgets.QLabel(settings_action) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_name.sizePolicy().hasHeightForWidth()) self.label_name.setSizePolicy(sizePolicy) self.label_name.setObjectName("label_name") self.gridLayout.addWidget(self.label_name, 4, 0, 1, 1) self.input_lineedit_re_service_pattern = QtWidgets.QLineEdit(settings_action) self.input_lineedit_re_service_pattern.setObjectName("input_lineedit_re_service_pattern") self.gridLayout.addWidget(self.input_lineedit_re_service_pattern, 24, 0, 1, 5) self.input_checkbox_re_service_reverse = QtWidgets.QCheckBox(settings_action) self.input_checkbox_re_service_reverse.setObjectName("input_checkbox_re_service_reverse") self.gridLayout.addWidget(self.input_checkbox_re_service_reverse, 24, 5, 1, 1) self.input_lineedit_description = QtWidgets.QLineEdit(settings_action) self.input_lineedit_description.setObjectName("input_lineedit_description") self.gridLayout.addWidget(self.input_lineedit_description, 5, 1, 1, 5) self.button_box = QtWidgets.QDialogButtonBox(settings_action) self.button_box.setOrientation(QtCore.Qt.Horizontal) self.button_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) self.button_box.setObjectName("button_box") self.gridLayout.addWidget(self.button_box, 34, 0, 1, 6) self.input_lineedit_re_status_information_pattern = QtWidgets.QLineEdit(settings_action) self.input_lineedit_re_status_information_pattern.setObjectName("input_lineedit_re_status_information_pattern") self.gridLayout.addWidget(self.input_lineedit_re_status_information_pattern, 26, 0, 1, 5) self.input_textedit_string = QtWidgets.QTextEdit(settings_action) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.input_textedit_string.sizePolicy().hasHeightForWidth()) self.input_textedit_string.setSizePolicy(sizePolicy) self.input_textedit_string.setObjectName("input_textedit_string") self.gridLayout.addWidget(self.input_textedit_string, 6, 1, 1, 5) self.input_checkbox_filter_target_host = QtWidgets.QCheckBox(settings_action) self.input_checkbox_filter_target_host.setObjectName("input_checkbox_filter_target_host") self.gridLayout.addWidget(self.input_checkbox_filter_target_host, 13, 1, 1, 1) self.label_description = QtWidgets.QLabel(settings_action) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_description.sizePolicy().hasHeightForWidth()) self.label_description.setSizePolicy(sizePolicy) self.label_description.setObjectName("label_description") self.gridLayout.addWidget(self.label_description, 5, 0, 1, 1) self.input_radiobutton_leave_popwin_open = QtWidgets.QRadioButton(settings_action) self.input_radiobutton_leave_popwin_open.setObjectName("input_radiobutton_leave_popwin_open") self.gridLayout.addWidget(self.input_radiobutton_leave_popwin_open, 32, 0, 1, 6) self.label_string = QtWidgets.QLabel(settings_action) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_string.sizePolicy().hasHeightForWidth()) self.label_string.setSizePolicy(sizePolicy) self.label_string.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) self.label_string.setObjectName("label_string") self.gridLayout.addWidget(self.label_string, 6, 0, 1, 1) self.input_lineedit_name = QtWidgets.QLineEdit(settings_action) self.input_lineedit_name.setObjectName("input_lineedit_name") self.gridLayout.addWidget(self.input_lineedit_name, 4, 1, 1, 5) self.input_checkbox_filter_target_service = QtWidgets.QCheckBox(settings_action) self.input_checkbox_filter_target_service.setObjectName("input_checkbox_filter_target_service") self.gridLayout.addWidget(self.input_checkbox_filter_target_service, 14, 1, 1, 1) self.input_combobox_monitor_type = QtWidgets.QComboBox(settings_action) self.input_combobox_monitor_type.setObjectName("input_combobox_monitor_type") self.gridLayout.addWidget(self.input_combobox_monitor_type, 3, 1, 1, 2) self.input_combobox_type = QtWidgets.QComboBox(settings_action) self.input_combobox_type.setObjectName("input_combobox_type") self.gridLayout.addWidget(self.input_combobox_type, 1, 1, 1, 2) self.input_lineedit_re_host_pattern = QtWidgets.QLineEdit(settings_action) self.input_lineedit_re_host_pattern.setObjectName("input_lineedit_re_host_pattern") self.gridLayout.addWidget(self.input_lineedit_re_host_pattern, 22, 0, 1, 5) self.input_checkbox_re_host_enabled = QtWidgets.QCheckBox(settings_action) self.input_checkbox_re_host_enabled.setObjectName("input_checkbox_re_host_enabled") self.gridLayout.addWidget(self.input_checkbox_re_host_enabled, 21, 0, 1, 6) self.input_checkbox_re_service_enabled = QtWidgets.QCheckBox(settings_action) self.input_checkbox_re_service_enabled.setObjectName("input_checkbox_re_service_enabled") self.gridLayout.addWidget(self.input_checkbox_re_service_enabled, 23, 0, 1, 6) self.input_checkbox_re_status_information_enabled = QtWidgets.QCheckBox(settings_action) self.input_checkbox_re_status_information_enabled.setObjectName("input_checkbox_re_status_information_enabled") self.gridLayout.addWidget(self.input_checkbox_re_status_information_enabled, 25, 0, 1, 6) self.label_python_re = QtWidgets.QLabel(settings_action) self.label_python_re.setOpenExternalLinks(True) self.label_python_re.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) self.label_python_re.setObjectName("label_python_re") self.gridLayout.addWidget(self.label_python_re, 27, 0, 1, 6) self.label_status_popup = QtWidgets.QLabel(settings_action) self.label_status_popup.setObjectName("label_status_popup") self.gridLayout.addWidget(self.label_status_popup, 29, 0, 1, 6) self.input_checkbox_recheck = QtWidgets.QCheckBox(settings_action) self.input_checkbox_recheck.setObjectName("input_checkbox_recheck") self.gridLayout.addWidget(self.input_checkbox_recheck, 33, 0, 1, 6) self.input_checkbox_enabled = QtWidgets.QCheckBox(settings_action) self.input_checkbox_enabled.setObjectName("input_checkbox_enabled") self.gridLayout.addWidget(self.input_checkbox_enabled, 0, 0, 1, 6) self.retranslateUi(settings_action) self.button_box.accepted.connect(settings_action.accept) self.button_box.rejected.connect(settings_action.reject) QtCore.QMetaObject.connectSlotsByName(settings_action) settings_action.setTabOrder(self.input_checkbox_enabled, self.input_combobox_type) settings_action.setTabOrder(self.input_combobox_type, self.input_combobox_monitor_type) settings_action.setTabOrder(self.input_combobox_monitor_type, self.input_lineedit_name) settings_action.setTabOrder(self.input_lineedit_name, self.input_lineedit_description) settings_action.setTabOrder(self.input_lineedit_description, self.input_checkbox_filter_target_host) settings_action.setTabOrder(self.input_checkbox_filter_target_host, self.input_checkbox_filter_target_service) settings_action.setTabOrder(self.input_checkbox_filter_target_service, self.input_checkbox_re_host_enabled) settings_action.setTabOrder(self.input_checkbox_re_host_enabled, self.input_lineedit_re_host_pattern) settings_action.setTabOrder(self.input_lineedit_re_host_pattern, self.input_checkbox_re_host_reverse) settings_action.setTabOrder(self.input_checkbox_re_host_reverse, self.input_checkbox_re_service_enabled) settings_action.setTabOrder(self.input_checkbox_re_service_enabled, self.input_lineedit_re_service_pattern) settings_action.setTabOrder(self.input_lineedit_re_service_pattern, self.input_checkbox_re_service_reverse) settings_action.setTabOrder(self.input_checkbox_re_service_reverse, self.input_checkbox_re_status_information_enabled) settings_action.setTabOrder(self.input_checkbox_re_status_information_enabled, self.input_lineedit_re_status_information_pattern) settings_action.setTabOrder(self.input_lineedit_re_status_information_pattern, self.input_checkbox_re_status_information_reverse) settings_action.setTabOrder(self.input_checkbox_re_status_information_reverse, self.input_radiobutton_close_popwin) settings_action.setTabOrder(self.input_radiobutton_close_popwin, self.input_radiobutton_leave_popwin_open) def retranslateUi(self, settings_action): _translate = QtCore.QCoreApplication.translate settings_action.setWindowTitle(_translate("settings_action", "Dialog")) self.input_radiobutton_close_popwin.setText(_translate("settings_action", "Close status popup window after action")) self.label_monitor_type.setText(_translate("settings_action", "Monitor type:")) self.label_target.setText(_translate("settings_action", "Target:")) self.input_checkbox_re_host_reverse.setText(_translate("settings_action", "reverse")) self.label_action_type.setText(_translate("settings_action", "Action type:")) self.input_checkbox_re_status_information_reverse.setText(_translate("settings_action", "reverse")) self.label_name.setText(_translate("settings_action", "Name:")) self.input_checkbox_re_service_reverse.setText(_translate("settings_action", "reverse")) self.input_textedit_string.setToolTip(_translate("settings_action", "Available variables for action strings:\n" "\n" "$HOST$ - host as in monitor\n" "$SERVICE$ - service as in monitor\n" "$MONITOR$ - monitor address\n" "$MONITOR-CGI$ - monitor CGI address\n" "$ADDRESS$ - address of host, delivered from connection method\n" "$USERNAME$ - username on monitor\n" "$STATUS-INFO$ - status information for host or service\n" "$PASSWORD$ - username\'s password on monitor\n" "$COMMENT-ACK$ - default acknowledge comment\n" "$COMMENT-DOWN$ - default downtime comment\n" "$COMMENT-SUBMIT$ - default submit check result comment\n" "\n" "$TRANSID$ - only useful for Check_MK as _transid=$TRANSID$")) self.input_checkbox_filter_target_host.setText(_translate("settings_action", "Host")) self.label_description.setText(_translate("settings_action", "Description:")) self.input_radiobutton_leave_popwin_open.setText(_translate("settings_action", "Leave status popup window open after action")) self.label_string.setText(_translate("settings_action", "String:")) self.input_checkbox_filter_target_service.setText(_translate("settings_action", "Service")) self.input_combobox_type.setToolTip(_translate("settings_action", "Available action types:\n" "\n" "Browser:\n" "Use given string as URL, evaluate variables and open it in your default browser, for example a graph page in monitor.\n" "\n" "Command:\n" "Execute command as given in string and evaluate variables, for example to open SSH connection.\n" "\n" "URL:\n" "Request given URL string in the background, for example to acknowledge a service with one click.\n" "")) self.input_checkbox_re_host_enabled.setText(_translate("settings_action", "Regular expressions for hosts")) self.input_checkbox_re_service_enabled.setText(_translate("settings_action", "Regular expressions for services")) self.input_checkbox_re_status_information_enabled.setText(_translate("settings_action", "Regular expressions for status informations")) self.label_python_re.setText(_translate("settings_action", "See Python Regular Expressions HOWTO for filtering details.")) self.label_status_popup.setText(_translate("settings_action", "Status popup window:")) self.input_checkbox_recheck.setText(_translate("settings_action", "Recheck after action to force result")) self.input_checkbox_enabled.setText(_translate("settings_action", "Enabled")) Nagstamon/Nagstamon/QUI/settings_action.ui000066400000000000000000000250301316117564000211320ustar00rootroot00000000000000 settings_action 0 0 555 849 0 0 Dialog true Close status popup window after action 0 0 Monitor type: 0 0 Target: reverse 0 0 Action type: reverse 0 0 Name: reverse Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok 0 0 Available variables for action strings: $HOST$ - host as in monitor $SERVICE$ - service as in monitor $MONITOR$ - monitor address $MONITOR-CGI$ - monitor CGI address $ADDRESS$ - address of host, delivered from connection method $USERNAME$ - username on monitor $STATUS-INFO$ - status information for host or service $PASSWORD$ - username's password on monitor $COMMENT-ACK$ - default acknowledge comment $COMMENT-DOWN$ - default downtime comment $COMMENT-SUBMIT$ - default submit check result comment $TRANSID$ - only useful for Check_MK as _transid=$TRANSID$ Host 0 0 Description: Leave status popup window open after action 0 0 String: Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop Service Available action types: Browser: Use given string as URL, evaluate variables and open it in your default browser, for example a graph page in monitor. Command: Execute command as given in string and evaluate variables, for example to open SSH connection. URL: Request given URL string in the background, for example to acknowledge a service with one click. Regular expressions for hosts Regular expressions for services Regular expressions for status informations <a href=http://docs.python.org/howto/regex.html>See Python Regular Expressions HOWTO for filtering details.</a> true Qt::TextBrowserInteraction Status popup window: Recheck after action to force result Enabled input_checkbox_enabled input_combobox_type input_combobox_monitor_type input_lineedit_name input_lineedit_description input_checkbox_filter_target_host input_checkbox_filter_target_service input_checkbox_re_host_enabled input_lineedit_re_host_pattern input_checkbox_re_host_reverse input_checkbox_re_service_enabled input_lineedit_re_service_pattern input_checkbox_re_service_reverse input_checkbox_re_status_information_enabled input_lineedit_re_status_information_pattern input_checkbox_re_status_information_reverse input_radiobutton_close_popwin input_radiobutton_leave_popwin_open button_box accepted() settings_action accept() 248 254 157 274 button_box rejected() settings_action reject() 316 260 286 274 Nagstamon/Nagstamon/QUI/settings_main.py000066400000000000000000003723321316117564000206260ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'settings_main.ui' # # Created by: PyQt5 UI code generator 5.8.1 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_settings_main(object): def setupUi(self, settings_main): settings_main.setObjectName("settings_main") settings_main.resize(620, 1109) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(settings_main.sizePolicy().hasHeightForWidth()) settings_main.setSizePolicy(sizePolicy) settings_main.setMaximumSize(QtCore.QSize(16777215, 16777215)) settings_main.setLocale(QtCore.QLocale(QtCore.QLocale.C, QtCore.QLocale.AnyCountry)) settings_main.setModal(True) self.verticalLayout = QtWidgets.QVBoxLayout(settings_main) self.verticalLayout.setObjectName("verticalLayout") self.tabs = QtWidgets.QTabWidget(settings_main) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.tabs.sizePolicy().hasHeightForWidth()) self.tabs.setSizePolicy(sizePolicy) self.tabs.setObjectName("tabs") self.tab_servers = QtWidgets.QWidget() self.tab_servers.setObjectName("tab_servers") self.gridLayout = QtWidgets.QGridLayout(self.tab_servers) self.gridLayout.setContentsMargins(10, 10, 10, 10) self.gridLayout.setSpacing(5) self.gridLayout.setObjectName("gridLayout") self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalLayout_2.setContentsMargins(5, 5, 5, 0) self.horizontalLayout_2.setSpacing(5) self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.input_checkbox_debug_mode = QtWidgets.QCheckBox(self.tab_servers) self.input_checkbox_debug_mode.setObjectName("input_checkbox_debug_mode") self.horizontalLayout_2.addWidget(self.input_checkbox_debug_mode) self.input_checkbox_debug_to_file = QtWidgets.QCheckBox(self.tab_servers) self.input_checkbox_debug_to_file.setEnabled(True) self.input_checkbox_debug_to_file.setObjectName("input_checkbox_debug_to_file") self.horizontalLayout_2.addWidget(self.input_checkbox_debug_to_file) self.input_lineedit_debug_file = QtWidgets.QLineEdit(self.tab_servers) self.input_lineedit_debug_file.setObjectName("input_lineedit_debug_file") self.horizontalLayout_2.addWidget(self.input_lineedit_debug_file) self.gridLayout.addLayout(self.horizontalLayout_2, 3, 1, 1, 3) self.verticalLayout_2 = QtWidgets.QVBoxLayout() self.verticalLayout_2.setContentsMargins(0, 5, 0, 5) self.verticalLayout_2.setSpacing(5) self.verticalLayout_2.setObjectName("verticalLayout_2") self.button_new_server = QtWidgets.QPushButton(self.tab_servers) self.button_new_server.setObjectName("button_new_server") self.verticalLayout_2.addWidget(self.button_new_server) self.button_edit_server = QtWidgets.QPushButton(self.tab_servers) self.button_edit_server.setObjectName("button_edit_server") self.verticalLayout_2.addWidget(self.button_edit_server) self.button_copy_server = QtWidgets.QPushButton(self.tab_servers) self.button_copy_server.setObjectName("button_copy_server") self.verticalLayout_2.addWidget(self.button_copy_server) self.button_delete_server = QtWidgets.QPushButton(self.tab_servers) self.button_delete_server.setObjectName("button_delete_server") self.verticalLayout_2.addWidget(self.button_delete_server) spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.verticalLayout_2.addItem(spacerItem) self.gridLayout.addLayout(self.verticalLayout_2, 0, 3, 1, 1) self.horizontalLayout_3 = QtWidgets.QHBoxLayout() self.horizontalLayout_3.setContentsMargins(5, 5, 5, 5) self.horizontalLayout_3.setSpacing(5) self.horizontalLayout_3.setObjectName("horizontalLayout_3") self.input_checkbox_use_system_keyring = QtWidgets.QCheckBox(self.tab_servers) self.input_checkbox_use_system_keyring.setObjectName("input_checkbox_use_system_keyring") self.horizontalLayout_3.addWidget(self.input_checkbox_use_system_keyring) self.gridLayout.addLayout(self.horizontalLayout_3, 2, 1, 1, 3) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setContentsMargins(5, 5, 5, 5) self.horizontalLayout.setSpacing(5) self.horizontalLayout.setObjectName("horizontalLayout") self.label_update_interval_seconds = QtWidgets.QLabel(self.tab_servers) self.label_update_interval_seconds.setObjectName("label_update_interval_seconds") self.horizontalLayout.addWidget(self.label_update_interval_seconds) self.input_spinbox_update_interval_seconds = QtWidgets.QSpinBox(self.tab_servers) self.input_spinbox_update_interval_seconds.setMaximum(999) self.input_spinbox_update_interval_seconds.setObjectName("input_spinbox_update_interval_seconds") self.horizontalLayout.addWidget(self.input_spinbox_update_interval_seconds) self.label_interval_seconds = QtWidgets.QLabel(self.tab_servers) self.label_interval_seconds.setObjectName("label_interval_seconds") self.horizontalLayout.addWidget(self.label_interval_seconds) spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem1) self.gridLayout.addLayout(self.horizontalLayout, 1, 1, 1, 3) self.horizontalLayout_4 = QtWidgets.QHBoxLayout() self.horizontalLayout_4.setContentsMargins(5, 5, 5, 0) self.horizontalLayout_4.setSpacing(5) self.horizontalLayout_4.setObjectName("horizontalLayout_4") self.input_checkbox_check_for_new_version = QtWidgets.QCheckBox(self.tab_servers) self.input_checkbox_check_for_new_version.setObjectName("input_checkbox_check_for_new_version") self.horizontalLayout_4.addWidget(self.input_checkbox_check_for_new_version) spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_4.addItem(spacerItem2) self.button_check_for_new_version_now = QtWidgets.QPushButton(self.tab_servers) self.button_check_for_new_version_now.setObjectName("button_check_for_new_version_now") self.horizontalLayout_4.addWidget(self.button_check_for_new_version_now) self.gridLayout.addLayout(self.horizontalLayout_4, 4, 1, 1, 3) self.list_servers = QtWidgets.QListWidget(self.tab_servers) self.list_servers.setMinimumSize(QtCore.QSize(0, 200)) self.list_servers.setMaximumSize(QtCore.QSize(16777215, 16777215)) self.list_servers.setResizeMode(QtWidgets.QListView.Adjust) self.list_servers.setObjectName("list_servers") self.gridLayout.addWidget(self.list_servers, 0, 1, 1, 1) spacerItem3 = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.gridLayout.addItem(spacerItem3, 5, 1, 1, 1) self.tabs.addTab(self.tab_servers, "") self.tab_display = QtWidgets.QWidget() self.tab_display.setObjectName("tab_display") self.gridLayout_2 = QtWidgets.QGridLayout(self.tab_display) self.gridLayout_2.setContentsMargins(10, 10, 10, 10) self.gridLayout_2.setSpacing(5) self.gridLayout_2.setObjectName("gridLayout_2") self.groupbox_display_size = QtWidgets.QGroupBox(self.tab_display) self.groupbox_display_size.setObjectName("groupbox_display_size") self.horizontalLayout_5 = QtWidgets.QHBoxLayout(self.groupbox_display_size) self.horizontalLayout_5.setContentsMargins(10, 10, 10, 10) self.horizontalLayout_5.setSpacing(5) self.horizontalLayout_5.setObjectName("horizontalLayout_5") self.gridLayout_14 = QtWidgets.QGridLayout() self.gridLayout_14.setContentsMargins(5, 5, 5, 5) self.gridLayout_14.setSpacing(5) self.gridLayout_14.setObjectName("gridLayout_14") self.input_radiobutton_short_display = QtWidgets.QRadioButton(self.groupbox_display_size) self.input_radiobutton_short_display.setObjectName("input_radiobutton_short_display") self.gridLayout_14.addWidget(self.input_radiobutton_short_display, 0, 1, 1, 1) self.input_radiobutton_long_display = QtWidgets.QRadioButton(self.groupbox_display_size) self.input_radiobutton_long_display.setObjectName("input_radiobutton_long_display") self.gridLayout_14.addWidget(self.input_radiobutton_long_display, 0, 0, 1, 1) self.horizontalLayout_5.addLayout(self.gridLayout_14) self.gridLayout_2.addWidget(self.groupbox_display_size, 0, 0, 1, 1) spacerItem4 = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.gridLayout_2.addItem(spacerItem4, 11, 0, 1, 1) self.groupbox_detailed_summary_popup = QtWidgets.QGroupBox(self.tab_display) self.groupbox_detailed_summary_popup.setObjectName("groupbox_detailed_summary_popup") self.gridLayout_3 = QtWidgets.QGridLayout(self.groupbox_detailed_summary_popup) self.gridLayout_3.setContentsMargins(10, 10, 10, 10) self.gridLayout_3.setSpacing(5) self.gridLayout_3.setObjectName("gridLayout_3") self.gridLayout_13 = QtWidgets.QGridLayout() self.gridLayout_13.setContentsMargins(5, 5, 5, 5) self.gridLayout_13.setSpacing(5) self.gridLayout_13.setObjectName("gridLayout_13") self.input_radiobutton_close_details_clicking = QtWidgets.QRadioButton(self.groupbox_detailed_summary_popup) self.input_radiobutton_close_details_clicking.setObjectName("input_radiobutton_close_details_clicking") self.buttongroup_close_statusbar = QtWidgets.QButtonGroup(settings_main) self.buttongroup_close_statusbar.setObjectName("buttongroup_close_statusbar") self.buttongroup_close_statusbar.addButton(self.input_radiobutton_close_details_clicking) self.gridLayout_13.addWidget(self.input_radiobutton_close_details_clicking, 1, 1, 1, 1) self.input_radiobutton_popup_details_clicking = QtWidgets.QRadioButton(self.groupbox_detailed_summary_popup) self.input_radiobutton_popup_details_clicking.setObjectName("input_radiobutton_popup_details_clicking") self.buttongroup_popup_statuswindow = QtWidgets.QButtonGroup(settings_main) self.buttongroup_popup_statuswindow.setObjectName("buttongroup_popup_statuswindow") self.buttongroup_popup_statuswindow.addButton(self.input_radiobutton_popup_details_clicking) self.gridLayout_13.addWidget(self.input_radiobutton_popup_details_clicking, 1, 0, 1, 1) self.input_radiobutton_popup_details_hover = QtWidgets.QRadioButton(self.groupbox_detailed_summary_popup) self.input_radiobutton_popup_details_hover.setObjectName("input_radiobutton_popup_details_hover") self.buttongroup_popup_statuswindow.addButton(self.input_radiobutton_popup_details_hover) self.gridLayout_13.addWidget(self.input_radiobutton_popup_details_hover, 0, 0, 1, 1) self.input_radiobutton_close_details_hover = QtWidgets.QRadioButton(self.groupbox_detailed_summary_popup) self.input_radiobutton_close_details_hover.setObjectName("input_radiobutton_close_details_hover") self.buttongroup_close_statusbar.addButton(self.input_radiobutton_close_details_hover) self.gridLayout_13.addWidget(self.input_radiobutton_close_details_hover, 0, 1, 1, 1) self.input_radiobutton_close_details_clicking_somewhere = QtWidgets.QRadioButton(self.groupbox_detailed_summary_popup) self.input_radiobutton_close_details_clicking_somewhere.setObjectName("input_radiobutton_close_details_clicking_somewhere") self.buttongroup_close_statusbar.addButton(self.input_radiobutton_close_details_clicking_somewhere) self.gridLayout_13.addWidget(self.input_radiobutton_close_details_clicking_somewhere, 2, 1, 1, 1) self.gridLayout_3.addLayout(self.gridLayout_13, 0, 0, 2, 2) self.input_checkbox_highlight_new_events = QtWidgets.QCheckBox(self.groupbox_detailed_summary_popup) self.input_checkbox_highlight_new_events.setObjectName("input_checkbox_highlight_new_events") self.gridLayout_3.addWidget(self.input_checkbox_highlight_new_events, 6, 0, 1, 2) self.input_checkbox_show_tooltips = QtWidgets.QCheckBox(self.groupbox_detailed_summary_popup) self.input_checkbox_show_tooltips.setObjectName("input_checkbox_show_tooltips") self.gridLayout_3.addWidget(self.input_checkbox_show_tooltips, 7, 0, 1, 2) self.gridLayout_15 = QtWidgets.QGridLayout() self.gridLayout_15.setContentsMargins(5, 5, 5, 5) self.gridLayout_15.setSpacing(5) self.gridLayout_15.setObjectName("gridLayout_15") self.input_combobox_default_sort_field = QtWidgets.QComboBox(self.groupbox_detailed_summary_popup) self.input_combobox_default_sort_field.setObjectName("input_combobox_default_sort_field") self.gridLayout_15.addWidget(self.input_combobox_default_sort_field, 0, 1, 1, 1) self.label_default_sort_field = QtWidgets.QLabel(self.groupbox_detailed_summary_popup) self.label_default_sort_field.setObjectName("label_default_sort_field") self.gridLayout_15.addWidget(self.label_default_sort_field, 0, 0, 1, 1) self.label_default_sort_order = QtWidgets.QLabel(self.groupbox_detailed_summary_popup) self.label_default_sort_order.setObjectName("label_default_sort_order") self.gridLayout_15.addWidget(self.label_default_sort_order, 1, 0, 1, 1) self.input_combobox_default_sort_order = QtWidgets.QComboBox(self.groupbox_detailed_summary_popup) self.input_combobox_default_sort_order.setObjectName("input_combobox_default_sort_order") self.gridLayout_15.addWidget(self.input_combobox_default_sort_order, 1, 1, 1, 1) self.gridLayout_3.addLayout(self.gridLayout_15, 8, 0, 1, 1) self.gridLayout_2.addWidget(self.groupbox_detailed_summary_popup, 10, 0, 1, 1) self.groupbox_appearance = QtWidgets.QGroupBox(self.tab_display) self.groupbox_appearance.setObjectName("groupbox_appearance") self.gridLayout_17 = QtWidgets.QGridLayout(self.groupbox_appearance) self.gridLayout_17.setContentsMargins(10, 10, 10, 10) self.gridLayout_17.setSpacing(5) self.gridLayout_17.setObjectName("gridLayout_17") self.input_radiobutton_icon_in_systray = QtWidgets.QRadioButton(self.groupbox_appearance) self.input_radiobutton_icon_in_systray.setObjectName("input_radiobutton_icon_in_systray") self.gridLayout_17.addWidget(self.input_radiobutton_icon_in_systray, 1, 0, 1, 1) self.input_checkbox_systray_offset_use = QtWidgets.QCheckBox(self.groupbox_appearance) self.input_checkbox_systray_offset_use.setObjectName("input_checkbox_systray_offset_use") self.gridLayout_17.addWidget(self.input_checkbox_systray_offset_use, 7, 0, 1, 2) self.gridLayout_20 = QtWidgets.QGridLayout() self.gridLayout_20.setObjectName("gridLayout_20") self.label_fullscreen_display = QtWidgets.QLabel(self.groupbox_appearance) self.label_fullscreen_display.setObjectName("label_fullscreen_display") self.gridLayout_20.addWidget(self.label_fullscreen_display, 1, 0, 1, 1) self.label_offset_statuswindow = QtWidgets.QLabel(self.groupbox_appearance) self.label_offset_statuswindow.setObjectName("label_offset_statuswindow") self.gridLayout_20.addWidget(self.label_offset_statuswindow, 0, 0, 1, 1) self.input_spinbox_systray_offset = QtWidgets.QSpinBox(self.groupbox_appearance) self.input_spinbox_systray_offset.setObjectName("input_spinbox_systray_offset") self.gridLayout_20.addWidget(self.input_spinbox_systray_offset, 0, 1, 1, 1) self.input_combobox_fullscreen_display = QtWidgets.QComboBox(self.groupbox_appearance) self.input_combobox_fullscreen_display.setObjectName("input_combobox_fullscreen_display") self.gridLayout_20.addWidget(self.input_combobox_fullscreen_display, 1, 1, 1, 1) self.gridLayout_17.addLayout(self.gridLayout_20, 12, 0, 1, 1) self.input_radiobutton_fullscreen = QtWidgets.QRadioButton(self.groupbox_appearance) self.input_radiobutton_fullscreen.setObjectName("input_radiobutton_fullscreen") self.gridLayout_17.addWidget(self.input_radiobutton_fullscreen, 5, 0, 1, 1) self.input_radiobutton_statusbar_floating = QtWidgets.QRadioButton(self.groupbox_appearance) self.input_radiobutton_statusbar_floating.setObjectName("input_radiobutton_statusbar_floating") self.gridLayout_17.addWidget(self.input_radiobutton_statusbar_floating, 0, 0, 1, 1) self.input_radiobutton_windowed = QtWidgets.QRadioButton(self.groupbox_appearance) self.input_radiobutton_windowed.setObjectName("input_radiobutton_windowed") self.gridLayout_17.addWidget(self.input_radiobutton_windowed, 4, 0, 1, 1) self.gridLayout_2.addWidget(self.groupbox_appearance, 2, 0, 1, 1) self.groupbox_font = QtWidgets.QGroupBox(self.tab_display) self.groupbox_font.setObjectName("groupbox_font") self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.groupbox_font) self.verticalLayout_3.setContentsMargins(10, 10, 10, 10) self.verticalLayout_3.setSpacing(5) self.verticalLayout_3.setObjectName("verticalLayout_3") self.verticalLayout_13 = QtWidgets.QVBoxLayout() self.verticalLayout_13.setContentsMargins(5, 5, 5, 5) self.verticalLayout_13.setSpacing(5) self.verticalLayout_13.setObjectName("verticalLayout_13") self.label_font = QtWidgets.QLabel(self.groupbox_font) self.label_font.setFrameShape(QtWidgets.QFrame.Box) self.label_font.setFrameShadow(QtWidgets.QFrame.Sunken) self.label_font.setWordWrap(True) self.label_font.setObjectName("label_font") self.verticalLayout_13.addWidget(self.label_font) self.horizontalLayout_6 = QtWidgets.QHBoxLayout() self.horizontalLayout_6.setContentsMargins(5, 5, 5, 5) self.horizontalLayout_6.setObjectName("horizontalLayout_6") self.button_fontchooser = QtWidgets.QPushButton(self.groupbox_font) self.button_fontchooser.setObjectName("button_fontchooser") self.horizontalLayout_6.addWidget(self.button_fontchooser) self.button_default_font = QtWidgets.QPushButton(self.groupbox_font) self.button_default_font.setObjectName("button_default_font") self.horizontalLayout_6.addWidget(self.button_default_font) self.verticalLayout_13.addLayout(self.horizontalLayout_6) self.verticalLayout_3.addLayout(self.verticalLayout_13) self.gridLayout_2.addWidget(self.groupbox_font, 4, 0, 1, 1) self.tabs.addTab(self.tab_display, "") self.tab_filters = QtWidgets.QWidget() self.tab_filters.setObjectName("tab_filters") self.gridLayout_4 = QtWidgets.QGridLayout(self.tab_filters) self.gridLayout_4.setContentsMargins(10, 10, 10, 10) self.gridLayout_4.setSpacing(5) self.gridLayout_4.setObjectName("gridLayout_4") self.horizontalLayout_9 = QtWidgets.QHBoxLayout() self.horizontalLayout_9.setContentsMargins(5, 5, 5, 5) self.horizontalLayout_9.setSpacing(5) self.horizontalLayout_9.setObjectName("horizontalLayout_9") self.input_lineedit_re_host_pattern = QtWidgets.QLineEdit(self.tab_filters) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.input_lineedit_re_host_pattern.sizePolicy().hasHeightForWidth()) self.input_lineedit_re_host_pattern.setSizePolicy(sizePolicy) self.input_lineedit_re_host_pattern.setObjectName("input_lineedit_re_host_pattern") self.horizontalLayout_9.addWidget(self.input_lineedit_re_host_pattern) self.input_checkbox_re_host_reverse = QtWidgets.QCheckBox(self.tab_filters) self.input_checkbox_re_host_reverse.setObjectName("input_checkbox_re_host_reverse") self.horizontalLayout_9.addWidget(self.input_checkbox_re_host_reverse) self.gridLayout_4.addLayout(self.horizontalLayout_9, 4, 0, 2, 1) self.label_python_re = QtWidgets.QLabel(self.tab_filters) self.label_python_re.setOpenExternalLinks(True) self.label_python_re.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) self.label_python_re.setObjectName("label_python_re") self.gridLayout_4.addWidget(self.label_python_re, 15, 0, 1, 1) spacerItem5 = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.gridLayout_4.addItem(spacerItem5, 16, 0, 1, 1) self.input_checkbox_re_status_information_enabled = QtWidgets.QCheckBox(self.tab_filters) self.input_checkbox_re_status_information_enabled.setObjectName("input_checkbox_re_status_information_enabled") self.gridLayout_4.addWidget(self.input_checkbox_re_status_information_enabled, 11, 0, 1, 1) self.input_checkbox_re_service_enabled = QtWidgets.QCheckBox(self.tab_filters) self.input_checkbox_re_service_enabled.setObjectName("input_checkbox_re_service_enabled") self.gridLayout_4.addWidget(self.input_checkbox_re_service_enabled, 8, 0, 1, 1) self.groupbox_filters = QtWidgets.QGroupBox(self.tab_filters) self.groupbox_filters.setObjectName("groupbox_filters") self.gridLayout_5 = QtWidgets.QGridLayout(self.groupbox_filters) self.gridLayout_5.setContentsMargins(10, 10, 10, 10) self.gridLayout_5.setHorizontalSpacing(10) self.gridLayout_5.setVerticalSpacing(5) self.gridLayout_5.setObjectName("gridLayout_5") self.input_checkbox_filter_all_down_hosts = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_all_down_hosts.setObjectName("input_checkbox_filter_all_down_hosts") self.gridLayout_5.addWidget(self.input_checkbox_filter_all_down_hosts, 0, 0, 1, 1) self.input_checkbox_filter_all_unreachable_hosts = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_all_unreachable_hosts.setObjectName("input_checkbox_filter_all_unreachable_hosts") self.gridLayout_5.addWidget(self.input_checkbox_filter_all_unreachable_hosts, 1, 0, 1, 1) self.input_checkbox_filter_all_flapping_hosts = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_all_flapping_hosts.setObjectName("input_checkbox_filter_all_flapping_hosts") self.gridLayout_5.addWidget(self.input_checkbox_filter_all_flapping_hosts, 2, 0, 1, 1) self.input_checkbox_filter_all_critical_services = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_all_critical_services.setObjectName("input_checkbox_filter_all_critical_services") self.gridLayout_5.addWidget(self.input_checkbox_filter_all_critical_services, 4, 0, 1, 1) self.input_checkbox_filter_acknowledged_hosts_services = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_acknowledged_hosts_services.setObjectName("input_checkbox_filter_acknowledged_hosts_services") self.gridLayout_5.addWidget(self.input_checkbox_filter_acknowledged_hosts_services, 0, 1, 1, 1) self.input_checkbox_filter_hosts_services_disabled_notifications = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_hosts_services_disabled_notifications.setObjectName("input_checkbox_filter_hosts_services_disabled_notifications") self.gridLayout_5.addWidget(self.input_checkbox_filter_hosts_services_disabled_notifications, 1, 1, 1, 1) self.input_checkbox_filter_hosts_services_disabled_checks = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_hosts_services_disabled_checks.setObjectName("input_checkbox_filter_hosts_services_disabled_checks") self.gridLayout_5.addWidget(self.input_checkbox_filter_hosts_services_disabled_checks, 2, 1, 1, 1) self.input_checkbox_filter_hosts_services_maintenance = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_hosts_services_maintenance.setObjectName("input_checkbox_filter_hosts_services_maintenance") self.gridLayout_5.addWidget(self.input_checkbox_filter_hosts_services_maintenance, 3, 1, 1, 1) self.input_checkbox_filter_services_on_acknowledged_hosts = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_services_on_acknowledged_hosts.setObjectName("input_checkbox_filter_services_on_acknowledged_hosts") self.gridLayout_5.addWidget(self.input_checkbox_filter_services_on_acknowledged_hosts, 4, 1, 1, 1) self.input_checkbox_filter_services_on_down_hosts = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_services_on_down_hosts.setObjectName("input_checkbox_filter_services_on_down_hosts") self.gridLayout_5.addWidget(self.input_checkbox_filter_services_on_down_hosts, 5, 1, 1, 1) self.input_checkbox_filter_all_high_services = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_all_high_services.setObjectName("input_checkbox_filter_all_high_services") self.gridLayout_5.addWidget(self.input_checkbox_filter_all_high_services, 13, 0, 1, 1) self.input_checkbox_filter_all_disaster_services = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_all_disaster_services.setObjectName("input_checkbox_filter_all_disaster_services") self.gridLayout_5.addWidget(self.input_checkbox_filter_all_disaster_services, 11, 0, 1, 1) self.input_checkbox_filter_all_flapping_services = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_all_flapping_services.setObjectName("input_checkbox_filter_all_flapping_services") self.gridLayout_5.addWidget(self.input_checkbox_filter_all_flapping_services, 5, 0, 1, 1) self.input_checkbox_filter_all_unknown_services = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_all_unknown_services.setObjectName("input_checkbox_filter_all_unknown_services") self.gridLayout_5.addWidget(self.input_checkbox_filter_all_unknown_services, 6, 0, 1, 1) self.input_checkbox_filter_all_warning_services = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_all_warning_services.setObjectName("input_checkbox_filter_all_warning_services") self.gridLayout_5.addWidget(self.input_checkbox_filter_all_warning_services, 7, 0, 1, 1) self.input_checkbox_filter_all_information_services = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_all_information_services.setObjectName("input_checkbox_filter_all_information_services") self.gridLayout_5.addWidget(self.input_checkbox_filter_all_information_services, 9, 0, 1, 1) self.input_checkbox_filter_all_average_services = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_all_average_services.setObjectName("input_checkbox_filter_all_average_services") self.gridLayout_5.addWidget(self.input_checkbox_filter_all_average_services, 10, 0, 1, 1) self.input_checkbox_filter_services_on_hosts_in_maintenance = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_services_on_hosts_in_maintenance.setObjectName("input_checkbox_filter_services_on_hosts_in_maintenance") self.gridLayout_5.addWidget(self.input_checkbox_filter_services_on_hosts_in_maintenance, 6, 1, 1, 1) self.input_checkbox_filter_services_on_unreachable_hosts = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_services_on_unreachable_hosts.setObjectName("input_checkbox_filter_services_on_unreachable_hosts") self.gridLayout_5.addWidget(self.input_checkbox_filter_services_on_unreachable_hosts, 7, 1, 1, 1) self.input_checkbox_filter_hosts_in_soft_state = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_hosts_in_soft_state.setObjectName("input_checkbox_filter_hosts_in_soft_state") self.gridLayout_5.addWidget(self.input_checkbox_filter_hosts_in_soft_state, 9, 1, 1, 1) self.input_checkbox_filter_services_in_soft_state = QtWidgets.QCheckBox(self.groupbox_filters) self.input_checkbox_filter_services_in_soft_state.setObjectName("input_checkbox_filter_services_in_soft_state") self.gridLayout_5.addWidget(self.input_checkbox_filter_services_in_soft_state, 10, 1, 1, 1) self.gridLayout_4.addWidget(self.groupbox_filters, 1, 0, 1, 1) self.input_checkbox_re_host_enabled = QtWidgets.QCheckBox(self.tab_filters) self.input_checkbox_re_host_enabled.setObjectName("input_checkbox_re_host_enabled") self.gridLayout_4.addWidget(self.input_checkbox_re_host_enabled, 3, 0, 1, 1) self.horizontalLayout_11 = QtWidgets.QHBoxLayout() self.horizontalLayout_11.setContentsMargins(5, 5, 5, 5) self.horizontalLayout_11.setSpacing(5) self.horizontalLayout_11.setObjectName("horizontalLayout_11") self.input_lineedit_re_status_information_pattern = QtWidgets.QLineEdit(self.tab_filters) self.input_lineedit_re_status_information_pattern.setObjectName("input_lineedit_re_status_information_pattern") self.horizontalLayout_11.addWidget(self.input_lineedit_re_status_information_pattern) self.input_checkbox_re_status_information_reverse = QtWidgets.QCheckBox(self.tab_filters) self.input_checkbox_re_status_information_reverse.setObjectName("input_checkbox_re_status_information_reverse") self.horizontalLayout_11.addWidget(self.input_checkbox_re_status_information_reverse) self.gridLayout_4.addLayout(self.horizontalLayout_11, 12, 0, 2, 1) self.horizontalLayout_10 = QtWidgets.QHBoxLayout() self.horizontalLayout_10.setContentsMargins(5, 5, 5, 5) self.horizontalLayout_10.setSpacing(5) self.horizontalLayout_10.setObjectName("horizontalLayout_10") self.input_lineedit_re_service_pattern = QtWidgets.QLineEdit(self.tab_filters) self.input_lineedit_re_service_pattern.setObjectName("input_lineedit_re_service_pattern") self.horizontalLayout_10.addWidget(self.input_lineedit_re_service_pattern) self.input_checkbox_re_service_reverse = QtWidgets.QCheckBox(self.tab_filters) self.input_checkbox_re_service_reverse.setObjectName("input_checkbox_re_service_reverse") self.horizontalLayout_10.addWidget(self.input_checkbox_re_service_reverse) self.gridLayout_4.addLayout(self.horizontalLayout_10, 9, 0, 2, 1) self.tabs.addTab(self.tab_filters, "") self.tab_actions = QtWidgets.QWidget() self.tab_actions.setObjectName("tab_actions") self.gridLayout_6 = QtWidgets.QGridLayout(self.tab_actions) self.gridLayout_6.setContentsMargins(10, 10, 10, 10) self.gridLayout_6.setSpacing(5) self.gridLayout_6.setObjectName("gridLayout_6") self.list_actions = QtWidgets.QListWidget(self.tab_actions) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.list_actions.sizePolicy().hasHeightForWidth()) self.list_actions.setSizePolicy(sizePolicy) self.list_actions.setMinimumSize(QtCore.QSize(0, 200)) self.list_actions.setResizeMode(QtWidgets.QListView.Adjust) self.list_actions.setObjectName("list_actions") self.gridLayout_6.addWidget(self.list_actions, 0, 0, 1, 1) self.verticalLayout_4 = QtWidgets.QVBoxLayout() self.verticalLayout_4.setContentsMargins(0, 5, 0, 5) self.verticalLayout_4.setSpacing(5) self.verticalLayout_4.setObjectName("verticalLayout_4") self.button_new_action = QtWidgets.QPushButton(self.tab_actions) self.button_new_action.setObjectName("button_new_action") self.verticalLayout_4.addWidget(self.button_new_action) self.button_edit_action = QtWidgets.QPushButton(self.tab_actions) self.button_edit_action.setObjectName("button_edit_action") self.verticalLayout_4.addWidget(self.button_edit_action) self.button_copy_action = QtWidgets.QPushButton(self.tab_actions) self.button_copy_action.setObjectName("button_copy_action") self.verticalLayout_4.addWidget(self.button_copy_action) self.button_delete_action = QtWidgets.QPushButton(self.tab_actions) self.button_delete_action.setObjectName("button_delete_action") self.verticalLayout_4.addWidget(self.button_delete_action) spacerItem6 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.verticalLayout_4.addItem(spacerItem6) self.gridLayout_6.addLayout(self.verticalLayout_4, 0, 2, 1, 1) self.groupbox_connection_method = QtWidgets.QGroupBox(self.tab_actions) self.groupbox_connection_method.setObjectName("groupbox_connection_method") self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.groupbox_connection_method) self.verticalLayout_5.setContentsMargins(10, 10, 10, 10) self.verticalLayout_5.setSpacing(5) self.verticalLayout_5.setObjectName("verticalLayout_5") self.input_radiobutton_connect_by_host = QtWidgets.QRadioButton(self.groupbox_connection_method) self.input_radiobutton_connect_by_host.setObjectName("input_radiobutton_connect_by_host") self.verticalLayout_5.addWidget(self.input_radiobutton_connect_by_host) self.input_radiobutton_connect_by_dns = QtWidgets.QRadioButton(self.groupbox_connection_method) self.input_radiobutton_connect_by_dns.setObjectName("input_radiobutton_connect_by_dns") self.verticalLayout_5.addWidget(self.input_radiobutton_connect_by_dns) self.input_radiobutton_connect_by_ip = QtWidgets.QRadioButton(self.groupbox_connection_method) self.input_radiobutton_connect_by_ip.setObjectName("input_radiobutton_connect_by_ip") self.verticalLayout_5.addWidget(self.input_radiobutton_connect_by_ip) self.gridLayout_6.addWidget(self.groupbox_connection_method, 1, 0, 1, 1) spacerItem7 = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.gridLayout_6.addItem(spacerItem7, 3, 0, 1, 1) self.groupbox_browser = QtWidgets.QGroupBox(self.tab_actions) self.groupbox_browser.setObjectName("groupbox_browser") self.verticalLayout_14 = QtWidgets.QVBoxLayout(self.groupbox_browser) self.verticalLayout_14.setContentsMargins(10, 10, 10, 10) self.verticalLayout_14.setSpacing(5) self.verticalLayout_14.setObjectName("verticalLayout_14") self.input_radiobutton_use_default_browser = QtWidgets.QRadioButton(self.groupbox_browser) self.input_radiobutton_use_default_browser.setObjectName("input_radiobutton_use_default_browser") self.verticalLayout_14.addWidget(self.input_radiobutton_use_default_browser) self.input_radiobutton_use_custom_browser = QtWidgets.QRadioButton(self.groupbox_browser) self.input_radiobutton_use_custom_browser.setObjectName("input_radiobutton_use_custom_browser") self.verticalLayout_14.addWidget(self.input_radiobutton_use_custom_browser) self.groupbox_custom_browser = QtWidgets.QGroupBox(self.groupbox_browser) self.groupbox_custom_browser.setObjectName("groupbox_custom_browser") self.gridLayout_19 = QtWidgets.QGridLayout(self.groupbox_custom_browser) self.gridLayout_19.setContentsMargins(10, 10, 10, 10) self.gridLayout_19.setSpacing(5) self.gridLayout_19.setObjectName("gridLayout_19") self.input_lineedit_custom_browser = QtWidgets.QLineEdit(self.groupbox_custom_browser) self.input_lineedit_custom_browser.setObjectName("input_lineedit_custom_browser") self.gridLayout_19.addWidget(self.input_lineedit_custom_browser, 0, 0, 1, 1) self.button_choose_browser = QtWidgets.QPushButton(self.groupbox_custom_browser) self.button_choose_browser.setObjectName("button_choose_browser") self.gridLayout_19.addWidget(self.button_choose_browser, 0, 1, 1, 1) self.verticalLayout_14.addWidget(self.groupbox_custom_browser) self.gridLayout_6.addWidget(self.groupbox_browser, 2, 0, 1, 1) self.tabs.addTab(self.tab_actions, "") self.tab_notifications = QtWidgets.QWidget() self.tab_notifications.setObjectName("tab_notifications") self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.tab_notifications) self.verticalLayout_6.setContentsMargins(10, 10, 10, 10) self.verticalLayout_6.setSpacing(5) self.verticalLayout_6.setObjectName("verticalLayout_6") self.input_checkbox_notification = QtWidgets.QCheckBox(self.tab_notifications) self.input_checkbox_notification.setObjectName("input_checkbox_notification") self.verticalLayout_6.addWidget(self.input_checkbox_notification) self.notification_groupbox = QtWidgets.QGroupBox(self.tab_notifications) self.notification_groupbox.setObjectName("notification_groupbox") self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.notification_groupbox) self.verticalLayout_7.setContentsMargins(10, 10, 10, 10) self.verticalLayout_7.setSpacing(5) self.verticalLayout_7.setObjectName("verticalLayout_7") self.horizontalLayout_notifications = QtWidgets.QHBoxLayout() self.horizontalLayout_notifications.setContentsMargins(0, 0, 0, 0) self.horizontalLayout_notifications.setSpacing(5) self.horizontalLayout_notifications.setObjectName("horizontalLayout_notifications") self.input_checkbox_notify_if_warning = QtWidgets.QCheckBox(self.notification_groupbox) self.input_checkbox_notify_if_warning.setObjectName("input_checkbox_notify_if_warning") self.horizontalLayout_notifications.addWidget(self.input_checkbox_notify_if_warning) self.input_checkbox_notify_if_critical = QtWidgets.QCheckBox(self.notification_groupbox) self.input_checkbox_notify_if_critical.setObjectName("input_checkbox_notify_if_critical") self.horizontalLayout_notifications.addWidget(self.input_checkbox_notify_if_critical) self.input_checkbox_notify_if_unknown = QtWidgets.QCheckBox(self.notification_groupbox) self.input_checkbox_notify_if_unknown.setObjectName("input_checkbox_notify_if_unknown") self.horizontalLayout_notifications.addWidget(self.input_checkbox_notify_if_unknown) self.input_checkbox_notify_if_unreachable = QtWidgets.QCheckBox(self.notification_groupbox) self.input_checkbox_notify_if_unreachable.setObjectName("input_checkbox_notify_if_unreachable") self.horizontalLayout_notifications.addWidget(self.input_checkbox_notify_if_unreachable) self.input_checkbox_notify_if_down = QtWidgets.QCheckBox(self.notification_groupbox) self.input_checkbox_notify_if_down.setObjectName("input_checkbox_notify_if_down") self.horizontalLayout_notifications.addWidget(self.input_checkbox_notify_if_down) spacerItem8 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_notifications.addItem(spacerItem8) self.verticalLayout_7.addLayout(self.horizontalLayout_notifications) self.horizontalLayout_notifications_zabbix = QtWidgets.QHBoxLayout() self.horizontalLayout_notifications_zabbix.setObjectName("horizontalLayout_notifications_zabbix") self.input_checkbox_notify_if_information = QtWidgets.QCheckBox(self.notification_groupbox) self.input_checkbox_notify_if_information.setObjectName("input_checkbox_notify_if_information") self.horizontalLayout_notifications_zabbix.addWidget(self.input_checkbox_notify_if_information) self.input_checkbox_notify_if_disaster = QtWidgets.QCheckBox(self.notification_groupbox) self.input_checkbox_notify_if_disaster.setObjectName("input_checkbox_notify_if_disaster") self.horizontalLayout_notifications_zabbix.addWidget(self.input_checkbox_notify_if_disaster) self.input_checkbox_notify_if_average = QtWidgets.QCheckBox(self.notification_groupbox) self.input_checkbox_notify_if_average.setObjectName("input_checkbox_notify_if_average") self.horizontalLayout_notifications_zabbix.addWidget(self.input_checkbox_notify_if_average) self.input_checkbox_notify_if_high = QtWidgets.QCheckBox(self.notification_groupbox) self.input_checkbox_notify_if_high.setObjectName("input_checkbox_notify_if_high") self.horizontalLayout_notifications_zabbix.addWidget(self.input_checkbox_notify_if_high) spacerItem9 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_notifications_zabbix.addItem(spacerItem9) self.verticalLayout_7.addLayout(self.horizontalLayout_notifications_zabbix) self.input_checkbox_notification_flashing = QtWidgets.QCheckBox(self.notification_groupbox) self.input_checkbox_notification_flashing.setObjectName("input_checkbox_notification_flashing") self.verticalLayout_7.addWidget(self.input_checkbox_notification_flashing) self.input_checkbox_notification_desktop = QtWidgets.QCheckBox(self.notification_groupbox) self.input_checkbox_notification_desktop.setObjectName("input_checkbox_notification_desktop") self.verticalLayout_7.addWidget(self.input_checkbox_notification_desktop) self.input_checkbox_notification_sound = QtWidgets.QCheckBox(self.notification_groupbox) self.input_checkbox_notification_sound.setObjectName("input_checkbox_notification_sound") self.verticalLayout_7.addWidget(self.input_checkbox_notification_sound) self.notification_sounds_groupbox = QtWidgets.QGroupBox(self.notification_groupbox) self.notification_sounds_groupbox.setObjectName("notification_sounds_groupbox") self.gridLayout_16 = QtWidgets.QGridLayout(self.notification_sounds_groupbox) self.gridLayout_16.setContentsMargins(10, 10, 10, 10) self.gridLayout_16.setSpacing(5) self.gridLayout_16.setObjectName("gridLayout_16") self.notification_custom_sounds_groupbox = QtWidgets.QGroupBox(self.notification_sounds_groupbox) self.notification_custom_sounds_groupbox.setObjectName("notification_custom_sounds_groupbox") self.gridLayout_8 = QtWidgets.QGridLayout(self.notification_custom_sounds_groupbox) self.gridLayout_8.setContentsMargins(10, 10, 10, 10) self.gridLayout_8.setSpacing(5) self.gridLayout_8.setObjectName("gridLayout_8") self.button_choose_critical = QtWidgets.QPushButton(self.notification_custom_sounds_groupbox) self.button_choose_critical.setObjectName("button_choose_critical") self.gridLayout_8.addWidget(self.button_choose_critical, 1, 2, 1, 1) self.button_play_warning = QtWidgets.QPushButton(self.notification_custom_sounds_groupbox) self.button_play_warning.setObjectName("button_play_warning") self.gridLayout_8.addWidget(self.button_play_warning, 0, 3, 1, 1) self.label_notification_sound_options_custom_sounds_warning = QtWidgets.QLabel(self.notification_custom_sounds_groupbox) self.label_notification_sound_options_custom_sounds_warning.setObjectName("label_notification_sound_options_custom_sounds_warning") self.gridLayout_8.addWidget(self.label_notification_sound_options_custom_sounds_warning, 0, 0, 1, 1) self.label_notification_sound_options_custom_sounds_critical = QtWidgets.QLabel(self.notification_custom_sounds_groupbox) self.label_notification_sound_options_custom_sounds_critical.setObjectName("label_notification_sound_options_custom_sounds_critical") self.gridLayout_8.addWidget(self.label_notification_sound_options_custom_sounds_critical, 1, 0, 1, 1) self.button_choose_down = QtWidgets.QPushButton(self.notification_custom_sounds_groupbox) self.button_choose_down.setObjectName("button_choose_down") self.gridLayout_8.addWidget(self.button_choose_down, 2, 2, 1, 1) self.button_choose_warning = QtWidgets.QPushButton(self.notification_custom_sounds_groupbox) self.button_choose_warning.setObjectName("button_choose_warning") self.gridLayout_8.addWidget(self.button_choose_warning, 0, 2, 1, 1) self.button_play_critical = QtWidgets.QPushButton(self.notification_custom_sounds_groupbox) self.button_play_critical.setObjectName("button_play_critical") self.gridLayout_8.addWidget(self.button_play_critical, 1, 3, 1, 1) self.label_notification_sound_options_custom_sounds_down = QtWidgets.QLabel(self.notification_custom_sounds_groupbox) self.label_notification_sound_options_custom_sounds_down.setObjectName("label_notification_sound_options_custom_sounds_down") self.gridLayout_8.addWidget(self.label_notification_sound_options_custom_sounds_down, 2, 0, 1, 1) self.button_play_down = QtWidgets.QPushButton(self.notification_custom_sounds_groupbox) self.button_play_down.setObjectName("button_play_down") self.gridLayout_8.addWidget(self.button_play_down, 2, 3, 1, 1) self.input_lineedit_notification_custom_sound_warning = QtWidgets.QLineEdit(self.notification_custom_sounds_groupbox) self.input_lineedit_notification_custom_sound_warning.setObjectName("input_lineedit_notification_custom_sound_warning") self.gridLayout_8.addWidget(self.input_lineedit_notification_custom_sound_warning, 0, 1, 1, 1) self.input_lineedit_notification_custom_sound_critical = QtWidgets.QLineEdit(self.notification_custom_sounds_groupbox) self.input_lineedit_notification_custom_sound_critical.setObjectName("input_lineedit_notification_custom_sound_critical") self.gridLayout_8.addWidget(self.input_lineedit_notification_custom_sound_critical, 1, 1, 1, 1) self.input_lineedit_notification_custom_sound_down = QtWidgets.QLineEdit(self.notification_custom_sounds_groupbox) self.input_lineedit_notification_custom_sound_down.setObjectName("input_lineedit_notification_custom_sound_down") self.gridLayout_8.addWidget(self.input_lineedit_notification_custom_sound_down, 2, 1, 1, 1) self.gridLayout_16.addWidget(self.notification_custom_sounds_groupbox, 3, 0, 1, 2) self.input_checkbox_notification_sound_repeat = QtWidgets.QCheckBox(self.notification_sounds_groupbox) self.input_checkbox_notification_sound_repeat.setObjectName("input_checkbox_notification_sound_repeat") self.gridLayout_16.addWidget(self.input_checkbox_notification_sound_repeat, 0, 0, 1, 2) self.input_radiobutton_notification_default_sound = QtWidgets.QRadioButton(self.notification_sounds_groupbox) self.input_radiobutton_notification_default_sound.setObjectName("input_radiobutton_notification_default_sound") self.gridLayout_16.addWidget(self.input_radiobutton_notification_default_sound, 1, 0, 1, 2) self.input_radiobutton_notification_custom_sound = QtWidgets.QRadioButton(self.notification_sounds_groupbox) self.input_radiobutton_notification_custom_sound.setObjectName("input_radiobutton_notification_custom_sound") self.gridLayout_16.addWidget(self.input_radiobutton_notification_custom_sound, 2, 0, 1, 2) self.verticalLayout_7.addWidget(self.notification_sounds_groupbox) self.input_checkbox_notification_actions = QtWidgets.QCheckBox(self.notification_groupbox) self.input_checkbox_notification_actions.setObjectName("input_checkbox_notification_actions") self.verticalLayout_7.addWidget(self.input_checkbox_notification_actions) self.notification_actions_groupbox = QtWidgets.QGroupBox(self.notification_groupbox) self.notification_actions_groupbox.setObjectName("notification_actions_groupbox") self.gridLayout_11 = QtWidgets.QGridLayout(self.notification_actions_groupbox) self.gridLayout_11.setContentsMargins(10, 10, 10, 10) self.gridLayout_11.setSpacing(5) self.gridLayout_11.setObjectName("gridLayout_11") self.input_lineedit_notification_action_ok_string = QtWidgets.QLineEdit(self.notification_actions_groupbox) self.input_lineedit_notification_action_ok_string.setObjectName("input_lineedit_notification_action_ok_string") self.gridLayout_11.addWidget(self.input_lineedit_notification_action_ok_string, 3, 2, 1, 1) self.input_checkbox_notification_action_critical = QtWidgets.QCheckBox(self.notification_actions_groupbox) self.input_checkbox_notification_action_critical.setObjectName("input_checkbox_notification_action_critical") self.gridLayout_11.addWidget(self.input_checkbox_notification_action_critical, 1, 0, 1, 1) self.input_checkbox_notification_action_down = QtWidgets.QCheckBox(self.notification_actions_groupbox) self.input_checkbox_notification_action_down.setObjectName("input_checkbox_notification_action_down") self.gridLayout_11.addWidget(self.input_checkbox_notification_action_down, 2, 0, 1, 1) self.input_checkbox_notification_action_ok = QtWidgets.QCheckBox(self.notification_actions_groupbox) self.input_checkbox_notification_action_ok.setObjectName("input_checkbox_notification_action_ok") self.gridLayout_11.addWidget(self.input_checkbox_notification_action_ok, 3, 0, 1, 1) self.input_checkbox_notification_action_warning = QtWidgets.QCheckBox(self.notification_actions_groupbox) self.input_checkbox_notification_action_warning.setObjectName("input_checkbox_notification_action_warning") self.gridLayout_11.addWidget(self.input_checkbox_notification_action_warning, 0, 0, 1, 1) self.input_lineedit_notification_action_down_string = QtWidgets.QLineEdit(self.notification_actions_groupbox) self.input_lineedit_notification_action_down_string.setObjectName("input_lineedit_notification_action_down_string") self.gridLayout_11.addWidget(self.input_lineedit_notification_action_down_string, 2, 2, 1, 1) self.input_checkbox_notification_custom_action = QtWidgets.QCheckBox(self.notification_actions_groupbox) self.input_checkbox_notification_custom_action.setObjectName("input_checkbox_notification_custom_action") self.gridLayout_11.addWidget(self.input_checkbox_notification_custom_action, 4, 0, 1, 3) self.notification_custom_action_groupbox = QtWidgets.QGroupBox(self.notification_actions_groupbox) self.notification_custom_action_groupbox.setObjectName("notification_custom_action_groupbox") self.gridLayout_7 = QtWidgets.QGridLayout(self.notification_custom_action_groupbox) self.gridLayout_7.setContentsMargins(10, 10, 10, 10) self.gridLayout_7.setSpacing(5) self.gridLayout_7.setObjectName("gridLayout_7") self.label_notification_custom_action_string = QtWidgets.QLabel(self.notification_custom_action_groupbox) self.label_notification_custom_action_string.setObjectName("label_notification_custom_action_string") self.gridLayout_7.addWidget(self.label_notification_custom_action_string, 1, 0, 1, 1) self.input_checkbox_notification_custom_action_single = QtWidgets.QCheckBox(self.notification_custom_action_groupbox) self.input_checkbox_notification_custom_action_single.setObjectName("input_checkbox_notification_custom_action_single") self.gridLayout_7.addWidget(self.input_checkbox_notification_custom_action_single, 3, 0, 1, 2) self.label_notification_custom_action_separator = QtWidgets.QLabel(self.notification_custom_action_groupbox) self.label_notification_custom_action_separator.setObjectName("label_notification_custom_action_separator") self.gridLayout_7.addWidget(self.label_notification_custom_action_separator, 2, 0, 1, 1) self.input_lineedit_notification_custom_action_separator = QtWidgets.QLineEdit(self.notification_custom_action_groupbox) self.input_lineedit_notification_custom_action_separator.setObjectName("input_lineedit_notification_custom_action_separator") self.gridLayout_7.addWidget(self.input_lineedit_notification_custom_action_separator, 2, 1, 1, 1) self.input_lineedit_notification_custom_action_string = QtWidgets.QLineEdit(self.notification_custom_action_groupbox) self.input_lineedit_notification_custom_action_string.setObjectName("input_lineedit_notification_custom_action_string") self.gridLayout_7.addWidget(self.input_lineedit_notification_custom_action_string, 1, 1, 1, 1) self.gridLayout_11.addWidget(self.notification_custom_action_groupbox, 6, 0, 1, 3) self.input_lineedit_notification_action_critical_string = QtWidgets.QLineEdit(self.notification_actions_groupbox) self.input_lineedit_notification_action_critical_string.setObjectName("input_lineedit_notification_action_critical_string") self.gridLayout_11.addWidget(self.input_lineedit_notification_action_critical_string, 1, 2, 1, 1) self.input_lineedit_notification_action_warning_string = QtWidgets.QLineEdit(self.notification_actions_groupbox) self.input_lineedit_notification_action_warning_string.setObjectName("input_lineedit_notification_action_warning_string") self.gridLayout_11.addWidget(self.input_lineedit_notification_action_warning_string, 0, 2, 1, 1) self.verticalLayout_7.addWidget(self.notification_actions_groupbox) self.verticalLayout_6.addWidget(self.notification_groupbox) spacerItem10 = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.verticalLayout_6.addItem(spacerItem10) self.tabs.addTab(self.tab_notifications, "") self.tab_colors = QtWidgets.QWidget() self.tab_colors.setObjectName("tab_colors") self.gridLayout_12 = QtWidgets.QGridLayout(self.tab_colors) self.gridLayout_12.setContentsMargins(10, 10, 10, 10) self.gridLayout_12.setSpacing(5) self.gridLayout_12.setObjectName("gridLayout_12") spacerItem11 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.gridLayout_12.addItem(spacerItem11, 12, 1, 1, 1) self.label_colors = QtWidgets.QLabel(self.tab_colors) self.label_colors.setObjectName("label_colors") self.gridLayout_12.addWidget(self.label_colors, 0, 0, 1, 3) self.states_groupbox = QtWidgets.QGroupBox(self.tab_colors) self.states_groupbox.setObjectName("states_groupbox") self.verticalLayout_11 = QtWidgets.QVBoxLayout(self.states_groupbox) self.verticalLayout_11.setContentsMargins(10, 10, 10, 5) self.verticalLayout_11.setSpacing(5) self.verticalLayout_11.setObjectName("verticalLayout_11") self.label_color_ok = QtWidgets.QLabel(self.states_groupbox) self.label_color_ok.setObjectName("label_color_ok") self.verticalLayout_11.addWidget(self.label_color_ok) self.label_color_information = QtWidgets.QLabel(self.states_groupbox) self.label_color_information.setObjectName("label_color_information") self.verticalLayout_11.addWidget(self.label_color_information) self.label_color_warning = QtWidgets.QLabel(self.states_groupbox) self.label_color_warning.setObjectName("label_color_warning") self.verticalLayout_11.addWidget(self.label_color_warning) self.label_color_average = QtWidgets.QLabel(self.states_groupbox) self.label_color_average.setObjectName("label_color_average") self.verticalLayout_11.addWidget(self.label_color_average) self.label_color_high = QtWidgets.QLabel(self.states_groupbox) self.label_color_high.setObjectName("label_color_high") self.verticalLayout_11.addWidget(self.label_color_high) self.label_color_critical = QtWidgets.QLabel(self.states_groupbox) self.label_color_critical.setObjectName("label_color_critical") self.verticalLayout_11.addWidget(self.label_color_critical) self.label_color_disaster = QtWidgets.QLabel(self.states_groupbox) self.label_color_disaster.setObjectName("label_color_disaster") self.verticalLayout_11.addWidget(self.label_color_disaster) self.label_color_unknown = QtWidgets.QLabel(self.states_groupbox) self.label_color_unknown.setObjectName("label_color_unknown") self.verticalLayout_11.addWidget(self.label_color_unknown) self.label_color_unreachable = QtWidgets.QLabel(self.states_groupbox) self.label_color_unreachable.setObjectName("label_color_unreachable") self.verticalLayout_11.addWidget(self.label_color_unreachable) self.label_color_down = QtWidgets.QLabel(self.states_groupbox) self.label_color_down.setObjectName("label_color_down") self.verticalLayout_11.addWidget(self.label_color_down) self.label_color_error = QtWidgets.QLabel(self.states_groupbox) self.label_color_error.setObjectName("label_color_error") self.verticalLayout_11.addWidget(self.label_color_error) self.gridLayout_12.addWidget(self.states_groupbox, 1, 0, 8, 1) self.text_groupbox = QtWidgets.QGroupBox(self.tab_colors) self.text_groupbox.setObjectName("text_groupbox") self.verticalLayout_8 = QtWidgets.QVBoxLayout(self.text_groupbox) self.verticalLayout_8.setContentsMargins(10, 10, 10, 5) self.verticalLayout_8.setSpacing(5) self.verticalLayout_8.setObjectName("verticalLayout_8") self.input_button_color_ok_text = QtWidgets.QPushButton(self.text_groupbox) self.input_button_color_ok_text.setText("") self.input_button_color_ok_text.setObjectName("input_button_color_ok_text") self.verticalLayout_8.addWidget(self.input_button_color_ok_text) self.input_button_color_information_text = QtWidgets.QPushButton(self.text_groupbox) self.input_button_color_information_text.setText("") self.input_button_color_information_text.setObjectName("input_button_color_information_text") self.verticalLayout_8.addWidget(self.input_button_color_information_text) self.input_button_color_warning_text = QtWidgets.QPushButton(self.text_groupbox) self.input_button_color_warning_text.setText("") self.input_button_color_warning_text.setObjectName("input_button_color_warning_text") self.verticalLayout_8.addWidget(self.input_button_color_warning_text) self.input_button_color_average_text = QtWidgets.QPushButton(self.text_groupbox) self.input_button_color_average_text.setText("") self.input_button_color_average_text.setObjectName("input_button_color_average_text") self.verticalLayout_8.addWidget(self.input_button_color_average_text) self.input_button_color_high_text = QtWidgets.QPushButton(self.text_groupbox) self.input_button_color_high_text.setText("") self.input_button_color_high_text.setObjectName("input_button_color_high_text") self.verticalLayout_8.addWidget(self.input_button_color_high_text) self.input_button_color_critical_text = QtWidgets.QPushButton(self.text_groupbox) self.input_button_color_critical_text.setText("") self.input_button_color_critical_text.setObjectName("input_button_color_critical_text") self.verticalLayout_8.addWidget(self.input_button_color_critical_text) self.input_button_color_disaster_text = QtWidgets.QPushButton(self.text_groupbox) self.input_button_color_disaster_text.setText("") self.input_button_color_disaster_text.setObjectName("input_button_color_disaster_text") self.verticalLayout_8.addWidget(self.input_button_color_disaster_text) self.input_button_color_unknown_text = QtWidgets.QPushButton(self.text_groupbox) self.input_button_color_unknown_text.setText("") self.input_button_color_unknown_text.setObjectName("input_button_color_unknown_text") self.verticalLayout_8.addWidget(self.input_button_color_unknown_text) self.input_button_color_unreachable_text = QtWidgets.QPushButton(self.text_groupbox) self.input_button_color_unreachable_text.setText("") self.input_button_color_unreachable_text.setObjectName("input_button_color_unreachable_text") self.verticalLayout_8.addWidget(self.input_button_color_unreachable_text) self.input_button_color_down_text = QtWidgets.QPushButton(self.text_groupbox) self.input_button_color_down_text.setText("") self.input_button_color_down_text.setObjectName("input_button_color_down_text") self.verticalLayout_8.addWidget(self.input_button_color_down_text) self.input_button_color_error_text = QtWidgets.QPushButton(self.text_groupbox) self.input_button_color_error_text.setText("") self.input_button_color_error_text.setObjectName("input_button_color_error_text") self.verticalLayout_8.addWidget(self.input_button_color_error_text) self.gridLayout_12.addWidget(self.text_groupbox, 1, 1, 8, 1) self.background_groupbox = QtWidgets.QGroupBox(self.tab_colors) self.background_groupbox.setObjectName("background_groupbox") self.verticalLayout_12 = QtWidgets.QVBoxLayout(self.background_groupbox) self.verticalLayout_12.setContentsMargins(10, 5, 10, 10) self.verticalLayout_12.setSpacing(5) self.verticalLayout_12.setObjectName("verticalLayout_12") self.input_button_color_ok_background = QtWidgets.QPushButton(self.background_groupbox) self.input_button_color_ok_background.setText("") self.input_button_color_ok_background.setObjectName("input_button_color_ok_background") self.verticalLayout_12.addWidget(self.input_button_color_ok_background) self.input_button_color_information_background = QtWidgets.QPushButton(self.background_groupbox) self.input_button_color_information_background.setText("") self.input_button_color_information_background.setObjectName("input_button_color_information_background") self.verticalLayout_12.addWidget(self.input_button_color_information_background) self.input_button_color_warning_background = QtWidgets.QPushButton(self.background_groupbox) self.input_button_color_warning_background.setText("") self.input_button_color_warning_background.setObjectName("input_button_color_warning_background") self.verticalLayout_12.addWidget(self.input_button_color_warning_background) self.input_button_color_average_background = QtWidgets.QPushButton(self.background_groupbox) self.input_button_color_average_background.setText("") self.input_button_color_average_background.setObjectName("input_button_color_average_background") self.verticalLayout_12.addWidget(self.input_button_color_average_background) self.input_button_color_high_background = QtWidgets.QPushButton(self.background_groupbox) self.input_button_color_high_background.setText("") self.input_button_color_high_background.setObjectName("input_button_color_high_background") self.verticalLayout_12.addWidget(self.input_button_color_high_background) self.input_button_color_critical_background = QtWidgets.QPushButton(self.background_groupbox) self.input_button_color_critical_background.setText("") self.input_button_color_critical_background.setObjectName("input_button_color_critical_background") self.verticalLayout_12.addWidget(self.input_button_color_critical_background) self.input_button_color_disaster_background = QtWidgets.QPushButton(self.background_groupbox) self.input_button_color_disaster_background.setText("") self.input_button_color_disaster_background.setObjectName("input_button_color_disaster_background") self.verticalLayout_12.addWidget(self.input_button_color_disaster_background) self.input_button_color_unknown_background = QtWidgets.QPushButton(self.background_groupbox) self.input_button_color_unknown_background.setText("") self.input_button_color_unknown_background.setObjectName("input_button_color_unknown_background") self.verticalLayout_12.addWidget(self.input_button_color_unknown_background) self.input_button_color_unreachable_background = QtWidgets.QPushButton(self.background_groupbox) self.input_button_color_unreachable_background.setText("") self.input_button_color_unreachable_background.setObjectName("input_button_color_unreachable_background") self.verticalLayout_12.addWidget(self.input_button_color_unreachable_background) self.input_button_color_down_background = QtWidgets.QPushButton(self.background_groupbox) self.input_button_color_down_background.setText("") self.input_button_color_down_background.setObjectName("input_button_color_down_background") self.verticalLayout_12.addWidget(self.input_button_color_down_background) self.input_button_color_error_background = QtWidgets.QPushButton(self.background_groupbox) self.input_button_color_error_background.setText("") self.input_button_color_error_background.setObjectName("input_button_color_error_background") self.verticalLayout_12.addWidget(self.input_button_color_error_background) self.gridLayout_12.addWidget(self.background_groupbox, 1, 2, 8, 1) self.horizontalLayout_12 = QtWidgets.QHBoxLayout() self.horizontalLayout_12.setContentsMargins(5, 5, 5, 5) self.horizontalLayout_12.setSpacing(5) self.horizontalLayout_12.setObjectName("horizontalLayout_12") self.button_colors_reset = QtWidgets.QPushButton(self.tab_colors) self.button_colors_reset.setObjectName("button_colors_reset") self.horizontalLayout_12.addWidget(self.button_colors_reset) self.button_colors_defaults = QtWidgets.QPushButton(self.tab_colors) self.button_colors_defaults.setObjectName("button_colors_defaults") self.horizontalLayout_12.addWidget(self.button_colors_defaults) self.gridLayout_12.addLayout(self.horizontalLayout_12, 10, 1, 1, 2) self.groupBox_color_grid = QtWidgets.QGroupBox(self.tab_colors) self.groupBox_color_grid.setObjectName("groupBox_color_grid") self.gridLayout_18 = QtWidgets.QGridLayout(self.groupBox_color_grid) self.gridLayout_18.setContentsMargins(10, 10, 10, 10) self.gridLayout_18.setSpacing(5) self.gridLayout_18.setObjectName("gridLayout_18") self.input_checkbox_show_grid = QtWidgets.QCheckBox(self.groupBox_color_grid) self.input_checkbox_show_grid.setObjectName("input_checkbox_show_grid") self.gridLayout_18.addWidget(self.input_checkbox_show_grid, 0, 0, 1, 1) self.input_checkbox_grid_use_custom_intensity = QtWidgets.QCheckBox(self.groupBox_color_grid) self.input_checkbox_grid_use_custom_intensity.setEnabled(True) self.input_checkbox_grid_use_custom_intensity.setObjectName("input_checkbox_grid_use_custom_intensity") self.gridLayout_18.addWidget(self.input_checkbox_grid_use_custom_intensity, 1, 0, 1, 1) self.input_slider_grid_alternation_intensity = QtWidgets.QSlider(self.groupBox_color_grid) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.input_slider_grid_alternation_intensity.sizePolicy().hasHeightForWidth()) self.input_slider_grid_alternation_intensity.setSizePolicy(sizePolicy) self.input_slider_grid_alternation_intensity.setMaximum(50) self.input_slider_grid_alternation_intensity.setOrientation(QtCore.Qt.Horizontal) self.input_slider_grid_alternation_intensity.setObjectName("input_slider_grid_alternation_intensity") self.gridLayout_18.addWidget(self.input_slider_grid_alternation_intensity, 2, 0, 1, 1) self.vbox_alternation_intensity_labels = QtWidgets.QVBoxLayout() self.vbox_alternation_intensity_labels.setContentsMargins(5, 5, 5, 5) self.vbox_alternation_intensity_labels.setSpacing(0) self.vbox_alternation_intensity_labels.setObjectName("vbox_alternation_intensity_labels") self.label_intensity_information_0 = QtWidgets.QLabel(self.groupBox_color_grid) self.label_intensity_information_0.setAutoFillBackground(True) self.label_intensity_information_0.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.label_intensity_information_0.setObjectName("label_intensity_information_0") self.vbox_alternation_intensity_labels.addWidget(self.label_intensity_information_0) self.label_intensity_information_1 = QtWidgets.QLabel(self.groupBox_color_grid) self.label_intensity_information_1.setAutoFillBackground(True) self.label_intensity_information_1.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.label_intensity_information_1.setObjectName("label_intensity_information_1") self.vbox_alternation_intensity_labels.addWidget(self.label_intensity_information_1) self.label_intensity_warning_0 = QtWidgets.QLabel(self.groupBox_color_grid) self.label_intensity_warning_0.setAutoFillBackground(True) self.label_intensity_warning_0.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.label_intensity_warning_0.setObjectName("label_intensity_warning_0") self.vbox_alternation_intensity_labels.addWidget(self.label_intensity_warning_0) self.label_intensity_warning_1 = QtWidgets.QLabel(self.groupBox_color_grid) self.label_intensity_warning_1.setAutoFillBackground(True) self.label_intensity_warning_1.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.label_intensity_warning_1.setObjectName("label_intensity_warning_1") self.vbox_alternation_intensity_labels.addWidget(self.label_intensity_warning_1) self.label_intensity_average_0 = QtWidgets.QLabel(self.groupBox_color_grid) self.label_intensity_average_0.setAutoFillBackground(True) self.label_intensity_average_0.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.label_intensity_average_0.setObjectName("label_intensity_average_0") self.vbox_alternation_intensity_labels.addWidget(self.label_intensity_average_0) self.label_intensity_average_1 = QtWidgets.QLabel(self.groupBox_color_grid) self.label_intensity_average_1.setAutoFillBackground(True) self.label_intensity_average_1.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.label_intensity_average_1.setObjectName("label_intensity_average_1") self.vbox_alternation_intensity_labels.addWidget(self.label_intensity_average_1) self.label_intensity_high_0 = QtWidgets.QLabel(self.groupBox_color_grid) self.label_intensity_high_0.setAutoFillBackground(True) self.label_intensity_high_0.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.label_intensity_high_0.setObjectName("label_intensity_high_0") self.vbox_alternation_intensity_labels.addWidget(self.label_intensity_high_0) self.label_intensity_high_1 = QtWidgets.QLabel(self.groupBox_color_grid) self.label_intensity_high_1.setAutoFillBackground(True) self.label_intensity_high_1.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.label_intensity_high_1.setObjectName("label_intensity_high_1") self.vbox_alternation_intensity_labels.addWidget(self.label_intensity_high_1) self.label_intensity_critical_0 = QtWidgets.QLabel(self.groupBox_color_grid) self.label_intensity_critical_0.setAutoFillBackground(True) self.label_intensity_critical_0.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.label_intensity_critical_0.setObjectName("label_intensity_critical_0") self.vbox_alternation_intensity_labels.addWidget(self.label_intensity_critical_0) self.label_intensity_critical_1 = QtWidgets.QLabel(self.groupBox_color_grid) self.label_intensity_critical_1.setAutoFillBackground(True) self.label_intensity_critical_1.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.label_intensity_critical_1.setObjectName("label_intensity_critical_1") self.vbox_alternation_intensity_labels.addWidget(self.label_intensity_critical_1) self.label_intensity_disaster_0 = QtWidgets.QLabel(self.groupBox_color_grid) self.label_intensity_disaster_0.setAutoFillBackground(True) self.label_intensity_disaster_0.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.label_intensity_disaster_0.setObjectName("label_intensity_disaster_0") self.vbox_alternation_intensity_labels.addWidget(self.label_intensity_disaster_0) self.label_intensity_disaster_1 = QtWidgets.QLabel(self.groupBox_color_grid) self.label_intensity_disaster_1.setAutoFillBackground(True) self.label_intensity_disaster_1.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.label_intensity_disaster_1.setObjectName("label_intensity_disaster_1") self.vbox_alternation_intensity_labels.addWidget(self.label_intensity_disaster_1) self.label_intensity_unknown_0 = QtWidgets.QLabel(self.groupBox_color_grid) self.label_intensity_unknown_0.setAutoFillBackground(True) self.label_intensity_unknown_0.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.label_intensity_unknown_0.setObjectName("label_intensity_unknown_0") self.vbox_alternation_intensity_labels.addWidget(self.label_intensity_unknown_0) self.label_intensity_unknown_1 = QtWidgets.QLabel(self.groupBox_color_grid) self.label_intensity_unknown_1.setAutoFillBackground(True) self.label_intensity_unknown_1.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.label_intensity_unknown_1.setObjectName("label_intensity_unknown_1") self.vbox_alternation_intensity_labels.addWidget(self.label_intensity_unknown_1) self.label_intensity_unreachable_0 = QtWidgets.QLabel(self.groupBox_color_grid) self.label_intensity_unreachable_0.setAutoFillBackground(True) self.label_intensity_unreachable_0.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.label_intensity_unreachable_0.setObjectName("label_intensity_unreachable_0") self.vbox_alternation_intensity_labels.addWidget(self.label_intensity_unreachable_0) self.label_intensity_unreachable_1 = QtWidgets.QLabel(self.groupBox_color_grid) self.label_intensity_unreachable_1.setAutoFillBackground(True) self.label_intensity_unreachable_1.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.label_intensity_unreachable_1.setObjectName("label_intensity_unreachable_1") self.vbox_alternation_intensity_labels.addWidget(self.label_intensity_unreachable_1) self.label_intensity_down_0 = QtWidgets.QLabel(self.groupBox_color_grid) self.label_intensity_down_0.setAutoFillBackground(True) self.label_intensity_down_0.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.label_intensity_down_0.setObjectName("label_intensity_down_0") self.vbox_alternation_intensity_labels.addWidget(self.label_intensity_down_0) self.label_intensity_down_1 = QtWidgets.QLabel(self.groupBox_color_grid) self.label_intensity_down_1.setAutoFillBackground(True) self.label_intensity_down_1.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.label_intensity_down_1.setObjectName("label_intensity_down_1") self.vbox_alternation_intensity_labels.addWidget(self.label_intensity_down_1) self.gridLayout_18.addLayout(self.vbox_alternation_intensity_labels, 2, 1, 1, 1) self.gridLayout_12.addWidget(self.groupBox_color_grid, 11, 0, 1, 3) self.tabs.addTab(self.tab_colors, "") self.tab_defaults = QtWidgets.QWidget() self.tab_defaults.setObjectName("tab_defaults") self.verticalLayout_9 = QtWidgets.QVBoxLayout(self.tab_defaults) self.verticalLayout_9.setContentsMargins(10, 10, 10, 10) self.verticalLayout_9.setSpacing(5) self.verticalLayout_9.setObjectName("verticalLayout_9") self.label_defaults = QtWidgets.QLabel(self.tab_defaults) self.label_defaults.setObjectName("label_defaults") self.verticalLayout_9.addWidget(self.label_defaults) self.label_defaults_acknowledge = QtWidgets.QGroupBox(self.tab_defaults) self.label_defaults_acknowledge.setObjectName("label_defaults_acknowledge") self.verticalLayout_10 = QtWidgets.QVBoxLayout(self.label_defaults_acknowledge) self.verticalLayout_10.setContentsMargins(10, 10, 10, 10) self.verticalLayout_10.setSpacing(5) self.verticalLayout_10.setObjectName("verticalLayout_10") self.input_checkbox_defaults_acknowledge_sticky = QtWidgets.QCheckBox(self.label_defaults_acknowledge) self.input_checkbox_defaults_acknowledge_sticky.setObjectName("input_checkbox_defaults_acknowledge_sticky") self.verticalLayout_10.addWidget(self.input_checkbox_defaults_acknowledge_sticky) self.input_checkbox_defaults_acknowledge_send_notification = QtWidgets.QCheckBox(self.label_defaults_acknowledge) self.input_checkbox_defaults_acknowledge_send_notification.setObjectName("input_checkbox_defaults_acknowledge_send_notification") self.verticalLayout_10.addWidget(self.input_checkbox_defaults_acknowledge_send_notification) self.input_checkbox_defaults_acknowledge_persistent_comment = QtWidgets.QCheckBox(self.label_defaults_acknowledge) self.input_checkbox_defaults_acknowledge_persistent_comment.setObjectName("input_checkbox_defaults_acknowledge_persistent_comment") self.verticalLayout_10.addWidget(self.input_checkbox_defaults_acknowledge_persistent_comment) self.input_checkbox_defaults_acknowledge_all_services = QtWidgets.QCheckBox(self.label_defaults_acknowledge) self.input_checkbox_defaults_acknowledge_all_services.setObjectName("input_checkbox_defaults_acknowledge_all_services") self.verticalLayout_10.addWidget(self.input_checkbox_defaults_acknowledge_all_services) self.horizontalLayout_13 = QtWidgets.QHBoxLayout() self.horizontalLayout_13.setContentsMargins(-1, 5, 5, 5) self.horizontalLayout_13.setSpacing(5) self.horizontalLayout_13.setObjectName("horizontalLayout_13") self.label_defaults_acknowledge_comment = QtWidgets.QLabel(self.label_defaults_acknowledge) self.label_defaults_acknowledge_comment.setObjectName("label_defaults_acknowledge_comment") self.horizontalLayout_13.addWidget(self.label_defaults_acknowledge_comment) self.input_lineedit_defaults_acknowledge_comment = QtWidgets.QLineEdit(self.label_defaults_acknowledge) self.input_lineedit_defaults_acknowledge_comment.setObjectName("input_lineedit_defaults_acknowledge_comment") self.horizontalLayout_13.addWidget(self.input_lineedit_defaults_acknowledge_comment) self.verticalLayout_10.addLayout(self.horizontalLayout_13) self.verticalLayout_9.addWidget(self.label_defaults_acknowledge) self.label_defaults_downtime = QtWidgets.QGroupBox(self.tab_defaults) self.label_defaults_downtime.setObjectName("label_defaults_downtime") self.gridLayout_10 = QtWidgets.QGridLayout(self.label_defaults_downtime) self.gridLayout_10.setContentsMargins(10, 10, 10, 10) self.gridLayout_10.setSpacing(5) self.gridLayout_10.setObjectName("gridLayout_10") self.label_defaults_downtime_comment = QtWidgets.QLabel(self.label_defaults_downtime) self.label_defaults_downtime_comment.setObjectName("label_defaults_downtime_comment") self.gridLayout_10.addWidget(self.label_defaults_downtime_comment, 3, 0, 1, 1) self.label_defaults_duration_comment = QtWidgets.QLabel(self.label_defaults_downtime) self.label_defaults_duration_comment.setObjectName("label_defaults_duration_comment") self.gridLayout_10.addWidget(self.label_defaults_duration_comment, 1, 0, 1, 1) self.input_lineedit_defaults_downtime_comment = QtWidgets.QLineEdit(self.label_defaults_downtime) self.input_lineedit_defaults_downtime_comment.setObjectName("input_lineedit_defaults_downtime_comment") self.gridLayout_10.addWidget(self.input_lineedit_defaults_downtime_comment, 3, 1, 1, 1) self.horizontalLayout_8 = QtWidgets.QHBoxLayout() self.horizontalLayout_8.setContentsMargins(-1, 5, 5, 5) self.horizontalLayout_8.setSpacing(5) self.horizontalLayout_8.setObjectName("horizontalLayout_8") self.input_spinbox_defaults_downtime_duration_hours = QtWidgets.QSpinBox(self.label_defaults_downtime) self.input_spinbox_defaults_downtime_duration_hours.setObjectName("input_spinbox_defaults_downtime_duration_hours") self.horizontalLayout_8.addWidget(self.input_spinbox_defaults_downtime_duration_hours) self.label_duration_hour = QtWidgets.QLabel(self.label_defaults_downtime) self.label_duration_hour.setObjectName("label_duration_hour") self.horizontalLayout_8.addWidget(self.label_duration_hour) self.input_spinbox_defaults_downtime_duration_minutes = QtWidgets.QSpinBox(self.label_defaults_downtime) self.input_spinbox_defaults_downtime_duration_minutes.setObjectName("input_spinbox_defaults_downtime_duration_minutes") self.horizontalLayout_8.addWidget(self.input_spinbox_defaults_downtime_duration_minutes) self.label_duration_minutes = QtWidgets.QLabel(self.label_defaults_downtime) self.label_duration_minutes.setObjectName("label_duration_minutes") self.horizontalLayout_8.addWidget(self.label_duration_minutes) spacerItem12 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_8.addItem(spacerItem12) self.gridLayout_10.addLayout(self.horizontalLayout_8, 1, 1, 1, 1) self.gridLayout_9 = QtWidgets.QGridLayout() self.gridLayout_9.setContentsMargins(-1, 5, 5, 5) self.gridLayout_9.setSpacing(5) self.gridLayout_9.setObjectName("gridLayout_9") self.input_radiobutton_defaults_downtime_type_flexible = QtWidgets.QRadioButton(self.label_defaults_downtime) self.input_radiobutton_defaults_downtime_type_flexible.setObjectName("input_radiobutton_defaults_downtime_type_flexible") self.gridLayout_9.addWidget(self.input_radiobutton_defaults_downtime_type_flexible, 1, 1, 1, 1) self.input_radiobutton_defaults_downtime_type_fixed = QtWidgets.QRadioButton(self.label_defaults_downtime) self.input_radiobutton_defaults_downtime_type_fixed.setObjectName("input_radiobutton_defaults_downtime_type_fixed") self.gridLayout_9.addWidget(self.input_radiobutton_defaults_downtime_type_fixed, 0, 1, 1, 1) self.label_defaults_downtime_type = QtWidgets.QLabel(self.label_defaults_downtime) self.label_defaults_downtime_type.setObjectName("label_defaults_downtime_type") self.gridLayout_9.addWidget(self.label_defaults_downtime_type, 0, 0, 1, 1) spacerItem13 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.gridLayout_9.addItem(spacerItem13, 0, 2, 1, 1) self.gridLayout_10.addLayout(self.gridLayout_9, 0, 0, 1, 2) self.verticalLayout_9.addWidget(self.label_defaults_downtime) self.label_acknowledge_submit_result = QtWidgets.QGroupBox(self.tab_defaults) self.label_acknowledge_submit_result.setObjectName("label_acknowledge_submit_result") self.horizontalLayout_14 = QtWidgets.QHBoxLayout(self.label_acknowledge_submit_result) self.horizontalLayout_14.setContentsMargins(10, 10, 10, 10) self.horizontalLayout_14.setSpacing(5) self.horizontalLayout_14.setObjectName("horizontalLayout_14") self.label_defaults_submit_check_result_comment = QtWidgets.QLabel(self.label_acknowledge_submit_result) self.label_defaults_submit_check_result_comment.setObjectName("label_defaults_submit_check_result_comment") self.horizontalLayout_14.addWidget(self.label_defaults_submit_check_result_comment) self.input_lineedit_defaults_submit_check_result_comment = QtWidgets.QLineEdit(self.label_acknowledge_submit_result) self.input_lineedit_defaults_submit_check_result_comment.setObjectName("input_lineedit_defaults_submit_check_result_comment") self.horizontalLayout_14.addWidget(self.input_lineedit_defaults_submit_check_result_comment) self.verticalLayout_9.addWidget(self.label_acknowledge_submit_result) spacerItem14 = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.verticalLayout_9.addItem(spacerItem14) self.tabs.addTab(self.tab_defaults, "") self.verticalLayout.addWidget(self.tabs) self.button_box = QtWidgets.QDialogButtonBox(settings_main) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.button_box.sizePolicy().hasHeightForWidth()) self.button_box.setSizePolicy(sizePolicy) self.button_box.setOrientation(QtCore.Qt.Horizontal) self.button_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) self.button_box.setObjectName("button_box") self.verticalLayout.addWidget(self.button_box) self.retranslateUi(settings_main) self.tabs.setCurrentIndex(4) self.button_box.accepted.connect(settings_main.accept) self.button_box.rejected.connect(settings_main.reject) QtCore.QMetaObject.connectSlotsByName(settings_main) settings_main.setTabOrder(self.list_servers, self.button_new_server) settings_main.setTabOrder(self.button_new_server, self.button_edit_server) settings_main.setTabOrder(self.button_edit_server, self.button_copy_server) settings_main.setTabOrder(self.button_copy_server, self.button_delete_server) settings_main.setTabOrder(self.button_delete_server, self.input_spinbox_update_interval_seconds) settings_main.setTabOrder(self.input_spinbox_update_interval_seconds, self.input_checkbox_use_system_keyring) settings_main.setTabOrder(self.input_checkbox_use_system_keyring, self.input_checkbox_debug_mode) settings_main.setTabOrder(self.input_checkbox_debug_mode, self.input_checkbox_debug_to_file) settings_main.setTabOrder(self.input_checkbox_debug_to_file, self.input_lineedit_debug_file) settings_main.setTabOrder(self.input_lineedit_debug_file, self.input_checkbox_check_for_new_version) settings_main.setTabOrder(self.input_checkbox_check_for_new_version, self.button_check_for_new_version_now) settings_main.setTabOrder(self.button_check_for_new_version_now, self.input_radiobutton_long_display) settings_main.setTabOrder(self.input_radiobutton_long_display, self.input_radiobutton_short_display) settings_main.setTabOrder(self.input_radiobutton_short_display, self.input_radiobutton_statusbar_floating) settings_main.setTabOrder(self.input_radiobutton_statusbar_floating, self.input_radiobutton_icon_in_systray) settings_main.setTabOrder(self.input_radiobutton_icon_in_systray, self.input_radiobutton_fullscreen) settings_main.setTabOrder(self.input_radiobutton_fullscreen, self.input_checkbox_systray_offset_use) settings_main.setTabOrder(self.input_checkbox_systray_offset_use, self.input_spinbox_systray_offset) settings_main.setTabOrder(self.input_spinbox_systray_offset, self.input_combobox_fullscreen_display) settings_main.setTabOrder(self.input_combobox_fullscreen_display, self.button_fontchooser) settings_main.setTabOrder(self.button_fontchooser, self.button_default_font) settings_main.setTabOrder(self.button_default_font, self.input_radiobutton_popup_details_hover) settings_main.setTabOrder(self.input_radiobutton_popup_details_hover, self.input_radiobutton_popup_details_clicking) settings_main.setTabOrder(self.input_radiobutton_popup_details_clicking, self.input_radiobutton_close_details_hover) settings_main.setTabOrder(self.input_radiobutton_close_details_hover, self.input_radiobutton_close_details_clicking) settings_main.setTabOrder(self.input_radiobutton_close_details_clicking, self.input_radiobutton_close_details_clicking_somewhere) settings_main.setTabOrder(self.input_radiobutton_close_details_clicking_somewhere, self.input_checkbox_highlight_new_events) settings_main.setTabOrder(self.input_checkbox_highlight_new_events, self.input_checkbox_show_tooltips) settings_main.setTabOrder(self.input_checkbox_show_tooltips, self.input_combobox_default_sort_field) settings_main.setTabOrder(self.input_combobox_default_sort_field, self.input_combobox_default_sort_order) settings_main.setTabOrder(self.input_combobox_default_sort_order, self.input_checkbox_filter_all_down_hosts) settings_main.setTabOrder(self.input_checkbox_filter_all_down_hosts, self.input_checkbox_filter_all_unreachable_hosts) settings_main.setTabOrder(self.input_checkbox_filter_all_unreachable_hosts, self.input_checkbox_filter_all_flapping_hosts) settings_main.setTabOrder(self.input_checkbox_filter_all_flapping_hosts, self.input_checkbox_filter_all_critical_services) settings_main.setTabOrder(self.input_checkbox_filter_all_critical_services, self.input_checkbox_filter_all_flapping_services) settings_main.setTabOrder(self.input_checkbox_filter_all_flapping_services, self.input_checkbox_filter_all_unknown_services) settings_main.setTabOrder(self.input_checkbox_filter_all_unknown_services, self.input_checkbox_filter_all_warning_services) settings_main.setTabOrder(self.input_checkbox_filter_all_warning_services, self.input_checkbox_filter_all_information_services) settings_main.setTabOrder(self.input_checkbox_filter_all_information_services, self.input_checkbox_filter_all_average_services) settings_main.setTabOrder(self.input_checkbox_filter_all_average_services, self.input_checkbox_filter_all_disaster_services) settings_main.setTabOrder(self.input_checkbox_filter_all_disaster_services, self.input_checkbox_filter_all_high_services) settings_main.setTabOrder(self.input_checkbox_filter_all_high_services, self.input_checkbox_filter_acknowledged_hosts_services) settings_main.setTabOrder(self.input_checkbox_filter_acknowledged_hosts_services, self.input_checkbox_filter_hosts_services_disabled_notifications) settings_main.setTabOrder(self.input_checkbox_filter_hosts_services_disabled_notifications, self.input_checkbox_filter_hosts_services_disabled_checks) settings_main.setTabOrder(self.input_checkbox_filter_hosts_services_disabled_checks, self.input_checkbox_filter_hosts_services_maintenance) settings_main.setTabOrder(self.input_checkbox_filter_hosts_services_maintenance, self.input_checkbox_filter_services_on_acknowledged_hosts) settings_main.setTabOrder(self.input_checkbox_filter_services_on_acknowledged_hosts, self.input_checkbox_filter_services_on_down_hosts) settings_main.setTabOrder(self.input_checkbox_filter_services_on_down_hosts, self.input_checkbox_filter_services_on_hosts_in_maintenance) settings_main.setTabOrder(self.input_checkbox_filter_services_on_hosts_in_maintenance, self.input_checkbox_filter_services_on_unreachable_hosts) settings_main.setTabOrder(self.input_checkbox_filter_services_on_unreachable_hosts, self.input_checkbox_filter_hosts_in_soft_state) settings_main.setTabOrder(self.input_checkbox_filter_hosts_in_soft_state, self.input_checkbox_filter_services_in_soft_state) settings_main.setTabOrder(self.input_checkbox_filter_services_in_soft_state, self.input_checkbox_re_host_enabled) settings_main.setTabOrder(self.input_checkbox_re_host_enabled, self.input_lineedit_re_host_pattern) settings_main.setTabOrder(self.input_lineedit_re_host_pattern, self.input_checkbox_re_host_reverse) settings_main.setTabOrder(self.input_checkbox_re_host_reverse, self.input_checkbox_re_service_enabled) settings_main.setTabOrder(self.input_checkbox_re_service_enabled, self.input_lineedit_re_service_pattern) settings_main.setTabOrder(self.input_lineedit_re_service_pattern, self.input_checkbox_re_service_reverse) settings_main.setTabOrder(self.input_checkbox_re_service_reverse, self.input_checkbox_re_status_information_enabled) settings_main.setTabOrder(self.input_checkbox_re_status_information_enabled, self.input_lineedit_re_status_information_pattern) settings_main.setTabOrder(self.input_lineedit_re_status_information_pattern, self.input_checkbox_re_status_information_reverse) settings_main.setTabOrder(self.input_checkbox_re_status_information_reverse, self.list_actions) settings_main.setTabOrder(self.list_actions, self.button_new_action) settings_main.setTabOrder(self.button_new_action, self.button_edit_action) settings_main.setTabOrder(self.button_edit_action, self.button_copy_action) settings_main.setTabOrder(self.button_copy_action, self.button_delete_action) settings_main.setTabOrder(self.button_delete_action, self.input_radiobutton_connect_by_host) settings_main.setTabOrder(self.input_radiobutton_connect_by_host, self.input_radiobutton_connect_by_dns) settings_main.setTabOrder(self.input_radiobutton_connect_by_dns, self.input_radiobutton_connect_by_ip) settings_main.setTabOrder(self.input_radiobutton_connect_by_ip, self.input_radiobutton_use_default_browser) settings_main.setTabOrder(self.input_radiobutton_use_default_browser, self.input_radiobutton_use_custom_browser) settings_main.setTabOrder(self.input_radiobutton_use_custom_browser, self.input_lineedit_custom_browser) settings_main.setTabOrder(self.input_lineedit_custom_browser, self.button_choose_browser) settings_main.setTabOrder(self.button_choose_browser, self.input_checkbox_notification) settings_main.setTabOrder(self.input_checkbox_notification, self.input_checkbox_notify_if_warning) settings_main.setTabOrder(self.input_checkbox_notify_if_warning, self.input_checkbox_notify_if_critical) settings_main.setTabOrder(self.input_checkbox_notify_if_critical, self.input_checkbox_notify_if_unknown) settings_main.setTabOrder(self.input_checkbox_notify_if_unknown, self.input_checkbox_notify_if_unreachable) settings_main.setTabOrder(self.input_checkbox_notify_if_unreachable, self.input_checkbox_notify_if_down) settings_main.setTabOrder(self.input_checkbox_notify_if_down, self.input_checkbox_notify_if_information) settings_main.setTabOrder(self.input_checkbox_notify_if_information, self.input_checkbox_notify_if_disaster) settings_main.setTabOrder(self.input_checkbox_notify_if_disaster, self.input_checkbox_notify_if_average) settings_main.setTabOrder(self.input_checkbox_notify_if_average, self.input_checkbox_notify_if_high) settings_main.setTabOrder(self.input_checkbox_notify_if_high, self.input_checkbox_notification_flashing) settings_main.setTabOrder(self.input_checkbox_notification_flashing, self.input_checkbox_notification_desktop) settings_main.setTabOrder(self.input_checkbox_notification_desktop, self.input_checkbox_notification_sound) settings_main.setTabOrder(self.input_checkbox_notification_sound, self.input_checkbox_notification_sound_repeat) settings_main.setTabOrder(self.input_checkbox_notification_sound_repeat, self.input_radiobutton_notification_default_sound) settings_main.setTabOrder(self.input_radiobutton_notification_default_sound, self.input_radiobutton_notification_custom_sound) settings_main.setTabOrder(self.input_radiobutton_notification_custom_sound, self.input_lineedit_notification_custom_sound_warning) settings_main.setTabOrder(self.input_lineedit_notification_custom_sound_warning, self.button_choose_warning) settings_main.setTabOrder(self.button_choose_warning, self.button_play_warning) settings_main.setTabOrder(self.button_play_warning, self.input_lineedit_notification_custom_sound_critical) settings_main.setTabOrder(self.input_lineedit_notification_custom_sound_critical, self.button_choose_critical) settings_main.setTabOrder(self.button_choose_critical, self.button_play_critical) settings_main.setTabOrder(self.button_play_critical, self.input_lineedit_notification_custom_sound_down) settings_main.setTabOrder(self.input_lineedit_notification_custom_sound_down, self.button_choose_down) settings_main.setTabOrder(self.button_choose_down, self.button_play_down) settings_main.setTabOrder(self.button_play_down, self.input_checkbox_notification_actions) settings_main.setTabOrder(self.input_checkbox_notification_actions, self.input_checkbox_notification_action_warning) settings_main.setTabOrder(self.input_checkbox_notification_action_warning, self.input_lineedit_notification_action_warning_string) settings_main.setTabOrder(self.input_lineedit_notification_action_warning_string, self.input_checkbox_notification_action_critical) settings_main.setTabOrder(self.input_checkbox_notification_action_critical, self.input_lineedit_notification_action_critical_string) settings_main.setTabOrder(self.input_lineedit_notification_action_critical_string, self.input_checkbox_notification_action_down) settings_main.setTabOrder(self.input_checkbox_notification_action_down, self.input_lineedit_notification_action_down_string) settings_main.setTabOrder(self.input_lineedit_notification_action_down_string, self.input_checkbox_notification_action_ok) settings_main.setTabOrder(self.input_checkbox_notification_action_ok, self.input_lineedit_notification_action_ok_string) settings_main.setTabOrder(self.input_lineedit_notification_action_ok_string, self.input_checkbox_notification_custom_action) settings_main.setTabOrder(self.input_checkbox_notification_custom_action, self.input_lineedit_notification_custom_action_string) settings_main.setTabOrder(self.input_lineedit_notification_custom_action_string, self.input_lineedit_notification_custom_action_separator) settings_main.setTabOrder(self.input_lineedit_notification_custom_action_separator, self.input_checkbox_notification_custom_action_single) settings_main.setTabOrder(self.input_checkbox_notification_custom_action_single, self.input_button_color_ok_text) settings_main.setTabOrder(self.input_button_color_ok_text, self.input_button_color_ok_background) settings_main.setTabOrder(self.input_button_color_ok_background, self.input_button_color_information_text) settings_main.setTabOrder(self.input_button_color_information_text, self.input_button_color_information_background) settings_main.setTabOrder(self.input_button_color_information_background, self.input_button_color_warning_text) settings_main.setTabOrder(self.input_button_color_warning_text, self.input_button_color_warning_background) settings_main.setTabOrder(self.input_button_color_warning_background, self.input_button_color_average_text) settings_main.setTabOrder(self.input_button_color_average_text, self.input_button_color_average_background) settings_main.setTabOrder(self.input_button_color_average_background, self.input_button_color_high_text) settings_main.setTabOrder(self.input_button_color_high_text, self.input_button_color_high_background) settings_main.setTabOrder(self.input_button_color_high_background, self.input_button_color_critical_text) settings_main.setTabOrder(self.input_button_color_critical_text, self.input_button_color_critical_background) settings_main.setTabOrder(self.input_button_color_critical_background, self.input_button_color_disaster_text) settings_main.setTabOrder(self.input_button_color_disaster_text, self.input_button_color_disaster_background) settings_main.setTabOrder(self.input_button_color_disaster_background, self.input_button_color_unknown_text) settings_main.setTabOrder(self.input_button_color_unknown_text, self.input_button_color_unknown_background) settings_main.setTabOrder(self.input_button_color_unknown_background, self.input_button_color_unreachable_text) settings_main.setTabOrder(self.input_button_color_unreachable_text, self.input_button_color_unreachable_background) settings_main.setTabOrder(self.input_button_color_unreachable_background, self.input_button_color_down_text) settings_main.setTabOrder(self.input_button_color_down_text, self.input_button_color_down_background) settings_main.setTabOrder(self.input_button_color_down_background, self.input_button_color_error_text) settings_main.setTabOrder(self.input_button_color_error_text, self.input_button_color_error_background) settings_main.setTabOrder(self.input_button_color_error_background, self.button_colors_reset) settings_main.setTabOrder(self.button_colors_reset, self.button_colors_defaults) settings_main.setTabOrder(self.button_colors_defaults, self.input_checkbox_defaults_acknowledge_sticky) settings_main.setTabOrder(self.input_checkbox_defaults_acknowledge_sticky, self.input_checkbox_defaults_acknowledge_send_notification) settings_main.setTabOrder(self.input_checkbox_defaults_acknowledge_send_notification, self.input_checkbox_defaults_acknowledge_persistent_comment) settings_main.setTabOrder(self.input_checkbox_defaults_acknowledge_persistent_comment, self.input_checkbox_defaults_acknowledge_all_services) settings_main.setTabOrder(self.input_checkbox_defaults_acknowledge_all_services, self.input_lineedit_defaults_acknowledge_comment) settings_main.setTabOrder(self.input_lineedit_defaults_acknowledge_comment, self.input_radiobutton_defaults_downtime_type_fixed) settings_main.setTabOrder(self.input_radiobutton_defaults_downtime_type_fixed, self.input_radiobutton_defaults_downtime_type_flexible) settings_main.setTabOrder(self.input_radiobutton_defaults_downtime_type_flexible, self.input_spinbox_defaults_downtime_duration_hours) settings_main.setTabOrder(self.input_spinbox_defaults_downtime_duration_hours, self.input_spinbox_defaults_downtime_duration_minutes) settings_main.setTabOrder(self.input_spinbox_defaults_downtime_duration_minutes, self.input_lineedit_defaults_downtime_comment) settings_main.setTabOrder(self.input_lineedit_defaults_downtime_comment, self.input_lineedit_defaults_submit_check_result_comment) settings_main.setTabOrder(self.input_lineedit_defaults_submit_check_result_comment, self.tabs) settings_main.setTabOrder(self.tabs, self.input_checkbox_show_grid) settings_main.setTabOrder(self.input_checkbox_show_grid, self.input_checkbox_grid_use_custom_intensity) settings_main.setTabOrder(self.input_checkbox_grid_use_custom_intensity, self.input_slider_grid_alternation_intensity) settings_main.setTabOrder(self.input_slider_grid_alternation_intensity, self.input_radiobutton_windowed) def retranslateUi(self, settings_main): _translate = QtCore.QCoreApplication.translate settings_main.setWindowTitle(_translate("settings_main", "Nagstamon 2.0 settings")) self.input_checkbox_debug_mode.setText(_translate("settings_main", "Debug mode")) self.input_checkbox_debug_to_file.setText(_translate("settings_main", "Debug to file:")) self.button_new_server.setText(_translate("settings_main", "New server...")) self.button_edit_server.setText(_translate("settings_main", "Edit server...")) self.button_copy_server.setText(_translate("settings_main", "Copy server...")) self.button_delete_server.setText(_translate("settings_main", "Delete server")) self.input_checkbox_use_system_keyring.setText(_translate("settings_main", "Use system keyring for server credentials")) self.label_update_interval_seconds.setText(_translate("settings_main", "Update interval:")) self.label_interval_seconds.setText(_translate("settings_main", "seconds")) self.input_checkbox_check_for_new_version.setText(_translate("settings_main", "Check for new version at startup")) self.button_check_for_new_version_now.setText(_translate("settings_main", "Check for new version now")) self.tabs.setTabText(self.tabs.indexOf(self.tab_servers), _translate("settings_main", "Servers")) self.groupbox_display_size.setTitle(_translate("settings_main", "Statusbar display size:")) self.input_radiobutton_short_display.setText(_translate("settings_main", "Short [ 1 | 4 ]")) self.input_radiobutton_long_display.setText(_translate("settings_main", "Long [ 1 WARNING | 4 CRITICAL ]")) self.groupbox_detailed_summary_popup.setTitle(_translate("settings_main", "Statusbar details popup:")) self.input_radiobutton_close_details_clicking.setText(_translate("settings_main", "Close when clicking statusbar")) self.input_radiobutton_popup_details_clicking.setText(_translate("settings_main", "Popup when clicking statusbar")) self.input_radiobutton_popup_details_hover.setText(_translate("settings_main", "Popup when hovering over statusbar")) self.input_radiobutton_close_details_hover.setText(_translate("settings_main", "Close when hovering out of popup")) self.input_radiobutton_close_details_clicking_somewhere.setText(_translate("settings_main", "Close when clicking somewhere")) self.input_checkbox_highlight_new_events.setText(_translate("settings_main", "Highlight new events in status details overview")) self.input_checkbox_show_tooltips.setText(_translate("settings_main", "Show tooltips in status details overview")) self.label_default_sort_field.setText(_translate("settings_main", "Default sort field:")) self.label_default_sort_order.setText(_translate("settings_main", "Default sort order:")) self.groupbox_appearance.setTitle(_translate("settings_main", "Appearance:")) self.input_radiobutton_icon_in_systray.setText(_translate("settings_main", "Icon in systray")) self.input_checkbox_systray_offset_use.setText(_translate("settings_main", "Use offset to correct position problems")) self.label_fullscreen_display.setText(_translate("settings_main", "Display to use:")) self.label_offset_statuswindow.setText(_translate("settings_main", "Offset for statuswindow:")) self.input_radiobutton_fullscreen.setText(_translate("settings_main", "Fullscreen")) self.input_radiobutton_statusbar_floating.setText(_translate("settings_main", "Floating statusbar")) self.input_radiobutton_windowed.setText(_translate("settings_main", "Window")) self.groupbox_font.setTitle(_translate("settings_main", "Font:")) self.label_font.setText(_translate("settings_main", "The CRITICAL Fox jumped over the WARNING dog.")) self.button_fontchooser.setText(_translate("settings_main", "Change Font...")) self.button_default_font.setText(_translate("settings_main", "Revert to default font")) self.tabs.setTabText(self.tabs.indexOf(self.tab_display), _translate("settings_main", "Display")) self.input_checkbox_re_host_reverse.setText(_translate("settings_main", "reverse")) self.label_python_re.setText(_translate("settings_main", "See Python Regular Expressions HOWTO for filtering details.")) self.input_checkbox_re_status_information_enabled.setText(_translate("settings_main", "Regular expression for status informations")) self.input_checkbox_re_service_enabled.setText(_translate("settings_main", "Regular expression for services")) self.groupbox_filters.setTitle(_translate("settings_main", "Filter out the following:")) self.input_checkbox_filter_all_down_hosts.setText(_translate("settings_main", "All down hosts")) self.input_checkbox_filter_all_unreachable_hosts.setText(_translate("settings_main", "All unreachable hosts")) self.input_checkbox_filter_all_flapping_hosts.setText(_translate("settings_main", "All flapping hosts")) self.input_checkbox_filter_all_critical_services.setText(_translate("settings_main", "All critical services")) self.input_checkbox_filter_acknowledged_hosts_services.setText(_translate("settings_main", "Acknowledged hosts and services")) self.input_checkbox_filter_hosts_services_disabled_notifications.setText(_translate("settings_main", "Hosts and services with disabled notifications")) self.input_checkbox_filter_hosts_services_disabled_checks.setText(_translate("settings_main", "Hosts and services with disabled checks")) self.input_checkbox_filter_hosts_services_maintenance.setText(_translate("settings_main", "Hosts and services down for maintenance")) self.input_checkbox_filter_services_on_acknowledged_hosts.setText(_translate("settings_main", "Services on acknowledged hosts")) self.input_checkbox_filter_services_on_down_hosts.setText(_translate("settings_main", "Services on down hosts")) self.input_checkbox_filter_all_high_services.setText(_translate("settings_main", "All high services")) self.input_checkbox_filter_all_disaster_services.setText(_translate("settings_main", "All disaster services")) self.input_checkbox_filter_all_flapping_services.setText(_translate("settings_main", "All flapping services")) self.input_checkbox_filter_all_unknown_services.setText(_translate("settings_main", "All unknown services")) self.input_checkbox_filter_all_warning_services.setText(_translate("settings_main", "All warning services")) self.input_checkbox_filter_all_information_services.setText(_translate("settings_main", "All information services")) self.input_checkbox_filter_all_average_services.setText(_translate("settings_main", "All average services")) self.input_checkbox_filter_services_on_hosts_in_maintenance.setText(_translate("settings_main", "Services on hosts in maintenance")) self.input_checkbox_filter_services_on_unreachable_hosts.setText(_translate("settings_main", "Services on unreachable hosts")) self.input_checkbox_filter_hosts_in_soft_state.setText(_translate("settings_main", "Hosts in soft state")) self.input_checkbox_filter_services_in_soft_state.setText(_translate("settings_main", "Services in soft state")) self.input_checkbox_re_host_enabled.setText(_translate("settings_main", "Regular expression for hosts")) self.input_checkbox_re_status_information_reverse.setText(_translate("settings_main", "reverse")) self.input_checkbox_re_service_reverse.setText(_translate("settings_main", "reverse")) self.tabs.setTabText(self.tabs.indexOf(self.tab_filters), _translate("settings_main", "Filters")) self.button_new_action.setText(_translate("settings_main", "New action...")) self.button_edit_action.setText(_translate("settings_main", "Edit action...")) self.button_copy_action.setText(_translate("settings_main", "Copy action...")) self.button_delete_action.setText(_translate("settings_main", "Delete action")) self.groupbox_connection_method.setTitle(_translate("settings_main", "Connection method:")) self.input_radiobutton_connect_by_host.setText(_translate("settings_main", "Hostname provided by monitor")) self.input_radiobutton_connect_by_dns.setText(_translate("settings_main", "DNS name by looking up IP address")) self.input_radiobutton_connect_by_ip.setText(_translate("settings_main", "IP resolved by hostname")) self.groupbox_browser.setTitle(_translate("settings_main", "Browser:")) self.input_radiobutton_use_default_browser.setText(_translate("settings_main", "Use default browser")) self.input_radiobutton_use_custom_browser.setText(_translate("settings_main", "Use alternative browser")) self.groupbox_custom_browser.setTitle(_translate("settings_main", "Custom browser:")) self.button_choose_browser.setText(_translate("settings_main", "Choose browser...")) self.tabs.setTabText(self.tabs.indexOf(self.tab_actions), _translate("settings_main", "Actions")) self.input_checkbox_notification.setText(_translate("settings_main", "Enable notification")) self.notification_groupbox.setTitle(_translate("settings_main", "Notifications:")) self.input_checkbox_notify_if_warning.setText(_translate("settings_main", "WARNING")) self.input_checkbox_notify_if_critical.setText(_translate("settings_main", "CRITICAL")) self.input_checkbox_notify_if_unknown.setText(_translate("settings_main", "UNKNOWN")) self.input_checkbox_notify_if_unreachable.setText(_translate("settings_main", "UNREACHABLE")) self.input_checkbox_notify_if_down.setText(_translate("settings_main", "DOWN")) self.input_checkbox_notify_if_information.setText(_translate("settings_main", "INFORMATION")) self.input_checkbox_notify_if_disaster.setText(_translate("settings_main", "DISASTER")) self.input_checkbox_notify_if_average.setText(_translate("settings_main", "AVERAGE")) self.input_checkbox_notify_if_high.setText(_translate("settings_main", "HIGH")) self.input_checkbox_notification_flashing.setText(_translate("settings_main", "Flashing statusbar/appindicator/systray icon")) self.input_checkbox_notification_desktop.setText(_translate("settings_main", "Desktop notification")) self.input_checkbox_notification_sound.setText(_translate("settings_main", "Enable sound")) self.notification_sounds_groupbox.setTitle(_translate("settings_main", "Sounds:")) self.notification_custom_sounds_groupbox.setTitle(_translate("settings_main", "Custom sounds:")) self.button_choose_critical.setText(_translate("settings_main", "Choose file...")) self.button_play_warning.setText(_translate("settings_main", "Play")) self.label_notification_sound_options_custom_sounds_warning.setText(_translate("settings_main", "WARNING:")) self.label_notification_sound_options_custom_sounds_critical.setText(_translate("settings_main", "CRITICAL:")) self.button_choose_down.setText(_translate("settings_main", "Choose file...")) self.button_choose_warning.setText(_translate("settings_main", "Choose file...")) self.button_play_critical.setText(_translate("settings_main", "Play")) self.label_notification_sound_options_custom_sounds_down.setText(_translate("settings_main", "DOWN:")) self.button_play_down.setText(_translate("settings_main", "Play")) self.input_checkbox_notification_sound_repeat.setText(_translate("settings_main", "Repeat sound until notification has been noticed")) self.input_radiobutton_notification_default_sound.setText(_translate("settings_main", "Use default Nagios sounds")) self.input_radiobutton_notification_custom_sound.setText(_translate("settings_main", "Use custom sounds")) self.input_checkbox_notification_actions.setText(_translate("settings_main", "Enable notification actions")) self.notification_actions_groupbox.setTitle(_translate("settings_main", "Notification actions:")) self.input_lineedit_notification_action_ok_string.setToolTip(_translate("settings_main", "Available variables for action strings:\n" "\n" "$HOST$ - host as in monitor\n" "$SERVICE$ - service as in monitor\n" "$MONITOR$ - monitor address\n" "$MONITOR-CGI$ - monitor CGI address\n" "$ADDRESS$ - address of host, delivered from connection method\n" "$USERNAME$ - username on monitor\n" "$STATUS-INFO$ - status information for host or service\n" "$PASSWORD$ - username\'s password on monitor\n" "$COMMENT-ACK$ - default acknowledge comment\n" "$COMMENT-DOWN$ - default downtime comment\n" "$COMMENT-SUBMIT$ - default submit check result comment\n" "\n" "$TRANSID$ - only useful for Check_MK as _transid=$TRANSID$")) self.input_checkbox_notification_action_critical.setText(_translate("settings_main", "CRITICAL")) self.input_checkbox_notification_action_down.setText(_translate("settings_main", "DOWN")) self.input_checkbox_notification_action_ok.setText(_translate("settings_main", "OK")) self.input_checkbox_notification_action_warning.setText(_translate("settings_main", "WARNING")) self.input_lineedit_notification_action_down_string.setToolTip(_translate("settings_main", "Available variables for action strings:\n" "\n" "$HOST$ - host as in monitor\n" "$SERVICE$ - service as in monitor\n" "$MONITOR$ - monitor address\n" "$MONITOR-CGI$ - monitor CGI address\n" "$ADDRESS$ - address of host, delivered from connection method\n" "$USERNAME$ - username on monitor\n" "$STATUS-INFO$ - status information for host or service\n" "$PASSWORD$ - username\'s password on monitor\n" "$COMMENT-ACK$ - default acknowledge comment\n" "$COMMENT-DOWN$ - default downtime comment\n" "$COMMENT-SUBMIT$ - default submit check result comment\n" "\n" "$TRANSID$ - only useful for Check_MK as _transid=$TRANSID$")) self.input_checkbox_notification_custom_action.setText(_translate("settings_main", "Use custom notification action")) self.notification_custom_action_groupbox.setTitle(_translate("settings_main", "Custom notification action:")) self.label_notification_custom_action_string.setText(_translate("settings_main", "Action string:")) self.input_checkbox_notification_custom_action_single.setText(_translate("settings_main", "Run one single action for every event")) self.label_notification_custom_action_separator.setText(_translate("settings_main", "Event separator:")) self.input_lineedit_notification_custom_action_string.setToolTip(_translate("settings_main", "Available variables for action strings:\n" "\n" "$HOST$ - host as in monitor\n" "$SERVICE$ - service as in monitor\n" "$MONITOR$ - monitor address\n" "$MONITOR-CGI$ - monitor CGI address\n" "$ADDRESS$ - address of host, delivered from connection method\n" "$USERNAME$ - username on monitor\n" "$STATUS-INFO$ - status information for host or service\n" "$PASSWORD$ - username\'s password on monitor\n" "$COMMENT-ACK$ - default acknowledge comment\n" "$COMMENT-DOWN$ - default downtime comment\n" "$COMMENT-SUBMIT$ - default submit check result comment\n" "\n" "$TRANSID$ - only useful for Check_MK as _transid=$TRANSID$")) self.input_lineedit_notification_action_critical_string.setToolTip(_translate("settings_main", "Available variables for action strings:\n" "\n" "$HOST$ - host as in monitor\n" "$SERVICE$ - service as in monitor\n" "$MONITOR$ - monitor address\n" "$MONITOR-CGI$ - monitor CGI address\n" "$ADDRESS$ - address of host, delivered from connection method\n" "$USERNAME$ - username on monitor\n" "$STATUS-INFO$ - status information for host or service\n" "$PASSWORD$ - username\'s password on monitor\n" "$COMMENT-ACK$ - default acknowledge comment\n" "$COMMENT-DOWN$ - default downtime comment\n" "$COMMENT-SUBMIT$ - default submit check result comment\n" "\n" "$TRANSID$ - only useful for Check_MK as _transid=$TRANSID$")) self.input_lineedit_notification_action_warning_string.setToolTip(_translate("settings_main", "Available variables for action strings:\n" "\n" "$HOST$ - host as in monitor\n" "$SERVICE$ - service as in monitor\n" "$MONITOR$ - monitor address\n" "$MONITOR-CGI$ - monitor CGI address\n" "$ADDRESS$ - address of host, delivered from connection method\n" "$USERNAME$ - username on monitor\n" "$STATUS-INFO$ - status information for host or service\n" "$PASSWORD$ - username\'s password on monitor\n" "$COMMENT-ACK$ - default acknowledge comment\n" "$COMMENT-DOWN$ - default downtime comment\n" "$COMMENT-SUBMIT$ - default submit check result comment\n" "\n" "$TRANSID$ - only useful for Check_MK as _transid=$TRANSID$")) self.tabs.setTabText(self.tabs.indexOf(self.tab_notifications), _translate("settings_main", "Notifications")) self.label_colors.setText(_translate("settings_main", "Set colors for text and background of status information:")) self.states_groupbox.setTitle(_translate("settings_main", "Status:")) self.label_color_ok.setText(_translate("settings_main", " OK ")) self.label_color_information.setText(_translate("settings_main", " INFORMATION")) self.label_color_warning.setText(_translate("settings_main", " WARNING ")) self.label_color_average.setText(_translate("settings_main", " AVERAGE ")) self.label_color_high.setText(_translate("settings_main", " HIGH ")) self.label_color_critical.setText(_translate("settings_main", " CRITICAL ")) self.label_color_disaster.setText(_translate("settings_main", " DISASTER ")) self.label_color_unknown.setText(_translate("settings_main", " UNKNOWN ")) self.label_color_unreachable.setText(_translate("settings_main", " UNREACHABLE ")) self.label_color_down.setText(_translate("settings_main", " DOWN ")) self.label_color_error.setText(_translate("settings_main", " ERROR ")) self.text_groupbox.setTitle(_translate("settings_main", "Text:")) self.background_groupbox.setTitle(_translate("settings_main", "Background:")) self.button_colors_reset.setText(_translate("settings_main", "Reset to previous colors")) self.button_colors_defaults.setText(_translate("settings_main", "Revert to default colors")) self.groupBox_color_grid.setTitle(_translate("settings_main", "Colored grid:")) self.input_checkbox_show_grid.setText(_translate("settings_main", "Use alternating colors for better readability")) self.input_checkbox_grid_use_custom_intensity.setText(_translate("settings_main", "Customize intensity of alternation")) self.label_intensity_information_0.setText(_translate("settings_main", " INFORMATION")) self.label_intensity_information_1.setText(_translate("settings_main", " INFORMATION")) self.label_intensity_warning_0.setText(_translate("settings_main", " WARNING")) self.label_intensity_warning_1.setText(_translate("settings_main", " WARNING")) self.label_intensity_average_0.setText(_translate("settings_main", " AVERAGE")) self.label_intensity_average_1.setText(_translate("settings_main", " AVERAGE")) self.label_intensity_high_0.setText(_translate("settings_main", " HIGH")) self.label_intensity_high_1.setText(_translate("settings_main", " HIGH")) self.label_intensity_critical_0.setText(_translate("settings_main", " CRITICAL")) self.label_intensity_critical_1.setText(_translate("settings_main", " CRITICAL")) self.label_intensity_disaster_0.setText(_translate("settings_main", " DISASTER")) self.label_intensity_disaster_1.setText(_translate("settings_main", " DISASTER")) self.label_intensity_unknown_0.setText(_translate("settings_main", " UNKNOWN")) self.label_intensity_unknown_1.setText(_translate("settings_main", " UNKNOWN")) self.label_intensity_unreachable_0.setText(_translate("settings_main", " UNREACHABLE")) self.label_intensity_unreachable_1.setText(_translate("settings_main", " UNREACHABLE")) self.label_intensity_down_0.setText(_translate("settings_main", " DOWN")) self.label_intensity_down_1.setText(_translate("settings_main", " DOWN")) self.tabs.setTabText(self.tabs.indexOf(self.tab_colors), _translate("settings_main", "Colors")) self.label_defaults.setText(_translate("settings_main", "Set some default settings for default actions:")) self.label_defaults_acknowledge.setTitle(_translate("settings_main", "Acknowledgements:")) self.input_checkbox_defaults_acknowledge_sticky.setText(_translate("settings_main", "Sticky acknowledgement")) self.input_checkbox_defaults_acknowledge_send_notification.setText(_translate("settings_main", "Send notification")) self.input_checkbox_defaults_acknowledge_persistent_comment.setText(_translate("settings_main", "Persistent comment")) self.input_checkbox_defaults_acknowledge_all_services.setText(_translate("settings_main", "Acknowledge all services on host")) self.label_defaults_acknowledge_comment.setText(_translate("settings_main", "Comment:")) self.label_defaults_downtime.setTitle(_translate("settings_main", "Downtime:")) self.label_defaults_downtime_comment.setText(_translate("settings_main", "Comment:")) self.label_defaults_duration_comment.setText(_translate("settings_main", "Duration:")) self.label_duration_hour.setText(_translate("settings_main", "hours")) self.label_duration_minutes.setText(_translate("settings_main", "minutes")) self.input_radiobutton_defaults_downtime_type_flexible.setText(_translate("settings_main", "Flexible")) self.input_radiobutton_defaults_downtime_type_fixed.setText(_translate("settings_main", "Fixed")) self.label_defaults_downtime_type.setText(_translate("settings_main", "Type:")) self.label_acknowledge_submit_result.setTitle(_translate("settings_main", "Submit check result:")) self.label_defaults_submit_check_result_comment.setText(_translate("settings_main", "Comment:")) self.tabs.setTabText(self.tabs.indexOf(self.tab_defaults), _translate("settings_main", "Defaults")) Nagstamon/Nagstamon/QUI/settings_main.ui000066400000000000000000003242461316117564000206140ustar00rootroot00000000000000 Henri Wahl settings_main 0 0 620 1109 0 0 16777215 16777215 Nagstamon 2.0 settings true 0 0 4 Servers 10 10 10 10 5 5 5 5 5 0 Debug mode true Debug to file: 5 0 5 0 5 New server... Edit server... Copy server... Delete server Qt::Vertical 20 40 5 5 5 5 5 Use system keyring for server credentials 5 5 5 5 5 Update interval: 999 seconds Qt::Horizontal 40 20 5 5 5 5 0 Check for new version at startup Qt::Horizontal 40 20 Check for new version now 0 200 16777215 16777215 QListView::Adjust Qt::Vertical 0 0 Display 10 10 10 10 5 Statusbar display size: 5 10 10 10 10 5 5 5 5 5 Short [ 1 | 4 ] Long [ 1 WARNING | 4 CRITICAL ] Qt::Vertical 0 0 Statusbar details popup: 10 10 10 10 5 5 5 5 5 5 Close when clicking statusbar buttongroup_close_statusbar Popup when clicking statusbar buttongroup_popup_statuswindow Popup when hovering over statusbar buttongroup_popup_statuswindow Close when hovering out of popup buttongroup_close_statusbar Close when clicking somewhere buttongroup_close_statusbar Highlight new events in status details overview Show tooltips in status details overview 5 5 5 5 5 Default sort field: Default sort order: Appearance: 10 10 10 10 5 Icon in systray Use offset to correct position problems Display to use: Offset for statuswindow: Fullscreen Floating statusbar Window Font: 5 10 10 10 10 5 5 5 5 5 QFrame::Box QFrame::Sunken The CRITICAL Fox jumped over the WARNING dog. true 5 5 5 5 Change Font... Revert to default font Filters 10 10 10 10 5 5 5 5 5 5 0 0 reverse <a href=http://docs.python.org/howto/regex.html>See Python Regular Expressions HOWTO for filtering details.</a> true Qt::TextBrowserInteraction Qt::Vertical 0 0 Regular expression for status informations Regular expression for services Filter out the following: 10 10 10 10 10 5 All down hosts All unreachable hosts All flapping hosts All critical services Acknowledged hosts and services Hosts and services with disabled notifications Hosts and services with disabled checks Hosts and services down for maintenance Services on acknowledged hosts Services on down hosts All high services All disaster services All flapping services All unknown services All warning services All information services All average services Services on hosts in maintenance Services on unreachable hosts Hosts in soft state Services in soft state Regular expression for hosts 5 5 5 5 5 reverse 5 5 5 5 5 reverse Actions 10 10 10 10 5 0 0 0 200 QListView::Adjust 5 0 5 0 5 New action... Edit action... Copy action... Delete action Qt::Vertical 20 40 Connection method: 5 10 10 10 10 Hostname provided by monitor DNS name by looking up IP address IP resolved by hostname Qt::Vertical 0 0 Browser: 5 10 10 10 10 Use default browser Use alternative browser Custom browser: 10 10 10 10 5 Choose browser... Notifications 5 10 10 10 10 Enable notification Notifications: 5 10 10 10 10 5 0 0 0 0 WARNING CRITICAL UNKNOWN UNREACHABLE DOWN Qt::Horizontal 40 20 INFORMATION DISASTER AVERAGE HIGH Qt::Horizontal 40 20 Flashing statusbar/appindicator/systray icon Desktop notification Enable sound Sounds: 10 10 10 10 5 Custom sounds: 10 10 10 10 5 Choose file... Play WARNING: CRITICAL: Choose file... Choose file... Play DOWN: Play Repeat sound until notification has been noticed Use default Nagios sounds Use custom sounds Enable notification actions Notification actions: 10 10 10 10 5 Available variables for action strings: $HOST$ - host as in monitor $SERVICE$ - service as in monitor $MONITOR$ - monitor address $MONITOR-CGI$ - monitor CGI address $ADDRESS$ - address of host, delivered from connection method $USERNAME$ - username on monitor $STATUS-INFO$ - status information for host or service $PASSWORD$ - username's password on monitor $COMMENT-ACK$ - default acknowledge comment $COMMENT-DOWN$ - default downtime comment $COMMENT-SUBMIT$ - default submit check result comment $TRANSID$ - only useful for Check_MK as _transid=$TRANSID$ CRITICAL DOWN OK WARNING Available variables for action strings: $HOST$ - host as in monitor $SERVICE$ - service as in monitor $MONITOR$ - monitor address $MONITOR-CGI$ - monitor CGI address $ADDRESS$ - address of host, delivered from connection method $USERNAME$ - username on monitor $STATUS-INFO$ - status information for host or service $PASSWORD$ - username's password on monitor $COMMENT-ACK$ - default acknowledge comment $COMMENT-DOWN$ - default downtime comment $COMMENT-SUBMIT$ - default submit check result comment $TRANSID$ - only useful for Check_MK as _transid=$TRANSID$ Use custom notification action Custom notification action: 10 10 10 10 5 Action string: Run one single action for every event Event separator: Available variables for action strings: $HOST$ - host as in monitor $SERVICE$ - service as in monitor $MONITOR$ - monitor address $MONITOR-CGI$ - monitor CGI address $ADDRESS$ - address of host, delivered from connection method $USERNAME$ - username on monitor $STATUS-INFO$ - status information for host or service $PASSWORD$ - username's password on monitor $COMMENT-ACK$ - default acknowledge comment $COMMENT-DOWN$ - default downtime comment $COMMENT-SUBMIT$ - default submit check result comment $TRANSID$ - only useful for Check_MK as _transid=$TRANSID$ Available variables for action strings: $HOST$ - host as in monitor $SERVICE$ - service as in monitor $MONITOR$ - monitor address $MONITOR-CGI$ - monitor CGI address $ADDRESS$ - address of host, delivered from connection method $USERNAME$ - username on monitor $STATUS-INFO$ - status information for host or service $PASSWORD$ - username's password on monitor $COMMENT-ACK$ - default acknowledge comment $COMMENT-DOWN$ - default downtime comment $COMMENT-SUBMIT$ - default submit check result comment $TRANSID$ - only useful for Check_MK as _transid=$TRANSID$ Available variables for action strings: $HOST$ - host as in monitor $SERVICE$ - service as in monitor $MONITOR$ - monitor address $MONITOR-CGI$ - monitor CGI address $ADDRESS$ - address of host, delivered from connection method $USERNAME$ - username on monitor $STATUS-INFO$ - status information for host or service $PASSWORD$ - username's password on monitor $COMMENT-ACK$ - default acknowledge comment $COMMENT-DOWN$ - default downtime comment $COMMENT-SUBMIT$ - default submit check result comment $TRANSID$ - only useful for Check_MK as _transid=$TRANSID$ Qt::Vertical 0 0 Colors 10 10 10 10 5 Qt::Vertical 20 40 Set colors for text and background of status information: Status: 5 10 10 10 5 OK INFORMATION WARNING AVERAGE HIGH CRITICAL DISASTER UNKNOWN UNREACHABLE DOWN ERROR Text: 5 10 10 10 5 Background: 5 10 5 10 10 5 5 5 5 5 Reset to previous colors Revert to default colors Colored grid: 10 10 10 10 5 Use alternating colors for better readability true Customize intensity of alternation 0 0 50 Qt::Horizontal 0 5 5 5 5 true INFORMATION Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 true INFORMATION Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter true WARNING Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 true WARNING Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter true AVERAGE Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 true AVERAGE Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter true HIGH Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 0 true HIGH Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter true CRITICAL Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter true CRITICAL Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter true DISASTER Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter true DISASTER Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter true UNKNOWN Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter true UNKNOWN Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter true UNREACHABLE Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter true UNREACHABLE Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter true DOWN Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter true DOWN Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Defaults 5 10 10 10 10 Set some default settings for default actions: Acknowledgements: 5 10 10 10 10 Sticky acknowledgement Send notification Persistent comment Acknowledge all services on host 5 5 5 5 Comment: Downtime: 10 10 10 10 5 Comment: Duration: 5 5 5 5 hours minutes Qt::Horizontal 40 20 5 5 5 5 Flexible Fixed Type: Qt::Horizontal 40 20 Submit check result: 5 10 10 10 10 Comment: Qt::Vertical 0 0 0 0 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok list_servers button_new_server button_edit_server button_copy_server button_delete_server input_spinbox_update_interval_seconds input_checkbox_use_system_keyring input_checkbox_debug_mode input_checkbox_debug_to_file input_lineedit_debug_file input_checkbox_check_for_new_version button_check_for_new_version_now input_radiobutton_long_display input_radiobutton_short_display input_radiobutton_statusbar_floating input_radiobutton_icon_in_systray input_radiobutton_fullscreen input_checkbox_systray_offset_use input_spinbox_systray_offset input_combobox_fullscreen_display button_fontchooser button_default_font input_radiobutton_popup_details_hover input_radiobutton_popup_details_clicking input_radiobutton_close_details_hover input_radiobutton_close_details_clicking input_radiobutton_close_details_clicking_somewhere input_checkbox_highlight_new_events input_checkbox_show_tooltips input_combobox_default_sort_field input_combobox_default_sort_order input_checkbox_filter_all_down_hosts input_checkbox_filter_all_unreachable_hosts input_checkbox_filter_all_flapping_hosts input_checkbox_filter_all_critical_services input_checkbox_filter_all_flapping_services input_checkbox_filter_all_unknown_services input_checkbox_filter_all_warning_services input_checkbox_filter_all_information_services input_checkbox_filter_all_average_services input_checkbox_filter_all_disaster_services input_checkbox_filter_all_high_services input_checkbox_filter_acknowledged_hosts_services input_checkbox_filter_hosts_services_disabled_notifications input_checkbox_filter_hosts_services_disabled_checks input_checkbox_filter_hosts_services_maintenance input_checkbox_filter_services_on_acknowledged_hosts input_checkbox_filter_services_on_down_hosts input_checkbox_filter_services_on_hosts_in_maintenance input_checkbox_filter_services_on_unreachable_hosts input_checkbox_filter_hosts_in_soft_state input_checkbox_filter_services_in_soft_state input_checkbox_re_host_enabled input_lineedit_re_host_pattern input_checkbox_re_host_reverse input_checkbox_re_service_enabled input_lineedit_re_service_pattern input_checkbox_re_service_reverse input_checkbox_re_status_information_enabled input_lineedit_re_status_information_pattern input_checkbox_re_status_information_reverse list_actions button_new_action button_edit_action button_copy_action button_delete_action input_radiobutton_connect_by_host input_radiobutton_connect_by_dns input_radiobutton_connect_by_ip input_radiobutton_use_default_browser input_radiobutton_use_custom_browser input_lineedit_custom_browser button_choose_browser input_checkbox_notification input_checkbox_notify_if_warning input_checkbox_notify_if_critical input_checkbox_notify_if_unknown input_checkbox_notify_if_unreachable input_checkbox_notify_if_down input_checkbox_notify_if_information input_checkbox_notify_if_disaster input_checkbox_notify_if_average input_checkbox_notify_if_high input_checkbox_notification_flashing input_checkbox_notification_desktop input_checkbox_notification_sound input_checkbox_notification_sound_repeat input_radiobutton_notification_default_sound input_radiobutton_notification_custom_sound input_lineedit_notification_custom_sound_warning button_choose_warning button_play_warning input_lineedit_notification_custom_sound_critical button_choose_critical button_play_critical input_lineedit_notification_custom_sound_down button_choose_down button_play_down input_checkbox_notification_actions input_checkbox_notification_action_warning input_lineedit_notification_action_warning_string input_checkbox_notification_action_critical input_lineedit_notification_action_critical_string input_checkbox_notification_action_down input_lineedit_notification_action_down_string input_checkbox_notification_action_ok input_lineedit_notification_action_ok_string input_checkbox_notification_custom_action input_lineedit_notification_custom_action_string input_lineedit_notification_custom_action_separator input_checkbox_notification_custom_action_single input_button_color_ok_text input_button_color_ok_background input_button_color_information_text input_button_color_information_background input_button_color_warning_text input_button_color_warning_background input_button_color_average_text input_button_color_average_background input_button_color_high_text input_button_color_high_background input_button_color_critical_text input_button_color_critical_background input_button_color_disaster_text input_button_color_disaster_background input_button_color_unknown_text input_button_color_unknown_background input_button_color_unreachable_text input_button_color_unreachable_background input_button_color_down_text input_button_color_down_background input_button_color_error_text input_button_color_error_background button_colors_reset button_colors_defaults input_checkbox_defaults_acknowledge_sticky input_checkbox_defaults_acknowledge_send_notification input_checkbox_defaults_acknowledge_persistent_comment input_checkbox_defaults_acknowledge_all_services input_lineedit_defaults_acknowledge_comment input_radiobutton_defaults_downtime_type_fixed input_radiobutton_defaults_downtime_type_flexible input_spinbox_defaults_downtime_duration_hours input_spinbox_defaults_downtime_duration_minutes input_lineedit_defaults_downtime_comment input_lineedit_defaults_submit_check_result_comment tabs input_checkbox_show_grid input_checkbox_grid_use_custom_intensity input_slider_grid_alternation_intensity input_radiobutton_windowed button_box accepted() settings_main accept() 404 986 386 376 button_box rejected() settings_main reject() 404 986 386 376 Nagstamon/Nagstamon/QUI/settings_server.py000066400000000000000000000646561316117564000212170ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'settings_server.ui' # # Created by: PyQt5 UI code generator 5.8.1 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_settings_server(object): def setupUi(self, settings_server): settings_server.setObjectName("settings_server") settings_server.resize(659, 1183) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(settings_server.sizePolicy().hasHeightForWidth()) settings_server.setSizePolicy(sizePolicy) settings_server.setModal(True) self.gridLayout = QtWidgets.QGridLayout(settings_server) self.gridLayout.setContentsMargins(10, 10, 10, 10) self.gridLayout.setHorizontalSpacing(5) self.gridLayout.setVerticalSpacing(2) self.gridLayout.setObjectName("gridLayout") self.label_password = QtWidgets.QLabel(settings_server) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_password.sizePolicy().hasHeightForWidth()) self.label_password.setSizePolicy(sizePolicy) self.label_password.setObjectName("label_password") self.gridLayout.addWidget(self.label_password, 8, 0, 1, 1) self.input_lineedit_monitor_url = QtWidgets.QLineEdit(settings_server) self.input_lineedit_monitor_url.setInputMask("") self.input_lineedit_monitor_url.setObjectName("input_lineedit_monitor_url") self.gridLayout.addWidget(self.input_lineedit_monitor_url, 4, 2, 1, 2) self.label_name = QtWidgets.QLabel(settings_server) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_name.sizePolicy().hasHeightForWidth()) self.label_name.setSizePolicy(sizePolicy) self.label_name.setObjectName("label_name") self.gridLayout.addWidget(self.label_name, 3, 0, 1, 1) self.input_lineedit_username = QtWidgets.QLineEdit(settings_server) self.input_lineedit_username.setObjectName("input_lineedit_username") self.gridLayout.addWidget(self.input_lineedit_username, 6, 2, 1, 1) self.input_lineedit_monitor_cgi_url = QtWidgets.QLineEdit(settings_server) self.input_lineedit_monitor_cgi_url.setObjectName("input_lineedit_monitor_cgi_url") self.gridLayout.addWidget(self.input_lineedit_monitor_cgi_url, 5, 2, 1, 2) self.input_combobox_type = QtWidgets.QComboBox(settings_server) self.input_combobox_type.setObjectName("input_combobox_type") self.gridLayout.addWidget(self.input_combobox_type, 1, 2, 1, 1) self.input_lineedit_password = QtWidgets.QLineEdit(settings_server) self.input_lineedit_password.setEchoMode(QtWidgets.QLineEdit.Password) self.input_lineedit_password.setObjectName("input_lineedit_password") self.gridLayout.addWidget(self.input_lineedit_password, 8, 2, 1, 1) self.input_checkbox_use_proxy = QtWidgets.QCheckBox(settings_server) self.input_checkbox_use_proxy.setObjectName("input_checkbox_use_proxy") self.gridLayout.addWidget(self.input_checkbox_use_proxy, 17, 0, 1, 1) self.groupbox_proxy = QtWidgets.QGroupBox(settings_server) self.groupbox_proxy.setObjectName("groupbox_proxy") self.gridLayout_2 = QtWidgets.QGridLayout(self.groupbox_proxy) self.gridLayout_2.setContentsMargins(10, 10, 10, 10) self.gridLayout_2.setSpacing(5) self.gridLayout_2.setObjectName("gridLayout_2") self.label_proxy_password = QtWidgets.QLabel(self.groupbox_proxy) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_proxy_password.sizePolicy().hasHeightForWidth()) self.label_proxy_password.setSizePolicy(sizePolicy) self.label_proxy_password.setObjectName("label_proxy_password") self.gridLayout_2.addWidget(self.label_proxy_password, 3, 0, 1, 1) self.label_proxy_username = QtWidgets.QLabel(self.groupbox_proxy) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_proxy_username.sizePolicy().hasHeightForWidth()) self.label_proxy_username.setSizePolicy(sizePolicy) self.label_proxy_username.setObjectName("label_proxy_username") self.gridLayout_2.addWidget(self.label_proxy_username, 2, 0, 1, 1) self.input_lineedit_proxy_username = QtWidgets.QLineEdit(self.groupbox_proxy) self.input_lineedit_proxy_username.setObjectName("input_lineedit_proxy_username") self.gridLayout_2.addWidget(self.input_lineedit_proxy_username, 2, 1, 1, 1) self.input_lineedit_proxy_address = QtWidgets.QLineEdit(self.groupbox_proxy) self.input_lineedit_proxy_address.setObjectName("input_lineedit_proxy_address") self.gridLayout_2.addWidget(self.input_lineedit_proxy_address, 1, 1, 1, 1) self.label_proxy_address = QtWidgets.QLabel(self.groupbox_proxy) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_proxy_address.sizePolicy().hasHeightForWidth()) self.label_proxy_address.setSizePolicy(sizePolicy) self.label_proxy_address.setObjectName("label_proxy_address") self.gridLayout_2.addWidget(self.label_proxy_address, 1, 0, 1, 1) self.input_lineedit_proxy_password = QtWidgets.QLineEdit(self.groupbox_proxy) self.input_lineedit_proxy_password.setEchoMode(QtWidgets.QLineEdit.Password) self.input_lineedit_proxy_password.setObjectName("input_lineedit_proxy_password") self.gridLayout_2.addWidget(self.input_lineedit_proxy_password, 3, 1, 1, 1) self.input_checkbox_use_proxy_from_os = QtWidgets.QCheckBox(self.groupbox_proxy) self.input_checkbox_use_proxy_from_os.setObjectName("input_checkbox_use_proxy_from_os") self.gridLayout_2.addWidget(self.input_checkbox_use_proxy_from_os, 4, 0, 1, 2) self.gridLayout.addWidget(self.groupbox_proxy, 18, 0, 1, 4) self.button_box = QtWidgets.QDialogButtonBox(settings_server) self.button_box.setOrientation(QtCore.Qt.Horizontal) self.button_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) self.button_box.setObjectName("button_box") self.gridLayout.addWidget(self.button_box, 29, 3, 1, 1) self.label_server_type = QtWidgets.QLabel(settings_server) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_server_type.sizePolicy().hasHeightForWidth()) self.label_server_type.setSizePolicy(sizePolicy) self.label_server_type.setObjectName("label_server_type") self.gridLayout.addWidget(self.label_server_type, 1, 0, 1, 1) self.label_username = QtWidgets.QLabel(settings_server) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_username.sizePolicy().hasHeightForWidth()) self.label_username.setSizePolicy(sizePolicy) self.label_username.setObjectName("label_username") self.gridLayout.addWidget(self.label_username, 6, 0, 1, 1) self.label_monitor_url = QtWidgets.QLabel(settings_server) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_monitor_url.sizePolicy().hasHeightForWidth()) self.label_monitor_url.setSizePolicy(sizePolicy) self.label_monitor_url.setObjectName("label_monitor_url") self.gridLayout.addWidget(self.label_monitor_url, 4, 0, 1, 1) self.input_lineedit_name = QtWidgets.QLineEdit(settings_server) self.input_lineedit_name.setObjectName("input_lineedit_name") self.gridLayout.addWidget(self.input_lineedit_name, 3, 2, 1, 2) self.label_monitor_cgi_url = QtWidgets.QLabel(settings_server) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_monitor_cgi_url.sizePolicy().hasHeightForWidth()) self.label_monitor_cgi_url.setSizePolicy(sizePolicy) self.label_monitor_cgi_url.setObjectName("label_monitor_cgi_url") self.gridLayout.addWidget(self.label_monitor_cgi_url, 5, 0, 1, 1) self.input_checkbox_enabled = QtWidgets.QCheckBox(settings_server) self.input_checkbox_enabled.setObjectName("input_checkbox_enabled") self.gridLayout.addWidget(self.input_checkbox_enabled, 0, 0, 1, 4) self.input_checkbox_save_password = QtWidgets.QCheckBox(settings_server) self.input_checkbox_save_password.setObjectName("input_checkbox_save_password") self.gridLayout.addWidget(self.input_checkbox_save_password, 8, 3, 1, 1) spacerItem = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.gridLayout.addItem(spacerItem, 26, 0, 1, 4) self.input_checkbox_show_options = QtWidgets.QCheckBox(settings_server) self.input_checkbox_show_options.setObjectName("input_checkbox_show_options") self.gridLayout.addWidget(self.input_checkbox_show_options, 27, 0, 1, 4) self.groupbox_options = QtWidgets.QGroupBox(settings_server) self.groupbox_options.setObjectName("groupbox_options") self.gridLayout_3 = QtWidgets.QGridLayout(self.groupbox_options) self.gridLayout_3.setContentsMargins(10, 10, 10, 10) self.gridLayout_3.setHorizontalSpacing(10) self.gridLayout_3.setVerticalSpacing(5) self.gridLayout_3.setObjectName("gridLayout_3") self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.input_lineedit_custom_cert_ca_file = QtWidgets.QLineEdit(self.groupbox_options) self.input_lineedit_custom_cert_ca_file.setObjectName("input_lineedit_custom_cert_ca_file") self.horizontalLayout.addWidget(self.input_lineedit_custom_cert_ca_file) self.button_choose_custom_cert_ca_file = QtWidgets.QPushButton(self.groupbox_options) self.button_choose_custom_cert_ca_file.setObjectName("button_choose_custom_cert_ca_file") self.horizontalLayout.addWidget(self.button_choose_custom_cert_ca_file) self.gridLayout_3.addLayout(self.horizontalLayout, 2, 2, 1, 2) self.label_host_filter = QtWidgets.QLabel(self.groupbox_options) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_host_filter.sizePolicy().hasHeightForWidth()) self.label_host_filter.setSizePolicy(sizePolicy) self.label_host_filter.setObjectName("label_host_filter") self.gridLayout_3.addWidget(self.label_host_filter, 9, 0, 1, 1) self.groupbox_check_mk_views = QtWidgets.QGroupBox(self.groupbox_options) self.groupbox_check_mk_views.setObjectName("groupbox_check_mk_views") self.gridLayout_4 = QtWidgets.QGridLayout(self.groupbox_check_mk_views) self.gridLayout_4.setObjectName("gridLayout_4") self.label_check_mk_view_hosts = QtWidgets.QLabel(self.groupbox_check_mk_views) self.label_check_mk_view_hosts.setObjectName("label_check_mk_view_hosts") self.gridLayout_4.addWidget(self.label_check_mk_view_hosts, 0, 0, 1, 1) self.label_check_mk_view_services = QtWidgets.QLabel(self.groupbox_check_mk_views) self.label_check_mk_view_services.setObjectName("label_check_mk_view_services") self.gridLayout_4.addWidget(self.label_check_mk_view_services, 1, 0, 1, 1) self.input_lineedit_check_mk_view_services = QtWidgets.QLineEdit(self.groupbox_check_mk_views) self.input_lineedit_check_mk_view_services.setObjectName("input_lineedit_check_mk_view_services") self.gridLayout_4.addWidget(self.input_lineedit_check_mk_view_services, 1, 1, 1, 1) self.input_lineedit_check_mk_view_hosts = QtWidgets.QLineEdit(self.groupbox_check_mk_views) self.input_lineedit_check_mk_view_hosts.setObjectName("input_lineedit_check_mk_view_hosts") self.gridLayout_4.addWidget(self.input_lineedit_check_mk_view_hosts, 0, 1, 1, 1) self.button_check_mk_view_hosts_reset = QtWidgets.QPushButton(self.groupbox_check_mk_views) self.button_check_mk_view_hosts_reset.setObjectName("button_check_mk_view_hosts_reset") self.gridLayout_4.addWidget(self.button_check_mk_view_hosts_reset, 0, 2, 1, 1) self.button_check_mk_view_services_reset = QtWidgets.QPushButton(self.groupbox_check_mk_views) self.button_check_mk_view_services_reset.setObjectName("button_check_mk_view_services_reset") self.gridLayout_4.addWidget(self.button_check_mk_view_services_reset, 1, 2, 1, 1) self.gridLayout_3.addWidget(self.groupbox_check_mk_views, 15, 0, 1, 4) self.label_auth_type = QtWidgets.QLabel(self.groupbox_options) self.label_auth_type.setObjectName("label_auth_type") self.gridLayout_3.addWidget(self.label_auth_type, 5, 0, 1, 1) self.input_checkbox_no_cookie_auth = QtWidgets.QCheckBox(self.groupbox_options) self.input_checkbox_no_cookie_auth.setObjectName("input_checkbox_no_cookie_auth") self.gridLayout_3.addWidget(self.input_checkbox_no_cookie_auth, 11, 0, 1, 4) self.input_checkbox_use_display_name_host = QtWidgets.QCheckBox(self.groupbox_options) self.input_checkbox_use_display_name_host.setObjectName("input_checkbox_use_display_name_host") self.gridLayout_3.addWidget(self.input_checkbox_use_display_name_host, 12, 0, 1, 4) self.input_checkbox_use_display_name_service = QtWidgets.QCheckBox(self.groupbox_options) self.input_checkbox_use_display_name_service.setObjectName("input_checkbox_use_display_name_service") self.gridLayout_3.addWidget(self.input_checkbox_use_display_name_service, 13, 0, 1, 4) spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.gridLayout_3.addItem(spacerItem1, 5, 3, 1, 1) self.label_timeout = QtWidgets.QLabel(self.groupbox_options) self.label_timeout.setObjectName("label_timeout") self.gridLayout_3.addWidget(self.label_timeout, 6, 0, 1, 1) self.input_checkbox_ignore_cert = QtWidgets.QCheckBox(self.groupbox_options) self.input_checkbox_ignore_cert.setObjectName("input_checkbox_ignore_cert") self.gridLayout_3.addWidget(self.input_checkbox_ignore_cert, 0, 0, 1, 4) self.input_lineedit_host_filter = QtWidgets.QLineEdit(self.groupbox_options) self.input_lineedit_host_filter.setText("") self.input_lineedit_host_filter.setObjectName("input_lineedit_host_filter") self.gridLayout_3.addWidget(self.input_lineedit_host_filter, 9, 2, 1, 2) self.label_service_filter = QtWidgets.QLabel(self.groupbox_options) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_service_filter.sizePolicy().hasHeightForWidth()) self.label_service_filter.setSizePolicy(sizePolicy) self.label_service_filter.setObjectName("label_service_filter") self.gridLayout_3.addWidget(self.label_service_filter, 10, 0, 1, 1) self.input_checkbox_force_authuser = QtWidgets.QCheckBox(self.groupbox_options) self.input_checkbox_force_authuser.setObjectName("input_checkbox_force_authuser") self.gridLayout_3.addWidget(self.input_checkbox_force_authuser, 14, 0, 1, 4) self.input_combobox_authentication = QtWidgets.QComboBox(self.groupbox_options) self.input_combobox_authentication.setObjectName("input_combobox_authentication") self.gridLayout_3.addWidget(self.input_combobox_authentication, 5, 2, 1, 1) self.input_lineedit_monitor_site = QtWidgets.QLineEdit(self.groupbox_options) self.input_lineedit_monitor_site.setObjectName("input_lineedit_monitor_site") self.gridLayout_3.addWidget(self.input_lineedit_monitor_site, 16, 2, 1, 2) self.input_checkbox_use_autologin = QtWidgets.QCheckBox(self.groupbox_options) self.input_checkbox_use_autologin.setObjectName("input_checkbox_use_autologin") self.gridLayout_3.addWidget(self.input_checkbox_use_autologin, 7, 0, 1, 4) spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.gridLayout_3.addItem(spacerItem2, 6, 3, 1, 1) self.label_autologin_key = QtWidgets.QLabel(self.groupbox_options) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_autologin_key.sizePolicy().hasHeightForWidth()) self.label_autologin_key.setSizePolicy(sizePolicy) self.label_autologin_key.setObjectName("label_autologin_key") self.gridLayout_3.addWidget(self.label_autologin_key, 8, 0, 1, 1) self.input_lineedit_autologin_key = QtWidgets.QLineEdit(self.groupbox_options) self.input_lineedit_autologin_key.setText("") self.input_lineedit_autologin_key.setObjectName("input_lineedit_autologin_key") self.gridLayout_3.addWidget(self.input_lineedit_autologin_key, 8, 2, 1, 2) self.input_lineedit_service_filter = QtWidgets.QLineEdit(self.groupbox_options) self.input_lineedit_service_filter.setText("") self.input_lineedit_service_filter.setObjectName("input_lineedit_service_filter") self.gridLayout_3.addWidget(self.input_lineedit_service_filter, 10, 2, 1, 2) self.horizontalLayout_timeout_seconds = QtWidgets.QHBoxLayout() self.horizontalLayout_timeout_seconds.setSpacing(5) self.horizontalLayout_timeout_seconds.setObjectName("horizontalLayout_timeout_seconds") self.input_spinbox_timeout = QtWidgets.QSpinBox(self.groupbox_options) self.input_spinbox_timeout.setObjectName("input_spinbox_timeout") self.horizontalLayout_timeout_seconds.addWidget(self.input_spinbox_timeout) self.label_timeout_sec = QtWidgets.QLabel(self.groupbox_options) self.label_timeout_sec.setObjectName("label_timeout_sec") self.horizontalLayout_timeout_seconds.addWidget(self.label_timeout_sec) self.gridLayout_3.addLayout(self.horizontalLayout_timeout_seconds, 6, 2, 1, 1) self.label_monitor_site = QtWidgets.QLabel(self.groupbox_options) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.label_monitor_site.sizePolicy().hasHeightForWidth()) self.label_monitor_site.setSizePolicy(sizePolicy) self.label_monitor_site.setObjectName("label_monitor_site") self.gridLayout_3.addWidget(self.label_monitor_site, 16, 0, 1, 1) self.input_checkbox_custom_cert_use = QtWidgets.QCheckBox(self.groupbox_options) self.input_checkbox_custom_cert_use.setObjectName("input_checkbox_custom_cert_use") self.gridLayout_3.addWidget(self.input_checkbox_custom_cert_use, 1, 0, 1, 3) self.label_custom_ca_file = QtWidgets.QLabel(self.groupbox_options) self.label_custom_ca_file.setObjectName("label_custom_ca_file") self.gridLayout_3.addWidget(self.label_custom_ca_file, 2, 0, 1, 1) self.gridLayout.addWidget(self.groupbox_options, 28, 0, 1, 4) self.retranslateUi(settings_server) QtCore.QMetaObject.connectSlotsByName(settings_server) settings_server.setTabOrder(self.input_checkbox_enabled, self.input_combobox_type) settings_server.setTabOrder(self.input_combobox_type, self.input_lineedit_name) settings_server.setTabOrder(self.input_lineedit_name, self.input_lineedit_monitor_url) settings_server.setTabOrder(self.input_lineedit_monitor_url, self.input_lineedit_monitor_cgi_url) settings_server.setTabOrder(self.input_lineedit_monitor_cgi_url, self.input_lineedit_username) settings_server.setTabOrder(self.input_lineedit_username, self.input_lineedit_password) settings_server.setTabOrder(self.input_lineedit_password, self.input_checkbox_save_password) settings_server.setTabOrder(self.input_checkbox_save_password, self.input_checkbox_use_proxy) settings_server.setTabOrder(self.input_checkbox_use_proxy, self.input_lineedit_proxy_address) settings_server.setTabOrder(self.input_lineedit_proxy_address, self.input_lineedit_proxy_username) settings_server.setTabOrder(self.input_lineedit_proxy_username, self.input_lineedit_proxy_password) settings_server.setTabOrder(self.input_lineedit_proxy_password, self.input_checkbox_use_proxy_from_os) def retranslateUi(self, settings_server): _translate = QtCore.QCoreApplication.translate settings_server.setWindowTitle(_translate("settings_server", "Dialog")) self.label_password.setText(_translate("settings_server", "Password:")) self.input_lineedit_monitor_url.setText(_translate("settings_server", "https://monitor-server")) self.label_name.setText(_translate("settings_server", "Monitor name:")) self.input_lineedit_username.setText(_translate("settings_server", "username")) self.input_lineedit_monitor_cgi_url.setText(_translate("settings_server", "https://monitor-server/monitor/cgi-bin")) self.input_lineedit_password.setText(_translate("settings_server", "1234567890")) self.input_checkbox_use_proxy.setText(_translate("settings_server", "Use proxy")) self.groupbox_proxy.setTitle(_translate("settings_server", "Proxy:")) self.label_proxy_password.setText(_translate("settings_server", "Proxy password:")) self.label_proxy_username.setText(_translate("settings_server", "Proxy username:")) self.input_lineedit_proxy_username.setText(_translate("settings_server", "proxyusername")) self.input_lineedit_proxy_address.setText(_translate("settings_server", "http://proxy:port/")) self.label_proxy_address.setText(_translate("settings_server", "Proxy address:")) self.input_lineedit_proxy_password.setText(_translate("settings_server", "1234567890")) self.input_checkbox_use_proxy_from_os.setText(_translate("settings_server", "Use proxy from OS")) self.label_server_type.setText(_translate("settings_server", "Monitor type:")) self.label_username.setText(_translate("settings_server", "Username:")) self.label_monitor_url.setText(_translate("settings_server", "Monitor URL:")) self.input_lineedit_name.setText(_translate("settings_server", "Monitor server")) self.label_monitor_cgi_url.setText(_translate("settings_server", "Monitor CGI URL:")) self.input_checkbox_enabled.setText(_translate("settings_server", "Enabled")) self.input_checkbox_save_password.setText(_translate("settings_server", "Save password")) self.input_checkbox_show_options.setText(_translate("settings_server", "Show more options")) self.groupbox_options.setTitle(_translate("settings_server", "Options:")) self.button_choose_custom_cert_ca_file.setText(_translate("settings_server", "Choose file...")) self.label_host_filter.setText(_translate("settings_server", "Host filter:")) self.groupbox_check_mk_views.setTitle(_translate("settings_server", "Views:")) self.label_check_mk_view_hosts.setText(_translate("settings_server", "Hosts:")) self.label_check_mk_view_services.setText(_translate("settings_server", "Services:")) self.button_check_mk_view_hosts_reset.setText(_translate("settings_server", "Reset")) self.button_check_mk_view_services_reset.setText(_translate("settings_server", "Reset")) self.label_auth_type.setText(_translate("settings_server", "Authentication:")) self.input_checkbox_no_cookie_auth.setText(_translate("settings_server", "Do not use cookie authentication")) self.input_checkbox_use_display_name_host.setText(_translate("settings_server", "Use display name as host name")) self.input_checkbox_use_display_name_service.setText(_translate("settings_server", "Use display name as service name")) self.label_timeout.setText(_translate("settings_server", "Timeout:")) self.input_checkbox_ignore_cert.setText(_translate("settings_server", "Ignore SSL/TLS certificate")) self.label_service_filter.setText(_translate("settings_server", "Service filter:")) self.input_checkbox_force_authuser.setText(_translate("settings_server", "Only show permitted hosts for \"see all\" users (1.4.0i1 or newer)")) self.input_lineedit_monitor_site.setText(_translate("settings_server", "Site 1")) self.input_checkbox_use_autologin.setText(_translate("settings_server", "Use autologin")) self.label_autologin_key.setText(_translate("settings_server", "Autologin Key:")) self.label_timeout_sec.setText(_translate("settings_server", "seconds")) self.label_monitor_site.setText(_translate("settings_server", "Monitor Site:")) self.input_checkbox_custom_cert_use.setText(_translate("settings_server", "Use custom CA file")) self.label_custom_ca_file.setText(_translate("settings_server", "Custom CA file: ")) Nagstamon/Nagstamon/QUI/settings_server.ui000066400000000000000000000450551316117564000211740ustar00rootroot00000000000000 settings_server 0 0 659 1183 0 0 Dialog true 10 10 10 10 5 2 0 0 Password: https://monitor-server 0 0 Monitor name: username https://monitor-server/monitor/cgi-bin 1234567890 QLineEdit::Password Use proxy Proxy: 10 10 10 10 5 0 0 Proxy password: 0 0 Proxy username: proxyusername http://proxy:port/ 0 0 Proxy address: 1234567890 QLineEdit::Password Use proxy from OS Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok 0 0 Monitor type: 0 0 Username: 0 0 Monitor URL: Monitor server 0 0 Monitor CGI URL: Enabled Save password Qt::Vertical 0 0 Show more options Options: 10 10 10 10 10 5 Choose file... 0 0 Host filter: Views: Hosts: Services: Reset Reset Authentication: Do not use cookie authentication Use display name as host name Use display name as service name Qt::Horizontal 40 20 Timeout: Ignore SSL/TLS certificate 0 0 Service filter: Only show permitted hosts for "see all" users (1.4.0i1 or newer) Site 1 Use autologin Qt::Horizontal 40 20 0 0 Autologin Key: 5 seconds 0 0 Monitor Site: Use custom CA file Custom CA file: input_checkbox_enabled input_combobox_type input_lineedit_name input_lineedit_monitor_url input_lineedit_monitor_cgi_url input_lineedit_username input_lineedit_password input_checkbox_save_password input_checkbox_use_proxy input_lineedit_proxy_address input_lineedit_proxy_username input_lineedit_proxy_password input_checkbox_use_proxy_from_os Nagstamon/Nagstamon/Servers/000077500000000000000000000000001316117564000163715ustar00rootroot00000000000000Nagstamon/Nagstamon/Servers/Centreon.py000066400000000000000000001450331316117564000205260ustar00rootroot00000000000000# encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2014 Henri Wahl et al. # # 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 import urllib.request import urllib.parse import urllib.error import socket import sys import re import copy from Nagstamon.Objects import * from Nagstamon.Servers.Generic import GenericServer from Nagstamon.Config import conf from Nagstamon.Helpers import webbrowser_open class CentreonServer(GenericServer): TYPE = 'Centreon' # centreon generic web interface uses a sid which is needed to ask for news SID = None # HARD/SOFT state mapping HARD_SOFT = {'(H)': 'hard', '(S)': 'soft'} # apparently necessesary because of non-english states as in https://github.com/HenriWahl/Nagstamon/issues/91 (Centeron 2.5) TRANSLATIONS = {'INDISPONIBLE': 'DOWN', 'INJOIGNABLE': 'UNREACHABLE', 'CRITIQUE': 'CRITICAL', 'INCONNU': 'UNKNOWN', 'ALERTE': 'WARNING'} # Entries for monitor default actions in context menu MENU_ACTIONS = ['Monitor', 'Recheck', 'Acknowledge', 'Downtime'] # Centreon works better or at all with html.parser for BeautifulSoup PARSER = 'html.parser' # Needed to detect each Centreon's version centreon_version = None # Token that centreon use to protect the system centreon_token = None # To only detect broker once first_login = True # limit number of services retrived limit_services_number = 9999 def init_config(self): ''' dummy init_config, called at thread start, not really needed here, just omit extra properties ''' # set URLs here already self.init_HTTP() if not self.tls_error: self._define_url() def init_HTTP(self): """ initialize HTTP connection """ if self.session is None: GenericServer.init_HTTP(self) if self.centreon_version is None: result_versioncheck = self.FetchURL(self.monitor_cgi_url + '/index.php', giveback='raw') raw_versioncheck, error_versioncheck = result_versioncheck.result, result_versioncheck.error if error_versioncheck == '': if re.search('2\.2\.[0-9]', raw_versioncheck): self.centreon_version = 2.2 if conf.debug_mode is True: self.Debug(server=self.get_name(), debug='Centreon version detected : 2.2') # URLs for browser shortlinks/buttons on popup window self.BROWSER_URLS = {'monitor': '$MONITOR$/main.php?p=1', 'hosts': '$MONITOR$/main.php?p=20103&o=hpb', 'services': '$MONITOR$/main.php?p=20202&o=svcpb', 'history': '$MONITOR$/main.php?p=203'} elif re.search('2\.[3-6]\.[0-5]', raw_versioncheck): self.centreon_version = 2.3456 if conf.debug_mode is True: self.Debug(server=self.get_name(), debug='Centreon version detected : 2.6.5 <=> 2.3') # URLs for browser shortlinks/buttons on popup window self.BROWSER_URLS = {'monitor': '$MONITOR$/main.php?p=1', 'hosts': '$MONITOR$/main.php?p=20103&o=hpb', 'services': '$MONITOR$/main.php?p=20202&o=svcpb', 'history': '$MONITOR$/main.php?p=203'} elif re.search('2\.6\.[6-9]', raw_versioncheck): self.centreon_version = 2.66 if conf.debug_mode is True: self.Debug(server=self.get_name(), debug='Centreon version detected : 2.6.6') # URLs for browser shortlinks/buttons on popup window self.BROWSER_URLS = {'monitor': '$MONITOR$/main.php?p=1', 'hosts': '$MONITOR$/main.php?p=20103&o=hpb', 'services': '$MONITOR$/main.php?p=20202&o=svcpb', 'history': '$MONITOR$/main.php?p=203'} elif re.search('2\.7\.[0-9]', raw_versioncheck): # Centreon 2.7 only support C. Broker self.centreon_version = 2.7 if conf.debug_mode is True: self.Debug(server=self.get_name(), debug='Centreon version detected : 2.7') # URLs for browser shortlinks/buttons on popup window self.BROWSER_URLS = {'monitor': '$MONITOR$/main.php?', 'hosts': '$MONITOR$/main.php?p=20202&o=hpb', 'services': '$MONITOR$/main.php?p=20201&o=svcpb', 'history': '$MONITOR$/main.php?p=203'} elif re.search('2\.8\.[0-9]', raw_versioncheck): # Centreon 2.8 only support C. Broker self.centreon_version = 2.8 if conf.debug_mode is True: self.Debug(server=self.get_name(), debug='Centreon version detected : 2.8') # URLs for browser shortlinks/buttons on popup window self.BROWSER_URLS = {'monitor': '$MONITOR$/main.php?', 'hosts': '$MONITOR$/main.php?p=20202', 'services': '$MONITOR$/main.php?p=20201', 'history': '$MONITOR$/main.php?p=203'} else: # unsupported version or unable do determine self.centreon_version = 2.8 if conf.debug_mode is True: self.Debug(server=self.get_name(), debug='Centreon version unknown : supposed to be >= 2.8') # URLs for browser shortlinks/buttons on popup window self.BROWSER_URLS = {'monitor': '$MONITOR$/main.php?', 'hosts': '$MONITOR$/main.php?p=20202&o=hpb', 'services': '$MONITOR$/main.php?p=20201&o=svcpb', 'history': '$MONITOR$/main.php?p=203'} else: if conf.debug_mode is True: self.Debug(server=self.get_name(), debug='Error getting the home page : ' + error_versioncheck) if self.first_login: self.SID = self._get_sid().result self.first_login = False del result_versioncheck, raw_versioncheck, error_versioncheck def reset_HTTP(self): ''' Centreon needs deletion of SID ''' self.SID = None self.SID = self._get_sid().result def open_monitor(self, host, service=''): if self.use_autologin is True: auth = '&autologin=1&useralias=' + self.username + '&token=' + self.autologin_key else: auth = '' # Meta - Centreon < 2.7 if host == '_Module_Meta' and self.centreon_version < 2.7: webbrowser_open(self.urls_centreon['index'] + '?' + urllib.parse.urlencode({'p': 20206, 'o': 'meta'}) + auth ) # Meta - Centreon 2.7 elif host == '_Module_Meta' and self.centreon_version == 2.7: webbrowser_open(self.urls_centreon['main'] + '?' + urllib.parse.urlencode({'p':20206, 'o':'meta'}) + auth ) # Meta - Centreon 2.8 elif host == '_Module_Meta' and self.centreon_version == 2.8: m = re.search(r'^.+ \((?P.+)\)$', service) if m: service = m.group('rsd') webbrowser_open(self.urls_centreon['main'] + '?' + urllib.parse.urlencode({'p':20201,'o':'svcd','host_name':'_Module_Meta','service_description':service}) + auth ) # must be a host if service is empty elif service == '': if self.centreon_version == 2.7 or self.centreon_version == 2.8: webbrowser_open(self.urls_centreon['main'] + '?' + urllib.parse.urlencode({'p':20202,'o':'hd', 'host_name':host}) + auth ) else: webbrowser_open(self.urls_centreon['main'] + '?' + urllib.parse.urlencode({'p':201,'o':'hd', 'host_name':host}) + auth ) # so it's a service else: if self.centreon_version == 2.7 or self.centreon_version == 2.8: webbrowser_open(self.urls_centreon['main'] + '?' + urllib.parse.urlencode({'p':20201,'o':'svcd', 'host_name':host, 'service_description':service}) + auth ) else: webbrowser_open(self.urls_centreon['main'] + '?' + urllib.parse.urlencode({'p':202, 'o':'svcd', 'host_name':host, 'service_description':service}) + auth ) def _get_sid(self): ''' gets a shiny new SID for XML HTTP requests to Centreon cutting it out via .partition() from raw HTML additionally get php session cookie ''' try: # Aulogin with key, BROWSER_URLS needs the key if self.use_autologin == True: auth = '&autologin=1&useralias=' + self.username + '&token=' + self.autologin_key self.BROWSER_URLS= { 'monitor': self.BROWSER_URLS['monitor'] + auth,\ 'hosts': self.BROWSER_URLS['hosts'] + auth,\ 'services': self.BROWSER_URLS['services'] + auth,\ 'history': self.BROWSER_URLS['history'] + auth} raw = self.FetchURL(self.monitor_cgi_url + '/index.php?p=101&autologin=1&useralias=' + self.username + '&token=' + self.autologin_key, giveback='raw') if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='Autologin : ' + self.username + ' : ' + self.autologin_key) # Password auth else: login = self.FetchURL(self.monitor_cgi_url + '/index.php') if login.error == '' and login.status_code == 200: # Centreon > 2.6.6 implement a token if self.centreon_version == 2.8 or self.centreon_version == 2.7 or self.centreon_version == 2.66: form = login.result.find('form') form_inputs = {} # Need to catch the centreon_token for login to work for form_input in ('centreon_token', 'submitLogin'): form_inputs[form_input] = form.find('input', {'name': form_input})['value'] self.centreon_token = form_inputs['centreon_token'] form_inputs['useralias'] = self.username form_inputs['password'] = self.password # fire up login button with all needed data raw = self.FetchURL(self.monitor_cgi_url + '/index.php', cgi_data=form_inputs) elif self.centreon_version == 2.3456: login_data = {"useralias" : self.username, "password" : self.password, "submit" : "Login"} raw = self.FetchURL(self.monitor_cgi_url + "/index.php",cgi_data=login_data, giveback="raw") if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='Password login : ' + self.username + ' : ' + self.password) sid = self.session.cookies['PHPSESSID'] if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='SID : ' + sid) # those broker urls would not be changing too often so this check migth be done here if self.first_login: self._get_xml_path(sid) self.first_login = False return Result(result=sid) except: import traceback traceback.print_exc(file=sys.stdout) result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) def get_start_end(self, host): ''' get start and end time for downtime from Centreon server ''' try: cgi_data = {'o':'ah',\ 'host_name':host} if self.centreon_version < 2.7: cgi_data['p'] = '20106' elif self.centreon_version == 2.7: cgi_data['p'] = '210' elif self.centreon_version == 2.8: cgi_data['o'] = 'a' cgi_data['p'] = '210' result = self.FetchURL(self.urls_centreon['main'], cgi_data = cgi_data, giveback='obj') html, error = result.result, result.error if error == '': start_date = html.find(attrs={'name':'start'}).attrs['value'] start_hour = html.find(attrs={'name':'start_time'}).attrs['value'] start_time = start_date + ' ' + start_hour end_date = html.find(attrs={'name':'end'}).attrs['value'] end_hour = html.find(attrs={'name':'end_time'}).attrs['value'] end_time = end_date + ' ' + end_hour # give values back as tuple return start_time, end_time except: self.Error(sys.exc_info()) return 'n/a', 'n/a' def GetHost(self, host): ''' Centreonified way to get host ip - attribute 'a' in down hosts xml is of no use for up hosts so we need to get ip anyway from web page ''' # the fastest method is taking hostname as used in monitor if conf.connect_by_host == True or host == '': return Result(result=host) # do a web interface search limited to only one result - the hostname cgi_data = {'sid': self.SID, 'search': host, 'num': 0, 'limit': 1, 'sort_type':'hostname', 'order': 'ASC', 'date_time_format_status': 'd/m/Y H:i:s', 'o': 'h', 'p': 20102, 'time': 0} centreon_hosts = self.urls_centreon['xml_hosts'] + '?' + urllib.parse.urlencode(cgi_data) result = self.FetchURL(centreon_hosts, giveback='xml') xmlobj, error, status_code = result.result, result.error, result.status_code # initialize ip string ip = '' if len(xmlobj) != 0: ip = str(xmlobj.l.a.text) # when connection by DNS is not configured do it by IP try: if conf.connect_by_dns == True: # try to get DNS name for ip (reverse DNS), if not available use ip try: address = socket.gethostbyaddr(ip)[0] except: if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='Unable to do a reverse DNS lookup on IP: ' + ip) address = ip else: address = ip except: result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) else: result, error = self.Error(sys.exc_info()) return Result(error=error) del xmlobj # print IP in debug mode if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='IP of %s:' % (host) + ' ' + address) # give back host or ip return Result(result=address) def _get_xml_path(self,sid): ''' Find out where this instance of Centreon is publishing the status XMLs Centreon 2.6 + ndo/c.broker - /include/monitoring/status/Hosts/xml/{ndo,broker}/hostXML.php according to configuration Centreon 2.7 + c.broker - /include/monitoring/status/Hosts/xml/hostXML.php Centreon 2.8 + c.broker - /include/monitoring/status/Hosts/xml/hostXML.php regexping HTML for Javascript ''' if self.centreon_version == 2.2: self.XML_PATH = 'xml' elif self.centreon_version == 2.3456 or self.centreon_version == 2.66: # 2.6 support NDO and C. Broker, we must check which one is used # cgi_data = {'p':201, 'sid':self.SID} cgi_data = {'p':201, 'sid':sid} result = self.FetchURL(self.monitor_cgi_url + '/main.php', cgi_data=cgi_data, giveback='raw') raw, error = result.result, result.error if error == '': if re.search('var _addrXML.*xml\/ndo\/host', raw): self.XML_PATH = 'xml/ndo' if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='Detected broker : NDO') elif re.search('var _addrXML.*xml\/broker\/host', raw): self.XML_PATH = 'xml/broker' if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='Detected broker : C. Broker') else: if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='Could not detect the broker for Centeron 2.[3-6]. Using Centreon Broker') self.XML_PATH = 'xml/broker' del raw else: if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='Unable to fetch the main page to detect the broker : ' + error) del result, error elif self.centreon_version == 2.7 or self.centreon_version == 2.8: self.XML_PATH = 'xml' if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='Only Centreon Broker is supported in Centeon >= 2.7 so: XML_PATH='+ self.XML_PATH) def _define_url(self): urls_centreon_2_2 = { 'main': self.monitor_cgi_url + '/main.php', 'index': self.monitor_cgi_url + '/index.php', 'xml_services': self.monitor_cgi_url + '/include/monitoring/status/Services/' + self.XML_PATH + '/serviceXML.php', 'xml_hosts': self.monitor_cgi_url + '/include/monitoring/status/Hosts/' + self.XML_PATH + '/hostXML.php', 'xml_meta': self.monitor_cgi_url + '/include/monitoring/status/Meta/' + self.XML_PATH + '/metaServiceXML.php', 'xml_hostSendCommand': self.monitor_cgi_url + '/include/monitoring/objectDetails/xml/hostSendCommand.php', 'xml_serviceSendCommand': self.monitor_cgi_url + '/include/monitoring/objectDetails/xml/serviceSendCommand.php', 'external_cmd_cmdPopup': self.monitor_cgi_url + '/include/monitoring/external_cmd/cmdPopup.php' } # inconsistant url in Centreon 2.7 urls_centreon_2_7 = { 'main': self.monitor_cgi_url + '/main.php', 'index': self.monitor_cgi_url + '/index.php', 'xml_services': self.monitor_cgi_url + '/include/monitoring/status/Services/' + self.XML_PATH + '/serviceXML.php', 'xml_hosts': self.monitor_cgi_url + '/include/monitoring/status/Hosts/' + self.XML_PATH + '/broker/hostXML.php', 'xml_meta': self.monitor_cgi_url + '/include/monitoring/status/Meta/' + self.XML_PATH + '/broker/metaServiceXML.php', 'xml_hostSendCommand': self.monitor_cgi_url + '/include/monitoring/objectDetails/xml/hostSendCommand.php', 'xml_serviceSendCommand': self.monitor_cgi_url + '/include/monitoring/objectDetails/xml/serviceSendCommand.php', 'external_cmd_cmdPopup': self.monitor_cgi_url + '/include/monitoring/external_cmd/cmdPopup.php' } urls_centreon_2_8 = { 'main': self.monitor_cgi_url + '/main.php', 'index': self.monitor_cgi_url + '/index.php', 'xml_services': self.monitor_cgi_url + '/include/monitoring/status/Services/' + self.XML_PATH + '/serviceXML.php', 'xml_hosts': self.monitor_cgi_url + '/include/monitoring/status/Hosts/' + self.XML_PATH + '/hostXML.php', 'xml_hostSendCommand': self.monitor_cgi_url + '/include/monitoring/objectDetails/xml/hostSendCommand.php', 'xml_serviceSendCommand': self.monitor_cgi_url + '/include/monitoring/objectDetails/xml/serviceSendCommand.php', 'external_cmd_cmdPopup': self.monitor_cgi_url + '/include/monitoring/external_cmd/cmdPopup.php' } if self.centreon_version < 2.7: self.urls_centreon = urls_centreon_2_2 elif self.centreon_version == 2.7: self.urls_centreon = urls_centreon_2_7 elif self.centreon_version == 2.8: self.urls_centreon = urls_centreon_2_8 # print IP in debug mode if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='URLs defined for Centreon %s' % (self.centreon_version)) def _get_host_id(self, host): ''' get host_id via parsing raw html ''' if self.centreon_version < 2.7: cgi_data = {'p': 20102, 'o': 'hd', 'host_name': host, 'sid': self.SID} else: cgi_data = {'p': 20202, 'o': 'hd', 'host_name': host, 'sid': self.SID} url = self.urls_centreon['main'] + '?' + urllib.parse.urlencode(cgi_data) result = self.FetchURL(url, giveback='raw') raw, error = result.result, result.error if error == '': host_id = raw.partition("var host_id = '")[2].partition("'")[0] del raw else: if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='Host ID could not be retrieved.') # some cleanup del result, error # only if host_id is an usable integer return it try: if int(host_id): if conf.debug_mode == True: self.Debug(server=self.get_name(), host=host, debug='Host ID is ' + host_id) return host_id else: return '' except: return '' def _get_host_and_service_id(self, host, service): ''' parse a ton of html to get a host and a service id... ''' cgi_data = {'p':'20201',\ 'host_name':host,\ 'service_description':service,\ 'o':'svcd'} # This request must be done in a GET, so just encode the parameters and fetch result = self.FetchURL(self.urls_centreon['main'] + '?' + urllib.parse.urlencode(cgi_data), giveback="raw") raw, error = result.result, result.error if error == '': host_id = raw.partition("var host_id = '")[2].partition("'")[0] svc_id = raw.partition("var svc_id = '")[2].partition("'")[0] del raw if conf.debug_mode == True: self.Debug(server=self.get_name(), host=host, service=service, debug='- Get host/svc ID : ' + host_id + '/' + svc_id) else: if conf.debug_mode == True: self.Debug(server=self.get_name(), host=host, service=service, debug='- IDs could not be retrieved.') # some cleanup del result, error # only if host_id is an usable integer return it try: if int(host_id) and int(svc_id): if conf.debug_mode == True: self.Debug(server=self.get_name(), host=host, service=service, debug='- Host & Service ID are valid (int)') return host_id,svc_id else: return '','' except: return '','' def _get_status(self): ''' Get status from Centreon Server ''' # get sid in case this has not yet been done if self.SID == None or self.SID == '': self.SID = self._get_sid().result # services (unknown, warning or critical?) if self.centreon_version == 2.7 or self.centreon_version == 2.8: nagcgiurl_services = self.urls_centreon['xml_services'] + '?' + urllib.parse.urlencode({'num':0, 'limit':self.limit_services_number, 'o':'svcpb', 'p':20201, 'nc':0, 'criticality':0, 'statusService':'svcpb', 'sSetOrderInMemory':1, 'sid':self.SID}) else: nagcgiurl_services = self.urls_centreon['xml_services'] + '?' + urllib.parse.urlencode({'num':0, 'limit':self.limit_services_number, 'o':'svcpb', 'sort_type':'status', 'sid':self.SID}) # hosts (up or down or unreachable) # define hosts xml URL, because of inconsistant url if self.centreon_version == 2.7: nagcgiurl_hosts = self.urls_centreon['xml_hosts'] + '?' + urllib.parse.urlencode({'num':0, 'limit':self.limit_services_number, 'o':'hpb', 'p':20202, 'criticality':0, 'statusHost':'hpb', 'sSetOrderInMemory':1, 'sid':self.SID}) elif self.centreon_version == 2.8: nagcgiurl_hosts = self.urls_centreon['xml_hosts'] + '?' + urllib.parse.urlencode({'num':0, 'limit':self.limit_services_number, 'o':'hpb', 'p':20202, 'criticality':0, 'statusHost':'hpb', 'sSetOrderInMemory':1, 'sid':self.SID}) else: nagcgiurl_hosts = self.urls_centreon['xml_hosts'] + '?' + urllib.parse.urlencode({'num':0, 'limit':self.limit_services_number, 'o':'hpb', 'sort_type':'status', 'sid':self.SID}) # hosts - mostly the down ones # unfortunately the hosts status page has a different structure so # hosts must be analyzed separately try: result = self.FetchURL(nagcgiurl_hosts, giveback='xml') xmlobj, error, status_code = result.result, result.error, result.status_code # check if any error occured errors_occured = self.check_for_error(xmlobj, error, status_code) # if there are errors return them if errors_occured != False: return(errors_occured) # Check if the result is not empty if len(xmlobj) == 0: if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='Empty host XML result') return Result(result=None, error="Empty host XML result") # in case there are no children session ID is expired if xmlobj.text.lower() == 'bad session id': del xmlobj if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='Bad session ID, retrieving new one...') # try again... self.SID = self._get_sid().result result = self.FetchURL(nagcgiurl_hosts, giveback='xml') xmlobj, error, status_code = result.result, result.error, result.status_code errors_occured = self.check_for_error(xmlobj, error, status_code) # if there are errors return them if errors_occured != False: return(errors_occured) # a second time a bad session id should raise an error if xmlobj.text.lower() == 'bad session id': if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='Even after renewing session ID, unable to get the XML') return Result(result='ERROR', error='Bad session ID', status_code=status_code) for l in xmlobj.findAll('l'): try: # host objects contain service objects if not l.hn.text in self.new_hosts: self.new_hosts[str(l.hn.text)] = GenericHost() self.new_hosts[str(l.hn.text)].name = str(l.hn.text) self.new_hosts[str(l.hn.text)].server = self.name self.new_hosts[str(l.hn.text)].status = str(l.cs.text) # disgusting workaround for https://github.com/HenriWahl/Nagstamon/issues/91 if self.new_hosts[str(l.hn.text)].status in self.TRANSLATIONS: self.new_hosts[str(l.hn.text)].status = self.TRANSLATIONS[self.new_hosts[str(l.hn.text)].status] self.new_hosts[str(l.hn.text)].attempt, self.new_hosts[str(l.hn.text)].status_type = str(l.tr.text).split(' ') self.new_hosts[str(l.hn.text)].status_type = self.HARD_SOFT[self.new_hosts[str(l.hn.text)].status_type] self.new_hosts[str(l.hn.text)].last_check = str(l.lc.text) self.new_hosts[str(l.hn.text)].duration = str(l.lsc.text) self.new_hosts[str(l.hn.text)].status_information= str(l.ou.text) if l.find('cih') != None: self.new_hosts[str(l.hn.text)].criticality = str(l.cih.text) else: self.new_hosts[str(l.hn.text)].criticality = '' self.new_hosts[str(l.hn.text)].acknowledged = bool(int(str(l.ha.text))) self.new_hosts[str(l.hn.text)].scheduled_downtime = bool(int(str(l.hdtm.text))) if l.find('is') != None: self.new_hosts[str(l.hn.text)].flapping = bool(int(str(l.find('is').text))) else: self.new_hosts[str(l.hn.text)].flapping = False self.new_hosts[str(l.hn.text)].notifications_disabled = not bool(int(str(l.ne.text))) self.new_hosts[str(l.hn.text)].passiveonly = not bool(int(str(l.ace.text))) except: import traceback traceback.print_exc(file=sys.stdout) # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) del xmlobj except: import traceback traceback.print_exc(file=sys.stdout) # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) # services try: result = self.FetchURL(nagcgiurl_services, giveback='xml') xmlobj, error, status_code = result.result, result.error, result.status_code # check if any error occured errors_occured = self.check_for_error(xmlobj, error, status_code) # if there are errors return them if errors_occured != False: return(errors_occured) # Check if the result is not empty if len(xmlobj) == 0: if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='Empty service XML result') return Result(result=None, error="Empty service XML result") # in case there are no children session id is invalid if xmlobj.text.lower() == 'bad session id': # debug if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='Bad session ID, retrieving new one...') # try again... self.SID = self._get_sid().result result = self.FetchURL(nagcgiurl_services, giveback='xml') xmlobj, error, status_code = result.result, result.error, result.status_code errors_occured = self.check_for_error(xmlobj, error, status_code) # if there are errors return them if errors_occured != False: return(errors_occured) # a second time a bad session id should raise an error if xmlobj.text.lower() == 'bad session id': return Result(result='ERROR', error='Bad session ID', status_code=status_code) # In Centreon 2.8, Meta are merge with regular services if self.centreon_version < 2.8: # define meta-services xml URL if self.centreon_version == 2.7: nagcgiurl_meta_services = self.urls_centreon['xml_meta'] + '?' + urllib.parse.urlencode({'num':0, 'limit':self.limit_services_number, 'o':'meta', 'sort_type':'status', 'sid':self.SID}) else: nagcgiurl_meta_services = self.urls_centreon['xml_meta'] + '?' + urllib.parse.urlencode({'num':0, 'limit':self.limit_services_number, 'o':'meta', 'sort_type':'status', 'sid':self.SID}) # retrive meta-services xml STATUS result_meta = self.FetchURL(nagcgiurl_meta_services, giveback='xml') xmlobj_meta, error_meta, status_code_meta = result_meta.result, result_meta.error, result_meta.status_code # check if any error occured errors_occured = self.check_for_error(xmlobj_meta, error_meta, status_code_meta) # if there are errors return them if errors_occured != False: return(errors_occured) # a second time a bad session id should raise an error if xmlobj_meta.text.lower() == 'bad session id': if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='Even after renewing session ID, unable to get the XML') return Result(result='ERROR', error='Bad session ID', status_code=status_code_meta) # INSERT META-services xml at the end of the services xml try: xmlobj.append(xmlobj_meta.reponse) except: import traceback traceback.print_exc(file=sys.stdout) # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) # do some cleanup del xmlobj_meta for l in xmlobj.findAll('l'): try: # host objects contain service objects ###if not self.new_hosts.has_key(str(l.hn.text)): if not l.hn.text in self.new_hosts: self.new_hosts[str(l.hn.text)] = GenericHost() self.new_hosts[str(l.hn.text)].name = str(l.hn.text) self.new_hosts[str(l.hn.text)].status = 'UP' # if a service does not exist create its object if not l.sd.text in self.new_hosts[str(l.hn.text)].services: self.new_hosts[str(l.hn.text)].services[str(l.sd.text)] = GenericService() self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].host = str(l.hn.text) self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].name = str(l.sd.text) self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].server = self.name self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].status = str(l.cs.text) if self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].host == '_Module_Meta': # ajusting service name for Meta services if self.centreon_version < 2.8: self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].name = '{} ({})'.format(str(l.sd.text), l.rsd.text) self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].attempt = str(l.ca.text) else: self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].name = '{} ({})'.format(str(l.sdn.text), l.sdl.text) self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].attempt, \ self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].status_type = str(l.ca.text).split(' ') else: self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].attempt, \ self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].status_type = str(l.ca.text).split(' ') # disgusting workaround for https://github.com/HenriWahl/Nagstamon/issues/91 # Still needed in Centreon 2.8 at least : https://github.com/HenriWahl/Nagstamon/issues/344 # Need enhancement, we can do service state matching with this field service_unknown #if self.centreon_version < 2.66: if self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].status in self.TRANSLATIONS: self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].status = self.TRANSLATIONS[\ self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].status] if not (self.centreon_version < 2.8 and self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].host == '_Module_Meta'): self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].status_type =\ self.HARD_SOFT[self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].status_type] if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='Service / status_type : ' + self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].name + '/' + self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].status_type) self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].last_check = str(l.lc.text) self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].duration = str(l.d.text) self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].status_information = str(l.po.text).replace('\n', ' ').strip() if l.find('cih') != None: self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].criticality = str(l.cih.text) else: self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].criticality = '' self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].acknowledged = bool(int(str(l.pa.text))) self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].notifications_disabled = not bool(int(str(l.ne.text))) # for features not available in centreon < 2.8 and meta services if not (self.centreon_version < 2.8 and self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].host == '_Module_Meta'): self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].scheduled_downtime = bool(int(str(l.dtm.text))) self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].flapping = bool(int(str(l.find('is').text))) self.new_hosts[str(l.hn.text)].services[str(l.sd.text)].passiveonly = not bool(int(str(l.ac.text))) except: import traceback traceback.print_exc(file=sys.stdout) # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) # do some cleanup del xmlobj except: import traceback traceback.print_exc(file=sys.stdout) # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) # return True if all worked well return Result() def _set_acknowledge(self, host, service, author, comment, sticky, notify, persistent, all_services=[]): # decision about host or service - they have different URLs try: if service == '': # host cgi_data = {'cmd': '14', 'host_name': host, 'author': author, 'comment': comment, 'submit': 'Add', 'notify': int(notify), 'persistent': int(persistent), 'sticky': int(sticky), 'ackhostservice': '0', 'en': '1'} if self.centreon_version < 2.7: cgi_data['p'] = '20105' cgi_data['o'] = 'hpb' elif self.centreon_version == 2.7 or self.centreon_version == 2.8: cgi_data['p'] = '20202' cgi_data['o'] = 'hpb' cgi_data['centreon_token'] = self.centreon_token # running remote cgi command, also possible with GET method raw = self.FetchURL(self.urls_centreon['main'], cgi_data=cgi_data, giveback='raw') del raw # if host is acknowledged and all services should be to or if a service is acknowledged # (and all other on this host too) if service != '' or len(all_services) > 0: # service(s) @ host # if all_services is empty only one service has to be checked - the one clicked # otherwise if there all services should be acknowledged if len(all_services) == 0: all_services = [service] # acknowledge all services on a host for s in all_services: cgi_data = {'cmd': '15', 'host_name': host, 'author': author, 'comment': comment, 'submit': 'Add', 'notify': int(notify), 'service_description': s, 'force_check': '1', 'persistent': int(persistent), 'persistant': int(persistent), 'sticky': int(sticky), 'o': 'svcd', 'en': '1'} if self.centreon_version < 2.7: cgi_data['p'] = '20215' elif self.centreon_version == 2.7 or self.centreon_version == 2.8: cgi_data['p'] = '20201' cgi_data['centreon_token'] = self.centreon_token # in case of a meta-service, extract the 'rsd' field from the service name : if host == '_Module_Meta': m = re.search(r'^.+ \((?P.+)\)$', s) if m: rsd = m.group('rsd') if self.centreon_version < 2.8: cgi_data = {'p': '20206', 'o': 'meta', 'cmd': '70', 'select[' + host + ';' + rsd + ']': '1', 'limit': '0'} elif self.centreon_version == 2.8: cgi_data['service_description'] = rsd # debug - redondant avec le FetchURL qui donne les données if conf.debug_mode == True: self.Debug(server=self.get_name(), host=host, service=s, debug=self.urls_centreon['main'] + '?' + urllib.parse.urlencode(cgi_data)) # running remote cgi command with GET method, for some strange reason only working if # giveback is 'raw' raw = self.FetchURL(self.urls_centreon['main'], cgi_data=cgi_data, giveback='raw') del raw except: self.Error(sys.exc_info()) def _set_recheck(self, host, service): ''' host and service ids are needed to tell Centreon what whe want ''' try: # decision about host or service - they have different URLs # Meta if host == '_Module_Meta': if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='Recheck on a Meta service, more work to be done') m = re.search(r'^.+ \((?P.+)\)$', service) if m: rsd = m.group('rsd') if self.centreon_version < 2.8: url = self.urls_centreon['main'] + '?' + urllib.parse.urlencode({'p': '20206','o': 'meta','cmd': '3','select[' + host + ';' + rsd + ']': '1','limit':'0'}) else: url = self.urls_centreon['main'] + '?' + urllib.parse.urlencode({'p': '202','o': 'svc','cmd': '3','select[' + host + ';' + rsd + ']': '1','limit':'1','centreon_token':self.centreon_token}) elif service == '': # ... it can only be a host, so check all his services and there is a command for that host_id = self._get_host_id(host) if self.centreon_version < 2.7: url = self.urls_centreon['xml_hostSendCommand'] + '?' + urllib.parse.urlencode({'cmd':'host_schedule_check', 'actiontype':1,'host_id':host_id,'sid':self.SID}) else: url = self.urls_centreon['xml_hostSendCommand'] + '?' + urllib.parse.urlencode({'cmd':'host_schedule_check', 'actiontype':1,'host_id':host_id}) del host_id else: # service @ host host_id, service_id = self._get_host_and_service_id(host, service) # fill and encode CGI data cgi_data = urllib.parse.urlencode({'cmd':'service_schedule_check', 'actiontype':1,\ 'host_id':host_id, 'service_id':service_id, 'sid':self.SID}) url = self.urls_centreon['xml_serviceSendCommand'] + '?' + cgi_data del host_id, service_id # execute POST request raw = self.FetchURL(url, giveback='raw') del raw except: self.Error(sys.exc_info()) def _set_downtime(self, host, service, author, comment, fixed, start_time, end_time, hours, minutes): ''' gets actual host and service ids and apply them to downtime cgi ''' try: # duration unit is minute duration = (hours * 60) + minutes # need cmdPopup.php needs boolean if fixed == 1: fixed = 'true' else: fixed = 'false' if service == '': # So it is a host downtime cgi_data = {'cmd':75,\ 'duration':duration,\ 'duration_scale':'m',\ 'start':start_time,\ 'end':end_time,\ 'comment':comment,\ 'fixed':fixed,\ 'downtimehostservice':'true',\ 'author':author,\ 'sid':self.SID,\ 'select['+host+']':1} # debug if conf.debug_mode == True: self.Debug(server=self.get_name(), host=host, debug=self.urls_centreon['external_cmd_cmdPopup'] + '?' + urllib.parse.urlencode(cgi_data)) else: # It is a service downtime # Centreon 2.8 only, in case of a meta-service, extract the 'rsd' field from the service name : if host == '_Module_Meta' and self.centreon_version == 2.8: m = re.search(r'^.+ \((?P.+)\)$', service) if m: rsd = m.group('rsd') service = rsd cgi_data = {'cmd':74,\ 'duration':duration,\ 'duration_scale':'m',\ 'start':start_time,\ 'end':end_time,\ 'comment':comment,\ 'fixed':fixed,\ 'downtimehostservice':0,\ 'author':author,\ 'sid':self.SID,\ 'select['+host+';'+service+']':1} # debug if conf.debug_mode == True: self.Debug(server=self.get_name(), host=host, service=service, debug=self.urls_centreon['external_cmd_cmdPopup'] + '?' + urllib.parse.urlencode(cgi_data)) # This request must be done in a GET, so just encode the parameters and fetch raw = self.FetchURL(self.urls_centreon['external_cmd_cmdPopup'] + '?' + urllib.parse.urlencode(cgi_data), giveback="raw") del raw except: self.Error(sys.exc_info()) # This Hook seems to not be called anymore from main loop def Hook(self): # debug if conf.debug_mode == True: self.Debug(server=self.get_name(), debug='Hook function') ''' in case count is down get a new SID, just in case was kicked out but as to be seen in https://sourceforge.net/p/nagstamon/bugs/86/ there are problems with older Centreon installations so this should come back ''' # renewing the SID once an hour might be enough # maybe this is unnecessary now that we authenticate via login/password, no md5 if self.SIDcount >= 3600: if conf.debug_mode == 'True': self.Debug(server=self.get_name(), debug='Old SID: ' + self.SID + ' ' + str(self.Cookie)) # close the connections to avoid the accumulation of sessions on Centreon url_disconnect = self.urls_centreon['index'] + '?disconnect=1' raw = self.FetchURL(url_disconnect, giveback='raw') del raw self.SID = self._get_sid().result if conf.debug_mode == 'True': self.Debug(server=self.get_name(), debug='New SID: ' + self.SID + ' ' + str(self.Cookie)) self.SIDcount = 0 else: self.SIDcount += 1 Nagstamon/Nagstamon/Servers/Generic.py000066400000000000000000002177111316117564000203300ustar00rootroot00000000000000# encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2016 Henri Wahl et al. # # 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 import sys import socket import copy import datetime import traceback import platform import urllib.parse from bs4 import BeautifulSoup from Nagstamon.Helpers import (host_is_filtered_out_by_re, ServiceIsFilteredOutByRE, StatusInformationIsFilteredOutByRE, CriticalityIsFilteredOutByRE, not_empty, webbrowser_open, STATES) from Nagstamon.Objects import (GenericService, GenericHost, Result) from Nagstamon.Config import (conf, AppInfo, debug_queue) from collections import OrderedDict import requests import requests_kerberos # disable annoying SubjectAltNameWarning warnings try: from requests.packages.urllib3.exceptions import SubjectAltNameWarning requests.packages.urllib3.disable_warnings(SubjectAltNameWarning) except: # older requests version might not have the packages submodule # for example the one in Ubuntu 14.04 pass class GenericServer(object): ''' Abstract server which serves as template for all other types Default values are for Nagios servers ''' TYPE = 'Generic' # dictionary to translate status bitmaps on webinterface into status flags # this are defaults from Nagios # 'disabled.gif' is in Nagios for hosts the same as 'passiveonly.gif' for services STATUS_MAPPING = {'ack.gif': 'acknowledged', 'passiveonly.gif': 'passiveonly', 'disabled.gif': 'passiveonly', 'ndisabled.gif': 'notifications_disabled', 'downtime.gif': 'scheduled_downtime', 'flapping.gif': 'flapping'} # Entries for monitor default actions in context menu MENU_ACTIONS = ['Monitor', 'Recheck', 'Acknowledge', 'Submit check result', 'Downtime'] # Arguments available for submitting check results SUBMIT_CHECK_RESULT_ARGS = ['check_output', 'performance_data'] # URLs for browser shortlinks/buttons on popup window BROWSER_URLS = {'monitor': '$MONITOR$', 'hosts': '$MONITOR-CGI$/status.cgi?hostgroup=all&style=hostdetail&hoststatustypes=12', 'services': '$MONITOR-CGI$/status.cgi?host=all&servicestatustypes=253', 'history': '$MONITOR-CGI$/history.cgi?host=all'} USER_AGENT = '{0}/{1}/{2}'.format(AppInfo.NAME, AppInfo.VERSION, platform.system()) # needed to check return code of monitor server in case of false authentication STATUS_CODES_NO_AUTH = [401, 403] # default parser for BeautifulSoup - the rediscovered lxml causes trouble for Centreon so there should be choice # see https://github.com/HenriWahl/Nagstamon/issues/431 PARSER = 'lxml' def __init__(self, **kwds): # add all keywords to object, every mode searchs inside for its favorite arguments/keywords for k in kwds: self.__dict__[k] = kwds[k] self.enabled = False self.type = '' self.monitor_url = '' self.monitor_cgi_url = '' self.username = '' self.password = '' self.use_proxy = False self.use_proxy_from_os = False self.proxy_address = '' self.proxy_username = '' self.proxy_password = '' self.auth_type = '' self.hosts = dict() self.new_hosts = dict() self.isChecking = False self.CheckingForNewVersion = False # store current and difference of worst state for notification self.worst_status_diff = self.worst_status_current = 'UP' self.nagitems_filtered_list = list() self.nagitems_filtered = {'services': {'DISASTER': [], 'CRITICAL': [], 'HIGH': [], 'AVERAGE': [], 'WARNING': [], 'INFORMATION': [], 'UNKNOWN': []}, 'hosts': {'DOWN': [], 'UNREACHABLE': []}} # number of filtered items self.nagitems_filtered_count = 0 self.down = 0 self.unreachable = 0 self.unknown = 0 self.critical = 0 self.warning = 0 # zabbix support self.information = 0 self.average = 0 self.high = 0 self.disaster = 0 self.all_ok = True self.status = '' self.status_description = '' self.status_code = 0 self.has_error = False self.timeout = 10 # The events_* are recycled from GUI.py # history of events to track status changes for notifications # events that came in self.events_current = {} # events that had been already displayed in popwin and need no extra mark self.events_history = {} # events to be given to custom notification, maybe to desktop notification too self.events_notification = {} # needed for looping server thread self.thread_counter = 0 # needed for RecheckAll - save start_time once for not having to get it for every recheck self.start_time = None # Requests-based connections self.session = None # flag which decides if authentication has to be renewed self.refresh_authentication = False # flag which tells GUI if there is an TLS problem self.tls_error = False # to handle Icinga versions this information is necessary, might be of future use for others too self.version = '' # Special FX # Centreon self.use_autologin = False self.autologin_key = '' # Icinga self.use_display_name_host = False self.use_display_name_service = False # Check_MK Multisite self.force_authuser = False # OP5 api filters self.host_filter = 'state !=0' self.service_filter = 'state !=0 or host.state != 0' # Sensu/Uchiwa/??? Datacenter/Site config self.monitor_site = 'Site 1' def init_config(self): ''' set URLs for CGI - they are static and there is no need to set them with every cycle ''' # create filters like described in # http://www.nagios-wiki.de/nagios/tips/host-_und_serviceproperties_fuer_status.cgi?s=servicestatustypes # # the following variables are not necessary anymore as with 'new' filtering # # hoststatus # hoststatustypes = 12 # servicestatus # servicestatustypes = 253 # serviceprops & hostprops both have the same values for the same states so I # group them together # hostserviceprops = 0 # services (unknown, warning or critical?) as dictionary, sorted by hard and soft state type self.cgiurl_services = { 'hard': self.monitor_cgi_url + '/status.cgi?host=all&servicestatustypes=253&serviceprops=262144&limit=0', 'soft': self.monitor_cgi_url + '/status.cgi?host=all&servicestatustypes=253&serviceprops=524288&limit=0'} # hosts (up or down or unreachable) self.cgiurl_hosts = { 'hard': self.monitor_cgi_url + '/status.cgi?hostgroup=all&style=hostdetail&hoststatustypes=12&hostprops=262144&limit=0', 'soft': self.monitor_cgi_url + '/status.cgi?hostgroup=all&style=hostdetail&hoststatustypes=12&hostprops=524288&limit=0'} def init_HTTP(self): ''' partly not constantly working Basic Authorization requires extra Authorization headers, different between various server types ''' if self.session is None: self.session = requests.Session() self.session.headers['User-Agent'] = self.USER_AGENT # support for different authentication types if self.authentication == 'basic': # basic authentication # self.session.auth = (self.username, self.password) self.session.auth = requests.auth.HTTPBasicAuth(self.username, self.password) elif self.authentication == 'digest': self.session.auth = requests.auth.HTTPDigestAuth(self.username, self.password) elif self.authentication == 'kerberos': self.session.auth = requests_kerberos.HTTPKerberosAuth() # default to check TLS validity if self.ignore_cert: self.session.verify = False elif self.custom_cert_use: self.session.verify = self.custom_cert_ca_file else: self.session.verify = True # add proxy information self.proxify(self.session) def proxify(self, requester): ''' add proxy information to session or single request ''' # check if proxies have to be used if self.use_proxy is True: if self.use_proxy_from_os is True: # if .trust_enf is true the system environment will be evaluated requester.trust_env = True requester.proxies = dict() else: # check if username and password are given and provide credentials if needed if self.proxy_username == self.proxy_password == '': user_pass = '' else: user_pass = '{0}:{1}@'.format(self.proxy_username, self.proxy_password) # split and analyze proxy URL proxy_address_parts = self.proxy_address.split('//') scheme = proxy_address_parts[0] host_port = ''.join(proxy_address_parts[1:]) # use only valid schemes if scheme.lower() in ('http:', 'https:'): # merge proxy URL proxy_url = '{0}//{1}{2}'.format(scheme, user_pass, host_port) # fill session.proxies for both protocols requester.proxies = {'http': proxy_url, 'https': proxy_url} else: # disable evaluation of environment variables requester.trust_env = False requester.proxies = None def reset_HTTP(self): ''' if authentication fails try to reset any HTTP session stuff - might be different for different monitors ''' self.session = None def get_name(self): ''' return stringified name ''' return str(self.name) def get_username(self): ''' return stringified username ''' return str(self.username) def get_password(self): ''' return stringified password ''' return str(self.password) def get_server_version(self): ''' dummy function, at the moment only used by Icinga ''' pass def set_recheck(self, info_dict): self._set_recheck(info_dict['host'], info_dict['service']) def _set_recheck(self, host, service): if service != '': if self.hosts[host].services[service].is_passive_only(): # Do not check passive only checks return try: # get start time from Nagios as HTML to use same timezone setting like the locally installed Nagios result = self.FetchURL( self.monitor_cgi_url + '/cmd.cgi?' + urllib.parse.urlencode({'cmd_typ': '96', 'host': host})) self.start_time = dict(result.result.find(attrs={'name': 'start_time'}).attrs)['value'] # decision about host or service - they have different URLs if service == '': # host cmd_typ = '96' else: # service @ host cmd_typ = '7' # ignore empty service in case of rechecking a host cgi_data = urllib.parse.urlencode([('cmd_typ', cmd_typ), ('cmd_mod', '2'), ('host', host), ('service', service), ('start_time', self.start_time), ('force_check', 'on'), ('btnSubmit', 'Commit')]) # execute POST request self.FetchURL(self.monitor_cgi_url + '/cmd.cgi', giveback='raw', cgi_data=cgi_data) except: traceback.print_exc(file=sys.stdout) def set_acknowledge(self, info_dict): ''' different monitors might have different implementations of _set_acknowledge ''' if info_dict['acknowledge_all_services'] is True: all_services = info_dict['all_services'] else: all_services = [] self._set_acknowledge(info_dict['host'], info_dict['service'], info_dict['author'], info_dict['comment'], info_dict['sticky'], info_dict['notify'], info_dict['persistent'], all_services) def _set_acknowledge(self, host, service, author, comment, sticky, notify, persistent, all_services=[]): ''' send acknowledge to monitor server - might be different on every monitor type ''' url = self.monitor_cgi_url + '/cmd.cgi' # the following flags apply to hosts and services # # according to sf.net bug #3304098 (https://sourceforge.net/tracker/?func=detail&atid=1101370&aid=3304098&group_id=236865) # the send_notification-flag must not exist if it is set to 'off', otherwise # the Nagios core interpretes it as set, regardless its real value # # for whatever silly reason Icinga depends on the correct order of submitted form items... # see sf.net bug 3428844 # # Thanks to Icinga ORDER OF ARGUMENTS IS IMPORTANT HERE! # cgi_data = OrderedDict() if service == '': cgi_data['cmd_typ'] = '33' else: cgi_data['cmd_typ'] = '34' cgi_data['cmd_mod'] = '2' cgi_data['host'] = host if service != '': cgi_data['service'] = service cgi_data['com_author'] = author cgi_data['com_data'] = comment cgi_data['btnSubmit'] = 'Commit' if notify is True: cgi_data['send_notification'] = 'on' if persistent is True: cgi_data['persistent'] = 'on' if sticky is True: cgi_data['sticky_ack'] = 'on' self.FetchURL(url, giveback='raw', cgi_data=cgi_data) # acknowledge all services on a host if len(all_services) > 0: for s in all_services: cgi_data['cmd_typ'] = '34' cgi_data['service'] = s self.FetchURL(url, giveback='raw', cgi_data=cgi_data) def set_downtime(self, info_dict): ''' different monitors might have different implementations of _set_downtime ''' self._set_downtime(info_dict['host'], info_dict['service'], info_dict['author'], info_dict['comment'], info_dict['fixed'], info_dict['start_time'], info_dict['end_time'], info_dict['hours'], info_dict['minutes']) def _set_downtime(self, host, service, author, comment, fixed, start_time, end_time, hours, minutes): ''' finally send downtime command to monitor server ''' url = self.monitor_cgi_url + '/cmd.cgi' # for some reason Icinga is very fastidiuos about the order of CGI arguments, so please # here we go... it took DAYS :-( cgi_data = OrderedDict() if service == '': cgi_data['cmd_typ'] = '55' else: cgi_data['cmd_typ'] = '56' cgi_data['cmd_mod'] = '2' cgi_data['trigger'] = '0' cgi_data['host'] = host if service != '': cgi_data['service'] = service cgi_data['com_author'] = author cgi_data['com_data'] = comment cgi_data['fixed'] = fixed cgi_data['start_time'] = start_time cgi_data['end_time'] = end_time cgi_data['hours'] = hours cgi_data['minutes'] = minutes cgi_data['btnSubmit'] = 'Commit' # running remote cgi command self.FetchURL(url, giveback='raw', cgi_data=cgi_data) def set_submit_check_result(self, info_dict): """ start specific submission part """ self._set_submit_check_result(info_dict['host'], info_dict['service'], info_dict['state'], info_dict['comment'], info_dict['check_output'], info_dict['performance_data']) def _set_submit_check_result(self, host, service, state, comment, check_output, performance_data): ''' worker for submitting check result ''' url = self.monitor_cgi_url + '/cmd.cgi' # decision about host or service - they have different URLs if service == '': # host cgi_data = urllib.parse.urlencode([('cmd_typ', '87'), ('cmd_mod', '2'), ('host', host), ('plugin_state', {'up': '0', 'down': '1', 'unreachable': '2'}[state]), ('plugin_output', check_output), ('performance_data', performance_data), ('btnSubmit', 'Commit')]) self.FetchURL(url, giveback='raw', cgi_data=cgi_data) if service != '': # service @ host cgi_data = urllib.parse.urlencode( [('cmd_typ', '30'), ('cmd_mod', '2'), ('host', host), ('service', service), ('plugin_state', {'ok': '0', 'warning': '1', 'critical': '2', 'unknown': '3'}[state]), ('plugin_output', check_output), ('performance_data', performance_data), ('btnSubmit', 'Commit')]) # running remote cgi command self.FetchURL(url, giveback='raw', cgi_data=cgi_data) def get_start_end(self, host): ''' for GUI to get actual downtime start and end from server - they may vary so it's better to get directly from web interface ''' try: result = self.FetchURL( self.monitor_cgi_url + '/cmd.cgi?' + urllib.parse.urlencode({'cmd_typ': '55', 'host': host})) start_time = dict(result.result.find(attrs={'name': 'start_time'}).attrs)['value'] end_time = dict(result.result.find(attrs={'name': 'end_time'}).attrs)['value'] # give values back as tuple return start_time, end_time except Exception: self.Error(sys.exc_info()) return 'n/a', 'n/a' def open_monitor(self, host, service=''): ''' open monitor from tablewidget context menu ''' # only type is important so do not care of service '' in case of host monitor if service == '': typ = 1 else: typ = 2 if conf.debug_mode: self.Debug(server=self.get_name(), host=host, service=service, debug='Open host/service monitor web page ' + self.monitor_cgi_url + '/extinfo.cgi?' + urllib.parse.urlencode( {'type': typ, 'host': host, 'service': service})) webbrowser_open(self.monitor_cgi_url + '/extinfo.cgi?' + urllib.parse.urlencode( {'type': typ, 'host': host, 'service': service})) def open_monitor_webpage(self): ''' open monitor from systray/toparea context menu ''' if conf.debug_mode: self.Debug(server=self.get_name(), debug='Open monitor web page ' + self.monitor_cgi_url) webbrowser_open(self.monitor_url) def _get_status(self): ''' Get status from Nagios Server ''' # create Nagios items dictionary with to lists for services and hosts # every list will contain a dictionary for every failed service/host # this dictionary is only temporarily nagitems = {'services': [], 'hosts': []} # new_hosts dictionary self.new_hosts = dict() # hosts - mostly the down ones # unfortunately the hosts status page has a different structure so # hosts must be analyzed separately try: for status_type in 'hard', 'soft': result = self.FetchURL(self.cgiurl_hosts[status_type]) htobj, error, status_code = result.result, result.error, result.status_code # check if any error occured errors_occured = self.check_for_error(htobj, error, status_code) # if there are errors return them if errors_occured is not False: return(errors_occured) # put a copy of a part of htobj into table to be able to delete htobj # too mnuch copy.deepcopy()s here give recursion crashs table = htobj('table', {'class': 'status'})[0] # access table rows # some Icinga versions have a tag in cgi output HTML which # omits the tags being found if len(table('tbody')) == 0: trs = table('tr', recursive=False) else: tbody = table('tbody')[0] trs = tbody('tr', recursive=False) # do some cleanup del result, error # kick out table heads trs.pop(0) # dummy tds to be deleteable tds = [] for tr in trs: try: # ignore empty rows if len(tr('td', recursive=False)) > 1: n = dict() # get tds in one tr tds = tr('td', recursive=False) # host try: n['host'] = str(tds[0].table.tr.td.table.tr.td.a.text) except Exception: n['host'] = str(nagitems[len(nagitems) - 1]['host']) # status n['status'] = str(tds[1].text) # last_check n['last_check'] = str(tds[2].text) # duration n['duration'] = str(tds[3].text) # division between Nagios and Icinga in real life... where # Nagios has only 5 columns there are 7 in Icinga 1.3... # ... and 6 in Icinga 1.2 :-) if len(tds) < 7: # the old Nagios table # status_information if len(tds[4](text=not_empty)) == 0: n['status_information'] = '' else: n['status_information'] = str(tds[4].text).replace('\n', ' ').replace('\t', ' ').strip() # attempts are not shown in case of hosts so it defaults to 'n/a' n['attempt'] = 'n/a' else: # attempts are shown for hosts # to fix http://sourceforge.net/tracker/?func=detail&atid=1101370&aid=3280961&group_id=236865 .attempt needs # to be stripped n['attempt'] = str(tds[4].text).strip() # status_information if len(tds[5](text=not_empty)) == 0: n['status_information'] = '' else: n['status_information'] = str(tds[5].text).replace('\n', ' ').replace('\t', ' ').strip() # status flags n['passiveonly'] = False n['notifications_disabled'] = False n['flapping'] = False n['acknowledged'] = False n['scheduled_downtime'] = False # map status icons to status flags icons = tds[0].findAll('img') for i in icons: icon = i['src'].split('/')[-1] if icon in self.STATUS_MAPPING: n[self.STATUS_MAPPING[icon]] = True # cleaning del icons # add dictionary full of information about this host item to nagitems nagitems['hosts'].append(n) # after collection data in nagitems create objects from its informations # host objects contain service objects if n['host'] not in self.new_hosts: new_host = n['host'] self.new_hosts[new_host] = GenericHost() self.new_hosts[new_host].name = n['host'] self.new_hosts[new_host].server = self.name self.new_hosts[new_host].status = n['status'] self.new_hosts[new_host].last_check = n['last_check'] self.new_hosts[new_host].duration = n['duration'] self.new_hosts[new_host].attempt = n['attempt'] # ##self.new_hosts[new_host].status_information = n['status_information'].encode('utf-8') self.new_hosts[new_host].status_information = n['status_information'] self.new_hosts[new_host].passiveonly = n['passiveonly'] self.new_hosts[new_host].notifications_disabled = n['notifications_disabled'] self.new_hosts[new_host].flapping = n['flapping'] self.new_hosts[new_host].acknowledged = n['acknowledged'] self.new_hosts[new_host].scheduled_downtime = n['scheduled_downtime'] self.new_hosts[new_host].status_type = status_type del tds, n except Exception: self.Error(sys.exc_info()) # do some cleanup htobj.decompose() del htobj, trs, table except Exception: # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) # services try: for status_type in 'hard', 'soft': result = self.FetchURL(self.cgiurl_services[status_type]) htobj, error, status_code = result.result, result.error, result.status_code # check if any error occured errors_occured = self.check_for_error(htobj, error, status_code) # if there are errors return them if errors_occured is not False: return(errors_occured) # too much copy.deepcopy()s here give recursion crashs table = htobj('table', {'class': 'status'})[0] # some Icinga versions have a tag in cgi output HTML which # omits the tags being found if len(table('tbody')) == 0: trs = table('tr', recursive=False) else: tbody = table('tbody')[0] trs = tbody('tr', recursive=False) del result, error # kick out table heads trs.pop(0) # dummy tds to be deleteable tds = [] for tr in trs: try: # ignore empty rows - there are a lot of them - a Nagios bug? tds = tr('td', recursive=False) if len(tds) > 1: n = dict() # host # the resulting table of Nagios status.cgi table omits the # hostname of a failing service if there are more than one # so if the hostname is empty the nagios status item should get # its hostname from the previuos item - one reason to keep 'nagitems' try: n['host'] = str(tds[0](text=not_empty)[0]) except Exception: n['host'] = str(nagitems['services'][len(nagitems['services']) - 1]['host']) # service n['service'] = str(tds[1](text=not_empty)[0]) # status n['status'] = str(tds[2](text=not_empty)[0]) # last_check n['last_check'] = str(tds[3](text=not_empty)[0]) # duration n['duration'] = str(tds[4](text=not_empty)[0]) # attempt # to fix http://sourceforge.net/tracker/?func=detail&atid=1101370&aid=3280961&group_id=236865 .attempt needs # to be stripped n['attempt'] = str(tds[5](text=not_empty)[0]).strip() # status_information if len(tds[6](text=not_empty)) == 0: n['status_information'] = '' else: n['status_information'] = str(tds[6].text).replace('\n', ' ').replace('\t', ' ').strip() # status flags n['passiveonly'] = False n['notifications_disabled'] = False n['flapping'] = False n['acknowledged'] = False n['scheduled_downtime'] = False # map status icons to status flags icons = tds[1].findAll('img') for i in icons: icon = i['src'].split('/')[-1] if icon in self.STATUS_MAPPING: n[self.STATUS_MAPPING[icon]] = True # cleaning del icons # add dictionary full of information about this service item to nagitems - only if service nagitems['services'].append(n) # after collection data in nagitems create objects of its informations # host objects contain service objects if n['host'] not in self.new_hosts: self.new_hosts[n['host']] = GenericHost() self.new_hosts[n['host']].name = n['host'] self.new_hosts[n['host']].status = 'UP' # trying to fix https://sourceforge.net/tracker/index.php?func=detail&aid=3299790&group_id=236865&atid=1101370 # if host is not down but in downtime or any other flag this should be evaluated too # map status icons to status flags icons = tds[0].findAll('img') for i in icons: icon = i['src'].split('/')[-1] if icon in self.STATUS_MAPPING: self.new_hosts[n['host']].__dict__[self.STATUS_MAPPING[icon]] = True # if a service does not exist create its object if n['service'] not in self.new_hosts[n['host']].services: new_service = n['service'] self.new_hosts[n['host']].services[new_service] = GenericService() self.new_hosts[n['host']].services[new_service].host = n['host'] self.new_hosts[n['host']].services[new_service].name = n['service'] self.new_hosts[n['host']].services[new_service].server = self.name self.new_hosts[n['host']].services[new_service].status = n['status'] self.new_hosts[n['host']].services[new_service].last_check = n['last_check'] self.new_hosts[n['host']].services[new_service].duration = n['duration'] self.new_hosts[n['host']].services[new_service].attempt = n['attempt'] self.new_hosts[n['host']].services[new_service].status_information = n['status_information'] self.new_hosts[n['host']].services[new_service].passiveonly = n['passiveonly'] self.new_hosts[n['host']].services[new_service].notifications_disabled = n[ 'notifications_disabled'] self.new_hosts[n['host']].services[new_service].flapping = n['flapping'] self.new_hosts[n['host']].services[new_service].acknowledged = n['acknowledged'] self.new_hosts[n['host']].services[new_service].scheduled_downtime = n['scheduled_downtime'] self.new_hosts[n['host']].services[new_service].status_type = status_type del tds, n except Exception: self.Error(sys.exc_info()) # do some cleanup htobj.decompose() del htobj, trs, table except Exception: # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) # some cleanup del(nagitems) # dummy return in case all is OK return Result() def GetStatus(self, output=None): ''' get nagios status information from cgiurl and give it back as dictionary output parameter is needed in case authentication failed so that popwin might ask for credentials ''' # set checking flag to be sure only one thread cares about this server self.isChecking = True # check if server is enabled, if not, do not get any status if self.enabled is False: self.worst_status_diff = 'UP' self.isChecking = False return Result() # initialize HTTP first self.init_HTTP() # get all trouble hosts/services from server specific _get_status() status = self._get_status() if status is not None: self.status = status.result self.status_description = status.error self.status_code = status.status_code else: return Result() # some monitor server seem to have a problem with too short intervals # and sometimes send a bad status line which would result in a misleading # ERROR display - it seems safe to ignore these errors # see https://github.com/HenriWahl/Nagstamon/issues/207 # Update: Another strange error to ignore is ConnectionResetError # see https://github.com/HenriWahl/Nagstamon/issues/295 if 'BadStatusLine' in self.status_description or\ 'ConnectionResetError' in self.status_description: self.status_description = '' self.isChecking = False return Result(result=self.status, error=self.status_description, status_code=self.status_code) if (self.status == 'ERROR' or self.status_description != '' or self.status_code >= 400): # ask for password if authorization failed if 'HTTP Error 401' in self.status_description or \ 'HTTP Error 403' in self.status_description or \ 'HTTP Error 500' in self.status_description or \ 'bad session id' in self.status_description.lower() or \ 'login failed' in self.status_description.lower() or \ self.status_code in self.STATUS_CODES_NO_AUTH: if conf.servers[self.name].enabled is True: # needed to get valid credentials self.refresh_authentication = True # clean existent authentication self.reset_HTTP() self.init_HTTP() status = self._get_status() self.status = status.result self.status_description = status.error self.status_code = status.status_code return(status) elif self.status_description.startswith('requests.exceptions.SSLError:'): self.tls_error = True else: self.isChecking = False self.tls_error = False return Result(result=self.status, error=self.status_description, status_code=self.status_code) # no new authentication needed self.refresh_authentication = False # this part has been before in GUI.RefreshDisplay() - wrong place, here it needs to be reset self.nagitems_filtered = {'services': {'DISASTER': [], 'CRITICAL': [], 'HIGH': [], 'AVERAGE': [], 'WARNING': [], 'INFORMATION': [], 'UNKNOWN': []}, 'hosts': {'DOWN': [], 'UNREACHABLE': []}} # initialize counts for various service/hosts states # count them with every miserable host/service respective to their meaning self.down = 0 self.unreachable = 0 self.unknown = 0 self.critical = 0 self.warning = 0 # zabbix support self.information = 0 self.average = 0 self.high = 0 self.disaster = 0 for host in self.new_hosts.values(): # Don't enter the loop if we don't have a problem. Jump down to your problem services if not host.status == 'UP': # add hostname for sorting host.host = host.name # Some generic filters if host.acknowledged is True and conf.filter_acknowledged_hosts_services is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: ACKNOWLEDGED ' + str(host.name)) host.visible = False if host.notifications_disabled is True and\ conf.filter_hosts_services_disabled_notifications is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: NOTIFICATIONS ' + str(host.name)) host.visible = False if host.passiveonly is True and conf.filter_hosts_services_disabled_checks is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: PASSIVEONLY ' + str(host.name)) host.visible = False if host.scheduled_downtime is True and conf.filter_hosts_services_maintenance is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: DOWNTIME ' + str(host.name)) host.visible = False if host.flapping is True and conf.filter_all_flapping_hosts is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: FLAPPING HOST ' + str(host.name)) host.visible = False # Check_MK and OP5 do not show the status_type so their host.status_type will be empty if host.status_type != '': if conf.filter_hosts_in_soft_state is True and host.status_type == 'soft': if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: SOFT STATE ' + str(host.name)) host.visible = False if host_is_filtered_out_by_re(host.name, conf) is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: REGEXP ' + str(host.name)) host.visible = False if StatusInformationIsFilteredOutByRE(host.status_information, conf) is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: REGEXP ' + str(host.name)) host.visible = False # The Criticality filter can be used only with centreon objects. Other objects don't have the criticality attribute. if self.type == 'Centreon': if CriticalityIsFilteredOutByRE(host.criticality, conf): if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: REGEXP Criticality ' + str(host.name)) host.visible = False # Finegrain for the specific state if host.status == 'DOWN': if conf.filter_all_down_hosts is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: DOWN ' + str(host.name)) host.visible = False if host.visible: self.nagitems_filtered['hosts']['DOWN'].append(host) self.down += 1 if host.status == 'UNREACHABLE': if conf.filter_all_unreachable_hosts is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: UNREACHABLE ' + str(host.name)) host.visible = False if host.visible: self.nagitems_filtered['hosts']['UNREACHABLE'].append(host) self.unreachable += 1 # Add host flags for status icons in treeview if host.acknowledged: host.host_flags += 'A' if host.scheduled_downtime: host.host_flags += 'D' if host.flapping: host.host_flags += 'F' if host.passiveonly: host.host_flags += 'P' for service in host.services.values(): # add service name for sorting service.service = service.name # Some generic filtering if service.acknowledged is True and conf.filter_acknowledged_hosts_services is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: ACKNOWLEDGED ' + str(host.name) + ';' + str(service.name)) service.visible = False if service.notifications_disabled is True and\ conf.filter_hosts_services_disabled_notifications is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: NOTIFICATIONS ' + str(host.name) + ';' + str(service.name)) service.visible = False if service.passiveonly is True and conf.filter_hosts_services_disabled_checks is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: PASSIVEONLY ' + str(host.name) + ';' + str(service.name)) service.visible = False if service.scheduled_downtime is True and conf.filter_hosts_services_maintenance is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: DOWNTIME ' + str(host.name) + ';' + str(service.name)) service.visible = False if service.flapping is True and conf.filter_all_flapping_services is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: FLAPPING SERVICE ' + str(host.name) + ';' + str(service.name)) service.visible = False if host.scheduled_downtime is True and conf.filter_services_on_hosts_in_maintenance is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: Service on host in DOWNTIME ' + str(host.name) + ';' + str( service.name)) service.visible = False if host.acknowledged is True and conf.filter_services_on_acknowledged_hosts is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: Service on acknowledged host' + str(host.name) + ';' + str( service.name)) service.visible = False if host.status == 'DOWN' and conf.filter_services_on_down_hosts is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: Service on host in DOWN ' + str(host.name) + ';' + str(service.name)) service.visible = False if host.status == 'UNREACHABLE' and conf.filter_services_on_unreachable_hosts is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: Service on host in UNREACHABLE ' + str(host.name) + ';' + str( service.name)) service.visible = False # Check_MK and OP5 do not show the status_type so their host.status_type will be empty if service.status_type != '': if conf.filter_services_in_soft_state is True and service.status_type == 'soft': if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: SOFT STATE ' + str(host.name) + ';' + str(service.name)) service.visible = False else: if len(service.attempt) < 3: service.visible = True elif len(service.attempt) == 3: # the old, actually wrong, behaviour real_attempt, max_attempt = service.attempt.split('/') if real_attempt != max_attempt and conf.filter_services_in_soft_state is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: SOFT STATE ' + str(host.name) + ';' + str(service.name)) service.visible = False if host_is_filtered_out_by_re(host.name, conf) is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: REGEXP ' + str(host.name) + ';' + str(service.name)) service.visible = False if ServiceIsFilteredOutByRE(service.get_name(), conf) is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: REGEXP ' + str(host.name) + ';' + str(service.name)) service.visible = False if StatusInformationIsFilteredOutByRE(service.status_information, conf) is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: REGEXP ' + str(host.name) + ';' + str(service.name)) service.visible = False # The Criticality filter can be used only with centreon objects. Other objects don't have the criticality attribute. if self.type == 'Centreon': if CriticalityIsFilteredOutByRE(service.criticality, conf): if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: REGEXP Criticality %s;%s %s' % ( (str(host.name), str(service.name), str(service.criticality)))) service.visible = False # Finegrain for the specific state if service.visible: if service.status == 'DISASTER': if conf.filter_all_disaster_services is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: DISASTER ' + str(host.name) + ';' + str(service.name)) service.visible = False else: self.nagitems_filtered['services']['DISASTER'].append(service) self.disaster += 1 if service.status == 'CRITICAL': if conf.filter_all_critical_services is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: CRITICAL ' + str(host.name) + ';' + str(service.name)) service.visible = False else: self.nagitems_filtered['services']['CRITICAL'].append(service) self.critical += 1 if service.status == 'HIGH': if conf.filter_all_high_services is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: HIGH ' + str(host.name) + ';' + str(service.name)) service.visible = False else: self.nagitems_filtered['services']['HIGH'].append(service) self.high += 1 if service.status == 'AVERAGE': if conf.filter_all_average_services is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: AVERAGE ' + str(host.name) + ';' + str(service.name)) service.visible = False else: self.nagitems_filtered['services']['AVERAGE'].append(service) self.average += 1 if service.status == 'WARNING': if conf.filter_all_warning_services is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: WARNING ' + str(host.name) + ';' + str(service.name)) service.visible = False else: self.nagitems_filtered['services']['WARNING'].append(service) self.warning += 1 if service.status == 'INFORMATION': if conf.filter_all_information_services is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: INFORMATION ' + str(host.name) + ';' + str(service.name)) service.visible = False else: self.nagitems_filtered['services']['INFORMATION'].append(service) self.information += 1 if service.status == 'UNKNOWN': if conf.filter_all_unknown_services is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug='Filter: UNKNOWN ' + str(host.name) + ';' + str(service.name)) service.visible = False else: self.nagitems_filtered['services']['UNKNOWN'].append(service) self.unknown += 1 # zabbix support if service.status == "INFORMATION": if conf.filter_all_unknown_services is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug="Filter: INFORMATION " + str(host.name) + ";" + str(service.name)) service.visible = False else: self.nagitems_filtered["services"]["INFORMATION"].append(service) self.informations += 1 if service.status == "AVERAGE": if conf.filter_all_unknown_services is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug="Filter: AVERAGE " + str(host.name) + ";" + str(service.name)) service.visible = False else: self.nagitems_filtered["services"]["AVERAGE"].append(service) self.average += 1 if service.status == "HIGH": if conf.filter_all_unknown_services is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug="Filter: HIGH " + str(host.name) + ";" + str(service.name)) service.visible = False else: self.nagitems_filtered["services"]["HIGH"].append(service) self.high += 1 if service.status == "DISASTER": if conf.filter_all_unknown_services is True: if conf.debug_mode: self.Debug(server=self.get_name(), debug="Filter: DISASTER " + str(host.name) + ";" + str(service.name)) service.visible = False else: self.nagitems_filtered["services"]["DISASTER"].append(service) self.disaster += 1 # Add service flags for status icons in treeview if service.acknowledged: service.service_flags += 'A' if service.scheduled_downtime: service.service_flags += 'D' if service.flapping: service.service_flags += 'F' if service.passiveonly: service.service_flags += 'P' # Add host of service flags for status icons in treeview if host.acknowledged: service.host_flags += 'A' if host.scheduled_downtime: service.host_flags += 'D' if host.flapping: service.host_flags += 'F' if host.passiveonly: service.host_flags += 'P' # find out if there has been some status change to notify user # compare sorted lists of filtered nagios items new_nagitems_filtered_list = [] for i in self.nagitems_filtered['hosts'].values(): for h in i: new_nagitems_filtered_list.append((h.name, h.status)) for i in self.nagitems_filtered['services'].values(): for s in i: new_nagitems_filtered_list.append((s.host, s.name, s.status)) # sort for better comparison new_nagitems_filtered_list.sort() # in the following lines worst_status_diff only changes from UP to another value if there was some change in the # worst status - if it is the same as before it will just keep UP # if both lists are identical there was no status change if (self.nagitems_filtered_list == new_nagitems_filtered_list): self.worst_status_diff = 'UP' else: # if the new list is shorter than the first and there are no different hosts # there one host/service must have been recovered, which is not worth a notification diff = [] for i in new_nagitems_filtered_list: if i not in self.nagitems_filtered_list: # collect differences diff.append(i) if len(diff) == 0: self.worst_status_diff = 'UP' else: # if there are different hosts/services in list of new hosts there must be a notification # get list of states for comparison diff_states = [] for d in diff: diff_states.append(d[-1]) # temporary worst state index worst = 0 for d in diff_states: # only check worst state if it is valid if d in STATES: if STATES.index(d) > worst: worst = STATES.index(d) # final worst state is one of the predefined states self.worst_status_diff = STATES[worst] # get the current worst state, needed at least for systraystatusicon self.worst_status_current = 'UP' if self.down > 0: self.worst_status_current = 'DOWN' elif self.unreachable > 0: self.worst_status_current = 'UNREACHABLE' elif self.disaster > 0: self.worst_status_current = 'DISASTER' elif self.critical > 0: self.worst_status_current = 'CRITICAL' elif self.high > 0: self.worst_status_current = 'HIGH' elif self.average > 0: self.worst_status_current = 'AVERAGE' elif self.warning > 0: self.worst_status_current = 'WARNING' elif self.information > 0: self.worst_status_current = 'INFORMATION' elif self.unknown > 0: self.worst_status_current = 'UNKNOWN' # when everything is OK set this flag for GUI to evaluate if self.down == 0 and\ self.unreachable == 0 and\ self.disaster == 0 and\ self.unknown == 0 and\ self.critical == 0 and\ self.high == 0 and\ self.average == 0 and\ self.warning == 0 and\ self.information == 0: self.all_ok = True else: self.all_ok = False # copy of listed nagitems for next comparison self.nagitems_filtered_list = copy.deepcopy(new_nagitems_filtered_list) del new_nagitems_filtered_list # put new informations into respective dictionaries self.hosts = copy.deepcopy(self.new_hosts) self.new_hosts.clear() # taken from GUI.RefreshDisplay() - get event history for notification # first clear current events self.events_current.clear() # get all nagitems for host in self.hosts.values(): if not host.status == 'UP': # only if host is not filtered out add it to current events # the boolean is meaningless for current events if host.visible: self.events_current[host.get_hash()] = True for service in host.services.values(): # same for services of host if service.visible: self.events_current[service.get_hash()] = True # check if some cached event still is relevant - kick it out if not for event in list(self.events_history.keys()): if event not in self.events_current.keys(): self.events_history.pop(event) self.events_notification.pop(event) # if some current event is not yet in event cache add it and mark it as fresh (=True) for event in list(self.events_current.keys()): if event not in self.events_history.keys() and conf.highlight_new_events: self.events_history[event] = True self.events_notification[event] = True # after all checks are done unset checking flag self.isChecking = False # return True if all worked well return Result() def FetchURL(self, url, giveback='obj', cgi_data=None, no_auth=False, multipart=False): ''' get content of given url, cgi_data only used if present 'obj' FetchURL gives back a dict full of miserable hosts/services, 'xml' giving back as objectified xml 'raw' it gives back pure HTML - useful for finding out IP or new version existence of cgi_data forces urllib to use POST instead of GET requests NEW: gives back a list containing result and, if necessary, a more clear error description ''' # run this method which checks itself if there is some action to take for initializing connection # if no_auth is true do not use Auth headers, used by check for new version try: try: # debug if conf.debug_mode is True: self.Debug(server=self.get_name(), debug='FetchURL: ' + url + ' CGI Data: ' + str(cgi_data)) # use session only for connections to monitor servers, other requests like looking for updates # should go out without credentials if no_auth is False: # most requests come without multipart/form-data if multipart is False: if cgi_data is None: response = self.session.get(url, timeout=self.timeout) else: response = self.session.post(url, data=cgi_data, timeout=self.timeout) else: # Check_MK and Opsview need multipart/form-data encoding # http://stackoverflow.com/questions/23120974/python-requests-post-multipart-form-data-without-filename-in-http-request#23131823 form_data = dict() for key in cgi_data: form_data[key] = (None, cgi_data[key]) # get response with cgi_data encodes as files response = self.session.post(url, files=form_data, timeout=self.timeout) else: # send request without authentication data temporary_session = requests.Session() temporary_session.headers['User-Agent'] = self.USER_AGENT # add proxy information if necessary self.proxify(temporary_session) # no need to check TLS validity for temporary sessions like update check temporary_session.verify = False # most requests come without multipart/form-data if multipart is False: if cgi_data is None: response = temporary_session.get(url, timeout=self.timeout) else: response = temporary_session.post(url, data=cgi_data, timeout=self.timeout) else: # Check_MK and Opsview nees multipart/form-data encoding # http://stackoverflow.com/questions/23120974/python-requests-post-multipart-form-data-without-filename-in-http-request#23131823 form_data = dict() for key in cgi_data: form_data[key] = (None, cgi_data[key]) # get response with cgi_data encodes as files response = temporary_session.post(url, files=form_data, timeout=self.timeout) # cleanup del temporary_session except Exception: traceback.print_exc(file=sys.stdout) result, error = self.Error(sys.exc_info()) if error.startswith('requests.exceptions.SSLError:'): self.tls_error = True else: self.tls_error = False return Result(result=result, error=error, status_code=-1) # give back pure HTML or XML in case giveback is 'raw' if giveback == 'raw': # .text gives content in unicode return Result(result=response.text, status_code=response.status_code) # objectified HTML if giveback == 'obj': yummysoup = BeautifulSoup(response.text, self.PARSER) return Result(result=yummysoup, status_code=response.status_code) # objectified generic XML, valid at least for Opsview and Centreon elif giveback == 'xml': xmlobj = BeautifulSoup(response.text, self.PARSER) return Result(result=xmlobj, status_code=response.status_code) except Exception: traceback.print_exc(file=sys.stdout) result, error = self.Error(sys.exc_info()) return Result(result=result, error=error, status_code=response.status_code) result, error = self.Error(sys.exc_info()) return Result(result=result, error=error, status_code=response.status_code) def GetHost(self, host): ''' find out ip or hostname of given host to access hosts/devices which do not appear in DNS but have their ip saved in Nagios ''' # the fasted method is taking hostname as used in monitor if conf.connect_by_host is True or host == '': return Result(result=host) # initialize ip string ip = '' # glue nagios cgi url and hostinfo cgiurl_host = self.monitor_cgi_url + '/extinfo.cgi?type=1&host=' + host # get host info result = self.FetchURL(cgiurl_host, giveback='obj') htobj = result.result try: # take ip from html soup ip = htobj.findAll(name='div', attrs={'class': 'data'})[-1].text # workaround for URL-ified IP as described in SF bug 2967416 # https://sourceforge.net/tracker/?func=detail&aid=2967416&group_id=236865&atid=1101370 if '://' in ip: ip = ip.split('://')[1] # last-minute-workaround for https://github.com/HenriWahl/Nagstamon/issues/48 if ',' in ip: ip = ip.split(',')[0] # print IP in debug mode if conf.debug_mode is True: self.Debug(server=self.get_name(), host=host, debug='IP of %s:' % (host) + ' ' + ip) # when connection by DNS is not configured do it by IP if conf.connect_by_dns is True: # try to get DNS name for ip, if not available use ip try: address = socket.gethostbyaddr(ip)[0] except socket.error: address = ip else: address = ip except Exception: result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) # do some cleanup del htobj # give back host or ip return Result(result=address) def GetItemsGenerator(self): ''' Generator for plain listing of all filtered items, used in QUI for tableview ''' # reset number of filtered items self.nagitems_filtered_count = 0 for state in self.nagitems_filtered['hosts'].values(): for host in state: # increase number of items for use in table self.nagitems_filtered_count += 1 yield (host) for state in self.nagitems_filtered['services'].values(): for service in state: # increase number of items for use in table self.nagitems_filtered_count += 1 yield (service) def Hook(self): ''' allows to add some extra actions for a monitor server to be executed in RefreshLoop inspired by Centreon and its seemingly Alzheimer disease regarding session ID/Cookie/whatever ''' pass def Error(self, error): ''' Handle errors somehow - print them or later log them into not yet existing log file ''' if conf.debug_mode: debug = '' for line in traceback.format_exception(error[0], error[1], error[2], 5): debug += line self.Debug(server=self.get_name(), debug=debug, head='ERROR') return ['ERROR', traceback.format_exception_only(error[0], error[1])[0]] def Debug(self, server='', host='', service='', debug='', head='DEBUG'): ''' centralized debugging ''' # initialize items in line to be logged log_line = [head + ':', str(datetime.datetime.now())] if server != '': log_line.append(server) if host != '': log_line.append(host) if service != '': log_line.append(service) if debug != '': log_line.append(debug) # put debug info into debug queue debug_queue.append(' '.join(log_line)) def get_events_history_count(self): """ return number of unseen events - those which are set True as unseen """ return(len(list((e for e in self.events_history if self.events_history[e] is True)))) def check_for_error(self, result, error, status_code): """ check if any error occured - if so, return error """ if error != '' or status_code > 400: return(Result(result=copy.deepcopy(result), error=copy.deepcopy(error), status_code=copy.deepcopy(status_code))) else: return(False) Nagstamon/Nagstamon/Servers/Icinga.py000066400000000000000000001053601316117564000201420ustar00rootroot00000000000000# encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2014 Henri Wahl et al. # # 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 import urllib.request, urllib.parse, urllib.error import sys import copy import json from bs4 import BeautifulSoup from collections import OrderedDict from distutils.version import LooseVersion from Nagstamon.Servers.Generic import GenericServer from Nagstamon.Objects import (GenericHost, GenericService, Result) from Nagstamon.Helpers import not_empty class IcingaServer(GenericServer): """ object of Incinga server """ TYPE = 'Icinga' # flag to handle JSON or HTML correctly - checked by get_server_version() json = None def init_config(self): """ set URLs for CGI - they are static and there is no need to set them with every cycle """ # dummy default empty cgi urls - get filled later when server version is known self.cgiurl_services = None self.cgiurl_hosts = None def init_HTTP(self): """ Icinga 1.11 needs extra Referer header for actions """ GenericServer.init_HTTP(self) if not "Referer" in self.session.headers: # to execute actions since Icinga 1.11 a Referer Header is necessary self.session.headers["Referer"] = self.monitor_cgi_url + "/cmd.cgi" def get_server_version(self): """ Try to get Icinga version for different URLs and JSON capabilities """ result = self.FetchURL('%s/tac.cgi?jsonoutput' % (self.monitor_cgi_url), giveback='raw') if result.error != '': return result else: tacraw = result.result if result.status_code < 400: if tacraw.startswith('<'): self.json = False tacsoup = BeautifulSoup(tacraw, 'html.parser') self.version = tacsoup.find('a', { 'class' : 'homepageURL' }) # only extract version if HTML seemed to be OK if 'contents' in self.version.__dict__: self.version = self.version.contents[0].split('Icinga ')[1] elif tacraw.startswith('{'): # there seem to be problems with Icinga < 1.6 # in case JSON parsing crashes fall back to HTML try: jsondict = json.loads(tacraw) self.version = jsondict['cgi_json_version'] self.json = True except: self.version = '1.6' self.json = False else: self.refresh_authentication = True def _get_status(self): """ Get status from Icinga Server, prefer JSON if possible """ try: if self.json == None: # we need to get the server version and its JSONability result = self.get_server_version() if self.version != '': # define CGI URLs for hosts and services depending on JSON-capable server version if self.cgiurl_hosts == self.cgiurl_services == None: if LooseVersion(self.version) < LooseVersion('1.7'): # http://www.nagios-wiki.de/nagios/tips/host-_und_serviceproperties_fuer_status.cgi?s=servicestatustypes # services (unknown, warning or critical?) as dictionary, sorted by hard and soft state type self.cgiurl_services = {'hard': self.monitor_cgi_url + '/status.cgi?host=all&servicestatustypes=253&serviceprops=262144', \ 'soft': self.monitor_cgi_url + '/status.cgi?host=all&servicestatustypes=253&serviceprops=524288'} # hosts (up or down or unreachable) self.cgiurl_hosts = {'hard': self.monitor_cgi_url + '/status.cgi?hostgroup=all&style=hostdetail&hoststatustypes=12&hostprops=262144', \ 'soft': self.monitor_cgi_url + '/status.cgi?hostgroup=all&style=hostdetail&hoststatustypes=12&hostprops=524288'} else: # services (unknown, warning or critical?) self.cgiurl_services = {'hard': self.monitor_cgi_url + '/status.cgi?style=servicedetail&servicestatustypes=253&serviceprops=262144', \ 'soft': self.monitor_cgi_url + '/status.cgi?style=servicedetail&servicestatustypes=253&serviceprops=524288'} # hosts (up or down or unreachable) self.cgiurl_hosts = {'hard': self.monitor_cgi_url + '/status.cgi?style=hostdetail&hoststatustypes=12&hostprops=262144', \ 'soft': self.monitor_cgi_url + '/status.cgi?style=hostdetail&hoststatustypes=12&hostprops=524288'} if self.json == True: for status_type in 'hard', 'soft': self.cgiurl_services[status_type] += '&jsonoutput' self.cgiurl_hosts[status_type] += '&jsonoutput' # get status depending on JSONablility if self.json == True: return(self._get_status_JSON()) else: return(self._get_status_HTML()) else: # error result in case version still was '' return result except: # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) # dummy return in case all is OK return Result() def _get_status_JSON(self): """ Get status from Icinga Server - the JSON way """ # new_hosts dictionary self.new_hosts = dict() # hosts - mostly the down ones # now using JSON output from Icinga try: for status_type in 'hard', 'soft': result = self.FetchURL(self.cgiurl_hosts[status_type], giveback='raw') # purify JSON result of unnecessary control sequence \n jsonraw, error, status_code = copy.deepcopy(result.result.replace('\n', '')),\ copy.deepcopy(result.error),\ result.status_code # check if any error occured errors_occured = self.check_for_error(jsonraw, error, status_code) # if there are errors return them if errors_occured != False: return(errors_occured) jsondict = json.loads(jsonraw) hosts = copy.deepcopy(jsondict['status']['host_status']) for host in hosts: # make dict of tuples for better reading h = dict(host.items()) # host if self.use_display_name_host == False: # according to http://sourceforge.net/p/nagstamon/bugs/83/ it might # better be host_name instead of host_display_name # legacy Icinga adjustments if 'host_name' in h: host_name = h['host_name'] elif 'host' in h: host_name = h['host'] else: # https://github.com/HenriWahl/Nagstamon/issues/46 on the other hand has # problems with that so here we go with extra display_name option host_name = h['host_display_name'] # host objects contain service objects if not host_name in self.new_hosts: self.new_hosts[host_name] = GenericHost() self.new_hosts[host_name].name = host_name self.new_hosts[host_name].server = self.name self.new_hosts[host_name].status = h['status'] self.new_hosts[host_name].last_check = h['last_check'] self.new_hosts[host_name].duration = h['duration'] self.new_hosts[host_name].attempt = h['attempts'] self.new_hosts[host_name].status_information = h['status_information'].replace('\n', ' ').strip() self.new_hosts[host_name].passiveonly = not(h['active_checks_enabled']) self.new_hosts[host_name].notifications_disabled = not(h['notifications_enabled']) self.new_hosts[host_name].flapping = h['is_flapping'] self.new_hosts[host_name].acknowledged = h['has_been_acknowledged'] self.new_hosts[host_name].scheduled_downtime = h['in_scheduled_downtime'] self.new_hosts[host_name].status_type = status_type # extra Icinga properties to solve https://github.com/HenriWahl/Nagstamon/issues/192 # acknowledge needs host_description and no display name self.new_hosts[host_name].real_name = h['host_name'] del h, host_name except: # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) # services try: for status_type in 'hard', 'soft': result = self.FetchURL(self.cgiurl_services[status_type], giveback='raw') # purify JSON result of unnecessary control sequence \n jsonraw, error, status_code = copy.deepcopy(result.result.replace('\n', '')),\ copy.deepcopy(result.error),\ result.status_code # check if any error occured errors_occured = self.check_for_error(jsonraw, error, status_code) # if there are errors return them if errors_occured != False: return(errors_occured) jsondict = json.loads(jsonraw) services = copy.deepcopy(jsondict['status']['service_status']) for service in services: # make dict of tuples for better reading s = dict(service.items()) if self.use_display_name_host == False: # according to http://sourceforge.net/p/nagstamon/bugs/83/ it might # better be host_name instead of host_display_name # legacy Icinga adjustments if 'host_name' in s: host_name = s['host_name'] elif 'host' in s: host_name = s['host'] else: # https://github.com/HenriWahl/Nagstamon/issues/46 on the other hand has # problems with that so here we go with extra display_name option host_name = s['host_display_name'] # host objects contain service objects if not host_name in self.new_hosts: self.new_hosts[host_name] = GenericHost() self.new_hosts[host_name].name = host_name self.new_hosts[host_name].status = 'UP' # extra Icinga properties to solve https://github.com/HenriWahl/Nagstamon/issues/192 # acknowledge needs host_description and no display name if 'host_name' in s: self.new_hosts[host_name].real_name = s['host_name'] elif 'host' in s: self.new_hosts[host_name].real_name = s['host'] if self.use_display_name_host == False: # legacy Icinga adjustments if 'service_description' in s: service_name = s['service_description'] elif 'description' in s: service_name = s['description'] elif 'service' in s: service_name = s['service'] else: service_name = s['service_display_name'] # if a service does not exist create its object if not service_name in self.new_hosts[host_name].services: self.new_hosts[host_name].services[service_name] = GenericService() self.new_hosts[host_name].services[service_name].host = host_name self.new_hosts[host_name].services[service_name].name = service_name self.new_hosts[host_name].services[service_name].server = self.name self.new_hosts[host_name].services[service_name].status = s['status'] self.new_hosts[host_name].services[service_name].last_check = s['last_check'] self.new_hosts[host_name].services[service_name].duration = s['duration'] self.new_hosts[host_name].services[service_name].attempt = s['attempts'] self.new_hosts[host_name].services[service_name].status_information = s['status_information'].replace('\n', ' ').strip() self.new_hosts[host_name].services[service_name].passiveonly = not(s['active_checks_enabled']) self.new_hosts[host_name].services[service_name].notifications_disabled = not(s['notifications_enabled']) self.new_hosts[host_name].services[service_name].flapping = s['is_flapping'] self.new_hosts[host_name].services[service_name].acknowledged = s['has_been_acknowledged'] self.new_hosts[host_name].services[service_name].scheduled_downtime = s['in_scheduled_downtime'] self.new_hosts[host_name].services[service_name].status_type = status_type # extra Icinga properties to solve https://github.com/HenriWahl/Nagstamon/issues/192 # acknowledge needs service_description and no display name self.new_hosts[host_name].services[service_name].real_name = s.get('service_description', s.get('service_display_name')) del s, host_name, service_name except: # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) # some cleanup del jsonraw, jsondict, error, hosts, services # dummy return in case all is OK return Result() def _get_status_HTML(self): """ Get status from Nagios Server - the oldschool CGI HTML way """ # create Nagios items dictionary with to lists for services and hosts # every list will contain a dictionary for every failed service/host # this dictionary is only temporarily # ##global icons nagitems = {'services':[], 'hosts':[]} # new_hosts dictionary self.new_hosts = dict() # hosts - mostly the down ones # unfortunately the hosts status page has a different structure so # hosts must be analyzed separately try: for status_type in 'hard', 'soft': result = self.FetchURL(self.cgiurl_hosts[status_type]) htobj, error, status_code = result.result,\ result.error,\ result.status_code # check if any error occured errors_occured = self.check_for_error(htobj, error, status_code) # if there are errors return them if errors_occured != False: return(errors_occured) # put a copy of a part of htobj into table to be able to delete htobj table = htobj('table', {'class': 'status'})[0] # do some cleanup del result, error # access table rows # some Icinga versions have a tag in cgi output HTML which # omits the tags being found if len(table('tbody')) == 0: trs = table('tr', recursive=False) else: tbody = table('tbody')[0] trs = tbody('tr', recursive=False) # kick out table heads trs.pop(0) for tr in trs: try: # ignore empty rows if len(tr('td', recursive=False)) > 1: n = {} # get tds in one tr tds = tr('td', recursive=False) # host try: n['host'] = str(tds[0].table.tr.td.table.tr.td.a.string) except: n['host'] = str(nagitems[len(nagitems) - 1]['host']) # status n['status'] = str(tds[1].string) # last_check n['last_check'] = str(tds[2].string) # duration n['duration'] = str(tds[3].string) # division between Nagios and Icinga in real life... where # Nagios has only 5 columns there are 7 in Icinga 1.3... # ... and 6 in Icinga 1.2 :-) if len(tds) < 7: # the old Nagios table # status_information if len(tds[4](text=not_empty)) == 0: n['status_information'] = '' else: n['status_information'] = str(tds[4].string) # attempts are not shown in case of hosts so it defaults to 'N/A' n['attempt'] = 'N/A' else: # attempts are shown for hosts # to fix http://sourceforge.net/tracker/?func=detail&atid=1101370&aid=3280961&group_id=236865 .attempt needs # to be stripped n['attempt'] = str(tds[4].string).strip() # status_information if len(tds[5](text=not_empty)) == 0: n['status_information'] = '' else: n['status_information'] = str(tds[5].string) # status flags n['passiveonly'] = False n['notifications_disabled'] = False n['flapping'] = False n['acknowledged'] = False n['scheduled_downtime'] = False # map status icons to status flags icons = tds[0].findAll('img') for i in icons: icon = i['src'].split('/')[-1] if icon in self.STATUS_MAPPING: n[self.STATUS_MAPPING[icon]] = True # cleaning del icons # add dictionary full of information about this host item to nagitems nagitems['hosts'].append(n) # after collection data in nagitems create objects from its informations # host objects contain service objects if not 'host' in self.new_hosts: new_host = n['host'] self.new_hosts[new_host] = GenericHost() self.new_hosts[new_host].name = n['host'] self.new_hosts[new_host].server = self.name self.new_hosts[new_host].status = n['status'] self.new_hosts[new_host].last_check = n['last_check'] self.new_hosts[new_host].duration = n['duration'] self.new_hosts[new_host].attempt = n['attempt'] self.new_hosts[new_host].status_information = n['status_information'].replace('\n', ' ').strip() self.new_hosts[new_host].passiveonly = n['passiveonly'] self.new_hosts[new_host].notifications_disabled = n['notifications_disabled'] self.new_hosts[new_host].flapping = n['flapping'] self.new_hosts[new_host].acknowledged = n['acknowledged'] self.new_hosts[new_host].scheduled_downtime = n['scheduled_downtime'] self.new_hosts[new_host].status_type = status_type # extra Icinga properties to solve https://github.com/HenriWahl/Nagstamon/issues/192 # acknowledge needs host_name and no display name self.new_hosts[new_host].real_name = n['host'] # some cleanup del tds, n except: self.Error(sys.exc_info()) # do some cleanup htobj.decompose() del htobj, trs, table except: # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) # services try: for status_type in 'hard', 'soft': result = self.FetchURL(self.cgiurl_services[status_type]) htobj, error, status_code = result.result,\ result.error,\ result.status_code # check if any error occured errors_occured = self.check_for_error(htobj, error, status_code) # if there are errors return them if errors_occured != False: return(errors_occured) table = htobj('table', {'class': 'status'})[0] # some Icinga versions have a tag in cgi output HTML which # omits the tags being found if len(table('tbody')) == 0: trs = table('tr', recursive=False) else: tbody = table('tbody')[0] trs = tbody('tr', recursive=False) # do some cleanup del result, error # kick out table heads trs.pop(0) for tr in trs: try: # ignore empty rows - there are a lot of them - a Nagios bug? tds = tr('td', recursive=False) if len(tds) > 1: n = {} # host # the resulting table of Nagios status.cgi table omits the # hostname of a failing service if there are more than one # so if the hostname is empty the nagios status item should get # its hostname from the previuos item - one reason to keep 'nagitems' try: n['host'] = str(tds[0](text=not_empty)[0]) except: n['host'] = str(nagitems['services'][len(nagitems['services']) - 1]['host']) # service n['service'] = str(tds[1](text=not_empty)[0]) # status n['status'] = str(tds[2](text=not_empty)[0]) # last_check n['last_check'] = str(tds[3](text=not_empty)[0]) # duration n['duration'] = str(tds[4](text=not_empty)[0]) # attempt # to fix http://sourceforge.net/tracker/?func=detail&atid=1101370&aid=3280961&group_id=236865 .attempt needs # to be stripped n['attempt'] = str(tds[5](text=not_empty)[0]).strip() # status_information if len(tds[6](text=not_empty)) == 0: n['status_information'] = '' else: n['status_information'] = str(tds[6](text=not_empty)[0]) # status flags n['passiveonly'] = False n['notifications_disabled'] = False n['flapping'] = False n['acknowledged'] = False n['scheduled_downtime'] = False # map status icons to status flags icons = tds[1].findAll('img') for i in icons: icon = i['src'].split('/')[-1] if icon in self.STATUS_MAPPING: n[self.STATUS_MAPPING[icon]] = True # cleaning del icons # add dictionary full of information about this service item to nagitems - only if service nagitems['services'].append(n) # after collection data in nagitems create objects of its informations # host objects contain service objects if not n['host'] in self.new_hosts: self.new_hosts[n['host']] = GenericHost() self.new_hosts[n['host']].name = n['host'] self.new_hosts[n['host']].status = 'UP' # extra Icinga properties to solve https://github.com/HenriWahl/Nagstamon/issues/192 # acknowledge needs host_description and no display name self.new_hosts[n['host']].real_name = n['host'] # trying to fix https://sourceforge.net/tracker/index.php?func=detail&aid=3299790&group_id=236865&atid=1101370 # if host is not down but in downtime or any other flag this should be evaluated too # map status icons to status flags icons = tds[0].findAll('img') for i in icons: icon = i['src'].split('/')[-1] if icon in self.STATUS_MAPPING: self.new_hosts[n['host']].__dict__[self.STATUS_MAPPING[icon]] = True # cleaning del icons # if a service does not exist create its object if not n['service'] in self.new_hosts[n['host']].services: new_service = n['service'] self.new_hosts[n['host']].services[new_service] = GenericService() self.new_hosts[n['host']].services[new_service].host = n['host'] self.new_hosts[n['host']].services[new_service].server = self.name self.new_hosts[n['host']].services[new_service].name = n['service'] self.new_hosts[n['host']].services[new_service].status = n['status'] self.new_hosts[n['host']].services[new_service].last_check = n['last_check'] self.new_hosts[n['host']].services[new_service].duration = n['duration'] self.new_hosts[n['host']].services[new_service].attempt = n['attempt'] self.new_hosts[n['host']].services[new_service].status_information = n['status_information'].replace('\n', ' ').strip() self.new_hosts[n['host']].services[new_service].passiveonly = n['passiveonly'] self.new_hosts[n['host']].services[new_service].notifications_disabled = n['notifications_disabled'] self.new_hosts[n['host']].services[new_service].flapping = n['flapping'] self.new_hosts[n['host']].services[new_service].acknowledged = n['acknowledged'] self.new_hosts[n['host']].services[new_service].scheduled_downtime = n['scheduled_downtime'] # extra Icinga properties to solve https://github.com/HenriWahl/Nagstamon/issues/192 # acknowledge needs service_description and no display name self.new_hosts[n['host']].services[new_service].real_name = n['service_description'] # some cleanup del tds, n except: self.Error(sys.exc_info()) # do some cleanup htobj.decompose() del htobj, trs, table except: # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) # some cleanup del nagitems # dummy return in case all is OK return Result() def _set_recheck(self, host, service): """ to solve https://sourceforge.net/p/nagstamon/feature-requests/74/ there is a comment parameter added to cgi request """ if service != '': if self.hosts[host].services[service].is_passive_only(): # Do not check passive only checks return # get start time from Nagios as HTML to use same timezone setting like the locally installed Nagios result = self.FetchURL(self.monitor_cgi_url + '/cmd.cgi?' + urllib.parse.urlencode({'cmd_typ':'96', 'host':host})) self.start_time = dict(result.result.find(attrs={'name':'start_time'}).attrs)['value'] # decision about host or service - they have different URLs if service == '': # host cmd_typ = '96' else: # service @ host cmd_typ = '7' # ignore empty service in case of rechecking a host cgi_data = urllib.parse.urlencode([('cmd_typ', cmd_typ), \ ('cmd_mod', '2'), \ ('host', host), \ ('service', service), \ ('start_time', self.start_time), \ ('force_check', 'on'), \ ('com_data', 'Recheck by %s' % self.username), \ ('btnSubmit', 'Commit')]) # execute POST request self.FetchURL(self.monitor_cgi_url + '/cmd.cgi', giveback='raw', cgi_data=cgi_data) def _set_acknowledge(self, host, service, author, comment, sticky, notify, persistent, all_services=[]): ''' send acknowledge to monitor server extra _method necessary due to https://github.com/HenriWahl/Nagstamon/issues/192 ''' url = self.monitor_cgi_url + '/cmd.cgi' # the following flags apply to hosts and services # # according to sf.net bug #3304098 (https://sourceforge.net/tracker/?func=detail&atid=1101370&aid=3304098&group_id=236865) # the send_notification-flag must not exist if it is set to 'off', otherwise # the Nagios core interpretes it as set, regardless its real value # # for whatever silly reason Icinga depends on the correct order of submitted form items... # see sf.net bug 3428844 # # Thanks to Icinga ORDER OF ARGUMENTS IS IMPORTANT HERE! # cgi_data = OrderedDict() if service == '': cgi_data['cmd_typ'] = '33' else: cgi_data['cmd_typ'] = '34' cgi_data['cmd_mod'] = '2' # better to use host_name instead of display_name cgi_data['host'] = self.hosts[host].real_name if service != '': # better to use service_description instead of display_name # this is an extra Icinga property cgi_data['service'] = self.hosts[host].services[service].real_name cgi_data['com_author'] = author cgi_data['com_data'] = comment cgi_data['btnSubmit'] = 'Commit' if notify == True: cgi_data['send_notification'] = '1' if persistent == True: cgi_data['persistent'] = 'on' if sticky == True: cgi_data['sticky_ack'] = '1' self.FetchURL(url, giveback='raw', cgi_data=cgi_data) # acknowledge all services on a host if len(all_services) > 0: for s in all_services: cgi_data['cmd_typ'] = '34' cgi_data['service'] = s self.FetchURL(url, giveback='raw', cgi_data=cgi_data) 0 Nagstamon/Nagstamon/Servers/IcingaWeb2.py000066400000000000000000000653641316117564000206730ustar00rootroot00000000000000# encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2014 Henri Wahl et al. # # 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 # Initial implementation by Marcus Mönnig # # This Server class connects against IcingaWeb2. The monitor URL in the setup should be # something like http://icinga2/icingaweb2 # # Status/TODOs: # # * The IcingaWeb2 API is not implemented yet, so currently this implementation uses # two HTTP requests per action. The first fetches the HTML, then the form data is extracted and # then a second HTTP POST request is made which actually executed the action. # Once IcingaWeb2 has an API, it's probably the better choice. from Nagstamon.Servers.Generic import GenericServer import urllib.parse import sys import copy import json import datetime from bs4 import BeautifulSoup from Nagstamon.Objects import (GenericHost, GenericService, Result) from Nagstamon.Config import (conf, AppInfo) from Nagstamon.Helpers import webbrowser_open def strfdelta(tdelta, fmt): d = {'days': tdelta.days} d['hours'], rem = divmod(tdelta.seconds, 3600) d['minutes'], d['seconds'] = divmod(rem, 60) return fmt.format(**d) class IcingaWeb2Server(GenericServer): """ object of Incinga server """ TYPE = 'IcingaWeb2' MENU_ACTIONS = ['Monitor', 'Recheck', 'Acknowledge', 'Submit check result', 'Downtime'] STATES_MAPPING = {'hosts' : {0 : 'UP', 1 : 'DOWN', 2 : 'UNREACHABLE'}, \ 'services' : {0 : 'OK', 1 : 'WARNING', 2 : 'CRITICAL', 3 : 'UNKNOWN'}} STATES_MAPPING_REV = {'hosts' : { 'UP': 0, 'DOWN': 1, 'UNREACHABLE': 2}, \ 'services' : {'OK': 0, 'WARNING': 1, 'CRITICAL': 2, 'UNKNOWN': 3}} BROWSER_URLS = { 'monitor': '$MONITOR-CGI$/dashboard', \ 'hosts': '$MONITOR-CGI$/monitoring/list/hosts', \ 'services': '$MONITOR-CGI$/monitoring/list/services', \ 'history': '$MONITOR-CGI$/monitoring/list/eventhistory?timestamp>=-7 days'} def init_config(self): """ set URLs for CGI - they are static and there is no need to set them with every cycle """ # dummy default empty cgi urls - get filled later when server version is known self.cgiurl_services = None self.cgiurl_hosts = None # https://github.com/HenriWahl/Nagstamon/issues/400 # The displayed name for host and service is the Icinga2 "internal" name and not the display_name from host/service configuration # This name is stored in host/service dict under key 'name' but is also used as dict key for dict containing all hosts/services # The "internal" name must still be used to query IcingaWeb2 and is in dict under key 'real_name' since https://github.com/HenriWahl/Nagstamon/issues/192 self.use_display_name_host = True self.use_display_name_service = True def init_HTTP(self): """ initializing of session object """ GenericServer.init_HTTP(self) if not 'Referer' in self.session.headers: self.session.headers['Referer'] = self.monitor_cgi_url + '/icingaweb2/monitoring' # normally cookie out will be used if not self.no_cookie_auth: if len(self.session.cookies) == 0: # get login page, thus automatically a cookie login = self.FetchURL('{0}/authentication/login'.format(self.monitor_url)) if login.error == '' and login.status_code == 200: form = login.result.find('form') form_inputs = {} for form_input in ('redirect', 'formUID', 'CSRFToken', 'btn_submit'): if not form.find('input', {'name': form_input}) is None: form_inputs[form_input] = form.find('input', {'name': form_input})['value'] else: form_inputs[form_input] = '' form_inputs['username'] = self.username form_inputs['password'] = self.password # fire up login button with all needed data self.FetchURL('{0}/authentication/login'.format(self.monitor_url), cgi_data=form_inputs) def _get_status(self): """ Get status from Icinga Server - only JSON """ # define CGI URLs for hosts and services if self.cgiurl_hosts == self.cgiurl_services == None: # services (unknown, warning or critical?) self.cgiurl_services = {'hard': self.monitor_cgi_url + '/monitoring/list/services?service_state>0&service_state<=3&service_state_type=1&addColumns=service_last_check&format=json', \ 'soft': self.monitor_cgi_url + '/monitoring/list/services?service_state>0&service_state<=3&service_state_type=0&addColumns=service_last_check&format=json'} # hosts (up or down or unreachable) self.cgiurl_hosts = {'hard': self.monitor_cgi_url + '/monitoring/list/hosts?host_state>0&host_state<=2&host_state_type=1&addColumns=host_last_check&format=json', \ 'soft': self.monitor_cgi_url + '/monitoring/list/hosts?host_state>0&host_state<=2&host_state_type=0&addColumns=host_last_check&format=json'} # new_hosts dictionary self.new_hosts = dict() # hosts - mostly the down ones # now using JSON output from Icinga try: for status_type in 'hard', 'soft': # first attempt result = self.FetchURL(self.cgiurl_hosts[status_type], giveback='raw') # authentication errors get a status code 200 too back because its # HTML works fine :-( if result.status_code < 400 and\ result.result.startswith('<'): # in case of auth error reset HTTP session and try again self.reset_HTTP() result = self.FetchURL(self.cgiurl_hosts[status_type], giveback='raw') # if it does not work again tell GUI there is a problem if result.status_code < 400 and\ result.result.startswith('<'): self.refresh_authentication = True return Result(result=result.result, error='Authentication error', status_code=result.status_code) # purify JSON result of unnecessary control sequence \n jsonraw, error, status_code = copy.deepcopy(result.result.replace('\n', '')),\ copy.deepcopy(result.error),\ result.status_code if error != '' or status_code >= 400: return Result(result=jsonraw, error=error, status_code=status_code) # check if any error occured self.check_for_error(jsonraw, error, status_code) hosts = json.loads(jsonraw) for host in hosts: # make dict of tuples for better reading h = dict(host.items()) # host if self.use_display_name_host == False: # according to http://sourceforge.net/p/nagstamon/bugs/83/ it might # better be host_name instead of host_display_name # legacy Icinga adjustments if 'host_name' in h: host_name = h['host_name'] elif 'host' in h: host_name = h['host'] else: # https://github.com/HenriWahl/Nagstamon/issues/46 on the other hand has # problems with that so here we go with extra display_name option host_name = h['host_display_name'] # host objects contain service objects if not host_name in self.new_hosts: self.new_hosts[host_name] = GenericHost() self.new_hosts[host_name].name = host_name self.new_hosts[host_name].server = self.name self.new_hosts[host_name].status = self.STATES_MAPPING['hosts'][int(h['host_state'])] self.new_hosts[host_name].last_check = datetime.datetime.fromtimestamp(int(h['host_last_check'])) self.new_hosts[host_name].attempt = h['host_attempt'] self.new_hosts[host_name].status_information = BeautifulSoup(h['host_output'].replace('\n', ' ').strip(), 'html.parser').text self.new_hosts[host_name].passiveonly = not(int(h['host_active_checks_enabled'])) self.new_hosts[host_name].notifications_disabled = not(int(h['host_notifications_enabled'])) self.new_hosts[host_name].flapping = bool(int(h['host_is_flapping'])) self.new_hosts[host_name].acknowledged = bool(int(h['host_acknowledged'])) self.new_hosts[host_name].scheduled_downtime = bool(int(h['host_in_downtime'])) self.new_hosts[host_name].status_type = status_type # extra Icinga properties to solve https://github.com/HenriWahl/Nagstamon/issues/192 # acknowledge needs host_description and no display name self.new_hosts[host_name].real_name = h['host_name'] # extra duration needed for calculation duration = datetime.datetime.now() - datetime.datetime.fromtimestamp(int(h['host_last_state_change'])) self.new_hosts[host_name].duration = strfdelta(duration, '{days}d {hours}h {minutes}m {seconds}s') del h, host_name except: import traceback traceback.print_exc(file=sys.stdout) # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) # services try: for status_type in 'hard', 'soft': result = self.FetchURL(self.cgiurl_services[status_type], giveback='raw') # purify JSON result of unnecessary control sequence \n jsonraw, error, status_code = copy.deepcopy(result.result.replace('\n', '')),\ copy.deepcopy(result.error),\ result.status_code if error != '' or status_code >= 400: return Result(result=jsonraw, error=error, status_code=status_code) # check if any error occured self.check_for_error(jsonraw, error, status_code) services = copy.deepcopy(json.loads(jsonraw)) for service in services: # make dict of tuples for better reading s = dict(service.items()) if self.use_display_name_host == False: # according to http://sourceforge.net/p/nagstamon/bugs/83/ it might # better be host_name instead of host_display_name # legacy Icinga adjustments if 'host_name' in s: host_name = s['host_name'] elif 'host' in s: host_name = s['host'] else: # https://github.com/HenriWahl/Nagstamon/issues/46 on the other hand has # problems with that so here we go with extra display_name option host_name = s['host_display_name'] # host objects contain service objects if not host_name in self.new_hosts: self.new_hosts[host_name] = GenericHost() self.new_hosts[host_name].name = host_name self.new_hosts[host_name].status = 'UP' # extra Icinga properties to solve https://github.com/HenriWahl/Nagstamon/issues/192 # acknowledge needs host_description and no display name self.new_hosts[host_name].real_name = s['host_name'] #if self.use_display_name_host == False: # # legacy Icinga adjustments # if 'service_description' in s: service_name = s['service_description'] # elif 'description' in s: service_name = s['description'] # elif 'service' in s: service_name = s['service'] #else: # service_name = s['service_display_name'] # regarding to https://github.com/HenriWahl/Nagstamon/issues/400 Icinga2 needs no legacy adjustments service_name = s['service_display_name'] # if a service does not exist create its object if not service_name in self.new_hosts[host_name].services: self.new_hosts[host_name].services[service_name] = GenericService() self.new_hosts[host_name].services[service_name].host = host_name self.new_hosts[host_name].services[service_name].name = service_name self.new_hosts[host_name].services[service_name].server = self.name self.new_hosts[host_name].services[service_name].status = self.STATES_MAPPING['services'][int(s['service_state'])] self.new_hosts[host_name].services[service_name].last_check = datetime.datetime.fromtimestamp(int(s['service_last_check'])) self.new_hosts[host_name].services[service_name].attempt = s['service_attempt'] self.new_hosts[host_name].services[service_name].status_information = BeautifulSoup(s['service_output'].replace('\n', ' ').strip(), 'html.parser').text self.new_hosts[host_name].services[service_name].passiveonly = not(int(s['service_active_checks_enabled'])) self.new_hosts[host_name].services[service_name].notifications_disabled = not(int(s['service_notifications_enabled'])) self.new_hosts[host_name].services[service_name].flapping = bool(int(s['service_is_flapping'])) self.new_hosts[host_name].services[service_name].acknowledged = bool(int(s['service_acknowledged'])) self.new_hosts[host_name].services[service_name].scheduled_downtime = bool(int(s['service_in_downtime'])) self.new_hosts[host_name].services[service_name].status_type = status_type # extra Icinga properties to solve https://github.com/HenriWahl/Nagstamon/issues/192 # acknowledge needs service_description and no display name self.new_hosts[host_name].services[service_name].real_name = s['service_description'] # extra duration needed for calculation duration = datetime.datetime.now() - datetime.datetime.fromtimestamp(int(s['service_last_state_change'])) self.new_hosts[host_name].services[service_name].duration = strfdelta(duration, '{days}d {hours}h {minutes}m {seconds}s') del s, host_name, service_name except: import traceback traceback.print_exc(file=sys.stdout) # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) # some cleanup del jsonraw, error, hosts, services # dummy return in case all is OK return Result() def _set_recheck(self, host, service): # First retrieve the info page for this host/service if service == '': url = self.monitor_cgi_url + '/monitoring/host/show?host=' + self.hosts[host].real_name else: url = self.monitor_cgi_url + '/monitoring/service/show?host=' + self.hosts[host].real_name + '&service=' + self.hosts[host].services[service].real_name result = self.FetchURL(url, giveback='raw') if result.error != '': return result else: pageraw = result.result pagesoup = BeautifulSoup(pageraw, 'html.parser') # Extract the relevant form element values formtag = pagesoup.find('form', {'name':'IcingaModuleMonitoringFormsCommandObjectCheckNowCommandForm'}) CSRFToken = formtag.findNext('input', {'name':'CSRFToken'})['value'] formUID = formtag.findNext('input', {'name':'formUID'})['value'] btn_submit = formtag.findNext('button', {'name':'btn_submit'})['value'] # Pass these values to the same URL as cgi_data cgi_data = {} cgi_data['CSRFToken'] = CSRFToken cgi_data['formUID'] = formUID cgi_data['btn_submit'] = btn_submit result = self.FetchURL(url, giveback='raw', cgi_data=cgi_data) def _set_acknowledge(self, host, service, author, comment, sticky, notify, persistent, all_services=[]): # First retrieve the info page for this host/service if service == '': # url = self.monitor_cgi_url + '/monitoring/host/acknowledge-problem?host=' + host url = '{0}/monitoring/host/acknowledge-problem?host={1}'.format(self.monitor_cgi_url, self.hosts[host].real_name) else: # url = self.monitor_cgi_url + '/monitoring/service/acknowledge-problem?host=' + host + '&service=' + service url = '{0}/monitoring/service/acknowledge-problem?host={1}&service={2}'.format(self.monitor_cgi_url, self.hosts[host].real_name, self.hosts[host].services[service].real_name) result = self.FetchURL(url, giveback='raw') if result.error != '': return result else: pageraw = result.result pagesoup = BeautifulSoup(pageraw, 'html.parser') # Extract the relevant form element values formtag = pagesoup.find('form', {'name':'IcingaModuleMonitoringFormsCommandObjectAcknowledgeProblemCommandForm'}) CSRFToken = formtag.findNext('input', {'name':'CSRFToken'})['value'] formUID = formtag.findNext('input', {'name':'formUID'})['value'] btn_submit = formtag.findNext('input', {'name':'btn_submit'})['value'] # Pass these values to the same URL as cgi_data cgi_data = {} cgi_data['CSRFToken'] = CSRFToken cgi_data['formUID'] = formUID cgi_data['btn_submit'] = btn_submit # cgi_data['comment'] = comment cgi_data['persistent'] = int(persistent) cgi_data['sticky'] = int(sticky) cgi_data['notify'] = int(notify) cgi_data['comment'] = comment self.FetchURL(url, giveback='raw', cgi_data=cgi_data) if len(all_services) > 0: for s in all_services: # cheap, recursive solution... self._set_acknowledge(host, s, author, comment, sticky, notify, persistent, []) def _set_submit_check_result(self, host, service, state, comment, check_output, performance_data): # First retrieve the info page for this host/service if service == '': url = self.monitor_cgi_url + '/monitoring/host/process-check-result?host=' + self.hosts[host].real_name status = self.STATES_MAPPING_REV['hosts'][state.upper()] else: url = self.monitor_cgi_url + '/monitoring/service/process-check-result?host=' + self.hosts[host].real_name + '&service=' + self.hosts[host].services[service].real_name status = self.STATES_MAPPING_REV['services'][state.upper()] result = self.FetchURL(url, giveback='raw') if result.error != '': return result else: pageraw = result.result pagesoup = BeautifulSoup(pageraw, 'html.parser') # Extract the relevant form element values formtag = pagesoup.find('form', {'name':'IcingaModuleMonitoringFormsCommandObjectProcessCheckResultCommandForm'}) CSRFToken = formtag.findNext('input', {'name':'CSRFToken'})['value'] formUID = formtag.findNext('input', {'name':'formUID'})['value'] btn_submit = formtag.findNext('input', {'name':'btn_submit'})['value'] # Pass these values to the same URL as cgi_data cgi_data = {} cgi_data['CSRFToken'] = CSRFToken cgi_data['formUID'] = formUID cgi_data['btn_submit'] = btn_submit cgi_data['status'] = status cgi_data['output'] = check_output cgi_data['perfdata'] = performance_data self.FetchURL(url, giveback='raw', cgi_data=cgi_data) def _set_downtime(self, host, service, author, comment, fixed, start_time, end_time, hours, minutes): # First retrieve the info page for this host/service if service == '': url = self.monitor_cgi_url + '/monitoring/host/schedule-downtime?host=' + self.hosts[host].real_name else: url = self.monitor_cgi_url + '/monitoring/service/schedule-downtime?host=' + self.hosts[host].real_name + '&service=' + self.hosts[host].services[service].real_name result = self.FetchURL(url, giveback='raw') if result.error != '': return result else: pageraw = result.result pagesoup = BeautifulSoup(pageraw, 'html.parser') # Extract the relevant form element values if service == '': formtag = pagesoup.find('form', {'name':'IcingaModuleMonitoringFormsCommandObjectScheduleHostDowntimeCommandForm'}) else: formtag = pagesoup.find('form', {'name':'IcingaModuleMonitoringFormsCommandObjectScheduleServiceDowntimeCommandForm'}) CSRFToken = formtag.findNext('input', {'name':'CSRFToken'})['value'] formUID = formtag.findNext('input', {'name':'formUID'})['value'] btn_submit = formtag.findNext('input', {'name':'btn_submit'})['value'] # Pass these values to the same URL as cgi_data cgi_data = {} cgi_data['CSRFToken'] = CSRFToken cgi_data['formUID'] = formUID cgi_data['btn_submit'] = btn_submit cgi_data['comment'] = comment if fixed: cgi_data['type'] = 'fixed' else: cgi_data['type'] = 'flexible' cgi_data['hours'] = hours cgi_data['minutes'] = minutes if start_time == '' or start_time == 'n/a': start = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S') else: start = start_time if end_time == '' or end_time == 'n/a': end = (datetime.datetime.now() + datetime.timedelta(hours=hours, minutes=minutes)).strftime('%Y-%m-%dT%H:%M:%S') else: end = end_time cgi_data['start'] = start cgi_data['end'] = end self.FetchURL(url, giveback='raw', cgi_data=cgi_data) def get_start_end(self, host): ''' for GUI to get actual downtime start and end from server - they may vary so it's better to get directly from web interface ''' try: downtime = self.FetchURL(self.monitor_cgi_url + '/monitoring/host/schedule-downtime?host=' + self.hosts[host].real_name) start = downtime.result.find('input', {'name': 'start'})['value'] end = downtime.result.find('input', {'name': 'end'})['value'] # give values back as tuple return start, end except: self.Error(sys.exc_info()) return 'n/a', 'n/a' def open_monitor(self, host, service=''): ''' open monitor from tablewidget context menu ''' # only type is important so do not care of service '' in case of host monitor if service == '': url = '{0}/monitoring/list/hosts?host_problem=1&sort=host_severity#!{1}/monitoring/host/show?{2}'.format(self.monitor_url, (urllib.parse.urlparse(self.monitor_url).path), urllib.parse.urlencode( {'host': self.hosts[host].real_name}).replace('+', ' ')) else: url = '{0}/monitoring/list/services?service_problem=1&sort=service_severity&dir=desc#!{1}/monitoring/service/show?{2}'.format(self.monitor_url, (urllib.parse.urlparse(self.monitor_url).path), urllib.parse.urlencode( {'host': self.hosts[host].real_name, 'service': self.hosts[host].services[service].real_name}).replace('+', ' ')) if conf.debug_mode: self.Debug(server=self.get_name(), host=host, service=service, debug='Open host/service monitor web page {0}'.format(url)) webbrowser_open(url) Nagstamon/Nagstamon/Servers/Livestatus.py000066400000000000000000000224161316117564000211130ustar00rootroot00000000000000# encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2016 Henri Wahl et al. # # 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 from Nagstamon.Objects import Result from Nagstamon.Objects import GenericHost from Nagstamon.Objects import GenericService from Nagstamon.Servers.Generic import GenericServer from Nagstamon.Config import conf import logging log = logging.getLogger('Livestatus') import re import json import socket import time def format_timestamp(timestamp): """format unix timestamp""" ts_tuple = time.localtime(timestamp) return time.strftime('%Y-%m-%d %H:%M:%S', ts_tuple) def duration(timestamp): """human representation of a duration""" factors = (60 * 60 * 24, 60 * 60, 60, 1) result = [] diff = time.time() - timestamp for f in factors: x = int(diff / f) result.append(x) diff = diff - x * f return '%02dd %02dh %02dm %02ds' % tuple(result) def service_to_host(data): """create the host data blob from the implicit join data of a service""" result = {} for key in data.keys(): if key.startswith('host_'): result[key[5:]] = data[key] return result class LivestatusServer(GenericServer): """A server running MK Livestatus plugin. Tested with icinga2""" TYPE = 'Livestatus' def init_config(self): log.info(self.monitor_url) # we abuse the monitor_url for the connection information self.address = ('localhost', 6558) m = re.match('.*?://([^:/]+?)(?::(\d+))?(?:/|$)', self.monitor_url) if m: host, port = m.groups() if not port: port = 6558 else: port = int(port) self.address = (host, port) else: log.error('unable to parse monitor_url %s', self.monitor_url) self.enable = False def init_HTTP(self): pass def communicate(self, data, response=True): buffersize = 2**20 data.append('') data.append('') s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) log.debug('connecting') s.connect(self.address) s.send('\n'.join(data).encode('utf8')) if not response: log.debug('no response required, disconnect') s.close() return '' result = bytes() line = s.recv(buffersize) while len(line) > 0: result += line line = s.recv(buffersize) log.debug('disconnect') s.close() log.debug('received %d bytes', len(result)) result = result.decode('utf8') return result def get(self, table, raw=[], headers={}): """send data to livestatus socket, receive result, format as json""" data = ['GET %s' % table, ] headers['OutputFormat'] = 'json' headers['ColumnHeaders'] = 'on' for k, v in headers.items(): data.append('%s: %s' % (k, v)) for line in raw: data.append(line) result = self.communicate(data) if result: return json.loads(result) return result def command(self, *cmd): """execute nagios command via livestatus socket. For commands see https://old.nagios.org/developerinfo/externalcommands/commandlist.php """ data = [] ts = str(int(time.time()) + 5) # current epoch timestamp + 5 seconds for line in cmd: line = 'COMMAND [TIMESTAMP] ' + line data.append(line.replace('TIMESTAMP', ts)) self.communicate(data, response=False) def table(self, data): """take a livestatus answer and format it as a table, list of dictionaries [ {host: 'foo1', service: 'bar1'}, {host: 'foo2', service: 'bar2'} ] """ try: header = data[0] except IndexError: raise StopIteration for line in data[1:]: yield(dict(zip(header, line))) def _get_status(self): """fetch any host/service not in OK state store the information in self.new_hosts applies basic filtering. All additional filtering and merging new_hosts to hosts is left to nagstamon """ log.debug('_get_status') self.new_hosts = dict() filters = [] filters.append('Filter: state != 0') # ignore OK state if conf.filter_acknowledged_hosts_services: filters.append('Filter: acknowledged != 1') # hosts data = self.get("hosts", raw=filters) for h in self.table(data): host = self._create_host(h) self.new_hosts[host.name] = host log.info("host %s is %s", host.name, host.status) # services data = self.get("services", raw=filters) for s in self.table(data): # service are attached to host objects if s['host_name'] in self.new_hosts: host = self.new_hosts[s['host_name']] else: # need to create the host # icinga2 adds all host information to the server # prefixed with HOST_ xdata = service_to_host(s) # any field starting with HOST_ host = self._create_host(xdata) self.new_hosts[host.name] = host service = self._create_service(s) service.host = host.name host.services[service.name] = service return Result() def _update_object(self, obj, data): """populate the generic fields of obj (GenericHost or GenericService) from data.""" result = obj result.server = self.name result.last_check = format_timestamp(data['last_check']) result.duration = duration(data['last_state_change']) result.attempt = data['current_attempt'] result.status_information = data['plugin_output'] result.passiveonly = False result.notifications_disabled = data['notifications_enabled'] != 1 result.flapping = data['is_flapping'] == 1 result.acknowledged = data['acknowledged'] == 1 result.scheduled_downtime = data['scheduled_downtime_depth'] == 1 if data['state'] == data['last_hard_state']: result.status_type = 'hard' else: result.status_type = 'soft' return result def _create_host(self, data): """create GenericHost from json data""" result = self._update_object(GenericHost(), data) result.name = data['name'] host_states = {0: 'UP', 1: 'DOWN', 2: 'UNKNOWN'} result.status = host_states[data['state']] return result def _create_service(self, data): """create GenericService from json data""" result = self._update_object(GenericService(), data) result.name = data['display_name'] service_states = {0: 'OK', 1: 'WARNING', 2: 'CRITICAL', 3: 'UNKNOWN'} result.status = service_states[data['state']] return result def set_recheck(self, info_dict): """schedule a forced recheck of a service or host""" service = info_dict['service'] host = info_dict['host'] if service: if self.hosts[host].services[service].is_passive_only(): return cmd = ['SCHEDULE_FORCED_SVC_CHECK', host, service, 'TIMESTAMP'] else: cmd = ['SCHEDULE_FORCED_HOST_CHECK', host, 'TIMESTAMP'] self.command(';'.join(cmd)) def set_acknowledge(self, info_dict): """acknowledge a service or host""" host = info_dict['host'] service = info_dict['service'] if service: cmd = ['ACKNOWLEDGE_SVC_PROBLEM', host, service] else: cmd = ['ACKNOWLEDGE_HOST_PROBLEM', host] cmd.extend([ '2' if info_dict['sticky'] else '1', '1' if info_dict['notify'] else '0', '1' if info_dict['persistent'] else '0', info_dict['author'], info_dict['comment'], ]) self.command(';'.join(cmd)) def set_downtime(self, info_dict): log.info('set_downtime not implemented') def set_submit_check_result(self, info_dict): log.info('set_submit_check_result not implemented') def get_start_end(self, host): log.info('get_start_end not implemented') return 'n/a', 'n/a' def open_monitor(self, host, service=''): log.info('open_monitor not implemented') # TODO figure out how to add more config options like socket and weburl def open_monitor_webpage(self): log.info('open_monitor_webpage not implemented') # TODO # config dialog fields # config Nagstamon/Nagstamon/Servers/Monitos3.py000066400000000000000000000365511316117564000204700ustar00rootroot00000000000000# encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2016 Henri Wahl et al. # # 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 # Nagstamon plugin for Monitos3 from Freicon GmbH & Co. KG # Based on the Livestatus plugin from MK # 2017_06_27 # studo from Nagstamon.Objects import Result from Nagstamon.Objects import GenericHost from Nagstamon.Objects import GenericService from Nagstamon.Servers.Generic import GenericServer from Nagstamon.Config import conf import logging logging.basicConfig( level=logging.INFO ) # logging.basicConfig(filename='nagstamon.log',level=logging.INFO) log = logging.getLogger('Monitos3') import re import json import socket import time def format_timestamp(timestamp): """format unix timestamp""" ts_tuple = time.localtime(timestamp) return time.strftime('%Y-%m-%d %H:%M:%S', ts_tuple) def duration(timestamp): """human representation of a duration""" factors = (60 * 60 * 24, 60 * 60, 60, 1) result = [] diff = time.time() - timestamp for f in factors: x = int(diff / f) result.append(x) diff = diff - x * f return '%02dd %02dh %02dm %02ds' % tuple(result) def service_to_host(data): """create the host data blob from the implicit join data of a service""" result = {} for key in data.keys(): if key.startswith('host_'): result[key[5:]] = data[key] return result class Monitos3Server(GenericServer): """A server running Monitos3 with the MK Livestatus NEB. Tested with Monitos3.7.17""" TYPE = 'Monitos3' MENU_ACTIONS = ['Monitor', 'Recheck', 'Acknowledge', 'Submit check result', 'Downtime'] # STATES_MAPPING = {'hosts' : {0 : 'UP', 1 : 'DOWN', 2 : 'UNREACHABLE'}, \ # 'services' : {0 : 'OK', 1 : 'WARNING', 2 : 'CRITICAL', 3 : 'UNKNOWN'}} # STATES_MAPPING_REV = {'hosts' : { 'UP': 0, 'DOWN': 1, 'UNREACHABLE': 2}, \ # 'services' : {'OK': 0, 'WARNING': 1, 'CRITICAL': 2, 'UNKNOWN': 3}} BROWSER_URLS = { 'monitor': '$MONITOR$/dashboard', \ 'hosts': '$MONITOR$/monitoring/list/hosts', \ 'services': '$MONITOR$/monitoring/list/services', \ 'history': '$MONITOR$/monitoring/list/eventhistory?timestamp>=-7 days'} def init_config(self): log.info( time.strftime('%a %H:%M:%S') ) log.info(self.monitor_url) # we abuse the monitor_url for the connection information self.address = ('localhost', 6558) m = re.match('.*?://([^:/]+?)(?::(\d+))?(?:/|$)', self.monitor_url) if m: host, port = m.groups() if not port: port = 6558 else: port = int(port) self.address = (host, port) else: log.error('unable to parse monitor_url %s', self.monitor_url) self.enable = False def init_HTTP(self): pass def communicate(self, data, response=True): buffersize = 2**20 data.append('') data.append('') s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) log.debug('connecting') s.connect(self.address) s.send('\n'.join(data).encode('utf8')) if not response: log.debug('no response required, disconnect') s.close() return '' result = bytes() line = s.recv(buffersize) while len(line) > 0: result += line line = s.recv(buffersize) log.debug('disconnect') s.close() log.debug('received %d bytes', len(result)) result = result.decode('utf8') return result def get(self, table, raw=[], headers={}): """send data to livestatus socket, receive result, format as json""" data = ['GET %s' % table, ] headers['OutputFormat'] = 'json' headers['ColumnHeaders'] = 'on' for k, v in headers.items(): data.append('%s: %s' % (k, v)) for line in raw: data.append(line) result = self.communicate(data) if result: return json.loads(result) return result def command(self, *cmd): """execute nagios command via livestatus socket. For commands see https://old.nagios.org/developerinfo/externalcommands/commandlist.php """ data = [] ts = str(int(time.time()) + 5) # current epoch timestamp + 5 seconds for line in cmd: line = 'COMMAND [TIMESTAMP] ' + line data.append(line.replace('TIMESTAMP', ts)) self.communicate(data, response=False) def table(self, data): """take a livestatus answer and format it as a table, list of dictionaries [ {host: 'foo1', service: 'bar1'}, {host: 'foo2', service: 'bar2'} ] """ try: header = data[0] except IndexError: raise StopIteration for line in data[1:]: yield(dict(zip(header, line))) def _get_status(self): """fetch any host/service not in OK state store the information in self.new_hosts applies basic filtering. All additional filtering and merging new_hosts to hosts is left to nagstamon """ log.debug('in def _get_status') self.new_hosts = dict() filters = [] filters.append('Filter: state != 0') # ignore OK state if conf.filter_acknowledged_hosts_services: filters.append('Filter: acknowledged != 1') # hosts data = self.get("hosts", raw=filters) for h in self.table(data): host = self._create_host(h) self.new_hosts[host.name] = host # from icinga2 # self.new_hosts[host_name].real_name = h['host_name'] # 2017_06_02 log.info("monitos3_host %s with svid %s, alias %s, dn %s, addr %s is %s", host.name, host.svid, host.alias, host.display_name, host.address, host.status) # log.debug("host %s is %s", host.name, host.status) # services data = self.get("services", raw=filters) for s in self.table(data): # service are attached to host objects """ 2017_07_12_22_29_43 """ if s['custom_variables']['_HOST_NAME'] in self.new_hosts: # if s['host_name'] in self.new_hosts: host = self.new_hosts[s['custom_variables']['_HOST_NAME']] log.info("In new_hosts services host %s with svid %s, svc %s, svid %s is %s", host.name, host.svid, service.name, service.svid, service.status) else: # need to create the host # icinga2 adds all host information to the server # prefixed with HOST_ xdata = service_to_host(s) # any field starting with HOST_ host = self._create_host(xdata) self.new_hosts[host.name] = host log.info("In else services host %s with svid %s", host.name, host.svid ) service = self._create_service(s) service.host = host.name host.services[service.name] = service log.debug("All services: host %s with svid %s, svc %s, svid %s is %s", host.name, host.svid, service.name, service.svid, service.status) log.debug("monitos3_svcs host.services are: %s", service ) return Result() def _update_object(self, obj, data): """populate the generic fields of obj (GenericHost or GenericService) from data.""" result = obj result.server = self.name result.last_check = format_timestamp(data['last_check']) result.duration = duration(data['last_state_change']) result.attempt = data['current_attempt'] result.status_information = data['plugin_output'] result.passiveonly = False result.notifications_disabled = data['notifications_enabled'] != 1 result.flapping = data['is_flapping'] == 1 result.acknowledged = data['acknowledged'] == 1 result.scheduled_downtime = data['scheduled_downtime_depth'] == 1 if data['state'] == data['last_hard_state']: result.status_type = 'hard' else: result.status_type = 'soft' return result def _create_host(self, data): """create GenericHost from json data""" result = self._update_object(GenericHost(), data) result.name = data['custom_variables']['_HOST_NAME'] result.svid = data['name'] result.display_name = data['custom_variables']['_HOST_NAME'] result.alias = data['alias'] result.address = data['address'] result.custom_variables = data['custom_variables'] result.show_name = data['custom_variables']['_HOST_NAME'] # 2017_06_02 log.debug('data for obj %s is: %s', result.name, data) log.debug("monitos3 host name is %s", result.name) log.debug("monitos3 host custom_variables are %s", result.custom_variables) log.debug("monitos3 host address is %s", result.address) # TODO: fix other host states host_states = { 0: 'UP', 1: 'DOWN', 2: 'UNREACHABLE' } # host_states = { 0: 'UP', 1: 'DOWN', 2: 'UNREACHABLE', 3: 'OTHER' } host_state_list = [ 0, 1, 2 ] if data['state'] in host_state_list: result.status = host_states[data['state']] else: result.status = 'OTHER' return result def _create_service(self, data): """create GenericService from json data""" result = self._update_object(GenericService(), data) result.name = data['custom_variables']['_SERVICE_NAME'] result.svid = data['display_name'] result.custom_variables = data['custom_variables'] # result.name = data['display_name'] service_states = {0: 'OK', 1: 'WARNING', 2: 'CRITICAL', 3: 'UNKNOWN'} service_state_list = [ 0, 1, 2, 3 ] if data['state'] in service_state_list: result.status = service_states[data['state']] else: result.status = 'OTHER' return result def set_recheck(self, info_dict): host = info_dict['host'] log.info('host is: %s', host) host_svid = self.hosts[host].svid log.info('host_svid is: %s', host_svid) service = info_dict['service'] if service: svc_svid = self.hosts[host].services[service].svid log.info('svc_svid is: %s', svc_svid) log.info('service is: %s', service) if self.hosts[host].services[service].is_passive_only(): return cmd = ['SCHEDULE_FORCED_SVC_CHECK', host_svid, svc_svid, 'TIMESTAMP'] log.info('cmd is: %s', cmd ) else: cmd = ['SCHEDULE_FORCED_HOST_CHECK', host_svid, 'TIMESTAMP'] log.info('cmd is: %s', cmd ) self.command(';'.join(cmd)) # log.debug('recheck cmd is: %s', self.command ) def set_acknowledge(self, info_dict): """ acknowledge a service or host {'author': 'username', 'all_services': ['PING', 'DUMMY'], 'host': 'fritz', 'service': '', 'server': , 'acknowledge_all_services': True, 'sticky': True, 'notify': True, 'comment': 'acknowledged', 'persistent': True} """ log.info('called def set_acknowledge') log.info('info_dict is: %s', info_dict ) host = info_dict['host'] log.info('host is: %s', host) host_svid = self.hosts[host].svid log.info('host_svid is: %s', host_svid) service = info_dict['service'] if service: svc_svid = self.hosts[host].services[service].svid log.info('svc_svid is: %s', svc_svid) cmd = ['ACKNOWLEDGE_SVC_PROBLEM', host_svid, svc_svid] else: cmd = ['ACKNOWLEDGE_HOST_PROBLEM', host_svid] cmd.extend([ '2' if info_dict['sticky'] else '1', '1' if info_dict['notify'] else '0', '1' if info_dict['persistent'] else '0', info_dict['author'], info_dict['comment'], ]) log.info('ack cmd is: %s', ';'.join(cmd) ) self.command(';'.join(cmd)) def set_downtime(self, info_dict): log.info('set_downtime not implemented') def set_submit_check_result(self, info_dict): # INFO:Monitos3:info_dict is: { # 'host': 'BPMon', 'performance_data': 'ggggg', 'service': 'BP_test cw', 'state': 'critical', # 'comment': 'check result submitted', 'server': , # 'check_output': 'tesstestus'} log.debug( json.dumps( str( info_dict ), sort_keys=True, indent=4) ) host = info_dict['host'] log.debug('host is: %s', host) host_svid = self.hosts[host].svid log.debug('host_svid is: %s', host_svid) plugin_output = info_dict['check_output'] service = info_dict['service'] if service: # PROCESS_SERVICE_CHECK_RESULT;;;; log.debug('service is: %s', service) svc_svid = self.hosts[host].services[service].svid log.debug('svc_svid is: %s', svc_svid) rev_service_states = { 'ok': 0, 'warning': 1, 'critical': 2, 'unknown': 3} return_code = rev_service_states[ info_dict[ 'state' ] ] cmd = ['PROCESS_SERVICE_CHECK_RESULT', host_svid, svc_svid, str( return_code ), plugin_output] log.debug('cmd is: %s', cmd ) else: # PROCESS_HOST_CHECK_RESULT;;; log.debug('host is: %s', host) rev_host_states = { 'up': 0, 'down': 1, 'unreachable': 2, 'unknown': 3 } return_code = rev_host_states[ info_dict[ 'state' ] ] cmd = ['PROCESS_HOST_CHECK_RESULT', host_svid, str( return_code ), plugin_output] log.debug('cmd is: %s', cmd ) self.command(';'.join(cmd)) def get_start_end(self, host): log.info('get_start_end not implemented') return 'n/a', 'n/a' def open_monitor(self, host, service=''): log.info('open_monitor not implemented. host is %s', host) # TODO figure out how to add more config options like socket and weburl # line 77 def open_monitor_webpage(self): ''' open monitor from systray/toparea context menu ''' log.info('trying to implement def open_monitor_webpage') log.info('address is: %s', self.address) # self.address = ('http://localhost') monitos3_url = 'http://172.16.10.102' log.info('monitos3_url is: %s', monitos3_url ) webbrowser_open( monitos3_url ) # TODO # config dialog fields # config Nagstamon/Nagstamon/Servers/Multisite.py000066400000000000000000000567341316117564000207410ustar00rootroot00000000000000# -*- encoding: utf-8; py-indent-offset: 4 -*- # +------------------------------------------------------------------+ # | ____ _ _ __ __ _ __ | # | / ___| |__ ___ ___| | __ | \/ | |/ / | # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / | # | | |___| | | | __/ (__| < | | | | . \ | # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ | # | | # | Copyright Mathias Kettner 2010 mk@mathias-kettner.de | # | lm@mathias-kettner.de | # +------------------------------------------------------------------+ # # The official homepage is at http://mathias-kettner.de/check_mk. # # check_mk 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 in version 2. check_mk is distributed # in the hope that it will be useful, but WITHOUT ANY WARRANTY; with- # out even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. See the GNU General Public License for more de- # ails. You should have received a copy of the GNU General Public # License along with GNU Make; see the file COPYING. If not, write # to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, # Boston, MA 02110-1301 USA. # hax0rized by: lm@mathias-kettner.de import sys import urllib.request, urllib.parse, urllib.error import time import copy import html from Nagstamon.Objects import (GenericHost, GenericService, Result) from Nagstamon.Servers.Generic import GenericServer from Nagstamon.Helpers import webbrowser_open from Nagstamon.Config import conf class MultisiteError(Exception): def __init__(self, terminate, result): self.terminate = terminate self.result = result class MultisiteServer(GenericServer): """ special treatment for Check_MK Multisite JSON API """ TYPE = 'Check_MK Multisite' # URLs for browser shortlinks/buttons on popup window BROWSER_URLS= { 'monitor': '$MONITOR$',\ 'hosts': '$MONITOR$/index.py?start_url=view.py?view_name=hostproblems',\ 'services': '$MONITOR$/index.py?start_url=view.py?view_name=svcproblems',\ 'history': '$MONITOR$/index.py?start_url=view.py?view_name=events'} def __init__(self, **kwds): GenericServer.__init__(self, **kwds) # Prepare all urls needed by nagstamon - self.urls = {} self.statemap = {} # Entries for monitor default actions in context menu self.MENU_ACTIONS = ['Monitor', 'Recheck', 'Acknowledge', 'Downtime'] # flag for newer cookie authentication self.CookieAuth = False def init_HTTP(self): # general initialization GenericServer.init_HTTP(self) # Fix eventually missing tailing '/' in url if self.monitor_url[-1] != '/': self.monitor_url += '/' # Prepare all urls needed by nagstamon if not yet done if len(self.urls) == len(self.statemap): self.urls = { 'api_services': self.monitor_url + 'view.py?view_name={0}&output_format=python&lang=&limit=hard'.\ format(self.check_mk_view_services), 'human_services': self.monitor_url + 'index.py?%s' % \ urllib.parse.urlencode({'start_url': 'view.py?view_name={0}'.\ format(self.check_mk_view_services)}), 'human_service': self.monitor_url + 'index.py?%s' % urllib.parse.urlencode({'start_url': 'view.py?view_name=service'}), 'api_hosts': self.monitor_url + 'view.py?view_name={0}&output_format=python&lang=&limit=hard'.\ format(self.check_mk_view_hosts), 'human_hosts': self.monitor_url + 'index.py?%s' % urllib.parse.urlencode({'start_url': 'view.py?view_name={0}'.\ format(self.check_mk_view_services)}), 'human_host': self.monitor_url + 'index.py?%s' % urllib.parse.urlencode({'start_url': 'view.py?view_name=hoststatus'}), # URLs do not need pythonic output because since werk #0766 API does not work with transid=-1 anymore # thus access to normal webinterface is used 'api_host_act': self.monitor_url + 'view.py?_transid=-1&_do_actions=yes&_do_confirm=Yes!&view_name=hoststatus&filled_in=actions&lang=', 'api_service_act': self.monitor_url + 'view.py?_transid=-1&_do_actions=yes&_do_confirm=Yes!&view_name=service&filled_in=actions&lang=', 'api_svcprob_act': self.monitor_url + 'view.py?_transid=-1&_do_actions=yes&_do_confirm=Yes!&view_name=svcproblems&filled_in=actions&lang=', 'human_events': self.monitor_url + 'index.py?%s' % urllib.parse.urlencode({'start_url': 'view.py?view_name=events'}), 'transid': self.monitor_url + 'view.py?actions=yes&filled_in=actions&host=$HOST$&service=$SERVICE$&view_name=service' } self.statemap = { 'UNREACH': 'UNREACHABLE', 'CRIT': 'CRITICAL', 'WARN': 'WARNING', 'UNKN': 'UNKNOWN', 'PEND': 'PENDING', } if self.CookieAuth: # get cookie to access Check_MK web interface if 'cookies' in dir(self.session): if len(self.session.cookies) == 0: # if no cookie yet login self._get_cookie_login() elif self.session == None: # if no cookie yet login self._get_cookie_login() def init_config(self): """ dummy init_config, called at thread start, not really needed here, just omit extra properties """ pass def _is_auth_in_cookies(self): """ check if there is any valid auth session in cookies which has the name 'auth_' """ if not self.session == None: for cookie in self.session.cookies: if cookie.name.startswith('auth_'): return True return False def _get_url(self, url): result = self.FetchURL(url, 'raw') content, error, status_code = result.result, result.error, result.status_code if error != '' or status_code >= 400: raise MultisiteError(True, Result(result=content, error=error, status_code=status_code)) if content.startswith('WARNING:'): c = content.split('\n') # Print non ERRORS to the log in debug mode self.Debug(server=self.get_name(), debug=c[0]) raise MultisiteError(False, Result(result='\n'.join(c[1:]), error=c[0], status_code=status_code)) elif content.startswith('ERROR:'): raise MultisiteError(True, Result(result=content, error=content, status_code=status_code)) # in case of auth problem enable GUI auth part in popup if self.CookieAuth == True and not self.session == None: if not self._is_auth_in_cookies(): self.refresh_authentication = True return '' # looks like cookieauth elif content.startswith('<'): self.CookieAuth = True # if first attempt login and then try to get data again if not self._is_auth_in_cookies(): self._get_cookie_login() result = self.FetchURL(url, 'raw') content, error = result.result, result.error if content.startswith('<'): return '' return eval(content) def _get_cookie_login(self): """ login on cookie monitor site """ # put all necessary data into url string login_data = {'_username': self.get_username(), '_password': self.get_password(), '_login': '1', '_origtarget' : '', 'filled_in' :' login'} # get cookie from login page via url retrieving as with other urls try: # login and get cookie self.FetchURL(self.monitor_url + '/login.py', cgi_data=login_data, multipart=True) except: import traceback traceback.print_exc(file=sys.stdout) self.Error(sys.exc_info()) def _get_status(self): """ Get status from Check_MK Server """ ret = Result() # Create URLs for the configured filters url_params = '' if self.force_authuser: url_params += "&force_authuser=1" url_params += '&is_host_acknowledged=-1&is_service_acknowledged=-1' url_params += '&is_host_notifications_enabled=-1&is_service_notifications_enabled=-1' url_params += '&is_host_active_checks_enabled=-1&is_service_active_checks_enabled=-1' url_params += '&host_scheduled_downtime_depth=-1&is_in_downtime=-1' try: response = [] try: response = self._get_url(self.urls['api_hosts'] + url_params) except MultisiteError as e: if e.terminate: return e.result if response == '': return Result(result='', error='Login failed', status_code=401) for row in response[1:]: host= dict(list(zip(copy.deepcopy(response[0]), copy.deepcopy(row)))) n = { 'host': host['host'], 'status': self.statemap.get(host['host_state'], host['host_state']), 'last_check': host['host_check_age'], 'duration': host['host_state_age'], 'status_information': html.unescape(host['host_plugin_output'].replace('\n', ' ')), 'attempt': host['host_attempt'], 'site': host['sitename_plain'], 'address': host['host_address'] } # host objects contain service objects if n['host'] not in self.new_hosts: new_host = n['host'] self.new_hosts[new_host] = GenericHost() self.new_hosts[new_host].name = n['host'] self.new_hosts[new_host].server = self.name self.new_hosts[new_host].status = n['status'] self.new_hosts[new_host].last_check = n['last_check'] self.new_hosts[new_host].duration = n['duration'] self.new_hosts[new_host].attempt = n['attempt'] self.new_hosts[new_host].status_information= html.unescape(n['status_information'].replace('\n', ' ')) self.new_hosts[new_host].site = n['site'] self.new_hosts[new_host].address = n['address'] # transisition to Check_MK 1.1.10p2 if 'host_in_downtime' in host: if host['host_in_downtime'] == 'yes': self.new_hosts[new_host].scheduled_downtime = True if 'host_acknowledged' in host: if host['host_acknowledged'] == 'yes': self.new_hosts[new_host].acknowledged = True if 'host_notifications_enabled' in host: if host['host_notifications_enabled'] == 'no': self.new_hosts[new_host].notifications_disabled = True # hard/soft state for later filter evaluation real_attempt, max_attempt = self.new_hosts[new_host].attempt.split('/') if real_attempt != max_attempt: self.new_hosts[new_host].status_type = 'soft' else: self.new_hosts[new_host].status_type = 'hard' del response except: import traceback traceback.print_exc(file=sys.stdout) self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) # Add filters to the url which should only be applied to the service request if conf.filter_services_on_unreachable_hosts == True: url_params += '&hst2=0' # services try: response = [] try: response = self._get_url(self.urls['api_services'] + url_params) except MultisiteError as e: if e.terminate: return e.result else: response = copy.deepcopy(e.result.content) ret = copy.deepcopy(e.result) for row in response[1:]: service = dict(list(zip(copy.deepcopy(response[0]), copy.deepcopy(row)))) n = { 'host': service['host'], 'service': service['service_description'], 'status': self.statemap.get(service['service_state'], service['service_state']), 'last_check': service['svc_check_age'], 'duration': service['svc_state_age'], 'attempt': service['svc_attempt'], 'status_information': html.unescape(service['svc_plugin_output'].replace('\n', ' ')), # Check_MK passive services can be re-scheduled by using the Check_MK service 'passiveonly': service['svc_is_active'] == 'no' and not service['svc_check_command'].startswith('check_mk'), 'flapping': service['svc_flapping'] == 'yes', 'site': service['sitename_plain'], 'address': service['host_address'], 'command': service['svc_check_command'], } # host objects contain service objects if n['host'] not in self.new_hosts: self.new_hosts[n['host']] = GenericHost() self.new_hosts[n['host']].name = n['host'] self.new_hosts[n['host']].status = 'UP' self.new_hosts[n['host']].site = n['site'] self.new_hosts[n['host']].address = n['address'] # if a service does not exist create its object if n['service'] not in self.new_hosts[n['host']].services: new_service = n['service'] self.new_hosts[n['host']].services[new_service] = GenericService() self.new_hosts[n['host']].services[new_service].host = n['host'] self.new_hosts[n['host']].services[new_service].server = self.name self.new_hosts[n['host']].services[new_service].name = n['service'] self.new_hosts[n['host']].services[new_service].status = n['status'] self.new_hosts[n['host']].services[new_service].last_check = n['last_check'] self.new_hosts[n['host']].services[new_service].duration = n['duration'] self.new_hosts[n['host']].services[new_service].attempt = n['attempt'] self.new_hosts[n['host']].services[new_service].status_information = n['status_information'].strip() self.new_hosts[n['host']].services[new_service].passiveonly = n['passiveonly'] self.new_hosts[n['host']].services[new_service].flapping = n['flapping'] self.new_hosts[n['host']].services[new_service].site = n['site'] self.new_hosts[n['host']].services[new_service].address = n['address'] self.new_hosts[n['host']].services[new_service].command = n['command'] # transistion to Check_MK 1.1.10p2 if 'svc_in_downtime' in service: if service['svc_in_downtime'] == 'yes': self.new_hosts[n['host']].services[new_service].scheduled_downtime = True if 'svc_acknowledged' in service: if service['svc_acknowledged'] == 'yes': self.new_hosts[n['host']].services[new_service].acknowledged = True if 'svc_flapping' in service: if service['svc_flapping'] == 'yes': self.new_hosts[n['host']].services[new_service].flapping = True if 'svc_notifications_enabled' in service: if service['svc_notifications_enabled'] == 'no': self.new_hosts[n['host']].services[new_service].notifications_disabled = True # hard/soft state for later filter evaluation real_attempt, max_attempt = self.new_hosts[n['host']].services[new_service].attempt.split('/') if real_attempt != max_attempt: self.new_hosts[n['host']].services[new_service].status_type = 'soft' else: self.new_hosts[n['host']].services[new_service].status_type = 'hard' del response except: import traceback traceback.print_exc(file=sys.stdout) # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=copy.deepcopy(result), error=copy.deepcopy(error)) del url_params return ret def open_monitor(self, host, service=''): """ open monitor from treeview context menu """ if service == '': url = self.urls['human_host'] + urllib.parse.urlencode({'x': 'site='+self.hosts[host].site+'&host='+host}).replace('x=', '%26') else: url = self.urls['human_service'] + urllib.parse.urlencode({'x': 'site='+self.hosts[host].site+'&host='+host+'&service='+service}).replace('x=', '%26') if conf.debug_mode == True: self.Debug(server=self.get_name(), host=host, service=service, debug='Open host/service monitor web page ' + url) webbrowser_open(url) def GetHost(self, host): """ find out ip or hostname of given host to access hosts/devices which do not appear in DNS but have their ip saved in Nagios """ # the fastest method is taking hostname as used in monitor if conf.connect_by_host == True or host == '': return Result(result=host) ip = '' try: if host in self.hosts: ip = self.hosts[host].address if conf.debug_mode == True: self.Debug(server=self.get_name(), host=host, debug ='IP of %s:' % (host) + ' ' + ip) if conf.connect_by_dns == True: try: address = socket.gethostbyaddr(ip)[0] except: address = ip else: address = ip except: result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) return Result(result=address) def get_start_end(self, host): return time.strftime('%Y-%m-%d %H:%M'), time.strftime('%Y-%m-%d %H:%M', time.localtime(time.time() + 7200)) def _action(self, site, host, service, specific_params): params = { 'site': self.hosts[host].site, 'host': host, } params.update(specific_params) # decide about service or host url if service != '': url = self.urls['api_service_act'] else: url = self.urls['api_host_act'] # set service params['service'] = service # get current transid transid = self._get_transid(host, service) url = url.replace('?_transid=-1&', '?_transid=%s&' % (transid)) if conf.debug_mode == True: self.Debug(server=self.get_name(), host=host, debug ='Submitting action: ' + url + '&' + urllib.parse.urlencode(params)) # apply action self.FetchURL(url + '&' + urllib.parse.urlencode(params)) def _set_downtime(self, host, service, author, comment, fixed, start_time, end_time, hours, minutes): self._action(self.hosts[host].site, host, service, { '_down_comment': author == self.username and comment or '%s: %s' % (author, comment), '_down_flexible': fixed == 0 and 'on' or '', '_down_custom': 'Custom+time+range', '_down_from_date': start_time.split(' ')[0], '_down_from_time': start_time.split(' ')[1], '_down_to_date': end_time.split(' ')[0], '_down_to_time': end_time.split(' ')[1], '_down_duration': '%s:%s' % (hours, minutes), 'actions': 'yes' }) def _set_acknowledge(self, host, service, author, comment, sticky, notify, persistent, all_services=[]): p = { '_acknowledge': 'Acknowledge', '_ack_sticky': sticky == 1 and 'on' or '', '_ack_notify': notify == 1 and 'on' or '', '_ack_persistent': persistent == 1 and 'on' or '', '_ack_comment': author == self.username and comment or '%s: %s' % (author, comment) } self._action(self.hosts[host].site, host, service, p) # acknowledge all services on a host when told to do so for s in all_services: self._action(self.hosts[host].site, host, s, p) def _set_recheck(self, host, service): p = { '_resched_checks': 'Reschedule active checks', '_resched_pread': '0' } self._action(self.hosts[host].site, host, service, p) def recheck_all(self): """ special method for Check_MK as there is one URL for rescheduling all problems to be checked """ params = dict() params['_resched_checks'] = 'Reschedule active checks' url = self.urls['api_svcprob_act'] if conf.debug_mode == True: self.Debug(server=self.get_name(), debug ='Rechecking all action: ' + url + '&' + urllib.parse.urlencode(params)) result = self.FetchURL(url + '&' + urllib.parse.urlencode(params), giveback = 'raw') def _get_transid(self, host, service): """ get transid for an action """ transid = self.FetchURL(self.urls['transid'].replace('$HOST$', host).replace('$SERVICE$', service.replace(' ', '+')),\ 'obj').result.find(attrs={'name' : '_transid'})['value'] return transid Nagstamon/Nagstamon/Servers/Nagios.py000066400000000000000000000022221316117564000201610ustar00rootroot00000000000000# encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2014 Henri Wahl et al. # # 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 from Nagstamon.Servers.Generic import GenericServer class NagiosServer(GenericServer): """ object of Nagios server - when nagstamon will be able to poll various servers this will be useful As Nagios is the default server type all its methods are in GenericServer """ TYPE = u'Nagios'Nagstamon/Nagstamon/Servers/Opsview.py000066400000000000000000000264011316117564000204020ustar00rootroot00000000000000# encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2016 Henri Wahl et al. # # Based on https://github.com/duncs/Nagstamon by @duncs # # 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 import sys import urllib.request, urllib.parse, urllib.error import copy import pprint import json from datetime import datetime, timedelta from ast import literal_eval from Nagstamon.Config import conf from Nagstamon.Objects import (GenericHost, GenericService, Result) from Nagstamon.Servers.Generic import GenericServer from Nagstamon.Helpers import (HumanReadableDurationFromSeconds, webbrowser_open) class OpsviewService(GenericService): """ add Opsview specific service property to generic service class """ service_object_id = "" class OpsviewServer(GenericServer): """ special treatment for Opsview XML based API """ TYPE = 'Opsview' # Arguments available for submitting check results SUBMIT_CHECK_RESULT_ARGS = ["comment"] # URLs for browser shortlinks/buttons on popup window BROWSER_URLS= {'monitor': '$MONITOR$/monitoring', 'hosts': '$MONITOR$/monitoring/#!/allproblems', 'services': '$MONITOR$/monitoring/#!/allproblems', 'history': '$MONITOR$/monitoring/#!/events'} def init_HTTP(self): """ things to do if HTTP is not initialized """ GenericServer.init_HTTP(self) # prepare for JSON self.session.headers.update({'Accept': 'application/json', 'Content-Type': 'application/json'}) # get cookie to access Opsview web interface to access Opsviews Nagios part if len(self.session.cookies) == 0: if conf.debug_mode: self.Debug(server=self.get_name(), debug="Fetching Login token") logindata = json.dumps({'username': self.get_username(), 'password': self.get_password()}) # the following is necessary for Opsview servers # get cookie from login page via url retrieving as with other urls try: # login and get cookie resp = literal_eval(self.FetchURL(self.monitor_url + "/rest/login", giveback='raw', cgi_data=logindata).result) if conf.debug_mode: self.Debug(server=self.get_name(), debug="Login Token: " + resp.get('token') ) self.session.headers.update({'X-Opsview-Username': self.get_username(), 'X-Opsview-Token':resp.get('token')}) except: self.Error(sys.exc_info()) def init_config(self): """ dummy init_config, called at thread start, not really needed here, just omit extra properties """ pass def get_start_end(self, host): """ Set a default of starttime of "now" and endtime is "now + 24 hours" directly from web interface """ start = datetime.now() end = datetime.now() + timedelta(hours=24) return str(start.strftime("%Y-%m-%d %H:%M:%S")), str(end.strftime("%Y-%m-%d %H:%M:%S")) def _set_downtime(self, host, service, author, comment, fixed, start_time, end_time, hours, minutes): url = self.monitor_url + "/rest/downtime?" data = dict(); data["comment"]=str(comment) data["starttime"]=start_time data["endtime"]=end_time if service == "": data["hst.hostname"]=str(host) if service != "": data["svc.hostname"]=str(host) data["svc.servicename"]=str(service) cgi_data = urllib.parse.urlencode(data) self.Debug(server=self.get_name(), debug="Downtime url: " + url) self.FetchURL(url + cgi_data, giveback="raw", cgi_data=({ })) def _set_submit_check_result(self, host, service, state, comment, check_output, performance_data): """ worker for submitting check result """ url = self.monitor_url + "/rest/status?" data = dict(); data["comment"]=str(comment) data["new_state"]=({"ok":0,"warning":1,"critical":2,"unknown":3})[state] if service == "": data["hst.hostname"]=str(host) if service != "": data["svc.hostname"]=str(host) data["svc.servicename"]=str(service) cgi_data = urllib.parse.urlencode(data) self.Debug(server=self.get_name(), debug="Submit result url: " + url) self.FetchURL(url + cgi_data, giveback="raw", cgi_data=({ })) def _set_acknowledge(self, host, service, author, comment, sticky, notify, persistent, all_services=[]): """ Sumit acknowledgement for host or service """ url = self.monitor_url + "/rest/acknowledge?" data=dict(); data["notify"]=str(notify) data["sticky"]=str(sticky) data["comment"]=str(comment) data["host"]=str(host) if service != "": data["servicecheck"]=str(service) cgi_data = urllib.parse.urlencode(data) self.Debug(server=self.get_name(), debug="ACK url: " + url) self.FetchURL(url + cgi_data, giveback="raw", cgi_data=({ })) def _set_recheck(self, host, service): """ Sumit recheck request for host or service """ url = self.monitor_url + "/rest/recheck?" data=dict(); data["host"]=str(host) if service != "": data["servicecheck"]=str(service) cgi_data = urllib.parse.urlencode(data) self.Debug(server=self.get_name(), debug="Recheck url: " + url) self.FetchURL(url + cgi_data, giveback="raw", cgi_data=({ })) def _get_status(self): """ Get status from Opsview Server """ # following XXXX to get ALL services in ALL states except OK # because we filter them out later # the REST API gets all host and service info in one call try: result = self.FetchURL(self.monitor_url + "/rest/status/service?state=1&state=2&state=3", giveback="raw") data, error, status_code = json.loads(result.result), result.error, result.status_code # check if any error occured errors_occured = self.check_for_error(data, error, status_code) # if there are errors return them if errors_occured != False: return(errors_occured) if conf.debug_mode: self.Debug(server=self.get_name(), debug="Fetched JSON: " + pprint.pformat(data)) for host in data["list"]: self.new_hosts[host["name"]] = GenericHost() self.new_hosts[host["name"]].name = str(host["name"]) self.new_hosts[host["name"]].server = self.name # states come in lower case from Opsview self.new_hosts[host["name"]].status = str(host["state"].upper()) self.new_hosts[host["name"]].status_type = str(host["state_type"]) self.new_hosts[host["name"]].last_check = datetime.fromtimestamp(int(host["last_check"])).strftime("%Y-%m-%d %H:%M:%S %z") self.new_hosts[host["name"]].duration = HumanReadableDurationFromSeconds(host["state_duration"]) self.new_hosts[host["name"]].attempt = host["current_check_attempt"]+ "/" + host["max_check_attempts"] self.new_hosts[host["name"]].status_information = host["output"].replace("\n", " ") # if host is in downtime add it to known maintained hosts if host['downtime'] != "0": self.new_hosts[host["name"]].scheduled_downtime = True #if host.has_key("acknowledged"): if 'acknowledged' in host: self.new_hosts[host["name"]].acknowledged = True #if host.has_key("flapping"): if 'flapping' in host: self.new_hosts[host["name"]].flapping = True #services for service in host["services"]: self.new_hosts[host["name"]].services[service["name"]] = OpsviewService() self.new_hosts[host["name"]].services[service["name"]].host = str(host["name"]) self.new_hosts[host["name"]].services[service["name"]].name = service["name"] self.new_hosts[host["name"]].services[service["name"]].server = self.name # states come in lower case from Opsview self.new_hosts[host["name"]].services[service["name"]].status = service["state"].upper() self.new_hosts[host["name"]].services[service["name"]].status_type = service["state_type"] self.new_hosts[host["name"]].services[service["name"]].last_check = datetime.fromtimestamp(int(service["last_check"])).strftime("%Y-%m-%d %H:%M:%S %z") self.new_hosts[host["name"]].services[service["name"]].duration = HumanReadableDurationFromSeconds(service["state_duration"]) self.new_hosts[host["name"]].services[service["name"]].attempt = service["current_check_attempt"]+ "/" + service["max_check_attempts"] self.new_hosts[host["name"]].services[service["name"]].status_information= service["output"].replace("\n", " ") if service['downtime'] != '0': self.new_hosts[host["name"]].services[service["name"]].scheduled_downtime = True #if service.has_key("acknowledged"): if 'acknowledged' in service: self.new_hosts[host["name"]].services[service["name"]].acknowledged = True #f service.has_key("flapping"): if 'flapping' in service: self.new_hosts[host["name"]].services[service["name"]].flapping = True # extra opsview id for service, needed for submitting check results self.new_hosts[host["name"]].services[service["name"]].service_object_id = service["service_object_id"] except: # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) #dummy return in case all is OK return Result() def open_monitor_webpage(self, host, service): webbrowser_open('%s/monitoring/#!?autoSelectHost=%s' % (self.monitor_url, host)) Nagstamon/Nagstamon/Servers/Sensu.py000066400000000000000000000211201316117564000200340ustar00rootroot00000000000000# adapted from the Zenoss.py code # Copyright (C) 2017 Vyron Tsingaras interworks.cloud # # 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 from datetime import timezone, datetime import sys import traceback from requests.structures import CaseInsensitiveDict from Nagstamon.Config import conf from Nagstamon.Objects import (GenericHost, GenericService, Result) from Nagstamon.Servers.Generic import GenericServer from Nagstamon.thirdparty.sensu_api import SensuAPI, SensuAPIException from Nagstamon.Helpers import HumanReadableDurationFromTimestamp, webbrowser_open class SensuServer(GenericServer): TYPE = 'Sensu' SEVERITY_CODE_TEXT_MAP = dict() SEVERITY_STATUS_TEXT_MAP = CaseInsensitiveDict() MENU_ACTIONS = ['Monitor', 'Recheck', 'Acknowledge', 'Submit check result', 'Downtime'] sensu_api = None authentication = 'basic' api_url = '' uchiwa_url = '' uchiwa_datacenter = '' username = '' password = '' def __init__(self, **kwds): GenericServer.__init__(self, **kwds) # Prepare all urls needed by nagstamon self.urls = {} self.statemap = {} self.api_url = conf.servers[self.get_name()].monitor_cgi_url self.uchiwa_url = conf.servers[self.get_name()].monitor_url self.uchiwa_datacenter = conf.servers[self.get_name()].monitor_site self.username = conf.servers[self.get_name()].username self.password = conf.servers[self.get_name()].password self.BROWSER_URLS = { 'monitor': '$MONITOR$', 'hosts': '$MONITOR$/#/clients', 'services': '$MONITOR$/#/checks', 'history': '$MONITOR$/#/clients' } self.SEVERITY_CODE_TEXT_MAP = { 0: 'OK', 1: 'WARNING', 2: 'CRITICAL', 3: 'UNKNOWN' } # SEVERITY_STATUS_TEXT_MAP is a Case-Insensitive dict self.SEVERITY_STATUS_TEXT_MAP['OK'] = 0 self.SEVERITY_STATUS_TEXT_MAP['WARNING'] = 1 self.SEVERITY_STATUS_TEXT_MAP['CRITICAL'] = 2 self.SEVERITY_STATUS_TEXT_MAP['UNKNOWN'] = 3 def init_HTTP(self): """ things to do if HTTP is not initialized """ GenericServer.init_HTTP(self) try: self.sensu_api = SensuAPI( self.api_url, username=self.username, password=self.password ) except SensuAPIException: self.Error(sys.exc_info()) def _insert_service_to_hosts(self, service: GenericService): service_host = service.get_host_name() if service_host not in self.new_hosts: self.new_hosts[service_host] = GenericHost() self.new_hosts[service_host].name = service_host self.new_hosts[service_host].services[service.name] = service @staticmethod def _aslocaltimestr(utc_dt): local_dt = utc_dt.replace(tzinfo=timezone.utc).astimezone(tz=None) return local_dt.strftime('%Y-%m-%d %H:%M:%S') def _get_status(self): self.new_hosts = dict() try: events = self._get_all_events() for event in events: event_check = event['check'] event_client = event['client'] new_service = GenericService() new_service.event_id = event['id'] new_service.host = event_client['name'] new_service.name = event_check['name'] new_service.status = None try: new_service.status = self.SEVERITY_CODE_TEXT_MAP.get(event_check['status']) except KeyError: new_service.status = 'UNKNOWN' last_check_time = datetime.utcfromtimestamp(int(event['timestamp'])) new_service.last_check = self._aslocaltimestr(last_check_time) new_service.duration = HumanReadableDurationFromTimestamp(int(event['last_state_change'])) new_service.status_information = event_check['output'] # needs a / with a number on either side to work new_service.attempt = str(event['occurrences']) + '/1' new_service.passiveonly = False new_service.notifications_disabled = event['silenced'] new_service.flapping = False new_service.acknowledged = event['silenced'] new_service.scheduled_downtime = False self._insert_service_to_hosts(new_service) except: self.isChecking = False result, error = self.Error(sys.exc_info()) print(traceback.format_exc()) return Result(result=result, error=error) return Result(error="") def get_username(self): return str(self.username) def get_password(self): return str(self.password) def set_acknowledge(self, info_dict): subscription = self._format_client_subscription(info_dict['host']) try: silenece_args = { 'check': info_dict['service'], 'subscription': subscription, 'reason': info_dict['comment'], 'creator': info_dict['author'], 'expire_on_resolve': True } self.sensu_api.post_silence_request(silenece_args) except SensuAPIException as e: pass @staticmethod def _format_client_subscription(client: str): return 'client:' + client def _acknowledge_client_check(self, client: str, check: str, comment: str = None, author: str = None): subscription = self._format_client_subscription(client) try: silenece_args = { 'check': check, 'subscription': subscription, 'reason': comment, 'creator': author, 'expire_on_resolve': True } self.sensu_api.post_silence_request(silenece_args) except SensuAPIException as e: pass def _get_all_events(self): events = self.sensu_api.get_events() return events def _get_event_duration_string(self, start_seconds: int, end_seconds: int): sec = end_seconds - start_seconds days, rem = divmod(sec, 60 * 60 * 24) hours, rem = divmod(rem, 60 * 60) mins, sec = divmod(rem, 60) if days == 0 and hours == 0 and mins == 0 and sec == 0: return None return '%sd %sh %sm %ss' % (days, hours, mins, sec) def set_recheck(self, info_dict): self.sensu_api.post_check_request(info_dict['service'], self._format_client_subscription(info_dict['host'])) def set_downtime(self, info_dict): subscription = self._format_client_subscription(info_dict['host']) silence_args = { 'check': info_dict['service'], 'subscription': subscription, 'reason': info_dict['comment'], 'creator': info_dict['author'], 'expire': int(info_dict['hours']) * 3600 + int(info_dict['minutes']) * 60, 'expire_on_resolve': False } self.sensu_api.post_silence_request(silence_args) def set_submit_check_result(self, info_dict): sensu_status = None try: sensu_status = self.SEVERITY_STATUS_TEXT_MAP[info_dict['state']] except KeyError: sensu_status = 3 # 3 stands for UNKNOWN, anything other than 0,1,2 is UNKNOWN to Sensu self.sensu_api.post_result_data(info_dict['host'], info_dict['service'], info_dict['check_output'], sensu_status) def get_start_end(self, host): return 'Not Supported!', 'Not Supported!' def open_monitor(self, host, service=''): ''' open monitor from tablewidget context menu ''' detail_url = self.monitor_url + '/#/client/' + self.uchiwa_datacenter + '/' + host if service != '': detail_url += '?check=' + service webbrowser_open(detail_url) Nagstamon/Nagstamon/Servers/SnagView3.py000066400000000000000000000550601316117564000205570ustar00rootroot00000000000000# encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2014 Henri Wahl et al. # # 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 # Initial implementation by Marcus Mönnig # # This Server class connects against SNAG-View 3. # # Status/TODOs: # import copy import datetime import json import logging import sys import time import requests from bs4 import BeautifulSoup from Nagstamon.Helpers import webbrowser_open from Nagstamon.Objects import (GenericHost, GenericService, Result) from Nagstamon.Servers.Generic import GenericServer log = logging.getLogger('SNAG-View 3') log.setLevel('INFO') def strfdelta(tdelta, fmt): d = {'days': tdelta.days} d['hours'], rem = divmod(tdelta.seconds, 3600) d['minutes'], d['seconds'] = divmod(rem, 60) return fmt.format(**d) class SnagViewServer(GenericServer): """ object of SNAG-View 3 server """ TYPE = 'SNAG-View 3' MENU_ACTIONS = [ 'Monitor', 'Recheck', 'Acknowledge', 'Submit check result', 'Downtime' ] STATES_MAPPING = { 'hosts': { 0: 'UP', 1: 'DOWN', 2: 'UNREACHABLE', 4: 'PENDING' }, 'services': { 0: 'OK', 1: 'WARNING', 2: 'CRITICAL', 3: 'UNKNOWN', 4: 'PENDING' } } STATES_MAPPING_REV = { 'hosts': { 'UP': 0, 'DOWN': 1, 'UNREACHABLE': 2, 'PENDING': 4 }, 'services': { 'OK': 0, 'WARNING': 1, 'CRITICAL': 2, 'UNKNOWN': 3, 'PENDING': 4 } } BROWSER_URLS = { 'monitor': '$MONITOR$', 'hosts': '$MONITOR$', 'services': '$MONITOR$', 'history': '$MONITOR$/#/alert/ticker' } def init_config(self): """ Set URLs for CGI - they are static and there is no need to set them with every cycle """ # dummy default empty cgi urls - get filled later when server version is known self.cgiurl_services = None self.cgiurl_hosts = None def init_HTTP(self): """ Initializing of session object """ GenericServer.init_HTTP(self) self.session.auth = NoAuth() if len(self.session.cookies) == 0: form_inputs = dict() if self.username.startswith('ldap:'): form_inputs['module'] = 'ldap' form_inputs['_username'] = self.username[5:] else: form_inputs['module'] = 'sv' form_inputs['_username'] = self.username form_inputs['urm:login:client'] = '' form_inputs['_password'] = self.password # call login page to get temporary cookie self.FetchURL('{0}/security/login'.format(self.monitor_url)) # submit login form to retrieve authentication cookie self.FetchURL( '{0}/security/login_check'.format(self.monitor_url), cgi_data=form_inputs, multipart=True ) def _get_status(self): """ Get status from SNAG-View 3 Server - only JSON """ # define CGI URLs for hosts and services if self.cgiurl_hosts == None: # hosts (up, down, unreachable or pending) self.cgiurl_hosts = self.monitor_cgi_url + '/rest/private/nagios/host' if self.cgiurl_services == None: # services (warning, critical, unknown or pending) self.cgiurl_services = self.monitor_cgi_url + \ '/rest/private/nagios/service_status/browser' self.new_hosts = dict() # hosts try: form_data = dict() form_data['acknowledged'] = 1 form_data['downtime'] = 1 form_data['inactiveHosts'] = 0 form_data['disabledNotification'] = 1 form_data['limit_start'] = 0 # Get all hosts form_data['limit_length'] = 99999 result = self.FetchURL( self.cgiurl_hosts, giveback='raw', cgi_data=form_data) # authentication errors get a status code 200 too if result.status_code < 400 and \ result.result.startswith('<'): # in case of auth error reset HTTP session and try again self.reset_HTTP() result = self.FetchURL( self.cgiurl_hosts, giveback='raw', cgi_data=form_data) if result.status_code < 400 and \ result.result.startswith('<'): self.refresh_authentication = True return Result(result=result.result, error='Authentication error', status_code=result.status_code) # purify JSON result jsonraw = copy.deepcopy(result.result.replace('\n', '')) error = copy.deepcopy(result.error) status_code = result.status_code if error != '' or status_code >= 400: return Result(result=jsonraw, error=error, status_code=status_code) self.check_for_error(jsonraw, error, status_code) hosts = json.loads(jsonraw) for host in hosts['data']: h = dict(host) # Skip if Host is 'Pending' if int(h['sv_host__nagios_status__current_state']) == 4: continue # Skip if Host has notifications disabled if int(h['sv_host__nagios_status__notifications_enabled']) == 0: continue # host host_name = h['sv_host__nagios__host_name'] # If a host does not exist, create its object if host_name not in self.new_hosts: self.new_hosts[host_name] = GenericHost() self.new_hosts[host_name].name = host_name self.new_hosts[host_name].svid = h['sv_host__svobjects____SVID'] self.new_hosts[host_name].server = self.name self.new_hosts[host_name].status = self.STATES_MAPPING['hosts'][int( h['sv_host__nagios_status__current_state'])] self.new_hosts[host_name].last_check = datetime.datetime.fromtimestamp( int(h['sv_host__nagios_status__last_check'])) self.new_hosts[host_name].attempt = h['sv_host__nagios__max_check_attempts'] self.new_hosts[host_name].status_information = h['sv_host__nagios_status__plugin_output'] self.new_hosts[host_name].passiveonly = not ( int(h['sv_host__nagios_status__checks_enabled'])) self.new_hosts[host_name].notifications_disabled = not ( int(h['sv_host__nagios_status__notifications_enabled'])) self.new_hosts[host_name].flapping = int( h['sv_host__nagios_status__is_flapping']) self.new_hosts[host_name].acknowledged = int( h['sv_host__nagios_status__problem_has_been_acknowledged']) self.new_hosts[host_name].scheduled_downtime = int( h['sv_host__nagios_status__scheduled_downtime_depth']) self.new_hosts[host_name].status_type = 'soft' if int( h['sv_host__nagios_status__state_type']) == 0 else 'hard' # extra duration needed for calculation duration = datetime.datetime.now( ) - datetime.datetime.fromtimestamp(int(h['sv_host__nagios_status__last_state_change'])) self.new_hosts[host_name].duration = strfdelta( duration, '{days}d {hours}h {minutes}m {seconds}s') del h, host_name except: import traceback traceback.print_exc(file=sys.stdout) # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) # services try: form_data = dict() form_data['acknowledged'] = 1 form_data['downtime'] = 1 form_data['inactiveHosts'] = 0 form_data['disabledNotification'] = 1 form_data['softstate'] = 1 form_data['limit_start'] = 0 # Get all services form_data['limit_length'] = 99999 result = self.FetchURL(self.cgiurl_services, giveback='raw', cgi_data=form_data) # purify JSON result jsonraw = copy.deepcopy(result.result.replace('\n', '')) error = copy.deepcopy(result.error) status_code = result.status_code if error != '' or status_code >= 400: return Result(result=jsonraw, error=error, status_code=status_code) self.check_for_error(jsonraw, error, status_code) services = json.loads(jsonraw) for service in services['data']: s = dict(service) # Skip if Host or Service is 'Pending' if int(s['sv_service_status__nagios_status__current_state']) == 4 or int( s['sv_host__nagios_status__current_state']) == 4: continue # Skip if Host or Service has notifications disabled if int(s['sv_service_status__nagios_status__notifications_enabled']) == 0 or int( s['sv_host__nagios_status__notifications_enabled']) == 0: continue # host and service host_name = s['sv_host__nagios__host_name'] service_name = s['sv_service_status__svobjects__rendered_label'] # If a service does not exist, create its object if service_name not in self.new_hosts[host_name].services: self.new_hosts[host_name].services[service_name] = GenericService( ) self.new_hosts[host_name].services[service_name].host = host_name self.new_hosts[host_name].services[service_name].svid = s[ 'sv_service_status__svobjects____SVID'] self.new_hosts[host_name].services[service_name].name = service_name self.new_hosts[host_name].services[service_name].server = self.name self.new_hosts[host_name].services[service_name].status = self.STATES_MAPPING['services'][int( s['sv_service_status__nagios_status__current_state'])] self.new_hosts[host_name].services[service_name].last_check = datetime.datetime.fromtimestamp( int(s['sv_service_status__nagios_status__last_check'])) self.new_hosts[host_name].services[service_name].attempt = s[ 'sv_service_status__nagios__max_check_attempts'] self.new_hosts[host_name].services[service_name].status_information = BeautifulSoup( s['sv_service_status__nagios_status__plugin_output'].replace( '\n', ' ').strip(), 'html.parser').text self.new_hosts[host_name].services[service_name].passiveonly = not ( int(s['sv_service_status__nagios_status__checks_enabled'])) self.new_hosts[host_name].services[service_name].notifications_disabled = not ( int(s['sv_service_status__nagios_status__notifications_enabled'])) self.new_hosts[host_name].services[service_name].flapping = int( s['sv_service_status__nagios_status__is_flapping']) self.new_hosts[host_name].services[service_name].acknowledged = int( s['sv_service_status__nagios_status__problem_has_been_acknowledged']) self.new_hosts[host_name].services[service_name].scheduled_downtime = int( s['sv_service_status__nagios_status__scheduled_downtime_depth']) self.new_hosts[host_name].services[service_name].status_type = 'soft' if int( s['sv_service_status__nagios_status__state_type']) == 0 else 'hard' # acknowledge needs service_description and no display name self.new_hosts[host_name].services[service_name].real_name = s[ 'sv_service_status__nagios__service_description'] # extra duration needed for calculation duration = datetime.datetime.now( ) - datetime.datetime.fromtimestamp( int(s['sv_service_status__nagios_status__last_state_change'])) self.new_hosts[host_name].services[service_name].duration = strfdelta( duration, '{days}d {hours}h {minutes}m {seconds}s') del s, host_name, service_name except: import traceback traceback.print_exc(file=sys.stdout) # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) del jsonraw, error, hosts # dummy return in case all is OK return Result() def _set_recheck(self, host, service): """ Do a POST-Request to recheck the given host or service in SNAG-View 3 :param host: String - Host name :param service: String - Service name """ form_data = dict() form_data['commandName'] = 'check-now' if service == '': form_data['params'] = json.dumps({'__SVID': self.hosts[host].svid}) form_data['commandType'] = 'sv_host' else: form_data['params'] = json.dumps( {'__SVID': self.hosts[host].services[service].svid}) form_data['commandType'] = 'sv_service_status' self.session.post( '{0}/rest/private/nagios/command/execute'.format(self.monitor_url), data=form_data) def _set_acknowledge(self, host, service, author, comment, sticky, notify, persistent, all_services=[]): """ Do a POST-Request to set an acknowledgement for a host, service or host with all services in SNAG-View 3 :param host: String - Host name :param service: String - Service name :param author: String - Author name (username) :param comment: String - Additional comment :param sticky: Bool - Sticky Acknowledgement :param notify: Bool - Send Notifications :param persistent: Bool - Persistent comment :param all_services: Array - List of all services (filled only if 'Acknowledge all services on host' is set) """ form_data = dict() if len(all_services) > 0: # Host & all Services form_data['commandType'] = 'sv_host' form_data['commandName'] = 'acknowledge-host-service-problems' form_data['params'] = json.dumps( {'__SVID': self.hosts[host].svid, 'comment': comment, 'notify': notify, 'persistent': persistent, 'sticky': sticky}) elif service == '': # Host form_data['commandType'] = 'sv_host' form_data['commandName'] = 'acknowledge-problem' form_data['params'] = json.dumps( {'__SVID': self.hosts[host].svid, 'comment': comment, 'notify': notify, 'persistent': persistent, 'sticky': sticky}) else: # Service form_data['commandType'] = 'sv_service_status' form_data['commandName'] = 'acknowledge-problem' form_data['params'] = json.dumps( {'__SVID': self.hosts[host].services[service].svid, 'comment': comment, 'notify': notify, 'persistent': persistent, 'sticky': sticky}) self.session.post( '{0}/rest/private/nagios/command/execute'.format(self.monitor_url), data=form_data) def _set_submit_check_result(self, host, service, state, comment, check_output, performance_data): """ Do a POST-Request to submit a check result to SNAG-View 3 :param host: String - Host name :param service: String - Service name :param state: String - Selected state :param comment: NOT IN USE - String - Additional comment :param check_output: String - Check output :param performance_data: String - Performance data """ state = state.upper() form_data = dict() form_data['commandName'] = 'process-check-result' # TODO 'state' contains wrong information # Variable 'state' can contain any standard state # ('up','down','unreachable', 'ok', 'warning', 'critical' or 'unknown') # Selecting something else for example 'information' or 'disaster' puts 'ok' into the variable state # This makes it impossible to log errors for unsupported states because you can't differentiate # between selecting 'ok' and 'information' because in both cases the variable contains 'ok' log.info( 'Selecting an unsupported check result submits \'UP\' for hosts and \'OK\' for services!') if service == '': # Host form_data['commandType'] = 'sv_host' if state == 'OK' or state == 'UNKNOWN': log.info('Setting OK or UNKNOWN to UP') state = 'UP' state_number = self.STATES_MAPPING_REV['hosts'][state] if performance_data == '': form_data['params'] = json.dumps( {'__SVID': self.hosts[host].svid, 'status_code': state_number, 'plugin_output': check_output}) else: form_data['params'] = json.dumps({'__SVID': self.hosts[host].svid, 'status_code': state_number, 'plugin_output': check_output + ' | ' + performance_data}) else: # Service form_data['commandType'] = 'sv_service_status' state_number = self.STATES_MAPPING_REV['services'][state] if performance_data == '': form_data['params'] = json.dumps( {'__SVID': self.hosts[host].services[service].svid, 'status_code': state_number, 'plugin_output': check_output}) else: form_data['params'] = json.dumps( {'__SVID': self.hosts[host].services[service].svid, 'status_code': state_number, 'plugin_output': check_output + ' | ' + performance_data}) self.session.post( '{0}/rest/private/nagios/command/execute'.format(self.monitor_url), data=form_data) def _set_downtime(self, host, service, author, comment, fixed, start_time, end_time, hours, minutes): """ Do a PUT-Request to create a downtime for a host or service in SNAG-View 3 :param host: String - Host name :param service: String - Service name :param author: String - Author name (username) :param comment: String - Additional comment :param fixed: Bool - Fixed Downtime :param start_time: String - Date in Y-m-d H:M:S format - Start of Downtime :param end_time: String - Date in Y-m-d H:M:S format - End of Downtime :param hours: NOT SUPPORTED - Integer - Flexible Downtime :param minutes: NOT SUPPORTED - Integer - Flexible Downtime """ form_data = dict() if service == '': form_data['type'] = 'sv_host' form_data['host_effects'] = 'hostOnly' form_data['svid'] = self.hosts[host].svid else: form_data['type'] = 'sv_service_status' form_data['svid'] = self.hosts[host].services[service].svid # Format start_time and end_time from user-friendly format to timestamp start_time = time.mktime(datetime.datetime.strptime( start_time, "%Y-%m-%d %H:%M:%S").timetuple()) start_time = str(start_time).split('.')[0] end_time = time.mktime(datetime.datetime.strptime( end_time, "%Y-%m-%d %H:%M:%S").timetuple()) end_time = str(end_time).split('.')[0] form_data['start'] = start_time form_data['end'] = end_time form_data['comment'] = comment self.session.put( '{0}/rest/private/nagios/downtime'.format(self.monitor_url), data=form_data) def get_start_end(self, host): """ Set default of start time to "now" and end time is "now + 24 hours" :param host: String - Host name """ log.info("Flexible Downtimes are not supported by SNAG-View 3") start = datetime.datetime.now() end = datetime.datetime.now() + datetime.timedelta(hours=24) return str(start.strftime("%Y-%m-%d %H:%M:%S")), str(end.strftime("%Y-%m-%d %H:%M:%S")) def open_monitor(self, host, service=''): """ Open specific Host or Service in SNAG-View 3 browser window :param host: String - Host name :param service: String - Service name """ if service == '': url = '{0}/#/object/details/{1}'.format( self.monitor_url, self.hosts[host].svid) else: url = '{0}/#/object/details/{1}'.format( self.monitor_url, self.hosts[host].services[service].svid) webbrowser_open(url) class NoAuth(requests.auth.AuthBase): """ Override to avoid auth headers Needed for LDAP login """ def __call__(self, r): return r Nagstamon/Nagstamon/Servers/Thruk.py000066400000000000000000000326411316117564000200460ustar00rootroot00000000000000# encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2014 Henri Wahl et al. # Thruk additions copyright by dcec@Github # # 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 from Nagstamon.Servers.Generic import GenericServer import sys import json import datetime import copy from Nagstamon.Helpers import HumanReadableDurationFromTimestamp from Nagstamon.Objects import (GenericHost, GenericService, Result) class ThrukServer(GenericServer): """ Thruk is derived from generic (Nagios) server """ TYPE = 'Thruk' # dictionary to translate status bitmaps on webinterface into status flags # this are defaults from Nagios # "disabled.gif" is in Nagios for hosts the same as "passiveonly.gif" for services STATUS_MAPPING = { "ack.gif" : "acknowledged", \ "passiveonly.gif" : "passiveonly", \ "disabled.gif" : "passiveonly", \ "ndisabled.gif" : "notifications_disabled", \ "downtime.gif" : "scheduled_downtime", \ "flapping.gif" : "flapping"} # Entries for monitor default actions in context menu MENU_ACTIONS = ["Monitor", "Recheck", "Acknowledge", "Submit check result", "Downtime"] # Arguments available for submitting check results SUBMIT_CHECK_RESULT_ARGS = ["check_output", "performance_data"] # URLs for browser shortlinks/buttons on popup window BROWSER_URLS = { "monitor": "$MONITOR$", \ "hosts": "$MONITOR-CGI$/status.cgi?hostgroup=all&style=hostdetail&hoststatustypes=12&page=1&entries=all", \ "services": "$MONITOR-CGI$/status.cgi?dfl_s0_value_sel=5&dfl_s0_servicestatustypes=29&dfl_s0_op=%3D&style=detail&dfl_s0_type=host&dfl_s0_serviceprops=0&dfl_s0_servicestatustype=4&dfl_s0_servicestatustype=8&dfl_s0_servicestatustype=16&dfl_s0_servicestatustype=1&hidetop=&dfl_s0_hoststatustypes=15&dfl_s0_val_pre=&hidesearch=2&dfl_s0_value=all&dfl_s0_hostprops=0&nav=&page=1&entries=all", \ "history": "$MONITOR-CGI$/history.cgi?host=all&page=1&entries=all"} STATES_MAPPING = {"hosts" : {0 : "OK", 1 : "DOWN", 2 : "UNREACHABLE"}, \ "services" : {0 : "OK", 1 : "WARNING", 2 : "CRITICAL", 3 : "UNKNOWN"}} def __init__(self, **kwds): GenericServer.__init__(self, **kwds) # flag for newer cookie authentication self.CookieAuth = False def reset_HTTP(self): """ brute force reset by GenericServer disturbs logging in into Thruk """ # only reset session if Thruks 2 cookies are there try: # only reset session if Thruks 2 cookies are there if len(self.session.cookies) > 1: self.session = None except: self.Error(sys.exc_info()) def init_HTTP(self): """ partly not constantly working Basic Authorization requires extra Autorization headers, different between various server types """ GenericServer.init_HTTP(self) # only if cookies are needed if self.CookieAuth: # get cookie to access Thruk web interface # Thruk first send a test cookie, later an auth cookie if len(self.session.cookies) < 2: # get cookie from login page via url retrieving as with other urls try: # login and get cookie self.login() except: self.Error(sys.exc_info()) def init_config(self): """ set URLs for CGI - they are static and there is no need to set them with every cycle """ # create filters like described in # http://www.nagios-wiki.de/nagios/tips/host-_und_serviceproperties_fuer_status.cgi?s=servicestatustypes # Thruk allows requesting only needed information to reduce traffic self.cgiurl_services = self.monitor_cgi_url + "/status.cgi?host=all&servicestatustypes=28&view_mode=json&"\ "entries=all&columns=host_name,description,state,last_check,"\ "last_state_change,plugin_output,current_attempt,"\ "max_check_attempts,active_checks_enabled,is_flapping,"\ "notifications_enabled,acknowledged,state_type,"\ "scheduled_downtime_depth" # hosts (up or down or unreachable) self.cgiurl_hosts = self.monitor_cgi_url + "/status.cgi?hostgroup=all&style=hostdetail&hoststatustypes=12&"\ "view_mode=json&entries=all&"\ "columns=name,state,last_check,last_state_change,"\ "plugin_output,current_attempt,max_check_attempts,"\ "active_checks_enabled,notifications_enabled,is_flapping,"\ "acknowledged,scheduled_downtime_depth,state_type" # get cookie from login page via url retrieving as with other urls try: # login and get cookie self.login() if len(self.session.cookies) > 0: self.CookieAuth = True except: self.Error(sys.exc_info()) def login(self): """ use pure session instead of FetchURL to get Thruk session """ self.session.post(self.monitor_cgi_url + '/login.cgi?', data={'login': self.get_username(), 'password': self.get_password(), 'submit': 'Login', 'referer': ''}) def _get_status(self): """ Get status from Thruk Server """ # new_hosts dictionary self.new_hosts = dict() # hosts - mostly the down ones # unfortunately the hosts status page has a different structure so # hosts must be analyzed separately try: # JSON experiments result = self.FetchURL(self.cgiurl_hosts, giveback='raw') jsonraw, error, status_code = copy.deepcopy(result.result),\ copy.deepcopy(result.error),\ result.status_code # check if any error occured errors_occured = self.check_for_error(jsonraw, error, status_code) # if there are errors return them if errors_occured != False: return(errors_occured) # in case basic auth did not work try form login cookie based login if jsonraw.startswith("<"): self.CookieAuth = True return Result(result=None, error="Login failed") # in case JSON is not empty evaluate it elif not jsonraw == "[]": hosts = json.loads(jsonraw) for h in hosts: if h["name"] not in self.new_hosts: self.new_hosts[h["name"]] = GenericHost() self.new_hosts[h["name"]].name = h["name"] self.new_hosts[h["name"]].server = self.name self.new_hosts[h["name"]].status = self.STATES_MAPPING["hosts"][h["state"]] self.new_hosts[h["name"]].last_check = datetime.datetime.fromtimestamp(int(h["last_check"])).isoformat(" ") self.new_hosts[h["name"]].duration = HumanReadableDurationFromTimestamp(h["last_state_change"]) self.new_hosts[h["name"]].attempt = "%s/%s" % (h["current_attempt"], h["max_check_attempts"]) self.new_hosts[h["name"]].status_information = h["plugin_output"].replace("\n", " ").strip() self.new_hosts[h["name"]].passiveonly = not(bool(int(h["active_checks_enabled"]))) self.new_hosts[h["name"]].notifications_disabled = not(bool(int(h["notifications_enabled"]))) self.new_hosts[h["name"]].flapping = bool(int(h["is_flapping"])) self.new_hosts[h["name"]].acknowledged = bool(int(h["acknowledged"])) self.new_hosts[h["name"]].scheduled_downtime = bool(int(h["scheduled_downtime_depth"])) self.new_hosts[h["name"]].status_type = {0: "soft", 1: "hard"}[h["state_type"]] del h except: import traceback traceback.print_exc(file=sys.stdout) # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) # services try: # JSON experiments result = self.FetchURL(self.cgiurl_services, giveback="raw") jsonraw, error, status_code = copy.deepcopy(result.result),\ copy.deepcopy(result.error),\ result.status_code # check if any error occured errors_occured = self.check_for_error(jsonraw, error, status_code) # if there are errors return them if errors_occured != False: return(errors_occured) # in case basic auth did not work try form login cookie based login if jsonraw.startswith("<"): self.CookieAuth = True return Result(result=None, error="Login failed") # in case JSON is not empty evaluate it elif not jsonraw == "[]": services = json.loads(jsonraw) for s in services: # host objects contain service objects if s["host_name"] not in self.new_hosts: self.new_hosts[s["host_name"]] = GenericHost() self.new_hosts[s["host_name"]].name = s["host_name"] self.new_hosts[s["host_name"]].server = self.name self.new_hosts[s["host_name"]].status = "UP" # if a service does not exist create its object if s["description"] not in self.new_hosts[s["host_name"]].services: # ##new_service = s["description"] self.new_hosts[s["host_name"]].services[s["description"]] = GenericService() self.new_hosts[s["host_name"]].services[s["description"]].host = s["host_name"] self.new_hosts[s["host_name"]].services[s["description"]].name = s["description"] self.new_hosts[s["host_name"]].services[s["description"]].server = self.name self.new_hosts[s["host_name"]].services[s["description"]].status = self.STATES_MAPPING["services"][s["state"]] self.new_hosts[s["host_name"]].services[s["description"]].last_check = datetime.datetime.fromtimestamp(int(s["last_check"])).isoformat(" ") self.new_hosts[s["host_name"]].services[s["description"]].duration = HumanReadableDurationFromTimestamp(s["last_state_change"]) self.new_hosts[s["host_name"]].services[s["description"]].attempt = "%s/%s" % (s["current_attempt"], s["max_check_attempts"]) self.new_hosts[s["host_name"]].services[s["description"]].status_information = s["plugin_output"].replace("\n", " ").strip() self.new_hosts[s["host_name"]].services[s["description"]].passiveonly = not(bool(int(s["active_checks_enabled"]))) self.new_hosts[s["host_name"]].services[s["description"]].notifications_disabled = not(bool(int(s["notifications_enabled"]))) self.new_hosts[s["host_name"]].services[s["description"]].flapping = not(bool(int(s["notifications_enabled"]))) self.new_hosts[s["host_name"]].services[s["description"]].acknowledged = bool(int(s["acknowledged"])) self.new_hosts[s["host_name"]].services[s["description"]].scheduled_downtime = bool(int(s["scheduled_downtime_depth"])) self.new_hosts[s["host_name"]].services[s["description"]].status_type = {0: "soft", 1: "hard"}[s["state_type"]] del s except: import traceback traceback.print_exc(file=sys.stdout) # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) # dummy return in case all is OK return Result() Nagstamon/Nagstamon/Servers/Zabbix.py000066400000000000000000000452221316117564000201670ustar00rootroot00000000000000# -*- encoding: utf-8; py-indent-offset: 4 -*- # # Zabbix.py based on Check_MK Multisite.py import sys import urllib.request import urllib.parse import urllib.error import time # import socket # never used from Nagstamon.Helpers import (HumanReadableDurationFromTimestamp, webbrowser_open) from Nagstamon.Config import conf from Nagstamon.Objects import (GenericHost, GenericService, Result) from Nagstamon.Servers.Generic import GenericServer from Nagstamon.thirdparty.zabbix_api import (ZabbixAPI, ZabbixAPIException, APITimeout, Already_Exists) class ZabbixError(Exception): def __init__(self, terminate, result): self.terminate = terminate self.result = result class ZabbixServer(GenericServer): """ special treatment for Zabbix, taken from Check_MK Multisite JSON API """ TYPE = 'Zabbix' zapi = None def __init__(self, **kwds): GenericServer.__init__(self, **kwds) # Prepare all urls needed by nagstamon - self.urls = {} # self.statemap = {} self.statemap = { 'UNREACH': 'UNREACHABLE', 'CRIT': 'CRITICAL', 'WARN': 'WARNING', 'UNKN': 'UNKNOWN', 'PEND': 'PENDING', '0': 'OK', '1': 'INFORMATION', '2': 'WARNING', '3': 'AVERAGE', '4': 'HIGH', '5': 'DISASTER'} # Entries for monitor default actions in context menu self.MENU_ACTIONS = ["Recheck", "Acknowledge", "Downtime"] self.username = conf.servers[self.get_name()].username self.password = conf.servers[self.get_name()].password self.ignore_cert = conf.servers[self.get_name()].ignore_cert if self.ignore_cert is True: self.validate_certs = False else: self.validate_certs = True def _login(self): try: self.zapi = ZabbixAPI(server=self.monitor_url, path="", log_level=0, validate_certs=self.validate_certs) self.zapi.login(self.username, self.password) except ZabbixAPIException: result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) def getLastApp(self, this_item): if len(this_item) > 0: last_app = len(this_item[0]['applications']) - 1 # use it to get the last application name if last_app > -1: return "%s" % this_item[0]['applications'][last_app]['name'] else: return "NO APP" else: return "Web scenario" def _get_status(self): """ Get status from Zabbix Server """ ret = Result() # create Nagios items dictionary with to lists for services and hosts # every list will contain a dictionary for every failed service/host # this dictionary is only temporarily nagitems = {"services": [], "hosts": []} # Create URLs for the configured filters if self.zapi is None: self._login() try: hosts = [] try: hosts = self.zapi.host.get( {"output": ["host", "ip", "status", "available", "error", "errors_from"], "filter": {}}) except (ZabbixError, ZabbixAPIException, APITimeout, Already_Exists): # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) for host in hosts: # if host is disabled on server safely ignore it if host['available'] != '0': n = { 'host': host['host'], 'status': self.statemap.get(host['status'], host['status']), 'last_check': 'n/a', 'duration': HumanReadableDurationFromTimestamp(host['errors_from']), 'status_information': host['error'], 'attempt': '1/1', 'site': '', 'address': host['host'], } # Zabbix shows OK hosts too - kick 'em! if not n['status'] == 'OK': # add dictionary full of information about this host item to nagitems nagitems["hosts"].append(n) # after collection data in nagitems create objects from its informations # host objects contain service objects if n["host"] not in self.new_hosts: new_host = n["host"] self.new_hosts[new_host] = GenericHost() self.new_hosts[new_host].name = n["host"] self.new_hosts[new_host].status = n["status"] self.new_hosts[new_host].last_check = n["last_check"] self.new_hosts[new_host].duration = n["duration"] self.new_hosts[new_host].attempt = n["attempt"] self.new_hosts[new_host].status_information = n["status_information"] self.new_hosts[new_host].site = n["site"] self.new_hosts[new_host].address = n["address"] except ZabbixError: self.isChecking = False result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) # services services = [] # groupids = [] # never used - probably old code zabbix_triggers = [] try: api_version = self.zapi.api_version() except ZabbixAPIException: # FIXME Is there a cleaner way to handle this? I just borrowed # this code from 80 lines ahead. -- AGV # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) print(sys.exc_info()) return Result(result=result, error=error) try: # response = [] # never used - probably old code try: triggers_list = [] hostgroup_ids = [x['groupid'] for x in self.zapi.hostgroup.get( {'output': 'extend', 'with_monitored_items': True}) if int(x['internal']) == 0] zabbix_triggers = self.zapi.trigger.get( {'sortfield': 'lastchange', 'skipDependent': True, 'withLastEventUnacknowledged': True, 'groupids': hostgroup_ids, 'monitored': True, 'filter': {'value': 1}}) triggers_list = [] for trigger in zabbix_triggers: triggers_list.append(trigger.get('triggerid')) this_trigger = self.zapi.trigger.get( {'triggerids': triggers_list, 'expandDescription': True, 'output': 'extend', 'select_items': 'extend', # thats for zabbix api 1.8 'selectItems': 'extend', # thats for zabbix api 2.0+ 'expandData': True, 'selectHosts': 'extend'} ) if type(this_trigger) is dict: for triggerid in list(this_trigger.keys()): services.append(this_trigger[triggerid]) # get Application name for the trigger this_item = self.zapi.item.get( {'itemids': [this_trigger[triggerid]['items'][0]['itemid']], 'selectApplications': 'extend'} ) this_trigger[triggerid]['application'] = self.getLastApp(this_item) elif type(this_trigger) is list: for trigger in this_trigger: services.append(trigger) # get Application name for the trigger this_item = self.zapi.item.get( {'itemids': trigger['items'][0]['itemid'], 'selectApplications': 'extend'} ) trigger['application'] = self.getLastApp(this_item) except ZabbixAPIException: # FIXME Is there a cleaner way to handle this? I just borrowed # this code from 80 lines ahead. -- AGV # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) print(sys.exc_info()) return Result(result=result, error=error) except ZabbixError as e: if e.terminate: return e.result else: service = e.result.content ret = e.result for service in services: # Zabbix probably shows OK services too - kick 'em! # UPDATE Zabbix api 3.0 doesn't but I didn't tried with older # so I left it status = self.statemap.get(service['priority'], service['priority']) # self.Debug(server=self.get_name(), debug="SERVICE (" + service['application'] + ") STATUS: **" + status + "** PRIORITY: #" + service['priority']) # self.Debug(server=self.get_name(), debug="-----======== SERVICE " + str(service)) if not status == 'OK': if not service['description'].endswith('...'): state = service['description'] else: state = service['items'][0]['lastvalue'] lastcheck = 0 for item in service['items']: if int(item['lastclock']) > lastcheck: lastcheck = int(item['lastclock']) if len(service['comments']) == 0: srvc = service['application'] else: srvc = self.nagiosify_service(service['comments']) n = { 'service': srvc, 'status': status, # 1/1 attempt looks at least like there has been any attempt 'attempt': '1/1', 'duration': HumanReadableDurationFromTimestamp(service['lastchange']), 'status_information': state, 'passiveonly': 'no', 'last_check': time.strftime("%d/%m/%Y %H:%M:%S", time.localtime(lastcheck)), 'notifications': 'yes', 'flapping': 'no', 'site': '', 'command': 'zabbix', 'triggerid': service['triggerid'], } if api_version >= '3.0': n['host'] = service['hosts'][0]['host'] else: n['host'] = service['host'] nagitems["services"].append(n) # after collection data in nagitems create objects of its informations # host objects contain service objects if n["host"] not in self.new_hosts: self.new_hosts[n["host"]] = GenericHost() self.new_hosts[n["host"]].name = n["host"] self.new_hosts[n["host"]].status = "UP" self.new_hosts[n["host"]].site = n["site"] self.new_hosts[n["host"]].address = n["host"] # if a service does not exist create its object if n["service"] not in self.new_hosts[n["host"]].services: # workaround for non-existing (or not found) host status flag if n["service"] == "Host is down %s" % (n["host"]): self.new_hosts[n["host"]].status = "DOWN" # also take duration from "service" aka trigger self.new_hosts[n["host"]].duration = n["duration"] if conf.debug_mode is True: self.Debug(server=self.get_name(), debug="Adding Host[" + n['host'] + "]") else: new_service = n["triggerid"] self.new_hosts[n["host"]].services[new_service] = GenericService() self.new_hosts[n["host"]].services[new_service].host = n["host"] self.new_hosts[n["host"]].services[new_service].name = n["service"] self.new_hosts[n["host"]].services[new_service].status = n["status"] self.new_hosts[n["host"]].services[new_service].last_check = n["last_check"] self.new_hosts[n["host"]].services[new_service].duration = n["duration"] self.new_hosts[n["host"]].services[new_service].attempt = n["attempt"] self.new_hosts[n["host"]].services[new_service].status_information = n["status_information"] self.new_hosts[n["host"]].services[new_service].passiveonly = False self.new_hosts[n["host"]].services[new_service].flapping = False self.new_hosts[n["host"]].services[new_service].site = n["site"] self.new_hosts[n["host"]].services[new_service].address = n["host"] self.new_hosts[n["host"]].services[new_service].command = n["command"] self.new_hosts[n["host"]].services[new_service].triggerid = n["triggerid"] if conf.debug_mode is True: self.Debug(server=self.get_name(), debug="Adding new service[" + new_service + "] **" + n['service'] + "**") except (ZabbixError, ZabbixAPIException): # set checking flag back to False self.isChecking = False result, error = self.Error(sys.exc_info()) print(sys.exc_info()) return Result(result=result, error=error) return ret def _open_browser(self, url): webbrowser_open(url) if conf.debug_mode is True: self.Debug(server=self.get_name(), debug="Open web page " + url) def open_services(self): self._open_browser(self.urls['human_services']) def open_hosts(self): self._open_browser(self.urls['human_hosts']) def open_monitor(self, host, service=""): """ open monitor from treeview context menu """ if service == "": url = self.urls['human_host'] + urllib.parse.urlencode( {'x': 'site=' + self.hosts[host].site + '&host=' + host}).replace('x=', '%26') else: url = self.urls['human_service'] + urllib.parse.urlencode( {'x': 'site=' + self.hosts[host].site + '&host=' + host + '&service=' + service}).replace('x=', '%26') if conf.debug_mode is True: self.Debug(server=self.get_name(), host=host, service=service, debug="Open host/service monitor web page " + url) webbrowser_open(url) # def GetHost(self, host): # """ # find out ip or hostname of given host to access hosts/devices which do not appear in DNS but # have their ip saved in Nagios # """ # # the fasted method is taking hostname as used in monitor # if conf.connect_by_host is True: # return Result(result=host) # ip = "" # try: # if host in self.hosts: # ip = self.hosts[host].address # if conf.debug_mode is True: # self.Debug(server=self.get_name(), host=host, debug="IP of %s:" % host + " " + ip) # if conf.connect_by_dns is True: # try: # address = socket.gethostbyaddr(ip)[0] # except socket.herror: # address = ip # else: # address = ip # except ZabbixError: # result, error = self.Error(sys.exc_info()) # return Result(result=result, error=error) # return Result(result=address) def _set_recheck(self, host, service): pass def get_start_end(self, host): return time.strftime("%Y-%m-%d %H:%M"), time.strftime("%Y-%m-%d %H:%M", time.localtime(time.time() + 7200)) def _action(self, site, host, service, specific_params): params = { 'site': self.hosts[host].site, 'host': host, } params.update(specific_params) if self.zapi is None: self._login() events = [] for e in self.zapi.event.get({'triggerids': params['triggerids'], # from zabbix 2.2 should be used "objectids" instead of "triggerids" 'objectids': params['triggerids'], 'hide_unknown': True, # zabbix 1.8 'acknowledged': False, 'sortfield': 'clock', 'sortorder': 'DESC'}): # stop at first event in "OK" status if e['value'] == '0': break events.append(e['eventid']) self.zapi.event.acknowledge({'eventids': events, 'message': params['message']}) def _set_downtime(self, host, service, author, comment, fixed, start_time, end_time, hours, minutes): pass def _set_acknowledge(self, host, service, author, comment, sticky, notify, persistent, all_services=[]): triggerid = self.hosts[host].services[service].triggerid p = { 'message': '%s: %s' % (author, comment), 'triggerids': [triggerid], } self._action(self.hosts[host].site, host, service, p) # acknowledge all services on a host when told to do so for s in all_services: self._action(self.hosts[host].site, host, s, p) def nagiosify_service(self, service): """ next dirty workaround to get Zabbix events to look Nagios-esque """ if (" on " or " is ") in service: for separator in [" on ", " is "]: service = service.split(separator)[0] return(service) Nagstamon/Nagstamon/Servers/Zenoss.py000066400000000000000000000200001316117564000202140ustar00rootroot00000000000000#!/usr/bin/python # adapted from the zabbix.py code # Copyright (C) 2016 Jake Murphy Far Edge Technology # # 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 import sys import urllib import base64 import time import copy import datetime import traceback from datetime import datetime from Nagstamon.Config import conf from Nagstamon.Objects import (GenericHost, GenericService, Result) from Nagstamon.Servers.Generic import GenericServer from Nagstamon.thirdparty.zenoss_api import ZenossAPI class ZenossServer(GenericServer): TYPE = 'Zenoss' zapi = None SEVERITY_MAP = {0: 'OK', 1: 'UNKNOWN', 2: 'UNKNOWN', 3: 'WARNING', 4: 'WARNING', 5: 'CRITICAL'} MENU_ACTIONS = ['Monitor', 'Acknowledge'] def __init__(self, **kwds): GenericServer.__init__(self, **kwds) # Prepare all urls needed by nagstamon self.urls = {} self.statemap = {} self.server = Server() if ":" in conf.servers[self.get_name()].monitor_url: self.server.server_url, self.server.server_port = conf.servers[self.get_name()].monitor_url.split(':') else: self.server.server_url = conf.servers[self.get_name()].monitor_url self.server.server_port = 8080 #the default is 8080 self.server.username = conf.servers[self.get_name()].username self.server.password = conf.servers[self.get_name()].password # Entries for monitor default actions in context menu self.MENU_ACTIONS = ["Acknowledge"] def _zlogin(self): try: self.zapi = ZenossAPI(Server=self.server) except Exception: result, error = self.Error(sys.exc_info()) return Result(result=result, error=error) def _get_status(self): nagitems = {"services":[], "hosts":[]} self.new_hosts = dict() try: hosts = self._get_all_events() if 'events' in hosts: hosts = hosts['events'] for host in hosts: n = dict() n['evid'] = host['evid'] n['host'] = host['device']['text'] n['service'] = host['eventClass']['text'] n['status'] = self.SEVERITY_MAP.get(host['severity']) n['last_check'] = host['lastTime'] duration = self._calc_duration(host['firstTime'], host['lastTime']) if (duration == None): continue #Zenoss needs a length to cause an error n['duration'] = duration n["status_information"] = host['message'] n["attempt"] = str(host['count'])+"/1" # needs a / with a number on either side to work n["passiveonly"] = False n["notifications_disabled"] = False n["flapping"] = False n["acknowledged"] = (host['eventState'] == 'Acknowledged') n["scheduled_downtime"] = False nagitems["hosts"].append(n) new_host = n["host"] if not new_host in self.new_hosts: self.new_hosts[new_host] = GenericHost() self.new_hosts[new_host].name = new_host if not new_host in self.new_hosts[new_host].services: new_service = new_host self.new_hosts[new_host].services[new_service] = GenericService() self.new_hosts[new_host].services[new_service].host = new_host self.new_hosts[new_host].services[new_service].evid = n['evid'] self.new_hosts[new_host].services[new_service].name = n["service"] self.new_hosts[new_host].services[new_service].server = self.name self.new_hosts[new_host].services[new_service].status = n["status"] self.new_hosts[new_host].services[new_service].last_check = n["last_check"] self.new_hosts[new_host].services[new_service].duration = n["duration"] self.new_hosts[new_host].services[new_service].status_information= n["status_information"].encode("utf-8") self.new_hosts[new_host].services[new_service].attempt = n["attempt"] self.new_hosts[new_host].services[new_service].passiveonly = n["passiveonly"] self.new_hosts[new_host].services[new_service].notifications_disabled = n["notifications_disabled"] self.new_hosts[new_host].services[new_service].flapping = n["flapping"] self.new_hosts[new_host].services[new_service].acknowledged = n["acknowledged"] self.new_hosts[new_host].services[new_service].scheduled_downtime = n["scheduled_downtime"] del n except: self.isChecking = False result, error = self.Error(sys.exc_info()) print(traceback.format_exc()) return Result(result=result, error=error) del nagitems return Result(error="") def get_username(self): return str(self.server.username) def get_password(self): return str(self.server.password) def set_acknowledge(self, info_dict): if info_dict['host'] in self.hosts: evid = self.hosts[info_dict['host']].services[info_dict['host']].evid self.zapi.set_event_ack(evid) def _open_browser(self, url): webbrowser.open(self.monitor_url) def _get_all_events(self): if self.zapi is None: self._zlogin() events = self.zapi.get_event() return events ##http://stackoverflow.com/questions/538666/python-format-timedelta-to-string def _calc_duration(self, startStr, endStr): # like: '2016-10-2213: 53: 43' (that day/hour gap..) start = datetime.strptime(startStr, '%Y-%m-%d %H:%M:%S') end = datetime.strptime(endStr, '%Y-%m-%d %H:%M:%S') sec = (int)((end - start).total_seconds()) days, rem = divmod(sec, 60*60*24) hours, rem = divmod(rem, 60*60) mins, sec = divmod(rem, 60) if (days == 0 and hours == 0 and mins == 0 and sec == 0): return None return '%sd %sh %sm %ss' % (days,hours,mins,sec) #Note these methods are invalid for the zenoss api that this uses def set_recheck(self, info_dict): pass def set_downtime(self, host, service, author, comment, fixed, start_time, end_time, hours, minutes): pass def set_submit_check_result(self, info_dict): pass def get_start_end(self, host): pass class Server(object): #server object for api configuration connections def __init__(self): self.server_url = "" self.server_port = "" self.username = "" self.password = "" Nagstamon/Nagstamon/Servers/__init__.py000066400000000000000000000160651316117564000205120ustar00rootroot00000000000000# encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2016 Henri Wahl et al. # # 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 """Module Servers""" import urllib.request import urllib.error import urllib.parse from collections import OrderedDict # load all existing server types from Nagstamon.Servers.Nagios import NagiosServer from Nagstamon.Servers.Centreon import CentreonServer from Nagstamon.Servers.Icinga import IcingaServer from Nagstamon.Servers.IcingaWeb2 import IcingaWeb2Server from Nagstamon.Servers.Multisite import MultisiteServer from Nagstamon.Servers.op5Monitor import Op5MonitorServer from Nagstamon.Servers.Opsview import OpsviewServer from Nagstamon.Servers.Thruk import ThrukServer from Nagstamon.Servers.Zabbix import ZabbixServer from Nagstamon.Servers.Livestatus import LivestatusServer from Nagstamon.Servers.Zenoss import ZenossServer from Nagstamon.Servers.Monitos3 import Monitos3Server from Nagstamon.Servers.SnagView3 import SnagViewServer from Nagstamon.Servers.Sensu import SensuServer from Nagstamon.Config import conf from Nagstamon.Helpers import STATES # dictionary for servers servers = OrderedDict() # contains dict with available server classes # key is type of server, value is server class # used for automatic config generation # and holding this information in one place SERVER_TYPES = OrderedDict() def register_server(server): """ Once new server class is created, should be registered with this function for being visible in config and accessible in application. """ if server.TYPE not in SERVER_TYPES: SERVER_TYPES[server.TYPE] = server def get_enabled_servers(): """ list of enabled servers which connections outside should be used to check """ return([x for x in servers.values() if x.enabled is True]) def get_worst_status(): """ get worst status of all servers """ worst_status = 'UP' for server in get_enabled_servers(): if STATES.index(server.worst_status_current) > STATES.index(worst_status): worst_status = server.worst_status_current return worst_status def get_status_count(): """ get all states of all servers and count them """ state_count = {'UNKNOWN': 0, 'INFORMATION': 0, 'WARNING': 0, 'AVERAGE': 0, 'CRITICAL': 0, 'HIGH': 0, 'DISASTER': 0, 'UNREACHABLE': 0, 'DOWN': 0} for server in get_enabled_servers(): state_count['UNKNOWN'] += server.unknown state_count['INFORMATION'] += server.information state_count['WARNING'] += server.warning state_count['AVERAGE'] += server.average state_count['CRITICAL'] += server.critical state_count['HIGH'] += server.high state_count['DISASTER'] += server.disaster state_count['UNREACHABLE'] += server.unreachable state_count['DOWN'] += server.down return(state_count) def get_errors(): """ find out if any server has any error, used by statusbar error label """ for server in get_enabled_servers(): if server.has_error: return True break # return default value return False def create_server(server=None): # create Server from config if server.type not in SERVER_TYPES: print(('Server type not supported: %s' % server.type)) return # give argument servername so CentreonServer could use it for initializing MD5 cache new_server = SERVER_TYPES[server.type](name=server.name) new_server.type = server.type new_server.enabled = server.enabled new_server.monitor_url = server.monitor_url new_server.monitor_cgi_url = server.monitor_cgi_url new_server.username = server.username new_server.password = server.password new_server.use_proxy = server.use_proxy new_server.use_proxy_from_os = server.use_proxy_from_os new_server.proxy_address = server.proxy_address new_server.proxy_username = server.proxy_username new_server.proxy_password = server.proxy_password new_server.authentication = server.authentication new_server.timeout = server.timeout # SSL/TLS new_server.ignore_cert = server.ignore_cert new_server.custom_cert_use = server.custom_cert_use new_server.custom_cert_ca_file = server.custom_cert_ca_file # if password is not to be saved ask for it at startup if (server.enabled is True and server.save_password is False and server.use_autologin is False): new_server.refresh_authentication = True # Special FX # Centreon new_server.use_autologin = server.use_autologin new_server.autologin_key = server.autologin_key # Icinga new_server.use_display_name_host = server.use_display_name_host new_server.use_display_name_service = server.use_display_name_service # IcingaWeb2 new_server.no_cookie_auth = server.no_cookie_auth # Check_MK Multisite new_server.force_authuser = server.force_authuser new_server.check_mk_view_hosts = server.check_mk_view_hosts new_server.check_mk_view_services = server.check_mk_view_services # OP5 api filters new_server.host_filter = server.host_filter new_server.service_filter = server.service_filter # server's individual preparations for HTTP connections (for example cookie creation) # is done in GetStatus() method of monitor if server.enabled is True: new_server.enabled = True # start with high thread counter so server update thread does not have to wait new_server.thread_counter = conf.update_interval_seconds # debug if conf.debug_mode is True: new_server.Debug(server=server.name, debug="Created server.") return new_server # moved registration process here because of circular dependencies for server in (CentreonServer, IcingaServer, IcingaWeb2Server, MultisiteServer, NagiosServer, Op5MonitorServer, OpsviewServer, ThrukServer, ZabbixServer, SensuServer, LivestatusServer, ZenossServer, Monitos3Server, SnagViewServer): register_server(server) # create servers for server in conf.servers.values(): created_server = create_server(server) if created_server is not None: servers[server.name] = created_server # for the next time no auth needed servers[server.name].refresh_authentication = False Nagstamon/Nagstamon/Servers/op5Monitor.py000066400000000000000000000374671316117564000210370ustar00rootroot00000000000000# encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2014 Henri Wahl et al. # # 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 import sys import json import time from datetime import datetime from Nagstamon.Helpers import webbrowser_open from Nagstamon.Objects import (GenericHost, GenericService, Result) from Nagstamon.Servers.Generic import GenericServer from Nagstamon.Config import BOOLPOOL def human_duration(start): """ transform timestamp to human readable some changes necessary due to https://github.com/HenriWahl/Nagstamon/issues/93 - move definition of stop out of def() statement because it kept static """ stop = time.time() if stop <= start: return "n/a" else: ret = '' first = True secs = stop - start units = 'wdhms' divisors = {'w': 86400 * 7, 'd': 86400, 'h': 3600, 'm': 60, 's': 1} for unit in units: divisor = divisors[unit] if secs < divisor: continue amount = int(secs / divisor) secs %= divisor if not first: ret += ' ' ret += "%d%c" % (amount, unit) first = False return ret class Op5MonitorServer(GenericServer): """ object of Nagios server - when nagstamon will be able to poll various servers this will be useful As Nagios is the default server type all its methods are in GenericServer """ TYPE = 'op5Monitor' api_count='/api/filter/count/?query=' api_query='/api/filter/query/?query=' api_cmd='/api/command' api_svc_col = [] api_host_col = [] api_host_col.append('acknowledged') api_host_col.append('active_checks_enabled') api_host_col.append('alias') api_host_col.append('current_attempt') api_host_col.append('is_flapping') api_host_col.append('last_check') api_host_col.append('last_state_change') api_host_col.append('max_check_attempts') api_host_col.append('name') api_host_col.append('notifications_enabled') api_host_col.append('plugin_output') api_host_col.append('scheduled_downtime_depth') api_host_col.append('state') api_svc_col.append('acknowledged') api_svc_col.append('active_checks_enabled') api_svc_col.append('current_attempt') api_svc_col.append('description') api_svc_col.append('host.name') api_svc_col.append('host.state') api_svc_col.append('host.active_checks_enabled') api_svc_col.append('host.scheduled_downtime_depth') api_svc_col.append('is_flapping') api_svc_col.append('last_check') api_svc_col.append('last_state_change') api_svc_col.append('max_check_attempts') api_svc_col.append('notifications_enabled') api_svc_col.append('plugin_output') api_svc_col.append('scheduled_downtime_depth') api_svc_col.append('state') # URLs for browser shortlinks/buttons on popup window BROWSER_URLS = { "monitor": "$MONITOR$/monitor",\ "hosts": "$MONITOR$/monitor/index.php/listview?q=%s" % '[hosts] all and state != 0'.replace(" ", "%20"),\ "services": "$MONITOR$/monitor/index.php/listview?q=%s" % '[services] all and state != 0'.replace(" ", "%20"),\ "history": "$MONITOR$/monitor/index.php/alert_history/generate"} def __init__(self, **kwds): GenericServer.__init__(self, **kwds) # Entries for monitor default actions in context menu self.MENU_ACTIONS = ["Monitor", "Recheck", "Acknowledge", "Downtime"] self.STATUS_SVC_MAPPING = {'0':'OK', '1':'WARNING', '2':'CRITICAL', '3':'UNKNOWN'} self.STATUS_HOST_MAPPING = {'0':'UP', '1':'DOWN', '2':'UNREACHABLE'} # Op5Monitor gives a 500 when auth is wrong self.STATUS_CODES_NO_AUTH.append(500) def _get_status(self): """ Get status from op5 Monitor Server """ # create Nagios items dictionary with to lists for services and hosts # every list will contain a dictionary for every failed service/host # this dictionary is only temporarily nagitems = {"hosts":[], "services":[]} # new_hosts dictionary self.new_hosts = dict() # Fetch api listview with filters try: # Fetch Host info api_default_host_query='[hosts] %s ' % self.host_filter api_default_host_query+='&columns=%s' % (','.join(self.api_host_col)) api_default_host_query+='&format=json' api_default_host_query = api_default_host_query.replace(" ", "%20") result = self.FetchURL(self.monitor_url + self.api_count + api_default_host_query, giveback="raw") data, error, status_code = json.loads(result.result),\ result.error,\ result.status_code # check if any error occured errors_occured = self.check_for_error(data, error, status_code) # if there are errors return them if errors_occured != False: return(errors_occured) if data['count']: count = data['count'] api_default_host_query='[hosts] %s ' % self.host_filter api_default_host_query+='&columns=%s' % (','.join(self.api_host_col)) api_default_host_query+='&format=json' api_default_host_query = api_default_host_query.replace(" ", "%20") result = self.FetchURL(self.monitor_url + self.api_query + api_default_host_query + '&limit=' + str(count), giveback="raw") data = json.loads(result.result) n = dict() for api in data: n['host'] = api['name'] n["acknowledged"] = BOOLPOOL[api['acknowledged']] n["flapping"] = BOOLPOOL[api['is_flapping']] n["notifications_disabled"] = False if api['notifications_enabled'] else True n["passiveonly"] = False if api['active_checks_enabled'] else True n["scheduled_downtime"] = True if api['scheduled_downtime_depth'] else False n['attempt'] = "%s/%s" % (str(api['current_attempt']), str(api['max_check_attempts'])) n['duration'] = human_duration(api['last_state_change']) n['last_check'] = datetime.fromtimestamp(int(api['last_check'])).strftime('%Y-%m-%d %H:%M:%S') n['status'] = self.STATUS_HOST_MAPPING[str(api['state'])] n['status_information'] = api['plugin_output'] n['status_type'] = api['state'] if not n['host'] in self.new_hosts: self.new_hosts[n['host']] = GenericHost() self.new_hosts[n['host']].name = n['host'] self.new_hosts[n['host']].acknowledged = n["acknowledged"] self.new_hosts[n['host']].attempt = n['attempt'] self.new_hosts[n['host']].duration = n['duration'] self.new_hosts[n['host']].flapping = n["flapping"] self.new_hosts[n['host']].last_check = n['last_check'] self.new_hosts[n['host']].notifications_disabled = n["notifications_disabled"] self.new_hosts[n['host']].passiveonly = n["passiveonly"] self.new_hosts[n['host']].scheduled_downtime = n["scheduled_downtime"] self.new_hosts[n['host']].status = n['status'] self.new_hosts[n['host']].status_information = n['status_information'].replace("\n", " ").strip() self.new_hosts[n['host']].status_type = n['status_type'] nagitems['hosts'].append(n) del n # Fetch services info api_default_svc_query='[services] %s ' % self.service_filter api_default_svc_query+='&columns=%s' % (','.join(self.api_svc_col)) api_default_svc_query+='&format=json' api_default_svc_query = api_default_svc_query.replace(" ", "%20") result = self.FetchURL(self.monitor_url + self.api_count + api_default_svc_query, giveback="raw") data, error, status_code = json.loads(result.result),\ result.error,\ result.status_code # check if any error occured errors_occured = self.check_for_error(data, error, status_code) # if there are errors return them if errors_occured != False: return(errors_occured) if data['count']: count = data['count'] api_default_svc_query='[services] %s ' % self.service_filter api_default_svc_query+='&columns=%s' % (','.join(self.api_svc_col)) api_default_svc_query+='&format=json' api_default_svc_query = api_default_svc_query.replace(" ", "%20") result = self.FetchURL(self.monitor_url + self.api_query + api_default_svc_query + '&limit=' + str(count), giveback="raw") data = json.loads(result.result) for api in data: n = dict() n['host'] = api['host']['name'] n['status'] = self.STATUS_HOST_MAPPING[str(api['host']['state'])] n["passiveonly"] = False if api['host']['active_checks_enabled'] else True if not n['host'] in self.new_hosts: self.new_hosts[n['host']] = GenericHost() self.new_hosts[n['host']].name = n['host'] self.new_hosts[n['host']].status = n['status'] self.new_hosts[n['host']].passiveonly = n["passiveonly"] n['service'] = api['description'] n["acknowledged"] = BOOLPOOL[api['acknowledged']] n["flapping"] = BOOLPOOL[api['is_flapping']] n["notifications_disabled"] = False if api['notifications_enabled'] else True n["passiveonly"] = False if api['active_checks_enabled'] else True n["scheduled_downtime"] = True if api['scheduled_downtime_depth'] or api['host']['scheduled_downtime_depth'] else False n['attempt'] = "%s/%s" % (str(api['current_attempt']), str(api['max_check_attempts'])) n['duration'] = human_duration(api['last_state_change']) n['last_check'] = datetime.fromtimestamp(int(api['last_check'])).strftime('%Y-%m-%d %H:%M:%S') n['status_information'] = api['plugin_output'] if not n['host'] in self.new_hosts: self.new_hosts[n['host']] = GenericHost() self.new_hosts[n['host']].name = n['host'] self.new_hosts[n['host']].status = n['status'] if not n['service'] in self.new_hosts[n['host']].services: n['status'] = self.STATUS_SVC_MAPPING[str(api['state'])] self.new_hosts[n['host']].services[n['service']] = GenericService() self.new_hosts[n['host']].services[n['service']].acknowledged = n['acknowledged'] self.new_hosts[n['host']].services[n['service']].attempt = n['attempt'] self.new_hosts[n['host']].services[n['service']].duration = n['duration'] self.new_hosts[n['host']].services[n['service']].flapping = n['flapping'] self.new_hosts[n['host']].services[n['service']].host = n['host'] self.new_hosts[n['host']].services[n['service']].last_check = n['last_check'] self.new_hosts[n['host']].services[n['service']].name = n['service'] self.new_hosts[n['host']].services[n['service']].notifications_disabled = n["notifications_disabled"] self.new_hosts[n['host']].services[n['service']].passiveonly = n['passiveonly'] self.new_hosts[n['host']].services[n['service']].scheduled_downtime = n['duration'] self.new_hosts[n['host']].services[n['service']].scheduled_downtime = n['scheduled_downtime'] self.new_hosts[n['host']].services[n['service']].status = n['status'] self.new_hosts[n['host']].services[n['service']].status_information = n['status_information'].replace("\n", " ").strip() nagitems['services'].append(n) return Result() except: self.isChecking = False # store status_code for returning result to tell GUI to reauthenticate status_code = result.status_code result, error = self.Error(sys.exc_info()) return Result(result=result, error=error, status_code=status_code) return Result() def open_monitor(self, host, service): if not service: url = "%s/monitor/index.php/extinfo/details?host=%s" % (self.monitor_url, host) else: url = "%s/monitor/index.php/extinfo/details?host=%s&service=%s" % (self.monitor_url, host, service) webbrowser_open(url) def get_start_end(self, host): return time.strftime("%Y-%m-%d %H:%M"), time.strftime("%Y-%m-%d %H:%M", time.localtime(time.time() + 7200)) def send_command(self, command, params=False): url = self.monitor_url + self.api_cmd + '/' + command self.FetchURL(url, cgi_data=params, giveback='raw') def _set_recheck(self, host, service): params = {'host_name': host, 'check_time': int(time.time())} if not service: command = 'SCHEDULE_HOST_CHECK' else: if self.hosts[host].services[service].is_passive_only(): return command = 'SCHEDULE_SVC_CHECK' params['service_description'] = service self.send_command(command, params) def _set_acknowledge(self, host, service, author, comment, sticky, notify, persistent, all_services): params = {'host_name': host, 'sticky': int(sticky), 'notify': int(notify), 'persistent': int(persistent), 'comment': comment} if not service: command = 'ACKNOWLEDGE_HOST_PROBLEM' else: params['service_description'] = service command = 'ACKNOWLEDGE_SVC_PROBLEM' self.send_command(command, params) def _set_downtime(self, host, service, author, comment, fixed, start_time, end_time, hours, minutes): start_time = int(time.mktime(time.strptime(start_time, "%Y-%m-%d %H:%M"))) end_time = int(time.mktime(time.strptime(end_time, "%Y-%m-%d %H:%M"))) duration = end_time - start_time params = {'host_name': host, 'comment': comment, 'fixed': fixed, 'trigger_id': '0', 'start_time': start_time, 'end_time': end_time, 'duration': duration} if not service: command = 'SCHEDULE_HOST_DOWNTIME' else: command = 'SCHEDULE_SVC_DOWNTIME' params['service_description'] = service self.send_command(command, params) Nagstamon/Nagstamon/__init__.py000066400000000000000000000015471316117564000170600ustar00rootroot00000000000000# encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2014 Henri Wahl et al. # # 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 """Module Nagstamon""" Nagstamon/Nagstamon/resources/000077500000000000000000000000001316117564000167525ustar00rootroot00000000000000Nagstamon/Nagstamon/resources/CREDITS000066400000000000000000000064711316117564000200020ustar00rootroot00000000000000Some lines of code are written by Henri Wahl @HenriWahl.

Thanks a lot for contributions, patches, hints, packaging, testing and ideas go at least to:

Andreas Ericsson @ageric,
Antoine Jacoutot,
Anton Löfgren @catharsis,
Arkadiy @arkadiyvleonov,
Arnaud Gomes @nono-gdv,
Benoit Poulet @BenoitPoulet,
Benoît Soenen,
Carl Chenet @chaica,
Carl Helmertz @chelmertz,
Christian @idl0r,
Christoph Handel @fragfutter,
@cventastic,
Davide Cecchetto @dcec,
Duncan Ferguson @duncs,
Emile Heitor,
Erinn Looney-Triggs @erinn,
@foscarini,
Gabriele Tozzi @gtozzi,
Georges Toth @sim0nx,
@janhenkins,
Jan-Philipp Litza @jplitza,
Jake Murphy @Murph9,
@jeromelebleu,
John Conroy,
Lars Michelsen @LarsMichelsen,
M. Cigdem Cebe,
Maik Lüdeke @MaikL,
Martin Campbell @martin-css,
Mattias Ryrlén @mattiasr,
Michał Rzeszut,
@minibbjd,
Nikita Klimov,
@oernii,
Patrick Cernko,
Pawel Połewicz,
Robin Sonefors,
Rym Rabehi @RymRabehi,
Salvatore LaMendola @vt0r,
Sandro Tosi,
Simon Oxwell @soxwellfb,
Stefano Stella @mprenditore,
Sven Nierlein @sni,
Thomas Gelf @Thomas-Gelf,
Tobias Scheerbaum,
Udo Stachowiak @studost,
Vladimir Lazarenko @favoretti,
Wouter Schoot,
Yannick Charton,
@yasa1987

...and all the bug reporters at GitHub!

Also thanks to the great thirdparty modules used by Nagstamon:

BeautifulSoup: https://www.crummy.com/software/BeautifulSoup/
Requests: http://www.python-requests.org
Keyring: https://github.com/jaraco/keyring
emwh.py: https://github.com/parkouss/pyewmh Nagstamon/Nagstamon/resources/Info.plist000066400000000000000000000012121316117564000207160ustar00rootroot00000000000000 CFBundleExecutable MacOS/Nagstamon CFBundleIconFile nagstamon.icns CFBundleName Nagstamon CFBundleDisplayName Nagstamon CFBundleIdentifier de.ifw-dresden.nagstamon NSHighResolutionCapable LSBackgroundOnly 0 CFBundlePackageType APPL Nagstamon/Nagstamon/resources/LICENSE000066400000000000000000001323301316117564000177610ustar00rootroot00000000000000Nagstamon is licensed under the following GPL2: 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. Copyright (C) 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. , 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. Nagstamon uses BeautifulSoup under the following license: Copyright (c) 2004-2010, Leonard Richardson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the the Beautiful Soup Consortium and All Night Kosher Bakery nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE, DAMMIT. Nagstamon's experimental Zabbix support is based on zabbix_api.py, which is licensed under LGPL 2.1: GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 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. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, 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 and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, 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 library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete 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 distribute a copy of this License along with the Library. 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 Library or any portion of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, 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 Library, 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 Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you 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. If distribution of 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 satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be 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. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library 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. 9. 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 Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library 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 with this License. 11. 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 Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library 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 Library. 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. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library 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. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser 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 Library 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 Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, 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 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. 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 LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. 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. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; 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. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it!Nagstamon/Nagstamon/resources/close_template.svg000066400000000000000000000132131316117564000224730ustar00rootroot00000000000000 image/svg+xml Nagstamon/Nagstamon/resources/critical.wav000066400000000000000000000440541316117564000212720ustar00rootroot00000000000000RIFF$HWAVEfmt +"VdataH(~-y})W;d#v q#]/+-t(n". cRMvL Ϧ %g'\Ee"J, 4$]GϘ1(6c[$#=Mj Z'!K,9R F""հf$9pWkq$jf)<Zˀ% `Jo#Gm(Uf-Xx+d%SN.θ^6 Ad bPc#!(uI]);T# ~9j !unU9t )#* !q҅P %jף %W(-,(e '-t!,~ ,+W-B*|Ҹ$}ȭ '@g:&4}ҵ[> &Ϸ 6w K$հY2 24B1& -e\gi1 Ia AQ'qS] d+)u#ڽ&D'Z%8n& g 2ҧ_ t ,zYz  )_ mxj$AԢ" + !S*\*+/2Y1rՐ/˭$y͚&8̎*>$ &{Ч$X;h" g;'@>|R "([~cڥ}   90UlRsh؏= Uh{"*K;aH $!*w(^!+%*$$"DׅcT"\ r"/%.zk -sv+A|CJ0dX < }  S%1 [km Oy & d٤"x.)ɟ0 BM$`${>)+v)4f1P+xL!b ǔ (n&>"e "gО-A%|8JOq̛ !O{,K  !RS=ao!v+BqOn)(ս" =:TR }2N #i.]$C(.$"uq xҒӵ"V3ݗ4. 0 6pZ_Mۈ0G A4K,؈, 655#elК[)A(R4ێ /%Fy MKn 1_^#t'+G*51R-4@kG3 lΛ* ɬ̈#+լ" (͏\ !+ =5  xNxt  3Nb5 )3Лs af\X4ԯ5*b"B:3E!R>#؊Y I)*)[̸qւU m) zд 3t6 $!+ٰR }q[*qFoL v'J+uq'[  X$ %ޯ%.~EvHϳ F&x0596L>c,>JҸ- ƣ([ҏ#BV,N%}!="*z !p #E}@!_Ә" %,x5J01)Z {**#j҇7Rp y&ө!e }'ڗ%f޽&ҳejβX >-ԏ &UdKo r + :s #&8y #H _$dm+\ ].u΄A> 7-. ,!B;oV~7Zz_ks(*{J026$e. >s m)t %fVFf@#ry) 8һ %s>%L EO _ J5e"b0p?"LMTן~  F.)He$i!ܮ)a ҫ! ų/I&NX.T8 ("Cي $HW#59~ HtP6&=|jx'( ҜB F .a܁2W b| K(՗ X #% # uG*T(gC0,I*I0' vZ~!Q(ncM#$b] `3'=qؒ. U! _+zW[49$x|#Ufجu.[ e !,<*%1;6+f/#cէ % 5/@? "0&ն'@A?!֞ ! ] )4\rSцL 3Fzk$Mۍ #ml{  #Iq9 & `\j$V&(*,,O:0fՠ5^QrM"l!f%:٣Yj).oVr"\ +7!E: \NwnR F]fpm= F~%t٨,+ ?0 -6#߰ !Q9W F)52K n-q4N.=2j?O%(|L *Z8!p69M&g@g &<hcg #ޯ 'N$  7oܬ$",[M7= {TW;T0#".A\2۵1m^#N'ː#(!~,r" |@h99 !.xct |5 )[y%loֺޣ )G0 ַu( / !'Ӯ )b0#d*ސ84#נ0f`%"-5; { $U*$oӈ$$4 g3'D,Bf ,-/rFnH<+3 A3U4Y5!s6U!JAN*(U)щ+۟'ב567k׃*"FU&8 \"-mZ!pu w W#/ '_~q  #cCzC])5+{3Ng)"`k &q+Ztc+y"q^55#EE /H#"ƹs (,$Ȏ!OC:U .#!hЛ:)&\ܫ  7 STݶA7 >.'nc@ُ+. &.wq!l8Qܷ "uG҈,=>'(,YׄS(jn9'3Ծ*#RR"'+ߍ [$qx{ 4C$y . @( "` ( % y wiSh|!\ k$_h-ֱ2,5{+AMU-: C),K^P,]B+"&Ԃ! Uɡ7Qnkc;JW&q| &>)l#: zEN E' qܰ% 8"st7 F]2WYtW &1/ %$BHm#';s :t#ҪK-U"u<.7҆/ Z!$*.ff5 O$]sۮ!7 -Ӭ+v7l q,gHІ~ U#"yՒ*-[C)m/:(ې)Iێ+ 1^&Jg!(kq# {W6Ht! 5_ \Җ`&. XJ'Viqzn,G " H6R:l7 )xNtg4Fѩ( Aܙ&j H ,*Ӷq'$hK|0w9'93v8# *z5/%49 =p&ݕ W΍F*,ʮ #H B$& m&!:8rJA?+ #'c4*psՁM?8 ҅'b# {!,ׇ kjY-:/%g'+ܚDfON&Q1)Q&X. c.@.#= ) C)D3VeBe*^֑!f@&E&o ۾,(W ! ' W֖x)&  ǡ3 q:H98rߪ;1yh,t],t&-L vʿH& 9{% g<|g $D>,{#TxPQP$( ^PE%G3 )"qR!h+jG.VK* S!()ی>#ϰ[/. c(q[0XɢD#UM.+Ea-$ Є #h-B"ILv,wpO!@0** n!N TOT*I$S'$`ԼڀQ%k%l*u<(o%͓?Yh <5ʁ68Ԓ7M9 #/_)38+" E&  # ߈؇>T&0.AW!=|d cG# :ߍ '!"a$, 9,Y&_,0'"2c(!)ҪsĬ  $= k  )L) p=C F%'04dBkx ( -bc {<)$$˭y)m94ܽ F+I !^|,Ē 8v:c@ջ1\0 C8c;V $.) 4" ;,c  F %x_C41 u.j$ +LXص '*?.4B!H #4Q!!Ϸ*"*.CnCxmAi:Hv.؍ Jn >b g -V *A"S9T h-|gݝ Je%$50K%  'h]HN  /`i C69?.,L/3p #*{G Ǭ9, ڏ  Su\m% 6wHG 1Fz>("R#)~s5r̞62'Ã O$ ׍ +7=lAz-$א-`ױ&JAux ф2\3)'.h^_j3x  6' Ds)Cp S=$3^()s_s$١z"z] Fͬ\o t+nğ`5b9} :*(1- b9 =jOڊZ }EcJ*ylʹa\\/ z* Alߑʖ] J* N+ϳ "lA5,uL|$h&(ɸ6t`0.#Ĉt+E#/$;]/))DYUK(7;Շ!Fw?  W %R0''( AZ$_ #r*Z/g!~Dk ]&>[$$ ;^Q.#f< +Bƶ v$60;.83>0F6 S % }:Ж'y jm lpY1͌. E NTΪFݿ' #tώ#& ːP5zn*R l)s(AP$af0$zP.{,ǐUb ~CԈ#ix wwEH B Nk!]{ &s$݂ "3Ϥ Rg@lcYu%,L n=Ҁ'0"W%!'H_/U`0-d7*Cu "@g2 Cjԋ Q 5(<Xz!p2s"I. Id#?40j s-0e=*R+@T s!b4Xϑ8#WC G 9A]!˝&-W/ٸ(ܤ/odw- 1{!<jP"6 zvqQ(Xl L)=WK " S٬Qx+#[' "4@>#VS_P&-m \6'~8 ?`RI"5X(ֿ0+3 6+i-V l"$ғ ! ̾/%w ׅ h Rh/ !zSs 0 ( 8NRegf !& ވڴ +Є/lNݦc/9 \Z qӐ/*]2'q4$g*8r՝)GWЉTZt^]G!&ڋx GA[ lZZen  $ Nk&O l <Y2s;Z2v,%o 35*L N~B+(@2rYWW:X>dP&{h>-:y0wq6d:uH  Vz~]##{@/;6 \ U9 vF @=vK ,9 %  RBj3 Ym %uDI > mr!)IQqg ?;tE"_ IY7 *2'V4;A_H+q ;12V h%^gWg'x> F N h%0x <t^sS`g%M'%Y[ G fR*Oyh1rf# f* Er*c7Z[ ) M: c m!@%61Bxv0EUx wT4. FzK}99u?V{dgE?DO4, N  7  #y +)@_%`veN* @H\E!^S[KQ~ QC/H>*+jw{~Acz\Fh; } -or JX VGU`kWTm(F 8+S Zmz &"|K `Hy n&n@V#X0 9 )6Ew(G`:EUKACh>lTfG!\{?M~Y_\Y#  9g_0 Z,M\pO ["Iuip~x$_o!"l 7IdP //6RZ  2i!GNf &7I /EY?O3 ^cH *szj.T(|_>j?AOaR |1 N}IR6E 'y3H3tYV ,B1OQK1I |[-+D+*!w? C wU| Hi#NkTt~`7^u*QgQ)g55=O=D4s[OF(-5jgn .ngCRK0?)Pk< Y;`l:qk 9v yZ" y8 &6<vu[J,D9=pJv_2*/,,,WoqFz3m6A M}>\>sZlWffQs~neY9'vo2['+%VR:1[F Qf=rBYa=LnPXclHKE<*~&RH BGy)99U||=43zP]QR/7dy EaU7OJgnH ?+U^N04cJ/ozp-=teM63 .JyH),w~jIZ_#VB06EuQ0:XS9p d3C=/P5-X},B*cAQEY<@7F$#'i=@VE|A<\e=%; ^~IE<&(?P*/nut0KQqw63r1h) 8}w)_WB*#(xqGq:KV/c6dj .Z]g0oq)/3_;TB %P5OK+  {(gb/9[09";SbkRp02JaK%M2hKPDZA l;w1S8yYLt6~.?^M5Rtq<[D|CAZ9QcTQASYK"t}L)*($pgmPmILzLivk-Xp=q h(A5AF2gO&U&Gb#=A9D*$%}"& >NW>{: k!p=rX6 8)TF.S=BV^2`:W{(c0'*-*U}+f{I5v:31z&:  K1 ' ,&># '!0 E=6! !%#'  E M* 0"  Nagstamon/Nagstamon/resources/down.wav000066400000000000000000001122541316117564000204450ustar00rootroot00000000000000RIFFWAVEfmt +"Vdata                  %     "   %!0  #  "  2  8$   (       $%       0 !                       "   $ $*"$''-$  "    ",+) AA AD!6L( "((<;9%IRO> 0 2J@FR>+, E+1<C%3L";A*#"4. !B=&>@!4FaPYW;- /.<=8_Q$3<&CN;>c? B0 ,B/,RS IgMD&:oF;ns6.N $GRYRSdVALB6)!0,h~Z2lbG+% )64#Nw~]H[6   3@+[~V@BP( I`:^~J-e]>$I?8YoEGii1 E:KpvF E\Ei~aBdV1 7ZOPo~e`X8'?LhZQd{zseA*"8XmJbcQZ_=-) *MK{_?_cP5/ &6CXkbcztQ2792;lsA_{J+G. ;(9gb4YyrM;D"*Ei_J_sW\Z8 ,Murhj]GQQ- /Qt`|~h=eY> 3Sdc~IX~`6$, 2TQj~TjxU 1,GKp`al@0<Q2YuNJ|}^*%3 F6AlrE_yT8B& 2=b~zTbwna_G$)Eh~a_qYVWB'Gkpr^^^I-(F]gfWLslJ"&  +LYZ~M[w[)+ /@Rild~rJ'& (C3PwgJwo@/A2>Ab~YIrhA922=^~~^c{w]^G# )?^~nehROK<$Cgnoo?W_@#)Ljdg_CnoH!%HXMw^Mva12 )BMjlZ{xS"+:X;Y}aIym=&9 ?E?ayNR}}\28%5>^|vSTpkXW=)Gh_fpSPR: *Lqbq~rMWQ?2Qbbg{PJrd=$5ZbW~QYqV!.  &>Hbug\tiC.M4Iq`<g~h8 5 BD?a~Q@mx\4-,3<_~~YXogQT?+Cf~jckSLL<#Dfqi|QT^C&%Iefb[>bc< *R[Jr~OBfM!.LM_{iFhc; 7S5Fue7[f92AP?_Y9n~_2). >Je]XfcOS>(@a~e[hN594 Aet^yvU<A2 )IWeVk]=EbD  !GjV]~m>DO- 7RL^`>cW%3TF>uy=@yg; ) :PHRukC4ZT7! +:Yz~oXd_)'$0)I`lhumT42):GIZdM/9Y@  <PJjeA29- "*;TF6D@" 0@45:4+))67!2>IF<40,$)& -=IIFLNE840 +8HONOTRH=4$ .FTSW[\VPL?*:FELSXQX^S>17>=^dWF?4& 6:350,>>*!/! 5G&.0!#24-*$71('+3' %"!  " &!  "C* , -70B5&084 ,- (  $;LE<  #)!/C=&     #-73  C;BKQ+ LS=#)9*)-0,04B<<.4''MP9'  ;-+0)!0  0! >"P F`:0 /%C4+N#).2M+]O"XA"3.#&9JE.!$(! 6A+*%  (3    $   /,  "            '$  #+'      (     "# %#  ""     "  $     %%   *&%                                                      !  ' !                                      !   .+LE  -7,> 0$2K  * &8 0  )(#%$@;     (!(  %+.                   "          " ,1# 2" 4-*%" 2-     *#         8 C+  07 ML    651$32" 16')1 #I\B'AG, (<e&2f- T7SV3% "0/GKB<;.5U9Ba= < #Ya6"TM =F-+3Yk=:$(VR<GPF&I[67&#OR]^QCMG<- 421D_Q1fq[?4.0,gURy]@-/LN1V~yCDL& #T{_;Ws]NBA'*>vD6p~W(H< &6^j^Pk~lRE1 +Fb|uihd]QB$=]d~NCtZD$Bp[]bTVhS<1@J;kN<zi89%CCMq@d~d$F ,&<kh@g~xBA;7);ff>hyI3Q2(/QscLB^`faA! =f~pXixH;O?9\_[aASN.0G_jwDDj{T-0 $Oi_n~f\UO2 =NHg^Fna."-O2Ih;tZ$1 .8+718eiYtnD-J;%K`]_rE5X\2  )\vHZ~\+=L EXHi}I,aQ D^3R~WTuQ."QS?lN-iuM'$FIisNGTG9>)"/Fl~gRWB&-0>gcxtV>6+"3TdbSdb2:eN+"Lz\Th"6V*)Cc^d{c?XR -WF4pq*)mY)%EU@W~R;pL&%8=f~}TT\N7C0.El~lXSB396CmvkgDEJ6 0b{b^~J2`a4 #Uc>^H&[Q)LPZzk9Od:# +TE3no3=xe5*>X8NW%Qy\..2DPu~^DQaHNI' #5Y~v^h];AK&-Rx[xzaDL@. !F[[SoW8SrM$ )XmL`~O2WS! #CLRzpIakB. F36or0Gm7 =  :G. =ciQub5LtP'>eOQr;Qg@);QRu|VYv[!.;H0X|Q=t^'8 <C>f~SCu^4-0 2=azPTd\NR9(Cg~gdhP?F<@kn]~wKEXF.  %Kcb^{I@lvM#) +Y_PqPHdY'(#DLdwYwuL 1 $L;;po1Ul:6:A3X~K6i}[2'0 /:_R]l`SX>+Dg~mbgUFHC9b|YyR9YC4 =_e]ya;^~\.(&G\CZ\?[f>%/BEjsVOq_-*$ /@)PwR9pb*><@4Y}N=o~e<-8 .7Y~SRhiU^M)!.Q{~yglcKNV:  EqWic0ORC&DYR`p7G|k@!4)WGDu|?JiY"3!<Ae`Q~r@+5 *G'Bu\0jh,D=70\{?Fz~_68:+5]sETe_OZ@ 4Y|dZaPBFI  $JqPt{[1YN> (J\Skl7G~i  'VtYupS5?<43VWH]U*@wY5$ 8gI@{l+5[? 2*FCa[6j`)!4 !:DvLWzX> B4%Tq2[vN"489keEMOE@K6?lxYMW8+<:3ahXkbE18;*0@:1O5!AlM&  :a<=|V2V0.,E7]vA$aQ 0>5oTJ{[B A: Ly<J{XB75[|rAHMI5{a -V< 2!D5O|VSb/9B%%mk1zo0='1E#3uW-qh1=* $2FouI>EE3;I,:g~kQKO/&>9   )`iNgkJ(;E2 @ng'0_D,+/6Jx<WzP =D7'ap, QvK ; 18WvuHJH>2LL%   Iwy_QS09F3 6dWSla<6=> ">:5>T4/^lD RY2`?=X)1 6O@ot4(dP,2/9K|> U|MBI8%hq)Z|K*>94a{kCED=8L?&Wz`MP?$8<  LhLco[?@D7'>;7EW?:ajL% QP2k~B"?99-  186>GH@B?*G`5%;=@S]K7' (7#=;G,"(+?/#   $," (,#  ')    !$      +   '%   ##   %/" ',  &=226)*  '.%&/- ?7  )0* &!5* -M-!:J.   !"0G;$"-  &1#/ "          *& +      (168    #      $CK ((  JP&qFQJJ h!H(D&sf4 !;6gXP)d -) O  Y ɠ~Bޅ6 8+'?t d  #t m l IJ`v [  U  D /G>I=@N s K%< G5(Z {{j-*e"&u }{c f6'ׁ(*"P$ hb*,,_0l6L* [ +E;0 Msvl$oG .y\Ps5Y? ) `K ՏPRF =v,(UPRU01]O n8VK& h[  g]|]u`$(Wg O {W P *d7 yp `u s  Ex!]to j #bjYYig- FCd # 1|HJ+ D|s ")D7?s!hx s S!LEG tf`b:fll%$& f9  Y<L|dPm6yf x Y f U !oE98(5KS[OW $'9Nt= 2 ? b/ex,d;7~  e ] | u*^fRUi %VQM K M (JPTzC @ w G)LgsQ&w = I j ,\Z% T2z7GDS{"oplmt}{ytjqjtt+$ prV=!!)Al VZ&wu$MZv2/@N< M;W`jiH,nGrEwo?mT\7TTU&9;txI7I~{u'Tow7!#~A~ZTk e\_w{43qk|/,) L90)7+pmkh^ZUWV cWRI;8,:57F>=Bc=omhX(]`5)n z Ccnlo&%8]XIG+:F@Vi!#Ev'^b 8L HE@:?MV[Z{;|1jhhw=>(XEL5YvU454KZYon2/4kGqslf r:]vvBLyB#S++5W'$P<L@N*YU2&OOUxsB^<d`vtuA3V6e!%nnrls0>h_1$c|0P#;6Z}8}``]|\[TW~YE.2V?=9;r%&7&xZuo=O}O2j3fHMb|}wcz/3.nBCz/omY[{{BhB;{P`M8N;%=c#=p`maH'$"I~CYfF34#~Nul~jb:3 !( )$61%rL\KPc72:."5o_c`VK_bG8'/F/3U<AE80'$-EQUi_W]`HR=0    $   !#      Nagstamon/Nagstamon/resources/menu_template.svg000066400000000000000000000233061316117564000223360ustar00rootroot00000000000000 image/svg+xml Nagstamon/Nagstamon/resources/nagstamon.1000066400000000000000000000031631316117564000210260ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH NAGSTAMON 1 "2016-09-05" "2.0" "" .SH NAME Nagstamon \- Nagios status monitor which takes place in systray or on desktop . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .SH SYNOPSIS .sp nagstamon [alternate\-config] .SH DESCRIPTION .sp Nagstamon is a Nagios status monitor which takes place in systray or on desktop as floating statusbar to inform you in realtime about the status of your Nagios monitored network&. Nagstamon connects to multiple Nagios, Opsview, Icinga, Centreon, Op5Monitor, Check_MK Multisite and Thruk monitoring servers. Experimental support for Zabbix, Zenoss and Livestatus is included. .sp The command can optionally take one argument giving the path to an alternate configuration file. .SH RESSOURCES .sp \fI\%https://nagstamon.ifw\-dresden.de\fP .SH AUTHOR This manual page has been written by Carl Chenet and updated by Henri Wahl .SH COPYRIGHT This manual page is licensed under the GPL-2 license. .\" Generated by docutils manpage writer. . Nagstamon/Nagstamon/resources/nagstamon.1.gz000066400000000000000000000014261316117564000214450ustar00rootroot00000000000000Ynagstamon.1Uj@}W K#BclPȔ4Kb/}gvR!m3(=!а*f0BFXyI0p<6?ϰuqBQO(vQn0b mX-!`2r#K`54F-rsK#^(PڜL\(LX +h9gmb#2"H/mHSL7͂]3u/}9 ཱི5;bl3ςhc0뎒 pFe4ǎSCo8W{R=Lxp´w5޵!Oi;lןH>`=Qq4ă8t~fA%hg0Ɲ` #\M$3\,ULQHUVZͥ5`Jƍ^q\ #4:B)b+L/%rHJe *AwӠ"° m'6) nagstamon.desktop GFDL-1.3 GPL-2.0+ and LGPL-2.1 Nagstamon

Nagios status monitor for your desktop

Nagstamon is a Nagios status monitor which takes place in systray, on desktop as floating statusbar or fullscreen to inform in realtime about the status of your Nagios and its derivatives monitored network. It allows to connect to multiple monitoring servers. Nagstamon supports the following server types:

  • Nagios
  • Icinga
  • Check_MK Multisite
  • Thruk
  • Op5Monitor
  • Centreon
  • Opsview
  • Experimental: Zabbix
  • Experimental: Zenoss
  • Experimental: Livestatus
  • Experimental: Sensu

Events could be handled by instant access to failed hosts/services. Nagstamon is very customizable by several types of event filters, notifications methods and actions.

https://nagstamon.ifw-dresden.de/files-nagstamon/appdata/nagstamon-appdata-01.png https://nagstamon.ifw-dresden.de/files-nagstamon/appdata/nagstamon-appdata-02.png https://nagstamon.ifw-dresden.de/files-nagstamon/appdata/nagstamon-appdata-03.png https://nagstamon.ifw-dresden.de/files-nagstamon/appdata/nagstamon-appdata-04.png https://nagstamon.ifw-dresden.de/files-nagstamon/appdata/nagstamon-appdata-05.png https://nagstamon.ifw-dresden.de h.wahl@ifw-dresden.de Nagstamon/Nagstamon/resources/nagstamon.desktop000066400000000000000000000003341316117564000223340ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=Nagstamon Comment=Nagios status monitor Icon=nagstamon Exec=nagstamon Terminal=false Categories=System;Monitor; StartupNotify=true GenericName=Nagios status monitor for the desktop Nagstamon/Nagstamon/resources/nagstamon.icns000066400000000000000000005267471316117564000216440ustar00rootroot00000000000000icnsis32H}}}|}z{z{yvCAƤ<3Ң80|50\b!50EgN"•5171]K51ޖf61M܊V&62ٍU75ٛŞ9AǯGD}~|{~wB  3H~~}~|wCAĿ̥=3V^accddgh^91W 62Qlnsym4 s62iWf_x PG72vGv,'62r(h~62ߎX(62ڎV75ٛŞ9AǯGD~}}{~wH  2H~~}~|wCAĿ̥=3V_accdegh^92X 62Usx}r=w73U[__dh3 ~73TNRZJ74ZSA84I84K86᠇!:BGDyH   s8mkUH!<<<<<<<<<%qa$7GOMG6"il32UCADUDI~nDDJBMЮD9BļrCD̝DDѺ 񫚦DDϭܛ DDͬĀɀӂ DDͬÀxxxr DDˬnE DDɫ׀dkdm DDƫZZV DDŪRd  DDê RRMزx DD© L*7  DD DDǀ8 DD DDҀG DDҀ DD򾅎DD|DBʫ'|zrEASɴFIBQ{HD(87689;=@CFILKHFB?<:86784" !$'*.001.,+($   UCADUDI~nDDQBMЮD9BļrCD̝m``abbccdeefghijnDDѺg DDϭ] mDDͬ[ dDDͬWxiK7+@DDˬQnlE ?DDɫ[tsdedet^ MDDƫZZV_V wDDŪRT |V xDDê RRM^?V yDD© L*nJyDDn`{DDҹ4 DD DDҀG DDҀ DD򾅎DD|DBʫ'|zrEASɴFIBQ{HD-8788:;=ADGKNMJGC@=;98784" !$),022100-*$!   UCADUDI~nDDQBMЮD9BļrCD̝m``abbccdeefghijnDDѺg DDϭ] mDDͬ[ dDDͬWxiK7+@DDˬQnlE ?DDɫOdK BDDƫGZV& @DDŪCRA ADDêCRM# BDD©CRL*BDDCRG4 DDD  GDD EDD EDD ODD\DDb'$$%%&&''(()*++5|DBʫ'|zrEASɴFIBQ{HD-8788:<>ADGKNNKGD@=;98784" !&),022320-*$!   l8mk.3333333333333333333333)R8 a9xKxKxKxKxKxKxKxKxKxKxKxKxKxKxKxKxKxKh?+tR?|Ĺu4&5CQ^ju}ti\OA3#  ih32FDCDBDGACR ~FCE+BCjOC+BJڬCB+DƺSD+Bʝ{|jDBˢz쮬oDBʅoDB{܂사ënDB}デ߂ˬnDB~ჁʬnDBz؁znDBāބsssd?7mDBÂ܅lπV#mDB„ہfifmDBւ́_q_ՁmDB؁XXXP:lDB῾ցRm9lDB྽ցRlDB޽րRQm/lDB޼RQ6KlDBݻ . K6|lDBܹÁK kDB۸ρ<2kDBڷׂÁKkDBڶׂÁKFjDBصׂÁKjDBسׂÁKNjDBײ͂ÁK~jDBֱ~|jDBհΝ}{jD+Bڮί~{|gD B泬 }|zWD+BR׮}|{CD+DCµý[C9)DCjSCD=AC@4'  "!$(,/239:?CEHKLLJHDB=984/-+&#  # !&'+,/1355776331.-)($"     FDCDBDGACR ~FCE+BCjOC+BJڬCB+DƺSD+Bʝ{|uuvwxz{|}jDBˢz` uoDBʅ]oDB{mgnDB}qdnDB~l_nDBfz wcOD8*$nDBābsd?mDBÂ\lV#mDB„Wf[mDB_sh_vlsfmDBXrXXP+}nlDB῾Rl`9Q}nlDB྽Rlm}nlDB޽RQPM}olDB޼RQ6'\ xolDBݻ . 'AolD Bܹg'`okDB۸3*kDBڷׂÁKkDBڶׂÁKFjDBصׂÁKjDBسׂÁKNjDBײ͂ÁK~jDBֱ~|jDBհΝ}{jD+Bڮί~{|gD B泬 }|zWD+BR׮}|{CD+DCµý[C9)DCjSCD=AC@7'  "!$(,/27;=@CHKLNNLJGD@;861/+)%!  # $&)-0134678877530/,($"      FDCDBDGACR ~FCE+BCjOC+BJڬCB+DƺSD+Bʝ{|uuvwxz{|}jDBˢz` uoDBʅ]oDB{mgnDB}qdnDB~l_nDBfz wcOD8*$nDBābsd?mDBÂ\lV#mDB„Wf[mDBR_;"mDBKXP lDB῾GR9 lDB྽GRM  lDB޽GRQ0!lDB޼GRQ6!lDBݻGRK. !lD BܹHRPC.!kDB۸ 'kDBڷ"kDBڶ"jDBص"jDBس"jDBײ+~jDBֱ)s~|jDBհ8Ν}{jD+BڮvLIIJKKMMNOOPQQRSSTUVVWXYYfί~{|gD B泬 }|zWD+BR׮}|{CD+DCµý[C9)DCjSCD=AC@7'  "!$*,/37;>AEHLMOOMKGDA<:63/+)%!   $&)-01466877532/,(&"       h8mk (ĀN%{>fiiiiiiiiiiiiiiiiiiiiiiiiiiiiK1 K hE $6HS^itƼ|qg\PF1! '1CKŽڿPCCDDCqMɼzz{{|}}~~RCCDDCO΀ȳyz{~ WCCDDCPɴyzz| ZDCDDCPʽyzzZDCDDCPƤyzzԳZDCDDCP˿zzزZDCDDCOɷ{z|ȯZDCDDCOǬzzɍ𹮯ZDCDDCOŦ{{ZDCDDC OŤ{}쫊奉ͲZDCDDC Oĥ{쪊榉ֵZDCDDC Oĥ|쩇壆۷ZDCDDC Oå|맅墄ڷZDCDDC Oå}륂䟁ڷZDCDDCOڀå~ꣀٷZDCDDC O¥~}~|ދ|~}ҶZDCDDC O¤z|~z|{ɴZDCDDC O¥xywۊwyǵZDCDDC OuwxuwxywnzۍȵZDCDDC Ostr؉rtuvtj\L7AˍȶYCCDDC Oprprstl\K2"ōȶYDCDDC OmomՈߐnpeS:#ōȶYDCDDC Okmߏ`L/#ōȷYDCDDCO׀hjhчr* #ōȸYDCDDC Oÿfhg@#ōȸ YDCDDC O¿ceghec|Ά:$ǍɸYDCDDC O¾؉acbowec a4 YDCDDC OЄ_`_w^` _p٨1ٌYDCDDC O\^\w³_]^\;$Ǎɹ YDCDDC OY[Ys}X[e:#ōȺYDCDDCO€WYWq[XYXH!h:#ōȻ YDCDDC OTVTo|SVWR;:#ōȻYDCDDC OQT RmWSTUK. ]:#ōȻYDCDDCOPR Pk{OQ? :#ōȼ YDCDDC OPR PkO1Z:#ōȼYDCDDC OҿPRPkŀ\:#ōɽXDCDDC OѿPRPkŀ Wހ:#ōɽXDCDDCOѾPRPlƁD:#ōɾ XDCDDC OѾPRSO`S:#ōɾXDCDDCOнPRSRF+9H:#ōɾ XDCDDC OнPRSSI2#P:#ōɿXDCDDC OмPRRSSI2&K:#ōɿXDCDDC OϼPTRF1&L:#ō·XDCDDC OϼN@,&O;#ō÷ XDCDDC Oλj!&L>#ōøXDCDDC OλK&RY5#ōøXDCDDC OκD&#ōĹ XDCDDC OͺE&V$ƍĹXDCDDCO͹>" Ź XDCDDCO͹@#T!ƹXDCDDC O͸E&$ƍƺXDCDDC O̸E&[#ōǺXDCDDC O˸E&#ōǺXDCDDC O˷E&^#ōȻXDCDDC O˷E& #ōȻXDCDDC OʶE&a#ōɼXDCDDC OʶE&##ōɼXDCDDC OɵE&d#ōʼXDCDDC OɵE&%#ōʼXDCDDC OɴE&f#ō˽XDCDDC OȴE&(#ō˽~XDCDDC OȴE&i#ō̽~~XDCDDC OdzC#*!ō;~~XDCDDCNdzl464S5565Rэ;~~}XDCDDCNDz֌͉͆κ~~}|XDCDDCNDzγ~}||XDCDDC NƱΤ}}||XDCDDCNƱˑ~}||{XDCDDCNưϰ}||{{XDCDDCNʰ~||{{zXDCDDCNΰÀȀʀ̀ЀØ||{{zzXDCDDCMذMϷ||{ zzSCCDDCeK෮ó~}||{{zƹOCCDDC Iͭ}}||{{zzyŦMCCBA-BBCDc޺+~}~}||{{zzy{ŅICDDCPֲ,~~}||{{zzyy`ECYC Gwҳ2%~~}||{{zzy{ŔLCABCP'ֻ %~~}||{{zzy^DCCBBBBCCD^м-~~}}|{{zz}ÀnGCBAABCEcρʀɀȀƀŀÁ€ÁrICD6CD[߀ހ݀܀ۀڀـ؁׀րՀԁҀсπ΀́̀ˀʀɁȀ ŠeHCDEDCOi߀ހ܀ہڀـ؀ׁրՀԀӀҀрЁπ΀̀́ˀʀɀǻpSECDFD?ACBDOYhw~|reXPGCB@@+=BCBBDGIKJKJIGDBBC@2-;ABCA>4%  &,57:;:9:99:;-<<==>??@@AABCCDEEFGHHIIKLMMLKKJHHGFEEDCCBBAA??>=<<;;:9::9:9:;:9962( i   ! !"#$&&()*,,-/11346889<=>>@ABDFFGIJKKMNMMNMLKJJIHFECBA?>=;;9875321/..,++)''&%$""   /   "#%'((*,-/02346889;<>>?@ABCDEF/EDCBB@?>=<;:97753210-,+)(&&%"   '!"$'()**,,.001234667899;;<=>=<;98 6543320/.,*)('&%$#"      !#$%&()*)*+,./011233434343321 0/-,-,+*)'&%#"   +  "#"$%&'(())*)*+*+,+*+*)('('&$$#""      ""##"#"!"                     ACDCBCDiABCDCBCDFCBDBCH9CCBCGNPSTTSRQOMIDCADBCDL[}ς΀́̃ˀʁɀȁǁƂŀāÀ`OECFGDBCG_שiLCDDCBCLy߀ހ݀ ՏSDCBDCKڀ؁ցӀрρɁǀŁ€۠TDCCBB?HDCGyǀ׀۔NCBA DDCCD\Ҁ рwGC7BBCCK4۹TCBBCCWĀ !uGCCB9C Fv̀ ڟLCCA>CKŽڿPCCDDCqMɼzz{||}~~RCCDDCO΀"ȳyz{|{xwwxxyyzz{{||}}~~ WCCDDCPɴyzzyw{smklmnoppqpqrstu vx ZDCDDCPʽyzzwvqÙ{ZDCDDCPƤyzzutŘ{ZDCDDCP˿zzxtɘzZDCDDCOɷ{zyu˕}ZDCDDCOǬz{wl˓ZDCDDCOŦ{{z͐ZDCDDCOŤ{yq͍mZDCDDC Oĥ|xi΋lZDCDDCOĥ|xg͈lZDCDDC Oå}xfφjZDCDDCOå~ye̓iZDCDDCOڀå~ye΁eZDCDDCO¥yd~zMZDCDDC O¤zd|}}~{wtplhd]G"ZDCDDCO¥{dzy$z{|zskd]XSLD;4*# ZDCDDC O{dw xywodYM=-ZDCDDC O{ctuvtj\L8" YCCDDC O{brstl\K2YDCDDC O|aoppeS:YDCDDC O|`mnnbL/YDCDDCO׀}`jklaK, YDCDDC Oÿ}`hjdO.  YDCDDC O¿~_dedecT6YDCDDC O¾gspgcfhdc gpsstmZ9&YDCDDC O{q_`_j|o_` gi`bdMLdb[YDCDDC Ou]^]k_^ ]ihr YDCDDC OrZ[ZhmZ[\\{hgqYDCDDCO€pXYXfZXYXH#:whgq YDCDDC OnUVUcjTVWR;VhgqYDCDDC OlRT SaVSTUK. 0vhgqYDCDDCOjQR Q_hQP? Thgq YDCDDC OjQR Q_M1/uhgqYDCDDC OҿjQRQ_@ ShgrXDCDDC OѿjQRQ_I-thgrXDCDDCOѾjQRQal$ Rhgr XDCDDC OѾjQRSPRtJ+rhgrXDCDDCOнjQRSRF-([n% Phgr XDCDDC OнjQRSSI2YL *qhgrXDCDDC OмjQRRSSI2Yp' Ohgr¶XDCDDC Oϼ jQTRF1YM (qjgr÷XDCDDC OϼlO@,Yq)Ofgr÷ XDCDDC OλQ"YO (Q grĸXDCDDC Oλ},Yr+/grĸXDCDDC Oκn#YP  grŹ XDCDDCOͺm#Wr+epŹXDCDDCO͹,mf~Ź XDCDDCO͹>"R ƹXDCDDC O͸E&$ǍƺXDCDDC O̸E&[#ōǺXDCDDC O˸E&#ōǺXDCDDC O˷E&^#ōȻXDCDDC O˷E& #ōȻXDCDDC OʶE&a#ōɼXDCDDC OʶE&##ōɼXDCDDC OɵE&d#ōʼXDCDDC OɵE&%#ōʼXDCDDC OɴE&f#ō˽XDCDDC OȴE&(#ō˽~XDCDDC OȴE&i#ō̽~~XDCDDC OdzC#*!ō;~~XDCDDCNdzl464S5565Rэ;~~}XDCDDCNDz֌͉͆κ~~}|XDCDDCNDzγ~}||XDCDDC NƱΤ}}||XDCDDCNƱˑ~}||{XDCDDCNưϰ}||{{XDCDDCNʰ~||{{zXDCDDCNΰÀȀʀ̀ЀØ||{{zzXDCDDCMذMϷ||{ zzSCCDDCeK෮ó~}||{{zƹOCCDDC Iͭ}}||{{zzyŦMCCBA-BBCDc޺+~}~}||{{zzy{ŅICDDCPֲ,~~}||{{zzyy`ECYC Gwҳ2%~~}||{{zzy{ŔLCABCP'ֻ %~~}||{{zzy^DCCBBBBCCD^м-~~}}|{{zz}ÀnGCBAABCEcρʀɀȀƀŀÁ€ÁrICD6CD[߀ހ݀܀ۀڀـ؁׀րՀԁҀсπ΀́̀ˀʀɁȀ ŠeHCDEDCOi߀ހ܀ہڀـ؀ׁրՀԀӀҀрЁπ΀̀́ˀʀɀǻpSECDFD?ACBDOYhw~|reXPGCB@@+=BCBBDGIKJKJIGDBBC@2-;ABCA>4%   &,57:;:9:;<=>?@AAB CEEFGHHIJJKLNMLKJIIGFFEEDCBBA@@?>?>=<;<;:9: ;:9964)" j   !"#$%&'')+,../1335579:<=?@ABDEGHIJKLMNOPQQPOONMKJJIGECBB@?=<:98654220/.-+*)'&%$""!  1 !$%&()+,./12345799;==?@ABCDFGGHHI2HHGGEDDCBA?>=<;98643210.-+*)'%$#!   #!"$%&'(*,-./1123457789:<=>?>=$;;98976542100.,,**'%$#"     !"$%&%'()*+,-.012345656765654432201/..-,+)('&$#"    %  "##%%&'())*+,-,-.-,,++*(('&&%%$"!!    !!""#$##$%$%$#$"!!                 ACDCBCDiABCDCBCDFCBDBCH9CCBCGNPSTTSRQOMIDCADBCDL[}ς΀́̃ˀʁɀȁǁƂŀāÀ`OECFGDBCG_שiLCDDCBCLy߀ހ݀ ՏSDCBDCKڀ؁ցӀрρɁǀŁ€۠TDCCBB?HDCGyǀ׀۔NCBA DDCCD\Ҁ рwGC7BBCCK4۹TCBBCCWĀ !uGCCB9C Fv̀ ڟLCCA>CKŽڿPCCDDCqMɼzz{||}~~RCCDDCO΀"ȳyz{|{xwwxxyyzz{{||}}~~ WCCDDCPɴyzzyw{smklmnoppqpqrstu vx ZDCDDCPʽyzzwvqÙ{ZDCDDCPƤyzzutŘ{ZDCDDCP˿zzxtɘzZDCDDCOɷ{zyu˕}ZDCDDCOǬz{wl˓ZDCDDCOŦ{{z͐ZDCDDCOŤ{yq͍mZDCDDC Oĥ|xi΋lZDCDDCOĥ|xg͈lZDCDDC Oå}xfφjZDCDDCOå~ye̓iZDCDDCOڀå~ye΁eZDCDDCO¥yd~zMZDCDDC O¤zd|}}~{wtplhd]G"ZDCDDCO¥{dzy$z{|zskd]XSLD;4*# ZDCDDC O{dw xywodYM=-ZDCDDC O{ctuvtj\L8" YCCDDC O{brstl\K2YDCDDC O|aoppeS:YDCDDC O|`mnnbL/YDCDDCO׀}`jklaK, YDCDDC Oÿ}`hjdO.  YDCDDC O¿~`efeV8YDCDDC O¾`ce\CYDCDDC O_`a`Q/ YDCDDC O]^_ZD YDCDDC O][]R4YDCDDCO€\YXH% YDCDDC O]VWR;YDCDDC O\TUK. YDCDDCO[RSP?  YDCDDC O[RSK1YDCDDC Oҿ\RSQ@"XDCDDC Oѿ\RSI0XDCDDCOѾ\RTO: XDCDDC OѾ]RSQB)XDCDDCOн]RSRF/ XDCDDC Oн]RSSI2¶XDCDDC Oм]RSSI2öXDCDDC Oϼ]RTRF1÷XDCDDC Oϼ]RSTP@,ķ XDCDDC Oλ]RSTSH5$ĸXDCDDC Oλ^RSSTSI9) ŸXDCDDC Oκ^RSSTM?2& Ź XDCDDCOͺ_TUTRLC;2(ŹXDCDDCO͹T,)%!ƹ XDCDDCO͹HǹXDCDDC O͸GǺXDCDDC O̸GȺXDCDDC O˸GȺXDCDDC O˷GɻXDCDDC O˷GɻXDCDDC OʶGʼXDCDDC OʶHʼXDCDDC OɵH˼XDCDDC OɵH˼XDCDDC OɴH̽XDCDDC OȴH̽~XDCDDC OȴIͽ~~XDCDDC OdzUξ~~XDCDDC Ndzo9ξ~~}XDCDDCNDz|κ~~}|XDCDDCNDz=γ~}||XDCDDCNƱwrΤ}}||XDCDDCNƱcUˑ~}||{XDCDDCNưgfϰ}||{{XDCDDCNʰ}VQ~||{{zXDCDDCNΰ {zyzz{||}}~Ø||{{zzXDCDDC?Mذ Ϸ||{ zzSCCDDCeK෮ó~}||{{zƹOCCDDC Iͭ}}||{{zzyŦMCCBA-BBCDc޺+~}~}||{{zzy{ŅICDDCPֲ,~~}||{{zzyy`ECYC Gwҳ2%~~}||{{zzy{ŔLCABCP'ֻ %~~}||{{zzy^DCCBBBBCCD^м-~~}}|{{zz}ÀnGCBAABCEcρʀɀȀƀŀÁ€ÁrICD6CD[߀ހ݀܀ۀڀـ؁׀րՀԁҀсπ΀́̀ˀʀɁȀ ŠeHCDEDCOi߀ހ܀ہڀـ؀ׁրՀԀӀҀрЁπ΀̀́ˀʀɀǻpSECDFD?ACBDOYhw~|reXPGCB@@+=BCBBDGIKJKJIGDBBC@2-;ABCA>4%   &,68:;:9:;:;;:;;<=+>>?@@AABBCDDEFGHHIJKLMNNONMLKJJIHGFEEDCCBAA@?>=<;<;:9: ;:9964)" 2   !"#$%&'()+,../1335679:<=?@ABEEGHJKLNNOPQ3PONMLKJIHFECBA?>><:8764430/.-+*)'&%$""!  0  #$%&()+,./1245679;;=>@ABCDEFGHHI1HHGGEDCBA@@>=<:8654310.-+*)(%$#!   (!"$%&')*,-./113556789:;<==>>@?@?>==<;:9976544310.,,**)&$#"    !"$%&') *+--.012344566765'43201/..-,+)('&$#"    !!#%%&')*)*+,-.-,,++*))''&%%$"!!     !""#$##$%$%$#$$#"!                   t8mk@ =~DzV! Y&,Q:j*[ 6S;4 u );>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>6! ^#m %3w q3< "l4  >ޣR*  +=Tu|aD3!  #)2:AEIMQVZ^bfkosw{¾}xtplhd_\WSOKGB=6,%    $(,159>BFJOSW\`dimquz~¾{wsokfb]YUPLHD?;72.*%!  !%*.26:>CGKOSW[^bfjorvz~{wtplhd`\XTPLHD@<840+'#  #'+/37;>BFJNQUX\`cgjmpsvy|}zxuqnkhea]ZVSOKGD@<841-)%!  !%),037:>ADHKORUXZ]`cehjloqsuvxz{|}~~}|zywusromkifda^[YVSPMIFB?<851-*&#  "&),/268;>ADFIKMPRTVXZ[]_`acddeffggggggffeedcba_^\ZYWUSPNLJGEB?<9740-*'$   !$&),.13579;=>@BCEFHIJJKLLMMMMMMMMLLKKJIHGEDBA?=<:8641/-*'%"   !#%'()+,-./011233444444332220/.-,+*('&$"    ic08 jP ftypjp2 jp2 Ojp2hihdrcolr"cdefjp2cOQ2d#Creator: JasPer Version 1.900.1R \@@HHPHHPHHPHHPHHP]@@HHPHHPHHPHHPHHP]@@HHPHHPHHPHHPHHP]@@HHPHHPHHPHHPHHP ߂@ }Vqw1}Riŭ!x !lelW8pw)Ϝ< 듛x[$??߂@ ֟twë t=ä\fW`<:m+.>*@tn u񦠈ɫ.o߂@ ֟Z\Wz{8Sm]o$f zOyIStSq6|e,0NX~U6߂ V0;`!*N%E"ܷqdz }irx6AP`ba\_Z؜ߙKd!(ze(\?ÉK{1;p;Vs>G 6@q^Ȓǣ4DV;  }x3ҸpBY/PBaϔxv`2GwOvߑ{SvEK`XbmB$[uVa Ց9E2w_RF@x$&$ زy K ٔbhPr%{*R&wmxY*H+~ _愀֣7`Fkͤndǥ۰B,hZst':h,AU$SST\YXhYdo,wz&@U'y~c)6} K;~6ׅٮHֶ߷M/s{DHNqD4ٯVx[|;vhìp#z `uLA>I ]x~7$#y-ScA(Zh?x u)NkiΘ]2ۛ)@tdj hT-RMdc&o²•ݷ;ob+}M./&r=ْ:"qP5n}!11TgOKTqLDeXD&9ivOgR[$dLQ5C si_1ZVpZ_$A'!T"TKui z1k=[UoV"47 68jGhd}0"wsyi@b!R☠˼p٭ 'rWA2j嬄Ί:> HlN["Ԅd/yeA *“IU:Q} FaUT+-,0ZoD2Z O몘Z8\ \뀈ꕧ=ӄ夤XqQ윴]o 2CIXNl7 Yrp`ĺ..i*|.Kf ZvhrO)6A-U,z@ĝvZgxlc}10 AkM=N{p&wW_C=uLC8;d܏69 >aP`]uF*66 M:oEz7<4&N8' 4ac%?4|7Եâ:W֎V>*T lvv"Đ<ʇdJ~ q`H(|#>яk(|3Ll@[ZG-kawBD)kf՝@duq3jI֛MY h1J@< EɉkZѯK:X!=`W_Fc+|=>o%^. .wX+ Kִ拓=4Ҕa~"0̬Ke=*{'QkhB Ccè}o[|1kbAm#k\zqh{PUfW `gwS> Ʉgm-ݠN5Š4`= ݤH { qĕQ!dt*I aŹ9gM%5gn),ȸ&m֚W᫘xqPKOySdz=4]|h`Y>iJ,Z@BX8hȶ/D*q̸hBmCwC@K; >iyQx Rak:8IeE[h,g2bp{;['Kf~,h؜bk\n\*eo D }2,{fȿiAB,7ʭ>pǧ6bT80Nk pH[ʒ '/ڵjJ43 ;\z@wy>p|slc"tSH,l Dn#nlQ3[bN.zӳAcxK9KC' kb4dY-:M-bZi~i>~6h2Cr&ͬC.ǵţ*62/#7 Si"1m(cmw'?ǡ攷ZW[A n}oج<f9|34jz g6cMeEih#s\>"UhcӔaluRcl/_6{mCovH} S\嗧 a/ ̛5SƔ8^]CdbMr&ZWP0u_Pc=M%*վĎdB$ m$: iW?bݩB[r2SZ4ٯ!;UQ*$k ` ~귖k 8[~NHxRP{tafhUqaQVaQViβfP:%+62>yT{^?:+`O@9#Hȣ{Ы5R/eYd%|/x mɊ6& LYh׼k>Raa\z* i`@L 6Dh=7Ƥ1A9dZ͗H3>_o qm8.dJ񦁯ҕ/CO V^7.}q/ꗮ#rA;iunȡ@9^g66ki0Y?(gyӝi<4a*Xr'kY+rs]nVU3f{wTȅpJ:o*d3u%,Ӵ4j=5@~am1癲P't:|۔ \b6h Qn-UmYfgnT=Z^G4tn.JhD&@ M1k&#Q#a5QȒ2p2r>1ikB²7g.+iFL31- K*ƲCg@w8z4I+?7Z֩H ?̥P/Pܠt:S Yn~理]6w2{@D:wJ:VۏT3khjc֔/8)gXn}^?v_N!ݧbc~<l+9|-MƩ}!Q6t鉠pN&^ayK`Qq- L5 lB?/Nn˟;NF"6 OxЅjf)c9DTL2:E"KN!VR &8Pms4d-r|AŌ[SEPtϞ5nYk1aI0*I&O+ }|n~`0d+3R,aFh-"~\k5SD8eJsFbv P+IP(@1a /ۡ4?JWWӆtW>2̰MhC]b/;.+A3hh0+2A嗣Hc1`X(hTm|]cXl@eul#lʘSG&Eu t~i\0HY,> $6ȜW˫fޘrB'͑`tl6gJ%ZqEvL@ g=5Hٯ!;i)0*hS Dw8>+h$b4SK'\Xi7}"M@;iT/EL O2\`͓>wPb'*Q; ܢmkإosOTF:lչ^Ԏq֛m.<6.ew: ]@5L1 F䧩}G$Yt#/2Ds{@#nvpltB*!5ʟ.S9A/ Q5O8ڛ?z)W8 jjS D#k#ƪGA4;#sn xZ :2bTc"ozuYh4\dJ/.-(q)~lQ]F7aD).$V ٩5 N`[0 ~4JaS tԚI$v:Zg1/5bi>ߍ/ Qnh(_*dCFuyQC'lVOAN}~b87dSS\[yhH{aKƤŜ/e¿ @[p&ANd,]~8K6*(s:$̲@Et=Mƨ\ bFi -w.j` y8{dE}gG*jՒ̔D, f9xR곘* ?3)/Y63_Dd4x7D~5 %Zb]fd|r+E]hǃ& 7ɕvD&! y6Z_U؏LX[, nʈIPōgcfٶ\ݸbkT*u˧P:xQͮzjn"ʣoź-q6 >윎% aA{嗣Hc1`X(2{H4m,Ä\Zb,$*L۽-`"C =ws 6 ^BPLN6izKnWi0eH e+MsQݔf|7ʄgwA#GOeNI44JY(]qs4\T3ɏ hJ3569];t!݃!' +&k<3YՀ:*D_K@pm2).-<w"aIp]H.G,}&lspyk{d2ט 켙_6 ZT]/Fx k5$`g  +ϕn:i$I!5x .&nFX3@sǃcH$!||[) 9b<% f2XM*`H]O9e{KKS&[jb5.hOK-TE j\0 nxu>f'fků 7+)'©ITw8m#-nA) Zy/^}p3WZXbhӬsJB,N@_~ߟ}0{Q@]ٷY=EN%( s!qՓyp.2g./ C0D.Y&闤oPC9o6`Mw20{$yC^#㻈*\eA}j D}|/ ! gxH;E%֨_jF8:w\* Z[u$2<+8{/9x,"do& W7ȀIRkvKk\hWqLcs{:L~dQAm(JToPZM2#YyIzOptOΌR2Dg"p1ҳ܏mP40*JboxS lu 8 A0e'Ƅ]"ζT HEesĢ{ `nAp&V9#FGĐOF {ڂ@e -DZ]v%s|rCW$ۋp}XVPG;KFB0E\sאń_z"j#G/(Ψ%B>̝ aɤjZ>hQ* ֪A)G{%J־<2Sdh)#fW#YG#P:n1mB`.ֿeVإQ^eDD wr?xwLD8`Ы#'.OG>/j:IZQ,;RopdN*N*C4 YFӝf-R6o?a߻r靋|IY2uĮiwP/`Oia:2E\ʪlTة҅*kfq8l 9:rQ̏]&S輨?ag+"dIIB?'w J$Bu\F_f1-1ޏ#LP@̝2>:ʅh杷}LaPlzM;Dek5r5wBiA31Vs>clK]5r(ʛ)!Yu7/8S"εm$wm%.U5Ĝ_7 5+f$-AID8R^d8"<[;\V\|34V/mOIMJ-hpysA* qX+[ud>&Fd ժXiXk;"6ܻ_|-4eJ<`RyZ"K 7%MZVRs  &ls &C!C]>0-ij\jOpt Rfmx>F'g'7̏3ZܼAt=/}6Sg(KZf2KaѺ?aW.:7O^%{Y.÷si Ma@4YjN57W BkOД4Fݡ or_ Ugj2 ) 'lҜ1l)y@q~O&LITM4& iF 1s`vgRaoLH)?Tk~ -ׯnpW4pȾ/LnЈۘ*j5<@k Nv87itǬoE /Γ)죮`Y{FiYΠr5C&{AX-|,ftNPpfX&xJ-V.fCte| Y npgnq ?&2'sࠧz_ e`&x ˸eQtHk1 TMBR$J%Wn5mNV8&;߿Sƅa# rIVC>/Շ6"'`EKQR`̑(m|/V%i{bV|SlbcaŵGj igkWf 6ΦSR`8k{lZ'l'WJd""StUeyE4M3HIkj4\ -KVk\=$͆ >=,<sv{B*j$>Ix}zeϊj} GE1pz6qnrmm_UmcDhC|AȠXH~$GkHb\ysVu,Z0mZ[,p/rýXۂ/ 1Rčp%p@]w"yE+ָ:^$_SyUj,f&PNX2FP mp4Oj_N BA +9σ f(&H*>Z(3nh>t&Сc!E<9)g@vSXd]f˩$ +hRf?PZPbLd)a2eu"xc YuE&P~3hA) {!'ya?TNv ?STuw Bqt@lYz;љ"_)k:'B{u0YWqigoL}Գ僸E00N"!i&yJ!RQHN,NB46+oӨNVj F2TщH}ҢoR  kp lMAӡ3w_~8RJsvLZLOazYY, $` >l<݄b+>:GR([LB?'w#XG{qIJAOf#e'MjF? Y{wߊGPA2y }K<(h-sxT=:As ؇E]kyaƁ :q5 mcʽ^`8MYmPǒN|z񾚤LBײrQ?|O G,2zXY%T]3K_4;LnH@AdW%YEݾ[\)e/g7Q˿hQwWGWM>Oy,zcCAXԨ>80Z#6أbo-vbj9O8%XAj$zmmmmu)"8C#K*9FS + F+Zrᑳ)&sSTqgy<E8 3~1ӴW<rk^~lO wyBM'WfP->l񌼥mF d?L[ QږZ֌M0"ni!A$l,Xs= $ 8tB;&;Ef:oIy{pdD{J͆f4U i  (LO~?M@?H%C/OJs11d xQe9XtSDzź_CC5OKGfu䉠@͍nnzOXqu xu2a>l/]rJeW` Ts |0Ԯm°zo2q7*f#q\a;F ɨ,Z*ӝ xSO<3k}-"Y[b̓/OȰCΙuf (x2ǵ B}JOsB-F8e2++wOUNP2 |FpjIRYZ>aQ4S)OUg[#P(@7J{E<3Ө{[ S..MktV7yE>r2JSkuV`k&e)CG)߼J%ئ+)9om69 ȂLEw[uUMA!.8@~d7^#a']cx%(Tn2>'߼^ kKwrҳU!95**e}^xZV-yTP+d}7fchN>+X'72u 2{PS]-•gB g\a,+&nBbȆuvfW6Ük]C?>V]w^ԫ P)AtT"C* k]Wn5%j,! nxf..M if Tb°v%GOJq_YH-ke.u&?~'m \j Ydak,k6-v2'۸_eΓI!v8tQ3a1FORAnAx@{BwFÐK߷&55(OKC_E'`AFzwŎ3n禺?#euA k2z8!AU~ t:/We5ͱeb-\H-Ws7yĴWh:Qs"YC& 4O|b8VHf{|yo`(I}:Y|\ pm"O a#!81Y ԁy A$Ģ#$nyH{O'~\~kSldVk-8!sI],]~qΏRٔG0f!͸ZyOd}{;۹A)r[S(yoZPRޱ1ٴ6SP#!}lwJ2BH'ahÂ=Eae> f, p}dfZ!mnԅIs3*G #8L {cX'vނOh>s)e) GDT(UMD*AYzT0](Sq+J+irW[M&XY[uz.'SxRp~ |ӏ/,VFa\`d'N4fQQ:q 70T`<5mby 3T950_D9n@{h bz\(` +9W-"Bz'gANOǸvogV1:r QR|ɋW61_4Q8?opְ0Pໟbkr2 ^Я:^/Hi8vWK 0< zp}Q3aU.&Fkx~zWC3Y$k.$X.#1<&,_g ܄Q6¡_H-]F(֩Qv@.k~`ƪ1 9k ɾX\22/DL=0(OCw.sxvmZ߿ ϒ%/}ܮ`3;^[K7Ӹ7~9&;wSGl5B³ k6h(/ 0M VP3v3-NK7ajFiLE|LhԵԨ#ii*Xl}Նť gMHP{~ɹ=@|^477B: ,N;'W8*9/0+#" Q,eİ)+0TjɢH'_&KðX,m|S~RmmmUנ  GZHzSU bGc,~]0z._|):cE+\XR?P,vxUlS?D_U2ےI$I$HT hW 3ޖye{U—9$51g-X;>!vF;ˎ ["k^KZu?|W# Sh ][@ex6 @ Mq1'0 J&+ W!`rv 6z) UQvi;n(Ğx׷~E{7G7iJMcċ L2 6!<5)-0]InQv4og X}4trU m@f>>hdux'}@-304n#&x`V;0Νr%`-\unMFy5?dM_H!B$r[եe'><ѝ- f9Raq=5Iv.H$2֧La]Mˈuy_\(b*o%xY*N|}kA{wʂ !?[>E7=T*c|O~+]XbR2`= _sgLh`07X$gDYO'u\!43ycu *Y &ӿ2oEdwܵH/PqPFA l܁ XrxZ>9I IY7wZdMyʁ{88g| oMCa94A^'4 R `}WMaW7jq8ߟ)Jd21_ۙ>7_b-(޿ء0z?~μ3l횂F/8Z3~DEXEqm$N/QV=0~Ks9>,1}Η`ܑRA-!o#~Ȇ@Kˋm ] {*P'8YwW@dz J`#<~"yuڷf2:xfCfomTkDIŴQ`>%,a3 [o>yn+zO?(Σe<RP^YB,^r@l2{{ٹ&Cg j0W~'  v "{N'l͖{ a1 G?AĽ塏$AU4GjVͷ* A2`dYEE$B$ћHY.20K^R򗔼㙃oPFyM +7x&ʧ%/)yK^R򗔼ȱw^csB6YT-][Kium.մU a A !:/A23צR򗔼/)yKU15¬dzs'Sth(:pIG., TDŏ$ QGfQoȞ_~EUq*=׷+9i%Phv9f[x^_ʏTazMzZ9c\oZloF6?(1Ik,ҾNV<&= -;͑x>@Vh`jأ?3؎赪?%`ރw)$I$I$I$I0+W3/vMW:{8&bA&gp~yQK1eTZԌ9n$I$I$I$I$`ҦCrmd 0r:C}Ll%*fKO&=3e fsmh>AХ? S=U b2qLŸY:j[xي 0m&nی~ݮ,H-^3Ap! D_AL[/򕏜]zwkmI sRgHhTFT̐";imm >;}5oUa?oNEuG}][`[;n서},}h1ý/E=]iQ0(CѐM_ <:$y(jqJ&4;G Dabv1wU}bv%39J~YT<>wbP]ooj)"ݪ 5lj"Y&nCϑzT` SQZǺIW䱀Bb& 8(A?]Ge-72T-D }D6OoJWgI7HwNTԈNF+H4/*/D8$gf?eyZ%H9'0*4abEA7j׃Zi͡}uX'[.~|?q۽Q~)- X+vR kry0a5н9Pܖ=Ybu8$iid9BI :=SCձ7HG>GƉŀgCmJK=xSZbipJiEa6.7;A|LDӺuS~;}-r PIQ낰H ]@&(\< İ;a*aai| H g>dAI%d ~*:e$C#k[F/_uh:NUODPvlrʐу1H;nr `OLGC: &xhFL2Zqƅ|R#,5t&t.b[$]8yMIq7C .V[ZP @$vJe76F1!֜*ZZya `Ǟ0~`i;,>Ed|NRyNJq%å8_&t0y. ҞA "̿ =@ ꁀ@=Y<0 a%\_=n{GQAXt+>"jF&ܐ4ٽy8B Y+pp M}}G1Gj@dz֢ɶv&: %q$nnL^;jcKRs2Mt"#oJ=S訠u d7)m Q7\UF :/88 sej?.[FҰt&d !NbZ.HE cHN"QpO`ǠySuX!a?y\OWb T͚ͣ踐|n$ '˴7]~.&]BAH@}~Ay`#GxMV7wiS\ Z@(̠u;ַ^ì!o'"Opef4Pʎ-*Ju-Z5G0 1:50;6/ЬQՃB!A_lܭܨf 1TKCD^aZӊ[UpS1F5҅egs\kҚcpBdҬC aڠ`Nd{2}Zd/btz޷ȉM'cȿ/ 0lK2k)ZeN(9~?Tam6?W;{cP;n$Aа#e+@ҙ)Οcb୙䄦{35\DhWj:L02woĶv%E{%oޠ(ef1qnJ[4< 6+QZottڹ J '=a!7izzk$᛭5 4eTl,B#.z_Ͼ6 GkbNeً zX]%+ga;R8Oۛƒ~'3kl*{> n?J͂ l፟6y!Ge?1apFܑrG vXj n&M **5 |Fs+:.u`W yQb "_U_OO'Hc:w)O#BU" N\/; 8bbw͓/qe эyYAB(KBwSCoO%tB|yCnٱA:2wPyS2j*?̣%@ߪ 9oxg~J\,9}!9Gj3M.&!( R4Stepo!{]XrvgZ jNl>@7cݫYU伎ZZ bQ23 By|gNUdlTH2xMȗj^ ZO1*Cx/݊+|ű룔D, ⊞}D8)\ HLʅ%C'L8a|`BA,^))ڏ@[\k4%*3㺈N3 0Q9+و# V7$,?;JxX=hAYDp .or-韭2&<}YXk[Q׃Jd:T vFolѷ'fn8*@] " &5yj]~`F]$!i5ZkrZH7!btgd_\n0ӺLE eZ'i3c0\%PfH,SH6׹A U@g@&>Vu,`l2P/{CyTrH;!B''ǐI e!|6od*N#6Wjś06`3~JvY!hyV7&v .YȝJJs)n?ҋMRd=Ғ&0X/6•7DxK;cݐl:Qa?>Q@+q;E`Ohp$E ɔhdD\/nܧULs"?YlFǠNYiABK*A-̢)>YqML&wyױcs~#BeHlzT$W-xIj^g*_"d8UWZ;ꗗ3m-D-#zy/Bod-/b` ܻqiw9fzPo[!9 y!,qP`^o@Tj!V"?u,̀ON^ '" hXÊ*&O3vf XE>zz߁LmRP&b4tq![wJ)" ,~[=N!yQ&YyB2`6ڃ}ņS(r\0G5r~UO,$&q{(!AU%|)6zF,XM6^Z0N;=ReKD0,TFgy#jq1s c2M6 W|!Z/cV H.hnÁח G%U=`E!/2i^'2S.֥Щ"n|ØVgU.L~`:∛{!`m#w۔$.}@cncJjbe+f!]sy/2dNђ5C˞DLz8(|&7+i(,bbðáikV5̐e dVHQUd~ΐusoHT}J~yS*vּށs_Ȉ]! ,*d ~v/JR1n8: 'z+`+ߨA.)(UyHob_l9jp"]UI!Ti"숯dqy} .VDZBSGtIP5K\KRV8y}XZ'yw{6}J/.n[hxMrXV}rr K,@%W.|TK\A>uv}4hS<[ͱ#N>;ayZ1bFMn_{Hz#u_RORY{5N/Ş#9ZQ! ?/=H5F~;Lb&Aˆm;9ngjOH+)wEg,JEuB9d!b8-5 ۴ڲ;3.xdPDf)^܌_uIrRTzgy[lz}+.9!>dAA.P^$I"ȷDGf-$\Dj K0JRfDёou:%ϝ$+_dUĴL1RtNVê#׫JVUE9+oWII ٺك²&8Qxؑ {3 lTbT5HU{>:RYKE#) :,v`&:*]eb+s,RV xs<ױsyu9bnjYyqgz#w$N)6w_"86+;n&rЈs/?4)खJ*gS z)J4_$Z^9h[mM(r|hq&^81۠T7O-C# iG[?icr8"Գm2<Hނ:)Y6&!5wlt ^Y_Z98ߗ~5v//UTj[\*uš]oc:TpثQ>mkaȬKf=q-c%s7>Z"r^&_QJ@#ӢR& _e0٥cE=݋`e,9O"*]X/m=v"* ՜6]Ȥ9S3upɸ1أ+p9>m(ZP:1/̋&&7݄3_ /(- w_>U h-잙i^^~0@Cy,|iZrlgg4C;7_;d[.qJq#ҋֽ+apf] d(P/AI='V”_IKj4EE}`tFqiH(34&RqeK|Q&QHV&Ѵg,0Z*lԱ |qܢЄ!n2jRI0-RuEzaU$f*o_>NEb)}wrfj/w^p ),gmQ^@c%uwa9h[N&Qu" @%$|ڼO&thE}#_"FOИ"~Cu~h(tj&wyq>مd ׃!*iNа9j*=tͅm=x݁0jm}"vw|FHJ̺)w Ko U:(e2VKmW:*L3}%SD:2yezT x('Q,Eu_q+0-0>qJk p`EU ;JD1jQ5%VO)B ~g5[zoRۖ4YgmrGmBE#}cs&K-Z>e o~Vi ]bAFS)aofƴ𓭇 Fύ0@Ayǒc!)G XN׆<6IG6w%e·p1L|F2_ވD_Zyk p7 oXkM}d_u>8]=ljhksfegA0nte}ɟWlJN"үQ1ˠ _ϕ,lUxC7#RD"mm&Z2rk:N"~8mPg?z0f'SgO:i3fB-:.nиrZɉ Tg1rBVq pM V@2_pd QkOh*\2kƞ)C9v35CQȥݘzW~4?<4*k [0$E.x[BG.h%{*_]Ҩas  akjV܁w, F& _Ug&P;s."<>Ԧh([FYDZi0BT Q@&Y+e/~+1D/e82q\`EEUҸٺ:3b׿a֜f}$O!f, ~v\ 7s@{Q 5 r'`^# +WuRLvxw?#2g& -qLZ==+m;@({7XTFTYJ;K 7^ߖ;pg PPJ;AB6N^XamwppFS2Qˀ)뒂hs bY6N :2 YYU,ZZ:﭅NaQ r.D*zX*ƊⲡR7o![J|Go }Mf{z|z&5)8AP_m*yvܗs4s$%+"`|HobcTiF gaj-.$),,F luhNv3Y'!?drAVVh]]'tY¼F lN&WHq՗R))wLxS[w^r0AKrڥ|`y+ M1RWrYb?J3%)Tb".1!tѰX4JE#y.+ְkػieW&Ow0 Z6H 3*Ću{'GuZYj*=Psϑ& ͮ[e^O3{8s=x; wx]vl mqܰ4̞2{M [ 1:a!eb #LbPMpȴZXH0ƌw WC1 72N+ͩD…10g0K  Vb,SX 凕wY_3")}w8Le7<PM|G~T0dpveє„bHVT2Ii 'U"HwDP} ljQu.vkїTVlӦx؀&VqFTqt৔@5adFHJ3{.ŞQyud{۹YWbsCؙG~VU1q[2T0&Uul\P ;# אkGX$sC@: Fk sj!Elz" "xrQ1@'c反axAbwiEuL8Q )BdD"CcIGrؑś06`3~J?F{r֕Q49ey̖/VQӈ@"m'WoKpcd tdzC\ 1,rjaj~pV }7˙E181a]D?p>Fͩ aS7)6QVQK1;[s'γxzl1КhPViT>\F3wx7IWN(aJ ^-1.8I)1u;,nocިLd;[# *f#HA1[\2B^{gp:xc#}.#G?Tu?7]5< y݋Po[;((-,<dlz[ka`Dƙ#)z`F_ WCw8hk4V<QhhΊXp6 I>TP=/.E˥! LdU'1 }N2#$#ŇBhk4X8d|ăi A(gFQyWXay;.nvJP U.6 ($ yLmI@(bf=P'{GO+}}l7\ss.m@䱕yx!`,,vE5?Ш\DgƆqӎzhB;>Gw LG:rb H.hM + ̽b !}@G;ҧQ r N_k[+6sr~q $֫>dQyZ+*~d⮏NΡe5koў50ȧ⿁;2Dl$|9Kf#UUz!:B]Yt:qt&b`Fػ~ |x(; ,2:x/wʃRbD#u~'fTOcX/V#5[{tQbd/#1כ8p2gW?v:'26J _iLau( F[u>UDud&f}C$G8f&cJF.m C|RAQA%w  oݔq T%@v~ (խ6~7/&ۋm ͮkoF&pO5 whW`+ p#=2# ȣKdzJMudH79?v%l p'-OĿܰ}ne4&WoVيڶ^f.iԇF"$²{y2pdߦ,1}iio;:fX"ZW_A4j,mq^B;߉ S}%9~S`jI֛f=Za}Z"|qv13\{en'Cv:WGf3}A7tf'1B1Ŋm Ud@E[8>KgWDZ!KMe, H![oq?28 N+?ÚƮ2#›.&ўy>Ec]W}] _WI?oDwu}]-Zm`섢HMh-+QDgv) $6h їB9d맓Z;!5T0uOA"S/DzIXxO峬?LCfw5Z]n'o?0aŒ ag>XyR42 w2hA:r?9JJ@$>| :J(5׫6qVUET*ю`jMDs`ou:%ϝ$+_/40^m' 8r;#;KA1\(E0mi2R +-7=e&<&('@WNn5tn`kut$psu]Cf1im᫁-vG%; 0.1 l%šLz'l.N%Xrobr {xdJfr(~ֿM%DfcYvvfE?खJ*AFCDY_8FH2\4 yUR~P] +Hȱ8W$=am\bm_Ywv.B(lvxVc1tecOA AnI2_y0d"me(IBv ҵb^-3e&S"[* k`R@V1D [KUm IUCGTzuqCmlbÒ=<3"w #e\V)^= j_qly Գ K sD3ZU&sӭ> JEq*a%#ޒW x[3׈3>D&ry4 x{`h&j`@c_X4y%P=i >i.K- p*kq+ .2<2z^BPcqy26_pa1r &ݹOb oKq#-#9zܦ`J/1$_oV9b<L< ' f憯X݋!EhUS?]lw# \k}ʝt I\3n;7%I,6f2HFcE>ԜitNX28v[ĺ?"R!! w[SbCьÚ~_7>V`Ry[kEBT2[կS0 fu0:"} Wx .dA4c2{cr:e \NxDҢ碊?aPwD$8X0+%S%N՛BĉUAs>$s!u:Ma.m_M`W|l=@CiA.pǶjGqo8>lm }>ܗ lG\p ڣ*_`Hȇv‰-g[w͓/q ,nsWC[J Ϗe~q@odP'T B0s mY w* Sv۔AB+5HD 0%ln+F3|!(b_2s -jzUeoL*u|vU Y~nSD tỈs -wiN`>TG_(ЈDJ+'ETu ƹ JL"ש4.p-M;H&K,vR;Տs.޸+WoA^j ]>5"7 gۻGbv8Cx=Wd+=ILז acZl2T?|LC ~ֱ!g;~X!>(?T};ZxO;wb i@οў;aûf*C[Z+cKd:T vFol{表{\a+sWS[X?IԘh{ݡ]8%W1 w?$9>?bf; x`NcĨS4Ks4e4>[0[)imd3w4}`b ICJw awY_3")}w822PN~c_ρ 鬴Tjm(gl).$/i_qIU GVo޳+ D]Vy%ܱGOG߰\}z%/T-qPw&<9 z"e'⺕T^ro0cKa[k9bH.4D1Xȍ:/AP)Pny&hnwd ƨ %=\L]?; R!t̳htPm5W"LMl4qe.zf [# 0s/\nHje[o&YXyk ͜p3S eY:F_s`KĔ/)2Qڪ,+uW2%řHK9;6=aGrؑś06`3~JFypCKphJZvzRJ6=`RT}ߢ-MxlYӆ4맯N[@ņ)yB\<  V6i5W5%zKYqxY.~Zr^1h""Vܝ%jK|Rm2<32O=u1$q]IY?[k_D<% 'Wn1da{ :Pk҆kx]C(pSbn'2qI%ʣ&/~q)2 !1ElF l7tmÁ 9UuvS՟ k=Hr@~+<`O 5la;m ׉};Cr]e'W6 Ox ζ:s%@(ŠC\s`J$pڻ$-&$;b>yФo6IҩI|C4u.Fd(]R @Ja@% >ݥi!n.]cJ'~j 'Yg>$$\e%* GiRBͧrZ4F%/`Sm_F&\(}yRm2(< JLͦ H`GPi@p 0F;;k3P6x|IG{^9F4ƋejV܋uWBlշs3`Ng18(Swt'KN$lEL:(gW%S "8x57$NMTP>LwGFD(ʬxJ`&pe+& ai5d6pDɟ5&=CCukI8lH'.k9,?;d&ٰ5 7h>QO Y gh?@^gW(%^___4K\R@0v*_L"9A7Ų mO"#|Ja 2DFb5^ʶ"~ѷ;:q1Ntd;YR0_r9HǂDrJ<¬x+{# 6W@f0/ B$9ףŸ:S3QɩDdbUDI?evNylQ.N\s:8Z%^LJٴbsJ0%cn`~spT>t+}u 0'sA3"Q% H.hM + ̽b !}@G4gE-sv(.:=VLeBua J +~A ol(TP~ N,N3. Z.)}ƋVZQ9hrZ=*!B7@`rJR @vW\*Cs3xsU&uA6i=MU`{$0 Sh(3laϕ06a1ÏuS .uh ^xtrw1adu "|f:m4$)#"ȡF"0zGY{Iґq8KkqXGepM_@f $8Ao^բtIP<뤞IvjzB4iK[qJdT?'=n͈j1H~!g#Fz|ӵ^9qx07-%1/Yk_DS[꬘%^]|t ?𫄯JI NzpI^sG 9 U&z? )bRHٔ8n;o6dᨘ;21H~Ň@)MDG]Ge*j-c\Ex"4LI!qjK K?¼4yv;:-:iܣ I g|h^ hqsoyyX@KG9òOm=kȚ[FA )%: W}w_gr< 7`aT|KcDliKkw9j4UGYnR9J n;á vp~j! ʛ<@M%:b s3T(oa#.y[kO␢ɷS`grgsD(czʖkb#DV5[ē%sݥZ4ZHͣ m}9Z‚Uz;5Q4R ͒.OuvOuBG,g;<ǭkӛg)Z Z@zV<Ƈ>\nt> EN0X1"E'&b@Z%P ŗ[wiO|s"mj7bd]E_hT )q.ZժL)nD#W:_ET߼?^~w?Q2TA=ۿP2\q;m|7y!#{,!i{Z!?3-bvi yIQ!('h)*&yd2F&?:UL6^ ՟ xToo$/b.`d[{#ZqW~or7<6r<On;;Н,|dx NCmT!ua[Z////// >Gl`y XUSjChVȈN~+N@'Dm`ҹ.Rmx\jнb1+s*B2,y頱V^5{4t<r{ Ū7v( ѹBFfDq-X/qԟ>'0ǵ7s*6d;;T?uWˑeRܓ).p˜29M]tSؑt}4:!()Kdu1lɋ%xPiu,2X!`-\JM!퀑{=O^4W3RWu-9s>;H;JP˺b|V6c p1l ! 2@G:Q[1`jݖ5ڑ:eKh-6ŖKk~AY=$Yb\W)Guג 4s¢KעzY>P7(zYS$gƬZ ^ ΢pMX;I<x~uEѱu~2 ug{9b ,__p1-'nF׼%Y;izK2'*ڝ ^jiN"+fBx*g[kؿ\@L9]A>p}4$xj87E>Rs?ӺEށSL ^. =/MEVrXNHNqpZXa vv(eS,/BB$Lϣ.4sOuxD|e&OY%Xn! X;&g 5U, I4.Rt2ic09T jP ftypjp2 jp2 Ojp2hihdrcolr"cdefjp2cOQ2d#Creator: JasPer Version 1.900.1R \@@HHPHHPHHPHHPHHP]@@HHPHHPHHPHHPHHP]@@HHPHHPHHPHHPHHP]@@HHPHHPHHPHHPHHP Sw߅|Xڝ77SY$dtmD*) _KGO2QvoT6ɀ~ifffrq`yS -7qIJfa $DjŮ9#?º”n;C6Xjbלeՙ SN) nXvk`0Mu0KG|dA&:rGY\!ځAn ieYx Ɣrc |Tcsw; Z Xi$jNmH GZ@=AнP-b3 5j͕ʒH'[e7&q9|/|F{0ٱADU狳B%Jg,hst8!L-.XYݱ.DŒ: ?}uLVt❼uW3 w%ySxPaz&bys,tY^=H`!@ t Ƕ73B9gb<j8) IHIfj.vg5 W.!oMm qLDFfڌXʠ)8ɶ3IT&+tHC'Rzxp 9#|o­’c-R[ (P(UƝsJOd l7fvÝ3H؝KÁ[{!)[-# X[w!?hs%%ja W~v8vvIM0IxcGL޼ڽ"EAjpyF6=6Vi9 wB ,ҙ[$ X4E}_M׉J{% *a?f0=?-UY:CѢ5`Y1R!-(qXQz;A-Ff5bﯼ?z.J"zcK5L FKJ =Dɪ JS‰Id: Ƿ_ef_{|CDaJio%X*lQG&H|RPr@ ]\n3$_U܇cCl-tHJsOg?+oXN%e_þh>#C7B:}}" Ļeu_lnѰE ed`ʷAr0Q]ìU)>7gk6z>3e9 ;:<NW3d8fb%p@EpEDb{)AMoY49K(\hg`-;3}=إ!kˎz$HjDF Q._o^^)R 4 ńq9Pe }UFEH&WЯ;N~ q`D@lTi.!BsMN. 6Dvužo':obpP.iy$ ;wLkyNz_)T>_ ڎo8/S'`Փ_\<^4i,p:5$Dom+IPk9wm+ϐOWpnc.s"Y'rICpVzd2CUo ̣7 CjF[⌤!.%ϰ#nئ ξZS/>ہ!'A:r/<56""(P"Grn f`jq9Īj=Y՘F}[a>(4omj猵(z^Aޱ_m\JK!0Ja@'SZP P m*)LW8>zb+UpC\`Ae_<Àÿ*B6ތbTgQk9]ێ0302.Y1h݊udB<~NP#J+ׯTT$*d!Oh0_@њѺd"~b]9:]i<-FҲMAfOiZ9)@S_Q-RaTͤxÕSeLtO](-Ve(OIW5$v񧃋a*~rià'Rhx8̬Fr(uK#ɳXP@E\:̶繰0o_66?4H*^-߾yG( o:࢔H&Q]`np$3#C0twhzmpr{܏s]IHOܔkg+hu/ åR}95AWI23ws"l튔*wl"MIFF:?*5MX0o.!r?Y/|X{dZ7f3S}"*g͖R3B|e#"d,Lb8ɇP /.mdFl$1ekcw9y?+]m+* L}ШےX;_ m 5q5,|yPS<7Fg0CO#V3Pu^bS-y᧴5nY7x`p[ĽﳠDw){3T*wdCb! !^W~~3\Ǖ\"w.u~ȣoἑkH|dGSvU)`cEz.TI&uD0KB4 AMv"yy I=?gJevۖEVեO:c\M8M4'B {ڀ=. *3ۖ}$[\G0%b:vۙ54憹[ s9 [ЖQ&;+9{p-X.wR}鍚,oHhZEuQ7i+w(@[p$$=p}(8UPfu e2r|ay;CY.qBL)CJ.gVw30~ns = h}LByKo^w80[Z;G`𺔌 90<ы#h.jveI9(M "Y\)(8(#2r*޶<, bJ h*UD)2C{ ?ͤ0d;0cA$ 4/T\mݑrVS.q +fl'mtY^O*kIjE jCi=Hj_k+RFA<]32 PN(&|$dnrZ2a Z {l$k*\l&ӼZ[.hv#M2 ^F3,b=fgfLd cKm6(B#i'IB n"@`ggC__~wp-S7Y'jba bVr8ߺ\c:LHO v?ƎY.KG 'S@vT0Ǹ8S0oj)=E%6!c v#i#xyU\:UqᲒ#/ 0. uӯ5 vHI<&!I m+QB0**-Cl$^0WO 9oj?p!uHkms !aO, Փ<'cE$q)w?e)i)uIOClԢ EA.qiƑEG4v7F?GYK(Rjv|VCtJӶ%ou)3'廙t?35s3U@zY7-G Z[e!_kḃ7V̧_#.uM ?*)8ߨ<}Y9fWeHGryՍ\VmGJĀ!օZ2e˱~,NpZ==G6')b^_"mxpDb$plߛ̉VE]c"{48{X،GdT뫚pKĻ=+-(,J(u^0z2,N3?eÿKv1>o Sey2:qp={9BB:Eç 4#-.e:CEC`vK̋КAQ];N7j|VU"yH4?U71?KȱI|pqrkV; Z"_e.E-.~qw`Y -nsvy"DRoXhFt"+0܅d$9ү#Y]+=]Ot -= CJprD!鏩>IaLH1Y4O kn/'dp@>fHTmj;ɵ./n|s!򂉯因mզ"֊5 -%NHzLk%n,KqR.8cBg FܹEi;9Ap|%ƹ½nj\_K'9GG$ h'aqe 駉R5š,9n@-8lURdM&8;ܽI-2P}"z.4 3u2W3齶$myq^W?Y^\M[\5?34B>?8V"{JUޠwySܠ"߳U:N%CX1(VX`əv<6o CСG Ϳ.x"+fx / sN`i;5S~P5/!!N%:l넟_8|n:*/r9(~832eo#km^EV wL XztMo Q!"O.`(5SX8j|7$tB=Fq k+WeP&\1s* zs߰R 6wYt6TRf:Hɟxm$XtI%^/顸n=XSZ #uؒ)w< ab lJHkIaL|."^kN(ԟs hc7i_0w ѲѸE^ߺx:/893h^_+rZ G!#[D#ӾHmRLZ{yT': KlGovI3M69}a Z^(7^B Jw~s6؋1{cόy%Xk 8qLc2BwG*kRńRqAN6J52?akWR+{9n *xgpCjmBSwuc}N޹z'EH슿?Ý[$W6SC^6J05-fjLհ^ g'tğPgec) F-w-fG0=[tB՚kFpNOlݩAn@f@ٌ))P O}lY\w(JIQRf%Oң[PL$] )0q:jo2lmF-}cHSÊ AMKRJm:ɀvda,LPȐ5V-qqMI9[iS[moݸOVܢ9GZduB ZOo׀ο$vY#g uGu TtfO%)3mĪ\̭CqK^/n쟑4G,d3`߿ejF֕&̭amX/ ¥J1x=}"4swsʺ]<]%ϡw-`egh8Uqcz8r77tZW$#~=x&pRv K|mh,L,{xA=6o Z3\#Vy[k]DTZNBy%4&awNlⷸՍ+UN4iGoN լ1uwl0h7֯Q[5X}PةϣAs85;6_YE# =Q n?<^J\y5 ʻd#T}"r3 ˛ 6$gz2s},]Jbj!rΆz).bҊ!Ae:fDQ4Ɵ`͙J>zs̪Vjz9.ۣ9&ɘHC6 ъseU fWa:!r" hAع!NxRG. &u$ jg GrSk=5?I;UcfU%zr(8vºc䒆z:ެWp!{!`GpĬV}!n95AzqˎDEB5-,=} z+u'Un5)HN@05't0u]\ F~qw~GpQ2{5Ռ¶YY.wSR*NP3b/kߗIczLYK,'3(c-h;[qD;2nB>s4@&g`Tb!OC MN@f00] EgvKś~$OYIQɹP] Ԑn*+QȓeSVXP_osXcM!^18Rx} .-ʵбE & rAD5SuX3E\!G&7uw8C^&,Ů+/\;Wtl%S/@_ZX򲯇m#RЕƹ0WleބQn諸 1lqO[ɳ|fd:3G5 uPIۆ@:lm $ﴈ_ڣ]#O='tw2;X`Oc'>mLյ y=U1^F3/ i[Npuy Y&l*a(RBᆸ};M} CdPiǿ)6sBnI1Za\@n5Su&S~?ΪXM1iz-w?J,J(LoH޸XqAQvAco$~ d-;D0B VzCQ@XV>M290Xp?wu9våI(Gb> X[}֮#*GNWX;V\m8:zW>LS }tccbRG#ZK cu+ƯJrS!L/jaR, eJCaBeUr;aT${FƼilD W8lByʋW+d_E;7ٓBVgR_tS]@!.DiK9րtr2ERtC%| l?8/yU{C*]7}/ևrsdEzGu_҃Gc763]b9`ջ4P·YO^ q1.Qqό .l,~)d+ϥHJf3C@jSAK Boapw~:M(e3tsX;h]{|S`b "y2teabCw)Y˪ ሥ _0 9;%8:SgIZTN=V=Ij=5![6n+h{إ cc@5vX 2*Y3(Hд- -G[}6#;v5VaxFn' I絮adƛBH ϳ,&˘HBW a| *)9pRAaeuAA$2acB#5 dz ,1c@Bًi'_Ɩ퇋MT+G{ЮKb̷Yؐ\ۻᲣCz d=>p#xm%Գ6lN/$|jLiB`SFya I)6 _G2Fvb،ק 5:z%/BN8ɺ!˩N16ـHuvbgtr:gudDf5;侨A7"Em[80׉Y$`K(X>qsAbL38aJ+㨼ssP2Hx LvB? H?R%uH}ef>v*P|&J}U+W/_B>\ȳ>,fh8sӕj5,I.-.bt884b֓ \?C<y&>) =IEe6gn 0\xN8ꏋf_ QCj_DZv:)]=#H#ԍ6=+rm}KL~Rw²wL=}}s]hdl#؝-u}N[Rݗ șew뻵di2P4Ѽ.~ܬ;WKED0Xh>rv {ra:̩@[̃."m3ѲR1{*ok];g|bU;gY>~݂ڦ[0uNOW]Q6d-.&g(tlH2'ݯוM;! (v%YxOҙ|GZJfO~vX) W^EMR~}`KPQ_Rp`Ld`~`J5 Udvqv,Gc6:}(,Cm`I+/ ri$2aw:!G]t/݅Fe^)Ν@ftJ7"1GiGv-7%L;"Ԋ8B-7QcYy~~T`oPN s?z?"T>aG,ͨ61g{ERU?|"S3+ ; ;7T,6=L_#v~uܧxs,뤹`)eB/JIƘ >6XwoSp;k}&PUbK޼4}d򬔉.|D*<{m/L{V?$S &:t*6.tFfVn'Uqc3{ڣ4nNBNjNґp]\*p*#I [=IrIhu5+8m'0BjC"-Xh_ 7_ KWĤzj*òz[GpbٚS%:r vU1(/Q=}|nI:Q$v/yň-j2tuúuezls @c)*kR9kԲR Aq%|%<d&Yˉ0+`%Z<9>5\]w 'kʞڟPlPm mߣ.$z,+Al_1bVGخUf _IAPS3~U!a)$\o VԐ8H$*a/V}.#Y&}\ i3q+†!@;ޣvir)gU;H<4'I˟ I_hf*7M'G6 P Ki>Rsf*l:R"y.qUGgs#EReQ`F,i+vC{݂Pc;H 1%%6Na0{ vA) p)nZǚQh7J'޸ yC&ifg'hRf OPeVUXß8dgP{GFegjrqe_U>қdF+ !տeV1)Agu@ZyfPދ!2wx[v`dU]p+$1 W/: *|dV!e; U S̐ջe2\txMӈд7$ۃs?Niw9zS)mb׌Ч 'ɦC )sb{Z*)M-s?<\9& Yە~,Ss}P"9THO1_u1X IO M?s}u\ ~ #cl?"OgAk福gAteAeގ9V4|ݍ6+GAc?K4-*oxŠL_LV d~.ڪY>\є0YNQc>U47_ B.F2YG3`.rRo8YYsO+޶h0|ϥZy_Zt>t'L(vݢ$)ƒyM`^Ӕ7 "u NZɳbCKu]<4X۸/l@Pp)1y,HxT=s/!OϞJuC]8Z|ǃt3 VNM³ Zu >ڲ)9Rn0.*U[ڏ=;Ѥ1XO*׸ ~})FsX1'S=˒b(0(eT|RIs"J OZ )D3pO@6hDi%!e@}1\/)yK^8/S;|}]hkL%kϳX3U!thCFK9 NK+:һFMjZIFq'窩|sZBi) ;*f*|GY8&;t%{R>Cm2[!щS Yg7 $`ؠےI$I$I$I$q޽u.S˺E5G9[ gqr=hEZ-6I$I$I$I%3e!ʧtx\)Kd^ٗV8%ELH$8j*rJe[S)JCNOFo+&*=7i?+@e!!wI K3Hn#i Xs{C$ 4pSp2B]/R\n/ Yu$Ԍj,JF>,dS% wzQxT+'6 #L4m{=;Aqp >b5;DO'XiZ:kug:ke}C﷮\?n~ݩފUΉCF+Y.5vLRQO=VHtkmy~i*Ќ0*uߨ3Y `C|= ~X>E$G%Wp`8 ̗.MJkZYQJwx6z [ow9Y:YB閵6U.zs.F_wnRJ&E }&Qy&W*>*f[hxqQ]{E6uJt]C/^Y$b "`&NܭhYqC>^07ZhN;|`-rna+v/+e1.VA#Ψ[G@~Wɨo5́vsuL5s 3`q/Jjea*a*AvWx:7-z#!@;\ZԞF{嬤;6H|xnB9LBE#cݢsl܅N)sQu> r $Oˇ+K{;%fX`čIKd <}"e}5t{P?W` М樾JFZKk ߳~݄bQ^dT,nGO^$tY1sX F:s9߮N9k5]MQho5UZIԈI]Qjxmums7z[ xK`~9ūmeitP0e#W mo񽐏6S`[. +AjoeŸyCuE,nWe+4N]U~,LMn#geo#ճ̀hv ^4q~,vVbgS)-e"#G^+z\Řsnȩᬰaq6H4\aM0__o$2[y(/X|yHSzs3:5, ^ OZ:x.fLY#qR,h^ClvHbĴ,:Qx_0mcT̵]B+(Y/(v0d2t %b uLo-``).=sGh1fYA =Υ7Nv-@>gMKkT&JQY_jt Wwx,q+Hmy|Y0l/:'JDž5? WA;E㰫 Ь'یtqIE?{o*_>SDm١&o]НG2eLtc=._kIPhG.PƸ]^V8Y&Y:;#_0Hp*E"U7f}Cm G8YД}TL'R,i:ܽ!f|vu.Jo8Мx]x-PU?d _̄&#"YUPhSޢ b(eSC1|%Ut9xquPnM2Cs@&K\(r<|68`-Ͼ64kH?{b i%&n,cfrcݳӎCGN-OO_:0H+7'FdiD!tMnAnmqnz.La8 CFAN, YJ$%>aw&lQqz<@IK7f5 JzrT:`\ʹxӐ/0=tVuČ ֲ5l)jؤ)B8^x rvN AshNzp CE\7B/Ӈ6rF9+ĶS?oV۴8"ƴE:i_|z F;[L*Ibind("k^9).Q*TSΕ<>.B}q0LI*! NZ ~R[ pE[~څE6[ :d$CoY ; ɹbJSLD"b~oش-V眺nP;i+,Ŷ|[J7+}_3x+ӰZ(yگ&t˖T$3}ٽB3Qp=g^π35&pۘSÿYE{!Q .% 6/) ?8W*/غO;kz%Bvq!`nx.>~9l].-+҃`N8k-Ꜭr'_兔>aq*5_3Ck1; d (k.D 3{AJTkwsO1v.RΝðb"X5 {u&!^~u0Fo6V*9ܴ'گR-.ư$GQ)[:*ۢ\_6vMEaiUfZAx* $٘Vz*?m֫]HqIgm OTig;/ЬdрGܐSimu,.`.qvT )UIf:QNxM qA0(Qd#Յl6+ꗵ֯ȕ=ÿNg0LP%/S=RڠXH59 *A:P,cf)-[\d7:#ZF5X (A̠CRO|JCWq _Uk*#3YNdTD!?7"YaT"[xn,5_ oҊ-0V-R-&Y!Pb*}ġ졲v}m߰<;]Vb>ì4u{0b&煕xg=7MoOr;UX#raFzX6]{=\y>&̺!DeZΑ.#5 ,+$A'D8h멌g; .ꑶh9 9s;#gTl}}r᪉^3]Ři&=%Qd<ث)PhH@.D IdF!:<.Ź!V{A5IvKn:\+ VMX8{-.8uR,+&1pJDwV9|jgh趱VCuw+3XzV.@|3)5f+sЧ0ĠVF)i6dr^.ք_sINsO!4AG|Xrr:2QK (oS4Y`'T by~)Oc;׈Ql&vA nYںYb\{A?/+e15J*9`'f%qv c=LȽh QojwaMFV7n[a+%}0ysJ ' 7ֱ2K%e>mAKx} & 5B f4:챉-FwajSxA- (筝 xrl_L 8o+綕 qgս UI(37?^./T.u;)G֢y Pɲ"Nc9)fzԺUAH3?-fGțXw\jl*Fyߊݰ~Yjw[c$bȱ_4Yx :38Qz6 y9 -jTD~z0`CʋތO8Xr|֛qB#6gA\<&.篿> Hn Lן1iܛ=6X;8bmE>A ߆ч̇x-{3#vu#ذxq~C 0 FsfsٯC 6_Pkm4lW&&J[4Kck撽|zMdvKVP!VmQ ڏ 5$>gA{JVWal𪨈x>TyV(?VrĶ #۲wdktI [!+0*1YsTb'+I(`D:$+GjSe2Fybɕst\/S,| @w/gz]#Ƕ z ZK ,\$X…^;֦Y:1I;;f'kJ^5Lif\%=&`&M7e*7$gӏ`/Śux>X4vpdG?Jx{c)[*_{*4!Z%HO+DW}Q[9}E_oUï+۸xoF`G# l$!JW/dG`N* ~[5,\Qd0J֢&υt}x#==E.+^b $e/fqlܴo:g&xya쭯&#'ttPJD.$sCQ Õ1PVt=Ы͚{.a@]9[ܝt.+]X>QPVU8$a4)2y̖9ٗ2>yǨ G_oh@%$(3K6X_'oʆuݢ%]?łJje_iyFgb*S{yRNJ|1퓳ʻ}q uNjm2۠|j9vJVa㍤8^4B3F jFa@"ZachI@,Fq*m!!83jUU-%ھnYA'ab} vۦ+l yPkpt8๬W< >itLCGjrgQσ~x Vzd`$F<#JM? ",}<:LbvoDv2&ȟfͨ!z/fZn8Dwrg&m4otNC QE}~֤{des dvŗԁ$h͖F%)Ǥc.et7?~PO1\+eèXB94cSQ>X/{rIs(2B!WB Sbu^NbA>GO- T5l3J!:?Z,ݦ]zQ8i.)s$}O)i0*]C,=RaK3O%C{XЬ\*nWDSaSI3Y5-t ɣ>DQSo- 9ܛdx5Ӷ|pHĜUF;3m]/nFf쪰:_؟I9 j<1?Fg/m8 :tK$/8ﬤ٨ ЇNS~XE(\e`k_ ;|;B.LQ w[&f}2u}NT}KdHM3`Q:*nrꈡam<6IuZ 3 N6k3L*UKNӐs״݊NjUץ1IzM1Z~ YdҏpP ^}_ W͌C3#ZO+qGizH<%/Ř|+5(oꡙ`~om2HˈLo@{/Mj^EPWZ5Q^JLXe!8ʆym em~ m0ɯaA̓G`Lʀ@$_,G:|Y c_`@0LXP!9{V6g9KNAh)wWqk1iյe3JN5 ;dkX"(}oNc;g~~?lj*@`S'HS RY3~c)u;jIB=Poc?Wv(eXt՗hMЅ]QT%5N֐Y `oGVJPy- 1M! |搵13MϹ*dP*WnJRWXִ-D,ť6Ͳr\V\/6 W3E0 `h+W^`%A)bx)\ [b@d"JP*toBC~R?IC,iJYekΈ5+ |=8NG/wVa R,ɫk@Wx,MSC^žWOlVIyt5V3~B7tфV*u&!^~u0Fo6V*9ܴ'گR-ٸjǰ]b|uzM4)ߞZqZӧ ϤQ"7U׼ױP$ r^3Ei/iOmP#EL$&DvрGܐSimu,.`.qvlkiY_' 1j$cfq2R6kmG-aMd[$sHӑx<),u%Sђ$?3*@缕8(DVEq >ۊ;gi&Ფl8ycKsr w^LxV9 ^v.:gg@ցFų D>nW@0V|zo*ZrHsorzOa|/Yq/<9S&g<.$;\jܧPk߫@):̞&K|zsbsDaQ7%ᔘ!@S0'W󳡸|n/W*C;FJJ'M;~-77`sP%.S7!b _@̀[ϰJ?s.-^ݪ˿n^ o]~^QG9(ԴQ YYWs O+UEf7yܴVac1ʷiMw }'Yvq~xWh_TPd!04/;pUm2<(EIDDף(1[;\9& 4'JAPjU)TC $%a #; Եb5f[une?Tp$,Wjga\m-q=pÙmL2/`Ija(Y:%m%O]^>P,cg{]) ]cR'UfIiKRӼj4}Z'' ozj΄O9^Zƍr_~Tܧc|4LŰ˷Ec.Wtlx5%[gы0Sowl{xMgm8Ir.(f7)e[C_Y[Ij$T]"p51jF| L97wiS̐٥[=ĭ2m 檱5GЛ uGK/VaM^gqS:(! ~% F?#X0l?, xKV}лF܏f olqt CW".GDzq!/\KS^ro`)Wm{~@Mrn9&zbq S%ei, g|_KpڙV7k^0YT0)NV)`Ǐ7.e2;{NpbRȂSje!Fw{&ҜȉzK(S}9 39V7TZz}Q ?R?yw-o,jDoaJH?g ƥzrpJ߭bt#<ao`7O6Ѡ]e NM? J׸6n%v⨁,f \m2(l#9[A?l΅:+i[!Xd`F0ɧZ;厩e7~xA{E-؊ k丱Dҝ8lw \K᝜\mt+4|$u-1w7]&H'y)qG[g@-xپn_X@ bQp@rEAҟഞq BL.C xuf#:#G] 6(nxYWsAZ o !&yLMDK ܂ ,?ƅ\y0_AV'5d;Ҷg>](ws) S]l3#pM5>`,EP2R0"E:?߆4[b訿'u)B?măY4oH`R[˳ouH`>aW)QTk?jGoo%-$C*8lw zohWW\"\6r.mJ\%,tM` : ZnƘL]cDF[T(1Xdq%F͠o6*#*zQ܇o ×MLLIɄ`'f{>TуEji6+E"8.jEkf{ >IGz3SETGPT +l E|m(Isyb qզr5Sh)`F{/c)䔟[eTHPfx#ZsoRH|ߐfƼ`wbt2涁_JI7*̓ ZgL]hxب&xt"s:Hks_A6[D;OC)>̙te8n ;$U>/FjtuItR"+B~Hш\s8 '[B# !1Adi|J\OŔxt+Tm<{5T$CI0 ^2p=*͜l ʇhTH(S۶TFmKVA '*Ev,A0u!{۩,bVCu+jg fCa#kAm5awxu tEIYw9I/АB VhD '1=X잹5ua 1dvo~|}ka*f;I%e>mAKx}3us_n:*gMmI5Ԇ,$obFjjqdXw(cXqDZ:w<ywm@CfnxNNU?HcI$q!m ]/u.9BLx'SC0/'K3dG@n֋* 0` =#̨LPNf\s2.TrfvޮQЍ{pnj\hKkak"I2EW'CۖKD)w\a=cFf>B\eDc:sa3 q(k"1OqX(ŭDRƮVRadKvW7qRO[MS j"seR{G5 > g }]]DUHؒUeh4㛸' & ΊʭqJ^A8o'f?Eňs퀬S R' jD䌛yro9 dNm-P(KKyDž/2y焖2_h1x[?1"M:i@俪W1}q;@N }ԈGTO xvޮsgاuO]y}:7 h9TLVRM"&˺b>¹PzYzRq쀨(9wyz^[PT!dUJ┍9;M:} x+5{dZ%j3.56n’/zV <o}on`/;_]&kp 6¤w֌ wfT͈jUcq@s -2\EyݠܙAntߟX2裖z[erfiFšQ(Ѷ BJUv⿀"c` V{7\qdX#][&d 'xM: -a]8@TYa}0ך`>ـ!Y kI}?E+CGWiJ9%:)7}J6A7?> HKqFgހ3a[dxsFmIB;R. ,qx + vْQ 6VyӚfшk/5ݞlwX0MFT4z9]H 7gf bװhܡBVPoqg$Vi?{e)A宕m^*X8L; ,vAeWhW/| YӷVWB2"Pr}$^r 5ZkX;8֕AV0&#.q{l 9K+9ñ.Bg(p/ F:%wT?ٙ^,uhZAl3deyEK 큯ݟF2 L&F^d"hbvBX&Hd~fb~)Xg'+ a$໇ UR" 5 9iT }k NX\w3G2}y'<?:n8`EnQOz`DޚzA7~ިoO~ݕoz `G# l$!JW/dG`NQzO3Z|7tEI5dd_@x )7jhu R;nwzKFk]\40wL*ry+pFӝQHSÑ%l3 -dm[] ڼ yߗRd%H/dida 7?j#yc!/s2C/Φ_?v#2GۜH!!_[o<~>C+gPPM@ )oDy(h~ٺ759g|(2P҄!&:պ\^^8o]^m7Z@#V$;e_T߀:@"`Ik!<}K>d\>*\Mř%'~Ieb?@p~4ePXCAUE{OT햼1zH=٢ﰃ')Ad$A%%̍5Eܪ3E2.OUDQܗ0XM!ץN@Ԭ;XAvE+@/2"XW'_SQ(6o J5 d ?89H`,c)6f&TCU{*w=P gm-I?e8Ж~lhaK*fcĭ'M}/$wׇa++M:R겻u共I %!Sī՜8ǶM:yJ s|/ݯ9 g\~&8\LV5. wyĦ1o8xCȎRY/E+@ ۾~Ÿ_VCՊ0RB4P`+&ԯquoWQS2OBi {xSB"|gaR `'i] .q|jE>QnIEUcm@d5"S0i'4eUym#AvdWu9픡'gYKoңF-~r~\BWpk4e7,n(qCX&-R+x4q8Bdm?F::/})@;th/9;O=A# ʁڋX'Py?^tdDl٩_ÛC"BjC[Q8=pigSА,R*W:qݷaG㧞}tz~?kUӾd-H֛v)554 ˔ewb92tF\/_Ki '32Z[RLKR&VdlZ|viU0fCFr7kcv=P{mWُX`Gx2w|r)OaET$`mwI>s72S^Ppf aF*AO'xFRa5E[DjUЪ_@Davf沒7E(X# *%! /Gߡ)͛BɰT{le$&:\cTn=<8" Z7c(LzA+Ꞟ5cA6s˕Šuh>XL^9,<~Ao.۰)502b먈hZK7ɒQ}J_ޤa4CGLHps[Y--9Yn$?e#ZQ`JWzF5gX*qWV._;+3 )H/ +*;l;H/Zc3󧮃n e?Ŷ|gY2cu;"$߮XsMU/XdRd7B_^xfj:j[~`(/=&SܭC"\(Z3LowlS` J1JƝ618p!+IJËhBc/{m- <1X ޯ|eF>)&? FCxd?(0B@rvFӜ\rPQť|pj \Ό/Fa:E^VyJZ PB D?Y_}ħ6fÄwGꌜ5Su8VTee%pH-`7`> cUZ**,o=JXLPА ƌ:M? 88`4CB~[yՊ\"Bz#D;>6w+,9S`Z`u-m xJs"%*Ddٗ'`aߞs1OJ0; 5nIx:he8}fJDZtvHخiftz9 uEv%/B>L&8ySA ®+/WO-=H(hUfl_agj^V,V>?B<_Kc{=~6 #1A)y8d_'8W=\ \-u"7&Pp_;}#tEMc+ΆxIZ4ANCd lw"Ǩ'z`؜ ͕ųzOBh]tpGO%e>mAKx}3uΫ}N`\8fDm%9f-Wⓔs2iWq 6) h}N"sU#aU7vsen4m}U]-lJ+Lnuc+n>䙲i lK2tx Vl&RMf≜E)4zpcQwayƀM^'5Yv/SBRTG^8QWfe&_]HǩQY + 4T{m{ \)d?߹"<$Mݫy(9/^ޮoSGW tcx5Y5]\(S_+A6[]XX5nN~vdJӯťr[yٶ\F~u/Od~p+n&]G{qLbSMn4؜@+85agH"?#O{XUƻB%&"bVI@Yhrr!=t$8K|8L3nLJ b}3i0KueZ*lEl5:vI)qf,sMX]DKe0J p%cxYg/g.TSFͪEt=T(A'4)*^#;l*JRWf\r[5P5 U L`Y&TlQJj T KTMfPS dB %A0޳Yf k?JVq}?@r+(fa;Wέ^P/"2 9mU2ؓ Z2y444>VSPU0y99e"A *Z'e.v\!p`ytjf`gb` -So*zK@#  ,>%-:=BddkBR \C YLlu5#^Z2RDDŽvuҴ 3 ȋ!03!2aoolңkP*(H%` ݝZXPTQj@+}X6X,lBCXΗAmMcd'sqK9lŏ+B`e+[%'6DB {Xz:f`v5-Cqfzx=g8e2c n`1d_(F!y3)3sd)uxc kՂGq(+_,N0_']ۯo Bhs!:/l&I3n[(J}Q>HK 6FxPwR`ġ@v ]4 A\'AޤxF ~.x㼺'Q zFUB"gUQd鬑k݌0y*/f/׹҃?ݎ[B 0-$X2i?Bxer g/Oۅ|llO WW-G]ԕWal6 9%I&u-ˆp:m (l_HT05ѣ<7d:gDH][3 i30ݜ,5,Q޴ϲhЁ!SB39nQߧT8qgQ77ujb1kUs&.,aԢңsO=3018fI_#5萏zK1_$^2J= }eD8Q6dZ>|H5Bwwtmt`m#*CI%z&!_輡+]+@EBjYx'6eyðsp :t}I85(Xw'8 ;Qx`2vOckC8t-bRdm +0cKdd6xb ܽEؔ36 † nGLJ@;*&'HɾjT,] vq Z9VP7own"X0 ذ4;cp7XQk($ƵXmޤvԆ8CގN͛.EF^ bVTIjOÎt?,pW!:om>^ (V3omJ0kˀ$tЫ\)Hj_?!`7SN3LɎ@/YS3R[ tM֋ 侧Eoi .X(V t`= M'UoowVOo7Hz ըQhUO>M UjVþCdbɊa++E\Saa^qu+~Ho?VD_ ŷΐïA~& UL?YVqDin=́@Qfi\8^I4ќ;qT L#NU%XY"V F󪅧gboI8 NfQ+Xtia<ڈg4'\p`},'r'8e\/>u*Ϙ6HQ~\(+]JiR(ʯN_B Րqa8|%@:epHij|8IJ$ O N W|iI:|m1;;˽W*|gڨ{ )4#{y l^*(@K*nRe+Oe:U+9< Y~h_'h"*m 7pV5!b ϕh%H(QŃwKN\BfT'*zyҫ™QtٹGZ -CV\\'9I߆6 nZ̦@.:>zlGǘqE.t$4y'Se(~f0taDM9tBl$2O@*Bj`.?]A;L>Tp sJƔvֳZ.]r^}`>02q'*S#lv #@^\h헒jW5JtNY0r1]PYH b=W $@5cX@x9lt+A6deX\gVd$f0o* {ICu|Fg*'IԷ ɐpJlpjԇC'BJ \;ގ74Fh}a_0WuvS;?'sc)">hWcC7b{yyFATnm"7F϶Q$?5u aaشT1˱6nFk"y9eZ*IJ`]+Tf5K;w vlwcPHZV W?KѽEA<@)\D+ N{dXІc0[N J/8;; Ѝ?hD5#CɌ)x0ybZ :3|>1s  jS,s9@<1㾦8,UaOνG)y||:_aZvT`<bZBj~eV>n?C/U $IN?k)RڗRcfօCLd"WM4>G< ңqiYw A`ɃޤAVx VU_=B!/ fN ds͵1‹4? k[.\| ]<ESŖGBeJtGj4r{~L\хPN hv1MR;Mdsx^֢35s^5ꄝn`X2HR#q;`&N|ob0L1 c03AVGBVj'lC*I- tB ^ j#8V5jY1zc55P_aNyÉN#Y|c5Tx5)@}QpOGQ0q%FCv3A %kQ ə~l×d>,Qm |8HQme2v}0GZå;!ev H܁fI8]fFi:#8*+_\Va7 jzdaӔ@ J|6, 4XKaz<ń$eHbo} "bmBΘ| >)xZamH7,f.>L9(&__A;c>{_ɖk2pZn_ڂu u7Ԕ/"E=ęYt͛@5xDvrPS8⋏@y#77O\!6̵I{zkľ9Ƞ]H9FSoooyx$ҎtVSiveoYO0>;+[X1vpE. 4"{F{@o<ۀ`%.5zrWݝYG~^狚V T3@mS2W)]9O8zfޘ5K*hwdq@ӐZog(.9b'K9t2AfDeU@6.+R0|u4fY;Y.t8o `C}S_1Oz$ ZTU'/޵}EE&H("YQ-G1j[_>EhHs]Za^"uXxUd9<+r&v^R2mzL% k>NP(*}D+Dv^-\$HҢZF[8~  u{znKC"FX{rVb{}lyI.r'hnI[21V]>YҞ28 veLHo02hgd*Ua]<& S7F;ݯ4UTXju30!uBEuJ͸˒}KKzeiJAvɒ"v&Y^9_4G\ 3we[H^S;)7.AW^dSR/U鯣LňJuю\L4lPDP瘼 ^w6 Tny M9qP^W4ƫSpD@E'a⮴nGkXdWxGE§#.=?QHϮvx聰}%c Vێ:{©=ȓ( E`s@%;Npq+J۸Xm@VymMlTb6P)F\—Nʒ #7 m-VJ]*F`tDz9=!tZ~+(ܫg\7 ῦW@? ``@%T(jx`~6Ŷ?oo7Ǔh]s S|?wyS-Hx+dM>(^ mC11>ӄ5= BtnHm=fV$F] a->H/؇MF;Pevw݉ܬt7 3@\B7O|zdnߊXmֵgϨM&ZawPQrjzw7Dsف( -i\ Ld@\( o;\ě(7V$i.G˯jav4ʥxIB;Gb ^"̧d8ZҀFգ=q T ZB)XRl'$NEJ(!Hڡ9{1뵇2V9:NG.!Ȧ"nScsQ>1g%b12"3.goU iP]T2(80,dOZqGdX}pQX/MTR۩_HgD~t9՘ {pAoJY0WpWistDueJ _NUϙ&WaE~'USh1N0ʐHN Yxϲ:$9$ $ yr'_%8{/[&(z7$% ֐J0(cIpWKkA5;!W!%" i/pm%YA2>Aɩ aUwZiԋIsXrAFQgp_ @ gHCFۆ&Q=1p+zaQcEWF<Ğ5GezٛrY.CʯGC-' 0$![V N$+a3Lm6UYw HjQ9|^Ї4 4'x{q 3;T(q~jL/ TbgvK<ƽ*eM9;_X !y8kKdhhao XݤN4ِ!&Qf6 ڲOd2:@bAs4H{#t((zMφ!pBMٌb5XI>aQC52U׺u?9VPذjj[ZWn -!{NF+mdoBX;`0~R"X ǞR[R3V0Hd:F(cJqRyV:b_cG KkhojCixɚwײ$vJ=ET#N1yFsdo0psUy75}$ YTi%bxC#PuĪUVJ_S2s &HZRBxBxU)qY_u?d }|0@PS*2WhoeNB1&HM?*2誧nq|P&LДO53GHl}5!bCzAtf;WrL!EYwvr)qw]Qw~dQ"Y3Wi2NIqK8Ј>hH5!/Dz_?̴L`5zFb}D*Gw~bAh+dfPp݉}Z տ"o'L'K@W/`g˔G|)@l7a7q*tWW9cozQ@*߀2U鵘 %0c\M7bE+/=ikfHMó-nkjNj\Ic^^ŷ_j{d|d#пEL\&]2|xONt6k%>YӼr)m*R^c f(I!k :}|JQ+Gr6]UW,Aet+C'od1Eas 򀩡b7]L":lmn?/ljOᛢTů3/-l E' (NX {T'3ȊHhyU^[ \O_p=@<ڸ ? ;XK\lvS(pi1:82 ujb1lAl9SiocWLaQVj{WQdyaJ"`,Vg/͇{4 UݰCB"ӫI:.uˉJ':5ٍ>럹<*x X3 Jme߱o,H oJd\"0CM[&VhΔcugD" eM1 RVyf(crg X;ww*iu'Ha~J,( %vx`G.V< AS];"+|kxKwr CW0_&>+] io(?2eL&eO 2SY $%.Xe_2j\0܂omoa:Br়Mzpm6_1ҜA6źjψ7 {j44liIH~) p)[dSͮޱ!+Jd[}bC[ HX p*rFIfJN_3r ry#"0aVZ2%uxculhb֘!ʿ<-|˒@.QT"֍V^ɠ$<__'u T +Th!Lpvpڗ>ګr;{lj죱pW'Gb[U\g}OC.؏daG4*]G{Zl$p>E^B&&r'b5l- +0BjGJtIIВ|T rMbc'a٣2- `M,7Y2!raQ=E&goTyWZX3Fi_aC@8bIy ͈>Ym*lJZ]uF;ڣ GsݛhK?rx!db Wva)R4ӑ-B`Yn܅p#e"  iURL`Ct|}9?>eDR3rWŶhTؠ0˙_0#L,Y,kI /0TYg:)ƋG }SR(hd+'&۹I{yR.1  HI .Ӕܒl zx%`  \;OI$C_0:OGBOykdkmq>ðν[-wFz }̔GvSM8 z]EىTֺV⚹8څZdW4gSvl;deX1,79o} kxVc酂64GsErL(pFc(΍Ҿu9 .mZcְa?{jiyXfbśuf$y =41H*4U(oܧ8 VR}Ը"6zEkx 16!2Q( jIyvbKmń6WHߗƣHxFc:Œ{wsO' dwhe Uj8G/;4n@`[ߓ^캪x }}0 B+q2V&A{1o=we/&YAlz"@f|]a!l# k'W,Z+f<4\Ȉⲫ0m2N`jQ6[y,hTfLICiupۇ#(i )CͪלuOص2Kyl$'Ocֆ3]a?2>Tk%pFXn~ӔDMb1g)XÞ1J`8ʕO`7 tme Iպ@'ͼgvWa6%z0H#tBP0Df(,!%#ox(4-҉ 9=J򿆩"B vM}E})D,3EU$!V(~<W'Y Z\ 3V.%Jpt/}d$oJz9jCըXiV3L/o@1i SzD#íg'\E|+10'Be$% j&Xx[縼P$U'v$-ή ^.R0 >"/%,jXpm4v;'vp:XFHgmgLQKR:Bh;k_O X@Za u- L62S\ ,wQrMV8Gpڦ͠}Va{vT]\eu Lz6ݻ8]S`gW "qɲ'ڀxdʣ϶YVL~B(u#A?tїЪu ~:SXAk`.!,}%qi/8Ӌxh?].8"MKn[$4f 5W ϔ!h[Y_{h U)+6^H.s0j[=i; G-ςTs(3-,LV{%'F2Br:rj}QM2\md=yMP \!ԘˠcMl'\KiT;Ӥ#DOl $X1 Rj|\K2wNUoVʑ6#YN(DR 6"tY޳|b{ U|\!剗<݈ :˶-F6@CXټSJLkuꚩ=PЬiYk%Hر\Tc>R?ByHQvJex&hK0-n+XfVh۰t :hHW)ˎ9@5ʈ 7=WwvS9zu*}o37׳ '@@@U6[?") ^^콇JlHzy؆ qMOQifpoB&[4'8ӰDzTͨ :v؃˂<* 9+I-'[Su-*@T.LJxw1NЏϯݥ59ˎ ^hsjP]WKK1=%hworx M]fjnZ_Om.m=¦|c\KZ(ه'r'ݒ~1cwq?52dqH\f쪗PIYDljd~Qh=6qߕ.bI͜?*Pt E I`)+zpθDyѫB%Q]Y{.Lτr+R)hR@M>Ч s{SݚR?{s| bܒr߲[.T\I1Ern`ؽwF138dP4h8a1I%Rrt;A%-ŰXYzЩT ةx^Y'r>@Ώ^ͧb&3aBbC zԒl8웈:c{ˣ)^xܝ";䬺? _#!:@F4PߓYS7k7gi%Qqo|!.9W:XE>݆4aZ,V/~~+IpEu,AG bHQwJӋ!%kYQ pi!m*$6)%?-Gp}-mdP_tWn ||b_ C&ӌ"ڕ/!i)ԺN ױ:ӇQEJn¬[KĤk$6>3 G? eX}5@IH(jLI"ɨP`<\d}zT'2%ԘxI]9eC| Jsb <9ą3Uըa9S+dr#>1s;Z^+h ˍwi&xG1XD_` m Sk.;Q=c3_1*VF#}Ů3VI{\?ǖt!ݘ!3i:RWG}: ;YLk="e ]Cgwc&\3w"QMW¥"{9ts/p FM J_DߝoEz(S\s=a$I#I[g&1{VbSTV^>t]"1 g<5 }ѹ؂zN{!|”I7oWn4,gĂAƢo"ƠJ9.=8OIA/FCz GY$S${0VΰK@lU 8lS&LA3y)w(pmIflQ@h Ǵ?WgK4M5>bH pHфCy7z!m}VW꽿Vܿա[ܗջgcbWղ\_r+Joյw϶ox0WW}WПߗxVϟooZ թPzf|"қRUo~}H}PZbɊa++E\Saa^qu+~Js3ypq0˱(a9ڏҚs#.֚2(j}By'^$k&qpÇ뿜gm#4,A8(z;Lr(6wUȉX0X540y} HEWw[b/rhBtaƪ5Dz0t .zɦuvCK4bgqB4U76@6 _8ٰ*Ɣ޺su dv_>^ `6ÁSG0+6s>~_֛V7IF2/ 2d7]Z"sܐuFjm][΁IM0lHvZ?S܋%`/zԨhp Z$Wiǜ-wE8,R "4`q+V{6}_ 3 \=CkpC9kmL}K5o2fA/4};IAsXO)>I8 NfQ+Xtia<ڈg4'\q mך`'8=F5*(ݡ4vx0U%/OUb:y*L/o_&,H;W䲢cw! [Պ a,,!DOj/P|m1;;˽W*|gڨ{ )4#{y y$d2,6&JsjܤV InȚaF͖ӺtF]V&w5z܊.#G= ҀFd4i+TT|Cѳ6J<cpڂnPu3$ H~ϒ ?O͆c5;<{M"q k.BD]tգHiZL@xa='֪J-o%Z7S^.0ļ4C٭PFDa[mtHejT'*B,i 胪[Df"TQO*YS"qT4Ł!kjq:KU/Ґ\үR;b @AJtGjh J >P*`ɠ\;kb^&ʘkkfU x{2nJ/MOjU(S 9(o$Tj cOy*|FwÄ i /NȏC"Iɚ4n֓>@ݲea|s>/.(Rg"?@=h৸ 3jD͌άr,tk#v_IKE)f3.Kc7G8S`*g$n\qf0Ӓ||j1^AZgT 2!{.Qu|KԇP''A~?K]b ExNyM/TAլ&Ԉyx xllF@ԓO.wuETH^~GFE('M8߻BolJe3KZkwdܛ5A#`]yD{Wk\dCJ+]6m KK|S D*u评40ܷNNXX/ iG8inH7v{Jeѯ\\A^!Z:Yk< WADI>v8-Ӧ\͜w[h2,V1U`T2eК&aՙ4!R,z5ݽ"6̴&S mOFrOC~ eNw373c#Vx:F%E =s͸,ådѨ謃d2PŎfzt_0(2 6']7F`_ZPĉm7\/x}K@U-/os1zq/D>R6?Wm?E˛rTC1;P1"t-3J9ˡ130!"XF<)tvs2=m:D QwŰe0+5WdG'bޙd6-`<ﶬɥTg) ޓE 4JXj-&v eA@Od,(ڞzml#f-|pM" @FWglt$jLrA[`[U_=c& ;bR&K)ʞ݁?Q 7 `z,ٯO nqZD'JtGj4r{~L\хPVE"I,1:ގdI̓!>,l?E5+\K~) C WZ9sR7P3Lff|0rwRieզ6$|uU_;{6Jo`2Fη![fW]\fQ+䣫KAFYfG8.}׋uSW8_Tu?DfaUZ~"`h5oo} "bm|@v3^4Z*_ 82Ø׍B^jف{ ,%Myr}fJ煠bN_8bK~DnUϹ/Aw AT?mw?wތ̾y1B+7%LMoi&Q|c`X䃠4+ ȥG5N>J+t3C7`4-) 3x]$U#-W<&>N ɒ{kŚdxl5;W|;J ` eL;*Fxo>`Z6h_n kװχQR!g8a; KLc83/lȯ.Sr7ÀN8SzfJ$z/d<`(#9B וcXs襮j4NȘP/Juю\L4lPDP瘼 ^w6 Tny M9qP^W GA_v'b%honn)k{:3JSFQ#ʈN<e47g.^a|+1F'YF yfp1PP2_I0HspjYxIS}Mh$% ,_~ (%fh7E&=)i1k ;rMyt[@26ۗu[m?hIi2Ӝc5L (e۲9EvNǏ#NsAtT#֟}*/$Wy{᐀ 7 [Àt,N@En.Uǡ g"9p+@o,=eP(bd=2NVvp7 ˘zDpJ'KmZ?f##7ʨ4h[nv-pM+@({aG Ѓǖʭ Kl/?N~ hi"-+;L]>/⾇A`)gHiPV Okճn˹E/teoe+G@U%J=q0hmpuF%s4*WXϱxYcy69m3Ṃ-pv`'ߎr-ճGM #+5Nh9UxI^7 jW a~371UNV?`@*by4'lei4u\n[YP8ŜVuXY>)O\rBa3pIp+lRP(6E0#>Eq\|X0H?+͗$L9JD +e0sC1|!B44R@/e$NPKu7v[ ])ESw8h޳ksw, Esܩh)BF❅ф/`c_a8.++h ; RXC S9& 2Rn<kyķƻBlsl[~2vIs8(jA1,) \z nʸgHN Nv h")/||MH)ײS 0p}1DCNKL@̰@cmW!j 7Ԁz!svx tf4y܌:1ʝ/E<l`lIB)97K $%[lϠa#SSC=.Laj˞棄`B:m" :AL6ٮӗVUqw9XUAOa-]9tSd+r">FzM@L٪Z9ܺ _ 4\L{"Y6kxfꀯ!,f~ॖȨ Pj(cuƷ'HM3*XzRH9O'YR[g#Q#(LAi 0> :?vv7^nCu8mMm&`Cz9RϏBG]69+;UX^v-/篶Ȅ8}a%u穕/*,Xs7V/`wpd@Yzt0]{ʬ2r5g%#5ӊ.α;,W< `? U'SgTMpkWQ}Iz3~)$g\Rz_W[嗇~'K .^O*My]&Tjbne UUrH,0qG>\k" J9jl.b\dI~Oѹ-l_9Uz$ bweYe.RӢWH-}׾U%cB7 1@ʾOSHԏ*>`юh學h[lND '2Qtg6Ho@@*9nOUy[L J~t Ъ%}$܋ ʈn)?mR0f3Zh6vcCET9Re+zRX9_8LՏ3ȀAxcVpMyUgmRr.U1*xu)?0TVDec75Y4bq:>0p}B0i6JaUF0ʅ Łki^He%: {g&۷9ZerNs}@EIn;_J"@+>fG1-Jms"^[Nc1~ck!C7vm2[V-3*0IHQJmfU_t%ȞPDVF; CE,4`{բ3~*quIҺs ${X?Di_ ܀ܻ#MYvnϕ\lRs#D~VǬfпwmචF᱂^Dx0ix*߇HR̳H4=9`j >M_ Ohfm k$]8ٚz?(! ,M`;g;blxRF;UdQŁV` }5k DN@gM9G'KpNGRWq<-M1cMd+Yɑ9n]Z5쯺'$HUGx&l(AbtbUў#D(RC ,4G2K}:ݥ9<"X"8Y\wQ rs` t&WHF $%`̲!Ϥ6mDQ$uwOkҾ W{^\PИ;0Mu7e~ n'HI="& d[t|f%1_zy/{dZ1(.X}Ada0$v_<1u9,d;yw/ -ɭp)*Lq.g;UKl_C2PsP|w}N:<88.$]!(%I ؓij~trswH%;$Zcoi1ܜ5ML7"u?t8D,kz0CĘo38~3'z(W6P.yi %*NMuT,Wo-[<)DSHP%ȋNDi  鴦h:z1 6%-̀Kv|p\J sճϨ-eS%pn4h\Z3~kO'dzn幵_nyrlV6' /_% R˒mC3wGk) j ПLjAF8jf_ҫ*ͅCm| $:.ĵЎr1U{8+A0կ4K G5j7Έ"wQ00=eڿkk3;BL2n'ŢPjO]"<ÑT(/ Q{(9^.+Gr !QJS2!/I)N{y|g*FƤ@Ñ3ljOᛢTů20-|"Ԅ',JPB,kPVΔ6v#,|8NpA3~<$w_)N&{SE&Jk1;*6@fpyJ` V(th?;sic[IYU|*4%Ԡ!j] M-OC:-Ne9\\?MBmR=Uς,{4nI}&'>ʷb6M~artX \^ {z;2T_ W: 2':y'${I TTE#G^P4𨼾0Bʿ0%{GϼY'{b G6cVtCzI $Qe*/p{~(;V36K^$0MF󵤅D^NeO 2SY $%/(;l]0C? K[\Ova?Oy]|P&Lg"TrF7ѺS&طMYA!S/` \vx#4|E6stK/,4)ˆ˗ߥƿaI)\x"H޿ ccy'[A{N)ѣ^]m;gS#W"KR; 2DgMY3 X2 4ٞFMq [k RL"=>8G05z;P2F=?km&L@'˜E9*}\7լVo+ OC.؏daG4hxNΰ9 \uiH9O&ߛ>BWrdT "@P ,4 aլ-usWb3Nqm;pzYF8%W<)@#~y(QDa|D̡a+1e<ۯ]ŭ%dhnqјo[ xh#5k&gHң  ^7?]@w`G2!0`}oHO`0žw?Pkl 11ZzZM=HK ѡy߲`\43Y؎m)'HM(3ǬU ә8H|jRyOյ6dFrH߅9XÍBF@s IȺb0VW&XaVb$H AX͜P59iᴷ/YHyFAŘNݪ[2.n,Řʬ}~FBW=,Ffߎۚbw[B߯ҿԾԎx3jͿ ;%"OhI W6Ij=V%=dRoUwnUڪ67WE=)k8`K'dtA`N(j`?Xp->vs gJ% l $Xx d8Oà__uڂh7X_^ʂξp/i.3rRCJ߹3֞ADn[J` >(f%z 9#p jȽ Ԡ? 0A6+L [ 9nWL/WceoG$M^fh 4  MyւUԢ2#JuM)M"۰s@DsN.~Q h֥%UyLV~~(Ý<Tdž=@Ь%v3%eIA&0GEr2v鶛C10 qMOQifo78ƍn&r;xBeZb]LFҥp}@rJi-/3A]J*/R hV7UĵvȑX_„z.Rx 7=*{I" oS &T`P5sޠyЭJh)xׄʛzu.Aݒ~1cw}GUL<-qaquRu5e/g×尗#IVGo'B&LMufOtʻw [h)VBO)N#Ge3uk.++;XDyQl.h<{^Ɔ*k0u4*]K2tIP2pH->͛ؖq`;:$RQD>)o4fT6Y0V!nF5yҭ+[ z qS~Q3 kP&NT01~7ċ^>vIQsIي$>T)>âhWhی \TArI( Tle+Da-Ge1A=K?LCAnjbL}ue('qs]CY_ gy/Elxe2Y<m kۨg_cOmf.T!2V#@qfh39☠h.#`4n*'EU$_kXRw_ ̌;/N0p^#ڙ@TN v gJqw? jJjD 9 0Tj=Z&1Y@H$s\ ,fJS4A0K۪Am}^ؘR# H?]smM%QAU ̌DߝoEz(S\s=a$I#I[g&1{VbSTV^>t]"1 g<5 }ѵ9='=c T=1P,ۚmml$(Da4p! CQH\zIop:I?$ˏ, o|&P.$b$aXGdݧXT61U[P!n0l05js/8BxF~噞`OJAk*Dk]yo2(٨ ӊEvo;Wјn#S_ ųޠ?P'˞k-@&PÐ.67z!m#\?Vhu|>V~ZWV~&Ⱦ{*6_U?tVOsoZ թPzZ|k_>ߪ%Ao~l;;րbɊa++E\Saa^qu+~Js3ypq0˱(a9ڏҚs#.֚2(j}By'^$k&qpÇ뿜gm#4,A8(z;Lr(6wUȉX0X540y} HEWw[b/rhBtaƪ5Dz0t .zɦuvCK4bgqB4U76@6 _8ٰ*Ɣ޺su dv_>^ `6ÁSG0+6s>~_֛V7IF2/ 2d7]Z"sܐuFjm][΁IM0lHvZ?S܋%`/zԨhp Z$Wiǜ-wE8,R "4`q+V{6}_ 3 \=CkpC9kmL}K5o2fA/4};IAsXO)>I8 NfQ+Xtia<ڈg4'\q mך`'8=F5*(ݡ4vx0U%/OUb:y*L/o_&,H;W䲢cw! [Պ a,,!DOj/P|m1;;˽W*|gڨ{ )4#{y y$d2,6&JsjܤV InȚaF͖ӺtF]V&w5z܊.#G= ҀFd4i+TT|Cѳ6J<cpڂnPu3$ H~ϒ ?O͆c5;<{M"q k.BD]tգHiZL@xa='֪J-o%Z7S^.0ļ4C٭PFDa[mtHejT'*B,i 胪[Df"TQO*YS"qT4Ł!kjq:KU/Ґ\үR;b @AJtGjh J >P*`ɠ\;k@S&@ 'Y&ᤫ4&`ă%F}i+YmD Xc 0"gg|8J@/pm0ޓ21ZG~F3Ѳ">9w W|wZxΡRH=&ڷa4=`"yV-LQka NүL(J*dh@뤗JLjk=stٛzwhU9q?Ϣuݜ0f Kq!%3 @,&hnY-]Fk6wU#`~٦*-0ZwÞ%W\MlGVH3q;w$]9o qTWh1N"?pi`AnܡuFIFzݨ61D |۽6+ yY^CXё3#2':y_0(2 pf____M-*2jn+e*/ϪGJl߇ .aeP'e.nwY9'H0B;gx!Q8DK!ۅy?$~@eJ=5u Oo#O6A}dwCFEGiXh܃$\vG+}j7ME_-gtIhcYtrU`qBkE8eiNҎI|yz#ٗBIc³U?ЪФpn9478nQu3'TAZc/3 `x"Ows:7wV햣Hcu////|zJ7sHKv{3%?+8S+TV926YtNFGORSWvZABq̳R!~c̥B@loA?,yI*.yS4c8łFXQD頑BEF ;sˆJK[|R=<7ʎسx>}ekᰀ3s Tը2.R?@奿 K+{ᰅ$H^dRY87Qf8VVBA;#"\V[B3q4 Q8_.,7'lZR`4RSrxdkY<䁩PA>!o%(E6UUK *" aA`QLwZ,+ߕ:hكL+#f5b4  kc>h XVscJuю\L4lPDP瘼 ^w6 Tny M9qP^W GA_v'b%hon m)k{gjܿ˻7Y KِH7g.^a~hj,:Z((VCZOTWe*a}>aS7Uvo8_OR̅!HTi#-8m0dg_ Miw`Э&cJ,(B6kUSț]( Oyx}:O$m6x#dOn#Z|QHj+B}1EK@E9j[%"n8c%J ~KH-6q;o,=eP,oS ΢8ivJ<#5 +lĶ䇃.8̕CU"FKvN-}0]"X$D<̜K0Nu^su-A|ƩKk1n.+RcғSnHB7hW@z@1 zo_Flqk^/ߒA!Q,U),_5X Z/F)Dn'?Z V0"#-Pܠ ` Qg7c-ЁDNn \͒e`n%IG'RLk2g;)E{Y %oCA]aݕ4-O9Jő+~d{oE (rDKh:bp7xRFr,sг:W$>9F}ψ+ I0f091a0*AбHt5 `}qPR^+cd"\!ﷴFfa3YB{O^AL[ݡ-H9}DAWT)'cS<侈K+O3P:|@qP/(16(TT/(Z7ΰDDsehw9՘ {pAoJY0W>D&1)N1SU9m}(ij:Bҩo,}ҮZ u e&-r A"H/'x;o@q2꟱w©{f~Q-q%a OhwZx*$=hYijdHuތV< Xߐ]0ă F5Ng6aT>X0H?+͗$L9JD +e0sC1|!B44R@/e$NPKu7v[ ])ESw8h޳ksw, Esܩh)BF❅ф/`c_a8.++h ; RXC S9& 2Rn<kyķƻBlsl[~2vIs8(jA1,) \z nʸgHN Nv h")/||MH)ײS 0p}1DCNKL@̰@cmW!j 7Ԁz!svx tf4y܌:1ʝ/E<l`lIB)97K $%[lϠa#SSC=.Laj˞棄`B:m" :AL6ٮӗVUqw9XUAOa-]9tSd+r">FzM@L٪Z9ܺ _ 4\L{"Y6kxfꀯ!,f~ॖȨ Pj(cuƷ'HM3*XzRH9O'YR[g#Q#(LAi 0> :?vv7^nCu8mMm&`Cz9RϏBG]69+;UX^v-/篶Ȅ8}a%u穕/*,Xs7V/`wpd@Yzt0]{ʬ2r5g%#5ӊ.α;,W< `? U'SgTMpkWQ}Iz3~)$g\Rz_W[嗇~'K .^O*My]&Tjbne UUrH,0qG>\k" J9jl.b\dI~Oѹ-l_B#UϨoEn1K^Pׁsz|]o% Sҷ[7/r\KLŻro~2W`y^h9p<|zuώ$X\xI]D&Hʹ\ 1l2\mo^W3q6C~$rѩOxݔ- pO)OV|Hag' }o>zoRDd`NV4v7QAu"X6(\h)L腿OlcLA^S9rK=1@=C\ms"«z_1jYCȨw@^*V}&XA&r>ÄUuz7;J4 >30S*ә̥#;?+Te餁 L[(Z؏|Q):52}6;9@\SgɟzrfZF;`Z(!@@md n%KmB4`x Gs63$F ~:!y lec(Kܻ.ͺJai=yO:6|0n^:1kQ15%}⺀%oEyL l5v*|E+F~s)L].aw_H,}Lnl.yX&~T:qbRVMH~DE~$/l,v$Wxd y)@y#Gmӌ'] " |'3:?6?~.9PФ돍t;fjVʕfeރ{q<2(xOdߨwVw {cE;3SK A: CM#O~906}n''d%S8P/ќߗhG[[c3s=TCcA\]o 79e;WlzA \ϴ*0_R+[s80N NKׯ5"z+n_8{!xU-L$ jt=)8&2CNhEN;K_'61-ޙhob (^b!-6 h)Pa˴~ZUXU k0+I68ar ॽAHQc(C)v\|-a"b8LY )񍓹 q͆xL8&oz-bX1 ʇqÐ _{%}GFDb͝v!x9 ͅ֐ UYj|iAI=ef̲0jZХ^B,9Hب' A֓!V"mH)d.L ؋9ss[+Q "8~3'z(W6P.yi %*NMuT,Wo-[<)DSHP%ȋNDi  鴦h:z1 6%-̀Kv|p\J sՐzyN .✦+p9?,Km; W~I<ܷ6/=p]RNL$uaӟi̓Η^V]gƵ? b  gg:iyodDWP(y˓ka1YЀe dHR3y_}n6`PYj-zCHhlM-`}{Co|KC4#qKk=lPD\b/& YJ^NjϚ|['guj 윍Uj#Y] p҄5-/ܪ O-wxFt삖؆r^HK#Q6t0H{٫)DsYljOᛢTů20-Fq$t9`%YL!PSNŸP<}ԃ XAM{[5$! ~r4?F}yDŽ#wמ4􏍚f/v:m' kft[=>h:bow_ -s|ۊ6AG(#34Ume*lֈ[Gb}U8R'M}1t-=P$BM(-B\lQ^@w1øy:AC  =1^Zb_JտZ?L|S L+d/gh6Α4lNAFƓp`ط)`qܫ$Oԝ!(R>}4MY=qb8Ida#-ʣta8ʨ@gcYM-h-[W;=b%A_5]l#b={ z=ü `"ZZiSeO 2SY $%/X302j\0܂y*|rs,ja9͢hCv¿iO}\1ҜA6źjψ7 {!2YʹlzFŶjgv:' J^ߣߔ9|(5R*LmL/|H݂!kG -z6It7VR#p4-gCF@pO20Y%<*uygouZgYX'(M(Xr9NXT dnxcցbu$1-hS92M=>[fMs51As9rV&ixBo:u`r$5V~H-KzA?/X'9_F:PDsM Il}Nunpc9HH\I%&هt"׈Y>5?ƻT^s11p䡱ӠZ~ }㲘omd- `M,7Y2!raQ=E&goTyWZX3k5T9_C†pēA)|:eH 1(9=@f@ږmT84=*?&WLPݛjgvdE!dawŝ kwxcL cs. -V/jm9!Oefy6;߃y}h4abdXFVZHTVh+N!&GҒkBThF!9SGvyr9s :xo|mOIt'KLnB䏐Lp^)^zgϐFꅙ4<0L^9ݩlUEj5Y9h duFdȝ+ޔcla)F: Qn^. 9\`$oS²`M8P'hCUmyI0M6јk!pzYF8%W<)@#~y(QDa|D̡a+1e<ۯ]ŭ%dhnqјo[ xh#5k&gHң  ^7?]@w`G2!0`}oHO`0žwi2=o)Cy<ڂj>@OoW40V)'RwdF]TQ淬**L 81޼|ڑ 4/},jolJ׀?Tf7$LS{ySPӾ\S+}[ j<Xj+;[we䠈e%E7Es9swYB^獦_U\blI @?԰/GF[K# 9f5#upG"ܮ,~,~,L374^1I)3o&uXa5}EԾڢi߉!ydP|`1ߧ {Mr{D*7o^*}vDv# _ٵ:CAT5@ ;m靷/ Q uG$luc9-fzJSّT .7ryzCk@oא[Qǎ,ٹ2{wyjZeVEDL I}' "Dxj1vՌb:>3f 7#P29}mﻳŮߟf<aƆc>/Yg.|s>بZjAWX˃Fꔤ;5Lr.'_Gbա%z1@HgMT4eZ5P֡]Mk0B p=bMլNmmKO=+v )§#t|昬 ֶri8V{cU,xZ\qT:cP!/M7j ~IhC!ER;J8^QsD[іl 6l)U(b@Y$v(\'w*0-/lR7_T C6D\i*%) MP|k ٞWuB7r9+@CA_d A<;:\`W\}k0RuN{ˮƟXQbx-!I_ݒ~1cw}GUL<-qaquRu5e/g×尗#IVGo'B&LMufOtʻw Rf-KB+a! ߧ򣲙Ҁ~ (!]C )kL PYL 4G^ۥ$= p퓆9v{ΌH$gc"5AHc g HL+Q*y^OAH rCD*|زb[ ^ju-K[It L>+l<ԭ?rGy.Rw[6UOeM

V鴡Ӵ~3`gN&DtJoZ{fha B`w -ԛڄ}^L~1E( ƌPbg ]hM_Dt~8BN}ZۈB4b>g|. 9Pa-B?q@}<8 L23ˎ( shKF;jvO6)DV G^huR/v0X%+ic pV@ Bhh"!Ltvİhe;7GJwC\ ШavPHhU8OLIV,α/jvmE;>qJ]/n3O~]r{hو|AtCe0_ ŪN!;~=H[nnl'3hZqlSwA. 8 愾#d  oOgs[ivj1VDߝoEz(S\s=a$I#I[g&1{VbSTV^>t]"1 g<5 }ѵ9='=c T=1P,ۚ`A 7|`@Z qD x ݮ 3~f&N t>7|Y( 1.I G-^\hYgu®nLj0e,C{=̘_/mj'=G"0aIe`DD3"-^[̸6jB~7*8ԸnzZƑ0p|{F}\ A,{HS_Hmyzw {5(I~|^ԦT$ "BE:ߙ ?\K|^r+v?o_tI@܈pw:}8fOٽQx1E/7Ama&,쯲t ۣXwVbz U+j8*8{s D$Nk^lYKz^TD͊~}aNqu}yE6ea~?3X~o씾qO.8dyt u} |AbooQT|7|7|7|O.8dyt u} |AbooQT|7|7|7|߻C2}ۇg} F0 Hi= 6$O xA1*)BYARE_/uyM {/Q&q ,#HC4P(S1Oj<#`)rW[&@"5Guj_kRpD nX~i4Hm51ty=N:3QV_&yjQV_&yjlOX8Ap/O:CH]Dfo@UQ?[xYjp<1{{JFFB!ESs|iIKrI qP*,O_\Gĵ4wC0`3ʎLSS7O_nF]*8>st>HH6hЉ1Mة>8>LA9OHlP׮܃2i/M[Iu@ۥ90ʺr wP.5'ZQyrG:!Wp^D2H6< :)Z&ah3t\}N&GUH):ye5W"@3~u79t_ ܬguL,K$0?؎aQ,iTZțdH `J 5M뀀DEwP,$i3'u~CIdVcMOY HF#0W=;ܒ2)]q&?F2&),6]=ȌQufGoȨD^x[SԼ[,~s2Ң4IF>cfSBdaƾz}/mȚ_b!\ǛS|$e!屷:#|K?>zGY]!rus-0@~g]Rnz=”}+U? Ì_ZZjr%0*Ji$ }5JǪK4qxmRq1䞚gtTZ[r CƄ/@`KA'Xw-L$ȳb͆_ݦ`Nϖ??(N'^lj"Йu ?LC4,_\K4"F[}{T:{XȽ N_,#;_-+z1֠sn<ؐKOR%0.ԉCSѽЮ8Y1NgHϮ{a8/=;uosNzn:ML#7SZO*&Ji-Ҝ@lГo>E'bjZ'#@hb5rV{tAHY8BŒy`l%($Eg03973DhmS\^>&hś蜧Fime:FP|i'3Hsćq)}u'`l|c-Gd?z1F<A79Z~MZ@!wtmo|zP2&ljs3Y@J H3z)pjlV%tpY pğ&L]݉Y@5gϺϻ#3|< ]JH0j%Cw :=.T [W@/f[?%LѕhC!3'zi4$`j(s Kx|}(am զ'PfM 1 xHT^.vm5g0Y[(@mj>b\Tn_Z'ιK9@,a?;I F5b4w .YQ^D`ׯӱ>WS%+`I;9-b@spnD<Ìtj6ϑTG j LBgt67s A3d%H(ДKWg4wxL*.j33"ķ$2BU-3Snig@6̺@9tlvy[j['qhAỤV]x;䫑-&)Z@ɐsTu%0Nagstamon/Nagstamon/resources/nagstamon.ico000077500000000000000000000226761316117564000214550ustar00rootroot0000000000000000 %(0`               "*18?DJNR V!!X!! Z!! Z!!!Z!! Y W UQMIC=70(     &0;EO Y##"b'&%k**(t-,+|//-22044266487598699699788677565343111///-,,*z))'r&&$i#"!`VMC8. #   #:"""K###U%%$`((&k+*)u.-,00/432775::8>=;A@>DDBGGEJJHMLJONKNNKLLIJIGGFDCCA@@>=<;:9766433100.--+}**(s''&h%$$^###S"!!H4   ;;;lBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCAAA777JCCCCCCkkkUUUCCCCCCNCCC4CCC]]]CCCCCC CCCTTT}}}|||{{{DDDCCCPCCC}}}|||zzzYYYCCCnCCCoopqrstvwxyz{|}~~~~{{{{{{fffCCCoCCC*[}}}{{{gggCCCoCCCF~~~|||gggCCCoCCCEES~~~gggCCCoCCCEENNNhhhCCCoCCCEENhhhCCCoCCCEEFFMhhhCCCoCCCEEMhhhCCCoCCC&  HjjjCCCoCCCe___________________q_ju_________^6sk ooooooHjjjCCCoCCCjjffffffffffffffffffffffffffhfffffffffffffffWWGGjjjCCCoCCCppllllllllllllllllllllllllllllllllllllllllllllllRRRGGjjjCCCoCCCuusssssssssssssssssssssssssssssssssssssssssssssssssssccc<<<1FFjjjCCCoCCC{{zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzttaaQQDD::--OOkkkCCCoCCC~~~kkkCCCoCCC}}}kkkCCCoCCC{{{kkkCCCoCCClllCCCoCCCzz{lllCCCoCCC{{{{{~jjjCCCoCCCUUUCCCkCCC|KKKCCCCCCACCCCCCkkkQQQCCCCCCCCCRCCCRRRGGGCCCCCC)CCC)CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCNagstamon/Nagstamon/resources/nagstamon.rst000066400000000000000000000020671316117564000215000ustar00rootroot00000000000000========= Nagstamon ========= ---------------------------------------------------------------- Nagios status monitor which takes place in systray or on desktop ---------------------------------------------------------------- :Author: This manual page has been written by Carl Chenet and updated by Henri Wahl :Date: 2016-09-05 :Version: 2.0 :Manual section: 1 :Copyright: This manual page is licensed under the GPL-2 license. SYNOPSIS ======== nagstamon [alternate-config] DESCRIPTION =========== Nagstamon is a Nagios status monitor which takes place in systray or on desktop as floating statusbar to inform you in realtime about the status of your Nagios monitored network\&. Nagstamon connects to multiple Nagios, Opsview, Icinga, Centreon, Op5Monitor, Check_MK Multisite and Thruk monitoring servers. Experimental support for Zabbix, Zenoss and Livestatus is included. The command can optionally take one argument giving the path to an alternate configuration file. RESSOURCES ========== https://nagstamon.ifw-dresden.de Nagstamon/Nagstamon/resources/nagstamon.sfd000066400000000000000000000344361316117564000214510ustar00rootroot00000000000000SplineFontDB: 3.0 FontName: Nagstamon FullName: Nagstamon FamilyName: Nagstamon Weight: Regular Copyright: Copyright (c) 2016, Henri Wahl UComments: "2016-4-6: Created with FontForge (http://fontforge.org)" Version: 001.000 ItalicAngle: 0 UnderlinePosition: -102.4 UnderlineWidth: 51.2 Ascent: 819 Descent: 205 InvalidEm: 0 LayerCount: 2 Layer: 0 0 "Back" 1 Layer: 1 0 "Zeichen" 0 XUID: [1021 5 1214093225 10093350] StyleMap: 0x0000 FSType: 0 OS2Version: 0 OS2_WeightWidthSlopeOnly: 0 OS2_UseTypoMetrics: 1 CreationTime: 1459941430 ModificationTime: 1462171755 PfmFamily: 17 TTFWeight: 400 TTFWidth: 5 LineGap: 92 VLineGap: 92 OS2TypoAscent: 0 OS2TypoAOffset: 1 OS2TypoDescent: 0 OS2TypoDOffset: 1 OS2TypoLinegap: 92 OS2WinAscent: 0 OS2WinAOffset: 1 OS2WinDescent: 0 OS2WinDOffset: 1 HheadAscent: 0 HheadAOffset: 1 HheadDescent: 0 HheadDOffset: 1 OS2Vendor: 'PfEd' MarkAttachClasses: 1 DEI: 91125 LangName: 1033 "" "" "" "" "" "" "" "" "" "" "" "" "" "Copyright (c) 2016, Henri Wahl (),+AAoA-with Reserved Font Name Untitled1.+AAoACgAA-This Font Software is licensed under the SIL Open Font License, Version 1.1.+AAoA-This license is copied below, and is also available with a FAQ at:+AAoA-http://scripts.sil.org/OFL+AAoACgAK------------------------------------------------------------+AAoA-SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007+AAoA------------------------------------------------------------+AAoACgAA-PREAMBLE+AAoA-The goals of the Open Font License (OFL) are to stimulate worldwide+AAoA-development of collaborative font projects, to support the font creation+AAoA-efforts of academic and linguistic communities, and to provide a free and+AAoA-open framework in which fonts may be shared and improved in partnership+AAoA-with others.+AAoACgAA-The OFL allows the licensed fonts to be used, studied, modified and+AAoA-redistributed freely as long as they are not sold by themselves. The+AAoA-fonts, including any derivative works, can be bundled, embedded, +AAoA-redistributed and/or sold with any software provided that any reserved+AAoA-names are not used by derivative works. The fonts and derivatives,+AAoA-however, cannot be released under any other type of license. The+AAoA-requirement for fonts to remain under this license does not apply+AAoA-to any document created using the fonts or their derivatives.+AAoACgAA-DEFINITIONS+AAoAIgAA-Font Software+ACIA refers to the set of files released by the Copyright+AAoA-Holder(s) under this license and clearly marked as such. This may+AAoA-include source files, build scripts and documentation.+AAoACgAi-Reserved Font Name+ACIA refers to any names specified as such after the+AAoA-copyright statement(s).+AAoACgAi-Original Version+ACIA refers to the collection of Font Software components as+AAoA-distributed by the Copyright Holder(s).+AAoACgAi-Modified Version+ACIA refers to any derivative made by adding to, deleting,+AAoA-or substituting -- in part or in whole -- any of the components of the+AAoA-Original Version, by changing formats or by porting the Font Software to a+AAoA-new environment.+AAoACgAi-Author+ACIA refers to any designer, engineer, programmer, technical+AAoA-writer or other person who contributed to the Font Software.+AAoACgAA-PERMISSION & CONDITIONS+AAoA-Permission is hereby granted, free of charge, to any person obtaining+AAoA-a copy of the Font Software, to use, study, copy, merge, embed, modify,+AAoA-redistribute, and sell modified and unmodified copies of the Font+AAoA-Software, subject to the following conditions:+AAoACgAA-1) Neither the Font Software nor any of its individual components,+AAoA-in Original or Modified Versions, may be sold by itself.+AAoACgAA-2) Original or Modified Versions of the Font Software may be bundled,+AAoA-redistributed and/or sold with any software, provided that each copy+AAoA-contains the above copyright notice and this license. These can be+AAoA-included either as stand-alone text files, human-readable headers or+AAoA-in the appropriate machine-readable metadata fields within text or+AAoA-binary files as long as those fields can be easily viewed by the user.+AAoACgAA-3) No Modified Version of the Font Software may use the Reserved Font+AAoA-Name(s) unless explicit written permission is granted by the corresponding+AAoA-Copyright Holder. This restriction only applies to the primary font name as+AAoA-presented to the users.+AAoACgAA-4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font+AAoA-Software shall not be used to promote, endorse or advertise any+AAoA-Modified Version, except to acknowledge the contribution(s) of the+AAoA-Copyright Holder(s) and the Author(s) or with their explicit written+AAoA-permission.+AAoACgAA-5) The Font Software, modified or unmodified, in part or in whole,+AAoA-must be distributed entirely under this license, and must not be+AAoA-distributed under any other license. The requirement for fonts to+AAoA-remain under this license does not apply to any document created+AAoA-using the Font Software.+AAoACgAA-TERMINATION+AAoA-This license becomes null and void if any of the above conditions are+AAoA-not met.+AAoACgAA-DISCLAIMER+AAoA-THE FONT SOFTWARE IS PROVIDED +ACIA-AS IS+ACIA, WITHOUT WARRANTY OF ANY KIND,+AAoA-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF+AAoA-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT+AAoA-OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE+AAoA-COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,+AAoA-INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL+AAoA-DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING+AAoA-FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM+AAoA-OTHER DEALINGS IN THE FONT SOFTWARE." "http://scripts.sil.org/OFL" Encoding: ISO8859-1 UnicodeInterp: none NameList: AGL For New Fonts DisplaySize: -48 AntiAlias: 1 FitToEm: 0 WinInfo: 63 21 9 BeginPrivate: 0 EndPrivate TeXData: 1 0 0 346030 173015 115343 0 1048576 115343 783286 444596 497025 792723 393216 433062 380633 303038 157286 324010 404750 52429 2506097 1059062 262144 BeginChars: 256 6 StartChar: A Encoding: 65 65 0 Width: 1024 VWidth: 0 Flags: HW LayerCount: 2 Fore SplineSet 828 788 m 0 854 788 878 776 891 754 c 0 904 732 903 706 890 685 c 2 518 55 l 1 518 55 l 2 518 55 l 0 506 36 485 23 462 21 c 0 439 19 415 29 401 46 c 2 173 320 l 1 172 320 l 1 172 320 l 2 170 322 169 325 169 327 c 0 155 345 150 367 157 388 c 0 166 413 189 431 216 434 c 0 239 437 261 426 276 409 c 0 278 408 280 407 282 405 c 2 282 405 l 1 446 209 l 1 766 752 l 1 766 752 l 2 767 753 l 2 767 753 l 0 769 756 l 1 767 753 l 1 779 774 802 788 828 788 c 0 828 788 m 1024 EndSplineSet Validated: 37 EndChar StartChar: D Encoding: 68 68 1 Width: 1024 VWidth: 0 Flags: HW LayerCount: 2 Fore SplineSet 510.130859375 820.473632812 m 0 736.26171875 820.306640625 918.166015625 638.131835938 918 412 c 0 917.844726562 185.879882812 735.66015625 3.9619140625 509.538085938 4.12890625 c 0 283.404296875 4.294921875 101.491210938 186.47265625 101.657226562 412.6015625 c 0 101.822265625 638.733398438 284.005859375 820.639648438 510.130859375 820.473632812 c 0 540.21875 427.099609375 m 1 548.076171875 828.592773438 453.51953125 856.967773438 443.743164062 410.92578125 c 0 443.724609375 391.669921875 452.58203125 374.499023438 466.563476562 363.0078125 c 0 469.541992188 359.637695312 472.920898438 356.520507812 476.698242188 353.740234375 c 0 700.552734375 190.201171875 831.282226562 225.140625 540.21875 427.099609375 c 1 540.21875 427.099609375 m 1024 EndSplineSet EndChar StartChar: P Encoding: 80 80 2 Width: 1024 VWidth: 0 Flags: H LayerCount: 2 Fore SplineSet 284.974609375 810 m 2 746 810 l 2 798.637695312 810 841.798828125 762.37890625 841.798828125 704.31640625 c 2 841.798828125 112.484375 l 2 841.826171875 53.98046875 799.076171875 6.48046875 746 6.7998046875 c 2 284.974609375 6.7998046875 l 2 231.92578125 6.48046875 189.169921875 54.4091796875 189.169921875 112.484375 c 2 189.169921875 704.31640625 l 2 189.169921875 762.37890625 232.328125 810 284.974609375 810 c 2 318.0078125 677.8359375 m 1 318.0078125 131.208007812 l 1 445.793945312 131.208007812 l 1 445.793945312 320.858398438 l 1 530.096679688 320.858398438 l 2 593.155273438 320.858398438 641.50390625 336.236328125 675.141601562 366.993164062 c 0 708.989257812 397.990234375 725.92578125 442.173828125 725.92578125 499.530273438 c 0 725.92578125 556.643554688 708.989257812 600.57421875 675.141601562 631.329101562 c 0 641.50390625 662.327148438 593.155273438 677.8359375 530.096679688 677.8359375 c 2 318.0078125 677.8359375 l 1 445.793945312 575.681640625 m 1 516.487304688 575.681640625 l 2 541.268554688 575.681640625 560.40625 569.08984375 573.907226562 555.915039062 c 0 587.409179688 542.731445312 594.155273438 523.935546875 594.155273438 499.530273438 c 0 594.155273438 475.123046875 587.409179688 456.205078125 573.907226562 442.780273438 c 0 560.40625 429.595703125 541.268554688 423.013671875 516.487304688 423.013671875 c 2 445.793945312 423.013671875 l 1 445.793945312 575.681640625 l 1 445.793945312 575.681640625 m 1024 EndSplineSet Validated: 33 EndChar StartChar: N Encoding: 78 78 3 Width: 1024 VWidth: 0 Flags: H LayerCount: 2 Fore SplineSet 753.772460938 11.3427734375 m 5 495.978515625 168.923828125 l 5 237.655273438 12.21484375 l 5 307.854492188 306.088867188 l 5 78.9951171875 503.356445312 l 5 380.182617188 527.395507812 l 5 497.068359375 806.008789062 l 5 613 527 l 5 914.10546875 501.93359375 l 5 684.5703125 305.446289062 l 5 753.772460938 11.3427734375 l 5 753.772460938 11.3427734375 m 1028 EndSplineSet Validated: 1 EndChar StartChar: F Encoding: 70 70 4 Width: 1024 VWidth: 0 Flags: HWO LayerCount: 2 Fore SplineSet 62.1015625 758.361328125 m 0 72.5205078125 758.37109375 86.8603515625 758.259765625 103.15234375 758.259765625 c 2 304.185546875 758.259765625 l 2 309.717773438 758.259765625 313.795898438 755.147460938 315.573242188 753.116210938 c 0 317.3515625 751.087890625 317.9140625 749.629882812 318.338867188 748.46484375 c 0 319.202148438 746.140625 319.306640625 744.7109375 319.306640625 743.069335938 c 2 319.306640625 144.499023438 l 1 433.311523438 144.31640625 l 1 433.311523438 742.670898438 l 2 433.311523438 745.080078125 433.662109375 747.731445312 435.69140625 750.935546875 c 0 437.719726562 754.13671875 442.969726562 758.079101562 448.72265625 758.077148438 c 2 816.37109375 759.907226562 l 2 823.99609375 759.9453125 828.444335938 754.53125 829.899414062 751.705078125 c 0 831.354492188 748.879882812 831.58203125 746.868164062 831.58203125 744.716796875 c 2 831.58203125 146.146484375 l 1 899.012695312 146.146484375 l 2 917.5 146.146484375 933.731445312 146.096679688 945.38671875 146.012695312 c 0 951.2109375 145.962890625 955.891601562 145.911132812 959.197265625 145.850585938 c 0 960.83984375 145.819335938 962.127929688 145.788085938 963.189453125 145.749023438 c 0 963.709960938 145.728515625 964.146484375 145.70703125 964.782226562 145.657226562 c 0 965.10546875 145.625976562 965.405273438 145.6171875 966.2890625 145.461914062 c 0 966.725585938 145.392578125 967.21484375 145.360351562 968.6796875 144.87109375 c 0 969.41796875 144.615234375 970.396484375 144.370117188 972.319335938 143.064453125 c 0 973.276367188 142.41015625 975.927734375 139.595703125 975.927734375 139.5859375 c 2 975.927734375 139.5859375 978.756835938 131.026367188 978.756835938 131.026367188 c 1 978.756835938 25.259765625 l 2 978.756835938 19.96484375 975.655273438 15.9736328125 973.733398438 14.302734375 c 0 971.809570312 12.619140625 970.560546875 12.1298828125 969.6484375 11.751953125 c 0 967.817382812 10.974609375 967.079101562 10.904296875 966.434570312 10.78125 c 0 965.143554688 10.5263671875 964.532226562 10.49609375 963.834960938 10.4345703125 c 0 962.431640625 10.3125 961.056640625 10.2626953125 959.333984375 10.220703125 c 0 955.869140625 10.119140625 951.168945312 10.0771484375 945.31640625 10.0595703125 c 0 933.6171875 10.0166015625 917.427734375 10.0693359375 899.012695312 10.0693359375 c 2 702.243164062 10.0693359375 l 2 696.708984375 10.0693359375 691.521484375 13.69921875 689.380859375 16.9140625 c 0 687.23828125 20.126953125 686.80078125 23.173828125 686.80078125 25.47265625 c 2 686.80078125 623.830078125 l 1 572 622 l 1 572 23.6416015625 l 2 572 20.458984375 571.103515625 17.296875 568.857421875 14.337890625 c 0 566.602539062 11.380859375 562.296875 8.236328125 556.517578125 8.2392578125 c 2 189.932617188 8.421875 l 2 181.958007812 8.462890625 177.62109375 14.1435546875 176.283203125 16.91015625 c 0 174.931640625 19.673828125 174.741210938 21.5712890625 174.741210938 23.6123046875 c 2 174.741210938 622.182617188 l 1 103.15234375 622.182617188 l 2 86.744140625 622.182617188 72.3330078125 622.23046875 61.9765625 622.323242188 c 0 56.798828125 622.364257812 52.6494140625 622.4140625 49.6875 622.477539062 c 0 48.2099609375 622.508789062 47.0556640625 622.537109375 46.0693359375 622.588867188 c 0 45.580078125 622.610351562 45.173828125 622.629882812 44.5087890625 622.690429688 c 0 44.1650390625 622.721679688 43.8427734375 622.733398438 42.8740234375 622.915039062 c 0 42.3984375 623.0078125 41.8369140625 623.059570312 40.2548828125 623.661132812 c 0 39.46484375 623.965820312 38.3828125 624.303710938 36.4697265625 625.762695312 c 0 35.5146484375 626.485351562 33.080078125 629.403320312 33.080078125 629.403320312 c 1 23.3173828125 662.143554688 35.2021484375 708.452148438 30.73046875 743.069335938 c 0 30.73046875 750.43359375 35.826171875 754.373046875 37.9150390625 755.625976562 c 0 40.0048828125 756.893554688 41.0576171875 757.096679688 41.88671875 757.321289062 c 0 43.541015625 757.782226562 44.2802734375 757.831054688 45.048828125 757.911132812 c 0 46.5771484375 758.086914062 47.8779296875 758.146484375 49.4794921875 758.198242188 c 0 52.7021484375 758.311523438 56.8916015625 758.350585938 62.1015625 758.361328125 c 0 62.1015625 758.361328125 m 1024 EndSplineSet EndChar StartChar: H Encoding: 72 72 5 Width: 1024 VWidth: 0 Flags: HW LayerCount: 2 Fore SplineSet 9.515625 160.0234375 m 1 1017 160.0234375 l 1 1017 -4.302734375 l 1 9.515625 -4.302734375 l 1 9.515625 160.0234375 l 1 9.515625 488.51171875 m 1 1017 488.51171875 l 1 1017 324.188476562 l 1 9.515625 324.188476562 l 1 9.515625 488.51171875 l 1 9.515625 817 m 1 1017 817 l 1 1017 652.67578125 l 1 9.515625 652.67578125 l 1 9.515625 817 l 1 EndSplineSet Validated: 1 EndChar EndChars EndSplineFont Nagstamon/Nagstamon/resources/nagstamon.svg000077500000000000000000000225661316117564000215000ustar00rootroot00000000000000 image/svg+xml Nagstamon/Nagstamon/resources/nagstamon.ttf000066400000000000000000000363641316117564000214740ustar00rootroot00000000000000`FFTMxz<GDEF<OS/2Y8bh`cmap jcvt "Pgasp<glyfEhhead %6hhea$$hmtx +locaTmaxpOH name-6post <4c _< LL 55\ h@.3  PfEdAP33\5- "e NdHADFHNPADFHNP"***^2P"2./<2<2/<2<23!'3#"V"f21'#&5&7676?63<+(%& ,"@)D# )#e5 ".466'&'&/޼mm߼lmI yI(5m޼mm߼@$4PY *`fg:;234763%23232323:230#"#"#*+"'&5#!"'&5#"#"#&#*#"'"'0'&6'476362323>r o C   s HW jW V W 1 7!!!!!! N ' -'%F-ut-E &J*%&!2#!"&54635327654'&#32+5'98(3(88IT_2332_TF&(&F*>,+>>+P,>ݾ..WU./f(I> q$ B g #5 < ] { H     S ">q 44Copyright (c) 2016, Henri WahlCopyright (c) 2016, Henri WahlNagstamonNagstamonRegularRegularFontForge 2.0 : Nagstamon : 2-5-2016FontForge 2.0 : Nagstamon : 2-5-2016NagstamonNagstamonVersion 001.000 Version 001.000 NagstamonNagstamonCopyright (c) 2016, Henri Wahl (<URL|email>), with Reserved Font Name Untitled1. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.Copyright (c) 2016, Henri Wahl (), with Reserved Font Name Untitled1. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.http://scripts.sil.org/OFLhttp://scripts.sil.org/OFL3 $')+13 92*LNagstamon/Nagstamon/resources/nagstamon_logo_bar.svg000066400000000000000000000221541316117564000233320ustar00rootroot00000000000000 image/svg+xml Nagstamon/Nagstamon/resources/nagstamon_logo_toparea_template.svg000077500000000000000000000434201316117564000261160ustar00rootroot00000000000000 image/svg+xml Nagstamon/Nagstamon/resources/nagstamon_systrayicon_empty.svg000066400000000000000000000107461316117564000253570ustar00rootroot00000000000000 image/svg+xml Nagstamon/Nagstamon/resources/nagstamon_systrayicon_template.svg000066400000000000000000000227331316117564000260330ustar00rootroot00000000000000 image/svg+xml Nagstamon/Nagstamon/resources/qt.conf000066400000000000000000000000261316117564000202430ustar00rootroot00000000000000[Paths] Plugins = '.' Nagstamon/Nagstamon/resources/warning.wav000066400000000000000000000427061316117564000211470ustar00rootroot00000000000000RIFFEWAVEfmt +"VdataE                                                                                                                                $  ( %( &7MI2' ;@8XX@.G7# ):GH+ /'! (0" .<6)!! '3  3*  !  +1&  6:4   &2$  # ! ! 7&1&#6& %! '   85% #3+ )"    44 /."  #%&    /(&.! +3&     $"($ *?"      #%& 18'   #$"+" /7*  $(  #'  %.%  ,50$!& 0/&    $+,# &/,& /.  %14&     +.+)  $*$ !,55$02+     02*   "$,043 %++( $+ !.0 $"0..* "%&$ #&+ ('  # !.*#! "  ('$& "  '% *)$   #    %&      !"          #           %          #         !                      !       #                !                                                                                                                                              ')* 13[`FC8 &=A-%B:*- YsYT7 1 2 FRScfV;>b^TPB2uEBi!rA ^[r'NN(g{}?~ATV-hNeM,E[qI02N/6h3i(z^lopsV7ZU42m// (%#}#"CbD58EWlfnq)o$IlomRMhX cO;9yR  ]+MN}T ) |UY_a:Y+}G7Kv/C-uG1@P`W3`&;r<h32200bca9;w?v` b(LQO::q:qUR~zG HtPS`/]%Ovw)jd*>km.2@MT<~:BC?@?~BL E9u5 {BM[QQE!21Ar_u\<?T!8Ak#Hn#r0NV/nA!/05{2[,d*e^w:a!skp]T}'-"- $ylS>KMOY TRXVY[ZMWU\] eVE,K]:jkK3t@]XFqz568}}|}^^mHROvU\7> ?,0g1eb7@\ Mx-P'A,xP(pll( .Ko.Oo@moVT6rD&-(/+Yw^N :aaeV,:=M\wxM[o|yrrf| |{yx||x   |}skkxz+1# Yb]\]e-2xyZLj3@UTC%Oy_,+*Fu@r6 3ostnvQ;m* c`"L  "#GImEI33O)n?Bjkk/)].],h;pm,b-c31eg23e3lpUUpHNu;*!#>}{~~@@__C%\^_rrK4|~GGYjjO@B@Mc`dle JJDOQUZZWYXUYVXSROV c aQ_RR^^qs e <a-|~N7~JXij*$Fpq#m  ST.GsEF.FFu1s4wwwwC:x}8|@BE?>?>?D>}@?FH F??8<9R8QZ8988787 ^(Z 6b,[.debe44j6khln70c_ep ?@@/11\1/uLKMx*.X+~P~Q|T2 5]^_Sz)vOb>hhh)#(ooqn&lHnn%K&ikxX{CAa"!Xz[$5Qmom;S:6xeiJ6UQEJ(U88#% !HE*       Nagstamon/Nagstamon/setup.py000066400000000000000000000106211316117564000164520ustar00rootroot00000000000000# encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2016 Henri Wahl et al. # # 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 # from distutils.core import setup import sys import platform from Nagstamon.Config import AppInfo NAME = AppInfo.NAME VERSION = AppInfo.VERSION # workaround to get directory of Qt5 plugins to add missing 'mediaservice' folder needed for audio on OSX and Windows import os.path from PyQt5 import QtCore if platform.system() == 'Windows': QTPLUGINS = os.path.join(os.path.dirname(QtCore.__file__), 'plugins') elif platform.system() == 'Darwin': # works of course only with Fink-based Qt5-installation QTPLUGINS = '/sw/lib/qt5-mac/plugins' if platform.system() in ('Windows', 'Darwin'): from cx_Freeze import setup, Executable os_dependent_include_files = ['Nagstamon/resources/qt.conf', 'Nagstamon/resources', '{0}/mediaservice'.format(QTPLUGINS)] else: from distutils.core import setup os_dependent_include_files = ['Nagstamon/resources'] if platform.system() == 'Windows': base = 'Win32GUI' if sys.platform == 'win32' else None CLASSIFIERS = [ 'Intended Audience :: System Administrators', 'Development Status :: 5 - Production/Stable', 'Environment :: Win32 (MS Windows)', 'Environment :: X11 Applications', 'Environment :: MacOS X', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX :: Linux', 'Operating System :: POSIX', 'Natural Language :: English', 'Programming Language :: Python', 'Topic :: System :: Monitoring', 'Topic :: System :: Networking :: Monitoring' ] # Dependencies are automatically detected, but it might need # fine tuning. build_exe_options = dict(packages=['PyQt5.QtNetwork', 'keyring.backends.file', 'keyring.backends.Gnome', 'keyring.backends.Google', 'keyring.backends.kwallet', 'keyring.backends.multi', 'keyring.backends.OS_X', 'keyring.backends.pyfs', 'keyring.backends.SecretService', 'keyring.backends.Windows'], include_files=os_dependent_include_files, include_msvcr=True, excludes=[]) bdist_mac_options = dict(iconfile='Nagstamon/resources/nagstamon.icns', custom_info_plist='Nagstamon/resources/Info.plist') bdist_dmg_options = dict(volume_label='{0} {1}'.format(NAME, VERSION), applications_shortcut=False) executables = [ Executable('nagstamon-qt.py', base=base, icon='Nagstamon/resources/nagstamon.ico') ] setup(name=NAME, version=VERSION, license='GNU GPL v2', description='Nagios status monitor for desktop', long_description='Nagstamon is a Nagios status monitor which takes place in systray or on desktop (GNOME, KDE, Windows) as floating statusbar to inform you in realtime about the status of your Nagios and derivatives monitored network. It allows to connect to multiple Nagios, Icinga, Opsview, Op5Monitor, Check_MK/Multisite, Centreon and Thruk servers.', classifiers=CLASSIFIERS, author='Henri Wahl', author_email='h.wahl@ifw-dresden.de', url='https://nagstamon.ifw-dresden.de', download_url='https://nagstamon.ifw-dresden.de/download', scripts=['nagstamon.py'], packages=['Nagstamon', 'Nagstamon.Server', 'Nagstamon.thirdparty'], package_dir={'Nagstamon': 'Nagstamon'}, package_data={'Nagstamon': ['resources/*']}, data_files=[('%s/share/man/man1' % sys.prefix, ['Nagstamon/resources/nagstamon.1'])], options=dict(build_exe=build_exe_options, bdist_mac=bdist_mac_options, bdist_dmg=bdist_dmg_options), executables=executables ) Nagstamon/Nagstamon/thirdparty/000077500000000000000000000000001316117564000171325ustar00rootroot00000000000000Nagstamon/Nagstamon/thirdparty/Xlib/000077500000000000000000000000001316117564000200305ustar00rootroot00000000000000Nagstamon/Nagstamon/thirdparty/Xlib/ChangeLog000066400000000000000000000101221316117564000215760ustar00rootroot000000000000002007-06-10 Mike Grant * (many files): (mgg) Converted tabs to spaces throughout the codebase, using reindent.py (SF id: 1559082) 2007-03-18 Mike Grant * Xlib/display.py: (mgg) Added a get_atom alias that uses the internal cache * Xlib/xobject/drawable.py: (mgg) Added a raise_window() alias to the Window class 2007-02-15 Mike Grant * Xlib/xauth.py: (mgg) Python 2.5 didn't like the way the buffer type was used, resulting in X authorisation failure, so reverted to using slices 2006-11-22 Mike Grant * Xlib/ext/record.py: Addition of RECORD extension by Alex Badea , SF patch id #1538663 (demo program in python-xlib/examples/record_demo.py) 2006-09-20 Mike Meyer * Xlib/ext/xinerama.py: (mwm) Addition of Xinerama extension 2006-07-22 Mike Grant Various typo fixes, general updates. Changelog hasn't been maintained since 2002, but some of the more significant comments from cvs logs follow: * Xlib/display.py: (petli) Fix bug in refresh_keyboard_mapping: ignore modifier and pointer remappings. Plays nice with pydoc. Copied some text from the docs to __doc__ strings in Xlib/display.py so that they appear when you use pydoc. Completed documentation for Display objects. * Xlib/XK.py: (calroc99) Minor doc string changes. Called load_keysym_group() for miscellany and latin1 keysyms, rather than importing the modules. * Xlib/keysymdef/*: (calroc99) Small change to keysym loading. Works the same way. * Xlib/support/*, Xlib/xauth.py, Xlib/error.py: (petli) Added ~/.Xauthority parsing by Python code instead of relying on /usr/X11R6/bin/xauth. Not activated yet in all cases yet? Activated in unix_support.py. * Xlib/xobject/drawable.py: (petli) Fix bugs in definition and method of GrabButton/Pointer * Xlib/xobject/icccm.py: (petli) Add WithdrawnState to WMHints * doc/*: (petli) documentation updates, typos and completing documentation for Display objects 2002-03-30 Peter Liljenberg * support/unix_connect.py: Handle fcntl/FCNTL changes in Python 2.2. 2002-03-11 Peter Liljenberg * xobject/drawable.py (Drawable.fill_arc): This should be a PolyFillArc. Fri Jan 19 17:49:45 2001 Peter Liljenberg * XK.py: Moved all keysyms into separate modules, based on their category. By default only the miscellany and latin1 keysyms are loaded, and other have to be loaded by importing the Xlib.keysymdef. module, or calling load_keysym_group('category'). * display.py (Display.lookup_string): (Display.rebind_string): Functions to translate keysyms to strings, and binding keysyms to new strings. 2001-01-16 * xobject/drawable.py (Window.send_event): * display.py (Display.send_event): Changed the order of the event_mask and propagate arguments. 2001-01-10 * display.py (Display._update_keymap): The first half of the update algorithm operated on an earlier type of code->sym map than the second half. Stupid, stupid. It would have been nice with a type-checker now. Tue Jan 9 13:03:19 2001 Peter Liljenberg * display.py (Display._update_keymap): Fixed call to append with 1.5.2 semantics, broke in newer Pythons. 2000-12-22 * display.py (Display.keycode_to_keysym): (Display.keysym_to_keycode): (Display.keysym_to_keycodes): (Display.refresh_keyboard_mapping): (Display._update_keymap): Added keymap cache implementation. 2000-12-21 * xobject/colormap.py (Colormap.alloc_named_color): Extended to handle #000000 style color specifications. * xobject/drawable.py (Window.reparent): Renamed from reparent_window. * display.py (Display.set_error_handler): Added. 2000-12-20 * display.py (_BaseDisplay): Implement a cache of atom names. Nagstamon/Nagstamon/thirdparty/Xlib/X.py000066400000000000000000000237471316117564000206260ustar00rootroot00000000000000# Xlib.X -- basic X constants # # Copyright (C) 2000 Peter Liljenberg # # 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 # Avoid overwriting None if doing "from Xlib.X import *" NONE = 0 ParentRelative = 1 # background pixmap in CreateWindow # and ChangeWindowAttributes CopyFromParent = 0 # border pixmap in CreateWindow # and ChangeWindowAttributes # special VisualID and special window # class passed to CreateWindow PointerWindow = 0 # destination window in SendEvent InputFocus = 1 # destination window in SendEvent PointerRoot = 1 # focus window in SetInputFocus AnyPropertyType = 0 # special Atom, passed to GetProperty AnyKey = 0 # special Key Code, passed to GrabKey AnyButton = 0 # special Button Code, passed to GrabButton AllTemporary = 0 # special Resource ID passed to KillClient CurrentTime = 0 # special Time NoSymbol = 0 # special KeySym #----------------------------------------------------------------------- # Event masks: # NoEventMask = 0 KeyPressMask = (1<<0) KeyReleaseMask = (1<<1) ButtonPressMask = (1<<2) ButtonReleaseMask = (1<<3) EnterWindowMask = (1<<4) LeaveWindowMask = (1<<5) PointerMotionMask = (1<<6) PointerMotionHintMask = (1<<7) Button1MotionMask = (1<<8) Button2MotionMask = (1<<9) Button3MotionMask = (1<<10) Button4MotionMask = (1<<11) Button5MotionMask = (1<<12) ButtonMotionMask = (1<<13) KeymapStateMask = (1<<14) ExposureMask = (1<<15) VisibilityChangeMask = (1<<16) StructureNotifyMask = (1<<17) ResizeRedirectMask = (1<<18) SubstructureNotifyMask = (1<<19) SubstructureRedirectMask = (1<<20) FocusChangeMask = (1<<21) PropertyChangeMask = (1<<22) ColormapChangeMask = (1<<23) OwnerGrabButtonMask = (1<<24) #----------------------------------------------------------------------- # Event names: # # Used in "type" field in XEvent structures. Not to be confused with event # masks above. They start from 2 because 0 and 1 are reserved in the # protocol for errors and replies. # KeyPress = 2 KeyRelease = 3 ButtonPress = 4 ButtonRelease = 5 MotionNotify = 6 EnterNotify = 7 LeaveNotify = 8 FocusIn = 9 FocusOut = 10 KeymapNotify = 11 Expose = 12 GraphicsExpose = 13 NoExpose = 14 VisibilityNotify = 15 CreateNotify = 16 DestroyNotify = 17 UnmapNotify = 18 MapNotify = 19 MapRequest = 20 ReparentNotify = 21 ConfigureNotify = 22 ConfigureRequest = 23 GravityNotify = 24 ResizeRequest = 25 CirculateNotify = 26 CirculateRequest = 27 PropertyNotify = 28 SelectionClear = 29 SelectionRequest = 30 SelectionNotify = 31 ColormapNotify = 32 ClientMessage = 33 MappingNotify = 34 LASTEvent = 35 # must be bigger than any event #----------------------------------------------------------------------- # Key masks: # # Used as modifiers to GrabButton and GrabKey, results of QueryPointer, # state in various key-, mouse-, and button-related events. # ShiftMask = (1<<0) LockMask = (1<<1) ControlMask = (1<<2) Mod1Mask = (1<<3) Mod2Mask = (1<<4) Mod3Mask = (1<<5) Mod4Mask = (1<<6) Mod5Mask = (1<<7) #----------------------------------------------------------------------- # Modifier names: # # Used to build a SetModifierMapping request or to read a # GetModifierMapping request. These correspond to the masks defined above. # ShiftMapIndex = 0 LockMapIndex = 1 ControlMapIndex = 2 Mod1MapIndex = 3 Mod2MapIndex = 4 Mod3MapIndex = 5 Mod4MapIndex = 6 Mod5MapIndex = 7 #----------------------------------------------------------------------- # Button masks: # # Used in same manner as Key masks above. Not to be confused with button # names below. Note that 0 is already defined above as "AnyButton". # Button1Mask = (1<<8) Button2Mask = (1<<9) Button3Mask = (1<<10) Button4Mask = (1<<11) Button5Mask = (1<<12) AnyModifier = (1<<15) # used in GrabButton, GrabKey #----------------------------------------------------------------------- # Button names: # # Used as arguments to GrabButton and as detail in ButtonPress and # ButtonRelease events. Not to be confused with button masks above. # Note that 0 is already defined above as "AnyButton". # Button1 = 1 Button2 = 2 Button3 = 3 Button4 = 4 Button5 = 5 #----------------------------------------------------------------------- # XXX These still need documentation -- for now, read # NotifyNormal = 0 NotifyGrab = 1 NotifyUngrab = 2 NotifyWhileGrabbed = 3 NotifyHint = 1 NotifyAncestor = 0 NotifyVirtual = 1 NotifyInferior = 2 NotifyNonlinear = 3 NotifyNonlinearVirtual = 4 NotifyPointer = 5 NotifyPointerRoot = 6 NotifyDetailNone = 7 VisibilityUnobscured = 0 VisibilityPartiallyObscured = 1 VisibilityFullyObscured = 2 PlaceOnTop = 0 PlaceOnBottom = 1 FamilyInternet = 0 FamilyDECnet = 1 FamilyChaos = 2 PropertyNewValue = 0 PropertyDelete = 1 ColormapUninstalled = 0 ColormapInstalled = 1 GrabModeSync = 0 GrabModeAsync = 1 GrabSuccess = 0 AlreadyGrabbed = 1 GrabInvalidTime = 2 GrabNotViewable = 3 GrabFrozen = 4 AsyncPointer = 0 SyncPointer = 1 ReplayPointer = 2 AsyncKeyboard = 3 SyncKeyboard = 4 ReplayKeyboard = 5 AsyncBoth = 6 SyncBoth = 7 RevertToNone = 0 RevertToPointerRoot = PointerRoot RevertToParent = 2 Success = 0 BadRequest = 1 BadValue = 2 BadWindow = 3 BadPixmap = 4 BadAtom = 5 BadCursor = 6 BadFont = 7 BadMatch = 8 BadDrawable = 9 BadAccess = 10 BadAlloc = 11 BadColor = 12 BadGC = 13 BadIDChoice = 14 BadName = 15 BadLength = 16 BadImplementation = 17 FirstExtensionError = 128 LastExtensionError = 255 InputOutput = 1 InputOnly = 2 CWBackPixmap = (1<<0) CWBackPixel = (1<<1) CWBorderPixmap = (1<<2) CWBorderPixel = (1<<3) CWBitGravity = (1<<4) CWWinGravity = (1<<5) CWBackingStore = (1<<6) CWBackingPlanes = (1<<7) CWBackingPixel = (1<<8) CWOverrideRedirect = (1<<9) CWSaveUnder = (1<<10) CWEventMask = (1<<11) CWDontPropagate = (1<<12) CWColormap = (1<<13) CWCursor = (1<<14) CWX = (1<<0) CWY = (1<<1) CWWidth = (1<<2) CWHeight = (1<<3) CWBorderWidth = (1<<4) CWSibling = (1<<5) CWStackMode = (1<<6) ForgetGravity = 0 NorthWestGravity = 1 NorthGravity = 2 NorthEastGravity = 3 WestGravity = 4 CenterGravity = 5 EastGravity = 6 SouthWestGravity = 7 SouthGravity = 8 SouthEastGravity = 9 StaticGravity = 10 UnmapGravity = 0 NotUseful = 0 WhenMapped = 1 Always = 2 IsUnmapped = 0 IsUnviewable = 1 IsViewable = 2 SetModeInsert = 0 SetModeDelete = 1 DestroyAll = 0 RetainPermanent = 1 RetainTemporary = 2 Above = 0 Below = 1 TopIf = 2 BottomIf = 3 Opposite = 4 RaiseLowest = 0 LowerHighest = 1 PropModeReplace = 0 PropModePrepend = 1 PropModeAppend = 2 GXclear = 0x0 GXand = 0x1 GXandReverse = 0x2 GXcopy = 0x3 GXandInverted = 0x4 GXnoop = 0x5 GXxor = 0x6 GXor = 0x7 GXnor = 0x8 GXequiv = 0x9 GXinvert = 0xa GXorReverse = 0xb GXcopyInverted = 0xc GXorInverted = 0xd GXnand = 0xe GXset = 0xf LineSolid = 0 LineOnOffDash = 1 LineDoubleDash = 2 CapNotLast = 0 CapButt = 1 CapRound = 2 CapProjecting = 3 JoinMiter = 0 JoinRound = 1 JoinBevel = 2 FillSolid = 0 FillTiled = 1 FillStippled = 2 FillOpaqueStippled = 3 EvenOddRule = 0 WindingRule = 1 ClipByChildren = 0 IncludeInferiors = 1 Unsorted = 0 YSorted = 1 YXSorted = 2 YXBanded = 3 CoordModeOrigin = 0 CoordModePrevious = 1 Complex = 0 Nonconvex = 1 Convex = 2 ArcChord = 0 ArcPieSlice = 1 GCFunction = (1<<0) GCPlaneMask = (1<<1) GCForeground = (1<<2) GCBackground = (1<<3) GCLineWidth = (1<<4) GCLineStyle = (1<<5) GCCapStyle = (1<<6) GCJoinStyle = (1<<7) GCFillStyle = (1<<8) GCFillRule = (1<<9) GCTile = (1<<10) GCStipple = (1<<11) GCTileStipXOrigin = (1<<12) GCTileStipYOrigin = (1<<13) GCFont = (1<<14) GCSubwindowMode = (1<<15) GCGraphicsExposures = (1<<16) GCClipXOrigin = (1<<17) GCClipYOrigin = (1<<18) GCClipMask = (1<<19) GCDashOffset = (1<<20) GCDashList = (1<<21) GCArcMode = (1<<22) GCLastBit = 22 FontLeftToRight = 0 FontRightToLeft = 1 FontChange = 255 XYBitmap = 0 XYPixmap = 1 ZPixmap = 2 AllocNone = 0 AllocAll = 1 DoRed = (1<<0) DoGreen = (1<<1) DoBlue = (1<<2) CursorShape = 0 TileShape = 1 StippleShape = 2 AutoRepeatModeOff = 0 AutoRepeatModeOn = 1 AutoRepeatModeDefault = 2 LedModeOff = 0 LedModeOn = 1 KBKeyClickPercent = (1<<0) KBBellPercent = (1<<1) KBBellPitch = (1<<2) KBBellDuration = (1<<3) KBLed = (1<<4) KBLedMode = (1<<5) KBKey = (1<<6) KBAutoRepeatMode = (1<<7) MappingSuccess = 0 MappingBusy = 1 MappingFailed = 2 MappingModifier = 0 MappingKeyboard = 1 MappingPointer = 2 DontPreferBlanking = 0 PreferBlanking = 1 DefaultBlanking = 2 DisableScreenSaver = 0 DisableScreenInterval = 0 DontAllowExposures = 0 AllowExposures = 1 DefaultExposures = 2 ScreenSaverReset = 0 ScreenSaverActive = 1 HostInsert = 0 HostDelete = 1 EnableAccess = 1 DisableAccess = 0 StaticGray = 0 GrayScale = 1 StaticColor = 2 PseudoColor = 3 TrueColor = 4 DirectColor = 5 LSBFirst = 0 MSBFirst = 1 Nagstamon/Nagstamon/thirdparty/Xlib/XK.py000066400000000000000000000062131316117564000207260ustar00rootroot00000000000000# Xlib.XK -- X keysym defs # # Copyright (C) 2000 Peter Liljenberg # # 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 # # This module defines some functions for working with X keysyms as well # as a modular keysym definition and loading mechanism. See the keysym # definition modules in the Xlib/keysymdef directory. from Xlib.X import NoSymbol def string_to_keysym(keysym): '''Return the (16 bit) numeric code of keysym. Given the name of a keysym as a string, return its numeric code. Don't include the 'XK_' prefix, just use the base, i.e. 'Delete' instead of 'XK_Delete'.''' return globals().get('XK_' + keysym, NoSymbol) def load_keysym_group(group): '''Load all the keysyms in group. Given a group name such as 'latin1' or 'katakana' load the keysyms defined in module 'Xlib.keysymdef.group-name' into this XK module.''' if '.' in group: raise ValueError('invalid keysym group name: %s' % group) G = globals() #Get a reference to XK.__dict__ a.k.a. globals #Import just the keysyms module. mod = __import__('Xlib.keysymdef.%s' % group, G, locals(), [group]) #Extract names of just the keysyms. keysyms = [n for n in dir(mod) if n.startswith('XK_')] #Copy the named keysyms into XK.__dict__ for keysym in keysyms: ## k = mod.__dict__[keysym]; assert k == int(k) #probably too much. G[keysym] = mod.__dict__[keysym] #And get rid of the keysym module. del mod def _load_keysyms_into_XK(mod): '''keysym definition modules need no longer call Xlib.XK._load_keysyms_into_XK(). You should remove any calls to that function from your keysym modules.''' pass # Always import miscellany and latin1 keysyms load_keysym_group('miscellany') load_keysym_group('latin1') def keysym_to_string(keysym): '''Translate a keysym (16 bit number) into a python string. This will pass 0 to 0xff as well as XK_BackSpace, XK_Tab, XK_Clear, XK_Return, XK_Pause, XK_Scroll_Lock, XK_Escape, XK_Delete. For other values it returns None.''' # ISO latin 1, LSB is the code if keysym & 0xff00 == 0: return chr(keysym & 0xff) if keysym in [XK_BackSpace, XK_Tab, XK_Clear, XK_Return, XK_Pause, XK_Scroll_Lock, XK_Escape, XK_Delete]: return chr(keysym & 0xff) # We should be able to do these things quite automatically # for latin2, latin3, etc, in Python 2.0 using the Unicode, # but that will have to wait. return None Nagstamon/Nagstamon/thirdparty/Xlib/Xatom.py000066400000000000000000000036461316117564000215030ustar00rootroot00000000000000# Xlib.Xatom -- Standard X atoms # # Copyright (C) 2000 Peter Liljenberg # # 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 PRIMARY = 1 SECONDARY = 2 ARC = 3 ATOM = 4 BITMAP = 5 CARDINAL = 6 COLORMAP = 7 CURSOR = 8 CUT_BUFFER0 = 9 CUT_BUFFER1 = 10 CUT_BUFFER2 = 11 CUT_BUFFER3 = 12 CUT_BUFFER4 = 13 CUT_BUFFER5 = 14 CUT_BUFFER6 = 15 CUT_BUFFER7 = 16 DRAWABLE = 17 FONT = 18 INTEGER = 19 PIXMAP = 20 POINT = 21 RECTANGLE = 22 RESOURCE_MANAGER = 23 RGB_COLOR_MAP = 24 RGB_BEST_MAP = 25 RGB_BLUE_MAP = 26 RGB_DEFAULT_MAP = 27 RGB_GRAY_MAP = 28 RGB_GREEN_MAP = 29 RGB_RED_MAP = 30 STRING = 31 VISUALID = 32 WINDOW = 33 WM_COMMAND = 34 WM_HINTS = 35 WM_CLIENT_MACHINE = 36 WM_ICON_NAME = 37 WM_ICON_SIZE = 38 WM_NAME = 39 WM_NORMAL_HINTS = 40 WM_SIZE_HINTS = 41 WM_ZOOM_HINTS = 42 MIN_SPACE = 43 NORM_SPACE = 44 MAX_SPACE = 45 END_SPACE = 46 SUPERSCRIPT_X = 47 SUPERSCRIPT_Y = 48 SUBSCRIPT_X = 49 SUBSCRIPT_Y = 50 UNDERLINE_POSITION = 51 UNDERLINE_THICKNESS = 52 STRIKEOUT_ASCENT = 53 STRIKEOUT_DESCENT = 54 ITALIC_ANGLE = 55 X_HEIGHT = 56 QUAD_WIDTH = 57 WEIGHT = 58 POINT_SIZE = 59 RESOLUTION = 60 COPYRIGHT = 61 NOTICE = 62 FONT_NAME = 63 FAMILY_NAME = 64 FULL_NAME = 65 CAP_HEIGHT = 66 WM_CLASS = 67 WM_TRANSIENT_FOR = 68 LAST_PREDEFINED = 68 Nagstamon/Nagstamon/thirdparty/Xlib/Xcursorfont.py000066400000000000000000000037561316117564000227510ustar00rootroot00000000000000# Xlib.Xcursorfont -- standard cursors # # Copyright (C) 2000 Peter Liljenberg # # 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 num_glyphs = 154 X_cursor = 0 arrow = 2 based_arrow_down = 4 based_arrow_up = 6 boat = 8 bogosity = 10 bottom_left_corner = 12 bottom_right_corner = 14 bottom_side = 16 bottom_tee = 18 box_spiral = 20 center_ptr = 22 circle = 24 clock = 26 coffee_mug = 28 cross = 30 cross_reverse = 32 crosshair = 34 diamond_cross = 36 dot = 38 dotbox = 40 double_arrow = 42 draft_large = 44 draft_small = 46 draped_box = 48 exchange = 50 fleur = 52 gobbler = 54 gumby = 56 hand1 = 58 hand2 = 60 heart = 62 icon = 64 iron_cross = 66 left_ptr = 68 left_side = 70 left_tee = 72 leftbutton = 74 ll_angle = 76 lr_angle = 78 man = 80 middlebutton = 82 mouse = 84 pencil = 86 pirate = 88 plus = 90 question_arrow = 92 right_ptr = 94 right_side = 96 right_tee = 98 rightbutton = 100 rtl_logo = 102 sailboat = 104 sb_down_arrow = 106 sb_h_double_arrow = 108 sb_left_arrow = 110 sb_right_arrow = 112 sb_up_arrow = 114 sb_v_double_arrow = 116 shuttle = 118 sizing = 120 spider = 122 spraycan = 124 star = 126 target = 128 tcross = 130 top_left_arrow = 132 top_left_corner = 134 top_right_corner = 136 top_side = 138 top_tee = 140 trek = 142 ul_angle = 144 umbrella = 146 ur_angle = 148 watch = 150 xterm = 152 Nagstamon/Nagstamon/thirdparty/Xlib/Xutil.py000066400000000000000000000042521316117564000215120ustar00rootroot00000000000000# Xlib.Xutil -- ICCCM definitions and similar stuff # # Copyright (C) 2000 Peter Liljenberg # # 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 NoValue = 0x0000 XValue = 0x0001 YValue = 0x0002 WidthValue = 0x0004 HeightValue = 0x0008 AllValues = 0x000F XNegative = 0x0010 YNegative = 0x0020 USPosition = (1 << 0) USSize = (1 << 1) PPosition = (1 << 2) PSize = (1 << 3) PMinSize = (1 << 4) PMaxSize = (1 << 5) PResizeInc = (1 << 6) PAspect = (1 << 7) PBaseSize = (1 << 8) PWinGravity = (1 << 9) PAllHints = (PPosition|PSize|PMinSize|PMaxSize|PResizeInc|PAspect) InputHint = (1 << 0) StateHint = (1 << 1) IconPixmapHint = (1 << 2) IconWindowHint = (1 << 3) IconPositionHint = (1 << 4) IconMaskHint = (1 << 5) WindowGroupHint = (1 << 6) MessageHint = (1 << 7) UrgencyHint = (1 << 8) AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint| IconPositionHint|IconMaskHint|WindowGroupHint|MessageHint| UrgencyHint) WithdrawnState = 0 NormalState = 1 IconicState = 3 DontCareState = 0 ZoomState = 2 InactiveState = 4 RectangleOut = 0 RectangleIn = 1 RectanglePart = 2 VisualNoMask = 0x0 VisualIDMask = 0x1 VisualScreenMask = 0x2 VisualDepthMask = 0x4 VisualClassMask = 0x8 VisualRedMaskMask = 0x10 VisualGreenMaskMask = 0x20 VisualBlueMaskMask = 0x40 VisualColormapSizeMask = 0x80 VisualBitsPerRGBMask = 0x100 VisualAllMask = 0x1FF ReleaseByFreeingColormap = 1 BitmapSuccess = 0 BitmapOpenFailed = 1 BitmapFileInvalid = 2 BitmapNoMemory = 3 XCSUCCESS = 0 XCNOMEM = 1 XCNOENT = 2 Nagstamon/Nagstamon/thirdparty/Xlib/__init__.py000066400000000000000000000022411316117564000221400ustar00rootroot00000000000000# Xlib.__init__ -- glue for Xlib package # # Copyright (C) 2000-2002 Peter Liljenberg # # 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 __version__ = (0, 15) __version_extra__ = '' __version_string__ = '.'.join(map(str, __version__)) + __version_extra__ __all__ = [ 'X', 'XK', 'Xatom', 'Xcursorfont', 'Xutil', 'display', 'error', 'rdb', # Explicitly exclude threaded, so that it isn't imported by # from Xlib import * ] Nagstamon/Nagstamon/thirdparty/Xlib/display.py000066400000000000000000001053261316117564000220560ustar00rootroot00000000000000# Xlib.display -- high level display object # # Copyright (C) 2000 Peter Liljenberg # # 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 # Python modules import types # Xlib modules from . import error, ext, X # Xlib.protocol modules from Xlib.protocol import display, request, event, rq # Xlib.xobjects modules import Xlib.xobject.resource import Xlib.xobject.drawable import Xlib.xobject.fontable import Xlib.xobject.colormap import Xlib.xobject.cursor _resource_baseclasses = { 'resource': Xlib.xobject.resource.Resource, 'drawable': Xlib.xobject.drawable.Drawable, 'window': Xlib.xobject.drawable.Window, 'pixmap': Xlib.xobject.drawable.Pixmap, 'fontable': Xlib.xobject.fontable.Fontable, 'font': Xlib.xobject.fontable.Font, 'gc': Xlib.xobject.fontable.GC, 'colormap': Xlib.xobject.colormap.Colormap, 'cursor': Xlib.xobject.cursor.Cursor, } _resource_hierarchy = { 'resource': ('drawable', 'window', 'pixmap', 'fontable', 'font', 'gc', 'colormap', 'cursor'), 'drawable': ('window', 'pixmap'), 'fontable': ('font', 'gc') } class _BaseDisplay(display.Display): resource_classes = _resource_baseclasses.copy() # Implement a cache of atom names, used by Window objects when # dealing with some ICCCM properties not defined in Xlib.Xatom def __init__(self, *args, **keys): display.Display.__init__(*(self, ) + args, **keys) self._atom_cache = {} def get_atom(self, atomname, only_if_exists=0): if atomname in self._atom_cache: return self._atom_cache[atomname] r = request.InternAtom(display = self, name = atomname, only_if_exists = only_if_exists) # don't cache NONE responses in case someone creates this later if r.atom != X.NONE: self._atom_cache[atomname] = r.atom return r.atom class Display: def __init__(self, display = None): self.display = _BaseDisplay(display) # Create the keymap cache self._keymap_codes = [()] * 256 self._keymap_syms = {} self._update_keymap(self.display.info.min_keycode, (self.display.info.max_keycode - self.display.info.min_keycode + 1)) # Translations for keysyms to strings. self.keysym_translations = {} # Find all supported extensions self.extensions = [] self.class_extension_dicts = {} self.display_extension_methods = {} self.extension_event = rq.DictWrapper({}) exts = self.list_extensions() # Go through all extension modules for extname, modname in ext.__extensions__: if extname in exts: # Import the module and fetch it __import__('ext.' + modname,globals(),level=1) mod = getattr(ext, modname) info = self.query_extension(extname) self.display.set_extension_major(extname, info.major_opcode) # Call initialiasation function mod.init(self, info) self.extensions.append(extname) # Finalize extensions by creating new classes for type_, dict in self.class_extension_dicts.items(): origcls = self.display.resource_classes[type_] self.display.resource_classes[type_] = type(origcls.__name__, (origcls, object), dict) # Problem: we have already created some objects without the # extensions: the screen roots and default colormaps. # Fix that by reinstantiating them. for screen in self.display.info.roots: screen.root = self.display.resource_classes['window'](self.display, screen.root.id) screen.default_colormap = self.display.resource_classes['colormap'](self.display, screen.default_colormap.id) def get_display_name(self): """Returns the name used to connect to the server, either provided when creating the Display object, or fetched from the environmental variable $DISPLAY.""" return self.display.get_display_name() def fileno(self): """Returns the file descriptor number of the underlying socket. This method is provided to allow Display objects to be passed select.select().""" return self.display.fileno() def close(self): """Close the display, freeing the resources that it holds.""" self.display.close() def set_error_handler(self, handler): """Set the default error handler which will be called for all unhandled errors. handler should take two arguments as a normal request error handler, but the second argument (the request) will be None. See section Error Handling.""" self.display.set_error_handler(handler) def flush(self): """Flush the request queue, building and sending the queued requests. This can be necessary in applications that never wait for events, and in threaded applications.""" self.display.flush() def sync(self): """Flush the queue and wait until the server has processed all the queued requests. Use this e.g. when it is important that errors caused by a certain request is trapped.""" # Do a light-weight replyrequest to sync. There must # be a better way to do it... self.get_pointer_control() def next_event(self): """Return the next event. If there are no events queued, it will block until the next event is fetched from the server.""" return self.display.next_event() def pending_events(self): """Return the number of events queued, i.e. the number of times that Display.next_event() can be called without blocking.""" return self.display.pending_events() def has_extension(self, extension): """Check if both the server and the client library support the X extension named extension.""" return extension in self.extensions def create_resource_object(self, type, id): """Create a resource object of type for the integer id. type should be one of the following strings: resource drawable window pixmap fontable font gc colormap cursor This function can be used when a resource ID has been fetched e.g. from an resource or a command line argument. Resource objects should never be created by instantiating the appropriate class directly, since any X extensions dynamically added by the library will not be available. """ return self.display.resource_classes[type](self.display, id) # We need this to handle display extension methods def __getattr__(self, attr): try: function = self.display_extension_methods[attr] return types.MethodType(function, self) except KeyError: raise AttributeError(attr) ### ### display information retrieval ### def screen(self, sno = None): if sno is None: return self.display.info.roots[self.display.default_screen] else: return self.display.info.roots[sno] def screen_count(self): """Return the total number of screens on the display.""" return len(self.display.info.roots) def get_default_screen(self): """Return the number of the default screen, extracted from the display name.""" return self.display.get_default_screen() ### ### Extension module interface ### def extension_add_method(self, object, name, function): """extension_add_method(object, name, function) Add an X extension module method. OBJECT is the type of object to add the function to, a string from this list: display resource drawable window pixmap fontable font gc colormap cursor NAME is the name of the method, a string. FUNCTION is a normal function whose first argument is a 'self'. """ if object == 'display': if hasattr(self, name): raise AssertionError('attempting to replace display method: %s' % name) self.display_extension_methods[name] = function else: types = (object, ) + _resource_hierarchy.get(object, ()) for type in types: cls = _resource_baseclasses[type] if hasattr(cls, name): raise AssertionError('attempting to replace %s method: %s' % (type, name)) # Maybe should check extension overrides too try: self.class_extension_dicts[type][name] = function except KeyError: self.class_extension_dicts[type] = { name: function } def extension_add_event(self, code, evt, name = None): """extension_add_event(code, evt, [name]) Add an extension event. CODE is the numeric code, and EVT is the event class. EVT will be cloned, and the attribute _code of the new event class will be set to CODE. If NAME is omitted, it will be set to the name of EVT. This name is used to insert an entry in the DictWrapper extension_event. """ newevt = type('{0}.SUB{1}'.format(evt.__name__, code), evt.__bases__, evt.__dict__.copy()) newevt._code = code self.display.add_extension_event(code, newevt) if name is None: name = evt.__name__ setattr(self.extension_event, name, code) def add_extension_error(self, code, err): """add_extension_error(code, err) Add an extension error. CODE is the numeric code, and ERR is the error class. """ self.display.add_extension_error(code, err) ### ### keymap cache implementation ### # The keycode->keysym map is stored in a list with 256 elements. # Each element represents a keycode, and the tuple elements are # the keysyms bound to the key. # The keysym->keycode map is stored in a mapping, where the keys # are keysyms. The values are a sorted list of tuples with two # elements each: (index, keycode) # keycode is the code for a key to which this keysym is bound, and # index is the keysyms index in the map for that keycode. def keycode_to_keysym(self, keycode, index): """Convert a keycode to a keysym, looking in entry index. Normally index 0 is unshifted, 1 is shifted, 2 is alt grid, and 3 is shift+alt grid. If that key entry is not bound, X.NoSymbol is returned.""" try: return self._keymap_codes[keycode][index] except IndexError: return X.NoSymbol def keysym_to_keycode(self, keysym): """Look up the primary keycode that is bound to keysym. If several keycodes are found, the one with the lowest index and lowest code is returned. If keysym is not bound to any key, 0 is returned.""" try: return self._keymap_syms[keysym][0][1] except (KeyError, IndexError): return 0 def keysym_to_keycodes(self, keysym): """Look up all the keycodes that is bound to keysym. A list of tuples (keycode, index) is returned, sorted primarily on the lowest index and secondarily on the lowest keycode.""" try: # Copy the map list, reversing the arguments return [(x[1], x[0]) for x in self._keymap_syms[keysym]] except KeyError: return [] def refresh_keyboard_mapping(self, evt): """This method should be called once when a MappingNotify event is received, to update the keymap cache. evt should be the event object.""" if isinstance(evt, event.MappingNotify): if evt.request == X.MappingKeyboard: self._update_keymap(evt.first_keycode, evt.count) else: raise TypeError('expected a MappingNotify event') def _update_keymap(self, first_keycode, count): """Internal function, called to refresh the keymap cache. """ # Delete all sym->code maps for the changed codes lastcode = first_keycode + count for keysym, codes in self._keymap_syms.items(): i = 0 while i < len(codes): code = codes[i][1] if code >= first_keycode and code < lastcode: del codes[i] else: i = i + 1 # Get the new keyboard mapping keysyms = self.get_keyboard_mapping(first_keycode, count) # Replace code->sym map with the new map self._keymap_codes[first_keycode:lastcode] = keysyms # Update sym->code map code = first_keycode for syms in keysyms: index = 0 for sym in syms: if sym != X.NoSymbol: if sym in self._keymap_syms: symcodes = self._keymap_syms[sym] symcodes.append((index, code)) symcodes.sort() else: self._keymap_syms[sym] = [(index, code)] index = index + 1 code = code + 1 ### ### client-internal keysym to string translations ### def lookup_string(self, keysym): """Return a string corresponding to KEYSYM, or None if no reasonable translation is found. """ s = self.keysym_translations.get(keysym) if s is not None: return s import Xlib.XK return Xlib.XK.keysym_to_string(keysym) def rebind_string(self, keysym, newstring): """Change the translation of KEYSYM to NEWSTRING. If NEWSTRING is None, remove old translation if any. """ if newstring is None: try: del self.keysym_translations[keysym] except KeyError: pass else: self.keysym_translations[keysym] = newstring ### ### X requests ### def intern_atom(self, name, only_if_exists = 0): """Intern the string name, returning its atom number. If only_if_exists is true and the atom does not already exist, it will not be created and X.NONE is returned.""" r = request.InternAtom(display = self.display, name = name, only_if_exists = only_if_exists) return r.atom def get_atom(self, atom, only_if_exists = 0): """Alias for intern_atom, using internal cache""" return self.display.get_atom(atom, only_if_exists) def get_atom_name(self, atom): """Look up the name of atom, returning it as a string. Will raise BadAtom if atom does not exist.""" r = request.GetAtomName(display = self.display, atom = atom) return r.name def get_selection_owner(self, selection): """Return the window that owns selection (an atom), or X.NONE if there is no owner for the selection. Can raise BadAtom.""" r = request.GetSelectionOwner(display = self.display, selection = selection) return r.owner def send_event(self, destination, event, event_mask = 0, propagate = 0, onerror = None): """Send a synthetic event to the window destination which can be a window object, or X.PointerWindow or X.InputFocus. event is the event object to send, instantiated from one of the classes in protocol.events. See XSendEvent(3X11) for details. There is also a Window.send_event() method.""" request.SendEvent(display = self.display, onerror = onerror, propagate = propagate, destination = destination, event_mask = event_mask, event = event) def ungrab_pointer(self, time, onerror = None): """elease a grabbed pointer and any queued events. See XUngrabPointer(3X11).""" request.UngrabPointer(display = self.display, onerror = onerror, time = time) def change_active_pointer_grab(self, event_mask, cursor, time, onerror = None): """Change the dynamic parameters of a pointer grab. See XChangeActivePointerGrab(3X11).""" request.ChangeActivePointerGrab(display = self.display, onerror = onerror, cursor = cursor, time = time, event_mask = event_mask) def ungrab_keyboard(self, time, onerror = None): """Ungrab a grabbed keyboard and any queued events. See XUngrabKeyboard(3X11).""" request.UngrabKeyboard(display = self.display, onerror = onerror, time = time) def allow_events(self, mode, time, onerror = None): """Release some queued events. mode should be one of X.AsyncPointer, X.SyncPointer, X.AsyncKeyboard, X.SyncKeyboard, X.ReplayPointer, X.ReplayKeyboard, X.AsyncBoth, or X.SyncBoth. time should be a timestamp or X.CurrentTime.""" request.AllowEvents(display = self.display, onerror = onerror, mode = mode, time = time) def grab_server(self, onerror = None): """Disable processing of requests on all other client connections until the server is ungrabbed. Server grabbing should be avoided as much as possible.""" request.GrabServer(display = self.display, onerror = onerror) def ungrab_server(self, onerror = None): """Release the server if it was previously grabbed by this client.""" request.UngrabServer(display = self.display, onerror = onerror) def warp_pointer(self, x, y, src_window = X.NONE, src_x = 0, src_y = 0, src_width = 0, src_height = 0, onerror = None): """Move the pointer relative its current position by the offsets (x, y). However, if src_window is a window the pointer is only moved if the specified rectangle in src_window contains it. If src_width is 0 it will be replaced with the width of src_window - src_x. src_height is treated in a similar way. To move the pointer to absolute coordinates, use Window.warp_pointer().""" request.WarpPointer(display = self.display, onerror = onerror, src_window = src_window, dst_window = X.NONE, src_x = src_x, src_y = src_y, src_width = src_width, src_height = src_height, dst_x = x, dst_y = y) def set_input_focus(self, focus, revert_to, time, onerror = None): """Set input focus to focus, which should be a window, X.PointerRoot or X.NONE. revert_to specifies where the focus reverts to if the focused window becomes not visible, and should be X.RevertToParent, RevertToPointerRoot, or RevertToNone. See XSetInputFocus(3X11) for details. There is also a Window.set_input_focus().""" request.SetInputFocus(display = self.display, onerror = onerror, revert_to = revert_to, focus = focus, time = time) def get_input_focus(self): """Return an object with the following attributes: focus The window which currently holds the input focus, X.NONE or X.PointerRoot. revert_to Where the focus will revert, one of X.RevertToParent, RevertToPointerRoot, or RevertToNone. """ return request.GetInputFocus(display = self.display) def query_keymap(self): """Return a bit vector for the logical state of the keyboard, where each bit set to 1 indicates that the corresponding key is currently pressed down. The vector is represented as a list of 32 integers. List item N contains the bits for keys 8N to 8N + 7 with the least significant bit in the byte representing key 8N.""" r = request.QueryKeymap(display = self.display) return r.map def open_font(self, name): """Open the font identifed by the pattern name and return its font object. If name does not match any font, None is returned.""" fid = self.display.allocate_resource_id() ec = error.CatchError(error.BadName) request.OpenFont(display = self.display, onerror = ec, fid = fid, name = name) self.sync() if ec.get_error(): self.display.free_resource_id(fid) return None else: cls = self.display.get_resource_class('font', Xlib.xobject.fontable.Font) return cls(self.display, fid, owner = 1) def list_fonts(self, pattern, max_names): """Return a list of font names matching pattern. No more than max_names will be returned.""" r = request.ListFonts(display = self.display, max_names = max_names, pattern = pattern) return r.fonts def list_fonts_with_info(self, pattern, max_names): """Return a list of fonts matching pattern. No more than max_names will be returned. Each list item represents one font and has the following properties: name The name of the font. min_bounds max_bounds min_char_or_byte2 max_char_or_byte2 default_char draw_direction min_byte1 max_byte1 all_chars_exist font_ascent font_descent replies_hint See the descripton of XFontStruct in XGetFontProperty(3X11) for details on these values. properties A list of properties. Each entry has two attributes: name The atom identifying this property. value A 32-bit unsigned value. """ return request.ListFontsWithInfo(display = self.display, max_names = max_names, pattern = pattern) def set_font_path(self, path, onerror = None): """Set the font path to path, which should be a list of strings. If path is empty, the default font path of the server will be restored.""" request.SetFontPath(display = self.display, onerror = onerror, path = path) def get_font_path(self): """Return the current font path as a list of strings.""" r = request.GetFontPath(display = self.display) return r.paths def query_extension(self, name): """Ask the server if it supports the extension name. If it is supported an object with the following attributes is returned: major_opcode The major opcode that the requests of this extension uses. first_event The base event code if the extension have additional events, or 0. first_error The base error code if the extension have additional errors, or 0. If the extension is not supported, None is returned.""" r = request.QueryExtension(display = self.display, name = name) if r.present: return r else: return None def list_extensions(self): """Return a list of all the extensions provided by the server.""" r = request.ListExtensions(display = self.display) return r.names def change_keyboard_mapping(self, first_keycode, keysyms, onerror = None): """Modify the keyboard mapping, starting with first_keycode. keysyms is a list of tuples of keysyms. keysyms[n][i] will be assigned to keycode first_keycode+n at index i.""" request.ChangeKeyboardMapping(display = self.display, onerror = onerror, first_keycode = first_keycode, keysyms = keysyms) def get_keyboard_mapping(self, first_keycode, count): """Return the current keyboard mapping as a list of tuples, starting at first_keycount and no more than count.""" r = request.GetKeyboardMapping(display = self.display, first_keycode = first_keycode, count = count) return r.keysyms def change_keyboard_control(self, onerror = None, **keys): """Change the parameters provided as keyword arguments: key_click_percent The volume of key clicks between 0 (off) and 100 (load). -1 will restore default setting. bell_percent The base volume of the bell, coded as above. bell_pitch The pitch of the bell in Hz, -1 restores the default. bell_duration The duration of the bell in milliseconds, -1 restores the default. led led_mode led_mode should be X.LedModeOff or X.LedModeOn. If led is provided, it should be a 32-bit mask listing the LEDs that should change. If led is not provided, all LEDs are changed. key auto_repeat_mode auto_repeat_mode should be one of X.AutoRepeatModeOff, X.AutoRepeatModeOn, or X.AutoRepeatModeDefault. If key is provided, that key will be modified, otherwise the global state for the entire keyboard will be modified.""" request.ChangeKeyboardControl(display = self.display, onerror = onerror, attrs = keys) def get_keyboard_control(self): """Return an object with the following attributes: global_auto_repeat X.AutoRepeatModeOn or X.AutoRepeatModeOff. auto_repeats A list of 32 integers. List item N contains the bits for keys 8N to 8N + 7 with the least significant bit in the byte representing key 8N. If a bit is on, autorepeat is enabled for the corresponding key. led_mask A 32-bit mask indicating which LEDs are on. key_click_percent The volume of key click, from 0 to 100. bell_percent bell_pitch bell_duration The volume, pitch and duration of the bell. """ return request.GetKeyboardControl(display = self.display) def bell(self, percent = 0, onerror = None): """Ring the bell at the volume percent which is relative the base volume. See XBell(3X11).""" request.Bell(display = self.display, onerror = onerror, percent = percent) def change_pointer_control(self, accel = None, threshold = None, onerror = None): """To change the pointer acceleration, set accel to a tuple (num, denum). The pointer will then move num/denum times the normal speed if it moves beyond the threshold number of pixels at once. To change the threshold, set it to the number of pixels. -1 restores the default.""" if accel is None: do_accel = 0 accel_num = 0 accel_denum = 0 else: do_accel = 1 accel_num, accel_denum = accel if threshold is None: do_threshold = 0 else: do_threshold = 1 request.ChangePointerControl(display = self.display, onerror = onerror, do_accel = do_accel, do_thres = do_threshold, accel_num = accel_num, accel_denum = accel_denum, threshold = threshold) def get_pointer_control(self): """Return an object with the following attributes: accel_num accel_denom The acceleration as numerator/denumerator. threshold The number of pixels the pointer must move before the acceleration kicks in.""" return request.GetPointerControl(display = self.display) def set_screen_saver(self, timeout, interval, prefer_blank, allow_exposures, onerror = None): """See XSetScreenSaver(3X11).""" request.SetScreenSaver(display = self.display, onerror = onerror, timeout = timeout, interval = interval, prefer_blank = prefer_blank, allow_exposures = allow_exposures) def get_screen_saver(self): """Return an object with the attributes timeout, interval, prefer_blanking, allow_exposures. See XGetScreenSaver(3X11) for details.""" return request.GetScreenSaver(display = self.display) def change_hosts(self, mode, host_family, host, onerror = None): """mode is either X.HostInsert or X.HostDelete. host_family is one of X.FamilyInternet, X.FamilyDECnet or X.FamilyChaos. host is a list of bytes. For the Internet family, it should be the four bytes of an IPv4 address.""" request.ChangeHosts(display = self.display, onerror = onerror, mode = mode, host_family = host_family, host = host) def list_hosts(self): """Return an object with the following attributes: mode X.EnableAccess if the access control list is used, X.DisableAccess otherwise. hosts The hosts on the access list. Each entry has the following attributes: family X.FamilyInternet, X.FamilyDECnet, or X.FamilyChaos. name A list of byte values, the coding depends on family. For the Internet family, it is the 4 bytes of an IPv4 address. """ return request.ListHosts(display = self.display) def set_access_control(self, mode, onerror = None): """Enable use of access control lists at connection setup if mode is X.EnableAccess, disable if it is X.DisableAccess.""" request.SetAccessControl(display = self.display, onerror = onerror, mode = mode) def set_close_down_mode(self, mode, onerror = None): """Control what will happen with the client's resources at connection close. The default is X.DestroyAll, the other values are X.RetainPermanent and X.RetainTemporary.""" request.SetCloseDownMode(display = self.display, onerror = onerror, mode = mode) def force_screen_saver(self, mode, onerror = None): """If mode is X.ScreenSaverActive the screen saver is activated. If it is X.ScreenSaverReset, the screen saver is deactivated as if device input had been received.""" request.ForceScreenSaver(display = self.display, onerror = onerror, mode = mode) def set_pointer_mapping(self, map): """Set the mapping of the pointer buttons. map is a list of logical button numbers. map must be of the same length as the list returned by Display.get_pointer_mapping(). map[n] sets the logical number for the physical button n+1. Logical number 0 disables the button. Two physical buttons cannot be mapped to the same logical number. If one of the buttons to be altered are logically in the down state, X.MappingBusy is returned and the mapping is not changed. Otherwise the mapping is changed and X.MappingSuccess is returned.""" r = request.SetPointerMapping(display = self.display, map = map) return r.status def get_pointer_mapping(self): """Return a list of the pointer button mappings. Entry N in the list sets the logical button number for the physical button N+1.""" r = request.GetPointerMapping(display = self.display) return r.map def set_modifier_mapping(self, keycodes): """Set the keycodes for the eight modifiers X.Shift, X.Lock, X.Control, X.Mod1, X.Mod2, X.Mod3, X.Mod4 and X.Mod5. keycodes should be a eight-element list where each entry is a list of the keycodes that should be bound to that modifier. If any changed key is logically in the down state, X.MappingBusy is returned and the mapping is not changed. If the mapping violates some server restriction, X.MappingFailed is returned. Otherwise the mapping is changed and X.MappingSuccess is returned.""" r = request.SetModifierMapping(display = self.display, keycodes = keycodes) return r.status def get_modifier_mapping(self): """Return a list of eight lists, one for each modifier. The list can be indexed using X.ShiftMapIndex, X.Mod1MapIndex, and so on. The sublists list the keycodes bound to that modifier.""" r = request.GetModifierMapping(display = self.display) return r.keycodes def no_operation(self, onerror = None): """Do nothing but send a request to the server.""" request.NoOperation(display = self.display, onerror = onerror) Nagstamon/Nagstamon/thirdparty/Xlib/error.py000066400000000000000000000112031316117564000215300ustar00rootroot00000000000000# Xlib.error -- basic error classes # # Copyright (C) 2000 Peter Liljenberg # # 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 # Xlib modules from Xlib import X # Xlib.protocol modules from Xlib.protocol import rq class DisplayError(Exception): def __init__(self, display): self.display = display def __str__(self): return 'Display error "%s"' % self.display class DisplayNameError(DisplayError): def __str__(self): return 'Bad display name "%s"' % self.display class DisplayConnectionError(DisplayError): def __init__(self, display, msg): self.display = display self.msg = msg def __str__(self): return 'Can\'t connect to display "%s": %s' % (self.display, self.msg) class ConnectionClosedError(Exception): def __init__(self, whom): self.whom = whom def __str__(self): return 'Display connection closed by %s' % self.whom class XauthError(Exception): pass class XNoAuthError(Exception): pass class ResourceIDError(Exception): pass class XError(rq.GetAttrData, Exception): _fields = rq.Struct( rq.Card8('type'), # Always 0 rq.Card8('code'), rq.Card16('sequence_number'), rq.Card32('resource_id'), rq.Card16('minor_opcode'), rq.Card8('major_opcode'), rq.Pad(21) ) def __init__(self, display, data): self._data, data = self._fields.parse_binary(data, display, rawdict = 1) def __str__(self): s = [] for f in ('code', 'resource_id', 'sequence_number', 'major_opcode', 'minor_opcode'): s.append('%s = %s' % (f, self._data[f])) return '%s: %s' % (self.__class__, ', '.join(s)) class XResourceError(XError): _fields = rq.Struct( rq.Card8('type'), # Always 0 rq.Card8('code'), rq.Card16('sequence_number'), rq.Resource('resource_id'), rq.Card16('minor_opcode'), rq.Card8('major_opcode'), rq.Pad(21) ) class BadRequest(XError): pass class BadValue(XError): pass class BadWindow(XResourceError): pass class BadPixmap(XResourceError): pass class BadAtom(XError): pass class BadCursor(XResourceError): pass class BadFont(XResourceError): pass class BadMatch(XError): pass class BadDrawable(XResourceError): pass class BadAccess(XError): pass class BadAlloc(XError): pass class BadColor(XResourceError): pass class BadGC(XResourceError): pass class BadIDChoice(XResourceError): pass class BadName(XError): pass class BadLength(XError): pass class BadImplementation(XError): pass xerror_class = { X.BadRequest: BadRequest, X.BadValue: BadValue, X.BadWindow: BadWindow, X.BadPixmap: BadPixmap, X.BadAtom: BadAtom, X.BadCursor: BadCursor, X.BadFont: BadFont, X.BadMatch: BadMatch, X.BadDrawable: BadDrawable, X.BadAccess: BadAccess, X.BadAlloc: BadAlloc, X.BadColor: BadColor, X.BadGC: BadGC, X.BadIDChoice: BadIDChoice, X.BadName: BadName, X.BadLength: BadLength, X.BadImplementation: BadImplementation, } class CatchError: def __init__(self, *errors): self.error_types = errors self.error = None self.request = None def __call__(self, error, request): if self.error_types: for etype in self.error_types: if isinstance(error, etype): self.error = error self.request = request return 1 return 0 else: self.error = error self.request = request return 1 def get_error(self): return self.error def get_request(self): return self.request def reset(self): self.error = None self.request = None Nagstamon/Nagstamon/thirdparty/Xlib/ext/000077500000000000000000000000001316117564000206305ustar00rootroot00000000000000Nagstamon/Nagstamon/thirdparty/Xlib/ext/__init__.py000066400000000000000000000023471316117564000227470ustar00rootroot00000000000000# Xlib.ext.__init__ -- X extension modules # # Copyright (C) 2000 Peter Liljenberg # # 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 # __extensions__ is a list of tuples: (extname, extmod) # extname is the name of the extension according to the X # protocol. extmod is the name of the module in this package. __extensions__ = [ ('XTEST', 'xtest'), ('SHAPE', 'shape'), ('XINERAMA', 'xinerama'), ('RECORD', 'record'), ('Composite', 'composite'), ('RANDR', 'randr'), ] __all__ = [x[1] for x in __extensions__] Nagstamon/Nagstamon/thirdparty/Xlib/ext/composite.py000066400000000000000000000164321316117564000232120ustar00rootroot00000000000000# $Id: xtest.py,v 1.1 2000/08/21 10:03:45 petli Exp $ # # Xlib.ext.composite -- Composite extension module # # Copyright (C) 2007 Peter Liljenberg # # 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 """Composite extension, allowing windows to be rendered to off-screen storage. For detailed description, see the protocol specification at http://freedesktop.org/wiki/Software/CompositeExt By itself this extension is not very useful, it is intended to be used together with the DAMAGE and XFIXES extensions. Typically you would also need RENDER or glX or some similar method of creating fancy graphics. """ from Xlib import X from Xlib.protocol import rq from Xlib.xobject import drawable extname = 'Composite' RedirectAutomatic = 0 RedirectManual = 1 class QueryVersion(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(0), rq.RequestLength(), rq.Card32('major_version'), rq.Card32('minor_version') ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('major_version'), rq.Card32('minor_version'), rq.Pad(16), ) def query_version(self): return QueryVersion( display = self.display, opcode = self.display.get_extension_major(extname), ) class RedirectWindow(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(1), rq.RequestLength(), rq.Window('window'), rq.Set('update', 1, (RedirectAutomatic, RedirectManual)), rq.Pad(3), ) def redirect_window(self, update): """Redirect the hierarchy starting at this window to off-screen storage. """ RedirectWindow(display = self.display, opcode = self.display.get_extension_major(extname), window = self, update = update, ) class RedirectSubwindows(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(2), rq.RequestLength(), rq.Window('window'), rq.Set('update', 1, (RedirectAutomatic, RedirectManual)), rq.Pad(3), ) def redirect_subwindows(self, update): """Redirect the hierarchies starting at all current and future children to this window to off-screen storage. """ RedirectSubwindows(display = self.display, opcode = self.display.get_extension_major(extname), window = self, update = update, ) class UnredirectWindow(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(3), rq.RequestLength(), rq.Window('window'), rq.Set('update', 1, (RedirectAutomatic, RedirectManual)), rq.Pad(3), ) def unredirect_window(self, update): """Stop redirecting this window hierarchy. """ UnredirectWindow(display = self.display, opcode = self.display.get_extension_major(extname), window = self, update = update, ) class UnredirectSubindows(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(4), rq.RequestLength(), rq.Window('window'), rq.Set('update', 1, (RedirectAutomatic, RedirectManual)), rq.Pad(3), ) def unredirect_subwindows(self, update): """Stop redirecting the hierarchies of children to this window. """ RedirectWindow(display = self.display, opcode = self.display.get_extension_major(extname), window = self, update = update, ) class CreateRegionFromBorderClip(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(5), rq.RequestLength(), rq.Card32('region'), # FIXME: this should be a Region from XFIXES extension rq.Window('window'), ) def create_region_from_border_clip(self): """Create a region of the border clip of the window, i.e. the area that is not clipped by the parent and any sibling windows. """ rid = self.display.allocate_resource_id() CreateRegionFromBorderClip( display = self.display, opcode = self.display.get_extension_major(extname), region = rid, window = self, ) # FIXME: create Region object and return it return rid class NameWindowPixmap(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(6), rq.RequestLength(), rq.Window('window'), rq.Pixmap('pixmap'), ) def name_window_pixmap(self): """Create a new pixmap that refers to the off-screen storage of the window, including its border. This pixmap will remain allocated until freed whatever happens with the window. However, the window will get a new off-screen pixmap every time it is mapped or resized, so to keep track of the contents you must listen for these events and get a new pixmap after them. """ pid = self.display.allocate_resource_id() NameWindowPixmap(display = self.display, opcode = self.display.get_extension_major(extname), window = self, pixmap = pid, ) cls = self.display.get_resource_class('pixmap', drawable.Pixmap) return cls(self.display, pid, owner = 1) def init(disp, info): disp.extension_add_method('display', 'composite_query_version', query_version) disp.extension_add_method('window', 'composite_redirect_window', redirect_window) disp.extension_add_method('window', 'composite_redirect_subwindows', redirect_subwindows) disp.extension_add_method('window', 'composite_unredirect_window', unredirect_window) disp.extension_add_method('window', 'composite_unredirect_subwindows', unredirect_subwindows) disp.extension_add_method('window', 'composite_create_region_from_border_clip', create_region_from_border_clip) disp.extension_add_method('window', 'composite_name_window_pixmap', name_window_pixmap) Nagstamon/Nagstamon/thirdparty/Xlib/ext/randr.py000066400000000000000000001026011316117564000223100ustar00rootroot00000000000000# Xlib.ext.randr -- RandR extension module # # Copyright (C) 2006 Mike Meyer # # 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 """RandR - provide access to the RandR extension information. This implementation is based off version 1.3 of the XRandR protocol, and may not be compatible with other versions. Version 1.2 of the protocol is documented at: http://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt """ from Xlib import X from Xlib.protocol import rq, structs extname = 'RANDR' # Event codes # RRScreenChangeNotify = 0 # V1.2 additions RRNotify = 1 # RRNotify Subcodes RRNotify_CrtcChange = 0 RRNotify_OutputChange = 1 RRNotify_OutputProperty = 2 # Event selection bits # RRScreenChangeNotifyMask = (1 << 0) # V1.2 additions RRCrtcChangeNotifyMask = (1 << 1) RROutputChangeNotifyMask = (1 << 2) RROutputPropertyNotifyMask = (1 << 3) # Constants # SetConfigSuccess = 0 SetConfigInvalidConfigTime = 1 SetConfigInvalidTime = 2 SetConfigFailed = 3 # used in the rotation field; rotation and reflection in 0.1 proto. Rotate_0 = 1 Rotate_90 = 2 Rotate_180 = 4 Rotate_270 = 8 # new in 1.0 protocol, to allow reflection of screen Reflect_X = 16 Reflect_Y = 32 # new in 1.2 protocol HSyncPositive = 0x00000001 HSyncNegative = 0x00000002 VSyncPositive = 0x00000004 VSyncNegative = 0x00000008 Interlace = 0x00000010 DoubleScan = 0x00000020 CSync = 0x00000040 CSyncPositive = 0x00000080 CSyncNegative = 0x00000100 HSkewPresent = 0x00000200 BCast = 0x00000400 PixelMultiplex = 0x00000800 DoubleClock = 0x00001000 ClockDivideBy2 = 0x00002000 # event types? Connected = 0 Disconnected = 1 UnknownConnection = 2 # Conventional RandR output properties PROPERTY_RANDR_EDID = "EDID" PROPERTY_SIGNAL_FORMAT = "SignalFormat" PROPERTY_SIGNAL_PROPERTIES = "SignalProperties" PROPERTY_CONNECTOR_TYPE = "ConnectorType" PROPERTY_CONNECTOR_NUMBER = "ConnectorNumber" PROPERTY_COMPATIBILITY_LIST = "CompatibilityList" PROPERTY_CLONE_LIST = "CloneList" # subpixel order - TODO: These constants are part of the RENDER extension and # should be moved there if/when that extension is added to python-xlib. SubPixelUnknown = 0 SubPixelHorizontalRGB = 1 SubPixelHorizontalBGR = 2 SubPixelVerticalRGB = 3 SubPixelVerticalBGR = 4 SubPixelNone = 5 # Error Codes # BadRROutput = 0 BadRRCrtc = 1 BadRRMode = 2 # Data Structures # RandR_ScreenSizes = rq.Struct( rq.Card16('width_in_pixels'), rq.Card16('height_in_pixels'), rq.Card16('width_in_millimeters'), rq.Card16('height_in_millimeters'), ) RandR_ModeInfo = rq.Struct( rq.Card32('id'), rq.Card16('width'), rq.Card16('height'), rq.Card32('dot_clock'), rq.Card16('h_sync_start'), rq.Card16('h_sync_end'), rq.Card16('h_total'), rq.Card16('h_skew'), rq.Card16('v_sync_start'), rq.Card16('v_sync_end'), rq.Card16('v_total'), rq.Card16('name_length'), rq.Card32('flags'), ) RandR_Rates = rq.Struct( rq.LengthOf('rates', 2), rq.List('rates', rq.Card16Obj) ) # TODO: This struct is part of the RENDER extension and should be moved there # if/when that extension is added to python-xlib. Render_Transform = rq.Struct( rq.Card32('matrix11'), #FIXME: All of these are listed as FIXED in the protocol header. rq.Card32('matrix12'), rq.Card32('matrix13'), rq.Card32('matrix21'), rq.Card32('matrix22'), rq.Card32('matrix23'), rq.Card32('matrix31'), rq.Card32('matrix32'), rq.Card32('matrix33'), ) # Requests # class QueryVersion(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(0), rq.RequestLength(), rq.Card32('major_version'), rq.Card32('minor_version'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('major_version'), rq.Card32('minor_version'), rq.Pad(16), ) def query_version(self): """Get the current version of the RandR extension. """ return QueryVersion( display=self.display, opcode=self.display.get_extension_major(extname), major_version=1, minor_version=3, ) class _1_0SetScreenConfig(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(2), rq.RequestLength(), rq.Drawable('drawable'), rq.Card32('timestamp'), rq.Card32('config_timestamp'), rq.Card16('size_id'), rq.Card16('rotation'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('status'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('new_timestamp'), rq.Card32('new_config_timestamp'), rq.Window('root'), rq.Card16('subpixel_order'), rq.Pad(10), ) def _1_0set_screen_config(self, size_id, rotation, config_timestamp, timestamp=X.CurrentTime): """Sets the screen to the specified size and rotation. """ return _1_0SetScreenConfig( display=self.display, opcode=self.display.get_extension_major(extname), drawable=self, timestamp=timestamp, config_timestamp=config_timestamp, size_id=size_id, rotation=rotation, ) class SetScreenConfig(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(2), rq.RequestLength(), rq.Drawable('drawable'), rq.Card32('timestamp'), rq.Card32('config_timestamp'), rq.Card16('size_id'), rq.Card16('rotation'), rq.Card16('rate'), # added in version 1.1 rq.Pad(2), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('status'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('new_timestamp'), rq.Card32('new_config_timestamp'), rq.Window('root'), rq.Card16('subpixel_order'), rq.Pad(10), ) def set_screen_config(self, size_id, rotation, config_timestamp, rate=0, timestamp=X.CurrentTime): """Sets the screen to the specified size, rate, rotation and reflection. rate can be 0 to have the server select an appropriate rate. """ return SetScreenConfig( display=self.display, opcode=self.display.get_extension_major(extname), drawable=self, timestamp=timestamp, config_timestamp=config_timestamp, size_id=size_id, rotation=rotation, rate=rate, ) class SelectInput(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(4), rq.RequestLength(), rq.Window('window'), rq.Card16('mask'), rq.Pad(2), ) def select_input(self, mask): return SelectInput( display=self.display, opcode=self.display.get_extension_major(extname), window=self, mask=mask, ) class GetScreenInfo(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(5), rq.RequestLength(), rq.Window('window'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('set_of_rotations'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Window('root'), rq.Card32('timestamp'), rq.Card32('config_timestamp'), rq.LengthOf('sizes', 2), rq.Card16('size_id'), rq.Card16('rotation'), rq.Card16('rate'), # added in version 1.1 rq.Card16('n_rate_ents'), # XCB's protocol description disagrees with the X headers on this; ignoring. rq.Pad(2), rq.List('sizes', RandR_ScreenSizes), #rq.List('rates', RandR_Rates) #FIXME: Why does uncommenting this cause an error? ) def get_screen_info(self): """Retrieve information about the current and available configurations for the screen associated with this window. """ return GetScreenInfo( display=self.display, opcode=self.display.get_extension_major(extname), window=self, ) # version 1.2 class GetScreenSizeRange(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(6), rq.RequestLength(), rq.Window('window'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card16('min_width'), rq.Card16('min_height'), rq.Card16('max_width'), rq.Card16('max_height'), rq.Pad(16), ) def get_screen_size_range(self): """Retrieve the range of possible screen sizes. The screen may be set to any size within this range. """ return GetScreenSizeRange( display=self.display, opcode=self.display.get_extension_major(extname), window=self, ) class SetScreenSize(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(7), rq.RequestLength(), rq.Window('window'), rq.Card16('width'), rq.Card16('height'), rq.Card32('width_in_millimeters'), rq.Card32('height_in_millimeters'), ) def set_screen_size(self, width, height, width_in_millimeters=None, height_in_millimeters=None): return SetScreenSize( display=self.display, opcode=self.display.get_extension_major(extname), window=self, width=width, height=height, width_in_millimeters=width_in_millimeters, height_in_millimeters=height_in_millimeters, ) class GetScreenResources(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(8), rq.RequestLength(), rq.Window('window'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('timestamp'), rq.Card32('config_timestamp'), rq.LengthOf('crtcs', 2), rq.LengthOf('outputs', 2), rq.LengthOf('modes', 2), rq.LengthOf('mode_names', 2), rq.Pad(8), rq.List('crtcs', rq.Card32Obj), rq.List('outputs', rq.Card32Obj), rq.List('modes', RandR_ModeInfo), rq.String8('mode_names'), ) def get_screen_resources(self): return GetScreenResources( display=self.display, opcode=self.display.get_extension_major(extname), window=self, ) class GetOutputInfo(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(9), rq.RequestLength(), rq.Card32('output'), rq.Card32('config_timestamp'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('status'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('timestamp'), rq.Card32('crtc'), rq.Card32('mm_width'), rq.Card32('mm_height'), rq.Card8('connection'), rq.Card8('subpixel_order'), rq.LengthOf('crtcs', 2), rq.LengthOf('modes', 2), rq.LengthOf('preferred', 2), rq.LengthOf('clones', 2), rq.LengthOf('name', 2), rq.List('crtcs', rq.Card32Obj), rq.List('modes', rq.Card32Obj), rq.List('preferred', rq.Card32Obj), rq.List('clones', rq.Card32Obj), rq.String8('name'), ) def get_output_info(self, output, config_timestamp): return GetOutputInfo( display=self.display, opcode=self.display.get_extension_major(extname), output=output, config_timestamp=config_timestamp, ) class ListOutputProperties(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(10), rq.RequestLength(), rq.Card32('output'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.LengthOf('atoms', 2), rq.Pad(22), rq.List('atoms', rq.Card32Obj), ) def list_output_properties(self, output): return ListOutputProperties ( display=self.display, opcode=self.display.get_extension_major(extname), output=output, ) class QueryOutputProperty(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(11), rq.RequestLength(), rq.Card32('output'), rq.Card32('property'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Bool('pending'), rq.Bool('range'), rq.Bool('immutable'), rq.Pad(21), rq.List('valid_values', rq.Card32Obj), ) def query_output_property(self, output, property): return QueryOutputProperty ( display=self.display, opcode=self.display.get_extension_major(extname), output=output, property=property, ) class ConfigureOutputProperty (rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(12), rq.RequestLength(), rq.Card32('output'), rq.Card32('property'), rq.Bool('pending'), rq.Bool('range'), rq.Pad(2), rq.List('valid_values', rq.Card32Obj), ) def configure_output_property (self, output, property): return ConfigureOutputProperty ( display=self.display, opcode=self.display.get_extension_major(extname), output=output, property=property, ) class ChangeOutputProperty(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(13), rq.RequestLength(), rq.Card32('output'), rq.Card32('property'), rq.Card32('type'), rq.Format('value', 1), rq.Card8('mode'), rq.Pad(2), rq.LengthOf('value', 4), rq.List('value', rq.Card8Obj), ) def change_output_property(self, output, property, type, format, mode, nUnits): return ChangeOutputProperty( display=self.display, opcode=self.display.get_extension_major(extname), output=output, property=property, type=type, format=format, mode=mode, nUnits=nUnits, ) class DeleteOutputProperty(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(14), rq.RequestLength(), rq.Card32('output'), rq.Card32('property'), ) def delete_output_property(self, output, property): return DeleteOutputProperty( display=self.display, opcode=self.display.get_extension_major(extname), output=output, property=property, ) class GetOutputProperty(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(15), rq.RequestLength(), rq.Card32('output'), rq.Card32('property'), rq.Card32('type'), rq.Card32('long_offset'), rq.Card32('long_length'), rq.Bool('delete'), rq.Bool('pending'), rq.Pad(2), ) _reply = rq.Struct( rq.ReplyCode(), rq.Format('value', 1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('property_type'), rq.Card32('bytes_after'), rq.LengthOf('value', 4), rq.Pad(12), rq.List('value', rq.Card8Obj), ) def get_output_property(self, output, property, type, longOffset, longLength): return GetOutputProperty( display=self.display, opcode=self.display.get_extension_major(extname), output=output, property=property, type=type, longOffset=longOffset, longLength=longLength, ) class CreateMode(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(16), rq.RequestLength(), rq.Window('window'), rq.Object('mode', RandR_ModeInfo), rq.String8('name'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('mode'), rq.Pad(20), ) def create_mode(self): return CreateMode ( display=self.display, opcode=self.display.get_extension_major(extname), window=self, ) class DestroyMode(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(17), rq.RequestLength(), rq.Card32('mode'), ) def destroy_mode(self, mode): return DestroyMode( display=self.display, opcode=self.display.get_extension_major(extname), mode=mode, ) class AddOutputMode(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(18), rq.RequestLength(), rq.Card32('output'), rq.Card32('mode'), ) def add_output_mode(self): return AddOutputMode( display=self.display, opcode=self.display.get_extension_major(extname), output=output, mode=mode, ) class DeleteOutputMode(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(19), rq.RequestLength(), rq.Card32('output'), rq.Card32('mode'), ) def delete_output_mode(self): return DeleteOutputMode( display=self.display, opcode=self.display.get_extension_major(extname), output=output, mode=mode, ) class GetCrtcInfo(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(20), rq.RequestLength(), rq.Card32('crtc'), rq.Card32('config_timestamp'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('status'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('timestamp'), rq.Card16('width'), rq.Card16('height'), rq.Card32('mode'), rq.Card16('rotation'), rq.Card16('possible_rotations'), rq.LengthOf('outputs', 2), rq.LengthOf('possible_outputs', 2), rq.List('outputs', rq.Card32Obj), rq.List('possible_outputs', rq.Card32Obj), ) def get_crtc_info(self, crtc, config_timestamp): return GetCrtcInfo ( display=self.display, opcode=self.display.get_extension_major(extname), crtc=crtc, config_timestamp=config_timestamp, ) class SetCrtcConfig(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(21), rq.RequestLength(), rq.Card32('crtc'), rq.Card32('timestamp'), rq.Card32('config_timestamp'), rq.Int16('x'), rq.Int16('y'), rq.Card32('mode'), rq.Card16('rotation'), rq.Pad(2), rq.List('outputs', rq.Card32Obj), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('status'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('new_timestamp'), rq.Pad(20), ) def set_crtc_config(self, crtc, config_timestamp, mode, rotation, timestamp=X.CurrentTime): return SetCrtcConfig ( display=self.display, opcode=self.display.get_extension_major(extname), crtc=crtc, config_timestamp=config_timestamp, mode=mode, rotation=rotation, timestamp=timestamp, ) class GetCrtcGammaSize(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(22), rq.RequestLength(), rq.Card32('crtc'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('status'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card16('size'), rq.Pad(22), ) def get_crtc_gamma_size(self, crtc): return GetCrtcGammaSize ( display=self.display, opcode=self.display.get_extension_major(extname), crtc=crtc, ) class GetCrtcGamma(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(23), rq.RequestLength(), rq.Card32('crtc'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('status'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card16('size'), rq.Pad(22), rq.List('red', rq.Card16Obj), rq.List('green', rq.Card16Obj), rq.List('blue', rq.Card16Obj), ) def get_crtc_gamma(self, crtc): return GetCrtcGamma ( display=self.display, opcode=self.display.get_extension_major(extname), crtc=crtc, ) class SetCrtcGamma(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(24), rq.RequestLength(), rq.Card32('crtc'), rq.Card16('size'), rq.Pad(2), rq.List('red', rq.Card16Obj), rq.List('green', rq.Card16Obj), rq.List('blue', rq.Card16Obj), ) def set_crtc_gamma(self, crtc, size): return SetCrtcGamma( display=self.display, opcode=self.display.get_extension_major(extname), crtc=crtc, size=size, ) # version 1.3 class GetScreenResourcesCurrent(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(25), rq.RequestLength(), rq.Window('window'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('timestamp'), rq.Card32('config_timestamp'), rq.LengthOf('crtcs', 2), rq.LengthOf('outputs', 2), rq.LengthOf('modes', 2), rq.LengthOf('names', 2), rq.Pad(8), rq.List('crtcs', rq.Card32Obj), rq.List('outputs', rq.Card32Obj), rq.List('modes', RandR_ModeInfo), rq.String8('names'), ) def get_screen_resources_current(self): return GetScreenResourcesCurrent( display=self.display, opcode=self.display.get_extension_major(extname), window=self, ) class SetCrtcTransform(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(26), rq.RequestLength(), rq.Card32('crtc'), rq.Object('transform', Render_Transform), rq.LengthOf('filter_name', 2), rq.Pad(2), rq.String8('filter_name'), rq.List('filter_params', rq.Card32Obj), #FIXME: The protocol says FIXED? http://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt#n2161 ) def set_crtc_transform(self, crtc, n_bytes_filter): return SetCrtcTransform( display=self.display, opcode=self.display.get_extension_major(extname), crtc=crtc, n_bytes_filter=n_bytes_filter, ) class GetCrtcTransform(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(27), rq.RequestLength(), rq.Card32('crtc'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('status'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Object('pending_transform', Render_Transform), rq.Bool('has_transforms'), rq.Pad(3), rq.Object('current_transform', Render_Transform), rq.Pad(4), rq.LengthOf('pending_filter_name', 2), rq.LengthOf('pending_filter_params', 2), rq.LengthOf('current_filter_name', 2), rq.LengthOf('current_filter_params', 2), rq.String8('pending_filter_name'), rq.List('pending_filter_params', rq.Card32Obj), #FIXME: The protocol says FIXED? http://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt#n2161 rq.String8('current_filter_name'), rq.List('current_filter_params', rq.Card32Obj), #FIXME: The protocol says FIXED? http://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt#n2161 ) def get_crtc_transform(self, crtc): return GetCrtcTransform( display=self.display, opcode=self.display.get_extension_major(extname), crtc=crtc, ) class GetPanning(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(28), rq.RequestLength(), rq.Card32('crtc'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('status'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('timestamp'), rq.Card16('left'), rq.Card16('top'), rq.Card16('width'), rq.Card16('height'), rq.Card16('track_left'), rq.Card16('track_top'), rq.Card16('track_width'), rq.Card16('track_height'), rq.Int16('border_left'), rq.Int16('border_top'), rq.Int16('border_right'), rq.Int16('border_bottom'), ) def get_panning(self, crtc): return GetPanning ( display=self.display, opcode=self.display.get_extension_major(extname), crtc=crtc, ) class SetPanning(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(29), rq.RequestLength(), rq.Card32('crtc'), rq.Card32('timestamp'), rq.Card16('left'), rq.Card16('top'), rq.Card16('width'), rq.Card16('height'), rq.Card16('track_left'), rq.Card16('track_top'), rq.Card16('track_width'), rq.Card16('track_height'), rq.Int16('border_left'), rq.Int16('border_top'), rq.Int16('border_right'), rq.Int16('border_bottom'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('status'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('new_timestamp'), rq.Pad(20), ) def set_panning(self, crtc, left, top, width, height, track_left, track_top, track_width, track_height, border_left, border_top, border_width, border_height, timestamp=X.CurrentTime): return SetPanning ( display=self.display, opcode=self.display.get_extension_major(extname), crtc=crtc, left=left, top=top, width=width, height=height, track_left=track_left, track_top=track_top, track_width=track_width, track_height=track_height, border_left=border_left, border_top=border_top, border_width=border_width, border_height=border_height, timestamp=timestamp, ) class SetOutputPrimary(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(30), rq.RequestLength(), rq.Window('window'), rq.Card32('output'), ) def set_output_primary(self, output): return SetOutputPrimary( display=self.display, opcode=self.display.get_extension_major(extname), window=self, output=output, ) class GetOutputPrimary(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(31), rq.RequestLength(), rq.Window('window'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('output'), rq.Pad(20), ) def get_output_primary(self): return GetOutputPrimary( display=self.display, opcode=self.display.get_extension_major(extname), window=self, ) # Events # class ScreenChangeNotify(rq.Event): _code = None _fields = rq.Struct( rq.Card8('type'), rq.Card8('rotation'), rq.Card16('sequence_number'), rq.Card32('timestamp'), rq.Card32('config_timestamp'), rq.Window('root'), rq.Window('window'), rq.Card16('size_id'), rq.Card16('subpixel_order'), rq.Card16('width_in_pixels'), rq.Card16('height_in_pixels'), rq.Card16('width_in_millimeters'), rq.Card16('height_in_millimeters'), ) class CrtcChangeNotify(rq.Event): _code = None _fields = rq.Struct( rq.Card8('type'), rq.Card8('sub_code'), rq.Card16('sequence_number'), rq.Card32('timestamp'), rq.Window('window'), rq.Card32('crtc'), rq.Card32('mode'), rq.Card16('rotation'), rq.Pad(2), rq.Int16('x'), rq.Int16('y'), rq.Card16('width'), rq.Card16('height'), ) class OutputChangeNotify(rq.Event): _code = None _fields = rq.Struct( rq.Card8('type'), rq.Card8('sub_code'), rq.Card16('sequence_number'), rq.Card32('timestamp'), rq.Card32('config_timestamp'), rq.Window('window'), rq.Card32('output'), rq.Card32('crtc'), rq.Card32('mode'), rq.Card16('rotation'), rq.Card8('connection'), rq.Card8('subpixel_order'), ) class OutputPropertyNotify(rq.Event): _code = None _fields = rq.Struct( rq.Card8('type'), rq.Card8('sub_code'), rq.Card16('sequence_number'), rq.Window('window'), rq.Card32('output'), rq.Card32('atom'), rq.Card32('timestamp'), rq.Card8('state'), rq.Pad(11), ) # Initialization # def init(disp, info): disp.extension_add_method('display', 'xrandr_query_version', query_version) disp.extension_add_method('window', 'xrandr_select_input', select_input) disp.extension_add_method('window', 'xrandr_get_screen_info', get_screen_info) disp.extension_add_method('drawable', 'xrandr_1_0set_screen_config', _1_0set_screen_config) disp.extension_add_method('drawable', 'xrandr_set_screen_config', set_screen_config) disp.extension_add_method('window', 'xrandr_get_screen_size_range', get_screen_size_range) disp.extension_add_method('window', 'xrandr_set_screen_size', set_screen_size) disp.extension_add_method('window', 'xrandr_get_screen_resources', get_screen_resources) disp.extension_add_method('display', 'xrandr_get_output_info', get_output_info) disp.extension_add_method('display', 'xrandr_list_output_properties', list_output_properties) disp.extension_add_method('display', 'xrandr_query_output_property', query_output_property) disp.extension_add_method('display', 'xrandr_configure_output_property ', configure_output_property ) disp.extension_add_method('display', 'xrandr_change_output_property', change_output_property) disp.extension_add_method('display', 'xrandr_delete_output_property', delete_output_property) disp.extension_add_method('display', 'xrandr_get_output_property', get_output_property) disp.extension_add_method('window', 'xrandr_create_mode', create_mode) disp.extension_add_method('display', 'xrandr_destroy_mode', destroy_mode) disp.extension_add_method('display', 'xrandr_add_output_mode', add_output_mode) disp.extension_add_method('display', 'xrandr_delete_output_mode', delete_output_mode) disp.extension_add_method('display', 'xrandr_get_crtc_info', get_crtc_info) disp.extension_add_method('display', 'xrandr_set_crtc_config', set_crtc_config) disp.extension_add_method('display', 'xrandr_get_crtc_gamma_size', get_crtc_gamma_size) disp.extension_add_method('display', 'xrandr_get_crtc_gamma', get_crtc_gamma) disp.extension_add_method('display', 'xrandr_set_crtc_gamma', set_crtc_gamma) disp.extension_add_method('window', 'xrandr_get_screen_resources_current', get_screen_resources_current) disp.extension_add_method('display', 'xrandr_set_crtc_transform', set_crtc_transform) disp.extension_add_method('display', 'xrandr_get_crtc_transform', get_crtc_transform) disp.extension_add_method('window', 'xrandr_set_output_primary', set_output_primary) disp.extension_add_method('window', 'xrandr_get_output_primary', get_output_primary) disp.extension_add_method('display', 'xrandr_get_panning', get_panning) disp.extension_add_method('display', 'xrandr_set_panning', set_panning) disp.extension_add_event(info.first_event, ScreenChangeNotify) disp.extension_add_event(info.first_event + 1, CrtcChangeNotify) disp.extension_add_event(info.first_event + 2, OutputChangeNotify) disp.extension_add_event(info.first_event + 3, OutputPropertyNotify) #disp.extension_add_error(BadRROutput, BadRROutputError) #disp.extension_add_error(BadRRCrtc, BadRRCrtcError) #disp.extension_add_error(BadRRMode, BadRRModeError) Nagstamon/Nagstamon/thirdparty/Xlib/ext/record.py000066400000000000000000000222011316117564000224550ustar00rootroot00000000000000# Xlib.ext.record -- RECORD extension module # # Copyright (C) 2006 Alex Badea # # 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 from Xlib import X from Xlib.protocol import rq extname = 'RECORD' FromServerTime = 0x01 FromClientTime = 0x02 FromClientSequence = 0x04 CurrentClients = 1 FutureClients = 2 AllClients = 3 FromServer = 0 FromClient = 1 ClientStarted = 2 ClientDied = 3 StartOfData = 4 EndOfData = 5 Record_Range8 = rq.Struct( rq.Card8('first'), rq.Card8('last')) Record_Range16 = rq.Struct( rq.Card16('first'), rq.Card16('last')) Record_ExtRange = rq.Struct( rq.Object('major_range', Record_Range8), rq.Object('minor_range', Record_Range16)) Record_Range = rq.Struct( rq.Object('core_requests', Record_Range8), rq.Object('core_replies', Record_Range8), rq.Object('ext_requests', Record_ExtRange), rq.Object('ext_replies', Record_ExtRange), rq.Object('delivered_events', Record_Range8), rq.Object('device_events', Record_Range8), rq.Object('errors', Record_Range8), rq.Bool('client_started'), rq.Bool('client_died')) Record_ClientInfo = rq.Struct( rq.Card32('client_resource'), rq.LengthOf('ranges', 4), rq.List('ranges', Record_Range)) class RawField(rq.ValueField): """A field with raw data, stored as a string""" structcode = None def pack_value(self, val): return val, len(val), None def parse_binary_value(self, data, display, length, format): return data, '' class GetVersion(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(0), rq.RequestLength(), rq.Card16('major_version'), rq.Card16('minor_version')) _reply = rq.Struct( rq.Pad(2), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card16('major_version'), rq.Card16('minor_version'), rq.Pad(20)) def get_version(self, major, minor): return GetVersion( display = self.display, opcode = self.display.get_extension_major(extname), major_version = major, minor_version = minor) class CreateContext(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(1), rq.RequestLength(), rq.Card32('context'), # Record_RC rq.Card8('element_header'), # Record_Element_Header rq.Pad(3), rq.LengthOf('clients', 4), rq.LengthOf('ranges', 4), rq.List('clients', rq.Card32Obj), rq.List('ranges', Record_Range)) def create_context(self, datum_flags, clients, ranges): context = self.display.allocate_resource_id() CreateContext( display = self.display, opcode = self.display.get_extension_major(extname), context = context, element_header = datum_flags, clients = clients, ranges = ranges) return context class RegisterClients(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(2), rq.RequestLength(), rq.Card32('context'), # Record_RC rq.Card8('element_header'), # Record_Element_Header rq.Pad(3), rq.LengthOf('clients', 4), rq.LengthOf('ranges', 4), rq.List('clients', rq.Card32Obj), rq.List('ranges', Record_Range)) def register_clients(self, context, element_header, clients, ranges): RegisterClients( display = self.display, opcode = self.display.get_extension_major(extname), context = context, element_header = element_header, clients = clients, ranges = ranges) class UnregisterClients(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(3), rq.RequestLength(), rq.Card32('context'), # Record_RC rq.LengthOf('clients', 4), rq.List('clients', rq.Card32Obj)) def unregister_clients(self, context, clients): UnregisterClients( display = self.display, opcode = self.display.get_extension_major(extname), context = context, clients = clients) class GetContext(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(4), rq.RequestLength(), rq.Card32('context')) # Record_RC _reply = rq.Struct( rq.Pad(2), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card8('element_header'), # Record_Element_Header rq.Pad(3), rq.LengthOf('client_info', 4), rq.Pad(16), rq.List('client_info', Record_ClientInfo)) def get_context(self, context): return GetContext( display = self.display, opcode = self.display.get_extension_major(extname), context = context) class EnableContext(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(5), rq.RequestLength(), rq.Card32('context')) # Record_RC _reply = rq.Struct( rq.Pad(1), rq.Card8('category'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card8('element_header'), # Record_Element_Header rq.Bool('client_swapped'), rq.Pad(2), rq.Card32('id_base'), # Record_XIDBase rq.Card32('server_time'), rq.Card32('recorded_sequence_number'), rq.Pad(8), RawField('data')) # This request receives multiple responses, so we need to keep # ourselves in the 'sent_requests' list in order to receive them all. # See the discussion on ListFonstsWithInfo in request.py def __init__(self, callback, *args, **keys): self._callback = callback rq.ReplyRequest.__init__(self, *args, **keys) def _parse_response(self, data): r, d = self._reply.parse_binary(data, self._display) self._callback(r) if r.category == StartOfData: # Hack ourselves a sequence number, used by the code in # Xlib.protocol.display.Display.parse_request_response() self.sequence_number = r.sequence_number if r.category == EndOfData: self._response_lock.acquire() self._data = r self._response_lock.release() else: self._display.sent_requests.insert(0, self) def enable_context(self, context, callback): EnableContext( callback = callback, display = self.display, opcode = self.display.get_extension_major(extname), context = context) class DisableContext(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(6), rq.RequestLength(), rq.Card32('context')) # Record_RC def disable_context(self, context): DisableContext( display = self.display, opcode = self.display.get_extension_major(extname), context = context) class FreeContext(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(7), rq.RequestLength(), rq.Card32('context')) # Record_RC def free_context(self, context): FreeContext( display = self.display, opcode = self.display.get_extension_major(extname), context = context) self.display.free_resource_id(context) def init(disp, info): disp.extension_add_method('display', 'record_get_version', get_version) disp.extension_add_method('display', 'record_create_context', create_context) disp.extension_add_method('display', 'record_register_clients', register_clients) disp.extension_add_method('display', 'record_unregister_clients', unregister_clients) disp.extension_add_method('display', 'record_get_context', get_context) disp.extension_add_method('display', 'record_enable_context', enable_context) disp.extension_add_method('display', 'record_disable_context', disable_context) disp.extension_add_method('display', 'record_free_context', free_context) Nagstamon/Nagstamon/thirdparty/Xlib/ext/shape.py000066400000000000000000000243531316117564000223110ustar00rootroot00000000000000# Xlib.ext.shape -- SHAPE extension module # # Copyright (C) 2002 Jeffrey Boser # # 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 # Constants to use # # Regions of a window ShapeBounding = 0 # the 'edge' of a shaped window ShapeClip = 1 # the clipping region # Shape Operations ShapeSet = 0 # Set the region unmodified (dest=src) ShapeUnion = 1 # Add the new region to the old (dest=src|dest) ShapeIntersect = 2 # Use the intersection (dest=src&dest) ShapeSubtract = 3 # remove region (dest = dest - intersect) ShapeInvert = 4 # opposite of subtract (dest = src - intersect) # Events ShapeNotifyMask = (1<<0) #a keypress mask? ShapeNotify = 0 #still unsure of these values # How to Use # The basic functions that change the shapes of things are: # shape_rectangles (uses a set of rectangles as the source) # operation, region, ordering, rects # shape_mask (uses a bitmap as the source) # operation, region, x_offset, y_offset, bitmap # shape_combine (uses a window as the source) # operation, src_region, dest_region, x_offset, y_offset, src_window # shape_offset (moves the region) # region, x_offset, y_offset # The functions to find stuff out (these three return mappings of field/values): # shape_query_version (shape extension version) # major_version, minor_version # shape_query_extents (rectangle boundaries of a window's regions) # clip_shaped, clip_x, clip_y, clip_width, clip_height, # bounding_shaped, bounding_x, bounding_y, bounding_width, bounding_height # shape_input_selected (if the window products shapenotify events) # enabled # shape_get_rectangles (the rectangles set by shape_rectangles) # ordering, rects # And to turn on shape notify events: # shape_select_input # enable from Xlib import X from Xlib.protocol import rq, structs extname = 'SHAPE' class QueryVersion(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(0), rq.RequestLength(), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card16('major_version'), rq.Card16('minor_version'), rq.Pad(20), ) def query_version(self): return QueryVersion( display = self.display, opcode = self.display.get_extension_major(extname), ) class Rectangles(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(1), rq.RequestLength(), rq.Card8('operation'), rq.Set('region', 1, (ShapeBounding, ShapeClip)), rq.Card8('ordering'), rq.Pad(1), rq.Window('window'), rq.Int16('x'), rq.Int16('y'), rq.List('rectangles', structs.Rectangle), ) def rectangles(self, region, operation, ordering, x, y, rectangles): Rectangles( display = self.display, opcode = self.display.get_extension_major(extname), operation = operation, region = region, ordering = ordering, window = self.id, x = x, y = y, rectangles = rectangles, ) class Mask(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(2), rq.RequestLength(), rq.Card8('operation'), rq.Set('region', 1, (ShapeBounding, ShapeClip)), rq.Pad(2), rq.Window('window'), rq.Int16('x'), rq.Int16('y'), rq.Pixmap('source', (X.NONE, )), ) def mask(self, operation, region, x, y, source): Mask(display = self.display, opcode = self.display.get_extension_major(extname), window = self.id, operation = operation, region = region, x = x, y = y, source = source, ) class Combine(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(3), rq.RequestLength(), rq.Card8('operation'), rq.Set('dest_region', 1, (ShapeBounding, ShapeClip)), rq.Set('source_region', 1, (ShapeBounding, ShapeClip)), rq.Pad(1), rq.Window('dest'), rq.Int16('x'), rq.Int16('y'), rq.Window('source'), ) def combine(self, operation, region, source, source_region, x, y): Combine( display = self.display, opcode = self.display.get_extension_major(extname), operation = operation, dest_region = region, source_region = source_region, dest = self.id, x = x, y = y, source = source, ) class Offset(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(4), rq.RequestLength(), rq.Set('region', 1, (ShapeBounding, ShapeClip)), rq.Pad(3), rq.Window('window'), rq.Int16('x'), rq.Int16('y'), ) def offset(self, region, x, y): Offset( display = self.display, opcode = self.display.get_extension_major(extname), region = region, window = self.id, x = x, y = y, ) class QueryExtents(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(5), rq.RequestLength(), rq.Window('window'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Bool('bounding_shaped'), rq.Bool('clip_shaped'), rq.Pad(2), rq.Int16('bounding_x'), rq.Int16('bounding_y'), rq.Card16('bounding_width'), rq.Card16('bounding_height'), rq.Int16('clip_x'), rq.Int16('clip_y'), rq.Card16('clip_width'), rq.Card16('clip_height'), rq.Pad(4), ) def query_extents(self): return QueryExtents( display = self.display, opcode = self.display.get_extension_major(extname), window = self.id, ) class SelectInput(rq.Request): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(6), rq.RequestLength(), rq.Window('window'), rq.Bool('enable'), rq.Pad(3), ) def select_input(self, enable = 1): SelectInput( display = self.display, opcode = self.display.get_extension_major(extname), window = self.id, enable = enable, ) class InputSelected(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(7), rq.RequestLength(), rq.Window('window'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Bool('enabled'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Pad(24), ) def input_selected(self): reply = InputSelected( display = self.display, opcode = self.display.get_extension_major(extname), window = self.id, ) return reply.enabled class GetRectangles(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(8), rq.RequestLength(), rq.Window('window'), rq.Set('region', 1, (ShapeBounding, ShapeClip)), rq.Pad(3), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('ordering'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.LengthOf('rectangles', 4), rq.Pad(20), rq.List('rectangles', structs.Rectangle), ) def get_rectangles(self, region): return GetRectangles( display = self.display, opcode = self.display.get_extension_major(extname), window = self.id, region = region, ) class ShapeNotify(rq.Event): _code = None _fields = rq.Struct( rq.Card8('type'), rq.Set('region', 1, (ShapeBounding, ShapeClip)), rq.Card16('sequence_number'), rq.Window('window'), rq.Int16('x'), rq.Int16('y'), rq.Card16('width'), rq.Card16('height'), rq.Card32('time'), rq.Bool('shaped'), rq.Pad(11), ) def init(disp, info): disp.extension_add_method('display', 'shape_query_version', query_version ) disp.extension_add_method('window', 'shape_rectangles', rectangles ) disp.extension_add_method('window', 'shape_mask', mask ) disp.extension_add_method('window', 'shape_combine', combine ) disp.extension_add_method('window', 'shape_offset', offset ) disp.extension_add_method('window', 'shape_query_extents', query_extents ) disp.extension_add_method('window', 'shape_select_input', select_input ) disp.extension_add_method('window', 'shape_input_selected', input_selected ) disp.extension_add_method('window', 'shape_get_rectangles', get_rectangles ) disp.extension_add_event(info.first_event, ShapeNotify) Nagstamon/Nagstamon/thirdparty/Xlib/ext/xinerama.py000066400000000000000000000154261316117564000230160ustar00rootroot00000000000000# Xlib.ext.xinerama -- Xinerama extension module # # Copyright (C) 2006 Mike Meyer # # 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 """Xinerama - provide access to the Xinerama extension information. There are at least there different - and mutually incomparable - Xinerama extensions available. This uses the one bundled with XFree86 4.6 and/or Xorg 6.9 in the ati/radeon driver. It uses the include files from that X distribution, so should work with it as well. I provide code for the lone Sun 1.0 request that isn't part of 1.1, but this is untested because I don't have a server that implements it. The functions loosely follow the libXineram functions. Mostly, they return an rq.Struct in lieue of passing in pointers that get data from the rq.Struct crammed into them. The exception is isActive, which returns the state information - because that's what libXinerama does.""" from Xlib import X from Xlib.protocol import rq, structs extname = 'XINERAMA' class QueryVersion(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(0), rq.RequestLength(), rq.Card8('major_version'), rq.Card8('minor_version'), rq.Pad(2), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card16('major_version'), rq.Card16('minor_version'), rq.Pad(20), ) def query_version(self): return QueryVersion(display=self.display, opcode=self.display.get_extension_major(extname), major_version=1, minor_version=1) class GetState(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(1), rq.RequestLength(), rq.Window('window'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Bool('state'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Window('window'), rq.Pad(20), ) def get_state(self): return GetState(display=self.display, opcode=self.display.get_extension_major(extname), window=self.id, ) class GetScreenCount(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(2), rq.RequestLength(), rq.Window('window'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('screen_count'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Window('window'), rq.Pad(20), ) def get_screen_count(self): return GetScreenCount(display=self.display, opcode=self.display.get_extension_major(extname), window=self.id, ) class GetScreenSize(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(3), rq.RequestLength(), rq.Window('window'), rq.Card32('screen'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.Card32('length'), rq.Card32('width'), rq.Card32('height'), rq.Window('window'), rq.Card32('screen'), rq.Pad(8), ) def get_screen_size(self, screen_no): """Returns the size of the given screen number""" return GetScreenSize(display=self.display, opcode=self.display.get_extension_major(extname), window=self.id, screen=screen_no, ) # IsActive is only available from Xinerama 1.1 and later. # It should be used in preference to GetState. class IsActive(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(4), rq.RequestLength(), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('state'), rq.Pad(20), ) def is_active(self): r = IsActive(display=self.display, opcode=self.display.get_extension_major(extname), ) return r.state # QueryScreens is only available from Xinerama 1.1 and later class QueryScreens(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(5), rq.RequestLength(), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('number'), rq.Pad(20), rq.List('screens', structs.Rectangle), ) def query_screens(self): # Hmm. This one needs to read the screen data from the socket. Ooops... return QueryScreens(display=self.display, opcode=self.display.get_extension_major(extname), ) # GetInfo is only available from some Xinerama 1.0, and *NOT* later! Untested class GetInfo(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), rq.Opcode(4), rq.RequestLength(), rq.Card32('visual'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Window('window'), # An array of subwindow slots goes here. Bah. ) def get_info(self, visual): r = GetInfo(display=self.display, opcode=self.display.get_extension_major(extname), visual=visual) def init(disp, info): disp.extension_add_method('display', 'xinerama_query_version', query_version) disp.extension_add_method('window', 'xinerama_get_state', get_state) disp.extension_add_method('window', 'xinerama_get_screen_count', get_screen_count) disp.extension_add_method('window', 'xinerama_get_screen_size', get_screen_size) disp.extension_add_method('display', 'xinerama_is_active', is_active) disp.extension_add_method('display', 'xinerama_query_screens', query_screens) disp.extension_add_method('display', 'xinerama_get_info', get_info) Nagstamon/Nagstamon/thirdparty/Xlib/ext/xtest.py000066400000000000000000000107251316117564000223560ustar00rootroot00000000000000# Xlib.ext.xtest -- XTEST extension module # # Copyright (C) 2000 Peter Liljenberg # # 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 from Xlib import X from Xlib.protocol import rq extname = 'XTEST' CurrentCursor = 1 class GetVersion(rq.ReplyRequest): _request = rq.Struct(rq.Card8('opcode'), rq.Opcode(0), rq.RequestLength(), rq.Card8('major_version'), rq.Pad(1), rq.Card16('minor_version') ) _reply = rq.Struct(rq.Pad(1), rq.Card8('major_version'), rq.Card16('sequence_number'), rq.Pad(4), rq.Card16('minor_version'), rq.Pad(22) ) def get_version(self, major, minor): return GetVersion(display = self.display, opcode = self.display.get_extension_major(extname), major_version = major, minor_version = minor) class CompareCursor(rq.ReplyRequest): _request = rq.Struct(rq.Card8('opcode'), rq.Opcode(1), rq.RequestLength(), rq.Window('window'), rq.Cursor('cursor', (X.NONE, CurrentCursor)), ) _reply = rq.Struct(rq.Pad(1), rq.Card8('same'), rq.Card16('sequence_number'), rq.Pad(28), ) def compare_cursor(self, cursor): r = CompareCursor(display = self.display, opcode = self.display.get_extension_major(extname), window = self.id, cursor = cursor) return r.same class FakeInput(rq.Request): _request = rq.Struct(rq.Card8('opcode'), rq.Opcode(2), rq.RequestLength(), rq.Set('event_type', 1, (X.KeyPress, X.KeyRelease, X.ButtonPress, X.ButtonRelease, X.MotionNotify)), rq.Card8('detail'), rq.Pad(2), rq.Card32('time'), rq.Window('root', (X.NONE, )), rq.Pad(8), rq.Int16('x'), rq.Int16('y'), rq.Pad(8) ) def fake_input(self, event_type, detail = 0, time = X.CurrentTime, root = X.NONE, x = 0, y = 0): FakeInput(display = self.display, opcode = self.display.get_extension_major(extname), event_type = event_type, detail = detail, time = time, root = root, x = x, y = y) class GrabControl(rq.Request): _request = rq.Struct(rq.Card8('opcode'), rq.Opcode(3), rq.RequestLength(), rq.Bool('impervious'), rq.Pad(3) ) def grab_control(self, impervious): GrabControl(display = self.display, opcode = self.display.get_extension_major(extname), impervious = impervious) def init(disp, info): disp.extension_add_method('display', 'xtest_get_version', get_version) disp.extension_add_method('window', 'xtest_compare_cursor', compare_cursor) disp.extension_add_method('display', 'xtest_fake_input', fake_input) disp.extension_add_method('display', 'xtest_grab_control', grab_control) Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/000077500000000000000000000000001316117564000220305ustar00rootroot00000000000000Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/__init__.py000066400000000000000000000021521316117564000241410ustar00rootroot00000000000000# Xlib.keysymdef -- X keysym defs # # Copyright (C) 2001 Peter Liljenberg # # 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 __all__ = [ 'apl', 'arabic', 'cyrillic', 'greek', 'hebrew', 'katakana', 'korean', 'latin1', 'latin2', 'latin3', 'latin4', 'miscellany', 'publishing', 'special', 'technical', 'thai', 'xf86', 'xk3270', 'xkb', ] Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/apl.py000066400000000000000000000005611316117564000231600ustar00rootroot00000000000000XK_leftcaret = 0xba3 XK_rightcaret = 0xba6 XK_downcaret = 0xba8 XK_upcaret = 0xba9 XK_overbar = 0xbc0 XK_downtack = 0xbc2 XK_upshoe = 0xbc3 XK_downstile = 0xbc4 XK_underbar = 0xbc6 XK_jot = 0xbca XK_quad = 0xbcc XK_uptack = 0xbce XK_circle = 0xbcf XK_upstile = 0xbd3 XK_downshoe = 0xbd6 XK_rightshoe = 0xbd8 XK_leftshoe = 0xbda XK_lefttack = 0xbdc XK_righttack = 0xbfc Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/arabic.py000066400000000000000000000023051316117564000236230ustar00rootroot00000000000000XK_Arabic_comma = 0x5ac XK_Arabic_semicolon = 0x5bb XK_Arabic_question_mark = 0x5bf XK_Arabic_hamza = 0x5c1 XK_Arabic_maddaonalef = 0x5c2 XK_Arabic_hamzaonalef = 0x5c3 XK_Arabic_hamzaonwaw = 0x5c4 XK_Arabic_hamzaunderalef = 0x5c5 XK_Arabic_hamzaonyeh = 0x5c6 XK_Arabic_alef = 0x5c7 XK_Arabic_beh = 0x5c8 XK_Arabic_tehmarbuta = 0x5c9 XK_Arabic_teh = 0x5ca XK_Arabic_theh = 0x5cb XK_Arabic_jeem = 0x5cc XK_Arabic_hah = 0x5cd XK_Arabic_khah = 0x5ce XK_Arabic_dal = 0x5cf XK_Arabic_thal = 0x5d0 XK_Arabic_ra = 0x5d1 XK_Arabic_zain = 0x5d2 XK_Arabic_seen = 0x5d3 XK_Arabic_sheen = 0x5d4 XK_Arabic_sad = 0x5d5 XK_Arabic_dad = 0x5d6 XK_Arabic_tah = 0x5d7 XK_Arabic_zah = 0x5d8 XK_Arabic_ain = 0x5d9 XK_Arabic_ghain = 0x5da XK_Arabic_tatweel = 0x5e0 XK_Arabic_feh = 0x5e1 XK_Arabic_qaf = 0x5e2 XK_Arabic_kaf = 0x5e3 XK_Arabic_lam = 0x5e4 XK_Arabic_meem = 0x5e5 XK_Arabic_noon = 0x5e6 XK_Arabic_ha = 0x5e7 XK_Arabic_heh = 0x5e7 XK_Arabic_waw = 0x5e8 XK_Arabic_alefmaksura = 0x5e9 XK_Arabic_yeh = 0x5ea XK_Arabic_fathatan = 0x5eb XK_Arabic_dammatan = 0x5ec XK_Arabic_kasratan = 0x5ed XK_Arabic_fatha = 0x5ee XK_Arabic_damma = 0x5ef XK_Arabic_kasra = 0x5f0 XK_Arabic_shadda = 0x5f1 XK_Arabic_sukun = 0x5f2 XK_Arabic_switch = 0xFF7E Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/cyrillic.py000066400000000000000000000047541316117564000242260ustar00rootroot00000000000000XK_Serbian_dje = 0x6a1 XK_Macedonia_gje = 0x6a2 XK_Cyrillic_io = 0x6a3 XK_Ukrainian_ie = 0x6a4 XK_Ukranian_je = 0x6a4 XK_Macedonia_dse = 0x6a5 XK_Ukrainian_i = 0x6a6 XK_Ukranian_i = 0x6a6 XK_Ukrainian_yi = 0x6a7 XK_Ukranian_yi = 0x6a7 XK_Cyrillic_je = 0x6a8 XK_Serbian_je = 0x6a8 XK_Cyrillic_lje = 0x6a9 XK_Serbian_lje = 0x6a9 XK_Cyrillic_nje = 0x6aa XK_Serbian_nje = 0x6aa XK_Serbian_tshe = 0x6ab XK_Macedonia_kje = 0x6ac XK_Byelorussian_shortu = 0x6ae XK_Cyrillic_dzhe = 0x6af XK_Serbian_dze = 0x6af XK_numerosign = 0x6b0 XK_Serbian_DJE = 0x6b1 XK_Macedonia_GJE = 0x6b2 XK_Cyrillic_IO = 0x6b3 XK_Ukrainian_IE = 0x6b4 XK_Ukranian_JE = 0x6b4 XK_Macedonia_DSE = 0x6b5 XK_Ukrainian_I = 0x6b6 XK_Ukranian_I = 0x6b6 XK_Ukrainian_YI = 0x6b7 XK_Ukranian_YI = 0x6b7 XK_Cyrillic_JE = 0x6b8 XK_Serbian_JE = 0x6b8 XK_Cyrillic_LJE = 0x6b9 XK_Serbian_LJE = 0x6b9 XK_Cyrillic_NJE = 0x6ba XK_Serbian_NJE = 0x6ba XK_Serbian_TSHE = 0x6bb XK_Macedonia_KJE = 0x6bc XK_Byelorussian_SHORTU = 0x6be XK_Cyrillic_DZHE = 0x6bf XK_Serbian_DZE = 0x6bf XK_Cyrillic_yu = 0x6c0 XK_Cyrillic_a = 0x6c1 XK_Cyrillic_be = 0x6c2 XK_Cyrillic_tse = 0x6c3 XK_Cyrillic_de = 0x6c4 XK_Cyrillic_ie = 0x6c5 XK_Cyrillic_ef = 0x6c6 XK_Cyrillic_ghe = 0x6c7 XK_Cyrillic_ha = 0x6c8 XK_Cyrillic_i = 0x6c9 XK_Cyrillic_shorti = 0x6ca XK_Cyrillic_ka = 0x6cb XK_Cyrillic_el = 0x6cc XK_Cyrillic_em = 0x6cd XK_Cyrillic_en = 0x6ce XK_Cyrillic_o = 0x6cf XK_Cyrillic_pe = 0x6d0 XK_Cyrillic_ya = 0x6d1 XK_Cyrillic_er = 0x6d2 XK_Cyrillic_es = 0x6d3 XK_Cyrillic_te = 0x6d4 XK_Cyrillic_u = 0x6d5 XK_Cyrillic_zhe = 0x6d6 XK_Cyrillic_ve = 0x6d7 XK_Cyrillic_softsign = 0x6d8 XK_Cyrillic_yeru = 0x6d9 XK_Cyrillic_ze = 0x6da XK_Cyrillic_sha = 0x6db XK_Cyrillic_e = 0x6dc XK_Cyrillic_shcha = 0x6dd XK_Cyrillic_che = 0x6de XK_Cyrillic_hardsign = 0x6df XK_Cyrillic_YU = 0x6e0 XK_Cyrillic_A = 0x6e1 XK_Cyrillic_BE = 0x6e2 XK_Cyrillic_TSE = 0x6e3 XK_Cyrillic_DE = 0x6e4 XK_Cyrillic_IE = 0x6e5 XK_Cyrillic_EF = 0x6e6 XK_Cyrillic_GHE = 0x6e7 XK_Cyrillic_HA = 0x6e8 XK_Cyrillic_I = 0x6e9 XK_Cyrillic_SHORTI = 0x6ea XK_Cyrillic_KA = 0x6eb XK_Cyrillic_EL = 0x6ec XK_Cyrillic_EM = 0x6ed XK_Cyrillic_EN = 0x6ee XK_Cyrillic_O = 0x6ef XK_Cyrillic_PE = 0x6f0 XK_Cyrillic_YA = 0x6f1 XK_Cyrillic_ER = 0x6f2 XK_Cyrillic_ES = 0x6f3 XK_Cyrillic_TE = 0x6f4 XK_Cyrillic_U = 0x6f5 XK_Cyrillic_ZHE = 0x6f6 XK_Cyrillic_VE = 0x6f7 XK_Cyrillic_SOFTSIGN = 0x6f8 XK_Cyrillic_YERU = 0x6f9 XK_Cyrillic_ZE = 0x6fa XK_Cyrillic_SHA = 0x6fb XK_Cyrillic_E = 0x6fc XK_Cyrillic_SHCHA = 0x6fd XK_Cyrillic_CHE = 0x6fe XK_Cyrillic_HARDSIGN = 0x6ff Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/greek.py000066400000000000000000000034601316117564000235020ustar00rootroot00000000000000XK_Greek_ALPHAaccent = 0x7a1 XK_Greek_EPSILONaccent = 0x7a2 XK_Greek_ETAaccent = 0x7a3 XK_Greek_IOTAaccent = 0x7a4 XK_Greek_IOTAdiaeresis = 0x7a5 XK_Greek_OMICRONaccent = 0x7a7 XK_Greek_UPSILONaccent = 0x7a8 XK_Greek_UPSILONdieresis = 0x7a9 XK_Greek_OMEGAaccent = 0x7ab XK_Greek_accentdieresis = 0x7ae XK_Greek_horizbar = 0x7af XK_Greek_alphaaccent = 0x7b1 XK_Greek_epsilonaccent = 0x7b2 XK_Greek_etaaccent = 0x7b3 XK_Greek_iotaaccent = 0x7b4 XK_Greek_iotadieresis = 0x7b5 XK_Greek_iotaaccentdieresis = 0x7b6 XK_Greek_omicronaccent = 0x7b7 XK_Greek_upsilonaccent = 0x7b8 XK_Greek_upsilondieresis = 0x7b9 XK_Greek_upsilonaccentdieresis = 0x7ba XK_Greek_omegaaccent = 0x7bb XK_Greek_ALPHA = 0x7c1 XK_Greek_BETA = 0x7c2 XK_Greek_GAMMA = 0x7c3 XK_Greek_DELTA = 0x7c4 XK_Greek_EPSILON = 0x7c5 XK_Greek_ZETA = 0x7c6 XK_Greek_ETA = 0x7c7 XK_Greek_THETA = 0x7c8 XK_Greek_IOTA = 0x7c9 XK_Greek_KAPPA = 0x7ca XK_Greek_LAMDA = 0x7cb XK_Greek_LAMBDA = 0x7cb XK_Greek_MU = 0x7cc XK_Greek_NU = 0x7cd XK_Greek_XI = 0x7ce XK_Greek_OMICRON = 0x7cf XK_Greek_PI = 0x7d0 XK_Greek_RHO = 0x7d1 XK_Greek_SIGMA = 0x7d2 XK_Greek_TAU = 0x7d4 XK_Greek_UPSILON = 0x7d5 XK_Greek_PHI = 0x7d6 XK_Greek_CHI = 0x7d7 XK_Greek_PSI = 0x7d8 XK_Greek_OMEGA = 0x7d9 XK_Greek_alpha = 0x7e1 XK_Greek_beta = 0x7e2 XK_Greek_gamma = 0x7e3 XK_Greek_delta = 0x7e4 XK_Greek_epsilon = 0x7e5 XK_Greek_zeta = 0x7e6 XK_Greek_eta = 0x7e7 XK_Greek_theta = 0x7e8 XK_Greek_iota = 0x7e9 XK_Greek_kappa = 0x7ea XK_Greek_lamda = 0x7eb XK_Greek_lambda = 0x7eb XK_Greek_mu = 0x7ec XK_Greek_nu = 0x7ed XK_Greek_xi = 0x7ee XK_Greek_omicron = 0x7ef XK_Greek_pi = 0x7f0 XK_Greek_rho = 0x7f1 XK_Greek_sigma = 0x7f2 XK_Greek_finalsmallsigma = 0x7f3 XK_Greek_tau = 0x7f4 XK_Greek_upsilon = 0x7f5 XK_Greek_phi = 0x7f6 XK_Greek_chi = 0x7f7 XK_Greek_psi = 0x7f8 XK_Greek_omega = 0x7f9 XK_Greek_switch = 0xFF7E Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/hebrew.py000066400000000000000000000016751316117564000236670ustar00rootroot00000000000000XK_hebrew_doublelowline = 0xcdf XK_hebrew_aleph = 0xce0 XK_hebrew_bet = 0xce1 XK_hebrew_beth = 0xce1 XK_hebrew_gimel = 0xce2 XK_hebrew_gimmel = 0xce2 XK_hebrew_dalet = 0xce3 XK_hebrew_daleth = 0xce3 XK_hebrew_he = 0xce4 XK_hebrew_waw = 0xce5 XK_hebrew_zain = 0xce6 XK_hebrew_zayin = 0xce6 XK_hebrew_chet = 0xce7 XK_hebrew_het = 0xce7 XK_hebrew_tet = 0xce8 XK_hebrew_teth = 0xce8 XK_hebrew_yod = 0xce9 XK_hebrew_finalkaph = 0xcea XK_hebrew_kaph = 0xceb XK_hebrew_lamed = 0xcec XK_hebrew_finalmem = 0xced XK_hebrew_mem = 0xcee XK_hebrew_finalnun = 0xcef XK_hebrew_nun = 0xcf0 XK_hebrew_samech = 0xcf1 XK_hebrew_samekh = 0xcf1 XK_hebrew_ayin = 0xcf2 XK_hebrew_finalpe = 0xcf3 XK_hebrew_pe = 0xcf4 XK_hebrew_finalzade = 0xcf5 XK_hebrew_finalzadi = 0xcf5 XK_hebrew_zade = 0xcf6 XK_hebrew_zadi = 0xcf6 XK_hebrew_qoph = 0xcf7 XK_hebrew_kuf = 0xcf7 XK_hebrew_resh = 0xcf8 XK_hebrew_shin = 0xcf9 XK_hebrew_taw = 0xcfa XK_hebrew_taf = 0xcfa XK_Hebrew_switch = 0xFF7E Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/katakana.py000066400000000000000000000025651316117564000241650ustar00rootroot00000000000000XK_overline = 0x47e XK_kana_fullstop = 0x4a1 XK_kana_openingbracket = 0x4a2 XK_kana_closingbracket = 0x4a3 XK_kana_comma = 0x4a4 XK_kana_conjunctive = 0x4a5 XK_kana_middledot = 0x4a5 XK_kana_WO = 0x4a6 XK_kana_a = 0x4a7 XK_kana_i = 0x4a8 XK_kana_u = 0x4a9 XK_kana_e = 0x4aa XK_kana_o = 0x4ab XK_kana_ya = 0x4ac XK_kana_yu = 0x4ad XK_kana_yo = 0x4ae XK_kana_tsu = 0x4af XK_kana_tu = 0x4af XK_prolongedsound = 0x4b0 XK_kana_A = 0x4b1 XK_kana_I = 0x4b2 XK_kana_U = 0x4b3 XK_kana_E = 0x4b4 XK_kana_O = 0x4b5 XK_kana_KA = 0x4b6 XK_kana_KI = 0x4b7 XK_kana_KU = 0x4b8 XK_kana_KE = 0x4b9 XK_kana_KO = 0x4ba XK_kana_SA = 0x4bb XK_kana_SHI = 0x4bc XK_kana_SU = 0x4bd XK_kana_SE = 0x4be XK_kana_SO = 0x4bf XK_kana_TA = 0x4c0 XK_kana_CHI = 0x4c1 XK_kana_TI = 0x4c1 XK_kana_TSU = 0x4c2 XK_kana_TU = 0x4c2 XK_kana_TE = 0x4c3 XK_kana_TO = 0x4c4 XK_kana_NA = 0x4c5 XK_kana_NI = 0x4c6 XK_kana_NU = 0x4c7 XK_kana_NE = 0x4c8 XK_kana_NO = 0x4c9 XK_kana_HA = 0x4ca XK_kana_HI = 0x4cb XK_kana_FU = 0x4cc XK_kana_HU = 0x4cc XK_kana_HE = 0x4cd XK_kana_HO = 0x4ce XK_kana_MA = 0x4cf XK_kana_MI = 0x4d0 XK_kana_MU = 0x4d1 XK_kana_ME = 0x4d2 XK_kana_MO = 0x4d3 XK_kana_YA = 0x4d4 XK_kana_YU = 0x4d5 XK_kana_YO = 0x4d6 XK_kana_RA = 0x4d7 XK_kana_RI = 0x4d8 XK_kana_RU = 0x4d9 XK_kana_RE = 0x4da XK_kana_RO = 0x4db XK_kana_WA = 0x4dc XK_kana_N = 0x4dd XK_voicedsound = 0x4de XK_semivoicedsound = 0x4df XK_kana_switch = 0xFF7E Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/korean.py000066400000000000000000000054541316117564000236710ustar00rootroot00000000000000XK_Hangul = 0xff31 XK_Hangul_Start = 0xff32 XK_Hangul_End = 0xff33 XK_Hangul_Hanja = 0xff34 XK_Hangul_Jamo = 0xff35 XK_Hangul_Romaja = 0xff36 XK_Hangul_Codeinput = 0xff37 XK_Hangul_Jeonja = 0xff38 XK_Hangul_Banja = 0xff39 XK_Hangul_PreHanja = 0xff3a XK_Hangul_PostHanja = 0xff3b XK_Hangul_SingleCandidate = 0xff3c XK_Hangul_MultipleCandidate = 0xff3d XK_Hangul_PreviousCandidate = 0xff3e XK_Hangul_Special = 0xff3f XK_Hangul_switch = 0xFF7E XK_Hangul_Kiyeog = 0xea1 XK_Hangul_SsangKiyeog = 0xea2 XK_Hangul_KiyeogSios = 0xea3 XK_Hangul_Nieun = 0xea4 XK_Hangul_NieunJieuj = 0xea5 XK_Hangul_NieunHieuh = 0xea6 XK_Hangul_Dikeud = 0xea7 XK_Hangul_SsangDikeud = 0xea8 XK_Hangul_Rieul = 0xea9 XK_Hangul_RieulKiyeog = 0xeaa XK_Hangul_RieulMieum = 0xeab XK_Hangul_RieulPieub = 0xeac XK_Hangul_RieulSios = 0xead XK_Hangul_RieulTieut = 0xeae XK_Hangul_RieulPhieuf = 0xeaf XK_Hangul_RieulHieuh = 0xeb0 XK_Hangul_Mieum = 0xeb1 XK_Hangul_Pieub = 0xeb2 XK_Hangul_SsangPieub = 0xeb3 XK_Hangul_PieubSios = 0xeb4 XK_Hangul_Sios = 0xeb5 XK_Hangul_SsangSios = 0xeb6 XK_Hangul_Ieung = 0xeb7 XK_Hangul_Jieuj = 0xeb8 XK_Hangul_SsangJieuj = 0xeb9 XK_Hangul_Cieuc = 0xeba XK_Hangul_Khieuq = 0xebb XK_Hangul_Tieut = 0xebc XK_Hangul_Phieuf = 0xebd XK_Hangul_Hieuh = 0xebe XK_Hangul_A = 0xebf XK_Hangul_AE = 0xec0 XK_Hangul_YA = 0xec1 XK_Hangul_YAE = 0xec2 XK_Hangul_EO = 0xec3 XK_Hangul_E = 0xec4 XK_Hangul_YEO = 0xec5 XK_Hangul_YE = 0xec6 XK_Hangul_O = 0xec7 XK_Hangul_WA = 0xec8 XK_Hangul_WAE = 0xec9 XK_Hangul_OE = 0xeca XK_Hangul_YO = 0xecb XK_Hangul_U = 0xecc XK_Hangul_WEO = 0xecd XK_Hangul_WE = 0xece XK_Hangul_WI = 0xecf XK_Hangul_YU = 0xed0 XK_Hangul_EU = 0xed1 XK_Hangul_YI = 0xed2 XK_Hangul_I = 0xed3 XK_Hangul_J_Kiyeog = 0xed4 XK_Hangul_J_SsangKiyeog = 0xed5 XK_Hangul_J_KiyeogSios = 0xed6 XK_Hangul_J_Nieun = 0xed7 XK_Hangul_J_NieunJieuj = 0xed8 XK_Hangul_J_NieunHieuh = 0xed9 XK_Hangul_J_Dikeud = 0xeda XK_Hangul_J_Rieul = 0xedb XK_Hangul_J_RieulKiyeog = 0xedc XK_Hangul_J_RieulMieum = 0xedd XK_Hangul_J_RieulPieub = 0xede XK_Hangul_J_RieulSios = 0xedf XK_Hangul_J_RieulTieut = 0xee0 XK_Hangul_J_RieulPhieuf = 0xee1 XK_Hangul_J_RieulHieuh = 0xee2 XK_Hangul_J_Mieum = 0xee3 XK_Hangul_J_Pieub = 0xee4 XK_Hangul_J_PieubSios = 0xee5 XK_Hangul_J_Sios = 0xee6 XK_Hangul_J_SsangSios = 0xee7 XK_Hangul_J_Ieung = 0xee8 XK_Hangul_J_Jieuj = 0xee9 XK_Hangul_J_Cieuc = 0xeea XK_Hangul_J_Khieuq = 0xeeb XK_Hangul_J_Tieut = 0xeec XK_Hangul_J_Phieuf = 0xeed XK_Hangul_J_Hieuh = 0xeee XK_Hangul_RieulYeorinHieuh = 0xeef XK_Hangul_SunkyeongeumMieum = 0xef0 XK_Hangul_SunkyeongeumPieub = 0xef1 XK_Hangul_PanSios = 0xef2 XK_Hangul_KkogjiDalrinIeung = 0xef3 XK_Hangul_SunkyeongeumPhieuf = 0xef4 XK_Hangul_YeorinHieuh = 0xef5 XK_Hangul_AraeA = 0xef6 XK_Hangul_AraeAE = 0xef7 XK_Hangul_J_PanSios = 0xef8 XK_Hangul_J_KkogjiDalrinIeung = 0xef9 XK_Hangul_J_YeorinHieuh = 0xefa XK_Korean_Won = 0xeff Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/latin1.py000066400000000000000000000065421316117564000236010ustar00rootroot00000000000000XK_space = 0x020 XK_exclam = 0x021 XK_quotedbl = 0x022 XK_numbersign = 0x023 XK_dollar = 0x024 XK_percent = 0x025 XK_ampersand = 0x026 XK_apostrophe = 0x027 XK_quoteright = 0x027 XK_parenleft = 0x028 XK_parenright = 0x029 XK_asterisk = 0x02a XK_plus = 0x02b XK_comma = 0x02c XK_minus = 0x02d XK_period = 0x02e XK_slash = 0x02f XK_0 = 0x030 XK_1 = 0x031 XK_2 = 0x032 XK_3 = 0x033 XK_4 = 0x034 XK_5 = 0x035 XK_6 = 0x036 XK_7 = 0x037 XK_8 = 0x038 XK_9 = 0x039 XK_colon = 0x03a XK_semicolon = 0x03b XK_less = 0x03c XK_equal = 0x03d XK_greater = 0x03e XK_question = 0x03f XK_at = 0x040 XK_A = 0x041 XK_B = 0x042 XK_C = 0x043 XK_D = 0x044 XK_E = 0x045 XK_F = 0x046 XK_G = 0x047 XK_H = 0x048 XK_I = 0x049 XK_J = 0x04a XK_K = 0x04b XK_L = 0x04c XK_M = 0x04d XK_N = 0x04e XK_O = 0x04f XK_P = 0x050 XK_Q = 0x051 XK_R = 0x052 XK_S = 0x053 XK_T = 0x054 XK_U = 0x055 XK_V = 0x056 XK_W = 0x057 XK_X = 0x058 XK_Y = 0x059 XK_Z = 0x05a XK_bracketleft = 0x05b XK_backslash = 0x05c XK_bracketright = 0x05d XK_asciicircum = 0x05e XK_underscore = 0x05f XK_grave = 0x060 XK_quoteleft = 0x060 XK_a = 0x061 XK_b = 0x062 XK_c = 0x063 XK_d = 0x064 XK_e = 0x065 XK_f = 0x066 XK_g = 0x067 XK_h = 0x068 XK_i = 0x069 XK_j = 0x06a XK_k = 0x06b XK_l = 0x06c XK_m = 0x06d XK_n = 0x06e XK_o = 0x06f XK_p = 0x070 XK_q = 0x071 XK_r = 0x072 XK_s = 0x073 XK_t = 0x074 XK_u = 0x075 XK_v = 0x076 XK_w = 0x077 XK_x = 0x078 XK_y = 0x079 XK_z = 0x07a XK_braceleft = 0x07b XK_bar = 0x07c XK_braceright = 0x07d XK_asciitilde = 0x07e XK_nobreakspace = 0x0a0 XK_exclamdown = 0x0a1 XK_cent = 0x0a2 XK_sterling = 0x0a3 XK_currency = 0x0a4 XK_yen = 0x0a5 XK_brokenbar = 0x0a6 XK_section = 0x0a7 XK_diaeresis = 0x0a8 XK_copyright = 0x0a9 XK_ordfeminine = 0x0aa XK_guillemotleft = 0x0ab XK_notsign = 0x0ac XK_hyphen = 0x0ad XK_registered = 0x0ae XK_macron = 0x0af XK_degree = 0x0b0 XK_plusminus = 0x0b1 XK_twosuperior = 0x0b2 XK_threesuperior = 0x0b3 XK_acute = 0x0b4 XK_mu = 0x0b5 XK_paragraph = 0x0b6 XK_periodcentered = 0x0b7 XK_cedilla = 0x0b8 XK_onesuperior = 0x0b9 XK_masculine = 0x0ba XK_guillemotright = 0x0bb XK_onequarter = 0x0bc XK_onehalf = 0x0bd XK_threequarters = 0x0be XK_questiondown = 0x0bf XK_Agrave = 0x0c0 XK_Aacute = 0x0c1 XK_Acircumflex = 0x0c2 XK_Atilde = 0x0c3 XK_Adiaeresis = 0x0c4 XK_Aring = 0x0c5 XK_AE = 0x0c6 XK_Ccedilla = 0x0c7 XK_Egrave = 0x0c8 XK_Eacute = 0x0c9 XK_Ecircumflex = 0x0ca XK_Ediaeresis = 0x0cb XK_Igrave = 0x0cc XK_Iacute = 0x0cd XK_Icircumflex = 0x0ce XK_Idiaeresis = 0x0cf XK_ETH = 0x0d0 XK_Eth = 0x0d0 XK_Ntilde = 0x0d1 XK_Ograve = 0x0d2 XK_Oacute = 0x0d3 XK_Ocircumflex = 0x0d4 XK_Otilde = 0x0d5 XK_Odiaeresis = 0x0d6 XK_multiply = 0x0d7 XK_Ooblique = 0x0d8 XK_Ugrave = 0x0d9 XK_Uacute = 0x0da XK_Ucircumflex = 0x0db XK_Udiaeresis = 0x0dc XK_Yacute = 0x0dd XK_THORN = 0x0de XK_Thorn = 0x0de XK_ssharp = 0x0df XK_agrave = 0x0e0 XK_aacute = 0x0e1 XK_acircumflex = 0x0e2 XK_atilde = 0x0e3 XK_adiaeresis = 0x0e4 XK_aring = 0x0e5 XK_ae = 0x0e6 XK_ccedilla = 0x0e7 XK_egrave = 0x0e8 XK_eacute = 0x0e9 XK_ecircumflex = 0x0ea XK_ediaeresis = 0x0eb XK_igrave = 0x0ec XK_iacute = 0x0ed XK_icircumflex = 0x0ee XK_idiaeresis = 0x0ef XK_eth = 0x0f0 XK_ntilde = 0x0f1 XK_ograve = 0x0f2 XK_oacute = 0x0f3 XK_ocircumflex = 0x0f4 XK_otilde = 0x0f5 XK_odiaeresis = 0x0f6 XK_division = 0x0f7 XK_oslash = 0x0f8 XK_ugrave = 0x0f9 XK_uacute = 0x0fa XK_ucircumflex = 0x0fb XK_udiaeresis = 0x0fc XK_yacute = 0x0fd XK_thorn = 0x0fe XK_ydiaeresis = 0x0ff Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/latin2.py000066400000000000000000000020631316117564000235740ustar00rootroot00000000000000XK_Aogonek = 0x1a1 XK_breve = 0x1a2 XK_Lstroke = 0x1a3 XK_Lcaron = 0x1a5 XK_Sacute = 0x1a6 XK_Scaron = 0x1a9 XK_Scedilla = 0x1aa XK_Tcaron = 0x1ab XK_Zacute = 0x1ac XK_Zcaron = 0x1ae XK_Zabovedot = 0x1af XK_aogonek = 0x1b1 XK_ogonek = 0x1b2 XK_lstroke = 0x1b3 XK_lcaron = 0x1b5 XK_sacute = 0x1b6 XK_caron = 0x1b7 XK_scaron = 0x1b9 XK_scedilla = 0x1ba XK_tcaron = 0x1bb XK_zacute = 0x1bc XK_doubleacute = 0x1bd XK_zcaron = 0x1be XK_zabovedot = 0x1bf XK_Racute = 0x1c0 XK_Abreve = 0x1c3 XK_Lacute = 0x1c5 XK_Cacute = 0x1c6 XK_Ccaron = 0x1c8 XK_Eogonek = 0x1ca XK_Ecaron = 0x1cc XK_Dcaron = 0x1cf XK_Dstroke = 0x1d0 XK_Nacute = 0x1d1 XK_Ncaron = 0x1d2 XK_Odoubleacute = 0x1d5 XK_Rcaron = 0x1d8 XK_Uring = 0x1d9 XK_Udoubleacute = 0x1db XK_Tcedilla = 0x1de XK_racute = 0x1e0 XK_abreve = 0x1e3 XK_lacute = 0x1e5 XK_cacute = 0x1e6 XK_ccaron = 0x1e8 XK_eogonek = 0x1ea XK_ecaron = 0x1ec XK_dcaron = 0x1ef XK_dstroke = 0x1f0 XK_nacute = 0x1f1 XK_ncaron = 0x1f2 XK_odoubleacute = 0x1f5 XK_udoubleacute = 0x1fb XK_rcaron = 0x1f8 XK_uring = 0x1f9 XK_tcedilla = 0x1fe XK_abovedot = 0x1ff Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/latin3.py000066400000000000000000000007211316117564000235740ustar00rootroot00000000000000XK_Hstroke = 0x2a1 XK_Hcircumflex = 0x2a6 XK_Iabovedot = 0x2a9 XK_Gbreve = 0x2ab XK_Jcircumflex = 0x2ac XK_hstroke = 0x2b1 XK_hcircumflex = 0x2b6 XK_idotless = 0x2b9 XK_gbreve = 0x2bb XK_jcircumflex = 0x2bc XK_Cabovedot = 0x2c5 XK_Ccircumflex = 0x2c6 XK_Gabovedot = 0x2d5 XK_Gcircumflex = 0x2d8 XK_Ubreve = 0x2dd XK_Scircumflex = 0x2de XK_cabovedot = 0x2e5 XK_ccircumflex = 0x2e6 XK_gabovedot = 0x2f5 XK_gcircumflex = 0x2f8 XK_ubreve = 0x2fd XK_scircumflex = 0x2fe Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/latin4.py000066400000000000000000000012461316117564000236000ustar00rootroot00000000000000XK_kra = 0x3a2 XK_kappa = 0x3a2 XK_Rcedilla = 0x3a3 XK_Itilde = 0x3a5 XK_Lcedilla = 0x3a6 XK_Emacron = 0x3aa XK_Gcedilla = 0x3ab XK_Tslash = 0x3ac XK_rcedilla = 0x3b3 XK_itilde = 0x3b5 XK_lcedilla = 0x3b6 XK_emacron = 0x3ba XK_gcedilla = 0x3bb XK_tslash = 0x3bc XK_ENG = 0x3bd XK_eng = 0x3bf XK_Amacron = 0x3c0 XK_Iogonek = 0x3c7 XK_Eabovedot = 0x3cc XK_Imacron = 0x3cf XK_Ncedilla = 0x3d1 XK_Omacron = 0x3d2 XK_Kcedilla = 0x3d3 XK_Uogonek = 0x3d9 XK_Utilde = 0x3dd XK_Umacron = 0x3de XK_amacron = 0x3e0 XK_iogonek = 0x3e7 XK_eabovedot = 0x3ec XK_imacron = 0x3ef XK_ncedilla = 0x3f1 XK_omacron = 0x3f2 XK_kcedilla = 0x3f3 XK_uogonek = 0x3f9 XK_utilde = 0x3fd XK_umacron = 0x3fe Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/miscellany.py000066400000000000000000000060751316117564000245520ustar00rootroot00000000000000XK_BackSpace = 0xFF08 XK_Tab = 0xFF09 XK_Linefeed = 0xFF0A XK_Clear = 0xFF0B XK_Return = 0xFF0D XK_Pause = 0xFF13 XK_Scroll_Lock = 0xFF14 XK_Sys_Req = 0xFF15 XK_Escape = 0xFF1B XK_Delete = 0xFFFF XK_Multi_key = 0xFF20 XK_SingleCandidate = 0xFF3C XK_MultipleCandidate = 0xFF3D XK_PreviousCandidate = 0xFF3E XK_Kanji = 0xFF21 XK_Muhenkan = 0xFF22 XK_Henkan_Mode = 0xFF23 XK_Henkan = 0xFF23 XK_Romaji = 0xFF24 XK_Hiragana = 0xFF25 XK_Katakana = 0xFF26 XK_Hiragana_Katakana = 0xFF27 XK_Zenkaku = 0xFF28 XK_Hankaku = 0xFF29 XK_Zenkaku_Hankaku = 0xFF2A XK_Touroku = 0xFF2B XK_Massyo = 0xFF2C XK_Kana_Lock = 0xFF2D XK_Kana_Shift = 0xFF2E XK_Eisu_Shift = 0xFF2F XK_Eisu_toggle = 0xFF30 XK_Zen_Koho = 0xFF3D XK_Mae_Koho = 0xFF3E XK_Home = 0xFF50 XK_Left = 0xFF51 XK_Up = 0xFF52 XK_Right = 0xFF53 XK_Down = 0xFF54 XK_Prior = 0xFF55 XK_Page_Up = 0xFF55 XK_Next = 0xFF56 XK_Page_Down = 0xFF56 XK_End = 0xFF57 XK_Begin = 0xFF58 XK_Select = 0xFF60 XK_Print = 0xFF61 XK_Execute = 0xFF62 XK_Insert = 0xFF63 XK_Undo = 0xFF65 XK_Redo = 0xFF66 XK_Menu = 0xFF67 XK_Find = 0xFF68 XK_Cancel = 0xFF69 XK_Help = 0xFF6A XK_Break = 0xFF6B XK_Mode_switch = 0xFF7E XK_script_switch = 0xFF7E XK_Num_Lock = 0xFF7F XK_KP_Space = 0xFF80 XK_KP_Tab = 0xFF89 XK_KP_Enter = 0xFF8D XK_KP_F1 = 0xFF91 XK_KP_F2 = 0xFF92 XK_KP_F3 = 0xFF93 XK_KP_F4 = 0xFF94 XK_KP_Home = 0xFF95 XK_KP_Left = 0xFF96 XK_KP_Up = 0xFF97 XK_KP_Right = 0xFF98 XK_KP_Down = 0xFF99 XK_KP_Prior = 0xFF9A XK_KP_Page_Up = 0xFF9A XK_KP_Next = 0xFF9B XK_KP_Page_Down = 0xFF9B XK_KP_End = 0xFF9C XK_KP_Begin = 0xFF9D XK_KP_Insert = 0xFF9E XK_KP_Delete = 0xFF9F XK_KP_Equal = 0xFFBD XK_KP_Multiply = 0xFFAA XK_KP_Add = 0xFFAB XK_KP_Separator = 0xFFAC XK_KP_Subtract = 0xFFAD XK_KP_Decimal = 0xFFAE XK_KP_Divide = 0xFFAF XK_KP_0 = 0xFFB0 XK_KP_1 = 0xFFB1 XK_KP_2 = 0xFFB2 XK_KP_3 = 0xFFB3 XK_KP_4 = 0xFFB4 XK_KP_5 = 0xFFB5 XK_KP_6 = 0xFFB6 XK_KP_7 = 0xFFB7 XK_KP_8 = 0xFFB8 XK_KP_9 = 0xFFB9 XK_F1 = 0xFFBE XK_F2 = 0xFFBF XK_F3 = 0xFFC0 XK_F4 = 0xFFC1 XK_F5 = 0xFFC2 XK_F6 = 0xFFC3 XK_F7 = 0xFFC4 XK_F8 = 0xFFC5 XK_F9 = 0xFFC6 XK_F10 = 0xFFC7 XK_F11 = 0xFFC8 XK_L1 = 0xFFC8 XK_F12 = 0xFFC9 XK_L2 = 0xFFC9 XK_F13 = 0xFFCA XK_L3 = 0xFFCA XK_F14 = 0xFFCB XK_L4 = 0xFFCB XK_F15 = 0xFFCC XK_L5 = 0xFFCC XK_F16 = 0xFFCD XK_L6 = 0xFFCD XK_F17 = 0xFFCE XK_L7 = 0xFFCE XK_F18 = 0xFFCF XK_L8 = 0xFFCF XK_F19 = 0xFFD0 XK_L9 = 0xFFD0 XK_F20 = 0xFFD1 XK_L10 = 0xFFD1 XK_F21 = 0xFFD2 XK_R1 = 0xFFD2 XK_F22 = 0xFFD3 XK_R2 = 0xFFD3 XK_F23 = 0xFFD4 XK_R3 = 0xFFD4 XK_F24 = 0xFFD5 XK_R4 = 0xFFD5 XK_F25 = 0xFFD6 XK_R5 = 0xFFD6 XK_F26 = 0xFFD7 XK_R6 = 0xFFD7 XK_F27 = 0xFFD8 XK_R7 = 0xFFD8 XK_F28 = 0xFFD9 XK_R8 = 0xFFD9 XK_F29 = 0xFFDA XK_R9 = 0xFFDA XK_F30 = 0xFFDB XK_R10 = 0xFFDB XK_F31 = 0xFFDC XK_R11 = 0xFFDC XK_F32 = 0xFFDD XK_R12 = 0xFFDD XK_F33 = 0xFFDE XK_R13 = 0xFFDE XK_F34 = 0xFFDF XK_R14 = 0xFFDF XK_F35 = 0xFFE0 XK_R15 = 0xFFE0 XK_Shift_L = 0xFFE1 XK_Shift_R = 0xFFE2 XK_Control_L = 0xFFE3 XK_Control_R = 0xFFE4 XK_Caps_Lock = 0xFFE5 XK_Shift_Lock = 0xFFE6 XK_Meta_L = 0xFFE7 XK_Meta_R = 0xFFE8 XK_Alt_L = 0xFFE9 XK_Alt_R = 0xFFEA XK_Super_L = 0xFFEB XK_Super_R = 0xFFEC XK_Hyper_L = 0xFFED XK_Hyper_R = 0xFFEE Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/publishing.py000066400000000000000000000036711316117564000245550ustar00rootroot00000000000000XK_emspace = 0xaa1 XK_enspace = 0xaa2 XK_em3space = 0xaa3 XK_em4space = 0xaa4 XK_digitspace = 0xaa5 XK_punctspace = 0xaa6 XK_thinspace = 0xaa7 XK_hairspace = 0xaa8 XK_emdash = 0xaa9 XK_endash = 0xaaa XK_signifblank = 0xaac XK_ellipsis = 0xaae XK_doubbaselinedot = 0xaaf XK_onethird = 0xab0 XK_twothirds = 0xab1 XK_onefifth = 0xab2 XK_twofifths = 0xab3 XK_threefifths = 0xab4 XK_fourfifths = 0xab5 XK_onesixth = 0xab6 XK_fivesixths = 0xab7 XK_careof = 0xab8 XK_figdash = 0xabb XK_leftanglebracket = 0xabc XK_decimalpoint = 0xabd XK_rightanglebracket = 0xabe XK_marker = 0xabf XK_oneeighth = 0xac3 XK_threeeighths = 0xac4 XK_fiveeighths = 0xac5 XK_seveneighths = 0xac6 XK_trademark = 0xac9 XK_signaturemark = 0xaca XK_trademarkincircle = 0xacb XK_leftopentriangle = 0xacc XK_rightopentriangle = 0xacd XK_emopencircle = 0xace XK_emopenrectangle = 0xacf XK_leftsinglequotemark = 0xad0 XK_rightsinglequotemark = 0xad1 XK_leftdoublequotemark = 0xad2 XK_rightdoublequotemark = 0xad3 XK_prescription = 0xad4 XK_minutes = 0xad6 XK_seconds = 0xad7 XK_latincross = 0xad9 XK_hexagram = 0xada XK_filledrectbullet = 0xadb XK_filledlefttribullet = 0xadc XK_filledrighttribullet = 0xadd XK_emfilledcircle = 0xade XK_emfilledrect = 0xadf XK_enopencircbullet = 0xae0 XK_enopensquarebullet = 0xae1 XK_openrectbullet = 0xae2 XK_opentribulletup = 0xae3 XK_opentribulletdown = 0xae4 XK_openstar = 0xae5 XK_enfilledcircbullet = 0xae6 XK_enfilledsqbullet = 0xae7 XK_filledtribulletup = 0xae8 XK_filledtribulletdown = 0xae9 XK_leftpointer = 0xaea XK_rightpointer = 0xaeb XK_club = 0xaec XK_diamond = 0xaed XK_heart = 0xaee XK_maltesecross = 0xaf0 XK_dagger = 0xaf1 XK_doubledagger = 0xaf2 XK_checkmark = 0xaf3 XK_ballotcross = 0xaf4 XK_musicalsharp = 0xaf5 XK_musicalflat = 0xaf6 XK_malesymbol = 0xaf7 XK_femalesymbol = 0xaf8 XK_telephone = 0xaf9 XK_telephonerecorder = 0xafa XK_phonographcopyright = 0xafb XK_caret = 0xafc XK_singlelowquotemark = 0xafd XK_doublelowquotemark = 0xafe XK_cursor = 0xaff Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/special.py000066400000000000000000000007521316117564000240260ustar00rootroot00000000000000XK_blank = 0x9df XK_soliddiamond = 0x9e0 XK_checkerboard = 0x9e1 XK_ht = 0x9e2 XK_ff = 0x9e3 XK_cr = 0x9e4 XK_lf = 0x9e5 XK_nl = 0x9e8 XK_vt = 0x9e9 XK_lowrightcorner = 0x9ea XK_uprightcorner = 0x9eb XK_upleftcorner = 0x9ec XK_lowleftcorner = 0x9ed XK_crossinglines = 0x9ee XK_horizlinescan1 = 0x9ef XK_horizlinescan3 = 0x9f0 XK_horizlinescan5 = 0x9f1 XK_horizlinescan7 = 0x9f2 XK_horizlinescan9 = 0x9f3 XK_leftt = 0x9f4 XK_rightt = 0x9f5 XK_bott = 0x9f6 XK_topt = 0x9f7 XK_vertbar = 0x9f8 Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/technical.py000066400000000000000000000022631316117564000243370ustar00rootroot00000000000000XK_leftradical = 0x8a1 XK_topleftradical = 0x8a2 XK_horizconnector = 0x8a3 XK_topintegral = 0x8a4 XK_botintegral = 0x8a5 XK_vertconnector = 0x8a6 XK_topleftsqbracket = 0x8a7 XK_botleftsqbracket = 0x8a8 XK_toprightsqbracket = 0x8a9 XK_botrightsqbracket = 0x8aa XK_topleftparens = 0x8ab XK_botleftparens = 0x8ac XK_toprightparens = 0x8ad XK_botrightparens = 0x8ae XK_leftmiddlecurlybrace = 0x8af XK_rightmiddlecurlybrace = 0x8b0 XK_topleftsummation = 0x8b1 XK_botleftsummation = 0x8b2 XK_topvertsummationconnector = 0x8b3 XK_botvertsummationconnector = 0x8b4 XK_toprightsummation = 0x8b5 XK_botrightsummation = 0x8b6 XK_rightmiddlesummation = 0x8b7 XK_lessthanequal = 0x8bc XK_notequal = 0x8bd XK_greaterthanequal = 0x8be XK_integral = 0x8bf XK_therefore = 0x8c0 XK_variation = 0x8c1 XK_infinity = 0x8c2 XK_nabla = 0x8c5 XK_approximate = 0x8c8 XK_similarequal = 0x8c9 XK_ifonlyif = 0x8cd XK_implies = 0x8ce XK_identical = 0x8cf XK_radical = 0x8d6 XK_includedin = 0x8da XK_includes = 0x8db XK_intersection = 0x8dc XK_union = 0x8dd XK_logicaland = 0x8de XK_logicalor = 0x8df XK_partialderivative = 0x8ef XK_function = 0x8f6 XK_leftarrow = 0x8fb XK_uparrow = 0x8fc XK_rightarrow = 0x8fd XK_downarrow = 0x8fe Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/thai.py000066400000000000000000000037241316117564000233350ustar00rootroot00000000000000XK_Thai_kokai = 0xda1 XK_Thai_khokhai = 0xda2 XK_Thai_khokhuat = 0xda3 XK_Thai_khokhwai = 0xda4 XK_Thai_khokhon = 0xda5 XK_Thai_khorakhang = 0xda6 XK_Thai_ngongu = 0xda7 XK_Thai_chochan = 0xda8 XK_Thai_choching = 0xda9 XK_Thai_chochang = 0xdaa XK_Thai_soso = 0xdab XK_Thai_chochoe = 0xdac XK_Thai_yoying = 0xdad XK_Thai_dochada = 0xdae XK_Thai_topatak = 0xdaf XK_Thai_thothan = 0xdb0 XK_Thai_thonangmontho = 0xdb1 XK_Thai_thophuthao = 0xdb2 XK_Thai_nonen = 0xdb3 XK_Thai_dodek = 0xdb4 XK_Thai_totao = 0xdb5 XK_Thai_thothung = 0xdb6 XK_Thai_thothahan = 0xdb7 XK_Thai_thothong = 0xdb8 XK_Thai_nonu = 0xdb9 XK_Thai_bobaimai = 0xdba XK_Thai_popla = 0xdbb XK_Thai_phophung = 0xdbc XK_Thai_fofa = 0xdbd XK_Thai_phophan = 0xdbe XK_Thai_fofan = 0xdbf XK_Thai_phosamphao = 0xdc0 XK_Thai_moma = 0xdc1 XK_Thai_yoyak = 0xdc2 XK_Thai_rorua = 0xdc3 XK_Thai_ru = 0xdc4 XK_Thai_loling = 0xdc5 XK_Thai_lu = 0xdc6 XK_Thai_wowaen = 0xdc7 XK_Thai_sosala = 0xdc8 XK_Thai_sorusi = 0xdc9 XK_Thai_sosua = 0xdca XK_Thai_hohip = 0xdcb XK_Thai_lochula = 0xdcc XK_Thai_oang = 0xdcd XK_Thai_honokhuk = 0xdce XK_Thai_paiyannoi = 0xdcf XK_Thai_saraa = 0xdd0 XK_Thai_maihanakat = 0xdd1 XK_Thai_saraaa = 0xdd2 XK_Thai_saraam = 0xdd3 XK_Thai_sarai = 0xdd4 XK_Thai_saraii = 0xdd5 XK_Thai_saraue = 0xdd6 XK_Thai_sarauee = 0xdd7 XK_Thai_sarau = 0xdd8 XK_Thai_sarauu = 0xdd9 XK_Thai_phinthu = 0xdda XK_Thai_maihanakat_maitho = 0xdde XK_Thai_baht = 0xddf XK_Thai_sarae = 0xde0 XK_Thai_saraae = 0xde1 XK_Thai_sarao = 0xde2 XK_Thai_saraaimaimuan = 0xde3 XK_Thai_saraaimaimalai = 0xde4 XK_Thai_lakkhangyao = 0xde5 XK_Thai_maiyamok = 0xde6 XK_Thai_maitaikhu = 0xde7 XK_Thai_maiek = 0xde8 XK_Thai_maitho = 0xde9 XK_Thai_maitri = 0xdea XK_Thai_maichattawa = 0xdeb XK_Thai_thanthakhat = 0xdec XK_Thai_nikhahit = 0xded XK_Thai_leksun = 0xdf0 XK_Thai_leknung = 0xdf1 XK_Thai_leksong = 0xdf2 XK_Thai_leksam = 0xdf3 XK_Thai_leksi = 0xdf4 XK_Thai_lekha = 0xdf5 XK_Thai_lekhok = 0xdf6 XK_Thai_lekchet = 0xdf7 XK_Thai_lekpaet = 0xdf8 XK_Thai_lekkao = 0xdf9 Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/xf86.py000066400000000000000000000135071316117564000232030ustar00rootroot00000000000000XK_XF86_MonBrightnessUp = 0x1008FF02 XK_XF86_MonBrightnessDown = 0x1008FF03 XK_XF86_KbdLightOnOff = 0x1008FF04 XK_XF86_KbdBrightnessUp = 0x1008FF05 XK_XF86_KbdBrightnessDown = 0x1008FF06 XK_XF86_Standby = 0x1008FF10 XK_XF86_AudioLowerVolume = 0x1008FF11 XK_XF86_AudioMute = 0x1008FF12 XK_XF86_AudioRaiseVolume = 0x1008FF13 XK_XF86_AudioPlay = 0x1008FF14 XK_XF86_AudioStop = 0x1008FF15 XK_XF86_AudioPrev = 0x1008FF16 XK_XF86_AudioNext = 0x1008FF17 XK_XF86_HomePage = 0x1008FF18 XK_XF86_Mail = 0x1008FF19 XK_XF86_Start = 0x1008FF1A XK_XF86_Search = 0x1008FF1B XK_XF86_AudioRecord = 0x1008FF1C XK_XF86_Calculator = 0x1008FF1D XK_XF86_Memo = 0x1008FF1E XK_XF86_ToDoList = 0x1008FF1F XK_XF86_Calendar = 0x1008FF20 XK_XF86_PowerDown = 0x1008FF21 XK_XF86_ContrastAdjust = 0x1008FF22 XK_XF86_RockerUp = 0x1008FF23 XK_XF86_RockerDown = 0x1008FF24 XK_XF86_RockerEnter = 0x1008FF25 XK_XF86_Back = 0x1008FF26 XK_XF86_Forward = 0x1008FF27 XK_XF86_Stop = 0x1008FF28 XK_XF86_Refresh = 0x1008FF29 XK_XF86_PowerOff = 0x1008FF2A XK_XF86_WakeUp = 0x1008FF2B XK_XF86_Eject = 0x1008FF2C XK_XF86_ScreenSaver = 0x1008FF2D XK_XF86_WWW = 0x1008FF2E XK_XF86_Sleep = 0x1008FF2F XK_XF86_Favorites = 0x1008FF30 XK_XF86_AudioPause = 0x1008FF31 XK_XF86_AudioMedia = 0x1008FF32 XK_XF86_MyComputer = 0x1008FF33 XK_XF86_VendorHome = 0x1008FF34 XK_XF86_LightBulb = 0x1008FF35 XK_XF86_Shop = 0x1008FF36 XK_XF86_History = 0x1008FF37 XK_XF86_OpenURL = 0x1008FF38 XK_XF86_AddFavorite = 0x1008FF39 XK_XF86_HotLinks = 0x1008FF3A XK_XF86_BrightnessAdjust = 0x1008FF3B XK_XF86_Finance = 0x1008FF3C XK_XF86_Community = 0x1008FF3D XK_XF86_AudioRewind = 0x1008FF3E XK_XF86_XF86BackForward = 0x1008FF3F XK_XF86_Launch0 = 0x1008FF40 XK_XF86_Launch1 = 0x1008FF41 XK_XF86_Launch2 = 0x1008FF42 XK_XF86_Launch3 = 0x1008FF43 XK_XF86_Launch4 = 0x1008FF44 XK_XF86_Launch5 = 0x1008FF45 XK_XF86_Launch6 = 0x1008FF46 XK_XF86_Launch7 = 0x1008FF47 XK_XF86_Launch8 = 0x1008FF48 XK_XF86_Launch9 = 0x1008FF49 XK_XF86_LaunchA = 0x1008FF4A XK_XF86_LaunchB = 0x1008FF4B XK_XF86_LaunchC = 0x1008FF4C XK_XF86_LaunchD = 0x1008FF4D XK_XF86_LaunchE = 0x1008FF4E XK_XF86_LaunchF = 0x1008FF4F XK_XF86_ApplicationLeft = 0x1008FF50 XK_XF86_ApplicationRight = 0x1008FF51 XK_XF86_Book = 0x1008FF52 XK_XF86_CD = 0x1008FF53 XK_XF86_Calculater = 0x1008FF54 XK_XF86_Clear = 0x1008FF55 XK_XF86_Close = 0x1008FF56 XK_XF86_Copy = 0x1008FF57 XK_XF86_Cut = 0x1008FF58 XK_XF86_Display = 0x1008FF59 XK_XF86_DOS = 0x1008FF5A XK_XF86_Documents = 0x1008FF5B XK_XF86_Excel = 0x1008FF5C XK_XF86_Explorer = 0x1008FF5D XK_XF86_Game = 0x1008FF5E XK_XF86_Go = 0x1008FF5F XK_XF86_iTouch = 0x1008FF60 XK_XF86_LogOff = 0x1008FF61 XK_XF86_Market = 0x1008FF62 XK_XF86_Meeting = 0x1008FF63 XK_XF86_MenuKB = 0x1008FF65 XK_XF86_MenuPB = 0x1008FF66 XK_XF86_MySites = 0x1008FF67 XK_XF86_New = 0x1008FF68 XK_XF86_News = 0x1008FF69 XK_XF86_OfficeHome = 0x1008FF6A XK_XF86_Open = 0x1008FF6B XK_XF86_Option = 0x1008FF6C XK_XF86_Paste = 0x1008FF6D XK_XF86_Phone = 0x1008FF6E XK_XF86_Q = 0x1008FF70 XK_XF86_Reply = 0x1008FF72 XK_XF86_Reload = 0x1008FF73 XK_XF86_RotateWindows = 0x1008FF74 XK_XF86_RotationPB = 0x1008FF75 XK_XF86_RotationKB = 0x1008FF76 XK_XF86_Save = 0x1008FF77 XK_XF86_ScrollUp = 0x1008FF78 XK_XF86_ScrollDown = 0x1008FF79 XK_XF86_ScrollClick = 0x1008FF7A XK_XF86_Send = 0x1008FF7B XK_XF86_Spell = 0x1008FF7C XK_XF86_SplitScreen = 0x1008FF7D XK_XF86_Support = 0x1008FF7E XK_XF86_TaskPane = 0x1008FF7F XK_XF86_Terminal = 0x1008FF80 XK_XF86_Tools = 0x1008FF81 XK_XF86_Travel = 0x1008FF82 XK_XF86_UserPB = 0x1008FF84 XK_XF86_User1KB = 0x1008FF85 XK_XF86_User2KB = 0x1008FF86 XK_XF86_Video = 0x1008FF87 XK_XF86_WheelButton = 0x1008FF88 XK_XF86_Word = 0x1008FF89 XK_XF86_Xfer = 0x1008FF8A XK_XF86_ZoomIn = 0x1008FF8B XK_XF86_ZoomOut = 0x1008FF8C XK_XF86_Away = 0x1008FF8D XK_XF86_Messenger = 0x1008FF8E XK_XF86_WebCam = 0x1008FF8F XK_XF86_MailForward = 0x1008FF90 XK_XF86_Pictures = 0x1008FF91 XK_XF86_Music = 0x1008FF92 XK_XF86_Battery = 0x1008FF93 XK_XF86_Bluetooth = 0x1008FF94 XK_XF86_WLAN = 0x1008FF95 XK_XF86_UWB = 0x1008FF96 XK_XF86_AudioForward = 0x1008FF97 XK_XF86_AudioRepeat = 0x1008FF98 XK_XF86_AudioRandomPlay = 0x1008FF99 XK_XF86_Subtitle = 0x1008FF9A XK_XF86_AudioCycleTrack = 0x1008FF9B XK_XF86_CycleAngle = 0x1008FF9C XK_XF86_FrameBack = 0x1008FF9D XK_XF86_FrameForward = 0x1008FF9E XK_XF86_Time = 0x1008FF9F XK_XF86_Select = 0x1008FFA0 XK_XF86_View = 0x1008FFA1 XK_XF86_TopMenu = 0x1008FFA2 XK_XF86_Red = 0x1008FFA3 XK_XF86_Green = 0x1008FFA4 XK_XF86_Yellow = 0x1008FFA5 XK_XF86_Blue = 0x1008FFA6 XK_XF86_Switch_VT_1 = 0x1008FE01 XK_XF86_Switch_VT_2 = 0x1008FE02 XK_XF86_Switch_VT_3 = 0x1008FE03 XK_XF86_Switch_VT_4 = 0x1008FE04 XK_XF86_Switch_VT_5 = 0x1008FE05 XK_XF86_Switch_VT_6 = 0x1008FE06 XK_XF86_Switch_VT_7 = 0x1008FE07 XK_XF86_Switch_VT_8 = 0x1008FE08 XK_XF86_Switch_VT_9 = 0x1008FE09 XK_XF86_Switch_VT_10 = 0x1008FE0A XK_XF86_Switch_VT_11 = 0x1008FE0B XK_XF86_Switch_VT_12 = 0x1008FE0C XK_XF86_Ungrab = 0x1008FE20 XK_XF86_ClearGrab = 0x1008FE21 XK_XF86_Next_VMode = 0x1008FE22 XK_XF86_Prev_VMode = 0x1008FE23 Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/xk3270.py000066400000000000000000000013421316117564000233400ustar00rootroot00000000000000XK_3270_Duplicate = 0xFD01 XK_3270_FieldMark = 0xFD02 XK_3270_Right2 = 0xFD03 XK_3270_Left2 = 0xFD04 XK_3270_BackTab = 0xFD05 XK_3270_EraseEOF = 0xFD06 XK_3270_EraseInput = 0xFD07 XK_3270_Reset = 0xFD08 XK_3270_Quit = 0xFD09 XK_3270_PA1 = 0xFD0A XK_3270_PA2 = 0xFD0B XK_3270_PA3 = 0xFD0C XK_3270_Test = 0xFD0D XK_3270_Attn = 0xFD0E XK_3270_CursorBlink = 0xFD0F XK_3270_AltCursor = 0xFD10 XK_3270_KeyClick = 0xFD11 XK_3270_Jump = 0xFD12 XK_3270_Ident = 0xFD13 XK_3270_Rule = 0xFD14 XK_3270_Copy = 0xFD15 XK_3270_Play = 0xFD16 XK_3270_Setup = 0xFD17 XK_3270_Record = 0xFD18 XK_3270_ChangeScreen = 0xFD19 XK_3270_DeleteWord = 0xFD1A XK_3270_ExSelect = 0xFD1B XK_3270_CursorSelect = 0xFD1C XK_3270_PrintScreen = 0xFD1D XK_3270_Enter = 0xFD1E Nagstamon/Nagstamon/thirdparty/Xlib/keysymdef/xkb.py000066400000000000000000000055521316117564000231750ustar00rootroot00000000000000XK_ISO_Lock = 0xFE01 XK_ISO_Level2_Latch = 0xFE02 XK_ISO_Level3_Shift = 0xFE03 XK_ISO_Level3_Latch = 0xFE04 XK_ISO_Level3_Lock = 0xFE05 XK_ISO_Group_Shift = 0xFF7E XK_ISO_Group_Latch = 0xFE06 XK_ISO_Group_Lock = 0xFE07 XK_ISO_Next_Group = 0xFE08 XK_ISO_Next_Group_Lock = 0xFE09 XK_ISO_Prev_Group = 0xFE0A XK_ISO_Prev_Group_Lock = 0xFE0B XK_ISO_First_Group = 0xFE0C XK_ISO_First_Group_Lock = 0xFE0D XK_ISO_Last_Group = 0xFE0E XK_ISO_Last_Group_Lock = 0xFE0F XK_ISO_Left_Tab = 0xFE20 XK_ISO_Move_Line_Up = 0xFE21 XK_ISO_Move_Line_Down = 0xFE22 XK_ISO_Partial_Line_Up = 0xFE23 XK_ISO_Partial_Line_Down = 0xFE24 XK_ISO_Partial_Space_Left = 0xFE25 XK_ISO_Partial_Space_Right = 0xFE26 XK_ISO_Set_Margin_Left = 0xFE27 XK_ISO_Set_Margin_Right = 0xFE28 XK_ISO_Release_Margin_Left = 0xFE29 XK_ISO_Release_Margin_Right = 0xFE2A XK_ISO_Release_Both_Margins = 0xFE2B XK_ISO_Fast_Cursor_Left = 0xFE2C XK_ISO_Fast_Cursor_Right = 0xFE2D XK_ISO_Fast_Cursor_Up = 0xFE2E XK_ISO_Fast_Cursor_Down = 0xFE2F XK_ISO_Continuous_Underline = 0xFE30 XK_ISO_Discontinuous_Underline = 0xFE31 XK_ISO_Emphasize = 0xFE32 XK_ISO_Center_Object = 0xFE33 XK_ISO_Enter = 0xFE34 XK_dead_grave = 0xFE50 XK_dead_acute = 0xFE51 XK_dead_circumflex = 0xFE52 XK_dead_tilde = 0xFE53 XK_dead_macron = 0xFE54 XK_dead_breve = 0xFE55 XK_dead_abovedot = 0xFE56 XK_dead_diaeresis = 0xFE57 XK_dead_abovering = 0xFE58 XK_dead_doubleacute = 0xFE59 XK_dead_caron = 0xFE5A XK_dead_cedilla = 0xFE5B XK_dead_ogonek = 0xFE5C XK_dead_iota = 0xFE5D XK_dead_voiced_sound = 0xFE5E XK_dead_semivoiced_sound = 0xFE5F XK_dead_belowdot = 0xFE60 XK_First_Virtual_Screen = 0xFED0 XK_Prev_Virtual_Screen = 0xFED1 XK_Next_Virtual_Screen = 0xFED2 XK_Last_Virtual_Screen = 0xFED4 XK_Terminate_Server = 0xFED5 XK_AccessX_Enable = 0xFE70 XK_AccessX_Feedback_Enable = 0xFE71 XK_RepeatKeys_Enable = 0xFE72 XK_SlowKeys_Enable = 0xFE73 XK_BounceKeys_Enable = 0xFE74 XK_StickyKeys_Enable = 0xFE75 XK_MouseKeys_Enable = 0xFE76 XK_MouseKeys_Accel_Enable = 0xFE77 XK_Overlay1_Enable = 0xFE78 XK_Overlay2_Enable = 0xFE79 XK_AudibleBell_Enable = 0xFE7A XK_Pointer_Left = 0xFEE0 XK_Pointer_Right = 0xFEE1 XK_Pointer_Up = 0xFEE2 XK_Pointer_Down = 0xFEE3 XK_Pointer_UpLeft = 0xFEE4 XK_Pointer_UpRight = 0xFEE5 XK_Pointer_DownLeft = 0xFEE6 XK_Pointer_DownRight = 0xFEE7 XK_Pointer_Button_Dflt = 0xFEE8 XK_Pointer_Button1 = 0xFEE9 XK_Pointer_Button2 = 0xFEEA XK_Pointer_Button3 = 0xFEEB XK_Pointer_Button4 = 0xFEEC XK_Pointer_Button5 = 0xFEED XK_Pointer_DblClick_Dflt = 0xFEEE XK_Pointer_DblClick1 = 0xFEEF XK_Pointer_DblClick2 = 0xFEF0 XK_Pointer_DblClick3 = 0xFEF1 XK_Pointer_DblClick4 = 0xFEF2 XK_Pointer_DblClick5 = 0xFEF3 XK_Pointer_Drag_Dflt = 0xFEF4 XK_Pointer_Drag1 = 0xFEF5 XK_Pointer_Drag2 = 0xFEF6 XK_Pointer_Drag3 = 0xFEF7 XK_Pointer_Drag4 = 0xFEF8 XK_Pointer_Drag5 = 0xFEFD XK_Pointer_EnableKeys = 0xFEF9 XK_Pointer_Accelerate = 0xFEFA XK_Pointer_DfltBtnNext = 0xFEFB XK_Pointer_DfltBtnPrev = 0xFEFC Nagstamon/Nagstamon/thirdparty/Xlib/protocol/000077500000000000000000000000001316117564000216715ustar00rootroot00000000000000Nagstamon/Nagstamon/thirdparty/Xlib/protocol/ChangeLog000066400000000000000000000103771316117564000234530ustar00rootroot000000000000002006-07-22 Mike Grant * Xlib/protocol/display.py: (mggrant) Fix for 1219457 - flushing was blocking and waiting for a read operation. Added missing "import socket" per bug report #681511. Fix for bug:1098695 & 1098738. The "recv" variable was being used for more than one thing - renamed one. Changelog hasn't been maintained since 2002, but some of the more significant comments from cvs logs follow: * Xlib/protocol/request.py: (petli) Fix bugs in definition and method of GrabButton/Pointer 2002-02-22 Peter Liljenberg * event.py(CirculateNotify, CirculateRequest): These are identical, so subclass the common Circulate. 2002-02-13 Peter Liljenberg * rq.py (ValueList.parse_binary_value): Use = both for calcsize and unpacking. Caused problems on Alpha. 2002-02-11 Peter Liljenberg * request.py (GetWindowAttributes): Rename class to win_class. (AllocColorPlanes): Fix Pad(4) to Pad(8) in reply. * rq.py (ReplyLength): Add a reply length field, for completeness and easier unit test generation. 2002-02-10 Peter Liljenberg * rq.py (DictWrapper.__cmp__): Let DictWrapper compare with plain dictionaries. (Event.__init__): Set send_event to 0 when creating new events objects, and allow events to be compared. (Struct.parse_binary): Allow LengthFields to have a parse_value method. (OddLength.parse_value): Decode field. (String16.parse_binary_value): Handle OddLength fields. (TextElements8.parse_binary_value): Bugfix: return values instead of v. (String8.parse_binary_value): Parse String8 with no LengthOf field. 2002-02-09 Peter Liljenberg * rq.py (TextElements16): Bugfix: inherit TextElements8 instead of TextElements16. Found while preparing unit tests, whee. 2002-01-14 Peter Liljenberg * display.py (Display.parse_event_response): Fix bug reported by Ilpo Nyyssnen, whereby ReplyRequests which generates events (e.g. get_property with delete = 1) will get dropped when the event is received. 2001-12-14 Peter Liljenberg * display.py (Display.parse_event_response): * rq.py (Event.__init__): Fixed bug in event type decoding: bit 0-6 is the event type, and bit 7 is set if the event was sent by SendEvent. 2001-01-16 * event.py: Changed some class names so that they correspond exactly to the event type constants. Tue Jan 9 10:03:25 2001 Peter Liljenberg * display.py (Display.send_request): Fixed a call to append() with multiple arguments, something that modern Pythons don't allow. 2001-01-04 * rq.py: The fix for 64-bit platforms didn't work, and close scrutiny of structmodule.c shows why: it turns out that '=' translates into '<' or '>', the one the platform would use. This means B is one byte, H is two and L is four, and no extra alignment, always. '@', which is the default, selects native number of bytes, which on Alpha means that 'L' is eight bytes. Now the code goes to pains to ensure that '=' encoding is always used, so _now_ it should work on all platforms. Ahem. 2000-12-29 * rq.py: Optimizations: + replace calls to Field.get_name() with access to attribute name. (Struct.build_from_args): Fri Dec 29 17:05:02 2000 Peter Liljenberg * rq.py: Alpha forces us to probe how many bytes each struct code in 'bhil' represents, instead of being able to assume that b is 1, h is 2 and l is 4. 2000-12-21 * request.py (SetClipRectangles): Fixed typo (attribute was "rectangels"). 2000-12-20 * rq.py (DictWrapper.__setitem__), (DictWrapper.__delitem__), (DictWrapper.__setattr__), (DictWrapper.__delattr__): Add a few methods to the DictWrapper, to make sure that even if attributes are changed, all attributes can be found in the _data mapping. (ValueField.__init__): (Object.__init__): (ValueField.pack_value): (Set.__init__): Added a default parameter, so that structure elements with a default value can be omitted when calling build_from_args. Nagstamon/Nagstamon/thirdparty/Xlib/protocol/__init__.py000066400000000000000000000016701316117564000240060ustar00rootroot00000000000000# Xlib.protocol.__init__ -- glue for Xlib.protocol package # # Copyright (C) 2000 Peter Liljenberg # # 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 __all__ = [ 'display', 'event', 'request', 'rq', 'structs', ] Nagstamon/Nagstamon/thirdparty/Xlib/protocol/display.py000066400000000000000000001011051316117564000237060ustar00rootroot00000000000000# -*- coding: latin-1 -*- # # Xlib.protocol.display -- core display communication # # Copyright (C) 2000-2002 Peter Liljenberg # # 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 # Standard modules import errno import select import socket import struct import sys # Xlib modules from Xlib import error from Xlib.support import lock, connect # Xlib.protocol modules from . import rq, event # in Python 3, bytes are an actual array; in python 2, bytes are still # string-like, so in order to get an array element we need to call ord() if sys.version[0] >= '3': def _bytes_item(x): return x else: def _bytes_item(x): return ord(x) class Display: resource_classes = {} extension_major_opcodes = {} error_classes = error.xerror_class.copy() event_classes = event.event_class.copy() def __init__(self, display = None): name, host, displayno, screenno = connect.get_display(display) self.display_name = name self.default_screen = screenno self.socket = connect.get_socket(name, host, displayno) auth_name, auth_data = connect.get_auth(self.socket, name, host, displayno) # Internal structures for communication, grouped # by their function and locks # Socket error indicator, set when the socket is closed # in one way or another self.socket_error_lock = lock.allocate_lock() self.socket_error = None # Event queue self.event_queue_read_lock = lock.allocate_lock() self.event_queue_write_lock = lock.allocate_lock() self.event_queue = [] # Unsent request queue and sequence number counter self.request_queue_lock = lock.allocate_lock() self.request_serial = 1 self.request_queue = [] # Send-and-recieve loop, see function send_and_recive # for a detailed explanation self.send_recv_lock = lock.allocate_lock() self.send_active = 0 self.recv_active = 0 self.event_waiting = 0 self.event_wait_lock = lock.allocate_lock() self.request_waiting = 0 self.request_wait_lock = lock.allocate_lock() # Data used by the send-and-recieve loop self.sent_requests = [] self.request_length = 0 self.data_send = b'' self.data_recv = b'' self.data_sent_bytes = 0 # Resource ID structures self.resource_id_lock = lock.allocate_lock() self.resource_ids = {} self.last_resource_id = 0 # Use an default error handler, one which just prints the error self.error_handler = None # Right, now we're all set up for the connection setup # request with the server. # Figure out which endianess the hardware uses self.big_endian = struct.unpack('BB', struct.pack('H', 0x0100))[0] if self.big_endian: order = 0x42 else: order = 0x6c # Send connection setup r = ConnectionSetupRequest(self, byte_order = order, protocol_major = 11, protocol_minor = 0, auth_prot_name = auth_name, auth_prot_data = auth_data) # Did connection fail? if r.status != 1: raise error.DisplayConnectionError(self.display_name, r.reason) # Set up remaining info self.info = r self.default_screen = min(self.default_screen, len(self.info.roots) - 1) # # Public interface # def get_display_name(self): return self.display_name def get_default_screen(self): return self.default_screen def fileno(self): self.check_for_error() return self.socket.fileno() def next_event(self): self.check_for_error() # Main lock, so that only one thread at a time performs the # event waiting code. This at least guarantees that the first # thread calling next_event() will get the next event, although # no order is guaranteed among other threads calling next_event() # while the first is blocking. self.event_queue_read_lock.acquire() # Lock event queue, so we can check if it is empty self.event_queue_write_lock.acquire() # We have too loop until we get an event, as # we might be woken up when there is no event. while not self.event_queue: # Lock send_recv so no send_and_recieve # can start or stop while we're checking # whether there are one active. self.send_recv_lock.acquire() # Release event queue to allow an send_and_recv to # insert any now. self.event_queue_write_lock.release() # Call send_and_recv, which will return when # something has occured self.send_and_recv(event = 1) # Before looping around, lock the event queue against # modifications. self.event_queue_write_lock.acquire() # Whiew, we have an event! Remove it from # the event queue and relaese its write lock. event = self.event_queue[0] del self.event_queue[0] self.event_queue_write_lock.release() # Finally, allow any other threads which have called next_event() # while we were waiting to proceed. self.event_queue_read_lock.release() # And return the event! return event def pending_events(self): self.check_for_error() # Make a send_and_recv pass, receiving any events self.send_recv_lock.acquire() self.send_and_recv(recv = 1) # Lock the queue, get the event count, and unlock again. self.event_queue_write_lock.acquire() count = len(self.event_queue) self.event_queue_write_lock.release() return count def flush(self): self.check_for_error() self.send_recv_lock.acquire() self.send_and_recv(flush = 1) def close(self): self.flush() self.close_internal('client') def set_error_handler(self, handler): self.error_handler = handler def allocate_resource_id(self): """id = d.allocate_resource_id() Allocate a new X resource id number ID. Raises ResourceIDError if there are no free resource ids. """ self.resource_id_lock.acquire() try: i = self.last_resource_id while i in self.resource_ids: i = i + 1 if i > self.info.resource_id_mask: i = 0 if i == self.last_resource_id: raise error.ResourceIDError('out of resource ids') self.resource_ids[i] = None self.last_resource_id = i return self.info.resource_id_base | i finally: self.resource_id_lock.release() def free_resource_id(self, rid): """d.free_resource_id(rid) Free resource id RID. Attempts to free a resource id which isn't allocated by us are ignored. """ self.resource_id_lock.acquire() try: i = rid & self.info.resource_id_mask # Attempting to free a resource id outside our range if rid - i != self.info.resource_id_base: return None try: del self.resource_ids[i] except KeyError: pass finally: self.resource_id_lock.release() def get_resource_class(self, class_name, default = None): """class = d.get_resource_class(class_name, default = None) Return the class to be used for X resource objects of type CLASS_NAME, or DEFAULT if no such class is set. """ return self.resource_classes.get(class_name, default) def set_extension_major(self, extname, major): self.extension_major_opcodes[extname] = major def get_extension_major(self, extname): return self.extension_major_opcodes[extname] def add_extension_event(self, code, evt): self.event_classes[code] = evt def add_extension_error(self, code, err): self.error_classes[code] = err # # Private functions # def check_for_error(self): self.socket_error_lock.acquire() err = self.socket_error self.socket_error_lock.release() if err: raise err def send_request(self, request, wait_for_response): if self.socket_error: raise self.socket_error self.request_queue_lock.acquire() request._serial = self.request_serial self.request_serial = (self.request_serial + 1) % 65536 self.request_queue.append((request, wait_for_response)) qlen = len(self.request_queue) self.request_queue_lock.release() # if qlen > 10: # self.flush() def close_internal(self, whom): # Clear out data structures self.request_queue = None self.sent_requests = None self.event_queue = None self.data_send = None self.data_recv = None # Close the connection self.socket.close() # Set a connection closed indicator self.socket_error_lock.acquire() self.socket_error = error.ConnectionClosedError(whom) self.socket_error_lock.release() def send_and_recv(self, flush = None, event = None, request = None, recv = None): """send_and_recv(flush = None, event = None, request = None, recv = None) Perform I/O, or wait for some other thread to do it for us. send_recv_lock MUST be LOCKED when send_and_recv is called. It will be UNLOCKED at return. Exactly or one of the parameters flush, event, request and recv must be set to control the return condition. To attempt to send all requests in the queue, flush should be true. Will return immediately if another thread is already doing send_and_recv. To wait for an event to be recieved, event should be true. To wait for a response to a certain request (either an error or a response), request should be set the that request's serial number. To just read any pending data from the server, recv should be true. It is not guaranteed that the return condition has been fulfilled when the function returns, so the caller has to loop until it is finished. """ # We go to sleep if there is already a thread doing what we # want to do: # If flushing, we want to send # If waiting for a response to a request, we want to send # (to ensure that the request was sent - we alway recv # when we get to the main loop, but sending is the important # thing here) # If waiting for an event, we want to recv # If just trying to receive anything we can, we want to recv if (((flush or request is not None) and self.send_active) or ((event or recv) and self.recv_active)): # Signal that we are waiting for something. These locks # together with the *_waiting variables are used as # semaphores. When an event or a request response arrives, # it will zero the *_waiting and unlock the lock. The # locks will also be unlocked when an active send_and_recv # finishes to signal the other waiting threads that one of # them has to take over the send_and_recv function. # All this makes these locks and variables a part of the # send_and_recv control logic, and hence must be modified # only when we have the send_recv_lock locked. if event: wait_lock = self.event_wait_lock if not self.event_waiting: self.event_waiting = 1 wait_lock.acquire() elif request is not None: wait_lock = self.request_wait_lock if not self.request_waiting: self.request_waiting = 1 wait_lock.acquire() # Release send_recv, allowing a send_and_recive # to terminate or other threads to queue up self.send_recv_lock.release() # Return immediately if flushing, even if that # might mean that not necessarily all requests # have been sent. if flush or recv: return # Wait for something to happen, as the wait locks are # unlocked either when what we wait for has arrived (not # necessarily the exact object we're waiting for, though), # or when an active send_and_recv exits. # Release it immediately afterwards as we're only using # the lock for synchonization. Since we're not modifying # event_waiting or request_waiting here we don't have # to lock send_and_recv_lock. In fact, we can't do that # or we trigger a dead-lock. wait_lock.acquire() wait_lock.release() # Return to caller to let it check whether it has # got the data it was waiting for return # There's no thread doing what we need to do. Find out exactly # what to do # There must always be some thread recieving data, but it must not # necessarily be us if not self.recv_active: recieving = 1 self.recv_active = 1 else: recieving = 0 flush_bytes = None sending = 0 # Loop, recieving and sending data. while 1: # We might want to start sending data if sending or not self.send_active: # Turn all requests on request queue into binary form # and append them to self.data_send self.request_queue_lock.acquire() for req, wait in self.request_queue: self.data_send = self.data_send + req._binary if wait: self.sent_requests.append(req) del self.request_queue[:] self.request_queue_lock.release() # If there now is data to send, mark us as senders if self.data_send: self.send_active = 1 sending = 1 else: self.send_active = 0 sending = 0 # We've done all setup, so release the lock and start waiting # for the network to fire up self.send_recv_lock.release() # If we're flushing, figure out how many bytes we # have to send so that we're not caught in an interminable # loop if other threads continuously append requests. if flush and flush_bytes is None: flush_bytes = self.data_sent_bytes + len(self.data_send) try: # We're only checking for the socket to be writable # if we're the sending thread. We always check for it # to become readable: either we are the recieving thread # and should take care of the data, or the recieving thread # might finish recieving after having read the data if sending: writeset = [self.socket] else: writeset = [] # Timeout immediately if we're only checking for # something to read or if we're flushing, otherwise block if recv or flush: timeout = 0 else: timeout = None rs, ws, es = select.select([self.socket], writeset, [], timeout) # Ignore errors caused by a signal recieved while blocking. # All other errors are re-raised. except OSError as err: if err.errno != errno.EINTR: raise err # We must lock send_and_recv before we can loop to # the start of the loop self.send_recv_lock.acquire() continue # Socket is ready for sending data, send as much as possible. if ws: try: i = self.socket.send(self.data_send) except OSError as err: self.close_internal('server: %s' % err[1]) raise self.socket_error self.data_send = self.data_send[i:] self.data_sent_bytes = self.data_sent_bytes + i # There is data to read gotreq = 0 if rs: # We're the recieving thread, parse the data if recieving: try: bytes_recv = self.socket.recv(4096) except OSError as err: self.close_internal('server: %s' % err.strerror) raise self.socket_error if not bytes_recv: # Clear up, set a connection closed indicator and raise it self.close_internal('server') raise self.socket_error self.data_recv = self.data_recv + bytes_recv gotreq = self.parse_response(request) # Otherwise return, allowing the calling thread to figure # out if it has got the data it needs else: # We must be a sending thread if we're here, so reset # that indicator. self.send_recv_lock.acquire() self.send_active = 0 self.send_recv_lock.release() # And return to the caller return # There are three different end of send-recv-loop conditions. # However, we don't leave the loop immediately, instead we # try to send and recieve any data that might be left. We # do this by giving a timeout of 0 to select to poll # the socket. # When flushing: all requests have been sent if flush and flush_bytes >= self.data_sent_bytes: break # When waiting for an event: an event has been read if event and self.event_queue: break # When processing a certain request: got its reply if request is not None and gotreq: break # Always break if we just want to recieve as much as possible if recv: break # Else there's may still data which must be sent, or # we haven't got the data we waited for. Lock and loop self.send_recv_lock.acquire() # We have accomplished the callers request. # Record that there are now no active send_and_recv, # and wake up all waiting thread self.send_recv_lock.acquire() if sending: self.send_active = 0 if recieving: self.recv_active = 0 if self.event_waiting: self.event_waiting = 0 self.event_wait_lock.release() if self.request_waiting: self.request_waiting = 0 self.request_wait_lock.release() self.send_recv_lock.release() def parse_response(self, request): """Internal method. Parse data recieved from server. If REQUEST is not None true is returned if the request with that serial number was recieved, otherwise false is returned. If REQUEST is -1, we're parsing the server connection setup response. """ if request == -1: return self.parse_connection_setup() # Parse ordinary server response gotreq = 0 while 1: # Are we're waiting for additional data for a request response? if self.request_length: if len(self.data_recv) < self.request_length: return gotreq else: gotreq = self.parse_request_response(request) or gotreq # Every response is at least 32 bytes long, so don't bother # until we have recieved that much if len(self.data_recv) < 32: return gotreq # Check the first byte to find out what kind of response it is rtype = _bytes_item(self.data_recv[0]) # Error resposne if rtype == 0: gotreq = self.parse_error_response(request) or gotreq # Request response elif rtype == 1: # Set reply length, and loop around to see if # we have got the full response rlen = int(struct.unpack('=L', self.data_recv[4:8])[0]) self.request_length = 32 + rlen * 4 # Else event response else: self.parse_event_response(rtype) def parse_error_response(self, request): # Code is second byte code = _bytes_item(self.data_recv[1]) # Fetch error class estruct = self.error_classes.get(code, error.XError) e = estruct(self, self.data_recv[:32]) self.data_recv = self.data_recv[32:] # print 'recv Error:', e req = self.get_waiting_request(e.sequence_number) # Error for a request whose response we are waiting for, # or which have an error handler. However, if the error # handler indicates that it hasn't taken care of the # error, pass it on to the default error handler if req and req._set_error(e): # If this was a ReplyRequest, unlock any threads waiting # for a request to finish if isinstance(req, rq.ReplyRequest): self.send_recv_lock.acquire() if self.request_waiting: self.request_waiting = 0 self.request_wait_lock.release() self.send_recv_lock.release() return request == e.sequence_number # Else call the error handler else: if self.error_handler: rq.call_error_handler(self.error_handler, e, None) else: self.default_error_handler(e) return 0 def default_error_handler(self, err): sys.stderr.write('X protocol error:\n%s\n' % err) def parse_request_response(self, request): req = self.get_waiting_replyrequest() # Sequence number is always data[2:4] # Do sanity check before trying to parse the data sno = struct.unpack('=H', self.data_recv[2:4])[0] if sno != req._serial: raise RuntimeError("Expected reply for request %s, but got %s. Can't happen!" % (req._serial, sno)) req._parse_response(self.data_recv[:self.request_length]) # print 'recv Request:', req self.data_recv = self.data_recv[self.request_length:] self.request_length = 0 # Unlock any response waiting threads self.send_recv_lock.acquire() if self.request_waiting: self.request_waiting = 0 self.request_wait_lock.release() self.send_recv_lock.release() return req.sequence_number == request def parse_event_response(self, etype): # Skip bit 8 at lookup, that is set if this event came from an # SendEvent estruct = self.event_classes.get(etype & 0x7f, event.AnyEvent) e = estruct(display = self, binarydata = self.data_recv[:32]) self.data_recv = self.data_recv[32:] # Drop all requests having an error handler, # but which obviously succeded. # Decrement it by one, so that we don't remove the request # that generated these events, if there is such a one. # Bug reported by Ilpo Nyyssnen self.get_waiting_request((e.sequence_number - 1) % 65536) # print 'recv Event:', e # Insert the event into the queue self.event_queue_write_lock.acquire() self.event_queue.append(e) self.event_queue_write_lock.release() # Unlock any event waiting threads self.send_recv_lock.acquire() if self.event_waiting: self.event_waiting = 0 self.event_wait_lock.release() self.send_recv_lock.release() def get_waiting_request(self, sno): if not self.sent_requests: return None # Normalize sequence numbers, even if they have wrapped. # This ensures that # sno <= last_serial # and # self.sent_requests[0]._serial <= last_serial if self.sent_requests[0]._serial > self.request_serial: last_serial = self.request_serial + 65536 if sno < self.request_serial: sno = sno + 65536 else: last_serial = self.request_serial if sno > self.request_serial: sno = sno - 65536 # No matching events at all if sno < self.sent_requests[0]._serial: return None # Find last req <= sno req = None reqpos = len(self.sent_requests) adj = 0 last = 0 for i in range(0, len(self.sent_requests)): rno = self.sent_requests[i]._serial + adj # Did serial numbers just wrap around? if rno < last: adj = 65536 rno = rno + adj last = rno if sno == rno: req = self.sent_requests[i] reqpos = i + 1 break elif sno < rno: req = None reqpos = i break # Delete all request such as req <= sno del self.sent_requests[:reqpos] return req def get_waiting_replyrequest(self): for i in range(0, len(self.sent_requests)): if hasattr(self.sent_requests[i], '_reply'): req = self.sent_requests[i] del self.sent_requests[:i + 1] return req # Reply for an unknown request? No, that can't happen. else: raise RuntimeError("Request reply to unknown request. Can't happen!") def parse_connection_setup(self): """Internal function used to parse connection setup response. """ # Only the ConnectionSetupRequest has been sent so far r = self.sent_requests[0] while 1: # print 'data_send:', repr(self.data_send) # print 'data_recv:', repr(self.data_recv) if r._data: alen = r._data['additional_length'] * 4 # The full response haven't arrived yet if len(self.data_recv) < alen: return 0 # Connection failed or further authentication is needed. # Set reason to the reason string if r._data['status'] != 1: r._data['reason'] = self.data_recv[:r._data['reason_length']] # Else connection succeeded, parse the reply else: x, d = r._success_reply.parse_binary(self.data_recv[:alen], self, rawdict = 1) r._data.update(x) del self.sent_requests[0] self.data_recv = self.data_recv[alen:] return 1 else: # The base reply is 8 bytes long if len(self.data_recv) < 8: return 0 r._data, d = r._reply.parse_binary(self.data_recv[:8], self, rawdict = 1) self.data_recv = self.data_recv[8:] # Loop around to see if we have got the additional data # already PixmapFormat = rq.Struct( rq.Card8('depth'), rq.Card8('bits_per_pixel'), rq.Card8('scanline_pad'), rq.Pad(5) ) VisualType = rq.Struct ( rq.Card32('visual_id'), rq.Card8('visual_class'), rq.Card8('bits_per_rgb_value'), rq.Card16('colormap_entries'), rq.Card32('red_mask'), rq.Card32('green_mask'), rq.Card32('blue_mask'), rq.Pad(4) ) Depth = rq.Struct( rq.Card8('depth'), rq.Pad(1), rq.LengthOf('visuals', 2), rq.Pad(4), rq.List('visuals', VisualType) ) Screen = rq.Struct( rq.Window('root'), rq.Colormap('default_colormap'), rq.Card32('white_pixel'), rq.Card32('black_pixel'), rq.Card32('current_input_mask'), rq.Card16('width_in_pixels'), rq.Card16('height_in_pixels'), rq.Card16('width_in_mms'), rq.Card16('height_in_mms'), rq.Card16('min_installed_maps'), rq.Card16('max_installed_maps'), rq.Card32('root_visual'), rq.Card8('backing_store'), rq.Card8('save_unders'), rq.Card8('root_depth'), rq.LengthOf('allowed_depths', 1), rq.List('allowed_depths', Depth) ) class ConnectionSetupRequest(rq.GetAttrData): _request = rq.Struct( rq.Set('byte_order', 1, (0x42, 0x6c)), rq.Pad(1), rq.Card16('protocol_major'), rq.Card16('protocol_minor'), rq.LengthOf('auth_prot_name', 2), rq.LengthOf('auth_prot_data', 2), rq.Pad(2), rq.String8('auth_prot_name'), rq.String8('auth_prot_data') ) _reply = rq.Struct ( rq.Card8('status'), rq.Card8('reason_length'), rq.Card16('protocol_major'), rq.Card16('protocol_minor'), rq.Card16('additional_length') ) _success_reply = rq.Struct( rq.Card32('release_number'), rq.Card32('resource_id_base'), rq.Card32('resource_id_mask'), rq.Card32('motion_buffer_size'), rq.LengthOf('vendor', 2), rq.Card16('max_request_length'), rq.LengthOf('roots', 1), rq.LengthOf('pixmap_formats', 1), rq.Card8('image_byte_order'), rq.Card8('bitmap_format_bit_order'), rq.Card8('bitmap_format_scanline_unit'), rq.Card8('bitmap_format_scanline_pad'), rq.Card8('min_keycode'), rq.Card8('max_keycode'), rq.Pad(4), rq.String8('vendor'), rq.List('pixmap_formats', PixmapFormat), rq.List('roots', Screen), ) def __init__(self, display, *args, **keys): self._binary = self._request.to_binary(*args, **keys) self._data = None # Don't bother about locking, since no other threads have # access to the display yet display.request_queue.append((self, 1)) # However, we must lock send_and_recv, but we don't have # to loop. display.send_recv_lock.acquire() display.send_and_recv(request = -1) Nagstamon/Nagstamon/thirdparty/Xlib/protocol/event.py000066400000000000000000000360451316117564000233740ustar00rootroot00000000000000# Xlib.protocol.event -- definitions of core events # # Copyright (C) 2000-2002 Peter Liljenberg # # 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 # Xlib modules from Xlib import X # Xlib.protocol modules from Xlib.protocol import rq class AnyEvent(rq.Event): _code = None _fields = rq.Struct( rq.Card8('type'), rq.Card8('detail'), rq.Card16('sequence_number'), rq.FixedString('data', 28), ) class KeyButtonPointer(rq.Event): _code = None _fields = rq.Struct( rq.Card8('type'), rq.Card8('detail'), rq.Card16('sequence_number'), rq.Card32('time'), rq.Window('root'), rq.Window('window'), rq.Window('child', (X.NONE, )), rq.Int16('root_x'), rq.Int16('root_y'), rq.Int16('event_x'), rq.Int16('event_y'), rq.Card16('state'), rq.Card8('same_screen'), rq.Pad(1), ) class KeyPress(KeyButtonPointer): _code = X.KeyPress class KeyRelease(KeyButtonPointer): _code = X.KeyRelease class ButtonPress(KeyButtonPointer): _code = X.ButtonPress class ButtonRelease(KeyButtonPointer): _code = X.ButtonRelease class MotionNotify(KeyButtonPointer): _code = X.MotionNotify class EnterLeave(rq.Event): _code = None _fields = rq.Struct( rq.Card8('type'), rq.Card8('detail'), rq.Card16('sequence_number'), rq.Card32('time'), rq.Window('root'), rq.Window('window'), rq.Window('child', (X.NONE, )), rq.Int16('root_x'), rq.Int16('root_y'), rq.Int16('event_x'), rq.Int16('event_y'), rq.Card16('state'), rq.Card8('mode'), rq.Card8('flags'), ) class EnterNotify(EnterLeave): _code = X.EnterNotify class LeaveNotify(EnterLeave): _code = X.LeaveNotify class Focus(rq.Event): _code = None _fields = rq.Struct( rq.Card8('type'), rq.Card8('detail'), rq.Card16('sequence_number'), rq.Window('window'), rq.Card8('mode'), rq.Pad(23), ) class FocusIn(Focus): _code = X.FocusIn class FocusOut(Focus): _code = X.FocusOut class Expose(rq.Event): _code = X.Expose _fields = rq.Struct( rq.Card8('type'), rq.Pad(1), rq.Card16('sequence_number'), rq.Window('window'), rq.Card16('x'), rq.Card16('y'), rq.Card16('width'), rq.Card16('height'), rq.Card16('count'), rq.Pad(14), ) class GraphicsExpose(rq.Event): _code = X.GraphicsExpose _fields = rq.Struct( rq.Card8('type'), rq.Pad(1), rq.Card16('sequence_number'), rq.Drawable('drawable'), rq.Card16('x'), rq.Card16('y'), rq.Card16('width'), rq.Card16('height'), rq.Card16('minor_event'), rq.Card16('count'), rq.Card8('major_event'), rq.Pad(11), ) class NoExpose(rq.Event): _code = X.NoExpose _fields = rq.Struct( rq.Card8('type'), rq.Pad(1), rq.Card16('sequence_number'), rq.Drawable('window'), rq.Card16('minor_event'), rq.Card8('major_event'), rq.Pad(21), ) class VisibilityNotify(rq.Event): _code = X.VisibilityNotify _fields = rq.Struct( rq.Card8('type'), rq.Pad(1), rq.Card16('sequence_number'), rq.Window('window'), rq.Card8('state'), rq.Pad(23), ) class CreateNotify(rq.Event): _code = X.CreateNotify _fields = rq.Struct( rq.Card8('type'), rq.Pad(1), rq.Card16('sequence_number'), rq.Window('parent'), rq.Window('window'), rq.Int16('x'), rq.Int16('y'), rq.Card16('width'), rq.Card16('height'), rq.Card16('border_width'), rq.Card8('override'), rq.Pad(9), ) class DestroyNotify(rq.Event): _code = X.DestroyNotify _fields = rq.Struct( rq.Card8('type'), rq.Pad(1), rq.Card16('sequence_number'), rq.Window('event'), rq.Window('window'), rq.Pad(20), ) class UnmapNotify(rq.Event): _code = X.UnmapNotify _fields = rq.Struct( rq.Card8('type'), rq.Pad(1), rq.Card16('sequence_number'), rq.Window('event'), rq.Window('window'), rq.Card8('from_configure'), rq.Pad(19), ) class MapNotify(rq.Event): _code = X.MapNotify _fields = rq.Struct( rq.Card8('type'), rq.Pad(1), rq.Card16('sequence_number'), rq.Window('event'), rq.Window('window'), rq.Card8('override'), rq.Pad(19), ) class MapRequest(rq.Event): _code = X.MapRequest _fields = rq.Struct( rq.Card8('type'), rq.Pad(1), rq.Card16('sequence_number'), rq.Window('parent'), rq.Window('window'), rq.Pad(20), ) class ReparentNotify(rq.Event): _code = X.ReparentNotify _fields = rq.Struct( rq.Card8('type'), rq.Pad(1), rq.Card16('sequence_number'), rq.Window('event'), rq.Window('window'), rq.Window('parent'), rq.Int16('x'), rq.Int16('y'), rq.Card8('override'), rq.Pad(11), ) class ConfigureNotify(rq.Event): _code = X.ConfigureNotify _fields = rq.Struct( rq.Card8('type'), rq.Pad(1), rq.Card16('sequence_number'), rq.Window('event'), rq.Window('window'), rq.Window('above_sibling', (X.NONE, )), rq.Int16('x'), rq.Int16('y'), rq.Card16('width'), rq.Card16('height'), rq.Card16('border_width'), rq.Card8('override'), rq.Pad(5), ) class ConfigureRequest(rq.Event): _code = X.ConfigureRequest _fields = rq.Struct( rq.Card8('type'), rq.Card8('stack_mode'), rq.Card16('sequence_number'), rq.Window('parent'), rq.Window('window'), rq.Window('sibling', (X.NONE, )), rq.Int16('x'), rq.Int16('y'), rq.Card16('width'), rq.Card16('height'), rq.Card16('border_width'), rq.Card16('value_mask'), rq.Pad(4), ) class GravityNotify(rq.Event): _code = X.GravityNotify _fields = rq.Struct( rq.Card8('type'), rq.Pad(1), rq.Card16('sequence_number'), rq.Window('event'), rq.Window('window'), rq.Int16('x'), rq.Int16('y'), rq.Pad(16), ) class ResizeRequest(rq.Event): _code = X.ResizeRequest _fields = rq.Struct( rq.Card8('type'), rq.Pad(1), rq.Card16('sequence_number'), rq.Window('window'), rq.Card16('width'), rq.Card16('height'), rq.Pad(20), ) class Circulate(rq.Event): _code = None _fields = rq.Struct( rq.Card8('type'), rq.Pad(1), rq.Card16('sequence_number'), rq.Window('event'), rq.Window('window'), rq.Pad(4), rq.Card8('place'), rq.Pad(15), ) class CirculateNotify(Circulate): _code = X.CirculateNotify class CirculateRequest(Circulate): _code = X.CirculateRequest class PropertyNotify(rq.Event): _code = X.PropertyNotify _fields = rq.Struct( rq.Card8('type'), rq.Pad(1), rq.Card16('sequence_number'), rq.Window('window'), rq.Card32('atom'), rq.Card32('time'), rq.Card8('state'), rq.Pad(15), ) class SelectionClear(rq.Event): _code = X.SelectionClear _fields = rq.Struct( rq.Card8('type'), rq.Pad(1), rq.Card16('sequence_number'), rq.Card32('time'), rq.Window('window'), rq.Card32('atom'), rq.Pad(16), ) class SelectionRequest(rq.Event): _code = X.SelectionRequest _fields = rq.Struct( rq.Card8('type'), rq.Pad(1), rq.Card16('sequence_number'), rq.Card32('time'), rq.Window('owner'), rq.Window('requestor'), rq.Card32('selection'), rq.Card32('target'), rq.Card32('property'), rq.Pad(4), ) class SelectionNotify(rq.Event): _code = X.SelectionNotify _fields = rq.Struct( rq.Card8('type'), rq.Pad(1), rq.Card16('sequence_number'), rq.Card32('time'), rq.Window('requestor'), rq.Card32('selection'), rq.Card32('target'), rq.Card32('property'), rq.Pad(8), ) class ColormapNotify(rq.Event): _code = X.ColormapNotify _fields = rq.Struct( rq.Card8('type'), rq.Pad(1), rq.Card16('sequence_number'), rq.Window('window'), rq.Colormap('colormap', (X.NONE, )), rq.Card8('new'), rq.Card8('state'), rq.Pad(18), ) class MappingNotify(rq.Event): _code = X.MappingNotify _fields = rq.Struct( rq.Card8('type'), rq.Pad(1), rq.Card16('sequence_number'), rq.Card8('request'), rq.Card8('first_keycode'), rq.Card8('count'), rq.Pad(25), ) class ClientMessage(rq.Event): _code = X.ClientMessage _fields = rq.Struct( rq.Card8('type'), rq.Format('data', 1), rq.Card16('sequence_number'), rq.Window('window'), rq.Card32('client_type'), rq.FixedPropertyData('data', 20), ) class KeymapNotify(rq.Event): _code = X.KeymapNotify _fields = rq.Struct( rq.Card8('type'), rq.FixedList('data', 31, rq.Card8Obj, pad = 0) ) event_class = { X.KeyPress: KeyPress, X.KeyRelease: KeyRelease, X.ButtonPress: ButtonPress, X.ButtonRelease: ButtonRelease, X.MotionNotify: MotionNotify, X.EnterNotify: EnterNotify, X.LeaveNotify: LeaveNotify, X.FocusIn: FocusIn, X.FocusOut: FocusOut, X.KeymapNotify: KeymapNotify, X.Expose: Expose, X.GraphicsExpose: GraphicsExpose, X.NoExpose: NoExpose, X.VisibilityNotify: VisibilityNotify, X.CreateNotify: CreateNotify, X.DestroyNotify: DestroyNotify, X.UnmapNotify: UnmapNotify, X.MapNotify: MapNotify, X.MapRequest: MapRequest, X.ReparentNotify: ReparentNotify, X.ConfigureNotify: ConfigureNotify, X.ConfigureRequest: ConfigureRequest, X.GravityNotify: GravityNotify, X.ResizeRequest: ResizeRequest, X.CirculateNotify: CirculateNotify, X.CirculateRequest: CirculateRequest, X.PropertyNotify: PropertyNotify, X.SelectionClear: SelectionClear, X.SelectionRequest: SelectionRequest, X.SelectionNotify: SelectionNotify, X.ColormapNotify: ColormapNotify, X.ClientMessage: ClientMessage, X.MappingNotify: MappingNotify, } Nagstamon/Nagstamon/thirdparty/Xlib/protocol/request.py000066400000000000000000001346051316117564000237440ustar00rootroot00000000000000# Xlib.protocol.request -- definitions of core requests # # Copyright (C) 2000-2002 Peter Liljenberg # # 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 # Xlib modules from Xlib import X # Xlib.protocol modules from Xlib.protocol import rq, structs class CreateWindow(rq.Request): _request = rq.Struct( rq.Opcode(1), rq.Card8('depth'), rq.RequestLength(), rq.Window('wid'), rq.Window('parent'), rq.Int16('x'), rq.Int16('y'), rq.Card16('width'), rq.Card16('height'), rq.Card16('border_width'), rq.Set('window_class', 2, (X.CopyFromParent, X.InputOutput, X.InputOnly)), rq.Card32('visual'), structs.WindowValues('attrs'), ) class ChangeWindowAttributes(rq.Request): _request = rq.Struct( rq.Opcode(2), rq.Pad(1), rq.RequestLength(), rq.Window('window'), structs.WindowValues('attrs'), ) class GetWindowAttributes(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(3), rq.Pad(1), rq.RequestLength(), rq.Window('window') ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('backing_store'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('visual'), rq.Card16('win_class'), rq.Card8('bit_gravity'), rq.Card8('win_gravity'), rq.Card32('backing_bit_planes'), rq.Card32('backing_pixel'), rq.Card8('save_under'), rq.Card8('map_is_installed'), rq.Card8('map_state'), rq.Card8('override_redirect'), rq.Colormap('colormap', (X.NONE, )), rq.Card32('all_event_masks'), rq.Card32('your_event_mask'), rq.Card16('do_not_propagate_mask'), rq.Pad(2), ) class DestroyWindow(rq.Request): _request = rq.Struct( rq.Opcode(4), rq.Pad(1), rq.RequestLength(), rq.Window('window') ) class DestroySubWindows(rq.Request): _request = rq.Struct( rq.Opcode(5), rq.Pad(1), rq.RequestLength(), rq.Window('window') ) class ChangeSaveSet(rq.Request): _request = rq.Struct( rq.Opcode(6), rq.Set('mode', 1, (X.SetModeInsert, X.SetModeDelete)), rq.RequestLength(), rq.Window('window'), ) class ReparentWindow(rq.Request): _request = rq.Struct( rq.Opcode(7), rq.Pad(1), rq.RequestLength(), rq.Window('window'), rq.Window('parent'), rq.Int16('x'), rq.Int16('y'), ) class MapWindow(rq.Request): _request = rq.Struct( rq.Opcode(8), rq.Pad(1), rq.RequestLength(), rq.Window('window') ) class MapSubwindows(rq.Request): _request = rq.Struct( rq.Opcode(9), rq.Pad(1), rq.RequestLength(), rq.Window('window') ) class UnmapWindow(rq.Request): _request = rq.Struct( rq.Opcode(10), rq.Pad(1), rq.RequestLength(), rq.Window('window') ) class UnmapSubwindows(rq.Request): _request = rq.Struct( rq.Opcode(11), rq.Pad(1), rq.RequestLength(), rq.Window('window') ) class ConfigureWindow(rq.Request): _request = rq.Struct( rq.Opcode(12), rq.Pad(1), rq.RequestLength(), rq.Window('window'), rq.ValueList( 'attrs', 2, 2, rq.Int16('x'), rq.Int16('y'), rq.Card16('width'), rq.Card16('height'), rq.Int16('border_width'), rq.Window('sibling'), rq.Set('stack_mode', 1, (X.Above, X.Below, X.TopIf, X.BottomIf, X.Opposite)) ) ) class CirculateWindow(rq.Request): _request = rq.Struct( rq.Opcode(13), rq.Set('direction', 1, (X.RaiseLowest, X.LowerHighest)), rq.RequestLength(), rq.Window('window'), ) class GetGeometry(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(14), rq.Pad(1), rq.RequestLength(), rq.Drawable('drawable') ) _reply = rq.Struct ( rq.ReplyCode(), rq.Card8('depth'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Window('root'), rq.Int16('x'), rq.Int16('y'), rq.Card16('width'), rq.Card16('height'), rq.Card16('border_width'), rq.Pad(10) ) class QueryTree(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(15), rq.Pad(1), rq.RequestLength(), rq.Window('window') ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Window('root'), rq.Window('parent', (X.NONE, )), rq.LengthOf('children', 2), rq.Pad(14), rq.List('children', rq.WindowObj), ) class InternAtom(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(16), rq.Bool('only_if_exists'), rq.RequestLength(), rq.LengthOf('name', 2), rq.Pad(2), rq.String8('name'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('atom'), rq.Pad(20), ) class GetAtomName(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(17), rq.Pad(1), rq.RequestLength(), rq.Card32('atom') ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.LengthOf('name', 2), rq.Pad(22), rq.String8('name'), ) class ChangeProperty(rq.Request): _request = rq.Struct( rq.Opcode(18), rq.Set('mode', 1, (X.PropModeReplace, X.PropModePrepend, X.PropModeAppend)), rq.RequestLength(), rq.Window('window'), rq.Card32('property'), rq.Card32('type'), rq.Format('data', 1), rq.Pad(3), rq.LengthOf('data', 4), rq.PropertyData('data'), ) class DeleteProperty(rq.Request): _request = rq.Struct( rq.Opcode(19), rq.Pad(1), rq.RequestLength(), rq.Window('window'), rq.Card32('property'), ) class GetProperty(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(20), rq.Bool('delete'), rq.RequestLength(), rq.Window('window'), rq.Card32('property'), rq.Card32('type'), rq.Card32('long_offset'), rq.Card32('long_length'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Format('value', 1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('property_type'), rq.Card32('bytes_after'), rq.LengthOf('value', 4), rq.Pad(12), rq.PropertyData('value'), ) class ListProperties(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(21), rq.Pad(1), rq.RequestLength(), rq.Window('window') ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.LengthOf('atoms', 2), rq.Pad(22), rq.List('atoms', rq.Card32Obj), ) class SetSelectionOwner(rq.Request): _request = rq.Struct( rq.Opcode(22), rq.Pad(1), rq.RequestLength(), rq.Window('window'), rq.Card32('selection'), rq.Card32('time'), ) class GetSelectionOwner(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(23), rq.Pad(1), rq.RequestLength(), rq.Card32('selection') ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Window('owner', (X.NONE, )), rq.Pad(20), ) class ConvertSelection(rq.Request): _request = rq.Struct( rq.Opcode(24), rq.Pad(1), rq.RequestLength(), rq.Window('requestor'), rq.Card32('selection'), rq.Card32('target'), rq.Card32('property'), rq.Card32('time'), ) class SendEvent(rq.Request): _request = rq.Struct( rq.Opcode(25), rq.Bool('propagate'), rq.RequestLength(), rq.Window('destination'), rq.Card32('event_mask'), rq.EventField('event'), ) class GrabPointer(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(26), rq.Bool('owner_events'), rq.RequestLength(), rq.Window('grab_window'), rq.Card16('event_mask'), rq.Set('pointer_mode', 1, (X.GrabModeSync, X.GrabModeAsync)), rq.Set('keyboard_mode', 1, (X.GrabModeSync, X.GrabModeAsync)), rq.Window('confine_to', (X.NONE, )), rq.Cursor('cursor', (X.NONE, )), rq.Card32('time'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('status'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Pad(24), ) class UngrabPointer(rq.Request): _request = rq.Struct( rq.Opcode(27), rq.Pad(1), rq.RequestLength(), rq.Card32('time') ) class GrabButton(rq.Request): _request = rq.Struct( rq.Opcode(28), rq.Bool('owner_events'), rq.RequestLength(), rq.Window('grab_window'), rq.Card16('event_mask'), rq.Set('pointer_mode', 1, (X.GrabModeSync, X.GrabModeAsync)), rq.Set('keyboard_mode', 1, (X.GrabModeSync, X.GrabModeAsync)), rq.Window('confine_to', (X.NONE, )), rq.Cursor('cursor', (X.NONE, )), rq.Card8('button'), rq.Pad(1), rq.Card16('modifiers'), ) class UngrabButton(rq.Request): _request = rq.Struct( rq.Opcode(29), rq.Card8('button'), rq.RequestLength(), rq.Window('grab_window'), rq.Card16('modifiers'), rq.Pad(2), ) class ChangeActivePointerGrab(rq.Request): _request = rq.Struct( rq.Opcode(30), rq.Pad(1), rq.RequestLength(), rq.Cursor('cursor'), rq.Card32('time'), rq.Card16('event_mask'), rq.Pad(2), ) class GrabKeyboard(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(31), rq.Bool('owner_events'), rq.RequestLength(), rq.Window('grab_window'), rq.Card32('time'), rq.Set('pointer_mode', 1, (X.GrabModeSync, X.GrabModeAsync)), rq.Set('keyboard_mode', 1, (X.GrabModeSync, X.GrabModeAsync)), rq.Pad(2), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('status'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Pad(24), ) class UngrabKeyboard(rq.Request): _request = rq.Struct( rq.Opcode(32), rq.Pad(1), rq.RequestLength(), rq.Card32('time') ) class GrabKey(rq.Request): _request = rq.Struct( rq.Opcode(33), rq.Bool('owner_events'), rq.RequestLength(), rq.Window('grab_window'), rq.Card16('modifiers'), rq.Card8('key'), rq.Set('pointer_mode', 1, (X.GrabModeSync, X.GrabModeAsync)), rq.Set('keyboard_mode', 1, (X.GrabModeSync, X.GrabModeAsync)), rq.Pad(3), ) class UngrabKey(rq.Request): _request = rq.Struct( rq.Opcode(34), rq.Card8('key'), rq.RequestLength(), rq.Window('grab_window'), rq.Card16('modifiers'), rq.Pad(2), ) class AllowEvents(rq.Request): _request = rq.Struct( rq.Opcode(35), rq.Set('mode', 1, (X.AsyncPointer, X.SyncPointer, X.ReplayPointer, X.AsyncKeyboard, X.SyncKeyboard, X.ReplayKeyboard, X.AsyncBoth, X.SyncBoth)), rq.RequestLength(), rq.Card32('time'), ) class GrabServer(rq.Request): _request = rq.Struct( rq.Opcode(36), rq.Pad(1), rq.RequestLength(), ) class UngrabServer(rq.Request): _request = rq.Struct( rq.Opcode(37), rq.Pad(1), rq.RequestLength(), ) class QueryPointer(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(38), rq.Pad(1), rq.RequestLength(), rq.Window('window') ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('same_screen'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Window('root'), rq.Window('child', (X.NONE, )), rq.Int16('root_x'), rq.Int16('root_y'), rq.Int16('win_x'), rq.Int16('win_y'), rq.Card16('mask'), rq.Pad(6), ) class GetMotionEvents(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(39), rq.Pad(1), rq.RequestLength(), rq.Window('window'), rq.Card32('start'), rq.Card32('stop'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.LengthOf('events', 4), rq.Pad(20), rq.List('events', structs.TimeCoord), ) class TranslateCoords(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(40), rq.Pad(1), rq.RequestLength(), rq.Window('src_wid'), rq.Window('dst_wid'), rq.Int16('src_x'), rq.Int16('src_y'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('same_screen'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Window('child', (X.NONE, )), rq.Int16('x'), rq.Int16('y'), rq.Pad(16), ) class WarpPointer(rq.Request): _request = rq.Struct( rq.Opcode(41), rq.Pad(1), rq.RequestLength(), rq.Window('src_window'), rq.Window('dst_window'), rq.Int16('src_x'), rq.Int16('src_y'), rq.Card16('src_width'), rq.Card16('src_height'), rq.Int16('dst_x'), rq.Int16('dst_y'), ) class SetInputFocus(rq.Request): _request = rq.Struct( rq.Opcode(42), rq.Set('revert_to', 1, (X.RevertToNone, X.RevertToPointerRoot, X.RevertToParent)), rq.RequestLength(), rq.Window('focus'), rq.Card32('time'), ) class GetInputFocus(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(43), rq.Pad(1), rq.RequestLength(), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('revert_to'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Window('focus', (X.NONE, X.PointerRoot)), rq.Pad(20), ) class QueryKeymap(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(44), rq.Pad(1), rq.RequestLength(), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.FixedList('map', 32, rq.Card8Obj), ) class OpenFont(rq.Request): _request = rq.Struct( rq.Opcode(45), rq.Pad(1), rq.RequestLength(), rq.Font('fid'), rq.LengthOf('name', 2), rq.Pad(2), rq.String8('name'), ) class CloseFont(rq.Request): _request = rq.Struct( rq.Opcode(46), rq.Pad(1), rq.RequestLength(), rq.Font('font') ) class QueryFont(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(47), rq.Pad(1), rq.RequestLength(), rq.Fontable('font') ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Object('min_bounds', structs.CharInfo), rq.Pad(4), rq.Object('max_bounds', structs.CharInfo), rq.Pad(4), rq.Card16('min_char_or_byte2'), rq.Card16('max_char_or_byte2'), rq.Card16('default_char'), rq.LengthOf('properties', 2), rq.Card8('draw_direction'), rq.Card8('min_byte1'), rq.Card8('max_byte1'), rq.Card8('all_chars_exist'), rq.Int16('font_ascent'), rq.Int16('font_descent'), rq.LengthOf('char_infos', 4), rq.List('properties', structs.FontProp), rq.List('char_infos', structs.CharInfo), ) class QueryTextExtents(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(48), rq.OddLength('string'), rq.RequestLength(), rq.Fontable('font'), rq.String16('string'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('draw_direction'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Int16('font_ascent'), rq.Int16('font_descent'), rq.Int16('overall_ascent'), rq.Int16('overall_descent'), rq.Int32('overall_width'), rq.Int32('overall_left'), rq.Int32('overall_right'), rq.Pad(4), ) class ListFonts(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(49), rq.Pad(1), rq.RequestLength(), rq.Card16('max_names'), rq.LengthOf('pattern', 2), rq.String8('pattern'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.LengthOf('fonts', 2), rq.Pad(22), rq.List('fonts', rq.Str), ) class ListFontsWithInfo(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(50), rq.Pad(1), rq.RequestLength(), rq.Card16('max_names'), rq.LengthOf('pattern', 2), rq.String8('pattern'), ) _reply = rq.Struct( rq.ReplyCode(), rq.LengthOf('name', 1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Object('min_bounds', structs.CharInfo), rq.Pad(4), rq.Object('max_bounds', structs.CharInfo), rq.Pad(4), rq.Card16('min_char_or_byte2'), rq.Card16('max_char_or_byte2'), rq.Card16('default_char'), rq.LengthOf('properties', 2), rq.Card8('draw_direction'), rq.Card8('min_byte1'), rq.Card8('max_byte1'), rq.Card8('all_chars_exist'), rq.Int16('font_ascent'), rq.Int16('font_descent'), rq.Card32('replies_hint'), rq.List('properties', structs.FontProp), rq.String8('name'), ) # Somebody must have smoked some really wicked weed when they # defined the ListFontsWithInfo request: # The server sends a reply for _each_ matching font... # It then sends a special reply (name length == 0) to indicate # that there are no more fonts in the reply. # This means that we have to do some special parsing to see if # we have got the end-of-reply reply. If we haven't, we # have to reinsert the request in the front of the # display.sent_request queue to catch the next response. # Bastards. def __init__(self, *args, **keys): self._fonts = [] ReplyRequest.__init__(*(self, ) + args, **keys) def _parse_response(self, data): if ord(data[1]) == 0: self._response_lock.acquire() self._data = self._fonts del self._fonts self._response_lock.release() return r, d = self._reply.parse_binary(data) self._fonts.append(r) self._display.sent_requests.insert(0, self) # Override the default __getattr__, since it isn't usable for # the list reply. Instead provide a __getitem__ and a __len__. def __getattr__(self, attr): raise AttributeError(attr) def __getitem__(self, item): return self._data[item] def __len__(self): return len(self._data) class SetFontPath(rq.Request): _request = rq.Struct( rq.Opcode(51), rq.Pad(1), rq.RequestLength(), rq.LengthOf('path', 2), rq.Pad(2), rq.List('path', rq.Str), ) class GetFontPath(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(52), rq.Pad(1), rq.RequestLength(), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.LengthOf('paths', 2), rq.Pad(22), rq.List('paths', rq.Str), ) class CreatePixmap(rq.Request): _request = rq.Struct( rq.Opcode(53), rq.Card8('depth'), rq.RequestLength(), rq.Pixmap('pid'), rq.Drawable('drawable'), rq.Card16('width'), rq.Card16('height'), ) class FreePixmap(rq.Request): _request = rq.Struct( rq.Opcode(54), rq.Pad(1), rq.RequestLength(), rq.Pixmap('pixmap') ) class CreateGC(rq.Request): _request = rq.Struct( rq.Opcode(55), rq.Pad(1), rq.RequestLength(), rq.GC('cid'), rq.Drawable('drawable'), structs.GCValues('attrs'), ) class ChangeGC(rq.Request): _request = rq.Struct( rq.Opcode(56), rq.Pad(1), rq.RequestLength(), rq.GC('gc'), structs.GCValues('attrs'), ) class CopyGC(rq.Request): _request = rq.Struct( rq.Opcode(57), rq.Pad(1), rq.RequestLength(), rq.GC('src_gc'), rq.GC('dst_gc'), rq.Card32('mask'), ) class SetDashes(rq.Request): _request = rq.Struct( rq.Opcode(58), rq.Pad(1), rq.RequestLength(), rq.GC('gc'), rq.Card16('dash_offset'), rq.LengthOf('dashes', 2), rq.List('dashes', rq.Card8Obj), ) class SetClipRectangles(rq.Request): _request = rq.Struct( rq.Opcode(59), rq.Set('ordering', 1, (X.Unsorted, X.YSorted, X.YXSorted, X.YXBanded)), rq.RequestLength(), rq.GC('gc'), rq.Int16('x_origin'), rq.Int16('y_origin'), rq.List('rectangles', structs.Rectangle), ) class FreeGC(rq.Request): _request = rq.Struct( rq.Opcode(60), rq.Pad(1), rq.RequestLength(), rq.GC('gc') ) class ClearArea(rq.Request): _request = rq.Struct( rq.Opcode(61), rq.Bool('exposures'), rq.RequestLength(), rq.Window('window'), rq.Int16('x'), rq.Int16('y'), rq.Card16('width'), rq.Card16('height'), ) class CopyArea(rq.Request): _request = rq.Struct( rq.Opcode(62), rq.Pad(1), rq.RequestLength(), rq.Drawable('src_drawable'), rq.Drawable('dst_drawable'), rq.GC('gc'), rq.Int16('src_x'), rq.Int16('src_y'), rq.Int16('dst_x'), rq.Int16('dst_y'), rq.Card16('width'), rq.Card16('height'), ) class CopyPlane(rq.Request): _request = rq.Struct( rq.Opcode(63), rq.Pad(1), rq.RequestLength(), rq.Drawable('src_drawable'), rq.Drawable('dst_drawable'), rq.GC('gc'), rq.Int16('src_x'), rq.Int16('src_y'), rq.Int16('dst_x'), rq.Int16('dst_y'), rq.Card16('width'), rq.Card16('height'), rq.Card32('bit_plane'), ) class PolyPoint(rq.Request): _request = rq.Struct( rq.Opcode(64), rq.Set('coord_mode', 1, (X.CoordModeOrigin, X.CoordModePrevious)), rq.RequestLength(), rq.Drawable('drawable'), rq.GC('gc'), rq.List('points', structs.Point), ) class PolyLine(rq.Request): _request = rq.Struct( rq.Opcode(65), rq.Set('coord_mode', 1, (X.CoordModeOrigin, X.CoordModePrevious)), rq.RequestLength(), rq.Drawable('drawable'), rq.GC('gc'), rq.List('points', structs.Point), ) class PolySegment(rq.Request): _request = rq.Struct( rq.Opcode(66), rq.Pad(1), rq.RequestLength(), rq.Drawable('drawable'), rq.GC('gc'), rq.List('segments', structs.Segment), ) class PolyRectangle(rq.Request): _request = rq.Struct( rq.Opcode(67), rq.Pad(1), rq.RequestLength(), rq.Drawable('drawable'), rq.GC('gc'), rq.List('rectangles', structs.Rectangle), ) class PolyArc(rq.Request): _request = rq.Struct( rq.Opcode(68), rq.Pad(1), rq.RequestLength(), rq.Drawable('drawable'), rq.GC('gc'), rq.List('arcs', structs.Arc), ) class FillPoly(rq.Request): _request = rq.Struct( rq.Opcode(69), rq.Pad(1), rq.RequestLength(), rq.Drawable('drawable'), rq.GC('gc'), rq.Set('shape', 1, (X.Complex, X.Nonconvex, X.Convex)), rq.Set('coord_mode', 1, (X.CoordModeOrigin, X.CoordModePrevious)), rq.Pad(2), rq.List('points', structs.Point), ) class PolyFillRectangle(rq.Request): _request = rq.Struct( rq.Opcode(70), rq.Pad(1), rq.RequestLength(), rq.Drawable('drawable'), rq.GC('gc'), rq.List('rectangles', structs.Rectangle), ) class PolyFillArc(rq.Request): _request = rq.Struct( rq.Opcode(71), rq.Pad(1), rq.RequestLength(), rq.Drawable('drawable'), rq.GC('gc'), rq.List('arcs', structs.Arc), ) class PutImage(rq.Request): _request = rq.Struct( rq.Opcode(72), rq.Set('format', 1, (X.XYBitmap, X.XYPixmap, X.ZPixmap)), rq.RequestLength(), rq.Drawable('drawable'), rq.GC('gc'), rq.Card16('width'), rq.Card16('height'), rq.Int16('dst_x'), rq.Int16('dst_y'), rq.Card8('left_pad'), rq.Card8('depth'), rq.Pad(2), rq.String8('data'), ) class GetImage(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(73), rq.Set('format', 1, (X.XYPixmap, X.ZPixmap)), rq.RequestLength(), rq.Drawable('drawable'), rq.Int16('x'), rq.Int16('y'), rq.Card16('width'), rq.Card16('height'), rq.Card32('plane_mask'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('depth'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('visual'), rq.Pad(20), rq.String8('data'), ) class PolyText8(rq.Request): _request = rq.Struct( rq.Opcode(74), rq.Pad(1), rq.RequestLength(), rq.Drawable('drawable'), rq.GC('gc'), rq.Int16('x'), rq.Int16('y'), rq.TextElements8('items'), ) class PolyText16(rq.Request): _request = rq.Struct( rq.Opcode(75), rq.Pad(1), rq.RequestLength(), rq.Drawable('drawable'), rq.GC('gc'), rq.Int16('x'), rq.Int16('y'), rq.TextElements16('items'), ) class ImageText8(rq.Request): _request = rq.Struct( rq.Opcode(76), rq.LengthOf('string', 1), rq.RequestLength(), rq.Drawable('drawable'), rq.GC('gc'), rq.Int16('x'), rq.Int16('y'), rq.String8('string'), ) class ImageText16(rq.Request): _request = rq.Struct( rq.Opcode(77), rq.LengthOf('string', 1), rq.RequestLength(), rq.Drawable('drawable'), rq.GC('gc'), rq.Int16('x'), rq.Int16('y'), rq.String16('string'), ) class CreateColormap(rq.Request): _request = rq.Struct( rq.Opcode(78), rq.Set('alloc', 1, (X.AllocNone, X.AllocAll)), rq.RequestLength(), rq.Colormap('mid'), rq.Window('window'), rq.Card32('visual'), ) class FreeColormap(rq.Request): _request = rq.Struct( rq.Opcode(79), rq.Pad(1), rq.RequestLength(), rq.Colormap('cmap') ) class CopyColormapAndFree(rq.Request): _request = rq.Struct( rq.Opcode(80), rq.Pad(1), rq.RequestLength(), rq.Colormap('mid'), rq.Colormap('src_cmap'), ) class InstallColormap(rq.Request): _request = rq.Struct( rq.Opcode(81), rq.Pad(1), rq.RequestLength(), rq.Colormap('cmap') ) class UninstallColormap(rq.Request): _request = rq.Struct( rq.Opcode(82), rq.Pad(1), rq.RequestLength(), rq.Colormap('cmap') ) class ListInstalledColormaps(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(83), rq.Pad(1), rq.RequestLength(), rq.Window('window') ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.LengthOf('cmaps', 2), rq.Pad(22), rq.List('cmaps', rq.ColormapObj), ) class AllocColor(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(84), rq.Pad(1), rq.RequestLength(), rq.Colormap('cmap'), rq.Card16('red'), rq.Card16('green'), rq.Card16('blue'), rq.Pad(2), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card16('red'), rq.Card16('green'), rq.Card16('blue'), rq.Pad(2), rq.Card32('pixel'), rq.Pad(12), ) class AllocNamedColor(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(85), rq.Pad(1), rq.RequestLength(), rq.Colormap('cmap'), rq.LengthOf('name', 2), rq.Pad(2), rq.String8('name'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('pixel'), rq.Card16('exact_red'), rq.Card16('exact_green'), rq.Card16('exact_blue'), rq.Card16('screen_red'), rq.Card16('screen_green'), rq.Card16('screen_blue'), rq.Pad(8), ) class AllocColorCells(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(86), rq.Bool('contiguous'), rq.RequestLength(), rq.Colormap('cmap'), rq.Card16('colors'), rq.Card16('planes'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.LengthOf('pixels', 2), rq.LengthOf('masks', 2), rq.Pad(20), rq.List('pixels', rq.Card32Obj), rq.List('masks', rq.Card32Obj), ) class AllocColorPlanes(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(87), rq.Bool('contiguous'), rq.RequestLength(), rq.Colormap('cmap'), rq.Card16('colors'), rq.Card16('red'), rq.Card16('green'), rq.Card16('blue'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.LengthOf('pixels', 2), rq.Pad(2), rq.Card32('red_mask'), rq.Card32('green_mask'), rq.Card32('blue_mask'), rq.Pad(8), rq.List('pixels', rq.Card32Obj), ) class FreeColors(rq.Request): _request = rq.Struct( rq.Opcode(88), rq.Pad(1), rq.RequestLength(), rq.Colormap('cmap'), rq.Card32('plane_mask'), rq.List('pixels', rq.Card32Obj), ) class StoreColors(rq.Request): _request = rq.Struct( rq.Opcode(89), rq.Pad(1), rq.RequestLength(), rq.Colormap('cmap'), rq.List('items', structs.ColorItem), ) class StoreNamedColor(rq.Request): _request = rq.Struct( rq.Opcode(90), rq.Card8('flags'), rq.RequestLength(), rq.Colormap('cmap'), rq.Card32('pixel'), rq.LengthOf('name', 2), rq.Pad(2), rq.String8('name'), ) class QueryColors(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(91), rq.Pad(1), rq.RequestLength(), rq.Colormap('cmap'), rq.List('pixels', rq.Card32Obj), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.LengthOf('colors', 2), rq.Pad(22), rq.List('colors', structs.RGB), ) class LookupColor(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(92), rq.Pad(1), rq.RequestLength(), rq.Colormap('cmap'), rq.LengthOf('name', 2), rq.Pad(2), rq.String8('name'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card16('exact_red'), rq.Card16('exact_green'), rq.Card16('exact_blue'), rq.Card16('screen_red'), rq.Card16('screen_green'), rq.Card16('screen_blue'), rq.Pad(12), ) class CreateCursor(rq.Request): _request = rq.Struct( rq.Opcode(93), rq.Pad(1), rq.RequestLength(), rq.Cursor('cid'), rq.Pixmap('source'), rq.Pixmap('mask'), rq.Card16('fore_red'), rq.Card16('fore_green'), rq.Card16('fore_blue'), rq.Card16('back_red'), rq.Card16('back_green'), rq.Card16('back_blue'), rq.Card16('x'), rq.Card16('y'), ) class CreateGlyphCursor(rq.Request): _request = rq.Struct( rq.Opcode(94), rq.Pad(1), rq.RequestLength(), rq.Cursor('cid'), rq.Font('source'), rq.Font('mask'), rq.Card16('source_char'), rq.Card16('mask_char'), rq.Card16('fore_red'), rq.Card16('fore_green'), rq.Card16('fore_blue'), rq.Card16('back_red'), rq.Card16('back_green'), rq.Card16('back_blue'), ) class FreeCursor(rq.Request): _request = rq.Struct( rq.Opcode(95), rq.Pad(1), rq.RequestLength(), rq.Cursor('cursor') ) class RecolorCursor(rq.Request): _request = rq.Struct( rq.Opcode(96), rq.Pad(1), rq.RequestLength(), rq.Cursor('cursor'), rq.Card16('fore_red'), rq.Card16('fore_green'), rq.Card16('fore_blue'), rq.Card16('back_red'), rq.Card16('back_green'), rq.Card16('back_blue'), ) class QueryBestSize(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(97), rq.Set('item_class', 1, (X.CursorShape, X.TileShape, X.StippleShape)), rq.RequestLength(), rq.Drawable('drawable'), rq.Card16('width'), rq.Card16('height'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card16('width'), rq.Card16('height'), rq.Pad(20), ) class QueryExtension(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(98), rq.Pad(1), rq.RequestLength(), rq.LengthOf('name', 2), rq.Pad(2), rq.String8('name'), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card8('present'), rq.Card8('major_opcode'), rq.Card8('first_event'), rq.Card8('first_error'), rq.Pad(20), ) class ListExtensions(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(99), rq.Pad(1), rq.RequestLength(), ) _reply = rq.Struct( rq.ReplyCode(), rq.LengthOf('names', 1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Pad(24), rq.List('names', rq.Str), ) class ChangeKeyboardMapping(rq.Request): _request = rq.Struct( rq.Opcode(100), rq.LengthOf('keysyms', 1), rq.RequestLength(), rq.Card8('first_keycode'), rq.Format('keysyms', 1), rq.Pad(2), rq.KeyboardMapping('keysyms'), ) class GetKeyboardMapping(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(101), rq.Pad(1), rq.RequestLength(), rq.Card8('first_keycode'), rq.Card8('count'), rq.Pad(2), ) _reply = rq.Struct( rq.ReplyCode(), rq.Format('keysyms', 1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Pad(24), rq.KeyboardMapping('keysyms'), ) class ChangeKeyboardControl(rq.Request): _request = rq.Struct( rq.Opcode(102), rq.Pad(1), rq.RequestLength(), rq.ValueList( 'attrs', 4, 0, rq.Int8('key_click_percent'), rq.Int8('bell_percent'), rq.Int16('bell_pitch'), rq.Int16('bell_duration'), rq.Card8('led'), rq.Set('led_mode', 1, (X.LedModeOff, X.LedModeOn)), rq.Card8('key'), rq.Set('auto_repeat_mode', 1, (X.AutoRepeatModeOff, X.AutoRepeatModeOn, X.AutoRepeatModeDefault)) ) ) class GetKeyboardControl(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(103), rq.Pad(1), rq.RequestLength(), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('global_auto_repeat'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card32('led_mask'), rq.Card8('key_click_percent'), rq.Card8('bell_percent'), rq.Card16('bell_pitch'), rq.Card16('bell_duration'), rq.Pad(2), rq.FixedList('auto_repeats', 32, rq.Card8Obj), ) class Bell(rq.Request): _request = rq.Struct( rq.Opcode(104), rq.Int8('percent'), rq.RequestLength(), ) class ChangePointerControl(rq.Request): _request = rq.Struct( rq.Opcode(105), rq.Pad(1), rq.RequestLength(), rq.Int16('accel_num'), rq.Int16('accel_denum'), rq.Int16('threshold'), rq.Bool('do_accel'), rq.Bool('do_thresh'), ) class GetPointerControl(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(106), rq.Pad(1), rq.RequestLength(), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card16('accel_num'), rq.Card16('accel_denom'), rq.Card16('threshold'), rq.Pad(18), ) class SetScreenSaver(rq.Request): _request = rq.Struct( rq.Opcode(107), rq.Pad(1), rq.RequestLength(), rq.Int16('timeout'), rq.Int16('interval'), rq.Set('prefer_blank', 1, (X.DontPreferBlanking, X.PreferBlanking, X.DefaultBlanking)), rq.Set('allow_exposures', 1, (X.DontAllowExposures, X.AllowExposures, X.DefaultExposures)), rq.Pad(2), ) class GetScreenSaver(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(108), rq.Pad(1), rq.RequestLength(), ) _reply = rq.Struct( rq.ReplyCode(), rq.Pad(1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Card16('timeout'), rq.Card16('interval'), rq.Card8('prefer_blanking'), rq.Card8('allow_exposures'), rq.Pad(18), ) class ChangeHosts(rq.Request): _request = rq.Struct( rq.Opcode(109), rq.Set('mode', 1, (X.HostInsert, X.HostDelete)), rq.RequestLength(), rq.Set('host_family', 1, (X.FamilyInternet, X.FamilyDECnet, X.FamilyChaos)), rq.Pad(1), rq.LengthOf('host', 2), rq.List('host', rq.Card8Obj) ) class ListHosts(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(110), rq.Pad(1), rq.RequestLength(), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('mode'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.LengthOf('hosts', 2), rq.Pad(22), rq.List('hosts', structs.Host), ) class SetAccessControl(rq.Request): _request = rq.Struct( rq.Opcode(111), rq.Set('mode', 1, (X.DisableAccess, X.EnableAccess)), rq.RequestLength(), ) class SetCloseDownMode(rq.Request): _request = rq.Struct( rq.Opcode(112), rq.Set('mode', 1, (X.DestroyAll, X.RetainPermanent, X.RetainTemporary)), rq.RequestLength(), ) class KillClient(rq.Request): _request = rq.Struct( rq.Opcode(113), rq.Pad(1), rq.RequestLength(), rq.Resource('resource') ) class RotateProperties(rq.Request): _request = rq.Struct( rq.Opcode(114), rq.Pad(1), rq.RequestLength(), rq.Window('window'), rq.LengthOf('properties', 2), rq.Int16('delta'), rq.List('properties', rq.Card32Obj), ) class ForceScreenSaver(rq.Request): _request = rq.Struct( rq.Opcode(115), rq.Set('mode', 1, (X.ScreenSaverReset, X.ScreenSaverActive)), rq.RequestLength(), ) class SetPointerMapping(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(116), rq.LengthOf('map', 1), rq.RequestLength(), rq.List('map', rq.Card8Obj), ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('status'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Pad(24), ) class GetPointerMapping(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(117), rq.Pad(1), rq.RequestLength(), ) _reply = rq.Struct( rq.ReplyCode(), rq.LengthOf('map', 1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Pad(24), rq.List('map', rq.Card8Obj), ) class SetModifierMapping(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(118), rq.Format('keycodes', 1), rq.RequestLength(), rq.ModifierMapping('keycodes') ) _reply = rq.Struct( rq.ReplyCode(), rq.Card8('status'), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Pad(24), ) class GetModifierMapping(rq.ReplyRequest): _request = rq.Struct( rq.Opcode(119), rq.Pad(1), rq.RequestLength(), ) _reply = rq.Struct( rq.ReplyCode(), rq.Format('keycodes', 1), rq.Card16('sequence_number'), rq.ReplyLength(), rq.Pad(24), rq.ModifierMapping('keycodes') ) class NoOperation(rq.Request): _request = rq.Struct( rq.Opcode(127), rq.Pad(1), rq.RequestLength(), ) major_codes = { 1: CreateWindow, 2: ChangeWindowAttributes, 3: GetWindowAttributes, 4: DestroyWindow, 5: DestroySubWindows, 6: ChangeSaveSet, 7: ReparentWindow, 8: MapWindow, 9: MapSubwindows, 10: UnmapWindow, 11: UnmapSubwindows, 12: ConfigureWindow, 13: CirculateWindow, 14: GetGeometry, 15: QueryTree, 16: InternAtom, 17: GetAtomName, 18: ChangeProperty, 19: DeleteProperty, 20: GetProperty, 21: ListProperties, 22: SetSelectionOwner, 23: GetSelectionOwner, 24: ConvertSelection, 25: SendEvent, 26: GrabPointer, 27: UngrabPointer, 28: GrabButton, 29: UngrabButton, 30: ChangeActivePointerGrab, 31: GrabKeyboard, 32: UngrabKeyboard, 33: GrabKey, 34: UngrabKey, 35: AllowEvents, 36: GrabServer, 37: UngrabServer, 38: QueryPointer, 39: GetMotionEvents, 40: TranslateCoords, 41: WarpPointer, 42: SetInputFocus, 43: GetInputFocus, 44: QueryKeymap, 45: OpenFont, 46: CloseFont, 47: QueryFont, 48: QueryTextExtents, 49: ListFonts, 50: ListFontsWithInfo, 51: SetFontPath, 52: GetFontPath, 53: CreatePixmap, 54: FreePixmap, 55: CreateGC, 56: ChangeGC, 57: CopyGC, 58: SetDashes, 59: SetClipRectangles, 60: FreeGC, 61: ClearArea, 62: CopyArea, 63: CopyPlane, 64: PolyPoint, 65: PolyLine, 66: PolySegment, 67: PolyRectangle, 68: PolyArc, 69: FillPoly, 70: PolyFillRectangle, 71: PolyFillArc, 72: PutImage, 73: GetImage, 74: PolyText8, 75: PolyText16, 76: ImageText8, 77: ImageText16, 78: CreateColormap, 79: FreeColormap, 80: CopyColormapAndFree, 81: InstallColormap, 82: UninstallColormap, 83: ListInstalledColormaps, 84: AllocColor, 85: AllocNamedColor, 86: AllocColorCells, 87: AllocColorPlanes, 88: FreeColors, 89: StoreColors, 90: StoreNamedColor, 91: QueryColors, 92: LookupColor, 93: CreateCursor, 94: CreateGlyphCursor, 95: FreeCursor, 96: RecolorCursor, 97: QueryBestSize, 98: QueryExtension, 99: ListExtensions, 100: ChangeKeyboardMapping, 101: GetKeyboardMapping, 102: ChangeKeyboardControl, 103: GetKeyboardControl, 104: Bell, 105: ChangePointerControl, 106: GetPointerControl, 107: SetScreenSaver, 108: GetScreenSaver, 109: ChangeHosts, 110: ListHosts, 111: SetAccessControl, 112: SetCloseDownMode, 113: KillClient, 114: RotateProperties, 115: ForceScreenSaver, 116: SetPointerMapping, 117: GetPointerMapping, 118: SetModifierMapping, 119: GetModifierMapping, 127: NoOperation, } Nagstamon/Nagstamon/thirdparty/Xlib/protocol/rq.py000066400000000000000000001324611316117564000226740ustar00rootroot00000000000000# Xlib.protocol.rq -- structure primitives for request, events and errors # # Copyright (C) 2000-2002 Peter Liljenberg # # 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 from array import array import struct import sys import traceback import types # Xlib modules from Xlib import X from Xlib.support import lock _PY3 = sys.version[0] >= '3' # in Python 3, bytes are an actual array; in python 2, bytes are still # string-like, so in order to get an array element we need to call ord() if _PY3: def _bytes_item(x): return x else: def _bytes_item(x): return ord(x) class BadDataError(Exception): pass # These are struct codes, we know their byte sizes signed_codes = { 1: 'b', 2: 'h', 4: 'l' } unsigned_codes = { 1: 'B', 2: 'H', 4: 'L' } # Unfortunately, we don't know the array sizes of B, H and L, since # these use the underlying architecture's size for a char, short and # long. Therefore we probe for their sizes, and additionally create # a mapping that translates from struct codes to array codes. # # Bleah. array_unsigned_codes = { } struct_to_array_codes = { } for c in 'bhil': size = array(c).itemsize array_unsigned_codes[size] = c.upper() try: struct_to_array_codes[signed_codes[size]] = c struct_to_array_codes[unsigned_codes[size]] = c.upper() except KeyError: pass # print array_unsigned_codes, struct_to_array_codes class Field: """Field objects represent the data fields of a Struct. Field objects must have the following attributes: name -- the field name, or None structcode -- the struct codes representing this field structvalues -- the number of values encodes by structcode Additionally, these attributes should either be None or real methods: check_value -- check a value before it is converted to binary parse_value -- parse a value after it has been converted from binary If one of these attributes are None, no check or additional parsings will be done one values when converting to or from binary form. Otherwise, the methods should have the following behaviour: newval = check_value(val) Check that VAL is legal when converting to binary form. The value can also be converted to another Python value. In any case, return the possibly new value. NEWVAL should be a single Python value if structvalues is 1, a tuple of structvalues elements otherwise. newval = parse_value(val, display) VAL is an unpacked Python value, which now can be further refined. DISPLAY is the current Display object. Return the new value. VAL will be a single value if structvalues is 1, a tuple of structvalues elements otherwise. If `structcode' is None the Field must have the method f.parse_binary_value() instead. See its documentation string for details. """ name = None default = None structcode = None structvalues = 0 check_value = None parse_value = None keyword_args = 0 def __init__(self): pass def parse_binary_value(self, data, display, length, format): """value, remaindata = f.parse_binary_value(data, display, length, format) Decode a value for this field from the binary string DATA. If there are a LengthField and/or a FormatField connected to this field, their values will be LENGTH and FORMAT, respectively. If there are no such fields the parameters will be None. DISPLAY is the display involved, which is really only used by the Resource fields. The decoded value is returned as VALUE, and the remaining part of DATA shold be returned as REMAINDATA. """ raise RuntimeError('Neither structcode or parse_binary_value provided for %s' % self) class Pad(Field): def __init__(self, size): self.size = size self.value = b'\0' * size self.structcode = '%dx' % size self.structvalues = 0 class ConstantField(Field): def __init__(self, value): self.value = value class Opcode(ConstantField): structcode = 'B' structvalues = 1 class ReplyCode(ConstantField): structcode = 'B' structvalues = 1 def __init__(self): self.value = 1 class LengthField(Field): """A LengthField stores the length of some other Field whose size may vary, e.g. List and String8. Its name should be the same as the name of the field whose size it stores. The lf.get_binary_value() method of LengthFields is not used, instead a lf.get_binary_length() should be provided. Unless LengthField.get_binary_length() is overridden in child classes, there should also be a lf.calc_length(). """ structcode = 'L' structvalues = 1 def calc_length(self, length): """newlen = lf.calc_length(length) Return a new length NEWLEN based on the provided LENGTH. """ return length class TotalLengthField(LengthField): pass class RequestLength(TotalLengthField): structcode = 'H' structvalues = 1 def calc_length(self, length): return length // 4 class ReplyLength(TotalLengthField): structcode = 'L' structvalues = 1 def calc_length(self, length): return (length - 32) // 4 class LengthOf(LengthField): def __init__(self, name, size): self.name = name self.structcode = unsigned_codes[size] class OddLength(LengthField): structcode = 'B' structvalues = 1 def __init__(self, name): self.name = name def calc_length(self, length): return length % 2 def parse_value(self, value, display): if value == 0: return 'even' else: return 'odd' class FormatField(Field): """A FormatField encodes the format of some other field, in a manner similar to LengthFields. The ff.get_binary_value() method is not used, replaced by ff.get_binary_format(). """ structvalues = 1 def __init__(self, name, size): self.name = name self.structcode = unsigned_codes[size] Format = FormatField class ValueField(Field): def __init__(self, name, default = None): self.name = name self.default = default class Int8(ValueField): structcode = 'b' structvalues = 1 class Int16(ValueField): structcode = 'h' structvalues = 1 class Int32(ValueField): structcode = 'l' structvalues = 1 class Card8(ValueField): structcode = 'B' structvalues = 1 class Card16(ValueField): structcode = 'H' structvalues = 1 class Card32(ValueField): structcode = 'L' structvalues = 1 class Resource(Card32): cast_function = '__resource__' class_name = 'resource' def __init__(self, name, codes = (), default = None): Card32.__init__(self, name, default) self.codes = codes def check_value(self, value): try: return getattr(value, self.cast_function)() except AttributeError: return value def parse_value(self, value, display): # if not display: # return value if value in self.codes: return value c = display.get_resource_class(self.class_name) if c: return c(display, value) else: return value class Window(Resource): cast_function = '__window__' class_name = 'window' class Pixmap(Resource): cast_function = '__pixmap__' class_name = 'pixmap' class Drawable(Resource): cast_function = '__drawable__' class_name = 'drawable' class Fontable(Resource): cast_function = '__fontable__' class_name = 'fontable' class Font(Resource): cast_function = '__font__' class_name = 'font' class GC(Resource): cast_function = '__gc__' class_name = 'gc' class Colormap(Resource): cast_function = '__colormap__' class_name = 'colormap' class Cursor(Resource): cast_function = '__cursor__' class_name = 'cursor' class Bool(ValueField): structvalues = 1 structcode = 'B' def check_value(self, value): return not not value class Set(ValueField): structvalues = 1 def __init__(self, name, size, values, default = None): ValueField.__init__(self, name, default) self.structcode = unsigned_codes[size] self.values = values def check_value(self, val): if val not in self.values: raise ValueError('field %s: argument %s not in %s' % (self.name, val, self.values)) return val class Gravity(Set): def __init__(self, name): Set.__init__(self, name, 1, (X.ForgetGravity, X.StaticGravity, X.NorthWestGravity, X.NorthGravity, X.NorthEastGravity, X.WestGravity, X.CenterGravity, X.EastGravity, X.SouthWestGravity, X.SouthGravity, X.SouthEastGravity)) class FixedString(ValueField): structvalues = 1 def __init__(self, name, size): ValueField.__init__(self, name) self.structcode = '%ds' % size class String8(ValueField): structcode = None def __init__(self, name, pad = 1): ValueField.__init__(self, name) self.pad = pad def pack_value(self, val): slen = len(val) if _PY3 and type(val) is str: val = val.encode('UTF-8') if self.pad: return val + b'\0' * ((4 - slen % 4) % 4), slen, None else: return val, slen, None def parse_binary_value(self, data, display, length, format): if length is None: try: return data.decode('UTF-8'), b'' except UnicodeDecodeError: return data, b'' if self.pad: slen = length + ((4 - length % 4) % 4) else: slen = length s = data[:length] try: s = s.decode('UTF-8') except UnicodeDecodeError: pass # return as bytes return s, data[slen:] class String16(ValueField): structcode = None def __init__(self, name, pad = 1): ValueField.__init__(self, name) self.pad = pad def pack_value(self, val): # Convert 8-byte string into 16-byte list if type(val) is str: val = [ord(c) for c in val] slen = len(val) if self.pad: pad = b'\0\0' * (slen % 2) else: pad = b'' return (struct.pack(*('>' + 'H' * slen, ) + tuple(val)) + pad, slen, None) def parse_binary_value(self, data, display, length, format): if length == 'odd': length = len(data) // 2 - 1 elif length == 'even': length = len(data) // 2 if self.pad: slen = length + (length % 2) else: slen = length return (struct.unpack('>' + 'H' * length, data[:length * 2]), data[slen * 2:]) class List(ValueField): """The List, FixedList and Object fields store compound data objects. The type of data objects must be provided as an object with the following attributes and methods: ... """ structcode = None def __init__(self, name, type, pad = 1): ValueField.__init__(self, name) self.type = type self.pad = pad def parse_binary_value(self, data, display, length, format): if length is None: ret = [] if self.type.structcode is None: while data: val, data = self.type.parse_binary(data, display) ret.append(val) else: scode = '=' + self.type.structcode slen = struct.calcsize(scode) pos = 0 while pos + slen <= len(data): v = struct.unpack(scode, data[pos: pos + slen]) if self.type.structvalues == 1: v = v[0] if self.type.parse_value is None: ret.append(v) else: ret.append(self.type.parse_value(v, display)) pos = pos + slen data = data[pos:] else: ret = [None] * int(length) if self.type.structcode is None: for i in range(length): ret[i], data = self.type.parse_binary(data, display) else: scode = '=' + self.type.structcode slen = struct.calcsize(scode) pos = 0 for i in range(0, length): # FIXME: remove try..except try: v = struct.unpack(scode, data[pos: pos + slen]) except Exception: v = b'\x00\x00\x00\x00' if self.type.structvalues == 1: v = v[0] if self.type.parse_value is None: ret[i] = v else: ret[i] = self.type.parse_value(v, display) pos = pos + slen data = data[pos:] if self.pad: data = data[len(data) % 4:] return ret, data def pack_value(self, val): # Single-char values, we'll assume that means integer lists. if self.type.structcode and len(self.type.structcode) == 1: data = array(struct_to_array_codes[self.type.structcode], val).tobytes() else: data = [] for v in val: data.append(self.type.pack_value(v)) data = b''.join(data) if self.pad: dlen = len(data) data = data + b'\0' * ((4 - dlen % 4) % 4) return data, len(val), None class FixedList(List): def __init__(self, name, size, type, pad = 1): List.__init__(self, name, type, pad) self.size = size def parse_binary_value(self, data, display, length, format): return List.parse_binary_value(self, data, display, self.size, format) def pack_value(self, val): if len(val) != self.size: raise BadDataError('length mismatch for FixedList %s' % self.name) return List.pack_value(self, val) class Object(ValueField): structcode = None def __init__(self, name, type, default = None): ValueField.__init__(self, name, default) self.type = type self.structcode = self.type.structcode self.structvalues = self.type.structvalues def parse_binary_value(self, data, display, length, format): if self.type.structcode is None: return self.type.parse_binary(data, display) else: scode = '=' + self.type.structcode slen = struct.calcsize(scode) v = struct.unpack(scode, data[:slen]) if self.type.structvalues == 1: v = v[0] if self.type.parse_value is not None: v = self.type.parse_value(v, display) return v, data[slen:] def parse_value(self, val, display): if self.type.parse_value is None: return val else: return self.type.parse_value(val, display) def pack_value(self, val): # Single-char values, we'll assume that mean an integer if self.type.structcode and len(self.type.structcode) == 1: return struct.pack('=' + self.type.structcode, val), None, None else: return self.type.pack_value(val) def check_value(self, val): if self.type.structcode is None: return val if type(val) is tuple: return val if type(val) is dict: data = val elif isinstance(val, DictWrapper): data = val._data else: raise TypeError('Object value must be tuple, dictionary or DictWrapper: %s' % val) vals = [] for f in self.type.fields: if f.name: vals.append(data[f.name]) return vals class PropertyData(ValueField): structcode = None def parse_binary_value(self, data, display, length, format): if length is None: length = len(data) // (format // 8) else: length = int(length) if format == 0: ret = None return ret, data elif format == 8: ret = (8, data[:length]) data = data[length + ((4 - length % 4) % 4):] elif format == 16: ret = (16, array(array_unsigned_codes[2], data[:2 * length])) data = data[2 * (length + length % 2):] elif format == 32: ret = (32, array(array_unsigned_codes[4], data[:4 * length])) data = data[4 * length:] if type(ret[1]) is bytes: try: ret = (ret[0], ret[1].decode('UTF-8')) except UnicodeDecodeError: pass # return as bytes return ret, data def pack_value(self, value): fmt, val = value if fmt not in (8, 16, 32): raise BadDataError('Invalid property data format %d' % fmt) if _PY3 and type(val) is str: val = val.encode('UTF-8') if type(val) is bytes: size = fmt // 8 vlen = len(val) if vlen % size: vlen = vlen - vlen % size data = val[:vlen] else: data = val dlen = vlen // size else: if type(val) is tuple: val = list(val) size = fmt // 8 data = array(array_unsigned_codes[size], val).tobytes() dlen = len(val) dl = len(data) data = data + b'\0' * ((4 - dl % 4) % 4) return data, dlen, fmt class FixedPropertyData(PropertyData): def __init__(self, name, size): PropertyData.__init__(self, name) self.size = size def parse_binary_value(self, data, display, length, format): return PropertyData.parse_binary_value(self, data, display, self.size // (format // 8), format) def pack_value(self, value): data, dlen, fmt = PropertyData.pack_value(self, value) if len(data) != self.size: raise BadDataError('Wrong data length for FixedPropertyData: %s' % (value, )) return data, dlen, fmt class ValueList(Field): structcode = None keyword_args = 1 default = 'usekeywords' def __init__(self, name, mask, pad, *fields): self.name = name self.maskcode = '=%s%dx' % (unsigned_codes[mask], pad) self.maskcodelen = struct.calcsize(self.maskcode) self.fields = [] flag = 1 for f in fields: if f.name: self.fields.append((f, flag)) flag = flag << 1 def pack_value(self, arg, keys): mask = 0 data = b'' if arg == self.default: arg = keys for field, flag in self.fields: if field.name in arg: mask = mask | flag val = arg[field.name] if field.check_value is not None: val = field.check_value(val) d = struct.pack('=' + field.structcode, val) data = data + d + b'\0' * (4 - len(d)) return struct.pack(self.maskcode, mask) + data, None, None def parse_binary_value(self, data, display, length, format): r = {} mask = int(struct.unpack(self.maskcode, data[:self.maskcodelen])[0]) data = data[self.maskcodelen:] for field, flag in self.fields: if mask & flag: if field.structcode: vals = struct.unpack('=' + field.structcode, data[:struct.calcsize('=' + field.structcode)]) if field.structvalues == 1: vals = vals[0] if field.parse_value is not None: vals = field.parse_value(vals, display) else: vals, d = field.parse_binary_value(data[:4], display, None, None) r[field.name] = vals data = data[4:] return DictWrapper(r), data class KeyboardMapping(ValueField): structcode = None def parse_binary_value(self, data, display, length, format): if length is None: dlen = len(data) else: dlen = 4 * length * format a = array(array_unsigned_codes[4], data[:dlen]) ret = [] for i in range(0, len(a), format): ret.append(a[i : i + format]) return ret, data[dlen:] def pack_value(self, value): keycodes = 0 for v in value: keycodes = max(keycodes, len(v)) a = array(array_unsigned_codes[4]) for v in value: for k in v: a.append(k) for i in range(len(v), keycodes): a.append(X.NoSymbol) return a.tobytes(), len(value), keycodes class ModifierMapping(ValueField): structcode = None def parse_binary_value(self, data, display, length, format): a = array(array_unsigned_codes[1], data[:8 * format]) ret = [] for i in range(0, 8): ret.append(a[i * format : (i + 1) * format]) return ret, data[8 * format:] def pack_value(self, value): if len(value) != 8: raise BadDataError('ModifierMapping list should have eight elements') keycodes = 0 for v in value: keycodes = max(keycodes, len(v)) a = array(array_unsigned_codes[1]) for v in value: for k in v: a.append(k) for i in range(len(v), keycodes): a.append(0) return a.tobytes(), len(value), keycodes class EventField(ValueField): structcode = None def pack_value(self, value): if not isinstance(value, Event): raise BadDataError('%s is not an Event for field %s' % (value, self.name)) return value._binary, None, None def parse_binary_value(self, data, display, length, format): from Xlib.protocol import event estruct = display.event_classes.get(_bytes_item(data[0]) & 0x7f, event.AnyEvent) return estruct(display = display, binarydata = data[:32]), data[32:] # # Objects usable for List and FixedList fields. # Struct is also usable. # class ScalarObj: def __init__(self, code): self.structcode = code self.structvalues = 1 self.parse_value = None Card8Obj = ScalarObj('B') Card16Obj = ScalarObj('H') Card32Obj = ScalarObj('L') class ResourceObj: structcode = 'L' structvalues = 1 def __init__(self, class_name): self.class_name = class_name def parse_value(self, value, display): # if not display: # return value c = display.get_resource_class(self.class_name) if c: return c(display, value) else: return value WindowObj = ResourceObj('window') ColormapObj = ResourceObj('colormap') class StrClass: structcode = None def pack_value(self, val): if type(val) is not bytes: val = val.encode('UTF-8') if _PY3: val = bytes([len(val)]) + val else: val = chr(len(val)) + val return val def parse_binary(self, data, display): slen = _bytes_item(data[0]) + 1 s = data[1:slen] try: s = s.decode('UTF-8') except UnicodeDecodeError: pass # return as bytes return s, data[slen:] Str = StrClass() class Struct: """Struct objects represents a binary data structure. It can contain both fields with static and dynamic sizes. However, all static fields must appear before all dynamic fields. Fields are represented by various subclasses of the abstract base class Field. The fields of a structure are given as arguments when instantiating a Struct object. Struct objects have two public methods: to_binary() -- build a binary representation of the structure with the values given as arguments parse_binary() -- convert a binary (string) representation into a Python dictionary or object. These functions will be generated dynamically for each Struct object to make conversion as fast as possible. They are generated the first time the methods are called. """ def __init__(self, *fields): self.fields = fields # Structures for to_binary, parse_value and parse_binary self.static_codes = '=' self.static_values = 0 self.static_fields = [] self.static_size = None self.var_fields = [] for f in self.fields: # Append structcode if there is one and we haven't # got any varsize fields yet. if f.structcode is not None: assert not self.var_fields self.static_codes = self.static_codes + f.structcode # Only store fields with values if f.structvalues > 0: self.static_fields.append(f) self.static_values = self.static_values + f.structvalues # If we have got one varsize field, all the rest must # also be varsize fields. else: self.var_fields.append(f) self.static_size = struct.calcsize(self.static_codes) if self.var_fields: self.structcode = None self.structvalues = 0 else: self.structcode = self.static_codes[1:] self.structvalues = self.static_values # These functions get called only once, as they will override # themselves with dynamically created functions in the Struct # object def to_binary(self, *varargs, **keys): """data = s.to_binary(...) Convert Python values into the binary representation. The arguments will be all value fields with names, in the order given when the Struct object was instantiated. With one exception: fields with default arguments will be last. Returns the binary representation as the string DATA. """ code = '' total_length = str(self.static_size) joins = [] args = [] defargs = [] kwarg = 0 # First pack all varfields so their lengths and formats are # available when we pack their static LengthFields and # FormatFields i = 0 for f in self.var_fields: if f.keyword_args: kwarg = 1 kw = ', _keyword_args' else: kw = '' # Call pack_value method for each field, storing # the return values for later use code = code + (' _%(name)s, _%(name)s_length, _%(name)s_format' ' = self.var_fields[%(fno)d].pack_value(%(name)s%(kw)s)\n' % { 'name': f.name, 'fno': i, 'kw': kw }) total_length = total_length + ' + len(_%s)' % f.name joins.append('_%s' % f.name) i = i + 1 # Construct argument list for struct.pack call, packing all # static fields. First argument is the structcode, the # remaining are values. pack_args = ['"%s"' % self.static_codes] i = 0 for f in self.static_fields: if isinstance(f, LengthField): # If this is a total length field, insert # the calculated field value here if isinstance(f, TotalLengthField): if self.var_fields: pack_args.append('self.static_fields[%d].calc_length(%s)' % (i, total_length)) else: pack_args.append(str(f.calc_length(self.static_size))) else: pack_args.append('self.static_fields[%d].calc_length(_%s_length)' % (i, f.name)) # Format field, just insert the value we got previously elif isinstance(f, FormatField): pack_args.append('_%s_format' % f.name) # A constant field, insert its value directly elif isinstance(f, ConstantField): pack_args.append(str(f.value)) # Value fields else: if f.structvalues == 1: # If there's a value check/convert function, call it if f.check_value is not None: pack_args.append('self.static_fields[%d].check_value(%s)' % (i, f.name)) # Else just use the argument as provided else: pack_args.append(f.name) # Multivalue field. Handled like single valuefield, # but the value are tuple unpacked into seperate arguments # which are appended to pack_args else: a = [] for j in range(f.structvalues): a.append('_%s_%d' % (f.name, j)) if f.check_value is not None: code = code + (' %s = self.static_fields[%d].check_value(%s)\n' % (', '.join(a), i, f.name)) else: code = code + ' %s = %s\n' % (', '.join(a), f.name) pack_args = pack_args + a # Add field to argument list if f.name: if f.default is None: args.append(f.name) else: defargs.append('%s = %s' % (f.name, repr(f.default))) i = i + 1 # Construct call to struct.pack pack = 'struct.pack(%s)' % ', '.join(pack_args) # If there are any varfields, we append the packed strings to build # the resulting binary value if self.var_fields: code = code + ' return %s + %s\n' % (pack, ' + '.join(joins)) # If there's only static fields, return the packed value else: code = code + ' return %s\n' % pack # Add all varsize fields to argument list. We do it here # to ensure that they appear after the static fields. for f in self.var_fields: if f.name: if f.default is None: args.append(f.name) else: defargs.append('%s = %s' % (f.name, repr(f.default))) args = args + defargs if kwarg: args.append('**_keyword_args') # Add function header code = 'def to_binary(self, %s):\n' % ', '.join(args) + code # self._pack_code = code # print # print code # print # Finally, compile function by evaluating it. This will store # the function in the local variable to_binary, thanks to the # def: line. Convert it into a instance metod bound to self, # and store it in self. # Unfortunately, this creates a circular reference. However, # Structs are not really created dynamically so the potential # memory leak isn't that serious. Besides, Python 2.0 has # real garbage collect. g = globals().copy() exec(code, g) self.to_binary = types.MethodType(g['to_binary'], self) # Finally call it manually return self.to_binary(*varargs, **keys) def pack_value(self, value): """ This function allows Struct objects to be used in List and Object fields. Each item represents the arguments to pass to to_binary, either a tuple, a dictionary or a DictWrapper. """ if type(value) is tuple: return self.to_binary(*value, **{}) elif type(value) is dict: return self.to_binary(*(), **value) elif isinstance(value, DictWrapper): return self.to_binary(*(), **value._data) else: raise BadDataError('%s is not a tuple or a list' % (value)) def parse_value(self, val, display, rawdict = 0): """This function is used by List and Object fields to convert Struct objects with no var_fields into Python values. """ code = ('def parse_value(self, val, display, rawdict = 0):\n' ' ret = {}\n') vno = 0 fno = 0 for f in self.static_fields: # Fields without names should be ignored, and there should # not be any length or format fields if this function # ever gets called. (If there were such fields, there should # be a matching field in var_fields and then parse_binary # would have been called instead. if not f.name: pass elif isinstance(f, LengthField): pass elif isinstance(f, FormatField): pass # Value fields else: # Get the index or range in val representing this field. if f.structvalues == 1: vrange = str(vno) else: vrange = '%d:%d' % (vno, vno + f.structvalues) # If this field has a parse_value method, call it, otherwise # use the unpacked value as is. if f.parse_value is None: code = code + ' ret["%s"] = val[%s]\n' % (f.name, vrange) else: code = code + (' ret["%s"] = self.static_fields[%d].' 'parse_value(val[%s], display)\n' % (f.name, fno, vrange)) fno = fno + 1 vno = vno + f.structvalues code = code + ' if not rawdict: return DictWrapper(ret)\n' code = code + ' return ret\n' # print # print code # print # Finally, compile function as for to_binary. g = globals().copy() exec(code, g) self.parse_value = types.MethodType(g['parse_value'], self) # Call it manually return self.parse_value(val, display, rawdict) def parse_binary(self, data, display, rawdict = 0): """values, remdata = s.parse_binary(data, display, rawdict = 0) Convert a binary representation of the structure into Python values. DATA is a string or a buffer containing the binary data. DISPLAY should be a Xlib.protocol.display.Display object if there are any Resource fields or Lists with ResourceObjs. The Python values are returned as VALUES. If RAWDICT is true, a Python dictionary is returned, where the keys are field names and the values are the corresponding Python value. If RAWDICT is false, a DictWrapper will be returned where all fields are available as attributes. REMDATA are the remaining binary data, unused by the Struct object. """ code = ('def parse_binary(self, data, display, rawdict = 0):\n' ' ret = {}\n' ' val = struct.unpack("%s", data[:%d])\n' % (self.static_codes, self.static_size)) lengths = {} formats = {} vno = 0 fno = 0 for f in self.static_fields: # Fields without name should be ignored. This is typically # pad and constant fields if not f.name: pass # Store index in val for Length and Format fields, to be used # when treating varfields. elif isinstance(f, LengthField): if f.parse_value is None: lengths[f.name] = 'val[%d]' % vno else: lengths[f.name] = ('self.static_fields[%d].' 'parse_value(val[%d], display)' % (fno, vno)) elif isinstance(f, FormatField): formats[f.name] = 'val[%d]' % vno # Treat value fields the same was as in parse_value. else: if f.structvalues == 1: vrange = str(vno) else: vrange = '%d:%d' % (vno, vno + f.structvalues) if f.parse_value is None: code = code + ' ret["%s"] = val[%s]\n' % (f.name, vrange) else: code = code + (' ret["%s"] = self.static_fields[%d].' 'parse_value(val[%s], display)\n' % (f.name, fno, vrange)) fno = fno + 1 vno = vno + f.structvalues code = code + ' data = data[%d:]\n' % self.static_size # Call parse_binary_value for each var_field, passing the # length and format values from the unpacked val. fno = 0 for f in self.var_fields: code = code + (' ret["%s"], data = ' 'self.var_fields[%d].parse_binary_value' '(data, display, %s, %s)\n' % (f.name, fno, lengths.get(f.name, 'None'), formats.get(f.name, 'None'))) fno = fno + 1 code = code + ' if not rawdict: ret = DictWrapper(ret)\n' code = code + ' return ret, data\n' # print # print code # print # Finally, compile function as for to_binary. g = globals().copy() exec(code, g) self.parse_binary = types.MethodType(g['parse_binary'], self) # Call it manually return self.parse_binary(data, display, rawdict) class TextElements8(ValueField): string_textitem = Struct( LengthOf('string', 1), Int8('delta'), String8('string', pad = 0) ) def pack_value(self, value): data = b'' args = {} for v in value: # Let values be simple strings, meaning a delta of 0 if _PY3 and type(v) is str: v = v.encode('UTF-8') if type(v) is bytes: v = (0, v) # A tuple, it should be (delta, string) # Encode it as one or more textitems if type(v) in (tuple, dict) or \ isinstance(v, DictWrapper): if type(v) is tuple: delta, s = v else: delta = v['delta'] s = v['string'] while delta or s: args['delta'] = delta args['string'] = s[:254] data = data + self.string_textitem.to_binary(*(), **args) delta = 0 s = s[254:] # Else an integer, i.e. a font change else: # Use fontable cast function if instance if hasattr(v, '__fontable__'): v = v.__fontable__() data = data + struct.pack('>BL', 255, v) # Pad out to four byte length dlen = len(data) return data + b'\0' * ((4 - dlen % 4) % 4), None, None def parse_binary_value(self, data, display, length, format): values = [] while 1: if len(data) < 2: break # font change if _bytes_item(data[0]) == 255: values.append(struct.unpack('>L', data[1:5])[0]) data = data[5:] # skip null strings elif _bytes_item(data[0]) == 0 and _bytes_item(data[1]) == 0: data = data[2:] # string with delta else: v, data = self.string_textitem.parse_binary(data, display) values.append(v) return values, b'' class TextElements16(TextElements8): string_textitem = Struct( LengthOf('string', 1), Int8('delta'), String16('string', pad = 0) ) class GetAttrData(object): def __getattr__(self, attr): try: if self._data: return self._data[attr] else: raise AttributeError(attr) except KeyError: raise AttributeError(attr) class DictWrapper(GetAttrData): def __init__(self, dict): self.__dict__['_data'] = dict def __getitem__(self, key): return self._data[key] def __setitem__(self, key, value): self._data[key] = value def __delitem__(self, key): del self._data[key] def __setattr__(self, key, value): self._data[key] = value def __delattr__(self, key): del self._data[key] def __str__(self): return str(self._data) def __repr__(self): return '%s(%s)' % (self.__class__, repr(self._data)) def __eq__(self, other): if isinstance(other, DictWrapper): return self._data == other._data else: return self._data == other def __ne__(self, other): return not self.__eq__(other) class Request: def __init__(self, display, onerror = None, *args, **keys): self._errorhandler = onerror self._binary = self._request.to_binary(*args, **keys) self._serial = None display.send_request(self, onerror is not None) def _set_error(self, error): if self._errorhandler is not None: return call_error_handler(self._errorhandler, error, self) else: return 0 class ReplyRequest(GetAttrData): def __init__(self, display, defer = 0, *args, **keys): self._display = display self._binary = self._request.to_binary(*args, **keys) self._serial = None self._data = None self._error = None self._response_lock = lock.allocate_lock() self._display.send_request(self, 1) if not defer: self.reply() def reply(self): # Send request and wait for reply if we hasn't # already got one. This means that reply() can safely # be called more than one time. self._response_lock.acquire() while self._data is None and self._error is None: self._display.send_recv_lock.acquire() self._response_lock.release() self._display.send_and_recv(request = self._serial) self._response_lock.acquire() self._response_lock.release() self._display = None # If error has been set, raise it if self._error: raise self._error def _parse_response(self, data): self._response_lock.acquire() self._data, d = self._reply.parse_binary(data, self._display, rawdict = 1) self._response_lock.release() def _set_error(self, error): self._response_lock.acquire() self._error = error self._response_lock.release() return 1 def __repr__(self): return '<%s serial = %s, data = %s, error = %s>' % (self.__class__, self._serial, self._data, self._error) class Event(GetAttrData): def __init__(self, binarydata = None, display = None, **keys): if binarydata: self._binary = binarydata self._data, data = self._fields.parse_binary(binarydata, display, rawdict = 1) # split event type into type and send_event bit self._data['send_event'] = not not self._data['type'] & 0x80 self._data['type'] = self._data['type'] & 0x7f else: if self._code: keys['type'] = self._code keys['sequence_number'] = 0 self._binary = self._fields.to_binary(*(), **keys) keys['send_event'] = 0 self._data = keys def __repr__(self): kwlist = [] for kw, val in self._data.items(): if kw == 'send_event': continue if kw == 'type' and self._data['send_event']: val = val | 0x80 kwlist.append('%s = %s' % (kw, repr(val))) kws = ', '.join(kwlist) return '%s(%s)' % (self.__class__, kws) def __eq__(self, other): if isinstance(other, Event): return self._data == other._data else: return cmp(self._data, other) def call_error_handler(handler, error, request): try: return handler(error, request) except: sys.stderr.write('Exception raised by error handler.\n') traceback.print_exc() return 0 Nagstamon/Nagstamon/thirdparty/Xlib/protocol/structs.py000066400000000000000000000123371316117564000237600ustar00rootroot00000000000000# Xlib.protocol.structs -- some common request structures # # Copyright (C) 2000 Peter Liljenberg # # 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 # Xlib modules from Xlib import X # Xlib.protocol modules from Xlib.protocol import rq def WindowValues(arg): return rq.ValueList( arg, 4, 0, rq.Pixmap('background_pixmap'), rq.Card32('background_pixel'), rq.Pixmap('border_pixmap'), rq.Card32('border_pixel'), rq.Gravity('bit_gravity'), rq.Gravity('win_gravity'), rq.Set('backing_store', 1, (X.NotUseful, X.WhenMapped, X.Always)), rq.Card32('backing_planes'), rq.Card32('backing_pixel'), rq.Bool('override_redirect'), rq.Bool('save_under'), rq.Card32('event_mask'), rq.Card32('do_not_propagate_mask'), rq.Colormap('colormap'), rq.Cursor('cursor'), ) def GCValues(arg): return rq.ValueList( arg, 4, 0, rq.Set('function', 1, (X.GXclear, X.GXand, X.GXandReverse, X.GXcopy, X.GXandInverted, X.GXnoop, X.GXxor, X.GXor, X.GXnor, X.GXequiv, X.GXinvert, X.GXorReverse, X.GXcopyInverted, X.GXorInverted, X.GXnand, X.GXset)), rq.Card32('plane_mask'), rq.Card32('foreground'), rq.Card32('background'), rq.Card16('line_width'), rq.Set('line_style', 1, (X.LineSolid, X.LineOnOffDash, X.LineDoubleDash)), rq.Set('cap_style', 1, (X.CapNotLast, X.CapButt, X.CapRound, X.CapProjecting)), rq.Set('join_style', 1, (X.JoinMiter, X.JoinRound, X.JoinBevel)), rq.Set('fill_style', 1, (X.FillSolid, X.FillTiled, X.FillStippled, X.FillOpaqueStippled)), rq.Set('fill_rule', 1, (X.EvenOddRule, X.WindingRule)), rq.Pixmap('tile'), rq.Pixmap('stipple'), rq.Int16('tile_stipple_x_origin'), rq.Int16('tile_stipple_y_origin'), rq.Font('font'), rq.Set('subwindow_mode', 1, (X.ClipByChildren, X.IncludeInferiors)), rq.Bool('graphics_exposures'), rq.Int16('clip_x_origin'), rq.Int16('clip_y_origin'), rq.Pixmap('clip_mask'), rq.Card16('dash_offset'), rq.Card8('dashes'), rq.Set('arc_mode', 1, (X.ArcChord, X.ArcPieSlice)) ) TimeCoord = rq.Struct( rq.Card32('time'), rq.Int16('x'), rq.Int16('y'), ) Host = rq.Struct( rq.Set('family', 1, (X.FamilyInternet, X.FamilyDECnet, X.FamilyChaos)), rq.Pad(1), rq.LengthOf('name', 2), rq.List('name', rq.Card8Obj) ) CharInfo = rq.Struct( rq.Int16('left_side_bearing'), rq.Int16('right_side_bearing'), rq.Int16('character_width'), rq.Int16('ascent'), rq.Int16('descent'), rq.Card16('attributes'), ) FontProp = rq.Struct( rq.Card32('name'), rq.Card32('value'), ) ColorItem = rq.Struct( rq.Card32('pixel'), rq.Card16('red'), rq.Card16('green'), rq.Card16('blue'), rq.Card8('flags'), rq.Pad(1), ) RGB = rq.Struct( rq.Card16('red'), rq.Card16('green'), rq.Card16('blue'), rq.Pad(2), ) Point = rq.Struct( rq.Int16('x'), rq.Int16('y'), ) Segment = rq.Struct( rq.Int16('x1'), rq.Int16('y1'), rq.Int16('x2'), rq.Int16('y2'), ) Rectangle = rq.Struct( rq.Int16('x'), rq.Int16('y'), rq.Card16('width'), rq.Card16('height'), ) Arc = rq.Struct( rq.Int16('x'), rq.Int16('y'), rq.Card16('width'), rq.Card16('height'), rq.Int16('angle1'), rq.Int16('angle2'), ) Nagstamon/Nagstamon/thirdparty/Xlib/rdb.py000066400000000000000000000466731316117564000211710ustar00rootroot00000000000000# Xlib.rdb -- X resource database implementation # # Copyright (C) 2000 Peter Liljenberg # # 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 # See end of file for an explanation of the algorithm and # data structures used. # Standard modules import functools import re import sys # Xlib modules from Xlib.support import lock # Set up a few regexpes for parsing string representation of resources comment_re = re.compile(r'^\s*!') resource_spec_re = re.compile(r'^\s*([-_a-zA-Z0-9?.*]+)\s*:\s*(.*)$') value_escape_re = re.compile('\\\\([ \tn\\\\]|[0-7]{3,3})') resource_parts_re = re.compile(r'([.*]+)') # Constants used for determining which match is best NAME_MATCH = 0 CLASS_MATCH = 2 WILD_MATCH = 4 MATCH_SKIP = 6 # Option error class class OptionError(Exception): pass class ResourceDB: def __init__(self, file = None, string = None, resources = None): self.db = {} self.lock = lock.allocate_lock() if file is not None: self.insert_file(file) if string is not None: self.insert_string(string) if resources is not None: self.insert_resources(resources) def insert_file(self, file): """insert_file(file) Load resources entries from FILE, and insert them into the database. FILE can be a filename (a string)or a file object. """ if type(file) is str: file = open(file, 'r') self.insert_string(file.read()) def insert_string(self, data): """insert_string(data) Insert the resources entries in the string DATA into the database. """ # First split string into lines lines = data.split('\n') while lines: line = lines[0] del lines[0] # Skip empty line if not line: continue # Skip comments if comment_re.match(line): continue # Handle continued lines while line[-1] == '\\': if lines: line = line[:-1] + lines[0] del lines[0] else: line = line[:-1] break # Split line into resource and value m = resource_spec_re.match(line) # Bad line, just ignore it silently if not m: continue res, value = m.group(1, 2) # Convert all escape sequences in value splits = value_escape_re.split(value) for i in range(1, len(splits), 2): s = splits[i] if len(s) == 3: splits[i] = chr(int(s, 8)) elif s == 'n': splits[i] = '\n' # strip the last value part to get rid of any # unescaped blanks splits[-1] = splits[-1].rstrip() value = ''.join(splits) self.insert(res, value) def insert_resources(self, resources): """insert_resources(resources) Insert all resources entries in the list RESOURCES into the database. Each element in RESOURCES should be a tuple: (resource, value) Where RESOURCE is a string and VALUE can be any Python value. """ for res, value in resources: self.insert(res, value) def insert(self, resource, value): """insert(resource, value) Insert a resource entry into the database. RESOURCE is a string and VALUE can be any Python value. """ # Split res into components and bindings parts = resource_parts_re.split(resource) # If the last part is empty, this is an invalid resource # which we simply ignore if parts[-1] == '': return self.lock.acquire() db = self.db for i in range(1, len(parts), 2): # Create a new mapping/value group if parts[i - 1] not in db: db[parts[i - 1]] = ({}, {}) # Use second mapping if a loose binding, first otherwise if '*' in parts[i]: db = db[parts[i - 1]][1] else: db = db[parts[i - 1]][0] # Insert value into the derived db if parts[-1] in db: db[parts[-1]] = db[parts[-1]][:2] + (value, ) else: db[parts[-1]] = ({}, {}, value) self.lock.release() def __getitem__(self, nc): """db[name, class] Return the value matching the resource identified by NAME and CLASS. If no match is found, KeyError is raised. """ name, cls = nc # Split name and class into their parts namep = name.split('.') clsp = cls.split('.') # It is an error for name and class to have different number # of parts if len(namep) != len(clsp): raise ValueError('Different number of parts in resource name/class: %s/%s' % (name, cls)) complen = len(namep) matches = [] # Lock database and wrap the lookup code in a try-finally # block to make sure that it is unlocked. self.lock.acquire() try: # Precedence order: name -> class -> ? if namep[0] in self.db: bin_insert(matches, _Match((NAME_MATCH, ), self.db[namep[0]])) if clsp[0] in self.db: bin_insert(matches, _Match((CLASS_MATCH, ), self.db[clsp[0]])) if '?' in self.db: bin_insert(matches, _Match((WILD_MATCH, ), self.db['?'])) # Special case for the unlikely event that the resource # only has one component if complen == 1 and matches: x = matches[0] if x.final(complen): return x.value() else: raise KeyError((name, cls)) # Special case for resources which begins with a loose # binding, e.g. '*foo.bar' if '' in self.db: bin_insert(matches, _Match((), self.db[''][1])) # Now iterate over all components until we find the best match. # For each component, we choose the best partial match among # the mappings by applying these rules in order: # Rule 1: If the current group contains a match for the # name, class or '?', we drop all previously found loose # binding mappings. # Rule 2: A matching name has precedence over a matching # class, which in turn has precedence over '?'. # Rule 3: Tight bindings have precedence over loose # bindings. while matches: # Work on the first element == the best current match x = matches[0] del matches[0] # print 'path: ', x.path # if x.skip: # print 'skip: ', x.db # else: # print 'group: ', x.group # print i = x.match_length() for part, score in ((namep[i], NAME_MATCH), (clsp[i], CLASS_MATCH), ('?', WILD_MATCH)): # Attempt to find a match in x match = x.match(part, score) if match: # Hey, we actually found a value! if match.final(complen): return match.value() # Else just insert the new match else: bin_insert(matches, match) # Generate a new loose match match = x.skip_match(complen) if match: bin_insert(matches, match) # Oh well, nothing matched raise KeyError((name, cls)) finally: self.lock.release() def get(self, res, cls, default = None): """get(name, class [, default]) Return the value matching the resource identified by NAME and CLASS. If no match is found, DEFAULT is returned, or None if DEFAULT isn't specified. """ try: return self[(res, cls)] except KeyError: return default def update(self, db): """update(db) Update this database with all resources entries in the resource database DB. """ self.lock.acquire() update_db(self.db, db.db) self.lock.release() def output(self): """output() Return the resource database in text representation. """ self.lock.acquire() text = output_db('', self.db) self.lock.release() return text def getopt(self, name, argv, opts): """getopt(name, argv, opts) Parse X command line options, inserting the recognised options into the resource database. NAME is the application name, and will be prepended to all specifiers. ARGV is the list of command line arguments, typically sys.argv[1:]. OPTS is a mapping of options to resource specifiers. The key is the option flag (with leading -), and the value is an instance of some Option subclass: NoArg(specifier, value): set resource to value. IsArg(specifier): set resource to option itself SepArg(specifier): value is next argument ResArg: resource and value in next argument SkipArg: ignore this option and next argument SkipLine: ignore rest of arguments SkipNArgs(count): ignore this option and count arguments The remaining, non-option, oparguments is returned. rdb.OptionError is raised if there is an error in the argument list. """ while argv and argv[0] and argv[0][0] == '-': try: argv = opts[argv[0]].parse(name, self, argv) except KeyError: raise OptionError('unknown option: %s' % argv[0]) except IndexError: raise OptionError('missing argument to option: %s' % argv[0]) return argv @functools.total_ordering class _Match: def __init__(self, path, dbs): self.path = path if type(dbs) is tuple: self.skip = 0 self.group = dbs else: self.skip = 1 self.db = dbs def __eq__(self, other): return self.path == other.path def __lt__(self, other): return self.path < other.path def match_length(self): return len(self.path) def match(self, part, score): if self.skip: if part in self.db: return _Match(self.path + (score, ), self.db[part]) else: return None else: if part in self.group[0]: return _Match(self.path + (score, ), self.group[0][part]) elif part in self.group[1]: return _Match(self.path + (score + 1, ), self.group[1][part]) else: return None def skip_match(self, complen): # Can't make another skip if we have run out of components if len(self.path) + 1 >= complen: return None # If this already is a skip match, clone a new one if self.skip: if self.db: return _Match(self.path + (MATCH_SKIP, ), self.db) else: return None # Only generate a skip match if the loose binding mapping # is non-empty elif self.group[1]: return _Match(self.path + (MATCH_SKIP, ), self.group[1]) # This is a dead end match else: return None def final(self, complen): if not self.skip and len(self.path) == complen and len(self.group) > 2: return 1 else: return 0 def value(self): return self.group[2] # # Helper function for ResourceDB.__getitem__() # def bin_insert(list, element): """bin_insert(list, element) Insert ELEMENT into LIST. LIST must be sorted, and ELEMENT will be inserted to that LIST remains sorted. If LIST already contains ELEMENT, it will not be duplicated. """ if not list: list.append(element) return lower = 0 upper = len(list) - 1 while lower <= upper: center = (lower + upper) // 2 if element < list[center]: upper = center - 1 elif element > list[center]: lower = center + 1 elif element == list[center]: return if element < list[upper]: list.insert(upper, element) elif element > list[upper]: list.insert(upper + 1, element) # # Helper functions for ResourceDB.update() # def update_db(dest, src): for comp, group in src.items(): # DEST already contains this component, update it if comp in dest: # Update tight and loose binding databases update_db(dest[comp][0], group[0]) update_db(dest[comp][1], group[1]) # If a value has been set in SRC, update # value in DEST if len(group) > 2: dest[comp] = dest[comp][:2] + group[2:] # COMP not in src, make a deep copy else: dest[comp] = copy_group(group) def copy_group(group): return (copy_db(group[0]), copy_db(group[1])) + group[2:] def copy_db(db): newdb = {} for comp, group in db.items(): newdb[comp] = copy_group(group) return newdb # # Helper functions for output # def output_db(prefix, db): res = '' for comp, group in db.items(): # There's a value for this component if len(group) > 2: res = res + '%s%s: %s\n' % (prefix, comp, output_escape(group[2])) # Output tight and loose bindings res = res + output_db(prefix + comp + '.', group[0]) res = res + output_db(prefix + comp + '*', group[1]) return res def output_escape(value): value = str(value) if not value: return value for char, esc in (('\\', '\\\\'), ('\000', '\\000'), ('\n', '\\n')): value = value.replace(char, esc) # If first or last character is space or tab, escape them. if value[0] in ' \t': value = '\\' + value if value[-1] in ' \t' and value[-2:-1] != '\\': value = value[:-1] + '\\' + value[-1] return value # # Option type definitions # class Option: def __init__(self): pass def parse(self, name, db, args): pass class NoArg(Option): """Value is provided to constructor.""" def __init__(self, specifier, value): self.specifier = specifier self.value = value def parse(self, name, db, args): db.insert(name + self.specifier, self.value) return args[1:] class IsArg(Option): """Value is the option string itself.""" def __init__(self, specifier): self.specifier = specifier def parse(self, name, db, args): db.insert(name + self.specifier, args[0]) return args[1:] class SepArg(Option): """Value is the next argument.""" def __init__(self, specifier): self.specifier = specifier def parse(self, name, db, args): db.insert(name + self.specifier, args[1]) return args[2:] class ResArgClass(Option): """Resource and value in the next argument.""" def parse(self, name, db, args): db.insert_string(args[1]) return args[2:] ResArg = ResArgClass() class SkipArgClass(Option): """Ignore this option and next argument.""" def parse(self, name, db, args): return args[2:] SkipArg = SkipArgClass() class SkipLineClass(Option): """Ignore rest of the arguments.""" def parse(self, name, db, args): return [] SkipLine = SkipLineClass() class SkipNArgs(Option): """Ignore this option and the next COUNT arguments.""" def __init__(self, count): self.count = count def parse(self, name, db, args): return args[1 + self.count:] def get_display_opts(options, argv = sys.argv): """display, name, db, args = get_display_opts(options, [argv]) Parse X OPTIONS from ARGV (or sys.argv if not provided). Connect to the display specified by a *.display resource if one is set, or to the default X display otherwise. Extract the RESOURCE_MANAGER property and insert all resources from ARGV. The four return values are: DISPLAY -- the display object NAME -- the application name (the filname of ARGV[0]) DB -- the created resource database ARGS -- any remaining arguments """ from Xlib import display, Xatom import os name = os.path.splitext(os.path.basename(argv[0]))[0] optdb = ResourceDB() leftargv = optdb.getopt(name, argv[1:], options) dname = optdb.get(name + '.display', name + '.Display', None) d = display.Display(dname) rdbstring = d.screen(0).root.get_full_property(Xatom.RESOURCE_MANAGER, Xatom.STRING) if rdbstring: data = rdbstring.value else: data = None db = ResourceDB(string = data) db.update(optdb) return d, name, db, leftargv # Common X options stdopts = {'-bg': SepArg('*background'), '-background': SepArg('*background'), '-fg': SepArg('*foreground'), '-foreground': SepArg('*foreground'), '-fn': SepArg('*font'), '-font': SepArg('*font'), '-name': SepArg('.name'), '-title': SepArg('.title'), '-synchronous': NoArg('*synchronous', 'on'), '-xrm': ResArg, '-display': SepArg('.display'), '-d': SepArg('.display'), } # Notes on the implementation: # Resource names are split into their components, and each component # is stored in a mapping. The value for a component is a tuple of two # or three elements: # (tightmapping, loosemapping [, value]) # tightmapping contains the next components which are connected with a # tight binding (.). loosemapping contains the ones connected with # loose binding (*). If value is present, then this component is the # last component for some resource which that value. # The top level components are stored in the mapping r.db, where r is # the resource object. # Example: Inserting "foo.bar*gazonk: yep" into an otherwise empty # resource database would give the folliwing structure: # { 'foo': ( { 'bar': ( { }, # { 'gazonk': ( { }, # { }, # 'yep') # } # ) # }, # {}) # } Nagstamon/Nagstamon/thirdparty/Xlib/support/000077500000000000000000000000001316117564000215445ustar00rootroot00000000000000Nagstamon/Nagstamon/thirdparty/Xlib/support/__init__.py000066400000000000000000000017011316117564000236540ustar00rootroot00000000000000# Xlib.support.__init__ -- support code package # # Copyright (C) 2000 Peter Liljenberg # # 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 __all__ = [ 'lock', 'connect' # The platform specific modules should not be listed here ] Nagstamon/Nagstamon/thirdparty/Xlib/support/connect.py000066400000000000000000000054201316117564000235500ustar00rootroot00000000000000# Xlib.support.connect -- OS-independent display connection functions # # Copyright (C) 2000 Peter Liljenberg # # 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 import sys # List the modules which contain the corresponding functions _display_mods = { 'OpenVMS': 'vms_connect', } _default_display_mod = 'unix_connect' _socket_mods = { 'OpenVMS': 'vms_connect' } _default_socket_mod = 'unix_connect' _auth_mods = { 'OpenVMS': 'vms_connect' } _default_auth_mod = 'unix_connect' # Figure out which OS we're using. # sys.platform is either "OS-ARCH" or just "OS". platform = sys.platform.split('-')[0] def get_display(display): """dname, host, dno, screen = get_display(display) Parse DISPLAY into its components. If DISPLAY is None, use the default display. The return values are: DNAME -- the full display name (string) HOST -- the host name (string, possibly empty) DNO -- display number (integer) SCREEN -- default screen number (integer) """ modname = _display_mods.get(platform, _default_display_mod) mod = __import__(modname, globals(),level=1) return mod.get_display(display) def get_socket(dname, host, dno): """socket = get_socket(dname, host, dno) Connect to the display specified by DNAME, HOST and DNO, which are the corresponding values from a previous call to get_display(). Return SOCKET, a new socket object connected to the X server. """ modname = _socket_mods.get(platform, _default_socket_mod) mod = __import__(modname, globals(),level=1) return mod.get_socket(dname, host, dno) def get_auth(sock, dname, host, dno): """auth_name, auth_data = get_auth(sock, dname, host, dno) Return authentication data for the display on the other side of SOCK, which was opened with DNAME, HOST and DNO. Return AUTH_NAME and AUTH_DATA, two strings to be used in the connection setup request. """ modname = _auth_mods.get(platform, _default_auth_mod) mod = __import__(modname, globals(),level=1) return mod.get_auth(sock, dname, host, dno) Nagstamon/Nagstamon/thirdparty/Xlib/support/lock.py000066400000000000000000000030041316117564000230430ustar00rootroot00000000000000# Xlib.support.lock -- allocate a lock # # Copyright (C) 2000 Peter Liljenberg # # 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 class _DummyLock: def __init__(self): # This might be nerdy, but by assigning methods like this # instead of defining them all, we create a single bound # method object once instead of one each time one of the # methods is called. # This gives some speed improvements which should reduce the # impact of the threading infrastructure in the regular code, # when not using threading. self.acquire = self.release = self.locked = self.__noop def __noop(self, *args): return # More optimisations: we use a single lock for all lock instances _dummy_lock = _DummyLock() def allocate_lock(): return _dummy_lock Nagstamon/Nagstamon/thirdparty/Xlib/support/unix_connect.py000066400000000000000000000120561316117564000246160ustar00rootroot00000000000000# Xlib.support.unix_connect -- Unix-type display connection functions # # Copyright (C) 2000,2002 Peter Liljenberg # Copyright (C) 2013 LiuLang # # 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 import fcntl import os import platform import re import socket F_SETFD = fcntl.F_SETFD FD_CLOEXEC = fcntl.FD_CLOEXEC from Xlib import error, xauth uname = platform.uname() if (uname[0] == 'Darwin') and ([int(x) for x in uname[2].split('.')] >= [9, 0]): display_re = re.compile(r'^([-a-zA-Z0-9._/]*):([0-9]+)(\.([0-9]+))?$') else: display_re = re.compile(r'^([-a-zA-Z0-9._]*):([0-9]+)(\.([0-9]+))?$') def get_display(display): # Use $DISPLAY if display isn't provided if display is None: display = os.environ.get('DISPLAY', '') m = display_re.match(display) if not m: raise error.DisplayNameError(display) name = display host = m.group(1) if host == 'unix': host = '' dno = int(m.group(2)) screen = m.group(4) if screen: screen = int(screen) else: screen = 0 return name, host, dno, screen def get_socket(dname, host, dno): try: # Darwin funky socket if (uname[0] == 'Darwin') and host and host.startswith('/tmp/'): s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.connect(dname) # If hostname (or IP) is provided, use TCP socket elif host: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, 6000 + dno)) # Else use Unix socket else: s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.connect('/tmp/.X11-unix/X%d' % dno) except OSError as val: raise error.DisplayConnectionError(dname, str(val)) # Make sure that the connection isn't inherited in child processes fcntl.fcntl(s.fileno(), F_SETFD, FD_CLOEXEC) return s def new_get_auth(sock, dname, host, dno): # Translate socket address into the xauth domain if (uname[0] == 'Darwin') and host and host.startswith('/tmp/'): family = xauth.FamilyLocal addr = socket.gethostname() elif host: family = xauth.FamilyInternet # Convert the prettyprinted IP number into 4-octet string. # Sometimes these modules are too damn smart... octets = sock.getpeername()[0].split('.') addr = ''.join(map(lambda x: chr(int(x)), octets)) else: family = xauth.FamilyLocal addr = socket.gethostname() au = xauth.Xauthority() while 1: try: return au.get_best_auth(family, addr, dno) except error.XNoAuthError: pass # We need to do this to handle ssh's X forwarding. It sets # $DISPLAY to localhost:10, but stores the xauth cookie as if # DISPLAY was :10. Hence, if localhost and not found, try # again as a Unix socket. if family == xauth.FamilyInternet and addr == '\x7f\x00\x00\x01': family = xauth.FamilyLocal addr = socket.gethostname() else: return '', '' def old_get_auth(sock, dname, host, dno): # Find authorization cookie auth_name = auth_data = '' try: # We could parse .Xauthority, but xauth is simpler # although more inefficient data = os.popen('xauth list %s 2>/dev/null' % dname).read() # If there's a cookie, it is of the format # DISPLAY SCHEME COOKIE # We're interested in the two last parts for the # connection establishment lines = data.split('\n') if len(lines) >= 1: parts = lines[0].split(None, 2) if len(parts) == 3: auth_name = parts[1] hexauth = parts[2] auth = '' # Translate hexcode into binary for i in range(0, len(hexauth), 2): auth = auth + chr(int(hexauth[i:i+2], 16)) auth_data = auth except os.error: pass if not auth_data and host == 'localhost': # 127.0.0.1 counts as FamilyLocal, not FamilyInternet # See Xtransutil.c:ConvertAddress. # There might be more ways to spell 127.0.0.1 but # 'localhost', yet this code fixes a the case of # OpenSSH tunneling X. return get_auth('unix:%d' % dno, 'unix', dno) return auth_name, auth_data get_auth = new_get_auth Nagstamon/Nagstamon/thirdparty/Xlib/support/vms_connect.py000066400000000000000000000040151316117564000244340ustar00rootroot00000000000000# Xlib.support.vms_connect -- VMS-type display connection functions # # Copyright (C) 2000 Peter Liljenberg # # 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 import re import socket from Xlib import error display_re = re.compile(r'^([-a-zA-Z0-9._]*):([0-9]+)(\.([0-9]+))?$') def get_display(display): # Use dummy display if none is set. We really should # check DECW$DISPLAY instead, but that has to wait if display is None: return ':0.0', 'localhost', 0, 0 m = display_re.match(display) if not m: raise error.DisplayNameError(display) name = display # Always return a host, since we don't have AF_UNIX sockets host = m.group(1) if not host: host = 'localhost' dno = int(m.group(2)) screen = m.group(4) if screen: screen = int(screen) else: screen = 0 return name, host, dno, screen def get_socket(dname, host, dno): try: # Always use TCP/IP sockets. Later it would be nice to # be able to use DECNET och LOCAL connections. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, 6000 + dno)) except OSError as val: raise error.DisplayConnectionError(dname, str(val)) return s def get_auth(sock, dname, host, dno): # VMS doesn't have xauth return '', '' Nagstamon/Nagstamon/thirdparty/Xlib/threaded.py000066400000000000000000000022201316117564000221560ustar00rootroot00000000000000# Xlib.threaded -- Import this module to enable threading # # Copyright (C) 2000 Peter Liljenberg # # 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 try: # Python 3 import _thread as thread except ImportError: # Python 2 import thread # We change the allocate_lock function in Xlib.support.lock to # return a basic Python lock, instead of the default dummy lock from Xlib.support import lock lock.allocate_lock = thread.allocate_lock Nagstamon/Nagstamon/thirdparty/Xlib/xauth.py000066400000000000000000000101611316117564000215320ustar00rootroot00000000000000# Xlib.xauth -- ~/.Xauthority access # # Copyright (C) 2000 Peter Liljenberg # Copyright (C) 2013 LiuLang # # 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 import os import struct from Xlib import X, error FamilyInternet = X.FamilyInternet FamilyDECnet = X.FamilyDECnet FamilyChaos = X.FamilyChaos FamilyLocal = 256 class Xauthority: def __init__(self, filename = None): if filename is None: filename = os.environ.get('XAUTHORITY') if filename is None: try: filename = os.path.join(os.environ['HOME'], '.Xauthority') except KeyError: raise error.XauthError( '$HOME not set, cannot find ~/.Xauthority') try: raw = open(filename, 'rb').read() except OSError as err: raise error.XauthError('~/.Xauthority: %s' % err) self.entries = [] # entry format (all shorts in big-endian) # short family; # short addrlen; # char addr[addrlen]; # short numlen; # char num[numlen]; # short namelen; # char name[namelen]; # short datalen; # char data[datalen]; n = 0 try: while n < len(raw): family, = struct.unpack('>H', raw[n:n+2]) n = n + 2 length, = struct.unpack('>H', raw[n:n+2]) n = n + length + 2 addr = raw[n - length : n] length, = struct.unpack('>H', raw[n:n+2]) n = n + length + 2 num = raw[n - length : n] length, = struct.unpack('>H', raw[n:n+2]) n = n + length + 2 name = raw[n - length : n] length, = struct.unpack('>H', raw[n:n+2]) n = n + length + 2 data = raw[n - length : n] if len(data) != length: break self.entries.append((family, addr, num, name, data, )) except struct.error as e: print("Xlib.xauth: warning, failed to parse part of xauthority file (%s), aborting all further parsing" % filename) if len(self.entries) == 0: print("Xlib.xauth: warning, no xauthority details available") # raise an error? this should get partially caught by the XNoAuthError in get_best_auth.. def __len__(self): return len(self.entries) def __getitem__(self, i): return self.entries[i] def get_best_auth(self, family, address, dispno, types = ( b"MIT-MAGIC-COOKIE-1", )): """Find an authentication entry matching FAMILY, ADDRESS and DISPNO. The name of the auth scheme must match one of the names in TYPES. If several entries match, the first scheme in TYPES will be choosen. If an entry is found, the tuple (name, data) is returned, otherwise XNoAuthError is raised. """ num = str(dispno).encode() address = address.encode() matches = {} for efam, eaddr, enum, ename, edata in self.entries: if efam == family and eaddr == address and num == enum: matches[ename] = edata for t in types: try: return (t, matches[t]) except KeyError: pass raise error.XNoAuthError((family, address, dispno)) Nagstamon/Nagstamon/thirdparty/Xlib/xobject/000077500000000000000000000000001316117564000214665ustar00rootroot00000000000000Nagstamon/Nagstamon/thirdparty/Xlib/xobject/__init__.py000066400000000000000000000017151316117564000236030ustar00rootroot00000000000000# Xlib.xobject.__init__ -- glue for Xlib.xobject package # # Copyright (C) 2000 Peter Liljenberg # # 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 __all__ = [ 'colormap', 'cursor', 'drawable', 'fontable', 'icccm', 'resource', ] Nagstamon/Nagstamon/thirdparty/Xlib/xobject/colormap.py000066400000000000000000000131501316117564000236540ustar00rootroot00000000000000# Xlib.xobject.colormap -- colormap object # # Copyright (C) 2000 Peter Liljenberg # # 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 import re from Xlib import error from Xlib.protocol import request from Xlib.xobject import resource rgb_res = [ re.compile(r'\Argb:([0-9a-fA-F]{1,4})/([0-9a-fA-F]{1,4})/([0-9a-fA-F]{1,4})\Z'), re.compile(r'\A#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])\Z'), re.compile(r'\A#([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])\Z'), re.compile(r'\A#([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])\Z'), re.compile(r'\A#([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])\Z'), ] class Colormap(resource.Resource): __colormap__ = resource.Resource.__resource__ def free(self, onerror = None): request.FreeColormap(display = self.display, onerror = onerror, cmap = self.id) self.display.free_resource_id(self.id) def copy_colormap_and_free(self, scr_cmap): mid = self.display.allocate_resource_id() request.CopyColormapAndFree(display = self.display, mid = mid, src_cmap = src_cmap) cls = self.display.get_resource_class('colormap', Colormap) return cls(self.display, mid, owner = 1) def install_colormap(self, onerror = None): request.InstallColormap(display = self.display, onerror = onerror, cmap = self.id) def uninstall_colormap(self, onerror = None): request.UninstallColormap(display = self.display, onerror = onerror, cmap = self.id) def alloc_color(self, red, green, blue): return request.AllocColor(display = self.display, cmap = self.id, red = red, green = green, blue = blue) def alloc_named_color(self, name): for r in rgb_res: m = r.match(name) if m: rs = m.group(1) r = int(rs + '0' * (4 - len(rs)), 16) gs = m.group(2) g = int(gs + '0' * (4 - len(gs)), 16) bs = m.group(3) b = int(bs + '0' * (4 - len(bs)), 16) return self.alloc_color(r, g, b) try: return request.AllocNamedColor(display = self.display, cmap = self.id, name = name) except error.BadName: return None def alloc_color_cells(self, contiguous, colors, planes): return request.AllocColorCells(display = self.display, contiguous = contiguous, cmap = self.id, colors = colors, planes = planes) def alloc_color_planes(self, contiguous, colors, red, green, blue): return request.AllocColorPlanes(display = self.display, contiguous = contiguous, cmap = self.id, colors = colors, red = red, green = green, blue = blue) def free_colors(self, pixels, plane_mask, onerror = None): request.FreeColors(display = self.display, onerror = onerror, cmap = self.id, plane_mask = plane_mask, pixels = pixels) def store_colors(self, items, onerror = None): request.StoreColors(display = self.display, onerror = onerror, cmap = self.id, items = items) def store_named_color(self, name, pixel, flags, onerror = None): request.StoreNamedColor(display = self.display, onerror = onerror, flags = flags, cmap = self.id, pixel = pixel, name = name) def query_colors(self, pixels): r = request.QueryColors(display = self.display, cmap = self.id, pixels = pixels) return r.colors def lookup_color(self, name): return request.LookupColor(display = self.display, cmap = self.id, name = name) Nagstamon/Nagstamon/thirdparty/Xlib/xobject/cursor.py000066400000000000000000000034611316117564000233610ustar00rootroot00000000000000# Xlib.xobject.cursor -- cursor object # # Copyright (C) 2000 Peter Liljenberg # # 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 from Xlib.protocol import request from Xlib.xobject import resource class Cursor(resource.Resource): __cursor__ = resource.Resource.__resource__ def free(self, onerror = None): request.FreeCursor(display = self.display, onerror = onerror, cursor = self.id) self.display.free_resource_id(self.id) def recolor(self, f_rgb, b_rgb, onerror = None): back_red, back_green, back_blue = b_rgb fore_red, fore_green, fore_blue = f_rgb request.RecolorCursor(display = self.display, onerror = onerror, cursor = self.id, fore_red = fore_red, fore_green = fore_green, fore_blue = fore_blue, back_red = back_red, back_green = back_green, back_blue = back_blue) Nagstamon/Nagstamon/thirdparty/Xlib/xobject/drawable.py000066400000000000000000001007561316117564000236320ustar00rootroot00000000000000# Xlib.xobject.drawable -- drawable objects (window and pixmap) # # Copyright (C) 2000 Peter Liljenberg # # 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 from Xlib import X, Xatom, Xutil from Xlib.protocol import request, rq # Other X resource objects from Xlib.xobject import resource, colormap, cursor, fontable, icccm class Drawable(resource.Resource): __drawable__ = resource.Resource.__resource__ def get_geometry(self): return request.GetGeometry(display = self.display, drawable = self) def create_pixmap(self, width, height, depth): pid = self.display.allocate_resource_id() request.CreatePixmap(display = self.display, depth = depth, pid = pid, drawable = self.id, width = width, height = height) cls = self.display.get_resource_class('pixmap', Pixmap) return cls(self.display, pid, owner = 1) def create_gc(self, **keys): cid = self.display.allocate_resource_id() request.CreateGC(display = self.display, cid = cid, drawable = self.id, attrs = keys) cls = self.display.get_resource_class('gc', fontable.GC) return cls(self.display, cid, owner = 1) def copy_area(self, gc, src_drawable, src_x, src_y, width, height, dst_x, dst_y, onerror = None): request.CopyArea(display = self.display, onerror = onerror, src_drawable = src_drawable, dst_drawable = self.id, gc = gc, src_x = src_x, src_y = src_y, dst_x = dst_x, dst_y = dst_y, width = width, height = height) def copy_plane(self, gc, src_drawable, src_x, src_y, width, height, dst_x, dst_y, bit_plane, onerror = None): request.CopyPlane(display = self.display, onerror = onerror, src_drawable = src_drawable, dst_drawable = self.id, gc = gc, src_x = src_x, src_y = src_y, dst_x = dst_x, dst_y = dst_y, width = width, height = height, bit_plane = bit_plane) def poly_point(self, gc, coord_mode, points, onerror = None): request.PolyPoint(display = self.display, onerror = onerror, coord_mode = coord_mode, drawable = self.id, gc = gc, points = points) def point(self, gc, x, y, onerror = None): request.PolyPoint(display = self.display, onerror = onerror, coord_mode = X.CoordModeOrigin, drawable = self.id, gc = gc, points = [(x, y)]) def poly_line(self, gc, coord_mode, points, onerror = None): request.PolyLine(display = self.display, onerror = onerror, coord_mode = coord_mode, drawable = self.id, gc = gc, points = points) def line(self, gc, x1, y1, x2, y2, onerror = None): request.PolySegment(display = self.display, onerror = onerror, drawable = self.id, gc = gc, segments = [(x1, y1, x2, y2)]) def poly_segment(self, gc, segments, onerror = None): request.PolySegment(display = self.display, onerror = onerror, drawable = self.id, gc = gc, segments = segments) def poly_rectangle(self, gc, rectangles, onerror = None): request.PolyRectangle(display = self.display, onerror = onerror, drawable = self.id, gc = gc, rectangles = rectangles) def rectangle(self, gc, x, y, width, height, onerror = None): request.PolyRectangle(display = self.display, onerror = onerror, drawable = self.id, gc = gc, rectangles = [(x, y, width, height)]) def poly_arc(self, gc, arcs, onerror = None): request.PolyArc(display = self.display, onerror = onerror, drawable = self.id, gc = gc, arcs = arcs) def arc(self, gc, x, y, width, height, angle1, angle2, onerror = None): request.PolyArc(display = self.display, onerror = onerror, drawable = self.id, gc = gc, arcs = [(x, y, width, height, angle1, angle2)]) def fill_poly(self, gc, shape, coord_mode, points, onerror = None): request.FillPoly(display = self.display, onerror = onerror, shape = shape, coord_mode = coord_mode, drawable = self.id, gc = gc, points = points) def poly_fill_rectangle(self, gc, rectangles, onerror = None): request.PolyFillRectangle(display = self.display, onerror = onerror, drawable = self.id, gc = gc, rectangles = rectangles) def fill_rectangle(self, gc, x, y, width, height, onerror = None): request.PolyFillRectangle(display = self.display, onerror = onerror, drawable = self.id, gc = gc, rectangles = [(x, y, width, height)]) def poly_fill_arc(self, gc, arcs, onerror = None): request.PolyFillArc(display = self.display, onerror = onerror, drawable = self.id, gc = gc, arcs = arcs) def fill_arc(self, gc, x, y, width, height, angle1, angle2, onerror = None): request.PolyFillArc(display = self.display, onerror = onerror, drawable = self.id, gc = gc, arcs = [(x, y, width, height, angle1, angle2)]) def put_image(self, gc, x, y, width, height, format, depth, left_pad, data, onerror = None): request.PutImage(display = self.display, onerror = onerror, format = format, drawable = self.id, gc = gc, width = width, height = height, dst_x = x, dst_y = y, left_pad = left_pad, depth = depth, data = data) # Trivial little method for putting PIL images. Will break on anything # but depth 1 or 24... def put_pil_image(self, gc, x, y, image, onerror = None): width, height = image.size if image.mode == '1': format = X.XYBitmap depth = 1 if self.display.info.bitmap_format_bit_order == 0: rawmode = '1;R' else: rawmode = '1' pad = self.display.info.bitmap_format_scanline_pad stride = roundup(width, pad) >> 3 elif image.mode == 'RGB': format = X.ZPixmap depth = 24 if self.display.info.image_byte_order == 0: rawmode = 'BGRX' else: rawmode = 'RGBX' pad = self.display.info.bitmap_format_scanline_pad unit = self.display.info.bitmap_format_scanline_unit stride = roundup(width * unit, pad) >> 3 else: raise ValueError('Unknown data format') maxlen = (self.display.info.max_request_length << 2) \ - request.PutImage._request.static_size split = maxlen // stride x1 = 0 x2 = width y1 = 0 while y1 < height: h = min(height, split) if h < height: subimage = image.crop((x1, y1, x2, y1 + h)) else: subimage = image w, h = subimage.size data = subimage.tostring("raw", rawmode, stride, 0) self.put_image(gc, x, y, w, h, format, depth, 0, data) y1 = y1 + h y = y + h def get_image(self, x, y, width, height, format, plane_mask): return request.GetImage(display = self.display, format = format, drawable = self.id, x = x, y = y, width = width, height = height, plane_mask = plane_mask) def draw_text(self, gc, x, y, text, onerror = None): request.PolyText8(display = self.display, onerror = onerror, drawable = self.id, gc = gc, x = x, y = y, items = [text]) def poly_text(self, gc, x, y, items, onerror = None): request.PolyText8(display = self.display, onerror = onerror, drawable = self.id, gc = gc, x = x, y = y, items = items) def poly_text_16(self, gc, x, y, items, onerror = None): request.PolyText16(display = self.display, onerror = onerror, drawable = self.id, gc = gc, x = x, y = y, items = items) def image_text(self, gc, x, y, string, onerror = None): request.ImageText8(display = self.display, onerror = onerror, drawable = self.id, gc = gc, x = x, y = y, string = string) def image_text_16(self, gc, x, y, string, onerror = None): request.ImageText16(display = self.display, onerror = onerror, drawable = self.id, gc = gc, x = x, y = y, string = string) def query_best_size(self, item_class, width, height): return request.QueryBestSize(display = self.display, item_class = item_class, drawable = self.id, width = width, height = height) class Window(Drawable): __window__ = resource.Resource.__resource__ def create_window(self, x, y, width, height, border_width, depth, window_class = X.CopyFromParent, visual = X.CopyFromParent, onerror = None, **keys): wid = self.display.allocate_resource_id() request.CreateWindow(display = self.display, onerror = onerror, depth = depth, wid = wid, parent = self.id, x = x, y = y, width = width, height = height, border_width = border_width, window_class = window_class, visual = visual, attrs = keys) cls = self.display.get_resource_class('window', Window) return cls(self.display, wid, owner = 1) def change_attributes(self, onerror = None, **keys): request.ChangeWindowAttributes(display = self.display, onerror = onerror, window = self.id, attrs = keys) def get_attributes(self): return request.GetWindowAttributes(display = self.display, window = self.id) def destroy(self, onerror = None): request.DestroyWindow(display = self.display, onerror = onerror, window = self.id) self.display.free_resource_id(self.id) def destroy_sub_windows(self, onerror = None): request.DestroySubWindows(display = self.display, onerror = onerror, window = self.id) def change_save_set(self, mode, onerror = None): request.ChangeSaveSet(display = self.display, onerror = onerror, mode = mode, window = self.id) def reparent(self, parent, x, y, onerror = None): request.ReparentWindow(display = self.display, onerror = onerror, window = self.id, parent = parent, x = x, y = y) def map(self, onerror = None): request.MapWindow(display = self.display, onerror = onerror, window = self.id) def map_sub_windows(self, onerror = None): request.MapSubwindows(display = self.display, onerror = onerror, window = self.id) def unmap(self, onerror = None): request.UnmapWindow(display = self.display, onerror = onerror, window = self.id) def unmap_sub_windows(self, onerror = None): request.UnmapSubwindows(display = self.display, onerror = onerror, window = self.id) def configure(self, onerror = None, **keys): request.ConfigureWindow(display = self.display, onerror = onerror, window = self.id, attrs = keys) def circulate(self, direction, onerror = None): request.CirculateWindow(display = self.display, onerror = onerror, direction = direction, window = self.id) def raise_window(self, onerror = None): """alias for raising the window to the top - as in XRaiseWindow""" self.configure(onerror, stack_mode = X.Above) def query_tree(self): return request.QueryTree(display = self.display, window = self.id) def change_property(self, property, type, format, data, mode = X.PropModeReplace, onerror = None): request.ChangeProperty(display = self.display, onerror = onerror, mode = mode, window = self.id, property = property, type = type, data = (format, data)) def delete_property(self, property, onerror = None): request.DeleteProperty(display = self.display, onerror = onerror, window = self.id, property = property) def get_property(self, property, type, offset, length, delete = 0): r = request.GetProperty(display = self.display, delete = delete, window = self.id, property = property, type = type, long_offset = offset, long_length = length) if r.property_type: fmt, value = r.value r.format = fmt r.value = value return r else: return None def get_full_property(self, property, type, sizehint = 10): prop = self.get_property(property, type, 0, sizehint) if prop: val = prop.value if prop.bytes_after: prop = self.get_property(property, type, sizehint, prop.bytes_after // 4 + 1) val = val + prop.value prop.value = val return prop else: return None def list_properties(self): r = request.ListProperties(display = self.display, window = self.id) return r.atoms def set_selection_owner(self, selection, time, onerror = None): request.SetSelectionOwner(display = self.display, onerror = onerror, window = self.id, selection = selection, time = time) def convert_selection(self, selection, target, property, time, onerror = None): request.ConvertSelection(display = self.display, onerror = onerror, requestor = self.id, selection = selection, target = target, property = property, time = time) def send_event(self, event, event_mask = 0, propagate = 0, onerror = None): request.SendEvent(display = self.display, onerror = onerror, propagate = propagate, destination = self.id, event_mask = event_mask, event = event) def grab_pointer(self, owner_events, event_mask, pointer_mode, keyboard_mode, confine_to, cursor, time): r = request.GrabPointer(display = self.display, owner_events = owner_events, grab_window = self.id, event_mask = event_mask, pointer_mode = pointer_mode, keyboard_mode = keyboard_mode, confine_to = confine_to, cursor = cursor, time = time) return r.status def grab_button(self, button, modifiers, owner_events, event_mask, pointer_mode, keyboard_mode, confine_to, cursor, onerror = None): request.GrabButton(display = self.display, onerror = onerror, owner_events = owner_events, grab_window = self.id, event_mask = event_mask, pointer_mode = pointer_mode, keyboard_mode = keyboard_mode, confine_to = confine_to, cursor = cursor, button = button, modifiers = modifiers) def ungrab_button(self, button, modifiers, onerror = None): request.UngrabButton(display = self.display, onerror = onerror, button = button, grab_window = self.id, modifiers = modifiers) def grab_keyboard(self, owner_events, pointer_mode, keyboard_mode, time): r = request.GrabKeyboard(display = self.display, owner_events = owner_events, grab_window = self.id, time = time, pointer_mode = pointer_mode, keyboard_mode = keyboard_mode) return r.status def grab_key(self, key, modifiers, owner_events, pointer_mode, keyboard_mode, onerror = None): request.GrabKey(display = self.display, onerror = onerror, owner_events = owner_events, grab_window = self.id, modifiers = modifiers, key = key, pointer_mode = pointer_mode, keyboard_mode = keyboard_mode) def ungrab_key(self, key, modifiers, onerror = None): request.UngrabKey(display = self.display, onerror = onerror, key = key, grab_window = self.id, modifiers = modifiers) def query_pointer(self): return request.QueryPointer(display = self.display, window = self.id) def get_motion_events(self, start, stop): r = request.GetMotionEvents(display = self.display, window = self.id, start = start, stop = stop) return r.events def translate_coords(self, src_window, src_x, src_y): return request.TranslateCoords(display = self.display, src_wid = src_window, dst_wid = self.id, src_x = src_x, src_y = src_y) def warp_pointer(self, x, y, src_window = 0, src_x = 0, src_y = 0, src_width = 0, src_height = 0, onerror = None): request.WarpPointer(display = self.display, onerror = onerror, src_window = src_window, dst_window = self.id, src_x = src_x, src_y = src_y, src_width = src_width, src_height = src_height, dst_x = x, dst_y = y) def set_input_focus(self, revert_to, time, onerror = None): request.SetInputFocus(display = self.display, onerror = onerror, revert_to = revert_to, focus = self.id, time = time) def clear_area(self, x = 0, y = 0, width = 0, height = 0, exposures = 0, onerror = None): request.ClearArea(display = self.display, onerror = onerror, exposures = exposures, window = self.id, x = x, y = y, width = width, height = height) def create_colormap(self, visual, alloc): mid = self.display.allocate_resource_id() request.CreateColormap(display = self.display, alloc = alloc, mid = mid, window = self.id, visual = visual) cls = self.display.get_resource_class('colormap', colormap.Colormap) return cls(self.display, mid, owner = 1) def list_installed_colormaps(self): r = request.ListInstalledColormaps(display = self.display, window = self.id) return r.cmaps def rotate_properties(self, properties, delta, onerror = None): request.RotateProperties(display = self.display, onerror = onerror, window = self.id, delta = delta, properties = properties) def set_wm_name(self, name, onerror = None): self.change_property(Xatom.WM_NAME, Xatom.STRING, 8, name, onerror = onerror) def get_wm_name(self): d = self.get_full_property(Xatom.WM_NAME, Xatom.STRING) if d is None or d.format != 8: return None else: return d.value def set_wm_icon_name(self, name, onerror = None): self.change_property(Xatom.WM_ICON_NAME, Xatom.STRING, 8, name, onerror = onerror) def get_wm_icon_name(self): d = self.get_full_property(Xatom.WM_ICON_NAME, Xatom.STRING) if d is None or d.format != 8: return None else: return d.value def set_wm_class(self, inst, cls, onerror = None): self.change_property(Xatom.WM_CLASS, Xatom.STRING, 8, '%s\0%s\0' % (inst, cls), onerror = onerror) def get_wm_class(self): d = self.get_full_property(Xatom.WM_CLASS, Xatom.STRING) if d is None or d.format != 8: return None else: parts = d.value.split('\0') if len(parts) < 2: return None else: return parts[0], parts[1] def set_wm_transient_for(self, window, onerror = None): self.change_property(Xatom.WM_TRANSIENT_FOR, Xatom.WINDOW, 32, window.id, onerror = onerror) def get_wm_transient_for(self): d = self.get_property(Xatom.WM_TRANSIENT_FOR, Xatom.WINDOW, 0, 1) if d is None or d.format != 32 or len(d.value) < 1: return None else: cls = self.display.get_resource_class('window', Window) return cls(self.display, d.value[0]) def set_wm_protocols(self, protocols, onerror = None): self.change_property(self.display.get_atom('WM_PROTOCOLS'), Xatom.ATOM, 32, protocols, onerror = onerror) def get_wm_protocols(self): d = self.get_full_property(self.display.get_atom('WM_PROTOCOLS'), Xatom.ATOM) if d is None or d.format != 32: return [] else: return d.value def set_wm_colormap_windows(self, windows, onerror = None): self.change_property(self.display.get_atom('WM_COLORMAP_WINDOWS'), Xatom.WINDOW, 32, [w.id for w in windows], onerror = onerror) def get_wm_colormap_windows(self): d = self.get_full_property(self.display.get_atom('WM_COLORMAP_WINDOWS'), Xatom.WINDOW) if d is None or d.format != 32: return [] else: cls = self.display.get_resource_class('window', Window) return list(map(lambda i, d = self.display, c = cls: c(d, i), d.value)) def set_wm_client_machine(self, name, onerror = None): self.change_property(Xatom.WM_CLIENT_MACHINE, Xatom.STRING, 8, name, onerror = onerror) def get_wm_client_machine(self): d = self.get_full_property(Xatom.WM_CLIENT_MACHINE, Xatom.STRING) if d is None or d.format != 8: return None else: return d.value def set_wm_normal_hints(self, hints = {}, onerror = None, **keys): self._set_struct_prop(Xatom.WM_NORMAL_HINTS, Xatom.WM_SIZE_HINTS, icccm.WMNormalHints, hints, keys, onerror) def get_wm_normal_hints(self): return self._get_struct_prop(Xatom.WM_NORMAL_HINTS, Xatom.WM_SIZE_HINTS, icccm.WMNormalHints) def set_wm_hints(self, hints = {}, onerror = None, **keys): self._set_struct_prop(Xatom.WM_HINTS, Xatom.WM_HINTS, icccm.WMHints, hints, keys, onerror) def get_wm_hints(self): return self._get_struct_prop(Xatom.WM_HINTS, Xatom.WM_HINTS, icccm.WMHints) def set_wm_state(self, hints = {}, onerror = None, **keys): atom = self.display.get_atom('WM_STATE') self._set_struct_prop(atom, atom, icccm.WMState, hints, keys, onerror) def get_wm_state(self): atom = self.display.get_atom('WM_STATE') return self._get_struct_prop(atom, atom, icccm.WMState) def set_wm_icon_size(self, hints = {}, onerror = None, **keys): self._set_struct_prop(Xatom.WM_ICON_SIZE, Xatom.WM_ICON_SIZE, icccm.WMIconSize, hints, keys, onerror) def get_wm_icon_size(self): return self._get_struct_prop(Xatom.WM_ICON_SIZE, Xatom.WM_ICON_SIZE, icccm.WMIconSize) # Helper function for getting structured properties. # pname and ptype are atoms, and pstruct is a Struct object. # Returns a DictWrapper, or None def _get_struct_prop(self, pname, ptype, pstruct): r = self.get_property(pname, ptype, 0, pstruct.static_size // 4) if r and r.format == 32: value = r.value.tostring() if len(value) == pstruct.static_size: return pstruct.parse_binary(value, self.display)[0] return None # Helper function for setting structured properties. # pname and ptype are atoms, and pstruct is a Struct object. # hints is a mapping or a DictWrapper, keys is a mapping. keys # will be modified. onerror is the error handler. def _set_struct_prop(self, pname, ptype, pstruct, hints, keys, onerror): if isinstance(hints, rq.DictWrapper): keys.update(hints._data) else: keys.update(hints) value = pstruct.to_binary(*(), **keys) self.change_property(pname, ptype, 32, value, onerror = onerror) class Pixmap(Drawable): __pixmap__ = resource.Resource.__resource__ def free(self, onerror = None): request.FreePixmap(display = self.display, onerror = onerror, pixmap = self.id) self.display.free_resource_id(self.id) def create_cursor(self, mask, f_rgb, b_rgb, x, y): fore_red, fore_green, fore_blue = f_rgb back_red, back_green, back_blue = b_rgb cid = self.display.allocate_resource_id() request.CreateCursor(display = self.display, cid = cid, source = self.id, mask = mask, fore_red = fore_red, fore_green = fore_green, fore_blue = fore_blue, back_red = back_red, back_green = back_green, back_blue = back_blue, x = x, y = y) cls = self.display.get_resource_class('cursor', cursor.Cursor) return cls(self.display, cid, owner = 1) def roundup(value, unit): return (value + (unit - 1)) & ~(unit - 1) Nagstamon/Nagstamon/thirdparty/Xlib/xobject/fontable.py000066400000000000000000000101711316117564000236320ustar00rootroot00000000000000# Xlib.xobject.fontable -- fontable objects (GC, font) # # Copyright (C) 2000 Peter Liljenberg # # 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 from Xlib.protocol import request from Xlib.xobject import resource, cursor class Fontable(resource.Resource): __fontable__ = resource.Resource.__resource__ def query(self): return request.QueryFont(display = self.display, font = self.id) def query_text_extents(self, string): return request.QueryTextExtents(display = self.display, font = self.id, string = string) class GC(Fontable): __gc__ = resource.Resource.__resource__ def change(self, onerror = None, **keys): request.ChangeGC(display = self.display, onerror = onerror, gc = self.id, attrs = keys) def copy(self, src_gc, mask, onerror = None): request.CopyGC(display = self.display, onerror = onerror, src_gc = src_gc, dst_gc = self.id, mask = mask) def set_dashes(self, offset, dashes, onerror = None): request.SetDashes(display = self.display, onerror = onerror, gc = self.id, dash_offset = offset, dashes = dashes) def set_clip_rectangles(self, x_origin, y_origin, rectangles, ordering, onerror = None): request.SetClipRectangles(display = self.display, onerror = onerror, ordering = ordering, gc = self.id, x_origin = x_origin, y_origin = y_origin, rectangles = rectangles) def free(self, onerror = None): request.FreeGC(display = self.display, onerror = onerror, gc = self.id) self.display.free_resource_id(self.id) class Font(Fontable): __font__ = resource.Resource.__resource__ def close(self, onerror = None): request.CloseFont(display = self.display, onerror = onerror, font = self.id) self.display.free_resource_id(self.id) def create_glyph_cursor(self, mask, source_char, mask_char, f_rgb, b_rgb): fore_red, fore_green, fore_blue = f_rgb back_red, back_green, back_blue = b_rgb cid = self.display.allocate_resource_id() request.CreateGlyphCursor(display = self.display, cid = cid, source = self.id, mask = mask, source_char = source_char, mask_char = mask_char, fore_red = fore_red, fore_green = fore_green, fore_blue = fore_blue, back_red = back_red, back_green = back_green, back_blue = back_blue) cls = self.display.get_resource_class('cursor', cursor.Cursor) return cls(self.display, cid, owner = 1) Nagstamon/Nagstamon/thirdparty/Xlib/xobject/icccm.py000066400000000000000000000064471316117564000231310ustar00rootroot00000000000000# Xlib.xobject.icccm -- ICCCM structures # # Copyright (C) 2000 Peter Liljenberg # # 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 from Xlib import X, Xutil from Xlib.protocol import rq Aspect = rq.Struct( rq.Int32('num'), rq.Int32('denum') ) WMNormalHints = rq.Struct( rq.Card32('flags'), rq.Pad(16), rq.Int32('min_width', default = 0), rq.Int32('min_height', default = 0), rq.Int32('max_width', default = 0), rq.Int32('max_height', default = 0), rq.Int32('width_inc', default = 0), rq.Int32('height_inc', default = 0), rq.Object('min_aspect', Aspect, default = (0, 0)), rq.Object('max_aspect', Aspect, default = (0, 0)), rq.Int32('base_width', default = 0), rq.Int32('base_height', default = 0), rq.Int32('win_gravity', default = 0), ) WMHints = rq.Struct( rq.Card32('flags'), rq.Card32('input', default = 0), rq.Set('initial_state', 4, # withdrawn is totally bogus according to # ICCCM, but some window managers seem to # use this value to identify dockapps. # Oh well. ( Xutil.WithdrawnState, Xutil.NormalState, Xutil.IconicState ), default = Xutil.NormalState), rq.Pixmap('icon_pixmap', default = 0), rq.Window('icon_window', default = 0), rq.Int32('icon_x', default = 0), rq.Int32('icon_y', default = 0), rq.Pixmap('icon_mask', default = 0), rq.Window('window_group', default = 0), ) WMState = rq.Struct( rq.Set('state', 4, ( Xutil.WithdrawnState, Xutil.NormalState, Xutil.IconicState )), rq.Window('icon', ( X.NONE, )), ) WMIconSize = rq.Struct( rq.Card32('min_width'), rq.Card32('min_height'), rq.Card32('max_width'), rq.Card32('max_height'), rq.Card32('width_inc'), rq.Card32('height_inc'), ) Nagstamon/Nagstamon/thirdparty/Xlib/xobject/resource.py000066400000000000000000000033441316117564000236730ustar00rootroot00000000000000# Xlib.xobject.resource -- any X resource object # # Copyright (C) 2000 Peter Liljenberg # # 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 from Xlib.protocol import request class Resource: def __init__(self, display, rid, owner = 0): self.display = display self.id = rid self.owner = owner def __resource__(self): return self.id def __eq__(self, obj): if isinstance(obj, Resource): if self.display == obj.display: return self.id == obj.id else: return self.display == obj.display else: return id(self) == id(obj) def __hash__(self): return int(self.id) def __str__(self): return '%s(0x%08x)' % (self.__class__, self.id) def __repr__(self): return '<%s 0x%08x>' % (self.__class__, self.id) def kill_client(self, onerror = None): request.KillClient(display = self.display, onerror = onerror, resource = self.id) Nagstamon/Nagstamon/thirdparty/__init__.py000066400000000000000000000015511316117564000212450ustar00rootroot00000000000000# encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2016 Henri Wahl et al. # # 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 """Module thirdparty""" Nagstamon/Nagstamon/thirdparty/ewmh.py000066400000000000000000000330331316117564000204460ustar00rootroot00000000000000"""This module intends to provide an implementation of Extended Window Manager Hints, based on the Xlib modules for python. See the freedesktop.org `specification `_ for more information. """ from Xlib import display, X, protocol, Xatom import time class EWMH: """This class provides the ability to get and set properties defined by the EWMH spec. Each property can be accessed in two ways. For example, to get the active window:: win = ewmh.getActiveWindow() # or: win = ewmh.getProperty('_NET_ACTIVE_WINDOW') Similarly, to set the active window:: ewmh.setActiveWindow(myWindow) # or: ewmh.setProperty('_NET_ACTIVE_WINDOW', myWindow) When a property is written, don't forget to really send the notification by flushing requests:: ewmh.display.flush() :param _display: the display to use. If not given, Xlib.display.Display() is used. :param root: the root window to use. If not given, self.display.screen().root is used.""" NET_WM_WINDOW_TYPES = ( '_NET_WM_WINDOW_TYPE_DESKTOP', '_NET_WM_WINDOW_TYPE_DOCK', '_NET_WM_WINDOW_TYPE_TOOLBAR', '_NET_WM_WINDOW_TYPE_MENU', '_NET_WM_WINDOW_TYPE_UTILITY', '_NET_WM_WINDOW_TYPE_SPLASH', '_NET_WM_WINDOW_TYPE_DIALOG', '_NET_WM_WINDOW_TYPE_DROPDOWN_MENU', '_NET_WM_WINDOW_TYPE_POPUP_MENU', '_NET_WM_WINDOW_TYPE_NOTIFICATION', '_NET_WM_WINDOW_TYPE_COMBO', '_NET_WM_WINDOW_TYPE_DND', '_NET_WM_WINDOW_TYPE_NORMAL') """List of strings representing all known window types.""" NET_WM_ACTIONS = ( '_NET_WM_ACTION_MOVE', '_NET_WM_ACTION_RESIZE', '_NET_WM_ACTION_MINIMIZE', '_NET_WM_ACTION_SHADE', '_NET_WM_ACTION_STICK', '_NET_WM_ACTION_MAXIMIZE_HORZ', '_NET_WM_ACTION_MAXIMIZE_VERT', '_NET_WM_ACTION_FULLSCREEN', '_NET_WM_ACTION_CHANGE_DESKTOP', '_NET_WM_ACTION_CLOSE', '_NET_WM_ACTION_ABOVE', '_NET_WM_ACTION_BELOW') """List of strings representing all known window actions.""" NET_WM_STATES = ( '_NET_WM_STATE_MODAL', '_NET_WM_STATE_STICKY', '_NET_WM_STATE_MAXIMIZED_VERT', '_NET_WM_STATE_MAXIMIZED_HORZ', '_NET_WM_STATE_SHADED', '_NET_WM_STATE_SKIP_TASKBAR', '_NET_WM_STATE_SKIP_PAGER', '_NET_WM_STATE_HIDDEN', '_NET_WM_STATE_FULLSCREEN','_NET_WM_STATE_ABOVE', '_NET_WM_STATE_BELOW', '_NET_WM_STATE_DEMANDS_ATTENTION') """List of strings representing all known window states.""" def __init__(self, _display=None, root = None): self.display = _display or display.Display() self.root = root or self.display.screen().root self.__getAttrs = { '_NET_CLIENT_LIST': self.getClientList, '_NET_CLIENT_LIST_STACKING': self.getClientListStacking, '_NET_NUMBER_OF_DESKTOPS': self.getNumberOfDesktops, '_NET_DESKTOP_GEOMETRY': self.getDesktopGeometry, '_NET_DESKTOP_VIEWPORT': self.getDesktopViewPort, '_NET_CURRENT_DESKTOP': self.getCurrentDesktop, '_NET_ACTIVE_WINDOW': self.getActiveWindow, '_NET_WORKAREA': self.getWorkArea, '_NET_SHOWING_DESKTOP': self.getShowingDesktop, '_NET_WM_NAME': self.getWmName, '_NET_WM_VISIBLE_NAME': self.getWmVisibleName, '_NET_WM_DESKTOP': self.getWmDesktop, '_NET_WM_WINDOW_TYPE': self.getWmWindowType, '_NET_WM_STATE': self.getWmState, '_NET_WM_ALLOWED_ACTIONS': self.getWmAllowedActions, '_NET_WM_PID': self.getWmPid, } self.__setAttrs = { '_NET_NUMBER_OF_DESKTOPS': self.setNumberOfDesktops, '_NET_DESKTOP_GEOMETRY': self.setDesktopGeometry, '_NET_DESKTOP_VIEWPORT': self.setDesktopViewport, '_NET_CURRENT_DESKTOP': self.setCurrentDesktop, '_NET_ACTIVE_WINDOW': self.setActiveWindow, '_NET_SHOWING_DESKTOP': self.setShowingDesktop, '_NET_CLOSE_WINDOW': self.setCloseWindow, '_NET_MOVERESIZE_WINDOW': self.setMoveResizeWindow, '_NET_WM_NAME': self.setWmName, '_NET_WM_VISIBLE_NAME': self.setWmVisibleName, '_NET_WM_DESKTOP': self.setWmDesktop, '_NET_WM_STATE': self.setWmState, } # ------------------------ setters properties ------------------------ def setNumberOfDesktops(self, nb): """Set the number of desktops (property _NET_NUMBER_OF_DESKTOPS). :param nb: the number of desired desktops""" self._setProperty('_NET_NUMBER_OF_DESKTOPS', [nb]) def setDesktopGeometry(self, w, h): """Set the desktop geometry (property _NET_DESKTOP_GEOMETRY) :param w: desktop width :param h: desktop height""" self._setProperty('_NET_DESKTOP_GEOMETRY', [w, h]) def setDesktopViewport(self, w, h): """Set the viewport size of the current desktop (property _NET_DESKTOP_VIEWPORT) :param w: desktop width :param h: desktop height""" self._setProperty('_NET_DESKTOP_VIEWPORT', [w, h]) def setCurrentDesktop(self, i): """Set the current desktop (property _NET_CURRENT_DESKTOP). :param i: the desired desktop number""" self._setProperty('_NET_CURRENT_DESKTOP', [i, X.CurrentTime]) def setActiveWindow(self, win): """Set the given window active (property _NET_ACTIVE_WINDOW) :param win: the window object""" self._setProperty('_NET_ACTIVE_WINDOW', [1, X.CurrentTime, win.id], win) def setShowingDesktop(self, show): """Set/unset the mode Showing desktop (property _NET_SHOWING_DESKTOP) :param show: 1 to set the desktop mode, else 0""" self._setProperty('_NET_SHOWING_DESKTOP', [show]) def setCloseWindow(self, win): """Colse the given window (property _NET_CLOSE_WINDOW) :param win: the window object""" self._setProperty('_NET_CLOSE_WINDOW', [int(time.mktime(time.localtime())), 1], win) def setWmName(self, win, name): """Set the property _NET_WM_NAME :param win: the window object :param name: desired name""" self._setProperty('_NET_WM_NAME', name, win) def setWmVisibleName(self, win, name): """Set the property _NET_WM_VISIBLE_NAME :param win: the window object :param name: desired visible name""" self._setProperty('_NET_WM_VISIBLE_NAME', name, win) def setWmDesktop(self, win, i): """Move the window to the desired desktop by changing the property _NET_WM_DESKTOP. :param win: the window object :param i: desired desktop number""" self._setProperty('_NET_WM_DESKTOP', [i, 1], win) def setMoveResizeWindow(self, win, gravity=0, x=None, y=None, w=None, h=None): """Set the property _NET_MOVERESIZE_WINDOW to move or resize the given window. Flags are automatically calculated if x, y, w or h are defined. :param win: the window object :param gravity: gravity (one of the Xlib.X.*Gravity constant or 0) :param x: int or None :param y: int or None :param w: int or None :param h: int or None""" gravity_flags = gravity | 0b0000100000000000 # indicate source (application) if x is None: x = 0 else: gravity_flags = gravity_flags | 0b0000010000000000 # indicate presence of x if y is None: y = 0 else: gravity_flags = gravity_flags | 0b0000001000000000 # indicate presence of y if w is None: w = 0 else: gravity_flags = gravity_flags | 0b0000000100000000 # indicate presence of w if h is None: h = 0 else: gravity_flags = gravity_flags | 0b0000000010000000 # indicate presence of h self._setProperty('_NET_MOVERESIZE_WINDOW', [gravity_flags, x, y, w, h], win) def setWmState(self, win, action, state, state2=0): """Set/unset one or two state(s) for the given window (property _NET_WM_STATE). :param win: the window object :param action: 0 to remove, 1 to add or 2 to toggle state(s) :param state: a state :type state: int or str (see :attr:`NET_WM_STATES`) :param state2: a state or 0 :type state2: int or str (see :attr:`NET_WM_STATES`)""" if type(state) != int: state = self.display.get_atom(state, 1) if type(state2) != int: state2 = self.display.get_atom(state2, 1) self._setProperty('_NET_WM_STATE', [action, state, state2, 1], win) # ------------------------ getters properties ------------------------ def getClientList(self): """Get the list of windows maintained by the window manager for the property _NET_CLIENT_LIST. :return: list of Window objects""" return map(self._createWindow, self._getProperty('_NET_CLIENT_LIST')) def getClientListStacking(self): """Get the list of windows maintained by the window manager for the property _NET_CLIENT_LIST_STACKING. :return: list of Window objects""" return map(self._createWindow, self._getProperty('_NET_CLIENT_LIST_STACKING')) def getNumberOfDesktops(self): """Get the number of desktops (property _NET_NUMBER_OF_DESKTOPS). :return: int""" return self._getProperty('_NET_NUMBER_OF_DESKTOPS')[0] def getDesktopGeometry(self): """Get the desktop geometry (property _NET_DESKTOP_GEOMETRY) as an array of two integers [width, height]. :return: [int, int]""" return self._getProperty('_NET_DESKTOP_GEOMETRY') def getDesktopViewPort(self): """Get the current viewports of each desktop as a list of [x, y] representing the top left corner (property _NET_DESKTOP_VIEWPORT). :return: list of [int, int]""" return self._getProperty('_NET_DESKTOP_VIEWPORT') def getCurrentDesktop(self): """Get the current desktop number (property _NET_CURRENT_DESKTOP) :return: int""" return self._getProperty('_NET_CURRENT_DESKTOP')[0] def getActiveWindow(self): """Get the current active (toplevel) window or None (property _NET_ACTIVE_WINDOW) :return: Window object or None""" active_window = self._getProperty('_NET_ACTIVE_WINDOW') if active_window == None: return None return self._createWindow(active_window[0]) def getWorkArea(self): """Get the work area for each desktop (property _NET_WORKAREA) as a list of [x, y, width, height] :return: a list of [int, int, int, int]""" return self._getProperty('_NET_WORKAREA') def getShowingDesktop(self): """Get the value of "showing the desktop" mode of the window manager (property _NET_SHOWING_DESKTOP). 1 means the mode is activated, and 0 means deactivated. :return: int""" return self._getProperty('_NET_SHOWING_DESKTOP')[0] def getWmName(self, win): """Get the property _NET_WM_NAME for the given window as a string. :param win: the window object :return: str""" return self._getProperty('_NET_WM_NAME', win) def getWmVisibleName(self, win): """Get the property _NET_WM_VISIBLE_NAME for the given window as a string. :param win: the window object :return: str""" return self._getProperty('_NET_WM_VISIBLE_NAME', win) def getWmDesktop(self, win): """Get the current desktop number of the given window (property _NET_WM_DESKTOP). :param win: the window object :return: int""" return self._getProperty('_NET_WM_DESKTOP', win)[0] def getWmWindowType(self, win, str=False): """Get the list of window types of the given window (property _NET_WM_WINDOW_TYPE). :param win: the window object :param str: True to get a list of string types instead of int :return: list of (int|str)""" types = self._getProperty('_NET_WM_WINDOW_TYPE', win) if not str: return types return map(self._getAtomName, wtypes) def getWmState(self, win, str=False): """Get the list of states of the given window (property _NET_WM_STATE). :param win: the window object :param str: True to get a list of string states instead of int :return: list of (int|str)""" states = self._getProperty('_NET_WM_STATE', win) if not str: return states return map(self._getAtomName, states) def getWmAllowedActions(self, win, str=False): """Get the list of allowed actions for the given window (property _NET_WM_ALLOWED_ACTIONS). :param win: the window object :param str: True to get a list of string allowed actions instead of int :return: list of (int|str)""" wAllowedActions = self._getProperty('_NET_WM_ALLOWED_ACTIONS', win) if not str: return wAllowedActions return map(self._getAtomName, wAllowedActions) def getWmPid(self, win): """Get the pid of the application associated to the given window (property _NET_WM_PID) :param win: the window object""" return self._getProperty('_NET_WM_PID', win)[0] def _getProperty(self, _type, win=None): if not win: win = self.root atom = win.get_full_property(self.display.get_atom(_type), X.AnyPropertyType) if atom: return atom.value def _setProperty(self, _type, data, win=None, mask=None): """Send a ClientMessage event to the root window""" if not win: win = self.root if type(data) is str: dataSize = 8 else: data = (data+[0]*(5-len(data)))[:5] dataSize = 32 ev = protocol.event.ClientMessage(window=win, client_type=self.display.get_atom(_type), data=(dataSize, data)) if not mask: mask = (X.SubstructureRedirectMask|X.SubstructureNotifyMask) self.root.send_event(ev, event_mask=mask) def _getAtomName(self, atom): try: return self.display.get_atom_name(atom) except: return 'UNKNOWN' def _createWindow(self, wId): if not wId: return None return self.display.create_resource_object('window', wId) def getReadableProperties(self): """Get all the readable properties' names""" return self.__getAttrs.keys() def getProperty(self, prop, *args, **kwargs): """Get the value of a property. See the corresponding method for the required arguments. For example, for the property _NET_WM_STATE, look for :meth:`getWmState`""" f = self.__getAttrs.get(prop) if not f: raise KeyError('Unknow readable property: %s' % prop) return f(self, *args, **kwargs) def getWritableProperties(self): """Get all the writable properties names""" return self.__setAttrs.keys() def setProperty(self, prop, *args, **kwargs): """Set the value of a property by sending an event on the root window. See the corresponding method for the required arguments. For example, for the property _NET_WM_STATE, look for :meth:`setWmState`""" f = self.__setAttrs.get(prop) if not f: raise KeyError('Unknow writable property: %s' % prop) f(self, *args, **kwargs) Nagstamon/Nagstamon/thirdparty/sensu_api.py000066400000000000000000000266251316117564000215050ustar00rootroot00000000000000# This is part of Sangoma's pysensu library found here: https://github.com/sangoma/pysensu # and licensed under the MIT License # # The MIT License (MIT) # Copyright (c) 2015 Sangoma Technologies # Permission is hereby granted, free of charge, to any person obtaining a copy of # this software and associated documentation files (the "Software"), to deal in # the Software without restriction, including without limitation the rights to # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of # the Software, and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import json import logging import requests from requests.auth import HTTPBasicAuth logger = logging.getLogger(__name__) class SensuAPIException(Exception): pass class SensuAPI(object): def __init__(self, url_base, username=None, password=None): self._url_base = url_base self._header = { 'User-Agent': 'PySensu Client v0.9.0' } self.good_status = (200, 201, 202, 204) if username and password: self.auth = HTTPBasicAuth(username, password) else: self.auth = None def _request(self, method, path, **kwargs): url = '{}{}'.format(self._url_base, path) logger.debug('{} -> {} with {}'.format(method, url, kwargs)) if method == 'GET': resp = requests.get(url, auth=self.auth, headers=self._header, **kwargs) elif method == 'POST': resp = requests.post(url, auth=self.auth, headers=self._header, **kwargs) elif method == 'PUT': resp = requests.put(url, auth=self.auth, headers=self._header, **kwargs) elif method == 'DELETE': resp = requests.delete(url, auth=self.auth, headers=self._header, **kwargs) else: raise SensuAPIException( 'Method {} not implemented'.format(method) ) if resp.status_code in self.good_status: logger.debug('{}: {}'.format( resp.status_code, ''.join(resp.text.split('\n'))[0:80] )) return resp elif resp.status_code == 400: logger.error('{}: {}'.format( resp.status_code, resp.text )) raise SensuAPIException('API returned "Bad Request"') else: logger.warning('{}: {}'.format( resp.status_code, resp.text )) raise SensuAPIException('API bad response {}: {}'.format(resp.status_code, resp.text)) """ Clients ops """ def get_clients(self, limit=None, offset=None): """ Returns a list of clients. """ data = {} if limit: data['limit'] = limit if offset: data['offset'] = offset result = self._request('GET', '/clients', data=json.dumps(data)) return result.json() def get_client_data(self, client): """ Returns a client. """ data = self._request('GET', '/clients/{}'.format(client)) return data.json() def get_client_history(self, client): """ Returns the history for a client. """ data = self._request('GET', '/clients/{}/history'.format(client)) return data.json() def delete_client(self, client): """ Removes a client, resolving its current events. (delayed action) """ self._request('DELETE', '/clients/{}'.format(client)) return True """ Events ops """ def get_events(self): """ Returns the list of current events. """ data = self._request('GET', '/events') return data.json() def get_all_client_events(self, client): """ Returns the list of current events for a given client. """ data = self._request('GET', '/events/{}'.format(client)) return data.json() def get_event(self, client, check): """ Returns an event for a given client & check name. """ data = self._request('GET', '/events/{}/{}'.format(client, check)) return data.json() def delete_event(self, client, check): """ Resolves an event for a given check on a given client. (delayed action) """ self._request('DELETE', '/events/{}/{}'.format(client, check)) return True def post_event(self, client, check): """ Resolves an event. (delayed action) """ self._request('POST', '/resolve', data=json.dumps({'client': client, 'check': check})) return True """ Checks ops """ def get_checks(self): """ Returns the list of checks. """ data = self._request('GET', '/checks') return data.json() def get_check(self, check): """ Returns a check. """ data = self._request('GET', '/checks/{}'.format(check)) return data.json() def post_check_request(self, check, subscribers): """ Issues a check execution request. """ data = { 'check': check, 'subscribers': [subscribers] } self._request('POST', '/request', data=json.dumps(data)) return True """ Silenced API ops """ def get_silenced(self, limit=None, offset=None): """ Returns a list of silence entries. """ data = {} if limit: data['limit'] = limit if offset: data['offset'] = offset result = self._request('GET', '/silenced', data=json.dumps(data)) return result.json() def post_silence_request(self, kwargs): """ Create a silence entry. """ self._request('POST', '/silenced', data=json.dumps(kwargs)) return True def clear_silence(self, kwargs): """ Clear a silence entry. """ self._request('POST', '/silenced/clear', data=json.dumps(kwargs)) return True """ Aggregates ops """ def get_aggregates(self): """ Returns the list of named aggregates. """ data = self._request('GET', '/aggregates') return data.json() def get_aggregate_check(self, check, age=None): """ Returns the list of aggregates for a given check """ data = {} if age: data['max_age'] = age result = self._request('GET', '/aggregates/{}'.format(check), data=json.dumps(data)) return result.json() def delete_aggregate(self, check): """ Deletes all aggregate data for a named aggregate """ self._request('DELETE', '/aggregates/{}'.format(check)) return True """ Status ops """ def get_info(self): """ Returns information on the API. """ data = self._request('GET', '/info') return data.json() def get_health(self, consumers=2, messages=100): """ Returns health information on transport & Redis connections. """ data = {'consumers': consumers, 'messages': messages} try: self._request('GET', '/health', data=json.dumps(data)) return True except SensuAPIException: return False """ Results ops """ def get_all_client_results(self): """ Returns the list of results. """ data = self._request('GET', '/results') return data.json() def get_results(self, client): """ Returns a result. """ data = self._request('GET', '/results/{}'.format(client)) return data.json() def get_result(self, client, check): """ Returns an event for a given client & result name. """ data = self._request('GET', '/results/{}/{}'.format(client, check)) return data.json() def delete_result(self, client, check): """ Deletes an check result data for a given check on a given client. """ self._request('DELETE', '/results/{}/{}'.format(client, check)) return True def post_result_data(self, client, check, output, status): """ Posts check result data. """ data = { 'source': client, 'name': check, 'output': output, 'status': status, } self._request('POST', '/results', data=json.dumps(data)) return True """ Stashes ops """ def get_stashes(self): """ Returns a list of stashes. """ data = self._request('GET', '/stashes') return data.json() def create_stash(self, payload, path=None): """ Create a stash. (JSON document) """ if path: self._request('POST', '/stashes/{}'.format(path), json=payload) else: self._request('POST', '/stashes', json=payload) return True def delete_stash(self, path): """ Delete a stash. (JSON document) """ self._request('DELETE', '/stashes/{}'.format(path)) return True """ Subscriptions ops (not directly in the Sensu API) """ def get_subscriptions(self, nodes=[]): """ Returns all the channels where (optionally specified) nodes are subscribed """ if len(nodes) > 0: data = [node for node in self.get_clients() if node['name'] in nodes] else: data = self.get_clients() channels = [] for client in data: if 'subscriptions' in client: if isinstance(client['subscriptions'], list): for channel in client['subscriptions']: if channel not in channels: channels.append(channel) else: if client['subscriptions'] not in channels: channels.append(client['subscriptions']) return channels def get_subscriptions_channel(self, search_channel): """ Return all the nodes that are subscribed to the specified channel """ data = self.get_clients() clients = [] for client in data: if 'subscriptions' in client: if isinstance(client['subscriptions'], list): if search_channel in client['subscriptions']: clients.append(client['name']) else: if search_channel == client['subscriptions']: clients.append(client['name']) return clients Nagstamon/Nagstamon/thirdparty/zabbix_api.py000066400000000000000000000263421316117564000216230ustar00rootroot00000000000000# This is a port of the ruby zabbix api found here: # http://trac.red-tux.net/browser/ruby/api/zbx_api.rb # # LGPL 2.1 http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html # Zabbix API Python Library. # Original Ruby Library is Copyright (C) 2009 Andrew Nelson nelsonab(at)red-tux(dot)net # Python Library is Copyright (C) 2009 Brett Lentz brett.lentz(at)gmail(dot)com # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library 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 # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # NOTES: # The API requires zabbix 1.8 or later. # Currently, not all of the API is implemented, and some functionality is # broken. This is a work in progress. import base64 import hashlib import logging import string import sys import ssl import socket try: import urllib2 except ImportError: import urllib.request as urllib2 # python3 import re from collections import deque try: from ssl import _create_unverified_context HAS_SSLCONTEXT = True except ImportError: HAS_SSLCONTEXT = False default_log_handler = logging.StreamHandler(sys.stdout) __logger = logging.getLogger("zabbix_api") __logger.addHandler(default_log_handler) __logger.log(10, "Starting logging") try: # Separate module or Python <2.6 import simplejson as json __logger.log(15, "Using simplejson library") except ImportError: # Python >=2.6 import json __logger.log(15, "Using native json library") def checkauth(fn): """ Decorator to check authentication of the decorated method """ def ret(self, *args): self.__checkauth__() return fn(self, args) return ret def dojson(fn): def wrapper(self, method, opts): self.logger.log(logging.DEBUG, "Going to do_request for %s with opts %s" % (repr(fn), repr(opts))) return self.do_request(self.json_obj(method, opts))['result'] return wrapper class ZabbixAPIException(Exception): """ generic zabbix api exception code list: -32602 - Invalid params (eg already exists) -32500 - no permissions """ pass class Already_Exists(ZabbixAPIException): pass class InvalidProtoError(ZabbixAPIException): """ Recived an invalid proto """ pass class APITimeout(ZabbixAPIException): pass class ZabbixAPI(object): __username__ = '' __password__ = '' auth = '' url = '/api_jsonrpc.php' params = None method = None # HTTP or HTTPS proto = 'http' # HTTP authentication httpuser = None httppasswd = None timeout = 10 validate_certs = None # sub-class instances. # Constructor Params: # server: Server to connect to # path: Path leading to the zabbix install # proto: Protocol to use. http or https # We're going to use proto://server/path to find the JSON-RPC api. # # user: HTTP auth username # passwd: HTTP auth password # log_level: logging level # r_query_len: max len query history # **kwargs: Data to pass to each api module def __init__(self, server='http://localhost/zabbix', user=httpuser, passwd=httppasswd, log_level=logging.WARNING, timeout=10, r_query_len=10, validate_certs=True, **kwargs): """ Create an API object. """ self._setuplogging() self.set_log_level(log_level) self.server = server self.url = server + '/api_jsonrpc.php' self.proto = self.server.split("://")[0] # self.proto=proto self.httpuser = user self.httppasswd = passwd self.timeout = timeout self.kwargs = kwargs self.id = 0 self.r_query = deque([], maxlen=r_query_len) self.validate_certs = validate_certs self.debug(logging.INFO, "url: " + self.url) def _setuplogging(self): self.logger = logging.getLogger("zabbix_api.%s" % self.__class__.__name__) def set_log_level(self, level): self.debug(logging.INFO, "Set logging level to %d" % level) self.logger.setLevel(level) def recent_query(self): """ return recent query """ return list(self.r_query) def debug(self, level, var="", msg=None): strval = str(level) + ": " if msg: strval = strval + str(msg) if var != "": strval = strval + str(var) self.logger.log(level, strval) def json_obj(self, method, params={}, auth=True): obj = {'jsonrpc': '2.0', 'method': method, 'params': params, 'auth': self.auth, 'id': self.id } if not auth: del obj['auth'] self.debug(logging.DEBUG, "json_obj: " + str(obj)) return json.dumps(obj) def login(self, user='', password='', save=True): if user != '': l_user = user l_password = password if save: self.__username__ = user self.__password__ = password elif self.__username__ != '': l_user = self.__username__ l_password = self.__password__ else: raise ZabbixAPIException("No authentication information available.") # don't print the raw password. hashed_pw_string = "md5(" + hashlib.md5(l_password.encode('utf-8')).hexdigest() + ")" self.debug(logging.DEBUG, "Trying to login with %s:%s" % (repr(l_user), repr(hashed_pw_string))) obj = self.json_obj('user.login', {'user': l_user, 'password': l_password}, auth=False) result = self.do_request(obj) self.auth = result['result'] def test_login(self): if self.auth != '': obj = self.json_obj('user.checkAuthentication', {'sessionid': self.auth}) result = self.do_request(obj) if not result['result']: self.auth = '' return False # auth hash bad return True # auth hash good else: return False def do_request(self, json_obj): headers = {'Content-Type': 'application/json-rpc', 'User-Agent': 'python/zabbix_api'} if self.httpuser: self.debug(logging.INFO, "HTTP Auth enabled") auth = 'Basic ' + string.strip(base64.encodestring(self.httpuser + ':' + self.httppasswd)) headers['Authorization'] = auth self.r_query.append(str(json_obj)) self.debug(logging.INFO, "Sending: " + str(json_obj)) self.debug(logging.DEBUG, "Sending headers: " + str(headers)) request = urllib2.Request(url=self.url, data=json_obj.encode('utf-8'), headers=headers) if self.proto == "https": if HAS_SSLCONTEXT and not self.validate_certs: ssl._create_default_https_context = ssl._create_unverified_context ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE https_handler = urllib2.HTTPSHandler(debuglevel=0, context=ctx) else: https_handler = urllib2.HTTPSHandler(debuglevel=0) opener = urllib2.build_opener(https_handler) elif self.proto == "http": http_handler = urllib2.HTTPHandler(debuglevel=0) opener = urllib2.build_opener(http_handler) else: raise ZabbixAPIException("Unknow protocol %s" % self.proto) urllib2.install_opener(opener) try: response = opener.open(request, timeout=self.timeout) except ssl.SSLError as e: if hasattr(e, 'message'): e = e.message raise ZabbixAPIException("ssl.SSLError - %s" % e) except socket.timeout as e: raise APITimeout("HTTP read timeout",) except urllib2.URLError as e: if hasattr(e, 'message'): e = e.message raise ZabbixAPIException("urllib2.URLError - %s" % e) self.debug(logging.INFO, "Response Code: " + str(response.code)) # NOTE: Getting a 412 response code means the headers are not in the # list of allowed headers. if response.code != 200: raise ZabbixAPIException("HTTP ERROR %s: %s" % (response.status, response.reason)) reads = response.read() if len(reads) == 0: raise ZabbixAPIException("Received zero answer") try: jobj = json.loads(reads.decode('utf-8')) except ValueError as msg: print ("unable to decode. returned string: %s" % reads) sys.exit(-1) self.debug(logging.DEBUG, "Response Body: " + str(jobj)) self.id += 1 if 'error' in jobj: # some exception msg = "Error %s: %s, %s while sending %s" % (jobj['error']['code'], jobj['error']['message'], jobj['error']['data'], str(json_obj)) if re.search(".*already\sexists.*", jobj["error"]["data"], re.I): # already exists raise Already_Exists(msg, jobj['error']['code']) else: raise ZabbixAPIException(msg, jobj['error']['code']) return jobj def logged_in(self): if self.auth != '': return True return False def api_version(self, **options): self.__checkauth__() obj = self.do_request(self.json_obj('apiinfo.version', options, auth=False)) return obj['result'] def __checkauth__(self): if not self.logged_in(): raise ZabbixAPIException("Not logged in.") def __getattr__(self, name): return ZabbixAPISubClass(self, dict({"prefix": name}, **self.kwargs)) class ZabbixAPISubClass(ZabbixAPI): """ wrapper class to ensure all calls go through the parent object """ parent = None data = None def __init__(self, parent, data, **kwargs): self._setuplogging() self.debug(logging.INFO, "Creating %s" % self.__class__.__name__) self.data = data self.parent = parent # Save any extra info passed in for key, val in kwargs.items(): setattr(self, key, val) self.debug(logging.WARNING, "Set %s:%s" % (repr(key), repr(val))) def __getattr__(self, name): if self.data["prefix"] == "configuration" and name == "import_": # workaround for "import" method name = "import" def method(*opts): return self.universal("%s.%s" % (self.data["prefix"], name), opts[0]) return method def __checkauth__(self): self.parent.__checkauth__() def do_request(self, req): return self.parent.do_request(req) def json_obj(self, method, param): return self.parent.json_obj(method, param) @dojson @checkauth def universal(self, **opts): return opts Nagstamon/Nagstamon/thirdparty/zenoss_api.py000066400000000000000000000142541316117564000216640ustar00rootroot00000000000000#!/usr/bin/python # port from the zenstamon zenoss_api.py code into nagstamon: # Zenoss-4.x JSON API Example (python) # Copyright (C) 2016 Jake Murphy Far Edge Technology # # 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 # NOTES: # Currently experimental version, only viewing and acknowledging works at present. import os import base64 import zlib import hashlib import logging import string import sys import urllib import re from collections import deque try: # Separate module or Python <2.6 import simplejson as json except ImportError: # Python >=2.6 import json ZENOSS_INSTANCE = '' ZENOSS_USERNAME = '' ZENOSS_PASSWORD = '' ROUTERS = {'MessagingRouter': 'messaging', 'EventsRouter': 'evconsole', 'ProcessRouter': 'process', 'ServiceRouter': 'service', 'DeviceRouter': 'device', 'NetworkRouter': 'network', 'TemplateRouter': 'template', 'DetailNavRouter': 'detailnav', 'ReportRouter': 'report', 'MibRouter': 'mib', 'ZenPackRouter': 'zenpack'} class ZenossAPI(object): def __init__(self, debug=False, Server=None): """ Initialise the API connection, log in, and store authentication cookie """ self.set_config_data(Server) self.urlOpener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor()) if debug: self.urlOpener.add_handler(urllib.request.HTTPHandler(debuglevel=1)) self.reqCount = 1 self._login() #login to the zenoss system def _login(self): # Construct POST parameters and submit login. loginParams = urllib.parse.urlencode(dict( __ac_name=self.ZENOSS_USERNAME, __ac_password=self.ZENOSS_PASSWORD, submitted='true', came_from=self.ZENOSS_INSTANCE + '/zport/dmd')).encode('ascii') self.urlOpener.open(self.ZENOSS_INSTANCE + '/zport/acl_users/cookieAuthHelper/login', loginParams) def set_config_data(self, Server): if True: self.ZENOSS_INSTANCE = 'http://' + Server.server_url + ':' + Server.server_port self.ZENOSS_USERNAME = Server.username self.ZENOSS_PASSWORD = Server.password else: pass def _router_request(self, router, method, data=[]): if router not in ROUTERS: raise Exception('Router "' + router + '" not available.') # Contruct a standard URL request for API calls req = urllib.request.Request(self.ZENOSS_INSTANCE + '/zport/dmd/' + ROUTERS[router] + '_router') # NOTE: Content-type MUST be set to 'application/json' for these requests req.add_header('Content-type', 'application/json; charset=utf-8') # Convert the request parameters into JSON reqData = json.dumps([dict( tid=self.reqCount, type='rpc', data=data, method=method, action=router)]).encode('ascii') # Increment the request count ('tid'). More important if sending multiple # calls in a single request self.reqCount += 1 # Submit the request and convert the returned JSON to objects try: response = self.urlOpener.open(req, reqData) except: self._login() return {'result': {'events': []}} #return an empty object if response.code != 200: raise ZenossAPIException("HTTP ERROR %s: %s" % (response.status, response.reason)) try: jsondata = response.read() except: raise ZenossAPIException("Couldn't read response data") if len(jsondata) == 0: raise ZenossAPIException("Received zero json data") try: jsonobj = json.loads(jsondata.decode('utf-8')) except ValueError as msg: self._login() return {'result': {'events': []}} #return an 'empty' json object on failure return jsonobj ''' The API ''' def get_event(self, device=None, component=None, eventClass=None): data = dict(start=0, limit=500, dir='ASC', sort='severity') data['uid'] = '/zport/dmd' data['sort'] = 'device' data['keys'] = ['eventState', 'severity', 'device', 'component', 'eventClass', 'message', 'firstTime', 'lastTime', 'count', 'DevicePriority', 'evid', 'eventClassKey'] data['params'] = dict(severity=[5, 4, 3], eventState=[0, 1], tags=[]) if device: data['params']['device'] = device if component: data['params']['component'] = component if eventClass: data['params']['eventClass'] = eventClass return self._router_request('EventsRouter', 'query', [data])['result'] def set_event_ack(self, evids): data = dict(limit=100) data['evids'] = [evids] return self._router_request('EventsRouter', 'acknowledge', [data])['result'] def remove_event_ack(self, evids): data = dict(limit=100) data['evids'] = [evids] return self._router_request('EventsRouter', 'reopen', [data])['result'] def remove_event(self, evids): data = dict(limit=100) data['evids'] = [evids] return self._router_request('EventsRouter', 'close', [data])['result'] class ZenossAPIException(Exception): """ generic zenoss api exception """ passNagstamon/README.md000066400000000000000000000031001316117564000142620ustar00rootroot00000000000000Nagstamon ========= Nagstamon is a status monitor for the desktop. It connects to multiple Nagios, Icinga, Opsview, Centreon, Op5 Monitor/Ninja, Check_MK Multisite and Thruk monitoring servers. Experimental support is provided for Zabbix, Zenoss and Livestatus monitors. It resides in systray, as a floating statusbar or fullscreen at the desktop showing a brief summary of critical, warning, unknown, unreachable and down hosts and services. It pops up a detailed status overview when being touched by the mouse pointer. Connections to displayed hosts and services are easily established by context menu via SSH, RDP, VNC or any self defined actions. Users can be notified by sound. Hosts and services can be filtered by category and regular expressions. It is inspired by Nagios Checker for Firefox – just without an open Firefox window all the time to monitor the network. Nagstamon is released under the GPLv2 and free to use and modify. Nagstamon is written in Python 3 and uses the Qt 5 GUI toolkit which makes it very portable. It has been tested successfully on latest Ubuntu, Debian, Windows, NetBSD, OpenBSD, FreeBSD and MacOS X. It works with GNOME, KDE, Windows and macOS desktops. Successfully tested monitors include: - Nagios 1.x, 2.x, 3.x and 4.x - Icinga 1.2+ and 2.3+ - Opsview 5+ - Centreon 2.3+ - Op5 Monitor 7+ - Check_MK/Multisite 1.1.10+ - Thruk 1.5.0+ - Livestatus – experimental - Zabbix 2.2+ – experimental - Zenoss – experimental - Monitos3 - experimental - SNAG-View3 - experimental See https://nagstamon.ifw-dresden.de for further information. Nagstamon/build/000077500000000000000000000000001316117564000141105ustar00rootroot00000000000000Nagstamon/build/build.py000066400000000000000000000211331316117564000155610ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2016 Henri Wahl et al. # # 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 import platform import os, os.path import sys import shutil import subprocess import zipfile import glob import time CURRENT_DIR = os.getcwd() NAGSTAMON_DIR = os.path.normpath('{0}{1}..{1}'.format(CURRENT_DIR, os.sep)) sys.path.append(NAGSTAMON_DIR) SCRIPTS_DIR = '{0}{1}scripts-{2}.{3}'.format(CURRENT_DIR, os.sep, sys.version_info.major, sys.version_info.minor) from Nagstamon.Config import AppInfo VERSION = AppInfo.VERSION ARCH = platform.architecture()[0][0:2] ARCH_OPTS = {'32': ('win32', 'win32', '', 'x86'), '64': ('win-amd64', 'amd64', '(X86)', 'x64')} PYTHON_VERSION = '{0}.{1}'.format(sys.version_info[0], sys.version_info[1]) def winmain(): """ execute steps necessary for compilation of Windows binaries and setup.exe """ # InnoSetup does not like VersionInfoVersion with letters, only 0.0.0.0 schemed numbers if 'alpha' in VERSION.lower() or 'beta' in VERSION.lower() or 'rc' in VERSION.lower() or '-' in VERSION.lower(): VERSION_IS = VERSION.replace('alpha', '').replace('beta', '').replace('rc', '').replace('-', '.').replace('..', '.') VERSION_IS = VERSION_IS.split('.') version_segments = list() for part in VERSION_IS: if len(part) < 4: version_segments.append(part) else: version_segments.append(part[0:4]) version_segments.append(part[4:]) VERSION_IS = '.'.join(version_segments) else: VERSION_IS = VERSION print('VERSION_IS:', VERSION_IS) ISCC = r'{0}{1}Inno Setup 5{1}iscc.exe'.format(os.environ['PROGRAMFILES{0}'.format(ARCH_OPTS[ARCH][2])], os.sep) DIR_BUILD_EXE = '{0}{1}dist{1}Nagstamon'.format(CURRENT_DIR, os.sep, ARCH_OPTS[ARCH][0], PYTHON_VERSION) DIR_BUILD_NAGSTAMON = '{0}{1}dist{1}Nagstamon-{2}-win{3}'.format(CURRENT_DIR, os.sep, VERSION, ARCH) FILE_ZIP = '{0}.zip'.format(DIR_BUILD_NAGSTAMON) # clean older binaries for file in (DIR_BUILD_EXE, DIR_BUILD_NAGSTAMON, FILE_ZIP): if os.path.exists(file): try: shutil.rmtree(file) except: os.remove(file) # now with pyinstaller - dev version is able to run with Python 3.6 subprocess.call(['{0}\\Scripts\\pyinstaller'.format(sys.base_prefix), '--noconfirm', '--add-data=..\\Nagstamon/resources;resources', '--icon=..\\Nagstamon\\resources\\nagstamon.ico', '--windowed', '--name=Nagstamon', '--hidden-import=PyQt5.uic.plugins', '..\\nagstamon.py'], shell=True) # rename output os.rename(DIR_BUILD_EXE, DIR_BUILD_NAGSTAMON) # after cleaning start zipping and setup.exe-building - go back to original directory os.chdir(CURRENT_DIR) # create .zip file if os.path.exists('{0}{1}dist'.format(CURRENT_DIR, os.sep)): os.chdir('{0}{1}dist'.format(CURRENT_DIR, os.sep)) zip_archive = zipfile.ZipFile(FILE_ZIP, mode='w', compression=zipfile.ZIP_DEFLATED) zip_archive.write(os.path.basename(DIR_BUILD_NAGSTAMON)) for root, dirs, files in os.walk(os.path.basename(DIR_BUILD_NAGSTAMON)): for file in files: zip_archive.write('{0}{1}{2}'.format(root, os.sep, file)) # execute InnoSetup with many variables set by ISCC.EXE outside .iss file subprocess.call([ISCC, r'/Dsource={0}'.format(DIR_BUILD_NAGSTAMON), r'/Dversion_is={0}'.format(VERSION_IS), r'/Dversion={0}'.format(VERSION), r'/Darch={0}'.format(ARCH), r'/Darchs_allowed={0}'.format(ARCH_OPTS[ARCH][3]), r'/Dresources={0}{1}resources'.format(DIR_BUILD_NAGSTAMON, os.sep), r'/O{0}{1}dist'.format(CURRENT_DIR, os.sep), r'{0}{1}windows{1}nagstamon.iss'.format(CURRENT_DIR, os.sep)], shell=True) def macmain(): """ execute steps necessary for compilation of MacOS X binaries and .dmg file """ # go one directory up and run pyinstaller os.chdir('{0}{1}..'.format(CURRENT_DIR, os.sep)) # create one-file .app bundle by pyinstaller subprocess.call(['/usr/local/bin/pyinstaller', '--noconfirm', '--add-data=Nagstamon/resources:Nagstamon/resources', '--icon=Nagstamon/resources/nagstamon.icns', '--name=Nagstamon', '--osx-bundle-identifier=de.ifw-dresden.nagstamon', '--windowed', '--onefile', 'nagstamon.py']) # go back to build directory os.chdir(CURRENT_DIR) # use own modified Info.plist for Retina compatibility shutil.copyfile('../Nagstamon/resources/Info.plist', '../dist/Nagstamon.app/Contents/Info.plist') # create staging DMG folder for later compressing of DMG shutil.rmtree('Nagstamon {0} Staging DMG'.format(VERSION), ignore_errors=True) # copy app bundle folder shutil.move('../dist/Nagstamon.app', 'Nagstamon {0} Staging DMG/Nagstamon.app'.format(VERSION)) # cleanup before new images get created for dmg_file in glob.iglob('*.dmg'): os.unlink(dmg_file) # create DMG subprocess.call([ 'hdiutil create -srcfolder "Nagstamon {0} Staging DMG" -volname "Nagstamon {0}" -fs HFS+ -format UDRW -size 100M "Nagstamon {0} uncompressed.dmg"'.format( VERSION)], shell=True) # Compress DMG subprocess.call([ 'hdiutil convert "Nagstamon {0} uncompressed".dmg -format UDZO -imagekey zlib-level=9 -o "Nagstamon {0}.dmg"'.format( VERSION)], shell=True) # Delete uncompressed DMG file as it is no longer needed os.unlink('Nagstamon {0} uncompressed.dmg'.format(VERSION)) def debmain(): shutil.rmtree(SCRIPTS_DIR, ignore_errors=True) shutil.rmtree('{0}{1}.pybuild'.format(CURRENT_DIR, os.sep), ignore_errors=True) shutil.rmtree('{0}{1}debian'.format(NAGSTAMON_DIR, os.sep), ignore_errors=True) os.chdir(NAGSTAMON_DIR) # masquerade .py file as .py-less shutil.copyfile('nagstamon.py', 'nagstamon') shutil.copytree('{0}{1}debian{1}'.format(CURRENT_DIR, os.sep), '{0}{1}debian'.format(NAGSTAMON_DIR, os.sep)) os.chmod('{0}{1}debian{1}rules'.format(CURRENT_DIR, os.sep), 0o755) subprocess.call(['fakeroot', 'debian/rules', 'build']) subprocess.call(['fakeroot', 'debian/rules', 'binary']) # copy .deb file to current directory for deb in glob.iglob('../nagstamon*.deb'): shutil.move(deb, CURRENT_DIR) def rpmmain(): """ create .rpm file via setup.py bdist_rpm - most settings are in setup.py """ os.chdir(NAGSTAMON_DIR) # masquerade .py file as .py-less shutil.copyfile('nagstamon.py', 'nagstamon') # workaround for manpage gzipping bug in bdist_rpm import gzip man = open('Nagstamon/resources/nagstamon.1', 'rb') mangz = gzip.open('Nagstamon/resources/nagstamon.1.gz', 'wb') mangz.writelines(man) mangz.close() man.close() # run setup.py for rpm creation subprocess.call(['python3', 'setup.py', 'bdist_rpm'], shell=False) DISTS = { 'debian': debmain, 'Ubuntu': debmain, 'LinuxMint': debmain, 'fedora': rpmmain } if __name__ == '__main__': if platform.system() == 'Windows': winmain() elif platform.system() == 'Darwin': macmain() else: dist = platform.dist()[0] if dist in DISTS: DISTS[dist]() else: print('Your system is not supported for automated build yet') Nagstamon/build/debian/000077500000000000000000000000001316117564000153325ustar00rootroot00000000000000Nagstamon/build/debian/README.source000066400000000000000000000035071316117564000175160ustar00rootroot00000000000000This package uses quilt to manage all modifications to the upstream source. Changes are stored in the source package as diffs in debian/patches and applied during the build. To configure quilt to use debian/patches instead of patches, you want either to export QUILT_PATCHES=debian/patches in your environment or use this snippet in your ~/.quiltrc: for where in ./ ../ ../../ ../../../ ../../../../ ../../../../../; do if [ -e ${where}debian/rules -a -d ${where}debian/patches ]; then export QUILT_PATCHES=debian/patches fi done To get the fully patched source after unpacking the source package, cd to the root level of the source package and run: quilt push -a The last patch listed in debian/patches/series will become the current patch. To add a new set of changes, first run quilt push -a, and then run: quilt new where is a descriptive name for the patch, used as the filename in debian/patches. Then, for every file that will be modified by this patch, run: quilt add before editing those files. You must tell quilt with quilt add what files will be part of the patch before making changes or quilt will not work properly. After editing the files, run: quilt refresh to save the results as a patch. Alternately, if you already have an external patch and you just want to add it to the build system, run quilt push -a and then: quilt import -P /path/to/patch quilt push -a (add -p 0 to quilt import if needed). as above is the filename to use in debian/patches. The last quilt push -a will apply the patch to make sure it works properly. To remove an existing patch from the list of patches that will be applied, run: quilt delete You may need to run quilt pop -a to unapply patches first before running this command. Nagstamon/build/debian/changelog000066400000000000000000000337231316117564000172140ustar00rootroot00000000000000nagstamon (3.0.2) stable; urgency=low * New upstream - fixed Retina problem for macOS -- Henri Wahl Fri, 22 Sep 2017 14:00:00 +0200 nagstamon (3.0.1) stable; urgency=low * New upstream - fixes for Zabbix - fixed filters for Op5 - fixes for update detection and self-signed certificates -- Henri Wahl Tue, 19 Sep 2017 16:00:00 +0200 nagstamon (3.0) stable; urgency=low * New upstream - added Kerberos authentification - added increased security by taking TLS seriously - added window mode - added large OK label for fullscreen and window mode - added click-somewhere-to-close-statuswindow mode - added custom views to Check_MK - added monitor type Monitos3 - added monitor type SNAG-View 3 - added monitor type Sensu - fixes statuswindow garbage when changing mode - fixed filename bug - fixed check for .Xauthority file - fixes for Centreon - fixes for Check_MK 1.4.0 - fixes for Zabbix - switched to pyinstaller -- Henri Wahl Wed, 13 Sep 2017 16:00:00 +0200 nagstamon (2.0.1) stable; urgency=low * New upstream - Major Centreon bug making it useless - Icinga version check fix - Thruk login fix - EWMH initialization change - Systrayicon left mouse click without context menu - DBus crash workaround -- Henri Wahl Thu, 20 Oct 2016 08:00:00 +0200 nagstamon (2.0) stable; urgency=low * New upstream - Based on Qt 5 it now comes with a better integrated look-and-feel – especially remarkable on MacOS - Partly simplified design - Less clutter in setting dialogs - Runs on latest Windows and MacOS - Uses QT 5 multimedia which means native sound on Linux and MacOS - Uses only SVG graphics – allows changing colors even in systray icon - Customizable font and font size - Adjust to dark or light desktop theme - Action allowing to copy host/service information to clipboard - Added ‘Archive Event’ action for Check_MK monitors - Additionally supports IcingaWeb2 - Updated Opsview and Centreon to support latest monitor server versions - Experimental support for Livestatus and Zenoss - New build script based on cx-Freeze for Windows and MacOS - Native 64 bit version for Windows - No or less memory leaks, especially in Windows - Make sure only one instance per config is running -- Henri Wahl Mon, 29 Aug 2016 16:00:00 +0200 nagstamon (1.0.1) stable; urgency=low * New upstream - added option to disable system keyring storage to prevent crashes - reverted default sorting order to "Descending" - fixed too narrow fullscreen display - fixed vanishing Nagstamon submenu in Ubuntu Appindicator -- Henri Wahl Mon, 22 Sep 2014 9:00:00 +0200 nagstamon (1.0) stable; urgency=low * New upstream - added custom event notification with custom commands - added highlighting of new events - added storage of passwords in OS keyring - added optional tooltip for full status information - added support for applying custom actions to specific monitor only - added copy buttons for servers and actions dialogs - added stopping notification if event already vanished - added support for Op5Monitor 6.3 instead of Ninja - added experimental Zabbix support - added automatic refreshing after acknowledging - added permanent hamburger menu - unified layout of dialogs - various Check_MK improvements - fixed old regression not-staying-on-top-bug - fixed Check_MK-Recheck-DOS-bug - fixed pop window size calculation on multiple screens - fixed following popup window on multiple screens - fixed hiding dialogs in MacOSX - fixed ugly statusbar font in MacOSX - fixed use of changed colors - fixed non-ascending default sort order - fixed Opsview downtime dialog - fixed sometimes not working context menu - fixed some GUI glitches - fixed password saving bug - fixed Centreon language inconsistencies - fixed regression Umlaut bug -- Henri Wahl Mon, 28 Jul 2014 09:30:00 +0200 nagstamon (1.0rc2) unstable; urgency=low * New upstream - added automatic refreshing after acknowledging - added permanent hamburger menu - unified layout of dialogs - fixed some GUI glitches - fixed password saving bug - fixed Centreon language inconsistencies - fixed regression Umlaut bug -- Henri Wahl Tue, 08 Jul 2014 11:00:00 +0200 nagstamon (1.0rc1) unstable; urgency=low * New upstream - added custom event notification with custom commands - added highlighting of new events - added storage of passwords in OS keyring - added optional tooltip for full status information - added support for applying custom actions to specific monitor only - added copy buttons for servers and actions dialogs - added stopping notification if event already vanished - added support for Op5Monitor 6.3 instead of Ninja - added experimental Zabbix support - fixed old regression not-staying-on-top-bug - fixed Check_MK-Recheck-DOS-bug - fixed pop window size calculation on multiple screens - fixed following popup window on multiple screens - fixed hiding dialogs in MacOSX - fixed ugly statusbar font in MacOSX - fixed use of changed colors - fixed non-ascending default sort order - fixed Opsview downtime dialog - fixed sometimes not working context menu - various Check_MK improvements -- Henri Wahl Tue, 24 Jun 2014 11:00:00 +0200 nagstamon (0.9.11) stable; urgency=low * New upstream - added Ubuntu AppIndicator support - added libnotify desktop notification support - added Centreon criticality support - fixed broken authentication dialog - fixed wrong OK state for Nagios and Icinga - fixed Correct-Statusbar-Position-O-Matic - fixed some Thruk issues - fixed popup resizing artefact - fixed some server edit dialog bugs - fixed missing auth field in Icinga when credentials are wrong - fixed quoting URLs for browser actions -- Henri Wahl Wed, 11 Sep 2013 09:00:00 +0200 nagstamon (0.9.11rc1) unstable; urgency=low * New upstream - added Ubuntu AppIndicator support - added libnotify desktop notification support - added Centreon criticality support - fixed broken authentication dialog - fixed wrong OK state for Nagios and Icinga - fixed Correct-Statusbar-Position-O-Matic - fixed some Thruk issues - fixed popup resizing artefact - fixed some server edit dialog bugs - fixed missing auth field in Icinga when credentials are wrong -- Henri Wahl Mon, 29 Jul 2013 10:35:00 +0200 nagstamon (0.9.10) stable; urgency=low * New upstream - added fullscreen option - added Thruk support - added Check_MK cookie-based auth - added new Centreon autologin option - added configurable default sort order - added filter for hosts in hard/soft state for Nagios, Icinga, Opsview and Centreon - added $STATUS-INFO$ variable for custom actions - added audio alarms also in fullscreen mode - improved update interval set in seconds instead minutes - improved Icinga JSON support - improved Centreon 2.4 xml/broker support - improved Nagios 3.4 pagination support - improved nicer GTK theme Murrine on MacOSX - fixed security bug - fixed some memory leaks - fixed superfluous passive icon for Check_MK - fixed blocking of shutdown/reboot on MacOSX - fixed saving converted pre 0.9.9 config immediately - fixed statusbar position when offscreen - fixed some GUI issues - fixed update detection -- Henri Wahl Wed, 11 Jul 2013 11:07:13 +0200 nagstamon (0.9.10rc2) unstable; urgency=low * New upstream - audio alarms also in fullscreen mode - adjust x0 y0 position of statusbar when offscreen - save converted pre 0.9.9 config immediately -- Henri Wahl Tue, 09 Jul 2013 14:25:00 +0200 nagstamon (0.9.10rc1) unstable; urgency=low * New upstream - added fullscreen option - added Thruk support - added Check_MK cookie-based auth - added new Centreon autologin option - added configurable default sort order - added filter for hosts in hard/soft state for Nagios, Icinga, Opsview and Centreon - added $STATUS-INFO$ variable for custom actions - update interval set in seconds instead minutes - improved Icinga JSON support - improved Centreon 2.4 xml/broker support - improved Nagios 3.4 pagination support - uses nicer GTK theme Murrine on MacOSX - fixed some memory leaks - fixed superfluous passive icon for Check_MK - fixed blocking of shutdown/reboot on MacOSX - fixed some GUI issues - fixed update detection -- Henri Wahl Wed, 03 Jul 2013 10:25:00 +0200 nagstamon (0.9.9.1-1) stable; urgency=low * New upstream - added custom actions in context menu - added reauthentication in case of authenticaton problems - changed configuration file to configuration directory (default: ~/.nagstamon) - added filter for flapping hosts and services - added history button for monitors - added shortcut to filter settings in popup window - improved keyboard usage in acknowledge/downtime/submit dialogs - fixed bug in Icinga acknowledgement - fixed bug in Check_MK Multisite sorting - fixed some Check_MK Multisite UTF trouble - fixed some GUI artefacts when resizing popup window -- Henri Wahl Fri, 13 Apr 2012 11:25:00 +0200 nagstamon (0.9.8.1-1) stable; urgency=low * New upstream - added customizable acknowledge/downtime/submit-result defaults - added regexp filter for status information column - added option to connect to hosts via its monitor hostname without HTTP overhead - added ability to keep status detail popup open despite hovering away - added option to change offset between popup window and systray icon to avoid partly hidden popup - fixed some popup artefacts - fixed various bugs with acknowledgement flags (persistent/sticky/notification), now they are actually working - fixed some issues when running on MacOS X -- Henri Wahl Wed, 10 Oct 2011 14:49:00 +0200 nagstamon (0.9.7.1-1) stable; urgency=low * New upstream - hot fix for broken Centreon support - sf.net bug 3309166 -- Henri Wahl Fri, 30 May 2011 12:01:00 +0200 nagstamon (0.9.7-1) stable; urgency=low * New upstream - on some servers now context menu allows submitting check results for hosts and services - added filter for services on acknowledged hosts - added icons for "passiveonly" and "flapping" hosts and services - fix for uneditable text entry fields in settings dialog - sf.net bug 3300873 - fix for not working filter "services on hosts in maintenance" - sf.net bug 3299790 - fix for soft state detection in Centreon - sf.net bug 3303861 - fix for not filtered services which should have been filtered - sf.net bug 3308008 -- Henri Wahl Fri, 27 May 2011 16:01:00 +0200 nagstamon (0.9.6.1-1) stable; urgency=low * fix for sf.net bug 3298321 - displaying error when all is OK -- Henri Wahl Fri, 06 May 2011 16:01:00 +0200 nagstamon (0.9.6-1) stable; urgency=low * New upstreeam release - improved, full Ninja support - rewritten filtering mechanism allows new features - displaying icons in status overview popup indicating states "acknowledged" and "scheduled downtime" - added option to play notification sounds more than once - small UI improvements - uses BeautifulSoup instead of lxml - uses GTK UI Builder instead of glade - as always: bugfixes -- Henri Wahl Fri, 06 May 2011 16:01:00 +0200 nagstamon (0.9.5-1) stable; urgency=low * New upstream release - added op5 Ninja support - added Check_MK Multisite support - improved Icinga support (compatibility with Icinga 1.3) - improved Centreon support (compatible with Centreon 2.1) - added sortable columns in status overview - added customizable colors - better debugging and error messages - password must not be stored in config file - major memory leak closed, various bugs fixed -- Henri Wahl Tue, 05 Apr 2011 13:23:00 +0200 nagstamon (0.9.4-1) stable; urgency=low * New upstream release (Closes: #582977) * removed debian/manpages * renamed debian/nagstamon.install to debian/install * debian/patches - removed settings_glade - removed setup_patch -- Carl Chenet Sat, 19 Jun 2010 12:46:42 +0200 nagstamon (0.9.3-2) stable; urgency=low * debian/patches/default_search - disable the default search for newer versions (Closes: #585928) * debian/patches/series - added default_search -- Carl Chenet Tue, 15 Jun 2010 00:41:22 +0200 nagstamon (0.9.3-1) stable; urgency=low * New upstream release * Switching to 3.0 source format * debian/patches - added settings_glade patch to close #2998035 upstream bug - added setup_patch to remove an absolute link for the upstream manpage * debian/control - added quilt in Build-Depends - added the support of Opsview servers in the long description * debian/nagstamon.manpages - switched to the file provided by the upstream * debian/nagstamon.desktop - commenting the OnlyShowIn directive -- Carl Chenet Sun, 23 May 2010 12:47:11 +0200 nagstamon (0.9.2-2) stable; urgency=low * debian/control - Added a mandatory runtime missing dependency python-pkg-resources. - Fixed a typo in the long message. -- Carl Chenet Wed, 24 Mar 2010 23:18:21 +0100 nagstamon (0.9.2-1) stable; urgency=low * Initial release. (Closes: #534842) -- Carl Chenet Mon, 22 Feb 2010 14:16:44 +0100 Nagstamon/build/debian/compat000066400000000000000000000000021316117564000165300ustar00rootroot000000000000009 Nagstamon/build/debian/control000066400000000000000000000025001316117564000167320ustar00rootroot00000000000000Source: nagstamon Section: utils X-Python3-Version: >= 3.4 Priority: optional Maintainer: Python Applications Packaging Team Uploaders: Carl Chenet Build-Depends: debhelper (>= 9), python3 (>= 3.4), quilt (>= 0.63) Build-Depends-Indep: python-support Standards-Version: 3.9.2 Homepage: https://nagstamon.ifw-dresden.de Vcs-Git: git://github.com/HenriWahl/Nagstamon.git Vcs-Browser: https://codeload.github.com/HenriWahl/Nagstamon/zip/master Package: nagstamon Architecture: all Depends: ${python3:Depends}, ${misc:Depends}, python3-pkg-resources, python3-bs4, python3-lxml, python3-pyqt5, python3-pyqt5.qtsvg, python3-pyqt5.qtmultimedia, libqt5multimedia5-plugins, python3-requests, python3-requests-kerberos, python3-psutil, python3-dbus.mainloop.pyqt5, python3-keyring Description: Nagios status monitor which takes place in systray or on desktop Nagstamon is a Nagios status monitor which takes place in systray or on desktop (GNOME, KDE) as floating statusbar to inform you in realtime about the status of your Nagios and some of its derivatives monitored network. It allows to connect to multiple Nagios, Icinga, Opsview, Centreon, Op5Monitor, Check_MK Multisite, Thruk, Zabbix and Zenoss servers. Events could be handled by instant access to failed hosts/services. Nagstamon/build/debian/copyright000066400000000000000000000023151316117564000172660ustar00rootroot00000000000000This package was debianized by Carl Chenet on Mon, 22 Feb 2010 00:00:15 +0200. It was downloaded from http://sourceforge.net/projects/nagstamon/ Upstream Author: Henri Wahl Copyright: Copyright © 2008-2011 Henri Wahl License: 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; version 2 of the License. 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. On Debian systems, the complete text of the GNU General Public License can be found in `/usr/share/common-licenses/GPL-2'. The Debian packaging is: Copyright © 2010 Carl Chenet and is also under the same license described above. Nagstamon/build/debian/install000066400000000000000000000000631316117564000167220ustar00rootroot00000000000000debian/nagstamon.desktop usr/share/applications Nagstamon/build/debian/nagstamon.desktop000066400000000000000000000002271316117564000207150ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=Nagstamon Comment=Nagios status monitor Icon=nagstamon Exec=nagstamon Terminal=false Categories=System;Monitor; Nagstamon/build/debian/patches/000077500000000000000000000000001316117564000167615ustar00rootroot00000000000000Nagstamon/build/debian/patches/default_search000066400000000000000000000014111316117564000216520ustar00rootroot00000000000000# Description: disable the default search for newer version # Author: Carl Chenet Index: nagstamon_0.9.3/Nagstamon/nagstamonConfig.py =================================================================== --- nagstamon_0.9.3.orig/Nagstamon/nagstamonConfig.py 2010-06-15 00:38:16.000000000 +0200 +++ nagstamon_0.9.3/Nagstamon/nagstamonConfig.py 2010-06-15 00:38:39.000000000 +0200 @@ -39,7 +39,7 @@ self.connect_by_dns_yes = True self.connect_by_dns_no = False self.debug_mode = False - self.check_for_new_version = True + self.check_for_new_version = False self.notification = True self.notification_flashing = True # because of nonexistent windows systray popup support I'll let it be now Nagstamon/build/debian/patches/series000066400000000000000000000000171316117564000201740ustar00rootroot00000000000000default_search Nagstamon/build/debian/rules000077500000000000000000000005561316117564000164200ustar00rootroot00000000000000#!/usr/bin/make -f # otherwise LICENSE file is missing for about dialog #export DH_ALWAYS_EXCLUDE=LICENSE %: dh $@ --with python3 --buildsystem=pybuild #override_dh_link: # dh_link usr/share/nagstamon/nagstamon.py usr/bin/nagstamon #override_dh_auto_install: # dh_auto_install -- --install-lib=/usr/share/nagstamon \ # --install-scripts=/usr/share/nagstamon \ Nagstamon/build/debian/source/000077500000000000000000000000001316117564000166325ustar00rootroot00000000000000Nagstamon/build/debian/source/format000066400000000000000000000000141316117564000200400ustar00rootroot000000000000003.0 (quilt) Nagstamon/build/debian/watch000066400000000000000000000000721316117564000163620ustar00rootroot00000000000000version=3 http://sf.net/nagstamon/nagstamon_(.+)\.tar\.gz Nagstamon/build/redhat/000077500000000000000000000000001316117564000153575ustar00rootroot00000000000000Nagstamon/build/redhat/nagstamon.desktop000066400000000000000000000003341316117564000207410ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=Nagstamon Comment=Nagios status monitor Icon=nagstamon Exec=nagstamon Terminal=false Categories=System;Monitor; StartupNotify=true GenericName=Nagios status monitor for the desktop Nagstamon/build/redhat/nagstamon.spec000066400000000000000000000052351316117564000202270ustar00rootroot00000000000000%global gitdate 20160602 %global commit 7139844d1a8109ba45f03601293ab70050b7dc94 %global shortcommit %(c=%{commit}; echo ${c:0:7}) Name: nagstamon Version: 2.0 Release: 0.1.%{gitdate}git%{shortcommit}%{?dist} Summary: Nagios status monitor for desktop License: GPLv2+ URL: https://nagstamon.ifw-dresden.de Source0: https://github.com/HenriWahl/Nagstamon/archive/%{commit}/nagstamon-%{commit}.tar.gz BuildArch: noarch BuildRequires: python3-devel BuildRequires: python3-qt5-devel BuildRequires: desktop-file-utils Requires: python3 Requires: python3-beautifulsoup4 Requires: python3-crypto Requires: python3-cryptography Requires: python3-keyring Requires: python3-lxml Requires: python3-psutil Requires: python3-qt5 Requires: python3-requests Requires: python3-requests-kerberos Requires: python3-SecretStorage Requires: qt5-qtsvg Requires: qt5-qtmultimedia %description Nagstamon is a Nagios status monitor which takes place in system tray or on desktop (GNOME, KDE, Windows) as floating status bar to inform you in real-time about the status of your Nagios and derivatives monitored network. It allows to connect to multiple Nagios, Icinga, Opsview, Op5Monitor, Check_MK/Multisite, Centreon and Thruk servers. %prep %setup -qn Nagstamon-%{commit} %build %{__python3} setup.py build %install %{__python3} setup.py install --single-version-externally-managed -O1 --root=%{buildroot} #Fix 'non-executable-script' error chmod +x %{buildroot}%{python3_sitelib}/Nagstamon/Servers/Multisite.py chmod +x %{buildroot}%{python3_sitelib}/Nagstamon/thirdparty/keyring/cli.py chmod +x %{buildroot}%{python3_sitelib}/Nagstamon/Config.py #Provide directory to install icon for desktop file mkdir -p %{buildroot}%{_datadir}/pixmaps #Copy icon to pixmaps directory cp Nagstamon/resources/%{name}.svg %{buildroot}%{_datadir}/pixmaps/%{name}.svg #Remove execute bit from icon chmod -x %{buildroot}%{_datadir}/pixmaps/%{name}.svg #Remove the file extension for convenience mv %{buildroot}%{_bindir}/%{name}.py %{buildroot}%{_bindir}/%{name} desktop-file-install --dir %{buildroot}/%{_datadir}/applications\ --delete-original\ --set-icon=%{name}.svg\ %{buildroot}%{python3_sitelib}/Nagstamon/resources/%{name}.desktop %files %doc ChangeLog %license COPYRIGHT LICENSE %{_datadir}/pixmaps/%{name}.svg %{_datadir}/applications/%{name}.desktop %{python3_sitelib}/Nagstamon/ %{_bindir}/%{name} %{_mandir}/man1/%{name}.1* %{python3_sitelib}/%{name}*.egg-info %changelog * Sun Jun 05 2016 Momcilo Medic 2.0-0.1.20160602git7139844 - Initial .spec file Nagstamon/build/windows/000077500000000000000000000000001316117564000156025ustar00rootroot00000000000000Nagstamon/build/windows/nagstamon.iss000066400000000000000000000021061316117564000203100ustar00rootroot00000000000000[Setup] AppName=Nagstamon AppVerName=Nagstamon {#version} DefaultDirName={pf}\Nagstamon DefaultGroupName=Nagstamon AlwaysUsePersonalGroup=false ShowLanguageDialog=no SetupIconFile={#resources}\nagstamon.ico UsePreviousGroup=false OutputBaseFilename=Nagstamon-{#version}-win{#arch}_setup UninstallDisplayIcon={app}\resources\nagstamon.ico UsePreviousAppDir=false AppID={{44F7CFFB-4776-4DA4-9930-A07178069517} UninstallRestartComputer=false VersionInfoVersion={#version_is} VersionInfoCopyright=Henri Wahl VersionInfoProductName=Nagstamon VersionInfoProductVersion={#version_is} InternalCompressLevel=max Compression=lzma SolidCompression=true SourceDir={#source} ArchitecturesAllowed={#archs_allowed} ArchitecturesInstallIn64BitMode=x64 [Icons] Name: {group}\Nagstamon; Filename: {app}\nagstamon.exe; WorkingDir: {app}; IconFilename: {app}\resources\nagstamon.ico; IconIndex: 0 Name: {commonstartup}\Nagstamon; Filename: {app}\nagstamon.exe; WorkingDir: {app}; IconFilename: {app}\resources\nagstamon.ico; IconIndex: 0 [Files] Source: "*"; DestDir: {app}; Flags: recursesubdirs createallsubdirs Nagstamon/nagstacli.py000077500000000000000000000117601316117564000153400ustar00rootroot00000000000000#!/usr/bin/env python3 # encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2016 Henri Wahl et al. Maik Lüdeke # # 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 import sys import socket # import datetime # never used import re # fix/patch for https://bugs.launchpad.net/ubuntu/+source/nagstamon/+bug/732544 socket.setdefaulttimeout(30) # checks if there is a current value for the variable, if not the default value will be returned def checkDefaultValue(value, default): if value is None: return default else: return value # extracts the time from the start_time and adds hours and minutes def createEndTime(info_dict): # extracts the time from arg 'start_time' # then adds the hours/minutes to the start_date and reassambles the string regex = re.compile('(.*)\s(\d{1,2})([\:\.])(\d{1,2})(.*)') time_string = re.search(regex, info_dict['start_time']) start_hour = int(time_string.group(2)) separator = (time_string.group(3)) start_minute = int(time_string.group(4)) # if the time has a format like "12:12:53.122" the seconds and milliseconds were # extracted and then attached as string again if (len(time_string.group()) == 6): attached_timestring = int(time_string.group(5)) else: attached_timestring = "" # calculate the hour/minutes for downtime. convert 120 minutes to 2 h or 90 minutes to 1 h 30 min hour, minute = divmod(info_dict['minutes'] + int(start_minute) + (60 * (start_hour + info_dict['hours'])), 60) if hour > 23: print("it is (at the moment) not possible to end the timer after 0:00 o`clock") sys.exit(0) info_dict['end_time'] = (time_string.group(1) + " " + str(hour) + separator + '{0:02d}'.format(minute) + attached_timestring) return info_dict def executeCli(): # from Nagstamon.Config import (conf, # debug_queue) from Nagstamon.Config import conf from Nagstamon.Servers import (create_server) # Initialize global configuration from Nagstamon.Objects import (GenericHost) # creates new server object from given servername server = create_server(conf.servers[conf.cli_args.servername]) # gets the current default start/endtime from the server (default current time + 2h) start_end_time = server.get_start_end(conf.cli_args.hostname) default_start_time = start_end_time[0] default_end_time = start_end_time[1] # gets the default downtime duration from the nagstamon config # default_downtime = conf.defaults_downtime_duration_minutes # never used server.GetStatus() server.hosts[conf.cli_args.hostname] = GenericHost() server.hosts[conf.cli_args.hostname].name = conf.cli_args.hostname server.hosts[conf.cli_args.hostname].server = server.name server.hosts[conf.cli_args.hostname].site = "monitor" fixedType = {"y": True, "n": False} info_dict = dict() info_dict['host'] = conf.cli_args.hostname info_dict['service'] = conf.cli_args.service info_dict['author'] = server.username info_dict['comment'] = conf.cli_args.comment info_dict['fixed'] = fixedType[conf.cli_args.fixed] info_dict['start_time'] = checkDefaultValue(conf.cli_args.start_time, default_start_time) info_dict['end_time'] = default_end_time info_dict['hours'] = checkDefaultValue(conf.cli_args.hours, 0) info_dict['minutes'] = checkDefaultValue(conf.cli_args.minutes, conf.defaults_downtime_duration_minutes) info_dict['view_name'] = "host" info_dict = createEndTime(info_dict) # creates output,v which parameter were processed if conf.cli_args.output == 'y': print('trying to downtime host "' + info_dict['host'] + '", with the following parameters:') if info_dict['service'] != "": print('service: ', info_dict['service']) if info_dict['comment'] is not None: print('comment: ', info_dict['comment']) print('fixed: ', info_dict['fixed']) print('start time: ', info_dict['start_time']) print('end time: ', info_dict['end_time']) server.set_downtime(info_dict) try: if __name__ == '__main__': # ##debug_queue = list() executeCli() except Exception as err: import traceback traceback.print_exc(file=sys.stdout) Nagstamon/nagstamon.py000077500000000000000000000043221316117564000153560ustar00rootroot00000000000000#!/usr/bin/env python3 # encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2016 Henri Wahl et al. # # 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 import os import sys import socket import platform # fix/patch for https://bugs.launchpad.net/ubuntu/+source/nagstamon/+bug/732544 socket.setdefaulttimeout(30) try: if __name__ == '__main__': from Nagstamon.Config import conf from Nagstamon.Helpers import lock_config_folder # if there are more args, than the config folder, nagstaCLI is been executed if len(sys.argv) > 2: import nagstacli # fix crash if more than 2 args are passed - mprenditore nagstacli.executeCli() sys.exit(1) # Acquire the lock if not lock_config_folder(conf.configdir): print('An instance is already running this config ({})'.format(conf.configdir)) sys.exit(1) # get GUI from Nagstamon.QUI import (APP, statuswindow, check_version, check_servers) # ask for help if no servers are configured check_servers() # show and resize status window statuswindow.show() if not conf.fullscreen: statuswindow.adjustSize() if conf.check_for_new_version is True: check_version.check(start_mode=True, parent=statuswindow) APP.exec_() del(APP) sys.exit(0) except Exception as err: import traceback traceback.print_exc(file=sys.stdout) Nagstamon/setup.py000066400000000000000000000120341316117564000145230ustar00rootroot00000000000000#!/usr/bin/env python3 # encoding: utf-8 # Nagstamon - Nagios status monitor for your desktop # Copyright (C) 2008-2016 Henri Wahl et al. # # 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 # from distutils.core import setup import sys import platform import os.path from Nagstamon.Config import AppInfo # dummy debug queue for compiling debug_queue = list() NAME = AppInfo.NAME OS = platform.system() # make name lowercase for Linux/Unix if OS not in ['Windows', 'Darwin']: DIST, DIST_VERSION, DIST_NAME = platform.dist() NAME = NAME.lower() VERSION = AppInfo.VERSION.replace('-', '.') + '.' + DIST + DIST_VERSION NAGSTAMON_SCRIPT = 'nagstamon.py' from setuptools import setup os_dependent_include_files = ['Nagstamon/resources'] executables = [] if os.path.exists('nagstamon'): NAGSTAMON_SCRIPT = 'nagstamon' CLASSIFIERS = ['Intended Audience :: System Administrators', 'Development Status :: 5 - Production/Stable', 'Environment :: Win32 (MS Windows)', 'Environment :: X11 Applications', 'Environment :: MacOS X', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX :: Linux', 'Operating System :: POSIX', 'Natural Language :: English', 'Programming Language :: Python', 'Topic :: System :: Monitoring', 'Topic :: System :: Networking :: Monitoring'] # Dependencies are automatically detected, but it might need # fine tuning. build_exe_options = dict(packages=['PyQt5.QtNetwork', 'keyring.backends.kwallet', 'keyring.backends.OS_X', 'keyring.backends.SecretService', 'keyring.backends.Windows'], include_files=os_dependent_include_files, include_msvcr=True, excludes=[]) bdist_mac_options = dict(iconfile='Nagstamon/resources/nagstamon.icns', custom_info_plist='Nagstamon/resources/Info.plist') bdist_dmg_options = dict(volume_label='{0} {1}'.format(NAME, VERSION), applications_shortcut=False) bdist_rpm_options = dict(requires='python3 ' 'python3-beautifulsoup4 ' 'python3-crypto ' 'python3-cryptography ' 'python3-keyring ' 'python3-lxml ' 'python3-psutil ' 'python3-qt5 ' 'python3-requests ' 'python3-requests-kerberos ' 'python3-SecretStorage ' 'qt5-qtmultimedia ' 'qt5-qtsvg ', dist_dir='./build') setup(name=NAME, version=VERSION, license='GNU GPL v2', description='Nagios status monitor for desktop', long_description='Nagstamon is a Nagios status monitor which takes place in systray or on desktop (GNOME, KDE, Windows) as floating statusbar to inform you in realtime about the status of your Nagios and derivatives monitored network. It allows to connect to multiple Nagios, Icinga, Opsview, Op5Monitor, Check_MK/Multisite, Centreon and Thruk servers.', classifiers=CLASSIFIERS, author='Henri Wahl', author_email='h.wahl@ifw-dresden.de', url='https://nagstamon.ifw-dresden.de', download_url='https://nagstamon.ifw-dresden.de/files-nagstamon/stable/', scripts=[NAGSTAMON_SCRIPT], packages=['Nagstamon', 'Nagstamon.QUI', 'Nagstamon.Servers', 'Nagstamon.thirdparty', 'Nagstamon.thirdparty.Xlib', 'Nagstamon.thirdparty.Xlib.ext', 'Nagstamon.thirdparty.Xlib.protocol', 'Nagstamon.thirdparty.Xlib.support', 'Nagstamon.thirdparty.Xlib.xobject'], package_dir={'Nagstamon': 'Nagstamon'}, package_data={'Nagstamon': ['resources/*']}, data_files=[('%s/share/man/man1' % sys.prefix, ['Nagstamon/resources/nagstamon.1.gz']), ('%s/share/pixmaps' % sys.prefix, ['Nagstamon/resources/nagstamon.svg']), ('%s/share/applications' % sys.prefix, ['Nagstamon/resources/nagstamon.desktop'])], options=dict(build_exe=build_exe_options, bdist_mac=bdist_mac_options, bdist_dmg=bdist_dmg_options, bdist_rpm=bdist_rpm_options), executables=executables )