pax_global_header00006660000000000000000000000064135667143320014524gustar00rootroot0000000000000052 comment=82cde82ea1329304b52d35e53a69df921d0cecad RaySession-0.8.3/000077500000000000000000000000001356671433200136335ustar00rootroot00000000000000RaySession-0.8.3/.gitignore000066400000000000000000000005151356671433200156240ustar00rootroot00000000000000#ignore hidden files except git /.* !/.git #UI builds /src/gui/ui_*.py /src/clients/proxy/ui_*.py #resources qt build /src/gui/resources_rc.py /src/.* /src/*/.* /src/*/*/.* #translations /locale/*.qm #python cache /src/*/__pycache__/ /src/*/*/__pycache__/ # ignore kdevelop file RaySession.kdev4 # ignore memo memo_snapshots RaySession-0.8.3/COPYING000066400000000000000000000355641356671433200147030ustar00rootroot00000000000000 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 RaySession-0.8.3/INSTALL.md000066400000000000000000000020061356671433200152610ustar00rootroot00000000000000# --- INSTALL for RAYSESSION --- To install RaySession, simply run as usual:
`$ make`
`$ [sudo] make install` depending of the distribution you'll need to use LRELEASE variable to install. If you don't have 'lrelease' executable but 'lrelease-qt5' use: `$ make LRELEASE=lrelease-qt5`
`$ [sudo] make install` You can run RaySession without install, by using instead:
`$ make`
`$ ./src/bin/raysession` Packagers can make use of the 'PREFIX' and 'DESTDIR' variable during install, like this:
`$ make install PREFIX=/usr DESTDIR=./test-dir` To uninstall RaySession, run:
`$ [sudo] make uninstall`
===== BUILD DEPENDENCIES ===== -------------------------------- The required build dependencies are: (devel packages of these) - PyQt5 - Qt5 dev tools - qtchooser On Debian and Ubuntu, use these commands to install all build dependencies:
`$ sudo apt-get install python3-pyqt5 pyqt5-dev-tools qtchooser` To run it, you'll additionally need: - python3-liblo RaySession-0.8.3/Makefile000066400000000000000000000141351356671433200152770ustar00rootroot00000000000000#!/usr/bin/make -f # Makefile for RaySession # # ---------------------- # # Created by houston4444 # PREFIX = /usr/local DESTDIR = DEST_RAY := $(DESTDIR)$(PREFIX)/share/raysession LINK = ln -s PYUIC := pyuic5 PYRCC := pyrcc5 LRELEASE := lrelease ifeq (, $(shell which $(LRELEASE))) LRELEASE := lrelease-qt5 endif ifeq (, $(shell which $(LRELEASE))) LRELEASE := lrelease-qt4 endif # ----------------------------------------------------------------------------------------------------------------------------------------- all: RES UI LOCALE # ----------------------------------------------------------------------------------------------------------------------------------------- # Resources RES: src/gui/resources_rc.py src/gui/resources_rc.py: resources/resources.qrc $(PYRCC) $< -o $@ # ----------------------------------------------------------------------------------------------------------------------------------------- # UI code UI: raysession ray_proxy raysession: src/gui/ui_abort_copy.py \ src/gui/ui_abort_session.py \ src/gui/ui_about_raysession.py \ src/gui/ui_add_application.py \ src/gui/ui_client_properties.py \ src/gui/ui_client_slot.py \ src/gui/ui_client_trash.py \ src/gui/ui_donations.py \ src/gui/ui_edit_executable.py \ src/gui/ui_daemon_url.py \ src/gui/ui_error_dialog.py \ src/gui/ui_list_snapshots.py \ src/gui/ui_new_executable.py \ src/gui/ui_new_session.py \ src/gui/ui_nsm_open_info.py \ src/gui/ui_open_session.py \ src/gui/ui_quit_app.py \ src/gui/ui_raysession.py \ src/gui/ui_remove_template.py \ src/gui/ui_save_template_session.py \ src/gui/ui_snapshot_name.py \ src/gui/ui_snapshots_info.py \ src/gui/ui_snapshot_progress.py \ src/gui/ui_stop_client.py \ src/gui/ui_stop_client_no_save.py \ src/gui/ui_template_slot.py \ src/gui/ui_waiting_close_user.py src/gui/ui_%.py: resources/ui/%.ui $(PYUIC) $< -o $@ ray_proxy: src/clients/proxy/ui_proxy_copy.py \ src/clients/proxy/ui_proxy_gui.py src/clients/proxy/ui_%.py: resources/ui/%.ui $(PYUIC) $< -o $@ # ----------------------------------------------------------------------------------------------------------------------------------------- # # Translations Files LOCALE: locale locale: locale/raysession_fr_FR.qm locale/raysession_en_US.qm locale/%.qm: locale/%.ts $(LRELEASE) $< -qm $@ # ----------------------------------------------------------------------------------------------------------------------------------------- clean: rm -f *~ src/*~ src/*.pyc src/gui/ui_*.py src/clients/proxy/ui_*.py \ src/gui/resources_rc.py locale/*.qm rm -f -R src/__pycache__ src/*/__pycache__ src/*/*/__pycache__ # ----------------------------------------------------------------------------------------------------------------------------------------- debug: $(MAKE) DEBUG=true # ----------------------------------------------------------------------------------------------------------------------------------------- install: #clean unwanted __pycache__ folders rm -f -R src/__pycache__ src/*/__pycache__ src/*/*/__pycache__ # Create directories install -d $(DESTDIR)$(PREFIX)/bin/ install -d $(DESTDIR)$(PREFIX)/share/applications/ install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/16x16/apps/ install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/24x24/apps/ install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/32x32/apps/ install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/ install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/64x64/apps/ install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/96x96/apps/ install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/128x128/apps/ install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/256x256/apps/ install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/ install -d $(DEST_RAY)/ install -d $(DEST_RAY)/locale/ # Copy Client Templates Factory cp -r client_templates $(DEST_RAY)/ cp -r session_templates $(DEST_RAY)/ # Copy Desktop Files install -m 644 data/*.desktop $(DESTDIR)$(PREFIX)/share/applications/ # Install icons install -m 644 resources/16x16/raysession.png \ $(DESTDIR)$(PREFIX)/share/icons/hicolor/16x16/apps/ install -m 644 resources/24x24/raysession.png \ $(DESTDIR)$(PREFIX)/share/icons/hicolor/24x24/apps/ install -m 644 resources/32x32/raysession.png \ $(DESTDIR)$(PREFIX)/share/icons/hicolor/32x32/apps/ install -m 644 resources/48x48/raysession.png \ $(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/ install -m 644 resources/48x48/raysession.png \ $(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/ install -m 644 resources/64x64/raysession.png \ $(DESTDIR)$(PREFIX)/share/icons/hicolor/64x64/apps/ install -m 644 resources/96x96/raysession.png \ $(DESTDIR)$(PREFIX)/share/icons/hicolor/96x96/apps/ install -m 644 resources/128x128/raysession.png \ $(DESTDIR)$(PREFIX)/share/icons/hicolor/128x128/apps/ install -m 644 resources/256x256/raysession.png \ $(DESTDIR)$(PREFIX)/share/icons/hicolor/256x256/apps/ # Install icons, scalable install -m 644 resources/scalable/raysession.svg \ $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/ # Install main code cp -r src $(DEST_RAY)/ # install main bash scripts to bin install -m 755 data/raysession $(DESTDIR)$(PREFIX)/bin/ install -m 755 data/ray-daemon $(DESTDIR)$(PREFIX)/bin/ install -m 755 data/ray_control $(DESTDIR)$(PREFIX)/bin/ # modify PREFIX in main bash scripts sed -i "s?X-PREFIX-X?$(PREFIX)?" \ $(DESTDIR)$(PREFIX)/bin/raysession \ $(DESTDIR)$(PREFIX)/bin/ray-daemon \ $(DESTDIR)$(PREFIX)/bin/ray_control # Install Translations install -m 644 locale/*.qm $(DEST_RAY)/locale/ ----------------------------------------------------------------------------------------------------------------------------------------- uninstall: rm -f $(DESTDIR)$(PREFIX)/bin/raysession rm -f $(DESTDIR)$(PREFIX)/bin/ray-daemon rm -f $(DESTDIR)$(PREFIX)/share/applications/raysession.desktop rm -f $(DESTDIR)$(PREFIX)/share/icons/hicolor/*/apps/raysession.png rm -f $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/raysession.svg rm -rf $(DEST_RAY) RaySession-0.8.3/README.md000066400000000000000000000021411356671433200151100ustar00rootroot00000000000000# ![RaySession Logo](https://raw.githubusercontent.com/Houston4444/RaySession/master/resources/128x128/raysession.png) RaySession # --- README for RaySession --- RaySession is a GNU/Linux session manager for audio programs as Ardour, Carla, QTractor, Non-Timeline, etc...
It uses the same OSC API as Non Session Manager, so programs compatible with NSM are also compatible with RaySession.
As Non Session Manager, the principle is to load together audio programs, then be able to save or close all documents together.
RaySession offers a little more: * Factory templates for NSM and LASH compatible applications * Possibility to save any client as template * Save session as template * Name files with a prettier way * remember if client was started or not * Abort session allmost anytime * Change Main Folder of sessions on GUI * Possibility to KILL client if clean exit is too long * Open Session Folder button (open default file manager) You can see documentation on NSM at: http://non.tuxfamily.org/wiki/Non%20Session%20Manager RaySession is being developed by houston4444, using Python3 and Qt5. RaySession-0.8.3/RaySession.kdev4000066400000000000000000000001151356671433200166660ustar00rootroot00000000000000[Project] CreatedFrom=Makefile Manager=KDevCustomMakeManager Name=RaySession RaySession-0.8.3/TODO000066400000000000000000000001571356671433200143260ustar00rootroot00000000000000# RaySession TODO Jack Patchbay drop file directly on session manual allow scripts ... RaySession-0.8.3/TRANSLATORS000066400000000000000000000011041356671433200153660ustar00rootroot00000000000000If you want to translate RaySession, you're welcome ! go to ./locale : cd locale copy file raysession_en_US.ts to raysession_xx_XX.ts replace xx and XX with the letters of your language. cp raysession_en_US.ts raysession_xx_XX.ts open your .ts file with QLinguist, make your translations and publish. in order to try it: go to parent dir: cd .. open Makefile in a text editor add raysession_xx_XX.qm to the Makefile (line 42) save Makefile then make: make RaySession-0.8.3/client_templates/000077500000000000000000000000001356671433200171675ustar00rootroot00000000000000RaySession-0.8.3/client_templates/Calf JACK Host/000077500000000000000000000000001356671433200214235ustar00rootroot00000000000000RaySession-0.8.3/client_templates/Calf JACK Host/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX/000077500000000000000000000000001356671433200275335ustar00rootroot00000000000000XXX_SESSION_NAME_XXX.calf000066400000000000000000000000661356671433200335460ustar00rootroot00000000000000RaySession-0.8.3/client_templates/Calf JACK Host/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX ray-proxy.xml000066400000000000000000000003661356671433200321550ustar00rootroot00000000000000RaySession-0.8.3/client_templates/Calf JACK Host/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX RaySession-0.8.3/client_templates/Guitarix/000077500000000000000000000000001356671433200207635ustar00rootroot00000000000000RaySession-0.8.3/client_templates/Guitarix/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX/000077500000000000000000000000001356671433200270735ustar00rootroot00000000000000RaySession-0.8.3/client_templates/Guitarix/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX/ray-proxy.xml000066400000000000000000000004171356671433200315710ustar00rootroot00000000000000 RaySession-0.8.3/client_templates/GxTuner/000077500000000000000000000000001356671433200205635ustar00rootroot00000000000000RaySession-0.8.3/client_templates/GxTuner/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX/000077500000000000000000000000001356671433200266735ustar00rootroot00000000000000RaySession-0.8.3/client_templates/GxTuner/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX/ray-proxy.xml000066400000000000000000000003731356671433200313720ustar00rootroot00000000000000 RaySession-0.8.3/client_templates/Hydrogen/000077500000000000000000000000001356671433200207465ustar00rootroot00000000000000RaySession-0.8.3/client_templates/Hydrogen/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX/000077500000000000000000000000001356671433200270565ustar00rootroot00000000000000RaySession-0.8.3/client_templates/Hydrogen/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX/ray-proxy.xml000066400000000000000000000004231356671433200315510ustar00rootroot00000000000000 RaySession-0.8.3/client_templates/Jack Mixer/000077500000000000000000000000001356671433200211045ustar00rootroot00000000000000RaySession-0.8.3/client_templates/Jack Mixer/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX/000077500000000000000000000000001356671433200272145ustar00rootroot00000000000000XXX_SESSION_NAME_XXX.xml000066400000000000000000000002461356671433200331220ustar00rootroot00000000000000RaySession-0.8.3/client_templates/Jack Mixer/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX RaySession-0.8.3/client_templates/Jack Mixer/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX/ray-proxy.xml000066400000000000000000000003571356671433200317150ustar00rootroot00000000000000 RaySession-0.8.3/client_templates/Qsampler/000077500000000000000000000000001356671433200207535ustar00rootroot00000000000000RaySession-0.8.3/client_templates/Qsampler/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX/000077500000000000000000000000001356671433200270635ustar00rootroot00000000000000XXX_SESSION_NAME_XXX.lscp000066400000000000000000000005401356671433200331270ustar00rootroot00000000000000RaySession-0.8.3/client_templates/Qsampler/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX# Qsampler - A LinuxSampler Qt GUI Interface # Version: 0.4.2 # Build: Dec 12 2017 23:26 +0000 # # File: test_megascène.lscp # Date: déc. 13 2017 22:49:48 # RESET # MIDI instrument map 0 - Chromatic ADD MIDI_INSTRUMENT_MAP 'Chromatic' # MIDI instrument map 1 - Drum Kits ADD MIDI_INSTRUMENT_MAP 'Drum Kits' # Global volume level SET VOLUME 0.35 RaySession-0.8.3/client_templates/Qsampler/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX/ray-proxy.xml000066400000000000000000000004141356671433200315560ustar00rootroot00000000000000 RaySession-0.8.3/client_templates/Rosegarden/000077500000000000000000000000001356671433200212605ustar00rootroot00000000000000RaySession-0.8.3/client_templates/Rosegarden/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX/000077500000000000000000000000001356671433200273705ustar00rootroot00000000000000XXX_SESSION_NAME_XXX.rg000066400000000000000000000222631356671433200331110ustar00rootroot00000000000000RaySession-0.8.3/client_templates/Rosegarden/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX]]6}Üx68gw^b͋ȞxZOŖWo?B$Sv˓¿?̷L*+2/VU7{%n>> 5DCۯLWʊ[^D:+e%VϦ~+"򭨟45Cfⷪ*ZɡR~ųg<ה/DͅM^T?u|v_ᆧ𒬬Vd< *7IJ ,Wy .$(s"U_7;o 77_/~d}Z|J J֗,S~.Tb!W=N.._D1QA{ D|QS * 8 Q!ATDE@Ԕ@A)5#5s5{ DbQS jN j jbל9|s4|ssٓ=g= QtٓEg= QtٓMg~:{*)z=uDS⪳^_=8[gO$FSu[gO$JSu[gO$N哂:{"*:{"*:{"*:{"*oiī|;)޺֟Fʧx[oiī8񪚫d+dj_ORJP|d*EY6\|ZZ1Uj+*~|'˸*w>t/:>ZMσGot% *!*z_fLEJZ.6#]]gOp iK|J$2^k=6_K^5ąyvj<ߦH~'q~̪6RnXnQm8]X.^|<}+_]o5bUYF'y6?Vw<)F\&UR*`SnQ WS?*eGGeMS}/SEu!g&SF;Jٱ0 ?7px熕_GsRSUDwe\Rg׫Kų>Hy#G`8xHyNG#p6xHyG"+3 W"g8D$p\I9q%B%겉 (}Vm]_'ٱe~S#HmPOINHG,fE')~d"aYtHG,b#N ȑE'(ȢEB(rdѹ?]q,k`q\<!X.vyǵC8]qq,k`q\<6"O?k.U"dɩlT$y <6~UkyoUH*o ܶi(dveQɍdǬ/d-Ė'vLԣޗ(.}7y_g2HйB%rE }g_Įf& fϗIu_'5ѣuARi(>]&[Q8w{5tS?OTN7َ_m䘷ubE*` Q|;y,ȳuQ'nU&(xtxY Ocܡ?JN^;,^kyIBZhvp z^'eM"ڈLcH/y*$[B9FIE}_J%I*u8&@HL$R;&*&MHR*mIeӱ9{s؜9q]<6coN7lJ׭YGqj B=zCl =đ!6}{cC=qlG֞~w;4rISu ucMūjb >Ajw-SVNQίRyt >ր4/{<ë7X&oD;9{MV.:dq9R5AY)E%^OZB6vQ$w)PWB7nʿWDp^wKH Ї?E̳uхoCW+Dk|;2Y)>sߟ?(v{^1m*IC!-/}" s^T13H)'~'EߗBfU'Tޏ"sWrgew)db0W{u@s]CSQ5Wy{rˮ[u9{kNB~U49CēvL%!/An\|2'ű2^Wu!B,M0Wnv- ;h ^LV:eiTF?#ĉh-4Bu W p/nS^ \5*ݰ >99$Hލ]%mYyda ~ALPY~[ g~yL>TE ) ߾WkTl0Tݩ^ +6gP$]+=Z32͉uf 0t+u/Ɩ:3kBf)"Sm_Y}TVH* X8=O}9xA<Ä0FF÷?iaq,кU-P%ͳ0<^Z۟EovWYWsQ!у9^#mlJ{/"n+p6fpxVW)AtGۉkEwIwiI`O_#nHpb] aY-$xp[ 4DbVLkrcL)"w6RM`Aɏjd4F"F&[naj1Qa6wQWcPح`kW)h_eaQb_:+5VK)8a.ꮻ;<5tZH)VMlc!AЦ {y!Wm$(¥lj(|] XM{B* T]@VW"D?D ΐBil)yYȪidhٯ0 #k Wndp}=@Z=RQ֒؉@eʦaF,Q ce1&F Ad } 4\w9Z{n0L ; LDkkQ0hAvN{W@s9%P ̋}`N>rbhu u0D)D=`}!h a~ ~?].ݨ'UbS oo6T)c$e$X茣z:e9(sHScMum 2yh8 䁗$TYq/tf1a⢞9XU݉ruB ^T T!3})|B wU`J}s-Wu*:C0A|$&LO} j8&'٭eP4h'@}4eHߝ$0;H_pArՍB-Ђ=hEG !Sn,pەes,v>),Na`&Da R|-Y>)0 צ~iqQ蟶.Uw\$fcnF&:h2MMÿ,{QxRUߜ70jS1ZRsc67 cK˓;FaTǠ6S:)in8d}{S(Sw̺Fs[CD O ȟgO.Ue1Ȃ]j[{.EZD#n=Z9ag' am90'aܢ5'e.6_oV~!HTJ^ m84 |1g%t[t/s ~e5n^$DZeh#3`m՝KgEb6"QűVd`F0-(24Y2 {ʈε1i#1 R"sUgK"q ,92xNj 1WxeXZ99 1P֨T#/n~ɑ]t3Dvg#sJw`Pm:{)H-h4PR_HMc;z^p#N_DQ <4rA╡ 7bvBniR#[VC۠58p$AӁ5!v`#sdiB:6 3l>q$$\p4?ᨃ`~f˵8`b,J`t:eqltO {nלcIPfG4Ǝ9BwI{:+h Ѹ`ӡu'  6HCt63 C4'Hi540vp55ffik1PIچc{~%c8>G l | %ٷkk-RFZSiVhC6W LI }  CdS@49jJ@Q 9nE{ \J*6X~i bI˰. VunD@lgv KB =A>p{50pmGvj\&,.Ŕ{[vKA4vJI$ xg'8\lЅu8$ذ pHQ9 dz?/mN)  ְ E"DڢbOUAaE.Ea =PRNA)RcUh950<4'3q$(-2{JZEs;]tb3=nA;etKM٥&RJ /A.4%=bReg&[dlB:H%AMH3%}]U5 %QT68ʞ  0=j6{HӦd YguȊ(@‘u{}Ҥ 1X?X/pg3| rjc%Fq [Z@9KJ+3|4V)@`Z4eQHisIڌ=>a *:5Dt w{ A 26&YF2#Lb?jL{Z`R5>jL #AQqgQ? @QqTzeJ|aGS쨸ure1rtDyu*thX+D аа6jsC(i (H;4YHal|Ш6baLNyhTHL-Pz,NyTսˆ6>#TS)wS~i;yH8c?2In!(7Pn4. D y ^&uࡧX̢%%e/DDimYm$zb85`>3FI Is S9M?$&;M)T  I~yU- ʨ!BF @QC؄tBd"%Fh%N&]w2b(@@ZC&Q  J84KA2ViC?AA.Ԡq]rzQF;Pbv6 (4CGg6Gh6!lk4dعWPfؖ&mT .@=uBN@HAqhAOq2|YTN1 YQNщ  唅BfH$L(W8-I A %d'm)AK$qe2<Ϡ%8ep 8Ɖ]Fr@(a`;vGHBc6zt-lohn)!0"ۋ h0ml8 R6̂pQ&m"ee*D?҆iHDNl DrC8@ڀqd֧qٌ4\9d? 9Hr"#g2TgعP!:)A+~;VRoJPt)k-Bdv]z[:,9dfm- "!*$FWd hrx$+O$]Na' lж|gGD& h _O+H(wb!j;I(@ QP[m#牲f1iNW߇F>jSo2ZZchK .k/=.Ryg LoAWRaySession-0.8.3/client_templates/Rosegarden/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX/ray-proxy.xml000066400000000000000000000004201356671433200320600ustar00rootroot00000000000000 RaySession-0.8.3/client_templates/Seq24/000077500000000000000000000000001356671433200200655ustar00rootroot00000000000000RaySession-0.8.3/client_templates/Seq24/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX/000077500000000000000000000000001356671433200261755ustar00rootroot00000000000000XXX_SESSION_NAME_XXX.midi000066400000000000000000000105201356671433200322210ustar00rootroot00000000000000RaySession-0.8.3/client_templates/Seq24/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXXMThdMTrkTUntitled`9d.9dB;d.;d4d.4d$$$$$$$$R/$$$$$$ $$x$$  RaySession-0.8.3/client_templates/Seq24/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX/ray-proxy.xml000066400000000000000000000003501356671433200306670ustar00rootroot00000000000000 RaySession-0.8.3/client_templates/Sequencer64/000077500000000000000000000000001356671433200212735ustar00rootroot00000000000000RaySession-0.8.3/client_templates/Sequencer64/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX/000077500000000000000000000000001356671433200274035ustar00rootroot00000000000000XXX_SESSION_NAME_XXX.midi000066400000000000000000000005051356671433200334310ustar00rootroot00000000000000RaySession-0.8.3/client_templates/Sequencer64/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXXMThdMTrk]Untitled`9d.9dB;d.;d4d.4d$$$$$$$$$$R/MTrk? Sequencer64-S$$$$F$$ $$$$$$$$$$$$$$/RaySession-0.8.3/client_templates/Sequencer64/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX/ray-proxy.xml000066400000000000000000000003301356671433200320730ustar00rootroot00000000000000 RaySession-0.8.3/client_templates/amsynth/000077500000000000000000000000001356671433200206525ustar00rootroot00000000000000RaySession-0.8.3/client_templates/amsynth/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX/000077500000000000000000000000001356671433200267625ustar00rootroot00000000000000RaySession-0.8.3/client_templates/amsynth/XXX_SESSION_NAME_XXX.XXX_CLIENT_ID_XXX/ray-proxy.xml000066400000000000000000000003511356671433200314550ustar00rootroot00000000000000 RaySession-0.8.3/client_templates/client_templates.xml000066400000000000000000000137321356671433200232530ustar00rootroot00000000000000 RaySession-0.8.3/data/000077500000000000000000000000001356671433200145445ustar00rootroot00000000000000RaySession-0.8.3/data/ray-daemon000066400000000000000000000001511356671433200165200ustar00rootroot00000000000000#!/bin/bash INSTALL_PREFIX="X-PREFIX-X" exec "$INSTALL_PREFIX/share/raysession/src/bin/ray-daemon" "$@" RaySession-0.8.3/data/ray_control000066400000000000000000000001521356671433200170200ustar00rootroot00000000000000#!/bin/bash INSTALL_PREFIX="X-PREFIX-X" exec "$INSTALL_PREFIX/share/raysession/src/bin/ray_control" "$@" RaySession-0.8.3/data/raysession000066400000000000000000000001511356671433200166630ustar00rootroot00000000000000#!/bin/bash INSTALL_PREFIX="X-PREFIX-X" exec "$INSTALL_PREFIX/share/raysession/src/bin/raysession" "$@" RaySession-0.8.3/data/raysession.desktop000066400000000000000000000003501356671433200203340ustar00rootroot00000000000000[Desktop Entry] Name=RaySession GenericName=RaySession Comment=Audio Session Manager Comment[fr]=Gestionnaire de sessions audios Exec=raysession Icon=raysession Terminal=false Type=Application Categories=AudioVideo;AudioEditing;Qt; RaySession-0.8.3/locale/000077500000000000000000000000001356671433200150725ustar00rootroot00000000000000RaySession-0.8.3/locale/raysession.pro000066400000000000000000000057301356671433200200200ustar00rootroot00000000000000FORMS += ../resources/ui/abort_copy.ui FORMS += ../resources/ui/abort_session.ui FORMS += ../resources/ui/about_raysession.ui FORMS += ../resources/ui/add_application.ui FORMS += ../resources/ui/client_properties.ui FORMS += ../resources/ui/client_slot.ui FORMS += ../resources/ui/client_trash.ui FORMS += ../resources/ui/daemon_url.ui FORMS += ../resources/ui/donations.ui FORMS += ../resources/ui/edit_executable.ui FORMS += ../resources/ui/error_dialog.ui FORMS += ../resources/ui/list_snapshots.ui FORMS += ../resources/ui/new_executable.ui FORMS += ../resources/ui/new_session.ui FORMS += ../resources/ui/nsm_open_info.ui FORMS += ../resources/ui/open_session.ui FORMS += ../resources/ui/proxy_copy.ui FORMS += ../resources/ui/proxy_gui.ui FORMS += ../resources/ui/quit_app.ui FORMS += ../resources/ui/raysession.ui FORMS += ../resources/ui/remove_template.ui FORMS += ../resources/ui/save_template_session.ui FORMS += ../resources/ui/snapshot_and_save.ui FORMS += ../resources/ui/snapshot_name.ui FORMS += ../resources/ui/snapshot_progress.ui FORMS += ../resources/ui/snapshots_info.ui FORMS += ../resources/ui/stop_client_no_save.ui FORMS += ../resources/ui/stop_client.ui FORMS += ../resources/ui/template_slot.ui FORMS += ../resources/ui/waiting_close_user.ui SOURCES += ../src/daemon/client.py SOURCES += ../src/daemon/daemon_tools.py SOURCES += ../src/daemon/ray-daemon.py SOURCES += ../src/daemon/session.py SOURCES += ../src/gui/add_application_dialog.py SOURCES += ../src/gui/child_dialogs.py SOURCES += ../src/gui/daemon_manager.py SOURCES += ../src/gui/gui_tools.py SOURCES += ../src/gui/list_widget_clients.py SOURCES += ../src/gui/main_window.py SOURCES += ../src/gui/nsm_child.py SOURCES += ../src/gui/raysession.py SOURCES += ../src/gui/snapshots_dialog.py SOURCES += ../src/gui/ui_abort_copy.py SOURCES += ../src/gui/ui_abort_session.py SOURCES += ../src/gui/ui_about_raysession.py SOURCES += ../src/gui/ui_add_application.py SOURCES += ../src/gui/ui_client_properties.py SOURCES += ../src/gui/ui_client_slot.py SOURCES += ../src/gui/ui_client_trash.py SOURCES += ../src/gui/ui_daemon_url.py SOURCES += ../src/gui/ui_donations.py SOURCES += ../src/gui/ui_edit_executable.py SOURCES += ../src/gui/ui_error_dialog.py SOURCES += ../src/gui/ui_list_snapshots.py SOURCES += ../src/gui/ui_new_executable.py SOURCES += ../src/gui/ui_new_session.py SOURCES += ../src/gui/ui_nsm_open_info.py SOURCES += ../src/gui/ui_open_session.py SOURCES += ../src/gui/ui_quit_app.py SOURCES += ../src/gui/ui_raysession.py SOURCES += ../src/gui/ui_remove_template.py SOURCES += ../src/gui/ui_save_template_session.py SOURCES += ../src/gui/ui_snapshot_name.py SOURCES += ../src/gui/ui_snapshot_progress.py SOURCES += ../src/gui/ui_snapshots_info.py SOURCES += ../src/gui/ui_stop_client_no_save.py SOURCES += ../src/gui/ui_stop_client.py SOURCES += ../src/gui/ui_template_slot.py SOURCES += ../src/gui/ui_waiting_close_user.py SOURCES += ../src/shared/shared.py TRANSLATIONS += raysession_en_US.ts TRANSLATIONS += raysession_fr_FR.ts RaySession-0.8.3/locale/raysession_en_US.ts000066400000000000000000002511621356671433200207410ustar00rootroot00000000000000 AbortSession Abort Session ? <html><head/><body><p>Are you sure to want to abort session without saving ?</p></body></html> Abort Cancel ClientSlotWidget Frame ... ClientName Show GUI GUI Launch <html><head/><body><p>Politely ask the client to stop.</p></body></html> <html><head/><body><p>Kill !</p></body></html> Status stopped Save Remove Save As Application Template Properties Return To A Previous State Dialog Abort Copy ? Do you want to abort current copy ? Abort Copy Cancel Properties : Executable executable Arguments Edit executable ... Label nsmid client_name Icon Name Client id Snapshots <html><head/><body><p>Edit here what types of files inside the client folders<br/>should be ignored in session snapshots.</p><p>Indexed files will remain so.</p></body></html> Snapshots ignored extensions : Prevent to stop without recent save Save Changes Restore Client ? client_label client_icon <html><head/><body><p align="center">Do you want to restore this client in the session ?<br/>You can also definitely remove the client and its files.</p></body></html> <html><head/><body><p>Remove definitely the client and its files.</p></body></html> Remove <html><head/><body><p>Restore this client in current session.</p></body></html> Restore Client Daemon URL ray-daemon url to connect to : osc.udp://192.168.XX.XX:1234/ Edit Executable <html><head/><body><p>Edit executable is strongly discouraged !<br/>It can be useful if you use many versions of a same software.<br/>Change it only if you are sure of what you are doing.</p></body></html> Executable : <html><head/><body><p>Arguments are supposed to not be supported by NSM protocol.<br/>In some cases it can works, but no warranty !</p></body></html> Arguments : <html><head/><body><p align="right"><span style=" font-style:italic;">You will have to save changes in Properties window<br/>and restart the client to apply these changes.</span></p></body></html> Error ! Error Text Snapshots Manager <html><head/><body><p>Select from the list below the snapshot to be recalled<br/>to return to a past state of the session :</p></body></html> Take a snapshot now ! <html><head/><body><p>Make a snapshot at each session save.</p></body></html> Auto snapshot at save for this session Opening NSM Session <html><head/><body><p>You are opening a session created by Non Session Manager.</p><p><br/>Ray Session will open it, but changes won't be applied to NSM file (session.nsm).</p><p>Also, Once you start to work with Ray Session, you have to continue with it !</p></body></html> Don't Show this Message Again Copy File ? file is not in proxy directory. <html><head/><body><p>Do you want to copy this file to proxy directory or to use directly this file ?</p></body></html> Copy file and rename it with session name Copy And Rename File Copy File Use This File Name Snapshot Snapshot Name : <html><head/><body><p>You can save the session before the snapshot.</p><p>Save is recommended,<br/>unless you made unwanted changes since the last session save.</p></body></html> Save && Snapshot Snapshot Only <html><head/><body><p>Snapshot process seems to be long.<br/>Maybe your session's folder contains too many new files<br/>whose extension is not ignored.</p><p>You can abort this snapshot,<br/>it will de-activate snapshots for this session.</p></body></html> Abort Snapshot Snapshots Informations <html><head/><body><p>Snapshots are NOT backups !!!</p><p>Besides, It's not overrated to copy your session folder elsewhere<br/>before to ask a previous snapshot.</p><p>Snapshots ignore audio files and other big files (&gt;50Mb),<br/>else snapshot process would be too long, <br/>and the session folder size would be too big.</p><p>That being said, you can decide that your work in the last hours<br/>was not a good idea and return to a previous snapshot !</p></body></html> Do not show this message again Stop Client ? <html><head/><body><p><span style=" font-weight:600;">%s</span> contains unsaved changes. </p><p>Do you really want to stop it ?</p></body></html> Don't prevent to stop this client again ! Save && Stop Just Stop Prevent to stop without recent or possible save Donations <html><head/><body><p>Hi !</p><p>it seems that you appreciate RaySession, that is already a good new.<br/>This software is free as in Speech and as in Beer,<br/>but it has required and still takes time.</p><p>Make a donation (even small) is a simple way to say &quot;Thank you&quot;.<br/>You can donate <a href="https://liberapay.com/Houston4444"><span style=" text-decoration: underline; color:#2980b9;">here</span></a>.</p><p>If ever you donate nothing,<br/>this program will continue to work without limits of functionnality,<br/>without limit of duration, and even without insulting you ;) .</p></body></html> Remove Template ? <html><head/><body><p>Are you sure to want to remove this template ?</p></body></html> <html><head/><body><p>We have no possibility to save the client <span style=" font-weight:600;">%s</span>.</p><p>For this reason, it's preferable that you close yourself this client,<br/>probably by closing its window, saving its changes or not.</p></body></html> Don't prevent to stop this client again (discouraged) Stop Anyway Close clients yourself ! <html><head/><body><p align="center">Some active clients do not offer any save possibility !</p><p align="center">Therefore, it is best that you close these clients yourself,<br/>probably by closing their windows and saving changes.</p><p align="center"><span style=" font-weight:600;">Please close yourself the programs with this save icon:</span></p></body></html> <html><head/><body><p>You've got 2 minutes !<br/><span style=" font-style:italic;">You can do it without closing this dialog window.</span></p></body></html> Do not show again Undo Ok Skip DialogAboutRaysession About Ray Session <html><head/><body><p><img src=":/128x128/raysession.png"/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Ray Session</span></p><p>version : %s</p></body></html> <html><head/><body><p>Ray Session is a Qt interface for the ray-daemon.</p><p>Its goal is to manage together audio programs as Ardour, Carla, Qtractor, Non-Timeline in an unique session.</p><p>Programs just have to be compatible with the <a href="http://non.tuxfamily.org/wiki/Non%20Session%20Manager"><span style=" text-decoration: underline; color:#2980b9;">NSM</span></a> API to work with Ray Session.<br/></p><p align="right">Copyright (C) 2016-2019 houston4444</p><p><br/></p></body></html> DialogAddApplication Add Application Filter : Factory User DialogNewExecutable New Executable Client Executable : <html><head/><body><p>If program is not compatible with the NSM API, </p><p>you should launch it in proxy to define a config file !</p></body></html> Run via Proxy <html><head/><body><p>Show more options ...</p></body></html> ... Advanced Prefix : Prefix Mode : Client ID : <html><head/><body><p>The Prefix Mode determines the pattern of the client files.</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Session Name (Default) :<br/>The client files names start with the session name.</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Client Name :<br/>The client files names start with the client name given by the client itself<br/>(as in Non Session Manager).</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom :<br/>The client files names start with the prefix written below.</li></ul></body></html> <html><head/><body><p>Use it If you want to add to session a file made outside from a session.</p><p>If you let this line empty, the client_id will be automaticely generated.</p></body></html> DialogNewSession New Session /home/user/Ray Sessions Folder New Session Name : Template : <html><head/><body><p><span style=" font-size:9pt; font-weight:600;">Sessions Folder :</span></p></body></html> Sub Folder : DialogOpenSession Open Session Folder Filter : <html><head/><body><p><span style=" font-size:9pt; font-weight:600;">Sessions Folder :</span></p></body></html> /home/user/Ray Sessions DialogQuitApp Quit RaySession <p>Session <bold>%s</bold> is running.</p><p>RaySession will be closed.</p><p>Do you want to save session ? Save && Quit Quit Without Saving Cancel <html><head/><body><p>Do you want to save the session before snapshot ?</p><p>Save is recommended,<br/>unless you made unwanted changes since the last session save.</p></body></html> Save && Snapshot Snapshot DialogSaveTemplateSession New Template Session Template Name : Create Template Cancel Duplicate Duplicated session name : Duplicate Session Frame Frame ... Template Name GUIMSG %s launching %s terminated as planned %s died unexpectedly. %s Failed to start ! %s stopping waiting for clients announces... waiting for clients to die... %sdidn't announce %sstill alive ! All expected clients are announced All expected clients are died Session saved. Can't save session, session file is unwriteable ! Commanding attached clients to quit. Creating new session "%s" Session is ready Session saved as template named %s Opening session %s Stop all clients before rename session ! Session %s has been renamed to %s . Session directory is now: %s Load Error Unknown error Could not create session file! Session is locked by another process! The named session does not exist. Could not load session file. MainWindow Ray Proxy <html><head/><body><p>Command-line options are incompatible with robust session management for a variety of reasons, so the NSM server does not support them directly.</p><p>Some audio programs doesn't have NSM implementation yet, but they can have a LASH/LADISH implementation, and we can use it for the save signal.</p><p>Be warned that referring to files outside of the session directory will impair your ability to reliably archive and transport sessions. <br/><br/>Patching the program to use NSM natively will result in a better experience. </p></body></html> <html><head/><body><p>The program will be started with its current directory being a uniquely named directory under the current session directory. </p><p>It is recommended that you only refer to files in the current directory.</p></body></html> Config File : Executable : Browse <html><head/><body><p>The environment variables $NSM_CLIENT_ID and $RAY_SESSION_NAME will contain the unique client ID (suitable for use as e.g. a JACK client name) and the display name for the session, respectively.<br/>The variable $CONFIG_FILE will contain the name of the config file selected above.</p></body></html> Arguments : <html><head/><body><p>Some (very few) programs may respond to a specific Unix signal by somehow saving their state.</p><p>If 'Save Signal' is set to something other than 'None', then Ray Proxy will deliver the specified signal to the proxied process upon an NSM 'Save' event.</p><p>If program is compatible with LASH/LADISH, save signal is SIGUSR1.</p><p>Most programs will treat these signals just like SIGTERM and die.</p><p>You have been warned.</p></body></html> <html><head/><body><p>Most programs will shutdown gracefully when sent a SIGTERM or SIGINT signal.<br/>It's impossible to know which signal a specific program will respond to.<br/>A unhandled signal will simply kill the process, and may cause problems with the audio subsystem (e.g. JACK).<br/>Check the program's documentation or source code to determine which signal to use to stop it gracefully.</p></body></html> Save Signal : Stop Signal : Test Wait window before reply (needs wmctrl) executable failed to launch ! Launch Stop Ray Session ... <html><head/><body><p>Save Session as Template</p></body></html> <html><head/><body><p>Save &amp; Close Session</p></body></html> Open Session Folder Run Command Application Executable <html><head/><body><p>Go back to a previous state of the session.</p></body></html> Snapshots Server Status off Save Session <html><head/><body><p>Trash</p><p>You will find here removed clients.</p></body></html> &Messages Fi&le &Session &View &Help Add Options toolBar &Show Messages Ctrl+L &Save Ctrl+S &Open Session Ctrl+O &New Session Ctrl+N &Quit Ctrl+Q &About Ray Session Show &Menu Bar Ctrl+M Control &Close Save and Close Session Ctrl+W &Abort Abort Current Session Ctrl+Shift+W &Open Session Folder Open session folder with file manager &Keep focus while opening clients Ctrl+F About &Qt &Duplicate <html><head/><body><p>Duplicate the current session</p></body></html> Ctrl+D &Executable Add executable to current session Ctrl+E Sa&ve As Template Save current session as template &Application Add application to current session Ctrl+A &Rename &Save All From Saved Client <html><head/><body><p>Some clients (Ardour, Qtractor...) sends their saved state to RaySession.</p><p>When this option is checked, the session is saved when a client sends that it has been saved.</p><p>For example, a Ctrl-S in Ardour will also save all the current Ray Session.</p></body></html> &Provide bookmarks for session folder &Desktops Memory (requires wmctrl) Return To A &Previous State (requires git) Auto Snapshot At Save (requires git) Auto Snapshot at Save (requires git) No Save Level : <html><head/><body><p>If this proxy has no save signal,<br/>it can sends to session manager this information.</p><p><ul><li>0 - this information is not sent.</li><li>1 - this information is sent, session manager will ask user to close programs himself at session unload.</li><li>2 - this information is sent, session manager can try to close window gracefully before to ask user to close programs himself at session unload. Good choice if the program displays a dialog if changes are not saved.</li></ul></p></body></html> Favorite Applications Donate Snapshot Snapshot aborted Snapshot Error Unknown error git didn't stop normally. %s git crashes. %s git exit with an error code. %s Snapshot error command didn't stop normally: %s command crashes: %s command exit with an error code: %s error reading file: %s actions Desktops Memory Auto Snapshot at Save add_app_dialog <p>Are you sure to want to remove<br>the template "%s" and all its files ?</p> child_session Child Session client Failed to launch process ! client status stopped launch open ready copy save switch quit noop error removed invalid client_slot proxy Display proxy window client_status invalid client_stop <strong>%s</strong> seems to has not been saved for %i minute(s).<br />Do you really want to stop it ? daemon Ray Network Sessions port %i is not free, try another one error impossible to read %s Some clients could not save Impossible to save template, unwriteable file ! No template named %s Folder %s already exists No announce from ray-daemon. RaySession can't works. Sorry. error_message Impossible, copy running errors daemon crash! ray-daemon crashed, sorry ! help A session manager based on the Non-Session-Manager API for sound applications. connect to this daemon url Use this folder as root for sessions Open this session at startup use a custom config dir display OSC messages debug without client messages main view No Session Loaded menu remove Favorites message Client template %s created network_session Network Session new client template New application template name : new_executable Custom Client Name Session Name new_session none rename_session Rename Session <p>In order to rename current session,<br>please first stop all clients.<br>then, double click on session name.</p> root_folder_dialogs Choose root folder for sessions unwritable dir server status off new open clear switch launch copy ready save close invalid server_status snapshot rewind waiting save session template Overwrite Template ? session_template empty with JACK patch memory Template <strong>%s</strong> already exists. Overwrite it ? snapshots Today Yesterday %s at %s at %s before rewind to url window <p align="left">To run a network session,<br>open a terminal on another computer of this network.<br>Launch ray-daemon on port 1234 (for example)<br>by typing the command :</p><p align="left"><code>ray-daemon -p 1234</code></p><p align="left">Then paste below the first url<br>that ray-daemon gives you at startup.</p><p></p> url_window <p>daemon at<br><strong>%s</strong><br>didn't announce !<br></p> <p>daemon at<br><strong>%s</strong><br>has a loaded self._session.<br>It can't be used for slave session</p> <p>daemon at<br><strong>%s</strong><br>uses an other session root folder !<.p> <p>daemon at<br><strong>%s</strong><br>uses a forbidden session root folder !<.p> <p>daemon at<br><strong>%s</strong><br>uses an other Ray Session version.<.p> RaySession-0.8.3/locale/raysession_fr_FR.ts000066400000000000000000003235471356671433200207350ustar00rootroot00000000000000 AbortSession Abort Session ? Abandonner la session ? <html><head/><body><p>Are you sure to want to abort session without saving ?</p></body></html> <html><head/><body><p>Êtes vous sûr de vouloir abandonner<br>la session sans la sauvegarder ?</p></body></html> Abort Abandonner Cancel Annuler ClientSlotWidget Frame Trame ... ... ClientName NomDuClient Show GUI Afficher l'interface graphique utilisateur GUI IGU Launch Lancer Status État stopped arrêté Save Sauvegarder Remove Supprimer <html><head/><body><p>Politely ask the client to stop.</p></body></html> <html><head/><body><p>Demander poliment au client de s'arrêter.</p></body></html> <html><head/><body><p>Kill !</p></body></html> <html><head/><body><p>Tuer !</p></body></html> Save As Application Template Sauvegarder Comme Modèle d'Application Properties Propriétés Return To A Previous State Retourner à un état précédent Dialog Error ! Erreur ! Error Text Texte d'erreur Copy File ? Copier le fichier ? file is not in proxy directory. Le fichier n'est pas dans le repertoire du proxy. <html><head/><body><p>Do you want to copy this file to proxy directory or to use directly this file ?</p></body></html> <html><head/><body><p>Voulez-vous copier ce fichier dans le repertoire du proxy<br>ou utiliser directement ce fichier ?</p></body></html> Copy file and rename it with session name Copier le fichier et le renommer avec le nom de la session Copy And Rename File Copier et renommer le fichier Copy File Copier le fichier Use This File Utiliser ce fichier %s is not in proxy directory %s n'est pas dans le répertoire du proxy Select File to use as CONFIG_FILE Sélectionnez le fichier à utiliser comme CONFIG_FILE Executable failed to launch ! It's maybe not present on system. Échec du lancement ! L'exécutable n'est peut-être pas présent sur le système. Properties Propriétés Client id Identifiant du client Executable Exécutable nsmid nsmid Label Étiquette executable exécutable client_name nom du client Name Nom Save Changes Sauvegarder les changements Icon Icône ... ... : : Opening NSM Session Ouverture d'une session NSM <html><head/><body><p>You are opening a session created by Non Session Manager.</p><p><br/>Ray Session will open it, but changes won't be applied to NSM file (session.nsm).</p><p>Also, Once you start to work with Ray Session, you have to continue with it !</p></body></html> <html><head/><body><p>Vous ouvrez une session créée par Non Session Manager</p><p><br/>Ray Session va l'ouvrir, mais les changements ne seront pas appliqués au fichier de session NSM (session.nsm).</p><p>En fait, dès que vous commencez à travailler sur une session avec Ray Session, vous devez continuer avec.</p></body></html> Don't Show this Message Again Ne plus afficher ce message Prevent to stop without recent save Empêcher l'arrêt sans sauvegarde récente Stop Client ? Arrêter le client ? <html><head/><body><p><span style=" font-weight:600;">%s</span> contains unsaved changes. </p><p>Do you really want to stop it ?</p></body></html> <html><head/><body><p><span style=" font-weight:600;">%s</span> contient des changements non sauvegardés. </p><p>Voulez-vous vraiment l'arrêter ?</p></body></html> Don't prevent to stop this client again ! Ne plus empêcher l'arrêt de ce client ! Save && Stop Sauvegarder && Arrêter Just Stop Arrêter Cancel Annuler Restore Client ? Restaurer le client ? client_label éltiquette du client client_icon icône du client <html><head/><body><p align="center">Do you want to restore this client in the session ?<br/>You can also definitely remove the client and its files.</p></body></html> <html><head/><body><p align="center">Voulez-vous restaurer ce client dans la session ?<br/>Vous pouvez aussi supprimer le client et tous ses fichiers.</p></body></html> <html><head/><body><p>Remove definitely the client and its files.</p></body></html> <html><head/><body><p>Supprimer définitivement le client et tous ses fichiers.</p></body></html> Remove Supprimer <html><head/><body><p>Restore this client in current session.</p></body></html> <html><head/><body><p>Restaurer ce client dans la session en cours.</p></body></html> Restore Client Restaurer le client Abort Copy ? Abandonner la Copie ? Do you want to abort current copy ? Voulez-vous abandonner la copie en cours ? Abort Copy Abandonner la copie Daemon URL URL du Démon ray-daemon url to connect to : se connecter à l'url du ray-daemon : osc.udp://192.168.XX.XX:1234/ osc.udp://192.168.XX.XX:1234/ Arguments Arguments Edit executable Éditer l'exécutable Edit Executable Éditer l'exécutable <html><head/><body><p>Edit executable is strongly discouraged !<br/>It can be useful if you use many versions of a same software.<br/>Change it only if you are sure of what you are doing.</p></body></html> <html><head/><body><p>Éditer l'exécutable est vivement déconseillé !<br/>Ça peut s'avérer pratique si vous utilisez plusieurs versions d'un même programme.<br/>Ne changez cela que si vous êtes certains de ce que vous faites.</p></body></html> Executable : Exécutable : <html><head/><body><p>Arguments are supposed to not be supported by NSM protocol.<br/>In some cases it can works, but no warranty !</p></body></html> <html><head/><body><p>Les arguments sont censés ne pas être supportés par le protocole NSM.<br/>Dans certains cas ça peut fonctionner, mais aucune garantie !</p></body></html> Arguments : Arguments : <html><head/><body><p align="right"><span style=" font-style:italic;">You will have to save changes in Properties window<br/>and restart the client to apply these changes.</span></p></body></html> <html><head/><body><p align="right"><span style=" font-style:italic;">Vous devez sauvegarder les changements dans la fenêtre de propriétés<br/>et redémarrer le client pour appliquer ces changements.</span></p></body></html> Snapshots Clichés <html><head/><body><p>Edit here what types of files inside the client folders<br/>should be ignored in session snapshots.</p><p>Indexed files will remain so.</p></body></html> <html><head/><body><p>Editez ici quels types de fichiers dans les dossiers du client<br/>doivent être ignorés dans les clichés de la session.</p><p>Les fichiers déjà indexés le resteront.</p></body></html> Snapshots ignored extensions : Extensions ignorées par les clichés : Name Snapshot Nommer le Cliché Snapshot Name : Nom du Cliché : <html><head/><body><p>Snapshot process seems to be long.<br/>Maybe your session's folder contains too many new files<br/>whose extension is not ignored.</p><p>You can abort this snapshot,<br/>it will de-activate snapshots for this session.</p></body></html> <html><head/><body><p>Le processus du cliché semble être long.<br/>Peut-être que votre dossier de session contient trop de fichiers<br/>dont l'extension n'est pas ignorée.</p><p>Vous pouvez abandonner ce cliché,<br/>celà désactivera les clichés automatiques pour cette session.</p></body></html> Abort Snapshot Abandonner le Cliché Snapshots Manager Gestionnaire de clichés <html><head/><body><p>Select from the list below the snapshot to be recalled<br/>to return to a past state of the session :</p></body></html> <html><head/><body><p>Selectionnez parmi la liste ci-dessous le cliché à rappeller<br/>pour revenir à un état antérieur de la session :</p></body></html> Take a snapshot now ! Prendre un cliché Maintenant ! <html><head/><body><p>Make a snapshot at each session save.</p></body></html> <html><head/><body><p>Prendre un cliché à chaque sauvegarde de session.</p></body></html> Auto snapshot at save for this session Cliché automatique à la sauvegarde pour cette session Snapshots Informations Infos sur les clichés <html><head/><body><p>Snapshots are NOT backups !!!</p><p>Besides, It's not overrated to copy your session folder elsewhere<br/>before to ask a previous snapshot.</p><p>Snapshots ignore audio files and other big files (&gt;50Mb),<br/>else snapshot process would be too long, <br/>and the session folder size would be too big.</p><p>That being said, you can decide that your work in the last hours<br/>was not a good idea and return to a previous snapshot !</p></body></html> <html><head/><body><p>Les clichés ne sont PAS des copies de sauvegarde !!!</p><p>D'ailleurs, il n'est pas surfait de copier votre dossier de session ailleurs<br/>avant de rappeler un cliché.</p><p>Les clichés ignorent les fichiers audios et les autres gros fichiers(&gt;50Mb),<br/>sinon le processus de cliché serait trop long,<br/>et le dossier de la session deviendrait trop lourd.</p><p>Ceci étant dit, vous pouvez estimer que le travail effectué ces dernières heures<br>n'était finalement pas une bonne idée, et décider de revenir à un cliché précédent !</p></body></html> Do not show this message again Ne plus afficher ce message <html><head/><body><p>You can save the session before the snapshot.</p><p>Save is recommended,<br/>unless you made unwanted changes since the last session save.</p></body></html> <html><head/><body><p>Vous pouvez sauvegarder la session avant de prendre le cliché.</p><p>La sauvegarde est recommandée,<br/>sauf si vous avez fait des changements indésirés depuis la dernière sauvegarde.</p></body></html> Save && Snapshot Sauvegarde && Cliché Snapshot Only Cliché uniquement Prevent to stop without recent or possible save Empêcher l'arrêt sans sauvegarde récente ou possible Donations Donations <html><head/><body><p>Hi !</p><p>it seems that you appreciate RaySession, that is already a good new.<br/>This software is free as in Speech and as in Beer,<br/>but it has required and still takes time.</p><p>Make a donation (even small) is a simple way to say &quot;Thank you&quot;.<br/>You can donate <a href="https://liberapay.com/Houston4444"><span style=" text-decoration: underline; color:#2980b9;">here</span></a>.</p><p>If ever you donate nothing,<br/>this program will continue to work without limits of functionnality,<br/>without limit of duration, and even without insulting you ;) .</p></body></html> <html><head/><body><p>Bonjour !</p><p>Il semble que vous appréciiez RaySession, c'est déjà une bonne nouvelle.<br/>C'est un logiciel libre,<br/>mais il a demandé et demande encore du temps.</p><p>Faire une donation (même petite) est un moyen simple de dire &quot;Merci&quot;.<br/>Vous pouvez donner <a href="https://liberapay.com/Houston4444"><span style=" text-decoration: underline; color:#2980b9;">ici</span></a>.</p><p>Si jamais vous ne donnez rien,<br/>ce programme continuera de fonctionner sans limite de fonctionnalité,<br/>sans limite de durée, et même sans vous insulter ;) .</p></body></html> Remove Template ? Supprimer le Modèle ? <html><head/><body><p>Are you sure to want to remove this template ?</p></body></html> <html><head/><body><p>Êtes-vous sûr de vouloir supprimer ce modèle ?</p></body></html> <html><head/><body><p>We have no possibility to save the client <span style=" font-weight:600;">%s</span>.</p><p>For this reason, it's preferable that you close yourself this client,<br/>probably by closing its window, saving its changes or not.</p></body></html> <html><head/><body><p>Nous n'avons aucune possibilité de sauvegarder le client <span style=" font-weight:600;">%s</span>.</p><p>Pour cette raison, il est préférable que vous fermiez ce client vous-même,<br/>Probablement en fermant sa fenêtre, en sauvegardant ou non ses changements.</p></body></html> Don't prevent to stop this client again (discouraged) Ne plus empêcher la fermeture de ce client (déconseillé) Stop Anyway Arrêter malgré tout Close clients yourself ! Fermez les client vous-même ! <html><head/><body><p align="center">Some active clients do not offer any save possibility !</p><p align="center">Therefore, it is best that you close these clients yourself,<br/>probably by closing their windows and saving changes.</p><p align="center"><span style=" font-weight:600;">Please close yourself the programs with this save icon:</span></p></body></html> <html><head/><body><p align="center">Certains clients actifs n'offrent aucune possibilité de sauvegarde !</p><p align="center">Par conséquent, il est préférable que vous fermiez ces clients vous-même<br/>probablement en fermant leurs fenêtres et en sauvegardant leurs changements.</p><p align="center"><span style=" font-weight:600;">Merci de fermer vous-même les programmes ayant cette icône de sauvegarde:</span></p></body></html> <html><head/><body><p>You've got 2 minutes !<br/><span style=" font-style:italic;">You can do it without closing this dialog window.</span></p></body></html> <html><head/><body><p>Vous avez 2 minutes !<br/><span style=" font-style:italic;">Vous pouvez le faire sans fermer cette fenêtre de dialogue.</span></p></body></html> Do not show again Ne plus afficher ce message Undo Défaire Ok Ok Skip Passer DialogAboutRaysession About Ray Session À propos de Ray Session <html><head/><body><p><img src=":/128x128/raysession.png"/></p></body></html> <html><head/><body><p><img src=":/128x128/raysession.png"/></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Ray Session</span></p><p>version : %s</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Ray Session</span></p><p>version : %s</p></body></html> <html><head/><body><p>Ray Session is a Qt interface for the ray-daemon.</p><p>Its goal is to manage together audio programs as Ardour, Carla, Qtractor, Non-Timeline in an unique session.</p><p>Programs just have to be compatible with the <a href="http://non.tuxfamily.org/wiki/Non%20Session%20Manager"><span style=" text-decoration: underline; color:#2980b9;">NSM</span></a> API to work with Ray Session.<br/></p><p align="right">Copyright (C) 2016-2019 houston4444</p><p><br/></p></body></html> <html><head/><body><p>Ray Session est une interface Qt pour ray-daemon.</p><p>Son rôle est d'organiser ensemble des programmes audio comme Ardour, Carla, QTractor, Non-Timeline dans une session unique.</p><p>Les programmes doivent seulement être comptatibles avec l'API de <a href="http://non.tuxfamily.org/wiki/Non%20Session%20Manager"><span style=" text-decoration: underline; color:#2980b9;">NSM</span></a> pour fonctionner avec Ray Session.<br/></p><p align="right">Copyright (C) 2016-2019 houston4444</p><p><br/></p></body></html> DialogAddApplication Add Application Ajouter une application Filter : Filtre : Factory Usine User Utilisateur DialogNewExecutable New Executable Client Ajouter un client exécutable Executable : Exécutable : <html><head/><body><p>If program is not compatible with the NSM API, </p><p>you should launch it in proxy to define a config file !</p></body></html> <html><head/><body><p>Si le programme n'est pas compatible avec l'API de NSM, </p><p>vous devriez le lancer dans un proxy pour définir un fichier de configuration !</p></body></html> Run via Proxy Lancer via un proxy <html><head/><body><p>Show more options ...</p></body></html> <html><head/><body><p>Afficher plus d'options ...</p></body></html> ... ... Advanced Options avancées Prefix : Préfixe : Prefix Mode : Mode de préfixe : Client ID : ID du client : <html><head/><body><p>The Prefix Mode determines the pattern of the client files.</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Session Name (Default) :<br/>The client files names start with the session name.</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Client Name :<br/>The client files names start with the client name given by the client itself<br/>(as in Non Session Manager).</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom :<br/>The client files names start with the prefix written below.</li></ul></body></html> <html><head/><body><p>Le mode de préfixe détermine le motif des noms de fichier du client.</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Nom de La Session (Par défaut) :<br/>Les noms de fichier du client commencent par le nom de la session.</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Nom du client :<br/>Les noms de fichier du client commencent par le nom de client donné par le client lui même<br/>(comme dans Non Session Manager).</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Personnalisé :<br/>Les noms de fichier du client commencent par le préfixe écrit ci-dessous.</li></ul></body></html> <html><head/><body><p>Use it If you want to add to session a file made outside from a session.</p><p>If you let this line empty, the client_id will be automaticely generated.</p></body></html> <html><head/><body><p>Utilisez ceci si vous voulez ajouter à la session un fichier fabriqué en dehors d'une session.</p><p>Si vous laissez cette ligne vide, l'ID du client sera généré automatiquement.</p></body></html> DialogNewSession New Session Nouvelle Session NSM Folder : Dossier des sessions : /home/user/Ray Sessions Sessions /home/user/Ray Folder Dossier New Session Name : Nom de la nouvelle session : Template : Modèle : <html><head/><body><p><span style=" font-size:9pt; font-weight:600;">Sessions Folder :</span></p></body></html> <html><head/><body><p><span style=" font-size:9pt; font-weight:600;">Dossier des Sessions :</span></p></body></html> Sub Folder : Sous Dossier : DialogOpenSession Open Session Ouvrir une session Sessions Folder : Dossier des sessions : /home/user/NSM Sessions Sessions /home/user/NSM Folder Dossier Filter : Filtre : <html><head/><body><p><span style=" font-size:9pt; font-weight:600;">Sessions Folder :</span></p></body></html> <html><head/><body><p><span style=" font-size:9pt; font-weight:600;">Dossier des Sessions :</span></p></body></html> /home/user/Ray Sessions /home/user/Ray Sessions DialogQuitApp Quit RaySession Quitter Ray Session <p>Session <bold>%s</bold> is running.</p><p>RaySession will be closed.</p><p>Do you want to save session ? <p>La session <bold>%s</bold> est en cours.</p><p>RaySession va se fermer.</p><p>Voulez-vous sauvegarder la session ? Save && Quit Sauvegarder && quitter Quit Without Saving Quitter sans sauvegarder Cancel Annuler <html><head/><body><p>Do you want to save the session before snapshot ?</p><p>Save is recommended,<br/>unless you made unwanted changes since the last session save.</p></body></html> <html><head/><body><p>Voulez-vous sauvegarder la session avant de prendre le cliché ?</p><p>La sauvegarde est recommandée,<br/>sauf si vous avez fait des changements indésirés depuis la dernière sauvegarde de session.</p></body></html> Save && Snapshot Sauvegarde && Cliché Snapshot Cliché DialogSaveTemplateSession New Template Nouveau Modèle Session Template Name : Nom du nouveau modèle de session : Create Template Créer le modèle Cancel Annuler Duplicate Duplicated session name : Nom de la session dupliquée : Duplicate Session Dupliquer La Session Frame Frame Trame ... ... Template Name Nom du Modèle GUIMSG Launching %s Lancement de %s Good Bye ! Clients are still running. Au Revoir ! Les clients tournent encore. Good Bye ! Au Revoir ! Client %s terminated because we told it to. Le client %s s'est arrêté car nous lui avons demandé. Client %s died unexpectedly. Le client %s s'est arrêté de manière inexpliquée. Failed to start %s Échec du lancement de %s Stopping client %s Arrêt du client %s waiting for clients announces... en attente des annonces des clients... waiting for clients to die... en attente de l'arrêt des clients... %sdidn't announce %s ne s'est pas annoncé %sstill alive ! %s est toujours en vie ! All expected clients are announced Tous les clients attendus sont annoncés All expected clients are died Tous les clients attendus sont arrêtés Can't save session, session file is unwriteable ! Impossible de sauvegarder la session, le fichier de session n'est pas inscriptible ! Session saved. Session sauvegardée. Commanding attached clients to quit. Ordonne aux clients attachés de s'arrêter. Creating new session "%s" Création d'une nouvelle session "%s" Session is ready La session est prête Opening session %s Ouverture de la session "%s" %s launching %s lancé %s terminated as planned %s s'est arrêté comme prévu %s died unexpectedly. %s s'est arrêté de manière inexpliquée. %s Failed to start ! %s n'a pas réussi à démarrer ! %s stopping %s est en train de s'arrêter Session saved as template named %s Session sauvegardée comme modèle nommé %s Stop all clients before rename session ! Arrêtez tous les clients avant de renommer la session ! Session %s has been renamed to %s . La session %s a été renommée %s. Session directory is now: %s Le répertoire de la session est maintenant: %s Load Error Unknown error Erreur inconnue Could not create session file! Impossible de créer le fichier de session ! Session is locked by another process! La session est verrouillée par un autre processus ! The named session does not exist. La session sus-nommée n'existe pas. Could not load session file. Impossible de charger le fichier de session. MainWindow Ray Session ... ... Session Name Nom de la session No Session Loaded Aucune session chargée Open Session Folder Ouvrir le dossier de la session Run Command Lancer une commande off éteint Save Session Sauvegarder la session &Messages &Messages Fi&le &Fichier &Session &Session &view &Affichage &Help &Aide toolBar Barre d'outils &Show Messages &Afficher les messages Ctrl+L Ctrl+L &Save &Sauvegarder Ctrl+S Ctrl+S &Open Session &Ouvrir une session Ctrl+O Ctrl+O &New Session &Nouvelle session Ctrl+N Ctrl+N &Quit &Quitter Ctrl+Q Ctrl+Q &About Ray Session À &propos de Ray Session Show &Menu Bar Afficher la barre de &menu Ctrl+M Ctrl+M Control Contrôle &Close &Fermer Ctrl+W Ctrl+W &Abort &Abandonner Abort Current Session Abandonner la session en cours Ctrl+Shift+W Ctrl+MàJ+W &Open Session Folder &Ouvrir le dossier de la session Open session folder with file manager Ouvrir le dossier de la session avec le gestionnaire de fichiers &Keep focus while opening clients &Garder le focus lors de l'ouverture des clients Ctrl+F Ctrl+F About &Qt À propos de &Qt &Duplicate &Dupliquer <html><head/><body><p>Duplicate the current session</p></body></html> <html><head/><body><p>Dupliquer la session en cours</p></body></html> Ctrl+D Ctrl+D Ctrl+A Ctrl+A <html><head/><body><p>Save &amp; Close Session</p></body></html> <html><head/><body><p>Sauvegarder &amp; et Fermer la Session</p></body></html> Ray Proxy Ray Proxy <html><head/><body><p>The program will be started with its current directory being a uniquely named directory under the current session directory. </p><p>It is recommended that you only refer to files in the current directory.</p></body></html> <html><head/><body><p>Le programme sera démarré avec comme dossier courant un sous-dossier de la session.</p><p>Il est recommandé de faire référence uniquement à des fichiers contenus dans ce dossier.</p></body></html> Config File : Fichier de configuration : <html><head/><body><p>Command-line options are incompatible with robust session management for a variety of reasons, so the NSM server does not support them directly.</p><p>Some audio programs doesn't have NSM implementation yet, but they can have a LASH/LADISH implementation, and we can use it for the save signal.</p><p>Be warned that referring to files outside of the session directory will impair your ability to reliably archive and transport sessions. <br/><br/>Patching the program to use NSM natively will result in a better experience. </p></body></html> <html><head/><body><p>Pour plusieurs raisons, les arguments de ligne de commande sont incompatibles avec une gestion de session robuste, Ray Session ne les supporte donc pas directement.</p><p>Quelques programmes audios n'ont pas (encore) d'implémentation NSM mais peuvent avoir l'implémentation LASH/LADISH que nous pouvons utiliser pour le signal de sauvegarde.</p><p>Si vous faites référence à des fichiers qui ne sont pas placés dans le repertoire de la session, vous aurez des soucis si vous copiez/deplacez la session, vous êtes prévenus ! <br/><br/>L'implémentation du protocole NSM dans le programme reste le fonctionnement idéal.</p></body></html> Executable : Exécutable : Browse Naviguer Arguments : Arguments : <html><head/><body><p>Most programs will shutdown gracefully when sent a SIGTERM or SIGINT signal.<br/>It's impossible to know which signal a specific program will respond to.<br/>A unhandled signal will simply kill the process, and may cause problems with the audio subsystem (e.g. JACK).<br/>Check the program's documentation or source code to determine which signal to use to stop it gracefully.</p></body></html> <html><head/><body><p>La plupart des programmes vont s'éteindre tranquillement avec l'envoi d'un signal SIGTERM ou SIGINT. <br/>Il est impossible de savoir à quel signal un programme spécifique va répondre.<br/>Un signal non géré peut simplement tuer le processus et causer des problèmes avec le pilote audio (JACK par exemple).<br/>Consultez la documentation ou le code source du programme pour déterminer quel signal utiliser pour une extinction sereine.</p></body></html> Save Signal : Signal de sauvegarde : Stop Signal : Signal d'extinction : Test Tester executable failed to launch ! L'exécutable n'a pas réussi à se lancer ! Launch Lancer Stop Arrêter <html><head/><body><p>Some (very few) programs may respond to a specific Unix signal by somehow saving their state.</p><p>If 'Save Signal' is set to something other than 'None', then Ray Proxy will deliver the specified signal to the proxied process upon an NSM 'Save' event.</p><p>If program is compatible with LASH/LADISH, save signal is SIGUSR1.</p><p>Most programs will treat these signals just like SIGTERM and die.</p><p>You have been warned.</p></body></html> <html><head/><body><p>Quelques programmes (vraiment très peu) peuvent répondre à un signal spécifiquement Unix pour sauvegarder leur état.</p><p>Si le signal de sauvegarde est réglé sur autre chose que 'None', alors Ray-Proxy délivrera le signal choisi au processus lors de la sauvegarde du proxy.</p><p>Si le programme est compatible LASH/LADISH, le signal de sauvegarde est SIGUSR1</p><p>La plupart des programmes vont traiter ce signal comme un SIGTERM et s'éteindre.</p><p>Vous êtes prévenus !</p></body></html> <html><head/><body><p>Save Session as Template</p></body></html> <html><head/><body><p>Sauvegarder la Session Comme Modèle</p></body></html> Application Application Executable Exécutable Sa&ve As Template Sau&vegarder comme modèle Save current session as template Sauver la session en cours comme modèle Add Ajouter &Executable &Exécutable &Application &Application Server Status État du serveur Save and Close Session Sauvegarder et fermer la session Add executable to current session Ajouter un exécutable à la session en cours Ctrl+E Ctrl+E Add application to current session Ajouter une application à la session en cours <html><head/><body><p>The environment variables $NSM_CLIENT_ID and $RAY_SESSION_NAME will contain the unique client ID (suitable for use as e.g. a JACK client name) and the display name for the session, respectively.<br/>The variable $CONFIG_FILE will contain the name of the config file selected above.</p></body></html> <html><head/><body><p>Les variables d'environnement $NSM_CLIENT_ID et $RAY_SESSION_NAME contiennent respectivement l'identifiant du client et le nom de la session.<br/>La variable $CONFIG_FILE contient le nom du fichier sélectionné ci-dessus.</p></body></html> <html><head/><body><p>Trash</p><p>You will find here removed clients.</p></body></html> <html><head/><body><p>Corbeille</p><p>Vous trouverez ici les clients supprimés.</p></body></html> &View &Affichage &Rename &Renommer &Save All From Saved Client Tout &sauvegarder depuis un client sauvegardé <html><head/><body><p>Some clients (Ardour, Qtractor...) sends their saved state to RaySession.</p><p>When this option is checked, the session is saved when a client sends that it has been saved.</p><p>For example, a Ctrl-S in Ardour will also save all the current Ray Session.</p></body></html> <html><head/><body><p>Certains clients (Ardour, Qtractor...) envoient leur statut de sauvegarde à RaySession.</p><p>Quand cette option est cochée, la session est sauvegardée quand un client envoie l'information comme quoi il a été sauvegardé.</p><p>Par exemple, un Ctrl-S dans Ardour sauvegardera aussi la session en cours dans Ray Session.</p></body></html> Wait window before reply (needs wmctrl) Attendre une fenêtre avant de répondre au serveur (requiert wmctrl) Options Options &Provide bookmarks for session folder Fournir des &raccourcis du dossier de la session Desktops Memory (requires wmctrl) Mémoire des Bureaux (requiert wmctrl) <html><head/><body><p>Go back to a previous state of the session.</p></body></html> <html><head/><body><p>Retourner à un état précédent de la session.</p></body></html> Snapshots Clichés &Desktops Memory (requires wmctrl) Mémoire des &Bureaux (nécessite wmctrl) Return To A &Previous State (requires git) Retourner à un état &Précédent (nécessite git) Auto Snapshot At Save (requires git) Cliché Automatique à la Sauvegarde (nécessite git) Auto Snapshot at Save (requires git) Cliché Automatique à la Sauvegarde (nécessite git) No Save Level : Niveau de non-sauvegarde : <html><head/><body><p>If this proxy has no save signal,<br/>it can sends to session manager this information.</p><p><ul><li>0 - this information is not sent.</li><li>1 - this information is sent, session manager will ask user to close programs himself at session unload.</li><li>2 - this information is sent, session manager can try to close window gracefully before to ask user to close programs himself at session unload. Good choice if the program displays a dialog if changes are not saved.</li></ul></p></body></html> <html><head/><body><p>Si ce proxy n'a pas de signal de sauvegarde,<br/>il peut transmettre au gestionnaire de session cette information.</p><p><ul><li>0 - cette information n'est pas transmise.</li><li>1 - cette information est transmise, le gestionnaire de session demandera à l'utilisateur de fermer le programme lui-même au déchargement de la session.</li><li>2 - cette information est transmise, le gestionnaire de session peut essayer de fermer gracieusement les fenêtres du programme au déchargement de la session. Bon choix si le programme affiche une fenêtre de dialogue si des changements n'ont pas été sauvegardés.</li></ul></p></body></html> Favorite Applications Applications Favorites Donate Donner Snapshot Snapshot aborted Cliché Abandonné Snapshot Error Unknown error Erreur inconnue git didn't stop normally. %s git ne s'est pas arrêté normalement. %s git crashes. %s git a planté. %s git exit with an error code. %s git s'est terminé avec un code d'erreur. %s Snapshot error Erreur de cliché command didn't stop normally: %s la commande ne s'est pas arrêtée normalement: %s command crashes: %s la commande a planté: %s command exit with an error code: %s la commande s'est terminée avec un code d'erreur: %s error reading file: %s erreur de lecture du fichier: %s actions Desktops Memory Mémoire des Bureaux Auto Snapshot at Save Cliché Automatique à la Sauvegarde add_app_dialog <p>Are you sure to want to remove<br>the template "%s" and all its files ?</p> <p>Êtes-vous sûr de vouloir supprimer<br>le modèle "%s" et tous ses fichiers ?</p> child_session Child Session Sous Session client Failed to launch process ! Échec de lancement du processus ! client status stopped arrêté launch lancé open ouverture ready prêt save sauvegarde switch changement quit arrêt error erreur invalid invalide noop pas-op removed supprimé copy copie client_slot proxy proxy Display proxy window Afficher la fenêtre de proxy client_status invalid invalide client_stop <strong>%s</strong> seems to has not been saved for %i minute(s).<br />Do you really want to stop it ? <strong>%s</strong> semble ne pas aboir été sauvegardé depuis %i minute(s).<br />Voulez-vous vraiment l'arrêter ? daemon Ray Network Sessions Ray Sessions Reseau port %i is not free, try another one le port %i n'est pas libre, essayez-en un autre error Some clients could not save Certains clients ne peuvent pas être sauvegardés Impossible to save template, unwriteable file ! Impossible de sauvegarder le modèle, fichier non inscriptible ! No template named %s Pas de modèle nommé %s Folder %s already exists Le dossier %s existe déjà impossible to read %s impossible de lire %s No announce from ray-daemon. RaySession can't works. Sorry. Pas d'annonce de ray-daemon. RaySession ne peut pas fonctionner. Désolé. error_message Impossible, copy running Impossible, une copie est en cours errors daemon crash! Plantage du démon ! ray-daemon crashed, sorry ! ray-daemon a planté, désolé ! help A session manager based on the Non-Session-Manager API for sound applications. Un gestionnaire de sessions basé sur l' API de Non-Session-Manager pour piloter les applications audio. connect to this daemon url connecter à cet url de démon Use this folder as root for sessions Utiliser ce dossier comme racine pour les sessions Open this session at startup Ouvrir cette session au démarrage use a custom config dir Utiliser un dossier de configuration personnalisé display OSC messages afficher les messages OSC debug without client messages débugger sans les messages des clients main view No Session Loaded Pas de session chargée menu remove supprimer Favorites Favoris message Client template %s created Modèle d'application %s créé network_session Network Session Session Réseau new client template New application template name : Nom du nouveau modèle d'application : new_executable Custom Personnalisé Client Name Nom du Client Session Name Nom de la session new_session none Aucun proxy None Aucun rename_session Rename Session Renommer la Session <p>In order to rename current session,<br>please first stop all clients.<br>then, double click on session name.</p> <p>Pour renommer la session en cours,<br>Arrêtez d'abord tous les clients.<br>ensuite, double-cliquez sur le nom de la session.</p> root_folder_dialogs Choose root folder for sessions Sélectionnez le dossier de racine pour les sessions unwritable dir Dossier non-inscriptible <p>You have no permissions for %s,<br>choose another directory !</p> <p>Vous n'avez pas les permissions pour %s,<br>Sélectionnez un autre répertoire !</p> server status invalid invalide off éteint new création open ouverture clear nettoyage switch changement launch lancement ready prêt save sauvegarde close fermeture copy copie server_status snapshot cliché rewind rappel waiting attente save sauvegarde session template Overwrite Template ? Écraser le modèle ? session_template Template <strong>%s</strong> already exists. Overwrite it ? Le modèle <strong>%s</strong> existe déjà. Voulez-vous l'écraser ? empty vide with JACK patch memory avec mémoire du patch de JACK snapshots Today Aujourd'hui Yesterday Hier %s at %s %s à %s at %s à %s before rewind to Avant de revenir à url window <p align="left">To run a network session,<br>open a terminal on another computer of this network.<br>Launch ray-daemon on port 1234 (for example)<br>by typing the command :</p><p align="left"><code>ray-daemon -p 1234</code></p><p align="left">Then paste below the first url<br>that ray-daemon gives you at startup.</p><p></p> <p align="left">Pour lancer une session réseau,<br>ouvrez un terminal sur un autre ordinateur de ce réseau.<br>Launcez ray-daemon sur le port 1234 (par exemple)<br>en tapant la commande suivante :</p><p align="left"><code>ray-daemon -p 1234</code></p><p align="left">Ensuite collez ci-dessous la première url<br>que ray-daemon affiche au démarrage.</p><p></p> url_window <p>daemon at<br><strong>%s</strong><br>didn't announce !<br></p> <p>Le démon à l'adresse<br><strong>%s</strong><br>ne s'est pas annoncé !<br></p> <p>daemon at<br><strong>%s</strong><br>has a loaded session.<br>It can't be used for slave session</p> <p>La démon à l'adresse<br><strong>%s</strong><br>a déjà une session chargée.<br>Il n'est pas utilisable pour une session esclave</p> <p>daemon at<br><strong>%s</strong><br>uses an other session root folder !<.p> <p>Le démon à l'adresse<br><strong>%s</strong><br>n'utilise pas le bon dossier de sessions racine !<.p> <p>daemon at<br><strong>%s</strong><br>uses a forbidden session root folder !<.p> <p>Le démon à l'adresse<br><strong>%s</strong><br>utilise un dossier de sessions racine interdit !<.p> <p>daemon at<br><strong>%s</strong><br>uses an other Ray Session version.<.p> <p>Le démon à 'adresse<br><strong>%s</strong><br>utilise une autre version de RaySession.<.p> <p>daemon at<br><strong>%s</strong><br>has a loaded self._session.<br>It can't be used for slave session</p> <p>le démon à<br><strong>%s</strong><br>a une session chargée.<br>Il ne peut pas être utilisé pour une session exclave</p> RaySession-0.8.3/locale/update_pro.sh000077500000000000000000000013531356671433200175750ustar00rootroot00000000000000#!/bin/bash # This is a little script for refresh raysession.pro and update .ts files. # TRANSLATOR: You don't need to run it ! contents="" this_script=`realpath "$0"` locale_root=`dirname "$this_script"` code_root=`dirname "$locale_root"` cd "$code_root/resources/ui/" for file in *.ui;do contents+="FORMS += ../resources/ui/$file " done for dir in daemon gui shared;do cd "$code_root/src/$dir" for file in *.py;do if cat "$file"|grep -q _translate;then contents+="SOURCES += ../src/$dir/${file} " fi done done contents+=" TRANSLATIONS += raysession_en_US.ts TRANSLATIONS += raysession_fr_FR.ts " echo "$contents" > "$locale_root/raysession.pro" pylupdate5 "$locale_root/raysession.pro" RaySession-0.8.3/resources/000077500000000000000000000000001356671433200156455ustar00rootroot00000000000000RaySession-0.8.3/resources/.directory000066400000000000000000000000741356671433200176530ustar00rootroot00000000000000[Dolphin] Timestamp=2018,2,27,13,18,37 Version=4 ViewMode=1 RaySession-0.8.3/resources/128x128/000077500000000000000000000000001356671433200166025ustar00rootroot00000000000000RaySession-0.8.3/resources/128x128/.directory.part000066400000000000000000000000001356671433200215420ustar00rootroot00000000000000RaySession-0.8.3/resources/128x128/raysession.png000066400000000000000000000621261356671433200215160ustar00rootroot00000000000000PNG  IHDR>agAMA a cHRMz&u0`:pQ<bKGD pHYs B(xtIME  Dlc IDATxwWy/=ulҮVҪ[͒-Ʀcph6%$77\B!B.I r{[>o=;ɖ$|>jwSsGX<4Ѫ*H) !IJtg=M &PmkfU@L;F ymm*5ʜ/[砪/%,k~ku4gtXX\\ZK c-*] eSz88MMC.\xCވ/]f Dh'wUBu}VYcڶ6J] SS9s051.֕ЂŋQ`MNcgϊ\2JW0RJ|7WhTaı'E"2tVjKd2Lp]P0lus3Ƈ߰z5t]ؿ{72׽T L#:!Ds]-,I.!iӼ<01<,~͛7_1_Wpwc||`LOOM3l_'.$I6!B@a qF9}ٲHWB#XDU>2zq]dm$!cYPCP09♴|u< J]qEs D,6e,V Q FdJ7 dmuQTR>]na7_RqRt.5-vwQ3HQ FtYmjJ72yw(t=իW_q_7pG&N&s[ŌRCf9iJ| ,Ca 2稐055u2Uz@7RXl%BTRRTP,x,6?kK/ŋ_ױ__mےmU~n)4,1PRHL)4Ib۲lo,ka:??^Ez5aJ(]Y(BK8"dZeBnJRUVuPԧ5˲Z#Xv]B u] sYB0 Q RD;{zo}.ҫQ_Oꊋ$mL)ׅ7箋AkQD>9xmGůktww34}q+US J"S 1S98(EոreΕ.)],gT!.rq 3sdbA"MCT9l6[fhnn~c_𶷽 ɓ'㺥 DuZR)J$-9-+Qh2 Y[XTe !MOs2(! a.i"ɀFcsɤuV288xP__cJ< c98A!*B,Ce0I@0, s m,/&ª,+>Bn E<፤*tQe%q]X, ۶ F(Sdnd2Uٳg^IwMFLjD"4N[RiFHHsibqӜsYJݰFualχH#Ip dUaf!9ǀd; $]!p98`'I]"ɲ>w\RUUu]pMPSSUV2&[bA1c22c) TY-4q6\8a.BTIbϩSox$S$! Xq0- c3&dK808f4sB !80Y5"YG>^9\S|>444X,cBZnY(-,8 Hpd$m{b,8j,Jo뮻"D](V*Yp$ɵ$@C! tlΑyTUl"k]w݅|wut][4yr 2BUnEU-Iss!PBRɲVC&_E[G.l4M0J4$! d,2JG$3;c"<.p8 *$DHcsիWk>kEL&eՒmjʄz?&R:dQ[aBTג#l- 4((մb]_ce.IC$ieEu~Y$`pI˚L[V$IӁP IKtYJBBD붥30~k_Ӹf(**ƍ8L PQ?r]؜å19dȒ5-%%S"$&M3"̲i.,-!n$s0*Y 1B@: cR-մ,ڵk~MrM~z⸮&\As9{ ! !s~hD(bRe!;XS +]585]&x"ՄX$DB () iI`bRihh]ǎeW!u|/ A4I$Aĭv4qE6!-+mHBɿB 麝 ozS\E7ne\w'~ximYB 6kZ" eϜ9QSWxRH6yG$8at6D"oop_\u ( (s- $یFo~ׯ$UUo.rN$Sڕ!䩞GvgЧ>)GGSjuiBpCsIǸs'KKK͓'Obɒ%H:& cOQ1X蒄2h%ljrrR?}͢Wh8wl;ˋ'坚e$)e3'嘦 (--<UUmqƄ#$m$)Bs>ƾXY[{0T\-[\A{a ;mz$QY!(=9O$7ߌo۹giQzh¶A 8fa:N.RH@ysLOO_HWa6l8R]i APӐ, UrJґl*3g,˃m+F)!Yi;jne3X0Yo^4͙s> J%d*$P(z^IH$SKڊr,EF4)d8 : u_m;5|_s#SιI&[枷"māCI5ׯE HYO+r1"?xBz5ы/]ױw~1y PMUO*rd'>X,V$IXG4L9E8GeZH$z\\5DQ!i20lrqr?JҔ&U3˝#x4ut0<D+**27o7oƩS. ۷qm9~*pDӴ~?&?gرc;v A,H'X>!`.ײڎ744֫>f?a``mmmȪp6U+ $IQpRsnY'O3111A*++1:: usah]d$SN~f_K]W!ySReH 88aYs_KKEqqĉ6(9sD2Brc9Y 9 Gd8iJOBt p`ւ>l>K Ƙ*9M]]=T^R,ɺn,c<я~ઈVTVV"PWHHJRN)<[sLrM q`02y_t]XtIQi2Jm0BP,I\UR!U?UUX Oq4 Ŷ\WXLۆOh!P:b1vxސMx#رcrqq`\;n$n>fpJtt,'W(m<$J2!-e SJ@>[ IBp~u_HV-ʦ8ܗL- FtK(Eei&-`XhUU/X,FabY R aHF 84͎L"556bBh .!%)W!$ٲLu0RKJJjBRNKBGO&x޹UĸB g1F?䓸{~gu]Fqu݅Gi iB$!PhKo,uwuap4+IC>U͕Q R% b8\ǩSEYv-b'+NXd RɴK#$ː!ϔmcu㶢fYeo8i@qΏy=˜2(8G R666vոM0|…Pl˪Qm{8r]u1A)ҒԗeTW%׹+ɪ'[7Ø䫉%B7 B H J}>Cmm! o+Vd@6;Ck{Ak׮M1! W+S媢Cu,/TSSS7oEEE?IaYaK%!)A)T6 N޹eљ~1-BBN'1.ª %E0͹TjԔd(pBoBL.LHuaWڲ .M0qb쐣 xuu,&$!1vNeT:j%)"D*+..W@mm-jkk (թ \VKyI2|eOlQ$mJ` r&cZ,/!gYvz@qq1~򓟠%S"uWT ɝz'Q8!{F!b1BNMXU dFu[M1_"]{$.455LFNRU!!3$ ?K-kh0LYhWP"M,YMB8? RB e>bXt"òty@9LE(m:3B!' ~^K|>X`6MQǙvCg @?,.y̝;uk}݇^xP"E*7[!L*>Ͼk{IUUUPĄvgMr( hJg2xuk@6׾5$I)TY4')-(RX:hڑX53;3 \U|IjOr;X$〺.e$S)iT J)|Ipۆ1oMQ(G\!`q1L9b] QB7zI4Չ,Ҕ&XީFbqšRt_w&444)R e6>9d|tдm*®ɶ ͖K$}ě}WիW㩧ؘ5he#m#f5q)1-1JuOCǎ\T|7R)1B`4&5F4ա!z\W j_5aU>YY6kPHw iI:*ZUJ'Fl{_O6=9GRRB]Ͱ,$In]WmmmE{{;FUK|?7Gl#4* eRUoKmJ'K<,uҎw GQvfE|O!,/IJ]WYnbҲ,&^ZZZpy$IjXV0HȲR$AY.$2ZA52g1`"ЫӟG-))s9d<LNLfD:H"}?Gȑ#8q8eD cϫ9iiL[{t'?r6&č!JQ* |Dy]dC:-˜z ())9EeM eYP)CYiNկ71zکe#sd"C`0]LѠ6Q?Ї>s碣oq>|LFH~?ގx۷o[wk/tR^^.)),**.--=ؿ?nx=HZ((Jgq2ܷtɒ7ȷnzuJpadYl8IRND@Lqalp]De|׿:[ٵږ_Z $\ ܼ|[o\l7q]7-λ[H$c+ٶz(XUYqϹs݋t:C~oihjLw%M h,Y_>?3+o{MMMӃO|}Vqq c㊦4vw4M! I!0M3kQC$R|`u9;trMESMsigy۫b]"ݽ:zU[9qA0fYPp3`bT__-쿑�Av$҆=@ڰ`:.~ ӆ$[AU 24۶5k.j-s5oeawtJ&''{^mjjs=i7y3~{iD"R6'~B`Y\ׅ|f3ɸk'fA4 PCDPNH(Li8EgΜW__Qr5L3 (8Rh[Jz/56+$Jb),8w20h*"{m4Ew=6w{e߻w?z;TghhMA:oxN+CMXuppqB Y,Hiu/C8R&/'HelkYu?q++r,\k֮\-R\w]OG`2T~ G{1?.0o/_yg]7YXltrY v*2zQHazb#aua_,<F6X䓿!c JCq066/߸aûVX1?C3wR,˺rMCcetuw2 H( ܀IL$enOame^h* .IH6|"l5]4h1vEJ^@s%.ku Ai$ 7.lp۰mG=ƺH՜+<+B}'ϜGnV\Z극.*܄xM T())p!$IBEEE E,˂ !с@ P򎷿CwgKJJNNN4LNMI+3YMg<۶A)\71Ie[%d*MAXŘ?0ޝCE@QX2{zz>uW¾"T"ԄXTJHYI]݂QCݞH#3^ :[W?>Ϙ7\UrӴeё'o( ,$ǁiZDQlFee%^Iy_GO!wvScۑɁP(h~* ?߼m/**<ٳԲE$~1ݱiSNG8 (6::ыN<͗ʻ$D:NsvuP4)sQUخI:\u8,-.or-/~|rLNNBQL嶽D`W pns\P8.ecԁ$L..kZƆP((YcXɅcS0R@o7$cHǧDŽa9q9> APWHg䚪}{V#?J޼q=zGЇl潄ȏ}osֶo۵kWxᔺ sB,KԩEHpB$[tYWz@9C8tؔBĶp+#HqSx53 eݚ;w.(P;4;Dz@Ept;dYeZ[ d,(Jj};NΑQǀI$1؎ qA(0s BL^6eyCcӜ<`|%CGяs477S<9z0t駟Vc߾}]k%$T]]ͽ}}Է޿a`bbN7ܶ58T&R kjhs\u޼y)Eӎ tu+uH@"_}e2ʃ XyYyƲ,>>>wFee%'nw;]d % ې5 vv\,I"¯)OH:qZ#i"52tMӆp4 "0ɬ F&0sv%"nɩήCu]o k|n{):Ic#}D C%q'~9o>K={tR?=|_:cǎ+qv?9u{ Mz~K%Lxm+WOM t8Lׅ?_aUU/>_n޼ g>k@]]lق~ZVZgyHe߰#cPU\,@}`tdT\\@.ua9ˑ1,VN윛Ye 3560A{~RnXlSVR^9Xw~ijta Dz뇎?朣*\|Q**~Sc E|83'0 B)ݺ~_t' hmE[Lָ4>nUUŜ9s0>>~e#<OL̯n.oz 9p# tvPȒtք$IPd`Lr (\+Fp\ 5Aie;Lcd fj8=:pG]s- m:t dV&)5_R\Z>yRQ×XvuuaUUUlds mBHsΑd w%I]tP)Ey˖ir2ɐNTT-[ }z SO?MD[#$UTvI[PLLNoH,_m1 $cZZ1!q9RY@$dM Bc+JzBhN d(e1v`4UE0`(e0- u^8׮u0"'sEr kZ 7Vv]]]?|`BB{Μ9[y"D"|4`&(ɠAikIIB80y ^ܾw,k:t,f:~EF/ /) 2!08͢v^+"磯eq9$Y i2 B)l'3F!aKER0IlF^PU ŊI tmcrbi9*1i6ob(V7FFFv+ػw/ٳg Cŋb]-B:tR˲l6[`> ؈ 7`钥hii7ވe˖e.D]m- 9韭+(d˶1002gxi>l۱HR9n)ːeiBe48pߏmw ?>-[7l2tueD,+ !?xwC6cG:. PdB( ӂ$+ \R 7" !ON.¯)`w&M0-%PU%+pENLvfnq$vAˁ=SCC,.Y$ Go%,n[ MǮ]n{gJA)7EYY.\˖u92 :::022&I:4MCQqn&HӧOС*XMD"-[RZ+!8ŋ,^wLOz7m䚦g@qq1VZEѩ *\qYQµN a<)f;$ EU Y]QQׄKW Z^87b||ClA(*h]u3ƉC0Ӂd<@(2ò2 B ҦF)xP I.Aòu'vܟNdEQ8u欮k:߹o>)5+csȲ [n88}, 2}\~eGG.˖c8p xxӨZxgrC*Ν;un7l߷oΝ;SKlڴ X%24߱(51NkLUEpF AvF,˄a z-'wUL(41F'cغ}'< .`||lm0 L&I38wʘӺu M0idi8\v\hLہaW@òA)Ce}p9K}4͡P(4RUU5~zHBE??ԧ\oY_lr  {>[oŢŋql}%:}"0SG#Nc`p]]]7a^K $BTU-| foL3 *++==G:::Ǐ獍KرcXn62>4Bnm" v ݶϜ((/s[nUڽf4LCQTUU[68ӇiP3L˂OSaZ6Yw`ÜXY)3>g7/32(StzqQ)>~Zm$mxHUUQ]] 7oۇ܍9_e5țwwq4MO>ܫa!p/29g(..M7$VZ5SO=#uO566&٬xg/Xl9qi$9!)J_a˗PwAḥ9R-X6L'xg1::Zx\;ۋ?8֬Yk60'`YeI$ T1:|;섍?j`0X{{Y:$P(DKLuR3 ёђÇtpMr]hz+t݇FFF 63y's>lq]o[h (`foM *!aH&F0!4 i=/' ( DuU)/ƞ{0::ZOgqr)1J@em.9s4im#H"^,ݰ5陟0 ض EQ `O^f x@C\J˟fivg pVZ+WsC{K!\FmC= v:5x*zYVTdY}yq|ض'N,/PS#븠yg&ox“)+WAG>Ys^("u3HWW=q<\ہk{u ۲q6qw Z`sJbX!Fqol@W,Ǒ#GH$ m6/7Ǚ|V)Hعsش eee˯@&w191cd2Rku}VeMG>+J]#@`tl 7[T2 all C(? `/Ybo!J]d^ja<66ٯ˝*9? prdM tbhx}=%a&'&da,C$|ٳmg=tT1΋&Y_jg115-`عȤsaix2ޞVQQ>4ŋunDiiiqEQ. xP48.i\ Ҝ^ 3NՍ|(|U`YuQ;o$ ŎP-keYQq$n֋2onX2895Y &$I@zQ3"666abrxѭKe/gBڶn7ͅa.@@sE ضؾ}?_?lr-)8R<6SN{y`%%%K`)~%Ҝf+  "Z\H8],@R@gyP@( 8SqZq>2SQP5 @ lVt,JiARB ;3k[(χZUWn6^ia@V5 ]Pq!@۶+_xX];w~űvoNQ^^^{c5˲i(c\n\X+sa#a0J S F18f+ IB@Ph`r59[~%KyP4͂_4BB+6U9%'pM\N8q9G FƀP TR008=wuMn}hʕȲ䴷?k/-^t{w-B4B!Df^帟7ٛ|9+gdžqM+`("Qئ qe„@Țf9rP8"Z U5!۶}a\uF `3;CRWr☦I( ˶2B2D"<;wJhiCeEp$<. FRTv`"(X^ڷ,3+H-kyq\30J@p]. 8\t<jp85(mB^zYzuݷ.wQZZ{j̙Ο?Q$b$24M(W;!C86DbfBVryd dN R 3g2/}<9]s+ٮlwKc !O@I#p7 d E"F`F\yzBy{bx-8~]׵/w.^hS''ӳgap`m#²m\65@JN4wB&[V34o3gfiR)adtTҩjj^dO4ضS.Q3}Lߙ^?y !rLaf2P% 0 jѺfKAu>pHsL4>3@zs[0::Xp˜`(xa5UM%B~Ν;YWW7\-K^B&-Y8hKs|癜yMQThfg2l0 ˲L (lm Ϟٛ=N̞c0mH- r" E|z8&Q^Sz.lEӹ Ҿc93˗/y ~'#<?L&'O>ioo׊%.hin{b|"Wi0 z>rvŚ W[K@t3ixJћ̓93sjfBK<C*i(//lhwvjv>s3!e`D1MH@f@\4s` ^X}ʪU+FxE9rK@Ns4-n&c1˲cǎ)JY.2-~yy9E)ao/@cR 5sq K22 dY.$J(\AQ$Iu!L7N}7_xu3;3ZpC4ijD26̂@ Fl\˞dlɣ"Anj[~fJH׆ >1BpA~Qȑ#t:QJu];_QQq,Z͆!Ȳ\eY8D0B(d,R%5x޿~rDubppl`,~)p J)055u= //< uvvBU&zs9W{YYJpM罁B>U$YX,ONNJ bbbcccH$طo6o,x'jvd2 ب"BBn/WZ xUUD"8lۆy۶aYV L&;-.3S H_$Z[~K +kr)KyZ`e*KysJ!8Ad 3 \1*=1U$\Q!<0yI+**z{{ۗN?0m8s,%ͻ٬r"ؤ)/tl |~it]`Ls^PTWW`0[r޾bϱ4EN>5W ݗX gϜ岝.Ƣ9} f6 ]0Jr!F5d @rzK$J 1]QQRV-Ot¯m!GVX~ƣGʞ0hjlĢEmطoEp&ό_3RxN l{pM-UQ,s˲@ ຮp /o'&rIyoى,Z]2s33[9GC|>F8${: 3  .1P"2$Jw J #LIONLIT%  h˿]57ʙ>q844Çc͚ɥ:JwN.8¡n _th1nqk#G1L#caZ]גenظayI4z]vElކ(EQ`6lɓƍ7ވ[03#Jx9KR+V`^S=yEQ:s49JDz,OC4a&tBlׅðBȨFww  a߾éh4.(%y?~;ģ>KWx :6nJvl~ z؄g={ey1NVkkk{` Dm{x t~ݷo^ض;v"f&s\v%e˖bh߷]d u p,4&059xl >µ P&#\RtNMMu:iu-\yh4;vV,^Ç ex2H嗷[V<_ ɿ|g6Eҟ7~;Z['?F~ l7;uXc'*xC*ka :t% zs4 <:w^pۭ¶lۿP r)r pIz*liއA r90y ѺjC,cĸeYݗM4Q̦e1FU_Щ!ĸ,IBя~WlOt: Cɟ?jj 3}*Lӄixy66gf ȣˉDp=?c*6vŊ[~'9uԡ`0xO3#_~?gQ[ߴcccؾ}FGG v֯^[FϹuSi@0(GIYn~h|Y˶'u]JJJR~Op.$2n?-[###V;3R$@ !.\'ظq#С8~xA^T ظ<&miwld =wMӆ,Is{wcKC=@{3{1X[oطΞ9X쒎s˖-Mn'~ ky?ncCа`9,>vONMu~ !B$QJ7(qw/˗?Q)>=ZtP$y<%RPJ155>hիV6"r! .G}}=6n܀%K`ll O?Lx8ߏKTVV~MLLt%rl``]l)20rùs4 7ǖ 2@,Ggg'`X{㍈D"ɋ^0!kVuk }qdVL Ay{?wH* s~y-9z|>_WuuĜ9sd2QVV/--BLȲl0GGFG~ϑ7y-f&ݼY:s leASUXcՊ ױ!3C$AII |>allDdTܶ(r  Af/ t:}! gih&vj@|>,ˍeòԐg֤{c3Lׇ~BX~?|>8tL& Ӵ 7 `ixa] q$I011ǵׯ\r]v'O6|+_>ӏGۖ/[vˮݻ( (ZZZ̲ҲL6ӱk׮Mn8s,lqp׶ A 62nV&NpϓH !]79rUrTعkAc`MMn:_7L;&:--+kc^|q4wqPUUr.BBTdӘF:F,C*7TM/Sw8Wfhll=bvSSS8q?_0cǏ45Ѳ5EE5na*^m[g7ӟt⮻ B3lU?6SE)Tʾ׮]cISSdf5'&<)Q?ʼn<3(d^jfΥ *r܂MMM~]Ξ?g֭Ν;կ~[lAC}=s}oqС{vڵ#GFGG|㜟jn;\]^VMR6 JFQ0J("Ab 9 ȌR<(! H4\T\"niis^_K/_[l}y -XpacB .X7N{v?~|ۂ%KDeYQ/J>;3R祂4+=[|fi{3=n*y$IB<G0㴏{zzhooǜ9MOk~S';:;It]|C$MDt2J 4Qkii)%I&eM])2$Ҷ(#!ir%J\ׅ<$?׶p`Y L9S]]m~? wjC֭/WTThEKGFt۶<~i.Sg>sLOOᆭƦcLfF3S ZD/wofŒl6 Mץe˖#t_8N_c*{ĉS񾾾qX\a7DƂ`0 nskkkŧœO>i;v/Hh3G/Ft?8_} ʀˌBffQ  ~$ȔFU% ,Fˢe*}gΜ=ijz˖-=yN:uۃ}'?2n~SS]zT h߹ssO>γ@ mY3gkkk9Mss4M xfLv_Q~DKh[7_o|MfyY)$|}^zII4PUUdct:66:̙Qtaՠ*s&3[n僃شiv؁G}Ν>9} +Sv(8dm(2iZ\~]:5p~LVGP\R&_J[L:M4Zn dFk9љwd:ECG/^$ &'?oi*...t>IFt]|>/3gΐ͛7O566imm}~ժiMo>s>22R`^ՍGަ%%Q SPC'WZ]߼tll),#N绍q8[`0(~y޵'Ot$3x5,'g> 2 vO:ASXr *j e 6mdcL79gN S]r!`U`$hp^yg***L۶_W ѣXOLM0r~5ްoǎK;vONNb˖-W?<y t=@)5|>_Rjo,pbm&>K$2_I::.yMMO)iuP(Fݗo8qÆϾyBɕPȒ%Kpm <Ï:C[>O|}ݛ=G,v,Kb!H9?"1\arŁ^$Im!$kv:Oqmyͭwk", ˂(A8\hM@23]je#wyk*?p0k_659E"p8w5M;vgqu~i"NUDQVZyttuTu@ Y3>/+˲o|ӱ鿺馛Ї?FFFzjf͚ޞ=Xr  %O?;1̧D"Yff3`v"|P$y 'O>UU ˲MUU];KK}Gw;PT^Ek/<bמS,IRVV ,\ӜϜ9.]=pV6L'!KȚ}A=c 2zg8yB7?U… wqw[_zCa-x6u>x-.Ң@l&FUA΁lրC8&322"8'? n~_hlL>w?p] T*eɲÇÅ ;pu~nX/-)]‹/߽{Y!Đ+))!^xWQl۶uoG>m9&K ctl`0G;:TEu=188hWVV 5 k׮G?Q{|_^rߢ.:mF*v?'&&&eY }Bd2ݲeO_)]ձ+ LMM!Ng=wgy9~?jkkӽ}=u+V /p3 !8v0Z[]ݿmŃp^@q /~]]]Qm [棼BK(m,=qqŤEEE~{(lbm߱qK|nj9{?~g^juD(:QZZS\\?ws}]z%8kA6мgf>.ׁ161D)MRJ3ne#FǎSJ~u=j }d&8wcDPYM.rlY|bogllv6yop&Ν;{vXqx0tG'&=E:VS]]괦iLWWWgWX>#b͸^< (u~bME 424$4M4́1UU}}}WTna,[⋙h4jȲ,>Ϝ9>͏@2*Տ],BJKTm2 Y$>L&;dt]tRd۷m79\SS$9FeMBdQQcݻwcxxum>pf8"?3g`¶9TTו#ٛf㊢5Mw!~۱}v ׷';,[ sT4 YRƊJKJJ('Nei4 Ƌ&\K/"J MD"asz)jJ;0l =:J*IZm˪$)}]SϧٳpOoH8|*G"H}b1TWWjj۶9W H$|`&#J~x7}|+_s.+Wu]"˲fmBٶmIe128cl, gő#G0_7͝;T 8iooJBIu3٬}v`VtP__i444jdh" $UUTJ$t]7(]vD_z[ނ4cɒ%͵3h>%tEXtdate:create2018-02-27T12:53:22+01:00~%%tEXtdate:modify2018-02-27T12:53:22+01:00#}tEXtSoftwarewww.inkscape.org<IENDB`RaySession-0.8.3/resources/16x16/000077500000000000000000000000001356671433200164325ustar00rootroot00000000000000RaySession-0.8.3/resources/16x16/raysession.png000066400000000000000000000015001356671433200213330ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDAT8_HSaƟsv3pjBL7iD'd3BLE ."A]vX&MŔ Jtzph144[k;_1نs}~} Hvs+@e0{HP   {B 7p*6 P'l}]Nݏzu"}=8<==-?S IENDB`RaySession-0.8.3/resources/24x24/000077500000000000000000000000001356671433200164305ustar00rootroot00000000000000RaySession-0.8.3/resources/24x24/raysession.png000066400000000000000000000026251356671433200213420ustar00rootroot00000000000000PNG  IHDRw=sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDATHLe_{119Ƀ(@%CZ-pLhUftZ*3̖/9#t Lj/DI(p&xwx?r~ޯϳG%BRlp#?fA ]UV`o9*3(생 lpg(nJ , "av@P fo7h#s~ B0N=_By=ax;:8j Q?&n/j qVb@}^pt@ߞT}tjETh 6AyI  .K}n q m0n=.?n?{qP^^>m֭iH,))96o^`מl f_ʟd˖人/7Y__z@J)}pTTT;r]v444|^UUVXJ@L6)En\ W8tЈ@*&MjqfϺ+߁(x;IYS<9m655YjkkRʯ6nx0&yXo: e_Rw=xe O:uDmEh߾'~u+)//7t B !III]qqdeeRu:URJ( `/ ;l6W۱c7_~1nbԩh$**@ VL4ߍ74Mo{wΨӧ{=zhD4^/]<ѥcF _ϛ<٥iTUU4X "tj555V4 zQ!RJrޓmIi0XOs5'gAÚ(B!>a4_{{0JIM:vфA~?PVCC{;&-F3tKxp\ndse9sjW=ۘt:uΟINNx~?IcZZZbϟdcպ˗}=0rlj X9a@}t(ޜk5''BNJwvvl6{9cƌ3'͛-ůtÆw/^@lk]B礔Juuu-@fmVUU涪Hh);55aMM͘aK,nڵr%!!ю;^R* .r)Xte;4=ñd͚5փZ #پ̃IENDB`RaySession-0.8.3/resources/256x256/000077500000000000000000000000001356671433200166065ustar00rootroot00000000000000RaySession-0.8.3/resources/256x256/raysession.png000066400000000000000000002317601356671433200215240ustar00rootroot00000000000000PNG  IHDR\rfsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< IDATxe}&YV{uW7UlSܣ${0#H @>|6c)Q${xcDKN[ Mj6{[wgy|j([cIc)ͪ p{{xxxxxx'q_GҥK1&K$|!󃃃{?ի:2PMq}T4|?|qu]ןJו1 !Biz7zz W^Mf1f9#pBHA)}@)~tΝᅦ$UM{nZ)_[[fx7mYUշ tڲ¦d,WrgR*؞ Bwܩ$Coݺ/Rzb !($L RxPQm &ZD񿍢֍7㾙' W^M>H9/8kYF(Bn1|>o8WI_oO{@^ʇh^~)֌a+! Fk%,B' r^0]r!8ʕ+KJE!7\h9wy%VR*#{b5IeiOʘ3v~RןBd/{M|$ !d̥28!(E)E" I)lR>O ܹi|3Y7mU~?X_@[{WZޓ6cs 鎕zqVUtee:=\v@wvv۷/>zv6{ ]cX!$RHƐJR (BE!@R PFHvP;::>880%):!#ރ3v[Q[Nb{t â0. HȹV ê'IqkZSTy㾀37xUUd2YbK8Bj?C[QY.j}0qq ZQ,C$ch1NR̊v:Hy.K҄ K'ָzjw1 vZRQ =baf qLJƐp=sXsh B RGg$n$|>9N{u]Gַ8q3{;;;UUE?!t%iER!o(B(K8* XAZ$r)Qwd3c\Xc$ _xpZnL]+n#M&)VPJJ :q m- sQ.% "{&)~:IsQe/_OU̖Is%GGGu]oq/ YCkvv1Z !؟N^A;IP7Ӏg`2AhJV: x/()Y{O?=sqKu<Ǵ~g (ncKin!|lp0NŎMgEWQ$ʊygv @& DZ1g},F-9Hb F)98Ӻư,qo[NlggL>ƙ7x~шe0>QWUʒ$V Fe-.zH8j6y$(Ee "0*B`TUHmƂӦɤ cH(͍1' Y/I)?C&d d0ރQzRik1q{̕B&%"0VwĨ,^YZzsxV")3B oߦBᜋ !S.kLX簔$؛L0klZ*BkXJSluH!8ъ"1'M^,zRtB=?1!!ZcP6әBkL[΂ L)Xs̔s_NscqwVq!u]wy3+++[e]?E!'Y]cV(!dC2ab^א8,PBI xԠ1l1kp9R|چӗ/#kV kyKKK@HedM]oqyB8l&h0,Ke~Q=9<Ͷ޽{9n3yB1^uj/~"(%ݕsXo0*8n710Js w%XNST`TU%fugz=g3|>+W>S믯:sX_'F#^p2ZCTjc7 ZQt W|9ǰ,1*K ÅnyO&jQh{ !zRx6ѳ8k7Gvvvp8u] kmkWppL8h衙wyi Ĺv>|l}G9Jc0W +YVa`P8i% !U+EQXN p8|-" B;qw8#30*pޣP hkQhTtXNSưý8QYx s<EX2:VY$Q;k,RrcLf픳ٳYK_Ӏ/XKBeTɘO[LJƠ2'>c-(R(>˂P 9se JsJ,򙺮Br38k`!)u})IJI+R.($*g]qg4iyXfƄGUh0Be!4o:qL:W9cK.ŏp>R.1`ۍi":q_ڂoq1RJ!X20RbKSĝh1ȣi 颤#Hcq.?_eqY1qyRe)}9P8BzK̽ϙ1t=H+0,K qRBJ!*u)ttCG`yy~ \RژltC!((=f) v !D2)Mxq0,!(lBv5@))8wscwRZk,n`wwl20غ3Y\&%!8l]^ZB9(?`>A?ϱW*U8o+UY{,,1k"'|pA9N1A Yh0iCͦ_am( sGRY[X;jh۫Θ_󶵭6d%ϑ1Z# iF5sf޿DžkK)= BA*%:)shXRӁ֊p~[j{{{֭[gNEH$js53F59CI6ME1f1ө߰!E)-(s)-\:G'{:sX1S 8&s{ųSпn|Oy~4-!Ȩ,1i:8R_I0Q!0srp]a?DN@P&Ȳ=&VKkg!v QBV ΑLJf[64>=9`I2&RۓR(h]3qXR5e )vщcTZ$頬k8iڅ/e!䂮 bc 7z[c>DŘcq<B !Ea$})  c>O(rR0 SO$tPh,r:dbaZYkyS))znˢ(bJF3]ƲH) D0}, b!0LS != =#c~.0%R75iDB$M6*5'e@|H]452S!bEQsosJ:ubB!{XPUd5{_ŝw?~j#c9o/rUXʒPJ>E@"%.v u0)>CZ;R!%x%uf؎"̵ƨ,p Z,v23|.ޙ `82wsp=9f͕:;&fX;.S!ZoK)8z8΍1zsB%YƐ0L  ƫ΅ )ߝ_pqc{{={Ȗ!UU8YUqP;!㊱~EY,߿UY{8?,%FdPa-ix=(A2q._mxoook׮,iݖIdVYaL敂QS3"O$cVբBPa0{Ƙ{? ! 3C'D8uI]cZev-/QORt:=N죃xeQG#kƪ-)7rpP+H9bM(2s/TBdYv[$R}Tȓ[H˫% J-v<\kQn`#c,e@2ih$>!1v.!duS!eC)Am-(! YFhbTU t(Y`g !8-49QjZK$(ƸI)}~QQ u:*ˍ= F(z-~9=8'G>ñ,Zq EJUA1!J)Uyn^ywiKy9E~:poZKhF8!qy< aM9wNk3x$ gx3]cq/3B. {93JFGM>S vwYg]Lfaii>v_ZZrB彟9JQUU1sZQ᰷ Ȧd_={A6ZwG#1S .RbB0*T`Xᰮڹ$PJ)4MUz48G%dF_! Ӻ>*.x$k-8ߴ.yLSsf:ܸq Cz%fL"%F*c0*L^`ZEhG`(EC`PN3_tΝs=2R GEvTE3&DFEc<ǎs%y2bC:Ic80*XhFŹ!Q4&ssBH)Ti{7+R;cƂm5!Ja=ϱjvkRX_:%۷o͛7\h[ZMܹ8Lz `\UyX(RBa%B`(Z=)ׯ_h4{罯BS\mUZ?@1 ! ^cȤD( fag6p~-VR8`AG)S)( ]lH^ʊq2 !hu Rl7|eYvPz`2"OU90S ZM&*u$su !QJ[9LHfv;ɲ[)]^\(ac,c1R<hkho<~;M45ޕ+Wt:5Ƙ:I9!)|2 1č]9h1F2Bh7Ϸz'>D?h4ZKX\'%[R! F N&7F3/ T!R6fs>ql\|>t:o<8&G,XT D :4~Fay6cK)8_`0Xk˗/ NmަZk QJ-k_L(mݤݝ8F'IyВG #%@)G"oG]"Hc%)ykm{/Sz;::aRE&ی@Kʓ֣`5JbB+񽃃:!?< W\ Zk7k4MBڅo ;GHfE*bqTVfZ#)c$w9c6sWlǡ(VUJAP`TXoRY%(hQ+~S)T霫Bv4h4:J>0V ~r痖༇~9Y!IJʋ,x>}kD)%!Y,+,k AJ&`޸0$X0k cz/^|EV1JBHMqB Bӌ]njkkOƃ1cH`>d:EQYE/5s' d4dIE|ZU$j|_,K,)8hEBu.vgL(4cLBJ{o|Ϳ7~*nw>%SaC+B J*B bX$@)ЍH^|Y|K_:i ڵkj)˲Dx+Ћc,{M?nJUw\Ơt.L8O7|{t\{_cjƘ*h`MJk{)fu4E!;9ע@ldىZεZOX4o'Ut8T>jǨ,n48Ơ2̛-k31')c !z+c5 HߟNQV!*aU{8{(kэ^x# 8m\v}iAi hpD2hR)O0!<Bbqw6ýĤy8^ݰ5!DyGZ"0`B<*3)KS$B`=q ɨj=Qʕ+A)u4Ng,4 1]^^{_RJc̼쇻jVVVsn=>yENj{ɲXm-8T =BHDiJy9!$Qk׮jNi ~276"~˝$8%dl1IaQ,uZ… vuc>mmmv]u]&IBuιey dL1kdnF ߾v$ a!$pJ1*Kl9V5,CLֵ܏xEQj޴Z-;܇?.m\ =Gq{4B57hJ68Fe6Ip}Opn=sYe],8/ VY@8iGτmu}w}}Iv/~ۢ(fKKK$2DZcBҦ9ۉch0*tkY[!!nP92Ƹ1䜗v[&kUYu}jN)s$xyeSP5|I]kly~R*/2v>UA|]>c̋B%Im-9~|}+k1k0*dBp{Jh8ΝobBmYu24M' oyoy,N/v:1\xo++@kٟ65l~?0Byx;<~t kBUU!ȲL7?ɟ; x}k GGXNSM9/nnbXU8, B`>zCP\Jr1HEQ޽+>|xS[@R.Md6Emn-}6Rzn.cpMDA'­\GQƍ9cZ;L&SJ5!wrn-WêB9:qV6798˾ni}nbCw_ϕ48Z|1favs)[IxcLatR~ww'RWzWM(-~tiqBiAYMƌ|s@kD ŢOfNe9GS9&Y]]%%T[k}S/^YxeT8nc$琍UQGeuY'7x0SJqBHkι[۫9w!xOއ%ׄt`?_~p>ZZi% J1u!wB16WVVR*jg/]C^jM.t:(Q‰e\m-dc &ZcjMWVVjBH}S)v+Wt0$ٞنrUja^HDE8ъ"te܉u( Ŀܺp<{]$Q,cι<ϥsO([BZЏ"s:U(_q!7sV??TYyK)>2L;NY1#[ys4 8I,ʯ|+?x<^BR ӆǸaCjƔq&1cxHGƘw*k׮,l5Л7o꽻w9'WhvRq#u;upX(dozGҥKBj6nʲi})הu-n_d// T?43וRh4/[Ym޾uUHJ}_)ZZ8'ιifM{06)eIQ=&!@; iȬLS:k_Nl{Zk/}btpZ,8+u]g>1uKBn,M@'IpRXJܟNB@{aneR~xBi!qܚcC5(Gˏ($I O9uo1µkxeYTJi*49\)Bs)1vjfϟW\?[Rp}-.@`F)jknC;R',Rօc5R^t:w777N,T4wwwIƘѣGf6?[YA{\vneeCȴ/ cLF_? ?yWu-pWWWιιI 9{!8w뺾2pBDZ OVWWͷieG1f EQ{?t=phee?˼כxf( )]8G7.{CF M . ]# nµ$86҇ZwjueALJ=)Qg?Yկ~w/W_}J)q$|>'<>h$FQ\bSK7|Ӎ=R Xk܂Wǭp!@5]Ǭ@cQ.źׄQrStzd28E] )%dQf9fX6cPJ9!u~o]s5$Ky8~{{F0[n{{wy@"n6~O(("YڍAQ!, ^QYx4=qJ8f㰪HȢhyRcBJ)9R{7E?,9rwW3h~)IpT҈FIIe e F=2BPN&Qr}}=ڵk rm:LH&e E}ljN,ѸF/ 9,Mm`쑵׾gx`3#<4@ܹsGQJZ|0.w1iA2Bk$BEI~,DK 13ٌUUu\QdB({.k܀f^HfQ# ` tO3)A }MN u(]B&" l86mc\[[;U `{{,ҡ{^D!(T)8(Ўc0koEAUkD6^5=,hDJ)!hsNcJٲ>k]??2Hgٌ((2X{'DbB8:Qy'͚!iByܾ}{o)#z:˲=EGZ[yxqi +IN%I/f 1 !@R+tK;q|) R@o|CL>\|B9={E q !LT)(^Ђ A=ͩB4fٽҹ9Ϝ 93ҹ5`cP72a0a-l9_F|.{]__' Ȗ;;;{ocy߉wު&"j٦K=#Of1B8/vA嗉6 %~xlL%$EKu-Vܳ~<ԭc]49_?8$p]= 9gm!TFJkz:L+qPX;ZyR~Q7 B {gc(EehD 0\oV*q]Q <}k8ΏUb-1VQj0D25 DخpQ!F;evQ}oGxx;u:m򜅔9ՑKbpC(h ZY/!f]] zǁJΝ;t0R* gSi~_駜uJm#CZ$.[߻zwM 9ucu!9JZ 2{'S"m RzjBpx;I~QC*T*Vjuv2,6fYc-|!Puceپ@o* %cjO~&rG-"cS =sip@Ծh(@!Bp3Ɩ\m$I"\+++p( իD)%Nms4E1t|_Z@t3V + Ƽ{wZf\DZ|bDd-gN)f e @`lmmR133ܹs#d›ͦv4m8_"<_# qw8Da jIchzfPR[nfx޿uS\|Yq| "IE\ SAr ,F!ߛ:$I$)DŽ4M| sG_eJIdT8Gq! FYlZ8ߦ>Q0 e^Wa?a Ai4dkRJJ1;$ڞaHQI-˲F)u8찏:XYYAETka-w \<31jc~?6Rb;M1sJKl&rqΜ93'b+jg`u'c}4=/4Z/p0(իWupAy,MS,ˆCȋMdL9'8*%H'7U(&ĮSlqwΝuEG[vYZZ"F`yHԾL1Ӄ1XFJqҢ;lT*# !~e-[ 5th.|Α%i m *S3ojXRDF9p- Ç.Sk9#Md#[`/Si YQLMf8 ׅe.5xV;#l&I+ ;SVVVHgeYq˲<ʲ2iKI\ưǸ}<]σ7my?#|uݣ#uV7 !VJt~W8tҔDuBƘDe k7ҀCIH#1_CExRʧ'g)u+!)KH)uXcXq^9S? e(l[fsHl^̭'$sXkA DӅ,<. RB *JY w}ʧA+2Qݩ͡á(榣1⬌"Y|_ "6' *hcp%}߿{{؋/nEi.!j'9T*|3IէD^ߗ,PBJ'^}o{8,D4%RJZ[ZOSӚP9;Zc*Sa%0,,+W us8q'Il7!P 3V t-a ,Ó&!vNjV*… r*0kqXkc3!|1rZE8Uc.hU8^b,Ѕ_Q˦XB{16rJt]$Fn IDAT LAڮ{1Fy6k3ANzal;ws˲ Ϝ=jQj?ja1O~۞+P<1uu[ǎ[Q8nf涔e^9 kw:9PJ֘TEucccÉho;PHRa~_yEq%B0766xmdl% `-YLm$ bU?zGѸu떭UP%cdپeX5Tk\F5,U& (0nvW/+>* myVv֭##|;(J/Y<0Jr1?a3ꎃnL::ff$f|$Iw@&HZfeA3!DIQ`! N@F(X8o`W/GǢ$SA% Ia~o' Wp8:fxf[ZA) wvv zJ("acQ#V .cp02uLs&(9,Ac(Dp]7~vndY~eDijCEJ8!/Kt{ev %yY/|siv8VyUXEA^JDu4u1gy-p,BOM U8UoN)N&.$smR2οZR/s+kR0,V ;i, A6C[}/VREQıt(3 FFQ$!h}UJ&%'dJa}2 0. By{1nZ˸(GIrH ޽[0Bf! AKӋiK1=b8ẰHtf;??pHҀJ7n`EQHBu9UJ+뢟hyk5B@Qd*xܝ2l[ETP0kaRCEțkE1T!(p@O&8Qb9N9 ,gZ7n4 zp_\DkJa-v7 DQ7-w={viv "kp2AFy]̀ @.. Q ,!\k2C)-ekR dA1qzdnnΡzEQt(Z2Q/r3**R".@5<C4Rp]!2){'Əណ=N3.UP9,2ֺ4֠tsԥrU$q 8_f-c( r#'UyءVbuBB|[ISLQQ ,CiUt&6J2Jp7w /bޞPJ<T0PnUTk\@P`eYvʲtGt:/իs4IT( Z$ijJ݀sY9>!L?~'I65DMZRj&ʘݵ^vG})#@Vd&ـbRo|̙3Gm"7RpSY4re`+Qs,j2Fh2:+YhDWҥK,KaUJ(97ƯXs0BiC^Q(ZY뀵ؘL{^o7kGxX]]5뮍]VxHFbXTP@14ҕ0i+KKH !tmmMy^I3.UGkbAqְ֢麨MìnU;^A LRsy|TBJI7Ipsxi('w5In d 9!kiS*]HG3ƘKRuOʧCkeCq )[I$A/I`ߍ5xAcǶ錂 +r4sG+WdBZHS{^U)1* eAXAc<#tk(I乧융f벧zꑧȃRJ*|u(R\7qu)}Uha{ 2qR~YC)"KSl& JcpPs'ts'I)# ;pXYYvSJd2g8!fY2RW0<k# :P_%F<-glnr]"DuJcǐA2m REY6V>vjZHk5 s*g>3G9?)_4ǎ3W\{׮]҈ϸ!ٌc|lnCy @u0N)Quhc0ydexZQe[Vcu/v#y)b`qqݸqj|Qs>i b-HV϶xmk 9^fA]8VNP.8Viǎ+>4Rq @/bI9JE QժO{1F}sW?QFqle6!i <|(p™Z O8U:Xk ~đqx)?9?GQDW[jA$Z8Ηw\N&8Ǽ#Te3c)1XcR$!ljǻjzx$́k_,˲R~|/""'Uhj%,RvJQÇ+++lvvGژCF3œv~?\UoZŋh4$|ԩ*Ä(dJ}UVמ9877~C%cEhT+p]SlaDd"m<,,uBSF׿~g).]?ϕ9 {n=z~YWguu/,,\u{!&Bb,//sJiH) 4]y$&&%R"@U)@q =[ 8GQZ-ypʲ#_Wz# B ,1Rs4IV JQl)e+˲u[p84[gϞW.=ic~OQk|{g>OzO"_MG#x c-|WbU`q:+HN:P<p(((np%3|^XX{w5}__\כ?ٟ~'nR:D$wf)g?a{7HCQԋB1VPJ8m5{Y=؉1c EY+ZZ5ι;tJ/UJ$ID eY;8p7\wT!^?nRUۄ "zپR=#卻wFᥗVX /yD?|ucB(E5\!lpp 9( Y@{(\a/{BY_>h_]|y7vvvF?Ϟ<noo[[[ݕ샬 h|Z^ͷx<B]Qڲ]cȲ B4E'? 7dgYUsTwcTDEKu?dQi/2)Zku]pQM~,˹Ǟ3nnl 3-Cq0Q 4!3~Y"Qa[-ӋKK$^&?gY+u( cl;]oEQ@J !|߇wހR*BC`f-&rܩD)TDuD%σ#;EQ3qDB@ `eeY IDATZItd^sI;O|JA meű} ǎG^ _e|Ν{[[[;Qe'ɲg{ݍ޻h iTqbc- !Y03$* Gbqnw=km9RJ#+++|:=A… M)\Ga7|xWɭ[P%1ɫw߅Қpƽ~꩏|#,M1/Fw>}饗(><GjbW;P%4ݳ+ au:.[kn*ƎAm-9|MRH@㢀6u)NӃ#BtJOApM)5ʲ{R>;;FQ4#8" @$1֢?Rsl`pΞ󗀙HA z Nr|ߧN]|y4K/Ė_čiaJfx!N UJݜ1Nst#+3Ko|9cwvvul6ikϴ͟:q?)Z?+❫WI研sh4PJ4Mq-|5 ӗ.^8Z֯kfeeE'b3)X,ݿw #$IBcZR ~*RJij BWDVEZ+ w8_b|'˚>:>xB4-tWp RSq]Dmc̃( 9nIRVo0Ơj,Cy.G>G?q0ؼ[XՓO3=Rght/,!BXka9wf )%$O@4C-I d2YaxJ)hMx^vb4Ivd`-ꄠC?.˥X9)ehح|1BH@)5!: ƴ7I,/b(q7}tz[=xSƓ_%yr@AƘ`4:mi fw7Laop?+ehT|h]Zw(1PDnm<8B !6RǏW~رb~~MeYJW^}F#Ip8AqMCJ7Ξ[d2җmE[;ӈ緶6OvHRJc[R(`fqc2,Kj5t:3*(8j'ˮ$<8` 4 2cnju+m-Դ8yڽ:(ϛ<*vEdeesJ)ΔyH+^IQe )a3'nq+7Pf*>f!6_MC?n5rc# Ϟ=;cee%NHW~Wz~*N6[MRT|IVF1Q &)!#. X[ JH)s]:Cb>gV-ߪZFݻ[kI`ke~x_~Tcؙӧ_ݭp]w{{^g˜'?yX3cgqqfjQ4>WwEۅR s$t1x^v]6eYnAల6wXR#yY.a%pZTÜ״oq|'ISҀ2u "ɲcSP<(@gg.^9~ QýqȆ]$ `pRHݕa? />,dyyAz[/BtVWW V^Ν;wy%3Hchf7]I~?$#8HJp@E蹈 auzl &Z\5jo>~~._ͿßδM'!:,'\m,!.‚Bs$Ri >~.\j‘7 cW*ⓟ~(0 <˲54M?=0Ơ( {t:~??,H<ܳvR ش )ӖDY=a< //b/=y"a[m1ڬ-w[**כyun$IQQUYYy}ǶmLLˆѕ ΄i W].^jv ۲r8E >^#=UUqLYeY5 4ma{VscIR< @IS;X+gaÐ<58|xJw}@ի/a8u4<8(A̋+++~:STT2e/Hf-ng)N f$elwS0p8qBUSV噦˲LE1WkBMoE&A ZA`C' 4NETףh+Ie-yX,2p؀q]j$Iӄc똻AVs`N0 "KrPdyM2h-a@Ylk]*1qEѫb|iHzJU8ͱёrQ0#Z[ܾ&vy2k֥7Q[_E9v},l4!dȩ2((!Jb"Uз=`9TJ8ALjFQԉ!b/ ?Ǘ~kv:AAn;vlsPZێ.]9}f,;tMZtG$i;x ;o{fI._B^w6778N,iߞeYq;0L&6/ -Z]Yyh6 tt]v]+Bq$JH, v$@V9.?&KՂvD(4 dlHQkpC`rI`Y^v}8aYViه (6h@>}48}QPUUtľă&,+ܟ)gV )θJ@a!X$I1!QO<ض\.W!V˽pBi 0(I()0̋abIy8sk@QWD1ƪ%awoGr@i^lA,vżcĔsKVmP_/V7byfVNCX9 B$dI (0Z΃FF X!84EBio J9 $I(\>_޻g;N7s۶;wL+onbggiZBN?5o![[¦ XE1[tקz|)e8:Id-T4B r܎0=( ك*r ImN}# I9nYy2"Y ǁgt}M7`A^ @WbWzA0L{tt4OB+'OrVKs]w㸃9}n7ÌEOw],Yayy= l48ݍ9B=ˁqPekmxAB8Az U$ A#GIrP(l:uꩧKO~OW ###,0REYBn|% tAǘ=, g0 b\I H @@з]h4 a;aAbv$$ID&\ج:րɡkm^c+6۽gYt6A)!I<\DǾIa2,(FՅ$x( ҬݳD1 AqQ<IJ rV u-'͓vêz>?6>y?/Ǜ ΝnϏ3w޿scbe49K1tQ__%ZTFrn?x]nkqOfgg[r+Ǎ8ìje);GQ]SeEr44-Q*(PU5 Iz4JRV bvFL=@ZR2L;@ jg!ݱ}ZCmߵ{~,., .AQI'I,"8F4` Eaci|a돧1lpg+_:\p, (=0/2R f_.GKRl0UxADc}}D%E,@ys*DAecYRAGg0m`m'ݑ! aڈNBNsm@ܑ4:}9HI#8 `@DA1Zۃn]Z6yt"^WLVfceV&Lɲ WU5nۿ pɘf _x{ǿ; d-?* ٢5?]2qYEdYDQiuAe$I],z^V2$8EQ![@( LD.KKr9 4Ae<|T&'o>/ťVh,K 8FYn C:bQD(‹=$ (Z=$۹\gϞd~~PVb(X%3 3jƉ,J)IHI 4G/6M}C460a2bn /`9as,Jy AE貀Q$2s׮IvJK.ǎ;+\~mrlP8u=[X("q@Ӵlҫd|$(HHh4 eA$TX`t `cc,²,J%B`:.N4U>t5Nm!An߫n& BIUA䓨+8+'7+J= IDATn0HLu#͛7o( t8pl6%۶ i#I8({,|4yX,3azP=pbݴB֛(4>z  9 ,C7Y($ ցA"(4x~EPiY,~C 11ZFA2,U i_Cgj a p|g<ܺ瘭Gwq B@ #thv{+k<vkFGG[;:"]3&%\.۶JY44-#EA` @ډrҀP*8Nm΂ (4\.<,BXDՂeYKEaf;Psuu&) *BPO* }O eY;C!h=_{ =0 ?Go~ރO#mˋWWךI_җWȁ8An[h"|ceqH Cϣ*˰(5G(`Y6<,4MȲf ]׳;Y3 6Raӽ{!O=4؛h5 Q(Çڅ`e? >aIHZ5U;zrOS^EQIeqyY2 ʢHoEPE m\BX,t<^ LEE&GTC(FID1(HmJpHR(l9%fDQL0p}1]gژ>NFF˵8"K}E/%!8뇃9DvP-гllF2\Z$ \"z-]z[ 4ip,Fx I1^)`m&#CܿC%Bv/..n>qƧJ/=v$IeEmY7Pղ޼4Sn$Yu !u=#2um6A۶R Q&0@'L| hv֪ڂit:rĮR<252O3`y=?y=hRZӴtue3gϐ|>7u0 [VIJ_*ꫬ$IRR:6۶h?6E!I(",6DKv~}t "bh@DxQ/ &G+0LIYwQuѷ]$th^߼,qV<_&}kŋ~ P8mɲ A "8ov$9ÁqI=r;v{nؾ|@pmO,v)!+uݴ=3}?;xd^[Z*y=qXZ^F@Ev1eYȲXv0 j8]Ӕ~ťۮ:a>,?V@,˼8r۶|k0EDyp]q /p$E1"(=$})lmmb}m]#My8,P䔩W.限W!xV+K3']y4: cH" F'c#D,cjBNB2 1m3@RsڶN$ȡhvzN/5bQ 0 A'pbJ!$,'fIF|r]CG^S!IJ=@s)rH욐4{/B(>$Iѷ>$IRF. Hf30ヒ۶~jdbb W\Y(=0W_}58NY8;wr9eYup; !c؃Ң8FYbݹdELߏ֕w056 *} BQKy8 aI*!TU#(VG0}^ (ȑ#K˫8 Cyťeq)vT|+߀" ~2u$Q{-Xa9AbRV O"b:\w QMt6$QFH,ô]0,iʅ]5@خYѳAr^G"HxWT&Bl_tQ/(b,//NBsnfffHGb[oŽ7H߇$I( Y@ :-P%ކe#ӡ97ɓt:7#VVVh6? a5<B! ]Q,8.vٍB;P.komض BeYeYEf "ѝ"}fj53'X*%'OޟTEANWA@[xQqv]\@Aax^@`0&XXI̞}xahj[}0hq,=9gCM#3Ʊm6T*LMNX,٧ N +ƒ0=NmX͎b"L躆z3] nFAW ?DO @X DђAT$E;&FFgν.?0{[??j6v\0d `|g'|PT&Fxa~~N/fQ~$vaSP UU(H|.z w,ƃpMƛq)}dY8&޽( Ν(J8qΜ9޻BkEML E@Ykqrez;-c8(XXXȰ'P FXqzzڝfEb̌v5Avڶn|^[J%u(5FNq*n}l9T#IwxzzY[^`y-t:ǰ!BH f0E±(z;vة3|ť\qo6^/cqqadTl,5, ~~f=<8X`lj;\%cTڼc|t =,C 4Er X<OZW |z@ikbG H}N??._rfm,.Ja/~\oosa>2Xʕ+b&ƥ InF|Z{8v(x \z oxMܺuN\S91mQi]M<&7#lnn\.Զ)qlmmetᓅѩ/0Y!4M˲4h,R|>i!E5-t+|T ry =e Dܹs,!Dr]8LyM aBx!Il>#P˦+ a!؁LgQܹߝg_Ij(I`900' $K)aA\Ł#q3q#ogx2`gHagTvx"Mth4%~IJ2xIe[L||E@3PFnsiJJuԀ"#Iы(dIX"H>{N'KK7x+Zmu&qV$m{ifgg˿X~|rMcO߼ySr & Qy4-ZVq!?A{._"z&̡p`wvZ8| N;w`Ν8y$*JZ72b:`ʊi)D#zz)0*2jZYH"YE255ɏT\_k4͛ѿ"I),JE^oJ_pѣ@ď"qmcݲ0(X,XV4N> !- I;qS0!C?  #x1;yۦwbye wpM,..bss3S(M0xd.>}V kkkX\Z(رs7vAo"<IL[aAWe *6s:ٶFWbqq 7;ػw/6j5gʅi6AAQa Rn;w8VY0;;OV nmmɮδYIb4] 5 @MSsPe]{IĒ9˶ ~< t BXN%c:z3{fa.p40N0<4ku> /^055{ǏEr (QD/`l2 $QBlq!7a945=+ xAZ c!(fϞO|Zؼ?7mO?}j f,'Bu]7?%(A$V{cB`TTNȒŋ1oFՂ,y>~+rƪ}fj8r(=v+W"4h@ >i^cE"Cy@Fm9=^x(w!5tdjv8wqhHt>za6VHzVyMIiA/ @L'HiA]S0Rb8{D-+ko߸q'sG']/RTq]i5'V(ꖦn7Ylmll+DQoii_}dd!:BF> u]iKTGj{:qgt?iFiDqa= *qv/_/EP$ItR"1(I URʔm\R|B3㱱q 敹Vņat:w||ܽp.',TUfaaP8nL<PEa3##gt`dÈ,qN`4 Rr,4S#Cl bF"!`i9}|no~3\OAax@o'\XX@*Ξ9@d}X]\Ja2Aϴ1dID2 ¤jG R0 d!a F`$Vtޝ_~p޽{(&&$yh6͛,˨T+{]:?m$v;,lRa@S6g':q͹k׮_[XZZ8.S%E1E#' sssDEqqbyS"j> D^;bP96='Ot6P>Xz0+]U :]#E0 x 0r2LVVV>p㋙'<6'-a4M⋘1g1޿;v!cH"q 838Aˡ: Hz[\}˲ZvKx|Z@^C^D8$8IJ6EIfTl;nih5hj,I&''6~݇G?J>L>hʕ+Pdgpit: v2JrV#RZ":ifG+Yk W^{LЋ!(at'0 ,|ǚ(fc:2ˢ( xBJKKK;p x@R0QoR#I!9z 9;eGzF6aӭ&G6>j+?~۶q~8qsPE 0 E98e~( XGid g~S;vw_~{\xg(bEܑVCy!z>yQgj8jaFKKoJuFJ%9s/pu aYzj$ :A~ @9~$6{=xg"@]dgYvf Q(k^|r۷o+hQP~2<)߇a_O4vQTPEVs !pMxH|qqC0,$aavA:~ 3nݹs孷߾Չu7==m<3ӕJ\|llldZ~ÞÖ3|ZRwj (UСC8u$0[q]0ko~x~XYHBrɉ̡yÎÔD?zQ N~I<}i19W❟,Y^] ˪;|(JCQ^>w9_GZ2aH0I9te59H("yE>Ja% Q>9;w2^E8I 2-Nplj1};vŎ;q=kXZZ{||IQ QA2 0 bfnH gg'&Ѿ?C8I=]/AIy㝮azժj֟&&&?ܽi$y&|qGFGU]}=3C4C1)KZxl_2`x eCwvSJ!9s1}]]gDD}QљMiY~>/,,܏4(;-ގ jqe@&F)R(e#Wy 8\..܏듯 +=OZ q7}t:]#Tnwh4Ed~q\&}p>ϠatqUJ=QSԖ(5[tr7=%|S(yO(AӓA M D4MۆJypl?JzrK#1aFτnPS*DQBW7 )!$_fG~afLOqt/K nq6e_^*YO1A+ǜ" TE(OL`y}Fi5R?;\Gۅ$x<^wF縯?h2ᓌ4A8%'|?7-܅ 򳳳XQJ*TUEш6dЋ׫B"t_VɗdPV#3 E5ߏkp;wfPV#K#ゴ]GH.cVIaasJbܽ޶R @8GFU*R R\tc:733#3]j^]qAXe53cV:EAՂ(PU~^?|PF`P6I~(:ɰ!]sMQUe!@EpB{>J,ttjK S'Oj#dzlahh" L6+Fk̈́1!a) 0N"bGzl3)j/]BVۥEB2n ^YAϳzn޺VO=,&&& ITU 2{49AX_C*ƾS)űb*VyyQ WUu,JeǑk y f  O y.(6n4aȚ>lj ؠET lV+ҨNJ|$w~X^^555'^ (O8X~('JԴ]P 84taupBZmܾufS$Lz=R)=l6X]]dYT*vB!2-ϐiAWPVn#RTvbqC8x6޽ I?8~4}J A+~lll^arrǎ&ʲ, αPd"C9,/2M|Gp e!phs%km4QtlF%+d&$2BQd\z \[òat]u]~r NG}_WH!iJ$I(ER)jl`Y/8-ˊ/~2$@YlttPSXpaKnVz6IjhaX'_k| \G\A& A7Lhi (Ad8nhn#гl}4R)NON{7|FVMg_OF*ٳg#:cq]#|Vv܊X]]E^ʀs\>>P7v/1>{Qھ{BuuuqYRlZ*D(bf wyN<4$IS~Ex`$A}E|ϊ( G}뺩qQΥKB!Nctt\.F`F& ÈOQY =ˁyR)Gs܊"fggw1 kL8v^-cjq-<8qF~@hvy> 3tGAVU y|7oa۶y?{?~LC~ꩧ'O}WQ,iZ`ZZVd ]"It]EyxiȊ>(8WM8떼x{ym&{ q>r-NjpܝW|>ŕ лpT6 Y&8K)_eL2,qaT4y˲f@:\.hZ!8gmfHREՊF'eY|8Ue骪"D48-g8MMc"ߤvgDO2O?sQL8!g%|Y , |%_YYAZÁ@8>WxVN,N) +>z~[nбĬǏursڍ;rth##¥w.0ut݈aڌvXj" Lf)7ՕUlnnE>=H3&#%ivsm0=׵Z[[[r$<ϡgH) |/p ]/އaڷtmcHBmfr]r&mO+rײ,yuu`}y .48۠hV+*&a`ҨuiLv("(6ylBL8ەKr?U=| _x3=y|GHR)d&ʲm4w* \XJm d3rɡ}k>z?unnnnfff*ʁFL0J7677>d2X__0a_ӴHB" S[[[$ SSSeò՛l-Z~8tKɢ~闹TJ`$QDQ.Ѩws(JDC#};c 3 aqaJayG@@öQ,t=@4S,Ey~ە3կ~ַ5(x<)SԮ@M|#A?@q$WdwEAP:8Oj~ h 2h&K{=ݿXi e8xwv<l :n8硪 cks 7o Ͽ /PRU_y}JUZܹ{o޼yǟx≣=7ԓO^~{ǭoc{,X("`E@PTP#`zt֏zCd IDATYh{rYOJnN&IA՞7}8b3MTeȒY <\GG˲ˤAwMK\r@T6?sbEi 0E vMGajYޘÇP,kX]]5Hqr~DYI'7{ȥAײ?EQpId3Yܼyay׃l BN܅GW3M\z ?HɁ n"!xO={WeI>w`zZt\{ceiZԇQ*9~8<2L3\5a]Vb=K/}hQ ]2-IP0CCݷ} rNC!Iªc0 \Ǵv}yN9C$b&JV)HHCXI8!$f`}sG1i/я{郴s{]Ap]7=z=ݮEPoP,2 Ӷ[upRM.N*e2]硽P2ƃ ;e8NڶmU$ᥗ^.] xHiQULPZO>B5TUt:(G$M$mx> jIeEMӐ2HiAvH=.0bc~Q`-ZYvFL[[[_tR 4 @zӴ`Z,ƻgL&CFGGCrOLLbYp;B~Ðl-˂nh$9yq/?'&ܬ6^ y^!p\A 3*AF8:$s$iac}a׀YԮ\i)\mۆG^fmZu\.g!yG?$ZCUՕg>yh}fff0??%ض]i?uZvYU d?4!K%E#OG3eIBET?kx^D È֏YWl] ;Q( ":t]~+aEUP`umRuJ")ѣGӇZ8eLᨯ ',F^ R$^homm~x{^<x+h)PmiC \Yy>zu68Q±'SXUUtS*A_RqMg+NAz#asVv# VaZkARuliF! hଙt{ w=Nu_xP|M9Y]|~||u#+I=Cャfr<`G ڶ E!3Ѵ,HU{~*Eu=,߿5ܟ[pYdƑ=tS f~3ЙYd]HP2TwRTRVaxxANݮ.~;ߙO~ٶMDI ҋ.… d|| CY=k$ah JEA$P(Cv33iv qxmO5iF{o/{!8~8[BSrL;29640󛙻2Le;B).>sLWo4uu]jZ a. ȑ#sBZ}Q4G$5_LDu(bzz>v;7iv&\"a\xPN\K)ᄨmK{/~$~Gr9~xԹQQ>t&ѨWp S|FCϴy Emk[kX^\G& AqlpI&6j~ч܀AӧN#v[<*r˲Vw:g5MSn߾S8tTTW, 蘜dzCf-p]7j j4[h;p=N*_m|gNېxq?&y0szsc]M~ LӌX*J!p ڲkYy5pRUsۆ"!@ٌq-NGCX{ 0 [[C.Y֫7%g[A|/z;@T06`&8P"CyLGOkp]](8 V@Aa,,]B&amƂӮbxbB8^>yB`Zg;>>,/]k^tzzY\\loSJT~J4A4<LӔ-ⷶҁ~IK&6(n7 uP.`h4f^c xyr ?⛺_=UQnqddڐP6l˲`&r\|^ s1Ʉ};iZ֯ UUQ(P*~-vEA&v8RL*n EQ*Jɤ xAh7oRfgE$iyzˀ" =(E9虡/i۠X;vWG p :z!=4ꠞ QkVe9v%80q&@"z- q(ZB纇uH:@T*U5LLL#͋gHg~=IlvO?xy؛B]׍!QL2eY6a}Yjcaxx66֌Vi,nMeRD՟@FPQYuO YI%%~O?{"9 "ڭ0/YdT#|4"0z& 0>PHPkuPgphjac ): !g /a9뙦PJKm[Zw9@ U; &ϓv0B>ϣlF Aqi,|5BVvZ)wIb J̕dAف~$8AL3HeY:tZZ<677ŠX C]fc DoEQ0gnV,/+ZZ;*jt ! :DAHa;3"zއ͍ Tը1^IA ޏVq%/ "IϽ,AEJ"[} N!(h)2LX4HZ]Gppr=ӂnZ0 avG& }4::Q@FK#B( Y *)(<@8nn4 # ҏcE: gΜi9r|ubb311|ʮ: f Ĉ@% 2ubllF3ȌxIJ2 {D?d&qsC,]akʢl^"#DZfUP$+ZZ64-t:I4>[xX?=d 4 dID}}++K 2 ð 8N8laB9as.塩2h>}X@8\~X'H"lBM@8kҒm@(՗Qq$ AA OMXweA".@SyJDȅ]$ 6 HM1z; a 8HX""ct| v؎X 89y_olCCCE6E,03NRkJP/~sX][|8q  |Ͻ6`ocڿRyܽ{kkas(raUu5 !zΘ/JC044a8;l.EQoǦSOKB>?9;7ޙCظk>,sz4 #r$IB'? ELNNh.Kٟ<41N<ʒWUSSGĽۨU7vX MtJ"8vAH鬩2 `Mi@DԛMRBU (AH½{M4{<ϛ(zV=zTՆ$I3õ󄥙X1 4}lll`!ǽ{ Ǣ궸NfE{w$5^>dRsPoݎ): eCꫯӟͤcbYJ宙/ɂJ@cY ˲Q,pillc^`0^ I ˜ ~~ $,ܫ瞃x۸y&,6 L^ r>xҥKyalX4( 8V.kQJ=|evmmm{#1.W-BXf0unqB@ T;A11>>zmx1qAk(;7( qؿ?{SHT֖8 jȧSEGAÞY. @NSsBN14:Bnς"KP%c94M,̜7?1%S_:3OԩSX^^[X[[G&AшRhDEJT@ҏ܀$+O}So~ph3GQ(Sª$ ##8|P r?RJ}QdYn{RUr,A^~k_^o`<ljCV%MZbtt4BsvC,Z5xBfs護~ sK_ ۷#!L&闤^A>*^gkhoWt 렘MCH $ 4TIDrv|Ϝ=q:qgخY!SSS#BZ>#GOPB( HT$ըñ,:]YYj6I迍pN)a>~JD,Ƣf3BaNlz=w?!N<|+fsw|AG`~BQ'4Ο_*\7nDŠXs'jrY>|m׷7]t: :Ng+lVU9ro67~7t}O_|b]zK=Xd#Il\nh3t-1zAX Ho_qI\xc~~N'Zx@y? )kApAkӧNŏ0;31\((d\sv*JԴ,.on |ȒHLa~vvX\\ZVuuq 3<\$Ƅo͔V*,Iʕ||'4 l6BEf"F}n˿KKv;O(6 +A xOdg?O= 7o4MsYf٨f9|gϞ \ҥ[ȈgY(%Bȑ#v oҿ7_x<ɟ 73i[Vg`GmM}]# akk+eG?B4^}࣏>vyI qGє)*EQŋmcT2=qCcLɤ$ JxP.W❟T T*Z 2$φrJ_}7n.4 #8m۰mif1&@ fgƍPV ADiE&DeYF׋6>eMI%ڊX|  ]ypL195\._j߸@y<`_SN Ϫo__{[[[ /2gΜp)}rA4BuA 4A=i,,˂fѣG#UR~7zۇ'=zڠmFK7KضR|38~8q]sk(BTUլT*>^,뾟{}33{o|,MFݬ yA&j@;lg0 ,(JloO]1?cTUET>x 잞sx[/>ہl(vTKYadh2GVW݀uw=x#{ 7i3 3 5 !(js/>}/һjǿ:9Y_[+;e^~eض=@K<F>c iIO=atzrAl&Ο?Z'`U\pa a? n;y4MC>޽{q1dY ױ/`Ϟ=yLfٓ/P Q (kSUUX53 \pܡy>|xxx0H.i1M X]]kYd3سwGS%\zpz\T0 d &vុƑ 7|]{MԷj]/]CV , <$M(aVW!p,dI*9:P3ڭ[=[nt]a}ttԷmV*\.|>O6,$Ae6MSt8]!$ S,8ӃSc~~K#qw&m.\o'}\.BF]e^7AnF;V%JÇ"Bu<L%?4߹_0L>/~,FFF;\pFsV3 v eQ,RFud2A=*>|O7^^]]Zʲ<6 S,va:zhvLL*6{ƍAБ$ lvPSngw`gBbhhA U$N$U*fz /^D8Q)q<&'1~AM EQ 2J"J*==/| < K%?zn\Ksn/ܛo¶,T򙞈" "n/mwm(BFEmA^/z_{j?qgL<^zu?{-BH3ckff&v[Jŭ䭷"|_MjʯJ 8 Xqqu0`޽ӧ?4`wa=s( v=yfaaaAv*Pudi X[[Л=gZK̀tǎC>þ}dz fť%ϣH>/P#Qj&'&pȒ UUp\z2: "j5>Hi-OXYYұhkY -#JĈx1e-HI$"+GO _,O=0P$|gμ˲0MLQAUU`0@\A $8xxxFu`ADzdQ, $2Q:26)~:KyQ?pܹ6݆iRd2k$sssz] 7JߓL|̭[ܫloo_4t(g熥)2 O/&XJS\zr:cǎAe:|B^G@mbeeeK m]7dP(091(155Pױť۸y&Zm{Zր@N{9'*=!$$%N'`4@Ů$Ii5Y'N$t,(CyaeeqBɱ1edmY]]q0Ms B=iYqvڅ){f дP( <}i*(CtUE0tkk'C!/24{QI޺\.e2xCD9eu(>;-؝67T0zi"M%N 9dC:Q84Mh6lPo4 UQg2<8|SӸv*fggqu^pU hEUv`}}lmHadQBa&! ɀ\.N3Laqq=<]9{lJq$0 cj$IM~MpԩԩSQ7}Amt;~鵠}|"hE133C!?9/ MӾSlI'=&Ut(x5[M,aߠ*ڦa/*Cp:K\1aY]lo +0[-t:&(@ج7l{/J p,*Ԛ& Y^"J n`}BF vZcP%>+w4}PV6Lq=˲f( ؿo&$D1nx{׿3woeY_իWiڀd64UDZ<,C{氚(BTp 0+AS8 $X)j0Ǣ1!h ʅ\ NShJijݱ <9bRחe\o 8b<^U*Ym?X֋/$IqoV]˲ZCCCɓ'SN}(`X?Vp̙g~,qrlVLټ#gA;mt  PPJ_$i0v `PzMFԮlv i$ l7fzi,hfA77, O_kn d2(h4``0`P2MSqcLܨjǵ=`\ IDAT[MwgϦ 4B>oj <2vލ#G±cpr=wǹLƓ$^zn-.ݬj۷o/,K*ёQYSU&MSf{{}0߇w)~tM빈&À*8,A$*!KޗيIaXsg0oh Iٞn?ǰ` BRH@xgc!IrX9Cyd]1?|'f~TXmTU=44qG~?ۀǏ:kI2=:::6;;#ft:;7&/m ҍaYAvN {1Y\0˲-}hL( uO{ze}4ML 0a Nuzӌv|>NfB Tc#LRTU 77˖e5!CZ@E X(L e `p]x'l(W.L6]3g{߻|ܹm8 8v]mv:l&6t ˲:k-L{`@QH2G["CwI ]$tϣmv`6PI#"Qe~TH"AHP)DG/7X]nRU$s}74 cϞؿX}[q: G4]%$S;jIeI@٩s`  0(*0cnV7vq77a /p?iJ^ vvfsOza_v^m/}w^|+λkNx<ϧivm^eAu092 G Xg\²&+aӵ>xϲ'5ԑ=GPe,dY(chwmHbO{/bcav帽' UD8$S eLqd[n} @?xjn3EQq\nTa覧'1(3ۺCC<ǿ|3PbdIMKSLzt;)H Q 0 MbIA?(@E5vP=6}$`/B ʲv:VU47 \tGտW UU%IٌU,i}+o_y+KKK˭VtZLUU0 aRdPt:uuo+,ʙl6Ƙj ,ˀaXQB>2,1 dQD$T- } nY.$DZ`@$)$SB; DCFP~9]x\2$ Ր/ULiFi7|oylj>9dY϶mP(ܸZ4|tA:@B/ߏ7~|'R²L2<X~, 2 w~q$fq4%(]ʞݻEUA?lAN*@D.8*@(G9p TYDVYqLϜ#N,],"+XPe @E9oA(%H@5rL޶mge{{{a7 ø_|cXXXH<!3==/*4L^PwwyCPimYR͝H>)p'Gu d0Lz4+ {zZriKKK< iZH7ՓO>Բ4MXEaeYy+tAr9_E?χ=\\?&#.]"=aY6Z[[7777F$\EkWau;` #p' X0x<"kg^Mhd4?.39 )C""E~ӴgH=+r,(!RBF9 :.tE(phuhMQ4IBz,gXp<&gˣcm;zEQu #vo>9R(00 Cwn0 ˶m|>I=r??C>MLNvVOʫrl%A0 ðafZ1;3Wٿ0;;ÕKezN~|D7=pUUa\ׅahР<ƥwJɞ,(ؽ{ff"M~Ĥ#4KN D4n-qA@Tq)ce*$,`nn:tkkki>O0L945>>E/F,G< e׸t* d2v[ x##cMnU7խUAH$Q@X &:9EV<˂0Jb߄O**:0Y z^G pCWa(f"A%DqBFyǁ!b>ɩ)4:ɤ)ojUa\.}_Hݛc} n"l4Rq|hhHÐ1 ǧ?irxveYϝ[jNc%I8#)IR;㖦imQM˲ZVǹ^?:: 3RMö$К>ߡh*dY I7ڽN9I,IT*8t >~7>C8~X֠ĠemC3J4RUu-T=m0(Fin5ubiҵ~q~$I(J_.ӧO GVY^^&_k׮BgYСC;sE]E8z?ŏ۵dQ*XR.Q˖tE,WwDw==B1F1>=Ij ]Qh   E`c}ˮ"CE= H:Fi&DPV?2cWonnut]Hh&LMN{NmYo_ko^_XX2w&ݷ_P(<^1v|lT*3*CA }2 A`16:Ȳ9˵Z(85.G ~u%/~Q.J CvS:?׵) P$O)0I{}{!z}gY8R> }S+b|jCyt33G!dJdxroﮣfEZ[^|`8Xn8t/{C>G6rUȗ%?̺At:z0[T>2ৢ <77| _h1 bQm7oUǶoVkEQd<_g8Ο$I $"N"O7ɓ'SEQnTUײŖi;v$>DAȴC:CxdA`R&4dPT* (,+T q\~{nM]׵ ,UG>G{iwi4txɁN}:˲l쳩,I~۷B0iTE?eMSnnbtm=(~翘ĶZ݌U8v67n궮]֩նmۮv$yNV ("MG52<}?gܾ}{hI&Ν8ٍ{ݻ7hsW:,+˄^t8KBBl#McBgG&I8"tEFr $A̱E"1RCb-W]؊;ṉlh;vq4ju3ft,yÇ0 }<ˀx~)2\?ĮJ.k{P$" B Bl-CfX(lmmRoN?5c(Eyhh$8S0 j{u$I 8t~~>@UySOKKKQX0LEQ\_[K%I a0͞%Uzbu@JNt0~jƺZVh4^ղ\׍ hj5TeY80vW._r?k?ᅬ s T;$U*e>' rh;!Ր lpXoٛolq컮i@eY0˲m&˲1q G:,9pt,bzȱmz.ˠ ȢkVr:q`bVm ?s\.Ugn^^C SN1NnA_TrIPT*eYG" mzIbQe+͒ʬ:0mTvNf28l6;1aE111޾|7ά,Q%i4$IBUU0 <χNB|7I,AD+++[Od/ sʕABΨ;PEf(JxOmw_|ᅛz(I4\XYB+qEQ:Lg) GIg VQk𼞐LVS HR|o}o/oƛkkk84M lMӘ>1)aHX88\EQL3 gyQ94ss\gufXCi ?zO4Q !h9:gsJl ssst0~~=U*ƙLF zw]2??W$%'O ! ֭EH"#T뎎6 `P P+lZ"LLL"%oڼ|Rr}Y8nɲLAHY 骪i X###Ttѣ;Ȳ³"Vیj~eA^cQjIOWv, .g`}q! lUUr֭Rb1\[[ <kq^XAe?W^ϼLjS:9ST*X," C /=}?{峷n-bS%:X uI### VXtDQt\VVVcǎ[[['kׯow(w#<;t!)AUU޽<yGZv;7o\]˲LƜp=kٳos=2ݿw(+V#1w' ohbIkc`RȂ5 =ndpIDATx{v̙hZ4M]׭4MWUU5Y5u]xX04cS.L&ht:`yyٖe>ySc{&kYid#S$}bbA+ σ-L}tO@h/.^|/_m6-B3 }R݀v@K.%NJyܼyӯ BZݔdE l<4M4MJ%ܾ}$ nC5L&8NLӴ-IR3yV(J:!Ē$%q>'_h# zJ {Q= X*ħ~A0`{{Rw?fffȰ$+W< ( SqڄfPLZHE2H I -gt9$q/dMX!|2cEl]]z[[[[QESSSs ?kjL'SX֛o+ $afA ͢l0t(f?|0Z*ZDQ,kBjVˑeoeY+ C1o~m[dLjuL&Hۍ6lO=Ia.r g=Q#3:FȍNfZw}EQgdd$СC@tMwcssSnu}O3=< ]΃dCO^ݭnzܥKfE0i]Rdڶm+++K>1y.n&N]?C>{֚fS$u]eY0@<>"L&N<ܼysn!'pή]ܸq|;Y xIt8K6˲vk'|r(گn+ITzuڦ ga : `|0!p,\?J {@TJnܼuVkXNeR%4-$)u֏PÕt==>6oO{8C ^r-W\y;׮]DZ*j0qȈmr9P(/B1~?t}RqeَJ㥧?B fs `H}NSKoضme2&I7}њHrqk~x˯²lUEu:!qܶ,rla.]@#˲ QСCj% KyAFS!W,Y "0DÍѴR, .vMR]#aLY#q“'O~ ׾;nmm}qx{9t ;ܾO?}̫{ò,ð%F&iA5;=܀O2xP.ɩela@_EQPP(T|reƍQ높$ْ$ 6vN>:u{WWWkU8A~,y1 #8}4EF-$cyMɑ{ (37 F^W0>T_\I_5$z)>sڵkm?(K Dg]OGnڵqf_}ܧ7ozZ.--!i aMoǟ!g]VJH-%m&9ݠ|w(sN uT@ZI4p#Gb[kIW}rI9/=X @Ư<-1gg81/Ll@F%NۓL~o*%$=N8=σ<ϡ8B[o.]rNgO)L̳,K+JA۟{7qs8ic4GiFRʔR4M~%KDUBJK)iʶ\.sqxLrEQ|1 6cŠ<7wv7Z!8<x&cBAn1J%i< &缔eT*)8勋/._..^裏ι*"朏|)ONhnWAh_ϐ?dP_~/{s*řa&^/^X>cUJWWd2A75~7IC Lɦ|uj8uQrff&RjH)eEQ4Mnk( !ȶmRN&eYز1@jv,N( C>S rx.M"ޣRJc<^19(Lmt(UWqGO }6k2xJ͗l pŔRb& 1,˲$Ip84 clPJǮbaLB7_ !\2aJ)$I!d !,5Nl믿aZڔ4M{e,)2s{8.R !8xpe Dc/ =}AQ͂/XȒ>1Npgz`,^a$x @sK@p~tXX2/ T-#H pÄ02HSeq͠녚8S h lTm۶ݹ'sF+++waqιOb c8̲Y\Y~t6_|pO4 N|ӦM/i.--m̜o}1)k~"`гɓ5W[ݯmK>gg/j^w_77fϹ/11 JC{񾰮nqstA4l^VNsEs ( "$dI@F0z7 PVV{vtv&PϜAUUVoݒ%-A_ ׭o252;}R2TWPդ(Qc5eIǏG;:;)S(\.C \喭8j peA^ogA` $\|!2T|.ˏ8Ҳ߷nnط$OMM՝&lOc͖Rf~WS[kɡM|hE UᡡGbL5M|A(X=kj5Dn1x3P[[Ŭ*_&ohiVPoX| : MFTU%""zŒt_ӟdKz]YV ^,{-\-^ؗL˫4M4M_uNKKkEAnrfy r \poIRbv9W#u:tlASS488(YVG$Iv@QѦڛrr?ŏ>:vvý-"Qa$ϗxGds*/oUok(*zxOl; %9 im^WF'k֯;$ϊy W˷4_moheOVXSOEQ+N o2Șo^~&Akŵ󟃂1͝;w.EeU yׁ":O]!kjS./u'^xIENDB`RaySession-0.8.3/resources/48x48/000077500000000000000000000000001356671433200164445ustar00rootroot00000000000000RaySession-0.8.3/resources/48x48/.directory.part000066400000000000000000000000001356671433200214040ustar00rootroot00000000000000RaySession-0.8.3/resources/48x48/raysession.png000066400000000000000000000076761356671433200213710ustar00rootroot00000000000000PNG  IHDR00WsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<;IDAThyTTWdJ8FVQ v4O3t^+c:혗%=ļtZc̠J1$N @ ?"mZwU{>ܫI!jϷc2 (vB]lyhpL!E |6<@.P:L+X`Q!Z46h!`Ő RU-E}0al& 谀#@ 33D0 xq% I08`)Wt@t?0;%p޻'U6 z{@$$F fojUUak+LNp \caOḯ!9 #|S {I-H IaB4*8׼a]t8*X p.WA4Begxxo;`/Q`ԡL0ЀZ(}m%*Ȼ :l0 a~5|+A ½x;;z Uo[CX \}jn0N8 8uthgj`hf~3sk,[>;ޚ'<ݺӏ8q؈%4h9TKDn'xL/1A>k =dߟ8'~W{2Laz"A 0zQ4"3d֭q1ի|as΅444_w10Y`ԡJ6`RPHzxTgYCK;q8hB{ 8h#i n+)8|F`ak~|+m"mH{.gdAydtǏo9@y?<0pX)ۆ"{IA3y=X7?jnv5Cu&ell=/TJkf2xzS7ncՐcMJ:l0 Wk[1 -:$5gHn k\wԱ7}uׄ@kGiM2-5UGAN޻w8}'離khxk4y-¯: RÆ= lG4jeHhB,Fz}6nݎNh4"pPg&I?}Ư=_ɔGEQ'߹;ʕV+s۷@JdBinN:EٳrOho/[&4\06m1crZK4lvSbõ33Wf_ !PUUU[ѣ;'NёXݻoII,W_WΝ{l6kx2Lڊ>xׇYVVfڽ{w"2(QլhC{hzjヵiBYµ_Dik;Mf ;,[otY7s_zT.=rdJC⊪c-zmnrؒcll9Mm=NF{s,FI'1Qp) z5(4ܵ{Twc4%E;uD`О).z7&~M$c_o^z'y)gpt-f&}<ݶO]8!Ft ]ڛL{8w*J[FU2bDF!%%%m={-i=;|+w”o.& mر={fytB`3hʄ^0|0R~Gtbyu qprfDDdʸq/nOII)--Sw;}a4M=q{E帊KCzcAFF;?1;x>993RDR8sַֿ[ZkO/;zEw ?Im~dQ(&^Dȓpzz-/vwbj06V?zt~ys3$z&''Lvzk 5]nu:{gkxv6Gф9l]_hazBS44RxEǁͭS/ܞO+o8 9-TA'{SO5>ɓ90JUŢٌs%"=RB)(\^_'"48xa*3Hä $VJ67w}I?Isж8ǚe1fFH 9.c>/l= `jjJawEщ:ƨ# tDQtBm?nwOsB<FcGT4&C3O0X1(13L31,:'+^zzN(y:!F9 Ea)@XJTѴ؅F5h&SPp q63E iכM&v0ժQ&Bcj$e8mRBWU#(GxW;Eqr~87 EA1ca+f{?v&L#U|E$aJ!dH)At״Sz4C6l`L{Q$=JnpyJB'5!k۩}8L˦iنZBa]ag$Ƭyi|oVa4vÝ+WBgq\E2Nܺ]{ŋۏ{\uU/~:<}s[/r֭]Otc eű:jHQYX>ͥ)A _$@JC=s²Z{bkcK*b Nϩwk}f:9 oh?}ݸqs󼣺N:+gff4uz94?(FU*uR lRlQo'IhǼ?Pom8at]302hǎl_Pԭ2"\CAlR77701jGZ?~zFUU ry'??ng(), 4 N$t}#\*cL4yO^J&7- p]G~=W.No˲*ӯ ߦ2YHDA3糈3'W^x7;;+^oTlBW>hff枂jjj*NiZi ðɁryVZRכkZR"L& GahZ 8m]_B}nPVM'uilfm# v;c,^Nڛ/ < A1 bL`qb.DK_$48t[o{&l^v`ZI\b뺣nWa!ΎTY__jNcw:CR0M uӭha/:1- Hcԙg"XHIߪ2KK+[(LJxly4 #"D0$CeIT,9SvB$KqK^BGB0Jk_9+iat!dh(BI !F4MQ(0&@0HEf٨HH>PJ\( W Bh^{<`jj#ZFEzaDB瑽cH!9)P*vו! j&"quqZ؅ RjσHUH-N(FXٌi7ܽ:ܾhfŢ(J8Au(JMӀREs1 {l}vv@gmǿt iu}<0!aXJJΣnJHY %&LbI $v{> &43vEEU7xW_E\.hd{{AE(`&5ۋ˝D4>4T9zc4eLV~!Jejg^E;(cB!a) Y]W8<}[N/xskG}χ1BNqr$"}B@ۏ9!0ƀ1vrIMRIq۲R&ۛ+kv:ZƔimq~WWrغdsn쵻h`,L{6~p EQx}ii m±a lGQ4A~{NB(SrPTdPs JA4MQa*aDUAD˗ZNsϬ[ǁsIBMǁBRQ(8c?!1H)s~D0 hA'e9y^Bi`($ B!}H`qOEx\8 Aq!eL&wBwghckSk1FJnobU@u!ƭV  UUR )%H)R\1ʠE1M54<>qd:ݮCiJv]VU@>p*r :E6uY-X"+PKVHfH)C?NyUQ=j!,B*t:]H@rW1PJM{BJA*( ]?}Rz$O<ܳfӪsC⾼ڗ>;|CƶW0, ld,3~Byb c&L>}:;~onnjab1j1v~0FZuyXUU$qUM ۶$I<眔eyql6<`_=g? c{-qu@#9ҏf9r6=B)333_2VXZ]|>`i砪A~x O}E4M`Aǀ1F3(91w+~cfv7 ]T)e#ׂbƁPxȕqpb+vX]]oݺz7Kv{/4 끪httF #hZ90 p)%/˂1i!#i*@ȶ,|{~4M1@IHpq /[|hb~~>ygiB4M,=Z]]$IO(066FCpQ.ٳgW'Nԧ;utcLFB'G[= P**{2cX\, 9Vhفcgo7?/}KXA$I`mm MLLAs8W!<``{@!.=766ùX,s&f1(MSD)F{YܼGӌJB>ǪPSJ1/\`SR4򁁒0{}v;Qun~~Lu캮R"UUe$|OOam`,/0 a@Iu3}ׅl6<˲s07] \~ 4Mq|h ۂ#QO>*P6_}w/LLsY'w(ds~ƍ{׾f}yuu_ܼy3O)8A$Gqu B4m0ή< !eYݹnnn.^t)v&QL&Brttaxxc$866Hasc $9(T0M0)%3 C>6;/kqmc~``j5eY}I1tdBJaO(?۪7UV7x@̌<|J)0 r$p7z MըJ)QH}GRJ,A!!-K$I!8úy?^\\ҥKuz. ou:dy mGv6L2U(n${T%Ϙ>(+ V"={vwff4n/V92._kwOv;eUՊaf*!P(4MaiXl;2 "- CE2MU*9_pyo'\n~onll׮~O/hDDU4MQ*U7Bθ#4 OBE;(FhyqoÎjo8矽~NL-4ܥ0TM C988 k ;5zX8E)xsGFFBZc'IS Bz S,)'ISP ±ayoq.v^=fWHr48[ 36v3UέC%nQLOOoJ%o~3ozzz ^޾siYxBcwgIH)wlEKid܊ߏZu0#%z2#db*24n'n'{a 3g,;*~׸kw^pߩC qDCd2Jsݭ*iv^ ?Z^^/_b7l2ƚRnwh@?i_M> n8:"x RPtRۼC]z8<~|`Hu,sLN(7֏7]m?,c{ @ݖQyIiȶBccc7x#jիs\tVWWIRǞG Vebڋxm{wo6MիWi033﫠;^i333><䭖&sȉn;޵~><<|ټk OMMmeYyƦ(dQ-aER9^o4iۍs̤ Oj\$ciږe{o)I۳:@ΝJ,P)U7ۄG|?IUIENDB`RaySession-0.8.3/resources/96x96/000077500000000000000000000000001356671433200164525ustar00rootroot00000000000000RaySession-0.8.3/resources/96x96/raysession.png000066400000000000000000000373241356671433200213700ustar00rootroot00000000000000PNG  IHDR``w8gAMA a cHRMz&u0`:pQ<bKGD pHYs B(xtIME  V=IDATxw]G/NΝԭj-,,ٖq< 5 3d?2<0a Y6lɖdvܧO;iZ0u[rzO\ɛW/[R泬ffkl۶MJ_].szXv-`$5Tm:!C܈BNL\_\ h(zLS!GHaYVґm?Dzm5 ==Wt$[=NUO:m^d$9KP?Nvs㊱. 7n܈|&tI,-\\p҈U<-I'd2ĕ%ipG:7eB.@qs^Metu^kvwöen JkjXjla|c%A@AP(T|{xVJh\w[۫ "sPB`&ܢ!Neo+˼ϟy30o`*Rs0B Q q%eW͕V6>@񈦕gYj,$0Ja&*(sN >|=W˶֭[Apq!=;IJ>!##1ض 10B`XViƲݙwމ+.Vq0Ip~Dݲ P JL˂DJ&YYA86A{bb^6~. hmm޽{1iy iY0LsTiw|#G^qmhwn۾KC7M0B9Up0 DӮΩ'֭[/+? N\wuDӴhkefmô,(%I 3\=+i(%mk!( s eu:Ld/lP2d0%ApȌRm0B"$Ydkj490/"@)PM]1fQ__@ii)~iDqE|e rPU) 'P %I3&v[q\W9^eAӮu"9Dzm)U{m{6˹@.A׶ˑϷ&i9H@?.qw|(:m꙲!Ompux8_WJfb(B*Z ,f(OMfҰ,{n:m%[zUU^@>t4ʨeչVѲ[ֶmR S^JXVϤB8,F2-[\oܸIxY:JHrRSe"@ns ò`6<7˵\\{;uJ@:E^0, c)/;]..ݺ @s\P*}+@:`KMN#3@E$l;#IR&X d1״־ATWWna>'k}Gb yÀfPM9`:a$ALX@ ܌'=|)!v8_6[갬ƈS9G0-B.״F+D!ୡ`\t&E{EǎV,k"(c 9B:bt:3Ѵ~ R/&dWpUW]VZ.aYe5a!VNccn+ih99r4e-U111A{c T9ouZV1gBXٺeҙ]Q؜CvC5G].F/+"c8J5eô,JH(nKM 9ۖ: y~,UmdYfXj{ 5k_33.'+Ôl˂j9jHsVW[P(dʒ4ATEqιmzksH$p8_EEȌ!k0602:j8ߟ<[$JTPBiIg/}a >RCJK)1 |>l۶v9i2z J!̥˶a3vٳ Ue̥tE)Eq^~e;)Ȋ!#؜#͹谬5|>SO`0p8O?<4e59orQ\v:'$<-[ߟE9(^onܲeZB&7[+H#NU۳lٲԩS I8!C*' c#iB^^f2uCCw@}}=v؁h<MsUH"iWc9M_{|\s2<6V翿;\#0Xw |DWDWZZZ107 BK r1nv,<{O9Iu]ǚ5ks 0M\N,jc$TB$ppr׮'x{QFћN;Jsseee0ͥx\e z=z?^n9mrtd 7콖E WQj8Uc,혫!HsP6;$ YjCWKIe2p"%]ek^с("#c0s6lAAR #twKgmBEA&e-`(fY6- \?ٌaHƣe (BA[L1 ڻmVZgy|*mWi#_SU+W|.lیj=A'Q$ZNZ5L*Ɗ+1}Q0Ѕd9ƺtΐ?^a$9?e<%iœ[ ^ۖC]wMJKKNt==5e m1g4SqdHקse/,A@ZFf3:~漢&dۆ4Z|bشi۾`Ŋԧ>\ q"s\1y*Or:?4}#RnLڕWc s=O!pRB6͕Rɓx'vEez9_4M} iAʊck]VjjlĊFq' !P*eRuLšSOo`0@ iAdB즴rrtãr _|e۷xW4cvDYpb4yVoY&y۵۷o"Ɗ0Ӳ`6LΡR?uﵼaMSz8fY1׺hXm#bXj"sv-H$?LRT0HȚ0NzB 1磓~#UUrﵬEJJn|܎@E!(!#dm*TeLb < i.G6f8?7#IﵬؿyqA蘵HtPs|6[J -[ܹsE^YAH>Rb'Sz L${<N \fTy,TP@Mi64MCss[M;fn\,iY(!I:}et>~^uSSq<0n9m(A)Jbk|衇Vz R)?qئYQBȪ @WM eŞlb~GL&Uz<Iq̺x:Ɏ?ֲ Js{-wjw8=;O=/|;?Q_5.Agt,kd"T*ը7 #H@4 ,_Li2WJ)Sz =8 VW_>? ML7R@8-ۉ۱k3I{O:ue藾9HV,[TU_K^w[o 3ӧOVRVV-)Lw?8&*Y0 6"7LfpeJ ŏ#ygA5M؜cX 8ӗC=EHfrKJRt.-Cc{***W^.ZgR)ݚdLuu?}7vر eCǎ+9ylv\κҺKR~]"קUU %PydXhxC(zAjhZeIJVjv0M)Ӊ(Й=-~="][?6%HeHg2HfagaӎL(F\zǏ /F"_9?~+?~_CIIoM, sd2Y@CmsRʵP6J[z%000QlPP}XߵGkΉlvÇ{/qL|i Ryi#aZEG{ Ʒ@&TdˇzG_9v |nsKIΜ9]L&9/shvaXT۷MKڀp8 I@ҕ>ۖ:(\qpYO>|7N9Coz SID-rXE.jp FxF)$QC4-X4/DAw/SO>14;;kqWup۳~~~GIIDZQ>'O4OOOCn۶9m$8?;j3%X/M>B\>֤n%FpVP(L&#Dh*iњ5h= ()o7n?l]ڱZ&pDԒtqÑim̪%Z y^Μ=KJJ.?4~K/4uy6޸a|xWz݀>ϡlo fff ]qiRUUfcD"۶aΟb FF,0c9Ql)m(pNiLq#0p܌buN0V(-Qj11Q9Dbth~Uzu\[yO|nhz23;d1 m8\ Sئ==.6^o|I"H_sOfq1O+;w\ [‘OOe}7@ /?0k4 01zB`Z6dI,Dg1F>B7MQF7- ôX6aAͦc`sH|tcn?u0&"lg&@IEꫮ_rk=Ճ;+?+Vڮt:]8kN.,/^S ^(N_bM[nZewzd {y+&oxܙwO  [n@]@80~3nk;ADJ2N"WaqU3n@lPF9AۜCAw,X sYLT'&G7u{|_'T14dz&Xܸe֛}Olذ^0O?\.kkGF7RLqfH4o3@Lӄ(*R ۶1::JfggߧDBA"46"r%\nkoY{{PPWf/OOO͛7σp֭uٞpXU?ʲVz)f'WVArB(,Ц&(cssP& dH)ndYad$, d Q9ʹQPwőO'!rvP:߿۶mC?82<{={]l 0YnYdYFee%֯_W(=XUVbɒ%A0$4 i`&'&K&=yuA%˒%uԩS#*&'J-zk80Mx)ށ;ng=~̌!0A_0 M7!2PF>B@@`r@EN6,˂n Nmph8w~dtɒ%>Og>O>}mgg7xɒ).e--KrJFwUEQhݺL'OB__J]hEԩSXl+o;x#/?~@mm-DQL<.5 ΝYTۆvD.dzyX6[6$Q'6 CrزHG}fgcsI)ACUU%Z[}is12mX 0m<^/WOKj_Iӆȑ#kTW㉧^y商Y0-Psoܸpmmm@>@/43gЀo Vċ/D200`_ںNLLX C:%4'Ic+2&tuwԩSm,ϫP!H$xp57ߌg{dj~%b]QVVnr=}{{{3P\. t  aAX5Z 558u`?ҙY\k6`W?0:f.B7/v+á}{`8cq2ʪɧ(#aɾ}`||B$I]| s !^\s :p)Bgk!y, LġCem܈W^7om0MU[nA:>壳'\.`(}ؼy3[K_"4MGFZμLO܆8Z SI 2MPJ@)AKJ{} -YX&_G -A^7aYfrc7\=bdj~HEQ泖řIAKK (%g/낰 XBp ÇnÒ%Kp \[F)q7"{rnN `p صk͛7C%^SMEFlT (W^4Ln^Ӷqܹ pPohsh4`a &$D^jjj޽+;X\P>֮ŋ/Z*Y\E.gq% c LXb:;; 1X ˁ[nՍg$|X̻:z(vUpC8n=/Y3st9!DeAE]z|r:xad! AX(K qyxUvuuuY+ { 4S4JrYT cd0 Rql6;?xM]3`fa Uz,˂i0͂:(2V^^9FΟݠN7\ c -ȩ:tFww7ae[ ,I]N{mIJ,TנaK؅|-TCUe%mT`jհ, 02jAחR[[6o\r;~MLNTb10 <ER$AETcjj /Kͬ⠊^<`FFF.)tA8G4G&uwu|l۶ͬ-D v!+2Uӥy1o$j  q9 @! ScP>@7MT3 k٬{( )a,96 [RC_TZ\q, _ ?T 0x|$Xēi 566F 9i[[766nooj;צnp#Jgx5^[T* Ya*ek A,xfXZe yl$Tsr(&8_28l6uSS/v9o۶ rkh{*UVVaƫFZYY|^/KRd2Z$)D7̅űchnjH-G),0 \x666rEVN%Lλ,Ϗ"Ͽ! o, pU Qp0bcTP@il ;踮I,mY5 /~ |흝TꗖeHssSL\z \|.y]MӜWAhŏ,z aLC#E`!:҇yåin6Y{O>[[zjvv!SN=ӟ>]Q)--3t]G:A$7ZQds>p,ZB*>sN/LPugc Wjmm 8`YDF@4,]Pً|b]Vj~/| Xx1vaK͵z{{kjj\.A r]rFt`jnq.xaܲ HODg}%B޲,v8S! yg57 clx@ PI`PDIPD2GF홙R!_1kΝW;wB@?v/pfggaV]\Хf,.EUU%բp8B!DDPQ^&466$kP6Au\ZQ ^j%,#!+W,!Qs~C~pA71cj>{^ s|A@cUgff>ǹs B<׻{ "9֭]t:qh6o0- ϪjȒl+C?V8pyQ0Fi8{֯_iDZ0)vK\*})^갬"@ mtMp 8(QI7=wAS@.S,^98JJJЀwv){2M Xz5ۇX,6sPJz*\];۳:}ˑHXu8>\#t0JRBXj%FG.~0 pÖk$ tF t{`q!KEH :z9---MkC`0 4-AsGefٻw4FB{jyfۿb '"3N:znvds?틺 )A^QBpqtMؽE$ -y1iF$1qrpB P@D4+@h[l9 F2`0Xp_?(ڇD{bո >֏8H %eXWy]S챩$'NؗgSq7Ϝ~^p R(D}}V^]בdi%bʸ ׼}hjj«ϫ ڦi~̩ӧy:x0?>1q\+z{Vtqʣx~]111If* zhjj­7߄M5h?)@ 8dH4g;E)mi>kZA]h }Gi ?y[O?t HREQ -& *NE*孭$KR`eeɓ'M X1~ȣLNsdڵ]wމT:g 3zv/8:X Xj%jRx$lnC$HC L!C$,]Nz>?ySSpƍsW]u}Y|>rYXE/^kkky*߅g۶uuv/o \/ {cbzŨ톪fH8<^T ߿ -KZ^|羓Jه~wj^Z/ݻw EWTT`VCcCRe/OCCCG` ^ؘ=}0 #t:^h(xAt:_AhgQeYc Ϋ VI-ʋԼ[?P1СChoog2?{{{-[`lFEy9:6i|b嫯ZX(d-Q,+M #ߋ9y c dP(쳧;:NsN/<\VZvAmnneҾIH, p$ΫI,Epۆ(PHBdt^^[t5rxK;O9shCCC T{zzz˛ 4믿~:˯< /mo8ƋWm]C^4Y&]lZg YzjdI3Hq, Q˖.ux#VTT>885Kb;[pΝ~7\;~\[\.7(HIIIl߾7nRSc]uu'? D m@)++CEEv3ey$YSO+Db,XnskmmE6WU,ZHsrpk7㥘CQmfaepHFQSߌfS$Pfؠ㍉LOm]z}v K[ZZ~ӧOЇVa$k׋? nN㳇.<Duuڵ^h~|ʕcA ,=|$q{:'&GGFΟ߿ب~O}2G{]4(bU'&'q=KĶ!܂I@0K[\՛>X&yK{Q`&Ϊʑ@ )xNb1N'J9s39r4x}6XǧƅDt G͒ةh4: ---0$~?}[8yKMziϞQʦӚY۶m7?(@ͫvVYÑbd2uK[ZihtJL8/e,5MâE!Ghb1bwY &f[) 5_e+~W.F}pUƃhOw8NPJcyww7~eRQ^Z+WZ.d2%L`^Jv^1M3r>ΝxB{B![p)w󕖖.dh*++3~?.= FZ{xp$ CjA>Cn{tZofS$mٲiR9Id©(+@ |G衇ӟrYذa4Mfi lV0M4M\.uI0r,C]nptv8}|us{%tEXtdate:create2018-02-27T12:53:22+01:00~%%tEXtdate:modify2018-02-27T12:53:22+01:00#}tEXtSoftwarewww.inkscape.org<IENDB`RaySession-0.8.3/resources/app_icons/000077500000000000000000000000001356671433200176205ustar00rootroot00000000000000RaySession-0.8.3/resources/app_icons/.directory000066400000000000000000000001121356671433200216170ustar00rootroot00000000000000[Dolphin] SortRole=modificationtime Timestamp=2019,5,29,14,1,17 Version=4 RaySession-0.8.3/resources/app_icons/ADLplug.png000066400000000000000000000024041356671433200216160ustar00rootroot00000000000000PNG  IHDR gAMA a cHRMz&u0`:pQ< pHYs  tIME 7+\8bKGDIDATHUk\U{7/3cf&MBҔ65-i`mUB )p+q nōb~BMښi41غ.߹;:[x!c`߇ ցhz $FX&|iZVlWR]zZ G@I/&e4ŶYese$-#I;nލ%юt!"$ƋSAV,?=ty0L_ꄎ^K c; 1fi[V7F~,YZX˿r+ne@쾹/l}#::Z~sC!Flw;m[e_JF;RdOğŸnm-vک"GR)w6%SA5OF1%B%"H]Z}|1;&_eT[#b&9&tOnyw^Ь$1 aic_*bFIK,,R&;#FP ?ywU^h$T{Y#BP^͆$gRJNl6 ijү+c#Po\PC2A(z{\pLk{& djT܁,:6wUuhVtW\rogּhi*Nj{ :q5 fOfGQsaj]hO_Y k)̭孎'ԵÇV:`r\zD&x#Ir9Ptho̔+1Ӗ q ‘dfXT(1W@(Cff+AhAưpb9<` e&ȇe4w1[Hېf6Q_K]ZWc#@KAW% ww WZ'U%tEXtdate:create2018-09-17T22:55:43+02:00n(%tEXtdate:modify2018-09-17T22:55:43+02:00IENDB`RaySession-0.8.3/resources/app_icons/OPNplug.png000066400000000000000000000022111356671433200216460ustar00rootroot00000000000000PNG  IHDR DgAMA a cHRMz&u0`:pQ<PLTE:F9F:FJKbQdNbQLLPMhRdPcQRN;F?HeRePcQ@H;G`QfQfRCI9FTNeG2%!0#bEXO7EbPF0%0#/"-!Z>ZOFIfF&H2dR?GkL+ _AOMCIjM",$^B_B/&iKDH]K5)gGdG.%jOIG[A`DVLbGT<8ESKU?!"gKLIyGChJ"#Q_P]LI<./VCSB52P?gSDEfK-.pNE9ȂW//aI8DTI]I;:rU@H}F:E{:B}>C@F>G}9C| image/svg+xml RaySession-0.8.3/resources/app_icons/ardour.svg000066400000000000000000000040411356671433200216340ustar00rootroot00000000000000 RaySession-0.8.3/resources/app_icons/calf.svg000066400000000000000000002575111356671433200212610ustar00rootroot00000000000000 image/svg+xml RaySession-0.8.3/resources/app_icons/carla.svg000066400000000000000000000760501356671433200214330ustar00rootroot00000000000000 image/svg+xml Carla Carla RaySession-0.8.3/resources/app_icons/curve-connector.svg000066400000000000000000000032001356671433200234500ustar00rootroot00000000000000 RaySession-0.8.3/resources/app_icons/dark/000077500000000000000000000000001356671433200205415ustar00rootroot00000000000000RaySession-0.8.3/resources/app_icons/dark/curve-connector.svg000066400000000000000000000032001356671433200243710ustar00rootroot00000000000000 RaySession-0.8.3/resources/app_icons/dark/network-wired.svg000066400000000000000000000011051356671433200240600ustar00rootroot00000000000000 RaySession-0.8.3/resources/app_icons/drumkv1.svg000066400000000000000000006261471356671433200217520ustar00rootroot00000000000000 image/svg+xml Jarle Akselsen RaySession-0.8.3/resources/app_icons/fluajho.png000066400000000000000000000262561356671433200217710ustar00rootroot00000000000000PNG  IHDR\rfbKGD pHYs(((btIME y&tEXtCommentCreated with GIMPW IDATxyPTg}ieEE D4" ݙ?RTfIj$e\l%12fЉ( b *eiӾ|^n߾}9ys@AAAAAAAAAAAAAAAAAAAAK% Ap/--{F'{0t T*mD@x+J_tqHo2~aa,^f. MdggcժUV B@x?ydر* & }}}>HWq;0ydN˲/---F@CJ(((@VVqtHoW\ L1D||___Ӆ& K,7Z$PXX8ڣ=$'PPyĈYtIO5~TM6[Vw B`@OOψ8qhs  !蟘͛7#00PqXEEE:::F|RtJhA0ܹ!qkT*Ŕ)S  <SO=tu~JG  e{D@@$;_R&M|7oރV ˏL8pKRlܸ;;;~477 % @ppCC"d-q@|||; +P ڴiشiCq pQ,kcB`'pf͛xwkx'HHMMى={FQ񂂂K? *{1[!_qxBiӦ ||CyfϟǑ#Glt:A HJHgPP~8r_~{yotP 'H 66VZ 13 68с{֭[0͂aϟ  F j*}qTTT B`6'3d2RRRR0 ?&&EEE qfرc6q߂2e # 4#..Nnܸ1(ϟ, 0̙3B̸ݺumnw8$M0X`eb5k8=z}n{{H$#))~8btt4~i]xf3Ν;cǎ>>ʢb4qڵk1oNRRM^y$^/\PpoSo .^8ydmADZcD10(Ž{F}3f`ŊtGxoܿsNq?˲(++'|hhh=Q|}}#**2~TB$'';aVԈr,J'|r|X$HƼbDDDq!t:TTT*)hGmI]`5k฿{U]?À;T-J6ۅ) 7bKcuuua޽.Geee fڴix'hB`((( h4ĉ(++󔯖13gΠCd2\Ν;#8~q;hQ}}=jjjDT*)C`|?0GZZ฿ {Yns?Ʉ'O:\2Dsbٲet3}ϟP ۷ׯ_8>UUUyrk֬A||2 lٲE_Ʉ/_~帻F'O5ExPBlS H6~.HMMqKo}}ww7> NZ͛7~ВFbDcLTVVqd2Aimm{gSnbF '-TVV/ HM6l.N8Ė-[*8:WR+/}Ʉ#GQdXbm*mAaK  ^z% 7/۔r90 ( "!!jf3F# z=ڊ(ܹSpbH\zlV(9tRqO2~n8Bjj*HaH$'8qN7Ǡ0C(---)FzJp7ٳ-E50$''cHIIA`` rzNiR梺gϞHH7lݺႎc)mW'>0PXr%͛X]vg hU@t,K~YŅ paj* +WDnn."""-u7SNE~~>|Q7-% 4sbʕo>g  ,ƍ+{y >>>Xx1._?82%`BBByfK~ÁZ۞W^K/1kQPPsy]uagI=BĀg-SO=q6116l{GyNrrl?ߒ3~R"⋘2eʸ_/ɰdL6q BQQT* ]PPPq{M86m)&J8d<999{z|ǨvGEEW_ŢE'kH$Cѐ@0:WTXvp˒߉'J鍎+ Q8˲00L0L0`YV-T*j*{.MZ+⏕}ccc{?<<?ϐpoI5 Ľ{ݍC`0<,! ???@R!44J.$LLLD^^JJJ=Lw6 /㉊’%KMn7~Jg3gCo؈˗/N`\.RRĤI3f ""N˄`iikF|ii[d$ÎO<"##yDzo`"KP6, 娩[:~(//B@jj*1o&4iݜuuu裏\gmRxxǡ/^g}zQxXtp.^H_>(&L@*bٲe8}4&gD999СC67;PsrrvZ`z.]ᅬZކ?wnd2oN:Bddd8e."" .'|2(E.* ˖-=,3gXoG\-\ (**qZ[[qa?~|B eY|իWcݺu\.GNNN<)jRq:k4A?O?.|׬M6!..?OTWW\)(i9=!jqMB>}:p9HFT"33wdӧquwVXcieQUU6\qC ˲(//GOO^xݕF.]rm2:k.&&3f7n?w5k֌X8TUU_kWC}/-H"`ڴi{ab='H!::E̳gqBףXx1B1ȕ6 Akk+^ӧOfpj)SFH$ <<ܦE {Jss3]s8 Jq=`0`…HNN\.[PPHJJqE?~}\ ^wpL0&M"9A&ϢRʝˬYF|]`` Ν B;h4կ~۷۝gpǼ@KK ܹ#ʱx 'X߼J^A#;8guG&O0/~a6Τ7oeE"I|KVL`L&Cff&^}U[?˲lqqq? x,9\O,BBB `̙صkģGM ##K@@RRRYj[h-Oss#""7 99۷oIv)Kۃ]T*֣͛Qf .ҥK{R?" QmĉnɄ̙3zaՂ H L8qG^)|{zzsv&,9ɓ'c…n ڠE9Vhhu>E" 7r3JXdB $$ xZOB^pjAMJIDD222͢U pF>0wwwZpGoiL:N; $o _!Gk8qqqh<*{¼ P(Qי4f-YwrD0~o`^j,`0xĨIknHn߾- HLG=q!"ܶ"q 00pc eH<w/ l61 e+ oI7D"W׋'1yeRI}h{L&ZZZ-y) Q[[;w~̙3C^`D!ىj)X|95pek4ܿ߭u I<, }}}+H0{lddddT*ϟ/jׯIAB B"?;;W&_X) @Z- O6 +VBlQ/߿:$AaXj&OL^Fd̞=[hmmECC]l[z{{qiA5OیR$׬Y#D+˲v$^ Avv6rssNq={6RRRD3(++'rqYA^B@~~>ON^ƯT*n:Qmͮ^J`x>s477 zODDmF+V@JJeY/_6$TPma-['?O4 Wm߿V Z#G/MMM Jl2xDP)]&h yEnoo,8cXh@,YG}T]Ir{PUU% 3G!9'''cӦM  /_6# ljZ-=QQQ(..ƬYHF!!!زe """D,Ʉc9x jkkq)za׮]vgUT*EQQRSSE]p->/MQxh43g6oތ|fss3 :/N X駟`08$x嗱fļ@GLL ~ӟw zF{{$ǎ7|pް0ر[n;Wo;jKHLLtJq\sY?Aw2 ^VŻヒׯ;|̀lܸ/s?^D@lMO0DxP,٢vܻw%%%8r? .\z 5I1={pI2~ `ժUXx1BBB(,wb(--`|0HJJ1w\b%XE]]~mԐxraƍ:uKSk=^r{ud$^,BFq/ǡGnp"'J!HIIA^^Vd^GUUmӹ !R={6}Q$&&BV#00ĀeYtuu=4[Ո)x!11AAAKE8CӡϟǥK8fKcF \.ǴiDFF"22/$$얿-EIZ[[܌ׯÖ7 jE@8A ,rrB&!""ј8q"B???r0 P˲0L00 jBgg'ܹzhڇ#?C4xF 0`&wYr2|b yp!D2v                   kש^GeIENDB`RaySession-0.8.3/resources/app_icons/gx_head.png000066400000000000000000000340371356671433200217340ustar00rootroot00000000000000PNG  IHDRddpTsBIT|d pHYs fmtEXtSoftwarewww.inkscape.org< IDATxy\W}995]UAR-<؎혀1&s LX$~ˉ+!@p pWb&6QnMR]\uGޮnIVcjUթ3{w)j(^@]=4 Ԁ2K^rK"y^QQV<+ !hO;ePVJ@ZҀr뻀 W?{ܲQly;0P/^ Go uԦo_} /V z6?u]UvװXt]X0t5M{EoVǑMQJ=OUծ(G-zhee=3:o~`gff.* ZW~)K("N3>>>nk߾}Cv;MW?S1 ݻwO\199ǥTE}k:CQEAUU6u]nF^נ( 177Ǿ}뮻!<9G~q󶷽|J_tӕp.X^ַ}h:[?.#H׽vWU/gﰴĝw'> rIGWMLNNwm۶IOKu"R,fy2 LNNrwK{%rxjxxA}Y NU!?7pxiXD"(E>|~=[l3$ rȑ5vZ200Ο277۹{{( ͗E<EQ-3GGO% IX~qiֺyRXF,.ݔ@UlӦQ00C$E+&:Al /eٴj]QC>u=*+Mf!,σjE29gqڍT劢0zܰ弲ސQ n~*ffyL()gr2 N08^lӭ|4<>CHd LMQTOce9=]SRCQV+8G*Y ,L&BnT C/ZDbJV* A& *ۄ~>Cu]rin~$;C32g+$"(ғ.O? O={K3EtY*?ykz-6_ϣݖM| H(r&!GnJe '.7!25I lt]O9"dX2fG2eply~PG~dFclޑ nh5<kD!㜣oq'kL\>Sr-fiz#cxkx*i70<˧+$QQ?~Hf#LO.rY4zo~b6< 6 MΨeN]*jwxGakx~S8C9`THRd2Qy߯!N,o}j` BȌQ-tBUpBϧ22>co ; I8 mSiO%;@k4evVf P)2+FE$3ZNSCersO,83D74<#X! 5:9T.&A@ȠXX)4]ȞS+7 >" fM!7MpAd ΜGeQIf#^M! a68\fC&RL~ VK0 r@QSG(j .ct׆ :D8>Mly6ŎKG6yA> ‹G9Tvͧ^/қbMj0L0 /UItTM=[j:ٱC={><4Me`0me۶ {^\(@{h gVL~3߄T* g*Dݻw-z}nMd2+<_ֿp?[sK~cg{]/l]EDr?SO]*MMM AVu]6Բ,4MLiC Ny*YRbT?iu]|>NV?~C|^•Ą~7 8j.N4iZ8N/VvuvVZ-(Us]W[Or4ufNCZY(VEZy&rjJP\.jjRf/vuf۶ǑZ뺄aj8}k)l޼9%맙(!gK+f=yKx<ϞX?sElj Bv58t~U81Y1t]QB\w6u8 ,4CyV+j+5^"k~I4& bY֚~qd5'{:q-t:B!y8&:[BdTA'Q]\m{M=E&(k;j[֙p m>#yCCC,//cY=i&<vrٖ-[PU|>/(077A&jQfQUr뺴mFGG{ E<#166,..( [lAuj+++~.rm۪agu]ǜ^WǡP(Hmzir47 ER`0Hݖ+ժ4rг!B7]*>SUHRD2RH':YVDVRՕe"fSֽX,8Jeli48Cݖ)R5iY/eUx*,kMD"ضG>o~UU,X,^ пٞXR2q]Ű,;`xxUUW]w݅mR^bF׿w! Gx&eeeM<* fp8u]G(c``kٿ?˘ɮ] O*bqqnӧOSOa6Xh4yTUjTU<0Ml6K(0 N>ya6net]g޽CNT*|;vÇit{ӟTkN?N"JPU\eppPjeTU%Ji[c(|jMh4Jѐ#\. qݻ|#A^}e*Ҿm&''YZZ"r뭷:7ٷoFcqT*RV MdS.I$T*W\f``\.ǭʻ.fff8pǏšz*(ϓ[n?1nOԷ{/[[ G<8O>I:'> &&&>'-4u4ϸp]宻o~3SSSضm| _lu?z(GeddG}{_=GF>n+"wr^:h۶eF NP eLӔ#JIW,N >jJ!JIo$h,.. +H*R ˲HhF0dii UUi|H$B*q$333,//.LjJ(Y8`V#.Pr8d2,~9ZL۶iubT)x1u^."Qp~9b\塇ⓟ$RPT( V ,eVՈFj5u(J Dal6 j5tuժ O}SQ(h,..DZeppB@Ta岬g$nl6e]T8rwy'_qMӤmZ|369WِeYv[o׾5wJ) !wͦK&1 1+NTU=c?HW" 77rm.EZ;]%jm??7oHY)Bӑj|eC;9Fi&fff6pX}WNpB($IKO\ZQjZ%H8pjc˖-J%4M\.F *ft:1778Ad2EB àVH$d_.,K>B'cYX qRV,// je,Qa/D"g?Y* a022BXP(P,& Ih4D`0Hە5MAxT`|#X #j*Ǥ߳grH STvj5٬ `믋@E}>v>@d2IѐF6=oHZu]LJ?aOmۦhH*0z~ZB!iDtq PV رcDe m)H˲h4ٳ`yyzNѠ^@0fizuR}j59H*A^j_"^xoo#ߝWe ߶mB|!fggL$QVIRj5eYNX$~ժL􈜂eYRW /Ķm* pXZm$IBDRD("l_X$H%:l6q]nA"vT*o?0OZJTQZJEz"X+H`0i2fD"rVXG ΢aHLO B% L&ȑ#5'ɻn|A\r,H9Yt@<^sN*333~9 WVV h|>O"X\|>$N{94nfff =388ȉ'$X:::*A!T*E^g֭Ç > Y=SVI2]+fVO?3( +~|>/=u]e&L2::i<\~\vePβk.!LdIUUH&"oZ FFFdeZ^r%EJ̥8!g[L&d2i8-[h۴m麮N9y$r ,--adR"B`0H:y}~?dRڌvM,0 4tBIb*AxTKKKrJ%.fS ZHLt8 p8,`Y4RJB"  IveRa۶m$IbmAiߚ&\N|^&ǡjn%9CB.m"so!yUҰ}Y*>yF4~I|>vf``@bFnV8p/}KzWr>u]w?i9{~P׹i,//sQ|>W^y%~Yn)>(벲‚_t]\.s ٵkiY^^sEqqj]e,R`6 wR ĩT\.G,iWRuJnFFGGd2|>x}Q[b&sssTUaw:w}7v/ӧOK‚6QRHqb o/2dL$EO x<.DCR%91DW,aN$ $WvӦM("9N}vN8>9AHӬȟҒDrrA.RN<)Sôm Z-nL bG x<ΓO>(OBa( Bd2I"rPJP FnT*%u]E"e"JUzidpX,FzH$0MSIm$t<쥨9uUUe6Qg``@@RL[C܆␁)~. u*jL&C$ѣt::D8ƶmPU+رc'L3Ϡ( RHE"oD1rB!oߎeY ͦ%!ɑ;00,Vi,aOc&bTUc˖-,n?O,rIlСCLLLȼx^'QFr)eY,--f b1iPo.eѰEj333[XXqx n*ĉl=99IɓE ÐP|?Q|qȆ,^4RlΝ;YZZș "ܦMH$LOO˩w^```Gr/'Jqnf2P(Q9<,kp8,c~XP{2$.$I:t]6o,T*g޽n8rT%ʉlԔ̵^K:q~a* o{l۶/LLL0::%k?E zlCQln$2y{9dJ%jVr駟ns/ h4$v,'rL&lbH&T*8v<55ť^*ՑՆ8x v'O233#74v-SȍFCXDUW6LHK H D\[~bqDbyyYf3QNm[2BI"EQ sR,9r isn݊LOOKLnllJ"שeb'~z jeymŗLk3{LjjeI淰/wy AR($!@, PRD!p7T8F*aWl۔J5) AD Km.k6X1Ľ7deU(֬e}EQ<g'"t7=_[Qz;0q]N!M,^0iT_D½8&{zB3,^ųHl'"#@<(L#J5222l6"!rV@ ISObŔ%E.Zp^B R ) H$"-¾$Ig"UEBJ8!b$r:Aʭ?bՋTo\.')to]XXXXCض!۶ Zs ~s@RTO4;}NgϹߺ||1Y(Jv}>_|[^^ί!õZ7;E")/l$J}e /Wf w]tXP(m۞ ŶSJ=嗾EEmۧVe=S(jo{Y D"Ų:εV+NM"@|^\ image/svg+xml RaySession-0.8.3/resources/app_icons/jack_mixer.svg000066400000000000000000000541201356671433200224570ustar00rootroot00000000000000 image/svg+xml Lapo Calamandrei Jack MiXer mixer jack audio Jakub Steiner RaySession-0.8.3/resources/app_icons/luppp.png000066400000000000000000000127131356671433200214720ustar00rootroot00000000000000PNG  IHDR>asBIT|d pHYs?F?F[`@tEXtSoftwarewww.inkscape.org<HIDATxytuE#if$[U1͖,IH 4lZ[خNMsBBCV %mW,Ҍ4fCZb;Gǣ<޻g"m64sDdUJ)J5{;73v(a '+TAee˖x|aEd 45ݿaFH* /?W؝Ls\^*VDVgwϼ>nz DRœ!~+0x</މ[o1[ !6G TjؼyBw"DWbr %& ;kՃo&:0VדBω˵ݿ֯hCFm5 "FԿ!]?en> tb<-PUUu-PP>yvbDjC8 <*ؒd6mڴf^W|ou }PGCU'=|uJ6zZU9Y&KZ9 F\vkR4QNpzh2'x~ "iQp =JWWW:L]S56u?'3V.b _MXYYu/7Qٿ}Row|Rg8 &w ~ @eeR0Kxz9HZ)aW&M 4,NO䦄 nyܗ(~Z"KYy]Yn#yLKh5TbEcǎ{3F7߮g#ґB<< MeeJ'( YgY5 Kw Mރ˷xyp"nݺ8)wk~E2dG޴jp!j3OwѸ mB!jjj>hX}RZ"xK )#YG0un7tе%ׁ:$IKKQZ)+er1hsf:%-+U҅Ę@y\4-oPUOp7=%&{v]T}hͤα=T\ ~wb>l^sڥCamځ9Ŏs]/)2ajScǸ(g^)y 9\)|Břڣ.B|CXK1PJ͆ޣվxxt%AVu?Ίs`l)%n뱄h^~wc-cv)PrMIxui 'z3ㇴ5L~@WwNNJ}){+SP/fݾ4E\(%֟Q%KĎA9%,6 xq*&J)RqFoiBHyi`?b[Ns5x5ia\.e}(iquہF4@6~OA1{+ TZGfGhՑd2Ցzi_̺ i ;(~4"yykLsZ(ZuȚw}֗) +Ӄәk?6;Q`5g=ŬWNS_X ]]34-)Q!+߈1=jA45LC)җ4_:`#K4ᐮDNڣeze=Ŭ[Ns߳RWh5n>ex'{5[o^?x<͖|B-C^llŬv.M,ԱRK?x`JŔ=ucv">v-(Ek/-Q9<$)M0zQ湴=j owh ,ug!ݝZDۡ*N3ӉSp7{**8Ȕy[^:udl]}pz|veNbT?g;e'cKI~zeeFFn:B<0o'6 6:ͬ6"OwM2LyhUWn"cA$}1?)-ݠ ;y`xS[P)fg,kvU0?v\ lӧ,,jkL˞A(|D2g ޠܪ3OgǏ 翮3U Í`0*+[R?ȥ2E[&;"lX&<3EFMNU{ټ JvLccЬCV ;^MH>lR_, =>K'gZ猌Fأ,/j೓۟(Ktq]_FwRlrpa!*;SS jK Ĭ5=V(>2oK&x'W+)Ex+QJtK(TwRU¡hjJa9=msOr#gW}b4BiX|Bi'Ww?e(7Ls̼nYS#::x'+ڶ i pr NT>h6 '_-cozSk0X܊b3gNL @NnR>,Y> FJa离FkU}]U'$ vd <.J!`0r3pL#c(#]jG3ׅ,@VYL%M6m0~d W &imVO! ]~" j=f3JGpV| $H#3 C=D˥AV@?NgOcpSne59aNzaU9(^yp\ `8=d9rȢ:`'k(^בIrF/;i1ysf(޳<}Af!lՕM?ؤ?-yIENDB`RaySession-0.8.3/resources/app_icons/network-wired.svg000066400000000000000000000011051356671433200231370ustar00rootroot00000000000000 RaySession-0.8.3/resources/app_icons/non-mixer.png000066400000000000000000000044351356671433200222500ustar00rootroot00000000000000PNG  IHDR 1gAMA asRGB cHRMz&u0`:pQ<bKGDC pHYsHHFk>IDATXWiTSg~f.I@Pj pZ"akգ eQƈk1!BQ+*j*@7ΏeѶy~|=}ᄍ'?a,ӏ3'pHtHSLT桉j4Q//e2*߫4 53 Y}0f*B"%DB rJg~ A')t艜QiՒXXҀyc^NTyH0RT+#޺ 3[sj"0)Ro+X6AGGX(f5A}M#>VwFs@~H#<w inh= DŽIHoMcPt# et>f$t$#a%aXvu #0# bJ-UT)9׀hk%Gu)6;iJHBLᤉybDi鶈ѕ\U̥OjFPT%צGoHkNwui?1,m )4}IhE&ROQ?zr]l:š5f+ٌ r _O=3\rwS**Ze|[n%4Lq𫕐!7IHByO^J ?j2?]Szs[tB TZYڃڳkxV; qf##C8nU?=r3q6|#n%F=0@ozQh +XR|cׁYnm?5,!ݜm/ŝbҒO5V^uaͱeHASt9eϨzԓ{=1T˴Ѧ.fCv9"g]w,D޳Zb^|Hs9' C-RfM 1͈x;!@-;_ؿ:pغIXTdP ƨB|ڞ=]k ecGĿ;@w$%tEXtdate:create2013-03-04T14:25:30-08:00J %tEXtdate:modify2013-03-04T14:25:30-08:00;iFtEXtsvg:base-urifile:///home/male/prog/non-daw/mixer/icons/icon-small.svg/!iIENDB`RaySession-0.8.3/resources/app_icons/non-sequencer.png000066400000000000000000000054401356671433200231130ustar00rootroot00000000000000PNG  IHDR #ꦷgAMA asRGB cHRMz&u0`:pQ<bKGDC pHYsHHFk> IDATh{xMW\s$$54Qԑ]D/.1aԃ*#ZEѸ"DF **"䜓>)ԥ3}zo}>kIR.ρA1M3i)*)aKmv(?k!l>ٯ4nM9sh{-௏;&?J3F̩g3ZTQgi^/`8b# q7'<~/+0RZEc)Í4RT"l5r})P蓝wZHAdt+7[_ r7 (i+k?NZgr0}ofGՂ,*}A`N2{ݽޥgDY;6*WA g5XkYêb?|d`<Y{*Py&cn*[1@l"W\UE/D8 `9G@CIQ)-#%8 ,:5^sPV}4 ;(0%01r`ϔ<֔*$k|Kw= iNv%JqP}ư-v@^oX?,rzhj ՙ65=7A?<^psvCQ:_*իpOo3xǁkc@j.QwSr[zfí׿/R^MvY'n=JJwLFΉ_ RsM{e+z@K&U<&W^9f{P "=L1CW@fW3`W׶.}&lGtz,hK:^wZ@};kAlЬP_-!!U#n)pho ]ԮO V(zڷ Oիtn!50 o,U?$M'w݀%tEXtdate:create2013-03-04T14:27:13-08:00=%tEXtdate:modify2013-03-04T14:27:13-08:00LxJtEXtsvg:base-urifile:///home/male/prog/non-daw/sequencer/icons/icon-small.svg.IENDB`RaySession-0.8.3/resources/app_icons/non-timeline.png000066400000000000000000000054611356671433200227320ustar00rootroot00000000000000PNG  IHDR #ꦷgAMA asRGB cHRMz&u0`:pQ<bKGDC pHYsHHFk> IDATh{\UUǿQA P143eQSGG( L#JD 5LIQ!"<=sq᧜Ϻk^9kﻗ*CJMcVCx1=E,edf%#T٪y4-0hȱr 3oXVìA1FOUO@E&`P+ dW^VkL(uuwuϜXb%" 'Edgm%iiqiB`ˆ۽a۹&S_9U;I&}k|Qk}XX]RǤ/: 򭄱+27gG eV6n[u7n/M7~ͨo  ׄjAkVk~=RY} mDmPf5#AYg6Ojj2fP[NAS(#D2R"#SƘdDH)NϫWYTeH1Z#FY'L5~yexV8ɓ'>KWςtRNĥe)W?jrllz{{%Q?FЬmڪ_ŔOo.4m.D.z!wݳpcy]yW—uNk<̅8dmBn,$'!p0i)sUW7‡=Oy> 8 A[7$UE׾o޼#R2Ӑ N[mcym@LK!rk~2D {/{IjX6OU,XX7\Cb͆?steUf];y$}|UXT7,_5__οt3IuuР~B (߯U=@Ĉ81V r)!17VPWx :ǭ v}Ν2l#t#b.IsӻiCE"\?P[-Y  ц@rNy9lت˫JFw\DO^5VGAml oE pSBp ~ . خNf25"T_V= 1p6@$ОQ1U7%]=>6̗E 'O28I9hun0kK߂`Wi%CYNA1R*Py5Voj%H^|*v@EzX41W-nܩoS2VkR~ H߽<[]x e։S!l^mBZ-7πx[\\=PR\=hiX]r?ue0 `,TbPayA,JhPR )_JA9hھH r\:S{TO;DN?T@TҀuU?/r/Y%tEXtdate:create2013-03-10T18:37:30-07:00,A%tEXtdate:modify2013-03-10T18:37:30-07:00oItEXtsvg:base-urifile:///home/male/prog/non-daw/timeline/icons/icon-small.svgyIENDB`RaySession-0.8.3/resources/app_icons/padthv1.svg000066400000000000000000004334631356671433200217250ustar00rootroot00000000000000 image/svg+xml Jarle Akselsen RaySession-0.8.3/resources/app_icons/patchage.svg000066400000000000000000001000571356671433200221200ustar00rootroot00000000000000 image/svg+xml Lapo Calamandrei Patchage patches audio cables jacks jack RaySession-0.8.3/resources/app_icons/patroneo.png000066400000000000000000000067531356671433200221700ustar00rootroot00000000000000PNG  IHDR\rfsBIT|d pHYsXdtEXtSoftwarewww.inkscape.org< hIDATx? )"!t&44A}AIK*6r,>M$h(" "@bEL9s<;g޽ IIZ+; lI濒~6~.CI[PSX~"9tSLa?Yt:/JzFp$8;D3Lw@'1F1!a#CW~TSY"Pmw@%!;cG~i_"1:wF!1)C a[~dԆȄ-"S;D SXے wbr6 OJwƽrBfbVOk}*"9_nh#k0<9S} 0s_Cx:`_~"PS;ړdԮD`' ,ԟD`~''0/?`G~"pi<ƖИ+|{z5!Qׂ4 [Fu#+|q /'$=3rQ@ $r0WuxoEn6a@rGG!+"#P@@~:Yk;R##StxF0r_W4_>9忩ß'.1(wHG nt+@kw1{ǒ<|8@ywΗ3׫axt~_̝o# \QBG c ߨ_DN_t X~iDALs=yB;-F a':'8;"ґ)"#*0y;#E`N x{1-Y+F-D~|ӰD~4)M~'5W$9o-"CRw@,@;-G`SE~!b+sYFi)9w@E7pO\Z@M>4/;1 0߉E))S{J1m#t8TlsybRte<Ӷ)$ߒ# o)|Q㘐 "S:O_ bB-' 0!u:@LȿD`D`&A~"" k,5OAf`B' '0! u&oI~""p [[c: {cG: {߇uL+߇eL+߇UL+߇EL+ ǐ߇tL+ ǒ߇dL+ǔ߇TL+ǖ߇DL+x@UG"CCҨ2ءI? O HX`G&!$3D *"`4v?e@D NBIȿf@F MBIȟc@D LBIȟs@E#` OBI_b@E"` NBI_r@Y#` MBIȿ4D!iEv!i$E";ȿ4E";_4fEݖty$~d"p?]F,uq}=û*#Z7#DZ廌Xv&3F/ HK\I<ߔu/6J*p/CQ:|C9rS?%\$}m~Tڣ*S)OC_eޡEv!xLGe"" w@|i$X%CCE~ćO#?ȟF""o?;D >D` A46!!E~ćOcW""p U UӨR~ć?w@|҄@4!1ȟF;D >#EhZ~g ]O+"#it)CS?w@|z13ȟP;D C>"i -CڎG6#i "V? ?h#ȟπO@ "i @@4?D` ȟ"O? /6ȟlO7 $=y'u}g2ȟƙۑ,ZCD@2OK&V/f)-+֨}R"#)oJz?ZdjCIw#?䯈8Ww/~ƌ@W| 9;E@%;D5\@X#Ъ CD`N]uZ~'5 K"C~vj%]I:܆sXo9)vj9hA~S#TZݘe@EiKhNZg@B_DNۆʈEX9@K9=ӁG|6R.@O$=>Oཋ{?W;z:GS"̣қ+6sIew@YRG3 񜤷$|Kw@Y\ ;9"kw@Y"Y!CBN"PV~*R@@}pk;"ۿyA",!C"p=o;ͼ(+tx<֝@G<e'؝-# I.&7Ȗ +i5"i%P#5E6 ?Nؙ"u2TJ ?@唊4B ?@c4 ?@F:ai3F: ?@O{"aNl򜊀[p0HZwn/MIIENDB`RaySession-0.8.3/resources/app_icons/petri-foo.png000066400000000000000000000063361356671433200222420ustar00rootroot00000000000000PNG  IHDR00W IDATxb Q0 F(<|"NުNjRRjnnnFv:t ݀x$AqW\8LpX⌻˺7t_'$_&g;PTTdgg 233AUUU[HAAA MX}#UQ^nļz~ѢEJJJ.^j&--M W]uiizjLRԏ=+//  w1::C]CCC%"RTakXv/vw^}ԩ[7nu={gͶm4mmmͅ 4.]Ҝ?^344ijjҬ_^se3GB B0 !p6;Q#lmbo4oEtEZr%uwwC/"˴o>uٳgܹst1=h =3tUpjii R}}Bőd2 033)~7+QRb%_A0?-]`1A400 Gw^joojh<4?k, C`b͍۵Y-8|ݻ†...U4J őO&&&&\.Fp_- DL&744,.Z ?w%%ݻ׮]+ kSPD"! T722'{{wrt[Ruu5M>ěJ%5_`55njxj ͝LO=`ŋSjj*9;;`zlr-6 mHP*YZ:҉iÆ ,0 buf3)rJiy]UI/O?́'+[7en88A/c``4*i(^Q짅xAM3gZ| tR(:r;[wwRxX<]ry\ɉ7럀 XSɟIL=ݽz:֚((0x]>v|I[@+kp\(ΟF*!0ad8;ssI~h|K8pb7⵾bNz&T%kS YFh0o&144,,Yu23#-*-͘+:˗RC} w} ±M]qZj6%eВ؈MjllꙖZԢZFAAfꍴo >}|M|?-Nmgg;-Y0%QPPM7V>OX|QrMjJf|j̾??L5jY};ovJSRRzw aJbMAHoܬ7] C2 6 55u]ss4\YTUUK۷o'___1kmP4%\""c^G]R~uzW֖sSEE#ʬ} \)9RhRabbZZ\T}[)rmUnqC¼O312VJڮ- /ӧ~-rGG`ZnиŖ{xxp&? -ccmuY{Μ9#[n .RaEFF|q3xܦGD$l޳ WWzr^dWy>V!|x1h7 پcq[׍;Ҏ;q$J掓OgxLwP}~qmUE/v?=/EΣ`gg0"+O l[$mܸO"Kо nȂϞ=pCb <ncUuj-'ܹs)$$paE͛7%]}U4=! +ܙ5NӍ7]Hy`u /jhԥT:Uh%&Lb jhhep!%3=Qr?Vc\^u[yS*Gё8LN6'*Ɯ:?ML-]]ѯmGОb'8#k?㒊 W^\V\ ^RBG)3XÎR8#ϥ9~6ο޶.fnn^7j6RAdR|x[\A ްGi$huu ?p9Bb,Fu_-,m]DT;y2qNP¸#0`2BGRy|`PO0 S%_90|`'@ \ wA뵏cb{X6#aQshĂz$GP NӾUB [ OU3 %!ړ$c;/_>oe'7P+ՊCRpV "'l^ؾ\@cKW`aッ+36':& X8F@4[8& ʦ46VN :H8a)Z&{-âڏ`yΝ|١Z Cjz@ҢZ7ZW|g8 Ε y6y&Th- '.|M\ |pݦ]v'܎8~‘7O1? Ϗi:!z8kU&I+^5+QQ 4 Cߦ^}=kM gDJ PzzEѨiO2lH6Ie0u} m&B-VTFgaE VP~&9a!i`Y&uc F BYg&uo4| =&섳3aF9dӧf>bY.V tdHefkYs?6t 揞g!oVu+K0љHC}>8k+8N?lz^s(smܽ2ÎF,qc "Ni)L8Y9rSڼm%T#~`*4b闾O' !CFX? X^H VD0p/UDX©sgBr" ubiS|x~ލ)MsA4.a21AE:MRmHByǽ}ov KցBSa5n!-9iACJuBE l} sגm莥EA|!rS۷}<4Fr2q{_m/{܀f,0l8DJZ Ȃe*˴</.AWj'+^b!QJ b:@ TZ`:D r%,`z1&0\GȬډ\rTJT3\qj*o@y/ pÜ_Ksk0Fׁ\ f?ZfA !D8GY_ȦߥYIENDB`RaySession-0.8.3/resources/app_icons/qtractor.svg000066400000000000000000000511021356671433200221770ustar00rootroot00000000000000 image/svg+xml Open Clip Art Library 2009-05-01T00:45:54 A Gramophone icon by Andrew Fitzsimon. Etiquette Icon set. From 0.18 OCAL database. http://openclipart.org/detail/25563/gramophone-by-anonymous-25563 Anonymous clip art clipart etiquette etiquette gramophone gramophone icon icon image media music music png public domain sound sound svg RaySession-0.8.3/resources/app_icons/rosegarden.png000066400000000000000000000045401356671433200224620ustar00rootroot00000000000000PNG  IHDR szzbKGD pHYs  d_tIME $ IDATXå{pU{#7 ($@tZQ:m>FX:UQfd""h<ַ֚ugڙ*f} >s\׿WrT;"2#Ոp0(2sRsY=BJc5?pj=B|3k']\Z]#~QqpWo.+v'^"cjN/G5LWWIfnn[(}>}_zEI]UuIo^[ɕU|k3z-aD/&ƐI9#U=֗pVL7OΊpyn5Dl٪?'V]\ۛ}DPII pğ@Lۃp9jN.RLµņ7>ڛjY m pdDlX˦y\w kkk;y.(Q4Cpn/6S߰O |c;6m-VGWQ_α?v\キ P_'ۘ4U\45OQTm( :VQۯw^jH5/ʄ}]sH_z`o|GM8pIENDB`RaySession-0.8.3/resources/app_icons/samplv1.svg000066400000000000000000004357741356671433200217500ustar00rootroot00000000000000 image/svg+xml Jarle Akselsen RaySession-0.8.3/resources/app_icons/seq24.png000066400000000000000000000007231356671433200212660ustar00rootroot00000000000000PNG  IHDR V%(gAMA a cHRMz&u0`:pQ<bKGD̿tIME  *IIDAT8cO0 w `;9vy{\bH)$}9h&2=\?!-ĉW/_ߕ<T Nj%%% @JWﻒq8߭_'?.,>q\Д  ".8u`W??߯-?~sz@4#kTQ`ױz%tEXtdate:create2018-09-22T11:41:52+02:00$?%tEXtdate:modify2010-12-06T18:08:57+01:00F@IENDB`RaySession-0.8.3/resources/app_icons/sequencer64.png000066400000000000000000000043021356671433200224710ustar00rootroot00000000000000PNG  IHDR szzgAMA a cHRMz&u0`:pQ<bKGDIDATX͗[l\}Ι{f<=c=v\! "PE U)T/T*T-R(HB)5p@h$63㹞LfV0{׿{n?Wu2P<.͑"&Ii^G;u`,s8PH6  $R)!(;7Mk։[[_:sܡVPKK׉)x_<ߔb*ڌ 8ssdl*K4.9NCw7wF8'Չq#>vk+,ZFFF/m۰gӭN❞fgpf?/(YKK 4b+,8Sغ&4sX(Jwd vt)$MO=g>˾f 6kBeͲV<2? 7O`4>וqMJ^/ƍ8,>]-feYaʫ<=;CnvV-#@4 %,RMh{7v>^,)E$hYX睇zﻏ@|6o.P(^` p "u RJE^j)zQTd"PJF}?>Ʃ2 ,bᇳ\'a8@\ßir|}وhvd|WN8RIq3\w]7[R"jॲΣN, Rȗjֶ(@q3r|XR7+ _VfkcLލZinү/|;b< E:ndppPV%dj*!^o▹fY=<2 ?}0lt#Ə2 >a0"_]9~g[XVv)+h\uM!gRUr³6>Bs Q#{>fgΝ%[vo_{QZZu6'c_GR4\n0*lڿ|y@ +<ƓOj{CS>\z/6C\τm۴'n *FE|;9(T*ܳ3FF8æMiyX=Ϋe81u.۷Wlٲ>8$t MsXl%iduPr=G4O#F`%z0M6/+tCWW\ye#Dz)FUx}WatJucx|*QxoTò ڗRKS_ǘf]4n曕.h3i΢SsO*&ɦMi33< oG=@ GqlHgK| win5M9'`)J%5׆xWB Ç ?縚q|M8th3[q.ܷjNjJJuS b\wp ?gELy2˟ a c`Y2z2i:,Joɨ: q;H$ziZ〩K4Q45A0B04_D/hmkHLPHL^ޯfTi ÓXX`LNcНJ{{$wltPZ[/`/?y%H]t-Ōդ%tEXtdate:create2019-01-07T10:40:37+01:00 K%tEXtdate:modify2018-09-29T17:41:30+02:00MIENDB`RaySession-0.8.3/resources/app_icons/shuriken.png000066400000000000000000000117101356671433200221560ustar00rootroot00000000000000PNG  IHDR>aIDATx]{pSW~ιW_e<LH` Pvtt;I۝tn6!MBw4됬p PR2 a8~`[tuO#پe]tw XRۇ  .۷vuugB ?_^)7j4Y\YY///!䇁@vhA`h4PRR3ջk.Aٌk"//8Z]]]]4-`X1X,lڴ @s Om׌ <3VeT|dl۶ D)MUUU@mԄK|l&0ͨhRJO\rʮy#`X^o$5,VW0/0m6{nt:'cBU @)qjuuK-?Հ0U3Ν;A)-jMMM.l| ++ ;wۢUVUr;д~dZ'%?jE}}=._ܤ@)<\'ȯؼy3!wt_>5дcy~&i0\.lܸln^f0LNCUU 轩{?&A@UUz!ca E @UUt:!!EEE;@l`^ 1Ʉ*<gb6!;+˖-!DQR2,[ BM X6mT&`ٲe(--!@ jA%$It"0Ɛ LP]] GcP'c 999(++98R~z94+M&t:x)ɗCmm-NR'jė,hV30q`)G$##fs*%_򐟟?i͏g34+BH!<WAAn!_t`M|*,,51h|ڏg34+ 1b8& ᙉ)`&@LC f"Rl6_NbCp@tn RVVx[˴M @Se[^ L Ф(kĆ@|eRKn-?ݡI0jOeSJQ^^Ѩ,9={t'xGYYA0Һ x8Rt:|Jm^%K8LyIpW8?mT`F\hJ?a?03e{dt~n@3hkkRڐ*d2@fM CM*4!CUBAt<%IG4$"O6\wǤ? 36QMM Y>M̩t|r߻wѺz\ɗmAp(掎ԕBr嬊 h|1Ko !&ɳrʲ*,]<ϧ͟nii;#y̙-5R>|xq(j"ȘtDQp7JNhbAAAMO$I@SS0!dӧF)rrr J'^t:n1(Ԑ/xquEEEyA)z>mȗ#_}՚-^k.b;Y*++O{<PJӂ+WpIr ˶Y*icVp-!ͦ|Av755}x 3pUDD N;7 v;kkkKꦌ;Q@SS?8e9515|k"q t'*SJx>VLxg}QB!*eJB wqݺu6Ǐ tsj.?@gYMMM¿ &IS pBiy///H|@Ahnn^$YcD6n{dnk>n7(F`҂9}4ёDR\u:jll\MIxuq tNr)&8x9A$IZ߿Yiܼy gC" %L]ccL"߳Ld<3FT/oee%v;%D'NX\M~4b |>#IR !7Ȝ6? %Dl|>߷) ;A̩[[[ qܽ$%I$)$'`0(iNZMVSןvQJŒ10[F1JN=Y8|YTeZ Z[[ D L;^mtnnlXTğ,W_z@A+**R,  lL>l8gdR*uvvnhhhvi-@ qy`<>z꩸Z\_cgjooL̀@IEo= i/*bJ&y:;;qƍgR# "Xl<+jǜL!T$N:>ݴ/ۃ융U9EķtMeS,5H{_)"zzzF46DQ;dCɱ`i_!.]>@T@ (oL-f3g!į"Nڏ4@~c푃>d"S:FxRc6L]TC O~p8 Bz$2Tyhz*cJ4Ӹ|o"MNCOO ΆjC fgSK4 邴06OvB! )BD0WiӦΒML`0JY?_ `͚5W|>N9B"9O"4!ظq9b"@>}&@Ќ`֭;]f0us}Z 24%رc^ocFFF9r@Мࡇoh@L924)رc+.믍Fc|t x|ٳ ́\嵃*4-صk 'kdZUh^k׮.,, a`lڏ3`z?y^kKZ`߾}.((8qd 8E*`߾}8>w{/..~%ZǼ!_hQ Ǐy) H[ ޠtbue[? zuyyyoM^Uk@EEEt]AAi`Zżlذa? ֭[{nwV رZła˖-?t:G.MbAq}_0$TS {y|~YTT!'NRۏ,` H>zN'6YIIENDB`RaySession-0.8.3/resources/app_icons/sooperlooper.png000066400000000000000000000022041356671433200230540ustar00rootroot00000000000000PNG  IHDR gAMA a cHRMz&u0`:pQ<bKGDtIME  *uIDATH]OG{f`!D*4Դ ߑR+EBUlƞӋ5TEʫؙsY-;ՏKr !"CBT&oS$e0?> image/svg+xml Jarle Akselsen RaySession-0.8.3/resources/app_icons/vmpk.svgz000066400000000000000000000252331356671433200215150ustar00rootroot00000000000000َוѬ yEEhŮ)iK։1L\U2`>{XkG?ozÛwo?dsWo޾~wŋ^{{wOvy7oŏgq㳧O~}ᯯlܷ/ϟ\sS~{ryzy8|_}/Oo^]|I ߟy߼zDOJO}][IO8{ۋu:/9pFW}xtD'W?\r~~ٽ|w|ݏ/^YvɻpЪ>a{5Y?,^>ۧ7?3e>!qREJڒ:@Kh~ϟeyP~Ȯtt}_t49\;zLxen&9Ͼ'tt{}8o.D?8sֿw/.޿acJm۷Vsrї>PGw ߐ>(rמENilѓrJZmrmÐƾ:oKL巡N& 2c,& tSJSx!r sOJ=DJ&n7ʾ&rCRiP IZ۷$'u?]z5~RT!r %[/]ߏ7wtv;}^CC FY>Muz~ŬEږ*V/>Lr&S7} cUmR˜TIQ=D~am up^y}crOAnI-f' /oRV8LrK[P> L)2$"RJ7Ԣk3:W;9Qגb~RUk$%l $#q7wBBMܐ{і%O{ _TSc6A^n KS7R&BJ~覠|RP ΚqwZF} սjl7i4{KN9lU$VM닋э:Z9=Lᾚ'5 {R<WTyP( Uj:&Oz/oC^MSfPglmJxұM93M1 @_߳a"[D)ʇ\!"Ro?˃\Y81VT^oҦ?^7x0;dل\KOcBnVǺVYۨΖ]_i^//ϛ~akD!6$Fan^Vd#2_uC^2oIGcr>,ԽKpZ1y'ltn]$1勪}// u#Xc+kDA>J(41 ,iPGI.^"Qt4py[XOStpY꾆Fѵ Y| 9%aEc w=D.=o[%8y}1Lk-GHjGIY/7$̸%.ݧm0zdfފ!nr6Z}\ϛ:6UөC ;iIrwmX3֡%dlN-^=ܷuFze=WDXX8&9vEPj:/ci6B(1M}qNv$ kAG3z_ك"]gh{WBJҭ& `{W12 Vw..n֬б'-f&U5pd֊gH~& SZҺ^FXː}$ 'o#5w^(*Zռ=eR֐SZ*%j:CkUr+nH,T {\7(v7ubQ>NzCB|R[>y\=gw59xew_ D:J{UzY-}擛"T>^;d;{ʦ5b(EA*._[e2d9p!u.)mZ tXԻ.r:Zt8S)N +[u@/qT~T&3ń&-9~T$!VeKw( M|^vFᬣ/-5M^L@iuI(01k ʰ'N\*ƒ hq F)T] P%n*.8YC7}rUp_}gg={ի?|xήuם[t%>zg/׏wo>ѫ2>B?]n*4:A}ԾҗPϪ. T_^bO“),˷ 6Xj0ֵ"y.|Hr1#mpMJ!v3.+3uGl%rAV"jQVxȈ`}O, y-KGkNP.m"VXd,;ϮQT\k))SW RY^ḏCy|iKV󔈪*xIJP%X.G028MX\F)$Q,ћ=@(:?iGR^I"M O,ψ9rJFk'?u\=%$nO,%E`ghSHHݫN"Z]Y,>V^bq#v9.;[Eyoq}+5GeS%,A¹pe\s0拣]wo^>J_>+G~}E.ͰXzrgT9@;%*~sS^ʣnjTGfm5| 6Z7ͮRHU$WbiQ,g!y:@j]Y굛MA=qQ]u<_ !F|rǭ %:Hb(.|FxϚ@c;88Mzvi5$陳2d1.Y q I)KX; N6G rp-QE}{ƒ}ZK% zSŋyC!9ʴbԱ*1sɧjC\)FK~V(4s5PP3UNJczU:]=s5|)H>^CZn+WIVI_x!V݀ðaG"sPj22$}rpN"T^<4ihvMYRd2:R&w CkHa$AsHKoRI:]bH@+%O7it X5F3GْTVi )$(pS~k04gXUA2,zu]٭r+Yunq;k Pz맛DH}5Jelǒ<[4 F0r=u8X>V6 c&UJaGF/'|ZKr- |W@jcɵ:.5ּ\@'2↔Odo"''͟6dXtaR$Y|'9c?{)yU* ).\>6/.ttp)ʩ;[%`z^%,. )  q΍1UB/bqT^#a*v]Ku"2&aTUi]Þjxbh~1Ɉ3$帩@ܖ+zw^_ߗu%,ܫܰi&=+8ph"֐QذQra~ /;[^U$nkTx"DsAH%ڠ7Dt&]c/H AV@3^Fn{X8M,!G/9^@Õj ՉC 鴮p!eT|gYIyDeZH;tJBˇw/q=[M$Y޹V 7Q'yKˠud S4j5lBB %ыyg/(q XG'G`UdAF=&fkiM\dlFiM%&5C_JƤkYxZl#v0I+ 7*g\I :]ݐg %PihO+ .:}|Dl e*F/?o+,`g.*(B?J+kn((GmX#wPP`p,B{8$eh 0L D~J:F/n˰uƧzэXhxfv 'pHp?He˲aM2Pet$mݕcu C15w]V_$jd,'$<ȝV$J%Gy{#)PSWLRL*i4wl;32YVTZO) vEHфeaH&(]H@TXhCOQyb;û8+õY(b~NhoӁF0Ԇ[h+ƢD s+:EZJ]%PԷnЧ耄Xf8c33׳Ҥp{HX'iA2vc22ĹAbt}Z[,Y N!1@C5i5qӓ·ץŒ Yئ\-M%st (7Wm6`Fj$˻ e鱱QPar= c:f5qn'|bAnaZ#И24&FUd I GZb.%dD""{ ކȬJAÃtz>b@ydF%i ʼnN0?IWD^ b2a4S ̝I,mJdt@^u2'aQ\o_x\;Dger)r&s)-T~ckZ l%aXÂ1 QH8 JyہgK#Op]SX|I`@Yz_c{cd4dz]w14CKK {g~NF鿚I- 0El*g~l+@wgbgc}Dv ҲktP ~beҀ,eI|VHd1 زLjh('%k~(xz1mBt xXQgKx,}Jld.o^'Uq!"V(^QVAXk;E)=v"0Wy\}5H2v9Џv!sp䏩W"62\5q1NaSFΘE)Z(|ַ'mvA%"yw8o c PhU8fѓ zgGBq1gvlN2 M{!z7 "wZ%srYU!Bmvw jJ I1,JLzRtcs,_@0B]2c17 хpZHk&WCMn=ǹ P]Nғt\ӭ WtӀÉ)ORDՆXMk.9'RYԱ|-IMn4_$,dC>3VRiJ@9O63܅rdZk^FՓϻSMilucN*-Gf081!" Ź)SJ( < 0E(dAVm\8}ƹ][V'طXuXdBD*2}IfC0״- ttztI\̐/-QYE:/PVHπ,[0F<Њ4ML0"*TB/ ah20yd$ǣ9Q^uXA4ax44]c ِSq9wۚX˔t@ *UhgeekU[}{u&gԐ;R5#r9dOTqG]{uV}f[J+^P4ߓ1dDU*D Yȁ`D€|W("JC- r*#Bl.\qeJ<ζuݥhm.[u%GSDf%͚3pud\~79*{ }q$eZx:W h"qj:lA*I4494rH0ѧ :57SH&bǪ Ɯ_W"-X?:{փ!1(PWSB]EfL5^]:X겏#^Ã0pʉ N+fb=VuS m lx"ء:NjNlT#{&11432WjVQ4/0A@ p3:pe--ZKN'tufPRNrh5Dd@Lt6صsG\ΊrRX)O>|FĎ?7 ٙDQ,^Hɪ5Hf%YSWAd=cj0d( ZV6.ct1 \zrcm?yv\:`%Urj,;iTG\#][/PO4aFPeGo3`"]U(aK#\%^>oN:QsPh+DĶf@zH溒{,ғ񗳋)dPEkl0pRU OC.Ⱥ|HcA wUBӂ1hJ ZJP p*ZY0:6þ gיqqf8sjHPupH#eߋ7eH3@ULŸu}va#o4g.{*>2&Obrd?@#v!Q\n_!cΊ%cD!35R(^~%98U77j(RpApF{͋Wb_*\ 9I\J3nȐ0';%ӷ,rhL$g^3nT՞FWk[+je475B{ ǁeՑ|{pҟ\f (,s[(kW;@*y!,S*"6om'FG 9U~Yv$be?!82{]\[R! Ǹ{"=Nyvk-GM04w֫%kk$ћ t,Ml]f|?ƌ$Q#n8n'[-A#Ia}:lJ㸳d'X01uɌb必ø~Ἱ5R>eep#yޔ{AlۼvIU l ]  r+:\S*[l(@hd"Q x:ZHcZ'b` uH{Nn&*<2.~.XI|zZy[pC2WuCz [;mi+wo܀986k\*vga띇%-o+4zP=l eBc'-|b r.䆘^@E EOF^DofNp#mQsޭvhDs]yiOW;Hdi6"љΟ]zn5ȫS |IeS4nv) 9&O9[F7goY)qB~d;ϧ7VOZym؍S=#%Pj+uteYVf uz.eoa,M7Ut[!UlW ^k@(ْR٘Kknb`_{{liߤ73V@(GL8q:[vv5F@Ep̏F*Zо]aݰ҂ԲhhԽ,1\ܸBmN{g9L{N6$H[_[c5{-1Ce:N:'WFsCxI6o,W]d&UVe5ȿDR! I;'LQ(G-d[*\[8\4Cc[I d5/@2D'TZ9P0ߝDN?SC[o~% image/svg+xml RaySession-0.8.3/resources/fonts/000077500000000000000000000000001356671433200167765ustar00rootroot00000000000000RaySession-0.8.3/resources/fonts/Ubuntu-C.ttf000066400000000000000000012543541356671433200211750ustar00rootroot00000000000000PDSIG?GPOSr}oGSUBUѼ 6LTSHyjOS/2.5`VDMXs{cmapίcvt  k, fpgmvD#gasp glyfhdmxGb mhead_\6hhea $hmtxT8kern%6*z(loca&j8 maxp _ nameh`h 8post=slX,preps{#Ϥ_<ɯ83=1 CH1n /YC"O P [DAMA@G8 V 2HzF7/70,FL#+&94F,**2?-/99.+.V8? T7@STT 7@TTTTEET]8T]8T%N(^o:)Ma0012N0NANLNN0M0HNU$AHG=d! % ^ ""1%%:*088'='H2o:sU$*1HH,  ^):?=$?:2 + :kN)6:6 l)*&V7TTTTP@ET]8]8]8]8]84]8%N%N%N%NTL)))))))a01111>1N00000+(GGGGM)))7a07a07a07a0@S0@0T1T1T1T1T1 70 70 70 70@T.@JNATATNTTND)T)TLTKL ETNETNETNETN]80]80]80THNTHTHNU$U$U$AHAH_H%NG%NG%NG%NG%NG%NG(=d!d!d!NU$AH:]:*:2:*:e:H:L:'(=(=(=  gA305U30*530*5&3*5P,'+Tc2+9$+++dNNN%N%NLNWN1*of #j 1l )(    :s:]:U:sL8?^ % H2o*H*V?TM7z0@x,01-717N.T" TN TEN]8]80D80MTXAH AH%NGc2'Nd!#km+ni-N ^^*HSSF0@TTVT6TN)]80%NG%NG%NG%NG%NG1)))70 70T.]80]80k SS9$ 70KT TETN))]8()B.T1]80]80;HTHN%N6%NG a @T.)T)1/2d!)T1]80]80]80]80G<NG007a0U$d!~ A % T1q80Hk :*: :D::2:::::D:':':::TTDT7T- *TRTET2TT TTET ETETTC E@T]83TT7-=T8TT! TT<T)ANKN 1nNNNbIN0NMa0xI(N,NN NN~ N-11KN1U$ANNNN1']801 JTNTMTPNM TNU  n%TN,TN8 LTN{TN%TNj817a0x d8*8%TN{ { T  TNH @TNETM8,EdIT)))T17171 nk ETNETN]80]80]80~ 8,TJMTNT 6 TTT@T]8TTEET-]83TT-k?c20N0-|&N5LNN|(0NHd00XHY0jH7 z<w;|0-NG0HHA7:t:\:00000000|GhBB------  NNNNNNNN  %21DD000000B.HHHHHHHH777777777LB00--NN%00HH7700000000t?`::NNNNNNNNuu77777777z?50000000 :[:S:[:*:NNNNN%%3T::7: __:::HHA@HHHHEc':::77777U2:T:P|GhBB  %LB@Tc24 HI* //& 1 !#**!!+ ! /&%/   /'///      11  &&&&!!!0*  *** . !!!*/-11&.       /1  &/    !!/- !& &1&&/*! //10 11// &&& //&  /-- 1 &/&/ /&. 1+#  !!& $ "1 %0 00&.!!!$ /.Z 4  !!""##$#%$&&'&(())**++,+-,.-/.0/102131425465768799:9;:<;=;>=?=@>A?BACADBEDFEGEHGIGJHKILJMKNLOMPNQORPSQTRUSVTWUXUYVZW[Y\Y]Z^\_]`^a^b`cbdbecfdgehfigjgkhljmknkolpmqmrosptquqvswsxuyvzx{x|y}{~{|}~                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           %                                                                                                                                                                                                                         %'                                                                                                                                                                                                        ' +                                                                                                                         !! " #" !      +!-                                                                            !   !                                                  """#!!$$!"!!     -%2               "                                          "                     %"$  %#                         "          !! !!'&&'%%!"((%'%%    2&*9    #          " '                           '  #$  #     #    *'##)#  *'##     "" ! # "   &      "! " #" !## "  &%#$##%%,++,**%'.-*,** "   $ "! " ## " 9(.>    & !#     &* # " !  ! !     "        #  ### +!!!!!!!!!!!!!!!!!!!!!!!!  ''  & " "  $&   .+'&-&! " .+&'"    %%!$$#&  !% " "#*" !   %$$%## &%#% " '&#%## ""*)&(&&))0//0..)+22.0.."&   '%$$%##'&#%##>-2D     ) $&  #  ). & % $  $ $ ###$ !  !     &  &&&/$$$$$$$$$$$$$$$$$$$$$$$$ *+ ) %  % '*"   2.**!1)$ $! 2/)*$! ##  )) $!''&)"""! ""$ ("   % $! &".% $!       ! "!!" (''(&&  "*)&(#%# "**&(&&  %%-,*+)),,433422 -.662422%!!)  #!#!+!  (''(&& "**&(&&D16I #   -'!!)  & ,2) ("' & & &&&'!!!!!!$      #     #!!!)  )))2''''''''''''''''''''''''! !.. -("( *!!-%!62--$5,' !'$!! 62-.'$!  !!!!!!%% ",, #'!#**)-! %%$# %%! '  "+$!!""# (!'$#!!!)$1(!'!#!!  !#""" ##"!%$#%##+**+)) #$-,)+&(&#########%.-)+)) ##'(10-/--00877966########02;:6866'$$, !#####&#&#.#"!##+**+))#%.-)+))!I5:N !%    0!!*"## ,(! /5, +$) )) )))*!"###### &!!!!&#"""&"###      ,!,!,!,!6************************ # $ 120+!!%+-"##0' # :610&90*#     *&##:601"*&###  ! ####!(( $"//""!"%"""*!#!&!-. ,0#"(('&(' #!")#  !"%/'$$%%%"!"+$*&%""###,'5+!#*"#!&$##$&%%!%&&%"$""(&&'%%.--/,,%'0/,.!(+)&&&&&&&&&(10,.,,&&*+43020033=;;=::&&&&&&&&46?>:<::* ''0"# &&&&&(&)&1&%"$""%%.--/,,&(10,.,,!#N8C[" &+"7"#'#'#1')!)"%!3  /  &7>32*0 00!$///""""""1''))))))%%%%!!,  """'"'####' ' , #(' ' ' + ' )))"""% % % % % % 3&3&3&3& >111111111111111111111111"%) )$$%%9972'"!!'*"$"#2#4' ))8.$"% )%#C>88,!A71")% % % % % ""1,$##))C?7#8#' "1,)""))""% % ' %%$ "))))&.."#%!*# ' 66(#' &""#'+''#'1')&! , &"45%2! 7"  $!!!)! ' ..-, .- ! % )& '!!!"0(%%"&'!+#6-) ")"""# +#+#+$#'!'!'!"2)""1,$$+$'!'!)))    "2-=2"#"')#!1')&!, )) ! !!  !()+"#**&+  !!!+ !,+')''##.,,- ++644633 !+-8735!!!!!!!!&/2/+++++++++.983533  !!++ 12=;8:77 ;;FEDFCC++++++++<>IHDFCC ""#!1 %%--7!!!!!!')%+++++/,/,9 !,+')''++644633+.983533"')[CKe&+ 0&   >&'#+" '+'6,-$-' )%9#"  !##!#$ 4$###$+  =E98/5" 5" 5 %("555&&&&&&7#""""+,----- -))))"$%!!!!!!2!!!!#$##### #$$$$#&!&!&!####+&+#"!"!"!"!"!'#'#'#'#+$+$1$' - ,$,$,$1$,$-#-#-#'''   )$)$)$)$)$)$9+"    9+9+9+" F666666666666666666666666#'*. "  .(())?@ >8""#+&##%#%+/&##"( &(8' :,$--#?3(#' # )$.)(" !#"!KE?>1$I>6&!-#)$)$)$)$)$!&!&!72(#'#' -#-#KF>'#?',$&!72-#&!&!"!"!-#-#'')$)$+$))($ &!"!-#-#-#-#"+44&##')%#!/#'#"",$==-',$*&&',"0 ,,'+6+-*$# $1#+&:;)8%$>'!#"$!(%% %.%#$#,$"43"2 1!!!#33$ %$#)#-#*#,%% $#&!5-)#*#& +",$0'<2.$# ""& .&&!&!'$0'0'0('",%+%,%&"8.&!&!72"!(!(!0( ,%,%-#-#-#$$$$&"82E8 &''" +-'%6,"-*$ "1#..#%#$% %#$##$-.1&'0/+"0#$#$$$1########$%10,.++''3213$$$$$$$$00<;:<99$#"%!!######03?=9<$$$$$$$$+4851111111113??9<99##$$##$$11########78DB>A>>$$$$$$$$BBNMLOKK11111111CFRQLNKK#######&&'$7$$$$$))22=$$$$##$$"",.)111114151@$%10,.++00<;:<9913??9<99&+. "eHSp*/$6* $$$$$$$$$$$$$$D*+'0&#,0!+#<02(2+!#.)?'%$$$''$'(#:(&''(!0!!$$$ D L!?! >4;%$$$$!;&$;$),&;;;******='&&&&0022222$2....%(($$$$$$7$$$$&(&&&&&$&((((!'!*$*$*$''''0+0'&$&$&$&$&$,',',','0(0(6(!+#1######0(0(0(6(0(2&2&2&+++!!!###.(.(.(.(.(.(?0%!$$$!#?0?0?0%! M<<<<<<<<<<<<<<<<<<<<<<<<&+.!3$ %$$$$$#3--..FG#""""D>&&'0*'')&)04*''&,$#*,>,#"@0(22&E8-'+!$&#.(3.,&$!!$!&%%SMFE7(QD<*$2&.(.(.(.(.($*$*$=7-','+#2&2&!SND,'F+0(*$=72&*$*$&$&$2&2&++.(.(#0(..,'$*$&$2&2&2&2&%!/99*'#' +.)&$!4'+'!&&0"(!!CC1+0'/**+"0&5#00+0<02/('#'6'0*@A->)(E+$'&($,))#)3)&('!1!'%99&8$ 6%$$' 98(#)!('.'2&/'1))#('"!*%;2".'.'*$/&1(5+C83('#% %"*#3+*%*%+(5+5+5,,%0)0)0(*%>3*$*$=7&$,$,$5,#!0)0)2&2&2&( '!'!'!*%">8L>$$$$*+"+&$02+)<0%2/($#%6'33')!' ()#!)! &(''(2!36*+54/%5' (&(((6''''''''()750300 ++9768((((((((55BA@C??(&&(%%&&&&&&58ED?B((((((((/:>:6666666669FE@B??'' ((&&((66''''''''=>KIEHEE((((((((IIWUTWSS66666666JMZYTVSS'''''''**+(=(((((..88D((((''((%%03.66666:6:6G()75030055BA@C??69FE@B??*03$&pPJ ~17HQS_awx67O_cuEMWY[]}    " & 0 : D p y !!!"!&!.!T!Z!^""""""""+"H"`"e%  28IRT`bxy78br HPY[]_    & 0 9 D p t !!!"!&!.!S!U![""""""""+"H"`"d%gfBb'aII {A;+߷2nUyT04*ߙߖߎߌ߉߆z^GD# vhlNVTp<hisj`lxmynzopdqrftubcdevf   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ah~sw}eg|nztuqrop{ocmyidfk jvbcdfe`@?XUTSRQPONMLKJIHGFEDCBA@?>=<;:98765/.-,(&%$#" ,E#F` &`&#HH-,E#F#a &a&#HH-,E#F` a F`&#HH-,E#F#a ` &a a&#HH-,E#F`@a f`&#HH-,E#F#a@` &a@a&#HH-, <<-, E# D# ZQX# D#Y QX# MD#Y &QX# D#Y!!-, EhD ` EFvhE`D-, C#Ce -, C#C -,(#p(>(#p(E: -, E%EadPQXED!!Y-, EC`D-,CCe -, i@a ,b`+ d#da\XaY-,E+)#D)z-,Ee,#DE+#D-,KRXED!!Y-,%# `#-,%# a#-,%-, ` <<-, a <<-,CC -,!! d#d@b-,!QX d#d b@/+Y`-,!QX d#dUb/+Y`-, d#d@b`#!-,E#E`#E`#E`#vhb -,&&%%E#E &`bch &ae#DD-, ETX@D E@aD!!Y-,E0/E#Ea``iD-,KQX/#p#B!!Y-,KQX %EiSXD!!Y!!Y-,EC`c`iD-,/ED-,E# E`D-,E#E`D-,K#QX34 34YDD-,CX&EXdf`d `f X!@YaY#XeY)#D#)!!!!!Y-,CX%Ed `f X!@Ya#XeY)#D%% XY%% F%#B<%% F%`#B< XY%%)%%)%% XY%%CH%%`CH!Y!!!!!!!-,CX%Ed `f X!@Ya#XeY)#D%% XY%% F%#B<%%%% F%`#B< XY%%)) EeD%%)%% XY%%CH%%%%`CH!Y!!!!!!!-,% F%#B%%EH!!!!-,% %%CH!!!-,E# E P X#e#Y#h @PX!@Y#XeY`D-,KS#KQZX E`D!!Y-,KTX E`D!!Y-,KS#KQZX8!!Y-,KTX8!!Y-,CTXF+!!!!Y-,CTXG+!!!Y-,CTXH+!!!!Y-,CTXI+!!!Y-, #KSKQZX#8!!Y-, IQX@# 84!!Y-,F#F`#Fa#  Fab@@pE`h:-, #Id#SX<!Y-,KRX}zY-,KKTB-,B#Q@SZX TXBYY-,Eh#KQX# E d@PX|Yh`YD-,%%#>#> #eB #B#?#? #eB#B-@ρvn)trd_rorrqF@4q FpnHnniagedeH)cadaDYQ)XU*WUdUUHTF@zTFSQHQD MDLD)JHdIH*HFDdDDDDCABA*|U*{U~*zU}*yU/z_zzzz0z*U * U*U*U * U*U*U *U*UTSKRKP[%S@QZUZ[XYBKSXYKSXBY+++++++++++sst++++++st+s+++++++++t++++++++s+++st++ss   GZ`NHPUFVZpJMGT>VZKL@Z`wNPwHr2FIAA""""Xj>,v,XRr` X d x ^  t 2 ^ (@t@(Zp$Dd>Nf$Xr(b $ ^ ! !Z!Z""6""""# #F$$$%b%%%%%&&&^&&''''(*(()j))**+,8,,,---. .//(/n//00H0001 1 1111122333334 4$4:4P4j4455$5<5R5l56D6\6r66677777888.8999999:::.::::;;(;@;<<(<<h>~>>>>>>??(?4?KrKKKKLL(LNrNNNNNOO@OTOzOOP P@PXPpPPPPPPQ QQR,RRST>TNT\TjTxTTTTTTUUUVV(V@VXVpVVVVVWWW.WDWZWpWWWWWWX XXYYXYYYYZZZ["[.[~[[\\\h\\]4]<]H]T]\]d]l]t]|]]]]]]]]]]]]]]^^^^&^0^:^D^N^X^b^l^^^^^^_____&_._6_>_F_N_V_^_f_p_x___``aNabbfbcHcPcd dzee>ef2fxfgzgh h8hiiPijNjjkhklmmnnoop(pzpq>qvqr r\rs*s~st^tu(uvvw wrwxndhܶl Fvr~PfȽ<F*NV^ŽºpJĪ jż^Ƣƪ@n 6ɦtZ˦>̌&Ͷ;"*@T\0JdltϊϠϺІќ2Lf|ҐҪ&LvӆӠ.6*Xְָ֨HPX`hpx@Hض0~ڎHۺV^fd4t޲d$4N^n 4L`|.F`x,8DXl&8Lb|(:L^jv 8JZl~.FXhz $753.#"w;=M&A<",*!-$*M1,$1') 8[~C3)&  +9(3+!vo D .2$ +90U '3ݵ' H# H!@ H H MM MMMسM@ M Hس H@J H H/ 5("". 41o%%+` ?]?3?3?]]2/]]]3/]]10+++++++++++++++%4632#"&74&#"326#4632#"&74&#"326k;::;;::;N;::;;::;Z^^ZZ^^ZC66CC66OKZ^^ZZ^^ZC66CC66,*4@@4400&&%fEU@{ Huf*:J     VE&8/;p_!!!.' *ppB5p02pA&8 .'/** >t+t ?3??9/993/]3/93/]910]]]]]]]]]]]]]+]]]]]]]#&'#".54>7.54632>7267'>54&#"& W  N(2G-)'NB$4!)w 7&4 +! a?k+"I! &&) 3@'?2*&U2FU(4"7.& N.!!F40@";>-&&-F n  /9/10#.= , 21j66i31LE [ M  @L MMM@M  p /3/39/2210]+++++]+.5467>8E>II>E8>+^&aa&^E V@9 L M9 *  MMM)9  p /3/39/2210]]+++]]]++4&'7'>{>8E>II>E8>+^&aa&^#`,L@$"#,+  '.-+,,#"/2239/329/33]2292210.=3>?'.''7>7./7B9X6B3G G5B $AA% T )4>>4* R+v .@/n    r/329/3]2]103533##5#+JJPI&fr @ p  9/107'>54&'$=r@v73i.  T //103#TQ9j @ q u?9/107#"&54632! !.$$$$G(0@ M MY?3?3//+}10++#36PP4z'x@Uy%%v!!v{     c )c(#g g??]10]]]]]]]]]]]]]]]]]]4>32#".74.#"32>4(=,+>((>+,=([fT%%TffT%%TfRnBBnRRnBBnF A@#u  a   ?33/]?9/]3/310]]>73#F)&! BV-+ %'K- ,qz@ u M M @4M4%jc  c e g?32?]2/2/10]]]]]]+++]]]6323!54>54&#",;UQV*2."-6-**/;UZ*LGFIO-NChTHEI,5/*j(@P4%%%f{}c&&c*  )g#e # g  ?32?39/922/2/3/210]]]]]]]]]]]]]]]#"&'732654&+532654&#"'>3227]^%F339?E<:.&/"G#NT-u[H`oF?EEFI@965?bQ9O*ع M@ M M(M0M(M M(MM0M M M@M M@(M 'MO@Hxk\J<+i a ?  e ?3?222]]22+}]]]]]]]]+q++10++++++++++++++7>733##5#7*8<7XDDO''$ >h;GGRPZ],2j]@~]mL{c a@ Hgeg ?32?9/]2/+323/10]]]]#"&'732654&'>73^X5H*<+-0BY[jH` n^5R8GFHOE2SOO-K?)@v"";*   @6 H& 6    'c +c*g$g$g ??9/32]22/10]]]]]+]]]]]]]]]>32#".54>7"32654&\&?2!'HOKQ)>)AiM ')q1P< qcjz IwVKoF   .S?%SEKG-z%@cc  e  ??29/103>7#5!s$$M:3&0orsiZ"LC']/~-=@~4411,*v&##|#5&:) )9  9 * 6 $ %36c c?.c(c> 3%3+9g+g ??9]3/3/910]]]]]]]]]]]]]]]]]]]]]]]]]]]#".5467.54>324.'326>54&#"u9&*>/< %>,7.&0-87,K#&-'!1 #( :_#Wa%Y<-A*)@_-' H66A; 1% A4=/$9z'@'v''#{! &6v@^M M9 (    M   M:)c)%c   (g"g "g ??9/32]2/210]]]]]]+]]+]]]+]]]+]]]]]]]7>7#"&54632274654.#"W&A2#'"HQKQ)>)BlO'+G0O<qcjz IwVLoEU .S?%SEKG96f& @ H+5.O8@r/332222/10]]]]5%.-%%>AA+@  rr?2210!!!!+XXIxI.O8@}/33222/210]]]]7'7'7S%%-OAA>@".I@*  } p n#q))/0,u&t?32?9/3/3/2/10]]]]"'>32#&54>54&#"&54632+"@'-@(!%!N",! !yB+8#7/+/6# #5,&)/)-$$$$?g?N@luNvJ>c:R::k6Z6[0k00#5#E##v"7"v7DED))F=PLF F3OFIJ J8B@J(%J)./322222?222/210]]]]]]]]]]]]]]]]]]]]]%"&'#"&5463232654.#"3267#".54>32%27.=.#"^&1+QT_V*C)!?cF>fI(&Fe?.? $)M^47`JOY0P0-&+vrk}*%ngBrU00]VW\/E3iplk65eZLNRIWF@MMMMMMMMM@MMM M@MM M'R  'R  'R  L 'R'R 'RL0`p  I ?3?3?33r9/32]]9/++qr]r++ć++qr]r++10++++++++++++++++#.'##>73.+/--Z W.-/...Km-X,,X-mKfSXXT /~@T)q)))%%##w#s|mYjz$F!!F'1 D/0$ IJ,J??9/923/210]]]]]]]]]]]]]]]]3254.#"32>54&#>32#"&'>|"-%2%CG#I$-Q>%4)3B#@Y5#L)p#-  1$>Fe )C2>ORH6J/7a@?uE6F7 u  u h Y  F J J ?22?322/10]]]]]]]]]]]]]"&54>32.#"32673z%BZ5,L7$PHLV)3KaT%KGS  Z@<;{{:  F EF"D!J J??10]]]]]]]]]]]]]]#"'>324.#"32> )MmCDL&H!DmM)`.K4 5J/[aT& &TaJhBAhT 0@  D I H H??9/22/2/103!#3#3T7MKMT )@  D IH??9/22/103!#3#T7MK7 s@"H'7w6 ' IY@&MD"F!J M L J?32++?9/2/10+]]]]]]]]]4>32.#"3273#".7$C_;-J6#WL*<%ZP,6\C%[bW'KLlD  +YT ;@! D D H   ?3?39/^]]2]2103###33ZZZZKBT@ D??9/103#TZZK<&@%D J ?32?10]%#"&'732>53<)C0&F9! Z.I4G  /$TW@/  L  L D ?3??9/33?322/+}ć+}10#.'#3>77:;FD<e7=?ZZ:92"RWT$[eh,.`YL!TTMT@ DH??10%!3ZMME.-M(MMM@6MMMMM)(M(M'(M'(M'M'(MMM@\M(M0L(M M M +.'.M'"$M "D!0 D /'." ?33/22?3?32229+}]3+}]10+++++++++++++++++++++++%.'#>73>73#.'E*'$V Y./,*.-Y V `Hu`$9BZK+w66w+KZB:KX]VGTԹ @ M M @ M M سM @ M M سM @MMM0M@=MMpMPM M  DDpM8MKk{ M H?2++??3]++?+}10++++++++++++++++!.'#33=CF"TK&G@9T;;>{7K8%'|@Xx&%)%9%!&!6!u w&6:)yTEI Y IYRDF)F  (J#J ??]]10]]]]]]]]]]]]]]]]]]#".54>324.#"32>%=]??]==]??]=`$9))9$$9))9$ZQ_44_QQ_55_Q?hK**Kh??hJ))JhTV@6jYkZ usFD HJ?32?9/2]10]]]]]]]]]632+#32>54.#"TFC4U=!">W5*ZZ$"7&#25T;324.#"32>%.H0VD(I;) 8Q6=]??]=`$9))9$$9))9$ZGx\= */ F $8,9^~LQ_55_Q?hK**Kh??hJ))JhT'|@NJ#j"j  s D uuL    F)D( J%J??3?9/2322/3+}10]]]]]]]]]]632#.'+#32654.#"TFA4T32.#"#"&'76;&2)3G)(D11;)0'3M40H7><3*"$0@+*D0G53*"$/?+'F5H @D H??29/10###5ZMhMN9@$s { DD  J ??310]]]]]"&5332653aaZ.<>-Zdnw)OOQM!upL@) L L   ?22?3?39]+}ć+}103.'3>73.-,^ $%%$ [,-.KnM??MnK((@MMM@MMMMM @aL#L(#(M  L  M0#@###  *)@'((# ?3?33?33?33/33]]]]9///]+}ć+}ć+}ć+}10+++++++++>73#.'#&'3>7 !" X9&[#" "#[&9[ "! c<|38P1x>>x1RP83|<v@B   L  L ?3?3?3?32/2/+}ć+}1073#.'#>73wZ/-(Y #%&" W(,-\-aa\(GJM$$MJG'Z_`,I`@5LL    D ?3?3933?39/]33]+}ć+}10#.'3>7).0Z2.*aB##B/jml1 1nnl0OLLO7@ L H H?2?22/2/+}10!!5>7#5!JIA(.11-Zn56M7(`ghbW!M^G@n rr//9/2103#3#^ccDDG(%@Y?2?3//+}103#PP8G@n rr//9/210#53#53ccD@D8P@0vym_;K?33]3/3/332210]]]]]]]]] 'HqqI`Js//10!!wmIF #@ //]10'Yv+ %)j*Q@1   &6#Q,S+&X#!X W?32?329/2/210]]]]>32#".54>3254&#"3275&#"V:"FX$K$$?/,<#!(-*)0, " W\ (=,)=)0:. ., #M O@2{ j Y ZjzS Q W W?32?32?3]210]]]]]732654.#"5>32#"&'7 :9&!!1F,cj%HVF]l8L.K 'Ge? 0RgM@;MM M   S 0 W W ?22?32]]2/10]]+]]+++".54632&#"32676H-_`.&63 ) #5 *Id; Gbg+I4 C 0 O@1 t U e euV Q S W W ?3?32?32]]210]]]]]&#"32677#"&54>320!&9: VH%la-F0!.L8l] ?eG' 1!_@6@ +  Q U X?22??22/10]]2.#"3##46,VL D'IJ=VU]0D+m@Eu'f'U'U#e#u#  +Q-%S ,W+(( "W X?32?329/32]2/210]]]]]]]]]%#"&'7326=#"&54>32.#"32672C& @418$^W1N8%GV <7628M0H 8D v?eG% 8\h^MNH@-y   Q Q W ?2?3?32]210]]]]]37>32#4&#"NV-!:+V(7$2U?6NH >A-@ TQ/ x ??^]9/3/103#3'"&54632VV+!!!! ]D2@TQ/x W??^]9/3/10#"'732>53'"&54632L?!   V+!!!!VJ I"$]Ne@   YYQ@ H  ?3??3?39/33+22/+}ć+}10#.'#7>7*M&2.(^(,,VV)($  Hw4FKK"!HE>#<=:L &@Q   /  U ??3^]9/10.57J@V# EKq~#Nr$b@>6   Q$Q&#Q$$ $$%$   W " W?32?32???]9/2210]]]]]]>32>32#4&#"#4&#"#N#R"#9<$$;*V$2'V$2!V 2U?6NH :$6NH >NQ@ (M M (M M@ M Q Q   W?32?3]10+++++>32#4&#"#N%V$'@-V(7$V 2U?6NH >0'd@Gx%i%f!v!fviy   S)S  (W#W ??]10]]]]]]]]]]]]#".54>324.#"32>-E//F--F//E-Z $# #$ >eH((He>=fI((If=,I55I,,I44IMG M@1{ Z j jzYS Q W W??32?32]210]]]]]732>54&#"#>32#"&'!&7< VG%7O1U`$O.L8l] &GgB 0G Q@2 v e V ucV Q S W W ?32?32?]]210]]]]]]].#"327#"&54>32#0 <7&!$`U1N8%GV]l8L.K BgG& HN@ 0@? O  Q X   ??3322]]10&#"#>321 (% VK-0 @$;-@TV%$%4%D%%%y;K* K K,S$/**S  .{j'+X*' X?32?3299]]]3/3/10]]]]]]]]]]]]72654.'.54632.#"#"&'7&'" QE4$ &%QN(@3=+# #-AP E %# #/!ET GH25@)9 Q  W U?2?32]210]3#3267#"&57'! ).JIV J6+ ES`G A@)u Q Q    W ?32?3]10]]]]]%#".5332673 %)))D0V/0 V 0S?CM?~ ,#$L"MMMسMLMMгMMMMMMгM@M #M !M M M M M M @#M #M L M M M M #$L سM M M L L M #$L !M سM M @MM M M 0M ML $M "MMLLM0M(MMM@M M Y  @$M Y   / ?3?3]3?39+}++}+10++++++++++++++++++++++++++++++++++++++++++++++++++++++++++7>73#.'3 V #%&Y&%# [e(lsr/B~..~B/rsl) (*O*(@ M'M'M&@ M"M!@M!M!MMMM M@ M MM@ MMM@ M M @0M M M  L  MMM M MM @MYZ$$$ YvgV MX Z$$$0 @ P $ $$ Oo**?*)@Mo $@M$d$$ @Mo    ?33]+?3?33]q+?3?3]+3]r]]r9///rr]+}]+]]]+}ć+}r+}10++r+++++++++++++++++++++++++++++]]3#&'3>73>73#.'W7U Q R7Tg +syq((tzs''r{t((rxs+gano..onay ~@P  M      Y  Y?O_@H   ?3?3?3?32/+]+}3/+}10+#.'#>7'373%$ U53R !$%WZ[TFHH!:i..i:!GHED "@&(M0M0 MYiyC 1 $   سM@nL8 M|]mOXI6RY t S c @ 3  6RY   M$#@H X?3??39+2/9+++qqqqr+++qqq+10++]]]]]]+++7>73#"&'7326?.'30U#%%#)0!$ $ '%"[mdrIu08M/ F **4E/ppi!F 7@ Y U U ?2?22/2/+}103!5>7#5!?871275%fkg&J>,lla J%G=@! H H rrr//9/99/332210++>=463"3"&=4&'%! QM*"EE"*MQ !L?8VFC+-u))u-+CFV8?^Gn//9/103#^QQ8GD H@ H nrrr//9/99/332210++#526=47&=44! QM*"EE"*MQ ! ?8VFC+-u))u-+CFV8?xC@ HYJ9 @ H ?3/2//10+]]]]+23267#".#"'>*#;))# ;)x( /&( /&3@(M'&M& M M M@g M LkM  L M !5c2,)/3@*`**42),/ !$ /32/3299//]]3232q2222222]22/2/]2/]10++]+]++++++]+3>32.#"3#3#3267#".'#53&45<7#R &5@#2#35 | j <5( 9+F4#PLLH^8E SUB ,B^O E<^CB   "r @ p  9/107'>54&'$=r 9h1-\)  DuH@)U D % 5  Q UXX?32?329/22/10]]]].#"3##"'732654632b .)IB !VN)41QI&LUF".{U["Jr&ddj&' @%o[*@Q    /?9/329/3210#.5#535332vvJvUCDUHH%o[F@!  Q  ?/99//32329/33222210735#53533#3##.5#%vvvJvvvv2vHHH)IDA!!ADI)*E*@ M/223+/32210'7'T*ss*IE##U0f '3?K@> M> M;>K>[><@ L332!#3#3!#".73267.#"8)KjA. (AkK)`Yb cY[_U'MKM'U_8 @  n /9/10&454679$= 9h1-\)  ' @ n  /9/10'>54&'$= 9h1-\)  =f&oo'O&pp2  //]10#".54>32****a))))sQ ///10!!wQIQ ///10!!QIn4@ H@H  /3/3//10+]+#".#"'>323267 ' 2( 0303lB$,_@/$   P  .&'*+-'*, ), ?3?3?2/2/2/322]2239332310.'#>73>73#.'###5< KK <P@P8972GQT499994TQG37:8 99$;&V`@ 1/*#P0O++4*?@@$o/222]22310]]]]]'7'^BB1)=H@}u@f@z;i;f7v7f1v1z-i-#%###    euCS**DS& &J4SIUCC>X!/W9W W ?322???29/2/9/29910]]]]]]]]]]]]]]]]]]]]]%3267#"&'#".54>32632%4.#"32>7"34. -#1  =*6JC./F--F/.C/R->' $# #$  &?/E ,**,(He>=fI(.+Y$B_:  ,I55I,,I44I+:!!9+r&<D@) P!OO+++44HG "@n q u /?9/3/1074673##"&54632U 3 ]j! !TDDT$$$$Hq#\@5 z 9 9{f fQ!% S$W!#W/222/2222/9/3210]]]]]]]].#"3267#5.546753("94 ,!$.O$1 ?BO, G [a)D1 C  /BR/e,!T@0 # a 0  "g e e ??39/322]22222/]2/10]]#3!>=#5354>32.#"3S  EE/@& 5'/2/ 1t3JFIE.G0 H ;8nL'@H&&$     ")(    %/93/33/32/33/33/33/393/33/310]]]]]]47'76327'#"''7&732654&#"C;:3#*+!4:;;:3!,+#2::F*%%**%%*\>/Q2GG2Q/>=/P2EE2P-?;AA;;AA "z@ALL   !D$#   !  ??399//]32332?39/333223܇+}ć+}10%##5#535#53.'3>733#3yvVyyy\,>U    R?+\vvyyyB^BgK'SSO""OSS'KgB^^G!@n /3//3/9/32103#^QQ)h<O@f+Ii6%'E'''t#EI+ |j)J$A&@MEpC"=3p%%Q=pp--PM8M*"CC .0t-*t /32/329939932/2/2/2/999910]]]]]]]]]]]]]]]]]467.54>32.#"#"&'732654.'.7654.'.')0+9 ';1"/#)!*->%&D7;$2 #-$O$,45K5&"5$A $# '2!3I7*#7&B'$ &5/% &A" 8j( %@H@H  /22/++10"&546323"&54632J!!!!!!!!j?'?@?*?6622*)Y&&j%+%%e!$!! U Ve$i,Y*p{ y & %r*--;A4( @.1-+:7;=##?q22?q222/10]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]#".54>324.#"32>%4632.#"3267#"&0Qi::jQ00Qj::iQ0D!3254&#"'>  &!1 + 69E!+!$ / ;!- 7<* "" : ? e @H @'H     /33322223]22310]]+]]+7'7'7BB^BB^\o//10%#5!5!oJXIT//103#TQ?3G@FYFjE,EEeA$AA@W@V<<f;#;;j7,77X66(2s1|-%,%(|'r#*"4 s d R 4 D     4 I@>*H 99/CC% ?q2?q9/2]q]2]q3/3210]]]]]]]]]]]_]]]_]]]]]]]]]]]]]]]]]%.'##>32"32654&#".54>324.#"32>))B1=)'0Qi::jQ00Qj::iQ0D!324&#"326!+,!!,+!>""""},,,,  + 2@ n  rr  /33?9/3222103533##5#!!+JJXVII 2 b@?   t b T C " 2  y " !} z??323/2/10]]]]]]]]]]]]3#54>7>54&#"'>32 !1/<S*%!%A.(% , 58+-@'e%D%T%sUeD& M& M@+ L  #/*. +*(~}?32?329/92/2/3/210]q+++]]]]]]]]2654&+532>54&#"'>32#"'7o"-  .* !1!-*k ;  6" )4 *!< kF #@ //10]'7+v֐%NG <@${Q Q    W ?332???]210]%#"&'#332673 %&% &7VV/0 V  C$L?:)G#@  /3?9/10*#.54632#.0HS[r K#H tl^mo )6b q u//10#"&54632! !&$$$$6C`@"teTRbr@ H  3/9/9/32+10]]]]]]]]7#"&'732654&'>7<,$   # #&*7  6  2 +@ z @My?3+]?9/310>73# :3F"a*}! )LC P@8 # 3 +;*:%5  }z??10]]]]]]]]#"&546324&#"326CO>>OO>>OF%"!&%""% _^]`a\\aB==BA>>*? Z@4 opo  p  p  /222222]]2233310]]]]'7'%'7'^BB^BB&'oq |?55&'o|?5&&'o$q 8|?55DE".Y@6z* :   #q)n) p ))0/&u,t/32/9/3/3/]2/10]]]]]]267#".54>5<'3#"&54632+"@'-@(!%!N",c! !pB+8#8/+/7# #6,&)0*-M$$$$&$C^@ PO++4&$_@ (PO++4&$j]@ PO++4x&$v^@ 'PO++4r&$^@/P'OO+++44:%1@%X%i$$$#f!!!V @S H Hx&@, " L #%  L32)/ I#"""/ ?3?3?3339/]329+}ć+}3/10]+]+]]]]]]#.'##>7.546323.74&#"326P--+Z W+,-2##2V.." Hj-X,,X-jH "'..SXXc@4 L  D IIH H??3?299///2/2/9/23+}10!5##>7!#3#3%3e"WJQV)b-V(-Y+WJMKMmU]7C2@700r05* r iZL ZjIsUeзHS@' H2 !!/4(F32 3.+J/2 "%J!?32?3329/2/9/32+10]+]]]]]]]]]]]]]]]]#"&'732654&'>7.54>32.#"3267#(<,$   ^c%BZ5,L7$PHLV)3K1( #&*7  !aT%KGT&(CQ@  PO++4T&(J@  P O++4T&(jU@  P O++4Tr&(Q@  PO O+++44&,C@ PO++4P&,@ (PO++4&,j@ PO++4 r&,@POO+++44 $}@Oh$y#<#y<h    8 9F &D%I"JJ?32?329/322210]]]]]]]]]]]]]]]]3>32#"'#%4&#"3#326D&H!CmM**MmBDLD\gll g]|/ 'U__U'/!BTx&1v@ PO++48%&2C@ +) P*O++48%&2@ '*( P)O++48%&2j@ )+ P(O++48%x&2v@ 4( P*O++48%r&2@*< P4O(O+++444z ;@      /32292/3292210''7'77 o4oo4oo4oo4+o4oo4oo4oo48%%0@gx/:.+.5#&#HFVYJ ()  M&F2F1)(!,J   !J  ?3/3?93/393/3/+}10]]]]]]]]]]]]]7.54>327#"'.#"4'32>?4=]?,G)95=]?V9( 0 )9$-0 )9$Y0QQ_5G!\0PQ_41F~aB*Kh?^Ds)JhN&8Cv@ PO++4N&8u@ 'PO++4N&8ju@ PO++4Nr&8v@&POO+++44&<D@ ' PO++4T <@"kZkZF "D!H J?22?2210]]]]>32+#32>54.#"  4U=!">W5*ZZ$"7&#2o5T;@m6===$=3%9%$$E%55 S(S/?O.S/?O@5Q66 66? .1W:6 W ???9]2/]3/]9/10]]]]]]]]]]]]#"'732>54.'.5467>54&#"#4632%  ':'($ &$"#*-VXR&;(t/E  G4$@0 K##7" ,0$#/1AGd]'7)j &DCA@ .,P-O++4)j &D+@ &-+P,O++4)j&Dj;@ ,.P+O++4)j&Dv<@ 7+P-O++4)j&D<@-?P7O+O+++44)j&Dd<@ 0+P.O+4+44)f7GN@sNIfIvI %%*%*!! !teuVf;;>**K S>>#LS  PES# 555#O;8X @X(( UKKW HX41W5?222??329/3/?322/]2/9/2333310]]]]]]]]]]]]]2632#3267#"&'#".54>3246=4&#"'>267.'&#""34&)A-LYJ -#1  =*'; A($@/,<# (-*):1*"/+' $Dy  &?/E (>*)=)*:. I % K- #'3VHIU0CR5w@% #9#t U e rSc @$ H !''7-S !!66 6*W$2W ?3?9/]2/9/+10]]]]]]]%#"&'732654&'>7.54632&#"3267R2<,$   &5 _`.&63 ) #  #&*7  " 0FX3 Gbg+I4 1 &HCL@ %#P$O++41 &H6@  $"P#O++41&HjA@ #%P"O++41&HB@$6P.O"O+++44 & C@ PO++4> & @ PO++4& j@ PO++4& @POO+++441 -@[z,i,f*v*e'u'    #!S/(S .&X   +W ??39///]9999]9/99910]]]]]]]]]]#".54>32&''7&'774'.#"326Y#%+F3-B-1C'$ PM$=*_R$e/&4+*I_?nP.#A\9A_? =3%9$-*+-4-N83 \PxN&QvT@ PO++40 &RCT@ +) P*O++40 &RA@ *( P)O++40&RjI@ )+ P(O++40&RvJ@ 4( P*O++40&RJ@*< P4O(O+++44+_+@  r/9/]3310!!7#"&54632#"&54632+XPI    ()$.@Vi,y,w"f"('   Z   %S0S /(' *W   W ?3/3?93/39]3/3/+}10]]]]]7.54>327#"'7&#"4'32>(.-F/B,"2/-E/@+#0*# ($ M$`9=fI()8M#^7>eH(%:(.(5I,.&"4IG &XCO@  P O++4G &XR@ ) P O++4G&XjS@  P O++4G&XT@* P "O O+++44D &\&@ *%#P$O++4MIZ@9{jYk{Y  S Q W W??3?32?32]2210]]]]]]]#7>32#"&'532>54&#"VVc_U`$!&7<  K.L8l]D&\(@%7P/O#O+++44]&$a^@ PO++4)j&Da<@ +,P-O++4&$b^@ &PO++4)j&Db<@ -6P+O++4E#** M%@EM '%$$L (*$$L$$,+!+ I('''$?3?3?339/]329/+}ć+}10++#"&5467.'##>733273.",8$" W.-/_/--'+.. +& 2-X,,X-mKKm)SXX)El.;@P5$&$$$  [k{4Q&)&=/S<,<W"57X"42X& ?332?39/3222/3210]]]]]]]]]]]]#"&5467"#".54>3254&#"'>323273275&#"l",8 $?/,<#!(-*):"FX'+0, " -(-(=,)=)0:. I W\- ., #7&&~@ DPO++40R &F/@ 2PO++47&&j@ PO++40R&Fj:@  PO++47r&&c@ $PO++40R&Fc;@ %PO++47&&`@ PO++40R&F`;@  PO++4S &'`p@ $"P #O++40&GT 0&]@6d"t"U"euV Q( S'W&#W ?3?32?39/32222210]]]]]3573##"&54>325#&#"3267rVMMH%la-F0!rr!&9: N]B ?eG' `.L8l]T]&(aV@ PO++41&HaB@ "#P$O++4T&(bV@  P O++41&HbB@ $-P"O++4Tr&(cV@  P O++41&HcB@ $*P"O++4TEL@)(H D I H H  ?3?9/22/2/2/10+]#"&5467#!#3#3327",8!7'+ +& 2MKM)1E)0@Wu+te6% {l[$  .S2-S 1'1U--*XW  ?32?9/23/3/310]]]]]]]]]]]]]]]#"&5467.54632#3267327"34&l",85I-`OYJ -#1  #&+' .)*)Gc32#4&#"##?V-!:+V(7$V?N]B_2U?6NH =jx&,v@ PO++4& v@ PO++4]&,a@ PO++4& a@ PO++4&,b@ PO++4& b@ PO++4E7@(H D  ??39/2/10+]]#"&54673327",8$"Z'+ +&!;K)EZ@7|mT Q! !!/!X  x ??3^]9/2/3/10]]]]]#"&54673327"&54632",8("V'+(!!!! +&$8)Jr&,c@  PO++4N !@Q/ ??^]9/103#3VV ALT>&,-AD&LM&-jq@ mPO++4F&^j@ 8PO++4TD&.T д% P+4ND&N' д#P+4Tw&.oN _@ M M Y  Q@ H  ?3??3?39/33+22/+}2++10#.'#3>7*M&2.(^(,,VV)($  Hw4FKK"!HE> <=:D&/@ PO++4)&O@  P O++4TD&/4 P+4)D&O ش  P+4T&/@@H@ H0P+++4L-&Of@ @H @ H++1T&/B J P+4LU&OF  K@%  D       H ??99//399399222210]7373! SZd"162M<8QM^@+ Q U ?3?99//33//9/332}3}10777.=TV8!Y# J@3s/W ;2#HEKT&1@ (PO++4N &QM@ PO++4TD&1k P+4ND&Q8 P+4T&1`@ PO++4N&Q`T@ PO++45 &1D_ &Q_`@ PO++4TB E@$MD"D! J?33?3?2/+}10#"&'732>=.'#33C<   ;@B TK&G@9TVJI"4:8>{7NBC@)zQ Q  X W?32??]9/10]]]>32#"'732>54&#"#N%V$'@-H<   (7$V 2U?VJ D"TNH >8%]&2a@ () P*O++40&RaJ@ () P*O++48%&2b@ *3 P(O++40&RbJ@ *3 P(O++48%&2f@.*, P-O)O+++440&RfK@*, P-O)O+++44T&5>@ *(P)O++4NA &U#@   P O++4TD&5R ش2*P+4D@&UѶ  P+4T&5`Y@ +)P*O++4N@&U`*@  P O++4y&64@ /42.%P3O++4$; &V@ &0.*#P/O++4y&6j5@  35.%P2O++4$;&Vj@  /1*#P.O++4CyJ@gu222d2V2E22&2q0R0b0/)6$u" z {  z>1+.D8AA&GF&LGGF K.AAD;4KD!+D ??399]9/3/3/9/3210]]]]]]]]]]]]]]]]]72654.'.54>32.#"#"&'732654&'>7.'76;&2)3G)(D11;)0'';(<,$   *@7><3*"$0@+*D0G53*"$/?+#>3# #&*7   H$C;F@.t..c.4.D.T..s,,2,B,b,T+++U&e&u&"%Zj+;K { J {:-'*@3==$CS$HCCS  G*==@60Gk@DXC'@ X?32?33299]9/]3/3/9/3210]]]]]]]]]]]]]]]]]]]72654.'.54632.#"#"&'732654&'>7.'7&'" QE4$ &%66<,$   #93=+# #-AP E %# #/!9M  #&*7  GC!i@ U e u  c s R D @! H D #"H! ?39/?29/333/+10]]]]]###"&'732654&'>7##5  <,$  M  #&*7  &hMHC2+@' 'teTشH @/ H &##) -Q)) )),## ,& W *+U?2??39/]22/9/32+10+]]]]]]]]3#3267#"&'732654&'>7.57'! ',<,$  /-V J6+ E #&*7  # RM&7`8@  P O++4H5&Wn N P+44@  D   H?2?9/329/331035#5!#3###OYYYZYMMB>H2O@.: Q U W U??329/?3]222/2/10]]]3#3#3267#"&57'! ).JIV JBN6+ ES`Nx&8vv@ PO++4G&XvT@ " P O++4N]&8av@ PO++4G&XaT@  P O++4N&8bv@ PO++4G&XbT@ ! P O++4N&8dv P+44G&XdT  P+44N&8f@(POO+++44G&XfT@! P O O+++44NE&c@=${s   l[! D(D '$' J ?3?39/310]]]]]]]]]]]]#"&5467.5332653327",8_`Z.<>-Z$   .))nv)OOQM!/C0$ GE $\@:u   | [ k  "Q&Q % %#"W ?332??]210]]]]]]]%327#"&5467#".5332673'+",8)D0V/0 V.? .)-0S?CM?(&:j@ *, P )O++4)&Zj@ *,P)O++4&<jC@  PO++4D&\j'@ $&P#O++4&=@  PO++4!F &]@  PO++4r&=c;@ PO++4!F&]c@ PO++4&=`;@ PO++4!F&]`@ PO++4N>*@ Q/ X?22?^]9/102.#"#46,VL D'VU]Dy&6 <4.%P+4$D;&V 80*#P+4D&7  P+4HD2&W ߴ P+4F &@Q/ W??^]9/10#"'732>53L?!   VVJ I""]= @ n  /9/210'>54&'$<  2[+'P# *E*@ ȴM/3+22/310'7*ss*I##U2 //103#2D*g @ @  /2/10"&53326738;: :<gIB!00!BIej   //]10"&54632!!!!jHU 5 @ H H H@ H  //10++++#"&546324&#"3262##22##2-'..''..'LE(@(H 3/9/3210+#"&54673327",8&"X'+ -'!5+'JN?@/333223/]2/]]10'7'7p.`p.`)(&:C@ ,* P +O++4) &ZC@ ,*P+O++4(&:@ %+) P *O++4) &Z@ (+)P*O++4(r&:@+= P 5O )O+++44)&Z@+=P5O)O+++44&<CD@  PO++4D &\C(@ &$P%O++4#@i ?3?3//+}10#KK+h@ L M  L M Lس M L@ M ! ~}??]10++++++++4>32#".74&#"326+,,+7M//M78L//L8HAAHH@@2 M@- Mx  0L     y z?222?3]29/33210+]+3##5#5673**@*N $E:__&xTP)+e@@eVD#3EUe3$!  y ~?32?9/2/32]3/10]]]]]]]]#"&'732654&#"#>73# 0/ "'.9 r>E-# ; !)/f->)B+@8z j \ J b u D T uTdC@ M  L M  M M M@= M MM@ M} ~??9/]]2]2/]10+++++++++]+]]]]]]]]]]]]]632#"&54>7"3254&6F /<;8<:"9H&V1+/4 @<=DWT7#53C%'#2'XVOA2HZg3+$0継@HHCSzK[k{  HTdtEu@ MMMM@9M(+/?2%1( .}" ~??9]3/]3/910+++++]]]]]]]+]]]]]]]]+]]#"&547.546324&'326'>54&#" ' *3>9 <0198&\W //%&<,:)/!,::" !! +@.vTdEy K [ k  Hiy0M( L L M M @ M M( M@$ M! ~ }??9/]322/]210+++++++++]+]]]]]]>7#"&54632726754#" 9G -::7=;$:J'\  2*b-5@=:EWN@X8 rG$p ?55 |?5  |?5(?5q |?55r?5s  ?55t|?5u  ?555v ?55R( M M M@ MdUC@x Mu( M M( MziJZ M MiyZI MM" /?*n)% t ?323/3?9999]222]2/10++]]]++]]]]]+++]]+]]]++++327#"&='74>32>54&#"DL? % 3$F=7(_) + * gbIv'.?ZH4-\b/>$,:9{P*/,-Ac!34@ 3!5-4000' ?2/?9/]23/310"32673#".54>32'4'.#"3!25 0|IO09ER\0Sl??lSSm? 0|HH|0Q 38A6 3$9aKKb99bK 06:0 &'o6?53&'o.M?5&'ou |?5550&'o&u =|?5555&r'o'u /|?555U&tA'ou |?555&'or|?53&'o3r@ ##*|?5^]^100&'o)r7|?5*&q'o)r|?5&'os |?555&r'o3s@ ""/|?55^]^10&'o t|?53&'o)t%|?50&'o)t2|?5*&q'o)t|?55&r'o)t$|?5&&s 'o't"|?5&'ov "|?553&'o0v 9|?55*&q'o.v )|?555&r'o+v 8|?55P&t<'o v '|?55,&u'o/v I|?55',u@L,,v(e( 9 I Y ;*  u d dt#p.+p-#&tt t?32?9/322/210]]]]]]]]]]]]]632#"&54632.#"2>7.#"P26EX30N:TUcW4$5$)o!- ,+ =fIXqAup6Y>" ?_n0$73!.-.2Z1., $('#jSSjE!dWWdT@n nr?3?10###ZZ,"yZ@0 L   L  H H ?2?29/32/2/9/+}ć+}10!!5>7.'5!u#L-.Q" #R0+T#egRLRVN323#5>54.2 6(>.q$#@Z76Z@#$q''7x=aDc=;LBNZ0O{S++S{O0ZNBL;GPY1Da=+P //10!!+XPIo9g~@ (M@>L 'R v'Rv ?3?339/33/399//++q++q10+q+73#.''Ul  Ck%BU0UQN( +7@W444000k&J&Z&;&*&" HvdEUvTdE 22$9,$8 5) //3/23/29/9910]]]]]]+]]]]]]]]]4&#"326'>32#"&'#".54>323267.#"[ *($$(**($$(*v +*("00"(w' !6**6! '' !6**6! 'g*("00"(G@n tt//9/107#"&'53265463:#"MO+%MN +*XiJ<9@XhJ<9&aga+0$]@-       @ /3/22/3/]2222/22/}10373#3#'7#537#+,F"Rn.-E"Qm-tZIxIv\Ix+ ?@t   /33?22/2/2/10]]5% !!+1""X)HCCAI+ ?@{   /33?2/2/22/10]]7'-7!!R""1XCCHI w@MMM M MM@M;K4DM@M (L/3+?3++/]]10+++++++>73#.7.'>)Z1 1Z))Z1 0]5445[[[\[[\\[7j55j77k55kNp&II2N"O@0*  TQ$ Q #x  U X?????]22/2/10]]2&#"3##46#3'"&54632&$'VL2VV+!!!!D /:IJ=VU] ]NN l@F* yjYJ;, Q  Q  UX  X ??3?32??]22/10]]]]]]]]].572&#"3##46J@V#&$'VL EKq~#D /:IJ=VU]NN '2INE&I21wT L# B1r X)x(i<p 6 <<6q6r=s6t<u<vpV  U UV qU rVsV tUuVvVs)?/]10#59]D@  9/103#"&'53265J<, 2 ?64(Ua @  /9/]210#&454>7S &$  a $5$- #sF//]10#59Lt@ /t@ /1.v(>/? #D^v>>/v(>?/v>@/%v>^/v>`/}7m>2s5s~t-5~u-*}7y>H}}7>*}7>E&i@@y"j"Y"k{Z  Q (Q 'W &$W?32?32?39/32]32310]]]]]]]3#>32#"&'#535732654.#"!1F,cj%H>>V :9&!B` 'Ge? [BN>]l8L. /9@^9|96{6" * | k Y j{7F F ;,4D(:I44J(%3) 0J?2/22?329/923/210]]]]]]]]]]]]]]]]]]32>54&#%.54>32#"&'7*3254&%2%CG4O^*.WC(4)3B#@Y5#L)& >|GF 1$>F:A!*E3>ORH6J/\ '#{p?3T]@:\KZKd  d t  eF  DHJH??329/22/10]]]]]]]]]!#32#"'732>54&+T7+6X?#">V4EFZ3$PG%M5U<;T5A!6)O@M T@5zYiZjzS  "Q !UW W?32?32?]22/10]]]]>32#"&'!32654.#"!1F,cj%H :9&! 'Ge? J]l8L.!U@4Zj[ks sS#Q "W !W?32?32?2]210]]]]]>32#"&''>732654.#"!1F,cj%H=!I) :9&!  'Ge? jL$ >]l8L. Z@8[kZjtF"D  ! HJ ?32?9/2]210]]]]]]32#"''>732>54.++6X?#"=W4DG=!L*3%'8#%5U<;T5"L$ !6)(6"Y@9{;):) ~ 9EUF  J  J?22?322/10]]]]]]]]]]]2#"&'732654&#"'>v(D[4*J4#SJIS'2IŲcV'GG7+*f@@))r)5#E#"D7  Yi (,!F+JJ '$J( ?22?32223/310]]]]]]]]]]"&54>32>32.#".#"32673z%B\8$:0   =#SJLV)3KcV'-*H& G0u+]@8u#f#fv   )- S ,W (%W) ?22?3222]2/310]]]]]]]]".5463:>32.#"&#"32676H+]` =2    (61 ) "4 *Id;3)C& bg+I4 C  A,j@Cz+;+x*:%z%F8)(F. D- J #J?22?22/210]]]]]]]]_]_]]]]"'.54>32*32>54.DL&E5Qa+GqN)(KmS   7K.1QX '# :A!&Ta_U'tChHIhB,U@6EUDTi ykiy DFH HJ??9/2/210]]]]]]]%#".54>;5#5!#"327GD6W32&#"32670H%la-F0!!&9: J ?eG' K.L8l]1G(8@]&&&"""iy% 5 E   UE6V%,)S/S:)S 9,,4W$X ?32?93]2/3/9910]]]]]]]]]]]]]]#"&'732654&'.54>32>54.#"<<# 0?"#I B (0(B8%-F00F-8(0. %$ 0Vy) # - F1FaB.TA''AT.Me$gE!9++9-t 0@D  IH H??9/2/2/2103535#535#5!-7MKMK7&m@FxkM] TCEU y j :(!F("F '!HJ J ?32?9/2/210]]]]]]]]]]]]]<7!.#"'632#".2>7#7G$=0!5>TF_;vc8O1, +97^E'G,4_O.Uz$>U10U?$1,@ ''u@GIru q :)  ".FF(-I!J"% J ?32?329/93/22/2/10]]]]]]]]]]+]]].54>32.#";#"3267#"&54>),0C)&I967DN NTL=<D(mm&tO9(C.H=06BGL;CFFqb8.#FI@*UD2#D IHJ/32?9/22/10]]]]]]#"'732>5!#3#F>"  7VJ I"MK71-h@?J++&6w5&$D %/F.$$#!J&) J ?3222?329/3/310]]]]]]]]]4>32632&#".#"3273#".7&Ec=#W  8'\P*<%ZP,6\C%[bW'XH ! LlD  +YH+@Xf$4i*:)F L L  F @PO_ - ,#  J/?39333?399//]]9+}ć+}10]]]]".54>733'2654./ , ZZ ,   (/+7M;r;M7+/(J% #..# %N!f@B; *   Q!!Q# Q "W W   ??3?32??]29/10]]]]]]]]4&#"#7>3232653#"&5I*8%VV-";,'-.'VUU6MI =2U?1992`VaT."@: J DJ ??32/10]33267#"&5TZC  ,J?\F XR  +@D    ??9/329/3210333### XZXXZX*BIT#n@<#!#L#L%!D$! J?3322?3??9/3322/3]+}+}10>32&#"#.'#3>L70  K&FD<e7=?ZZ3Qg0$A ,l3[fh,.`YLCvN"q@A H  Y"Y""$Q #"W  ?3??32?39/33]32/+}ć+}10+#.'#4632&#">7*M&2.(^(,,VH<  )($  Hw4FKK"!HE>hVJ I#4<=: 8@0 H Q X ?3?9/329/3210+373#.=# ?VDD# J@?G?#HEK)@oE4F 5 $   l_{'!!Y"'""'Y"+"*())'!)) "!  W ??3?39/9333/993992/93/+}ć+}3/10]]]_]_]]]]]]]]]7.#"'>327#.'#>7'1W'  ;BQN&((X  W')*Zg"L17?/Z.prk).kol0F~64T$N@-u D"D&D%#J " J ?32?32?339/3310]]]]%#"&'#".53327&533273'Z%%=J&%@/Z"2(Z-4$"Z0Q=+7 *EN?gFZ@+ LDD ZKJ @ HU?2]+/?3]]?]+}]]10!.'#"'732>533=CF"E<"  K&G@9T;;VJ I">{7KNI:@$ z  Q Q  W?32??]10]]]>32#4&#"#N%V$'@-V(7$V 2U? NH >8%)|@T()(9(y'v"!!&!6!wyTBJ Z YJDT%F+$F *$$$JJ ??9/]2210]]]]]]]]]]]]]]]]]#".54>322>7!"!.%=]??]==]??]='6$$8&&7%,$7ZQ_44_QQ_55_$A\99\A$6$B\88\B$87! 4@Zx32)292.6.'.v-v)(&(6($*$:$X J  [JUFD!F6+F 5&J0J ??333310]]]]]]]]]]]]]]]]]]]#".54>32>54&'34.#"32>%=]??]==]?Y;V$"`$9))9$$9))9$ZQ_44_QQ_563  0Z$0}L?hK**Kh??hJ))Jh0y 4x@Qi2y2f.v.u(f(i$y$  !S6+S  5&W0W ??33]3310]]]]]]]]]]]]#".54>32>54&'34.#"32>-E//F--F/C- R" Z $# #$ >eH((He>=fI(+/  /W$"U2,I55I,,I44I8$8@U)696&2626,',9(((yK JF,M(M""F%% D:/F 9J"*J4M4J ?+?33??9/2310++]]]]]]]]]]]]]#".54>32>32#4&#"4.#"32>;[>>[<<[>M6%K$)C/Z,5.`"7('8##8'(7"ZQ_44_QQ_5*8\D+]M 0Q?hK**Kh??hJ))Jh0I`$8@_z6i6u2g2u,g,{(j(3%   x""S%% Q:/S 9"W*W4W ???32?9/2310]]]]]]]]]]]]]]]]]#".54>32>32#4&#"4.#"32>-E//F--F/C,9"!8'V ,!Z $# #$ >eH((He>=fI(*2U? NH #X4,I55I,,I44I+\@8j*Y*j$[$ts) 'F-!D ,H!! J??239/210]]]]]]]]]3.54>32+*32>54.&E5P_+5W="">W5*(  $"7&&4a'# :A!5T;32#"&'532>54&#"VH<  c_U`$!&7< VJ I#4R K.L8l]T*{I&Y&@BMVfEcdL#F, D+I(J ??9/23/3229/Ƈ+}10]]]]]]+]+#36232#.'32654.#""XX  4T7>54&#"'>32"6I-2I0)3' 5-6J+)C0*3&8>H3G*&>2' $+35G0D*)?1&"*3<2/v@K++%5!$!!!i ; K *   S##1-S  0+(;( ((XX ?32?3929]]2/2/10]]]]]]]]]]]]7267#"&54>7>54&#"'>323@(NQ ')#1!8(##+=G TE!/" !' F %6!-# #+Z@0 L   L  H H ?2?29/32/2/9/+}ć+}10!!5>7.'5!#L+.Q"#Q0+R$egD?EHN3226754&#"KC7: .E<# EK SA!7(UBd# F#$1!&/HE&$Y@6 !!, E4%Q  &Q"" ""%%#$ W U??2?3/]2/2/10]]]]]3#3267#"&'732654&5.57'!!  0'  $IFV J6+ -+E1D-2Q` '@ FD H??29/10###".54>3Z4$H(2Mh)   &5 H8 U@5:JJ;*  "Q !X W U??32?32]22/2/10]]]]]3#3267#"&54632.#"'! ).JILM, J6+ ES`U] D'F,@j DH J /32?29/10]#327#"&5#5  ?IM~"DJVMNC! C@) z t  D"D!  J ??310]]]]]]"&53326532654&'3aaZ.<>-:R *dnw)OOQM%!   .'dupGy$E@+u !Q &Q %$W ??3]10]]]]]#".53326732654&' ) %)))D0V/0 5y  .'E 0S?CM?"$ 21-h@>,5,&,v+IEy*: F / $$)F!!.$$ H #J ??32232/2/32/2/310]]]]]]]]]%2>54&'53##".54>7#531 6(>.q$#@Z76Z@#$q''6==aDc=;LBNZ0PzS++SzP0ZNBL;GPY1Da=NI@.DvYiK9 u F!D J J ???9/10]]]]]]]"&5332654.+'6232e^Z27E9-% 7I+3Uju KJ:aF&Q1Y}LJf<"[@1  L  L D#$ J ?2222??39339/2݇+}ć+}102.#"#.'3>7>.   Z2.*aB# 5B 2NEC( 1nnl0OL19F3*0D0`@3"%"%Y+2"Y 1,/X+%("X ?32?3933?3322/2/+}2+}10#"&'732>?.'3>7>32.#"U   '0 %  %$![ # ?+2 (;ja\-7L0 F %4|E.lmf)8a6/ >x@<       L   HI H ?2?9/3222/2/2/+}99993/103#!!5>7#53>7#5!> i!:=#Po<Zn-v@BG2M79JB@s*M!F s@9       Y   U U ?2?9/3222/2/2/+}9993/103#3!5>7#53>7#5!?-Ee) *2R* Q->/W!J>&_0>,KJp"@Q} l [ ziZ& r F  wIZjF $ ""#"J J H?2?329/32/2/33/3]10]]]]]]]]]]]]]]]>7#5!#"&'732>54&+k&F 7!G'LX7Q4$E>-"PA9rE$4Q7WM'H 7 G&MGE!2! J  8L+Ul;w?2M7>54&'5>7#5!$0"C@ <%?"3&*4" $ + #317%Q+J90[*&7%(9' +u$a@6$4F  &F% HH$!J?32?9/32]2/2/2/2/999910]]6323#3!54>7#53>54#"+=XSY9o& (cW1;YV&E L6;A%N4UI>L B'dtk@Am\Ji[t u&F!D HJJ ?32?9/22/3/]323/10]]]]]]]]]]#"&'732654&'>7#5!he!;P.!C27#5!KZ1D'8)%9QAE3? QG)>*H -.04:V/KJ-D'f@?4%5S)Q $$  (YJ!UvX! ?3?9/9]9]]]2/23/10]]]]]]72654.'.573##"&'7&'#V %QN(@3=+# '4$J)$ "/!ET GNG >@% |kS Q XW?3/?32]210]]]4&#">7#>32B9/$;)Z(DZ2VI&+H3,LQ ^1@S6HpU>;W^G_^Ga'__*GD@    ??99//32329/332222107#535#5333#3##QQDpDdDpDH #@n qu ??9/3/10#.=3#"&54632 3 ]! !TDDTy$$$$S&''=@`{S&'']@`V0(&G']`T&/-TDR&/MVD&O MT&1-DTD&1MDND&QM&$`]@ PO++4)j&D`?@ .,P-O++4&,`@ PO++4& `@ PO++48%&2`@ +) P*O++40&R`K@ +) P*O++4N&8`u@ PO++4G&X`T@  P O++4N&8u@*P"OO++4+444G(&XT@. P &O O++4+444N&8u@$P"OOO++++444Gn&XS@* P /O "O O++++444N&8v@&P,OOO++++444GO&XT@* P 0O "O O++++444N&8t!@*P"OOO++++444Gn&XT@$ P 0O "O O++++4441#e@@#)9 {j S%!S $U X W ?32?9/]2/210]]]]]]]]]].#"'>32#"&5<7267#+ -#1  =*7K..A'YJ+'!%@/E (Hd@1CP;O/O++4+444&$_@ '(PO+4+44)j(&D?@ 78P+O+4+44c& @ QP O++4)f&@ OP"PQO++47 (@W"""u"&"6"!w&6tJJ ((('D*F)J(&$J ?32?399//32223/2]10]]]]]]]]]]]3533##".54>32.#"3275#TZEEP,6\C%$C_;-J6#WL*<%T LLB +Y^bW'KLlD 0D"3y@Gt-e-V-t)e)V) 332$Q5+S 42$$1.W4%(W" X ?32?323/329/32]2/222]10]]]]]]]]3##"&'7326=#"&54>3235.#"32675#??2C& @418$^W1N8%GQ <762Q9B8M0H 8D v?eG% Ǝ\h^M7&*`@ ,$"P#O++40D&J`M@ /-P.O++4T&.`p@  PO++4.&N@ PO++48E%%9@yy8887)7&363v2u.5--&-)9)*)y(T#E#IYv{l[UDUC &F;0F: :+J!5J ?3?229/310]]]]]]]]]]]]]]]]]]]]]]]]]]]327#"&547.54>324.#"32>%*$",8(>Z;=]??]=`$9))9$$9))9$ZEgM6? .(/(6^PQ_55_Q?hK**Kh??hJ))Jh0E%9@gi7y7f3v3u-f-i)y)###  |k\V &S;0S : :+W!5W ??22]9/310]]]]]]]]]]]]]]]]]]]327#"&547.54>324.#"32>&  ",8$*=)-F//E-Z $# #$ ;S:) ? /*/',Ga:=fI((If=,I55I,,I44I8E%]&Pa@ :;P 325>54.#"Z'U0ir/Qn?*K7!"0%'GRlU![?RgB3I-T&1C@ PO++4N &QCU@ PO++4%)5@ydtCS4# H/ *0&((00 " L%#   L76&@)(''-3 I#""3 ?3?3?3339/322/9+}ć+}3/2/10]]+]]]]]]]]]#.'##>7.546323.'74&#"326P--+Z W+,-2##2V..7 " Hj-X,,X-jH "'..SXX92N)j}&D@@ -+P+444&@ P O++4)f &@ 5QO"PPO++48%&@ '31P 2O++4( &A@ 1/ P 0O++4&$6@ "P!OO+++44j&D@ 2,P1O-O+++44&$^@ &PO++4)j&D<@ 6-P.O++4B&(9@  POO+++44.&H%@ )#P(O$O+++44T&(V@  PO++41&HA@ -$P%O++4&,@ P OO+++44& @ P OO+++44&,@ PO++4& @ PO++48%&2w@ /) P.O*O+++440&R,@ /) P.O*O+++448%&2@ 3* P+O++40&RL@ 3* P+O++4;&52@ /)P.O*O+++44@&U @  P O O+++44T&5^@ 3*P+O++4N@&U)@  P O++4N&8L@ POO+++446&X-@  P O O+++44N&8u@ PO++4G&XT@ ! P O++4 Iz*@W*u*)&Zjn\l[=.DCtF((F, +J#I # J /?39/922/2/3/210]]]]]]]]]]]]]]]]]#*/32654&+532654&#"'>323A0X}N s[J&#EN<71C#0L510lJIkF"QagQRKTK9L H8N.?g JB0w@K4,,&,,'{{JeVS..S2 &1%"W)U&) X ??39/922/2/]3/210]]]]]]]]]]]]]7+'232>54.+532654&#"'>32'-Mg; (F5!,62+'0?&%=+, "-58S6I$8' 1!GC9)5 E-=%4TT&+`@  PO++4.&K@  PO++4TG1@[ k u D D J?32/?10]]]>32#4&#"#T,e*.K5Z5G,Z?hLgbf 1D3#1=@ju-d-V-v(U(e( U F 4   % $4UD ;#1Q5?+S >$&W# 421.W8 ?3?33/3/222/32?32]223310]]]]]]]]]]]]]]]]]7632#"&''75#"&54>32&#"3267"32654&1V$(0"$>00O  la-F0!!&9: J'43*$#J!?eG' K.L8l] -)0'*'./;r@F31z$$$ujk ed85F9 F=,/F+((%F"<%%J228+J??39/9323/3223/3210]]]]]]]]]]"32>54&7#".5467.5467332654&'3 8H#./#H#%'7R67S7:1%# Q 0440 T QAB"1!!1"BA=V $09,L77L,=dV=-?$#F#CGGC#F#$?2 9@X00z0,,u,y utz63S7 S;&)S%""S:W..6% W ??39/993/3223/32210]]]]]]]]]]]]]]]]]%4&#"326#".5467.5467332>54&'3P4..44..4?%&72E()E27&%U  U[PP[[PPAPiU?]>=]?UiPA*Q*#G#,;##;,#G#*QE#D@$V D  L%!!$ H# H?2?22/އ+}3/10]!#"&'7326=!5>7#5!JIA 0'  $(.11-Zn56M+E1D-27(`ghbW!M!EF !D@$T QY#"U! U ?2?22/އ+}3/10]3#"&'7326=#5>7#5!?871 0'  $275%fkg&J+E1D-2>,lla Jt&$c`@ #PO++4)j&Dc<@ -3P+O++4TC%@$udUscT@+ H !!$'#D&"I#H#H %?39/?9/22/2/9/32+10]]]]]]]]]]!#"&'732654&'>7#!#3#3# <,$  7  #&*7  &MKM1C3:@Ju55.5.&...,'' '&ubUqSceu@. H %""*8S0 0<7S*;"" ;U774X-W% ?332?9/9/22/9/32+10]]]]]]]]]]]]]]]]]]]]73267#"&'732654&'>7.54632'"34& -#1  6$<,$   *9$`OYJ+'&?/E #&*7  "-E[6y  VHIU8%&2@.@ P8O,O++4+4440(&RK@.@ P8O,O++4+4448%/C@yBA9A*A5==&=v<w87&767*3:33y2V-E-Z)M)I#Y#TF HteTE@O&0FE:F&D 5J+?J! ??2/3/9/3/]10]]]]]+]]]]]]]]]]]]]]]]]]]]3##".#"'>323267#".54>324.#"32> ' 2( =]??]==]??]=`$9))9$$9))9$B=0303Q_44_QQ_55_Q?hK**Kh??hJ))Jh07/C@@zAiAf=v=v7g7j3z3--- ))) ###HI:@8 H@?O0@&0SE:S &&D 5W+?W! ??2/3/]9/]3/]10+]]+]]]]]]]]]]]]3##".#"'>323267#".54>324.#"32>} ' 2( i-E//F--F//E-Z $# #$ 7B=0303=>eH((He>=fI((If=,I55I,,I44I8%&2c@ *0 P(O++40&RcK@ *0 P(O++48%&2@ 45 P(O+4+440(&RL@ 45 P(O+4+44^&<F@  PO++4D&\+@ #$P%O++4DQ w@KJ9, E U 4  %  TC%5Q!  ?3/32293?32210]]]]]]]]]]]]]]7632#"&''77"32654&$(0"&?30UV5R'43*'&P!! -)0'*'.NDF#/@  H H@9 H%5- Q& 1"Q## ##0# $ &$*W??3/32299?]22210]]+++]]>32632#"&''74&#"#%"32654&N%V$'@-$(0"&?30U(7$V 2U?'43*'&P!FNH >) -)0'*'.DQ$m H H@1 H"Q %U  ?3/39999?3?]2233/310+++7632#"&''773#"32654&$(0"&?30UV5R'43*'&P!Jf -)0'*'.0,:@[T6d6t6d1t1V1z(z#      Q::%S <4S;,--*/W::"7W  ?3?33223?3332239/32910]]]]]]]]]]]]]]7>32#"&'#"&54>3232654.#"#&#"3267/T!1E,ci00ka-E0!T :9&!T!&9:  'Ge?  ?eG' =]l8L..L8l]0I,:@\z6z1v(U(e(s#U#e#   -Q, 4S<%S ;:7"W /-,*W ??333222?332239/3210]]]]]]]]]]]]]]]]#5#".54632>32#"&'.#"327332>54&#"T!1E,ci00ka-E0!T :9&!T!&9:  'Ge?  ?eG' ]l8L..L8l]$#((سM'@-M&M& M&M%M$ M# M# L"M MMMM MMMس MȳM@ MMMM سM @4 M M M (M MM MM' M'0M}'MMгM гM @u M'#("?$$  L  != ,  L  *)I($"!!    ?33/3?33333/39/333222?32/9+}qqć+}q3/]}10+++++]++++++++++++++++++++++++++++++++++##>737#.'#3.'<8 W.-/T$44$##Z j>;.83:7.'3267#"'#"79%BZ5 7& }-)3K10(/tPHQl)~aT%V T K G]Jp&0Rj&@G#(#X#  $# ( S 0'#W $W  ?3/33322?3223333/3]3/2/3/}10]7.54637&'3267#"'( _b/  B#5 / Qԕ#TM$G;>@]++s%d%%%U%%%Jz;) 00$S$@';;S  ?-X3{j)'<X;' X?32?3299]]]?]3/33/3/10]]]]]]]]]]]]72654.'.54632.#"#327#".'.'7&'" QE4$ &%PQ "  '! (3=+# #-AP E %# #/!ET F! ?G!Cn "X@6+;{vY   $#U" X U?2??22/2/+}10]]]]]7>7#5!327#".+!275:93#."  #0% '4&>,lla J@$hol)"'*"D*1* b]@=wS&6e| FD   J ?32?9/10]]]]]]]]]]]]]74>54&#"'>32#d$*$=289I)*I6%*%Z7K6))2$-4"G+B,-@1*1=,)!C@)veep6%S Q!!#"!  W?32?9/10]]]]]]]74>7>54&#"'>32#W   /*-32#"&'5#73#32>54&#'3254.#"D#I$-Q>%4)3B#@Y5#L)Dgg%2%CGH>|"- )C2>ORH6J/`BX 1$>FHp#-  \@6t{  D  D IJ ?3?9/3322222210]]]]]]]]33333##"&=#26=# DZZDDdcaaD>-.BupnwQM{{OO@ LMMM@IM  L  L 0 ?3?3?33q9/+}qrqr+}qrqr10+++++#.'#>7).-,^ $%%$ [,-.KmM??MmKT@_0 M( MXy  M M  /  D? I H H?33/2?3/39/32]2/]2]2/2/9910++]]]++7#!73#3#37#37#{E6h+AJ#c@KMffEKMu1g!).3@_#g*:  .-  !23&'  *S5%/S!4!! 3U.&&2W -'"X  ?3/3322?239/32333/32/22/3/9910]]]]]]7.546327#3267#"&'"37.4&'7n-&`O+/(''1  =*$G+'@&D o EY!|RP Tx[  E O!VH(?{F"9rB@$ %  DJ ??329/322210]]]]333##"&'732>=#bZDD)C0&F9! b+B.I4G  /$F$B@!"TQ &%x W ?32?9/329/323/103##"'732>5#5353'"&54632??L?!   UUV+!!!!4BVJ I"B]8F 4@Yx32)22..5.'.(5((&($$:$($EI x 6J!FD6+F5J&J 0J ?2?/10]]]]]]]]]]]]]]]]]]]]%#".54>32327#"&54.#"32>M5?]==]??]=  ">F$9))9$$9))9$Q.24_QQ_55_Q"I JVt?hK**Kh??hJ))Jh0F*R@2u&V&f&d!t!U! *Q,#S +W W *(W ?32?32?32]210]]]]]%#"&54>32327#"&5.#"3270$`U1N8%G   =E <7&! BgG& "I JV]l8L.,@Q~(I(Y(k'h#~"l"Z"I"  L%F.D- H,*J?32?3?9/3222322/2/+}10]]]]]]]]]]]632#.'+##5;2>54.#"UFC4U=!60 #&$ Z ##*ZDDZ$"7&#24R:K]CPW'JJD O!5&'5@>@! Q     X ??33229/32]3210&#"3##5#535>321 (% ssV??K-0 BB$@T! R  R "RR  L   DL%&"!  ?3?3?9/9333332229/݇+}·+}++++103.'33>733##.'#>7#: a  ^ :V-Z/U  ` (M#$M''M$#M(B4e0 0i6q99D '.@P  + ( ,(&#( ( Y   0(Y##/#,+& (##X ?3?3?9/933333222/2/+}3/2/+}103.'33>733##"&'7326?.'#>7#8  [ s  U  8G$78A-$   $ -G  X N0`/+a3.^31_/8o2 F**;P*T+0V DM"@Q { z 6 %  V f jYzS $u  #!X WU?2?329/32/2/]3]3/3]]10]]]]]]]]]]]]7>7#5!#"&'732>54&+O&F"H)RV7O1 80-"LE9q=J2Bw=iU/L6 J !4$?F*l @   y?3/102#.#"#467<: :;IB!00!BI J00@/32/3]]2]2]210/'`.pK`.p)DA}K@ H  H H@ H  @ /2//3/10++++'7#"&546324&#"3262##22##2-692N'..''..'P$(5@H@H   /32/3/+2/+103#"&546323"&546322(B2O( ! @ H  //+3/10"&54632'3#!!!!OBP$n ; @H @H@ /322//++3/3210"&54632#"&54632'_+nP"~P$n = @H@H@  /222//++3/3210"&546323"&546327'7Jn+_P~"P$O G@  @H@H  /222/223/++9/32210"&546323"&54632''7J vvWP*__*<U7h@AHX HUeu@ /3/3//3/10]]]]]+]]]]+3##".#"'>3232672 ' 2( 7B=0303D6@//10'7o92N';!@ /223/32210'7'G vvW;*__*<'3!@ /223/32210'7 vvW*__*<YO@M``@H@H  /33/322/++3/]3]210+'"&546323"&54632r_+n#"~Y2@ @ /33/322/3/3210'7"&54632!"&54632n+_]~"Y G@#  /223/]223/33/]2]210"&546323"&54632''7H vvW*__*<T&(CZ@  PO++4Tr&(Q@  PO O+++44  c@=Yis%  /  HI F"D!H J J ???9/32239/+]10^]]]]]#632#"'7232654.#"##5()^o\N-/!,'ZM vmboL=E*9# hMT&H@ !PO++47h@Ar6  p YiLL  FH J J ?22?329/]22/2/10]]]]]]]]]]"&54>32.#"3#32673z%BZ5,L7$IILR)3KaT%KptMGy6T, r&,@POO+++44<-  #1@^j.[.J.Y(i(H(vzi X   { %5E1D#D## +F3 21H$&J" H H ???329/99//210]]]]]]]]]]]]]]]]]#'>7>=!32#"'32>54.+ cL +<C+6X?#"=W4DGZ3%'8#%i2lom2UWM0<*ahn7g5U<;T5_!6)(6"T#s@J\!I!kJZ v   vDF%D$#HJ ??32??9/3229/3210]]]]]]]]]]332#"'##3332>54&+Z+6X?#"=W4DGZZZ3%PG%4T<;S4Q !5)N>N@.is    D DHJ ?3?9/322]310]]]]#>32#54&#"##53%B2V1C#,ZM2O9KI hMT&.l@   PO++4T&C@ PO++4~&[@ & P"O++4TG ,@ DD D  H?3?39/105#333#ZZgK$TT%T@ DH??10#!Z.hMG'G@)D DDF DH H?222?]]9/105!#3>=!33V='@B#5乹2x]5!kT( y1@Y L  L  $$*/*L)$))$$$LD/3))20/ $$*)?3?3??3?39/33333?2/2/9/3223+}ć+}ć+}ć+}10>73#.'##>7.'33k.,'Z+//75-_(-1S2.)\-581.)Z (,.S!TTM!SVT$Xdi02aWIMIWa2/idW)WUO MTT!2.@a..u.****u**)t)%l[Zj   srF,,F0$$ /# J'I$' J  ?32?39/922/2/3/210]]]]]]]]]]]]]]]]]]#"&'732>54&+532654&#"'>32 2A":O-'J< /#SD009J90 <K&)D1/tYC5O4H!4$@GG;=67 F.C(4RT]@)JZ M DD  l  @ Ise?2/3]]+]??3]]?+}]1033>73#TT4|RGT"FC=yK;;T~&@ PO++4T. M@1YyzkD 5 $ DDHH ???9/10]]]]]]]]###'>7>=Z  cL +<Ki-3jif.UWM0<*`eg0yE0T+8%2T@D DH?3?10###ZZKhT37&7h@AtLL / J??39]?32/9+}ć+}10]]]#"&'7326?&'3>7UB N@ "  %( =[ZD*$;aUKL1+_UkkU-a;TG ,@D  D D  H?2?3]105!3333ZZBh8?@&fv D D J ??39/32210]]]]!5#".=33273Q3&A2V1C#,Z.L9EE oKT -@DD D H?2?3?9/10333333TZZZhhKTG:@ D D  DD   H?22?3?]9/105!333333ZZZBhh K@/ZjjYsvF!D  H HJ ??9/210]]]]]]2#"'#5332>54.+6X?#"=W4DG3%'8#%5U<;T5_L!6)(6"T",TT@7ZjiZ    v FDHJ ??9/]210]]]]]]]]]32#"'32>54.++6X?#"=W4DGZ3%'8#%5U<;T5!6)(6"v@KEF:xx ;)z F  J H J ?22?39/22/2/210]]]]]]]]]]]]]]]"&'73267#53.#"'>321K3)QLJJ;H,4ZB&GMwmI%TaT.@jx-,),,((&(w'""&")xF5K:; J;5EF0%F  D / J H  *J ???9/]?2210]]]]]]]]]]]]]]]]]]]]]]]#".'##33>324.#"32>;Z>;Y; uZZw";U9>Z;`"7('8##8'(7"[Q_40X|KFqP,4_Q?hJ))Jh??hJ))Jh'k@B]KK[kJZ   L  'D) #F(I''J ??3?9/223/29+}10]]]]]]!##"&'#>7.54>32&#";X"$$! Z %'$ .6!32#".54>7>7"32654& 4NZWZ5F* FNN!"?8.0,)9)**@7# ux0VyH#JIF ;A! M * $xQcSYN ,Z@74%(SS.$Q   -U$$#!XX ?32?329/9]23/210]]]]732654&#"&'>32"32654.!!*:75.RO.!?0& %23F09-0!'13&  "9(*9B-.=$"/!N= "@Q U ??]10##3=V=  o E@(Q@ H Q QS  Q U U??222]3/+1073>7!3#5!#3 5@TT F*bpJ9בT6f^Y*}1H ,@[  YY**%Y,*,,*Q$*%*Y$$,.,,%-..%$ **   ,?2??3?3?9/33333?3]2/2/9/3+}2+}ć+}ć+}1053>73#.'#5#>7&'j !"O"! P#A!,(# T "%%O&&" Q "(,AD :<<<<:Hv4FKL" HE==EH !KKEfH,c@;5)$)))3$S**S.$$ -# W'U$' W  ?32?39/922/]2/3/210]]]]]]]#"&'732>54.+532654&#"'>32'4_L"B< "-";8/ 0+=N`@&Z  Q Q  $  Y  I @Ir d    ??3]_]++]??3]]+}10##3>7Q43-DP021|'dhc&)_]U N&Z@  PO++4N  =@% ; VfvEQ Q U U ???9/10]]]]!#'>7>=!MlL9 +-5`655M+"-TnI (e@9(#(Z#Z Q* Q   )#( ?33/2]2?3?3]2229+}3+}10%.'#>73>73#.'  Q ^77^ Q  F&^^X -ork'=7574&'>3O77O3N3O66O3=BB=K32#"&'732>7#R65#0fa.L9 7&* 1TI G ;gM- C ->%N`.v@Nj,e(e"k{  S0%S  Q   / W U  *W ???9/?]2210]]]]]]]]]]]]]#".'##33>324.#"32>`-D.+@,VVVV-?).D-Z #" "# >eH(#?Y74T7.54>32.#";)&X %)1C(!?V  \(5)-i0:;8I6,@* d*71 &HCL@ %#P$O++41&HB@$6P.O"O+++44F(w@J!!z!5E$ %Q*%Q(&& &&)& W$"W 'U ?3?39/]322??]2239/10]]]]]]]3573#>32#"'732>54&#"##?V-!:+H<   (7$V?N]B_2U?VJ I"KNH =jN= &@ PO++41_ P@/fv   "S !U W W ?22?329/]22/2/10]]]]".54632.#"3#32679L.af0#66 *&7 -Mg; G ITH%>- C $;VAL& @POO+++44DM .l@Av$e$T$E$Q.Q...'S0'/ WU.'W& W ?32??9/3299//]210]]]]]]]%32654.#"632#"&'#'>7>=  33 & Pc4F*#AlL9 +- G.4$ ZS1C) 5`655M+".TnN (m @> H4%Q S*Q )U(%W W ?32??99//32?]29/3210]]]+632#"&'5##33532654.#"(B/4F*#AVVV  33 &  ,@)0C(  :-3$N &F@ PO++4N &Ce@  PO++4D \No 4@Q Q Q   U ?2?3]9/10333##5#NVVwTx =1*;@ky9h9g5w5W11x.i.(($$K < * Z yj0+S!!=S2S <07W& X??9]3/2/9910]]]]]]]]]]]]]]]]]]]]]7467.5467>7"#"#".%4.'32>1D9 FE)<$ !! (" 6,-F00F-"a $% \- (7HI! -FfH2[E**F[/6L7$ N%?//?%u@IZ"j"jYu   u  u F 'D &I%HJ?32?99//]32]32310]]]]]]]]]3#32#"'#53532>54.+銊+6X?#"=W4DGZ3%'8#%QB5U<;T5EBQi!6)(6"&g@=&F  % E S (Q'U&#W    W ?3?3299//3232]22310]]]3573#632#"&'#32654.#"hVPc4F*#Ah  33 &  BZS1C) |.4$8%(=@^3/&/6/'5'e'&'v:)yEUI Y XIFV6F?,!F >,)J$69J!$$$1J1J ??9/]32222210]]]]]]]]]]]]]]]]#".54>32267.#">32"32>7#".%=]??]==]??]='$8''9%%)"!%%8&'8$ ')# ZQ_44_QQ_55_N7\@$%C^9 6X>"#@Z8 0#3|@Tt1f*v*j(z(j"fv   &S5,S 4&$$$,//)WW ??9/32]222210]]]]]]]]]]]#".54>32"3267#".727.#">32-E//F--F//E-/23. Y..0/ >eH((He>=fI((IfASX_Y IOUN A j@> }jYLL   ! ;/  J?322/3?33]]?3/39+}ć+}10]]]]>32.#"#.'3>RD>- ! #! a.-,^ $%6%MSB *)Zv:KnM?c  j@>y&'   Y  Y !)X  ?33?3?3223/3]9+}ć+}10]]]]]#.'3>7>32.#"V  Y&%# [ # :31  5mg\$.~B/rsl(8ZA= @ T=:~&r@&$##MD/F (D'@I`p H$ ?2?3??3]+]2+}9/1033>733'>7#267#"&'7TT4|RGI79B+ ="FC=? D00D ?y D:*P-;;6"&A88A&"Nd(@$$ H9$ $$*$ @R H  Q&%%% Z Q@ H*Q ) &"p U ??3]?2?3]+2+}99//]210]+]]+]3'>7##3>7'267#"&'7B3-=# 943-DP021b? D00D ?7 ;u*';"|'dhc&)_]U "&A88A&"%s@IZ"j"Zjtt     F 'D&I%JJ?32?99//]32]32310]]]]]]]3#32#"'#53532>54.+ll+6X?#"=W4DGDDZ3%'8#%QBn5U<;T5BQ!6)(6"(&w@ %5E @: HS (Q 'U&#W   0   W ??3299//]]3232]22310+]]3533#632#"&'#32654.#"?VNa3E)"A?  21 % KKBPZS1C) .4$T#@n"jY\l  v C $ 4   ut" #   @ H F%##D$ H"0@J?32?9/3/]3322/223/3]3/+9939910]]]]]]]]]]]]]]]632'+#>54.#"327'TFC4U=!70*;+#&*Z(#2$)5T;M_eic7)(7!dMG'@lk{Zt 3 C   $ Ra&' ?OS )'@ H''!!Q (&! 0@#W  W??32?33/332/]222]23/+33/]9939910]]]]]]]]]#>32'#"&'7>54&#"3:7'VG%7O1'*&=&$b(7< ! . &GgB[{\] cL9l]qT9@D DH??10##35Z9NB'@Q Q U ??]10##35BV= } /@ D   I H??9/3222103!#3###D.ԀZD*MFE ? 5@Q U U  ??9/32]3210#3##5#5353?ggV??îBBTF#s@I|Jj{j)I R   U##F%D  $J H! J /??9/322/23/10]]]]]]]]]]]]]632#"&'5232>54.#"#!&%7R6ql )6 !0&Z8h )NoFN#@Y5>T4 MNF Z@7zkiyS "Q !XU X???9/32]22/2/10]]]]]#>32#*'532>54&#"#3L %A0\X  '2/VÏ;_CxG#5@_R  G5@c"L  L00+L50550*0+0L%*%%*%"#D$$+/D755+65+*%"00$H ??3??9/33333?3?32/]2/9/3223+}ć+}ć+}ć+}103>733#5#.'##>7.' (,.V.,']+..,+(GV8(-1V1-(b-47/.+MTT!2!TTM!SWU$CMT)2aWIMIWa20hdW$UWS! oK /G-M- M+M* M#سM#M# M"M"M"M"@# MMM M M L MسMM@ MMسM@-MM M M  M  M L M  MM LسM LM M M M@ MQ@BMQ,@M,@M,! ' M 8M L 8 M /?oO_1سMMM@ M/&гM&M&гM&M&@% M&&'0!,,,'/&' U ??3???3?39/3333/32]+++++2]++++]]2++++9/333++22+2210++++++++++++++++++++++++++++++++++++++++53>733#5#.'#5#>7.'x !W! X!= >ET1 $$W$$ \ &) /I00;>#&J " /?339/2?39/922/2/9/322/+3/210]]]]]]]]]]]]]]]]]]]]]]]]]]#"&'732654&'>7.'732>54&+532654&#"'>322A,;# <,$   %D< /#SD009J90 <K&)D1/tYC,F4  #&*7    H!4$@GG;=67 F.C(4RCHC@*5@$@@@U e S c #34SAA @2 H  )SE;;/// D:7W>/U00;>!$X   /?339/2?39/922/]2/9/32+3/210]_]_]]]]]]#"&'732654&'>7.'732>54.+532654&#"'>32'4G; <,$   6< "-";8/ 0+=TGm@?L  L  /  D D H???9/33?3]2]2/+}ć+}103#5#.'#3>76:;674BV,7=?ZZ:92"SWU$DMS(.`YL!TTMNo o@A  YY QQ  U ???9/33?3]2]2/+}ć+}103#5#.'#3>7*M&%KDT-(,,VV)($  Hw4*q9ב!HE> <=:T"@M!L L !"@ H$D #!H ??9/333?3?3]29/+]3223/+}ć+}1075##3353>73#.',ZZ,B&# [',*64. `%)+ڒ!NLF"RWT$[eh,+YTKN "|@F Y Y !$Q #  U  ??9/339?3?3]22/9/3223+}ć+}1075##33533>73#.'#*VV*< U5$! W  q gg885Hw4FKK"A@;q"{@FL  L$ D" #!@ H  ??3?3?99//+333222]32/+}ć+}103533#>73#.'##DZAA:92b7:;FD<e7=?ZDgNNB!TTM"RWT$[eh,.`YL%0 @K  YY"0Q  !@ H    ?33/?3?9/339/+32]22]32/+}ć+}103533#>73#.'##?V44)($ \*M&2.(^(,,V?;;B<=:Hw4FKK"!HE>4\@2  L  L D H ?3???39/3322/+}ć+}10#.'##53>77:;FD<e7=?Z:92"RWT$[eh,.`YLiL!TTM  `@5  YYQ U  ?3???39/33]22/+}ć+}10#.'##53>7*M&2.(^(,,V_)($  Hw4FKK"!HE>J<=:TG.=@!DD D  H  H?3?39/]2]21033#5###33ZBVFZZBNo B@&QQ Q   U  U ???39/]2]2103#5#5##335@TBVV 9ב Tm >@" D?O  DH H  ?3?9/]32]210###333ZZZiB%LN 9@  Q Q U U   ?3?9/?]2210##5##335_VVV J= TF%[@6S!c!eTI:)%%DF'D&J" JH???9/32/9/22/10]]]]]]232>54.#"###!632#"&')6 !0&ZZ&%7R6qlj#@Y5>T4 h )NoFNF "\@:jzziQ S$Q   #X U!  X ????9/]9/22/10]]]]>32#*'532>54&#"###! %A0\X  !(2/VVC4;_CxG#5@_R = 8H80<@|;v;;z5/u..t,++++&&6%5&u5#  K4-01F 7F(00(>F =:J###J4J- 0/?332?329/]/2/2/9/]999910]]]]]]]]]]]]]]]_]_]]].'"#"&54>32.#".54>32>54&#"#Sj {*If< ?/WcIG%;)&9'(:%K: 03 !$Y9[\- I 9k%;^B$ 9P03\L;*6p1^*ySI>T1q(6@Tg&U&{ d V hZvgV,))S@ H/S  8S 72X@ H$X,X ??32?9/+]/]2/2/9/+]99]9910]]]]]]]]]]]]]7&54632.'#".54632.#">54&#",.7B9<:3&G!0J 4K1ma(-"   Nh ER[^ZNIu8!=B+)Ha8B5R[-I S93- )7C0CRG 2@DD    H H??29/]]10#3#5##5BVFMhMoj *@QQ U  U?2?9/10!#3#5##\@TB Jב< I{ P@+YY  Q  ?3?333?39/+}ć+}107>73#5.'3 V #%&V'&$ [e(lsr/A/.B/rslk@<LL  D   I  ??39/332?39/]33]]+}ć+}103##5#53.'3>7(-/cfZf_/-(aB##B/gkj1BB0jkh/OLLOI u@FiyY  Y / Q   U ?3??322?39/]33]]+}ć+}10]7>733##5#53.'3 V "PcVcK! [j(jqq/7yxs0BB0sxy7/qqjG@M     LL DH ?3??3?32/2/+}ć+}10733#5#.'#>73yW!JGV) #&&# T(,-ZFFGKL$$LKG'Z_`,Io l@@ YYQ U ??3?3?32/]+}3/+}ć103#5#.'#>7'3739@T$53R !$%WZ[T.l6ב:i..i:!GHEGU8@D DD  HH ??2?2]10#333#5!#5VrZBV|MhhMo F@+Q?O_@ H Q QU U?2??2+]q10!#333#5!#VV@Te J9ב8GI@+  HDDD J H??39/32]210+]]5#5#".=332733F3&A2V1C#,ZB.L9EE o*o Q@2J Z + ;   Q?QQW U ??39/32]210]]]]3#5#5#".=332675t@TB,!9*V # 9ב+I7(8U@0  u d   D D J?3?9/332329/3210]]]]]75.=353673#5 7(V(E"ZZ{v/G37C pKw%j K@)  Q  Q W ?3?9/3323]29/3210]75.=3536753#53&V>VV  d^,F419 SWbTE@)}i D DJ ?3?9/32210]]]]]]>32#54&#"#3%B2V1C#,Z.L9EE NKE)4@^v---6,F,tc5E'eSZD 0F  6/F#5H&//J *J??329/32]222/10]]]]]]]]]]_]]_]]]]>32!3267#".'.5467%"34.QN t\8N0$=0!5>TD\;!9(H) , (H.UzK7^E'G,1X|K#5& $?T10T?%'.@cv)e)eugeu&6(8,S  0+ SH@ H^!/ U$++W (X??329/32+_]+222/10]]_]]]]]]]]]]>32#3267#".'.54677"34&M"^HYJ -#1  =*4I/>ED+'"*wny  &?/E $A\8FE CVHIUGE-8@bv150E0sbD5'fTZFD"4F  :3F"'9"H*33J .J??3329/32222/9/10]]]]]]]]]]]]]]]]>32!3267#5.'.5467%"34.QN t\8N0$=0!5A%V/B*!9(H) , (H.UzK7^E'G 9Um@#5& $?T10T?%o(/@cf*v*fv dt6%8) Q-S  1"0, S( U%,,W )X??39/32/]222/9/10]]]]]]]]]]]]]]]>32#3267#5.'.54677"34&M"^HYJ -#1  3"TE=>ED+'"*wny  &?/E~^FE CVHIUT, y~&@ <6P8O++4&k@ 71%P3O++4TG'j@?X#R"LHLF)'' D(J$ J??339/3/2/23/+}10]]]]]]]232654&#"#3>73#"&' EB\QZZ:92b267ah6N2  jopq`!TTM KPP# |BlM*ND )c@54&#*#3>7&E# 9*/E-&,?K VV)($  Bl0!:T89^C$I"5A SY <>: =8^@8iXE4 D D D  HJ H /???]9/]10]]]]]3'>7###'>7>=I79B+ C  bL +<  D:*P-i-3kie.UWM07##'>7>=!3H# >lL9 +- B2.u;"5`655M+".Tn: ;u*TF6@DDH J/??39/]22/2103#"'732>5##33ZG?"  ZZ1VJ I"WNF <@"QQ U W???9/?]22/210#"'732>=##335H<   VV VJ I" T=5R@.p D DDHH ?/???9/]2]2]1033'>7###33ZI79B+ CZZ D:*P-BMd Q@. Q QQ  U U ????9/?]2]2103'>7#5##335=2.=# 9VV : ;u*';" 8GP@0fv     DDD JH?/?9/32?210]]]]]!#35#".=33273eVB3&A2V1C#,Z.L9EE oK,ov I@+ :  QQQW U ???9/32?210]]]##535#".=332675vBT@,!9*V # ׀+I7(E=5@ M$%(F 0 L5.5M.)D@4 LM / 7 D 6H)%$.5 ?3/22?3?3/?22]29+}+2+}+10+%.'#>73>733'>7#.'E*'$V Y./,*.-Y I79B+ = `Hu`$9BZK+w66w+£ D:*P-B:KX]VGIdY 1@Q"#&Q1,1Z,'QYiZ @ H3 Q   2U' #",1 ?3/22?3?3??]22+29+}]2+}10%.'#>73>733'>7#.'  Q ^77^B2.=# 7  F&^^X -ork'=32#"&5<7267#+ -#1  =*7K..A'YJ+' %@/E (HdO2O+++44&i@ /A%P9O-O+++44r&#@ 1C P&;O&/O+++44H&@ /A P&9O&-O+++44p DMT]&@ PO++4N&Q@  PO++4Tr&@$POO+++44N&P@ & POO+++448%r&2@*< P4O(O+++440&RJ@*< P4O(O+++448%)}@T()(9(y'w"!!&!6!wxVDY J YJVE%F+$F *I$$JJ ??9/2210]]]]]]]]]]]]]]]]]]]#".54>322>7!"!.%=]??]==]??]='6$$8&&7%,$7ZQ_44_QQ_55_$A\99\A$6$B\88\B$0!i@Fy!j!ve   S#S  "UWW ??9/]2210]]]]]]]]]]]]#".54>32267#"3.-E//F--F//E-/++0.+*>eH((He>=fI((If]NN]XJJX8%r)5A@_()(9(y'w"!!&!6!wxVDY J YJVE3-9?? %FC$F B0<*6I$$JJ ??9/22229/10]]]]]]]]]]]]]]]]]]]#".54>322>7!"!.7"&54632#"&54632%=]??]==]??]='6$$8&&7%,$7,!!!!!!!!ZQ_44_QQ_55_$A\99\A$6$B\88\B$0!-9@Qy!j!ve   +%177 S;S  :(4".UWW ??9/22]229/10]]]]]]]]]]]]#".54>32267#"3.7"&54632#"&54632-E//F--F//E-/*,/.++%!!!!!!!!>eH((He>=fI((If\NM]XIIXr&5@ !3P+OO+++44 N&@ #5 P -O !O+++44]&b@  PO++4D&\+@ #$P%O++4r&^@0P(OO+++44D&\(@%7P/O#O+++44&ff@. P!OO+++44D&\f0@*%'P(O$O+++448r&W@)P !O O+++44,v&6@, P$OO+++44TG $@ DD HH??103#5#!BVF.hMMo< ,@ QQ  UU ??]10#3#5#3<@TBב Tr&',NR& @%7P/O#O+++44T^!-@\6 % 9*)9&6  M  D(S"S/ D .%X   +XUp?3]??3]??2/]2/]+}10]]]]]]%!!.'#33#"&546324&#"326K =CF"TK&G@9TmO>>OO>>OF%"!&%""%F;;>{7K _^]`a\\aB==BA>>Qu8@P6*-:-"u""D 147/F'':82 F/ /9714444 &#J'* J ?32?3299//32322/22/99210]]]]]]3>54&#"'6323#3#3267#".547#53>7# 6*,5?(A.+NH :+1? +C/0LC 08 D0C(=!3=5: D1D)!=$3p@9   D   I ?3/32?29929999299////9/33]]332210#77#5'75'75#5ZxZxVZxZxJ/;>n/;>͡.;=n.;=J +@ D  II?2?9/2210!!!###ppVJ^J= 'b@9z##z!F )D(J$J??9/3/_]222222310]_]]]]3>32+3##5#535#732654&#" D!@!-I56L-'UDDD$8>:0  UV 4R:;R4[BnnBcG=MN;a   /2/10267#"&'7? D00D ?"&A88A&"$T%TT(=T+8%+@fx*)()8)%&%6%w$v &6;)xUFYJZ I VEF-"F,H'J'J ??9/]]10]]]]]]]]]]]]]]]]]]]]]3#%#".54>324.#"32>ҷS=]??]==]??]=`$9))9$$9))9$O%Q_44_QQ_55_Q?hK**Kh??hJ))JhT,T.D@% L L  ?3?3?339+}ć+}10#.'#>7).-,^ $%%$ [,-.KmM??MmKE0T1- 7@@    HHH??9/2/2/2/2/]10!5!!!!!]h'dQQP8%2TT37<-a '\@6&#uu% D !F)F(%J$J /22?229/332210]]]]]]]]%.54>753#4&'>7X>"">X7V6Y>"">Y6VHLLH~HLLHV-Ie<53# alS6DSD6Sn_SstLVLVL ru210%d@@u%s!g!      Q'#S & WW U ??32?32]]210]]]]]]]]]]%.'#"&54632267.5.#"#  4^\il G   ;?7 !H  @ ?\meaNG,u@J*($${$Y Zj  ""&S S.Q -"U##)X W ?22??9/9]23/29/10]]]]]]]]]]"&'#4632'32654.#52654&#"+V\KQV3(:D4J$?A 1#(9')&+ _^cQ9VjQ6V= \PM#<,G?D3=8<G{ M@(6  RYS  ??3?3999/+}+10]>73#.'g2V JHM(- Sq,fkm3y*X.aƹA0%7@aY5i5y5f1v1W1g,i)y)## #iyZjz+&S9S.S 8+ 3W! X ?32?99]3/2/9910]]]]]]]]]]]]]]]]]7467.54>32.#"#".%4.'32>0DH ,'68,D&%5*-F01F- 51 %% W.<-!/ E 6.7G[;3_I+*G]9.E4(#tN!A3 3D-e.i@@+ H +;*  &0S,S ,,/U%"X&) X ?32?329/9]2/22/2/]10]]]]]+.54>32.#";#"3267#"&546 ,,<#@)#13; #,!% 2>#Ue/:02$ E "&!/H $  ENO2?&Ds"@ZJ!KziudU3$cst U HI~lK[ S$ S# U?2?992/3/3]10]]]]+]]]]]]]]]]]]]]74>7#5!'>54&'.& :P/'lz%6!8,E0'?+8vxx:J@q*4! @,#C*& 2FNG@@( z  Q Q  W?32??]10]]]]]>32#4&#"#N%V$'@-V(7$V 2U?NH <5#s@Mj#[#"fUeVZjz v ty!S% S $U W W??9/]2210]]]]]]]]]]]]]]]4632#".2>7#"3.5\e4I.\e4I/'  '' 1|Ϳ/af̽.a FnNNnF }CiJL "@ Q    U ??]9/10.53J@V& AKv#N ~$l@?[k{!Y$!$$!  Y  0 $& %%! W $ ?2?3?9333/9]+}ć+}10]]!.'#>7.#"'>32&  W')*   ?C)./.prk).kol0F~69B! L6?&gNG ~ Y(Dw3@W+ **&$yeuCtc 00S5%S(( S"4%U-1X0-??3299/993/22/3/10]]]]]]]]]]]]]]]";#"'>54&'.5467.54>32.0:J:&)$13=E0'?,<53+4E&9&==@?I)3  99#C*& 0G4N^O@/G/ F 0RN HG ]@=ts j jYS"Q !W  W???32]210]]]]]]]]]]]732>54&#"#4>32#"&'#),--'V.@$-C+Wc$O0N9f^?=8N0&Ge? 0DN%k@G Yx  f % 5 E  v v%%S'S 0& "W%"??3299]3/10]]]]]]]]]]]].#"'>54&'.54>328&71*#0'E%*4 2E( . ka*;+ ?,#C*%.;#'"32654.'.A(%B18O0o((/--, A`? @cDKiBJo-O>X`_[ =5* C 1@YQ X U?2?322/9/10]5!#3267#".55}   !3'JJG 8-;H @@)   Q Q W ???]10]]]]]]32653#"&5'-.'VUUUV 5::6\Xd^^\0I) N@/ DTCSQS"S!W W ??32?29/3210]]]]]5.54>324&'>dr$B]::]B#qd@@@@M?@@? t?gH''Hg?t Qo  gQQg t oy [HG" G@+ZjzEUeQ Q Q  X ??32???9/3210]]#5.533>5"e_S_dT69S97 huuh7AN2NA:7R-@c,,y$$$h$fvfv &S%%/%%"Q "S)/ S S   .&  W ?322?3/3]]9/9]10]]]]]]]]]]"&'#".546732=332>54&'7#;(I,<$,&P"(>U>("P&,$<  ?*G]4L?:E$B1YY1B$E:?L4]G* &$RP+4?5&(<?5&&+:?5&,:?5x&,@POO+++44?&2+?58&@/B+&PCO@O+++44-e&(@ 3B+&PCO/O+++44-&2@-/A+&PBO@O+++44-}&E@3A+&PBO/O+++44&(B{?5&(DϏ?5}'(Ѹ ?55i'(и ?55a'( ?55y'(и ?55NG&U@ P$O++4NG&U@ $PO++4NG&P@ &P'O$O+++44NG&E@ &P'OO+++44NG&<@ %P&O$O+++44NG&Z@%P&OO+++44NGq&X@  P6O+4+44NGq&N@ P%O+4+44.&+B{?5.&+BϏ?5'+Ѹ ?55'+и ?55'+ ?55'+ٸ ?55'+'+2&@  PO++41&@  P O++4&@  POO+++44&@  PO O+++440&@  POO+++44&@  PO O+++44q&@  P-O+4+44q&@  PO+4+44&,B{ ?5&,BϏ ?5',Ѹ  ?55z',и  ?55r',  ?55',ٸ  ?55f',f',0&RH@ (3 P9O++40&RO@ ,9 P(O++40&RQ@ (; P@ (&P'O++4-e&X@  20+&P1O++4-e&+@ "1/+&P0O++4NG&[@ PO++4NG&A@  PO++4&@  P O++4%&@  P O++40&RZ@ +) P*O++40&R=@ *( P)O++4H&[@  PO++4H&P@  PO++47R&@ 1/ (P 0O++47R&@ (0. (P /O++40D&&pZ@ 7+PHO++440D&&pU@ ;+P7O++440D&&p[@7JPKO+4+4440D&&pL@ ;JPKO+4+4440D&&pM@7IPJO+4+4440D&&pP@ ;IPJO+4+4440Dq&&pb@D;PZO+4+4440Dq&&pH@ D;PIO+4+444&$&$?5&$&Ϗ$?5w'$&Ѹ $?55b'$&и $?55-&$Z&L $?55N&${&ٸm $?55(&$U&G(&$U&GND&&U@ 0P$O++44ND&&U@ 0$PO++44ND&&W@ 4&P'O+4+444ND&&E@ 4&P'O+4+444ND&&<@ 4%P&O+4+444ND&&Z@ 4%P&O+4+444NDq&&X@ BP6O+4+444NDq&&N@ BP%O+4+444c&+B'{?5c&+B&Ϗ?5'+&Ѹ ?55'+&и ?55'+& ?55'+&ٸ% ?55'+&'+&7DR&'@ .E (P ?O++447DR&'@  2E (P .O++447DR&'@.A (P ?O+4+4447DR&'@ 2A (P BO+4+4447DR&'@$.@ (P AO+4+4447DR&'@2@ (P AO+4+4447DRq&'@;2 (P QO+4+4447DRq&'@;2 (P @O+4+444h'{&$ ?5&Ϗ&I?5-&Ѹ'L ?55#&и'B ?55&' ?55&ٸ' ?55&'&'0&bL@ (1P&O++40&N@ &'P(O++40D&&qp@ )'P(O++440D&p  1+P+40D&&Op@ (&P'O++440&V@ 1(P)O++40D&&pVB9P+44&$b]@ &PO++4c&$^@ PO++4&$C?5&$k?5 &$[@SD /10327#"&5<73 ,<J?(46? [@@ P   //2]910>7.54632[# "&a'*&" *l   /3/102#.#"#467<: :;IB!00!BIP%a )*@$%% !$ /322/2/10"&54632#"&546322#.#"#4><0$8&##%8$0P .&&. ND&&[@ %P&O++44ND& P+4ND&&A@ $P%O++44NG&V@ PO++4ND&&V&P+44&(]C ?5'k(]?5I&+]C ?5I'k+]?5T!&+@>;*@    /3//2910>7.546327'  #T/ha'*&" ε7>Q*@   /3//292/10>7.546327'77  #h/Ta'*&" @!q#*@#   #/2/3/29102#.#"#4>>7.546320$8&##%8$0. #q.%%.  # &b@  P O++4 &@  P O++4)&!@  P$OO O++++444*&!@  P#OO O++++444&@   P O++4a&@ PO O++4+444&,b@ PO++4c&,@ PO++4 &,]C?5 'k,]?5>(&@ /3//9210.546327'p" ET/h@ "&*'>8&@ /3//9210.546327'7g" h/T@ "&*'@!q#*@#     /2/3/92102#.#"#4>.546320$8&##%8$0J" q.%%. #  H&bV@  PO++4H&W@  PO++4A&V@& P,OOO++++444@&U@& P+OOO++++444HG&Z@ !,P2O++4HG&L@ %2P!O++4H&U@  PO++4Ha&U@  POO++4+444&<bF@  PO++4c&<G@  PO++4B'<C?5`'k<?5&3BϏ&?5LO (@   /3/32/3/10"&546323"&54632''L2^nLOG//10'>#.{7DR&'@ B3 (P AO++447DR& 93 (P+47DR&'@ (A? (P @O++447R&@ 90 (P 1O++47DR&' 90 (P+44&2]C(?5T'k2/?5&C\?5^'k-?52C'bTG,//10'7,#{.P@ H //+9210.54632&" #@ "&*'D&$&w$?5D&$&Ϗ$?5D'$&Ѹ# $?55Dr'$&и $?55D=&$Z& $?55D^&${&ٸ $?55D8&$U&D8&$U&D.&+B'{?5D.&+B&Ϗ?5D'+&Ѹ ?55D'+&иs ?55D'+&k ?55D'+&ٸ ?55D'+&_D'+&_DU'{&$ ?5Dz&Ϗ&I?5D&Ѹ' ?55D&и' ?55D&'T ?55D&ٸ'u ?55D&'LD&'QD&$ & P+4TD&+  P+42D1&  93 !P+44#u@@F V v F   L %$ I II?2?399//2322/2/2333/+}210]]]5!#3##.'53267#53.#4C4 fJ01-Z/33*?S 97mHH2HQS AJM#JJDJ0;H*8 Y +7I[my@;8AAC?Y<_FkM jN/"MF<5N?N?N"SJ32"32654&467'7.723&'#"&'&#"6"'327>3267#-    ":O--N:"":N--O:"  2T"/)##)/    b-N:"":N--O:"":O  $ $%  $$ $j, /! ## !/ ,1'p''u'p0I #'+/37"@&6Ff= &6Ff= &f&fIYIY i i6f 6 f  9  9=11i1y111@ H 11/4;H/((^(n(M(>((@M(+H((i(y(((@ H (($4;H$$^$n$M$.$>$ $$$$$i$y$$$@ H $$6@ 4;H6652@ H2'2722+@-1H1+ +++++@ H+'+7++'-1H'@(,H'@ H'''7''  "/ ;^[Ly  ; ? o =O   { L   9 i   /_ ;{L8 f3`=PSDv 0`=ArD@=f7  0 = !   T   F   9@ 4EH",585@91;Ha5@5P5!5155555555555c5s5T5C555@ H55'55"@H"""""X33,,@@ &HK,[,<,,@H,,,,HZ,I,,@ H,2EH11&40;H4,/H4@3#+H|44k4\4K44HZ44@ H4#+#;##H#W))+-$'H-#H-H-@H-@ H- -@2EH$$& @P`;`!Arc$DfG6'  <wC i3/?<O `@*IL\l )i AL\  /O_o;Oo.Ll| +Ii8) >xK f  3  `=AScDe@:' APA`p ?r2_]_q_qqrrrr^]2^]]]qqqqrr^]?^]]2^]]qqrrr^]2^]]]]]]qqqrrrr^]9/]3^]]]qqqrr^]3^]]]]qq+rrrr^]]^]]2^]]qqrrr^]]2^]]]]]qqqqqqrrrr^]]]?^]2+2^]+++++?]2+q2+]+qqqq+++9/3+3+]]+]+qq+q]2]]+2]+]qqqqqqqrrrrr+22+22r22]]]]qqqrr^]2^]]]]qqqrr^]2^]]]qqqqr^]^]]2^]]qqqrrr^]]2^]]]qqqqrr^]2^]2^]]]qqqqrr^]]^]22]+++2]+qrrr+2]+]+2]+]]qrrrrr+2]+]]+rrrr2+2]+]]10^]]]]]]]]]]]]^]^]^]^]335!5!35!3335!5!35!3j444555k**yhk**L**yhk**  $ - 6 7 ; < =  $ & * - 2 4 7 9 : < D F G H J Q R S T U V X *     %                                # & F G L [ r s v w ~                        F & * 2 4 7 9 < M7 \ F                    F G L M Z [ r v          $ & * - 2 4 6 7 9 : ; < =             F L v ~           $ & * - 2 4 7 9 <                       & ' G L M r s                 &*-24679:;<=FGHJRT  FGLZ[rvw ~ $&*-246789:;<=FGHJORTWXYZ\    #&'FGLMZ[rv~  $&*-24719(: ;(<(=FGHJRT#((2& - # (#&#FGL-Y[rsvw~26(- $-789:<=      # 'LMr #$#-#6#7#9#:#;#<#=###############L#~########$ $ $ $$$$"$$$&$*$-$2$4$6 $7$8$9$:$;$<$=$?$@$D$V $X$Z $[$]$^$`$o$p$"$'$$$$$$$$$$$$$%$$&$$$$ $$ $!$"$#$$ $~$$$$% % % %%%"%7%9%:%;%<%@%]%`%%' %%%%%%%%%P%% %~%%& !& & #& &&&&&"&#&$&&&*&-!&2&4&6&7&9&;&<&=&?2&@&F&G&H&J&R&T&X&[&]&^&`&m&o!&p!&y&&&'(&&#&&(&(&&&&&&&&&#&t&#&&&!&"#&#&$&o2&q&~&&&' ' ' ' ' ''''"'#'$'-'7'9';'<'@'D'['`'o'p''''''''''''''''C'' '"'# '~'''' ''''''( ( ( (($ (&(*(-(2(4(<(= (?(Y([ (\(^(('(( ((((((S((("(#(o(~((() ) ) ) ) )))))" )#)$)&)*)-)2)4)7)9):); )<)?#)@)D)F)G)H)J)Q)R)S)T)U)V)W)X)[)])`)o)p)y)))))) )'2)*)))))))))))))v)))")$ )%)C)o2)~)!* **7*@*Y*Z*\*`** *****H* * *o**++++o ,,,,o--$------o. . . .... .#.$.&.*.-.2.4.7.9 .:.;.<.=.?.@.D.F.G.H.J.R.T.V .X.Y.Z.[.\.] .^.m........ ...... .....*.X.(.. .# .C.o2../ / / / /////#/$/&/*/-/2/4/6/7/8/9/:/;/</=/?/F/G/H/J/R/T/X/Y/Z/[/\/]/^/` /m/o/p/y///'////#/////////// ///~////0<0X0Y0Z0\0 000 0<0 1111o2 2 2 2 2 2222"2#2$2-27292;2<2@2D2[2`2o2p22'222222222222C22 2"2# 2~2222 2222223 3 3 3 3 3333$3-36 393: 3;3< 3?3D3F3G3H3J3R3T3Y3Z3[3\3m3o3p33 33 3'33333333&333 3i33 3"3# 3$3o3!3334 4 4 4 4 4444"4#4$4-47494;4<4@4D4[4`4o4p44'44444444444C44 4"4# 4~4444 4444445 5 555 5"5#5$ 5&5*5-525457595; 5<5=5@5D5F5G5H5J5O5R5T5W5X5Y5Z5\5]5^5`5m5555555555555555555O555 5!5"5~66" 6$6& 6* 6- 62 64 69 6; 6?6D6F6G6H6J6R6T6m6 6666 6'66 6 66 6 6666b6666! 6"6o#6~67 7 7 7 7 77777"7#7$7&7*7-727477797:7;7<#7?27@7D7F7G7H7J7Q7R7S7T7U7V7X7Y7Z7[7\7]7^7`7m7o7p7y77777 77<77777#7#7 7'27*7:777777(7777777777777 7!7"#7#7$7%767C7V 7g7k7oF7q7~%77)77888$8-88888O8o8 9 9 9 9 9 99999"9#9$9&9*9-929497999:9;9<#9?#9D9F9G9H9J9Q9R9S9T9U9V9X9]9^9`9m9o9p9y99 9(999(99 9'(9*99999999999!9999~9999 9"9#9$9%969C9V 9o<9q 9~"9 999: : : :::::":#:$:-:7:9:: :;:<:? :D:F:G:H:J:Q:R:S:T:U:V:X:^:m:o:p::::: :':*:::::::::::::l:: :!:":#:%:o-:~:: ::; ; ;;; ;#;$;&;*;-;2;4;7 ;9;;;<;= ;?;F;G;H;J;R;T;W;X;Y;Z;[;\;^;m;;;;;; ;';;;;; ;;;;;;;;;;;f;;$;;; ;! ;";#;$ ;C;o7;~;;< < < < < <<<<<"<#<$<&<*<-<0<2<4<7#<9!<:<;<<#<=<?(<@ <D<F<G<H<J<Q<R<S<T<U<V<X<Y<Z<[<\<]<^<`<m<o<p<y<<<< << <2<<<(< < <'2<*<<<<<<< <<<<<<<<(< <|<<< <!<"<$<%<6<C<E<V<g<k<oF<q<w<~!<<<$= = == = =#=$ =&=*=-=2=4=9 =< =? =F=G=H=J=R=T=X=Y=Z=[=\=^=m====='=== =========k== =!="=#=$ =V =o<=~ ===>$>&>*>2>4>6>7>@P>M<>\>>P>>>>>>>>>>>>>F>Z>r>v>~ >>>>>>>?$?%?&?'?(?)?*?+?,?-?.?/?1?2?3?4?5?6?7?8?9?:?<?D?E ?F?G?H?I ?J?K ?MP?N ?O?Q ?R?S ?T?U ?V ?W?Y?[ ?\(?] ???? ?* ??????????% ? ????F?? ???????????? ? ?F?? ?? ??<?? ?&?'?F?L?Z?[?s ?v?~?<? ??? ?? ????? ????? ???? ???? ????? ? ?????????DD?DYDZD\DDDD!DE E EE E"E?E@EYE[E\E]E`EoEpEE EEEEEE$E#F F FFFF? F@ FFFGFHFJFRFTFYFZF[ F\F^F` FmFoFpFFFFF F FF FFF$F[FF #F! F#F$F$FFGVH H?HYH[H\H HH#I 2I 7I IIII"I?<I@'IFIGIHIJIRITIYIZI[I\I`2ImIo2Ip2Iy III(II#II -I(I7III<I'I2II II!III (I#I$I6(IEIVIgIk IoqIwI{ III'I"J?JJ+J!K K K KK"K?K@KYKZK\K`KoKpKKKKKKK!K#LKMM MKNN N#N?NFNGNHNJNRNTNY N[N\ N] NmNNN NNNNNNNINN N!N#NO O? O O OO]O!O#O$Q Q Q QQ"Q?Q@QYQZQ\Q`QoQpQQQQQQQ!Q#R R RR R"R?R@RYR[R\R]R`RoRpRR RRRRRR$R#S S SS S"S?S@SYS[S\S]S`SoSpSS SSSSSS$S#TMT,U U UUUU"U@UDUFUGUHUJURUTUY#UZ#U[U\#UoUpUUUUUUlU (U!U#UU!UVV?VVVVEV V V#VV V WW W W?W@WFWGWHWJWRWTW[W^WmWWW WWWWWW$WYWW W!W#W$WWWX X X5Y Y YYY"Y@YDYFYGYHYJYRYTYYYZY[Y\YoYpYYYYYY^Y Y#Y$ Y Y!YZ Z ZZ"Z@ZDZYZZZ[Z\ZoZpZZ\ZZ #Z#Z$ZZ Z[ [@[D[F[G[H[J[R[T[[[^[m[[ [[[[[[W[[ [! [#[$[[ [\ \ \\\"\@\D\F\G\H\J\R\T\Y\Z\[\\\o\p\\\\\\^\ \# \$ \ \!\]]?]@]F]G]H]J]R]T]`]]]]]]]]]O]] ]#]$ ]]]^$^&^*^-^2^4^6 ^7^9^<^M7^\^`F^^F^ ^^^^^^ ^^^(^^^F^Z^[^r^~^^^^ ^^^^^`$`-`6`7`9`:`;`<`=````````````L`~```````cccccm- m7m9m<mmmLmZmmm m m mo$o&o*o-o2o4o7o9o:o<oDoFoGoHoJoQoRoSoToUoVoXoooo*oo%oooooooooo ooooooooooooooooo ooooo#o&oFoGoLo[orosovowo~ oooooo ooooooooooooooooop$p&p*p-p2p4p7p9p:p<pDpFpGpHpJpQpRpSpTpUpVpXpppp*pp%pppppppppp ppppppppppppppppp ppppp#p&pFpGpLp[prpspvpwp~ pppppp pppppppppppppppppy-y6y7y9y:y;y<y=yyyyyyyyyyyyyyyyy yLyZy~yyyy y}7}9}:}<~  $&*-24789:;<= "$& * -02 4 79:;<=?@FGHJRTVYZ\`mop#"~ "? @D YZ[\m op   "? @ `     "?@Y[\]`op $#M- (DEFGHJKLMNOQRTVW o(p(A-K77 7MDFGHJRTYZ[\]FGHJRTVYZ\))* * * **"*?*@*Y*Z*\*`*o*p*******!*#&*24 ? ~  ? ~   ?   op   ? ~   ~?2~ 2?~?~ F&*2479<M7\ F  FGLMZ[rv$&*-24679:;<=FLv~$&*-24679:;<=FGHJRT  FGLZ[rvw ~ $%&'()*+,-./01234567'89:;<= FGHJRT#((2& - # (#&#FGL-Y[rsvw~26(- $-679:;<=L~$&*-2467@PM<\PFZrv~ $%&'()*+,-(./0 123456789:; <= DE FGHI JK MPN OQ RS TU V WY[ \(]  * %  F   F  < &'FLZ[s v~<         $&*-246 79<M7\`FF  (FZ[r~ $-679:;<=L~$-79:;<=LZ   $-679:;<= LZ~ 79:<$&*-24789:;<= :;[]DE,I,K,L'M'N,O-QSUVW/XY9Z6[6\9]**?! >!9#7$<%:/@7   "?@Y[\]`op  $#&*24FGHJRTVWXYZ[\]d ! "#~$79:;<=+ !&*24789:<YZ\~D VY[$\]'6 !"#$YZ[\]{ %!#$"9< M>  * * F G H J R T  &*24FGHJRTXYZ\DEIKLMNOQSUVWXY4Z1[1\4] *-% 8!!#2$*%-62$-D[]$-9;<=D!"~9(DFGHJQRSTUVXYZ[\]2( (*,.06:DFHLRX," ! "-#$%468<>@BEagikmoFq(~.$DFHRY.Z0[%\)]q 8#1$/0'    #$ &*-249 < ? FGHJRTXYZ[\^m'  k !"#$ V o<~ D Y[\ d  "~&*24FGHJRTVXYZ[\] %n" ! " #$~M?V YZ[\(]g -!#$)5 , Y Z  [ \  B #  !9!<!!!O! !~!"D"F "G "H "J "R "T "Y"Z"\" " """g"" "" """~ ""#Q#S#U#Y #Z #[ #\ ##*# #A# # ## #%##$$=$$ %[%]%%%% ]$]-];]<]=]D]Q]S]U]]]*]]]]]]!]%~9~<~[~] ~ ~~U~~~ ~~ ~#~~~~ ~~V[]1 !#    9<M&*24789:<DFGHJM@RTVXYZ[&\]'3+ 6!#$$+1- 7(oZMDFGHJMRT\FGHJRTYZ[\](u "#$ ~DF G H J R T V YZ\]  I "#$ $-=DVY'Z%[\']p '!#$#$#!MV]A ! #$ ?      "@op #'FMZv    "#?@^`mop          #&FG LZ[rsv~   "@`op         #&'G LMZ[rs w~   "@`        #G L[s  `mopy   #'+FGMYZ[qrswy}   $ A ?@`op$A( (   A#&'FLMZ[rs ~(     #?^op   #'MZr~  "#? ^m (    #'FGL MZ[rsvw~   "@`op G[w   "#? ^m (    #'FGL MZ[rsvw~  ?op&L   "#@`opGLZ v ~    ?@`op   #&'+FLMZ[rs    "#?#mop#  #&FGLZ[rv~    "#&*24?2^` mopy2  #  #<#&'+FGLMZ[qrsvwy} ~   "#?^mo p y     ) #&'+FGLMZ[qrsvwy}    "#?@`op ' LM Y[rs~  #& 'GM[w}   $ A ?@`op$A( (   A#&'FLMZ[rs ~(   "?`op         #&G LZ[ rs w ~   "#@`opGLZ v ~   "?op #'M   "? @`op  #'M   "?@` #s   "?mop    #'+GMsw < ?@`!<!  'M[   "@`op 'M     "#@FGHJRTmop   ' GM [sw   "?@`op?     "#@FGHJRTmop   ' GM [sw??#'M?   "?@`op #'M?   "?@`op #'M    ?@`mop  # ' GM    "?@^mop   #'+GMsw   "@FGHJRTop ##'+GMs   "?@`m op  #'Ms < ?@`!<!  2'M[ ?     " ? @ o p            # ' M [    "?@`op #'M   "?@`op #'M?      "#^mop G "@^`# # # #"#?#@#o#p########## ###'#M#[#&& & & & &&&&&"&#&?(&@&`&m&o&p&y&&&&&&(&&&&&&&&&&&&&&&&& &#&&&&&&~&&&&&&&&&&&&&&&&& & &&&&2&&&5&#&&-&'&+&F&G&L(&M&Z&[&q&r&s&u&v&w&y&}&~&&&&&&&&&' ' '''''"'@'m'''''''''' '''''#'!''' #''''#''!'+'G'M!'['s'w'+3 3 3 3 3333"3?3o3p3333333 333 3#3[5(5 7#8#99(9#9E E E E EEEE"E?EoEpEEEEEEEE EEEEEEE EEE#E'EGEME[F F F FFFF"F?FoFpFFFFFFFFFFFFFFFFF FFFFFF FFFFF&F'FLFMFZFrF~G G G GG GG"G?GoGpG G GGGGGGG G'GML L L L LLLLL"L#L?L^LmLoLpLyLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL L LLLL!LLL!L#L&L'L+LFLGLL#LMLZL[LrLsLvLwLyL}L~ LMMM#QQ(QS<U<X X X X"X?X@X`XoXpXXXXXXXXXX XXXX X#X&X'XLXMXZZ Z ZZZZ"Z@ZZZZZZZZZZZZZZZZ ZZ ZZZZ Z Z Z# Z'Z+ZLZMZYZ[ ZrZsZ~Z[ [?[[a a a aaaaaaaa aaaaaaaa a aa#a'aFaGaMaZa[asa~ b bbbb"bmbbbbbbbbbbbbbGb[r r r r r r#r?r^rorprrrrrrr rr rrr r#r'rMrZrrr~s s s sssss"s@s`sospssssssssssss ss'sMv v v vvvv"v#v@v`vovpvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvGvLvZ vv v~w w w www"w?w@w`wowpwwwwwwwwwwww ww#w'wM~ ~ ~~~~?~@~~~~~~~~~ ~ ~~ ~~~ ~ ~~ ~~ ~ ~G ~Z~s ~w ~ "#?^mG[   "#&*24?@^`op!!!           "     "#?2@^` mopy 2  6U#A7%&Q R[\^klnUU#-   "#&*24?@^`op!!!          #?    (( #?^`         2((<<   "#?@`opr  " #^m  #      "#&*24?@^`op!!!             "#`op o p  -##   "#?@`opr   ?@op   #?^m222    "#&*24?(^`mopy( #K#-#&2Q R-__#-    "#&*24?(^`mopy( #-U--#Q #-#   "#@op      #&*24?^m   <(<< #?@    "#?op               ?@opy        ? opy      "?op #?@^my    # <"?2@(FGHJQRSTU`(mop*#<2((%(<<(Q  -<   #?op  ? @     ?@opy   #mo p    #?@`opy     "? opy      <?(@`#op<(#  F2  Q ((  "?op"  "?op   7@#^`#mopy7##   "?@opy   mopy "?   "?op      "#mop "?  "?op#AA##R(--#nI4E%44 M@Y% %  4E4     J    .z    4 2     Copyright 2011 Canonical Ltd. Licensed under the Ubuntu Font Licence 1.0Dalton Maag LtdUbuntu Condensed Regular Version 0.83Ubuntu and Canonical are registered trademarks of Canonical Ltd.UbuntuCondensed-Regularhttp://www.daltonmaag.com/Copyright 2011 Canonical Ltd. Licensed under the Ubuntu Font Licence 1.0Dalton Maag LtdUbuntu Condensed Regular Version 0.83Ubuntu and Canonical are registered trademarks of Canonical Ltd.UbuntuCondensed-Regularhttp://www.daltonmaag.com/O  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghjikmlnoqprsutvwxzy{}|~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~.nullglyph127Eurouni00A0uni00ADmacronperiodcenteredAmacronamacronAbreveabreveAogonekaogonek Ccircumflex ccircumflex Cdotaccent cdotaccentDcarondcaronDcroatdcroatEmacronemacronEbreveebreve Edotaccent edotaccentEogonekeogonekEcaronecaron Gcircumflex gcircumflex Gdotaccent gdotaccent Gcommaaccent gcommaaccent Hcircumflex hcircumflexHbarhbarItildeitildeImacronimacronIbreveibreveIogonekiogonek Idotaccenti.loclIJij Jcircumflex jcircumflex Kcommaaccent kcommaaccentkgreenlandic.case kgreenlandicLacutelacute Lcommaaccent lcommaaccentLcaronlcaronLdotldotNacutenacute Ncommaaccent ncommaaccentNcaronncaronnapostrophe.case napostropheEngengOmacronomacronObreveobreve Ohungarumlaut ohungarumlautRacuteracute Rcommaaccent rcommaaccentRcaronrcaronSacutesacute Scircumflex scircumflexuni0162uni0163TcarontcaronTbartbarUtildeutildeUmacronumacronUbreveubreveUringuring Uhungarumlaut uhungarumlautUogonekuogonek Wcircumflex wcircumflex Ycircumflex ycircumflexZacutezacute Zdotaccent zdotaccentlongs Scommaaccent scommaaccentuni021Auni021Bdotlessj apostropheuni02C9WgravewgraveWacutewacute Wdieresis wdieresisYgraveygrave zerosuperior foursuperior fivesuperior sixsuperior sevensuperior eightsuperior ninesuperior zeroinferior oneinferior twoinferior threeinferior fourinferior fiveinferior sixinferior seveninferior eightinferior nineinferior afii61289 estimatedonethird twothirds oneeighth threeeighths fiveeighths seveneighthsonefifth twofifths threefifths fourfifthsonesixth fivesixths oneseventh twosevenths threesevenths foursevenths fivesevenths sixseventhsoneninth twoninths fourninths fiveninths sevenninths eightninthsDeltauni2126uni2215uni2219f_ff_if_lf_f_if_f_lzero.altone.alttwo.alt three.altfour.altfive.altsix.alt seven.alt eight.altnine.alt zero.supsone.supstwo.sups three.sups four.sups five.supssix.sups seven.sups eight.sups nine.sups zero.sinfone.sinftwo.sinf three.sinf four.sinf five.sinfsix.sinf seven.sinf eight.sinf nine.sinf caron.alt commaaccentrevcommaaccentcaron.alt.short Parenleft ParenrightHyphenSlashAt Bracketleft Backslash Bracketright Braceleft Braceright GuilsinglleftBulletEndashEmdashGuilsinglright Exclamdown GuillemotleftGuillemotright Questiondownuni0180uni0181uni0182uni0183uni0184uni0185uni0186uni0187uni0188uni0189uni018Auni018Buni018Cuni018Duni018Euni018Funi0190uni0191uni0193uni0194uni0195uni0196uni0197uni0198uni0199uni019Auni019Buni019Cuni019Duni019Euni019FOhornohornuni01A2uni01A3uni01A4uni01A5uni01A6uni01A7uni01A8uni01A9uni01AAuni01ABuni01ACuni01ADuni01AEUhornuhornuni01B1uni01B2uni01B3uni01B4uni01B5uni01B6uni01B7uni01B8uni01B9uni01BAuni01BBuni01BCuni01BDuni01BEuni01BFuni01C0uni01C1uni01C2uni01C3uni01C4uni01C5uni01C6uni01C7uni01C8uni01C9uni01CAuni01CBuni01CCuni01CDuni01CEuni01CFuni01D0uni01D1uni01D2uni01D3uni01D4uni01D5uni01D6uni01D7uni01D8uni01D9uni01DAuni01DBuni01DCuni01DDuni01DEuni01DFuni01E0uni01E1uni01E2uni01E3uni01E4uni01E5Gcarongcaronuni01E8uni01E9uni01EAuni01EBuni01ECuni01EDuni01EEuni01EFuni01F0uni01F1uni01F2uni01F3uni01F4uni01F5uni01F6uni01F7uni01F8uni01F9 Aringacute aringacuteAEacuteaeacute Oslashacute oslashacuteuni0200uni0201uni0202uni0203uni0204uni0205uni0206uni0207uni0208uni0209uni020Auni020Buni020Cuni020Duni020Euni020Funi0210uni0211uni0212uni0213uni0214uni0215uni0216uni0217uni021Cuni021Duni021Euni021Funi0220uni0221uni0222uni0223uni0224uni0225uni0226uni0227uni0228uni0229uni022Auni022Buni022Cuni022Duni022Euni022Funi0230uni0231uni0232uni0233uni0234uni0235uni0236uni0238uni0239uni023Auni023Buni023Cuni023Duni023Euni023Funi0240uni0241uni0242uni0243uni0244uni0245uni0246uni0247uni0248uni0249uni024Auni024Buni024Cuni024Duni024Euni024Funi0292breve_inverted double_grave ring_acutedieresis_macron dot_macrondieresis_gravedieresis_acutedieresis_breve tilde_macron acute.asccircumflex.asc caron.ascdieresis_grave.capdieresis_acute.capdieresis_breve.capuni0400 afii10023 afii10051 afii10052 afii10053 afii10054 afii10055 afii10056 afii10057 afii10058 afii10059 afii10060 afii10061uni040D afii10062 afii10145 afii10017 afii10018 afii10019 afii10020 afii10021 afii10022 afii10024 afii10025 afii10026 afii10027 afii10028 afii10029 afii10030 afii10031 afii10032 afii10033 afii10034 afii10035 afii10036 afii10037 afii10038 afii10039 afii10040 afii10041 afii10042 afii10043 afii10044 afii10045 afii10046 afii10047 afii10048 afii10049 afii10065 afii10066 afii10067 afii10068 afii10069 afii10070 afii10072 afii10073 afii10074 afii10075 afii10076 afii10077 afii10078 afii10079 afii10080 afii10081 afii10082 afii10083 afii10084 afii10085 afii10086 afii10087 afii10088 afii10089 afii10090 afii10091 afii10092 afii10093 afii10094 afii10095 afii10096 afii10097uni0450 afii10071 afii10099 afii10100 afii10101 afii10102 afii10103 afii10104 afii10105 afii10106 afii10107 afii10108 afii10109uni045D afii10110 afii10193afii10066.locluni0462uni0463uni0472uni0473uni0474uni0475uni048Auni048Buni048Cuni048Duni048Euni048F afii10050 afii10098uni0492uni0493uni0494uni0495uni0496uni0497uni0498uni0499uni049Auni049Buni049Cuni049Duni049Euni049Funi04A0uni04A1uni04A2uni04A3uni04A4uni04A5uni04A6uni04A7uni04A8uni04A9uni04AAuni04ABuni04ACuni04ADuni04AEuni04AFuni04B0uni04B1uni04B2uni04B3uni04B4uni04B5uni04B6uni04B7uni04B8uni04B9uni04BAuni04BBuni04BCuni04BDuni04BEuni04BFuni04C0uni04C1uni04C2uni04C3uni04C4uni04C5uni04C6uni04C7uni04C8uni04C9uni04CAuni04CBuni04CCuni04CDuni04CEuni04CFuni04D0uni04D1uni04D2uni04D3uni04D4uni04D5uni04D6uni04D7uni04D8uni04D9uni04DAuni04DBuni04DCuni04DDuni04DEuni04DFuni04E0uni04E1uni04E2uni04E3uni04E4uni04E5uni04E6uni04E7uni04E8uni04E9uni04EAuni04EBuni04ECuni04EDuni04EEuni04EFuni04F0uni04F1uni04F2uni04F3uni04F4uni04F5uni04F6uni04F7uni04F8uni04F9 afii61352 afii00208uni20B4uni20AEtengeroublekratkaAlphaBetaGammauni0394EpsilonZetaEtaThetaIotaKappaLambdaMuNuXiOmicronPiRhoSigmaTauUpsilonPhiChiPsialphabetagammadeltaepsilonzetaetathetaiotakappalambdauni03BCnuxiomicronrhosigma1sigmatauupsilonphichipsiomega Alphatonos EpsilontonosEtatonos Iotatonos Iotadieresis Omicrontonos UpsilontonosUpsilondieresis Omegatonos alphatonos epsilontonosetatonos iotatonos iotadieresisiotadieresistonos omicrontonosupsilondieresis upsilontonosupsilondieresistonos omegatonostonos tonos.cap dieresistonosuni1F00uni1F01uni1F02uni1F03uni1F04uni1F05uni1F06uni1F07uni1F08uni1F09uni1F0Auni1F0Buni1F0Cuni1F0Duni1F0Euni1F0Funi1F10uni1F11uni1F12uni1F13uni1F14uni1F15uni1F18uni1F19uni1F1Auni1F1Buni1F1Cuni1F1Duni1F20uni1F21uni1F22uni1F23uni1F24uni1F25uni1F26uni1F27uni1F28uni1F29uni1F2Auni1F2Buni1F2Cuni1F2Duni1F2Euni1F2Funi1F30uni1F31uni1F32uni1F33uni1F34uni1F35uni1F36uni1F37uni1F38uni1F39uni1F3Auni1F3Buni1F3Cuni1F3Duni1F3Euni1F3Funi1F40uni1F41uni1F42uni1F43uni1F44uni1F45uni1F48uni1F49uni1F4Auni1F4Buni1F4Cuni1F4Duni1F50uni1F51uni1F52uni1F53uni1F54uni1F55uni1F56uni1F57uni1F59uni1F5Buni1F5Duni1F5Funi1F60uni1F61uni1F62uni1F63uni1F64uni1F65uni1F66uni1F67uni1F68uni1F69uni1F6Auni1F6Buni1F6Cuni1F6Duni1F6Euni1F6Funi1F70uni1F71uni1F72uni1F73uni1F74uni1F75uni1F76uni1F77uni1F78uni1F79uni1F7Auni1F7Buni1F7Cuni1F7Duni1F80uni1F81uni1F82uni1F83uni1F84uni1F85uni1F86uni1F87uni1F88uni1F89uni1F8Auni1F8Buni1F8Cuni1F8Duni1F8Euni1F8Funi1F90uni1F91uni1F92uni1F93uni1F94uni1F95uni1F96uni1F97uni1F98uni1F99uni1F9Auni1F9Buni1F9Cuni1F9Duni1F9Euni1F9Funi1FA0uni1FA1uni1FA2uni1FA3uni1FA4uni1FA5uni1FA6uni1FA7uni1FA8uni1FA9uni1FAAuni1FABuni1FACuni1FADuni1FAEuni1FAFuni1FB0uni1FB1uni1FB2uni1FB3uni1FB4uni1FB6uni1FB7uni1FB8uni1FB9uni1FBAuni1FBBuni1FBCuni1FBDuni1FBEuni1FBFuni1FC0uni1FC1uni1FC2uni1FC3uni1FC4uni1FC6uni1FC7uni1FC8uni1FC9uni1FCAuni1FCBuni1FCCuni1FCDuni1FCEuni1FCFuni1FD0uni1FD1uni1FD2uni1FD3uni1FD6uni1FD7uni1FD8uni1FD9uni1FDAuni1FDBuni1FDDuni1FDEuni1FDFuni1FE0uni1FE1uni1FE2uni1FE3uni1FE4uni1FE5uni1FE6uni1FE7uni1FE8uni1FE9uni1FEAuni1FEBuni1FECuni1FEDuni1FEEuni1FEFuni1FF2uni1FF3uni1FF4uni1FF6uni1FF7uni1FF8uni1FF9uni1FFAuni1FFBuni1FFCuni1FFDuni1FFE uni1F88.alt uni1F89.alt uni1F8A.alt uni1F8B.alt uni1F8C.alt uni1F8D.alt uni1F8E.alt uni1F8F.alt uni1F98.alt uni1F99.alt uni1F9A.alt uni1F9B.alt uni1F9C.alt uni1F9D.alt uni1F9E.alt uni1F9F.alt uni1FA8.alt uni1FA9.alt uni1FAA.alt uni1FAB.alt uni1FAC.alt uni1FAD.alt uni1FAE.alt uni1FAF.alt uni1FBC.alt uni1FCC.alt uni1FFC.altuni20B9uniE0FFuniEFFDuniF000 8cyrlgrekJlatnVBGR MKD &SRB ."AZE *CRT 2MOL :ROM BTRK J  kernDkernJkernPkernVkern\kernbkernhkernnkerntkernzkern,r%2<Ldh|^j4rxHNt.DJ $*06< &,28>DJPV\bhnx~ &,BXn  L R X ^ d j p ~  Z h v t   F $*06HNTZ`flrx~BHR&TjJx06<Rh~DZ|Z &Xntz"(.DZp $*0~hnl"(.4:@o2qo o2oo ooo2o  o  o# <## :6V gkoFqo (( 6V o<q  o- o7 2(  6EVgkoFqwV o<(# -(6(EVgk oqw{   o#o 2(  6EVgkoFqwoo2qooooooooooooo 2(  6EVgkoFqwM--^-V-o2qo2qo2qo2qA-K77 7oooM^Vooooooo o oooooooo2A-K77 7TFGHJRTVYZ\xz,.08:<PR[hjln ,8KMQSY[ekmsuoooo  o#  o#  o# <## :6V gkoFq <## :6V gkoFq <## :6V gkoFqoooooo o- 2(  6EVgkoFqwV o<V o<V o<  o# <## :6V gkoFq o- o- o- 2(  6EVgkoFqw*&*24n+-/7JLPRZdrt(# -(6(EVgk oqw{  o2 (( 6V o<q o  o M>>^>U*V>*** <## :6V gkoFq <## :6V gkoFq0 2(  (,.06:DFHLRX468<>@BEVagikmoFqwV o<V o<oooooooooooo2V o<oooooooooo V o<M^Vo 2(  6EVgkoFqwo2q<## :6V gkoZqM^Vo 2(  #6EUVgkoFqw!? qy} )qy} qy} <qy} )qy}} ? quy}KS qy}  qy} ( ##(#<qy} <qy} y}y}} (<<)qy})qy})qy} qy} #6U#A7%&Q R[\^klnUU#-((2((<< #   -##222#K#-#&2Q R-__#--U--#Q #-#  <(<<  (<<(Q  -< F2  Q ((((-U--#Q #-#-U--#Q #-##A((((((((((((A##-U--#Q #-#-U--#Q #-#-U--#Q #-#-U--#Q #-#(((((--#-U--#Q #-#-U--#Q #-#-U--#Q #-#-U--#Q #-#6   [    $#$V # <<2 '222'27!('"7 +KK I   ]     ,###l (!E  Y  $ 5 ^   !\ #  W  ^   !O   ,g  ((( &   "% P ((! 2!!t##!###(C    S    #v  2! H   X  *(  b  # < C !& i   C O      b   %(2##)2 O" !(#~# zil  f$     (|!$2#!#(    k     #  FF7PP<FF7   fۼ#W ( P  1( ((#((2  7FF' #((2<PP (( P   7FF      2@l26*,,,'',-/969?!>97<:/@7+ {%"1 414-%8!2*-62%.0)q81/0' k    &'@3+6$+1-7( ("-.$,   d $ '6  d %n "  ? (g-)5 , t2BO g   A  =A  U   1  u (I"   p$'#'%'VdD   (                  F F<<   -#(-&  #  v(g  -& - #(# <  FF < (           (( A $Ai a ($A    (        (       ## 2 2 ##    ^j`       ($A$A( (A    !!#       SΚe         2(~(&#( 5-     < <! !      E@     # !! < <2   #!#!!   9L@BCEGIKMOQSVXY[\_acegikmoqsuwy{}c$%&'()*+,-./n  *-035DFHJLNZ`bfhjlnpX/0123456789:l!#%'+-/13579;=?ACEGIKMOZ\gik  79;=?APR^drtvxz|0 :;<=>^`gmopqrtuy{)OQSUW_gikm*W* #?cg}~  T !"#$%U]~e #'24>?EKPQRS^`gmopqrtuy"$&(*+,-./0_ 78CPQRS_derstu".02np5$468:<>BHJ^_cmtz|&(*@DFLNPRTVXZ\agik1 !%&/1579;=?C`ajoqruvx{}~4    #%')+-3AEGIKMOQSU[]bdhlsswy)*+,-.b #'24>?^`gmopqrtuy+-/_7PRdrt  "&)..48<=?# )17:9CR=YxMm|}  #(/6;BSX'y-5=EEFFGGHHIIJJKKLLMM NN OO PQ RR SSTTUUVVWWXXYYZZ[[\\]]xxzz               "" $$ && (( ,, .. 00 22446688::<<@@BBDDFFHHJJLLNNPPRRTTVVXX[[]]^^ hhjjllnn       +,./ 12 6688 ::<<>>@@BBCC IIKKMMOO QQ SS VV XY[[\\__ ccee kkmmooqqss uu wwyy{{}}       ""##2??@@DD5FFGGHHJJMM1RR TT!YYZZ[[\\]]^^"``ggmm#ooppqqrrtu yy/zz # /5$ 5551,, .. 00 PPRRTTVVXX^^1__hhjjllnn, 4 -.0.03%&     '!!##($$,,44588 EE5GG5II5KKMMQQ SS UU+VV1YY[[aa5cc5ee gg5ii5kkmmss uu )5    !*+V$$%%&&''(()) ** ++ ,, -- ..//nn             ** -- 00 3355 DDFFHHJJ LL NNZZ ``bbffhhjjllnn pp  [.// W0123456789:;<=  !"#$%&--'_(`)*>  ?6X4)Y'<XY000000@14444448888< ####+-''''''>>>>a0001111 2!2!2!2!""####-##3$%&&&&&---S-e4'4'4'```6)6)6)77*7*8>8>8>8>8>8>:<===6)7*$:::<PTQRURUO\1bf,]A49%&BC-44'4'^*778>DE=FGHIJc&-0#4'8>8>8>8>8>d00@2!2!%4'4'FN$2!-0@4'00 ##4'4'``8>8>KL"=0 4'4'4'4'<-(017)ZVM0 <NK001122334455667788 99 :: ll !!##%%''++--//1133557799;;==??AACC EE GG II KK MM OO ZZ\\gg ii kk      7799 ;; == ?? AA PPRR^^ddrrttvvxxzz || /./012434-HH567OP8QRI9:J;+SK<=TZZ[\]U 4IQ33]+3777777OQQQQQQ+=======Z ^ 7=7=7=OOOOPPPPZ8TTTTTZZZ)ZaQQQ\\\I]I]I]99U9UJ + S S S I]9UJ J J + !"#$%&F'G(##*(* >OLV?YQ:TWZQQQM@,U99 S AXBCN_TZ7=Q`7=7==PPQQPZ7==Q7=7=QQ\\S 7=QQQQ+ Z7O9] DE7+ *   ;;<<==>> ^^ `` ggmmooppqqrrtuyy{{))QQSSUUWW__mm**WWEEFGHHI !"#$J%&b'(K)*+,LM-./0aNN1O2PQ345678R9Sb:%TUTUQV1*:V!!!!!!;"%%%%%%*MMMMMMM-////<N11111144448W8!M!M!M"-"-"-"-../////#0#0#0#0N$aNNNN%1%1%1PPPbQbQbQ''3'3444444K6*8+R+R+RbQ'3aUK6K6K6*8cfgdehZD[\_"-=>%(XN%%1%1`3'?'48+R@A B .N!M%144444C!M!M;M#0#0%1%1a.#0N!M;M%1!M!M//%1%1PP44 +R!M/%1%1%1%1*8YNY.2!"-]'QR^!/*8% ##??cc }}~~   !"JRJCUKUP !"#$%&E'()*+,LF-. / 012 33 45678HUR,(STSTKK6 OKV $$$$'((((((9------- 3 :--- " " " " #0#0$$$$$3$1%2%& & & & & '3'3'33A( ( ( +5+5+5,6,6,6888,61T///////B;<=>?DMQNGIMM...!(%2 '3(( ( +8@ && &&3-$( ---" " %2( ( 1 " '3--( -- $$( ( +5+5#08- ( ( ( ( 3 68        TT  `=`>a ??b@4ABCDE5[678c<F:G;H !"#$%IdJDefef ;7J 4444449ADDDDDD[[[[7<::::K;;;;;;""""%&%444A<A<A<A<FF:::::BGBGBGBGCY3D;D;D; 55!5!["["["["["["$7%888 5!f$$$7% UVgWhXZXZTA<LMkD6'\DD;D;ij(!55["]^%8N_)*+O,-.F4D;["["["["["449BGBGD;D;N2FBG49D;44::D;D;["["P/84:D;D;D;D;7%00FH4A<Q5 R1S4:7%2!!""##$$%%UU]]~~     $$*--+99;;,<<==-DD FF GGHHJJMM%PQRRSSTTUUVV&YYZZ[[ \\]]$xx&zz{{*. !* * *   +%""$$&&((**#,,..0022446688&::&<<&PPQQRRSS-TT$UU-VV$WW-XX$[[&^^%hhjjllmmnn1   /0-$  !!'""##$$(%%",,2233*44 88DD*EE FF*GG HH.II KKMMQQSSTTUUVV%YY[[__``*aa bb.cc eeff*gg hh*ii kkmmssuuwwyy~~  -$* ))* &$*W  ##''2244 >> ??KKPQRRSS^^ ``ggmmooppqqrrtuyy""$$&&((**++,,--..//00__  7788CCPPQQRRSS__ddeerrssttuu==<@@&@?>":;<= !>>?@@@"A@#@@B@@@$' ,0 7(--. /0 12030800945 0 0.-  )*-2<0<0@0 7!(?.?.?.?.@0@0@0A##C@ 70 @0@0#/@''+= 66 7!(%>->-   ("""#<00        "" ..0022 nn pp dC C D \ EF]FGH""I##J??K@@L^^M``NggFmmOooPppQqqPrrQtu]yy`O]`__QT^_UVWXYZ[_e[e*) --,+ '()*  +,- - .--/--0162345678 A!a6"  9  6  #  6R:;<6$Sf6b43A!!""##=$$ %%&&>''B))3++c--8..)//600)11622-33644 5566 77288,994::,;;4<<,==4>>,??4@@-AA6BB-CC6DD-EE6FFGG%HH IIJJKK LLMMNNOOPP.QQaRRSS TTUU"VVWW"YY?ZZ[[\\]]^^-__ ``bb6ccddee-ff6gg-hh6iijj"ll5mm-nn oo0pp qq0rrss@tt*uuvvww&xxyy&zz {{|| }}2~~+3+3   2AAA")661        $$ 446688::<<>>BB HH JJ ^^__ccmmttzz||       F $%""&##&&Q**R22S44T??@@G^^``Hggmm'nnSooppqqrrtu$yyU'$UQSSQQQQRRRR++S--S//S__!D" # ED`E`QSSS77SJJRLLRPPSRRSZZRddSrrSttSSSSSQ-,*-I()*V+,-IM.K/012WZ345XY6NZ7[\89Z:  ]  Z    Z;^O3<Z=_L6Z>YX!!2""-##$$+%%7&&J''))X++P--\//Z11Z33Z44(55466)77599Y;;Y==Y??YAAZCCZEEZFF?GG@HH,II8JJ-KKLLAMMNNAOOPPMRR-SSTT.UU:VV.WW:ZZ [[\\ ]]__(``4bbZcc*dd6ffZhhZii.jj:llNnnoo1ppqq1rrssuu3vvBwwCxxByyCzz({{4||)}}5~~XX+7+7+7/5III.:ZZ  ((**@@DDFF LL NN PPRRTTVVXX ZZ \\ aaggiikk- - . / 02123R""4##5??6@@7^^Z``8gg2mm[oo9pp:qq9rr:tu1yy\[1\__:$%&'()L*M+&,+,ATY;@AB<=>?@]STABNCUWDEFO^GPHV_          Q  PXI_ !!O""A##$$S%%&&''))_++--//113344>55H66?77V99;;==??AACCEEFFGGHHTIIJJAKK LLMMNNOOPPCRRASS TTUUU VVUWW YYJZZ[[\\]]__>``Hbbcc@ddffhhiiUjj llnn<ooFpp<qqFrrss uuPvv!ww"xx!yy"zz>{{H||?}}V~~K#__SSSDVB B B U /         !!%%&&//1155 77 99 ;; == ?? CC`` jjooqqrruu vvxx{{ }} ~~ Z   % &'""(##??@@)FF]GG^HH_JJ`RRaTTb^^``*ggmmEooppqqrrtu&yyFzzaE&F]_caa]]]]^^_____````,,a..a00a__!>?"#@$A[?\[\]da  a,,^88aKK`MM`QQaSSaYY^[[`eeakk_mm_ssauua_aaaa^b]_.I-./+,-GHI./J012B3KO456LM7NO8PQ9:O  R  O    OSTU4OV;7OWML!!3"".##$$H%%8&&X''))L++Y--Q//O11O33O44+55566,77699M;;M==M??MAAOCCOEEOFFGGHHIII9JJ.KKLL<MM NN<OO RR.SSTTJUUVVJWWZZ [[ \\ ]] __+``5bbOcc-dd7ffOhhOiiJjjllNnnooBppqqBrr ss uu4vv=wwCxx=yyCzz+{{5||,}}6~~ DLLH8H8H816///JOO.            ##''))++ --33AAEEGGIIKKMMOOQQSSUU[[]]bbddhhllss  <     "" ##=??@@ FF1GG2HH3JJ4RR5TT6^^)`` gg mmooppqqrrtu zz5 13755111122333334444,,5..5005__>'?0((185  5,,2885KK4MM4QQ5SS5YY2[[4ee5kk3mm3ss5uu5355552613* 9!"#$  + %:!!##%%!''++,5577GG&II"KKMMOOSSUU$WW$[[;]];``ddjj$ll9oo*qq*ss-uu ww.yy.{{}}/!!!$wwyy).R    ""## ??@@^^!``ggmm"oo pp qq rr tu"__ +,-..# $  %&'  $(!!###%%%''55 77 GG)II&KKMMOOSS[[*]]*`` ddrr/uu${{ }} %%% D   ##2244>>??^^``ggmm oo pp qq rr tuyy  ++--//__ 77PPRRddrrtt 2 2 3 4556""7##8&&9**:22;44<??=@@>^^?``@gg5nn;ooAppBqqArrB9;;9999::::++;--;//;__BCDEFGHI9;;;77;JJ:LL:PP;RR;ZZ:dd;rr;tt;;;;;9$%&'()*+,   ./!0 - 1" #(/!" /!#(/6";BKLMNOP/QR!STUV"WXY`ip/y/!""        ").   ""##&&@**A22B44C??@@^^``ggmmnnBooppqqrrtuyy@BB@@@@AAAA++B--B//B__ 78 9:;<=>8?>?@BBB77BJJALLAPPBRRBZZAddBrrBttBBBBB@D !E"#$%&'F()*+,G-./01H23456%)+,-5)+ ,-#(/65;BKL)MN+OP,QR-STUV5WXY`)ip,y),-535%(0         7:CJKLMN OP QRY`ahip qx   ""##&&**2244??@@AFFIGGJHHKJJLPQMRRNSSOTTPUUQ^^``ggmmnnooppqqrrtuyy zzN IKRMNNSIIIIJJKKKKKLLLL  M""M$$M&&M((M**V++,,N--..N//00N22Q44Q66Q__"F     ITMN  N%%U,,J22M7788NJJKKLLLMMLPPQQNRRSSNYYJZZ[[L__MddeeNkkKmmKrrssNttuuNwwQyyQKNNNNMJPIK!"#$%&B'C()*+H,-./0D1G23456E789:;<=>?@)-0127=-0 12#(7/6=;BKL-MN0OP1QR2ST7UV=WXY`-ip1y-12=9=),    #(/6;B STUVWX y  H  )  '""##*??@@^^#``ggmm$oo pp qq rr tuyy!$!__ ,-%&"&"  + (   #(;BKL STWXY` y  zcyrlgreklatnBGR 0MKD JSRB d !/;HU`k  )0<IVal  *1=JWbm +2>KXcn "3?FLSYdo"AZE BCRT ^MOL zROM TRK #4:@GMTZep $,5AN[fq %-6BO\gr &7CP]hs '8DQ^it  (.9ER_juvcasecasecasecasecasecasecasecasecasecasecasednomdnom$dnom*dnom0dnom6dnomnumrDnumrJnumrPnumrVnumr\numrbnumrhnumrnnumrtordnzpnumpnumpnumpnumpnumpnumpnumpnumpnumpnumpnumsaltsaltsinfsinfsinfsinfsinfsinfsinfsinfsinfsinf sinfss01ss01"subs,subs2subs8subs>subsDsubsJsubsPsubsVsubs\subsbsubshsupsnsupstsupszsupssupssupssupssupssupssupssupstnumtnumtnumtnumtnumtnumtnumtnumtnumtnumtnum                                             8@HPX`hpx*,R)l))**+*+X++++++++/+/+/++a+2pqrstuvpqrstuvpqrstuvpqrstuvpqrstuv+d+b2wxyz{|}~yzxw{|}~wxyz{|}~wxyz{|}~wxyz{|}~+2*2*n2*&oo*$0fH:, F 8 v .TtfX>rd  Z"<#.$ $%&&0bjrz "*2:BJRZbjrzoooooooooooqorosotouovoyozo{o|o}o~oooooooooooooooooooooooooo2:BJRZbjrzoooooorotovozo|o~ooooooooooooo2:BJRZbjrzoooooqorotouo{o|o~ooooooooooooo&.6>FNV^fnv~oooorotovo|o~oooooooooo2:BJRZbjrzooooosotouovo}o~oooooooooooooo&.6ooto~ooo "*2:BJRZbjrooouovoooooooo&.6oovoooo2:BJRZbjrzoooooorotovozo|o~ooooooooooooo2:BJRZbjrzoooooqorotouo{o|o~ooooooooooooo0bjrz "*2:BJRZbjrzoooooooooooqorosotouovoyozo{o|o}o~oooooooooooooooooooooooooo&.6>FNV^fnv~oooorotovo|o~oooooooooo2:BJRZbjrzooooosotouovo}o~oooooooooooooo&.6ooto~ooo "*2:BJRZbjrooouovoooooooo&.6oovoooo0bjrz "*2:BJRZbjrzoooooooooooqorosotouovoyozo{o|o}o~oooooooooooooooooooooooooo2:BJRZbjrzoooooorotovozo|o~ooooooooooooo2:BJRZbjrzoooooqorotouo{o|o~ooooooooooooo&.6>FNV^fnv~oooorotovo|o~oooooooooo2:BJRZbjrzooooosotouovo}o~oooooooooooooo&.6ooto~ooo "*2:BJRZbjrooouovoooooooo&.6oovoooo0bjrz "*2:BJRZbjrzoooooooooooqorosotouovoyozo{o|o}o~oooooooooooooooooooooooooo2:BJRZbjrzoooooorotovozo|o~ooooooooooooo2:BJRZbjrzoooooqorotouo{o|o~ooooooooooooo&.6>FNV^fnv~oooorotovo|o~oooooooooo2:BJRZbjrzooooosotouovo}o~oooooooooooooo&.6ooto~ooo "*2:BJRZbjrooouovoooooooo&.6oovoooo0bjrz "*2:BJRZbjrzoooooooooooqorosotouovoyozo{o|o}o~oooooooooooooooooooooooooo2:BJRZbjrzoooooorotovozo|o~ooooooooooooo2:BJRZbjrzoooooqorotouo{o|o~ooooooooooooo&.6>FNV^fnv~oooorotovo|o~oooooooooo2:BJRZbjrzooooosotouovo}o~oooooooooooooo&.6ooto~ooo "*2:BJRZbjrooouovoooooooo&.6oovoooo0bjrz "*2:BJRZbjrzoooooooooooqorosotouovoyozo{o|o}o~oooooooooooooooooooooooooo2:BJRZbjrzoooooorotovozo|o~ooooooooooooo2:BJRZbjrzoooooqorotouo{o|o~ooooooooooooo&.6>FNV^fnv~oooorotovo|o~oooooooooo2:BJRZbjrzooooosotouovo}o~oooooooooooooo&.6ooto~ooo "*2:BJRZbjrooouovoooooooo&.6oovoooo2pqrstuvpqrstuvpqrstuvpqrstuvpqrstuv0LP6-N$%&'()*+,-./0123456789:;<=ln{  !#%')+-/13579;=?ACEGIKMOQSUWZ\gikmL "(ILIOILO "(ILIOILO "(ILIOILO    yzxw{|}~Lw DR pv  p ( p  qu x (oowpv #>?@^`mstuy}Y^DEFGHIJKLMNOPQRSTUVWXYZ[\]xz   "$&(*,.02468:<>@BDFHJLNPRTVX[]hjlnIahqx0 *H 01 0 +0a +7S0Q0, +7<<<Obsolete>>>0!0 +{GS%xNM#pb040l.60  *H 01 0 UUS10UArizona10U Scottsdale1%0#U Starfield Technologies, Inc.1:08U 1http://certificates.starfieldtech.com/repository/1604U-Starfield Services Root Certificate Authority0 150316070000Z 200316070000Z01 0 UUS10UArizona10U Scottsdale1%0#U Starfield Technologies, Inc.1402U+Starfield Services Timestamp Authority - G10"0  *H 0 xo(QQ`L~&aF=Ӌd="?\̈bDطl=HSN̢A;C) (TXA-Nفk1 ag,4L{I hEKbK!(7pOX_Μ8Bk[4e/ ^S7AboB \ 2%|cA˫FkT240BpN0J0 U00U0U% 0 +0U O fer0U#0C̛u]/KQ0:+.0,0*+0http://ocsp.starfieldtech.com/0DU=0;09753http://crl.starfieldtech.com/repository/sfsroot.crl0PU I0G0E `Hn0604+(http://crl.starfieldtech.com/repository/0  *H 33%ýB6,phiDmBϯi e̷JݙadrmDҺ?03ݶ")fPNwqcZ>,/H t87ޞ[Vb4˖ }{Z+Rrc"r(^&a D{-RķT=ayP[,e՞^T\m?^a_+}R00Ơ0  *H 0c1 0 UUS1!0U The Go Daddy Group, Inc.110/U (Go Daddy Class 2 Certification Authority0 061116015437Z 261116015437Z01 0 UUS10UArizona10U Scottsdale10U GoDaddy.com, Inc.1301U *http://certificates.godaddy.com/repository100.U'Go Daddy Secure Certification Authority10U079692870"0  *H 0 -&L25_YZaY;pc=*3y:<0#0=Tߙ %!e)~5T29&UXמ* BΧ?Rifھ],fkQJ/Hǘuع)fm x|z%.enjDSp0Ü+X+=tJQL'Xk5ŝ1 6:%IgE96~7qt0? O20.0Ua2lE_vh0U#0İґLqa=ݨj0U003+'0%0#+0http://ocsp.godaddy.com0FU?0=0;975http://certificates.godaddy.com/repository/gdroot.crl0KU D0B0@U 0806+*http://certificates.godaddy.com/repository0U0  *H ҆gf :PrJtS7DIk3ٖV0<2!{ $F%#go]{z̟X*Ğ!ZFc/))r,)7'Oh! SY ;$IHE:6oEEADN>tvբU,ƇuLn=qQ@"(IK4Zц6d5oownP^S#c͹c:h5S000  *H 0c1 0 UUS1!0U The Go Daddy Group, Inc.110/U (Go Daddy Class 2 Certification Authority0 040629170620Z 340629170620Z0c1 0 UUS1!0U The Go Daddy Group, Inc.110/U (Go Daddy Class 2 Certification Authority0 0  *H  0ޝWI[_HgehWq^wIp=Vco?T"Tزu=Kw>x k/j+ň~ĻE'o7X&-r6N?e*n] :-؎_=\e8E``tArbbo_BQe#jxMZ@^s wyg ݠXD{ >b(_ASX~8tit00UİґLqa=ݨj0U#0İґLqa=ݨjge0c1 0 UUS1!0U The Go Daddy Group, Inc.110/U (Go Daddy Class 2 Certification Authority0 U00  *H 2K>ơw3\= ni04cr8(1zT1Xb۔EsE$Ղ#yiML3#An 剞;p~& T%ns! l l a+r9 ͗nN&s+L&qatJWuH.Qia@LĬC Օb ψ2 +E (*ZW7۽0@0(5y0  *H 01 0 UUS10UArizona10U Scottsdale10U GoDaddy.com, Inc.1301U *http://certificates.godaddy.com/repository100.U'Go Daddy Secure Certification Authority10U079692870 120807154320Z 150924134423Z0k1 0 UGB10 ULondon10 ULondon10U Dalton Maag Limited10UDalton Maag Limited0"0  *H 0 YZ=&ȯ p)ZrYyW ,=|QQ7 Ex>+3>L woZ"Cs0J1*#\>xOh+w{wܘe,̵>%]D@E*v1,g6Fpgm# O8(z;+uKXF[cK d^<3 MD4ݽP;t#wE EiP%~"9":I00U00U% 0 +0U03U,0*0(&$"http://crl.godaddy.com/gds5-16.crl0SU L0J0H `Hm0907++http://certificates.godaddy.com/repository/0+t0r0$+0http://ocsp.godaddy.com/0J+0>http://certificates.godaddy.com/repository/gd_intermediate.crt0U#0a2lE_vh0U=QCo vڧԚ)0  *H !.w*=`oi e̦d`zBe+kCz~KMO> !X0jVfjݥ!rewQY!ivԣpƠkOw?szg9ӽd\:Nz %[n@Ԝ7*暹мmew"ɳ0oH;@']&l6$FM: Ej?:,aAr%'l3YZ5F9QY10001 0 UUS10UArizona10U Scottsdale10U GoDaddy.com, Inc.1301U *http://certificates.godaddy.com/repository100.U'Go Daddy Secure Certification Authority10U079692875y0 +0 +7 100 +7(10 *H  1  +70 +7 10  +70# *H  1c@kWRV0  *H ;y,#Z=xGM6z w_En v0r *H  1c0_001 0 UUS10UArizona10U Scottsdale1%0#U Starfield Technologies, Inc.1:08U 1http://certificates.starfieldtech.com/repository/1604U-Starfield Services Root Certificate Authorityl.60 +]0 *H  1  *H 0 *H  1 150710120401Z0# *H  1zJ|1"?240  *H 2M쫟\#C*L0 `'@+䤥e泹PN3MPM3P(J>J $M(`M4*4-!!b''x42!#:<!<!h4x(#(3VG4V4<4,4`+x#.A&45+.Ax.L45gg xDP+8xEg2+(j2j2j l:;V;V;V;V  D  V : : : : :4P :QQQQVaVoP + + + + + +[,3/3/3/3/3<M3>PN3N3N3N3N345N3>J>J>J>JMP + + +l:3l:3l:3l:3Vn3M3;V/3;V/3;V/3;V/3;V/3:B3:B3:B3:B3V;5;      KPBVBuV PVPV;V'VNVqNV>PV>PV>PH>V>P :N3 :N3 :N3uVPuVuVP!(!(!(5J5J5JQ>JQ>JQ>JQ>JQ>JQ>J V=!$=!$=!$P!(5Jxtx4x.x9xxUxGx$   VYggg(g%g%g ggg2gg gg(g%g%g gAj2jj2j j(j%j2jj jj2j(j2jj jj(j%j2jjj(j%j I)V445Y84 Lq4*454545(PPPPP PP42u"3&1"/gg2gg gg(g%g%g ggg2gg gg(g%g%g gx@xDND+AI`IM(M#h4#(G(MkVMPM ll:3.q.M3F3;%8<3:{PxVEV PV>P : :N3953MPuV'3!JPJ5QUJ4Q:=!$772(!(/;P``9,G VV(3VVNVV?P +  :N3Q>JQ>JQ>JQ>JQ>J/3 + +[,:N3:B3uV 5 :N3 :N37 VV(3:B3!VVV>P +[, :N3 + +;V/3;V/3   :N3 :N3uTuVPQ>JQ>J V;5V31O4=!$ +;V/3 :N3 :N3 :N3 :N3VP33 :3l#& SV>3#:M3jx6xx_x!x=xxxx"x]xAxAxxx;V;VVl:! V  VuVVKVkVVV;V<VVuV gCV :V`Vl:5K`8wVw:VVXVhVlV{$ +S>$PP[/3,UPUPPV CJPN3BPMP32JP.7PBPVPP"+P$/3/3;P3(B_ SP;PUP?PL3V :N3 4V`Pu `VMPVP pV P<,V"PVkP,_VUP/VPV\P:a3l:35VV N: .~:'V;PC C  VwV P a VJPVUP~A.CC V + +[,;V/38/68/6<,7VUPVUP :N3 :N3 :N3l"KKKw:.VPXVPjV4%4(404x*VV;V=!V : VuVgCV(% :V`V3!5V`8w/A4^3cPM374>PZ7NPDP2N3BPSJ3e3@J3JF9zQ #V&^37>P9N3@J@J@JF9xxwx^3^3^3^3^3^3^3^3 777777zz8)"=>P>P>P>P>P>P>P>Pqq>/LL N3N3N3N3N3N38@J@J@J@J@J@J@J@JcMF9F9F9F9F9F9F9F9:e^3^377>P>P+N3N3@J@JF9F9^3^3^3^3^3^3^3^3">P>P>P>P>P>P>P>PF9F9F9F9F9F9F9F9,O z^3^3^3^3^3^3^3xxZxxx6x!>P>P>P>P>P::Vxxx7  xxx7@J@J@J@JSJSJ@J@JVVxxx/F9F9F9F9F9qBsE%4xnxo qq:eV44CI   /( & ^z* !(x Zd   z //////z dd/ / / !!!!(((ĸĸĸĸ -+     ^Zzd* * *  ^Z^Z^Zzd0-+1KK  /(7f   -z*  ( / / /  !  (!/  / / ĸ   ((* / zd  /   / d (&T2 &  h/U!d = .( (d f& & #g`/ {+ zz # (dh#( |B/ /  ((dh/ ddd  /*( 1&zT=-. 2 ! /w/"z2///w//#/1 &0////////!wwwwwwww +//ww, wwwwwwww////ȸ//////zz('wwwww///  +/(-(Z$ 8 L  !!"!#"$$%%&%'&('))**+*,+-,.-/.0/102131425364768798:9;:<;=<><?>@?A@BBCBDCEDFEGEHGIGJHKILJMKNKOMPMQORPSPTQURVSWUXVYVZW[X\Y]Z^[_\`^a^b_c`daeafbgchfifjgkhlimjnkokplqmrnsotpuqvrwsxtyuzw{x|y}y~z{|}}~  !!"!#"$$%%&%'&('))**+*,+-,.-/.0/102131425364768798:9;:<;=<><?>@?A@BBCBDCEDFEGEHGIGJHKILJMKNKOMPMQORPSPTQURVSWUXVYVZW[X\Y]Z^[_\`^a^b_c`daeafbgchfifjgkhlimjnkokplqmrnsotpuqvrwsxtyuzw{x|y}y~z{|}}~  !!"!#"$$%%&%'&('))**+*,+-,.-/.0/102131425364768798:9;:<;=<><?>@?A@BBCBDCEDFEGEHGIGJHKILJMKNKOMPMQORPSPTQURVSWUXVYVZW[X\Y]Z^[_\`^a^b_c`daeafbgchfifjgkhlimjnkokplqmrnsotpuqvrwsxtyuzw{x|y}y~z{|}}~! % * - 1 49>BEIMPVY^afimquy} !"#$%&'()*+,-./0123456789:;<=>?@AB C DEFGH!I%J)K.L1M6N:O=PAQFRJSMTRUVVYW]XaYeZj[m\q]v^y_}`abcdefghijklmnopqrstuvwxyz{|}~ "'*.26:>AGINRVZ^bfknsvz~~||{{zxxxvuuurrrqqonnlllkiihh gfeecb"b  !!"!#"$$%%&%'&('))**+*,+-,.-/.0/102131425364768798:9;:<;=<><?>@?A@BBCBDCEDFEGEHGIGJHKILJMKNKOMPMQORPSPTQURVSWUXVYVZW[X\Y]Z^[_\`^a^b_c`daeafbgchfifjgkhlimjnkokplqmrnsotpuqvrwsxtyuzw{x|y}y~z{|}}~ '                                                                                                                    ' *                                                                                                                                              * .                                                                                                                                                                                           .5                                                                                                                                                                             58                                                                                                                                                                           8<                                                                                                                                                            <C                                                                                                                                                                                                     C F                                                                                                                                                                                                                "F J                                                                                                                                                                                                                         #J Q                                                                                                                                                                                                                         'Q T                                                                                                                                                                             (T X                                                                                                                                                             *X _               !                                                     # ! #                                        !!!!##!"       -_ b               "                                               $!" $!                                #""# $$"# !      /b f               $                                               %"$ %"                         !         $##$"" &%#$!"      1f i              %                                            '$ % '$                         "          %%$%## !'&%%"#      2i p                '                                !"        !    )&"!'  )&""     !!     !! !   !   $         !  "! !!!(''(%%"#))'(%&   " ! 6p!t               ! ( ! !                              "#    !    !"    +'#"(! +'##   !!    "" !" "  %!         !! "!  #"!""")(()&&#$+*()&'   #   "! 8t#{    !       # + # #  !       !            !  $%  ! #   !$   -*%$+" ! -*%%! !!   %$!##$ !$ !  !!(#      "!!" ##!" $#!" !!%%#$!!%%++*+))%'--+,()! "   % "!!"$#!";#{%     # #    !  %- %  % #        #         #  ### "  &'   #  % !  #&   0,'&-$ #  0,''#  ##  '&##% & #&  #  # # *%    ###$!! &%#$ "&%#$ ! ##''%&##''.--.++()0/-.*+# $ !!'###$!!&%#$ !>&& !   $ "#      &/ & & $     !!!$ !        #  ####!!!!!!!!!!!!!!!!!!!!!!!! '(  $  & !  $'   1-('/%  $! 1-(($! &&  ('"""%&!'  ! "'!    "# $!"!+& ""      $$$%!! '&$%!# '&$%!"  $$((&'$$((/../,, )*11./,-$ %  ! " ($$$%!!'&$%!"@&( "   & "%    "  (1 ( ( &  ! !  ###% "       "%  %%%%######################## )*  &  ( !$  ()!   40+)1'! %" 40+*%" ((  *)$""&(")  !  #"%!)""!! $# %"$"-( ""!   "   "! !&&%'##  !)(&'#%""""""""" !)(&'#$ ""&&**()&&**1112..""""""""+,4312./& !!' """""#!#!*&&%'## !)(&'#$C(* $   ( &!!'  $!  *4 * *  (  " " %%%' !!!!!!$       #!!!'!  '!'!'!'%%%%%%%%%%%%%%%%%%%%%%%%! !+,  ( * "% *!!+" !  62-+!4)# !'$!! 62-,'$! !!!!!!(( !  ,+&&!$**$+  "# " $$!' "+$!$## & ( '$& !!!$ /*! &!$!!   "  "" "!  #""# (('(%% !#+*()$'#""""""""!#+*()%& ""'',,*+'(,,433411""""""""-.663401' ##) !"""""%#%#-!   (('(%%!#+*()%&!!G(. '   ,! (!$$ +  ($  .8 . .# ,  % % (((+ !!$$$$$$ ( !! " # "    !!!'!$$$      +$  +$+$+$+(((((((((((((((((((((((( $ $00  , . "!% ( ,!$$/&! !$   ;71/$8-& $     +( $$ ;711!+($ $$   !$$$$!,,! $# 0/"! !*!!!( $ &!,.!'/$!"&&#% ('!$#"+% "!%/($'&& *$! !*# +(  *$!!$$$'#4. $ (!$ &%$ "$&$ %!% &$#!#&%%&##,++,)) $&/.+- (*'&&&&&&&&$&/.+-() &&++10./+,00988955&&&&&&&&13;;8956+  &&- "$&&&&&)&)'1$#!###,++,))$&/.+-() $$M,2 !+!    0! $"# -$''"#/  +' 2=2 2&/ )) ,,,!!!!!!/ $$''''''""""+ !!!$$""""#% & %$$$*$'''""""""/' /'/'/'/,,,,,,,,,,,,,,,,,,,,,,,,!#' ' !!45 02%!$)#"!,!1$''3)$$'#!A;53'=1*! '"""""!!/+#"'' A;5"5 $!/+'!! ''""#"$"!''''$11! !$!' & 43%$#! $-$$#-#'#+ $ 12$+4 '#'(*&) ++$'&%-'!# %$)!4+'!*!!  *"*" -' $#$ .% !!/+##-'$$''' +&92! !#' !-$'#+ )'''+! ' ($(+!!'&$&!" )(()&&0//0,,')32/0#+.*++++++++()32/1+-++//5423/044>==>::++++++++57A@=>9;!!!!/##))1 %'"+++++,*,*5!!'&$&!"&&0//0,,()32/1+-!#''T16 $.$    3$#!'$&"/'*!*"%%3"   / ) 6B6 6)3 ,,# ///$$$$$$2!''******%%%%!".    $$$!!!!'"' $$$$&'     )"('''-'* * * """%%%%%%3)3)3)3)2//////////////////////// #&*  *####89 36 (! !!$'," &$#/$5'** 7,& " & *%#F@97*B5-$ * %%%%%$$2.& $"* * F@9$9#'$2.* $$  * * ""%%&%'$ $* * * * '33$#!#'# * #!)!87("' &$!#'1''"&/&*&!! /"'"56'.!!8" !)  )  + .- ), /.  ' * )(!"! "2*#&!#(!' ,#8.*!!#.$#"#-$-$1)"'!& ' "1)$$2.&&1)' ' * * * !   ".)=6$##&*"#/'*&!/"-*!! !  !++-$"*+',! -!!!!!!!!$$*)')$$""-,+-))433400 +-7634&/2.--------+-7634/0!! --!!!!!!!!3398683388CBBC??--------:?!!!!!!!$$$$3%%--5 (*$-----0-0-9$$*)')$$))433400+-7634/0$&**[5: '!2'!!!!!!!!!!!!!!7&%$)!')$2*-#-$"((6%"!!"" "!3!"""!-!!:G::,7" !!!"/!/!"%333&&&&&&6$!!!!**-----!-(((("#$2 "!"""""!"!!!!"&&&$$$$)$*"! ! ! ! ! '"'"'"'")!*!-$+*!*!*!1!*!-"-"-"$$$"""(!(!(!(!(!(!6-"!!!"6-6-6-" 6333333333333333333333333"&(-! !"!!!! -%%''<=!  7:"+$""$$&*/$""!)!'&3'8*!--";0)"$!#")#-(&!!!! !!KE>;-G90&-"(!(!(!(!(! &&62)"'"$-"-"!KE>'"=%*!&62-"&&! ! -"-"$$(!(!)!'*'"!&! -"-"-"-""*99&%$&)&#!."%$!!,$=<+$*")&$%*!4!**$)2)-)#$""2%*%::*2$$<%# # -###+""""-"/0#,/ !21!#!"*#-"-!+#$#"$ 5-!& )%% +#*#/&<2.#$"""&1&& %%!0'0'4-% *#)"*#%4,&&62! ) ) 4-!!*#*#-"-"-"$"""%2,B:! !!&%&!!)-$&2* -)#!""2%.-##"!#"""#$!-//&%-/*"/#!"!!!/########&&-,*,&'%%0//0!!!!!!!!--876833"""""".0;:78!!!!!!!!)251////////.0;:7824##!!""!!//########77==:<77!!!!!!!!==HGFHCC////////>@KJGHBD#######&&&&7!!!!!((009!!!!##!!""+-'/////3031>&&-,*,&'--876833.0;:7824&)-!-b8C"-&9- &&&&&&&&&&&&&&@,+*0&$-/"*#;14)4*$'.->*'&&!#''%'&#<&(''&"4"! &&C R$C!C3?'&&&'!7!&7&'+!;;;,,,,,,?*&&&&1144444&4....')*######:%%%%'&(((((&(&&&&!'!,#,#,#****0*1'&%&%&%&%&%-'-'-'-'/&1&3""*#2$####$1&1&1&8&1&4(4(4(***$$$'''.&.&.&.&.&.&>4'!& & & $'>4>4>4'! >;;;;;;;;;;;;;;;;;;;;;;;;',/#5& &'&&&&%4++--EG&!"$!% %%@!C!!'2)''**,"17*''&/&$-,<,#"C1&44(E70'*$&('0(5/-&& && %$!& &VPGD4$RB8,#4(.&.&.&.&.&%,#,#?:/(-'*#4(4(& VPG-'G+1&,#?:4(,#,#&%&%4(4(**.&.&$/&-1-(& ,#&%4(4(4(4('!0BB,+ $* ,0,(&#5'+)# &&2"*$"FE2*1'/,)+"0&=&11*/;/4/)*'';*0*BC19)*E+#(%(%4!(($(0'(''!4"'$68(3#!6%%%&!:9&$(!''1(4(3&2)*#)'"#*$>4&!,%0)+%2)1(7,E:5)*''"'",#9,,$+$+&8-8-=4*$1)/'0(+$=2,#,#?:&%/%/%=4&!& 1(1(4(4(4(*!'!'!'!*$"93LC&'&&,+",&&/4*,;1%4/)&'';*75))"'"&($"'"('()'4"68,*461'6)"&('''8)))))))),,4312,-""""""**7668&&&&&&&&33@??A;;#""#((((((57DC?A''''''''/:>98888888857DC?A:<))""&&((''88))))))))??GFCE?@&&&&&&&&FFSRQSNN88888888HJWVRTMO))))))),,,,?&&&&&..77B''''((''''24-88888;8;8G,,4312,-33@??A;;57DC?A:<,/5&4qCN ~17HQS_awx67O_cuEMWY[]}    " & 0 : D p y !!!"!&!.!T!Z!^""""""""+"H"`"e%  28IRT`bxy78br HPY[]_    & 0 9 D p t !!!"!&!.!S!U![""""""""+"H"`"d%feAa&`HH z@:*߶1mTxS/3)ߘߕߍߋ߈߅y]FC# zlpRZXt<ghri_kwlxmynocpqestabcdue   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ag}~rv|df{mystpqnoznblxhcej iuabced_@?XUTSRQPONMLKJIHGFEDCBA@?>=<;:98765/.-,(&%$#" ,E#F` &`&#HH-,E#F#a &a&#HH-,E#F` a F`&#HH-,E#F#a ` &a a&#HH-,E#F`@a f`&#HH-,E#F#a@` &a@a&#HH-, <<-, E# D# ZQX# D#Y QX# MD#Y &QX# D#Y!!-, EhD ` EFvhE`D-, C#Ce -, C#C -,(#p(>(#p(E: -, E%EadPQXED!!Y-, EC`D-,CCe -, i@a ,b`+ d#da\XaY-,E+)#D)z-,Ee,#DE+#D-,KRXED!!Y-,%# `#-,%# a#-,%-, ` <<-, a <<-,CC -,!! d#d@b-,!QX d#d b@/+Y`-,!QX d#dUb/+Y`-, d#d@b`#!-,E#E`#E`#E`#vhb -,&&%%E#E &`bch &ae#DD-, ETX@D E@aD!!Y-,E0/E#Ea``iD-,KQX/#p#B!!Y-,KQX %EiSXD!!Y!!Y-,EC`c`iD-,/ED-,E# E`D-,E#E`D-,K#QX34 34YDD-,CX&EXdf`d `f X!@YaY#XeY)#D#)!!!!!Y-,CX%Ed `f X!@Ya#XeY)#D%% XY%% F%#B<%% F%`#B< XY%%)%%)%% XY%%CH%%`CH!Y!!!!!!!-,CX%Ed `f X!@Ya#XeY)#D%% XY%% F%#B<%%%% F%`#B< XY%%)) EeD%%)%% XY%%CH%%%%`CH!Y!!!!!!!-,% F%#B%%EH!!!!-,% %%CH!!!-,E# E P X#e#Y#h @PX!@Y#XeY`D-,KS#KQZX E`D!!Y-,KTX E`D!!Y-,KS#KQZX8!!Y-,KTX8!!Y-,CTXF+!!!!Y-,CTXG+!!!Y-,CTXH+!!!!Y-,CTXI+!!!Y-, #KSKQZX#8!!Y-, IQX@# 84!!Y-,F#F`#Fa#  Fab@@pE`h:-, #Id#SX<!Y-,KRX}zY-,KKTB-,B#Q@SZX TXBYY-,Eh#KQX# E d@PX|Yh`YD-,%%#>#> #eB #B#?#? #eB#B-dA  0P`69F AU UU/(F@F߳@@ F@P`` }G2}HdOo@8;F}>"d 0P`@ʇ69Fυ ~y3}{7|yd{-z_yoyyxtwu~vsdu}t`spss sssr~oUq~pUh[[Ug[_Uf[]Ud[ZUc[\Ub[YUXCVUWCUUSMURKUQJUPIUONU/NIIIG[CUF[BUE[AUoATSKRKP[%S@QZUZ[XYBKSXYKHSX@YKSXBY++s+++ss++++++++++++++++tu++++++t++++++t++++++s+++tu++++++++stu+++++++++++++++++++++++++stts+ssss++ss++++t+++tu+EEGGGo GbHNgCDh;aIfNO :TCn]D`LETPk=\`3LQuY2EH]O>Bn /rRn=*X<R5NPH>b]`TP DDDD$X0  t p x@0` th$  l ""l"#h$$&'|()+D,D-.P.01334d445(556H789:<<<>8>?d@ A`ACtD0EEGGIDIJKOPR RSlSTlU,VVWWWXdY Yp[L[x[\]@]]]^$^P^_4`X``bbPbPbcdefgii|k$lLldllnnop`q,rrlrststttu0uHuhuuvvvww@wty0z{||<|l|||}(}\~H~t~~~(\LxDp<8@lt@p, Hp(T|(T|@PT4\$LxDp<(T|,X 4D\tPXHt` Hp0 $Lx8`4\|hhP,T$H|HpHt0XP0xLL|8l4XŠƈƜư(<Pd<|ɼ,ʜ |˼<|,lͬ,lά\ϴ dмD4DX֐ 8 0@P`pۀې۠۰ 0@P`p܄ܘܬ$8l<tބޔޤ޴$4DTdxߐߨ߼d(Xd xpH |Ld Xx      t8 8d|,<T  4 L d |    !!D!l!!!""L"""#,#d##$$%(%\%%%'(\((() *,,H,p,,,-- -8-d-.@.//@1D1l11122L2223 3@3p3344,4T44455D5x556686d6799D9p9;h<=>?L?x?@BTBBBC(CTC|CCD D4EF|GpHIKMN\NOQtRXS\T`UVVWYPZZ\X]H^@^`0abcdcde<efghiijXjkxl4mmLmnnppppppqrspssstXthtxttudutwxxyyyz\zlz|zzzz{ |P|`|p|}l~<~DPdLdt( `p  lXD\t $lpPP, D<`0T<8 Ĕ 0@ƴ,ːL|8hx\׀אר`pH$|,x(hx(| ,D\t $<Tll ` 0@P`pP`pPdP<( p 0P   P    $ x    8 `    $ T |  8|(TP4<(T0H`x,`$<Tl<p<p0XLd|0`(XpH|  L d |     ! !8!`!!!""4"`"""##8#d##$($p$%%H%%%%&&0&P&p&&''D''((D(((())$)D)d))**D**++X++++,,<,\,|,,,-4-\--...4.L.d.|../X/0011<1d11111223d404\4455,5h555567,788<8t8899,9d9999:::;;H;l;;<<(<@><>\>|>>>>??<?\?|????@@<@T@l@ACCK|OQ,2@  //103!'!222vG #@   c[??9/3210#.=3#"&54632 : d$$$$TDDT((((HZ&  1j@              ?        /!0 yA y B BA???]?]]]+}]]+}10]]3733#3##7##7#537#537337##T#Xg"#T##T#Xf"#TT#JJJJL=3@4))(W%%h Y  +) `'+- $ $s  $ 0'--0'''''5 104jw & f   10,*--g-w--Jx?O/]3]3]2?]]33299]]]]]]2^]]]]9/99+}2/]2]10]]]]]]]]]]]%2654.'.546753.#"#5.'7M>-;;.UQS,LH6,GW ~ K2(3?C@fCA@ABCBCB@B@*  )  : 44)44?***&**E  &  & )DCABBAB@A%}7=}/F}}E??????^]]]]]]]99//+}104>32#".74&#"3264>32#".74&#"326#2+:"":++:"":+*''**''*+:"":++:"":+*''**''*7&54>3267#.'#".73267'4&#">.)6J/A'(?+'2" M!#;b #)lFAV5] 3&1S %-',/D''=1%JO(@,(8#8.&?M )a0&J#*)3%9D!*#$! #0Q"/5'A> ")H @   Z?9/10#.= 7 21k55k21NG= a@B *           m }   W db?3?3]]]]]]]]q2]]qq210.5467JHBN__NBHJ+]-__-]G a@B % brX bd?3?3]]]]]]]]q2]]qq2104&'7'>JHBN__NBHJ+]-__-]%7,@yvxi#@]H#""t@{ H,,**' .- 7' !'##+de+[?2/2]3]9/93233]339/33333/3/3/3+3/]3]]3/]]3]]3/3+10]]]]>?'.''7>7./7.=3$F)(P*!:I##I:!*P()F$Z*M)&T 5#3$G&&G$3#5 T&)M*55! 1@ P  /]3/9/2103533##5#5PPSO&ar .@ H  ? O  /3/]9/322+107'>54&'% Hr?{83i.  W-@@@ $H/+]10^]3#WW8y " @ H  c?q+q107#"&54632$$$$6((((G'@ddbb????/և+}10#3G_S]2 ݵMM@ MM @M M MMM@*M M M @M@ML L!MML L M @M WMMX?++?++++++++++10++++++++++++4632#"&%4.#"32>2{mm{{mm{n 4&&4 4&&4 [9fL--Lf99fL--Lf]q P@5 _o 0 P ` p  UV??9/]3]]9/^]]3910>73#]9l-B] #)-&E4K4 8,@/ @M@ L.$%k {  -$M$M$@M$ML LLM %L% M%*L*M* L*@ M*X U??++++3++99+++++2+++q2++99//+}10!!&454>7>54.#"'>320<-(?\2>-# +0&. $2>$mm$CA?.2.O /QH@//2* B e78@!!!MM6M4M@bMYi#####+{&]&m& & &5jIY++++:659&& 65M50L0W LX?+32?+3+29/9=/22^]]]]]99//]]]]]10+++++]]]72654.+532>54.#"'>32#".'7YM 4E$!5,!+0C$ %.57P4>,2%AbC70$ODF;&4 L -"( G 1C'6M.<%-M8  Q( k@?(+(8   ;_o   UV?3?9/3/22r^]22/^]]q210^]]7>733##5!3(BS_0YRRZ6=:3,u|x0HL OVZ+A'@ M M M@;MM"j)##    (#" LMM@ M M V M LW?+3+2?9/+++++229+}^]]10+++++#".'732>54.#>7!#Ǘ?`B6.$M8-=&BnTNxg.N8  Q"-&;(1VRQ-O374?--M$MM @9MMW0Y++w/  !GW. &@ L&WMX?+?+9/32]2]2]]9/]]310]+++++4>7>32#"&7"32>54&?8g\;aL4 A#;R45R8rw#< 4(!. Aej7N4S= $;M*&PB*  ,N;#,3DH<%@U V?2?9/103>7!5!-BO&!PG3P-QM&]7!3E2M2@ M.M MLEM@M4@!LCA A1//M/A / A//A@7MP M_   ,vG>L" M F 9 LX'M'@ M LW?+++?+9/3/++]]99//]+]++}++10+++++++%#".54>7&54>3232>54.'4.#">9V8AW4)b7N08P3%v 2&"3 -?(-1 , - "7&*2*G5%8C5.%8m&E4"5@3," 8'!( 0$ J0$%,& C3-@ $ M MMMMMM@) M ! L!wOh/+M+@M+M. @ M &M&XMW?+?+9/+32+++^]]9/]]]3+2210++++++++'2>7#".54>322676454.#"?eL3 B#;R45R89W;#> 4(!. AN5T= #;M*%PB*(Lm  +O;#+3DG8&@  PO++4&a&@ PO++45L `@& M@ MP/]++//+}/+}10%5%cP+MPM5.@ ?O0@/]]9/33/3107!!!!566O$O5L d@"@ M MP/]++///+}+}107'%5PcMPMy!- M MM@6L"(@8H((Xhx ' 7  ///. ++%cMLh?3+2+?q2]]]9/^]3/+10++++"'>32#&54>54&#"&54632B?"U09I,&.&N$($6 $$$$w"H0;"5.*-5!  4-'(,*5((((AguEU@DCCg?U?X5)##i IM.//VxYAAAAAAWS y  v&&&'&7&W&&777VMP3232>54.#"3267#".54>32'267.=.#" *>0O: ;U63U' ,RuIIzX14]L;M Y3YvEDsTRn@4H+"GFD+ ;V90U?$ $:I&?oT01[TXY-J3iplk65eZDhG$Q  XCGTܹ@4 M     L s  @C M s  @py_o B A AB????99//]32^]]]q9/+}++}+10+!.'!#>73.'%?e(FCB$Y$BCF( ?"#?-W-nKKnWKKWVP)8W0vML@ M$v@ L@L@ M0M::@ M8s985| *y)) E|F?22?39/922+++++++2+10]".'>3232>54.#'32>54.#"11001032.#"3267IxV/5ZxD+E3  "+37X>!:W:CX$6H0\WW\0 P &Hg@>fI( P V@L M MMv@ MM@L@ Ms M| M E@ M|MF?+3+2?+3+2++++++++10#"&'>322654&#"8fU*e))e*Uf8)- -[ZW+  +WV 5@   s y y ByA??9/2^]103!!!!!V"_SQSV (@ s y ByA??9/2103!!!!VSR:^'ѵ M@,M M's@M@M))@M)@ M)M@ Mv @M M @ M @M (@M M|@ LME'$M$ M$|F?3++2?+3+9/+2+++++++]++]++210+++3#".54>32.#"3267a -;F$IxV/5ZyE/I4!`25W=";W;*; U 0\WW\0 Q&Hg@>fI( Vk 7@ s  s yABBA????9/2^]103#!#3! aaaaSK@V$@MsBA??]+103#VaaKy@ Mgs@ M@ M @ M @ M  A @M|  L MMMF?+++3++2?^]++2+]+10]+%#".'7326535YC"9." &C2HBa2VA%  M!IYVo@`MYy M   M H  (  s Ms)9 @/M0 P `  sA{AB BA????9/]q9qq?22]+]]+}]q+3^]++10]]]]+]+#.'#3>7WNUT%+`\QrNWY*aa#TRI"SWT$Xdh//_WJ!STMV@ sAyB??10%!3_aTTC%)J%M$M!@Hf!! fMW@9M i iWs+ M+ Ls@$ H"sO)vO@& H  L s  *@H|))BAAA" M"L@ H|A B??]+]+2+????9/+^]++]]]]]]2+++]]210]]]]]]q]]]]+]+]]]]+++%.'#>73>73#&' !&+&! _  U>>::>>U"_ !&+&! `JV[Q@vZK,x66x,v@Q[VJV@  w  s  s@ L@C Ms BzkZIbr@P4A A ABB?????99]]_]]]]]]]?^]+++}_]10]]!.'#3319BGFB_M/ki] _%\cf_T 25K:' M M@M MMM M MMMM@MM Mv @ M M @M @ M ))M@ Mv@MMM@M(M M@ M|F#M# M# M#|E?+++?+++++++++]++++++10++++++++++++4>32#".732>54.#":5[yDCwZ55ZwCDy[5f ;U55T; ;T55U; [Y[..[YY[..[Y?gH''Hg??gH''HgV=R M M Mv@L s y B| E?22?9/2+10+++2+#>"32>54.+QtIMa)e2>!I2P75Fon32.'.732>54.#":5[yDCwZ5(G_6'32"32>54.143j/0/ Wa)e*I>!E2P73E"AOV(&NG> nmEb- 3*'4 !0@9'  &"%!!!s!!v%L% L%@a Mp%%2v M M M  M .-@H-1y=M tBG..g|-MX--(FwV|@MwVE?3]]]+2]]?3]+]2]99//]]^]]]]+2++++]+++99+}]]]]]210]]]]]]7254.'.54632.#"#".'7.;!&D2|m?gS70$(5 -K7y)E6' ^Eh -! !->,[fO  %& $2E1[b P %:@$ sByA??^]]]]]10###5%aTaTQ_Y@. M M M MssAA | M MF?++??]^]10++++".5332>53XDd@a\J%=,aAc*Id:Xh^0K4M:dI* @*xg7 G w   x 8H@0 L s  L  s   LHs@L   A A ABBA??????^]]+++]Ƈ+}++}+]]10]]]]]]]]]#.'367&EBA"["ACF%k6h77h5nKKn܀% ^@$8  M8x7w @ Mt@U Ls""7 6s { L s@`M6)IY sp   !BBA Mxf ` D T  AAABB?????99]]]]]]+3???^]]]qՇ+}]]]q+]+}+]]q]q+}]]q]]]+}+]q+10]]]]]q]q+]q#&'3>73>73#.*V+a5Q#f (Z'T'\( c$Q4a,TֆlTN9ii8Nlc㳠@, HM        L L@ L  s  @H LM Ls@P`/   BAAAA B BB????????^]]]]+}+++և+}+++10]+]+]!.'#>733*2661+k-wCpoDx.IOO##OOIX`GaYP @R L  s  o  s Lso B A AAAB??????9/3/^]]2+}+]3+}+103.'3>73Ez6r&^21^'l6zDkdRNNRdj!M@*   s yA y B?2?3^]և+}ć10!!5>7!5!=DHE> 9AFEAhIYced-SA(`df^RS`GA@  //103#3#`ኊIIG)@bbdd????/Ƈ+}103#]S_8G@  //10#53#53ኊI6I/;l@B8hx7gwJZ/]23/3/3/2/2/10]]]]]]] 'CJJ&&TG@ K?3/3/10!! jO=D7@ LM M/+++10'y/*+5@ ! M,M"M@M M)M)@ L@ M8 L7723@ L3 @ LM@MP L@M6)$@M@ M@L@MQ2/3P?32?329/++++32++++++2]++++10+++++%2675.#"2#".54>3254.#"'>!3 - /'@/8M/ +6?,J6#&-# MP! M@+ MMMM L#!"N L M Q!L!@ LMP?+3+2+?++32?22++++10++>32#"&'732654.#"E-8W<%D`32.#"32672?_A #@]9#F 7NS)@.%< &+ (Hc<32@&(9$ZK&3] dA<`D%32!3267#".%6&#"3(BV-ipRW1C U6Dc@eA9 1"EgD"QTN )Hcn?Q(4PD@!  J ILMO?+2+2??210]^]2.#"3##46);  , A4]b P G<-NF7dm3D-( M' M#س MMM M @ M-@ L@ L/%M% M%@ M%@ LL@M@ MHM.@!LR" P- L-* L/^]3+2+?32?+32++++++++2++210+++++++%#".54>32#"&'73265.#"3267 E./S>$"A^;Aa!|~1W Q,SI/(KQ'3%>4?^A9_E'0xl Q BHbQ-@*Pn MK L@,M  @ L @ L OJ JMPN??+2??2]q++10++]+37>32#4.#"P];>R1] 2%  #?X50D*PB3@ @ H SIJ???+22^]103#3'"&54632]]/####^!!!!EO L@M  @ HS IR?22??+32^]10++"&'732653"&54632 &  0)][,####L;9da!!!!!P@M MM M @ M  M  M MMMLMM@LM M LL@MMMM@" H I LIN J JJJ??????9/+?+++++}+++++++2++10++++++++++#.'#7>73 CA:m7<>]]:82m6:;DOQ##IC8&:<7<==N =@ ML  L @ H  N Q??+10+++.57VH] FJp P-!@# ML MLMгM L M سM M @M M MLMLMгMM/ L/@7 M/@M O+-.,JJJ+&&M M L P?+3+3/32+2/3???^]q29]+++10+++++++++++++++++++>32>32#4.#"#4.#"#P iE2D,6".B,IPn@M M MMM@M J JMP?+32??]^]10++++++>32#4.#"#P jE>R1] 2% ]#?X50D*I3س M@ M( M( MM!!@#M @ L @ L MMPMMQ?++?+++++]+10++++#".54>324&#"326$AZ55ZA$$AZ55ZA$aODDOODDO>dH''Hd>=eH''He=XggXXggPG!z@X  M MM0 MMMMMM L#"PK L  LQ?3+2+??322++++++10++++4&#"32>7#"&'#>32ZK*/@&(9$a32#5#".$9(&@/*KZa%D`  3(3]!bQ K (-:%س M%@M 0 M @M M+@M& L%M"M"M"@ M!M!M! M L @ M M M@BMMLM L!!!$/?/ *.MM+M+@M+*'QMP?+32?32++99++]]99+}10+++++++++++++++++++++++72654&'.54632.#"#"&'7976>7(fX,&@-': (%:)kcENFC!"( "0#EQP! $3%HJPJ^@/? M  @ H O @L  L MQI??++3+22]+310+]3#3267#".57 $*3 F-5E*]N'3 M6O4JP@!MT  I I@ LLQ?+3+2??]^]10]+%#".5332>73 iE7'i1T1i'd !%&&%! jj 1omf((fmo1$@ @&MMM MM@M(MM#M@ MM$ M$L$ L$@ M@$4$$&*H$@ %Hr$@$P$`$$@H$  !$!$&!M!M!M@J M ML M M[ (+H}_oJM@M(M M@ M LMM@ MM@M M M@ M6vdRF?OM MLM M MO;@&*H %H}O_o H    M@ MMLLM MT@'+HvDd@HMM M M@MM ML M M6 gVJ?O_@`@    %$J!I II$dt+k{I I IJJJ??????99]]]q]q3????^]q]q]q+}]]]qqqqqqrr+++++++++++qqqq+r++++++++}+qq++rr++++++]q+}]]qqqqqqqrr++++++++++++++]qqqqqqqqq+r++++++++Ƈ+}+qq++rr++++10+++++++++++q!.'#&'3>73>73?>Q.X'd "! M !!  `'X.HddHg/nnf&/ikg--gki/&fnn/g@$F  I      L@ L    @0 L L   JIIII J JJ????????^]Ƈ+}++և+}++qq10!.'#>7'373 &((% `#a0i|}c0e#698996D@AED MM@ MMM  M "@L@ M M@ M    &M '*H       ! RII I I?????329q^]2+}++++++Ƈ+}210+]++]+]++3267&'3>73#".' #7>?m$d &,&8`$X3+4A) a 17x -hnn3imv-A*$A@%  I J??^]]և+}10!!5>7!5!EOOsFMIwTfp3N>,ji] N(GE(Z@   @( H((b(r(("" H")(  //9/3/+]2]]+2210^]]2>=4>;"3#".=4.#(" ,I691# #196I, "O#)@+I):BHHB:)I,@)#`G//9/103#`WW8G%(^@$g    H"m}@ H*((  //9/3/+]]]3+2210^]]]"+526=467.=4Ȕ3%" ,I691# #196I, "#)@,I):BHHB:)I+@)#*~!X@: y!Pp$ )"!  /223/]]]10^]]]]]#"&'.#"'>3232>7 1#32 @1#32 e0+ 1+ -'3@y'X&.!"53*G1+(....F...4!!!%11((+"""%%Q T?]]3]]2]?]3]99//23/33/^]22]]]]2222]2222]22299//3310]]>32.#"3#3#3267#"&'#53&45<7#5j,:9&9)YC*=T3uZRRuu P (9#H   HYF O qH   H!r 4@ H  ? O   /]3/]9/322+107'>54&'&Hr  4f,,Z'  E_@  M(M L@$M M R IO?22??3222210+++++2.#"3##"'7326546';  -D4[Q)  /(a P F=-NPda L;9.dm!er&ccby&*'w'oF@* O_    L?//9///^]]]]210#.=#53533 . PTDDTMM'ox@L O_O_    L?////9/////^]]]]]]]]223310%#.5#535#53533#3 . PQCCQMMMM4FD.@P_/3]3/2/]2/]10'''aa',XX,23?CWc@CA@ABCBCB@B@*  )  : 44)44?***$** DD)DD^ XX)XX&NNNNe  &  & )dCABBAB@AI%}[7a=}S/F}}E??3222????^]]]]]]]]]]]]99//+}104>32#".74&#"3264>32#".74&#"326# 4>32#".74&#"3262+:"":++:"":+*''**''*+:"":++:"":+*''**''*32!!!!!!&Gf?! ,?fG&Qe9:eQ8P`9[@dE%%Ed_,XYYX,QOQ< KM@&M: + @  /?o/]]3/]22^]]310++<7>7<&H  4f,,Z'  ! )@  / ? o  @/]3/]]3210'>54&'&H  4e--Y'  <&nn!e&oo44 /9/10#".54>324!//!!//!a.""./""/S/10!! SOS/1010!!SOg`U@7iy/  4D9I/]3/3]]]3/32^]210]#"&'.#"'>323267`## 0##   h0D0@SH0  p  p++"K&@H&%!!!!"10[%[+t++[" /33?229/3]]??]]+9/]]]]+9/]10###5!#.'#.'#>73>7.iEi F 1 E B ==3{99832, ,23899{308::80(&V_#@ .2*#P 3O++4(?(@@H/322+22107'7'7iAooA?##3}'3<@hw$%<V.f..  =<gP7I+Y++P$!!F1V11Q%hQ?]22?]23?]2?]39/^]]]]99//]]]]2]99310]"&'#".54>32>32!32674&#"326%6&#"Oi b?5ZA$$AZ5?c!e6hqSV1C UODDOODDObA9 1! :368'Hd>=eH';386  PUN XggXXgg?Q(4Pz&<o@% POO+++44GG #@   PK??9/321074673#4632#"&X : d$$$$TDDT~((((V$l@C@H &&V%FV  J IY P?^]33]2?]33]2]]]229/+32104>753.#"3267#5.V1H-S:47NS)@.%< D$S0H0#1VD. M ^Z(C0L -CV<"M@TMMM M M$"s L 0#|y gEy B??]39/^]3/22]]+29/210]++++++3#!!>=#5354>32&#"!v ]]9P0,<5<,uK2n2PGGKSJa9 P&@0,mI2@`4/4O4 e u   jz))3jz eu$$$$..../]]3]]3223]]3]3]]32]23]]310^]47'76327'#"&''7&%4.#"32>YI:J/;;.K:JJ:K096J:I7 ++  ++ [=-J:KK:J5 4J:JJ:I->.!!.-!!-$@n s os  s  o   yyBAA A AB??????9/3/222^]]22]3]+}2]3]+}2]1035#535#53.'3>733#3#6a+g$T-,Q%e,a6jGfGXRNHHNSWGfGj`G@  ZY??9/21073#3#`WWWW+;Q @%67++(WE7 7432/e+  JM-58 6* *27227--S?B;#8R w?2 22J*v**k*V*?z?m?J z i Z  J?*S;;8Q?32]?3]9]]]]]]]]]]]]3^]]3]]2/]2]22]2]]99]]2210]]]]]]]]]]]]]]]]32654&'.54>7.54632.#"#"&'.'>54.DH8896@$A1 aX3XE4'9 ($A1$jbCW $)8! $(4 !$#' (8(-% 4"EQM !! (8(,$ 8%KM 3# 2##hU )@ H@ H  //3/++10"&546323"&54632]########h!!!!!!!!A'I@7EEhwXh( K>>>>>w--gwW g   &  J88;}7p2a22}gHXEDA}E(n((#####}hGWF?]]]]]]]22?]]]]]]]22]^]]]]]]]]_]]_]]]]]210#".54>324.#"32>".54>32.#"32678^|DD|^88^|DD|^8I(Gc;;cG((Gc;;cG(+F34F'+  5#3D><'3"+[U]11]UU]11]UBmN**NmBBmN**Nm4M21L5 <GD>O< &IP1@*HXHW%%%%Wg&6F3/@ H/  IYi@$ H2.+%""" /h?39/]^]22+]]+]]]10]]]]]2675.#"2#".54>3254.#"'>, "#/")8#R)!7(,: " 7 =d ;$3  , , ? ?&ll55@ //99//10#!5POWA'=HE@V757E754E4U4 w4((BBBBw5+++88==5j5'5W55w(Xh J,HHHHu.Y.i..@.)..w  & V f  I>>>{8 +0+++-E}2a2U22F22}gIYE=-n--I-Y--#####}hFVF?]]]]]]]2?]]]]]]]]9/]3]^]]]]]]]]2_]]]]]]]2}/23]]2}/10_]]]]]]]#".54>324.#"32>.'##>32'32654&#"8^|DD|^88^|DD|^8I(Gc;;cG((Gc;;cG(9CG<VZ'$  &-<9(  [U]11]UU]11]UBmN**NmBBmN**Nm-Q@A%7 $,0'%.J@0/]]3/2/10!!.H2i@H   @ H  ! F=&h H/+]?]]]]]99//+^]]]]10#".54>324&#"3262&33&&33&C++++} 4$$4 4$$4 #))##))5l `@:   /P   \ @O  _?]]]3/?9///]]]3]23103533##5#!!5PP6OO;8!_@; # 3 gwV    #"I  fvUFq?]3]]]299//33/3/]]10]3!&>7>54&#"'>32( *!&"+!$E-EBV(''! ?!3*#1 1#: 36-g@$$7gv" *"@H"/+`**. +*'q?32229/9]22+9/]310]^]]2654.+532>54&#"'>32#"&'75((%.C"I=7#)(?,@1q!9 7 ;.1 3#+! < D;8@@L@M M/+++10'7;/͉*PG8@    IK0 HQ I??3+??99//21032>73#"&'#>E ] e<7A\_L9g+G!1@uu CEC??]?9///10"#.54>32#.No|-TxJ*`)N*o hf7R8 #8iEC2@  ij??29/9/310#"&'73254&/>73 (+ $ @%! 9   "2; .@   { p?2]]9/]210>73#2&J1F9m )~&+HZ@>     ! hg??99//^]]]]]]]10#".54>324&#"3260B''B00B''B0J73287337+F22F++G11G+8CC87DD(?&xx2R&'nkp2;&'nV R&'nzpD!-a@>f "((p/@ H.+%Py  )  R?]3]2?+]29/3/^]10]]327#".54>5<'34632#"&~71B?"U06H-&+&N!(!%$$$$&1"H,7!4/*/4!  4-((*((((&$C@ P O++4&$@ P O++4&$i@ P O++4{&$u@ (P 'O++4z&$@-P %O O+++44G '3" M @H .@H..1@H(@RH((11 !"#$$$s'&%$$s$$5@5p5554HW@- H+1 y'@!!!$$z$Y$i$!$!$1ABB???2/99//]]]]]32+]+^]]]9/+}+}ć33/++3/++10+7#>7.54>32#./.'4&#"326?e%B?=!%%!>?B&j ?"#?fG*%%)Gg-X-OWKKX@F Hs syy   yAyB?3/3/?299//329/32+}+10!!!!!5!#>7!3i FZ/h9mhe2s5r=dOQ,Y-lGLHj:CS@@LB@ Hd.S.B.#.3...U*#*3*C***h)9j/z//.s***b*E*U*@~H*>:>>4,::"#BvhWA18>)J"Z"j""Wg|J#Z#j##h&x&&W&&FEUeXh|EUe g X  E?]]]3]]]2]?]]33]]2]]]]]22/39/]10+]]]]]]]^]]]]]]]]]]+.54>32.#"3267#"&'73254&/>J>dG'5ZxD+E3  "+37X>!:W:CXkR (+ $ 7[}OW\0 P &Hg@>fI( P  ! 9  V&(C@  PO++4V&(~@ # P O++4V&(im@  PO++4Vz&(m@  PO O+++44&,C@ PO++4D&,@ PO++4&,i@  PO++4 z&,@POO+++44%w@M)iy vVf''p''# s & y !!| WE#|XF?]3]2?]3]29/3233]]]]^]]]10#"&'#53>322654&#"3#8fU*e)PP)e*Uf8)- -[ZW+ 9K +WKV{&1u@ # P "O++4:&2C@ +) P*O++4:&2@ *( P)O++4:&2i@ -) P*O++4:{&2u@ 7( P6O++4:z&2@*< P4O(O+++44Pa @X `          /3]qr33]qr3/]23/]r39/3]qr3]qr3/]33/]r310%''7'77888a888:$/l@&E.e.6-f-,xh9(JddfMY  @TMF j l My..Z-Z,f)U(H#VETWejY j w)9 L@ MMMM@ M % M M- M @ M.-  @ L @ M*M*M* M*v M @M @ M 11M@ MvM@MM@M0L..%M% M% M%|@=L MMMF-L- M  M  M | L  M MME?++33+2++++23+?++33+2++++23+++++++]++++++99//+992+}++++333++++++]10]]]]]]]]]]]]]]]]]]]+]]]+]+]]]]]]]]]]]4>327#"&''7.7&#"2>54&':5[yDjR;>@'-5ZwC5_):>>(.f,?7O5U; 5T; 5[Y[.8M.S-RY[.K.P-TmF+'Hg'Hg?5W#e)Q_&8C@ PO++4Q_&8@ PO++4Q_&8i@ PO++4Q_z&8@,P$OO+++44P&<j@  PO++4V=RM@#Mvs | y  AB??99//299//22310++732>54.#*5>3 +#I2P78N/')7+QtIMa 6*(5 q54&#"#4>32  /#af*$!'20.9!+ ,  44C=]6P46N1h'! ! '5%NT Q ,%'2(6 ,1LC*2R: +;+&DCR@ 97P8O++4+&DE@ +86P7O++4+&DiK@ ;7P8O++4+&DuL@ E6PDO++4+&DK@8JPBO6O+++44+ &DcK ?6P+44,%;LU @VRc4T4w+%H y, M9-''fUG%'UUfUUF22B?JJ! w---{;   - -WV0WU111BEEEP'w*h***P !$P?<Q859Q?22?32?32?^]]]39/]323/]]9///]_]]]]_]]33]2]]99]]]]]]_]10]]]]]_]]]]]"&'#".54>3254.#"'>32>32!3267%267.'.#"%6&#"e9U !.9"2O8#&-# M/*-,  PUN O @$!3&?Q(43C:@ O<_<<<@H@ H &`666<<W--@DMW;'X*I**&#P5W2F22  H 6M )iyQ?2^]+22+2]]2?3]]2]]+]]]9/]10++]#"&'73254&/>7.54>32.#"3267>  (+ $3M4#@]9#F 7NS)@.%< "' ! 9  "-F[632&''7&'774&5.#"32>4k ,8_H;Y;!327&#"4'326$AZ5O:156$AZ5'E15}$1EO("3EO#]9>dH')@(F#^9=eH'@(A-gY>/gJ&XCg@ P O++4J&XZ@ P O++4J&Xi`@ P O++4J&X`@*P "O O+++44D&\C@ .#! P "O++4PG%h@E#ZIX)9I'&!PNK  Q?3]2]???3222]]]]]]]10]]4&#"32>7#"&'#7>32ZK @&(9$a733267#"&5467>.'&?e(FCB$Y$BBF( (  #86 W ?"#?-Y-nKKm=)#+ WKKW+EK@u(7&0 HB8)& '''!!??'M/MH I0I@II 555,555L!$ 0?:-0QHEIP?32?3329/^]]3222]_]]_]]2]]]]]]10]]]]]+]%2675.#"23267#"&5467>7#".54>3254.#"'>!3 - /'@/8M/ (  #861,J6#&-# M:S&&@ B*(#P )O++43&F]@ @$"P #O++4:S&&i@ (-)#P *O++43&Fic@ ''#P $O++4:S|&&b@ (*0#P (O++43&Fbc@ '$*P "O++4:S&&_@ ((,#P -O++43&F_c@ '"&P 'O++4V&'_@ "P #O++43&G#@"@H"@H"@H"@ H"@H+++++103G)@^  7 G '%+VfVf*(((('%%%"""P  QN??3]2?^]3]2]2]2]]]]3310]]].#"326773##".54>325#53@&(9$ZK&3]JJ dA<`D%7V"_ (  #86 SQQ=)#+ 3E09۵%0H@M.M% ML@ M)&&&  @M1;_;9M9 M M@ L:#,9  ),@MG,,Q4M4 M4P?++?]+339/]2^]++++]+2]2]210++++++4>32!32673267#"&5467#".%6&#"3(BV-ipRW1C "  &3;!Dc@eA9 1"EgD"QTN   =$(4)Hcn?Q(4V&(_d@  PO++43&H_b@ $(P*O++4:^&*i@ +-) P*O++43D&Jis@ 3/P 0O++4:^&*a@ +,: P(O++43D&Jap@ 2@P .O++4:^|&*b@ +*0 P(O++43D&Jbs@ 06P .O++4:D^&* 4* P+43D &J@ 708P .O++4Vk&+i@  PO++456Kf@>s   Ps y yB BAA????9/^]2222]322]]22210]353!533##!##)!PaSaPPaaPSRccccG@ w!@DL@###_##  "!JJ   @LPN??^]+]2]22??]323]q]10]]+]3#53573#>32#4.#"PJJ];>R1] 2% fEM]El #?X5 0D*T,{&,u@ PO++4$& u@ PO++4c&,>M@&M@ L@ L@ L@ LPO++4++++++ & >M@&M@ L@ L@ L@ LPO++4++++++ &,a@ PO++4& a@ PO++4E7@!0Hs BA??3]]]10+33267#"&5467>7Va  ( *95 K = )#+  E$S@ 0H  &&&"%@ HSI J?222??+22^]]]10+33267#"&5467>73'"&54632( *95]/####= )#* ^!!!!K|&,b@  PO++4P@IJ??^]103#3]]BLV&,-BE&LM&-i@ PO++4G&]i@ J P O++4VDo&. ˴' P+4PD&N д' P+4V&.n-@@H@H@H@ H++++10P @%z Wi 6 ) i (  h@RH$+vtf Ie)9II J JJJ??????9/]3]?]]Ƈ+}]]]q]q2+10q]]]]]]]]]]]#.'#3>73HE<n;@B]]>?6l;?>CNQ#"GB8=?9=@?V&/@ PO++4;6OVD&/ P+4'D&O Ѵ P+4V&/{?@H@H@H@H@H@!"H@%%HP+4+++++++10NJ&O[ H @ H++10V&/b dP+4Ns&Ob{ H @ H++10  `@;j {i sv    )  A yB??99]]]]3]3]222210]]]%!'737 _I!jat TT%B6D-V&>6 V&1@  P O++4P&Q`@ PO++4VD&1   P+4PD&Q $P+4V&1_@  P O++4P&Q_f@ PO++4&1p^&Q^&@ #PO++4VE @/ Hg  s  s"s!@H]K@ I@P`4A AAB?3]322???99]]+]]]+2+}]210+%.'#33#"&'73265##]db(_M/ki] _ :-% %9-25K.F/I1-PE%fh ж H@'L''#%&$JR J#LP?+32??32?]^]210+]+]>32#"&'732654.#"#P jE>R1 !:-% % 2% ]#?X5.F/I1-!0D*I:c&2@ () P*O++43&Rk@  ! P"O++4:&2a@ ,: P(O++43&Rah@ $2 P O++4:&2e@**, P-O)O+++443&Re@*"$ P%O!O+++44Vc&5d@ +)P*O++4Px&U$@ PO++4VDc&5 δ5+P+4Dx&U |P+4Vc&5_j@ )-P.O++4Px&U_)@ PO++4!&6R@ "31-$P2O++4(&V!@ 0.*#P /O++4!&6iX@  62-$P3O++4(&Vi'@ 3/*#P 0O++4!CI8.@DH "54>,))))(A (%:: %!!!s!!v%@ M%%%@_ H%%Kv M   GF@HFJ18>Ay=MRGGGg|FMXFF(AFwV|@MwVE?3]]]+2]]?33]+]2]99//^]]]]]]+2]]+]+]+99+}]]]]]29/993]210]]+7254.'.54632.#"#"&'73254&/>7.'7.;!&D2|m?gS70$(5 -K7kd  (+ $ &?2$ ^Eh -! !->,[fO  %& $2E1Q` ! 9   P (CH/@H%%   F&*&:&v"!r!% 5  zh)956?-g*w**'BE'$;;E$!!!!$J EI29?zFFFE'BQP?]32?332]]99]]]99+}9/993]210]]]]]]]]]]]]]^]]]]+72654&'.54632.#"#"&'73254&/>7.'7976>7(fX,&@-': ($:*PK (+ $ ?G FC!"( "0#EQP! $3%>H ! 9  PC%#@OM L( L!! % s$   ByyAB???22^]]]2]]993310]+++#"&'73254&/>7##5!##& (+ $ %! 9 (aTT JC2l@O4 H00 ",-@H-4#3,)0  -0Q" I??332222+29/2333310+]]#"&'73254&/>7.573#3267 (+ $ (5 ] $*3  9&%! 9  ! 4I/N'3 M %&7_^@  P O++4J&W^!@H@ H@ H@ H++++10%J@,s B  yA?22?^]]]32]]10#3###535#5%ڂaTK>KTJ]@ @ H @)H  ( QI??]]3]29/22+2210^]+3#3#3267#".57 $*3 F-5E*]NF"'3 M6O4Q_{&8u@ 'P&O++4J&Xuc@ %P $O++4Q_c&8@ PO++4J&X`@ P O++4Q_&8a@ *PO++4J&Xa]@ (P O++4Q_&8c !P+44J &Xc` P+44Q_&8e@*POO+++44J&Xez@*P O O+++44QE_+'0HȳM@LMI';'++i+y++(((!"" `s-s,AA"%! | M M MF?+++22??^]9/]993]2]2]]10]]+++".5332>533267#"&5467XDd@a\J%=,a;?%(  #86%*Id:Xh^0K4MQ~$=)#3JE-p@J  )0H  +---/" .,I!I+F&V&&  iyQ?]3]322]2??^]]]210+]]%3267#"&5467>7#".5332>73 (  #86154&'"C  3U&&I& 4JD4@_Pp/]23/3/]3/]310774'aa',XX,.J9bE.@ @H (/^]+]210".7332>731%<<%1b#4!  !4#f @ @ H /9/+10"&54632####f!!!!U?# t@'@H@H     ! @&H/mHk?+]q?^]qq+]q99//]]++10#".54>324&#"326#%%%%2%%%%GE/@0 H   B/22?9/3]2210+"&5467>73326786 ] (  #)#+ =$DtT@+@DT/3]222]]10]]]]'7'7{0s{0sЌ$8$&:C@ $"P#O++4&ZC@ (& !P 'O++4&:@ #!P"O++4&Z@ '% !P &O++4z&:@#5P-O!O+++44&Z@'9 !P 1O %O+++44P&<Cv@  PO++4D&\CP@ $" P %O++4Y7'@ABBA????/ԇ+}10 #7yWK3I <@& !   q?^]]]]]104632#"&74.#"32>PFFPPFFPahhaahha3&&32&&2;OF@(p p?]2/22]]]222103##5#5>7322D,4:@v;]].@DAS Q&(3>_@  @+ H#30 p?229/]2^]_]+10]_]#"&'732654.#>73#>PT A35))D4 ؜^V5E <# 2f/?(A%3I(l@J&&p6FV *6 F V  ) &!)9IYq?]]9/3]22]]]]10]]4>7>32#"&7"32>54&%$D_<!=2#%&6!#5$KM'$0$7V;= $#,.%Wk7C  "%;CD@+MziO_o  p?2]]2]]]+9/10>7#5!_'/2-";*[VKA7GYd2 3F+7@a75,&o 9)2   O_8555)) k { 4 D T  # H0 H/ H Hq?++++9/]]]3]3]3]]2]]2]]2]]210]#".5467.54>3232654&'74&#">FLH*8"$$2$4!$)#93%%*/2? '1/(%-.#  " &  9C#z@O :JZ4kZK$%"L\9$*  &q?]]9/]3]_]]]]]9/]32_]]10]#'>7#".54>322676&#"CIh%&6!#5$KM'$0!px=0/#,-&Wj7D  AIo28 6Op(>q%Ir%Cs FtCu#1@X   7GW% 'w?  3"# $1s!P  2H!"!#$|,E|F?3]2?9]+]2222]210^]]]]]]]]]74>323267#".='7>54.#"l.;!*5 6M//3 +%H02B)B$?I !EZ4 1=5aYS',EA E2B$/5:U! !;0Ac!3G@&" 5-54*$$'0!!'y'''T Q?22/?]]9/339/210"32673#".54>32'4'.#"3!25 0|IO09ER\0Sl??lSSm? 0|HG~/Q 38A6 3$9aKKb99bK 06:0 29&'nV HH@ H+++109&'nk& H&H&@ H+++102I&'nVtM; H- H H;H-H@H;@ H-@ H@ H+++++++++10 I&'nktM^ HP H2 H^HPH2@H^@ HP@ H2@ H+++++++++10(I&q'nktMN H@ H" HNH@H"@HN@ H@@ H"@ H+++++++++10%I&s'nBtM? H1 H H?H1H@H?@ H1@ H@ H+++++++++102A&'nVq HH@ H+++10A&'nzq& H&H&@ H+++10 A&'nkq2 H2H2@ H+++10A&p'nuq HH@ H+++102L&'nkr5* H H*H@ H*@ H@ H++++++10(L&q'nvr5= H" H=H"@ H=@ H"@ H++++++102F&'nNs HH@ H+++10F&'nks& H&H&@ H+++10 F&'ncs2 H2H2@ H+++10F&p'ncs HH@ H+++10(F&q'ncs" H"H"@ H+++10%F&r'ncs- H-H-@ H+++102F&'nVu5& H H&H@ H&@ H@ H++++++10F&'nsu5= H& H=H&@ H=@ H&@ H++++++10F&p'nsu5/ H H/H@ H/@ H@ H++++++10(F&q'nku59 H" H9H"@ H9@ H"@ H++++++10%F&s'nBu5* H H*H@ H*@ H@ H++++++10 F&t'nku5S H< HSH<@ HS@ H<@ H++++++10)"2@u.y.u*)H%!!K!  y hYLJ Ey    0  4(30##v++QT?22?]9/32]^]]210]]]]]]]]]]]]]]]]]]]]]]]"'>32#".54>32."32>7.@ P#WqBAkO/N9 #B_=2H2N/@(I74C'G O @jKFtJ:T6@jK)!7Y?##9H&MG;Yi- @~H Xudszks  Iiy*s 0 ` p  &Ffvsy B  } l D #  AAB???]]]]]q?22]]]2]]]9/]]]]]]]]]10]]3>73!."ABE'Y'EBA"dFFdlRRlF-Vc%@  /9/9/10#!#caa2"cMM@&M vvv A/2?9/322]9/10+++!!5>7.'5!!d>v-o!({B@<409?0TQSCN\-ZUK?TGRW45N/3/10!!56NOY7n8i @@Cx            //9/3/99//+}+}ć+}10]73#.'  RT D$YZ4'EFJ,UN. 3?@ ==@3H 7**D*t****$*D*T******t***@D H*A k{\8H, @444= = %/////:::%/3]2/^]393]2]_]]]]+]]]qr9/]+q]9=/3107267.#"7#".54>32>32#".72654&#",,++  &0++0&   &0++0& p++,,#**#'&&'  !7))7!   !7))7! '&&'#**#GiH@.  Ry'O?^]]]?]]]9///10463:#"#*'53265bi ?7bi ?77dmPE9dmPE9*o6ala5:i@Ay v     /33229/33/399=//^]]10]]]]]]737#5!73#3!'7#5DBB0qDBB0qO$_OO$_5N6D5N6!D #n@GO  !O$@ H p!!!Q!0!@!!/]]]222+]^]]]]222]]10>7.%.'>#@<::<@##@<::<@i&$$$$&*F&&F[7aXS))SXa77aXS))SXa7"<7447<"Dk44kP&IIP@#@1wf !%`%p%%%%/% $@ HSIJJ IhxO?]2]2?????+2^]]22]210]]]]2.#"3##46#3'"&54632#2 &A4]b]]/#### P G<-NF7dm^!!!!P@P{@wf  H @ !@. H pPNQJ IhxO?]2]2????/]]]+210^]+]]]2.#"3##46.57#2 &A4]bVH] P G<-NF7dmFJp PP&IP&I2#31/I=ox25x8=x 6=xO5px(>5qx%I7rx%C5sx F=txC=uxIoN2 N8N 6NO pN(> qN%I rN%C sN FtNCuN)@ N?9/210#5>D4@H  @ /^]9/]10+3#"&'52>5UY +  2  , 8 @a &@Y @ /2/]210]#&454>7Y!(! a $5$- #F@Z?9/]10#5>Nv=> /v> ///v>/Au #D`vA>>/v>?/v>@/(vE>^/v%>`/}7l>44r3s/3t/(}7x>G|}7&l>l>(}7&x>x>)@Wh!!h9IY+)*N V f  Q)))&hYP?^]]]3]222]?]]32?3223]]]]]10>32#"&'#53573#32654.#"E-8W<%D`|3y22E#(|F?22?9/93]2]^]]]]_]]2]10]_]]".'.54>32232>54.#'32>54.#"+v110[M:cH54.+2VIsP*110'G6 6O2R  $b5S32#"&'!32654.#"E-8W<%D`32#"&''>732654.#"E-8W<%D`>3 #"&''>7 9O0'  #'H7!);*e)F$U.*8  5 pp !L$ 2% Mv@LM@ M' @ L &@ LM L|M M!M!@ M!E  L M@ L| M  M MMF?++3++++2+?++3++++2++22++++10#".'732>54.#"'>322/VwI*H7$WB:W;:X;BU#6G*IxV/[U]1 P (If>>fI( M 1]:3ߵ''@ Hw x  .//?/o///5W&&&&vhW4EUe!!X!h!!| |W  U u F  g X  EJ.Z.j..++W+g++|J/Z/j//hxF?]2]]2]?]]]3]]]]]22]2]]]]]]]]^]22/323/]10]]+]".54>3>32.#".#"3267IxV/5ZxD)8??&    ",37X>!:W:CX$6H0\WW\0 4.G$- &Hg@>fI( P 30u0e@?6$F$%$$$"(/o2F1),@(%I" PF  Q?3]2?3]222]^]10]]]].#"3267#".54>32>32.#""NS)@.%< &+?_A #@]9>;$ b^*F2N (Hc<322654&#"#28fU*e)\M;bGUf8-1  #[ZW+ VH ;@+W.#?@$s%FvY  $y|FyA??329/]]]2105!#"&54>;5:767#"o020*PsIVb$  R2O6 6GdQUon325@&(9$ZK&3 dA<`D%32>54.#"+<$!,5D$2,!J404*7ES8_H;X:" 6'#5#.;! "#E?42&$2  L&lT*YH.*DW  )3:!;-1;,$% @@ s   y y y A B??9/9/9/9/9/1035!5!5!5!5!%_"SQSK8)"@MM M MȳM MML!M @'M)MLM M Lv@ M@L@ M+ +*M@Mv@MM@M M M@M*y))$|@ MF| M M E?3++2?+9/++++++++9/++++++++10++++++++++.#"'>32#"&546732>7" wz 5* $4D'WT)0TqBc-C,3K2r} P 6`M\\-O1R;!(?P(38"M4 M$M#M#L#M@$MM M88+:2v!vMM@ M MPM9,@M,/|&7y+M+&@L&E |MM MF?++3+2?+3+9/92++++++29/10+++++++++#"32>7#".54>7.54>32.#";>-M9!_U8.%  (6D&?hI))7;=$@X4=3) X7@M$8D!4F 3(D9 P 2O7(<-T0/E- O 9-#. E8@s| y  H GyA??+39/2210!!!!#"&'73265V !:-% %QO.F/I1-:5@}  x !#o##W###_5s7?7o77g-&--vx g   6##u#T#E##((||FgXE5W222|hF?]3]]2?]]3]9/22]2]]]]^]]]]]]]]23/]]]3]310]]]3#".54>32>32.#".#"3267a -;F$IxV/4YxE-:??&    #.75VfI( H'3@91s+s""$(2* * )* *%&' $   $2 $* $ s * *322s22$  54*$2$t$$?$O$$.y) C A A?.??^]]9]]333]99+}ćć+}]3/3/10]%.'3>73#".54>732654'!@@B%e5i;;h6c%C@?!  "8))8"?':zQrttsQx:1,*1((1*,15@ '' P1)@'&)++$+T+d++++:++++4+T+t++++++++@+`+/+++++ +@+p+++++! *!M$L$@  L$P NJ@MMM LQI??++++???+++322]]]]qqq_qqqqrrr^]9/10^]]_]]%32653#".=4.#"#7>329<<9].RAAR. 1%: ]]6?Q/XIIX,TA''AT,30D* P #?X5Vp0@   s|  H FA??+32210^]33267#"&5Va/0#  9"`R>6 P ak7 0@s  y BA??9/3/29/3210333###daddad&KDV$@dy '$#"! +;M$4Ks  I :  (  /OM H(s@BMD0& s % |tE;   {6 A BB???9/]]9]]]?33]]]322]]+2^]++]2]]]+}]]]q+q10]]]#.'#3>7>32.#"+c`SrPY\*aa59<7-*:,%9UtYdi//_WJAED #E:fP(@# F 9 8  xL#M#@]L M LLt#$##$* )$I9I#I&6O J JJJ?????^]]32?9/]?]2]+}]+++++2+10q]q]]]q]]#.'#4>32.#">73 CA:m7<>] !:-% %:82m6:;DOQ##IC8M.F/I1-:<7<==G@ 8  H @ H   NQ??9/3232+10+].=#5373#VHNN]\\ FJFCF '@Vf3 $    #!$$ $% %     %  ' %%$%$$$  W0  ?$$)0(%J$J  OJJ???392333]??qq9/9q]3+}ć+}ćć+}]^]10]]]]]]]]]#>7''7.#"'>327#./' _,6? jd' %5$?"K;0+?)R?^Vy/j@D///|/x1)s''s s 0(AA A*--'"V"f""Vf|F?^]3]22]333???]]10]]]]%#".5332>7.5332>73#"& %1<"EW1a 5*+$ a 5)$"a%zE3P- )Jf<j9R5  M'j9R5cW E!@1wss@ #s"@Hm[@*IP`pD!BAAA|  H   GB??^]+32????99]]+]]]+]2+}]10!.'#"&'732653319BGFB :-% %M/ki] _%\cf_T .F/I1-25KPGL@ L J KXLP?+3]2??^]10+]>32#4.#"#P jE>R1] 2% ]#?X5#0D*I:%@LMM M M@ M MM@M!M!MM Mv @M @ M  '' M MM@" MvM@M&! yM@ M|MFM|ME?++?+++9/]2^]+++++2+q]+++++2+10+++++++++4>32#".2>7!"!.:5[yDCwZ55ZwCDy[5K1P:#: wddv #;O[Y[..[YY[..["?Y8p*~o7W>!:!5@\))$/h )  v,hw77g&vx$$$$63,X |g)))EW|hF?]]]?]]]33^]]]]q]]22]2]]29331032>54.#"#".54>32>54&'3 ;U54U; ;U45U; 1)&$*5ZwCDy[55[yD7d*^[?gH''Hg??gH''Hgc0V$-|OY[..[YY[. 5#  3Et ,x @= M Y'i''*.V!f!!-G$W$$$QH*X***@ L[P?3]+3]?]^]]3933]+2104>32>54&'3#".732654&#"3$AZ5.P\R$AZ55ZA$aNEENNEEN=eH'3#  eI P.>dH''Hd>XggXXgg9';@n y yv v--)-i---=< s ==&7f77v##<((|x)iF BIY|fv22|w&fE?]]]3/]]3]2??]]]^]]]9/]]3210]]]]]2>32#4.#"#".54>2>54.#"w3_'U:ES-a 0" 1!'2WsAAsW33Wt@3P77P33P88P(&Ec32>32#4.#"4&#"326$?W33W@$$@W3'EI,dH''Hd>=eH'#@X4#.B, "T2XggXXgg(u@K( GW$vGWg*0** s s P p  )y B |@E?23?9/]]]222]]]10]]^]]2+#.54>"#32>54.t+QtIMa\M;bO..I2P75Fon7#"&'#4>32.#">32ZK @&(9$a54.8AFhHz'aa%2M ,E2P73E$MhG.RkmDa+ 3*'4 0-M)M(M( M@M M  M M M@L(L(,,(s(,M@ M, Mv%M%M%M% L% H2/M/vMM@M M L @ L HM 1M|"M M@M,M,, MML"M"@M"E|L M F?+3+2?++3+++99//++++2+++++++++++++++99+++}++10+++++++++%267#"&54>7>54.#"'>32D^ '6E)y7K-6($07Sg?m|2D&!;.E P b[1E2$ &%  Of[,>-! !- h'+@wv'v& 8'$ 6F%'   :J+ !$' $ '$'$ ''''  0-)   ,8''PQ?32?399]2^]2]299]]+}10]]]]]]]]]]]]]]7267#"&54>7>54&#"'>32:FNEbl*:$75:'-@&+Xf(7>67CPJH%3$$#!PQE#0" ("!! @aWV    s   s   z  wy AyB?2?9/2^]]]9]+}ć+}ćć10]]!!5>7.'5!!t7|/y5=@=91-5:iHCSIDIL'&LG>=S9AGE(V@. H (( @  ) @%OgwHR?+]?23/310^]+.5#".54>32'4&#"3267 VH!+"1 !7*UF]"" !  NFJ9%0/*`U(+)  JE|,R@1  H$  .,*-  $ '')''QR,I??32?]3]2232210+3#3267#"&'732654&=#"&57 $+:  6,$ & "UI]N'3 +E1D2-gi@9@!  * su@   ByA?22?^]10]".54>3!##*2P 1=ad+% -;"QdJ#j@D v  %#$h  vO  ( QI??]]3]2?^]]]3]22310]]]3#3267#".54632.#" $+2 F-5E*aj*:  , A4N'3 L6O4ndm P G<E%Z@;  ( O_syA|  H G?+32?2^]]]]2]]10]3267#".5#5!#K% %-:! -1I/F.dQQQ!(K@.s% s @   **s@)({!AA|I'7F?]]??]^]]]10#".5332>532654&' 1$AcDDd@a,=%%=,I#! .':dI**Id:X4K00K4"$ Jkt$f@1 M"!@M@M@ L& &@%"II@ L L Q?+3+2??]+++2210+#".5332>732654&'3k 1$ iE7#5!32>54.'5!#AuY5$. 6( :R22R: (7 .$5Yv+U~S/VJ=RD=JY6;^A""A^;6YJ=DR=JV/S~U+Qt#[@;e V IJs(8Hvh% s $|A A'7|F?]??]]]10]]]]]]]#".5332654&#"'>32tCpUEa>a*9!aaAM @V4dBkB*Id:X4K0Q3[} @[MsK[k8$ "ss!(H B|EAAB????322?9]3^]Ň+}2]]]]+}10+3.'3>7>32.#"Ez6r'^1 ')#4/,:0+.,jdRNKLA +,!BLVSG8/@ g&&.-7"G"""'((@YH""((hW6F%10.(-*P II K?32??99?333^]]9]]]]]]]+}]+}+]210]]]]"#".'732>7&'3>7>32&A&*4A)  #*! ?m$d &,  :>)vY-@)K 'w-hno3'LMQ,7>H !y@?   s      yy A y B?2?9/3223333^]Ƈ+}ć103#!!5>7#53>7!5! \1,O S/w-T#h$qDK?|8QA7EK?n(Q$y@A          I J??9/322333^]]Ƈ+}ć103#!!5>7#53>7!5!A%p9"s=#X8wK0B*T'L:)^0B)FL,~@OX sx  H&X&&vwi.-{Y+++#|XIF yA?2?3]]29/]2^]]]]9/]+}10]]>7!5!#".'732654.+74..8<-Q<#)Ih?&D6(  %.8U_!9M-<<8Q::?@1J16O3 P :D(5 2,@P'''#s,',,',&.#viyV7-'#&y%,{YH%A |IYF?3]2?9/]]222]]]9/3+}^]]]]10]#"32>7#".54>7.'5!!-M9!_U8.%  (6D&?hI)#7.'5!!_&H8!VQ/R]7;`E% 7K*50) )/2 1'AG P 6Q61E-AA;6N7=<C-@&  ,Z7H W | #"! $  <   $ $ $ $$  x  h //*.wfx$$5$$$$+   0  +I?2/9/]]]99]]]^]]]]]2]]]]]]]9+}qr^]^]+}^]10]]^]]]]]]]]4>7>54&'5>7!5!.'2$B3Xk3S W60H/%AW2"I r!) ' -/>-SN9)[0&8'0;'  $(*@] !)X))$v7###,,!v%%*$+%!y#y))#By  h  |&E?]3]]]]2]?9/3/2223]^]]99]]9910]]3>54.#"'>323#!!&>7#U#1)!4(.'2?&gq) W8*<?W*9ti#I,* Da\*O&I9+K Q.OF?+@hX%vc@B I@--'s" ###,yH)9&y"y$A|h  HHF?+]3]2?9/]]+^]222]]+_]10]_]#".'732>54.#>7#5!!Ř CfE;3&!+5/A)GtUawg.N8  R "-&:(&D@> QQ274)U@2(+$ *$HI Q?32?9/+2^]222]10]%#".'732654.+>7#5!#32up91$+3GC2T@2Q9]B$KW N +)' 8Z0JJ !# +B/%@f$#x 9" "" " " ""   e  %''%&J ;  *: QI??399]^]]]22]]99]]]+}10]]]]]]]73##"&'732654&'.5J^ %$:*qbESL:9<6>4%M& $2%HJP!") &9,PG  T@4 g{  WgwPvGiK?]]]?]3222^]10]]]4&#">7#>32NF*/,ZH-a8`~F] dA9Z?",EO r(>W3232>7(BV-hqdSV1C U6Dc@A9 1!EgD"PUN )Hdm?Q(4&$@-P,3O %O O++++444+"&DK@8JPHPOBO6O++++444&$@%&P'O O+++44+"&DK@BCP32.#"32675#53aPP -;F$IxV/4YxE/J5"b45VfI( pI3G@"5M@sMM"44#17W)g)F)7))UeHI6fvR#& 43 P111./^]3]2]?39/322?]32+]]]]]]2]]32]210+]]++%#".54>323##"&'73265.#"32675#53 E./S>$"A^;Aa!NN|~1W Q,SI/(KQ'3%>pp4?^A9_E'Dxl Q BHbQ-@*jD:^&*_@ ,(, P-O++43D&J_e@ .2P 3O++4Vo&._@  P O++45&N@  P O++4:E,@*@*M. >   I:i @ L!@L!'@M 7M7 M7v M @M @ M BB-M-@+ M-vMM@MAL LM2M2 M2@$ M2|(M(M(F<M< M< M<|MME?+++++?++3+++++++++++]+++++9/9+9++210]]]^]]+4>323267#"&5467>7#".732>54.#":5[yDCwZ52D)&(  #86Dy[5f ;U55T; ;T55U; [Y[..[Y@iT< =)#+.[Y?gH''Hg??gH''Hg3E(4@i(<L\ +@ Y)i))6V/f//5H,X,,,$PYi G2W222Q?3]2]2?]^]]]9/92]2]10]]]]3267#"&5467>7.54>324&#"326F<+(  #86 2S<"$AZ55ZA$aODDOODDOV =)#+ *Ga;=eH''He=XggXXgg:Ec&O@ AB PCO++43E&Pk@ 56P#7O++4&_[@ -1P2O++4G&_@ )-P.O++4G &]_@ L P O++4V&'=V~&']3&G]Q:^&*@ L*( P)O++43D&Jd@ .0.P /O++4VX@4s ss  yBu f U F  |F IAA????]]]]]?9/9///2103!332653#".=!#VaSa=II=`4YEDY5a"Nb[[b7]D&&D]7SVGSM@-   vsI|B9I|EC??3]2?99//]]2210]]#>325>54&#"a19@!@oWAqS0cU0<G ~WjQ![=QmKQWV&1C@  P O++4P&QCm@ PO++4#/3e@ M20Hwew e $122 @zH ****-300@H@$$$$--   s#"!  s 5@5p55540H03211 HW  @- H '-y#@  z Y i   -ABB???2/99//]]]]]32+]+2/+^]]]9/+}+}ć33/]+]2/33/]+2/3]10]]]]+]+7#>7.54632#./.'4&#"3267'7?e%C?>!7&%8!>?C&j ?"#?-gG $+11+# Gg-X-OXKKX<7N+&DK@ ?6P+444&w@ PO++4,%&@ XV ,P#WO++4:&@ 20 P1O++43&e@ -+P,O++4&$j@ P O O+++44+&D!@ =7P54.+532>54&#"'>32#"&'7O9lT4-BN!&(I8!ME7Sh?6ZA%!2LUCoN & d,I92A&N%9&BH O6S:60( rHPlBT G3@lK:Uw  wvw ((w5t  ""54./4g  #!!wR.+/P?22?]]9/2]^]229/9]]]]10]]]]]]]]]]]]]]]]2#'>54.+52>54&#"'>,L7 ) 2"DlA>?=/'7> =/;6-J *.,C0+' *1F\6O "1"&,M , 05P Vk&+_@  PO++45&K@  PO++4VGS?@&WDEs s  | 7GE BC???]32^]10]]]#4&#"#>32SbRZ(&" b7AH&Hb>Org )Jf<3D(7D3@e H4'$G3iX&(# ###77;BGF711E%$? KO)),f W  P8874Q?32^]?]]3]2]??2]]]2223]]310]]]]]]]]+]%#".54>327>32#"&''7&4.#"3267"32654&8<`D%54.7#".54>7.5467332654&'3O)C23C%%C32D-03%MlF &20- ^ ELLE ^ O 1"#22#"1 9T +9#bq7N1$8+ T90D&#F#CGGC#F#&D4 9e@>637 ;*-)#&:#v00D0T00RI6N)NF V  Q?]??9/]]]]99^]22222210%4&#"326#".54>7.5467332654&'3ODDOODDO>0',!$AY55ZA$!,'0 [;:9;[ MZZMMZZ9X )7C'8[A$$A[8'C7) X9*Q*#G#QHHQ#G#*Q!E#a@7v   s%!$ !y"Ay  H GyB?32?+?3^]և+}]10!#"&'7326=!5>7!5!#cje$ :-% %f9AFEAh&~CQ.F/I1-A(`ef^SQ$E!e@:##" I  H RJ?3?+32?^]]և+}_]210!#"&'7326=!5>7!5!EOO 6,$ &FMIwTfp3N+E1D2->,ji] N|&$b@ !P O++4+&DbK@ 8>P6O++4VC'|@K H @$''@ H    )s( H$@ H$ y y ByA??39/+^]+2]9/+23/]10+3!!!!!##"&'73254&/>7V"_  (+ $SQS ! 9 (3C6? @  HcD@M4M944*4 4  HL@"M2//&%++@M7 A?M? M M@- L@%"H"&)/@ H/?  2@MQ:M:P?+?+339/]2+2+2^]+++++29/]3/2]10+++]]]++]]+4>32!3267#"&'73254&/>7.%6&#"3(BV-hqSV1C K1 (+ $8P5eA9 1!EgD"  PUN ! 9  "-G\h?Q(4:&2@*< P:BO4O(O++++4443"&Rk@"4 P2:O,O O++++444:&2@7( P32#"&''7&457"32654&,1#$8'9HK0h]L"(##%$H'43*/)[*~$,+*PD,9@fvdt  vvD117;p;O;+:+J{ %5   ---4 K*X%%P?3]2?]]33]]3]3]]?]]]2222]310^]]]]]]]]]]]]>32>32#"&''7&454.#"#%"32654&P jE>R1,1#$8'9HK0h 2% ]"(##%$#?X5'43*/)[*~0D*I$,+*D)x@J  ''++  *IS& J$    R?]33/?^]]2]]]2??2]22/3]10]]]7>32#"&''7&4573#"32654&,1#$8'9HK0h]L"(##%$H'43*/)[*~Ne$,+*3j@@=B433!BB< P11 P)A4117  L .7@L7P3N!$$ LQ?+23333??+3+22333]2/]]93/310^]%32654.#"#.#"3267"&'#".54>327>323&KZ$9(&@]@&(9$ZK&3-NM-<`D%32>32>54&#"$9(&@/*KZ<`D%73737.'-[6ehD__?c(FCB$V/FE#CaSV  .j-X-nKfNX=z:a %0@ *)h)W#$ $k{9Y)* $% %$ $ %$ @ H2a202$&&&&vhV1))IY|$#i[IkW!!F*V,|FV XE?]333]2]22?]3]3]]]322]2]]]]]]]++}]]210]]]]]]7.54>327.'3267#"''&#"BK4ZvB/ G '5B[%8I*@4+G&&3T327#"   u"%@ (,%#>==#@]9  >nNSN N PQ!}S '"',,("+'()I"!JZjI:K?]]]]>]2?^]]2/+}10]3267#".'.+5>7!5!EOQ!(  !#' & !1($ !*6 FKIwRgs7 "O$ >,ji] N'@T  iyJDsi y  v)(yH"6"("""|@ HEBB???3+29/]]]^]2]]]9/]]10]]]]]]]3#54>7>54.#"'>32a!1!!&27X )5<sy#/% );/% !%  P _S&7*! * %@eJ;J%%2  '&vE h 7    wVfPJJ???3]]29]]]^]]]]]]2]9/]10]]]]]]]]3#54>7>54&#"'>32Y*.@/,)"  '27bg&  !0'+&  M MD.#  ]1B@bH*g8vgsf,vgw D DPDD" Bs CB=|2y1y    1 1 E"'|F?22?399//^]3292323]]]]]_]]2]10_]]".'5#53>323#232>54.#'32>54.#"110PP011=!KaLaKKAcDDd@KR%=,\KS:dI**Id:S0K4HHh^V3@d()  )8H* s9( y y ByA?3?3339/32223323]]2^]]]+}]10]]3'7#!73#!37#37#1E&XzFFgfYfv\EMP3g%,15@u# '&54#" "" &   10 ! ?!! ! -7?72,VfG66"!G444W1&,5# @ HQ0Y'' v e  IP?]33]]2]2?+3339/]3322]]222]2^]]]]]99//]3}]]]3210]]4>327#3267#"''7.?&!4&'73)DX0  >|3'2GY62*>>90%:)*/)EgD"TP4 $  SU#{}+91>DT,e@= swh yyA |  L  M MF?+3++2?99//9////]]210^]%#".'7326=#5333#5YC"9." &C2HBaPP2VA%  M!IY\K&KE#e@)!   %$   @ HSIR???+99//9///^]32310]"&'7326=#53533#"&54632 &  0)\\]NNZ+####L;9FFda!!!!!:E?$8@  L  L$@L$M$ M$s%M% M% M%v/@L/M/ LL@ M//:94M4|@ L ME*@M*|LL@ MFy G?2?++3++?+++9///++++++++3+++210++3267#".=#".54>32!32>54.#"% %-:! 3>D!Dy[55[yDCvY4 ;U54U; ;U45U; -1I/F.c*.[YY[..[Y?gH''Hg??gH''Hg3Es/a@<  H/'''''10( 8  G,W,,P" H"QR?2?+3?]]99//^]3210+3267.#"3267#".=#".54>32$9(&@/*KZi% %-:! E-8W<%D`32"#32>54.243j/0/aPP)e*L#  X.H33E$BOV(&OH?KM nmDa+ 3('4 :@ J  P?2222?9/32332102.#"3##5#535>  3(3]JJ!bQ FF Z$@"!   $ s     s  s  & &P&&&%By B A AAA?????9/923/3?^]2/]]2/9/2+}3+}ć103.'33>733##.'#>7#S)n&'h*T~:a ;}($# &J%%J&&J%%J&I0`01a0w: :G*/y%M@M.+*- &*)&* *   +++ &  &+&+&&&&& &   vU7G 101@1p111 *0 #*&6@ K I III?????39/]9]232^]22]]29]]]]+}+}10]++]3&'33>733##".'73267.'#67#K&a  ]BX0*4@*  #6? ;eu;jc-i73e56d3DH<-@)K .7<CLOTG(@e     H( 8 X   "vi 0**)g((K I?2?329/]^]3]]]]]]+9+}]]10]]]]]]7>7!5!#"&'732654.+x2/()05*K7 %E`;7]R/QV"7H&<=7N6;AA-E16Q6 P GA'1 6bB.@@H'  /3]^]+102#.#"#&>1%<<%1#4!  !4#DdG@#DT/32]2]2]]10/'s0{ms0{$8$_?t@ HiH@@H  iH H @ HH/++++]2/++2/]10+'7#"&546324&#"3268%&77&%8-E<7N+11++11+!KW" A@H @H  @H/+/3/+]+]10"&546323"&54632'3#W K@=K;" 1@ @H@  H /+9/+10"&54632'3#  K@K\V @,tE4%Dt6$T1A"@; IKktcT@wVv~o @.H@H  @Od@H/+]]]/3/++9/]]]]]]]]]]]10]^]+]]]]]]]]]]]]]"&546323"&54632'R `-oK &K\V @Lv  Huv v@ H{o @%H@H  @O@H/+]]/3/++9/]10]]]]_^]+]]]]]]+]_]"&546323"&54632'R L323267'3#U " - "      j@]30@ZzM@/10+]'7o<7NA67:@  @/]3339/^]2]210]''{^^_-==-A675@ @/]]9/32210^]]77A^^{-==-_zp@yjy @"H@H  @/329/3/2/2/++10]]^]]]]]'"&546323"&54632`-o  &zb@Ue@H @H  @/329/3/2/2/++10]^]]'7"&54632!"&54632$o-`\ &z @@6H@`@H@ ` xH~xI]N0 H/+]]+_]+3/3/3++10]]]]"&546323"&54632%774 ^^{-==-_V&(C@  PO++4Vz&(m@  PO O+++44'MM@%MM L`pvs@0M@`)(#| MyyAB |F?32??9/+29////^]]+22]]]10+++++%#"&'732654&#"##5!#>323K1 <;^T#Ca $&DeB!,M9 MJ9LF aTT 32.#"!!3267IxU//VxI*G6#UB5R:!0!;S6BW$7H1]VU]1 M !&v (ssI Y   21 I Y  1) ,||%% FyA??339/2223/]39///]2210^]]#'>7>5!>3 #"&'%4.#*32>  )=S8 FM)4*NpF*`*|8M.'/%F5 bpZ-O;$THJU8T8 *8  5V)T@/v&ssIY+*y|##AAB| F????9/23/9///]22210>32#"&'!#3!32>54.#"\)*NpF*`*aaDa[%F5 8M.'/ls8T8 M 5**6 t@Jf  ss|wVfHyyABB???9/]]]^]29////]]]]]2210]#>32#54&#"##5, $&D[7`DS#CaT 7!3#5!l\I37W\,(= :VbB;V(r1 @,$$ssr$$% %3'', && ,''s--'  s} s%%%----2-B,B&B %BBBAA A AAA?????????9/3???^]]]2]]q+}+}3/}]3/99}3]]q+}]+}10]].'33>73#.'##>6IIEq=EG ]GE>pEIH(PKBnDHI!]!JHCmBKQp#UWT"LSU%5%UTK"TWT#$Y`d0.]WLKLW].0d`X 8@!!fgO8_888) Mv@ M0L2vM@L: *)))9y77*@ M*/|)M)$F | L L M ME?+3+++2?3+2+9/2^]22+++++39/]10]]]]]32>54&#"'>32#".'732654.+4!D8$M@7X )3< 4X@$=;7))Ih?&D6(  %.8U_!9M-> .#-9 O -E/0T-<(7O2 P 9D(3 V@   v   s s   @Ps B B;   H9 *    AAA$rc@P2#B?2]]]]]_]]]q2???33^]_]]+]q??]q]+}ć10]]]]]]]33>73#V_ ]ik/M_BFGB952K T_fc\%V&@  PO++4Vo. mO@2(s s    ByA F  y F?]]??^]2]9/]10]'>7>5!#C)=S8 FMab8ic^--O;$THJTKbC%0Vk+:2Ve*@s sBByA???^]10#!#eaaKbV=3:S&%7C$@sXs $! cs4DT' &&o& &P&&& % AXAAA | F?32.???9]?^]2/3]]]]9]]]]]]]}+}]+}ԇ10]%#".'732>7.'3>73+;S=  %-$ ,NB6n!g?(I i....Q<"R 'Gw2Wu[lHuo8(c;VG 1@s s s AAyBC??2??9/2/10%3#5!3!3eW\aMaSb:!L@, s s|y A B A???9/^]]399//]]31032>73##".=3%?0'$ aaO-L`7`*?)_K #A]9V @w s s s o       0 P p 90 P   0 p   0 P ` yy AA A B????]qr^]^]qr_]q9/9/9/1033!3!3VaaabbKVG]@9 s sssp_ @ yyB C AAA?????2^]]]]]9///2/1033!3!33#5VaaaW\bb M@-v sYi"! |y A|F??9/29///^]]]2210%#"&'#5!>3 4.#*32>*d*);f 9O0'- 'H7!pp XS*8  5V $O@-"s$VvsI$$&%&|!#B!AA |F????9/2]9///]22]10%4.#*232>7#"&'3>3 73#8M.'""%F5 f*NpF*`*a)4iaa*8  5*8T8 KVC <@ v sVI  |A|F??9/299//]]2210%#"&'3>3 4.#*32>C*d*a);f 9O0'- 'H7!pp *8  52(@$v&W gy )vh)vM@MM*    )@ L|$yM L$M$@ M$E  L M@!M| M M  M MMF?++3+++++2+?++3++9/]2+^]22+++]]]]9/10]]]]#".'732>7!5!.#"'>322/VwI*H7$WB6S;!0!:R5BU#6G*IxV/[U]1 P #@Y7R4T2>54.#"@sW32WsA?pU5aa8Tk;3P77P33P88P.[YY[.+T}SKrL''Hf??fH''Hf??fH'$%/@3 s 11vx&&+0 M+@Hl+/+?+O+++ s!@! H!!!0H&&y BB | ||E???9/33]^]+3/]_]++3]2]]10_]";"'.'2##"&'#>7.54>c&E6 __d  #010aS**)f,/+2**Opg 4'J9UU@HN%'TLA.E06R7+D>"7@WV&%f-Y-i--95#f##g8g OW(F(( Q5I22P?3]2?]]?^]]]]]]]22]]9/310]]]]>32#".54>7>732>54.#"S&ps;Z>G^7Bu\) -$cj !8,&6" "9+*Mz4`J,4ZxCWrK T b/T@%2C&"@2$ (P6b@<4&*/',7,, 2x222 228#7'/ $'P# Q?32?39/222]]]]]310]^]732>54.+72>54.#""&'>325'$1oc-!'1/a;dd;)N<%& +3%AXF!!E  !9*&6;5/>&P@ IJ??10##!]BFoFX@4  GW L J LI???22?]9///]32]2/1073>7!3#5!#%#8!'UFXzX!L0fmwBDݑn/_]Y*3H- @'$$###g#w##*-- *)*)     4));(hx '('(/ (.-I)I(I$I#IJJJJ J JI??????????9/3??^]2/99}]3]]q+}]3/3]]q+}]]9ć+}]]+}10#.'#5#>7.'353>7267@=8h7;;Y;;7g7=@762g$^?Y?^$;?>DIM$!ID;;DI!$LID>?;-nHHn-,6@ M%   M@< M& 87' G  8127 !Q1,2P?22?329/]2^]22]]29/10]]++]]]+2#"&'732654.+532>54.#"'>-N8  .5xg?[S:?B&,WT'! +*( "+0$6$&G6JH O''#J  P P@8y   ZjI I I J @I a q R 4 D  JJJI????3]]]]+????3]]]2/2]^]22/]]10##3>7ZHHBSZBGG&fmj)+ge]"P6kP  N@'8H      JI H Q?+??^]2]9/]10]'>7>5!# !0@+ 19Z]!KKI 8+N 6'"V]^*C$@K)9I +&&&   & 6 F   $%JJII0@@&+H| I? O  @&+H |  IJJ???9/+q?+q2????2/^]3]]322]2/]33]]10#>73>73#.'#. Y U V88V V \ *)$ N %)*cW=<4vv4<=WcWZRR[WP ;@"      IIJJ????9/2]^]210!#5##3353]]]]3RP*@ IJJ???^]10!###!]]FPGS3F.@ JI??^]]]10###5]NFND\2G /@/1**% /$"##0 @3M&6//v//J)9"y%%%%I#$OKK???3?]3]2?]]3]]+2^]]]}}]]10>54.'#5.54>7574G))G4)G44G)Z;hO..Oh;Z:iO..Oi:,D/r/D/D,+D/$Ca>>aC$$Ca>>aC$[Po5 1@   IIJL??2??9/2/10%3#5!333FXs]]MޑE.T@3)        I JI???9/]32]^]310]]]]326753#5#".=<4&3]]!$+G3;/*E4P @s    ? O   H     0 @ ` : P p  P p   @ ` p  @BEH II I J????+^]qr^]^]qr^]_^]r9/9/9/10333333P]]]EEPo-_@;  0`p J L III?????2]]]_]]9///2/103333333#5P]]]FXEEEޑ8'b@> &)"@ ( "#QI??329/32^]]22]]]10]#53>32#"&'72654.#""+L9" 32#"&'#33 =B#,]A)J8! :R1/`-V]].3$ )A-/B* P%P@0'' &  #QI??329/3222]^]]]]10]%2654.#"3>32#"&;D#.9]"+L9" 32@`@8V %(QQ MK)> !'*?_@!32#".'##34&#"326r)>P.3W@$$@W30S>'q]]MAAMMAAM14T< 'He=>dH'#?Z7XggXXgg'u@L ) )   ##(& JJ PJJ???32??9/^]33]]]2]]2]210]]%"&'#>7.54>32#=.#";' ;^ !"$ !:O/B\\ 3!9FH9]0m/=:4 $),B+ /32&36HCi36HbE/@  H @IL#@111_1 .0/J R.h)) @LPN??^]+32]2]2?32?]323]q]210]]+]+]3#53573#>32#"&'732654.#"PJJ];>R1 !:-% % 2% fEM]Ek #?X5.F/I1-0D*SP6J3$]@ &P&W@ H%  Q  P?32?39/2^]+2]]229/104>32.#"3#32>7#".3!@_?+'! =*KM QQ)% U9A_@9bJ* K OCKKU O )Id(VBL6 @POO+++44EM A7r@F8+)+7"#66"6"6. 9-..8"7I.f.v... -!Q?3322]]?9/32]2]99//]210]]"32654.'>32#"&'#'>7>5!g3 =B#,zA)J8! :R1/_- !0@+ 19Y.3$ @)A-/B* !KKI 8+N 6'"V]^*P5+Z@7  '&-, IIJ$ Q????9/3/2^]9/]3210]2#"&'5##3353>"32654.i)J8! :R1/_-]]\A3 =B#,M)A-/B* L.3$ P 6[P6CxD6\FPo 8@      IIJJ????^]9/10!##5#333X]]E3(8@&''* 0WE4%$#"&"..i.W.,&&&",&,"&"&"", i))/:hf11   9v"""7"G"g"",NF6V666Q?]?3299]]^]]]]]99+}]]]]]10]]]]]]]]]#".54>7.5463267#"4.'326;\A=[<*9" -]NAKI-0:  .T@%`.5AL#8'HK3bK.)G]3-OB5;+EKO# 1AY@1F1 sJ"@1h)r@D$s+Yivw+*y### yD%| B?32?]22/9/^]]]]]99//]2310#32#".'#535332>54.+2VIsP*110ak'G6 6O2R  $B5S32#"&'#2654.#"]"+L9" 32#".%267.#">32".#"3267:5[yDCwZ55ZwCDy[5-#:Q23R;"<-$<99$<99!&<t\^t.[Y[..[YY[..[T9]A#$C`;L%bowf 3$5s@0!8Hx(Y"i""7.Vf   61@H1@H%%Y++PFVQ?]?]9/++^]]2]210]]#".54>32".#"3267'267.#">32$AZ55ZA$$AZ55ZA$&$#& L<>M ! LdH''Hd>=eH''HerEOVK B FPVK  @V(7hs   vG  s    s  A B B |E?222???^]]]2+}]]]+}10]]]]2.#"#.'3>!9+  ["ACF%k6h7 $2F &0Kn܀ 4&:@R(x     PIIJJ?????322^]]]+}]+}210]]]!#&'3>32&#"%T1i'd !%&f *)! j 1omf(3+H VC-!@/x-, v+*#$$ ss  s  /@/P///s. yBG; H9* A#+$****'''' AA$rc@P2#B?2]]]]]_]]]q2??]]22?33^]_]]+]q?3?]q]2/2]+}ć9/10]]]]]]]]]]]33>733'>7#"&'73267V_ ]ik/M\!-C TBFGB9>PG&!!&FO52#JHA* ', T_fc\%@9 (( 9@PZ^+@f- )(!""y -P--,ZjI!)"((((%%%%IIJ@IaqR4DJJKI??3??3]]]]+???]]22?3]]]2/2]^]]222/]]9/10]]3'>7##3>7'"&'73267X'@RHHBSZBGG>PG&!!&FOE#E?7("'&fmj)+ge]"T@9 (( 9@P(d@3 #"&'#535334.#*232>F);*e)PPa 9O0'  #'H7!fpp IMMq*8  5"+k@ '@4 H-,$  0  QPP???39/]32222]32]3+]10]3533#>32#"&'#"32654.J\A)K9"!;S1/_-J3 =E#.EEAa'?-/A( +3# V=$@\H"6F  s0 vGWg& s%6vy 6  B|E?22?9/9933]322]32]99]+}10^]]]2'+#>"3:7'7>54.FA@@K3Ma)e2>!I.?:&(5FonN_o% UP&e:1(4 PG'@eeu     ZIX)9I)( #PK  Q?333]2]23??322]]]]]]]99+}ć10]]4&#"327'7>7'#"&'#>32ZK*/@&;==a416>9 $-E]!cA<`D%[bg%kO0Mx#\$b  &FeV9#@s syABB???1053!#XaP)@  IJJ???^]1053##HT]F 0@s  y y AB??9/323310!3###53ʛaPPTK>K, 6@    IJ??9/^]323310#3##5#535!咒]JJBFFVGJ'@V/Iiyv8))s(8|##yABB7 &  yC?]]???9/3]22]]^]]]10]]]]]%#"&'732>54&#"#!!>32J$Ec@ (D1gQ#Fa $&DgE"AqU1O;V8rp S,PsPF"a@=vg%/?  gV$#IJJR????9/322]]^]]10]]]]2#"&'732654&#"#!!> si6P4   ?EEK6]b"@x6]E'JW]_PMG39@ +&&s% % %3.33s .-.- sr--s    }  , 55  +,+,   s ,s43A-A ,A&A%ABBBBy B CA??????????9/3??^]]]2/]]]]]99}3/]]]3]]q+}3/3]]q+}9ć+}]և+}10]]3#5#.'##>7.'33>7bFJG>{3S\3DHI!]!JHCmCJP'GJGq?EF]FE>"SWT$:N.]WLKLW].0c`Y$$TWT"KSU&5&USKo/5@11)&&% % %g%w%%,// ,+,+  H x   4++  ;    *   HH)*)*1W*0/I+I *I&I%IJJJJ J LI??????????9/3??^]2/]]99}]+]3]]q+}]3/3]]q+}]]9ć+}]]+}10]3#5#.'#5#>7.'353>7267-_)GX$7;;Y;;7g7=@762g$^?Y?^$;?>+k8ޑ!ID;;DI!$LID>?;-nHHn-C S@==&<<3-,@ H6''%''!---{-\-l-K-fg32*8OS_SS8S8DXvMvfvU EDDDTyRRGEEJ|?2/36'54&#"'>32#"&'73254&/>7".'732654.+4!D8$M@7X )3< 4X@$=;7)9Q1 (+ $ &C6'  %.8U_!9M-> .#-9 O -E/0T-<(0H2 ! 9   P 9D(3 ,CQ@H%)5)))5('2#sRbC5#%5SE@Q H  4&$;$;SRB G5  SLM/...R <:::/2+"(.+QLGMP?22?332229/]2^]22]]299//210]]]]]+]]]]]]]]]]]]]]]]]]]2#"&'73254&/>7.'732654.+532>54.#"'>-N8  .5[Q (+ $ 9US:?B&,WT'! +*( "+0$6$&G6@G! 9   O''#J  P VG@)ss  s @$HT  sCAA AABByB??????9/??2}3]+ć+}2+}ć10]!.'#3>733#5NWY*aa#TRIuNUT%"JKGR\/_WJ!STM"SWT$AKR(Po@`          p d  IIJ JJII????9/???2]]299}^]ć+}]2/}+}]10>733#5#.'#3>?6l;?>453EX%;AB]])=?9=@?/6<ޑ"GB8V"@    /""   s   s    $o !s#AAB"! BBBAA?????9/3/22???222]}+}qև+}q3qq10^]]>73#.'#5##3353?B@8pEIH(PKBm?DDH@aa@H$PNF"TWT#$Y`d0-YRI֘P] @;4  4    4   " !9   t + ;    IIJ JJJII?????9/3/???/_^]]]]]q22]}+}q]+}q3qq10_]>73#.'#5##335.9R"f267@=8h146D=]]=lAf*;?>DIM$D@:srs{"@\Yy M   M H  (  s !"""M""s"")9 @8M0 P `  $s#"A{AB BA????9/]9]2]2?323]+]]+}]+3^]++10]]]]+]#.'##53533#>7cNUT%+`\QrNWY*`PP`aa#TRI"SWT$Xdh//_WJ"FMMF!STM""@ !"  $+ ++;6tf$$    #III J JJJ?????22?9/3/?239}]]]]+}]]]q]]]q+}ć10]#.'##53533#>73!HE<l;CG"]JJ]NNC@6l;?>CNQ#"HC;@EE@B@:=@?@xYy     H (   s  s)9Y 0 P `  sA{yAB BA????9/]9qq?22]]]]]+}q^]]]q3]]+]10]]]]]]]]#.'##5!>7NUT%+`\QrNWY*a#TRI"SWT$Xdh//_WJbS!STMQ@*z Wi 6 ) i (  h@\H$+vtf Ie)9II J JJJ??????9/]3]]]?]]Ƈ+}]]]q]q2+10q]]]]]]]]]]]]#.'##53>73THE<l;AB]>?6l;?>CNQ#"GB8N=?9=@?VGE@'ss s  A A B ByyBCAA????9/????221033#5#!#3! aW\\aaS@Po@H@*   I IJJL?????9/2]^]210%3#5#5##3353FXK]]]MޑV ;@! s s  y ABByA????9/2^]10!##!#3! aaaST@P ?@$      IIJJ????9/2]^]210!#5##3353#]]]NVG)}@RvY#i#y#(#8##v(8+s*&6yC)&y  yABB???9/]32?]]]]]]9/3^]210]!#!#!>32#"&'732>54&#"Qaa $&DgE"$Ec@ (D1gQ#Fb,PsFAqU1O;V8rp PF>"`@9"!i$!#v!IJJV R?3]2???9/^]]32]]29/310>32#"&'732654&#"###!Bsi6P4   ?EEK4]]1 x6]E'JW]_PF:H5E@M*vDvC+*y*wv6A>v(( gv-f-"65--56"GFG@\H|;;;;;# |5522Eg  wAAA&A6AFAAyw(g((((FW""y#W###G?^]]]]?]]3]]]2]?]3]]]29/]]]+]]9/////]]33]2]]]210]]]]]]].#".54>32.'.54>324.#">C"9^C%cX 1O:6M0"A[9:@; 'WRGExX37^~G/ZB (96 *F2Y $FgDp&Z47bJ+(DZ12]L6 % N)@.2ZVW\0 o!:,]d9V##7I3p3 A@d< 6'!fv$ A#))%44V9/9.A99A.CB I>&6   @% H 4A>PFfw)))4Q.w///L?]]?3]]2?39/+]]]]]]29/////^]]]33]22]310]]]]]]%>54&#"7.#".54>32.'.54>32L :F('%*&"=-?< &=+(;&.B*+-* C@85YA$)E[24&>V<->>1I1Pe>$+J61A%'G:* D 2#'EcABdC# :CS3CG% G@,s s   yBC yA???^]]]]]10#3#5##5%Y\^TaTo ;@"    JL I???^]]]10#3#5##5FXKNޑNP<G@RG(       __    IKI??9/3/?^]]]]]+}]+}]10]#5&'3>7&g0]0f&d !%&&%! jj1omf((fmo1P@O  sos  soyBAAAAB??????9/3/2^]]3+}]3+}1035#53.'3>733#Cx5r&^21^'l6xBJicRNNRciJG@qyD3:J(x_ 5E     _   IK  JI??3/2??^]]]+}]]]]]]+}]]]]]]103##5#53.'3>7"X,r]s,Y!d !%&&%! ~bDDb1nme((emn1GZ@@MM7'W MM@8MMMx        s  @M{m\K=+ GgMsMtbZC: )so   CAAAA B ByB????????^]]]]_]]_]]]]]]++}]+]]]]]]]]]++}]10]]+++++]]]+]+]!.'#>7333#5*2661+k-wCpo4_)R\IOO##OOIX`GKEo@LF  I        | Hv@N Hz\l+;K    LIIII J JJ????????^]]]]]]]]]]+}+]]]]+]]+}qq10]!.'#>7'3733#5 &((% `#a0i|}c#I FX698996D@0f4ޑG<B@%ss s  A yyA yBC????9/////10%3#5!#5!#!3W\MaSaTTboB@%   I I JL????9/////10%3#5!#5!#33:FXsn]MޑNN:Gx\@8s s s|yA yB A???9/^]]399//]]310]32>733#5##".=3%?0'$ aW\\O-L`7`*?)_ #A]9.o l@D)     @/I J LI????9/]32^]]]310]]]]]3267533#5#5#".=<4&3]FXK!$+G3;/Eޑ*E4:(o@B  s us/f|yAB A???9/^]]332]]9///]]32310]53>73##5.=3 ,!K&;aa;&K@R.`$9({~ _K'@X6'|@L)     +; wp I J I???9/]33]2]3_]]^]39/3210_]]]]53>753#5#5.=*&E%]] )E'?.23_` ^\*C1VEI@(fs s  |VHAB B???9/]]3299//2210^]]>32#54.#"#3Q-Mb8`'@1'Maa $B^;+A* PK /:@:g)8**u*)u)7) Hx& 0v/',,,<:@LMv"W;""0y@X55|''7'g'w'''EY&|Z  hxW(F?]]]3]2]]?]]9/3/]22/22]2+2]2210^]]]+]]]]]]]]32>7#".'.5467>32'4.#"! wz 5* $4D'T~T+&?.M,36Qi32!3267#".%6&#"!8'IM,?M)hqSV1C U6A`A!bA9 1!#5& F 7R6  PUN &C\}?Q(4G 2=@Cg);--u-,u,7, Hxs/3v 2'///?=@RMv%W">%%3y@X88|'*7*g*w***ECY&|Z  hxW(F?]]]3]3]2]]??]]9/3/]22/22]2+2]229/]]10^]]]+]]]]]]]]32>7#5.'.5467>32'4.#"! wz 5* "2@%[@_@"&?.M,36Qi32!3267#5.%6&#"!8'IM,?M)hqSV1C I0X/G/cA9 1!#5& F 7R6  PUN ,?Qv?Q(4V,r66VGL+!@ #LM MM@ M7MLMM@2M  s   L( M L  M vMM@ L@ M- @,L M ()s+,*B(%y )ByCAAA????]?9/2?2}3++3/]3+++++++++}ć10+++++]+++++>73#"&'732654.#"#!QPHrEJM#;gL,!Aa@ Oa2KU$a"VWQ!MPP#"BgI73#"&'732654&#"#3??9m7;:gv7Q4   ?Ia[#]]%=?;797r0WC'JUQ_X C!l@C H(s ss###    "yBG!yA F  y F?]]??3?^]2]9/3]]310]+'>7>5!3'>7#C)=S8 FM\!-C Vb8ic^--O;$THJT#JHA* ',b Z^#k@8H8H!  "!%`%%    $!JY#I H Q?+??3?^]2]]9/]3310]+'>7>5!3'>7# !0@+ 19ZX&@U!KKI 8+N 6'"V]^*E#GA6("'VEkU@/ H ssyAB y GA??32??9/2^]210]]+3#"&'73265!#3! a !:-% %aaSK.F/I1-QPE]@5 H&  IIJ R?32???9/2]^]2210]]+!#"&'7326=##3353 !:-% %]]].F/I1-VC`@; Hss ssyAByB GA??3???9/2^]]]2]]310+33'>7#!#3! a\!-C VaaS#JHA* ',@PZSW@5 H  0IIJ JK?3????9/2^]]23210+%3'>7#5##3353X'@U]]]M#E?7("'AG(T@2s s ss|y ACy B A????9/^]]3]99//]1032>73##35#".=3%?0'$ a\\WO-L`7`*?)_K  #A]9.o_@;)       I L JI????9/]32]^]10]]]]326753##535#".=<4&3]KXF!$+G3;/}*E4CC~6D.@cHf..-j))(fW i iWs&%%%8,s s++@* LO+_++/s@ MW6v@ M@6 LX 0 s  7@H|66 y,B&%GAAA/L@ H|A B??]+]+2????3?9/+^]]]]++]]]+]2]+2/]3]3]]210]]]]]]q]]]]]]]]]]]+%.'#>73>733'>7#&' !&+&! _  U>>::>>U\!-C P !&+&! `JV[Q@vZK,x66x,ў#JHA* ',v@Q[VJCZ0@b0H)9I &&+&&&#2p222""",+  & 6 F   $1#J"JKII&0@@&+H| I? O  @&+H |,, IJJ???9/+q?+q2???3??2/^]3]]322]2/]332/]33]]10+#>73>733'>7#.'#. Y U V88V V X'@Q *)$ N %)*cW=<4vv43nrr6#E?7("'WcWZRR[WV,6$+6DHz6$+6DK,%V6(a36H[8)@*& M&M(MM0M8L0 M MLM@ MMM) M)v @ M@:L@ M+v@ML@M*y))$|L MF| LE?+32?++9/++++++9/3+2+10++++++++++++.#"'>32#"&546732>7" wz 5* $4D'WT)0TqBc-C,3K2r} P 6`M\\-O1R;!(?P(6#@Y!x e!j x #I#Y##%x$  gXPhWQ?]]?]]39/]2^]]9/3]]210]]]]]]]#"&=47!.#"'>3232>7(BV-hqdSV1C U6Dc@A9 1!EgD"PUN )Hdm?Q(48z6u66vUrz66 z6C,6+GVc6P6qVz6P6q:z6236Rk:3!o@MXh8H#Wg7G   "P`@PIYPFVQ?]?]9|/]q^]]]]]10#".54>32267!"!.$AZ55ZA$$AZ55ZA$?MM?dH''Hd>=eH''HeXLLX~QFFQ:z636j2z6>"6 Cc6vD6\ICz6vD6\IC6eD6\ec:!z6x.6=VG &@ ss y AyBC???10!3#5#W\\TPo &@  IJL???10#3#5#!FXKBޑVz6P6 V2'37@uGg H5(((9?97...o    sxs s 8775 )1 ++)++##&##EA ABB?????^]]]]22]+}ć]]]]]10]]]!.'#33#".54>324&#"326!!6+g&w&&|/ 7{8{@,///F  |  E?]]3]]2?]]39|/]3222]2++2]210]]]]]^]]]]]]]5!654.#"'>323#!!32>7#".547#53>?%U"+3E*O);Q1 4[A&,?H/'#.e0BV4 5]H-%C'  Q !4C!C)C!/6 J0>"&C)( @U''   s      ByA??9/333]222222]222^]]]2222]]22222310]]#77#5'75'75#5 tt[ttP7?Es7?E6?Ds6?DP0 1@s  y y AB??29/////10###5%!5[+NFNNN )x@JH#x#"Y"i"% % H%   %+*X  `_U?]2/2]2/2?9/////+2^]10]]]%!#5#535#53>32+"32>54.[RRRR'_))MoE:[&,50L5.=GjjGfHJ ji9O2f5'&2 *\U 6@       i?]]2210^]"&'73267?OG&!!&FN\@9 (( 9@$VP%VV(!=Vk+:'+@ )h)v M @M @ M  -(M@, MvMM@M,+y))?)O))))M M@# M|MMF#M# M# M#|MME?+++++?+++++9/^]]+++++]+++]]104>32#".732>54.#"!!:5[yDCwZ55ZwCDy[5f ;U54U; ;U45U; Q'[Y[..[YY[..[Y?gH''Hg??gH''HgPV,Vo.y@I7w8 x w  s   s @p BIYAB??33]?^]]]9/+}+}10]]]3>73#&'%FBA"["ACE&k6h77h5nKKn$۞C%0V1% 3@     y yByA??9/10^]!5!!!!!6,"6raTTT:2VeV=3! %7P<8( $/@@ /%$" #1x9s0s0w*6**v0$B/L| "M"L"@ M"#BD %L%|LMD?++2+2??+++2+2?^]]]]]]]]]]3}310%2>54.#".54>753##"30S=$$=S0^@w[77[w@^@v\77\v@^0S=$$=S05M22M5&IkEEjI&PP&IjEEkI&TB5M22M5c;A@Mss s @H  dK$48$4T$DT @/ L M @ L |MMAAAB????9/+3/++++2^]]]]_]]qrrr^]]]]]q9///+^]210+!#5".=33326=3#_QpG^k^_^k^GpQ+Kh54.#"!53.54>@vY5$.7( :R22R: (6 .$5Yu+U~S/VJ=RD>JY6;^A""A^;6YJ>DR=JV/S~U+3I,a@: YJ! o.P.(-  Q"%(%8%%P?2]]2?3/322]]2210]]]^]2"&'#".54>267&5.#";92)  -=N6;Y<%Eb*)?5P[$9 # N!(Hc;?eF&3 !  b[(E3PG085M5@ M" M @M))0M0 @.L %M%::89 (**J3M3MMOK??+++?39/3/22]+++9/10++++#4>32#"&'532>54&+532>54&#"]5T;.M863);%%E`;)A=3!<-]c5';:#02U?#1H0-U +8@!:W: V $7%LYL-1B*9!G^@+H8H IKK@ HQ I II????+???^]9/]]+9310>73#.'z "" +% ] #3E-X%'%$eru3+ioo12~FVX:uf'3>@*F*WC404*,,**,:Y i  0@( fW:::?Vf5Q#O,v,, ,,7WI?^]]]3]]]?3]2?]]]]]]99+}qq210]]]]]]]]32>54.'.54632.#"#".54>-HR$8'$7%)2`&/eW1( ", #3-'K:#;\A=[=/AmK$C30A&-C0#=>+FF J  +7DX=/[I-*H`6.P@079@` +&7710 0000+)+"#;5---:?00""w#(Q  P?2]2?3]2]9/3/]q^]2210]]]qqq]]]2.#";#"32>7#".5467.54>$0)&,6J&Y[,"6-! &4C'$N@*2) 9QO #&I !S M  ">1(F$%8&4D(@25(E(#((('I :I: hx)@T HU 0 H9I W!w!!!xg  *)*NW&& R?3/]?2]9/////^]]]]3]]+]10]]+]]]]]]]]]]]]]]]'>54&'.54>7#5!P 9A3H/32#4.#"#P jE>R1] 2% ]#?X5#0D*I7#)f@Cx%+w$*$'7g H  Q(8GO?]]]?]]]9/^]2]]10]]2#".54>2>7!"!.-=]==]=>\==\>'8$$8('7$-$77g[\g77g\[g7:(JhAAhJ(t&Ec==cE&N ;@    @ @ H  I  HQ?+?+^]10].53VH] FJ P "@ ! L L Mس M M@2 M  !"    LسM@" M$# JJO JJ??/?32??9^]29/++++}ć+}10++++++#>7.#"'>32#./' _,7> $ %"5( Jp$aA6sog+:>*A,K$1^PGY2D>@sW>:W: j FV5$v g  5&fv@@v//ihV<G<<?'*#755&#OJ R?3??39/3/2^]]]]2]]]2]]2]]10]]]]]]]]]]]%'>54&'.54>7.54>32.#";#").@'P 8Bdd.5+"<3RPJG !w@Oif hXI#!"XI PKziFVziQ?]2]]]]]2]]]??]]2]]10]]]^]"&'#4>32'32>54&#"1,I]9R6w =YB(:&DQ?D  6X?#;cH)z3F([bR[3D)h@EEU$4fvTf T  +7%G%%Xh*"PJ R?3??^]]]9/3]]2]]]10]]%'>54&'.54>32.#"1&4!P (->R2 @_?#C6RT+9Q +&G  +>R58dL, O iW4?&3U']@;Yh)) W    (HI%F%%Q?]]?]2^]]]2210]]]]]%#".54>3!#4&'#"326#@Y52XA&-Kd6 _.&!<5#'5DN:]A#$D`;JhBN.5:>l"*M>)B.[J@,   @    I HQ?+32?^]]]210]%#".=#5!#3267)5B' !! 4O4NN'3 J:@%IIW g  yQ?^]]]??]10".5332653 AS0]=<<=]0S 'AT,+XIIX,TA'3G$q@&L L"L@ L%K!@LQ" L P?+?+?^]++22++105.54>324.'>%TBkK)/W|LQ|U,0I3f\"aaaa)E_;?fF&&Ff?v (C3 } fPPe  i[JGR@ II L@ LQ KI???++??2^]10>=3#5.53S]]BfFZGeB]\T?^j9bI++Ib9j^9 8@6++.L.M.@M.)M) M):/L/гM/@!M/44 L MMM@G M L0M M   9./PP& MM MLMQ?++3+++?3?3^]2+++++2+++]9]2+++++2+++10]]]]"&'#".54>732>=332654.'7=7SS7-L8"S:9(+\+(9:S #8L 1,,1?cH0WH7 /AQ-Sa.@EE@.aS-QA/ 7HW0Hd>6$H H++10U&(?l@ @H @H @ H+++10&+Dl@ @H @H @ H+++10&,Dl@@H@H@ H+++10 z&,@POO+++44&2l(@H( H( H+++10&<~l9@H@ H@H@ H@H@H H+++++++10Pz&<o@% POO+++44&l0H0 H0 H+++103I&[@ /- P.O++47&7@ <:,#P;O++4PG&J@ PO++49&@   P O++4&@   PO O+++44E &!@ POO O++++4443&RS@ " P!O++4J&d@*P"OO+++44J&L@ PO++4J &d@("P&OOO++++4449 &@ ;9 3P:O++4?F;@"BR@H@/]+^]]10'7F6qݞ&w)@A`?]?9/]q10'7DD.B Q @,H @H@H /O/]^]+]++10'7"&54632!"&54632 h4Y 3I &u@ -8 P>O++43I &p@ 1> P-O++43I&x@ -? P@O=O+++443I&Y@ 1? P@O-O+++443I&@-> P?O=O+++443I&x@ 1> P?O-O+++443Iq&s@ LB P5DO=O+++443Iq&s@ LB P5DO-O+++44&$c@$%HHH@ H@ H&'H&'HHHH H+++++++++++10&$Π@$%HHH@ H@ H@ ''H@%%HHHHHH H H H+++++++++++++++10&$vǹ*@$%H*H*H*@H*@ H@$%HHH@H@ H/@H/@H/H/H/@ H/@ H/@ H/@H*$&H$&H*HH*HH*HH* H H++++++++++++++++++++++++++++10&$gH*@$%H*H*H*@H*@ H@$%HHH@H@ H/@""H/@ H/@H/H/H/@ H/@H/H/@ H/@H*03H03H*''H''H* !H !H*HH*HH*HH*HH*HH* H H++++++++++++++++++++++++++++++++++++++10&$>T*@$%H*H*H*@H*@ H@$%HHHH@ H++++++++++10&$Y̹6$!1O+&$ !1O+7 &U@  :E,#PKO++47 &P@ >K,#P:O++47&X@:L,#PMOJO+++447&9@ >L,#PMO:O+++447&x@(:K,#PLOJO+++447&X@>K,#PLO:O+++44U&(?_U&(?m'(ǹ'(¹'('(̹PG &h@ #P)O++4PG &c@ )PO++4PG&k@*P+O(O+++44PG&L@ *P+OO+++44PG&@%)P*O(O+++44PG&k@)P*OO+++44PGq&f@7-P /O(O+++44PGq&f@7-P /OO+++44&+?_&+?mh'+ǹY'+¹R'+m'+̹'+'+> &@  PO++4/ &@  P O++40&@  POO+++44&@  PO O+++44E&@  POO+++440&@  PO O+++44q&@ +! P#OO+++44q&@ +! P#O O+++44&,?_&,?m',ǹ',¹',',̹g',g',3 &Rm@  + P1O++43 &Ri@ $1 P O++43&Rp@ 2 P3O0O+++443&RR@ $2 P3O O+++443&R@% 1 P2O0O+++443&Rx@ $1 P2O O+++44&2 _&2.m'2ǹ'2¹s'2'2̹J &f@ !P'O++4J &a@ 'PO++4J&i@(P)O&O+++44J&J@ (P)OO+++44J&@%'P(O&O+++44J&i@'P(OO+++44Jq&d@5+P-O&O+++44Jq&d@5+P-OO+++44&ONQ P-8 P+4+4+3DI &&p-O@ NQ P1> P+4+4+3DI&&x&@ @O=OPT P-? P++444++3DI&&Y&@ @O-OPT P״1? P+44+4++3DI&'&@ ?O=OPT P״-> P+44+4++3DI&&x&@ ?O-OPT P1> P+44+4++3DIq&&s&ad P@ LB P5DO=O+++44+43DIq&&q&ad P@ LB P5DO-O+++44+4&$&&$&δ &$v&ǹ&$g&¹&$>&&$Y&̹&$&&$ &PD &&h@9<P #P)O++4+4PD &&c9<P@ )PO++4+4PD&&k @'&ǹ'&¹'&'&̹b&U&i&r&3I&ap@ 1? P-O++43I&s@ -. P/O++43DI&&y?C P@ 0. P/O++4+43DI& :? P+43DI&&m@@C P /- P.O+++443I&s@ ?1 P2O++43DI&&sRU P@ ?1 P2O++4+46$ac6$6$C6$&$x?  ZD!@ H@ 10+3#".5<73  + Y? 8 ,  x?  .@  H H  /++]10>7.54632x !$'a &&$6bB1@@H& H /+^]+102#.#"#&>2$<<$2#4!  !4#!KWa +W@ Hp %p&&&@H ,@H"@ H"%O /]+++2/]]+10"&546323"&546322#.#"#54>W {0%5&((&5%0K.)).PD&&l+.PP+4+4PD&'*P%P+4+PD&&`'*Pݴ%+P+4+4PG&f@ *PO++4PD&&f@=@P*PO++4+4&(yC'b(y&+yC'b+yV&+?hT@ H  @H /2/3/3/3/+]+10>7&546327' $"%m4~a% &&$Ъ&?]f@ Hd$4D  @H /2/3/3/3/+]^]]]]+10>7&54632%'7 $"%~4ma% &&$&7?Aq$Z@/  @H %@H@ H/^]+]++qq3/]1067.546322#.#"#54>l) '40%5&((&5%0a " 2.)).&a@  P O++4 &@  PO++4E &!@$ POOO++++444E &!@ POO O++++444&@  PO++4a&!@  P*OO O++++444&,a@ PO++4c&,@ PO++40&,yC0'b,y?^V@0FV@ @H /2/3/3/3/+]]]10].546327'&"$ Lm4~?$&& %&?hT@/JZ@ @H /2/3/3/3/+]10]]].546327'7w&"$ ~4m?$&& %|&7?Aq$R@# @H &@H@ H/^]+]++qq3/10.546322#.#"#54>' )P0%5&((&5%0? " .)).J&aa@ (PO++4J&d@ PO++4J &d@.P&OOO++++444J &d@("P&OOO++++444JG  &s@ "- P 3O++4JG  &n@ &3 P "O++4J&d@ (PO++4Ja&d@*P4O"OO++++444P&<al@ # PO++4Pc&<o@  PO++4'<C'b<|&3?mB Y@H @ H@H/O  /]^]]+]++10'"&54632!"&54632Y4hB   tB /D @ </10]'d(49D &'LO P@ <: 3P;O++4+49D & HK P+49D &'@LO P;9 3P:O++4+49 &@ K= 3P>O++49D &'@^a PK= 3P>O++4+47&2gC'b28?&cC'b54 &nDI-@3 H@/]+]10'7I(4o? (@@H@ H /++]10.54632'$! ?$&& D6$&D6$&δD&$v&ǹ9D&$g&¹*D&$>&D&$Y&̹D6$&D&$ &D&+?'_D&+?'mDh'+&ǹDY'+&¹DR'+&Dm'+&̹D'+&D'+&D&'_D&*'m*D'&ǹD'&¹D{'&D'&̹D1&U&UDN&r&rD6$VDk6+4D6C&)MMLMMM@M s+#MMM@# M"s$)*y))y 0@@314H 0`p_$B#ByA?2??9/]]]qqqqq+qr2/9/32++++3222210+++++++32>7!5!.+5!#3##.'C]=6) !%/E:1HY/?C@mAB@d %H&HH2H,=&BMR&'OI?+?Xs@)]<#0Wz EF??910#".54>32"32654&4&#"326%467'7.76323.'#".'.#"3267>32>7##"&2654&#"8bLKb99bKLb8!&.  .&!1! 1&F!   1 !/   !F&1ZKa99aKLb88b%=<?' &>>=<#.&F :  F)0#-!t'o'g't]'o+I #'+/37@&6Ff= &6Ff= &f&fIYIY i i6f 6 f  9  9=11i1y111@ H 11/4;H/(((,/H(@#(+H((i(y(((@ H (($4;H$$$@!+/H $$$$$i$y$$$@ H $$6@ 4;H6652@ H2'2722+@-1H1+ +++++@ H+'+7++'-1H'@(,H'@ H'''7''  "/ ;^[Ly  ; ? o =O   { L   9 i   /_ ;{L8 f3`=PSDv 0`=ArD@=f7  0 = !   T   F   9@ 4EH",585@91;Ha5@5P5!5155555555555c5s5T5C555@ H55'55"@H"""""3,,@@ &HK,[,<,,@H,,,,H,@ H,2EH11&40;H4,/H4@-#+H|44k4\4K44H4@ H4#+#;##H#)+B-$'H-#H-H-@H-@ H- -@2EH$&A @P`;`!Arc$D6Ff'  <wC i3/?<O `@*IL\l )i AL\  /O_o;Oo.Ll| +Ii8) >xK f  3 B `=AScDe@:' APA`p A?r2_]_q_qqrrrr^]2^]]]qqqqrr^]?^]]2^]]qqrrr^]2^]]]]]]qqqrrrr^]9/]3^]]]qqqrr^]3^]]]]qq+rrrr^]]^]]2^]]qqrrr^]]2^]]]qqqqqqrrrr^]]]?2+2^]+++++?2+q2++qqqq+++9/3+3++]+qq+q2]]+2]+]qqqqqqqrrrrr+22+22r22]]]]qqqrr^]2^]]]]qqqrr^]2^]]]qqqqr^]^]]2^]]qqqrrr^]]2^]]]qqqqrr^]2^]2^]]]qqqqrr^]]^]22]+++2]+qrrr+2]+]+2]+]]qr+r+2]+]]++r2+2]+]]10^]]]]]]]]]]]]^]^]^]^]335!5!35!3335!5!35!3j444555k**yhk**L**yhk** (7Me}@LL[ LTMSMDMD@L=M=M1(M.M.гMM @LMM~{igfXA VNdKM@B/7&A (  @ A8ov}ifd X ^QcNLBF;(,#4 ???????????????????9910++++++++++++++++%#".5332>73>32#"&'732654.#"#".5332>73>32#4.#"#3#3267#".57#".5332>73%#".54>32"32654&32654&#"'467'7.723&'#"&'&#"6"'327>3267#72654&#" iER1] 2% ]u $*3 F-5E*] iE32"32654&32654&#"'467'7.723&'#"&'&#"6"'327>3267#72654&#"":O--N:"":N--O:"    T"/)##)/(  0-N:"":N--O:"":O    u$ $%  $$ $j- 0! ## !0 -  {n` ` $ & * - 2 4 9 : = z        E u }       P & * 2 4 6 7 8 9 <       E K Y u     $ & * - 2 4 6 7 9 : ; < =              E K u }          $ & * - ;         u }    -79;<=EKu}$&*-246789:; <= EKYu $&*-2479 :;<"= EK" "&*24789:<Ku#$#&#*#-#2#4#6#7#8#9#:#;#<#=##################K#}#########$ $ $ $$$#$$!$&$*$-$2$4$6$7$8$9$:$;$<$=$@$F$G$H$J$R$T$V $X$Y$Z$[$\$]$^$`$c$l$n$o$"$$$ $!$$$$$$$$$}$% % % % %%%%"%9%:%;%<%@%]%`%%%%%}& &&&"&#&$&&&*&-&2&4&6 &7&9&; &<&= &>&F&G&H&J&R&T&X&Y&Z&[&\&^&c&l&o &&&&&&&&&&&'' ' ' '''"'#'$'-'6'7'9':';'<'='>'@'D'`'c'n'o'''''''''''' '!'}''( ( ( ( (#($(&(*(-(2(4(8(>(@(F(G(H(J(R(T(W(X(Y(Z([ (\(^(`(l(n((((((((((((()) ))))" )#)$)&)*)-)2)4)7)9)<)>)@)D)F)G)H)J)Q)R)S)T)U)X)[)])c)x)))))))))))B)* * *@*Y*\*`**++,,- ---$---=-@-]-`-c------. . ...." .#.$".&.*.-.2.4.7.9.: .;.<.=.@.F.G.H.J.R.T.V.X.Y.Z.[.\.].^.c.l.#... . .. ..".........B/ / / u/// /$/&/*/-/2/4/6/7/8/9/:/;/</=/@/F/G/H/J/R/T/Y/Z/[/\/] /^/`/c/l/n/o/x ///////////}/090<01122 2 2 222"2#2$2-2627292:2;2<2=2>2@2D2`2c2n2o222222222222!2}233 3 333$3-3;3>3@3D3F3G3H3J3R3T3Y 3Z 3\ 3`3c3l3o 3333 33333 3344 4 44"4#4$4-4647494:4;4<4=4>4D4M4c4n4o44444444!45 55 5"5#5$5&5*5-5254595;5<5=5@5[5] 5^5`5c5l55555566 7 7 77777"7#7$7&7*7-727477797:7;7<7@7D7F7G7H7J7Q7R7S7T7U7V7X7Y7Z7[7\7]7^7c7l7x77777377 7)77777777B8 8888$8-8=8@8D8]8^8`8c8888889 9 9 99999"9#9$9&9*9-90929497999:9;9<!9@9D9F9G9H9J9Q9R9S9T9U9V9X9]9^9c9l9n9o9x99 9 99)99 9)9999999B: : ::::":#:$:&:*:-:2:4:7:9:: :; :<:@:D:F:G:H:J:Q:R:S:T:U:V:X:^:c:l:n:o:::: :):::::; ; ;; ; ;#;$;&;*;-;2;4;7 ;9;;;<;= ;@;F;G;H;J;R;T;X;Y;Z;[;\;^;c ;l;;;;;;;;;;;;;;;;B< < <<<<<"<#<$<&<*<-<0<2<4<7<9!<:<;<<#<= <@<D<F<G<H<J<Q<R<S<T<U<V<X<[<]<^<c<l<n <o<x<<)<<(< <)<< <<<<"<<<<B= == =#=$=&=*=-=2=4=8=9 =< =>=@=F=G=H=J=R=T=W=X=Y=Z=[ =\=^=`=l== == ===== ==========>$>&>*>->2>4>6>7>8>9>:>;><>=>@P>>>>>>>>>>>>>>>>E>K>Y>u>}>>>>>>>>>@&@*@2@4@:@;@=@@@@@@@@E@u@@@@D D D D"D@DYDZD\D`DoE E EE"E@EYEZE[E\E]E`EnEoEE#EFF F@FGFJFRFY FZ F[F\ F^FlFI 'I IIII"I@'IYIZI[I\I`'IcIlIoIx II(II(II(K K K K"K@KYKZK\K`KnKoLMM NN N#N@NFNGNHNJNRNTN[N^NlNNNOQ Q Q Q"Q@QYQZQ\Q`QnQoR R R RR"R@RYRZR[R\R]R`RnRoRR#RS S S SS"S@SYSZS[S\S]S`SnSoSS#STMU UUUU"U@UTUYUZU[U\UcUlUoUWW W@WFWGWHWJWRWTW[W^WlWY YYY"Y@YFYGYHYJYRYTYYYZY[Y\YcYo YYZ ZZZ"Z@ZFZRZYZZZ[Z\ZcZo ZZ[@[F[G[H[J[R[T[[[^[l[[[]@]F]G]H]I]J]R]T]W]X]^]`]l]]]]]^$^&^*^-^2^4^6^8^=^`P^^^^^^^^^^E^Y^u^}^^^^^^`$`-`6`7`8`9`:`;`<`=``````````````K`}```````bbbbc$c&c*c-c2c4c6c7c8c9c:c; c<c=ccccccc cKccccccccc l7l9l<llKlln$n&n*n-n2n4n9n:n< n|nnnnnK nnnnn nx$x-x6x7x9x:x;x<x=xxxxxxxxxxxxxxxKxYx}xxxxxxx|7|8|9|:|<|=~~~~~~~~$&*-246789:;<= "$-79;<=>@D`cno# !} "@YZ\`no~"@ ` "@YZ[\]`no#  <"-?Z@FDEFGHJKLMNOQRSTUVW X`Fo>VfffN(M(]) ) )")Y)Z)n)o}}}}   } }} }}$&*24< P#EK Yqu $9;<=K`q}%&'()*+,-./0 1234578;<=EKYqu}$&*-2479:;<!= EK!q   !$-9;<=K`q}$%&'()*+,-./0123456789:;<=P#EKWYqu}$"&*-246789:;<=#"4EKYq#u}""" E&*24P#EWu7;=Y }7Y$-79;<=Kq}$&*-"246;=:;[][]#&*248FGHJRTWXYZ\~$79:;<=&*24789:<JYZ\}~YZ\~9< F G H J R T   &*24FGHJRTXYZ\~$-D[] $-9;<=D !#}MYZ[\~&*248FGHJRTXYZ\~> 9 < M O W X       } !Y!Z!\!!~#$[$]$$$\$\-\;\<\=\D\E\I\K\L\M\N\Q\S\U\\\\(\\\\\\\\ \\\\}9}<}(}}}}}}}}9<&*24789:<FGHRTXYZ???&*24789:<YZ\}~$&*-24DFGHJQRSTUVXYZ[\]) "$~DFGHRT$-=D    "L~    "#>@` "*ELXYu~   #@^`clno !!  "EKLYZu}~   ">@` "*LY~    ">@`K  "#@^clx   **EFLZrv|~   *@^`l"3""AEu}~    #>@^`ln  "EFLu~   ">@`  " #@^cl ""  "EFLYZuv}~    "#>@`K   "#>@`cnoK}  >@`clo   *L Yr "#>^clo   EFKLZu~  "#@^clx   **EFKLZrv|~   "#@^clnox    ) EFrv   "#>@`cno Xr}    #@^c l  "FKLZv~   *@^`l"3""FEu}~    "#>@`cno}   "@`oL   >   "@` "@^`l *LZ~  "@`~   ">@`oL   #@^lFv   "@`  #@^lF   "@`no L    ">@`noL @^`l  L  "@^`l *LZ~ "@`co   *L~   ">@`no ~ "#@^`lF  "@`T~    " @ `     L ~        " @ ` n o         "@`no    "#>  "#>@^`lF  "@^`    "#>@`" " " """@"`""" "L"~% % %%%%"%%#%@%`%l%x%%%%(%%%%%%%%%%&%!%%%%!%%%%%%%%%%%%%%%%%%%%%% % % %%%,%%%,%%*%E%F%L%Z%r%v%}%~& &&&&"&@&l&x&&&&!&!&&& !& &*&L!&Z&~2 2 2 2"2D D D"DDDD DLDZEE E E EEEE"E>E EEEEEEEEEEEEEEEEEEEEE E EEEEEEXE}F F FFF"F>FFFF FLK K KKKKK"K#K@K^KcKlKn KoKxKK KKKK"KKKKKKKKKKKKKKKKKKKKKKKKKKKKK K KKK!KKK!KK*KFKK#KZKrKvL L LLL"L@L`LcLo LLLLLLLLL L*LLL~WW W W W W"W>W@W`WWWWWWYX X X X X"X#X>X@X`XXY Y Y YYY"Y>Y@Y`YY YYYYYYYYYYYYYrYvY}Z Z Z ZZZZZ"Z>Z@Z`Z``a a"a^ale e e"eeFqqqqqqr r r rrrrr"r>r@r`ruu u u uuuu"u#u>u@u`ucunuouuuuuuuuuuuuuuuuKv v v vvvvv"v@v`vnvovvvvv vL} } }}}}>}@}`}}}}}}}}} }}}}"}*}L}Y}~~ ~ ~"~#~^~l~~~F   #@^`clno !!!!    ">@`  "#@^clx4h?O/  P Q*^^ 4   #@^`clno !!!!    #>@^`ln 4  #>@^`l   I  " #@^cl """"?)?4 >I !    "#>@`    #>@^`ln  I   "#>@`cno  >@`clo     #>@^l*S?I  "#@^clx*SII>PQ)^^4  "#@^cln ox " #*S)T 4P)^^*   "#>@`cno    #@^c l   >>> #>@`l*   "#>@`no     "@^`lno  "@`co     ">cno  #@^`l # ,"@&`&cl##4^   "#no  @`c   @^`lno  #@^`l   #@^`no   "#>@`n  "@`co    ">@`cno  ">@`n    ">@`cno "@^`cl "@`c  "@^`l  "@`cn    ">@`cno "#@^`l   "#>@`cn   "@`cno>I \"F  K @ 7 7 h  W  W   d  D  d   d :   F 4 4Copyright 2011 Canonical Ltd. Licensed under the Ubuntu Font Licence 1.0Dalton Maag LtdDaltonMaagLtd: Ubuntu Regular 0.83The Ubuntu Font Family are libre fonts funded by Canonical Ltd on behalf of the Ubuntu project. The font design work and technical implementation is being undertaken by Dalton Maag. The typeface is sans-serif, uses OpenType features and is manually hinted for clarity on desktop and mobile computing screens. The scope of the Ubuntu Font Family includes all the languages used by the various Ubuntu users around the world in tune with Ubuntu's philosophy which states that every user should be able to use their software in the language of their choice. The project is ongoing, and we expect the family will be extended to cover many written languages in the coming years.Ubuntu and Canonical are registered trademarks of Canonical Ltd.http://www.daltonmaag.com/Copyright 2011 Canonical Ltd. Licensed under the Ubuntu Font Licence 1.0Dalton Maag LtdDaltonMaagLtd: Ubuntu Regular 0.83The Ubuntu Font Family are libre fonts funded by Canonical Ltd on behalf of the Ubuntu project. The font design work and technical implementation is being undertaken by Dalton Maag. The typeface is sans-serif, uses OpenType features and is manually hinted for clarity on desktop and mobile computing screens. The scope of the Ubuntu Font Family includes all the languages used by the various Ubuntu users around the world in tune with Ubuntu's philosophy which states that every user should be able to use their software in the language of their choice. The project is ongoing, and we expect the family will be extended to cover many written languages in the coming years.Ubuntu and Canonical are registered trademarks of Canonical Ltd.Version 0.83http://www.daltonmaag.com/O  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghjikmlnoqprsutvwxzy{}|~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~.nullEurouni00A0uni00ADmacronperiodcenteredAmacronamacronAbreveabreveAogonekaogonek Ccircumflex ccircumflex Cdotaccent cdotaccentDcarondcaronDcroatdcroatEmacronemacronEbreveebreve Edotaccent edotaccentEogonekeogonekEcaronecaron Gcircumflex gcircumflex Gdotaccent gdotaccent Gcommaaccent gcommaaccent Hcircumflex hcircumflexHbarhbarItildeitildeImacronimacronIbreveibreveIogonekiogonek Idotaccenti.loclIJij Jcircumflex jcircumflex Kcommaaccent kcommaaccentkgreenlandic.case kgreenlandicLacutelacute Lcommaaccent lcommaaccentLcaronlcaronLdotldotNacutenacute Ncommaaccent ncommaaccentNcaronncaronnapostrophe.case napostropheEngengOmacronomacronObreveobreve Ohungarumlaut ohungarumlautRacuteracute Rcommaaccent rcommaaccentRcaronrcaronSacutesacute Scircumflex scircumflexuni0162uni0163TcarontcaronTbartbarUtildeutildeUmacronumacronUbreveubreveUringuring Uhungarumlaut uhungarumlautUogonekuogonek Wcircumflex wcircumflex Ycircumflex ycircumflexZacutezacute Zdotaccent zdotaccentlongs Scommaaccent scommaaccentuni021Auni021Bdotlessj apostropheuni02C9WgravewgraveWacutewacute Wdieresis wdieresisYgraveygrave zerosuperior foursuperior fivesuperior sixsuperior sevensuperior eightsuperior ninesuperior zeroinferior oneinferior twoinferior threeinferior fourinferior fiveinferior sixinferior seveninferior eightinferior nineinferior afii61289 estimatedonethird twothirds oneeighth threeeighths fiveeighths seveneighthsonefifth twofifths threefifths fourfifthsonesixth fivesixths oneseventh twosevenths threesevenths foursevenths fivesevenths sixseventhsoneninth twoninths fourninths fiveninths sevenninths eightninthsDeltauni2126uni2215uni2219f_ff_if_lf_f_if_f_lzero.altone.alttwo.alt three.altfour.altfive.altsix.alt seven.alt eight.altnine.alt zero.supsone.supstwo.sups three.sups four.sups five.supssix.sups seven.sups eight.sups nine.sups zero.sinfone.sinftwo.sinf three.sinf four.sinf five.sinfsix.sinf seven.sinf eight.sinf nine.sinf caron.alt commaaccentrevcommaaccentcaron.alt.short Parenleft ParenrightHyphenSlashAt Bracketleft Backslash Bracketright Braceleft Braceright GuilsinglleftBulletEndashEmdashGuilsinglright Exclamdown GuillemotleftGuillemotright Questiondownuni0180uni0181uni0182uni0183uni0184uni0185uni0186uni0187uni0188uni0189uni018Auni018Buni018Cuni018Duni018Euni018Funi0190uni0191uni0193uni0194uni0195uni0196uni0197uni0198uni0199uni019Auni019Buni019Cuni019Duni019Euni019FOhornohornuni01A2uni01A3uni01A4uni01A5uni01A6uni01A7uni01A8uni01A9uni01AAuni01ABuni01ACuni01ADuni01AEUhornuhornuni01B1uni01B2uni01B3uni01B4uni01B5uni01B6uni01B7uni01B8uni01B9uni01BAuni01BBuni01BCuni01BDuni01BEuni01BFuni01C0uni01C1uni01C2uni01C3uni01C4uni01C5uni01C6uni01C7uni01C8uni01C9uni01CAuni01CBuni01CCuni01CDuni01CEuni01CFuni01D0uni01D1uni01D2uni01D3uni01D4uni01D5uni01D6uni01D7uni01D8uni01D9uni01DAuni01DBuni01DCuni01DDuni01DEuni01DFuni01E0uni01E1uni01E2uni01E3uni01E4uni01E5Gcarongcaronuni01E8uni01E9uni01EAuni01EBuni01ECuni01EDuni01EEuni01EFuni01F0uni01F1uni01F2uni01F3uni01F4uni01F5uni01F6uni01F7uni01F8uni01F9 Aringacute aringacuteAEacuteaeacute Oslashacute oslashacuteuni0200uni0201uni0202uni0203uni0204uni0205uni0206uni0207uni0208uni0209uni020Auni020Buni020Cuni020Duni020Euni020Funi0210uni0211uni0212uni0213uni0214uni0215uni0216uni0217uni021Cuni021Duni021Euni021Funi0220uni0221uni0222uni0223uni0224uni0225uni0226uni0227uni0228uni0229uni022Auni022Buni022Cuni022Duni022Euni022Funi0230uni0231uni0232uni0233uni0234uni0235uni0236uni0238uni0239uni023Auni023Buni023Cuni023Duni023Euni023Funi0240uni0241uni0242uni0243uni0244uni0245uni0246uni0247uni0248uni0249uni024Auni024Buni024Cuni024Duni024Euni024Funi0292breve_inverted double_grave ring_acutedieresis_macron dot_macrondieresis_gravedieresis_acutedieresis_breve tilde_macron acute.asccircumflex.asc caron.ascdieresis_grave.capdieresis_acute.capdieresis_breve.capuni0400 afii10023 afii10051 afii10052 afii10053 afii10054 afii10055 afii10056 afii10057 afii10058 afii10059 afii10060 afii10061uni040D afii10062 afii10145 afii10017 afii10018 afii10019 afii10020 afii10021 afii10022 afii10024 afii10025 afii10026 afii10027 afii10028 afii10029 afii10030 afii10031 afii10032 afii10033 afii10034 afii10035 afii10036 afii10037 afii10038 afii10039 afii10040 afii10041 afii10042 afii10043 afii10044 afii10045 afii10046 afii10047 afii10048 afii10049 afii10065 afii10066 afii10067 afii10068 afii10069 afii10070 afii10072 afii10073 afii10074 afii10075 afii10076 afii10077 afii10078 afii10079 afii10080 afii10081 afii10082 afii10083 afii10084 afii10085 afii10086 afii10087 afii10088 afii10089 afii10090 afii10091 afii10092 afii10093 afii10094 afii10095 afii10096 afii10097uni0450 afii10071 afii10099 afii10100 afii10101 afii10102 afii10103 afii10104 afii10105 afii10106 afii10107 afii10108 afii10109uni045D afii10110 afii10193afii10066.locluni0462uni0463uni0472uni0473uni0474uni0475uni048Auni048Buni048Cuni048Duni048Euni048F afii10050 afii10098uni0492uni0493uni0494uni0495uni0496uni0497uni0498uni0499uni049Auni049Buni049Cuni049Duni049Euni049Funi04A0uni04A1uni04A2uni04A3uni04A4uni04A5uni04A6uni04A7uni04A8uni04A9uni04AAuni04ABuni04ACuni04ADuni04AEuni04AFuni04B0uni04B1uni04B2uni04B3uni04B4uni04B5uni04B6uni04B7uni04B8uni04B9uni04BAuni04BBuni04BCuni04BDuni04BEuni04BFuni04C0uni04C1uni04C2uni04C3uni04C4uni04C5uni04C6uni04C7uni04C8uni04C9uni04CAuni04CBuni04CCuni04CDuni04CEuni04CFuni04D0uni04D1uni04D2uni04D3uni04D4uni04D5uni04D6uni04D7uni04D8uni04D9uni04DAuni04DBuni04DCuni04DDuni04DEuni04DFuni04E0uni04E1uni04E2uni04E3uni04E4uni04E5uni04E6uni04E7uni04E8uni04E9uni04EAuni04EBuni04ECuni04EDuni04EEuni04EFuni04F0uni04F1uni04F2uni04F3uni04F4uni04F5uni04F6uni04F7uni04F8uni04F9 afii61352 afii00208uni20B4uni20AEtengeroublekratkaAlphaBetaGammauni0394EpsilonZetaEtaThetaIotaKappaLambdaMuNuXiOmicronPiRhoSigmaTauUpsilonPhiChiPsialphabetagammadeltaepsilonzetaetathetaiotakappalambdauni03BCnuxiomicronrhosigma1sigmatauupsilonphichipsiomega Alphatonos EpsilontonosEtatonos Iotatonos Iotadieresis Omicrontonos UpsilontonosUpsilondieresis Omegatonos alphatonos epsilontonosetatonos iotatonos iotadieresisiotadieresistonos omicrontonosupsilondieresis upsilontonosupsilondieresistonos omegatonostonos tonos.cap dieresistonosuni1F00uni1F01uni1F02uni1F03uni1F04uni1F05uni1F06uni1F07uni1F08uni1F09uni1F0Auni1F0Buni1F0Cuni1F0Duni1F0Euni1F0Funi1F10uni1F11uni1F12uni1F13uni1F14uni1F15uni1F18uni1F19uni1F1Auni1F1Buni1F1Cuni1F1Duni1F20uni1F21uni1F22uni1F23uni1F24uni1F25uni1F26uni1F27uni1F28uni1F29uni1F2Auni1F2Buni1F2Cuni1F2Duni1F2Euni1F2Funi1F30uni1F31uni1F32uni1F33uni1F34uni1F35uni1F36uni1F37uni1F38uni1F39uni1F3Auni1F3Buni1F3Cuni1F3Duni1F3Euni1F3Funi1F40uni1F41uni1F42uni1F43uni1F44uni1F45uni1F48uni1F49uni1F4Auni1F4Buni1F4Cuni1F4Duni1F50uni1F51uni1F52uni1F53uni1F54uni1F55uni1F56uni1F57uni1F59uni1F5Buni1F5Duni1F5Funi1F60uni1F61uni1F62uni1F63uni1F64uni1F65uni1F66uni1F67uni1F68uni1F69uni1F6Auni1F6Buni1F6Cuni1F6Duni1F6Euni1F6Funi1F70uni1F71uni1F72uni1F73uni1F74uni1F75uni1F76uni1F77uni1F78uni1F79uni1F7Auni1F7Buni1F7Cuni1F7Duni1F80uni1F81uni1F82uni1F83uni1F84uni1F85uni1F86uni1F87uni1F88uni1F89uni1F8Auni1F8Buni1F8Cuni1F8Duni1F8Euni1F8Funi1F90uni1F91uni1F92uni1F93uni1F94uni1F95uni1F96uni1F97uni1F98uni1F99uni1F9Auni1F9Buni1F9Cuni1F9Duni1F9Euni1F9Funi1FA0uni1FA1uni1FA2uni1FA3uni1FA4uni1FA5uni1FA6uni1FA7uni1FA8uni1FA9uni1FAAuni1FABuni1FACuni1FADuni1FAEuni1FAFuni1FB0uni1FB1uni1FB2uni1FB3uni1FB4uni1FB6uni1FB7uni1FB8uni1FB9uni1FBAuni1FBBuni1FBCuni1FBDuni1FBEuni1FBFuni1FC0uni1FC1uni1FC2uni1FC3uni1FC4uni1FC6uni1FC7uni1FC8uni1FC9uni1FCAuni1FCBuni1FCCuni1FCDuni1FCEuni1FCFuni1FD0uni1FD1uni1FD2uni1FD3uni1FD6uni1FD7uni1FD8uni1FD9uni1FDAuni1FDBuni1FDDuni1FDEuni1FDFuni1FE0uni1FE1uni1FE2uni1FE3uni1FE4uni1FE5uni1FE6uni1FE7uni1FE8uni1FE9uni1FEAuni1FEBuni1FECuni1FEDuni1FEEuni1FEFuni1FF2uni1FF3uni1FF4uni1FF6uni1FF7uni1FF8uni1FF9uni1FFAuni1FFBuni1FFCuni1FFDuni1FFE uni1F88.alt uni1F89.alt uni1F8A.alt uni1F8B.alt uni1F8C.alt uni1F8D.alt uni1F8E.alt uni1F8F.alt uni1F98.alt uni1F99.alt uni1F9A.alt uni1F9B.alt uni1F9C.alt uni1F9D.alt uni1F9E.alt uni1F9F.alt uni1FA8.alt uni1FA9.alt uni1FAA.alt uni1FAB.alt uni1FAC.alt uni1FAD.alt uni1FAE.alt uni1FAF.alt uni1FBC.alt uni1FCC.alt uni1FFC.altuni20B9uniE0FFuniEFFDuniF000ubuntuuniF0FF Bcyrlgrek latn,kernkern&kern6  $,4<DLTH-^|i .8Ndr*4>H^t~,6@N\jp~DJTZ|.Pfpv| "(.4:@FLDJPV\TZ`flrx~  3  )  )(   (((((()( )( >VfffN    >VfffN3 3 3  )(       (((3    )( ((( )   3 3 )(         )( )( *|)*|*|)*|*|*|*|)))*|4h?O/  P Q*^^ 44I?)?4 >II *S?I*SII>PQ)^^4*S)T 4P)^^* >>>*4^4*S)T 4P)^^**S)T 4P)^^*444444*S)T 4P)^^**S)T 4P)^^**S)T 4P)^^**S)T 4P)^^*44*S)T 4P)^^**S)T 4P)^^**S)T 4P)^^**S)T 4P)^^* Lp.g  ' ''      <-FF Z !" !      "# "u       lP!      !#"    #P PPz |    "  P ! P#"P" ???>zx@NPRTVX[fhjl)MV-/AI    6789P$9DFIIKUWW%Y[&]])dd*kk+mm,-DIPQ\]^opqrstuvw'x)688::<BDDFFHHJJLLOOSSUUWY[]ggiikk  *8::<<>>@@BGIIKKMRUUWY]a cikkmy{{#$&*278:t #9:;<=>@^`bceflnpstxz|~(NPRTVfhjl  !#$)STV\}%  ## >> @@ ^^``ccefllnnppstxx&)+CLT Z!b(-j69pBIt| $, 2:"'B.5H:Pi #>@^`ceflnpstx!#-/13579;=AGI]^blmosy{q     $%'()*,.0468:<>?@ABCEHIJKMOPQRSTUWY[_`cdfghjknpqtuwz|} "&2DFLNXZ\aervx~$ !"#$%&'()*+,- * %&&&&&&,,,,      !!""#####% % % % & & & ) ) ) ***+++,,,,,,*+&&-"% &' )++!! & ,,,,, "& & % &   & & ) ) ,,& & & &  )V? ++!@67Af89B:;<=C>DS*50$/.-,334 12%&  B8 2>@@@@@@E6888888;;;;>5555555....3&&&&@5@5@566667777//.......A-,,,,,3333a888111B2B2B2:%:%:%;&;&;&;&;&;&=>DDD$B2:% ===>$$$$$$$PLTMNQRU^OTT`O`000\6c"]K8<_-#F3888dW%':;&GH>DXe(YZ,3@5.8;&;&;&;&;&b@5@5E577-88X(73@5E58@5@5..8811;&;&I)/D@58888>3 @6[2J@>(n    ##::;;<<==>> @@ ^^ `` bbcceeffllnnppstxxzz||~~((NNPPRRTTVVffhhjjll*+,-./0123 !"#$%&' ('()45<=6>?7  @ABC8DEFG  H!!I##J$$K))SSDTTGVV\\L}}MN:;O9GLm7''!`#abc$de"fg@h%i&jUB:D (z)*+{,|}T~x--./012E5896 C T 'mU %2 .l !!!!!!#bbbbeeeeah%%%%%%BBBBk((((((()++++}}}}-......66!(!(!(#)#)#)#)a**b+b+b+b+b+$,$,$,$,d|d|e}e}e}e}e}}e}"Tf~fgxgxgxgxgxh-h-h-h-%.%.%.j1j1j1U2U2U2EEEBBBBBB86:::{U2ET8886{{{{{{{XY_VZW\^[]A> ?K>>zzzR#)43tS=;c%Ff~qGh-%%.%.vME<Bno6:NyrpwOsaa*ggxgg-!(e}%.BBBBB!(!(($,$,f~%.%.NpTaa*$,h-!((%.!(!(b+b+e}e}%.%.j1j1BBPud|*:!(b+%.%.%.%.6H-H*0!#)IQ2J!b+6p$)* .))))))+  )))*!       ...   "( #, .-$%&')     ))+-%)+))  .)))%Y      ##>>@@^^``cc ee ff ll nnppst xx  !"#$%&'()*+,-./01234!#%+04! #!%(-+690BI4!#%0-+4#44O( ( " : #@#+9"";##>><@@^^``cc'ee'ff#llnn%oo&pp%qq&st@xx??^^&47K82Q56L=3KJ3J     >P.ABNC/DE-F M!GH*)0,$1I>BCD!,I>B CD"'!.5,:AIJK>LMBNOCPQDRS!TU,VWIX_>hoCxI>CD,H,I<       "' .5:AJKLMNOPQRS TUVWX_`ghopwxP"    #"" ##&>>$@@^^``cceeffllnnoo ppqq st ^^ '!,*( +)% ',*%', *"'.5%:AJK'LM,NO*RSTU%VWX_'ho*x'*%%b      ##>>@@^^``cc ee ff ll nnppst xx " ,)&%) !"#$%&'()*)+,-))*!!##*-- // 1133#55$77&99&;;&==&AA-GG,II-]])^^#bb'll)mmooss"yy#{{$%%*** )L, , ' M !(I(-N""O##">>P@@%^^#``$cc+ee+ff(ll&nn)oo*pp)qq*stIxxS&S^^*ECgHBFGhQDgfDf      V;W[5.6JXY7Z[/\]0<123[=^  _  >  [  8`9.T[a:iR7[UYX1b  ;!!""?##$$/%%&&1((X**K,,]--..[//00[1122[33 4465566J77 88Y99 ::Y;; <<Y== >>Y??@@[AABB[CCDD[EEFFcGGHH0IIJJ<KKLL4MMNN4OO PP3QQRR<SSTT=UUVV=WWYYZZ@[[\\@]]^^ __6``aa[bbcc7ddee[ffgg[hhii=kkZllmm nnVoo ppVqqrrdsstt.uu vveww xxeyy zz6{{||J}}~~AXX///J111=[[   !"#$%%&&'&&"%&% ! $%%% %%"""M6 6 / 0 W1.17+""2##(>>L@@*^^)``Kcc5ee5ff1ll,nn3oo4pp3qq4st.xx-,-^^4JHIU V%$a%!GS"#$%&']%`:;<=>NO?@ABCDEXFY          >8  PQA9@?X   ;!!%""\###$$C%%&&X((?**,,D..002233G44N55S66O88@::@<<@>>@@@BBDDEEFFGG$HHEII%JJKKZLL[MMZNN[OO'PPYQQ%RRSS]TTUU]VVXXRYY_ZZ[[_\\^^G__NaabbccAeegghh]iikkBmm!nn:oo!pp:rrtt>uu^vvww^xxyyGzzN{{S||O}}T~~??#C#C#COXXX]""&&22DD FF LL NN XX ZZ \\ aaeerrvvxx~~Q   "" ##>>@@ ^^``cc)ee)ffllnn oo pp qq stxx&&^^ '(#    (  $$&&**$4466FFHHJJLL!NN!PPRRTT#VV#ZZ%\\%__cc(ii#ttzz||~~"# ZcyrlgreklatnBGR 0MKD JSRB d -9FS^i  '.:GT_j  (/;HU`k  )0<IVal   1=DJQWbm"AZE BCRT ^MOL zROM TRK !28>EKRXcn "*3?LYdo #+4@MZep $5AN[fq %6BO\gr &,7CP]hstcasecasecasecasecasecasecasecasecasednomdnomdnomdnomdnomdnom dnom&dnom,dnom2dnom8dnom>fracDfracPfrac\frachfractfracfracfracfracfracfracligaligaligaligaligaligaligaligalocllocllocllocl loclloclnumrnumr"numr(numr.numr4numr:numr@numrFnumrLnumrRnumrXordn^pnumdpnumjpnumppnumvpnum|pnumpnumpnumpnumpnumpnumsaltsaltsinfsinfsinfsinfsinfsinfsinfsinfsinfsinfsinfss01ss01subssubssubssubs"subs(subs.subs4subs:subs@subsFsubsLsupsRsupsXsups^supsdsupsjsupspsupsvsups|supssupssupstnumtnumtnumtnumtnumtnumtnumtnumtnumtnumtnum                                             8@HPX`hpx*,R)l))**+*+X++++++++/+/+/++b+2opqrstuopqrstuopqrstuopqrstuopqrstu+d+b2vwxyz{|}~xywvz{|}~vwxyz{|}~vwxyz{|}~vwxyz{|}~+2*2*n2*&nn*$0fH:, F 8 v .TtfX>rd  Z"<#.$ $%&&0bjrz "*2:BJRZbjrznnnnnnnnnnnpnqnrnsntnunxnynzn{n|n}n~nnnnnnnnnnnnnnnnnnnnnnnnn2:BJRZbjrznnnnnnqnsnunyn{n}nnnnnnnnnnnnn2:BJRZbjrznnnnnpnqnsntnzn{n}n~nnnnnnnnnnnn&.6>FNV^fnv~nnnnqnsnun{n}nnnnnnnnnn2:BJRZbjrznnnnnrnsntnun|n}n~nnnnnnnnnnnnn&.6nnsn}nnn "*2:BJRZbjrnnntnun~nnnnnnn&.6nnunnnn2:BJRZbjrznnnnnnqnsnunyn{n}nnnnnnnnnnnnn2:BJRZbjrznnnnnpnqnsntnzn{n}n~nnnnnnnnnnnn0bjrz "*2:BJRZbjrznnnnnnnnnnnpnqnrnsntnunxnynzn{n|n}n~nnnnnnnnnnnnnnnnnnnnnnnnn&.6>FNV^fnv~nnnnqnsnun{n}nnnnnnnnnn2:BJRZbjrznnnnnrnsntnun|n}n~nnnnnnnnnnnnn&.6nnsn}nnn "*2:BJRZbjrnnntnun~nnnnnnn&.6nnunnnn0bjrz "*2:BJRZbjrznnnnnnnnnnnpnqnrnsntnunxnynzn{n|n}n~nnnnnnnnnnnnnnnnnnnnnnnnn2:BJRZbjrznnnnnnqnsnunyn{n}nnnnnnnnnnnnn2:BJRZbjrznnnnnpnqnsntnzn{n}n~nnnnnnnnnnnn&.6>FNV^fnv~nnnnqnsnun{n}nnnnnnnnnn2:BJRZbjrznnnnnrnsntnun|n}n~nnnnnnnnnnnnn&.6nnsn}nnn "*2:BJRZbjrnnntnun~nnnnnnn&.6nnunnnn0bjrz "*2:BJRZbjrznnnnnnnnnnnpnqnrnsntnunxnynzn{n|n}n~nnnnnnnnnnnnnnnnnnnnnnnnn2:BJRZbjrznnnnnnqnsnunyn{n}nnnnnnnnnnnnn2:BJRZbjrznnnnnpnqnsntnzn{n}n~nnnnnnnnnnnn&.6>FNV^fnv~nnnnqnsnun{n}nnnnnnnnnn2:BJRZbjrznnnnnrnsntnun|n}n~nnnnnnnnnnnnn&.6nnsn}nnn "*2:BJRZbjrnnntnun~nnnnnnn&.6nnunnnn0bjrz "*2:BJRZbjrznnnnnnnnnnnpnqnrnsntnunxnynzn{n|n}n~nnnnnnnnnnnnnnnnnnnnnnnnn2:BJRZbjrznnnnnnqnsnunyn{n}nnnnnnnnnnnnn2:BJRZbjrznnnnnpnqnsntnzn{n}n~nnnnnnnnnnnn&.6>FNV^fnv~nnnnqnsnun{n}nnnnnnnnnn2:BJRZbjrznnnnnrnsntnun|n}n~nnnnnnnnnnnnn&.6nnsn}nnn "*2:BJRZbjrnnntnun~nnnnnnn&.6nnunnnn0bjrz "*2:BJRZbjrznnnnnnnnnnnpnqnrnsntnunxnynzn{n|n}n~nnnnnnnnnnnnnnnnnnnnnnnnn2:BJRZbjrznnnnnnqnsnunyn{n}nnnnnnnnnnnnn2:BJRZbjrznnnnnpnqnsntnzn{n}n~nnnnnnnnnnnn&.6>FNV^fnv~nnnnqnsnun{n}nnnnnnnnnn2:BJRZbjrznnnnnrnsntnun|n}n~nnnnnnnnnnnnn&.6nnsn}nnn "*2:BJRZbjrnnntnun~nnnnnnn&.6nnunnnn2opqrstuopqrstuopqrstuopqrstuopqrstu0LP6-N$%&'()*+,-./0123456789:;<=kmz   "$&(*,.02468:<>@BDFHJLNPRTVY[fhjlL "(ILIOILO "(ILIOILO "(ILIOILO    xywvz{|}~Lv DR ou  o ( o  pt w~ (nnvou #>?@^`lrstx|X]DEFGHIJKLMNOPQRSTUVWXYZ[\]wy  !#%')+-/13579;=?ACEGIKMOQSUWZ\gikmI`gpw0 *H 01 0 +0a +7S0Q0, +7<<<Obsolete>>>0!0 +Faܶ9eՔǠb040l.60  *H 01 0 UUS10UArizona10U Scottsdale1%0#U Starfield Technologies, Inc.1:08U 1http://certificates.starfieldtech.com/repository/1604U-Starfield Services Root Certificate Authority0 150316070000Z 200316070000Z01 0 UUS10UArizona10U Scottsdale1%0#U Starfield Technologies, Inc.1402U+Starfield Services Timestamp Authority - G10"0  *H 0 xo(QQ`L~&aF=Ӌd="?\̈bDطl=HSN̢A;C) (TXA-Nفk1 ag,4L{I hEKbK!(7pOX_Μ8Bk[4e/ ^S7AboB \ 2%|cA˫FkT240BpN0J0 U00U0U% 0 +0U O fer0U#0C̛u]/KQ0:+.0,0*+0http://ocsp.starfieldtech.com/0DU=0;09753http://crl.starfieldtech.com/repository/sfsroot.crl0PU I0G0E `Hn0604+(http://crl.starfieldtech.com/repository/0  *H 33%ýB6,phiDmBϯi e̷JݙadrmDҺ?03ݶ")fPNwqcZ>,/H t87ޞ[Vb4˖ }{Z+Rrc"r(^&a D{-RķT=ayP[,e՞^T\m?^a_+}R00Ơ0  *H 0c1 0 UUS1!0U The Go Daddy Group, Inc.110/U (Go Daddy Class 2 Certification Authority0 061116015437Z 261116015437Z01 0 UUS10UArizona10U Scottsdale10U GoDaddy.com, Inc.1301U *http://certificates.godaddy.com/repository100.U'Go Daddy Secure Certification Authority10U079692870"0  *H 0 -&L25_YZaY;pc=*3y:<0#0=Tߙ %!e)~5T29&UXמ* BΧ?Rifھ],fkQJ/Hǘuع)fm x|z%.enjDSp0Ü+X+=tJQL'Xk5ŝ1 6:%IgE96~7qt0? O20.0Ua2lE_vh0U#0İґLqa=ݨj0U003+'0%0#+0http://ocsp.godaddy.com0FU?0=0;975http://certificates.godaddy.com/repository/gdroot.crl0KU D0B0@U 0806+*http://certificates.godaddy.com/repository0U0  *H ҆gf :PrJtS7DIk3ٖV0<2!{ $F%#go]{z̟X*Ğ!ZFc/))r,)7'Oh! SY ;$IHE:6oEEADN>tvբU,ƇuLn=qQ@"(IK4Zц6d5oownP^S#c͹c:h5S000  *H 0c1 0 UUS1!0U The Go Daddy Group, Inc.110/U (Go Daddy Class 2 Certification Authority0 040629170620Z 340629170620Z0c1 0 UUS1!0U The Go Daddy Group, Inc.110/U (Go Daddy Class 2 Certification Authority0 0  *H  0ޝWI[_HgehWq^wIp=Vco?T"Tزu=Kw>x k/j+ň~ĻE'o7X&-r6N?e*n] :-؎_=\e8E``tArbbo_BQe#jxMZ@^s wyg ݠXD{ >b(_ASX~8tit00UİґLqa=ݨj0U#0İґLqa=ݨjge0c1 0 UUS1!0U The Go Daddy Group, Inc.110/U (Go Daddy Class 2 Certification Authority0 U00  *H 2K>ơw3\= ni04cr8(1zT1Xb۔EsE$Ղ#yiML3#An 剞;p~& T%ns! l l a+r9 ͗nN&s+L&qatJWuH.Qia@LĬC Օb ψ2 +E (*ZW7۽0@0(5y0  *H 01 0 UUS10UArizona10U Scottsdale10U GoDaddy.com, Inc.1301U *http://certificates.godaddy.com/repository100.U'Go Daddy Secure Certification Authority10U079692870 120807154320Z 150924134423Z0k1 0 UGB10 ULondon10 ULondon10U Dalton Maag Limited10UDalton Maag Limited0"0  *H 0 YZ=&ȯ p)ZrYyW ,=|QQ7 Ex>+3>L woZ"Cs0J1*#\>xOh+w{wܘe,̵>%]D@E*v1,g6Fpgm# O8(z;+uKXF[cK d^<3 MD4ݽP;t#wE EiP%~"9":I00U00U% 0 +0U03U,0*0(&$"http://crl.godaddy.com/gds5-16.crl0SU L0J0H `Hm0907++http://certificates.godaddy.com/repository/0+t0r0$+0http://ocsp.godaddy.com/0J+0>http://certificates.godaddy.com/repository/gd_intermediate.crt0U#0a2lE_vh0U=QCo vڧԚ)0  *H !.w*=`oi e̦d`zBe+kCz~KMO> !X0jVfjݥ!rewQY!ivԣpƠkOw?szg9ӽd\:Nz %[n@Ԝ7*暹мmew"ɳ0oH;@']&l6$FM: Ej?:,aAr%'l3YZ5F9QY10001 0 UUS10UArizona10U Scottsdale10U GoDaddy.com, Inc.1301U *http://certificates.godaddy.com/repository100.U'Go Daddy Secure Certification Authority10U079692875y0 +0 +7 100 +7(10 *H  1  +70 +7 10  +70# *H  1?{s.dpL0  *H ֏R:"hLrOT{q+; <58+,i @^aΰ=W~)hau>#^"s1PQ+RaySession-0.8.3/resources/resources.qrc000066400000000000000000000150701356671433200203710ustar00rootroot00000000000000 48x48/raysession.png 128x128/raysession.png 256x256/raysession.png scalable/raysession.svg scalable/breeze/media-playback-start.svg scalable/breeze/media-playback-stop.svg scalable/breeze/media-playback-stop-red.svg scalable/breeze/document-save.svg scalable/breeze/document-saved.svg scalable/breeze/document-unsaved.svg scalable/breeze/document-nosave.svg scalable/breeze/window-close.svg scalable/breeze/list-remove.svg scalable/breeze/document-save-as-template.svg scalable/breeze/xml-node-duplicate.svg scalable/breeze/view-list-icons.svg scalable/breeze/im-user.svg scalable/breeze/draw-star.svg scalable/breeze/star-yellow.svg scalable/breeze/empty-icon.svg scalable/breeze/configure.svg scalable/breeze/document-open.svg scalable/breeze/folder-new.svg scalable/breeze/list-add.svg scalable/breeze/media-seek-backward.svg scalable/breeze/run-install.svg scalable/breeze/system-file-manager.svg scalable/breeze/trash-empty.svg scalable/breeze/disabled/configure.svg scalable/breeze/disabled/document-open.svg scalable/breeze/disabled/folder-new.svg scalable/breeze/disabled/list-add.svg scalable/breeze/disabled/media-seek-backward.svg scalable/breeze/disabled/run-install.svg scalable/breeze/disabled/system-file-manager.svg scalable/breeze/disabled/trash-empty.svg scalable/breeze/disabled/media-playback-start.svg scalable/breeze/disabled/media-playback-stop.svg scalable/breeze/disabled/document-save.svg scalable/breeze/disabled/window-close.svg scalable/breeze/disabled/list-remove.svg scalable/breeze/disabled/document-save-as-template.svg scalable/breeze/disabled/xml-node-duplicate.svg scalable/breeze/disabled/view-list-icons.svg scalable/breeze-dark/media-playback-start.svg scalable/breeze-dark/media-playback-stop.svg scalable/breeze-dark/document-save.svg scalable/breeze-dark/document-saved.svg scalable/breeze-dark/document-unsaved.svg scalable/breeze-dark/document-nosave.svg scalable/breeze-dark/window-close.svg scalable/breeze-dark/list-remove.svg scalable/breeze-dark/document-save-as-template.svg scalable/breeze-dark/xml-node-duplicate.svg scalable/breeze-dark/view-list-icons.svg scalable/breeze-dark/im-user.svg scalable/breeze-dark/draw-star.svg scalable/breeze-dark/configure.svg scalable/breeze-dark/document-open.svg scalable/breeze-dark/folder-new.svg scalable/breeze-dark/list-add.svg scalable/breeze-dark/media-seek-backward.svg scalable/breeze-dark/run-install.svg scalable/breeze-dark/system-file-manager.svg scalable/breeze-dark/trash-empty.svg scalable/breeze-dark/disabled/configure.svg scalable/breeze-dark/disabled/document-open.svg scalable/breeze-dark/disabled/folder-new.svg scalable/breeze-dark/disabled/list-add.svg scalable/breeze-dark/disabled/media-seek-backward.svg scalable/breeze-dark/disabled/run-install.svg scalable/breeze-dark/disabled/system-file-manager.svg scalable/breeze-dark/disabled/trash-empty.svg scalable/breeze-dark/disabled/media-playback-start.svg scalable/breeze-dark/disabled/media-playback-stop.svg scalable/breeze-dark/disabled/document-save.svg scalable/breeze-dark/disabled/window-close.svg scalable/breeze-dark/disabled/list-remove.svg scalable/breeze-dark/disabled/document-save-as-template.svg scalable/breeze-dark/disabled/xml-node-duplicate.svg scalable/breeze-dark/disabled/view-list-icons.svg app_icons/dark/curve-connector.svg app_icons/dark/network-wired.svg app_icons/ADLplug.png app_icons/ardour.svg app_icons/amsynth.svg app_icons/calf.svg app_icons/carla.svg app_icons/curve-connector.svg app_icons/network-wired.svg app_icons/drumkv1.svg app_icons/fluajho.png app_icons/gx_head.png app_icons/h2-icon.svg app_icons/jack_mixer.svg app_icons/luppp.png app_icons/non-mixer.png app_icons/non-sequencer.png app_icons/non-timeline.png app_icons/OPNplug.png app_icons/padthv1.svg app_icons/patchage.svg app_icons/patroneo.png app_icons/petri-foo.png app_icons/qsampler.png app_icons/qtractor.svg app_icons/rosegarden.png app_icons/samplv1.svg app_icons/seq24.png app_icons/sequencer64.png app_icons/shuriken.png app_icons/sooperlooper.png app_icons/synthv1.svg app_icons/vmpk.svgz app_icons/zynaddsubfx.svg fonts/Ubuntu-C.ttf fonts/Ubuntu-R.ttf RaySession-0.8.3/resources/scalable/000077500000000000000000000000001356671433200174135ustar00rootroot00000000000000RaySession-0.8.3/resources/scalable/breeze-dark/000077500000000000000000000000001356671433200216065ustar00rootroot00000000000000RaySession-0.8.3/resources/scalable/breeze-dark/.directory000066400000000000000000000001671356671433200236170ustar00rootroot00000000000000[Dolphin] GroupedSorting=true SortOrder=1 SortRole=modificationtime Timestamp=2019,11,20,12,14,42 Version=4 ViewMode=1 RaySession-0.8.3/resources/scalable/breeze-dark/configure.svg000066400000000000000000000013301356671433200243050ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/disabled/000077500000000000000000000000001356671433200233555ustar00rootroot00000000000000RaySession-0.8.3/resources/scalable/breeze-dark/disabled/configure.svg000066400000000000000000000013301356671433200260540ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/disabled/document-open.svg000066400000000000000000000011331356671433200266510ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/disabled/document-save-as-template.svg000066400000000000000000000021021356671433200310550ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/disabled/document-save.svg000066400000000000000000000012331356671433200266470ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/disabled/folder-new.svg000066400000000000000000000012741356671433200261440ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/disabled/list-add.svg000066400000000000000000000007521356671433200256030ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/disabled/list-remove.svg000066400000000000000000000013071356671433200263450ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/disabled/media-playback-start.svg000066400000000000000000000013411356671433200300730ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/disabled/media-playback-stop.svg000066400000000000000000000013471356671433200277310ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/disabled/media-seek-backward.svg000066400000000000000000000004561356671433200276630ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/disabled/run-install.svg000066400000000000000000000026161356671433200263530ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/disabled/system-file-manager.svg000066400000000000000000000224411356671433200277520ustar00rootroot00000000000000 image/svg+xml RaySession-0.8.3/resources/scalable/breeze-dark/disabled/trash-empty.svg000066400000000000000000000006121356671433200263520ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/disabled/view-list-icons.svg000066400000000000000000000010441356671433200271310ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/disabled/window-close.svg000066400000000000000000000010501356671433200265040ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/disabled/xml-node-duplicate.svg000066400000000000000000000011251356671433200275700ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/document-nosave.svg000066400000000000000000000062361356671433200254450ustar00rootroot00000000000000 image/svg+xml RaySession-0.8.3/resources/scalable/breeze-dark/document-open.svg000066400000000000000000000011331356671433200251020ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/document-save-as-template.svg000066400000000000000000000020401356671433200273070ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/document-save.svg000066400000000000000000000012311356671433200250760ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/document-saved.svg000066400000000000000000000044471356671433200252560ustar00rootroot00000000000000 image/svg+xml RaySession-0.8.3/resources/scalable/breeze-dark/document-unsaved.svg000066400000000000000000000056631356671433200256220ustar00rootroot00000000000000 image/svg+xml RaySession-0.8.3/resources/scalable/breeze-dark/draw-star.svg000066400000000000000000000010501356671433200242270ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/folder-new.svg000066400000000000000000000012741356671433200243750ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/im-user.svg000066400000000000000000000012431356671433200237100ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/list-add.svg000066400000000000000000000007521356671433200240340ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/list-remove.svg000066400000000000000000000012661356671433200246020ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/media-playback-start.svg000066400000000000000000000013371356671433200263310ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/media-playback-stop.svg000066400000000000000000000013451356671433200261600ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/media-playback-stop_red.svg000066400000000000000000000013451356671433200270120ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/media-seek-backward.svg000066400000000000000000000004561356671433200261140ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/run-install.svg000066400000000000000000000026161356671433200246040ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/system-file-manager.svg000066400000000000000000000224411356671433200262030ustar00rootroot00000000000000 image/svg+xml RaySession-0.8.3/resources/scalable/breeze-dark/trash-empty.svg000066400000000000000000000006121356671433200246030ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/view-list-icons.svg000066400000000000000000000010441356671433200253620ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/window-close.svg000066400000000000000000000010161356671433200247370ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze-dark/xml-node-duplicate.svg000066400000000000000000000011231356671433200260170ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/000077500000000000000000000000001356671433200206675ustar00rootroot00000000000000RaySession-0.8.3/resources/scalable/breeze/.directory000066400000000000000000000001671356671433200227000ustar00rootroot00000000000000[Dolphin] GroupedSorting=true SortOrder=1 SortRole=modificationtime Timestamp=2019,11,20,12,32,14 Version=4 ViewMode=1 RaySession-0.8.3/resources/scalable/breeze/configure.svg000066400000000000000000000013301356671433200233660ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/disabled/000077500000000000000000000000001356671433200224365ustar00rootroot00000000000000RaySession-0.8.3/resources/scalable/breeze/disabled/configure.svg000066400000000000000000000013301356671433200251350ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/disabled/document-open.svg000066400000000000000000000011331356671433200257320ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/disabled/document-save-as-template.svg000066400000000000000000000021021356671433200301360ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/disabled/document-save.svg000066400000000000000000000012331356671433200257300ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/disabled/folder-new.svg000066400000000000000000000012741356671433200252250ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/disabled/list-add.svg000066400000000000000000000007521356671433200246640ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/disabled/list-remove.svg000066400000000000000000000013071356671433200254260ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/disabled/media-playback-start.svg000066400000000000000000000013411356671433200271540ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/disabled/media-playback-stop.svg000066400000000000000000000013471356671433200270120ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/disabled/media-seek-backward.svg000066400000000000000000000004561356671433200267440ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/disabled/run-install.svg000066400000000000000000000026161356671433200254340ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/disabled/system-file-manager.svg000066400000000000000000000224411356671433200270330ustar00rootroot00000000000000 image/svg+xml RaySession-0.8.3/resources/scalable/breeze/disabled/trash-empty.svg000066400000000000000000000006121356671433200254330ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/disabled/view-list-icons.svg000066400000000000000000000011441356671433200262130ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/disabled/window-close.svg000066400000000000000000000010501356671433200255650ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/disabled/xml-node-duplicate.svg000066400000000000000000000011251356671433200266510ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/document-nosave.svg000066400000000000000000000062361356671433200245260ustar00rootroot00000000000000 image/svg+xml RaySession-0.8.3/resources/scalable/breeze/document-open.svg000066400000000000000000000011331356671433200241630ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/document-save-as-template.svg000066400000000000000000000021001356671433200263650ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/document-save.svg000066400000000000000000000012311356671433200241570ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/document-saved.svg000066400000000000000000000044541356671433200243350ustar00rootroot00000000000000 image/svg+xml RaySession-0.8.3/resources/scalable/breeze/document-unsaved.svg000066400000000000000000000056571356671433200247060ustar00rootroot00000000000000 image/svg+xml RaySession-0.8.3/resources/scalable/breeze/draw-star.svg000066400000000000000000000010501356671433200233100ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/empty-icon.svg000066400000000000000000000030721356671433200234760ustar00rootroot00000000000000 image/svg+xml RaySession-0.8.3/resources/scalable/breeze/folder-new.svg000066400000000000000000000012741356671433200234560ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/im-user.svg000066400000000000000000000012431356671433200227710ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/list-add.svg000066400000000000000000000007521356671433200231150ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/list-remove.svg000066400000000000000000000012661356671433200236630ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/media-playback-start.svg000066400000000000000000000013371356671433200254120ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/media-playback-stop-red.svg000066400000000000000000000013451356671433200260110ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/media-playback-stop.svg000066400000000000000000000013451356671433200252410ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/media-seek-backward.svg000066400000000000000000000004561356671433200251750ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/run-install.svg000066400000000000000000000026161356671433200236650ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/star-yellow.svg000066400000000000000000000056641356671433200237050ustar00rootroot00000000000000 image/svg+xml RaySession-0.8.3/resources/scalable/breeze/system-file-manager.svg000066400000000000000000000224411356671433200252640ustar00rootroot00000000000000 image/svg+xml RaySession-0.8.3/resources/scalable/breeze/trash-empty.svg000066400000000000000000000006121356671433200236640ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/view-list-icons.svg000066400000000000000000000011451356671433200244450ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/window-close.svg000066400000000000000000000010161356671433200240200ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/breeze/xml-node-duplicate.svg000066400000000000000000000011231356671433200251000ustar00rootroot00000000000000 RaySession-0.8.3/resources/scalable/raysession.svg000066400000000000000000000562171356671433200223460ustar00rootroot00000000000000 image/svg+xmlRaySession-0.8.3/resources/scalable/raysession16.svg000066400000000000000000000470031356671433200225060ustar00rootroot00000000000000 image/svg+xmlRaySession-0.8.3/resources/scalable/raysession24.svg000066400000000000000000000523051356671433200225060ustar00rootroot00000000000000 image/svg+xmlRaySession-0.8.3/resources/scalable/raysession256.svg000066400000000000000000000561001356671433200225720ustar00rootroot00000000000000 image/svg+xmlRaySession-0.8.3/resources/scalable/raysession48.svg000066400000000000000000000560341356671433200225170ustar00rootroot00000000000000 image/svg+xmlRaySession-0.8.3/resources/scalable/raysession64.svg000066400000000000000000000561771356671433200225250ustar00rootroot00000000000000 image/svg+xmlRaySession-0.8.3/resources/scalable/raysession_little.svg000066400000000000000000000357321356671433200237220ustar00rootroot00000000000000 image/svg+xmlRaySession-0.8.3/resources/screenshots/000077500000000000000000000000001356671433200202055ustar00rootroot00000000000000RaySession-0.8.3/resources/screenshots/Screenshot_20190724_084844.png000066400000000000000000001514271356671433200246650ustar00rootroot00000000000000PNG  IHDRxQ pHYs+ IDATx^u|ݥIS lƆO0e0swgo. acp{]iFGiB*m;r{s;cC2 q l,A?     cuN@9sf@A̙3='v9u< ǔ Pd0&D*?"uZUcnnrAAN\\OGC)))RMX\\\m  7aoڪ_`ñBHJJrW Bm޼`0ti\\'8(óNCs̙… ep1$KJJ$Z pZ:@XXOn޼`;[UUMQMUUd2p8L&p؃~n5Pz$+t:-u]$IQUUeY4MVEu]4MvdEcA`Y:%I5M*{i6]mWfi6N"V!AܖCv*{~NjIDTMv4kق sMk7̀g;=mLFj:-fdE!=5UUQU̴tO%8=ܶ3Qtޕ0!7;5+<$4?_fȲL4n֔ sEذf=% \lLm r Dr.$6nDl|<;'n#i4?+ٴn9HDI4i Ť`+`X9 YK'#5\;#]{u':6I)9X̪cj|^ ik];vsvI∊n@yy[6lYMiق:ٵm'wi$wLHKg&͛Ѷc;V+eelݘBVF&5=yoް:q}uN8=^ǫGJx3{FpoJz70ydgW,ށTSo*nzXf\Bag@_V.Y]NNvaǏ$IGӰI#(RfѲmk_e=p!D6bߝҒR гooRm[3Ϡ?- SR\LNf6u!#5;v( aUK@l|Avmwϙmݮ YJEy9:%ѵW0ꥬ@tL4{v&m~ }g굤(os I,Nа0vȨH3V1,W˶сy\;6S_*+-e}w, F %7;̴ bԭ /@! ߗ%?w '|ZRYYTsW=j?I;XC؃;Yl}أ LUX+i\rpVF+]PL&.. ʶ۽S0"$PiX4n@N҅Pt3 dгtK~nG_)Ir]jY9oԆq[V똦֏aM6p߳wҳong]8C{wW>]ڧd2ܵ3aHDiI)7l ZNݺ2s$26^KyY5O躎`8*ˍWږISԥQ (//g( ;'i~Ziæuѷ` e%Zsby۳u$Gʘ3r+7kLQILfiVEQBt]4-B{U c!}Hp_ Յ%w4kުZP>=)/+g*A8,($I5M+*,!˲B rss8-..NONNgˉ~Bz[dY?p8XϲzPqz` ;qzcP$ICŔ{qA8Eי[y;2MfZ yA8B'44\ʛP7&&.p<2RMOA3&SKUǦTpC]'7;爯#  MAAMAAMAAMAAMAAMAA q+AAi BDh BDh B^{^:AA8H3gx_MAŢ) Ғ(..qA%LJJAMLLL@y<>k>Scpn|bnjiwԱl_"''LNz̈́cIbr\5qX8;c:=w%8B]wtcA8mO&Əa1b#3 'SlWu ޺u췭‰RЌJKKQUU GƂ[_}cNqqqL:l6*^zYuynF~WSǹ#ss}MLjgz-Zp(--_12ڴnS<պs72~XsoMsQQQ>0ݻ1~앴mIaTGbe\rPTTĆM)<ԳZ20s/vLHmӚҩS2!V+/cZk߮-ݻSIo8I+IѶMkv'дIcYؾc @q3ǟ /aڥ3w6hqcO^L??ΝYӦuN1{99L>cqusO??۳B7u}Y|͓$ u:-=iB3O?뮹 vzc5IIiܸ1yy,֭O}Ìd|姤l'`ӥs'h >. 7]}G3 ;xW&<b QUrtIae,HLHog3+ a<8PLnr#s{~)۶}W}̇|ʕ_FbbYYڛl޼)7Mf@~t֕k;g<쓼k̘>Se5WNN١#u`>`;6VO<ʷ.|c̅Ft 6z=v[bīYTI~L門sɧ^f2?k2Vsfdee`bXI^~>ztkޭ++IOOKNL&:wJfgKÏr֠3nzBBB1|}::oOll $u}@$^s^^35mJӦMhԨ!IOQU,Y/Lt ψ5k))-IFz\n6"""x<{JfV\tqHKKgo={݋HHsdf}v2f}'44/faX2>ʵ]sr3O_BPP&lsɧNgu]t]p}K;opbtMТy3KOb&44Vd2~7J3 Ya# [xG:bcf? i[QQADx8_b^9[g_~oAZzMƄc8n >D=C$;' jt(Oɢ xv$HxSo 2֛8eff;1~야Lfr5]׹ir47n⍷%7/_{.8AiI o>7mZTZZzA$"##Tk>7p:U>νccbPp] ^@s±W$hTu:{Urrr=N~:UPp۶s]w{>@CDTd$R6S^^#22OxWeG>3cS5roזg| ֭}y爎nc?WL{||BY ٻo`}>u쇪T[[HHj2vvOoGf,--> ΣU5֊|Lq27;@4~W~7=y/gڂ Ąt]'77h?o*ʮYi}U>.='|N@Ψʯ!77/Sd2Zhι d2( mZ\f-W^q9.[ܵ 4U~<`@3iҸf(.8\l݆i̙'II$ Lhݪg1-=b Eq]Ą=Yi0їbU9z#}.8?wq+|)_t e{?5tm-7@HH$@0]:wm6`Z9 Iٻךd"9#,cٵgm)9 Ōl۾Y!gd)e37\w 믽/8lDxg䷟g>Y>K/IѨQC, M7qFu>MրkŸ=w:8%{G^<.,,dƽ06&TUc=<4q***o.8o$3+9s<W"#"())aͺ-7ןdeekoԺ01 d{C!Ī^kqТysY ||M(.)_0EcOrǭ73/*X|:=ަu'{^ѣw:esƍ+.L~~oG>[fLʈ)ei;H?>fΜ)HRqqԪU+)77W6JiiiZPHHH0,z 'y<ꯇ‰Smlt4}<+YX%#=L~A>11!նN}!ABMӊTU-$b9###Uݮiw8=99Y9sX~J4o N/U F-\1z+F:/'ZuhA<S /´we6ycAmhI4‰S_؉Wc,''{{X,)u  K"4A!@"4A!@"4A!@"4A!@"4A!@"4A!@"4A!@"4A!@"4A!@"4A!@"4A!@$a Be$Y܂IAivsc4%IlA1)"0A:%Il9.=i DAXvCS"A1_A7<4AT!BSA$BSA$BSA$BSAt\t{cQl6=7A~fmmEvLg_)`+I#$+VT:)8XI5k" $Dlqju^& ظ)}A pladoλj9ٕ!/IO cn+2c`KÎ6$aabanyX%'04^={J&`Fw)Z1?iUlI5~l<,! w)aL]JC7*Yj5cD/߯z@{qS X, Q d2iV?嗎BQ[Xy G\Be2&ZJce+i&QRU()X 5bMZ#N8hޜ7^}7Bff:`,/_SOgLd(!E:Zƣ0jC6dK:' FOs(߰٫X$uH2yK64X8u2a.bJKKǟ):xFҷOL&׬a=+$Eͱll۾={r%I~gаa"˖`ދ rf~qA֭_OZzIdDSR8OoTʚػog-)90+u6[3?1~}d2q&Cvmܩ,?55kzdYfHLL 33]yHQmIJI1zZKpp0cі['/H)u PyK-18И$YRG8H6 4M#=i)syKUh KN US;/:vwOCQTMc[|EEE^}6#@miִ){aaa Q^^j={һWBBC0) WO#=uޭ+|]ױ[cޟ;hތ/!C?_~咋/knw &Y|Ȋ༑(nl6s9#uq4 Ijx>3y_9S{kC8% 慎iXp՝(}${ |E4[t-8 tXVFOMBQCdff!2?$y}mv^xi&_{5K-O^XǟzLYa#<|Soז8c~"#"HLL"""|W4nԐ7^}sG glܸ7zp3Oq;޽}kp0>$& ؿ?\wv;I;{^"8xi3%7/wgѴidI/eyQ DI&OuzcU /}5洢LuL⺫'1cڝH*2u,1|~v܅i/L> ZjIgxn>Xp@!}+:d0 M}p%y3Wkcƍf4owƤ(,]⦅=HXX_xmU32hۦ]%Ir 0opzufph||fZk$)5X:izfΜ)HRqqԪU+)77W6JiiiZPHHH0,z 'yT_/x"wϿ/M /n$I3IxNqI ϽP$&u~êGC[y쉧YjASK݁·jy=d8tԒwWڢ;yI-ve"CFZ&IRb94MsFFFv]vޭqqqzrr0sLEOfB-tWUyѥHs/r؉@"2 9k+y{cp<9V51OVwkN&1L23W1WUj޼=poZ cԋ_60a\Y0E` xgEpJqi,]WQ|W2ͺǴd1;Y2y杯NЬM kc siv._w,] ` BSA$BSA$BSA$BSA$BSA$BSA$BSA$BSA$BSA$BSA$BSAT//^(2jՖcPmV$YF;V_UdpCss=$$Iv%*T߲FŶ;ZjKFV&99y*]lݺSСnSUKN+IY2$ w&Lƹ!VrrrB=C- ۭ~j{/2:4j 6V*HK#X[$4w,U>$$qӦf>}XT5ګcP?^I&ؿYƪSBbB"M7'--XŜ7Ͽ/X|ZCi SI(99Pw3TZ É6!S60hסcE~3j~Q28Oٳ뷻X0߶ pdrCп__ƏX};=MI16Lo~u8=23Ajc&ttI1>6 6εP4LL 3+fh!'>4U y3tybF( 4"hۦ5#/_~`1ҤI#TU5APwhc_~3XtBnqZcA(9=*; t,-gKߺ[]hH身hHD4>{ N{<>S526;*6n2V _ATxQt]?q&6nko*HSO֧xMN@MM:vwI|\aX]]}`ٍE'%SUCU9!zCs8K*ShT߫tVj{1 r9ѧ7˖06kAi׶+ZQQ,['0zl&Y^-]t "##}ʫ3jg&IM:DS1W+.G6HUWwڴiMvZY~=;w[hޜwy6mB]PL k֬!--3=ӴISbbhܨ{Ͽ"77D̼?"/]h/w*鄇Sʊ\îYt]m4ԑ= Ktuy|@vŢK>yw?5ݻѡ};r65{;wpyO@Nhղ%eee,\}Uk|O|:d0]= DYޙ>_}1/*׬`sqqdeG3蔜L䎔_ۜg]xiۦ5w5ço>э*8z_ȫ/=Ǡ:VӃ{FfLʵoƍΛ2m}l߱v4nܘܼu:͚5MچnaSmۆz*w~F3 ;xWʮ $&$p]wҾ};cUuG+>>{kᣏ?.KGQ\RBDx8/_-WM!I&S7t6qt:1\vމaC[_/#'<45 K(z Eי^ Rrs*|yylݺgɷGټy E MU5-zzOA?vzemoWV7אqU9Xc i,:L-HLLdɿPUἑ'e Ep9O~׀r{ݷg/PQq(Kcn H?^rsaOm5X|ww.[Nz-%4nԈ M+Ts=o:Rxx7x'4/]~X|yynh IDATѝ۶Ӯm͸Z\ nCݵ v Uٱs'}853bo~`!//r Fz(((Mhٲ%Æa( ٓ2`""iӦ5sCu8%sc7ѠAݻw/ MMs}id AQQ8$GCdzM|-.U| Sm} .`$|헀[ZZ'}9FVXT= D@D7h`2tYw9 >}>>.;]C\\qnАV]_;٧n7׾ó6sοȧ*zoLl6wv <4ؘZ4oP4,^ taa%/?k&6&q "ë [p1[lc;С}uud Ӧ~O}ճ'?ɗ_vlN!;;kp˔g݄3OPд^aeIAV$MG:^}PS U@u]g亖S@ն}X, 6;}{Fω1:p =Yz ?dnI>n;r'MgڤMhjAq"" Dz^W{DFe2i&i&)MKUkg;C%omIUԔ[SS))- }5 wxFniS&_9l?^z0zwċ Ą^=~Y3Xq.*Bcʍu֍=z}te ^Itgk aJhdSZYҶ-?i޴]ʕW\dϡ:֯yE Kƌ ct58sؑwDg /. I u4BP k @HC7m{E\Rk8mmiu\϶;X*Bo_ppQ#No3ǍGIՇڵޝs> ZM||_|ocɤ?0ktdR?FkX,0c:ϽϽϿe\6}5, 6m_ׅ=j-~e˖ /5*lظ_{`Hy؎n2ٷBJj6yټ`+kALh?FG6wÌ0czW:6syE^u>kO宻ٳsl|>?{rP__>97dѻH|,k|x NzUl:6l@ŗ_9=x忯1{RQÏS6 (` HMM⧟~aGNjǎ<+7͞%:d]{3ӛmݺzwywdƬuޝ03Ym]rr$Ck6SzHm6bQl6EVVb(Zn+~Fk<R*1$Id>i|-г/<| :Dži`H6 GC`J 6\G~hޒڷ垻bi{駝cN} nՓ~^?Ȗw.mpe wN Hk&~9 Sӹ{|ng2R\\d2H=rR4ಐ4yҀ~op$0ypL!xX~`pou6Z1kuZ{ Νt\l޲;L|7|uǮ?f<$:>nбZRۦ G_{9˯K6gh>\.Z64KqpAj:=4 {PU+@#xh0^O:xHwEMV/4N?l)׆ A?NM cAR(PB0Ժc㐔w׶I[p(\aAC!Ale6 >!,iƳnexo vQ =1l](k!mQ2k{t:1)-k9- 3cQ8ᖋ+kM{9tzhlb?LZaix 95O7"Av߾ᠨX^ }=\cx@6I 7|KH4!_$891AzUp80MXZ @&)lUo]SKhKbAHfOA~7?y5C){nzMVVOyvc7Jϑo]SKKH~y юCR(nRCBעBJ}pXj~ ±kfP(P7<@8v&BG# ! ! :SZ\$/AbNϐ1) ) ) ) ) :gA% 8p8TVVb"BSAtZ4RRRP)Kd=YAS)RHII!== E::DKS?Rɐ!ÈWllڸ?ӣP`OlL uv;6[-i餤PUiV+#BS:IDthZ>^ N.?((`pkX[ƺQbM!'<=&;Cb6IOK#oGua*GAA!*+tJي˙:e 7Ϲyyv;/<~g|9:XG$>.aVX*0YBI\l` t ]*4CFKs buH?酪^%;ev{P_d[xDzZ[mkW7km8`.-qcs:e ӦψL>7_/eytdpVVVb2Q)UlRRB] X|~.*S7|U%U'7\|>>x)>jAafe"oG^3vk.Kݖ¼Ms+0˹λ ϱ}Ga]KLGf<$-I]\ll(0ʱunc Mo>bN*)_U1t] -~Uy˛pjѣGqݵHIMw%?D[f&eeewJNyY9WuY,h)yy;Um>oÏC^e( Tʏ!sw_;4%I fQqI ԥBӱbs8YV%bG_}_n[_;8PPc<̈Ͽ:H'ҿ?~J´:$7nW[Qq1C(fs*cFsKUo%ͩm^r\x>rƎhߵjʋv*$CdN'6zH. 8vϝ;#Kis`}7̝s3Æ 84/x7OlZ>^IrZR5?7P\\' /T>sJq:j6oΝu3g㧟bǐ$C"*kYAe8#VYI6$:)(7'V% ͗;ȅ[&aYj css +룩 IN]ٚ.*_(4ryEfG'==Ihx :f<?._Au}d&=i_oqMp͔)߰>f`|fT*m\uͶ9k67u\#=|"01Deeg NHB'դGdGˋۊ+ҋ;44O87裣ٴy3waȐу]`tɠRذaEk 6nHNN #=SN9wSѰ.hC˅fH%a`6䒋h4\8~] ӳO?MBB4 }S9k( j ffm<<.p^:vo:>Ɵ^Bnh&ZMΘsXtt4{˖HFF&MwC$9,L&#,xE|U+s9pםw'|ǤqWcRPPWp1 k!I/O>95rӍ~ǝ_կWqmsq( 4 O= /\бMhHjzIjj*oҘAiw'd ӑ,m:-eӦӇ|~g>#3#{: Dž.^wkqӍTTQ#G6t(۷hZ|]649q<Çi9gELL ~=wOu߿?˗T5r慝1hC)w-7֭_ϸqcnG'޽[<zteċem`Pr II9C>.`KCw+{ERq^oIyu|w/L8pTUUvAttqpBo[$Ij2qogc0$2t}PtLMM7 *JKRYٶ yyZtmu%v{ۧ1 n'Mo)ͤv{Ջy!,k 6۶ۺu,[#SNmr߻*L)O=V+O>'W8p|x_O=M]kCE$~]~?{e˸IOK װr*nq6/]ҥpޅ(AE륰ҲC^i4ܞֻ-LF# Ud4ly;.O޻oPSb4&SVV{ŗ_qM7r"..e~ |1^NbA1KBW]MIi۷6?u-qcs1C_zI!ּ<ȣW?cͺ,W=7NY׍n'N#[gJEGT^iSCNM̶džxM M'e@_k}r:M[̍xk5!rz8[C$J(p"^h7Bzuxhfe~֭_Omm-KqI [ ѣct58sؑrV)>SMTqn~$tPku:F8Z&%_krpp߼&]Ȫի~uDbZ[-?oRRRxᇚ͎lڴ$77I]>D^O^=ٿ@ګbƝycrDž}} DQQQ?^_jB]R R\شPj5~\31+ Zm`ǫ =_1p@.r!w֭cȑlEs߫ XDEE1d`=sBbb"wnx;xA.gz^6l(^p%߻ٌB00JXUj233QW8;T* I "hXf^OLLL/Y KΘQ_@^n ?&e[n3λav2c MʺwTWWwڬٶ~04۴Qpr-͒N''&H„1z臎8Y&,?tQ:].;\h>&~? N9e8{g{Ys 0?3ϲu6f0aC6ۑǵӯgٲK.f/ SR䞿j|Ŀp͔[=j$99cxwزw֬|ʅ.`g6ƞ={smTUUqݻ7 ^//&ǵ~cذ0k&css`ÄvVYɐ1>TUU@ii+VfuNeU]v7Kffw/`r00=].Z:ᭉzHm6bQl6EVVb(Zn+~Fk<R*]d IDAT1$Idlᢥŭm ?aP\5gju+ 6w2͢bm8o&^3Vq]w˫ḣj4$v;?111ח-[~x]db{os˭8NpkX,jrrDzrryqu65sa^5JVs9 z$|>Bpt:~oBBvL&???_L&4` ࡇj6+-zn{ĎDT" kMeG^>JKn=n }ٳ'MװX,S^{V;&&MIc>L$75C(|+ma'tBlgҡVMW/&YĎ2ބ}u Ÿje˖`!XXq8^!1C6aʕb54{-]UkXr%{CFzCΘQX+n] mqOQqFY vc Yh|k:dɉ nRXTLRb"))thvxܸ]XV "%1N9ܜ.4 RL- X'/XYuu6̩fJ:`NKm8ry_'stt{99d m.X$ Gt6v܅&Z"(].J(-k[(u2}Ƀ:¦$NAb 's,0CJ%$ՔujCq`zx^|>^A}}= RRD$n7ߏ㡮RT*TTVEp ŸRdȐaʫ uu66m܈WuY}ѡf%ܺ{Н؋^&򓳖-. GOB@Qf $ ׬qfu$q:f TVYC ٜʸMFF&"V^͏WPZZ&;chu:  DEhxn]XZq\3maPvfN53xP6nX/bˋڥByqB-Lğ=:Oqg@řOs$IhZ^ Bj[2y$z%Ipl4RQY%ZЂn=1Yj5H~^,Fz맳bj^rٕG-3CkrN.O`,K`H$33kBnJ PZVJv߾␟E^ĈO[̓vuÂ38^lj59O:鱝CS(p biz..7VQ6Rq5z H׋FFĥ 3K?‰`p,_z }G'W&IY(/+gMaƜ>zUHOOcР߳*kpy<^yPDcjTTFz=6l@ףT*@-LA+/g)<6vɫò|~Gy T.Z,?,"f4vQ&|bN8nEv=.F~NEk =+yv ۙOS$f0z=={d2R@pjZj5* Nj]6CV $'BS76S0mL>i\yuHOKcmfcuv;[m#--$CZ8DffMV:jp7؟oSwAˉg GMS.JNݻ7 ..BALL * Ʉh$99109ARhh4n`0)M0﾿swָdyy9y/)))iZzk׮%qڵ޽6L <ո~|hUڹ'PU]5;NGU f0bz1 j* Zt.ZOee%zF"4AٳXRvʫ,/o'} 70S^բn)WgS^VNffMRRkEa[X\[uաgz,UtM")WWWf#)) Ia0h4jWFHMM 8<  ͩo-j|ܜ1ͩf:$%v+*.&)) m s JзoPPgLfKx~hJjʘg96+[lw<^.wpu@ Eh$IvnJ||<jJ%˅^>!**=z|5<$u t%99X:,٠^x'~{ɫ²\9N J2$b% ͗;ȅ[&VYI6$vȤ'; g8tFu$Z^ nIbѩ0[%B3( Nk9KJJ$ ֬嘘`}xDGGѣGO hZ|>uuuܹ ڷo_l۶ BG=j$K>X^x{|#^Z!ͬX.4$vfZSN9$6/R^_ UV+ ++?vŰl߾]^Vm"ӌeذ[ؼy NVKBB<Ûعs';wƆ :?=H- [R8sxe|xICdd> {}8QQmݻ7o:B?a]$ƴiSzUZ UnF._p:DGGSTTĬ7!Io?^/$Vdū VX**j233x lT*YfM/W]RoV լqyYhMyvjLn:c[oʫH\=*|F7בf1+ILL䆙3Bx5&%} ffx'3d {Ɲy#Kf $fouuuoN3ynnwZC.u\ܹ~Rի'.D***x֮5k~g@k>5S8N}UUUv\#;)DKt28c6׋d"::8#PLLL5VXҸ~$''p@識1AN"&&_-tڷo_bd2`2q:-^J\.IIЦ555ŸCłh@A*Z濲~tYQ hpZhp{2\|޹rM|7(MVHLLDC"-ظOX۱;nMir̹Z IGnLS\czɄnj <$lpRVL~_Nk7ٓO>١݉?iʁ([޹G t:11zFW*ToC񫠠^c1&eej6{=W}=zh+^O}}۷[;ep@qI EF6v;[Oʽͧ}OKRRSL||ž_RRBII-Y&e%Mllhັ;vȋUy6?,[߯. WLF^բB :ʆY#&&l]^uT]-ٓkBkDhA @'y^oެj$ɮ@cA+Wfwg}yͷn *NعkW[11dggSRRm/GB$)s<J ]ɗвS\Rʋ?ǟ|kjxpݴ\<"y쟇ۚ2]nKyy9EEŭիT*2IIM!+r(?ߏjnۖ0|Lχx*A"Ը+Wqki,>[ְbJ6+0de"7'1XrW^=ݺYVl ʊaF|pFAcHLĐd-[~]Q顩P(p8$SWWb6~Ǎ˃[$a6)++k֊ٜJnNOHff) ;S[,zO_ YmvKaQ1IҡiKqwaZ)(*j,ٺ:T3ee[sZuXnHִA$Iى%55N RQ)/jFd# B{ulا  WxfcG%x$) tAJ  DH  DH  DH  DH  Dӌ 1C4A!B"4A!B"4A!B"4A!B"4A!BGwlJP6-  q1$ 8N*Vluv.Īc64/2k)W VC9c2**TɘDyE%ťeݞFg}dlFÐ$ A0ilA*tsj?L)*Tx8?>׫* T*ү1BMw;CWq1 -L%ťPp`H7I1&SUUMm] G1}tGX<uT0hZ1$ӢjԻXkjvƍEΨd1TTRTR5?5mfpL^u\0ܿWqvڛ[vK6B-̸8,J$c` )pUJIRSWkWTTsNL, j!bA8icHLZ]Cg'TQ!1t3 ;l)dג;j+̢>!>,UՓ#yVzbm`ݷ8d3El\CrhgLW{Â38 VG2JY0BN}tT;1cb1ttE>zF`Wb'?E6HJL g-l}0SNgjT*GTUWo#y;/k;C8PXā"ZC^ϵW__ǟf5Åf憹wuxp+Td5:({RX^N$c9%ts yvS&|`֐}B:@ tS︇;Wew8xa*#SL,\T~::8'z9TTTaN5YBA\L` ٳ$ 5&^/|Ő泮Ah]!ts [vF9lI9DyubU3m]0mMcFɫ# gSmRiz5l6&S2q11`2%c}TV^o:&B3Z$'@6  ^O#Z ӥc,]V!Gwv?qIîdVW(dd qp-xb2ʫ#VgO IDATL\L "L7[*;o#yn\<1Jt"}"y(^ND3sL[z:eKZru 2U-qƵ, v)> nZyUP(HK3T)OWEL$K(8}[*(*-]KR89.LuJ^//C+1&A7͜%/.CbHLm6D KTŜBByUBrG"2.p}(d= `_PDޮ=[٨(-k xQ|oyqQ< -N|^U:vKÝ.VZr9k\.K>RyuJV _~Je%f>} qG6a!91IAGbagBrIVGvIAnבYz8Z^ $Q[Wש0[ӥC3S_Q1(}~^_) `7#[^f&͘6cFVyjA8)TʋHg~Y*((*{ Ԑb4O?%~!/>l+eҡy Fͦ͛ٽ{C gw6-A' BVa B6l(2@Fz:r }=Kf3ƍᄃmŇ^cUHQ73gC -;'X dLdƌ< K k`\I,_sǟ_xEIB|ya߻6&%%7K.ᒋ'a#>.'z>`ԩ5L jYgu6|`N׋al_o ?dgp[z 鲡_VK@0@!0=H`BTr*:g[cg>#3#{: Dž.^wkqӍTTQ#G6t(۷n?Etɉ@JIb|lJ U`]ϋjlXrϋ?*wQ?% ɦRHE)[NAivONE<*z*"*bCDBz$!M2c%Iz <3SFRZՁF#qa} |@a.n*7zw6`n>FLOͽ sx8wM>Imv ^zY`q&S ը#됡y?aq) V:].owTe;vPRRU&'7}{0z(Y&jk;vbZ㏱jg :wV7e^RٸN Ʀ pI:\h7,AO@,G Bv{P<2nHe& ynpF^x߬ҷ/~Ib^{vkܼ>ϻwCNn./*3OI9q998{?;͞{q ]z };-ZD=6,@ 'Lr&PO((ìѡxx085]=k՟C}.F^xS:E웦RV^ά:_FGj\y1uQZ-d6e>{:AT07GpP#/wZb0삓Ngͷ*XJV|R]LJ!ns{nݺ1p&Oqqq͚mZ3lk7$w;eo5w\n.T  =.dN_+TM$xhzZ=xgͦgdw߸J\uyj~wW|D]q,-\_{Ln:4 {i 5~ߙӘ1z67n"Ӊ1Vhé-l$Ӹ٪f` ynsg|`B -ut.S⬠YIrpUr"\QVd|Gp` !7/[qԶlbҿoI7_eG @g ER+xzOnhԇڽ{V0rv++)Dx={8=2:E|\IdN(2B$LhG)o'g\KN'{c),$;7AմZ-1HMhtnS>+rym?x. dM70y%m> l Ĺ$Dn,uĝM~O%RJU]Ldr:Jth4j5x?E!@rvcv;]q* !H$㦟EB">S^Od9p('(+/Uݞ^#0 Sξ*{lQlC~Ϻ;oOW/lgRSPX>Kzug$- o?X ^~WL:Y%kvM-c4uGn!]#QӂX"ITJ :Փd(˃NQV5Yso |jU&w3,c /?}> ÆHkP7hOx ͂z@pPJxyȨ_ 0C&ʼn KQ1VZىK1[NJgLxI$l )BM&z%Br.<=+*ڬhӵDGG1hй;fU"cG5\y223-I1kt>YOY~ܒ>\hc6︻̓Ϣ]CZ70zd0ԩRO)wR [PohjQVRxdE` BmDGEr0zª>,lV zr[يz_qMuϽLIQWpoͼ"##djaBk,++^pPXPHdT힕$ `tYy} HZ $db2W/h4 QΛ ev@$hN @hh(QQۿفYS&nҸcqM̼ffM))y̜>c:8'snUWHQQlK711 7WazdEE5O=ڵSnykԣ )f\.]?tV zՓd@ji GϞqL9qIÇۯ/{J}-{7{\>|́)t-yڪi۱X,fk1=X-KOoi 2trVl=уPQ8[7)**BSTTDFFXV4 \=Gt3T {gz[mǸOkl,ja:X-ƨt9|WRWXJ!r w~C$3bBbupΝ\QrrrX,gMՁۮk4& 6ȲX(sAvOIϞ= D.] B(~#e܍c[gJ6PXXHyUwvXfh䞻b*AhW~P99V!44?A]UGddcFVZ0&q41+`횯o=3ls:gryPRRB^~>)ռދ?RhvIpKpiV+^Mee$NG`` %%%aZ}_㑑vpJ'W5PuMO՟mC3&&]d{paaM fhZ~gu PS6YKZz&)FSH$&ezgBj-p8$mc; ge5^Pb/iuM)nizd#6ix<dY&88;v 2Ȳ\Z ~0zɋ놦$!IM75 Οms撼kW2A2o|/$$:w&??̬,؊1M樑#/ŧlKRw6 ޽{O`@vc |=w#8x]ct :-;w$++w!Cە.]b9~F dl~#UROn>uh$<TUMgM\.Ws品,m:4oqӦ]yqKOK#<<wB$6>}h4ܙ}8aaa]qK^r1qZo|A8 $H"??ѱfu=vKjqu)KM=NLLUb6䣕MソLakhdKdէ̜1I' I:9so~BrnoֿvϘ8a<5_C&#&5C٪%'h4Lz%%%ؽ323;2{LNxOnzFAv-tܙ:p:~CףS\\>5-݆-=o>vo>|=j ;vdڴ1&>Y)K^z׮%6&=O+.W_gŇ2{Lc.b|3$$p!BSz̓v>^OpVJg%.N}6t( _Ȇ J%މ-aa]=~]w#?hdӦ;:)iB ohj$ΝڧyGrPn?7@/3 ʀ"IMiVIPx ^K : R=SOk R^QEQjD@**~Iff={ƵyhGv 8mE~dn߱>ZEpp09띛0z( ;vj,W IDATct@5V+o.))o1^tT2C3, =q!.**(H<`YRBj]v3dHc$E0tחjӘS=_ڒ؆F63‰mgƌIl$&u骶̻>>ϻwCNn./*3OI9q998{?;͞{}7  ]z };-Zlq!BS]Dz W/w]vOdSTThb :=H;Kntr H<\doA翞 G.458Z2Okd6]Z̓-ܩGb 5ٌޖ$nҚ`GeUfXvkYeŇ+YJu1))uʺu8~<6kskLx_i"i$L-llz!7? =z T1FM7),,IƻE[|O9if| 6m&>>)S&3rE|b>FpPP&Жج6bZ:c11Xf=://-[qV=k[a@~<`1<8Kki*Tj}(I.,Dj18L&EEEywe=eTmnz8z<@] ++.`ѓOɧkֺKm|A824\S l6g^uU>Z~mZګiUw:qg!B@t2kP߾};,U{G epw~Js4lt:I=Jx짓'>cR[t|^|9f|kNb6yX3MhVe,UmWʩjBee%1w>_g9[[@}OA6 -nqOnnN6ޒH4Ug0j2a ߀IEE%b6Jՙm#22qcǐ8j111PPPHvv6I۶-ly C$YDFDrwPWWuQmZպtka(XP jej5Zuِ'NPVVk׮].RRRuܱcx뭷ɡnݺQV^]4|8̺VuiuuS5jdBϏBMج6-Qr` d !66FfVV5f;1Ge6VFϞqm̖xeX,Օg >c /?}> ÆHkP7hOx ͂z@pPJxyȨ_ 0]CJ 8q"$O?1|ƍ<kƷ~?lf޽<dgg^W}* ©3VF.>>sTW5*dgX-$'覆W㡼͊6]KttKXmV-2&q4O,|/ZÕLp!#3L~ܒDPPfL瓕?-IÅ6fٸ<8,54k VVVr ֮]ĉkM43g{=IOOgt'*Çgݺul޼ Ʉ_Uג =.j7;wT5*22(RPZOX񐙙j#>>AOn+[qSo+3n))z9^M~ؼ@ddZ>Lhcmeet2vcDFEYI` Akꖦj3bĈZuմZ-:Æ Ce~ZYӸqظq#'N$44vR44.j7 PطYKt9nft3oݬ5bͷw-Ӡ-0-v{ fKV4% Tg^#(^6 f5,Y?Oyy9T3|p,XfeffMc5ѳgnU`Vs:>|ZԳc6YأC j|?>i jM><QFpwrqYYMbfs9j>Ku)ے]7MCw^ݻ9zG9|Ɛveйܹl5 IklWt8DGsƍqcfn>uЬn4j ,'DղtR,X#G3gl ˇ+W2mTuU-78iSo`׮ݼ%FBBɻv!IF>h4vLf}!I]x!s[nٴZǩ1z(/t)Nt< XBv&'E^H֭LM7Hqڵkp :NF#;v&;yg߳˕W_CEE}.l7&?e:$I"2202otlY]]ƒz\]|RSqզf>hsݔ)L|-R:,~q 3f0i$IB3g\_ȕW\w:߽n>]'g=w5Ёkh춫{l̔)S+t9rG}GR\\ٳj||9q=Հ7MA_0337^us=k&N I ` ~,xoL!Hœ&4W_u7L?y;mT4 ɀ+oy y嗈yOiNW_fС_#x;vI=r h4~YL ѣgeh׏[oEQxKuՇ]_o}Χ?[:t odLb"wbcbXn=;v~c1[szFV&^9t:q=zAzz:CﳟpN?m06lmyٵk7}Wkرc'Ӧ7ɪOYK|v-11Gx$ٳfrs)(,$%#G՟1$!6]-v Mjrr26g$Ix</^Lii)|LOOGe<O=F{[sjf06cԚ~-{㧟~&$$kK& ͊J>Z@<hؽ{/:&OnfCcİڵ[]]KdDӦZ{kaС[rwdcqi$ERV!  oN+/W_{Ɬ~׃4h/4?]'aС,z)&߷o_ϿfqsoqT֭_ϤIu,^,0a8Ə x{xZ-K^\̰C)++# N 6(uowd !!a0+?W't<:{zhNG̶mp:Mmۆjft:{=t:]J6RKU5'eddP`)`ĉL? BBBԇ#^ӦbXH<=p+:cjݺusWEaŇ+eiii1t _#1q4o/{qc6gj~nm5Zj:]6Now``` ZN 58dDK8]. m3YMZq`+lLGKA"  CZխ2o}<ȄI0a%\|e݋aC$!DXwXXd;$I⑇h4if_khS+>3/Zu|d,Xbҥ=zq5_UV_kK} ${j o=YFj'L=jÀj2b!""Y[F3kI޵K.-Ν:r}MϽ?K~sx8xI.:yFNCpٳG}W.R{4qx~};$艅kl=Οx7[3SXTD^wtKTVT.CQrH޵ܼ3M?$33=Գg''9}JJJXc\7e2f'a/Ԏ5I'\ێ;Zǘ]vsf :t?XA\~US#oW^+.Ϗ[j}ΜF['ssc. q\^6g}K vaGsx}ϸc}wgS^WηN}o-{pV}wYƚגkSoB6m+>~9|_VwڊmLؽgiii͚9 ` DqksgjLb"I[OUnۙw}K̟w7\WW^Oo0o/{3v{??V;uj1ML &PRRŒ3뮻ׯ:u"==o~ .Κ5_eN$I̟?~1c FEnn.۶mcҥ(`hK~~##=`=@\\#7>H_3]Ϣ⬬fjhVkNϖ۸D*("IDATy uU͞5-I[[p0?_xzoEzղh"iRAAdۥ={J???Ȳ Ի\.?_)bTdyoy9<|Z_7Ǵi|N9*V]tZ9^z2a8ϴsj6zHn瑓[g7Se2_v#ֻ^4ϏAS _ٳ8xcY wo߯ONAA1cI1y{݇MW֔Ωt>&UŲ,x;ڢk ϵ`]. ϱg LA8Uuʲ;ÏϬùW/ML!dDZZCN xDEݹ3;23I:zwp58zϫ_߾vӒja01p>gpPj-I[dCٯo_,~+VU]-m]CSҔeEqzu3fS4kSro21} 4+W--SG:Ơ?!CMKܼ|**BvvNQ5VKLt43ǎ5}j59yK|k}oy3|*mJbL LD@؋taHDUUϣ 2;vYZڽ v<iiiOEE͛I)-%00лaGǏQ1Odg  wvs!!)GAծ-SXsJK"6քV+n7AAA5zaƘLy<+,rs&*5vo4 NQl*. L,?]ugF}*u^g@CIfgf'7AhYAA3) $BSAI  4MAAh& L"4ADh B3Af) $BSAI  4MAekK I1p2AF/}y kArLڻrȴ$/r۱.DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD 1"6M{1i@҃Di4M_&| DDD7}\{EFDt]WWUmޕ{Ef Wkڃ%I,{.NNNٺT4D4.|-Mҍ<>>8==}Q=>>q/IDDKIDDDDDDDDDD4Mx1DCA _vq𡚦MԶmOMmZk0> ܵi&N&$w41M>gmjU{G>w=|>l'\۶1ϧoZj4mT-o%hfnѶsbuӤYKMwW4G)%yF)ewS fj^߯Ţ/_W׾vLsF\\ G1uk5E].omM*Y7Eux 𥳐0b~|C۶7>RgIsxx}ZkVjn'͈p7Missb>fuooFDرOmaC>RJDZ4MS/Ӥqͭ<>>GGGF\\.쬞W^ծj۶u])7O뺱i: ծٮ<>>[y]fSm-]׍ORW_zU#"nmvY_E۵߿:{뺺ZjקLhfC)eL+}_y].$aޮhb=*8F0͆a㨛|zð0 2X,t2}N=Wt\W/nz.Rvmv?^ʟ0 ۈRʸ^j*.̩n'òlGG14M~Sk] '0l?c6]Rzb(?f3LZnϛ'OGX,ֳni&"jZ,"mqmZkSJikjo|]D].mNOO|m6)f~^Ju8]]3"hn\t]Y.5㝱TXFsqNJ۶cnLnɓŋ`F|`4'W駟殉sX"l6]8mMDZ8"HR /tmeǴm۫hPmn0pc}wF3uGGGŋobڶm"nm){,w>}N5sxouql6-8fmD >{,h".w>>qxfYu]l|ގ،ؔRbq̈#"ތMۧnl0 b(w>e'믇iWm0 znfi|r@Vq{b(}afGΉ_FD,v7gz<cWU?zo#XN7ZwNժ9??o?~DDl6&"b}q떽l6ZrŸhNn8>}ڼ|YVMDz~#SL}.>\.~ü%Cuy~~\\\4?CDD|" a\zrr{{{w3Esq~~DD\\\4SD!\_=5"`W,'͈3"bd(<)MxDsr5qFDLFaCGϛk7pݕXNn~/+^w~m1;+phN>M4wM;7*IENDB`RaySession-0.8.3/resources/screenshots/Screenshot_20190724_085017.png000066400000000000000000001304071356671433200246510ustar00rootroot00000000000000PNG  IHDRxQ pHYs+ IDATx^w|םey#$vE&$! ZlKm)@~ZPJ)@#28gyKqw?d)IeNyq`}>5$18$}  C` AA) I) I) I voEEER Bd/Iv FhͳpLX. =)// B~В >$ Gә\AzIq thEBR-].W H0-//tpЊ'Q`ñS(++ W Bm۶ 0'exhhVTTH˗/!t C6MHMM m۶aZ|ԨQkhƴ0/_.\.Drݒ>}>.7;;;a Ԕ0f`Xm6UUUiC;3ԁjqmHŴ0Át:e'~):av dHx555ifYX,ZNN΁jq70sssVp~_ R0 c(JrA`0$ 7Sn45׫B(PfgCl6l6 %EQ ݱl= ''כ0!Sy<`0hMOOW\.ZTT T _%#,0|>b~_l((bTUMjɎ' \dY1>O m6U׫fv] ShZ} [~ `2`0(fIU՘jFBQ Гp0ى,Zx,˚(jU( g[ɆUR-L(I4`e٠(l2$EQd(i Lb ' \$IJZ@8R8(%I҂&I&˲%I  $Apřl% f`Ql6@(E5Mb0z\ ɡT%<:<+ UQh4*@0F?M&3zB}jaJd5 )`b0̚$I2(beYVUU6 ir8F#ǂ 烾e %ITUUZi>M|uf>N&<{ >0 CŽ ł 0$Yd`aVLG%iaBޱ_+|`0HdT ->o~W p\hf&EQL>OzᐝN\RR"\.tJХC CGeeec"_+e`6 1QUլIe`0Ff~$Ozw?cЌ>r$-E!r^S4ha 'MU+~+r0%+Mk3NSz{*"l6) ʊ` IA?  GFY FQ6͑{'+tjVfYYN;VeEf eYeEQ #3T5n֖\.h BB]$j$JHE6phREE z͞8I4WK$M$EQdYnYVF,ƞj`Z6,uvGAE1 id0l֏ޫ>ZJ׽dUUܰ@U>Ϟ ^_A4UUUUfdX$BIB.vxFQKKbR:uD1 )bNX//>*gdA-tᆞ~H*:;;%-n.Œ>ݽG98n7vn#]t@(8G,)/><4 TpgdPRZBIiA`6tdp' lƍ/ &.d_A;UuN;fUT}{2d4QcB[[QRZ)p8pGyb fX J`QT`Q߷֘%θ :MR_W ׏((*j(*^}@ HmM-qTݎix=}UF쩩uq<|^[YmZmVBK,KadZ  $8-U2G#f̌BӞ,˽HKCUH$IB뺺W!!AUU6m؈$XmVF*Guh42ښZ RSS C~04ME_|LvԡIJJq}Mz֕KdBQ)>@IMp1OzzzLpq!-556D#2%%=W$1TƖbK }`eZ[OPx=Z۰l@::<45" ˈb*n$/??pf}><FMmmm׎JJJx=ۼ uhokhD---8Fe, @zIusNBn^cƎe{ee 7'/^P#ٱmnN=Şjf jcPs))-rK%V̬Lvlۆ(X,fa4>p@Xrsܲ`GZ=mtvz0~Z[gn=핕AGd玝x=F#rŀnނhd \pP8xpQRZ®hinT#>>fIGϵ }bg-Zƾq&>"[ٞVVpjO>)Ӧ1~7w\㶶6̖wZZ[1L@ ݻwx;n,ƏǑbjM|iwv$nGUU^ozYYj8H0bBBaFPX4`32|t9 e# |>>wHIu`ѵ{ڮN:;C0GxF[[Zz=] l6 cK~ pw`MsS3ikm#++*}$zni[$E~7~5>|stݳ>we\HL I:0!te1$ZR䜦,SV^{:$OZZHREϚDl<dfeQ_Wwd(P_2()-a呿eY-⮭fذa= %PWW\d20ߏt#ܺPuw=ASOCܱ}G.M&6I)e)v"x>狯 * yy(`+3+P_t0L;c ]njkk t-+תBWdfM@#a J>;49ɥsq}O8$]v&́p$B]ˣFIzD -* uچ*呞{ XV"4662~x0 E1i475:܌iTnݚ4Mtt:Φtlڄ f9&j <vܩ}0L?/嚝=:N<#hnnކЍ0RF#$p`pCwx(**|lD>\WOx:8^4jkk;n,M>|QG1E x؉ < v}GS_WG{>ɓ$ OMu5~̬LJJK4P @Qn S $ '"Iݻ{Ϡt:x#΢fCaQ!E#Fi^}{0= фt;8t  &OLKs 5ձ b"-UUiin鵥NM٤r?n;LAa))gUUeVf\2rhL&#>U >'oqEԯ8NX:|j$I2j2fUUm!E4Yk^aw,V+'mEa玝qWPOt;BeFlvH ) -#GnPP EN* I㸇&Nj]Ax_ BDh BDh BDh BDh BDh BDh B^ BRDKSA$BSA$BSA޳ѿAYeeTQQw5EKSAkh\.ANjeee\^{X{ MAAB Ͳ2^o+ CMvvv-@'C}Q5ksfǬ]V_,K4lwrG&fY̛;W_,DΙ͜9sInҥdx.]Eٛٳg1{,}qHbΙ99ze60>fvvс(RJJ,[-'|Nr.';`@_}uz_WQTy\S[Nj/5ɣI&N`} ͓Ʌ^l~|H3ͷފ{L&.\ȘL&3n|[ee%`P_R\\̬3Ae[Xa;vЏzL̘1SN륮7| MQxX20{oǝ\fϚEAaf77mfQ,//EUht6꫄ܐ d/Jԝ,?t{oj̙=ttxh^%=Vw! RTTęa QF1g,8NRSSP]vOx.>+,, /`ㆍXCVVրSg̠Ji$IG:H`{Kz$}9m~ [n,3pyʹOZ`„ ̞5 J d lذ\-: 44To5Wngae?_*[̙b1[h8o㉬PrTnU"eYf 3fL'=-v> ,\1QļyXr%+]Ȼ-c钳QT{9ah:C򣕜,dz]Y6 .2˖Ϯ](=/~||)jjj)**<|>F,F6P#طov9KĎ;P%RW\\ /駝ysSNE\<EEE?~ <ì6]x˗Ȏ;IOO/ϦM?~<?$%%/٣8%%,v[_޽{dP6u*~|~?O?;g,ryqa8.ZZZؿ?r}Tqq1\eg?4?u>Fzzu|,Yr6=/N'6  صk76lh4gsMMM<ēȲ%K8yHKs _̚5˗Gehln=HӦ˯9sX|Y<+>|86nb{W7ZZsݑ{NzWNzQAٲe+mm6oҒ#\Uv(..Q}him``p8=; ^eر\{5,??XV6۷Ԅi߰\RSSimm%??р9jvݘ&1ʹŇfѴ}4M֯,2N-[*n:2}mZ͆iqi=lM7zQիאAVV&zTU={0f̘"j:;;>jo~;72^O5MUikmeʔSp8xHOEKk cnj!/?`0Hc>JKK`x}`0USZbǟ ݳȼ4M6v4vFeV@R9aii}>Q{\yܵ+855LHhb6sOG6(),' R2z4`KKKKdh֒Ι/}^|TJF0nܸȸ@ʎ;d橧p>P෶^d̙̚5۶j,#55C=ZPIUV\JJJL@$l>v=G B-TBn[[[.+V`̙\zxgz|i~e]J}}=˗fٲ8ɜt)~+VRWWPt=B蹗@v(A6D%2b+G@w9ݞ{N|@ Ώր`%ѓ?w~O^^|KR[[KSSov/fd͚sn IDATiGLYzZZQ㡩cSWZZ!@l6cZ# USyɧ?UGV1y$egZdGfZ,p/"i&6oĉ _قbBZZzޏ8y<8aB5y*jdsr/Jږ-[pw9e){Eyy,`2aƌڇi8N cnj@TK$ژ1c]1ɓ8tRYI&1|p$Ih4@NNNN& xc4/޽{q8L8IIOOgڴiTn=rph*fLԩSbX3f g.ZĚkbaO2ݎ$ɜ~i466BKK ff$IBrss1͘f_k#]縠шi:tI0b֭444pia41͜vI1WۿI^[TTȢ3bu嵮Yb%:4`}qArrr`4 ٹk'''KbOAQ&Χ套_a YtB<lr6'}k3kLΙ,K4ܹ3fܝ;w.nOoiikp!\-$I? 7̚y*TU˯BiO?LE{B7?Mu;"^{u-:뮽&Zq^Lcc#o3fLgҥz*W5s&ӧO`4`ʕux1'+׏|}H]C Rr:JJJ$)fCGG)%%̀UeiM2T׼_$͚ӗXhջ[e"W ]Sv;]{~ s/Fy!Ъj(Ix߯UUUrrrr "3^;)Z7@~ 0xdƌ|zf̘Nm]4aС'BrhK9N>/o~oG  L#?'ēOꋄArx饗łp;A$BSA$BSA$BSA$BSA$BSAt~͛ANt#W蛓?4E g<|vhԋ@AHN2'qLDw GX$HOLa`"0 gaO>DC MO) EO}Ag c$a0$)W?+ a9M4 EQI zhJd<>% ]9[c,=c8r4  Kvf0 nKkC`AAH$gwipB3A>_oUAi1 h' g t,}}*N6ǂ  3ͱvfPBsW=%}(pRH33q&8ሃKӺ[1࡙h- >sJ?b#+t<4~go_/HԔ[44W gVqOB~@ ,[L Eff2bB7 .5HߏWQb#I P0E} l޲eu,{} ڄf 9f+~| Y4JKO]~#"=@0%u6@BcGyk n8Qpe9KS?Rq" baʔShhhV_-J")HteXР(4P^pyCQFc` Ibރ<4e֤\w5<׿ElŔE'Lw}}!n\a3 zϭlӉd21y$$$jCq.|,VdlHD$ҔT -6j}^Xql6ҌqsYmt>o cɷ,5-\d"]{M/p~k:qwoʅ+++ /^I%_ׯg]^_C} qaeFvc)5U`BjVi4@mICD]Z^dz} -4;ٿWzώAh7qbXĵL{VvNiٺN,.FehjjB!1b233Q Nf&Oj8NCL&#G$55z=zR\<MU%2mff&fZ[[A4=z;vdȑȒCb!''<$YPR4$YÁžP#aÆDm]---F&OƸ}tSNL>_U`ԓ6Yt-m2oL<MUٱc,\Y4S&Of5ٻ3-`05k)--NNcc,3w.v@0h)**di@(L{ާu&h`=Y4&ȑ#YL֭m۷S^^ƌQI%O}FMM ~13EQ1dJǔiӦMc&0e)_gcS./+G͡apuGD" SD>G]UQ6;eQl>uUNdhKC[}s*t6/{,~;a4/21Reet:%%HNS6͆YUUSJJ),vMe?PJb;0=AX}:рͮV~?qQ)$kX|'j0==]JNNZUU9-''G+//***S_$4Rǂ  Eg|p&bT#M_$ iD.$]6A ]iw5)BRADMUmPZUFHĴ)0yL*Ȱa466uvV~ 6kނ ש*HR|X€f*m cRYB 9gbn׏& fC3F2sRY ݮ3?<wmՂ 0(54$S; 0~|ːiq^y٥joc] I$"Sx i7x]eV֭5x~-[jsӍ7 !DRp٥qΚ;~ysg͎O1MW.1=ZN̤#0;}~nUkD)d՚5qH٤2N7_8,ߜٳQq}T}#̞7PgZy|O|; twƎ-eԨqk/q_WCquw {ǞҠ& ÂO;2>0st~[cs՚5s߃gpj:vz| ʘ>ŒiSC#%Kf0Ȇ_R<-#tsg[nǚf햛Cݵ j^˜~pO_{`qӁe}.BJF+Ofs^W~ًٿ?p=TRp9^T~a͝ _ߧ͝&O*#eY,&nϹ߲fmss[~̝wK23 <'Ï2+xM0} ̊__sfzǑ>}jCipGd1K,h2՟p>{qWq71o͝ |ks\M[Z쒋c[~ŗ={oEe/ip7˾Ο+-m,>~q;}rV^o^v:~u:R7~o[şW~:V|e}q%3gPS[ˡ 7Nů~γ v9{i.ȲMµW_w\r-f3|q66mĐwH 6-45M poC/$11fXf-?c͞)7{V(xkMחa{(JO=s;2ޠ` SO#QX@]]='cάH]c߫~RX2rrT^ߞ;~OG{~\)8=.+z Ho. 矆bMܥKxw."<2~uu Two-tՇ޾}h7lMix::ظqvٶ};yi̙u*5|b%kn>pedgu&>T*}Ə˰vʫlڴ?A QAң78ݳ]J7661P_9_=uĚUk?gxۺ^=@ݚ  2<O?ۿ.ٰ-̛3;n}gx۴e+l%??p=wK~|ш .eRhll-ԢvvvwŲr^dz=otd`0a9xx"<|IHKK*׺ei8xv`[q׽:}Ϟ>c"ywC1ޞ#90r"8p'z?>{dIM ]&ڟ-7N~|9,:caò~RRRHKs'O1nXu lܴ94pb܌=%{J iZ]d7~/|eg ] ՗M~p#?ɭ<Ј1iF._y4H}KK Q H=RK/G^MƱ;9ݳQo{ÕEX~Cp8GM\7dUkrpjڸ sg{xUkײjZ~}߃|Ӎ]ӧO~ڵ1}Z\PN7Lf3sرsWGeddk|~?f3.'33Ioo~kk))MZZP[_l-#8\VW@\ob6l\}7XrΘҒдCx9+WxD]ex띸fu50def'Ѽ- )))lٶUeڔ)~ѣF1{L43̏Lֻr0q$f3Ə[pCÀ4c6%#7ek%'Myǯ9cnPpUq;)RyV֯ߘԲYe0M7r z>߀/Oݿfga5q~S&tٰq3w6it?jۓr_wϒgŌ%33UUy}hJih~]q;fN?O>ȴ{OX<?LϏa0z^Vr~?_θqcjrd=>SX0gxgc#6oOYCp\x޻inn7~'2~sSww[r3`jOM6t#y|>V8ruqx9q>Ҵn%u 1***JtJ.K*))Nl6 S 0VY횦94MˬڷM/;nÿ(Y.o1u`0u8 ٧=̆UB]{Ux?a4C;J%8rҒ)$kX|'j0==]JNNZUU9-''G+//***Xfjҫ嗷ů=v;V_u\&}} G̝36zY\ً]ߏcK#Ao jhŖ[ď&OeV~G9ZygdƇ+bKcB<n}=ʌiSY`>LDN0F6oʊ+C7!+ IU"A8.mqjhi[_:Ac`|$BA  c>) IG%AsE00o/4Aa bBspW[AJ0  '  E'A 0HkMAa 6>45-nAX Acl02MAaJQE jĀ`  o p) Q}ǢS 0$F $ 6ɞAA}Ifͯ_'}9 뫏yӧ,?/y~S&Oҍ7y@m[_ұvΒyO'%<(1B_, NМ5sQyogx~~AƼsEE\ 7rߤA?ʠmO>Mվ}8_XD_4 ?zW PuB;~+/*Wz^߫8 3ol}qĈB>Dcc~__}Liǟvwc}M_GUU} !zW'CN׉0 FQ_0pif>XfiXF IDATϏgUL~9er9>_Qs8l"7^ q66~o 7']Oo]o`41m??X89Æ3O᷿[~#Ao|ZƏ{Fʫ_@4CKF=+>~xo~KվnqĈ"up!~ٶm;зg?g7E\yťɋ}o;E~^=F~gC0_dOB+/ݻyws!\h4r7E qsnyOTp=wiV׿m?eƴi|+4 ͗rI$G۬V2/Z|)I[䬳aXhjnOlܴy'4|+p`sVTX=Fdd  9gٜ:c.7gy[} ߽,Y unt/{zQn c ӛٸq3ӧMeyw|/w."rshll֟ںz.bt v\n7_tKgf! >O AgÚ: (Fպgmmm Vk[qϺWj{m]'* DAd FA{u2y֙>sN|^yAͅ= FʂEK_=۷iS+7{8sAL+3gБTcE_Fq]Z适B>|GDPG0m,9F6Nѩk ~ڵaDEEcccÔI;`AW&Kҳgعg|uΎ% R;C"<<zӵsG%*plױ &&m[3_t ߲h2h=z'8$ˣG̙zҦCgjzuN똑t҉R*tډ~?PvȘQgq3mݒʕ*ҫL6l܌|\.U7oRJ%.\ 4޽w/o uʕ*2exNXڵiEre mZJmXtA*V^:tڃgϞQحqqO?6aMvm YtL,6m+XYeԭS 2g>fLLXC9J]9|(۶`aBˬTVٱpl_ =wW|13\"2k|\]]v\҉62c\^y>]:w`8]nn}굿 66oL޽hݾŒ;SvuJJJ {aaaa܌Er( EI̙&{Bq/WVΣG;~54j9:rf|'Wpa7J,OH !!1+US3k<>|DJJ ?ƣDQ>|^[IKKqd$7nqq= GQ8En_g]p1|@AN[vyىظ8= 7.JdloRRlf͌{zݻ(lc;eJhPu7KZZlCRqʜ; h4TTq^ +W F-fݺ @$$&R-#4lM[OHj"su!AgOD( 3gϞq1:D]G?xEQw>~{>xF5(]))#OGQz~9c>(su ʞ&@RR+VaԯWahoXl\s/8::PȇlDȃ5vv$&>#1r"A###3B0HֿOKK#?JR'OӴiU2PLoQ~ef,7+W2wk~$*ȗ_T~ݺdl7&Vb82;;;Nyʖ)͗?\|zʻcڌG$pi44ckUY={H?Ӧe ڶj>,[J_6|ʔ.M섢@<ظ8{h{9f̞Kof!$&$0R\ d̴i/k깛:fY|@Irt>x`Uޞn\333Z6oFO?A1LJW}>/*::ro~K@zz:]*V`CӲm>ÍA88?kk+YRaW9 &&gLø~e#ˮ۫dzxrV^Kn]9|^;urt$6.tc\cTe18;;d3tqvpJ+P6j prtV<$"ŗs٭cb4.\ 4zɽ{0y v3-rZ cfnnδ&0}pԈe{W]]b:;wk Y&}EU 3cخ+}qp7kJ_2thbccpaV-_l\%;gZ+sKr)\؍*+Qsss*/Oev/<f-zu JqOS-ZF =h4Qx1,--ʝ;wՙyba"E> S>*Zƴɬ8yW<CVS+͚>o$똓F{jRSS sG%..{ r}ZhN<W-77_S\YT*y([Q iݲ9vh47kJrJ dΝHڵjܻwNǟgѪOM]Щ}[\]2lll(QQ2N!8ci}+K z:֞srt䛯V'G{9,,( 7nVWyݹ}U뎙1g''5@VS =pXϕ>rF ([4*gˆERѨNj_UAǎefgFMF-Ay777f=|Ȣ_xc~Gظvj35̜=^\ll,Cgٝt!w0{ɌG=غq- s7KA:6̚3ߠ]S1d(N=e=&h~ˎ.)ZZ-3}nБXǑOۨ6ܺ}[on2?ϲx _ȂK<..$p,VYڌ% VcCl/\`lܼU_?_W23eZ93ammM\S6lζ66:;hZ^u?* ,YM :*ktx2Jīkgٰi>ԫSޣѥOH`&ݎxݹ}U뎙>([4=u%%99VNΕ;~g'GF=##2|CY| O>eݯ;hيU݃+jYv2oooյkTxUbT' !! B!+ &lll2[hx6j !w4B$h !&)оmkmZ[6PPAl..kvuj䷉㌓9k*]$ڴ4^'O6zj׬sJotsz|sZ>' vOh۱3Of6ٻ\?ʕkӑ !Li6o#?8]&_T8R k++9=9-8'O^ nOb%d!g@͕=͞ݻRT\NڱpR;GzjNNGTNJJ*nc;W;CPJ;Kٻ ͠[@Fcoذf%'ҧgwJ,Aء-aa)Ny$$&fze^E̘>u̝=] <U*St)"Dxr} ѷwO)\Å +W>=F'_.ꗟYvNt'GG6Y_~#x3ԫSۓ*/7ndsnYISٷRұ=‚[@4W6[`kcKrr2[ζ>4w \ٽ+E%)) ]{ظi?176lB?ammnl_fÇLf)YB#ʠ9˕c.dP_REzvbQܺLʕ=ɉ0hTIޣGrv0M~jf 4xatZ9lgf-inP*L+'Ni0G`zz:b;Plr9ME <;wY ~5xLtL,6m+XYeԭSٽ+ev,,VPYS(B%BےUL+3gБTcE_UciԱ0Y..QRvJb/iӲs/ѺeoWȇԫ[N]{3 ".)!УwCB(`kK^˃ߴM4! te$={ƎpqqLR_ح[\;~ uEQp10csΚ3BBq/WVΣG;~52fS!e<'dȐur9MS( G 8Wgϸx NGBB"׮EAWW cZZ5kPt)RRR_#;aa)W _|*JcNN#G8DEUJ|jMhZRSS9uO> gݾ}UO<ښyb7/q^}oqvv"6.N?wif?ƣDQ>|ШA56BJJ Oolg˶B+{Yqvr"8ذG'GB3M1YeKisM+f)PYၗz]i41mgNg3;ʔ.M섢@<ۯ9l _+W14>͛ѻgw֬9GGm,,24}sZVsnW !}L-h۪l*Z-LX8o6W\eE/NN<4.c EFf}B/˵{mElsۈ!==m;dFHKK7Eh6t:,o#l;s:cƅs p!0sss6 珢(1T_Nӱcn|vA=NwMgΞs.UK_mqn 2iZӬ/sn׵q}&Lcnju\x/]k٭+#C b4Kf}B/˵óqORDq c}~x6O%PTT\?F^0nݢWw/PTGӧ|T(͋NT\ FJ۠jyۙ俬9K^:CN>VVV\vEx㿷{9,,( 7nVh4Yfŋ6|xub\S-ZF =h4Qx1}dfxٯun߾ gg'pllñ@^,jThpi\]\hPZM4kF\w㋽\ܰq3C~g[E5|(vvxc'p?4Ըj:^=q*fj#9{.G _~Qmsaf͙Ϝy ؿ%3:{rW.[}>8 MΜ& ^;RTI"# a%V0 DGE!{3}֫SޣѥOH`$&f,x>+QBMFp/W|W%'rePJ2"AS("Wg rvvb ^Lcf-Ex#x|%8YǏ)Q#EHooF1N"S#4$) B!$h !&)BHBa" ɣ"7S;MfwA<Δ#-BdEx2CS `՚uYHd\m%xC4y݅|N`vёM}[bkk{rrmw`aBXWgʔ.E]ڴl z:~׻wԷ8Dt{tI Wvٳgvs#..BCO NЭ[TT)&pibcciۺ+UWc2y<;uC/6L>Sƚ7k ~\IǚOٰqfff|qRTInܼI*p1LND!E!U+tqB{>nsFYj5zsb 6lkрu74>dvC_-#+Aj֠tR$'rWzzSHNN؉S|Wgpuu1s71qhi,[?… 5_5$o޼>i|BF=N:͠[( \ LBb"n (Ѡ>ko$::6VR~mc 8uko(pE?{ʻcfRŊ=|ĩ?PPRgΝiϞ%R xg`~L> !wQNQCPP_\ ~oP՜9{E%сzjlﵾn͘5~CHHHd%\rUb4mMƟZDO?AcfFɱqq$$$`oυ?ZжuKt*D>14rxn82;ɼM`zNI||e˔/>3gt }{`T(δMfϒdxV$h7c…kء-K2芕kJ9re4} vp67(sIYӹrW] &&rbAyc,9Nǎٵ:y?H_2d(bcc¬^Ġ666DEEp}OcMV-X*c?gL\.J@Ͻ?w*V?gф>ё5kq$ߐ'_j _yrYDղxrhMYdy7agWD޻g>ΠLTTweŌ9өC;\]\}TEſʻ"p3jZz;{>*WĈ>V^Gv0wX[[ 6rA,^ڌu t L;ϗϸv0z0֬XJӧعD2fƴ߰vp0(g!@r_۰ɠnvVYKC6 _V-8x033j^ 9|'OGz,YB3U*WBR}/ުk׮"##UbŊ"##y1KLLTt:s+++sVT֊W.8((:EO|VSNy8Y׮b3^;,^j8qq^ цmB(Q@NKOOR,,,R={ti驩NNN%x{{gSdxV G ;HoHDn&x7䞦\6퍓i7#"7S#4BIoD.En&xW$h !&A[$h7Ka6'''⍓x+$h7r_w/Kbg JHHTrI}4śQ+Pq:JLx$h7R0ӘfYB\ !&)BHBa" B!$h !&)B($ /qBM!q^7H~j^ȵA3_lNE?,U~BBVUbk4z/YkB˳~4B\FBa" B!) !AS!H| *WB!ɵ|v…݌h7DEE' !O?gdx]sM~lJۏWBó>g%NGzz>@_|677W?oE,?CضeʕKVZ|]۶ѝߧN1(۫g> M!Dͳg1c欗9c,Ξ=Gժ2UN䓪tؙ8} J g ꇅ=_}Cճ;l߱իJQÆڵ۠B r4S㗩+/_Itt4iiiYZMO?ͭJdEhZcbذiA;wrAAALBdEoQ/dwnڵm @ÆA~}jBCO\I\\QQQ\k++RSSw>!hkPlƏMdn!w/14JU^n 66'BIO3 U~l%n/,, oﱄܹCRXjSdI eu,,,puu%88Dvv0E|W9sfNg歴jՂ0 c=BQz4O;n5kЧt:fϙˤp!>F>?e !)$hf8ݻAAA}]\\ݫ'^{:to~}zcoo/޿b\L!D$hE|߸1(=*ʸKtXYY5:Tʗwg ݇[S6Φw~jPՌ՛sey3,YϏ%J0illlӸB#4ߒ*0jxu#((?=|EQt:BCC)^>ؕ(^{lЀC#G]u1e˖J-Z`ccÈC:|gII~nݾUi[ Ba o= dܗ888cccjU4 :{ұC{]F]۶ @pZjQQQR*>#cv!8p /o37iSٵV wr\vyRNmnܸiPG!D$h%'N$0>۷1_S{}S6~۔juqssg6t:zZvMBYl ssٸi3GEVIժ\v4 MKK#!!x?q"G&?p /XhPG!D$h%ZF'EūqVeӵ: `",t:M57N೿iB!L#B!L$AS!0M!D4BIB!L$AS!0M!D4BIB!L$AS!0M!D4BIB!L$AS!0HUq^<58x V80Hh@^={,PTeBdMzoQ/dwnڵm @ÆA~}jBCO\I\\QQQ\k++RSSw>!hkPlƏMdn!w/1={` (JB!#=,TqRΟ`9,, oﱄܹCRXjSdI eu,,,puu%88Dvv0E|W9sfNg歴jՂ0 c=BQz4O;n5kЧ;;9䈓6#o޼;~Y琚/'"k4` Mu] S_v ...=ttG'')^mu %3NNYpAY!/yzz}X[[( vvԯ_NʊxtR;'MW>ݺEڵ7w6#-};jqz3{,ukHIIat:Z-[nyf44ߒ*0jxu#((?=W7tRx1}+QG٠GPv-֭c4˖-C*)ZCƆŇRϙ;o>˗O")24+{{{ ɸ/qppRXq4w޳ckkڶe^_CPOR*ܸyÆPOy3F_Km7=wg$**'N2t ,,,prryf>rDB!'=ͷɓ6g6"? ?VY ݻ)T +-AcnNrr27mȑj>Zk׮FTTAiii$$$}e!!)){i:B!&A-j58Y/:&.^݌Z-'Ldt,X(:͚'uei|+S!D^'B!D$h !&)BHBa" B!$h !&)BHBa" B!$h !&)BHBa" B!$h !&)Pj4N˓ǜ1Fr@0p@ɪ6iطw7[6oQÆjg|yw`+Wr9f͜[iժ[a@z>xѣhPB 1vj֨Owvr'g'6mXG޼y9vf!55U_N!D$h19q] S_v ...=ttG'')^mu %3NNYpAY!/yzz}X[[( vvԯ_NʊxtR;'MW>ݺEڵ7w6#-};jqz3{,ukHIIat:Z-[nyf44ߒ*0jxu#((?=W7tRx1}+QG٠GPv-֭c4˖-C*)ZCƆŇRϙ;o>˗O")24+{{{ ɸ/qppRXq4w޳ckkڶe^_CPOR*ܸyÆPOy3F_Km7=wg$**'N2t ,,,prryf>rDB!'=ͷɓ6g6"? ?VY ݻ)T +-AcnNrr27mȑj>Zk׮FTTAiii$$$}e!!)){i:B!&A-j58Y/:&.^݌Z-'Ldt,X(:͚'uei|+S!D^'B!D$h !&)BHBa" B!$h !&)BHBa" B!$h !&)BHBa" B!$hΜ>IѢEBg8AZb8~0 \ٲ,Q8S~= ԬQϷT*-׭Caɓ'SͬmӚsgN'677gȠc|t"ʖ)cTK!YAs=/d=ٴy g*imP4j56hT*M^OȄ[?hEҥhۦA9cJQÆ~{*T@h7 3Bl s{}ʕ+Q޽&9}V}h4/8> @0Wov`>.^fff,Z8''GV._.8;;UwҹqO XɵkIOO'..KPdItR~>`ݭ'>>{B.zUqRΟ`y%l۲5}D޽Yx1T)Ww޻G^=4q;vJ4HLL> 66/PbE6o^}`LYӦL@>X|~BZZ}GEiO$''XZZ9*:UpYj% GBBjC|U#|jgIhZ&NG;~ztԅǏ{nI/133R4hP_~O9"qqOD IDATǡGt> g' ==y._LΝ:mA!A8Dtt4k׭gi|7ٳ;wX~#ڶɉǑgw ܤZ5k#pq"#g@`%6mޢS^]&6O3˒iڤ>?11˗q+TEQؾc7nN:tꥯى'O:_5ʔ} t:˖ŋDDD0|H}Z5kPdIzuק=-[׷6ի^ 4.&>h֮[Omt:Nԩ]F%%%acktENܩ#va >}{i˗O{ }1|4ִ4ʫW^} |>C={pu7`ذ/0q f guV|0=}lܴ 7nT9xg0+/kƇ [~ڬ~bT_yjS`Ԩxx?> 8ct UU0r||ګ=S p Dmm-***>v@]]{ |0=|G0 }I"f%aJJJĪUDyyp:H+VUQt]OLzV銢dI)sזDa6-zG_c15BQ|lXdhT?xmX{,XNN.O=l]+5Ms !jm6ryu]iGzYYɑv]K()) =g%9lcƎj',]j""&bhz 9닉wNɹjW϶_  \KDD&I M"""DDD&14Lbh$""2IDDdC$&I M"""DDD&14Lbh$""2IDDdC$&I M"""DDD&14Lbh$""2IDDdC$&I M"""DDD&14Lbh$""2IDDdC$&I M"""DDD&14Lbh$""2ņzyc1b'Q=rcibCuUHc)>bgZ&I M"""DDD&14Lbh$""2IDDdCȤ{G #sԽP 1Ĥm_ ׌;!4(a-:43G݋sZE1?(V{Qx!DDD 1DRxH܁ ʅP*""&``f1PYCiRIu),ě3^Ơài]٬ݫ'vUeW^cldݎgz\ mާZB|CVFi,jګ;oh6//?cqu)'#33XlJCQaЀ(((kXxw=x[d(m#JZ4޽w:N=?xcq( Jd%?riNݺ?X '9Ż> ~c,nZBM5¯Ű3b%-_~Ż3g>37x .{, 9h,[<7Et^}% Eŷ/3N #``~=jrPWWfYo[lEb,>55yłn {Dzz:6nڄmێ.xⱇ1㍷0f(lV,^T\.n@XTlܴi= ^<?#=on= At0VWyp 2YpVWg~5W]O:G9 M{ 6lh}/jj\xqUbM,~-\uݍظq.d, UUՏCҋ`郡*;oxɧ1h@\0<흱}NLgnƊGiG/Upkb10m6Tރy+~%:uĸG^p=xY5C ])Z~rωBB<ֻ3q!++9#juh; ̜5? W_y v,/76k˯Go|~p,, fvMB[nڲ2tCFfFwq_ƃ݃-×_-cO< ׇ&ގ#yӡaԘKn)w.O>sHHwR?( / BL.9OM}@}IeUy_p7^w-FS{=z?ҸX̾-ZAŗ^X~=ᒱg#1+}`̅1ŗB^HOOx>G5W]'Nš?>Gޒpø۰ peO> ʈk %_ ˅nwEeeUXQk(NZs(('ǦMl}}Uc¯1aY׮/s8sq?Zv;={AA.ŨłCL+,,@Ř6ex<\.L2N=$dee4 ۶oǿq pTVb۶-Ao~Z55ރԒ?@u,'ץKz{&^/x㭷q)'lيGp<cxx^x<,Zx-_W /Y.] -"kżc@ 0d@L8QWW>:c)f_o޼r2w?neևɌ7ނƷ-D-Ui; b;oPSk =WUb}nGM+l*vǎB //555r)%<8{IJ<=(8Zy}XoU~NpHvءmQYwy.t˃Wr:10ͷɧs Y fckWE<UU18ؿbQ}*V_ꋥ?Kzc۶8M4( ^o5c)۱vDcNtw>ߴysHy?-[q[0z\x|>j^w:`[K?S̾~ɧwGMu5>"~]2ω^'QK.?6 Mގ{c8iԐ+j332`ق!ïݽ{2 .R$N'^yu\ssxtxڀَwx<vﴰ6;cm.]pʰb˖ؾ}@E?\rI}]p88[7Ҵ`} WBuߗп/mێEt_Ų+B^Ŝ{ Ka跱VhÆѩSGL< FW^{,{ac#^u]s>Ƈ}A㑇?=?l>':0D-QT߰|׮c_`ؙC6mQ{GDU t,i֭XW\z1l6+232p%c0˯, { EQxvݺ@cXf <l6=GoCf&.5 _XsHQrG,N顋Fؾy^jkqG;v-[p8 CWVUE]6V_5s}pG&6w8*uvGD{7oނ?VW]L!Х}ݳl6+c(`焨-h#agO?;-0h(,,f՗^?cÆ'DzM|{17vK!৪ bb۶x쉧"˸ګ@ee%|ctX,iXt)^2Cpwj]Z%K@aIcqM7<[n ͚&MQ]w[axî6Wņ| O˂O?ģBeezwftF˖a҄ѱclݶ %SMt0U&QbczXYx]c5L^\{xW o߁'|Q0.gu5z1 ';;evDZJ )DyybZ՚E4k(J2GJqmٺ Kv;ȸd1;?Ⱦc6LҬO?+/Qkaa499?z<88t]4%lnuݗy<neee2''GvY\\,$|lӳ ?_gж6{]wʏi0׌;9^u*&m8_#B""jѡ)4\uh{J);b""JQ*9_$""2IDDdC$&I M"""DDD&14Lj77 "sΆ;$BzQE}71UUQܣUdB? Z}DmDvNj]\gСc{*ãgضc;vSCv +V[*Nl )c^Vv&v, HsNd5 K蕡IVHcLiC>dhš[܇~z|6s|~-t==EV+?~W8׋6j ,_K2Y|$X~CX]K7t`xm,[3 &9SWq꺎wfO,.77]<=o|Is1t`dggY]g}K pm7c˖Y\G3^|6$ťğkqBJCc#k;v_HH>RU5!/>/ˑE{hq/sƩ`AիWXb&9Gn5Ai|>x76 /XVoߎ9[lݻ@+~zt?0,X,[>8'MĔ?ÆF++йsgcq`y8ؿP-?\^;>/GJǎcΰz蜟N< !7'.Wm=`hQXRJ>x {_Qro+0Q:5 j?]ի'tV,]|:wFw.,!l‚qᨑ3_EfV&N`ddd@:*_tM#J"޽RƝG;k.%80\8j$V|T>7m:v؉矇[زu+&܋f}3^~UU`G1!i//<4l6X7|~i>; | Pv0”0%%%bժU\8NQTT$ժ(effy^+tEQ9Rʎk}R(QtEbC~Nص\%Kō FӇ_% lXΝnwマϱlX{ع\ CuR4f].Wu_^^x4ݮɜieqq7FrID ӹ1I-CPuVKC^DDDB\;}IDATdR CR"֯ZaM ChIE24ڊCMDDD&Y65ߺ+4j5I6 >___ USS {#5qIFTVU#7/Y*2󡲲 BK\zUdBuu5~]Yh?T M6BQTUVD!Em`._~]~nSb ? M6BբPUD%4 %DDD-T4顙 %""vy$""j6-;DD&"J~h lODD&#&GC4 uL&5O&4SDDDDgJ&4o9Q3K]h5СfjGl) M]׌DDD 4Y,41%嬂Hh2ްY UU {Y=wz6@ WM 3GooIlÊDDE4Il"RqJih;~chF詆% DDVcm hYM c kxDz#DD:@<*R)3&Cxq.A ]"jM=VPؖ%Lm&`NiM$8R<:C3qDD-QSH00IC>C !!Š@XG>QE +AM}ÒK@DíAcq܄hY"ADDXqGqo40#TSGtZQ-ھGQ(ڦJ80U M·8L~ECFh@ҷ95dHublÒImhKO"-+4+<㕂7=IFXZG4J}SBd:5౼afj7AGMOxX)+DF~ZOS<4!Dr3ܰ$%A"̬L&H}hD 1!wDDUxJA`f@&Lz:̤Q; RdHk+ؽA%Xc= q|ZhqP%Lf@#!٘8;8%"j<+%< "4LBHxhj7=;6\ "44-!Ĺ""jBjBH#DDmRkˀԅfD"'%"jMDc+ ˀ&Є]%NXla (!q%"jQ"Fb PI<a `%_-6C3QKC$8gRDDͫc`4QVB3Q ~0–J0xR"ӾC3%"jRqtn?BMDDT&$"6&U pzZHmX\iQ)0GDDj5))1Qr HŵDD !ˎx& #P6=t%o @DDqdDIDJC1dxQrH~h 8$"j3! SN %"j;&5r73vp!"g"MI M"",)ziZRӜR!EIJ>%%4 ^H)g&""BFMqrIDDdR¡#1iQK( W¡IDD%{09M*sRQYWW8`㤔B0)^/?NTU50Ҕ7\(B3PKDDB%uijz6==]fggK&Vknw/ijf,"""JHyȥhմl2##Ay4IiF M)%^/TUSDD4RJH)iZđ(za+',S#KKKnX,) !8;pѾȩ:!jJǃl.8bEQ)ҍDDD뺦iBZ,=0EkXbc4QCDK.y͌ YQQ!Nt\RUU(TUUWUUuIDD-B|9MUU%%%Q뢆f,VUUz<z뚪O%z``W?ȓ.K@EEEpafggUUUJ:K""j1^>]5ϧdXj999nKcCQ6Mfee/kː|iii>#\DDDt]-if@\D3p^ `׫nj꺮k^׫(v+.|>/==ݧvZjJkG nv]xMhSL7 |R4(^@N4OZZZNuinUU|n[֭VVV&srrd߾}Qfl,CH+VUx|R^^.b8m6[Q-ibXRhB1-- @ÐuvQne 4^/B utժ?*5ƍ̙3nXZ sNHn޼6 ?>(zj@(ZzXÝɵ$z=sWsx/뚦iiii^hY\\,gΜxOӮZJ8wܩiiiQVi4M.l6[00!i|NDDdnYV>OlzgZ6 ސ  rZŢ|>vLiZ`dx_h'n?6MX,iNS7@B3 3??_(| <'Q<azZ=<++Kw:zFTX$Z1Gt*<JDD_#IKK 浢B)4"8Duuv3DDD0l=pȐ2=(WX45"8N=zTWW (Q2ddd_vv,--EzzzQh4qPWW' DDDzOt999fBa'3 DDDP%ZHhGc%n';,ba 1LWI"e@*b7Z#̶3%UᕪţUf@ODDIR2x"""jC#P3,IENDB`RaySession-0.8.3/resources/screenshots/Screenshot_20190725_233420.png000066400000000000000000001477751356671433200246630ustar00rootroot00000000000000PNG  IHDRܕO pHYs+ IDATx^w|e̖lRR.@ {/y z]c靧g?Yxw*D: l6f3 @Wϴgghin{hiq4MӴNDiu":5M4q :6uJiti/''t:kx =Nii->|xn&v5=ւu4M:al O8ћѠn.kiڎ;wHC|Ohפ'mh/5M4mY$zca o[ 3gΔ=@]]]GM4x< K. y4;0#4wkRӞ;w|_~"Q8 HKKKXiRnxǣtU=%ؚԴ瓁@@Aah`BiZ't:4xTYYzVFFLxs."GFG\4M70hGp*-KIIݻ[%%%V{:ؚi^WhX,}l/x<-4M.uuu-j40 U[[ PCY f>}=]SA֦vEEt: aAxD0e -/9mN4M;H)RJR PuuurY@rn%a7 kna# J0p8,].,ѐ4hM4%@R*ߏR`PI),2pth%nSM; :iNSJiH)P(d8Nat8B)(Ngl]5MӴ"aB!aBEC\B(4-0b ; m o؅M5m \N]N 20MS*D4eFOi] n4 !Ttai0R&,+`f`;; iK)`-HBnݲ,)0,˒a,KBC ;+X4Mۻ<8K)-˲,)eYe2p8r:~4k3a 6״nߟr<^oU5uj͆iin8x^};܆Դ׬Y#*++% jò,p8vr0l]R^UevkiZB[ BJ^,BXN* ]RUUEee%)))Yf1w\`{#w^ƴ\H4cn˲.hi%Rnk*iv + hRzn| Cn{4M4˲b/m*іde]+Yr{.ۅIӧt%i"NIӧ>2|CzѳW/{if#]n;Zv{pF' oh0.[KK&jPR\R c&V43{铙8}]ᘓO rmw;?ӏ8^i;-.bwY0qNjlx0r1ddf4j&2~&aTTXȒL6%VNFfFa:ykce۫g^`vk-7;kn=ܭiHH5 ' de00mr_h&B=D/^Jykw8졝]-t-V>hNP0e+))*fRXŦ=(ܴ)VrM1M4R96ޝcIH2MSlztgѤx_q1K}lyrJ2Gxy(R2d0zb6V.]AuUGtkWaА$$S[Sˊ%V5bQ݋L&o* 6#-5z$}`3ߏP[[(*B~}lҒ~}R*_{n4Err#]4[-M0eؕnZ9J)/^AS& ݙlOorۖ!v_^/qme[ٴ~@cO z`,dSR֣;~?yyWB3jITx9èdK1#ƌpfZatIh졤gf_ x :/i9uu 1lKjY򕤦~]>6r|4V.YMdɄiOvJ~}BtB0  ѭ{7\n/>AN p▶e.nݻqGycuyfܯ,`>9hD>3a<_}%؝33(ذO_n7s1ʷiyl޸vgf$Gھp`fga"?iwm{p?dwFQa!-\\egB4-Ϗt9oiPTXȷe fI3NᐣِKl|4ŅE$DB`3nO=R{з?J5 -5ʙzL?ة.)5(z^}{=iRYQb]q,b?"geĦY.?lycޮظѳ~԰龸]=3to8j Ijzk?ʷnk42*+INI/\kR zF:Or2@Q?Z6~2ޮЏMl|[9%noHܮ==^㎊#]]Mܢ{Д@M{~ެ sR ,RthC=$>h%aw6 n4>5)) |>ACУWe+bgYBFxxi=)aBGjQ&wTIp'GC)Qx'mMtдB/(H{mˎL="pACd޽j>@.^/Gtl ?#g 8-)~~}z ,c-J)֯g|ǔC}i ~?ɍ:ycM56Os֟& $BUeNj={GS K-}/2M*+ڴNlotՓMDط};ښKZJ|l--0$MN<={b)F×j4rӑ(w؃O%am9nK9nMdaq/'r Yg~y=MȲo4*r8 =oTװfXs3O:!5\ZziQJ XBP+!s$Bʷn#`̄ߟ#?}[7 =cGsq]t$,bkY?_6_^{l޸Kѭ{w&MaHLӤ`æV[<Jׇ#O8]%>J4~nڰQ#ޣ;uuu,^: `xuŲ,jkXdYySZ\+7y"$75,^(YѾ>ҴmW=h|w0'3222jjjeYdg pIRQۅ<ekB_eو ֶ_|} 8:_>Jv;ok)V!DiRʺ`0RRaofff%%%Uvwwvb5n4.fv]< *rZIn ƪe+죴v УS]U돱Kꅦu2{EpCןm=z}~ޮ]| R~n/4m7k"5o zMZFz*:|n9k{hObm\_MۗU RKvei֑5͊iinM4MDtpkiZ'[4M:ܚi։4MӴNDiu"w"i ;}/]4MӴNDiu":5M4\4Mfyyy"''oiigkM4M4M4m4MӴv&ߥ-̻,^ƛ忾f/:^㿾&}M{` ד=l(k~ϣO}2MtvkJdggۋϚͅk/"ssX2FƍMRqcGs˜hZOԸ[sY9#9^u2~;Z[52:=pXH[+)++ͷ߱r<'9{\sąWobq1GsruUaw,Kx<Q[[[lcQSS+540~,n]Sx(--㣏?kt}ɧK{w7v gq: 0ذqǿ/f)'DzZ\>R;}Fn3CvK~&wApYg0bp=ʶOmȁ 篷Ҵ:Tp+viOxm픖i-w_x>j93<.ZnF>qŏ?uxm֖xu-k< 5kԴT}Yvܳ`mwnϤѯovmp8y_ۋ;7|x1G}Em+קϜE:;mYH ݥ=^{-.<;eWG=.޽{+-=+0;X{y5r0{ Fmw- 23Yj[Xs~{ g9ˏ?b^/7\{5#gplT]OEE<GPJq1G3~Xjjj8Omm-=xGx8Y$f<lV{~ɕ_>^~5VÏ?oi8}iŖ"|֬Y忻Ӧ0vh.<\{E~'{xixɹϜo}aWq C^x$hwCûOɋ/½w߁e= bUU59;f4,__;=hqc ~},~iwq r2 Ŭ373gByy9G‹/v0еkW}~>cN9t?h~cq9ˮdL65n)/5n޽;端b${X٩̵73uȹ6rǩn3Ng℃b>4^{MN:e= wدgO.Buh\sݻp8su#fl..]y_q39y-7o393ιo.tõYY r ]t 3fhgGֽ[7>>*0pwdE\Ui9Wsƛo]ѦϜ9>nEg))**y_pѷqи|d)cnj`).͛73j#G gwK~=zߒ 1Gɧ}Ά QJ?!CHOOcB eI!bRRRR8cHx(\HUuu*2i"[Ͽ@)Ea}w1i|R>0M_i)VqKCa@>sھch*ߙ&2c/< s}eR߯/&vHNN%Kp8L2 ~ɓ& V^NA|V\ŊدgOr.Ҙ:e2GvXlں:z|ŗTVU1{ R^mO(\Uצ4 IDATYO3NO?W_# mKzz%%нKپ股Pֹ+ujNkVTT"G***BЭ[W***Ȳ~HKM%w82%[(mRcO??gΞş=^xI5W^sg?=ŪUyW'm7DMu5>VQfF5f`{[XR 46> bFqg}қ҆}?6}k}=#h1G}MQ%%>enwnrm|Ï\{Ülݶ=ݺ:o uuuuǭTVV&mm)*W_?)%[n @1_)>X П9 Áa :``|˘5s.D)źq:z|$ҩn E9cYXǟ~I'@v0. ƚ 6RUUERR HJK}EGJ~di㏛?ٽҫ>4NHKKۥ OuW_KM{')%_x>a 6Ï?qYYdھb 4d<{4_~ip8= )%`ׯ]B)?CJ٤yk#x.8ϾP(h%33G?;=ϖzɜsn>{ѻWvp@'}35O׸wǟ|6o.-W^;L"z~~?K.煗^͓-'p\GW^F]f{'x//pW?`\~؟ E/ :` ι= Xf-omP^Qͷɕ]W\ζrߧgt[̹8 ;0ذa#<_|9Ѵ_|9'}gX{]ۯ4$EE' eKO>'?,ӢG]ĭr{xvuװ~z}If8ontl rr35T\.ee[y\XoM_1Gɟ ZoϜּćw;/ܜ'|>>O\.R:@ X;u"6y'iu^mٞng{WxɧXl}|9K(ZFZjᄑ}i^y_ ! !,˪N3X[[RRRR`0h|>+##PÇW999Cm痹oiS5֬eSx1s),_]kܚ3&mskk}x)j]<}M۫##h{NGy<{;Śҽ5M4iinM4MDtpkiZ'WwNkRimoYf nƚiZk'+:[wޞii;騁ޡ[4M}Oӝ4M4iinM4MDtpkiZ': !p9H)r4Mz{Ge :l4d-n ځwi֊oKVR`H!:lxyyy /E zB)%L.KX%Lam3nir:^C5MӴh.[v} e ˲[@8M~~~q+ObnoR_M43j&K'a.e+~ֶDvv6>OA!44'UlvM4mՖLh$qq ^>OI!BHe\.iY.˩rn'f5"A{'^S4Mv* Ps''9RʐeY!r,áեKu+uVk;'''<E]UU%D(`0(t\B)%,RJ8m86hi{!!{4),˒eIPJp8,5n-B@ |>U{"999bܹJD `0(jjj4t0 ep8,pD[wqoً44УQi{5MBfRJ!4olpz'JJJDr2''GYu+k78333e}[$YQQ!^D\h~Bei զ;oڋb pgً,ѴFK={|CruWOfXiiBpHb4H2bPY9Z[JpV_^k > $mR!RRDN9+ ﺺ:5p@ܹseNNU;lGQM;0p@QPP ^BQSS#nw4}ãZ !l PJ9fCոl͚q%m&Ow;_Νga)"?TJ{nR0\afà=l(RH 7rgMiS̠o%n">`@ 0Xb6m"Oh7G?n?Ztp3O٦0yɬB#WZX|i"\]5ԺHgy\?nǟ gb)L.ޯ_v;q;B),->hG,@XJ&K vSPPw[h \B$˲\.+ p8)"M0LtP_, xwlű͈O"5ljf1Ms@"5WxLСC,1FaZ&_|1+.GJ,N;^xjƎM r!ŗ_,@c֭پJRRRx$%%QYUQ;z$Oju#G n@)E0vy_ӧ7'<ӦNk*|wO9c8klB0R feXGP>R3N׿: eY 3O;?OݿIMw尡G k?]2yIr0-W0 OCp+6{<'n' @ ?X Q?WTI4,LB*BJ)Ja&!)e0 ʤ$eYZ 6i-\6Mi0 O(Jr8ZmȹkY?qRe4$)27'RWWG'fvouف@ ƒ0ϻR~Z>rrqrPRCJ7]OB|1 z33~s }˰C]뮹7zo_Ù蒒«Χ~K22 N'C޻b)Xyy]p8m7aXE8?̳!斛oN`W_1vhJJJsB!: %OEE%?0[n cdD"9#Yb{apUW_ͷ3Kv:\x9MwIXb3L|U,^,5x.;Ej;:Ym}i2!~ ?5Fɨf{#PD~RJRaj١c!DPJp\GWl%[y'8iD@⚶2v}h'XbuFtOaDi¡[}63zi֚p:A>}1UvX|ͻ!VӮ.˨w,+I)層^&䚦i+I)INa鴔R*XPHuڕ**++IIIQ555̚5 4oQ57MAA㑀 RJiPJzg4MӴ}RtO"EDvv66G%qDk8NYYY)R)eatOJd%]/C4MURI)C)BLT@@9NUVV֝P۳~k4MxD8ip8)i,1MSZ%<Oiiif'=׶;UTTRJvI)p8YJt[[^!;zOʈaPZZ5k,[2amM4MȒdhܲ)eiB`0hfddXNjju; "83MSJ)Eӻ\u9#ӧ7}cb5iuXJPC ,Bp8D(>6M]bĈl#׬4MӴRTJ )Le 4c=~jw;eYBJ)PJI8Lm VaNy}FjVo7ۘ7e$.Zdiu4R-,ZwjGnD6 !+RګV& qи)#/YJ#蒒µW]My}l>72iwq om8i{~׽QB`YHJJѫBi6kSpRJt=٨cF1bx9m aڔ)qS yG7Xوٌ3VMٱc w"Ls#羇e;&A. 4bsl>3DH:L4!s0w?{ns}<{SOp8f;)cjZZ!hh} #s˜:ib|꤉P|q,m2 Ïbnoo5C d͹r} = rϽ̯=<Gz7^};{xi`u௫# qe۶mCa{)3zTl sGY_30qxPcAIIIlyi陕Ųe+@)mÏ>v@g)nۺ~n9RfSUYɋ/k.[\7jLaZnrӉasa7sF|tY€ﰶxҸ9`_7Yn[uum2~G)'s7#3?7У\UHh2 ƍil%g"^~Կ?#\ɸW2Yj5<(% Ӱ3kоތ {i;G=fgn.>x<~aCng+֛p[_ϣ?3ki"nKc_䯭Ns+Ǝ;\.0B, ,+rmێ,+.؞Vy{Ӽy3ƌ~!p1HK:b\.AHms0^yoJD!OqiYa~iM,mПߴi`]Oڛ7#cUOysYRg!fwMD49Dze :?-sVUDze zgڊ؈¦iq׽tzzFNǓ7,aoѳύr_Hm 1`* " n  " n  " n B]_MDDDZ 5 " n  "{ v։ȡz˗))){=O5c⋈Hصkqq*sk n?jwvSZ n ,8LӤm6ݑ*gvf]*e9گ76JJJk^&m۶emddd9DDiO6YfJ,#22(((0Ls'!䎌PhLnXq%۶0:u|۶ n n9l0@hڶmQ7Vk6mVyM:[DD,?mc[VKy@ae?+x}¶mm,¶,"\thVMJJ(ȳ'4" OIdt8nQ(+d,e-]E4H!QC$РQCe吗C^fy, }Z!x li@aژ͡>?Puܶm"""Fڶ*:N$) &Ǧ0?>~n6PM 䲝=m~6!e1eQ|U""rԱmSmR.okeUysoR!@o}z /;^_i-""T`e%¶H\R WqHʣuʣ劶Grg((/89%t7ti?,0t0yf[8볤|>/~Y-"qa`oR@ܲQnEhra,q>l_uP" gSL vn*ef--ƶ-LUgTgzT)[|ф*eCÆ 9q̚=Y%""ӏТ# ȷm~޸,k߁[XXȉɄTJih꣠ nƽO&>ڷۯඏkWHh:$ [XEb6}OlzB6{nР 4pWSPP aU?40vr+Fameզv/0 ltlϓN{sɡY9Us8Vc:67oכ˯e$&&0tt/W^Ljgd%LI8}9-pн{7~^;']CbB#,fҤ)|4abTT/Ivb7=gn W_IhV^ÈF٬4hL S}bo}_㼾}t߰믽??}v ,8<w&5V=sm#2T+~(I{e߻ybb"111@ж `?bA{2g_l*?}'3=g.ތ zʕо9*v6nxf:23HJjY~~}>׬'1eg~}7~φs>)WFfdTi'#FrUsL{}5˶1rT 5d͇Q](*"cbwǎһ7ť>cmĊmO$556l@f͈%77M` QF4!tkWPXTSv`aӲe ~qmxx8QqqF5eufo.8/ۆ*k5;MFF&qUʠ5zJO>@Yaa! kܵ^غu^Cj/"(n(e)Ur3.Cx:,ˢuב;ji-Zb 2228HNNa&III4mڔo;Ć;PxNvTQ ޛ\{M&:[̂(,*"+++W1h0gv4TRZZ_&"<|w/Oǎq4dk֮`uĿWu4})=оaprϓh۶}ͷ\ 8 <7ndܹ^LҘ7o֕ #9"?}>KZZiiidg#n}ϮiA7x2 |oqLf<zq4sc5^6o.u=>(<У :'b6S}θdǟ}0wXpQ3wi´O?3f,K{΄h-`7uۡ=^o>Ο1wԸ1E̚=?Wt64 vŠ5{wk|2w/"`u?ɡ8YfTNKpޱr 'ٺlۼ^6؁k׮viL4ƍӮ];~7"""8uֆ5\eYn;$ 0Mcvm۱B4j.?XDDqU9@S{#$d=xRgq5eQZRL9o/7 jԈbΝ߹qx5k;viӦO$''ӦMfΜY6׀<ͻ_Ha CxzΙC:ea)Ҟx BB|wtb0 YYxbBBjGGG vǎ픔j{ [xLn?-~j٬*Uw}!;/""3'_~!,,,v܉&33۷rJ2331 +" _YiϕlτΗVm֪r3_( n<0 8msLRXWT nÀz>U . $+?8W#8>CkRRRBQP\\L Y1ǐS--^&""u[E<-v_e=#xhAկVA+ٔe v݉aF4\DD~[E< 5GrfPrKlZP%ѕò$2NU_)}T%iVuۀmj}Ucܼr?o+? HEɃg9zUgqwՑnHAKj",,Y\MII m8/--=u4+q6yoQLXW[O pjR(=z/[\E-_ ʈ{oذa^`ٛ7|m=+Ηە6sEDNJf-]<# ۶I˯X qӱ<2?BڏkkOSUa{c6daXlwYѶmf2G f187'&m`z r+дaC.Ё}PT\̜1-U~umpb"gh[QW3""6 JlZDhq-޶[O"+&].'qw>:11tNrrݷܲe M6W^$&&NRR'tv]vmΩr*c۔ݾ :>ACnU2.Wwc+n};˖-KoORib-<+~ǔy<1iK-",j1> pt*q[M;u"4. _f0 6n܈mہ͛K"ɽŲ}:(|yF9n#6S؇Q|]1ҭ 9/p[Vm6؁K.ٓiӦH۶m?>Ӈ/˗c@x%ectϊQZ&wPAqzSUz@09C{JJJYZNשCtj#nb^F?eb"M‚ q4mڔ !!( 11< ^lY;3ˏ5~{/N  㬳z}zwf͞lԮwfQpHu:>a+U}Ǘrή2:_ '333p[OKzz:Wfǎ /J*fw`WlaTy:Yf-3fEJp?pp.ZBn]}F/F<#_/ URЅ{(< dҧ׷ѝ"Nu%%qѠA4KO>C~ /8/W_}% Yz #Ezzٗ ?0~~_JRR۶3,_'nȩB'0yulݺ;NfͰm_SόV'4hL S} fS9VG+)3-"#;Rn>TֳrhB9wݺ d~}>nvؽc-`Gzq4Rzt";fM=nVZ]iv֙y-{}Lnyx3c^}ȧS?{gĤSYeY,Z-[U"쎂7ӵPZZJ\EדovVedvi`VUpjՒ&IMul~?gąh˘3{}oiھ}Ps/)S7q`!4o@sͷ߳)WFf]r)>_lޜb?YrM1rHDG9n,K.fy}%77Y|P,`e]_AB6lO? 44|}"M*w!>>Uѵ[NίT|7''''OHH'FzJO>{bBDzw AIi .?ݍzq4_t!3Xa#˗Yo. ̟~;W_'H۶m7qU+ˣsNe}_i/7ȃ3go hӺXlG;w]|AǎDZzZXj5i }]Æ2YpJD/akë}? è6_J1 Du,v/Vx]Uº}x\1^9+B|8mȷn+f'AB+g1_]sI _g|Ls^̙/ |f7ǍeΝlKO3*a̟1wԸ1E̚=G޻;f,?z1,ˏϗώ 0ڵ +Vx"< 穵(?ol&r(6isn$+ڵב3#=UlW^p1l+-0%%X|z gnzfXX+??ߴ,+vizlێm;W_mХ3/p׹^}Œ?;DDd?ѝK:)8szA6˧nz!~z)b߁]6\> IDATjƍƨ>~raO#\mϲ|0444cT;::NHH;vh͈@ѤIVsک'Ӻu+TDD!01L?2p_Wn!x]BDr{D"EQQ)Y[2N"{k&D'(&32ƃ/s'lr3e$?;0e4!: ތ͛2 /B:;<EDMW՗\RvLj*#I-""՞HS6([4U_S}#H>8qDDD)EDDH jQF$Sq]J۶iղJDDꙂ֮Ku!V۲,V^,rj\DDDM-""D"""AD-""D"""AD-""D"""AD-""DXLӤm6ݑ*nWAk֥bYꈩWݶm[vlFFFJDDkO6Y\ճ^M##""RoddfvGT n7HQpHQpHQpH9j ѽ8G>hqh :_,xqH.BgԘg|rVH=R.yڵk˵W_ENvGɔ)1IU-vY@5lؐ;ǬsU|7*,tԉ Νe):vYٴlmΦdffY7R}F/:o^zHDDmp|_} x+PrJ-v;o.|n|;AѼy3 ^~e,,ʦz]]~)IIIlۖΨG|}Vox=!!tލ~WƎ|(o8;;\rϋ8}?X8GeO:sgu |;ݺu/WkHLheL4&L<:<7lkfO+vED iHv Ϊ4d3O1L1{SOk{}Lnyx3?86mӰA}کpEKzp6lg.䧟q6²,-^BږΪj|<7pY6DEyغmO?3 7Үm^~>;vp)'BRTTDllL_DD\Ӱ,dv:jjv'3=g.ތ zhix32mϤE@8i|Dz,G69ш?c7Cvw̫|y呞7aF֬]GffII}-bQRTT(PS92r],Q~/:KhO˖-ǁpĿw_DDq̚=x__JXXy5cQWvNw A dK]σ=Ja-w?pک0Oat^|egcxDDD|wf՘A׮]XrzaC3uDlfʴywYt&|>^E0woή{5w/""QUb,_z3Znmx^3,,̕oZvCKJJ€4=mG۶+OsNҹY"""SYd֢#\mϲ|0444cT;::NHH;vhT;S"""E n  " n  " n  " n  " n  " n  " n")1?z hghժ%\7@y/]6<Գ]vmxH22323&~2TDD\7@HH FxY&O«cǑMv[Q >7,\Y @+.1c|VYˋ_f .י1'-79E̟1c)**|ȃf& ?aϝU |_} x+Pr']CbB#,fҤ)|4a"7н[WzыϽ˖_DD>ǽsg.: g5,Aq?/ev*\n2y)çS? /7wbeL]{5w<\ޟc뮮:aY/!mVgU11 i߮_~*`m'lk]E+qFHWaT ǥx )[a߈{R4).) ?v @BcqcAش9Rj/""Qwqor=w_ʯwv*ws?99$s ROkWvv6O>, .rV99lܸsڱtx=ACf:Nz~z_6ED:jN}%;v_\(esZ99y[opu кu+GqװtY\kɿu ]s͚6%"<͛ѬYSbccN;qqED# #{y#Iywݙ˧S?#//RλuDyѺukaaa|ӲPZRRDm;ڶX_suB5.9z%Kŵ0l0|e%ix<؟`vBBݱcG;%%̣f\DD@-""D"""AD-""D"""AD-""D"""AD-""D"""AD-""DkIx*AA~+W,gUr:t -m#ӷ;DDIhߡ=+W|SBSAq+EDPt<gq " n  " n  " n  "xxzt,΀w6AϏ|~_,9mp`&HeG_q9ߗ(|>/˼~ԷoߎaCǺT%6m @\\w9Ν;QTTĄɤO}4hL S}r?4xRLaw RzӸ}dRHy0xGرCWr҉P,EeJDD^sr3gMO r}>,Gr1$''ӬiSxo.~۶/S?F ""wM\._ُ7BB\B0GrC/8JKKہАP~}4ȑt.N{';'cqq4hMff~eR!].aaar4sr0 ;ٍK.X\.^s7nbӦMlNKcU VIywݙ˧S?#///Ʋ,~=^~iqYSDzm|1 O&~iogԋپcG`itڅ+W:DD/.h{wz*Ͽ~W^eeggO;xEe\u b*QpHQpHQpHQp|WNR&HW0 HUV?Xg //U85w,Ҩ""* " n  " n  " n  "w3M:U""RZDD<1g"""?/GsV2$447ଳzqYv-òmsWo獷f׷ѝ|z (`藱l!4hL S}/7fw=:3^|i4r 7<ƉX&L>^@.]HMM7;w""r5}ܹoFIIl]O`r=zZu=|uufoF0/B\\o6 /:rxXŢKH۲Yߞ,֥.زe ))~=۷'=={"44K ךȡ>`֭if9oU`6 .b친@^/}%~˒h"9P$98Krr2w챌zEr}>|>[m` _իȠi&mOsNj/""Q3⎍ewJLL`ڵUʶoNBBBy7R- ?F呗笢Os䢋x<ضMlliҶ+""rh:ٙCbbb`BVV'V)KJjmtԹC-+;(n7Ν:0[Xz5~eǟImȡqLϘ\ݟ\.m۴૯osоaЭ ^ :bbcy8^A׮]Xrju=;n+Iym,^|6ӦLf,Xٳ8լٳ8|"%%X|z gnzfXX+??ߴ,+vizlێm;W_mХA/ѣ;?4Y,""3==ztgZp]lFa>˲₂4K<U\\OHHRSSh;!!رRQ3U.""W " n  " n  " n r$5NrH-$5iB~~9gAy4mժt8==Y%""5cժ5zM,bşiT * " n  " n  "* o,9"5jDAAW_[.m[ӪeKgaWPPu#^eY^Y,"""T웂[DD$(EDD[DD$(EDD[DD$(EDD[DD$(EDDHrim;Y%""r*(`ͺT,rV1*۶mˎrVviۦ5ԟq׫rwdB[DDꍌLnW-""" " n  " n  " n<{\.JDDѦY1d]:w¶mҶl7fߜM_P@-r=_^@mH߾*xɐq˭"G[JLH>"//Eo8ii[x?#y|4oތaCӲe BCB`!<;RۇΤp>>mӾ];z;͚5öm~}O=3YgMnfʕ\py 2Y%"uG,^Y\k 6 gYV>PZ\PPPbf㱊 Vjjm'$$;vSRRTH2*NH9n 779E/@#n  " n  " n  " n  " n  " n  " n  " p^>KC.11)&f-8oKNb#"Ŀx/MfӤIYP༾΢eee1RXTK9^xngq{wXY,"rDMp7'R:3ο&>2 74h$'Upר3rɐxUun[ IDAT9EDgA}հaC˜>}&EEEϿ?0~~_JRR۶3,_'=O:믻ĄFXͤIShDo87mGn{l2̚=s>kOÆ UXǟLf)@Y6f?K_ƲmsWo獷f카jْ||/~;.:oGFf&7!o]gǐAr ] _g1fX IyoCv ~s(;w ?Iɐq=))->Aǎ1tm4o 7_ 8qpl朳gͷ(>^߲}09眳q/):tb`63zsď{ֹِG<4jx @zurcN'%ѥ[/~:unyTrCeʔ7^i> ^OD2Lbaaa<@'"+zӠOfUkX:׿ Wߠo޼̛]/m[of@VVs&rdeguZjW_Kzz:N~*vk=Oں[rr /*IYBQr233bڗL5-7W_acٱ7DN&yo%À2-gϞ<HII!**00p(=zt&?𣉤9vӁjh+u&%%aZ}N8ɀAЧwݓ0yT 3GEErEϰ6_~mmEPU2KsE?/[6 gLزu+z?}RIMMCusoֲQT|=?ϙI ",CÑGپ}'6/{%*,Ϗz`R\Cmp:0˖cѽ+C`W0nVa$'I˯ {/ !DQi,Iԭ[yeqq0 ƁtoY˖y^\,w\C~6f0h@^<\'si8\ӧOyoGDFFڸݫS}_#-=BpP*pRy@jZ bcu'U?--MҭvRw~z:VkY%$![ZZ:Ǡ!Wunͪk]o ?qKAk-lڼ5s)v漷RsC}Shq^V{I!"i1T븵^]ʔ)M@@r3n?w%2" .pk; 22ҧ=M͚5hѼ) 7`6S;.UUJH,ȂiӪ%5WCQnWﺓKp9@Ub ={S :ȡy7bXxNu)s7| BCCy~,["ߞ'ػo>%4EQ\3f:ztJXXf3鷆K֮[ +Ӯmkf3&jUo$880NIIIaKBPP|7Tawp*1w-MW?_ 2"N}Bq׿ߊ0`+bu"`pX,n׬V`VՈ3sͽKQ^]QJ{.-PbEnŘ# m|4bB!q͝d3_,$CB!D "[!(A$p !%n!-B BDBQqJ5XBB˖q{kԨGI:%B\ؘXר޿g[%jb -9t0?X+Q[!-B BDBQHB!J B!D "[!(A$p_*UbƗX|)Gdܟ 1rs{?ֱC{&:?X*Iz%ԭ[%x[Xt1WI_WVR=ǟyQA""֫v8ń7ԩSE{J+PYW̼y,e{dn۞T쫪}Ҧ]Ea *]_+fѪM;ڴӧ;ybWU"&:?k^9,EիWM8NJ*g7k>pL&V\͵k{ oy޽whx=rg_ӻ7 S`A4o;TFF<Lp2fq3o,[)?B zu崩lX%ҲEsyuM|>SV,_ysHOw~;?| W.g'ѻWO^"c:Ǝy˗dtLm9Z{K˗+ysy,?˖0.eOXz YYY8ou^mSysC̚=kVdgZF}-VXƯӖL֭=VXO'Q^k*WgLOzA*U}ȂXxG@@6dт_QKӀ%b6E3{&K/Y?|S{b4cV._ʏ~VZO:ҶM Q_5@4mNvzq&Nr oE;qf)t:Yd wJ68zr6˖-ˇ/~iӗ-[Wti>C~gZlSOྎCDDwbSY >x\jaiݪ۷oE֌ F-֣:܇|S>&&իC/_>IUQzӛWzǟx0c4fΞͽZI8vOֵ O<-[e޽5 O~@@&pvS0ii|Wtx?>ܙ8Zl P;nڶi….׳Gw}}4mƪkGdddж]} tF%K/dzn=È2|{|ͷ4mޒ?;oEdD֢aCYj5]'U̜5Q#GW~)EQ$$UvۄqqnWL:uظi3!aϙ3g(_  AZz:u!5d<3t4i܈7|l5jH=g9vZ}aX0 H5}'?)9! sN UҽS(dB4""ЭkWrvnwx!ot:IOW=l6ʖ-KYkYn{“_TqqӫA 81{6z?M67Ic9¡CP8xϜիSF ǥv'ۓ1r 7Z:,, (} m-LQ$**Æ1p4Md}oL&>҇wߍll6_օb; p'V''HMMeDDDkddds96 r͌})=Yx Biެ)='ENNOcyhӪii(Xjj*1>iʗtϡa-k/]qa1t?PVN8 @HKO(=,ӿcB >r'?/Q7EgҥK°gr}J̟7s@a K-A$$$xy `˼y믎'>̑#iܸk.[qym~)~1>{匯=TO>W0Emʹ/>Io8ieʔYӦ 2tz~Uq~)$b!$$L׷ʔ.)+DA١]:ӭkMЧwoV^߿Z4o&DܠDy Xv{cǎ{vA"##믽viݲ'ouP2;l6Stiʗ+UVYCh׮-RBz_UoQÆ⠖^YXG;=(AAAX,˖-&M(Su2 Ѿ<Vxa aPUr!;_⁇:ӽgoOStٛG}f\b]c˕)_Jm[./ˋgqfc /XGSO^Q'Z*7Vᾎ0͘L&WNHHHڧmu'm2kl:sl{~dDFFr1QrW:q$'mwVslQ:իW<ߴy3UȪUǟL஻ofݚU̟ 5kdР!y~5\X׿K٤,_*Wfu6l[qPh߮-۷(rOzWT1ٌFE  _בǎ%00s3sl-wI%_!FOQXr*1w.TwOEPaq#)or!-B BDBQ}\B!rx=KuU}Q:kB!.ۅ طrVgBQʅB: B!D "[!(A$p !%n!)V;##QB!DٲeO*V_;x(UpCYB!UC S*pBX !`BDBQHB!J B!D "[!(A$p !%n!-B BX`B{G?@Cd|5Eg !׌bC{'s/hWE0nBhd|9EQBkB#UxA@s50t?K!f7 a„=5@5yegȠw?g0jpRR0u ,!O(ۭ 8pg{_E4j;իp:IH8[\/!++mV,\īĿCaJ.E\Zlظ?Kq kgSҢM{]ɛ'88ؿ : IDATP,nO^rq*UH]JK;IF4Nd!5qa֏<һ'Œoƻ#өǩU&?*T &~:;v'ǂEe|:'}Kdsiۺzt#<<{r:)ؘM9]`篽Lx]Οo3ds9y=wSnçz:Z6oFn]Zr:)/}M=y!Ԏlʫ9֨^rdff2w|aaЦUKV 4ov/YYYu|;1qǟ|L^=n=1 5jWxRRΰbjƿ"11|tRΜaȰ "3# `6͚ۧ6!<,}O|X=7};oN:۾FozuiҸ!!֬~}iټ&ʚudgdgб};:? 1VΜIe c9qT/<{3iѬ)zvtdee1{O\f3#}[j&88cǏ3wx1r,̙ رE!22=sL*O jo^<7?2Kƫ0s{3SƸRL-^B<뼮Byn W[xx 䥱=ecbY:<$w녪 M1z, {>A`⧟s!&~:z:ֹ䍷ޡd>?T `00ң7t'>ٳyTRƫ:i۪%wlY_g_tz+#˃ƍ^eԫs &Ԩ^zco^9z>DRR2{m֭Z2}`&.CL2ٝunaa<й;۶ 詟!/O>ޏnZ>zPM}IK޽;x!Ƽ 'O%r!f| :=~+WE\ f cko@n<=h(r>9NV\Mǟ䡮=HKKWn?7ѲEsz4n#GHH8\> !\~__3O 3|h|':Hvv6 ӧXz 7oLj*o򕫩V*мYS6l̹siղ9K-c9?QF ʖlW|Gvv6٬[J*z.WYt0 v6Ӳťn'OM5ipם(u; tK0 NJd9iS&14V0 =Kil۾ka6[NJdӖ-4n[v߆ /r]lьo8N~#rkݺޫGs7? YYY8NZj?vgQhv/,]t:9r(ar۰q3{ ;} x4@tΑxړyVΖ)z/^M|E,\}B\=ϧNg=ܽoe|}%~[OZPP Ɔhټ3͚2X˖wӼiSO,J @Jʥl"jСdZ,[6ݻvfЀ|w_ȿVkYSR|Ғ#<{W.9CZ>iGfM5kpwiV~'CgQ;|IMyt8=mmվaBDD''|bϠiUU=###k>@/7,a ,ݻvO?'~Fʙ3DEFһgwU@Rٲu37׎#147լK B3lZZ:{g^%.Yd? fm1租:+ҽIKO'::'-&6Ƨmmlٺի1LPPev اKipם4i3LNoW pYFÀO0x@4M'a^OVVd wLǞ۾c'>ÆMVv66ns4/SgyfHN:30Zh'>cيLl2cEDDN%&?5UުXƿL`` Y,_[ywc=ȿH._~-&>AUUc^ ~ɧ f0͛5%3#e+WW~^P/>x¹sn,aD[DEFǒxWa,[= Ǚ߲b*>h'dRyG|O0y4|:ܹ,]V-/]vqSm-˶헮;(hWWvޭ(6MR.^p8`UU-aars'6z# P>\ mZmV !]x~EQE~ gdd8TUuX,nkVUOHH0 jĊPvڮa L\CĿV!ĿuM3ChwU l3Fc'B!JbNtq7P0L*=hђ,Zr%!b5L+_B!WB!J B!D "[!(A$p !%n!-B BnEQ (wJB!?rEB! B!D R,oy*%(Ԩv#?KP/^䯽@)>([!իrIRD M5{>LWZ0 Bq,Ar:9jիa:tYsܩd2233 UUȧ!6)teFffa2֊/+p !(: bW BqBq-B E {!$$}BbȐ[]NJH[!C;<<> !򄄄{)R̕q !S9wIdgg숑l۾?K|\<{F\\aZ !D w=F숑:no'i߮-'MOE`Z8#>>>,=n!R<^mw0xhҬGHp0ÞJƍ !%%~eW_ה)S'=4 *2mlٺq_dJ-Wȕ:o۾ d=JSbB\IKf)#n݆ ZjEppgRSyرc{=e5!ՠQKQ-[:c9N,YFh>Τҷo~mZ{VxժVeҥ~{} |FuݻƏv]w`BOKzu9r(:O}iܤm%ZtދuBU}ĕ_LzsS}IVm8Seʔw~eLoMx֭C-ѫv`c8pࠧnIiB!5k4o֔_!Fu6mLXt5kԠBnՊu7p9ڷk˂9|?PMXVz""ʐT/%%'3ΙT~}+_oݦNNVVYYY^SQ{8z(˗0 ڻe˖{5nԐӞu7wNJJ 0:vhOhh(Ǐfy2T.WQ`PkV.OQԞΝso/_=;oM遃)EGRFuEtR$'py֬]G6btZjVkY7jHV.̢TpRR\Ξ=g@_p`6NAUt`6FtR' 9907Pfp |i>҇GaLt2SKB+3M.ĩSLl2ӾUUyH:cyjy)ϟCqfBBCظi3i0sM);vҦM+fΚhw:P6ça-['TRǩiO==Ы%qDEE2H>1/ !D toTxDDD_Ndd${nL}nڼKh(}…= x\6D\\-n_~U_j jRFuy*l aFn i[h_f-U@0͘L&WFHH011Txg9uWj+AzBq\Ŏ٭ST).\;xa̋|$>gϱpbڵm㩫: -wt˓eV>#^x~dggf:v)㖔LǟSO}@xx86c߾s)l r&5/eX%$vzPyzz:CgA<;5J.+/Ddd]ɗ3 U>^ٽ{*URM&EQ̺b2B u=Җ]TBzf; pm;gc4wbzƒM(MӴ UU3v]UUaά,-::Z;vaZ|&sBq1@|4lx۷ 33nL۶mx]׫MB\A$ء=c^M`@ۘX_$[!( W'gUB!D "[!m_q[QwB!5n!O˵+B!|]xEBp1xC,1h.dd'O㯃j9ww:Bq`իUUD u1"DQ~?WQC40 f?}-"{0U+Q2)jJہd2qn7,B\%`2s:((FNd2/[!BB]V;00UU5 tsB!5JQ,=qp.B`FXXdz6 ;;PUOB!ᎏٞXWⲆ #[!&O7yn6 (t: MӮ؆ !M 6Ll6 #,,ș.TwATU BEUUuO/x#..ΰZCBBTf^dM&n!‹dҽ322 yg j|wAܷl3Ln7f24B!dt]4MӝNåXz ?,,sd2233 ۖ !^ ܽn٬gff!!!>_;/AAAbs&uM4t:L.B\N8p聁l6,no=eв@]up8TUKKB!ΦA4nw꺮kٺL-o$n;ԬVnZuݮY,bhSQlxH[!LR"I'EqNŢY,nkZ?xeժUfSճgϪAAA&\7q1;@EQfs(za|UUUMLjcEQLrzn,Wved2ei(JYu]sMQaN]םfn:GGG [qX-99Yl pL&k dej(#SQLEQ2UUPU5\QL 2 îj6QQ\<բy4;SWHq!%9Y?,hGQ@]f{3 cXiفف&ɡ0 gVVymъPeժUt҅/t:IJJ"**tEU]2w_|< @7 C3L]Q]u0 P纮df;:y^ص^c2.^r)3Ws9B7/o)?ٹj Na&)SQlEQ=na]AAAUUNp8éif=11'h{k96Tx#>>ݻwp1RJZrrr<4FFF:%00Páv]5͚ls``iqޝZ80?0EjJ{x^!D^yH | WfAE.iSRXw NM9ߦ3뺖kt:u]םKWosZ+rADGGL@@b6U@PCCCMTɤj&ݮ+bv\#N#.3⶧r}(ڢo5#\ދ!(tzTx\.2 C7N0404r.DSUsZN;hk!!!fӯ:=!! ˎ:}Zzvv\X,TNsV0rH'((Hq8&EQ4Piz~` YޭwjGV!D;%DKϽlr:0gIDATii!!!iK.l6L2={{n}qZ#g!-)))z2etݮeggkEw:,it:5M4]5٬뺮,u3v]Q=7,.rJ^B q%wJ ,;((@+hkKRSS #fǗ\UUuEQ ɤkvCUUO, 333@#<<\ 1CO浽#>>ްZ`XV=$$ 2l6nXL=((H7L;pvCӴ\?6iK͝!+5eEYd)K.| 㗘;`ݏfp8L###0F@@atp:s7cǎjxzv]w8r7Nn\-_A?tBQ\ >Ofq'g4z9N=nvPPon=;<<\ 4{9s(tm^!ŵdEYdx--qc[*` <'Id>OO'zC#v8Pb"Yr.塻DఃέB07,[6sN{?J-dӧOEG;Gp&uRr{keۖGlE6;z=nit:է'urx}ܡTU]^_\k:\LLLLLST}ڰs=Cj$IVW{ewŽp*Su{Mִv wڻ q6QwhEP4KpjTݽW9or w~]+Uok#w׮tym#Њ^1ʯop;gT{<~jj{,kjDj3btcwpok˄:vxPEU]af#o@46j̖%բE.2b[Vm;1yv^,Vg+ஶ*+> LjlMp&QwѮstʂ*vcȉ3x"8 𾄫mHȃ{]AS`Rfۊ* } uʈD]f[/{@@z|D͡oc͇h#}G'3l}A 1*Obxc*S2pEZf{] ^= t<]4q.d~_{Y^iYӏ h]YV ]NFT=t4ɗ/_Z._fFWgcHG82&mhTb㝫g)8sykÑ.[аC; @ @ @ @ @Ht_2bUUU97p,cLD @DSq[kUD,1F˲TU$ITTՃ9L7Q*BDw,ųM,Ղ{2H$ZĒc)o,&d1FZ9h: .yY'""ph_z5MSt:k`]UM 4s@dTySmkZk5˲U u:MToooRmSˆ2{Τ,z(eIYmq/b˗/""r{{u9ͲlKoڍ2v: vs*\Dt<p8pؘap;\1Zey?ײQмh4ݮitիZEQ1Fse1\;|tӓN|SY;>OOOSZUTU \/˲EQ4M˙KXpF#Wx,~_'7Ee$, E$lux2h߿H_,©pKXeYI(Ƙ,nE.EQN"sODonn`0LIDUp8`sy^fY16(^zp-9k<{_z22x<`z}{-O!.ߴ|>/onn|1>t:M\CQ2j$HqssS|^CӧO㐜/83#"2ϟt:5?]u"equ]UEuΥQՎ֖e&:㽷IU5""gE}[ UYFۿxmc߱2htaɗA\œ>w:,aY? /e}*ҁz_><|`}zt:.wQi$,Ip[sn-]zཊu,S,wZ˲9/EEQ4ͼBׯPmiuՑԾ~www㣊:,KQU_,w$I,1C)ˆt: j}TAdqO8%w|9&/۶}iyϝsY]eמo4/_m,+mk.MS7EQf0cS`MɶrgIŀ`>I_eJ;h|SV<>>p;2$KԖeiʲ4{vWl rD6:o7~Yk5MS?},2C#{0$I\Q&2+"vHq~bLEUdYݮODgo 톡Mmp{+``յ``538>lik?LtOh=k;l63Om|f@u:Y)t0~;B[; `kGl6[mey jZd-ݮ} ߳ ۨ?lEDөy~~6!Uc/ ㎏cz2f|Kl*,G"eX@EDBD"xt:X[á, v|[\D)8V |1V vk6u!xu*[׷-8DC@[1^} _-D؁/"8w$TIENDB`RaySession-0.8.3/resources/ui/000077500000000000000000000000001356671433200162625ustar00rootroot00000000000000RaySession-0.8.3/resources/ui/abort_copy.ui000066400000000000000000000064201356671433200207640ustar00rootroot00000000000000 Dialog 0 0 249 118 Abort Copy ? Qt::Vertical 20 40 Do you want to abort current copy ? 0 Qt::Vertical 20 40 Qt::Horizontal 40 20 Abort Copy .. Cancel .. pushButtonCancel clicked() Dialog reject() 198 92 124 58 pushButtonAbort clicked() Dialog accept() 98 92 124 58 RaySession-0.8.3/resources/ui/abort_session.ui000066400000000000000000000037761356671433200215100ustar00rootroot00000000000000 AbortSession Qt::NonModal 0 0 342 72 Abort Session ? false <html><head/><body><p>Are you sure to want to abort session without saving ?</p></body></html> Qt::AlignCenter Abort .. Cancel .. pushButtonCancel clicked() AbortSession reject() 326 120 187 79 RaySession-0.8.3/resources/ui/about_raysession.ui000066400000000000000000000067041356671433200222210ustar00rootroot00000000000000 DialogAboutRaysession 0 0 813 284 About RaySession <html><head/><body><p><img src=":/128x128/raysession.png"/></p></body></html> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop <html><head/><body><p><span style=" font-weight:600;">RaySession</span></p><p>version : %s</p></body></html> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop <html><head/><body><p>Ray Session is a Qt interface for the ray-daemon.</p><p>Its goal is to manage together audio programs as Ardour, Carla, Qtractor, Non-Timeline in an unique session.</p><p>Programs just have to be compatible with the <a href="http://non.tuxfamily.org/wiki/Non%20Session%20Manager"><span style=" text-decoration: underline; color:#2980b9;">NSM</span></a> API to work with Ray Session.<br/></p><p align="right">Copyright (C) 2016-2019 houston4444</p><p><br/></p></body></html> true Qt::Horizontal QDialogButtonBox::Ok buttonBox accepted() DialogAboutRaysession accept() 248 254 157 274 buttonBox rejected() DialogAboutRaysession reject() 316 260 286 274 RaySession-0.8.3/resources/ui/add_application.ui000066400000000000000000000066511356671433200217440ustar00rootroot00000000000000 DialogAddApplication Qt::NonModal 0 0 336 407 Add Application false Filter : Qt::LeftToRight Factory true User true false true true Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok OpenSessionFilterBar QLineEdit
surclassed_widgets
buttonBox accepted() DialogAddApplication accept() 248 254 157 274 buttonBox rejected() DialogAddApplication reject() 316 260 286 274
RaySession-0.8.3/resources/ui/client_properties.ui000066400000000000000000000343511356671433200223610ustar00rootroot00000000000000 Dialog 0 0 446 418 0 0 Properties Qt::Vertical QSizePolicy::MinimumExpanding 20 0 0 0 : 0 0 Executable 0 0 executable 0 0 Arguments 0 0 : 0 0 Edit executable ... .. 48 48 QToolButton{border:none} ... .. 48 48 Qt::Vertical QSizePolicy::MinimumExpanding 20 0 0 0 Label 0 0 : 0 0 : 0 0 nsmid 0 0 : 0 0 client_name 0 0 : 0 0 Icon 0 0 Name 0 0 Client id Qt::Horizontal QSizePolicy::Fixed 3 20 Qt::Vertical QSizePolicy::MinimumExpanding 20 0 Snapshots 0 0 <html><head/><body><p>Edit here what types of files inside the client folders<br/>should be ignored in session snapshots.</p><p>Indexed files will remain so.</p></body></html> Snapshots ignored extensions : <html><head/><body><p>Edit here what types of files inside the client folders<br/>should be ignored in session snapshots.</p><p>Indexed files will remain so.</p></body></html> Qt::Vertical QSizePolicy::MinimumExpanding 20 0 Prevent to stop without recent or possible save true Qt::Vertical QSizePolicy::MinimumExpanding 20 0 Qt::Horizontal 40 20 Save Changes .. RaySession-0.8.3/resources/ui/client_slot.ui000066400000000000000000000332551356671433200211500ustar00rootroot00000000000000 ClientSlotWidget 0 0 514 133 Qt::DefaultContextMenu Frame 0 0 3 0 3 0 Qt::Horizontal QSizePolicy::Fixed 4 20 0 0 32 32 32 32 QToolButton::menu-indicator{ image: url(none.jpg);} QToolButton {border: none} ... 32 32 QToolButton::InstantPopup Qt::Horizontal QSizePolicy::Fixed 5 20 0 0 font-weight : bold ClientName Qt::Horizontal 40 20 0 0 36 20 36 24 Show GUI false GUI ../../../../.designer/backup../../../../.designer/backup 32 32 true Qt::ToolButtonTextOnly Qt::Horizontal QSizePolicy::Fixed 4 20 32 32 Launch QToolButton {border: none} ... :/scalable/breeze/media-playback-start.svg :/scalable/breeze/disabled/media-playback-start.svg:/scalable/breeze/media-playback-start.svg 32 32 false 32 32 <html><head/><body><p>Politely ask the client to stop.</p></body></html> QToolButton {border: none} ... :/scalable/breeze/media-playback-stop.svg :/scalable/breeze/disabled/media-playback-stop.svg:/scalable/breeze/media-playback-stop.svg 32 32 true 32 32 <html><head/><body><p>Kill !</p></body></html> QToolButton {border: none} ... :/scalable/breeze/media-playback-stop-red.svg:/scalable/breeze/media-playback-stop-red.svg 32 32 true 0 0 60 28 Serif 8 50 false Status stopped true Qt::AlignCenter true false 32 32 Save QToolButton {border: none} ... :/scalable/breeze/document-save.svg :/scalable/breeze/disabled/document-save.svg:/scalable/breeze/document-save.svg 32 32 Qt::Horizontal QSizePolicy::Fixed 6 20 0 0 22 22 Remove QToolButton {border: none} ... :/scalable/breeze/window-close.svg:/scalable/breeze/window-close.svg Qt::Vertical 20 40 ../../../../.designer/backup../../../../.designer/backup Save As Application Template ../../../../.designer/backup../../../../.designer/backup Properties Return To A Previous State StatusBar QLineEdit
surclassed_widgets
HideGuiButton QToolButton
surclassed_widgets
RaySession-0.8.3/resources/ui/client_trash.ui000066400000000000000000000200401356671433200212740ustar00rootroot00000000000000 Dialog 0 0 343 329 Restore Client ? client_name 0 0 : executable Label Icon QToolButton{border:none} ... .. 48 48 nsmid 0 0 : 0 0 : 0 0 Executable 0 0 : Name 0 0 : Client id false Prevent to stop without recent save true client_label Qt::Vertical 20 40 client_icon <html><head/><body><p align="center">Do you want to restore this client in the session ?<br/>You can also definitely remove the client and its files.</p></body></html> <html><head/><body><p>Remove definitely the client and its files.</p></body></html> Remove Cancel Qt::Vertical 20 40 <html><head/><body><p>Restore this client in current session.</p></body></html> Restore Client .. pushButtonCancel clicked() Dialog reject() 337 176 192 99 pushButtonRestore clicked() Dialog accept() 226 176 192 99 RaySession-0.8.3/resources/ui/daemon_url.ui000066400000000000000000000042111356671433200207440ustar00rootroot00000000000000 Dialog 0 0 289 146 Daemon URL Qt::AlignCenter ray-daemon url to connect to : Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft osc.udp://192.168.XX.XX:1234/ true Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() Dialog accept() 248 254 157 274 buttonBox rejected() Dialog reject() 316 260 286 274 RaySession-0.8.3/resources/ui/donations.ui000066400000000000000000000055571356671433200206330ustar00rootroot00000000000000 Dialog 0 0 427 320 Donations <html><head/><body><p>Hi !</p><p>it seems that you appreciate RaySession, that is already a good new.<br/>This software is free as in Speech and as in Beer,<br/>but it has required and still takes time.</p><p>Make a donation (even small) is a simple way to say &quot;Thank you&quot;.<br/>You can donate <a href="https://liberapay.com/Houston4444"><span style=" text-decoration: underline; color:#2980b9;">here</span></a>.</p><p>If ever you donate nothing,<br/>this program will continue to work without limits of functionnality,<br/>without limit of duration, and even without insulting you ;) .</p></body></html> true Qt::Vertical 20 40 Do not show this message again false Qt::Horizontal QDialogButtonBox::Ok buttonBox accepted() Dialog accept() 248 254 157 274 buttonBox rejected() Dialog reject() 316 260 286 274 RaySession-0.8.3/resources/ui/edit_executable.ui000066400000000000000000000102121356671433200217430ustar00rootroot00000000000000 Dialog 0 0 411 338 Edit Executable <html><head/><body><p>Edit executable is strongly discouraged !<br/>It can be useful if you use many versions of a same software.<br/>Change it only if you are sure of what you are doing.</p></body></html> Qt::Vertical 20 40 Executable : Qt::Vertical 20 40 <html><head/><body><p>Arguments are supposed to not be supported by NSM protocol.<br/>In some cases it can works, but no warranty !</p></body></html> Qt::Vertical 20 40 Arguments : Qt::Vertical 20 40 <html><head/><body><p align="right"><span style=" font-style:italic;">You will have to save changes in Properties window<br/>and restart the client to apply these changes.</span></p></body></html> Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() Dialog accept() 248 254 157 274 buttonBox rejected() Dialog reject() 316 260 286 274 RaySession-0.8.3/resources/ui/error_dialog.ui000066400000000000000000000030021356671433200212640ustar00rootroot00000000000000 Dialog 0 0 102 70 Error ! Error Text Qt::Horizontal QDialogButtonBox::Cancel buttonBox accepted() Dialog accept() 248 254 157 274 buttonBox rejected() Dialog reject() 316 260 286 274 RaySession-0.8.3/resources/ui/list_snapshots.ui000066400000000000000000000102011356671433200216700ustar00rootroot00000000000000 Dialog Qt::NonModal 0 0 399 338 Snapshots Manager false <html><head/><body><p>Select from the list below the snapshot to be recalled<br/>to return to a past state of the session :</p></body></html> Qt::Vertical 20 40 Qt::Horizontal 40 20 Take a snapshot now ! .. Qt::Horizontal 40 20 false false true 1 <html><head/><body><p>Make a snapshot at each session save.</p></body></html> Auto snapshot at save for this session true Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() Dialog accept() 248 254 157 274 buttonBox rejected() Dialog reject() 316 260 286 274 RaySession-0.8.3/resources/ui/new_executable.ui000066400000000000000000000141671356671433200216240ustar00rootroot00000000000000 DialogNewExecutable Qt::NonModal 0 0 240 310 New Executable Client false Executable : <html><head/><body><p>If program is not compatible with the NSM API, </p><p>you should launch it in proxy to define a config file !</p></body></html> Run via Proxy <html><head/><body><p>Show more options ...</p></body></html> ... .. Advanced Prefix : Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Prefix Mode : Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Client ID : Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter <html><head/><body><p>The Prefix Mode determines the pattern of the client files.</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Session Name (Default) :<br/>The client files names start with the session name.</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Client Name :<br/>The client files names start with the client name given by the client itself<br/>(as in Non Session Manager).</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Custom :<br/>The client files names start with the prefix written below.</li></ul></body></html> <html><head/><body><p>Use it If you want to add to session a file made outside from a session.</p><p>If you let this line empty, the client_id will be automaticely generated.</p></body></html> Qt::Vertical 20 40 true Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() DialogNewExecutable accept() 248 254 157 274 buttonBox rejected() DialogNewExecutable reject() 316 260 286 274 RaySession-0.8.3/resources/ui/new_session.ui000066400000000000000000000133451356671433200211630ustar00rootroot00000000000000 DialogNewSession Qt::NonModal 0 0 228 279 New Session false 0 <html><head/><body><p><span style=" font-size:9pt; font-weight:600;">Sessions Folder :</span></p></body></html> Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter font-style : italic /home/user/Ray Sessions Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Folder .. Qt::Vertical 20 40 0 0 New Session Name : 0 0 Sub Folder : 0 0 Template : Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Qt::Vertical 20 40 true Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() DialogNewSession accept() 248 254 157 274 buttonBox rejected() DialogNewSession reject() 316 260 286 274 RaySession-0.8.3/resources/ui/nsm_open_info.ui000066400000000000000000000040151356671433200214520ustar00rootroot00000000000000 Dialog 0 0 500 237 Opening NSM Session <html><head/><body><p>You are opening a session created by Non Session Manager.</p><p><br/>RaySession will open it, but changes won't be applied to NSM file (session.nsm).</p><p>Also, Once you start to work with RaySession, you have to continue with it !</p></body></html> Don't Show this Message Again Qt::Horizontal QDialogButtonBox::Ok buttonBox accepted() Dialog accept() 248 254 157 274 buttonBox rejected() Dialog reject() 316 260 286 274 RaySession-0.8.3/resources/ui/open_session.ui000066400000000000000000000111201356671433200213200ustar00rootroot00000000000000 DialogOpenSession Qt::NonModal 0 0 290 379 Open Session false 0 <html><head/><body><p><span style=" font-size:9pt; font-weight:600;">Sessions Folder :</span></p></body></html> Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter font-style : italic /home/user/Ray Sessions Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Folder .. Qt::Vertical QSizePolicy::Fixed 20 10 Filter : false false true 1 true Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok OpenSessionFilterBar QLineEdit
surclassed_widgets
buttonBox accepted() DialogOpenSession accept() 248 254 157 274 buttonBox rejected() DialogOpenSession reject() 316 260 286 274
RaySession-0.8.3/resources/ui/proxy_copy.ui000066400000000000000000000060121356671433200210330ustar00rootroot00000000000000 Dialog 0 0 459 145 Copy File ? file is not in proxy directory. <html><head/><body><p>Do you want to copy this file to proxy directory or to use directly this file ?</p></body></html> Qt::Horizontal 40 20 Copy file and rename it with session name Copy And Rename File Copy File .. Use This File .. pushButtonCopy clicked() Dialog accept() 288 118 229 72 pushButtonUseThisFile clicked() Dialog reject() 395 118 229 72 RaySession-0.8.3/resources/ui/proxy_gui.ui000066400000000000000000000352101356671433200206470ustar00rootroot00000000000000 MainWindow 0 0 456 370 Ray Proxy <html><head/><body><p>Command-line options are incompatible with robust session management for a variety of reasons, so the NSM server does not support them directly.</p><p>Some audio programs doesn't have NSM implementation yet, but they can have a LASH/LADISH implementation, and we can use it for the save signal.</p><p>Be warned that referring to files outside of the session directory will impair your ability to reliably archive and transport sessions. <br/><br/>Patching the program to use NSM natively will result in a better experience. </p></body></html> <html><head/><body><p>The program will be started with its current directory being a uniquely named directory under the current session directory. </p><p>It is recommended that you only refer to files in the current directory.</p></body></html> <html><head/><body><p>The program will be started with its current directory being a uniquely named directory under the current session directory. </p><p>It is recommended that you only refer to files in the current directory.</p></body></html> Config File : Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter <html><head/><body><p>Command-line options are incompatible with robust session management for a variety of reasons, so the NSM server does not support them directly.</p><p>Some audio programs doesn't have NSM implementation yet, but they can have a LASH/LADISH implementation, and we can use it for the save signal.</p><p>Be warned that referring to files outside of the session directory will impair your ability to reliably archive and transport sessions. <br/><br/>Patching the program to use NSM natively will result in a better experience. </p></body></html> Executable : Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter <html><head/><body><p>Command-line options are incompatible with robust session management for a variety of reasons, so the NSM server does not support them directly.</p><p>Some audio programs doesn't have NSM implementation yet, but they can have a LASH/LADISH implementation, and we can use it for the save signal.</p><p>Be warned that referring to files outside of the session directory will impair your ability to reliably archive and transport sessions. <br/><br/>Patching the program to use NSM natively will result in a better experience. </p></body></html> <html><head/><body><p>The program will be started with its current directory being a uniquely named directory under the current session directory. </p><p>It is recommended that you only refer to files in the current directory.</p></body></html> Browse .. Qt::ToolButtonTextBesideIcon <html><head/><body><p>The environment variables $NSM_CLIENT_ID and $RAY_SESSION_NAME will contain the unique client ID (suitable for use as e.g. a JACK client name) and the display name for the session, respectively.<br/>The variable $CONFIG_FILE will contain the name of the config file selected above.</p></body></html> Arguments : Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter <html><head/><body><p>The environment variables $NSM_CLIENT_ID and $RAY_SESSION_NAME will contain the unique client ID (suitable for use as e.g. a JACK client name) and the display name for the session, respectively.<br/>The variable $CONFIG_FILE will contain the name of the config file selected above.</p></body></html> Qt::Vertical 20 40 Wait window before reply (needs wmctrl) <html><head/><body><p>Some (very few) programs may respond to a specific Unix signal by somehow saving their state.</p><p>If 'Save Signal' is set to something other than 'None', then Ray Proxy will deliver the specified signal to the proxied process upon an NSM 'Save' event.</p><p>If program is compatible with LASH/LADISH, save signal is SIGUSR1.</p><p>Most programs will treat these signals just like SIGTERM and die.</p><p>You have been warned.</p></body></html> 0 0 <html><head/><body><p>Most programs will shutdown gracefully when sent a SIGTERM or SIGINT signal.<br/>It's impossible to know which signal a specific program will respond to.<br/>A unhandled signal will simply kill the process, and may cause problems with the audio subsystem (e.g. JACK).<br/>Check the program's documentation or source code to determine which signal to use to stop it gracefully.</p></body></html> Stop Signal : Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Test .. Qt::ToolButtonTextBesideIcon <html><head/><body><p>Most programs will shutdown gracefully when sent a SIGTERM or SIGINT signal.<br/>It's impossible to know which signal a specific program will respond to.<br/>A unhandled signal will simply kill the process, and may cause problems with the audio subsystem (e.g. JACK).<br/>Check the program's documentation or source code to determine which signal to use to stop it gracefully.</p></body></html> 0 0 <html><head/><body><p>Some (very few) programs may respond to a specific Unix signal by somehow saving their state.</p><p>If 'Save Signal' is set to something other than 'None', then Ray Proxy will deliver the specified signal to the proxied process upon an NSM 'Save' event.</p><p>If program is compatible with LASH/LADISH, save signal is SIGUSR1.</p><p>Most programs will treat these signals just like SIGTERM and die.</p><p>You have been warned.</p></body></html> Save Signal : Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 No Save Level : Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 1 0 <html><head/><body><p>If this proxy has no save signal,<br/>it can sends to session manager this information.</p><p><ul><li>0 - this information is not sent.</li><li>1 - this information is sent, session manager will ask user to close programs himself at session unload.</li><li>2 - this information is sent, session manager can try to close window gracefully before to ask user to close programs himself at session unload. Good choice if the program displays a dialog if changes are not saved.</li></ul></p></body></html> color: red executable failed to launch ! Qt::AlignCenter Qt::Horizontal 40 20 Launch .. Stop .. RaySession-0.8.3/resources/ui/quit_app.ui000066400000000000000000000043521356671433200204470ustar00rootroot00000000000000 DialogQuitApp Qt::NonModal 0 0 376 159 Quit RaySession false <p>Session <bold>%s</bold> is running.</p><p>RaySession will be closed.</p><p>Do you want to save session ? Qt::AlignCenter Save && Quit Quit Without Saving Cancel pushButtonCancel clicked() DialogQuitApp reject() 326 120 187 79 RaySession-0.8.3/resources/ui/raysession.ui000066400000000000000000001212311356671433200210200ustar00rootroot00000000000000 MainWindow 0 0 448 570 Qt::CustomContextMenu RaySession true QMainWindow::AllowNestedDocks|QMainWindow::AllowTabbedDocks|QMainWindow::AnimatedDocks|QMainWindow::VerticalTabs true 6 QLayout::SetDefaultConstraint 0 2 0 4 0 QLayout::SetMinAndMaxSize false SessionFrame{background-color: rgba(127, 127, 127, 35);border-radius:4px} 3 3 3 3 2 0 QToolButton{border: none} ... :/scalable/breeze/xml-node-duplicate.svg:/scalable/breeze/xml-node-duplicate.svg 22 22 <html><head/><body><p>Save Session as Template</p></body></html> QToolButton{border: none} ... :/scalable/breeze/document-save-as-template.svg:/scalable/breeze/document-save-as-template.svg 22 22 0 0 0 -1 QToolButton{border: none} ... ../../../.designer/backup../../../.designer/backup 22 22 0 0 22 22 22 22 <html><head/><body><p>Save &amp; Close Session</p></body></html> QToolButton::menu-indicator{ image: url(none.jpg);} QToolButton {border: none} ... ../../../.designer/backup../../../.designer/backup 22 22 QToolButton::menu-indicator{ image: url(none.jpg);} QToolButton {border: none} ... .. Qt::Vertical QSizePolicy::Fixed 20 2 2 0 0 Qt::Horizontal QSizePolicy::Fixed 4 20 false 32 32 Open Session Folder QToolButton{border: none} ... ../../../.designer/backup../../../.designer/backup 32 32 Qt::Horizontal QSizePolicy::Fixed 6 20 Qt::Horizontal 40 20 Favorite Applications QToolButton::menu-indicator{ image: url(none.jpg);} QToolButton {border: none} ... :/scalable/breeze/star-yellow.svg:/scalable/breeze/star-yellow.svg 0 0 3200 32 Run Command QToolButton{margin:0px} Application ../../../.designer/backup../../../.designer/backup 22 22 Qt::ToolButtonTextBesideIcon Qt::Horizontal 40 20 0 0 3200 32 Run Command QToolButton{margin:0px} Executable ../../../.designer/backup../../../.designer/backup 22 22 Qt::ToolButtonTextBesideIcon Qt::Horizontal 40 20 Qt::Horizontal QSizePolicy::Fixed 6 20 32 32 <html><head/><body><p>Go back to a previous state of the session.</p></body></html> QToolButton { border: none} Snapshots :/scalable/breeze/document-save.svg:/scalable/breeze/document-save.svg 16 16 60 0 60 16777215 Qt::NoContextMenu Server Status off Qt::AlignCenter true 32 32 Save Session QToolButton { border: none} Save Session :/scalable/breeze/document-save.svg:/scalable/breeze/document-save.svg 32 32 Qt::Horizontal QSizePolicy::Fixed 7 20 <html><head/><body><p>Trash</p><p>You will find here removed clients.</p></body></html> QToolButton::menu-indicator{ image: url(none.jpg);} QToolButton {border: none} ... ../../../.designer/backup../../../.designer/backup true QFrame{border:none} true QAbstractItemView::InternalMove QAbstractItemView::SingleSelection true 80 Qt::NoContextMenu false &Messages 8 0 30 QPlainTextEdit { font-size: 12px} true 0 0 448 30 Fi&le &Session &View &Help Add Options 0 0 Qt::NoContextMenu toolBar false Qt::ToolButtonTextBesideIcon false TopToolBarArea false true ../../../.designer/backup../../../.designer/backup &Show Messages Ctrl+L false ../../../.designer/backup../../../.designer/backup &Save Ctrl+S Qt::ApplicationShortcut ../../../.designer/backup../../../.designer/backup &Open Session Ctrl+O ../../../.designer/backup../../../.designer/backup &New Session Ctrl+N ../../../.designer/backup../../../.designer/backup &Quit Ctrl+Q :/48x48/raysession.png:/48x48/raysession.png &About Ray Session true true ../../../.designer/backup../../../.designer/backup Show &Menu Bar Ctrl+M ../../../.designer/backup../../../.designer/backup Control Control false :/scalable/breeze/window-close.svg :/scalable/breeze/disabled/window-close.svg:/scalable/breeze/window-close.svg &Close Save and Close Session Ctrl+W false :/scalable/breeze/list-remove.svg :/scalable/breeze/disabled/list-remove.svg :/scalable/breeze/list-remove.svg:/scalable/breeze/list-remove.svg &Abort Abort Current Session Ctrl+Shift+W true ../../../.designer/backup../../../.designer/backup &Open Session Folder Open session folder with file manager true true ../../../.designer/backup../../../.designer/backup &Keep focus while opening clients Ctrl+F About &Qt false :/scalable/breeze/xml-node-duplicate.svg:/scalable/breeze/xml-node-duplicate.svg &Duplicate <html><head/><body><p>Duplicate the current session</p></body></html> Ctrl+D ../../../.designer/backup../../../.designer/backup &Executable Add executable to current session Ctrl+E Qt::WindowShortcut ../../../.designer/backup../../../.designer/backup Sa&ve As Template Save current session as template ../../../.designer/backup../../../.designer/backup &Application Add application to current session Ctrl+A false .. &Rename true true .. &Save All From Saved Client <html><head/><body><p>Some clients (Ardour, Qtractor...) sends their saved state to RaySession.</p><p>When this option is checked, the session is saved when a client sends that it has been saved.</p><p>For example, a Ctrl-S in Ardour will also save all the current Ray Session.</p></body></html> true true .. &Provide bookmarks for session folder true :/scalable/breeze/view-list-icons.svg:/scalable/breeze/view-list-icons.svg &Desktops Memory (requires wmctrl) false .. Return To A &Previous State (requires git) true true .. Auto Snapshot At Save (requires git) Auto Snapshot at Save (requires git) Donate SessionFrame QFrame
surclassed_widgets
1
ListWidgetClients QListWidget
list_widget_clients
StatusBarNegativ QLineEdit
surclassed_widgets
StackedSessionName QStackedWidget
surclassed_widgets
1
actionToggleShowMessages toggled(bool) dockWidgetMessages setVisible(bool) -1 -1 219 446 dockWidgetMessages visibilityChanged(bool) actionToggleShowMessages setChecked(bool) 219 446 -1 -1 actionShowMenuBar toggled(bool) menuBar setVisible(bool) -1 -1 251 12
RaySession-0.8.3/resources/ui/remove_template.ui000066400000000000000000000044541356671433200220200ustar00rootroot00000000000000 Dialog Qt::NonModal 0 0 342 72 Remove Template ? false <html><head/><body><p>Are you sure to want to remove this template ?</p></body></html> Qt::AlignCenter Remove .. Cancel .. pushButtonCancel clicked() Dialog reject() 326 120 187 79 pushButtonRemove clicked() Dialog accept() 87 47 170 35 RaySession-0.8.3/resources/ui/save_template_session.ui000066400000000000000000000074621356671433200232260ustar00rootroot00000000000000 DialogSaveTemplateSession Qt::NonModal 0 0 371 112 New Template false Qt::Vertical 20 40 0 0 Session Template Name : 0 0 Qt::Vertical 20 40 QLayout::SetFixedSize Qt::Horizontal QSizePolicy::Expanding 40 20 0 0 Create Template 0 0 Cancel pushButtonCancel clicked() DialogSaveTemplateSession reject() 240 88 144 55 RaySession-0.8.3/resources/ui/snapshot_and_save.ui000066400000000000000000000047041356671433200223250ustar00rootroot00000000000000 DialogQuitApp Qt::NonModal 0 0 401 159 Quit RaySession false <html><head/><body><p>Do you want to save the session before snapshot ?</p><p>Save is recommended,<br/>unless you made unwanted changes since the last session save.</p></body></html> Qt::AlignCenter Save && Snapshot .. Snapshot .. Cancel .. pushButtonCancel clicked() DialogQuitApp reject() 326 120 187 79 RaySession-0.8.3/resources/ui/snapshot_name.ui000066400000000000000000000066321356671433200214670ustar00rootroot00000000000000 Dialog 0 0 401 198 Name Snapshot Qt::Vertical 20 40 Snapshot Name : Qt::Vertical 20 40 <html><head/><body><p>You can save the session before the snapshot.</p><p>Save is recommended,<br/>unless you made unwanted changes since the last session save.</p></body></html> Qt::AlignCenter Save && Snapshot .. Snapshot Only .. Cancel .. pushButtonCancel clicked() Dialog reject() 334 173 200 98 pushButtonSnapshot clicked() Dialog accept() 210 173 200 98 RaySession-0.8.3/resources/ui/snapshot_progress.ui000066400000000000000000000070721356671433200224120ustar00rootroot00000000000000 Dialog 0 0 357 202 Abort Copy ? Qt::Vertical 20 40 <html><head/><body><p>Snapshot process seems to be long.<br/>Maybe your session's folder contains too many new files<br/>whose extension is not ignored.</p><p>You can abort this snapshot,<br/>it will de-activate snapshots for this session.</p></body></html> 0 Qt::Vertical 20 40 Qt::Horizontal 40 20 Abort Snapshot .. Cancel .. pushButtonCancel clicked() Dialog reject() 198 92 124 58 pushButtonAbort clicked() Dialog accept() 98 92 124 58 RaySession-0.8.3/resources/ui/snapshots_info.ui000066400000000000000000000051501356671433200216570ustar00rootroot00000000000000 Dialog 0 0 403 300 Snapshots Informations <html><head/><body><p>Snapshots are NOT backups !!!</p><p>Besides, It's not overrated to copy your session folder elsewhere<br/>before to ask a previous snapshot.</p><p>Snapshots ignore audio files and other big files (&gt;50Mb),<br/>else snapshot process would be too long, <br/>and the session folder size would be too big.</p><p>That being said, you can decide that your work in the last hours<br/>was not a good idea and return to a previous snapshot !</p></body></html> Qt::Vertical 20 40 Do not show this message again true Qt::Horizontal QDialogButtonBox::Ok buttonBox accepted() Dialog accept() 248 254 157 274 buttonBox rejected() Dialog reject() 316 260 286 274 RaySession-0.8.3/resources/ui/stop_client.ui000066400000000000000000000073251356671433200211530ustar00rootroot00000000000000 Dialog 0 0 315 159 Stop Client ? Qt::Vertical 20 40 <html><head/><body><p><span style=" font-weight:600;">%s</span> contains unsaved changes. </p><p>Do you really want to stop it ?</p></body></html> Qt::AlignCenter Qt::Vertical 20 40 Don't prevent to stop this client again ! Qt::Horizontal 40 20 Save && Stop .. Just Stop .. Cancel .. pushButtonClancel clicked() Dialog reject() 265 89 157 56 pushButtonStop clicked() Dialog accept() 172 89 157 56 RaySession-0.8.3/resources/ui/stop_client_no_save.ui000066400000000000000000000070131356671433200226570ustar00rootroot00000000000000 Dialog 0 0 400 172 Stop Client ? Qt::Vertical 20 40 <html><head/><body><p>We have no possibility to save the client <span style=" font-weight:600;">%s</span>.</p><p>For this reason, it's preferable that you close yourself this client,<br/>probably by closing its window, saving its changes or not.</p></body></html> Qt::AlignCenter Qt::Vertical 20 40 Don't prevent to stop this client again (discouraged) Qt::Horizontal 40 20 Stop Anyway .. Cancel .. pushButtonCancel clicked() Dialog reject() 265 89 157 56 pushButtonStop clicked() Dialog accept() 172 89 157 56 RaySession-0.8.3/resources/ui/template_slot.ui000066400000000000000000000052361356671433200215030ustar00rootroot00000000000000 Frame 0 0 369 33 Frame 4 2 4 2 QToolButton {border: none} ... 22 22 Template Name Qt::Horizontal 40 20 QToolButton::menu-indicator{ image: url(none.jpg);} QToolButton {border: none} ... :/scalable/breeze/im-user.svg:/scalable/breeze/im-user.svg QToolButton::InstantPopup QToolButton {border: none} ... :/scalable/breeze/draw-star.svg:/scalable/breeze/draw-star.svg RaySession-0.8.3/resources/ui/waiting_close_user.ui000066400000000000000000000117771356671433200225230ustar00rootroot00000000000000 Dialog 0 0 353 272 Close clients yourself ! <html><head/><body><p align="center">Some active clients do not offer any save possibility !</p><p align="center">Therefore, it is best that you close these clients yourself,<br/>probably by closing their windows and saving changes.</p><p align="center"><span style=" font-weight:600;">Please close yourself the programs with this save icon:</span></p></body></html> Qt::Horizontal 40 20 :/scalable/breeze/document-nosave.svg Qt::Horizontal 40 20 Qt::Vertical 20 40 <html><head/><body><p>You've got 2 minutes !<br/><span style=" font-style:italic;">You can do it without closing this dialog window.</span></p></body></html> Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::Vertical 20 40 Do not show again Qt::Horizontal 40 20 Undo .. Ok .. Skip .. pushButtonOk clicked() Dialog accept() 213 239 176 131 RaySession-0.8.3/session_templates/000077500000000000000000000000001356671433200173745ustar00rootroot00000000000000RaySession-0.8.3/session_templates/withJACKPATCH/000077500000000000000000000000001356671433200215605ustar00rootroot00000000000000RaySession-0.8.3/session_templates/withJACKPATCH/raysession.xml000066400000000000000000000004751356671433200245070ustar00rootroot00000000000000 RaySession-0.8.3/snapshots explain000066400000000000000000000040071356671433200172220ustar00rootroot00000000000000Snapshots Explains If git is present on system, a new button will appears on RaySession at the left of the server status bar, with a "rewind" icon. Now, the principle: Snapshots ARE NOT backups. Snapshots will ignore many types of files with their extension (including .wav, .flac ...), and for security files bigger than 50Mb. This limit of 50Mb is not adjustable for the moment, maybe it should (per session? globally ?), I consider for now it's useless, maybe I am wrong. If the auto-snapshot option is checked, a snapshot will be taken each time the session is saved, and you will see the server status saying 'snapshot' after 'save'. If at one point, user decides that his last works on the session were useless, he can rewind the session to a previous snapshot. For this, he will click on the "rewind" button (at the left of the server status bar) and follow instructions. If a snapshot is long to take (>2s), a dialog window with a progress bar will appears. User can safely abort snapshot, and then auto-snapshot will be disabled for this session. If taking a snapshot is long, there is certainly a client software which uses many files with an unignored extension. If this file extension is globally not used by another software, then this extension should be added to the list of extensions globally ignored. There is also the possibility to manage ignored and unignored extensions per client, in client properties window. The edit line "Snapshots Ignored Extensions" contains all ignored extensions, including all globally ignored extensions. By default, this line will only contains globally ignored extensions. You can also return to a previous snapshot one client only in client context menu. For your information, snapshots are saved in the session directory in the hidden folder .ray-snapshots which is the same as would be a .git folder in a git project. Obviously, duplicate or save as template a session doesn't copy session's historization. It would not make sense. Auto-snapshot is enabled by default (if git is present). RaySession-0.8.3/src/000077500000000000000000000000001356671433200144225ustar00rootroot00000000000000RaySession-0.8.3/src/bin/000077500000000000000000000000001356671433200151725ustar00rootroot00000000000000RaySession-0.8.3/src/bin/ray-daemon000077700000000000000000000000001356671433200232302../daemon/ray-daemon.pyustar00rootroot00000000000000RaySession-0.8.3/src/bin/ray-jackpatch000077700000000000000000000000001356671433200265302../clients/jackpatch/ray-jackpatch.pyustar00rootroot00000000000000RaySession-0.8.3/src/bin/ray-listsnapshots000077500000000000000000000004321356671433200206260ustar00rootroot00000000000000#!/bin/bash session_path="$1" [ "$session_path" ] || exit 1 # check git presence on system which git > /dev/null || exit 1 # check .git dir. exit if not exists [ -d "$session_path/.git" ] || exit 1 all_tags=$(git -C "$session_path" tag 2>/dev/null) || exit 1 echo "$all_tags" RaySession-0.8.3/src/bin/ray-network000077500000000000000000000000561356671433200174030ustar00rootroot00000000000000#!/bin/bash exec raysession --out-daemon "$@" RaySession-0.8.3/src/bin/ray-proxy000077700000000000000000000000001356671433200244432../clients/proxy/ray-proxy.pyustar00rootroot00000000000000RaySession-0.8.3/src/bin/ray-snapshot000077500000000000000000000020121356671433200175430ustar00rootroot00000000000000#!/bin/bash session_path="$1" date="$2" [ "$3" ] && tagname="$3" || tagname="ray" # check arguments exists [ "$session_path" ] && [ "$date" ] || exit 1 # check git presence on system which git > /dev/null || exit 1 # check .git, if it's not a dir, exit. [ -f "$session_path/.git" ] && exit 1 # if dir doesn't exists, init git. [ -d "$session_path/.git" ] || git -C "$session_path" init ignored_extensions="wav flac ogg mp3 mp4 avi peak midi mid" contents="" # prepare exclude of unwanted files for extension in $ignored_extensions; do contents+="*.$extension " done # add to exclusion too big files big_files=$(find "$session_path" -size +50M) contents+="$big_files" # write exclude file # echo "$contents" > "$session_path/.git/info/exclude" # add all modified files to git git -C "$session_path" add -A remove_last_tag=false # make commit if git -C "$session_path" commit -m "$date";then # if commit works with modifications, then tag. git -C "$session_path" tag -a "$date" -m "$tagname" fi RaySession-0.8.3/src/bin/ray_control000077700000000000000000000000001356671433200240262../daemon/ray_control.pyustar00rootroot00000000000000RaySession-0.8.3/src/bin/ray_git000077500000000000000000000001151356671433200165530ustar00rootroot00000000000000#!/bin/sh exec git --work-tree "$PWD" --git-dir "$PWD/.ray-snapshots" "$@" RaySession-0.8.3/src/bin/raysession000077700000000000000000000000001356671433200230572../gui/raysession.pyustar00rootroot00000000000000RaySession-0.8.3/src/bin/save_window.sh000077500000000000000000000007241356671433200200610ustar00rootroot00000000000000#!/bin/bash #This script could be used in a future version for saving incompatible clients via fake Ctrl+s on window # wclass="$1" # filename="$2" # windowid=$(xdotool search --class "$wclass" search --name "$filename") # # [ "$windowid" ] || exit # # windowid="$1" currentWin=$(xdotool getactivewindow) for windowid in "$@";do xdotool windowactivate "$windowid" xdotool key ctrl+s done [ "$currentWin" ] && xdotool windowactivate "$currentWin" exit 0 RaySession-0.8.3/src/bin/sooperlooper_lash000077700000000000000000000000001356671433200307352../clients/sooperlooper/sooperlooper_lashustar00rootroot00000000000000RaySession-0.8.3/src/bin/sooperlooper_nsm000077700000000000000000000000001356671433200311002../clients/sooperlooper/sooperlooper_nsm.pyustar00rootroot00000000000000RaySession-0.8.3/src/clients/000077500000000000000000000000001356671433200160635ustar00rootroot00000000000000RaySession-0.8.3/src/clients/jackpatch/000077500000000000000000000000001356671433200200135ustar00rootroot00000000000000RaySession-0.8.3/src/clients/jackpatch/jacklib.py000077700000000000000000000000001356671433200256242../../shared/jacklib.pyustar00rootroot00000000000000RaySession-0.8.3/src/clients/jackpatch/nsm_client.py000077700000000000000000000000001356671433200271142../../shared/nsm_client.pyustar00rootroot00000000000000RaySession-0.8.3/src/clients/jackpatch/ray-jackpatch.py000077500000000000000000000362761356671433200231270ustar00rootroot00000000000000#!/usr/bin/python3 -u from PyQt5.QtCore import QCoreApplication, QObject, QTimer, pyqtSignal from PyQt5.QtXml import QDomDocument import sys, signal, os, time #from shared import * import jacklib import nsm_client import ray connection_list = [] saved_connections = [] port_list = [] PORT_MODE_OUTPUT = 0 PORT_MODE_INPUT = 1 PORT_MODE_NULL = 2 PORT_TYPE_AUDIO = 0 PORT_TYPE_MIDI = 1 PORT_TYPE_NULL = 2 file_path = "" is_dirty = False pending_connection = False def signalHandler(sig, frame): if sig in (signal.SIGINT, signal.SIGTERM): app.quit() class JackPort(object): #is_new is used to prevent reconnections when a disconnection has not been saved and one new port append. slots = ['id', 'name', 'mode', 'type', 'is_new'] class ConnectTimer(QObject): def __init__(self): self.timer = QTimer() self.timer.setInterval(200) self.timer.setSingleShot(True) self.timer.timeout.connect(makeMayConnections) def start(self): self.timer.start() def portExists(name, mode, port_type): for port in port_list: if port.name == name and port.mode == mode and port.type == port_type: return True return False def setDirtyClean(): global is_dirty is_dirty = False NSMServer.sendDirtyState(False) def timerDirtyFinish(): global is_dirty if is_dirty: return if isDirtyNow(): is_dirty = True NSMServer.sendDirtyState(True) def isDirtyNow(): for connection in connection_list: if not connection in saved_connections: return True output_ports = [] input_ports = [] for port in port_list: if port.mode == PORT_MODE_OUTPUT: output_ports.append(port.name) elif port.mode == PORT_MODE_INPUT: input_ports.append(port.name) for connection in saved_connections: if connection in connection_list: continue if connection[0] in output_ports and connection[1] in input_ports: return True return False class DirtyChecker(QObject): timer = QTimer() def __init__(self): self.timer.setInterval(500) self.timer.setSingleShot(True) self.timer.timeout.connect(timerDirtyFinish) def start(self): self.timer.start() def readyToConnect(): pass class Signaler(nsm_client.NSMSignaler): port_added = pyqtSignal(str, int, int) port_removed = pyqtSignal(str, int, int) port_renamed = pyqtSignal(str, str, int, int) connection_added = pyqtSignal(str, str) connection_removed = pyqtSignal(str, str) def JackShutdownCallback(arg=None): app.quit() return 0 def JackPortRegistrationCallback(portId, registerYesNo, arg=None): portPtr = jacklib.port_by_id(jack_client, portId) portFlags = jacklib.port_flags(portPtr) port_name = str(jacklib.port_name(portPtr), encoding="utf-8") port_mode = PORT_MODE_NULL if portFlags & jacklib.JackPortIsInput: port_mode = PORT_MODE_INPUT elif portFlags & jacklib.JackPortIsOutput: port_mode = PORT_MODE_OUTPUT port_type = PORT_TYPE_NULL portTypeStr = str(jacklib.port_type(portPtr), encoding="utf-8") if portTypeStr == jacklib.JACK_DEFAULT_AUDIO_TYPE: port_type = PORT_TYPE_AUDIO elif portTypeStr == jacklib.JACK_DEFAULT_MIDI_TYPE: port_type = PORT_TYPE_MIDI if registerYesNo: signaler.port_added.emit(port_name, port_mode, port_type) else: signaler.port_removed.emit(port_name, port_mode, port_type) return 0 def JackPortRenameCallback(portId, oldName, newName, arg=None): portPtr = jacklib.port_by_id(jack_client, portId) portFlags = jacklib.port_flags(portPtr) port_mode = PORT_MODE_NULL if portFlags & jacklib.JackPortIsInput: port_mode = PORT_MODE_INPUT elif portFlags & jacklib.JackPortIsOutput: port_mode = PORT_MODE_OUTPUT port_type = PORT_TYPE_NULL portTypeStr = str(jacklib.port_type(portPtr), encoding="utf-8") if portTypeStr == jacklib.JACK_DEFAULT_AUDIO_TYPE: port_type = PORT_TYPE_AUDIO elif portTypeStr == jacklib.JACK_DEFAULT_MIDI_TYPE: port_type = PORT_TYPE_MIDI signaler.port_renamed.emit(str(oldName, encoding='utf-8'), str(newName, encoding='utf-8'), port_mode, port_type) return 0 def JackPortConnectCallback(port_id_A, port_id_B, connect_yesno, arg=None): port_ptr_A = jacklib.port_by_id(jack_client, port_id_A) port_ptr_B = jacklib.port_by_id(jack_client, port_id_B) port_str_A = str(jacklib.port_name(port_ptr_A), encoding="utf-8") port_str_B = str(jacklib.port_name(port_ptr_B), encoding="utf-8") if connect_yesno: signaler.connection_added.emit(port_str_A, port_str_B) else: signaler.connection_removed.emit(port_str_A, port_str_B) return 0 def portAdded(port_name, port_mode, port_type): port = JackPort() port.name = port_name port.mode = port_mode port.type = port_type port.is_new = True port_list.append(port) connect_timer.start() def portRemoved(port_name, port_mode, port_type): for i in range(len(port_list)): port = port_list[i] if (port.name == port_name and port.mode == port_mode and port.type == port_type): break else: return port_list.__delitem__(i) def portRenamed(old_name, new_name, port_mode, port_type): for port in port_list: if (port.name == old_name and port.mode == port_mode and port.type == port_type): port.name = new_name port.is_new = True connect_timer.start() break def connectionAdded(port_str_A, port_str_B): connection_list.append((port_str_A, port_str_B)) if pending_connection: makeMayConnections() if not (port_str_A, port_str_B) in saved_connections: dirty_checker.start() def connectionRemoved(port_str_A, port_str_B): for i in range(len(connection_list)): if (connection_list[i][0] == port_str_A and connection_list[i][1] == port_str_B): connection_list.__delitem__(i) break dirty_checker.start() def makeAllSavedConnections(port): if port.mode == PORT_MODE_OUTPUT: connectAllInputs(port) elif port.mode == PORT_MODE_INPUT: connectAllOutputs(port) def connectAllInputs(port): if port.mode != PORT_MODE_OUTPUT: return input_ports = [] for jack_port in port_list: if jack_port.mode == PORT_MODE_INPUT: input_ports.append(jack_port.name) for connection in saved_connections: if connection in connection_list: continue if connection[0] == port.name and connection[1] in input_ports: jacklib.connect(jack_client, port.name, connection[1]) def connectAllOutputs(port): if port.mode != PORT_MODE_INPUT: return output_ports = [] for jack_port in port_list: if jack_port.mode == PORT_MODE_OUTPUT: output_ports.append(jack_port.name) for connection in saved_connections: if connection in connection_list: continue if connection[0] in output_ports and connection[1] == port.name: jacklib.connect(jack_client, connection[0], port.name) def makeMayConnections(): output_ports = [] input_ports = [] new_output_ports = [] new_input_ports = [] for port in port_list: if port.mode == PORT_MODE_OUTPUT: output_ports.append(port.name) if port.is_new: new_output_ports.append(port.name) elif port.mode == PORT_MODE_INPUT: input_ports.append(port.name) if port.is_new: new_input_ports.append(port.name) global pending_connection one_connected = False for connection in saved_connections: if (not connection in connection_list and connection[0] in output_ports and connection[1] in input_ports and (connection[0] in new_output_ports or connection[1] in new_input_ports)): if one_connected: pending_connection = True break jacklib.connect(jack_client, connection[0], connection[1]) one_connected = True else: pending_connection = False for port in port_list: port.is_new = False def c_char_p_p_to_list(c_char_p_p): i = 0 retList = [] if not c_char_p_p: return retList while True: new_char_p = c_char_p_p[i] if new_char_p: retList.append(str(new_char_p, encoding="utf-8")) i += 1 else: break jacklib.free(c_char_p_p) return retList def openFile(project_path, session_name, full_client_id): saved_connections.clear() global file_path file_path = "%s.xml" % project_path if os.path.isfile(file_path): try: file = open(file_path, 'r') except: print('unable to read file %s' % file_path, file=sys.stderr) app.quit() return xml = QDomDocument() xml.setContent(file.read()) content = xml.documentElement() if content.tagName() != "RAY-JACKPATCH": file.close() NSMServer.openReply() return cte = content.toElement() node = cte.firstChild() while not node.isNull(): el = node.toElement() if el.tagName() != "connection": continue port_from = el.attribute('from') port_to = el.attribute('to') saved_connections.append((port_from, port_to)) node = node.nextSibling() makeMayConnections() NSMServer.openReply() setDirtyClean() dirty_checker.start() def saveFile(): if not file_path: return for connection in connection_list: if not connection in saved_connections: saved_connections.append(connection) delete_list = [] for i in range(len(saved_connections)): if (portExists(saved_connections[i][0], PORT_MODE_OUTPUT, PORT_TYPE_AUDIO) and portExists(saved_connections[i][1], PORT_MODE_INPUT, PORT_TYPE_AUDIO)): if not saved_connections[i] in connection_list: delete_list.append(i) delete_list.reverse() for i in delete_list: saved_connections.__delitem__(i) try: file = open(file_path, 'w') except: print('unable to write file %s' % file_path, file=sys.stderr) app.quit() return xml = QDomDocument() p = xml.createElement('RAY-JACKPATCH') for con in saved_connections: ct = xml.createElement('connection') ct.setAttribute('from', con[0]) ct.setAttribute('to' , con[1]) p.appendChild(ct) xml.appendChild(p) file.write(xml.toString()) file.close() NSMServer.saveReply() setDirtyClean() if __name__ == '__main__': NSM_URL = os.getenv('NSM_URL') if not NSM_URL: print('Could not register as NSM client.', file=sys.stderr) sys.exit() daemon_address = ray.getLibloAddress(NSM_URL) jack_client = jacklib.client_open( "ray-patcher", jacklib.JackNoStartServer | jacklib.JackSessionID, None) if not jack_client: print('Unable to make a jack client !', file=sys.stderr) sys.exit() jacklib.set_port_registration_callback(jack_client, JackPortRegistrationCallback, None) jacklib.set_port_connect_callback(jack_client, JackPortConnectCallback, None) jacklib.set_port_rename_callback(jack_client, JackPortRenameCallback, None) jacklib.on_shutdown(jack_client, JackShutdownCallback, None) jacklib.activate(jack_client) signaler = Signaler() signaler.port_added.connect(portAdded) signaler.port_removed.connect(portRemoved) signaler.port_renamed.connect(portRenamed) signaler.connection_added.connect(connectionAdded) signaler.connection_removed.connect(connectionRemoved) signaler.server_sends_open.connect(openFile) signaler.server_sends_save.connect(saveFile) #makeMayConnections() NSMServer = nsm_client.NSMThread('ray-jackpatch', signaler, daemon_address, False) NSMServer.start() NSMServer.announce('JACK Connexions', ':dirty:switch:', 'ray-jackpatch') #connect signals signal.signal(signal.SIGINT , signalHandler) signal.signal(signal.SIGTERM, signalHandler) #get all currents Jack ports and connections portNameList = c_char_p_p_to_list(jacklib.get_ports(jack_client, "", "", 0)) for portName in portNameList: jack_port = JackPort() jack_port.name = portName portPtr = jacklib.port_by_name(jack_client, portName) portFlags = jacklib.port_flags(portPtr) if portFlags & jacklib.JackPortIsInput: jack_port.mode = PORT_MODE_INPUT elif portFlags & jacklib.JackPortIsOutput: jack_port.mode = PORT_MODE_OUTPUT else: jack_port.mode = PORT_MODE_NULL portTypeStr = str(jacklib.port_type(portPtr), encoding="utf-8") if portTypeStr == jacklib.JACK_DEFAULT_AUDIO_TYPE: jack_port.type = PORT_TYPE_AUDIO elif portTypeStr == jacklib.JACK_DEFAULT_MIDI_TYPE: jack_port.type = PORT_TYPE_MIDI else: jack_port.type = PORT_TYPE_NULL jack_port.is_new = True port_list.append(jack_port) if jacklib.port_flags(portPtr) & jacklib.JackPortIsInput: continue portConnectionNames = c_char_p_p_to_list( jacklib.port_get_all_connections(jack_client, portPtr)) for portConName in portConnectionNames: connection_list.append((portName, portConName)) app = QCoreApplication(sys.argv) #needed for signals SIGINT, SIGTERM timer = QTimer() timer.start(200) timer.timeout.connect(lambda: None) connect_timer = ConnectTimer() dirty_checker = DirtyChecker() app.exec() jacklib.deactivate(jack_client) jacklib.client_close(jack_client) RaySession-0.8.3/src/clients/jackpatch/ray.py000077700000000000000000000000001356671433200242142../../shared/ray.pyustar00rootroot00000000000000RaySession-0.8.3/src/clients/proxy/000077500000000000000000000000001356671433200172445ustar00rootroot00000000000000RaySession-0.8.3/src/clients/proxy/nsm_client.py000077700000000000000000000000001356671433200263452../../shared/nsm_client.pyustar00rootroot00000000000000RaySession-0.8.3/src/clients/proxy/ray-proxy.py000077500000000000000000000631261356671433200216030ustar00rootroot00000000000000#!/usr/bin/python3 -u import argparse import os import sys import time import signal import shutil import subprocess from liblo import ServerThread, Address, make_method, Message from PyQt5.QtCore import (pyqtSignal, QObject, QTimer, QProcess, QSettings, QLocale, QTranslator, QFile) from PyQt5.QtWidgets import (QApplication, QDialog, QFileDialog, QMessageBox, QMainWindow) from PyQt5.QtXml import QDomDocument import ray import nsm_client import ui_proxy_gui import ui_proxy_copy ERR_OK = 0 ERR_NO_PROXY_FILE = -1 ERR_NOT_ENOUGHT_LINES = -2 ERR_NO_EXECUTABLE = -3 ERR_WRONG_ARGUMENTS = -4 ERR_WRONG_SAVE_SIGNAL = -5 ERR_WRONG_STOP_SIGNAL = -6 def signalHandler(sig, frame): if sig in (signal.SIGINT, signal.SIGTERM): if proxy.isRunning(): proxy.waitForStop() proxy.stopProcess() else: #sys.exit() app.quit() def ifDebug(string): if debug: sys.stderr.write(string + '\n') class ProxyCopyDialog(QDialog): def __init__(self): QDialog.__init__(self) self.ui = ui_proxy_copy.Ui_Dialog() self.ui.setupUi(self) self.rename_file = False self.ui.pushButtonCopyRename.clicked.connect(self.setRenameFile) def setRenameFile(self): self.rename_file = True self.accept() def setFile(self, path): self.ui.labelFileNotInFolder.setText( _translate( 'Dialog', '%s is not in proxy directory') % ('' + os.path.basename(path) + '')) class ProxyDialog(QMainWindow): def __init__(self, executable=''): QMainWindow.__init__(self) self.ui = ui_proxy_gui.Ui_MainWindow() self.ui.setupUi(self) self.server = server self.proxy = proxy self.config_file = '' self.args_edited = False self.fields_allow_start = False self.process_is_running = False self.ui.toolButtonBrowse.clicked.connect(self.browseFile) self.ui.lineEditExecutable.textEdited.connect( self.lineEditExecutableEdited) self.ui.lineEditArguments.textChanged.connect( self.lineEditArgumentsChanged) self.ui.lineEditConfigFile.textChanged.connect( self.lineEditConfigFileChanged) self.ui.comboSaveSig.addItem(_translate('proxy', 'None'), 0) self.ui.comboSaveSig.addItem('SIGUSR1', int(signal.SIGUSR1)) self.ui.comboSaveSig.addItem('SIGUSR2', int(signal.SIGUSR2)) self.ui.comboSaveSig.addItem('SIGINT', int(signal.SIGINT)) self.ui.comboSaveSig.activated.connect(self.comboSaveSigChanged) self.ui.comboSaveSig.setCurrentIndex(0) self.ui.comboNoSave.addItem( _translate('proxy', "0 - Ignore missing save")) self.ui.comboNoSave.addItem( _translate('proxy', "1 - Transmit missing save")) self.ui.comboNoSave.addItem( _translate('proxy', "2 - Accept windows close")) self.ui.comboNoSave.setCurrentIndex(2) self.ui.comboNoSave.activated.connect(self.comboNoSaveChanged) self.ui.labelNoSaveLevel.setToolTip(self.ui.comboNoSave.toolTip()) self.ui.comboStopSig.addItem('SIGTERM', int(signal.SIGTERM)) self.ui.comboStopSig.addItem('SIGINT', int(signal.SIGINT)) self.ui.comboStopSig.addItem('SIGHUP', int(signal.SIGHUP)) self.ui.comboStopSig.activated.connect(self.comboStopSigChanged) self.ui.comboStopSig.setCurrentIndex(0) self.ui.comboSaveSig.currentTextChanged.connect(self.allowSaveTest) self.ui.toolButtonTestSave.clicked.connect(self.testSave) self.ui.toolButtonTestSave.setEnabled(False) self.ui.pushButtonStart.clicked.connect(self.startProcess) self.ui.pushButtonStop.clicked.connect(self.stopProcess) self.ui.pushButtonStop.setEnabled(False) self.ui.lineEditExecutable.setText(executable) self.lineEditExecutableEdited(executable) self.ui.labelError.setText('') proxy.process.started.connect(self.proxyStarted) proxy.process.finished.connect(self.proxyFinished) if ray.QT_VERSION >= (5, 6): proxy.process.errorOccurred.connect(self.proxyErrorInProcess) def checkAllowStart(self): self.fields_allow_start = True if not self.ui.lineEditExecutable.text(): self.fields_allow_start = False if ray.shellLineToArgs(self.ui.lineEditArguments.text()) is None: self.fields_allow_start = False self.ui.pushButtonStart.setEnabled( bool(not self.process_is_running and self.fields_allow_start)) def updateValuesFromProxyFile(self): self.ui.lineEditExecutable.setText(proxy.executable) self.ui.lineEditConfigFile.setText(proxy.config_file) self.ui.lineEditArguments.setText(proxy.arguments_line) save_index = self.ui.comboSaveSig.findData(proxy.save_signal) self.ui.comboSaveSig.setCurrentIndex(save_index) self.ui.comboNoSave.setCurrentIndex(proxy.no_save_level) self.ui.comboNoSave.setEnabled(not bool(proxy.save_signal)) stop_index = self.ui.comboStopSig.findData(proxy.stop_signal) self.ui.comboStopSig.setCurrentIndex(stop_index) self.ui.checkBoxWaitWindow.setChecked(proxy.wait_window) self.checkAllowStart() def browseFile(self): config_file, ok = QFileDialog.getOpenFileName( self, _translate('Dialog', 'Select File to use as CONFIG_FILE')) if not ok: return if not config_file.startswith(os.getcwd() + '/'): qfile = QFile(config_file) if qfile.size() < 20971520: # if file < 20Mb copy_dialog = ProxyCopyDialog() copy_dialog.setFile(config_file) copy_dialog.exec() if copy_dialog.result(): if copy_dialog.rename_file: base, pt, extension = os.path.basename( config_file).rpartition('.') config_file = "%s.%s" % (proxy.session_name, extension) if not base: config_file = proxy.session_name else: config_file = os.path.basename(config_file) qfile.copy(config_file) self.config_file = os.path.relpath(config_file) if (proxy.session_name and (self.config_file == proxy.session_name or self.config_file.startswith("%s." % proxy.session_name))): self.config_file = self.config_file.replace(proxy.session_name, "$RAY_SESSION_NAME") self.ui.lineEditConfigFile.setText(self.config_file) def lineEditExecutableEdited(self, text): self.checkAllowStart() def lineEditArgumentsChanged(self, text): self.checkAllowStart() if ray.shellLineToArgs(text) is not None: self.ui.lineEditArguments.setStyleSheet('') else: self.ui.lineEditArguments.setStyleSheet( 'QLineEdit{background: red}') self.ui.pushButtonStart.setEnabled(False) def lineEditConfigFileChanged(self, text): if text and not self.ui.lineEditArguments.text(): self.ui.lineEditArguments.setText('"$CONFIG_FILE"') elif (not text and self.ui.lineEditArguments.text() == '"$CONFIG_FILE"'): self.ui.lineEditArguments.setText('') def comboSaveSigChanged(self, index): self.ui.comboNoSave.setEnabled(bool(index == 0)) save_signal = 0 if index == 1: save_signal = signal.SIGUSR1 elif index == 2: save_signal = signal.SIGUSR2 elif index == 3: save_signal = signal.SIGINT save_signal = int(save_signal) self.proxy.setSaveSignal(save_signal) def comboStopSigChanged(self, index): stop_signal = signal.SIGTERM if index == 1: stop_signal = signal.SIGINT elif index == 2: stop_signal = signal.SIGTERM stop_signal = int(stop_signal) self.proxy.setStopSignal(stop_signal) def comboNoSaveChanged(self, index): self.proxy.no_save_level = index self.proxy.sendNoSaveLevel() def allowSaveTest(self, text=None): if text is None: text = self.ui.comboSaveSig.currentText() self.ui.toolButtonTestSave.setEnabled( bool(self.process_is_running and text != 'None')) def testSave(self): save_signal = self.ui.comboSaveSig.currentData() proxy.saveProcess(save_signal) def saveProxy(self): executable = self.ui.lineEditExecutable.text() config_file = self.ui.lineEditConfigFile.text() arguments_line = self.ui.lineEditArguments.text() save_signal = self.ui.comboSaveSig.currentData() no_save_level = self.ui.comboNoSave.currentIndex() stop_signal = self.ui.comboStopSig.currentData() wait_window = self.ui.checkBoxWaitWindow.isChecked() proxy.updateAndSave( executable, config_file, arguments_line, save_signal, no_save_level, stop_signal, wait_window) def startProcess(self): self.saveProxy() if proxy.is_launchable: proxy.startProcess() def stopProcess(self): proxy.stopProcess(self.ui.comboStopSig.currentData()) def proxyStarted(self): self.process_is_running = True self.ui.pushButtonStart.setEnabled(False) self.ui.pushButtonStop.setEnabled(True) self.allowSaveTest() self.ui.labelError.setText('') def processTerminateShortly(self, duration): self.ui.labelError.setText('Process terminate in %f ms') def proxyFinished(self): self.process_is_running = False self.ui.pushButtonStart.setEnabled(self.fields_allow_start) self.ui.pushButtonStop.setEnabled(False) self.allowSaveTest() self.ui.labelError.setText('') def proxyErrorInProcess(self): self.ui.labelError.setText( _translate( 'Dialog', 'Executable failed to launch ! It\'s maybe not present on system.')) if not self.isVisible(): self.show() def closeEvent(self, event): server.sendToDaemon('/nsm/client/gui_is_hidden') settings.setValue( 'ProxyGui%s/geometry' % self.proxy.full_client_id, self.saveGeometry()) settings.setValue( 'ProxyGui%s/WindowState' % self.proxy.full_client_id, self.saveState()) settings.sync() if self.fields_allow_start: self.saveProxy() QMainWindow.closeEvent(self, event) # Quit if process is not running yet if not proxy.process.state() == QProcess.Running: sys.exit(0) def showEvent(self, event): self.server.sendToDaemon('/nsm/client/gui_is_shown') if settings.value('ProxyGui%s/geometry' % self.proxy.full_client_id): self.restoreGeometry( settings.value( 'ProxyGui%s/geometry' % self.proxy.full_client_id)) if settings.value('ProxyGui%s/WindowState' % self.proxy.full_client_id): self.restoreState( settings.value( 'ProxyGui%s/WindowState' % self.proxy.full_client_id)) self.updateValuesFromProxyFile() QMainWindow.showEvent(self, event) ########################## class Proxy(QObject): def __init__(self, executable=''): QObject.__init__(self) self.process = QProcess() self.process.setProcessChannelMode(QProcess.ForwardedChannels) self.process.finished.connect(self.processFinished) #self.proxy_file = None self.is_launchable = False self.project_path = "" self.path = "" self.session_name = "" self.full_client_id = "" self.executable = executable self.arguments = [] self.arguments_line = '' self.config_file = "" self.save_signal = 0 self.no_save_level = 2 self.stop_signal = int(signal.SIGTERM) self.label = "" self.wait_window = False self._config_file_used = False self._wait_for_stop = False self.timer_save = QTimer() self.timer_save.setSingleShot(True) self.timer_save.setInterval(300) self.timer_save.timeout.connect(self.timerSaveFinished) self.timer_open = QTimer() self.timer_open.setSingleShot(True) self.timer_open.setInterval(500) self.timer_open.timeout.connect(self.timerOpenFinished) self.is_finishable = False self.timer_close = QTimer() self.timer_close.setSingleShot(True) self.timer_close.setInterval(2500) self.timer_close.timeout.connect(self.timerCloseFinished) self.timer_close.start() self.process_start_time = time.time() self.timer_window = QTimer() self.timer_window.setInterval(100) self.timer_window.timeout.connect(self.checkWindow) self.timer_window_n = 0 signaler.server_sends_open.connect(self.initialize) signaler.server_sends_save.connect(self.saveProcess) signaler.show_optional_gui.connect(self.showOptionalGui) signaler.hide_optional_gui.connect(self.hideOptionalGui) def isRunning(self): return bool(self.process.state() == QProcess.Running) def waitForStop(self): self._wait_for_stop = True def setSaveSignal(self, int_signal): self.save_signal = int_signal self.sendNoSaveLevel() def setStopSignal(self, int_signal): self.stop_signal = int_signal def readFile(self): self.is_launchable = False try: file = open(self.path, 'r') except BaseException: return xml = QDomDocument() xml.setContent(file.read()) content = xml.documentElement() if content.tagName() != "RAY-PROXY": file.close() return cte = content.toElement() file_version = cte.attribute('VERSION') self.executable = cte.attribute('executable') self.config_file = cte.attribute('config_file') self.arguments_line = cte.attribute('arguments') save_signal = cte.attribute('save_signal') no_save_level = cte.attribute('no_save_level') stop_signal = cte.attribute('stop_signal') wait_window = cte.attribute('wait_window') if wait_window.isdigit(): self.wait_window = bool(int(wait_window)) else: self.wait_window = False file.close() if save_signal.isdigit(): self.save_signal = int(save_signal) if no_save_level.isdigit(): self.no_save_level = int(no_save_level) else: self.no_save_level = 2 versions = [file_version, '0.7.1'] versions.sort() if file_version != versions[0]: # something was wrong in old version, # save signal was saved as stop signal too. # so don't read stop signal if this is an old file. if stop_signal.isdigit(): self.stop_signal = int(stop_signal) if not self.executable: return arguments = ray.shellLineToArgs(self.arguments_line) if arguments is None: return self.is_launchable = True def saveFile( self, executable, config_file, arguments_line, save_signal, no_save_level, stop_signal, wait_window): try: file = open(self.path, 'w') except BaseException: return if not save_signal: save_signal = 0 xml = QDomDocument() p = xml.createElement('RAY-PROXY') p.setAttribute('VERSION', ray.VERSION) p.setAttribute('executable', executable) p.setAttribute('arguments', arguments_line) p.setAttribute('config_file', config_file) p.setAttribute('save_signal', str(int(save_signal))) p.setAttribute('no_save_level', str(no_save_level)) p.setAttribute('stop_signal', str(int(stop_signal))) p.setAttribute('wait_window', wait_window) xml.appendChild(p) contents = "\n" contents += "\n" contents += xml.toString() file.write(contents) file.close() self.readFile() def updateValues(self, executable, config_file, arguments_line, save_signal, no_save_level, stop_signal, wait_window): self.executable = executable self.config_file = config_file self.arguments_line = arguments_line self.save_signal = save_signal self.no_save_level = no_save_level self.stop_signal = stop_signal self.wait_window = wait_window def saveProxyFile(self): self.saveFile( self.executable, self.config_file, self.arguments_line, self.save_signal, self.no_save_level, self.stop_signal, self.wait_window) def updateAndSave(self, executable, config_file, arguments_line, save_signal, no_save_level, stop_signal, wait_window): self.updateValues(executable, config_file, arguments_line, save_signal, no_save_level, stop_signal, wait_window) self.saveProxyFile() def processFinished(self, exit_code): if self._wait_for_stop: app.quit() if self.is_finishable: if not proxy_dialog.isVisible(): app.quit() else: duration = time.time() - self.process_start_time proxy_dialog.processTerminateShortly(duration) # proxy_dialog.show() def checkWindow(self): self.timer_window_n += 1 if self.timer_window_n > 600: # 600 x 50ms = 30s max until ray-proxy # replyOpen to Session Manager self.checkWindowEnded() return try: # get all windows and their PID with wmctrl wmctrl_all = subprocess.check_output( ['wmctrl', '-l', '-p']).decode() except BaseException: self.checkWindowEnded() return if not wmctrl_all: self.checkWindowEnded() return all_lines = wmctrl_all.split('\n') pids = [] # get all windows pids for line in all_lines: if not line: continue line_sep = line.split(' ') non_empt = [] for el in line_sep: if el: non_empt.append(el) if len(non_empt) >= 3 and non_empt[2].isdigit(): pids.append(int(non_empt[2])) else: # window manager seems to not work correctly with wmctrl, so # replyOpen now self.checkWindowEnded() return parent_pid = self.process.pid() # check in pids if one comes from this ray-proxy for pid in pids: if pid < parent_pid: continue ppid = pid while ppid != parent_pid and ppid > 1: try: proc_file = open('/proc/%i/status' % ppid, 'r') proc_contents = proc_file.read() except BaseException: self.checkWindowEnded() return for line in proc_contents.split('\n'): if line.startswith('PPid:'): ppid_str = line.rpartition('\t')[2] if ppid_str.isdigit(): ppid = int(ppid_str) break else: self.checkWindowEnded() return if ppid == parent_pid: # a window appears with a pid child of this ray-proxy, # replyOpen QTimer.singleShot(200, self.checkWindowEnded) break def checkWindowEnded(self): self.timer_window.stop() server.openReply() def initialize(self, project_path, session_name, full_client_id): self.project_path = project_path self.session_name = session_name self.full_client_id = full_client_id server.sendGuiState(False) if not os.path.exists(project_path): os.mkdir(project_path) os.chdir(project_path) #proxy_dialog.setWindowTitle(self.full_client_id) self.path = os.path.join(project_path, "ray-proxy.xml") self.readFile() proxy_dialog.updateValuesFromProxyFile() if not self.is_launchable: server.openReply() proxy_dialog.show() return self.startProcess() def sendNoSaveLevel(self): if ':no-save-level:' in server.getServerCapabilities(): nsl = self.no_save_level if self.save_signal: nsl = 0 server.sendToDaemon('/nsm/client/no_save_level', nsl) def startProcess(self): os.environ['NSM_CLIENT_ID'] = self.full_client_id os.environ['RAY_SESSION_NAME'] = self.session_name # enable environment vars in config_file config_file = os.path.expandvars(self.config_file) os.environ['CONFIG_FILE'] = config_file # Useful for launching NSM compatible clients with specifics arguments os.unsetenv('NSM_URL') arguments_line = os.path.expandvars(self.arguments_line) arguments = ray.shellLineToArgs(arguments_line) self._config_file_used = bool(config_file and config_file in arguments) self.sendNoSaveLevel() self.process.start(self.executable, arguments) self.timer_open.start() def saveProcess(self, save_signal=0): if not save_signal: save_signal = self.save_signal if self.isRunning() and save_signal: os.kill(self.process.processId(), save_signal) self.timer_save.start() def stopProcess(self, signal=signal.SIGTERM): if signal is None: return if not self.isRunning(): return os.kill(self.process.processId(), signal) def timerSaveFinished(self): server.saveReply() def timerOpenFinished(self): if self.wait_window: self.timer_window.start() else: server.openReply() if self.isRunning() and proxy_dialog.isVisible(): proxy_dialog.close() def timerCloseFinished(self): self.is_finishable = True def stop(self): if self.process.state: self.process.terminate() def showOptionalGui(self): proxy_dialog.show() def hideOptionalGui(self): if not proxy_dialog.isHidden(): proxy_dialog.close() class ProxyFile(object): def __init__(self, project_path, executable=''): self.executable = executable self.arguments_line = '' self.config_file = '' self.args_line = '' self.save_signal = 0 self.stop_signal = int(signal.SIGTERM) self.wait_window = False if __name__ == '__main__': NSM_URL = os.getenv('NSM_URL') if not NSM_URL: sys.stderr.write('Could not register as NSM client.\n') sys.exit() daemon_address = ray.getLibloAddress(NSM_URL) parser = argparse.ArgumentParser() parser.add_argument('--executable', default='') parser.add_argument('--debug', '-d', action='store_true', help='see all OSC messages') parser.add_argument('-v', '--version', action='version', version=ray.VERSION) parsed_args = parser.parse_args() debug = parsed_args.debug executable = parsed_args.executable app = QApplication(sys.argv) app.setApplicationName("RaySession") # app.setApplicationVersion(ray.VERSION) app.setOrganizationName("RaySession") app.setQuitOnLastWindowClosed(False) settings = QSettings() signal.signal(signal.SIGINT, signalHandler) signal.signal(signal.SIGTERM, signalHandler) # Translation process locale = QLocale.system().name() appTranslator = QTranslator() if appTranslator.load( "%s/locale/raysession_%s" % (os.path.dirname( os.path.dirname( os.path.dirname( sys.argv[0]))), locale)): app.installTranslator(appTranslator) _translate = app.translate timer = QTimer() timer.setInterval(200) timer.timeout.connect(lambda: None) timer.start() signaler = nsm_client.NSMSignaler() server = nsm_client.NSMThread('ray-proxy', signaler, daemon_address, debug) server.start() proxy = Proxy(executable) proxy_dialog = ProxyDialog() server.announce('Ray Proxy', ':optional-gui:warning-no-save:', 'ray-proxy') app.exec() settings.sync() server.stop() del server del proxy del app RaySession-0.8.3/src/clients/proxy/ray.py000077700000000000000000000000001356671433200234452../../shared/ray.pyustar00rootroot00000000000000RaySession-0.8.3/src/clients/sooperlooper/000077500000000000000000000000001356671433200206135ustar00rootroot00000000000000RaySession-0.8.3/src/clients/sooperlooper/jacklib.py000077700000000000000000000000001356671433200264242../../shared/jacklib.pyustar00rootroot00000000000000RaySession-0.8.3/src/clients/sooperlooper/nsm_client.py000077700000000000000000000000001356671433200277142../../shared/nsm_client.pyustar00rootroot00000000000000RaySession-0.8.3/src/clients/sooperlooper/ray.py000077700000000000000000000000001356671433200250142../../shared/ray.pyustar00rootroot00000000000000RaySession-0.8.3/src/clients/sooperlooper/sooperlooper_lash000077500000000000000000000111071356671433200243000ustar00rootroot00000000000000#!/usr/bin/python3 -u # old file, prefer use sooperlooper_nsm. But it's maybe used by someones. import sys import subprocess import time try: import liblo import xml.etree.ElementTree as ET from signal import signal, SIGINT, SIGTERM, SIGUSR1, SIGUSR2 except: sys.exit() class OSCServer(liblo.Server): def __init__(self): liblo.Server.__init__(self) self.m_number_of_loops = 0 self.m_last_called_looplen = 0 self.sl_url = liblo.Address(sooploop_port) self.m_save_last_called_loop = True self.add_method('/pingfromsl', None, self.pingFeedBack) self.add_method('/audio_save_error', None, self.audioSaveError) self.add_method('/loop_lenght', None, self.setLoopLenght) def pingFeedBack(self, path, arg_list): if len(arg_list) > 1: self.m_number_of_loops = arg_list[2] def setLoopLenght(self, path, arg_list): self.m_last_called_looplen = arg_list[2] def audioSaveError(self, path, arg_list): if len(arg_list) > 1: if arg_list[1] == 'Loop Save Failed': self.m_save_last_called_loop = False def saveSession(self): self.send(self.sl_url, '/ping', ('s', self.url), ('s', '/pingfromsl')) self.send(self.sl_url, '/save_session', ('s', filename), ('s', ''), ('s', '')) self.recv(1) #save audio loops basefile = filename.rsplit('.slsess', 1)[0] for i in range(self.m_number_of_loops): #send signal to know if loop contains audio self.send(self.sl_url, '/sl/%i/get' % i, ('s', 'loop_len'), ('s', self.url), ('s', '/loop_lenght')) self.recv(1) if self.m_last_called_looplen > 0: #if loop contains audio audio_filename = basefile + str(i) + '.wav' server.send(self.sl_url, '/sl/' + str(i) + '/save_loop', ('s', audio_filename), ('s', ''), ('s', ''), ('s', self.url), ('s', '/audio_save_error')) server.recv(10) if self.m_save_last_called_loop: #rewrite .slsess file to include audio tree = ET.parse(filename) root = tree.getroot() Loopers = root.find('Loopers') for Looper in Loopers.iter('Looper'): if str(Looper.get('index')) == str(i): Looper.set('loop_audio', audio_filename) tree.write(filename) self.m_save_last_called_loop = True def pingSooperLooper(self, port): self.send(self.sl_url, '/ping', ('s', self.url), ('s', '/pingfromsl')) self.recv(1) if self.sl_port == None: return False else: return self.sl_port def quitSooperLooper(self): self.send(self.sl_url, '/quit') def signalHandler(sig, frame): if sig in (SIGTERM, SIGINT): slgui_process.send_signal(SIGTERM) server.quitSooperLooper() elif sig == SIGUSR1: server.saveSession() return global main_loop main_loop = False ########################################SCRIPT START######################################################### #set filename from argument, exit if no argument try: filename = sys.argv[1] except: print('need file as argument, exit', file=sys.stderr) sys.exit(0) #get a free OSC port for sooperlooper, start from 9951 (default soopperlooper osc port) sooploop_port = 9951 UsedPort = True testport = None while UsedPort: try: testport = liblo.Server(sooploop_port) UsedPort = False except: sooploop_port += 1 UsedPort = True del testport, UsedPort #lanch sooperlooper sooperlooper_process = subprocess.Popen(['sooperlooper', '-p', str(sooploop_port),'--load-session', filename ]) #launch slgui slgui_process = subprocess.Popen(['slgui', '-P', str(sooploop_port)]) #construct OSC server server = OSCServer() main_loop = True while main_loop: #quit script if slgui closed if slgui_process.poll() != None: server.quitSooperLooper() break #send signal if any signal(SIGINT, signalHandler) signal(SIGTERM, signalHandler) signal(SIGUSR1, signalHandler) time.sleep(0.050) #refresh time (50ms) RaySession-0.8.3/src/clients/sooperlooper/sooperlooper_nsm.py000077500000000000000000000244531356671433200246050ustar00rootroot00000000000000#!/usr/bin/python3 -u import os import signal import sys import time from liblo import ServerThread, Address, make_method, Message from PyQt5.QtCore import (QCoreApplication, pyqtSignal, QObject, QTimer, QProcess, QSettings, QLocale, QTranslator, QFile) from PyQt5.QtXml import QDomDocument import ray import nsm_client import jacklib import threading def signalHandler(sig, frame): if sig in (signal.SIGINT, signal.SIGTERM): general_object.leave() class SlOSCThread(nsm_client.NSMThread): def __init__(self, name, signaler, daemon_address, debug): nsm_client.NSMThread.__init__(self, name, signaler, daemon_address, debug) self.sl_is_ready = False self.number_of_loops = 0 @make_method('/pongSL', 'ssi') def pong(self, path, args): self.sl_is_ready = True self.number_of_loops = args[2] if general_object.wait_for_load: general_object.sl_ready.emit() class GeneralObject(QObject): sl_ready = pyqtSignal() def __init__(self): QObject.__init__(self) self.sl_process = QProcess() self.sl_process.setProcessChannelMode(QProcess.ForwardedChannels) self.sl_process.finished.connect(self.slProcessFinished) self.sl_port = ray.getFreeOscPort(9951) self.sl_url = Address(self.sl_port) self.sl_process.start('sooperlooper', ['-p', str(self.sl_port)]) self.gui_process = QProcess() self.gui_process.setProcessChannelMode(QProcess.ForwardedChannels) self.gui_process.started.connect(self.guiProcessStarted) self.gui_process.finished.connect(self.guiProcessFinished) self.project_path = '' self.session_path = '' self.session_name = '' self.full_client_id = '' self.session_file = '' self.session_bak = '' self.file_timer = QTimer() self.file_timer.setInterval(100) self.file_timer.timeout.connect(self.checkFile) self.n_file_timer = 0 signaler.server_sends_open.connect(self.initialize) signaler.server_sends_save.connect(self.saveSlSession) signaler.show_optional_gui.connect(self.showOptionalGui) signaler.hide_optional_gui.connect(self.hideOptionalGui) self.sl_ready.connect(self.loadSession) self.leaving = False self.wait_for_load = False self.showOptionalGui() self.ping_timer = QTimer() self.ping_timer.setInterval(100) self.ping_timer.timeout.connect(self.pingSL) self.ping_timer.start() self.transport_timer = QTimer() self.transport_timer.setInterval(2) self.transport_timer.timeout.connect(self.checkTransport) self.transport_playing = False self.will_trig = False def JackShutdownCallback(self, arg=None): self.transport_timer.stop() return 0 def checkTransport(self): pos = jacklib.jack_position_t() pos.valid = 0 state = jacklib.transport_query(jack_client, jacklib.pointer(pos)) if self.will_trig: if pos.beat == pos.beats_per_bar: if (pos.ticks_per_beat - pos.tick) <= 4: # we are at 4 ticks or less from next bar (arbitrary) # so we send a trig message to sooperlooper. server.send(self.sl_url, '/sl/-1/hit', 'trigger') self.will_trig = False return if (self.transport_playing and state == jacklib.JackTransportStopped): if self.will_trig: self.will_trig = False else: server.send(self.sl_url, '/sl/-1/hit', 'pause_on') self.transport_playing = False elif (not self.transport_playing and state == jacklib.JackTransportRolling): if pos.beat == 1 and pos.tick == 0: server.send(self.sl_url, '/sl/-1/hit', 'trigger') else: self.will_trig = True self.transport_playing = True def pingSL(self): if server.sl_is_ready: self.ping_timer.stop() else: server.send(self.sl_url, '/ping', server.url, '/pongSL') def leave(self): self.leaving = True if self.gui_process.state(): self.gui_process.terminate() else: if self.sl_process.state(): server.send(self.sl_url, '/quit') else: app.quit() def isGuiShown(self): return bool(self.sl_process.state() == QProcess.Running) def slProcessFinished(self, exit_code): app.quit() def guiProcessStarted(self): server.sendGuiState(True) def guiProcessFinished(self, exit_code): if self.leaving: if self.sl_process.state(): server.send(self.sl_url, '/quit') else: app.quit() server.sendGuiState(False) def startFileChecker(self): self.n_file_timer = 0 if os.path.exists(self.session_file): self.stopFileChecker() return self.file_timer.start() def stopFileChecker(self): self.n_file_timer = 0 self.file_timer.stop() self.xmlCorrection() server.saveReply() def checkFile(self): if self.n_file_timer > 200: #more than 20 second self.stopFileChecker() return if os.path.exists(self.session_file): self.stopFileChecker() return self.n_file_timer+=1 def xmlCorrection(self): try: sl_file = open(self.session_file) xml = QDomDocument() xml.setContent(sl_file.read()) sl_file.close() except: return content = xml.documentElement() if content.tagName() != 'SLSession': return nodes = content.childNodes() for i in range(nodes.count()): node = nodes.at(i) if node.toElement().tagName() != 'Loopers': continue sub_nodes = node.childNodes() for j in range(sub_nodes.count()): sub_node = sub_nodes.at(j) element = sub_node.toElement() if element.tagName() != 'Looper': continue audio_file_name = str(element.attribute('loop_audio')) if audio_file_name.startswith("%s/" % self.project_path): element.setAttribute('loop_audio', os.path.relpath(audio_file_name)) try: sl_file = open(self.session_file, 'w') except: return sl_file.write(xml.toString()) sl_file.close() def initialize(self, project_path, session_name, full_client_id): self.project_path = project_path self.session_name = session_name self.full_client_id = full_client_id self.session_file = "%s/session.slsess" % self.project_path self.session_bak = "%s/session.slsess.bak" % self.project_path self.midi_bindings_file = "%s/session.slb" % self.project_path #self.midi_bindings_bak = "%s/session.slb.bak" % self.project_path if not os.path.exists(self.project_path): os.makedirs(self.project_path) os.chdir(self.project_path) if server.sl_is_ready: self.loadSession() else: self.wait_for_load = True if jack_client: self.transport_timer.start() def loadSession(self): self.wait_for_load = False server.send(self.sl_url, '/load_session', self.session_file, server.url, '/re-load') server.send(self.sl_url, '/load_midi_bindings', self.midi_bindings_file, '') server.openReply() def saveSlSession(self): if os.path.exists(self.session_bak): os.remove(self.session_bak) if os.path.exists(self.session_file): os.rename(self.session_file, self.session_bak) server.send(self.sl_url, '/save_session', self.session_file, server.url, '/re-save', 1) server.send(self.sl_url, '/save_midi_bindings', self.midi_bindings_file, '') self.startFileChecker() def showOptionalGui(self): if not self.gui_process.state(): self.gui_process.start('slgui', ['-P', str(self.sl_port)]) def hideOptionalGui(self): if self.gui_process.state(): self.gui_process.terminate() if __name__ == '__main__': NSM_URL = os.getenv('NSM_URL') if not NSM_URL: sys.stderr.write('Could not register as NSM client.\n') sys.exit() daemon_address = ray.getLibloAddress(NSM_URL) signal.signal(signal.SIGINT, signalHandler) signal.signal(signal.SIGTERM, signalHandler) app = QCoreApplication(sys.argv) app.setApplicationName("SooperLooperNSM") app.setOrganizationName("SooperLooperNSM") timer = QTimer() timer.setInterval(200) timer.timeout.connect(lambda: None) timer.start() signaler = nsm_client.NSMSignaler() server = SlOSCThread('sooperlooper_nsm', signaler, daemon_address, False) if len(sys.argv) > 1 and sys.argv[1] == '--transport_workaround': jack_client = jacklib.client_open( "sooper_ray_wk", jacklib.JackNoStartServer | jacklib.JackSessionID, None) else: jack_client = None general_object = GeneralObject() server.start() server.announce('SooperLooper', ':optional-gui:switch:', 'sooperlooper_nsm') app.exec() server.stop() del server del app RaySession-0.8.3/src/daemon/000077500000000000000000000000001356671433200156655ustar00rootroot00000000000000RaySession-0.8.3/src/daemon/bookmarker.py000066400000000000000000000443651356671433200204070ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os import pathlib import sys from PyQt5.QtCore import QSettings, QDataStream, QIODevice, QUrl, QByteArray from PyQt5.QtXml import QDomDocument, QDomText import ray from daemon_tools import getAppConfigPath QFileDialogMagic = 190 class PickerType: def __init__(self, config_path): self.config_path = config_path self.written = False def makeBookmark(self, spath): pass def removeBookmark(self, spath): pass def getContents(self): if os.path.exists(self.config_path): try: file = open(self.config_path, 'r') contents = file.read() file.close() return contents except: return None else: return "" def printContents(self, contents): try: file = open(self.config_path, 'w') except: return False file.write(contents) file.close() return True class PickerTypeGtk(PickerType): def makeBookmark(self, spath): if self.written: return url = pathlib.Path(spath).as_uri() config_dir = os.path.dirname(self.config_path) if not os.path.exists(config_dir): try: os.makedirs(config_dir) except: return contents = self.getContents() if contents == None: return bookmarks = contents.split('\n') if url in bookmarks: return contents += "%s\n" % url if self.printContents(contents): self.written = True def removeBookmark(self, spath): if not self.written: return url = pathlib.Path(spath).as_uri() contents = self.getContents() if not contents: self.written = False return bookmarks = contents.split('\n') if url in bookmarks: bookmarks.remove(url) else: self.written = False return contents = '' for url in bookmarks: if url: contents += "%s\n" % url self.printContents(contents) self.written = False class PickerTypeFltk(PickerType): def makeBookmark(self, spath): if self.written: return contents = self.getContents() if not contents: #we won't write a file for fltk if file doesn't already exists return lines = contents.split('\n') contents = "" num = -1 empty_fav = False fav0_found = False for line in lines: if line.startswith('favorite') and ':' in line: fav0_found = True if line.partition(':')[2]: if line.partition(':')[2] == spath: #bookmark already written, do nothing return num_s = line.partition(':')[0].replace('favorite', '', 1) if num_s.isdigit(): num = int(num_s) else: if not empty_fav: line += spath empty_fav = True if line or not fav0_found: contents+= "%s\n" % line if not empty_fav: num+=1 contents += "favorite%.2d:%s" % (num, spath) if self.printContents(contents): self.written = True def removeBookmark(self, spath): if not self.written: return contents = self.getContents() if not contents: self.written = False return lines = contents.split('\n') favorites = [] for line in lines: if line.startswith('favorite') and ':' in line: fav = line.partition(':')[2] favorites.append(fav) if not spath in favorites: self.written = False return favorites.remove(spath) contents = "" num = 0 for line in lines: if line.startswith('favorite') and ':' in line: fav = '' if num < len(favorites): fav = favorites[num] contents+= "favorite%.2d:%s\n" % (num, fav) num+=1 else: contents+= "%s\n" % line self.printContents(contents) self.written = False class PickerTypeQt4(PickerType): def makeBookmark(self, spath): if self.written: return if not os.path.exists(self.config_path): #do not write shortcuts if file was not created by Qt4 himself return url = pathlib.Path(spath).as_uri() settings_qt4 = QSettings(self.config_path, QSettings.IniFormat) if not settings_qt4.isWritable(): return data = settings_qt4.value('Qt/filedialog') stream = QDataStream(data, QIODevice.ReadOnly) magic = stream.readUInt32() version = stream.readUInt32() if not (magic == QFileDialogMagic and version == 3): return split_states = stream.readBytes() bookmarks_len = stream.readUInt32() bookmarks = [] for bm in range(bookmarks_len): qUrl = QUrl() stream >> qUrl if qUrl.isLocalFile() and qUrl.toLocalFile() == spath: #spath already in qt4 bookmarks return bookmarks.append(qUrl) history_len = stream.readUInt32() history = [] for h in range(history_len): his = stream.readQString() history.append(his) current_dir = stream.readQString() header_data = stream.readBytes() view_mode = stream.readUInt32() #now rewrite bytes new_data = QByteArray() new_stream = QDataStream(new_data, QIODevice.WriteOnly) new_stream.writeUInt32(magic) new_stream.writeUInt32(3) new_stream.writeBytes(split_states) new_stream.writeUInt32(bookmarks_len+1) for bm in bookmarks: new_stream << bm qUrl = QUrl(url) new_stream << qUrl new_stream.writeQStringList(history) new_stream.writeQString(current_dir) new_stream.writeBytes(header_data) new_stream.writeUInt32(view_mode) settings_qt4.setValue('Qt/filedialog', new_data) settings_qt4.sync() self.written = True def removeBookmark(self, spath): if not self.written: return if not os.path.exists(self.config_path): self.written = False return url = pathlib.Path(spath).as_uri() settings_qt4 = QSettings(self.config_path, QSettings.IniFormat) if not settings_qt4.isWritable(): self.written = False return data = settings_qt4.value('Qt/filedialog') stream = QDataStream(data, QIODevice.ReadOnly) magic = stream.readUInt32() version = stream.readUInt32() if not (magic == QFileDialogMagic and version == 3): self.written = False return split_states = stream.readBytes() bookmark_found = False bookmarks_len = stream.readUInt32() bookmarks = [] for bm in range(bookmarks_len): qUrl = QUrl() stream >> qUrl if qUrl.isLocalFile() and qUrl.toLocalFile() == spath: bookmark_found = True else: bookmarks.append(qUrl) if not bookmark_found: self.written = False return history_len = stream.readUInt32() history = [] for h in range(history_len): his = stream.readQString() history.append(his) current_dir = stream.readQString() header_data = stream.readBytes() view_mode = stream.readUInt32() #now rewrite bytes new_data = QByteArray() new_stream = QDataStream(new_data, QIODevice.WriteOnly) new_stream.writeUInt32(magic) new_stream.writeUInt32(3) new_stream.writeBytes(split_states) new_stream.writeUInt32(bookmarks_len-1) for bm in bookmarks: new_stream << bm qUrl = QUrl(url) new_stream << qUrl new_stream.writeQStringList(history) new_stream.writeQString(current_dir) new_stream.writeBytes(header_data) new_stream.writeUInt32(view_mode) settings_qt4.setValue('Qt/filedialog', new_data) settings_qt4.sync() self.written = False class PickerTypeQt5(PickerType): def makeBookmark(self, spath): if self.written: return if not os.path.exists(self.config_path): #do not write shortcuts if file was not created by Qt5 himself return url = pathlib.Path(spath).as_uri() settings_qt5 = QSettings(self.config_path, QSettings.IniFormat) if not settings_qt5.isWritable(): return shortcuts = ray.getListInSettings(settings_qt5, 'FileDialog/shortcuts') for sc in shortcuts: sc_url = QUrl(sc) if sc_url.isLocalFile() and sc_url.toLocalFile() == spath: return shortcuts.append(url) settings_qt5.setValue('FileDialog/shortcuts', shortcuts) settings_qt5.sync() self.written = True def removeBookmark(self, spath): if not self.written: return if not os.path.exists(self.config_path): self.written = False return url = pathlib.Path(spath).as_uri() settings_qt5 = QSettings(self.config_path, QSettings.IniFormat) shortcuts = ray.getListInSettings(settings_qt5, 'FileDialog/shortcuts') for sc in shortcuts: sc_url = QUrl(sc) if sc_url.isLocalFile() and sc_url.toLocalFile() == spath: shortcuts.remove(sc) break else: self.written = False return settings_qt5.setValue('FileDialog/shortcuts', shortcuts) settings_qt5.sync() self.written = False class PickerTypeKde5(PickerType): def makeBookmark(self, spath): if self.written: return contents = self.getContents() if not contents: #we won't write a file for kde5 if file doesn't already exists return url = pathlib.Path(spath).as_uri() xml = QDomDocument() xml.setContent(contents) content = xml.documentElement() if content.tagName() != 'xbel': return node = content.firstChild() while not node.isNull(): el = node.toElement() if el.tagName() == 'bookmark': if el.attribute('href') == url: #bookmark already exists return node = node.nextSibling() bk = xml.createElement('bookmark') bk.setAttribute('href', url) title = xml.createElement('title') title_text = xml.createTextNode(os.path.basename(spath)) title.appendChild(title_text) bk.appendChild(title) content.appendChild(bk) if self.printContents(xml.toString()): self.written = True def removeBookmark(self, spath): if not self.written: return contents = self.getContents() if not contents: self.written = False return url = pathlib.Path(spath).as_uri() xml = QDomDocument() xml.setContent(contents) content = xml.documentElement() if content.tagName() != 'xbel': self.written = False return node = content.firstChild() while not node.isNull(): el = node.toElement() if el.tagName() == 'bookmark': if el.attribute('href') == url: content.removeChild(node) break node = node.nextSibling() else: self.written = False return self.printContents(xml.toString()) self.written = False class BookMarker: def __init__(self): self.bookmarks_memory = "%s/bookmarks.xml" % getAppConfigPath() self.daemon_port = 0 HOME = os.getenv('HOME') self.gtk2 = PickerTypeGtk("%s/.gtk-bookmarks" % HOME) self.gtk3 = PickerTypeGtk("%s/.config/gtk-3.0/bookmarks" % HOME) self.fltk = PickerTypeFltk("%s/.fltk/fltk.org/filechooser.prefs" % HOME) self.kde5 = PickerTypeKde5("%s/.local/share/user-places.xbel" % HOME) self.qt4 = PickerTypeQt4("%s/.config/Trolltech.conf" % HOME) self.qt5 = PickerTypeQt5("%s/.config/QtProject.conf" % HOME) def setDaemonPort(self, port): self.daemon_port = port def getXml(self): xml = QDomDocument() file_exists = False if os.path.exists(self.bookmarks_memory): try: file = open(self.bookmarks_memory, 'r') xml.setContent(file.read()) file_exists = True except: try: os.path.remove(self.bookmarks_memory) except: return None if not file_exists: bms_xml = xml.createElement('Bookmarks') xml.appendChild(bms_xml) return xml def writeXmlFile(self, xml): try: file = open(self.bookmarks_memory, 'w') file.write(xml.toString()) except: return def getPickersForXml(self): string = ":" if self.gtk2.written: string += "gtk2:" if self.gtk3.written: string += "gtk3:" if self.fltk.written: string += "fltk:" if self.kde5.written: string += "kde5:" if self.qt4.written: string += "qt4:" if self.qt5.written: string += "qt5:" return string def makeAll(self, spath): for picker in (self.gtk2, self.gtk3, self.fltk, self.kde5, self.qt4, self.qt5): picker.makeBookmark(spath) xml = self.getXml() if not xml: return xml_content = xml.documentElement() node = xml_content.firstChild() bke = xml.createElement('bookmarker') bke.setAttribute('port', self.daemon_port) bke.setAttribute('session_path', spath) bke.setAttribute('pickers', self.getPickersForXml()) node = xml_content.firstChild() xml_content.appendChild(bke) self.writeXmlFile(xml) def removeAll(self, spath): for picker in (self.gtk2, self.gtk3, self.fltk, self.kde5, self.qt4, self.qt5): picker.removeBookmark(spath) xml = self.getXml() if not xml: return xml_content = xml.documentElement() nodes = xml_content.childNodes() for i in range(nodes.count()): node = nodes.at(i) bke = node.toElement() port = bke.attribute('port') session_path = bke.attribute('session_path') if (port.isdigit() and int(port) == self.daemon_port and session_path == spath): xml_content.removeChild(node) break self.writeXmlFile(xml) def clean(self, all_session_paths): xml = self.getXml() if not xml: return xml_content = xml.documentElement() nodes = xml_content.childNodes() nodes_to_remove = [] for i in range(nodes.count()): node = nodes.at(i) bke = node.toElement() spath = bke.attribute('session_path') pickers = bke.attribute('pickers') if not spath: nodes_to_remove.append(node) continue if not spath in all_session_paths: if ":gtk2:" in pickers: self.gtk2.written = True self.gtk2.removeBookmark(spath) if ":gtk3:" in pickers: self.gtk3.written = True self.gtk3.removeBookmark(spath) if ":fltk:" in pickers: self.fltk.written = True self.fltk.removeBookmark(spath) if ":kde5:" in pickers: self.kde5.written = True self.kde5.removeBookmark(spath) if ":qt4:" in pickers: self.qt4.written = True self.qt4.removeBookmark(spath) if ":qt5:" in pickers: self.qt5.written = True self.qt5.removeBookmark(spath) nodes_to_remove.append(node) for node in nodes_to_remove: xml_content.removeChild(node) self.writeXmlFile(xml) if __name__ == '__main__': bm_maker = BookMarker() bm_maker.makeAll(sys.argv[1]) RaySession-0.8.3/src/daemon/client.py000066400000000000000000001160701356671433200175220ustar00rootroot00000000000000import os import shlex import shutil import subprocess from liblo import Address from PyQt5.QtCore import (QCoreApplication, QProcess, QProcessEnvironment, QTimer) from PyQt5.QtXml import QDomDocument import ray from server_sender import ServerSender from daemon_tools import TemplateRoots, Terminal, RS NSM_API_VERSION_MAJOR = 1 NSM_API_VERSION_MINOR = 0 _translate = QCoreApplication.translate def dirname(*args): return os.path.dirname(*args) def basename(*args): return os.path.basename(*args) class Client(ServerSender): _reply_errcode = 0 _reply_message = None #can be directly changed by OSC thread gui_visible = True progress = 0 #have to be modified by main thread for security addr = None pid = 0 pending_command = ray.Command.NONE active = False client_id = '' capabilities = '' did_announce = False status = ray.ClientStatus.STOPPED name = '' executable_path = '' arguments = '' running_executable = '' running_arguments = '' tmp_arguments = '' label = '' icon = '' custom_prefix = "" prefix_mode = ray.PrefixMode.SESSION_NAME auto_start = True start_gui_hidden = False check_last_save = True no_save_level = 0 is_external = False sent_to_gui = False net_session_template = '' net_session_root = '' net_daemon_url = '' net_duplicate_state = -1 ignored_extensions = ray.getGitIgnoredExtensions() last_save_time = 0.00 last_dirty = 0.00 def __init__(self, parent_session): ServerSender.__init__(self) self.session = parent_session self.is_dummy = self.session.is_dummy process_env = QProcessEnvironment.systemEnvironment() process_env.insert('NSM_URL', self.getServerUrl()) self.process = QProcess() self.process.started.connect(self.processStarted) if ray.QT_VERSION >= (5, 6): self.process.errorOccurred.connect(self.errorInProcess) self.process.finished.connect(self.processFinished) self.process.readyReadStandardError.connect(self.standardError) self.process.readyReadStandardOutput.connect(self.standardOutput) self.process.setProcessEnvironment(process_env) #if client is'n't stopped 2secs after stop, #another stop becames a kill! self.stopped_since_long = False self.stopped_timer = QTimer() self.stopped_timer.setSingleShot(True) self.stopped_timer.setInterval(2000) #2sec self.stopped_timer.timeout.connect(self.stoppedSinceLong) self.net_daemon_copy_timer = QTimer() self.net_daemon_copy_timer.setSingleShot(True) self.net_daemon_copy_timer.setInterval(3000) self.net_daemon_copy_timer.timeout.connect(self.netDaemonOutOfTime) def sendToSelfAddress(self, *args): if not self.addr: return self.send(self.addr, *args) def sendStatusToGui(self): server = self.getServer() if not server: return server.sendClientStatusToGui(self) def readXmlProperties(self, ctx): #ctx is an xml sibling for client self.executable_path = ctx.attribute('executable') self.arguments = ctx.attribute('arguments') self.name = ctx.attribute('name') self.label = ctx.attribute('label') self.icon = ctx.attribute('icon') self.auto_start = bool(ctx.attribute('launched') != '0') self.check_last_save = bool(ctx.attribute('check_last_save') != '0') self.start_gui_hidden = bool(ctx.attribute('gui_visible') == '0') ign_exts = ctx.attribute('ignored_extensions').split(' ') unign_exts = ctx.attribute('unignored_extensions').split(' ') global_exts = ray.getGitIgnoredExtensions().split(' ') self.ignored_extensions = "" for ext in global_exts: if ext and not ext in unign_exts: self.ignored_extensions+= " %s" % ext for ext in ign_exts: if ext and not ext in global_exts: self.ignored_extensions+= " %s" % ext prefix_mode = ctx.attribute('prefix_mode') if (prefix_mode and prefix_mode.isdigit() and 0 <= int(prefix_mode) <= 2 ): self.prefix_mode = int(prefix_mode) if self.prefix_mode == ray.PrefixMode.CUSTOM: self.custom_prefix = ctx.attribute('custom_prefix') self.net_session_template = ctx.attribute('net_session_template') if basename(self.executable_path) == 'ray-network': if self.arguments: eat_url = False eat_root = False for arg in shlex.split(self.arguments): if arg in ('--daemon-url', '-u'): eat_url = True continue elif arg in ('--session-root', '-r'): eat_root = True continue elif not (eat_url or eat_root): eat_url = False eat_root = False continue if eat_url: self.net_daemon_url = arg eat_url = False elif eat_root: self.net_session_root = arg eat_root = False if ctx.attribute('id'): #session use "id" for absolutely needed client_id self.client_id = ctx.attribute('id') elif ctx.attribute('client_id'): #template use "client_id" for wanted client_id self.client_id = self.session.generateClientId(ctx.attribute('client_id')) def writeXmlProperties(self, ctx): ctx.setAttribute('executable', self.executable_path) ctx.setAttribute('name', self.name) if self.label: ctx.setAttribute('label', self.label) if self.icon: ctx.setAttribute('icon', self.icon) if not self.check_last_save: ctx.setAttribute('check_last_save', 0) if self.arguments: ctx.setAttribute('arguments', self.arguments) if self.prefix_mode != ray.PrefixMode.SESSION_NAME: ctx.setAttribute('prefix_mode', self.prefix_mode) if self.prefix_mode == ray.PrefixMode.CUSTOM: ctx.setAttribute('custom_prefix', self.custom_prefix) if self.isCapableOf(':optional-gui:'): if self.executable_path != 'ray-proxy': if self.start_gui_hidden: ctx.setAttribute('gui_visible', '0') if self.net_session_template: ctx.setAttribute('net_session_template', self.net_session_template) if self.ignored_extensions != ray.getGitIgnoredExtensions(): ignored = "" unignored = "" client_exts = [e for e in self.ignored_extensions.split(' ') if e] global_exts = [e for e in ray.getGitIgnoredExtensions().split(' ') if e] for cext in client_exts: if not cext in global_exts: ignored += " %s" % cext for gext in global_exts: if not gext in client_exts: unignored += " %s" % gext if ignored: ctx.setAttribute('ignored_extensions', ignored) else: ctx.removeAttribute('ignored_extensions') if unignored: ctx.setAttribute('unignored_extensions', unignored) else: ctx.removeAttribute('unignored_extensions') def setReply(self, errcode, message): self._reply_message = message self._reply_errcode = errcode def setLabel(self, label): self.label = label self.sendGuiClientProperties() def setIcon(self, icon_name): self.icon = icon_name self.sendGuiClientProperties() def hasError(self): return bool(self._reply_errcode) def errorCode(self): return self._reply_errcode def getMessage(self): return self._reply_message def isReplyPending(self): return bool(self.pending_command) def isDumbClient(self): return bool(not self.did_announce) def isCapableOf(self, capability): return bool(capability in self.capabilities) def guiMsgStyle(self): return "%s (%s):" % (self.name, self.client_id) def setNetworkProperties(self, net_daemon_url, net_session_root): if not self.isCapableOf(':ray-network:'): return if (net_daemon_url == self.net_daemon_url and net_session_root == self.net_session_root): return self.net_daemon_url = net_daemon_url self.net_session_root = net_session_root self.arguments = '--daemon-url %s --net-session-root "%s"' % ( self.net_daemon_url, self.net_session_root.replace('"', '\\"')) def netDaemonOutOfTime(self): self.net_duplicate_state = -1 if self.session.wait_for == ray.WaitFor.DUPLICATE_FINISH: self.session.endTimerIfLastExpected(self) def setStatus(self, status): #ray.ClientStatus.COPY is not a status as the other ones. #GUI needs to know if client is started/open/stopped while files are #copied, so self.status doesn't remember ray.ClientStatus.COPY, #although it is sent to GUI if status != ray.ClientStatus.COPY: self.status = status self.sendStatusToGui() if (status == ray.ClientStatus.COPY or self.session.file_copier.isActive(self.client_id)): self.sendGui("/ray/gui/client/status", self.client_id, ray.ClientStatus.COPY) def getJackClientName(self): jack_client_name = self.name numid = '' if '_' in self.client_id: numid = self.client_id.rpartition('_')[2] if numid.isdigit(): jack_client_name += '_' jack_client_name += numid return jack_client_name def getPrefixString(self): if self.prefix_mode == ray.PrefixMode.SESSION_NAME: return self.session.name elif self.prefix_mode == ray.PrefixMode.CLIENT_NAME: return self.name elif self.prefix_mode == ray.PrefixMode.CUSTOM: return self.custom_prefix return '' def getProjectPath(self): if self.executable_path == 'ray-network': # for ray-network, use custom_prefix for template, # quite ugly but simple code. return self.net_session_template if self.prefix_mode == ray.PrefixMode.SESSION_NAME: return "%s/%s.%s" % (self.session.path, self.session.name, self.client_id) elif self.prefix_mode == ray.PrefixMode.CLIENT_NAME: return "%s/%s.%s" % (self.session.path, self.name, self.client_id) elif self.prefix_mode == ray.PrefixMode.CUSTOM: return "%s/%s.%s" % (self.session.path, self.custom_prefix, self.client_id) # should not happens return "%s/%s.%s" % (self.session.path, self.session.name, self.client_id) def getProxyExecutable(self): if os.path.basename(self.executable_path) != 'ray-proxy': return "" xml_file = "%s/ray-proxy.xml" % self.getProjectPath() xml = QDomDocument() try: file = open(xml_file, 'r') xml.setContent(file.read()) except: return "" content = xml.documentElement() if content.tagName() != "RAY-PROXY": file.close() return "" executable = content.attribute('executable') file.close() return executable def setDefaultGitIgnored(self, executable=""): executable = executable if executable else self.executable_path executable = os.path.basename(executable) if executable == 'ray-proxy': executable = self.getProxyExecutable() if executable in ( 'ardour', 'ardour4', 'ardour5', 'ardour6', 'Ardour', 'Ardour4', 'Ardour5', 'Ardour6', 'qtractor'): self.ignored_extensions += " .mid" elif executable in ('luppp', 'sooperlooper', 'sooperlooper_nsm'): if '.wav' in self.ignored_extensions: self.ignored_extensions = \ self.ignored_extensions.replace('.wav', '') elif executable == 'samplv1_jack': for ext in ('.wav', '.flac', '.ogg', '.mp3'): if ext in self.ignored_extensions: self.ignored_extensions = \ self.ignored_extensions.replace(ext, '') def start(self): self.session.setRenameable(False) self.last_dirty = 0.00 if self.is_dummy: return self.sendGuiMessage(_translate("GUIMSG", "%s launching") % self.guiMsgStyle()) self.pending_command = ray.Command.START arguments = [] if self.tmp_arguments: arguments += shlex.split(self.tmp_arguments) if self.arguments: arguments += shlex.split(self.arguments) if self.hasServer() and self.executable_path == 'ray-network': arguments.append('--net-daemon-id') arguments.append(str(self.getServer().net_daemon_id)) self.running_executable = self.executable_path self.running_arguments = self.arguments self.process.start(self.executable_path, arguments) ## Here for another way to debug clients. ## Konsole is a terminal software. #self.process.start( #'konsole', #['--hide-tabbar', '--hide-menubar', '-e', self.executable_path] #+ arguments) def terminate(self): if self.isRunning(): if self.is_external: os.kill(self.pid, 15) # 15 means signal.SIGTERM else: self.process.terminate() def kill(self): if self.is_external: os.kill(self.pid, 9) # 9 means signal.SIGKILL return if self.isRunning(): self.process.kill() def isRunning(self): if self.is_external: return True return bool(self.process.state() == 2) def standardError(self): standard_error = self.process.readAllStandardError().data() Terminal.clientMessage(standard_error, self.name, self.client_id) def standardOutput(self): standard_output = self.process.readAllStandardOutput().data() Terminal.clientMessage(standard_output, self.name, self.client_id) def processStarted(self): self.stopped_since_long = False self.pid = self.process.pid() self.setStatus(ray.ClientStatus.LAUNCH) #Terminal.message("Process has pid: %i" % self.pid) if self.session.osc_src_addr: self.session.oscReply("/reply", self.session.osc_path, ray.Err.OK, "Launched.") def processFinished(self, exit_code, exit_status): self.stopped_timer.stop() self.is_external = False if self.pending_command in (ray.Command.KILL, ray.Command.QUIT): self.sendGuiMessage(_translate('GUIMSG', "%s terminated as planned") % self.guiMsgStyle()) else: self.sendGuiMessage(_translate('GUIMSG', "%s died unexpectedly.") % self.guiMsgStyle()) if self.session.wait_for: self.session.endTimerIfLastExpected(self) if self.pending_command == ray.Command.QUIT: self.session.removeClient(self) return else: self.setStatus(ray.ClientStatus.STOPPED) self.pending_command = ray.Command.NONE self.active = False self.pid = 0 self.addr = None self.session.setRenameable(True) def errorInProcess(self, error): if error == QProcess.FailedToStart: self.sendGuiMessage(_translate('GUIMSG', "%s Failed to start !") % self.guiMsgStyle()) self.active = False self.pid = 0 self.setStatus(ray.ClientStatus.STOPPED) self.pending_command = ray.Command.NONE if self.session.osc_src_addr: error_message = "Failed to launch process!" if not self.session.osc_path.startswith('/nsm/server/'): error_message = _translate('client', "Failed to launch process !") self.session.oscReply("/error", self.session.osc_path, ray.Err.LAUNCH_FAILED, error_message) if self.session.wait_for: self.session.endTimerIfLastExpected(self) self.session.setRenameable(True) def stoppedSinceLong(self): self.stopped_since_long = True self.sendGui('/ray/gui/client/still_running', self.client_id) def tellClientSessionIsLoaded(self): if self.active and not self.isDumbClient(): Terminal.message("Telling client %s that session is loaded." % self.name) self.sendToSelfAddress("/nsm/client/session_is_loaded") def canSaveNow(self): return bool(self.active and not self.no_save_level) def save(self): if self.isRunning(): if self.canSaveNow(): Terminal.message("Telling %s to save" % self.name) self.sendToSelfAddress("/nsm/client/save") self.pending_command = ray.Command.SAVE self.setStatus(ray.ClientStatus.SAVE) elif self.isDumbClient(): self.setStatus(ray.ClientStatus.NOOP) if self.isCapableOf(':optional-gui:'): self.start_gui_hidden = not bool(self.gui_visible) def stop(self): self.sendGuiMessage(_translate('GUIMSG', "%s stopping") % self.guiMsgStyle()) #if self.is_external: #os.kill(self.pid, 15) # 15 means signal.SIGTERM #return if self.isRunning(): self.pending_command = ray.Command.KILL self.setStatus(ray.ClientStatus.QUIT) if not self.stopped_timer.isActive(): self.stopped_timer.start() self.terminate() def quit(self): Terminal.message("Commanding %s to quit" % self.name) if self.isRunning(): self.pending_command = ray.Command.QUIT self.terminate() self.setStatus(ray.ClientStatus.QUIT) else: self.sendGui("/ray/gui/client/status", self.client_id, ray.ClientStatus.REMOVED) def switch(self, new_client): old_client_id = self.client_id self.client_id = new_client.client_id self.name = new_client.name self.prefix_mode = new_client.prefix_mode self.custom_prefix = new_client.custom_prefix self.label = new_client.label self.icon = new_client.icon jack_client_name = self.getJackClientName() client_project_path = self.getProjectPath() Terminal.message("Commanding %s to switch \"%s\"" % (self.name, client_project_path)) self.sendToSelfAddress("/nsm/client/open", client_project_path, self.session.name, jack_client_name) self.pending_command = ray.Command.OPEN self.setStatus(ray.ClientStatus.SWITCH) self.sendGui("/ray/gui/client/switch", old_client_id, self.client_id) def sendGuiClientProperties(self, removed=False): ad = '/ray/gui/client/update' if self.sent_to_gui else '/ray/gui/client/new' if removed: ad = '/ray/gui/trash/add' self.sendGui(ad, self.client_id, self.executable_path, self.arguments, self.name, self.prefix_mode, self.custom_prefix, self.label, self.icon, self.capabilities, int(self.check_last_save), self.ignored_extensions) self.sent_to_gui = True def updateClientProperties(self, client_data): self.client_id = client_data.client_id self.executable_path = client_data.executable_path self.arguments = client_data.arguments self.prefix_mode = client_data.prefix_mode self.custom_prefix = client_data.custom_prefix self.label = client_data.label self.icon = client_data.icon self.capabilities = client_data.capabilities self.check_last_save = client_data.check_last_save self.ignored_extensions = client_data.ignored_extensions self.sendGuiClientProperties() def prettyClientId(self): wanted = self.client_id if self.executable_path == 'ray-proxy': proxy_file = "%s/ray-proxy.xml" % self.getProjectPath() if os.path.exists(proxy_file): file = open(proxy_file, 'r') xml = QDomDocument() xml.setContent(file.read()) file.close() content = xml.documentElement() if content.tagName() == 'RAY-PROXY': executable = content.attribute('executable') if executable: wanted = executable if '_' in wanted: begin, udsc, end = wanted.rpartition('_') if not end: return wanted if not end.isdigit(): return wanted return begin return wanted def getProjectFiles(self): #return a list of full filenames client_files = [] project_path = self.getProjectPath() if os.path.exists(project_path): client_files.append(project_path) if project_path.startswith('%s/' % self.session.path): base_project = project_path.replace('%s/' % self.session.path, '', 1) for filename in os.listdir(self.session.path): if filename == base_project: full_file_name = "%s/%s" % (self.session.path, filename) if not full_file_name in client_files: client_files.append(full_file_name) elif filename.startswith('%s.' % base_project): client_files.append('%s/%s' % (self.session.path, filename)) return client_files def saveAsTemplate(self, template_name): #copy files client_files = self.getProjectFiles() template_dir = "%s/%s" % (TemplateRoots.user_clients, template_name) if os.path.exists(template_dir): if os.access(template_dir, os.W_OK): shutil.rmtree(template_dir) else: #TODO send error return os.makedirs(template_dir) if self.net_daemon_url: self.net_session_template = template_name self.send(Address(self.net_daemon_url), '/ray/session/save_as_template', self.session.name, template_name, self.net_session_root) if client_files: self.setStatus(ray.ClientStatus.COPY) fc = self.session.file_copier fc.startClientCopy(self.client_id, client_files, template_dir, self.saveAsTemplate_step1, self.saveAsTemplateAborted, [template_name]) else: self.saveAsTemplate_step1(template_name) def saveAsTemplate_step1(self, template_name): self.setStatus(self.status) #see setStatus to see why if self.prefix_mode != ray.PrefixMode.CUSTOM: self.adjustFilesAfterCopy(template_name, ray.Template.CLIENT_SAVE) xml_file = "%s/%s" % (TemplateRoots.user_clients, 'client_templates.xml') #security check if os.path.exists(xml_file): if not os.access(xml_file, os.W_OK): # TODO send error return if os.path.isdir(xml_file): #should not be a dir, remove it ! subprocess.run('rm', '-R', xml_file) if not os.path.isdir(TemplateRoots.user_clients): os.makedirs(TemplateRoots.user_clients) #create client_templates.xml if not exists if not os.path.isfile(xml_file): file = open(xml_file, 'w') xml = QDomDocument() rct = xml.createElement('RAY-CLIENT-TEMPLATES') xml.appendChild(rct) file.write(xml.toString()) file.close() del xml file = open(xml_file, 'r') xml = QDomDocument() xml.setContent(file.read()) file.close() content = xml.documentElement() if not content.tagName() == 'RAY-CLIENT-TEMPLATES': return # remove existing template if it has the same name as the new one node = content.firstChild() while not node.isNull(): if node.toElement().tagName() != 'Client-Template': node = node.nextSibling() continue if node.toElement().attribute('template-name') == template_name: content.removeChild(node) node = node.nextSibling() #create template rct = xml.createElement('Client-Template') self.writeXmlProperties(rct) rct.setAttribute('template-name', template_name) rct.setAttribute('client_id', self.prettyClientId()) if not self.isRunning(): rct.setAttribute('launched', False) content.appendChild(rct) file = open(xml_file, 'w') file.write(xml.toString()) file.close() self.sendGuiMessage( _translate('message', 'Client template %s created') % template_name) def saveAsTemplateAborted(self, template_name): self.setStatus(self.status) def adjustFilesAfterCopy(self, new_session_full_name, template_save=ray.Template.NONE): old_session_name = self.session.name new_session_name = basename(new_session_full_name) new_client_id = self.client_id old_client_id = self.client_id xsessionx = "XXX_SESSION_NAME_XXX" xclient_idx = "XXX_CLIENT_ID_XXX" if template_save == ray.Template.NONE: if self.prefix_mode != ray.PrefixMode.SESSION_NAME: return spath = "%s/%s" % (self.session.root, new_session_full_name) elif template_save == ray.Template.RENAME: spath = self.session.path elif template_save == ray.Template.SESSION_SAVE: spath = "%s/%s" % (TemplateRoots.user_sessions, new_session_full_name) new_session_name = xsessionx elif template_save == ray.Template.SESSION_SAVE_NET: spath = "%s/%s/%s" % (self.session.root, TemplateRoots.net_session_name, new_session_full_name) new_session_name = xsessionx elif template_save == ray.Template.SESSION_LOAD: spath = "%s/%s" % (self.session.root, new_session_full_name) old_session_name = xsessionx elif template_save == ray.Template.SESSION_LOAD_NET: spath = "%s/%s" % (self.session.root, new_session_full_name) old_session_name = xsessionx elif template_save == ray.Template.CLIENT_SAVE: spath = "%s/%s" % (TemplateRoots.user_clients, new_session_full_name) new_session_name = xsessionx new_client_id = xclient_idx elif template_save == ray.Template.CLIENT_LOAD: spath = self.session.path old_session_name = xsessionx old_client_id = xclient_idx old_prefix = old_session_name new_prefix = new_session_name if self.prefix_mode == ray.PrefixMode.CLIENT_NAME: old_prefix = new_prefix = self.name project_path = "%s/%s.%s" % (spath, old_prefix, old_client_id) if not os.path.exists(project_path): for file in os.listdir(spath): if (file.startswith("%s.%s." % (old_prefix, old_client_id)) or file == "%s.%s" % (old_prefix, old_client_id)): if not os.access("%s/%s" % (spath, file), os.W_OK): continue endfile = file.replace("%s.%s." % (old_prefix, old_client_id), '', 1) os.rename('%s/%s' %(spath, file), "%s/%s.%s.%s" % (spath, new_prefix, new_client_id, endfile)) return if not os.path.isdir(project_path): if not os.access(project_path, os.W_OK): return os.rename(project_path, "%s/%s.%s" % (spath, new_prefix, new_client_id)) return #only for ardour ardour_file = "%s/%s.ardour" % (project_path, old_prefix) ardour_bak = "%s/%s.ardour.bak" % (project_path, old_prefix) ardour_audio = "%s/interchange/%s.%s" % (project_path, old_prefix, old_client_id) if os.path.isfile(ardour_file) and os.access(ardour_file, os.W_OK): os.rename(ardour_file, "%s/%s.ardour" % (project_path, new_prefix)) if os.path.isfile(ardour_bak) and os.access(ardour_bak, os.W_OK): os.rename(ardour_bak, "%s/%s.ardour.bak" % (project_path, new_prefix)) if os.path.isdir(ardour_audio and os.access(ardour_audio, os.W_OK)): os.rename(ardour_audio, "%s/interchange/%s.%s" % (project_path, new_prefix, new_client_id)) #change last_used snapshot of ardour instant_file = "%s/instant.xml" % project_path if os.path.isfile(instant_file) and os.access(instant_file, os.W_OK): try : file = open(instant_file, 'r') xml = QDomDocument() xml.setContent(file.read()) content = xml.documentElement() if content.tagName() == 'instant': node = content.firstChild() while not node.isNull(): tag = node.toElement() if tag.tagName() == 'LastUsedSnapshot': if tag.attribute('name') == old_prefix: tag.setAttribute('name', new_prefix) file = open(instant_file, 'w') file.write(xml.toString()) break node = node.nextSibling() file.close() except: False #for Vee One Suite for extfile in ('samplv1', 'synthv1', 'padthv1', 'drumkv1'): old_veeone_file = "%s/%s.%s" % (project_path, old_session_name, extfile) new_veeone_file = "%s/%s.%s" % (project_path, new_session_name, extfile) if (os.path.isfile(old_veeone_file) and os.access(old_veeone_file, os.W_OK) and not os.path.exists(new_veeone_file)): os.rename(old_veeone_file, new_veeone_file) #for ray-proxy, change config_file name proxy_file = "%s/ray-proxy.xml" % project_path if os.path.isfile(proxy_file): try: file = open(proxy_file, 'r') xml = QDomDocument() xml.setContent(file.read()) content = xml.documentElement() if content.tagName() == "RAY-PROXY": cte = content.toElement() config_file = cte.attribute('config_file') if (('$RAY_SESSION_NAME' or '${RAY_SESSION_NAME}') in config_file): for env in ('"$RAY_SESSION_NAME"', '"${RAY_SESSION_NAME}"', "$RAY_SESSION_NAME", "${RAY_SESSION_NAME}"): config_file = \ config_file.replace(env, old_session_name) if (config_file and (config_file.split('.')[0] == old_session_name)): config_file_path = "%s/%s" % (project_path, config_file) if (os.path.exists(config_file_path) and os.access(config_file_path, os.W_OK)): os.rename(config_file_path, "%s/%s" % (project_path, config_file.replace( old_session_name, new_session_name))) file.close() except: False if os.access(project_path, os.W_OK): subprocess.run(['mv', project_path, "%s/%s.%s" % (spath, new_prefix, new_client_id)]) def serverAnnounce(self, path, args, src_addr, is_new): client_name, capabilities, executable_path, major, minor, pid = args if self.pending_command in (ray.Command.QUIT, ray.Command.KILL): # assume to not answer to a dying client. # He will never know, or perhaps, it depends on beliefs. return if major > NSM_API_VERSION_MAJOR: Terminal.message( "Client is using incompatible and more recent " + "API version %i.%i" % (major, minor)) self.send(src_addr, "/error", path, ray.Err.INCOMPATIBLE_API, "Server is using an incompatible API version.") return self.capabilities = capabilities self.addr = src_addr self.name = client_name self.active = True self.did_announce = True if is_new: self.is_external = True self.pid = pid self.running_executable = executable_path if self.executable_path in RS.non_active_clients: RS.non_active_clients.remove(self.executable_path) Terminal.message("Process has pid: %i" % pid ) Terminal.message( "The client \"%s\" at \"%s\" " % (self.name, self.addr.url) + "informs us it's ready to receive commands.") server = self.getServer() if not server: return # if this daemon is under another NSM session # do not enable server-control # because new, open and duplicate are forbidden server_capabilities = "" if not server.is_nsm_locked: server_capabilities += ":server-control" server_capabilities += ":broadcast:optional-gui:no-save-level:" self.send(src_addr, "/reply", path, "Well hello, stranger. Welcome to the party." if is_new else "Howdy, what took you so long?", ray.APP_TITLE, server_capabilities) self.sendGuiClientProperties() self.setStatus(ray.ClientStatus.OPEN) jack_client_name = self.getJackClientName() client_project_path = self.getProjectPath() self.send(src_addr, "/nsm/client/open", client_project_path, self.session.name, jack_client_name) self.pending_command = ray.Command.OPEN if self.isCapableOf(":optional-gui:"): self.sendGui("/ray/gui/client/has_optional_gui", self.client_id) if self.start_gui_hidden: self.send(src_addr, "/nsm/client/hide_optional_gui") RaySession-0.8.3/src/daemon/daemon_tools.py000066400000000000000000000142641356671433200207310ustar00rootroot00000000000000import argparse import os import sys from PyQt5.QtCore import QCoreApplication, QStandardPaths, QSettings import ray settings = QSettings() #non_active_clients = [] def dirname(*args): return os.path.dirname(*args) def getAppConfigPath(): return "%s/%s" % ( QStandardPaths.writableLocation(QStandardPaths.ConfigLocation), QCoreApplication.organizationName()) def getCodeRoot(): return dirname(dirname(dirname(os.path.realpath(__file__)))) def initDaemonTools(): #global non_active_clients #del non_active_clients if CommandLineArgs.config_dir: settings = QSettings(CommandLineArgs.config_dir) else: settings = QSettings() RS.setSettings(settings) RS.setNonActiveClients(ray.getListInSettings(settings, 'daemon/non_active_list')) RS.setFavorites(ray.getListInSettings(settings, 'daemon/favorites')) TemplateRoots.initConfig() def getGitDefaultUnAndIgnored(executable): ignored = [] unignored = [] if executable in ('luppp', 'sooperlooper', 'sooperlooper_nsm'): unignored.append('.wav') elif executable == 'samplv1_jack': unignored = ['.wav', '.flac', '.ogg', '.mp3'] return (ignored, unignored) class RS: settings = QSettings() non_active_clients = [] favorites = [] @classmethod def setSettings(cls, settings): del cls.settings cls.settings = settings @classmethod def setNonActiveClients(cls, nalist): del cls.non_active_clients cls.non_active_clients = nalist @classmethod def setFavorites(cls, favorites): cls.favorites = favorites class TemplateRoots: net_session_name = ".ray-net-session-templates" factory_sessions = "%s/session_templates" % getCodeRoot() factory_clients = "%s/client_templates" % getCodeRoot() @classmethod def initConfig(cls): if CommandLineArgs.config_dir: app_config_path = CommandLineArgs.config_dir else: app_config_path = getAppConfigPath() cls.user_sessions = "%s/session_templates" % app_config_path cls.user_clients = "%s/client_templates" % app_config_path class Terminal: _last_client_name = '' @classmethod def message(cls, string): if cls._last_client_name and cls._last_client_name != 'daemon': sys.stderr.write('\n') sys.stderr.write('[\033[90mray-daemon\033[0m]\033[92m%s\033[0m\n' % string) cls._last_client_name = 'daemon' @classmethod def snapshoterMessage(cls, byte_string, command=''): snapshoter_str = "snapshoter:.%s" % command if cls._last_client_name != snapshoter_str: sys.stderr.write('\n[\033[90mray-daemon-git%s\033[0m]\n' % command) sys.stderr.buffer.write(byte_string) cls._last_client_name = snapshoter_str @classmethod def clientMessage(cls, byte_string, client_name, client_id): client_str = "%s.%s" % (client_name, client_id) if not CommandLineArgs.debug_only: if cls._last_client_name != client_str: sys.stderr.write('\n[\033[90m%s-%s\033[0m]\n' % (client_name, client_id)) sys.stderr.buffer.write(byte_string) cls._last_client_name = client_str @classmethod def warning(cls, string): sys.stderr.write('[\033[90mray-daemon\033[0m]%s\033[0m\n' % string) cls._last_client_name = 'daemon' class CommandLineArgs(argparse.Namespace): session_root = '' osc_port = 0 findfreeport = True gui_url = None config_dir = '' debug = False debug_only = False session = '' @classmethod def eatAttributes(cls, parsed_args): for attr_name in dir(parsed_args): if not attr_name.startswith('_'): setattr(cls, attr_name, getattr(parsed_args, attr_name)) if cls.debug_only: cls.debug = True if cls.osc_port == 0: cls.osc_port = 16187 cls.findfreeport = True if cls.config_dir and not os.access(cls.config_dir, os.W_OK): sys.stderr.write( '%s is not a writable config dir, try another one\n' % cls.config_dir) sys.exit(1) class ArgParser(argparse.ArgumentParser): def __init__(self): argparse.ArgumentParser.__init__(self) _translate = QCoreApplication.translate default_root = "%s/%s" % (os.getenv('HOME'), _translate('daemon', 'Ray Network Sessions')) self.add_argument('--session-root', '-r', type=str, default=default_root, help='set root folder for sessions') self.add_argument('--session', '-s', type=str, default='', help='session to load at startup') self.add_argument('--osc-port', '-p', type=int, default=0, help='select OSC port for the daemon') self.add_argument('--findfreeport', action='store_true', help='find another port if port is not free') self.add_argument('--gui-url', type=ray.getLibloAddress, help=argparse.SUPPRESS) self.add_argument('--config-dir', '-c', type=str, default='', help='use a custom config dir') self.add_argument('--debug','-d', action='store_true', help='see all OSC messages') self.add_argument('--debug-only', '-do', action='store_true', help='debug without client messages') self.add_argument('-v', '--version', action='version', version=ray.VERSION) parsed_args = argparse.ArgumentParser.parse_args(self) CommandLineArgs.eatAttributes(parsed_args) RaySession-0.8.3/src/daemon/desktops_memory.py000077500000000000000000000157161356671433200215000ustar00rootroot00000000000000#!/usr/bin/python3 -u import os import subprocess import sys from PyQt5.QtCore import QProcess import ray class WindowProperties(object): id = "" desktop = 0 pid = 0 wclass = "" name = "" def moveWin(win_id, desktop_from, desktop_to): if desktop_from == desktop_to: return if desktop_to == -1: subprocess.run(['wmctrl', 'i', '-r', win_id, '-b', 'add,sticky']) return if desktop_from == -1: subprocess.run(['wmctrl', 'i', '-r', win_id, '-b', 'remove,sticky']) subprocess.run(['wmctrl', '-i', '-r', win_id, '-t', str(desktop_to)]) class DesktopsMemory(object): def __init__(self, session): self.session = session self.saved_windows = [] self.active_window_list = [] self.daemon_pids = [] self.non_daemon_pids = [] def isChildOfDaemon(self, pid): if pid in self.daemon_pids: return True if pid in self.non_daemon_pids: return False daemon_pid = os.getpid() if pid < daemon_pid: self.non_daemon_pids.append(pid) return False ppid = pid if ray.isPidChildOf(pid, daemon_pid): self.daemon_pids.append(pid) return True #while ppid > daemon_pid and ppid > 1: #try: #ppid = int(subprocess.check_output(['ps', '-o', 'ppid=', #'-p', str(ppid)])) #except: #self.non_daemon_pids.append(pid) #return False #if ppid == daemon_pid: #self.daemon_pids.append(pid) #return True self.non_daemon_pids.append(pid) return False def isNameInSession(self, name): for client in self.session.clients: if client.name == name and client.active: return True return False def setActiveWindowList(self): try: wmctrl_all = subprocess.check_output(['wmctrl', '-l', '-p', '-x']).decode() except: sys.stderr.write('unable to use wmctrl') return self.active_window_list.clear() all_lines = wmctrl_all.split('\n') for line in all_lines: if not line: continue line_sep = line.split(' ') properties = [] for el in line_sep: if el: properties.append(el) if (len(properties) >= 6 and properties[1].lstrip('-').isdigit() and properties[2].isdigit()): wid = properties[0] desktop = int(properties[1]) pid = int(properties[2]) wclass = properties[3] ignore_pid = False # fltk based apps don't send their pids to wmctrl, # so if win seems to be one of these apps # and app is running in the session, # assume that this window is child of this ray-daemon if pid == 0 and '.' in wclass: class_name = wclass.split('.')[0] exceptions = {'luppp' : 'Luppp', 'Non-Mixer' : 'Non-Mixer', 'Non-Sequencer': 'Non-Sequencer', 'Non-Timeline' : 'Non-Timeline'} if class_name in exceptions: if self.isNameInSession(exceptions[class_name]): ignore_pid = True if not (ignore_pid or self.isChildOfDaemon(pid)): continue name = "" for prop in properties[5:]: name+=prop name+=" " name = name[:-1] #remove last space awin = WindowProperties() awin.id = wid awin.pid = pid awin.desktop = desktop awin.wclass = wclass awin.name = name self.active_window_list.append(awin) def save(self): self.setActiveWindowList() if not self.active_window_list: return for awin in self.active_window_list: for win in self.saved_windows: if win.wclass == awin.wclass and win.name == awin.name: win.desktop = awin.desktop break else: win = WindowProperties() win.id = awin.id win.desktop = awin.desktop win.wclass = awin.wclass win.name = awin.name self.saved_windows.append(win) def replace(self): if not self.saved_windows: return self.setActiveWindowList() if not self.active_window_list: return for awin in self.active_window_list: for win in self.saved_windows: if win.wclass == awin.wclass and win.name == awin.name: win.id = awin.id moveWin(awin.id, awin.desktop, win.desktop) break elif win.wclass == awin.wclass: if self.session.name: win_name_sps = win.name.split(self.session.name, 1) if (len(win_name_sps) == 2 and awin.name.startswith(win_name_sps[0]) and awin.name.endswith(win_name_sps[1])): moveWin(awin.id, awin.desktop, win.desktop) break def readXml(self, xml_element): self.saved_windows.clear() nodes = xml_element.childNodes() for i in range(nodes.count()): node = nodes.at(i) el = node.toElement() if el.tagName() != "window": continue win = WindowProperties() win.wclass = el.attribute('class') win.name = el.attribute('name') desktop = el.attribute('desktop') if desktop.lstrip('-').isdigit(): win.desktop = int(desktop) self.saved_windows.append(win) def findAndClose(self, pid): for awin in self.active_window_list: if ray.isPidChildOf(awin.pid, pid): QProcess.startDetached('wmctrl', ['-i', '-c', awin.id]) RaySession-0.8.3/src/daemon/file_copier.py000066400000000000000000000166321356671433200205270ustar00rootroot00000000000000import os import subprocess from PyQt5.QtCore import QProcess, QTimer from osc_server_thread import OscServerThread from server_sender import ServerSender import ray class CopyFile(object): slots = ['orig_path', 'dest_path', 'state', 'size'] class FileCopier(ServerSender): def __init__(self, session): ServerSender.__init__(self) self.session = session self.client_id = '' self.next_function = None self.abort_function = None self.next_args = [] self.copy_files = [] self.copy_size = 0 self.aborted = False self.is_active = False self.process = QProcess() self.process.finished.connect(self.processFinished) if ray.QT_VERSION >= (5, 6): self.process.errorOccurred.connect(self.errorOccurred) self.timer = QTimer() self.timer.setInterval(250) self.timer.setSingleShot(True) self.timer.timeout.connect(self.checkProgressSize) def informCopytoGui(self, copy_state): server = OscServerThread.getInstance() if not server: return server.informCopytoGui(copy_state) def getFileSize(self, filepath): if not os.path.exists(filepath): return 0 try: du_full = subprocess.check_output( ['nice', '-n', '15', 'du', '-sb', filepath]).decode() except: du_full = "" if not du_full: return 0 du_str = du_full.split('\t')[0] if not du_str.isdigit(): return 0 return int(du_str) def checkProgressSize(self): current_size = 0 self.timer.stop() for copy_file in self.copy_files: if copy_file.state == 2: current_size += copy_file.size elif copy_file.state == 1: current_size += self.getFileSize(copy_file.dest_path) break if current_size and self.copy_size: progress = float(current_size/self.copy_size) if self.client_id: self.sendGui('/ray/gui/client/progress', self.client_id, progress) else: self.sendGui('/ray/gui/server/progress', progress) self.session.oscReply('/ray/net_daemon/duplicate_state', progress) self.timer.start() def processFinished(self, exit_code, exit_status): self.timer.stop() for copy_file in self.copy_files: if copy_file.state == 1: copy_file.state = 2 break if self.aborted: ##remove all created files for copy_file in self.copy_files: if copy_file.state > 0: file_to_remove = copy_file.dest_path if os.path.exists(file_to_remove): try: subprocess.run(['rm', '-R', file_to_remove]) except: pass self.is_active = False self.informCopytoGui(False) self.abort_function(*self.next_args) return #run next_function if copy is terminated for copy_file in self.copy_files: if copy_file.state != 2: break else: self.is_active = False self.informCopytoGui(False) if self.next_function: self.next_function(*self.next_args) return self.nextProcess() def errorOccurred(self): #todo make something else self.processFinished(0, 0) def nextProcess(self): self.is_active = True for copy_file in self.copy_files: if copy_file.state == 0: copy_file.state = 1 self.process.start('nice' , ['-n', '+15', 'cp', '-R', copy_file.orig_path, copy_file.dest_path]) break self.timer.start() def start(self, src_list, dest_dir, next_function, abort_function, next_args=[]): self.abort_function = abort_function self.next_function = next_function self.next_args = next_args self.aborted = False self.copy_size = 0 self.copy_files.clear() dest_path_exists = bool(os.path.exists(dest_dir)) if dest_path_exists: if not os.path.isdir(dest_dir): #TODO send error, but it should not append self.abort_function(*self.next_args) return if type(src_list) == str: src_dir = src_list src_list = [] if not os.path.isdir(src_dir): self.abort_function(*self.next_args) return try: tmp_list = os.listdir(src_dir) except: self.abort_function(*self.next_args) return for path in tmp_list: if path == '.ray-snapshots': continue full_path = "%s/%s" % (src_dir, path) src_list.append(full_path) if not dest_path_exists: os.makedirs(dest_dir) for orig_path in src_list: copy_file = CopyFile() copy_file.state = 0 copy_file.orig_path = orig_path copy_file.size = self.getFileSize(orig_path) self.copy_size+=copy_file.size if dest_path_exists: copy_file.dest_path = "%s/%s" % (dest_dir, os.path.basename(orig_path)) else: #WARNING works only with one file !!! copy_file.dest_path = dest_dir self.copy_files.append(copy_file) if self.copy_files: self.informCopytoGui(True) self.nextProcess() else: self.next_function(*self.next_args) def startClientCopy(self, client_id, src_list, dest_dir, next_function, abort_function, next_args=[]): self.client_id = client_id self.start(src_list, dest_dir, next_function, abort_function, next_args) def startSessionCopy(self, src_dir, dest_dir, next_function, abort_function, next_args=[]): self.client_id = '' self.start(src_dir, dest_dir, next_function, abort_function, next_args) def abort(self, abort_function=None, next_args=[]): if abort_function: self.abort_function = abort_function self.next_args = next_args self.timer.stop() if self.process.state() == QProcess.Running: self.aborted = True self.process.terminate() def isActive(self, client_id=''): if client_id and client_id != self.client_id: return False return self.is_active RaySession-0.8.3/src/daemon/multi_daemon_file.py000066400000000000000000000121201356671433200217070ustar00rootroot00000000000000import os from PyQt5.QtXml import QDomDocument instance = None class MultiDaemonFile(object): def __init__(self, session, server): self.session = session self.server = server self.file_path = '/tmp/RaySession/multi-daemon.xml' self.xml = QDomDocument() global instance instance = self @staticmethod def getInstance(): return instance def pidExists(self, pid): if type(pid) == str: pid = int(pid) try: os.kill(pid, 0) except OSError: return False else: return True def removeFile(self): try: os.remove(self.file_path) except: return def openFile(self): if not os.path.exists(self.file_path): if not os.path.exists(os.path.dirname(self.file_path)): os.makedirs(os.path.dirname(self.file_path)) return False try: file = open(self.file_path, 'r') self.xml.setContent(file.read()) file.close() return True except: self.removeFile() return False def writeFile(self): try: file = open(self.file_path, 'w') file.write(self.xml.toString()) file.close() except: return def setAttributes(self, element): element.setAttribute('net_daemon_id', self.server.net_daemon_id) element.setAttribute('root', self.session.root) element.setAttribute('session_path', self.session.path) element.setAttribute('pid', os.getpid()) element.setAttribute('port', self.server.port) element.setAttribute('user', os.getenv('USER')) def update(self): if not self.openFile(): ds = self.xml.createElement('Deamons') dm_xml = self.xml.createElement('Deamon') self.setAttributes(dm_xml) ds.appendChild(dm_xml) self.xml.appendChild(ds) else: found = False xml_content = self.xml.documentElement() nodes = xml_content.childNodes() for i in range(nodes.count()): node = nodes.at(i) dxe = node.toElement() pid = dxe.attribute('pid') if pid.isdigit() and pid == str(os.getpid()): self.setAttributes(dxe) found = True if not found: dm_xml = self.xml.createElement('Deamon') self.setAttributes(dm_xml) self.xml.firstChild().appendChild(dm_xml) self.writeFile() def quit(self): if not self.openFile(): return xml_content = self.xml.documentElement() nodes = xml_content.childNodes() for i in range(nodes.count()): node = nodes.at(i) dxe = node.toElement() pid = dxe.attribute('pid') if pid.isdigit() and pid == str(os.getpid()): break else: return xml_content.removeChild(node) self.writeFile() def isFreeForRoot(self, daemon_id, root_path): if not self.openFile(): return True xml_content = self.xml.documentElement() nodes = xml_content.childNodes() for i in range(nodes.count()): node = nodes.at(i) dxe = node.toElement() if (dxe.attribute('net_daemon_id') == str(daemon_id) and dxe.attribute('root') == root_path): pid = dxe.attribute('pid') if pid.isdigit() and self.pidExists(int(pid)): return False return True def isFreeForSession(self, session_path): if not self.openFile(): return True xml_content = self.xml.documentElement() nodes = xml_content.childNodes() for i in range(nodes.count()): node = nodes.at(i) dxe = node.toElement() if dxe.attribute('session_path') == session_path: pid = dxe.attribute('pid') if pid.isdigit() and self.pidExists(int(pid)): return False return True def getAllSessionPaths(self): if not self.openFile(): return [] all_session_paths = [] xml_content = self.xml.documentElement() nodes = xml_content.childNodes() for i in range(nodes.count()): node = nodes.at(i) dxe = node.toElement() spath = dxe.attribute('session_path') pid = dxe.attribute('pid') if spath and pid.isdigit() and self.pidExists(int(pid)): all_session_paths.append(spath) return all_session_paths RaySession-0.8.3/src/daemon/nsm_server_control.py000077500000000000000000000107121356671433200221660ustar00rootroot00000000000000#!/usr/bin/python3 import argparse import liblo import os import signal import sys import time from PyQt5.QtCore import QCoreApplication, QTimer, pyqtSignal, QObject import ray def signalHandler(sig, frame): if sig in (signal.SIGINT, signal.SIGTERM): QCoreApplication.quit() class Signaler(QObject): done = pyqtSignal(int) class OscServerThread(liblo.ServerThread): def __init__(self, daemon_port): liblo.ServerThread.__init__(self) self._daemon_port = daemon_port #@liblo.make_method('/reply', 'ss') #def replyMessage(self, path, args, types, src_addr): #reply_path, message = args #print('fko', reply_path, message) #sys.stdout.write("%s\n" % message) #if reply_path != '/nsm/server/list': #signaler.done.emit(0) #@liblo.make_method('/reply', 'sis') #def replyMessage2(self, path, args, types, src_addr): #reply_path, err, message = args #sys.stdout.write("%s\n" % message) #signaler.done.emit(- err) @liblo.make_method('/reply', None) def replyNone(self, path, args, types, src_addr): #print('fkorf', args) if len(args) == 2: reply_path, message = args #print('fko', reply_path, message) sys.stdout.write("%s\n" % message) if reply_path != '/nsm/server/list': signaler.done.emit(0) elif len(args) == 3: reply_path, err, message = args sys.stdout.write("%s\n" % message) signaler.done.emit(- err) @liblo.make_method('/error', 'sis') def errorMessage(self, path, args, types, src_addr): error_path, err, message = args sys.stdout.write('%s\n' % message) signaler.done.emit(- err) # this is very strange # nsmd sends a /nsm/server/list err Ok message when list is done. @liblo.make_method('/nsm/server/list', 'is') def nsmServerList(self, path, args, types, src_addr): signaler.done.emit(0) def toDaemon(self, *args): self.send(liblo.Address(self._daemon_port), *args) def printHelp(): sys.stdout.write( """control NSM server, opions are add executable_name Adds a client to the current session. save Saves the current session. open project_name Saves the current session and loads a new session. new project_name Saves the current session and creates a new session. duplicate new_project Saves and closes the current session, makes a copy, and opens it. close Saves and closes the current session. abort Closes the current session *WITHOUT SAVING* quit Saves and closes the current session and terminates the server. list Lists available projects. """) sys.exit(0) #class finisher(QObject): #def finished(self, err_code): #global exit_code #exit_code = err_code #QCoreApplication.quit() def finished(err_code): global exit_code exit_code = err_code QCoreApplication.quit() if __name__ == '__main__': if len(sys.argv) <= 1: printHelp() sys.exit(100) operation = sys.argv[1] if not operation in ('add', 'save', 'open', 'new', 'duplicate', 'close', 'abort', 'quit', 'list'): printHelp() sys.exit(100) arg_list = [] if len(sys.argv) >= 3: arg_list = sys.argv[2:] if operation in ('add', 'open', 'new', 'duplicate'): if not arg_list: sys.stderr.write('missing argument after "%s"\n' % operation) sys.exit(100) nsm_port = 13555 if ray.isOscPortFree(nsm_port): sys.stderr.write("No server at port %i\n" % nsm_port) sys.exit(100) exit_code = 0 app = QCoreApplication(sys.argv) signaler = Signaler() signaler.done.connect(finished) osc_server = OscServerThread(nsm_port) osc_server.start() #connect SIGINT and SIGTERM signal.signal(signal.SIGINT, signalHandler) signal.signal(signal.SIGTERM, signalHandler) #needed for SIGINT and SIGTERM timer = QTimer() timer.setInterval(200) timer.timeout.connect(lambda: None) timer.start() #time.sleep(0.201) osc_server.toDaemon('/nsm/server/%s' % operation, *arg_list) app.exec() osc_server.stop() sys.exit(exit_code) RaySession-0.8.3/src/daemon/osc_server_thread.py000066400000000000000000001154401356671433200217450ustar00rootroot00000000000000import os import sys import random import shutil import subprocess import time import liblo from PyQt5.QtXml import QDomDocument import ray from signaler import Signaler from multi_daemon_file import MultiDaemonFile from daemon_tools import (TemplateRoots, CommandLineArgs, Terminal, RS, getGitDefaultUnAndIgnored) instance = None signaler = Signaler.instance() def pathIsValid(path): return not bool('../' in path) def ifDebug(string): if CommandLineArgs.debug: sys.stderr.write(string + '\n') def ray_method(path, types): def decorated(func): @liblo.make_method(path, types) def wrapper(*args, **kwargs): #ifDebug('serverOSC::ray-daemon_receives %s, %s' #% (path, str(args))) t_thread, t_path, t_args, t_types, src_addr, rest = args if CommandLineArgs.debug: sys.stderr.write('\033[94mOSC::daemon_receives\033[0m %s, %s, %s, %s\n' % (t_path, t_types, t_args, src_addr.url)) response = func(*args[:-1], **kwargs) if response != False: signaler.osc_recv.emit(t_path, t_args, t_types, src_addr) return response return wrapper return decorated # Osc server thread separated in many classes for confort. # ClientCommunicating contains NSM protocol. # OSC paths have to be never changed. class ClientCommunicating(liblo.ServerThread): def __init__(self, session, osc_num=0): liblo.ServerThread.__init__(self, osc_num) self.session = session self.gui_list = [] self.server_status = ray.ServerStatus.OFF self.is_nsm_locked = False self.nsm_locker_url = '' self.net_master_daemon_addr = None self.net_master_daemon_url = '' self.net_daemon_id = random.randint(1, 999999999) self.list_asker_addr = None @ray_method('/osc/ping', '') def oscPing(self, path, args, types, src_addr): self.send(src_addr, "/reply", path) @ray_method('/reply', None) def reply(self, path, args, types, src_addr): if len(args) < 2: return False if not ray.areTheyAllString(args): return False if args[0] == '/ray/server/list_sessions': # this reply is only used here for reply from net_daemon # it directly resend its infos # to the last gui that asked session list if self.list_asker_addr: self.send(self.list_asker_addr, *args) return False if not len(args) == 2: return False @ray_method('/error', 'sis') def error(self, path, args, types, src_addr): client = self.session.getClientByAddress(src_addr) if not client: Terminal.warning("Error from unknown client") return False err_code = args[1] message = args[2] client.setReply(err_code, message) Terminal.message("Client \"%s\" replied with error: %s (%i)" % ( client.name, message, err_code )) client.pending_command = ray.Command.NONE client.setStatus(ray.ClientStatus.ERROR) # SERVER_CONTROL messages # following messages only for :server-control: capability @ray_method('/nsm/server/add', 's') def nsmServerAdd(self, path, args, types, src_addr): executable_path = args[0] if not self.session.path: self.send(src_addr, "/error", path, ray.Err.NO_SESSION_OPEN, "Cannot add to session because no session is loaded.") return False if '/' in executable_path: self.send(src_addr, "/error", path, ray.Err.LAUNCH_FAILED, "Absolute paths are not permitted. Clients must be in $PATH") return False @ray_method('/nsm/server/save', '') def nsmServerSave(self, path, args, types, src_addr): if not self.session.path: self.send(src_addr, "/error", path, ray.Err.NO_SESSION_OPEN, "No session to save.") return False @ray_method('/nsm/server/open', 's') def nsmServerOpen(self, path, args, types, src_addr): pass @ray_method('/nsm/server/new', 's') def nsmServerNew(self, path, args, types, src_addr): if self.is_nsm_locked: return False if not pathIsValid(args[0]): self.send(src_addr, "/error", path, ray.Err.CREATE_FAILED, "Invalid session name.") return False @ray_method('/nsm/server/duplicate', 's') def nsmServerDuplicate(self, path, args, types, src_addr): if self.is_nsm_locked or not self.session.path: self.send(src_addr, "/error", path, ray.Err.NO_SESSION_OPEN, "No session to duplicate.") return False if not pathIsValid(args[0]): self.send(src_addr, "/error", path, ray.Err.CREATE_FAILED, "Invalid session name.") return False @ray_method('/nsm/server/close', '') def nsmServerClose(self, path, args, types, src_addr): if not self.session.path: self.send(src_addr, "/error", path, ray.Err.NO_SESSION_OPEN, "No session to close.") return False @ray_method('/nsm/server/abort', '') def nsmServerAbort(self, path, args, types, src_addr): if not self.session.path: self.send(src_addr, "/error", path, ray.Err.NO_SESSION_OPEN, "No session to abort." ) if self.server_status == ray.ServerStatus.PRECOPY: # normally no session path if PRECOPY status signaler.copy_aborted.emit() return False @ray_method('/nsm/server/quit', '') def nsmServerQuit(self, path, args, types, src_addr): pass @ray_method('/nsm/server/list', '') def nsmServerList(self, path, args, types, src_addr): pass # END OF SERVER_CONTROL messages @ray_method('/nsm/server/announce', 'sssiii') def nsmServerAnnounce(self, path, args, types, src_addr): if not self.session.path: self.send(src_addr, "/error", path, ray.Err.NO_SESSION_OPEN, "Sorry, but there's no session open " + "for this application to join.") return False @ray_method('/nsm/server/broadcast', None) def nsmServerBroadcast(self, path, args, types, src_addr): if not args: return False #don't allow clients to broadcast NSM commands if args[0].startswith('/nsm/') or args[0].startswith('/ray'): return False for client in self.session.clients: if not client.addr: continue if not ray.areSameOscPort(client.addr.url, src_addr.url): self.send(client.addr, liblo.Message(*args)) # TODO broadcast to slave daemons #for gui_addr in self.gui_list: ##also relay to attached GUI so that the broadcast can be ##propagated to another NSMD instance #if gui_addr.url != src_addr.url: #self.send(gui_addr, Message(*args)) @ray_method('/nsm/client/progress', 'f') def nsmClientProgress(self, path, args, types, src_addr): client = self.session.getClientByAddress(src_addr) if not client: return False client.progress = args[0] self.sendGui("/ray/gui/client/progress", client.client_id, client.progress) @ray_method('/nsm/client/is_dirty', '') def nsmClientIs_dirty(self, path, args, types, src_addr): client = self.session.getClientByAddress(src_addr) if not client: return False Terminal.message("%s sends dirty" % client.client_id) client.dirty = 1 client.last_dirty = time.time() self.sendGui("/ray/gui/client/dirty", client.client_id, client.dirty) @ray_method('/nsm/client/is_clean', '') def nsmClientIs_clean(self, path, args, types, src_addr): client = self.session.getClientByAddress(src_addr) if not client: return False Terminal.message("%s sends clean" % client.client_id) client.dirty = 0 self.sendGui("/ray/gui/client/dirty", client.client_id, client.dirty) if self.option_save_from_client: if (client.pending_command != ray.Command.SAVE and client.last_dirty and time.time() - client.last_dirty > 0.20 and time.time() - client.last_save_time > 1.00): signaler.server_save_from_client.emit(path, args, src_addr, client.client_id) return True return False @ray_method('/nsm/client/message', 'is') def nsmClientMessage(self, path, args, types, src_addr): client = self.session.getClientByAddress(src_addr) if not client: return False self.sendGui("/ray/gui/client/message", client.client_id, args[0], args[1]) @ray_method('/nsm/client/gui_is_hidden', '') def nsmClientGui_is_hidden(self, path, args, types, src_addr): client = self.session.getClientByAddress(src_addr) if not client: return False Terminal.message("Client '%s' sends gui hidden" % client.client_id) client.gui_visible = False self.sendGui("/ray/gui/client/gui_visible", client.client_id, int(client.gui_visible)) @ray_method('/nsm/client/gui_is_shown', '') def nsmClientGui_is_shown(self, path, args, types, src_addr): client = self.session.getClientByAddress(src_addr) if not client: return False Terminal.message("Client '%s' sends gui shown" % client.client_id) client.gui_visible = True self.sendGui("/ray/gui/client/gui_visible", client.client_id, int(client.gui_visible)) @ray_method('/nsm/client/label', 's') def nsmClientLabel(self, path, args, types, src_addr): pass @ray_method('/nsm/client/network_properties', 'ss') def nsmClientNetworkProperties(self, path, args, types, src_addr): pass @ray_method('/nsm/client/no_save_level', 'i') def nsmClientNoSaveLevel(self, path, args, types, src_addr): pass class OscServerThread(ClientCommunicating): def __init__(self, session, osc_num=0): ClientCommunicating.__init__(self, session, osc_num) self.option_save_from_client = RS.settings.value( 'daemon/save_all_from_saved_client', True, type=bool) self.option_bookmark_session = RS.settings.value( 'daemon/bookmark_session_folder', True, type=bool) self.option_desktops_memory = RS.settings.value( 'daemon/desktops_memory', False, type=bool) self.option_snapshots = RS.settings.value( 'daemon/auto_snapshot', True, type=bool) self.option_has_wmctrl = bool(shutil.which('wmctrl')) if not self.option_has_wmctrl: self.option_desktops_memory = False self.option_has_git = bool(shutil.which('git')) if not self.option_has_git: self.option_snapshots = False global instance instance = self @staticmethod def getInstance(): return instance @ray_method('/ray/server/gui_announce', 'sisii') def rayGuiGui_announce(self, path, args, types, src_addr): version = args[0] nsm_locked = bool(args[1]) is_net_free = True if nsm_locked: self.net_master_daemon_url = args[2] self.is_nsm_locked = True self.nsm_locker_url = src_addr.url for gui_addr in self.gui_list: if not ray.areSameOscPort(gui_addr.url, src_addr.url): self.send(gui_addr, '/ray/gui/server/nsm_locked', 1) self.net_daemon_id = args[4] multi_daemon_file = MultiDaemonFile.getInstance() if multi_daemon_file: is_net_free = multi_daemon_file.isFreeForRoot( self.net_daemon_id, self.session.root) self.announceGui(src_addr.url, nsm_locked, is_net_free) @ray_method('/ray/server/gui_disannounce', '') def rayGuiGui_disannounce(self, path, args, types, src_addr): for addr in self.gui_list: if addr.url == src_addr.url: break else: return False self.gui_list.remove(addr) if src_addr.url == self.nsm_locker_url: self.net_daemon_id = random.randint(1, 999999999) multi_daemon_file = MultiDaemonFile.getInstance() if multi_daemon_file: multi_daemon_file.update() self.is_nsm_locked = False self.nsm_locker_url = '' self.sendGui('/ray/gui/server/nsm_locked', 0) @ray_method('/ray/server/set_nsm_locked', '') def rayServerSetNsmLocked(self, path, args, types, src_addr): self.is_nsm_locked = True self.nsm_locker_url = src_addr.url for gui_addr in self.gui_list: if gui_addr.url != src_addr.url: self.send(gui_addr, '/ray/gui/server/nsm_locked', 1) @ray_method('/ray/server/quit', '') def rayServerQuit(self, path, args, types, src_addr): sys.exit(0) @ray_method('/ray/server/abort_copy', '') def rayServerAbortCopy(self, path, args, types, src_addr): pass @ray_method('/ray/server/abort_snapshot', '') def rayServerAbortSnapshot(self, path, args, types, src_addr): pass @ray_method('/ray/server/change_root', 's') def rayServerChangeRoot(self, path, args, types, src_addr): if self.isOperationPending(src_addr, path): self.send(src_addr, '/error', "Can't change session_root. Operation pending") return False session_root = args[0] self.session.setRoot(session_root) self.sendGui('/ray/gui/server/root', session_root) @ray_method('/ray/server/list_path', '') def rayServerListPath(self, path, args, types, src_addr): exec_list = [] tmp_exec_list = [] pathlist = os.getenv('PATH').split(':') for pathdir in pathlist: if os.path.isdir(pathdir): listexe = os.listdir(pathdir) for exe in listexe: fullexe = pathdir + '/' + exe if (os.path.isfile(fullexe) and os.access(fullexe, os.X_OK) and not exe in exec_list): exec_list.append(exe) tmp_exec_list.append(exe) if len(tmp_exec_list) == 100: self.send(src_addr, '/reply', path, *tmp_exec_list) tmp_exec_list.clear() if tmp_exec_list: self.send(src_addr, '/reply', path, *tmp_exec_list) @ray_method('/ray/server/list_session_templates', '') def rayServerListSessionTemplates(self, path, args, types, src_addr): if not os.path.isdir(TemplateRoots.user_sessions): return False template_list = [] all_files = os.listdir(TemplateRoots.user_sessions) for file in all_files: if os.path.isdir("%s/%s" % (TemplateRoots.user_sessions, file)): template_list.append(file) if len(template_list) == 100: self.send(src_addr, '/reply', path, *template_list) template_list.clear() if template_list: self.send(src_addr, '/reply', path, *template_list) @ray_method('/ray/server/list_user_client_templates', '') def rayServerListUserClientTemplates(self, path, args, types, src_addr): self.listClientTemplates(src_addr, path) @ray_method('/ray/server/list_factory_client_templates', '') def rayServerListFactoryClientTemplates(self, path, args, types, src_addr): self.listClientTemplates(src_addr, path) @ray_method('/ray/server/remove_client_template', 's') def rayServerRemoveClientTemplate(self, path, args, types, src_addr): template_name = args[0] templates_root = TemplateRoots.user_clients templates_file = "%s/%s" % (templates_root, 'client_templates.xml') if not os.path.isfile(templates_file): return if not os.access(templates_file, os.W_OK): return file = open(templates_file, 'r') xml = QDomDocument() xml.setContent(file.read()) file.close() content = xml.documentElement() if content.tagName() != "RAY-CLIENT-TEMPLATES": return nodes = content.childNodes() for i in range(nodes.count()): node = nodes.at(i) ct = node.toElement() tag_name = ct.tagName() if tag_name != 'Client-Template': continue if template_name == ct.attribute('template-name'): break else: return False content.removeChild(nodes.at(i)) file = open(templates_file, 'w') file.write(xml.toString()) file.close() template_dir = '%s/%s' % (templates_root, template_name) if os.path.isdir(template_dir): subprocess.run(['rm', '-R', template_dir]) @ray_method('/ray/server/list_sessions', 'i') def rayServerListSessions(self, path, args, types, src_addr): self.list_asker_addr = src_addr @ray_method('/ray/server/new_session', None) def rayServerNewSession(self, path, args, types, src_addr): if not ray.areTheyAllString(args): return False if self.is_nsm_locked: return False if not pathIsValid(args[0]): self.send(src_addr, "/error", path, ray.Err.CREATE_FAILED, "Invalid session name.") return False @ray_method('/ray/server/open_session', None) def rayServerOpenSession(self, path, args, types, src_addr): if not ray.areTheyAllString(args): return False @ray_method('/ray/session/save', '') def raySessionSave(self, path, args, types, src_addr): if not self.session.path: self.send(src_addr, "/error", path, ray.Err.NO_SESSION_OPEN, "No session to save.") return False @ray_method('/ray/session/save_as_template', None) def nsmServerSaveSessionTemplate(self, path, args, types, src_addr): if not len(args) in (1, 3): return False if not ray.areTheyAllString(args): return False session_name = args[0] if not pathIsValid(session_name): self.send(src_addr, "/error", path, ray.Err.CREATE_FAILED, "Invalid session name.") return False if len(args) == 3: #save as template an not loaded session session_name, template_name, sess_root = args if not pathIsValid(template_name): self.send(src_addr, "/error", path, ray.Err.CREATE_FAILED, "Invalid session name.") return False if not (sess_root == self.session.root and session_name == self.session.name): signaler.dummy_load_and_template.emit(*args) return False @ray_method('/ray/session/take_snapshot', 'si') def raySessionTakeSnapshot(self, path, args, types, src_addr): pass @ray_method('/ray/session/close', '') def raySessionClose(self, path, args, types, src_addr): if not self.session.path: self.send(src_addr, "/error", path, ray.Err.NO_SESSION_OPEN, "No session to close.") return False @ray_method('/ray/session/abort', '') def raySessionAbort(self, path, args, types, src_addr): if self.server_status == ray.ServerStatus.PRECOPY: signaler.copy_aborted.emit() return False @ray_method('/ray/session/cancel_close', '') def raySessionCancelClose(self, path, args, types, src_addr): if not self.session.path: self.send(src_addr, "/error", path, ray.Err.NO_SESSION_OPEN, "No session to cancel close.") return False @ray_method('/ray/session/skip_wait_user', '') def raySessionSkipWaitUser(self, path, args, types, src_addr): if not self.server_status == ray.ServerStatus.WAIT_USER: return False @ray_method('/ray/session/duplicate', 's') def raySessionDuplicate(self, path, args, types, src_addr): if self.is_nsm_locked: return False if not self.session.path: self.send(src_addr, "/error", path, ray.Err.NO_SESSION_OPEN, "No session to duplicate.") return False if not pathIsValid(args[0]): self.send(src_addr, "/error", path, ray.Err.CREATE_FAILED, "Invalid session name.") return False @ray_method('/ray/session/duplicate_only', 'sss') def nsmServerDuplicateOnly(self, path, args, types, src_addr): self.send(src_addr, '/ray/net_daemon/duplicate_state', 0) @ray_method('/ray/session/open_snapshot', 's') def raySessionOpenSnapshot(self, path, args, types, src_addr): pass @ray_method('/ray/session/rename', 's') def rayServerRename(self, path, args, types, src_addr): new_session_name = args[0] #prevent rename session in network session if self.nsm_locker_url: NSM_URL = os.getenv('NSM_URL') if not NSM_URL: return False if not ray.areSameOscPort(self.nsm_locker_url, NSM_URL): return False if '/' in new_session_name: self.send(src_addr, "/error", path, ray.Err.CREATE_FAILED, "Invalid session name.") return False if self.isOperationPending(src_addr, path): return False if not self.session.path: self.send(src_addr, "/error", path, ray.Err.NO_SESSION_OPEN, "No session to rename.") return False @ray_method('/ray/session/add_executable', 's') def raySessionAddExecutable(self, path, args, types, src_addr): executable_path = args[0] if not self.session.path: self.send(src_addr, "/error", path, ray.Err.NO_SESSION_OPEN, "Cannot add to session because no session is loaded.") return False if '/' in executable_path: self.send(src_addr, "/error", path, ray.Err.LAUNCH_FAILED, "Absolute paths are not permitted. Clients must be in $PATH") return False @ray_method('/ray/session/add_executable', 'siiss') def raySessionAddExecutableAdvanced(self, path, args, types, src_addr): executable_path, via_proxy, prefix_mode, prefix_pattern, client_id = args if not self.session.path: self.send(src_addr, "/error", path, ray.Err.NO_SESSION_OPEN, "Cannot add to session because no session is loaded.") return False if '/' in executable_path: self.send(src_addr, "/error", path, ray.Err.LAUNCH_FAILED, "Absolute paths are not permitted. Clients must be in $PATH") return False @ray_method('/ray/session/add_proxy', 's') def rayServerAddProxy(self, path, args, types, src_addr): if not self.session.path: self.send(src_addr, "/error", path, ray.Err.NO_SESSION_OPEN, "Cannot add to session because no session is loaded.") return False @ray_method('/ray/session/add_client_template', 'is') def rayServerAddClientTemplate(self, path, args, types, src_addr): if not self.session.path: self.send(src_addr, "/error", path, ray.Err.NO_SESSION_OPEN, "Cannot add to session because no session is loaded.") return False @ray_method('/ray/session/reorder_clients', None) def rayServerReorderClients(self, path, args, types, src_addr): if not ray.areTheyAllString(args): return False @ray_method('/ray/session/list_snapshots', '') def rayServerListSnapshots(self, path, args, types, src_addr): pass @ray_method('/ray/session/set_auto_snapshot', 'i') def rayServerSetAutoSnapshot(self, path, args, types, src_addr): pass @ray_method('/ray/session/open_folder', '') def rayServerOpenFolder(self, path, args, types, src_addr): if self.session.path: subprocess.Popen(['xdg-open', self.session.path]) @ray_method('/ray/client/stop', 's') def rayGuiClientStop(self, path, args, types, src_addr): pass @ray_method('/ray/client/kill', 's') def rayGuiClientKill(self, path, args, types, src_addr): pass @ray_method('/ray/client/trash', 's') def rayGuiClientRemove(self, path, args, types, src_addr): pass @ray_method('/ray/client/resume', 's') def rayGuiClientResume(self, path, args, types, src_addr): pass @ray_method('/ray/client/save', 's') def rayGuiClientSave(self, path, args, types, src_addr): pass @ray_method('/ray/client/save_as_template', 'ss') def rayGuiClientSaveAsTemplate(self, path, args, types, src_addr): pass @ray_method('/ray/client/show_optional_gui', 's') def nsmGuiClientShow_optional_gui(self, path, args, types, src_addr): client = self.session.getClient(args[0]) if client and client.active: self.send(client.addr, "/nsm/client/show_optional_gui") @ray_method('/ray/client/hide_optional_gui', 's') def nsmGuiClientHide_optional_gui(self, path, args, types, src_addr): client = self.session.getClient(args[0]) if client and client.active: self.send(client.addr, "/nsm/client/hide_optional_gui") @ray_method('/ray/client/update_properties', 'ssssissssis') def rayGuiClientUpdateProperties(self, path, args, types, src_addr): pass @ray_method('/ray/client/list_snapshots', 's') def rayClientListSnapshots(self, path, args, types, src_addr): pass @ray_method('/ray/client/open_snapshot', 'ss') def rayClientLoadSnapshot(self, path, args, types, src_addr): pass @ray_method('/ray/net_daemon/duplicate_state', 'f') def rayDuplicateState(self, path, args, types, src_addr): pass @ray_method('/ray/trash/restore', 's') def rayGuiTrashRestore(self, path, args, types, src_addr): if not self.session.path: self.send(src_addr, "/error", path, ray.Err.NO_SESSION_OPEN, "Cannot add to session because no session is loaded.") return False @ray_method('/ray/trash/remove_definitely', 's') def rayGuiTrashRemoveDefinitely(self, path, args, types, src_addr): pass @ray_method('/ray/option/save_from_client', 'i') def rayOptionSaveFromClient(self, path, args, types, src_addr): self.option_save_from_client = bool(args[0]) options = self.getOptions() for gui_addr in self.gui_list: if not ray.areSameOscPort(gui_addr.url, src_addr.url): self.send(gui_addr, '/ray/gui/server/options', options) @ray_method('/ray/option/bookmark_session_folder', 'i') def rayOptionBookmarkSessionFolder(self, path, args, types, src_addr): self.option_bookmark_session = bool(args[0]) options = self.getOptions() for gui_addr in self.gui_list: if not ray.areSameOscPort(gui_addr.url, src_addr.url): self.send(gui_addr, '/ray/gui/server/options', options) @ray_method('/ray/option/desktops_memory', 'i') def rayOptionDesktopsMemory(self, path, args, types, src_addr): self.option_desktops_memory = bool(args[0]) options = self.getOptions() for gui_addr in self.gui_list: if not ray.areSameOscPort(gui_addr.url, src_addr.url): self.send(gui_addr, '/ray/gui/server/options', options) @ray_method('/ray/option/snapshots', 'i') def rayOptionSnapshots(self, path, args, types, src_addr): self.option_snapshots = bool(args[0]) options = self.getOptions() for gui_addr in self.gui_list: if not ray.areSameOscPort(gui_addr.url, src_addr.url): self.send(gui_addr, '/ray/gui/server/options', options) @ray_method('/ray/favorites/add', 'ssi') def rayFavoriteAdd(self, path, args, types, src_addr): name, icon, int_factory = args for favorite in RS.favorites: if (favorite.name == name and bool(int_factory) == favorite.factory): favorite.icon = icon break else: RS.favorites.append(ray.Favorite(name, icon, bool(int_factory))) for gui_addr in self.gui_list: if not ray.areSameOscPort(gui_addr.url, src_addr.url): self.send(gui_addr, '/ray/gui/favorites/added', *args) @ray_method('/ray/favorites/remove', 'si') def rayFavoriteRemove(self, path, args, types, src_addr): name, int_factory = args for favorite in RS.favorites: if (favorite.name == name and bool(int_factory) == favorite.factory): RS.favorites.remove(favorite) break for gui_addr in self.gui_list: if not ray.areSameOscPort(gui_addr.url, src_addr.url): self.send(gui_addr, '/ray/gui/favorites/removed', *args) def isOperationPending(self, src_addr, path): if self.session.file_copier.isActive(): self.send(src_addr, "/error", path, ray.Err.COPY_RUNNING, "ray-daemon is copying files. " + "Wait copy finish or abort copy, " + "and restart operation !") return True if self.session.process_order: self.send(src_addr, "/error", path, ray.Err.OPERATION_PENDING, "An operation pending.") return True return False def send(self, *args): ifDebug('\033[96mOSC::daemon sends\033[0m ' + str(args[1:])) ClientCommunicating.send(self, *args) def sendGui(self, *args): for gui_addr in self.gui_list: self.send(gui_addr, *args) def sendClientStatusToGui(self, client): self.sendGui("/ray/gui/client/status", client.client_id, client.status) def setServerStatus(self, server_status): self.server_status = server_status self.sendGui('/ray/gui/server/status', server_status) def getServerStatus(self): return self.server_status def informCopytoGui(self, copy_state): self.sendGui('/ray/gui/server/copying', int(copy_state)) def rewriteUserTemplatesFile(self, content, templates_file): if not os.access(templates_file, os.W_OK): return False file_version = content.attribute('VERSION') if ray.versionToTuple(file_version) >= ray.versionToTuple(ray.VERSION): return False content.setAttribute('VERSION', ray.VERSION) if ray.versionToTuple(file_version) >= (0, 8, 0): return True nodes = content.childNodes() for i in range(nodes.count()): node = nodes.at(i) ct = node.toElement() tag_name = ct.tagName() if tag_name != 'Client-Template': continue executable = ct.attribute('executable') if not executable: continue ign_list, unign_list = getGitDefaultUnAndIgnored(executable) if ign_list: ct.setAttribute('ignored_extensions', " ".join(ign_list)) if unign_list: ct.setAttribute('unignored_extensions', " ".join(unign_list)) return True def listClientTemplates(self, src_addr, path): template_list = [] tmp_template_list = [] templates_root = TemplateRoots.user_clients factory = bool('factory' in path) if factory: templates_root = TemplateRoots.factory_clients templates_file = "%s/%s" % (templates_root, 'client_templates.xml') if not os.path.isfile(templates_file): return if not os.access(templates_file, os.R_OK): return file = open(templates_file, 'r') xml = QDomDocument() xml.setContent(file.read()) file.close() content = xml.documentElement() if content.tagName() != "RAY-CLIENT-TEMPLATES": return file_rewritten = False if not factory: if content.attribute('VERSION') != ray.VERSION: file_rewritten = self.rewriteUserTemplatesFile( content, templates_file) nodes = content.childNodes() for i in range(nodes.count()): node = nodes.at(i) ct = node.toElement() tag_name = ct.tagName() if tag_name != 'Client-Template': continue template_name = ct.attribute('template-name') if not template_name or template_name in template_list: continue executable = ct.attribute('executable') if not executable: continue try_exec_line = ct.attribute('try-exec') try_exec_list = [] if try_exec_line: try_exec_list = ct.attribute('try-exec').split(';') try_exec_list.append(executable) try_exec_ok = True for try_exec in try_exec_list: exec_path = shutil.which(try_exec) if not exec_path: try_exec_ok = False break if not try_exec_ok: continue template_list.append("%s/%s" % (template_name, ct.attribute('icon'))) tmp_template_list.append("%s/%s" % (template_name, ct.attribute('icon'))) if len(tmp_template_list) == 20: self.send(src_addr, '/reply', path, *tmp_template_list) template_list.clear() if tmp_template_list: self.send(src_addr, '/reply', path, *tmp_template_list) if file_rewritten: try: file = open(templates_file, 'w') file.write(xml.toString()) file.close() except: sys.stderr.write( 'unable to rewrite User Client Templates XML File\n') def sendRenameable(self, renameable): if not renameable: self.sendGui('/ray/gui/session/renameable', 0) return if self.nsm_locker_url: NSM_URL = os.getenv('NSM_URL') if not NSM_URL: return if not ray.areSameOscPort(self.nsm_locker_url, NSM_URL): return self.sendGui('/ray/gui/session/renameable', 1) def getOptions(self): options = ( ray.Option.NSM_LOCKED * self.is_nsm_locked + ray.Option.SAVE_FROM_CLIENT * self.option_save_from_client + ray.Option.BOOKMARK_SESSION * self.option_bookmark_session + ray.Option.HAS_WMCTRL * self.option_has_wmctrl + ray.Option.DESKTOPS_MEMORY * self.option_desktops_memory + ray.Option.HAS_GIT * self.option_has_git + ray.Option.SNAPSHOTS * self.option_snapshots) return options def announceGui(self, url, nsm_locked=False, is_net_free=True): gui_addr = liblo.Address(url) options = self.getOptions() self.send(gui_addr, "/ray/gui/server/announce", ray.VERSION, self.server_status, options, self.session.root, int(is_net_free)) self.send(gui_addr, "/ray/gui/server/status", self.server_status) self.send(gui_addr, "/ray/gui/session/name", self.session.name, self.session.path) for favorite in RS.favorites: self.send(gui_addr, "/ray/gui/favorites/added", favorite.name, favorite.icon, int(favorite.factory)) for client in self.session.clients: self.send(gui_addr, '/ray/gui/client/new', client.client_id, client.executable_path, client.arguments, client.name, client.prefix_mode, client.custom_prefix, client.label, client.icon, client.capabilities, int(client.check_last_save), client.ignored_extensions) self.send(gui_addr, "/ray/gui/client/status", client.client_id, client.status) self.gui_list.append(gui_addr) Terminal.message("Registered with GUI") RaySession-0.8.3/src/daemon/ray-daemon.py000077500000000000000000000106601356671433200203010ustar00rootroot00000000000000#!/usr/bin/python3 -u import argparse import os import signal import sys import unicodedata from PyQt5.QtCore import (QCoreApplication, QTimer, QSettings, QStandardPaths, QLocale, QTranslator) import ray from daemon_tools import (initDaemonTools, RS, getCodeRoot, CommandLineArgs, ArgParser, Terminal) from osc_server_thread import OscServerThread from multi_daemon_file import MultiDaemonFile from signaler import Signaler from session import SignaledSession def signalHandler(sig, frame): if sig in (signal.SIGINT, signal.SIGTERM): session.terminate() if __name__ == '__main__': #add RaySession/src/bin to $PATH ray.addSelfBinToPath() #create app app = QCoreApplication(sys.argv) app.setApplicationName("RaySession") app.setOrganizationName("RaySession") initDaemonTools() ### Translation process locale = QLocale.system().name() appTranslator = QTranslator() if appTranslator.load("%s/locale/raysession_%s" % (getCodeRoot(), locale)): app.installTranslator(appTranslator) _translate = app.translate #check arguments parser = ArgParser() #manage session_root session_root = CommandLineArgs.session_root if not session_root: session_root = "%s/%s" % (os.getenv('HOME'), _translate('daemon', 'Ray Network Sessions')) #make session_root folder if needed if not os.path.isdir(session_root): if os.path.exists(session_root): sys.stderr.write( "%s exists and is not a dir, please choose another path !\n" % session_root) sys.exit(1) try: os.makedirs(session_root) except: sys.stderr.write("impossible to make dir %s , aborted !\n" % session_root) sys.exit(1) #create session session = SignaledSession(session_root) #create and start server if CommandLineArgs.findfreeport: server = OscServerThread( session, ray.getFreeOscPort(CommandLineArgs.osc_port)) else: if ray.isOscPortFree(CommandLineArgs.osc_port): server = OscServerThread(session, CommandLineArgs.osc_port) else: sys.stderr.write( _translate('daemon', 'port %i is not free, try another one\n') % CommandLineArgs.osc_port) sys.exit() server.start() #announce server to GUI if CommandLineArgs.gui_url: server.announceGui(CommandLineArgs.gui_url.url) #print server url Terminal.message('URL : %s' % ray.getNetUrl(server.port)) Terminal.message(' %s' % server.url) Terminal.message('ROOT: %s' % CommandLineArgs.session_root) #create or update multi_daemon_file in /tmp multi_daemon_file = MultiDaemonFile(session, server) multi_daemon_file.update() #clean bookmarks created by crashed daemons session.bookmarker.clean(multi_daemon_file.getAllSessionPaths()) #load session asked from command line if CommandLineArgs.session: session.serverOpenSessionAtStart(CommandLineArgs.session) #connect SIGINT and SIGTERM signal.signal(signal.SIGINT, signalHandler) signal.signal(signal.SIGTERM, signalHandler) #needed for SIGINT and SIGTERM timer = QTimer() timer.setInterval(200) timer.timeout.connect(lambda: None) timer.start() #start app app.exec() #app is stopped #update multi_daemon_file without this server multi_daemon_file.quit() #save RS.settings RS.settings.setValue('daemon/non_active_list', RS.non_active_clients) RS.settings.setValue('daemon/favorites', RS.favorites) RS.settings.setValue('daemon/save_all_from_saved_client', server.option_save_from_client) RS.settings.setValue('daemon/bookmark_session_folder', server.option_bookmark_session) RS.settings.setValue('daemon/auto_snapshot', server.option_snapshots) RS.settings.setValue('daemon/desktops_memory', server.option_desktops_memory) RS.settings.sync() #stop the server server.stop() del server del session del app RaySession-0.8.3/src/daemon/ray.py000077700000000000000000000000001356671433200216532../shared/ray.pyustar00rootroot00000000000000RaySession-0.8.3/src/daemon/ray_control.py000077500000000000000000000017571356671433200206070ustar00rootroot00000000000000#!/usr/bin/python3 import liblo import os import sys from PyQt5.QtXml import QDomDocument def noGoodArg(): strerror = "%s currently only recognizes the following options:\n" \ % sys.argv[0] strerror += "save close abort\n" sys.stderr.write(strerror) sys.exit(1) if __name__ == "__main__": if len(sys.argv) <= 1: noGoodArg() option = sys.argv[1] if not option in ('save', 'close', 'abort'): noGoodArg() multi_daemon_file = '/tmp/RaySession/multi-daemon.xml' if not os.path.isfile(multi_daemon_file): sys.exit(0) file = open(multi_daemon_file, 'r') xml = QDomDocument() xml.setContent(file.read()) daemons_root = xml.documentElement() nodes = daemons_root.childNodes() for i in range(nodes.count()): node = nodes.at(i) node_el = node.toElement() port = node_el.attribute('port') liblo.send(liblo.Address(port), '/ray/session/%s' % option) RaySession-0.8.3/src/daemon/server_sender.py000066400000000000000000000037741356671433200211200ustar00rootroot00000000000000from PyQt5.QtCore import QObject from osc_server_thread import OscServerThread class ServerSender(QObject): def __init__(self): QObject.__init__(self) self.is_dummy = False def hasServer(self): if not OscServerThread.getInstance(): return False return not self.is_dummy def serverSend(self, *args): if self.is_dummy: return server = OscServerThread.getInstance() if not server: return server.send(*args) def send(self, *args): if self.is_dummy: return server = OscServerThread.getInstance() if not server: return server.send(*args) def sendGui(self, *args): if self.is_dummy: return server = OscServerThread.getInstance() if not server: return server.sendGui(*args) def sendGuiMessage(self, message): self.sendGui('/ray/gui/server/message', message) def setServerStatus(self, server_status): if self.is_dummy: return server = OscServerThread.getInstance() if not server: return server.setServerStatus(server_status) def getServerStatus(self): if self.is_dummy: return -1 server = OscServerThread.getInstance() if not server: return -1 return server.server_status def isNsmLocked(self): if self.is_dummy: return False server = OscServerThread.getInstance() if not server: return False return server.is_nsm_locked def getServer(self): return OscServerThread.getInstance() def getServerUrl(self): server = OscServerThread.getInstance() if server: return server.url else: return '' RaySession-0.8.3/src/daemon/session.py000066400000000000000000002622051356671433200177310ustar00rootroot00000000000000import functools import math import os import random import shutil import string import subprocess import sys import time from liblo import Address from PyQt5.QtCore import QCoreApplication, QTimer, QProcess from PyQt5.QtXml import QDomDocument import ray from bookmarker import BookMarker from desktops_memory import DesktopsMemory from snapshoter import Snapshoter from multi_daemon_file import MultiDaemonFile from signaler import Signaler from server_sender import ServerSender from file_copier import FileCopier from client import Client from daemon_tools import TemplateRoots, RS, Terminal, CommandLineArgs _translate = QCoreApplication.translate signaler = Signaler.instance() def dirname(*args): return os.path.dirname(*args) def basename(*args): return os.path.basename(*args) def session_operation(func): def wrapper(*args, **kwargs): if len(args) < 4: return sess, path, osc_args, src_addr, *rest = args if sess.process_order: sess.send(src_addr, "/error", path, ray.Err.OPERATION_PENDING, "An operation pending.") return if sess.file_copier.isActive(): if path.startswith('/nsm/server/'): sess.send(src_addr, "/error", path, ray.Err.OPERATION_PENDING, "An operation pending.") else: sess.send(src_addr, "/error", path, ray.Err.COPY_RUNNING, "ray-daemon is copying files.\n" + "Wait copy finish or abort copy,\n" + "and restart operation !\n") return sess.rememberOscArgs(path, osc_args, src_addr) response = func(*args) sess.nextFunction() return response return wrapper class Session(ServerSender): def __init__(self, root): ServerSender.__init__(self) self.root = root self.is_dummy = False self.clients = [] self.new_clients = [] self.removed_clients = [] self.favorites = [] self.name = "" self.path = "" self.is_renameable = True self.forbidden_ids_list = [] self.file_copier = FileCopier(self) self.bookmarker = BookMarker() self.desktops_memory = DesktopsMemory(self) self.snapshoter = Snapshoter(self) ############# def oscReply(self, *args): if not self.osc_src_addr: return self.send(self.osc_src_addr, *args) def setRenameable(self, renameable): if not renameable: if self.is_renameable: self.is_renameable = False if self.hasServer(): self.getServer().sendRenameable(False) return for client in self.clients: if client.isRunning(): return self.is_renameable = True if self.hasServer(): self.getServer().sendRenameable(True) def message(self, string, even_dummy=False): if self.is_dummy and not even_dummy: return Terminal.message(string) def setRoot(self, session_root): if self.path: raise NameError("impossible to change root. session %s is loaded" % self.path) return self.root = session_root multi_daemon_file = MultiDaemonFile.getInstance() if multi_daemon_file: multi_daemon_file.update() def setName(self, session_name): self.name = session_name def setPath(self, session_path): if self.path: self.bookmarker.removeAll(self.path) self.path = session_path self.setName(session_path.rpartition('/')[2]) multi_daemon_file = MultiDaemonFile.getInstance() if multi_daemon_file: multi_daemon_file.update() if self.path: server = self.getServer() if server and server.option_bookmark_session: self.bookmarker.setDaemonPort(server.port) self.bookmarker.makeAll(self.path) def getClient(self, client_id): for client in self.clients: if client.client_id == client_id: return client else: sys.stderr.write("client_id %s is not in ray-daemon session\n") def getClientByAddress(self, addr): if not addr: return None for client in self.clients: if client.addr and client.addr.url == addr.url: return client def newClient(self, executable, client_id=None): client = Client(self) client.executable_path = executable client.name = basename(executable) client.client_id = client_id if not client_id: client.client_id = self.generateClientId(executable) self.clients.append(client) return client def trashClient(self, client): if not client in self.clients: raise NameError("No client to remove: %s" % client.client_id) return client.setStatus(ray.ClientStatus.REMOVED) if client.getProjectFiles() or client.net_daemon_url: self.removed_clients.append(client) client.sendGuiClientProperties(removed=True) self.clients.remove(client) def removeClient(self, client): if not client in self.clients: raise NameError("No client to remove: %s" % client.client_id) return client.setStatus(ray.ClientStatus.REMOVED) self.clients.remove(client) def restoreClient(self, client): client.sent_to_gui = False if not self.addClient(client): return self.sendGui('/ray/gui/trash/remove', client.client_id) self.removed_clients.remove(client) if client.auto_start: client.start() def tellAllClientsSessionIsLoaded(self): self.message("Telling all clients that session is loaded...") for client in self.clients: client.tellClientSessionIsLoaded() def purgeInactiveClients(self): remove_item_list = [] for i in range(len(self.clients)): if not self.clients[i].active: self.sendGui("/ray/gui/client/status", self.clients[i].client_id, ray.ClientStatus.REMOVED) remove_item_list.append(i) remove_item_list.reverse() for i in remove_item_list: self.clients.__delitem__(i) del remove_item_list def clientsHaveErrors(self): for client in self.clients: if client.active and client.hasError(): return True return False def updateForbiddenIdsList(self): if not self.path: return self.forbidden_ids_list.clear() for file in os.listdir(self.path): if os.path.isdir("%s/%s" % (self.path, file)) and '.' in file: client_id = file.rpartition('.')[2] if not client_id in self.forbidden_ids_list: self.forbidden_ids_list.append(client_id) elif os.path.isfile("%s/%s" % (self.path, file)) and '.' in file: for string in file.split('.')[1:]: if not string in self.forbidden_ids_list: self.forbidden_ids_list.append(string) for client in self.clients + self.removed_clients: if not client.client_id in self.forbidden_ids_list: self.forbidden_ids_list.append(client.client_id) def generateClientIdAsNsm(self): client_id = 'n' for l in range(4): client_id += random.choice(string.ascii_uppercase) return client_id def generateClientId(self, wanted_id=""): self.updateForbiddenIdsList() wanted_id = basename(wanted_id) if wanted_id: for to_rm in ('ray-', 'non-', 'carla-'): if wanted_id.startswith(to_rm): wanted_id = wanted_id.replace(to_rm, '', 1) break wanted_id = wanted_id.replace('jack', '') #reduce string if contains '-' if '-' in wanted_id: new_wanted_id = '' seplist = wanted_id.split('-') for sep in seplist[:-1]: if len(sep) > 0: new_wanted_id += (sep[0] + '_') new_wanted_id += seplist[-1] wanted_id = new_wanted_id #prevent non alpha numeric characters new_wanted_id = '' last_is_ = False for char in wanted_id: if char.isalnum(): new_wanted_id += char else: if not last_is_: new_wanted_id += '_' last_is_ = True wanted_id = new_wanted_id while wanted_id and wanted_id.startswith('_'): wanted_id = wanted_id[1:] while wanted_id and wanted_id.endswith('_'): wanted_id = wanted_id[:-1] if not wanted_id: wanted_id = self.generateClientIdAsNsm() while wanted_id in self.forbidden_ids_list: wanted_id = self.generateClientIdAsNsm() #limit string to 10 characters if len(wanted_id) >= 11: wanted_id = wanted_id[:10] if not wanted_id in self.forbidden_ids_list: self.forbidden_ids_list.append(wanted_id) return wanted_id n=2 while "%s_%i" % (wanted_id, n) in self.forbidden_ids_list: n+=1 self.forbidden_ids_list.append(wanted_id) return "%s_%i" % (wanted_id, n) client_id = 'n' for l in range(4): client_id += random.choice(string.ascii_uppercase) while client_id in self.forbidden_ids_list: client_id = 'n' for l in range(4): client_id += random.choice(string.ascii_uppercase) self.forbidden_ids_list.append(client_id) return client_id def getListOfExistingClientIds(self): if not self.path: return [] client_ids_list = [] for file in os.listdir(self.path): if os.path.isdir(file) and file.contains('.'): client_ids_list.append(file.rpartition('.')[2]) elif os.path.isfile(file) and file.contains('.'): file_without_extension = file.rpartition('.')[0] def addClient(self, client): self.clients.append(client) client.sendGuiClientProperties() return True def reOrderClients(self, client_ids_list): client_newlist = [] for client_id in client_ids_list: for client in self.clients: if client.client_id == client_id: client_newlist.append(client) break if len(client_ids_list) != len(self.clients): return self.clients.clear() for client in client_newlist: self.clients.append(client) class OperatingSession(Session): def __init__(self, root): Session.__init__(self, root) self.wait_for = ray.WaitFor.NONE self.timer = QTimer() self.timer_redondant = False self.expected_clients = [] self.timer_launch = QTimer() self.timer_launch.setInterval(100) self.timer_launch.timeout.connect(self.timerLaunchTimeOut) self.clients_to_launch = [] self.timer_quit = QTimer() self.timer_quit.setInterval(100) self.timer_quit.timeout.connect(self.timerQuitTimeOut) self.clients_to_quit = [] self.timer_waituser_progress = QTimer() self.timer_waituser_progress.setInterval(500) self.timer_waituser_progress.timeout.connect( self.timerWaituserProgressTimeOut) self.timer_wu_progress_n = 0 self.osc_path = None self.osc_args = None self.osc_src_addr = None self.process_order = [] self.terminated_yet = False self.externals_timer = QTimer() self.externals_timer.setInterval(100) self.externals_timer.timeout.connect(self.checkExternalsStates) def rememberOscArgs(self, path, args, src_addr): self.osc_path = path self.osc_args = args self.osc_src_addr = src_addr def waitAndGoTo(self, duration, follow, wait_for, redondant=False): self.timer.stop() # we need to delete timer to change the timeout connect del self.timer self.timer = QTimer() if type(follow) in (list, tuple): if len(follow) == 0: return elif len(follow) == 1: follow = follow[0] else: follow = functools.partial(follow[0], *follow[1:]) if self.expected_clients: if wait_for == ray.WaitFor.ANNOUNCE: self.sendGuiMessage( _translate('GUIMSG', 'waiting for clients announces...')) elif wait_for == ray.WaitFor.STOP: self.sendGuiMessage( _translate('GUIMSG', 'waiting for clients to die...')) self.timer_redondant = redondant self.wait_for = wait_for self.timer.setSingleShot(True) self.timer.timeout.connect(follow) self.timer.start(duration) else: follow() def endTimerIfLastExpected(self, client): if client in self.expected_clients: self.expected_clients.remove(client) if self.timer_redondant: self.timer.start() if self.timer_waituser_progress.isActive(): self.timer_wu_progress_n = 0 self.timer_waituser_progress.start() if not self.expected_clients: self.timer.setSingleShot(True) self.timer.stop() self.timer.start(0) self.timer_waituser_progress.stop() def cleanExpected(self): if self.expected_clients: client_names = "" for client in self.expected_clients: client_names += client.name + ', ' if self.wait_for == ray.WaitFor.ANNOUNCE: self.sendGuiMessage(_translate('GUIMSG', "%sdidn't announce") % client_names) elif self.wait_for == ray.WaitFor.STOP: self.sendGuiMessage(_translate('GUIMSG', "%sstill alive !") % client_names) self.expected_clients.clear() else: if self.wait_for == ray.WaitFor.ANNOUNCE: self.sendGuiMessage( _translate('GUIMSG', 'All expected clients are announced')) elif self.wait_for == ray.WaitFor.STOP: self.sendGuiMessage( _translate('GUIMSG', 'All expected clients are died')) self.wait_for = ray.WaitFor.NONE def nextFunction(self): if len(self.process_order) > 0: next_item = self.process_order[0] next_function = next_item arguments = [] if type(next_item) in (tuple, list): if len(next_item) == 0: return else: next_function = next_item[0] if len(next_item) > 1: arguments = next_item[1:] self.process_order.__delitem__(0) next_function(*arguments) def timerLaunchTimeOut(self): if self.clients_to_launch: self.clients_to_launch[0].start() self.clients_to_launch.__delitem__(0) if not self.clients_to_launch: self.timer_launch.stop() def timerQuitTimeOut(self): if self.clients_to_quit: self.clients_to_quit[0].quit() self.clients_to_quit.__delitem__(0) if not self.clients_to_quit: self.timer_quit.stop() def timerWaituserProgressTimeOut(self): if not self.expected_clients: self.timer_waituser_progress.stop() self.timer_wu_progress_n += 1 ratio = float(self.timer_wu_progress_n / 240) self.sendGui('/ray/gui/server/progress', ratio) def checkExternalsStates(self): has_externals = False for client in self.clients: if client.is_external: has_externals = True if not os.path.exists('/proc/%i' % client.pid): # Quite dirty, but works. client.processFinished(0, 0) if not has_externals: self.externals_timer.stop() def sendError(self, err, error_message): #clear process order to allow other new operations self.process_order.clear() if not (self.osc_src_addr or self.osc_path): return self.oscReply("/error", self.osc_path, err, error_message) def adjustFilesAfterCopy(self, new_session_full_name, template_mode): new_session_name = basename(new_session_full_name) spath = "%s/%s" % (self.root, new_session_full_name) #create temp clients from raysession.xml to adjust Files after copy session_file = "%s/%s" % (spath, "raysession.xml") try: ray_file = open(session_file, 'r') except: self.sendError(ray.Err.BAD_PROJECT, _translate("error", "impossible to read %s") % session_file) return tmp_clients = [] xml = QDomDocument() xml.setContent(ray_file.read()) content = xml.documentElement() if content.tagName() != "RAYSESSION": ray_file.close() self.loadError(ray.Err.BAD_PROJECT) return content.setAttribute('name', new_session_name) nodes = content.childNodes() for i in range(nodes.count()): node = nodes.at(i) tag_name = node.toElement().tagName() if tag_name in ('Clients', 'RemovedClients'): clients_xml = node.toElement().childNodes() for j in range(clients_xml.count()): client_xml = clients_xml.at(j) client = Client(self) cx = client_xml.toElement() client.readXmlProperties(cx) tmp_clients.append(client) ray_file.close() ray_file_w = open(session_file, 'w') ray_file_w.write(xml.toString()) ray_file_w.close() for client in tmp_clients: client.adjustFilesAfterCopy(new_session_full_name, template_mode) ############################## COMPLEX OPERATIONS ################### # All functions are splitted when we need to wait clients # for something (announce, reply, quit). # For example, at the end of save(), timer is launched, # then, when timer is timeout or when all client replied, # save_step1 is launched. def save(self, from_client_id='', outing=False): if not self.path: self.nextFunction() return if outing: self.setServerStatus(ray.ServerStatus.OUT_SAVE) else: self.setServerStatus(ray.ServerStatus.SAVE) for client in self.clients: if from_client_id and client.client_id == from_client_id: continue if client.canSaveNow(): self.expected_clients.append(client) client.save() self.waitAndGoTo(10000, (self.save_step1, outing), ray.WaitFor.REPLY) def save_step1(self, outing=False): self.cleanExpected() if outing: for client in self.clients: if client.hasError(): self.oscReply('/error', self.osc_path, ray.Err.GENERAL_ERROR, "Some clients could not save") break if not self.path: self.nextFunction() return session_file = self.path + '/raysession.xml' if (os.path.isfile(session_file) and not os.access(session_file, os.W_OK)): self.saveError(ray.Err.CREATE_FAILED) return try: file = open(session_file, 'w') except: self.saveError(ray.Err.CREATE_FAILED) return xml = QDomDocument() p = xml.createElement('RAYSESSION') p.setAttribute('VERSION', ray.VERSION) p.setAttribute('name', self.name) xml_cls = xml.createElement('Clients') xml_rmcls = xml.createElement('RemovedClients') xml_wins = xml.createElement('Windows') for client in self.clients: cl = xml.createElement('client') cl.setAttribute('id', client.client_id) cl.setAttribute('launched', int(bool(client.isRunning()))) client.writeXmlProperties(cl) xml_cls.appendChild(cl) for client in self.removed_clients: cl = xml.createElement('client') cl.setAttribute('id', client.client_id) client.writeXmlProperties(cl) xml_rmcls.appendChild(cl) if self.hasServer() and self.getServer().option_desktops_memory: self.desktops_memory.save() for win in self.desktops_memory.saved_windows: xml_win = xml.createElement('window') xml_win.setAttribute('class', win.wclass) xml_win.setAttribute('name', win.name) xml_win.setAttribute('desktop', win.desktop) xml_wins.appendChild(xml_win) p.appendChild(xml_cls) p.appendChild(xml_rmcls) p.appendChild(xml_wins) xml.appendChild(p) contents = ("\n" "\n") contents += xml.toString() try: file.write(contents) except: file.close() self.saveError(ray.Err.CREATE_FAILED) file.close() self.sendGuiMessage(_translate('GUIMSG', "Session saved.")) self.message("Session saved.") self.nextFunction() def saveDone(self): self.message("Done.") self.oscReply("/reply", self.osc_path, "Saved." ) self.setServerStatus(ray.ServerStatus.READY) def saveError(self, err_saving): self.message("Failed") m = _translate('Load Error', "Unknown error") if err_saving == ray.Err.CREATE_FAILED: m = _translate( 'GUIMSG', "Can't save session, session file is unwriteable !") self.message(m) self.sendGuiMessage(m) self.oscReply("/error", self.osc_path, ray.Err.CREATE_FAILED, m) self.process_order.clear() self.setServerStatus(ray.ServerStatus.READY) def snapshot(self, snapshot_name='', rewind_snapshot='', force=False, outing=False): if not force: server = self.getServer() if not (server and server.option_snapshots and not self.snapshoter.isAutoSnapshotPrevented() and self.snapshoter.hasChanges()): # add snapshot at the beginning of process_order self.nextFunction() return if outing: self.setServerStatus(ray.ServerStatus.OUT_SNAPSHOT) else: self.setServerStatus(ray.ServerStatus.SNAPSHOT) self.snapshoter.save(snapshot_name, rewind_snapshot, self.snapshot_step1, self.snapshotError) def snapshot_step1(self, aborted=False): if aborted: self.message('Snapshot aborted') self.sendGuiMessage(_translate('Snapshot', 'Snapshot aborted')) self.nextFunction() def snapshotDone(self): self.setServerStatus(ray.ServerStatus.READY) def snapshotError(self, err_snapshot, info_str=''): m = _translate('Snapshot Error', "Unknown error") if err_snapshot == ray.Err.SUBPROCESS_UNTERMINATED: m = _translate('Snapshot Error', "git didn't stop normally.\n%s") % info_str elif err_snapshot == ray.Err.SUBPROCESS_CRASH: m = _translate('Snapshot Error', "git crashes.\n%s") % info_str elif err_snapshot == ray.Err.SUBPROCESS_EXITCODE: m = _translate('Snapshot Error', "git exit with an error code.\n%s") % info_str self.message(m) self.sendGuiMessage(m) self.oscReply("/error", self.osc_path, err_snapshot, m) self.nextFunction() def closeNoSaveClients(self): self.cleanExpected() nosave2_clients_n = 0 for client in self.clients: if client.isRunning() and client.no_save_level == 2: self.expected_clients.append(client) nosave2_clients_n += 1 if nosave2_clients_n: server = self.getServer() if server and server.option_has_wmctrl: self.desktops_memory.setActiveWindowList() for client in self.expected_clients: self.desktops_memory.findAndClose(client.pid) duration = int(1000 * math.sqrt(nosave2_clients_n)) self.waitAndGoTo(duration, self.closeNoSaveClients_step1, ray.WaitFor.STOP) def closeNoSaveClients_step1(self): self.cleanExpected() has_nosave_clients = False for client in self.clients: if client.isRunning() and client.no_save_level: self.expected_clients.append(client) has_nosave_clients = True if has_nosave_clients: self.setServerStatus(ray.ServerStatus.WAIT_USER) self.timer_wu_progress_n = 0 self.timer_waituser_progress.start() # Timer (2mn) is restarted if an expected client has been closed self.waitAndGoTo(120000, self.nextFunction, ray.WaitFor.STOP, True) def close(self): self.sendGuiMessage( _translate('GUIMSG', "Commanding attached clients to quit.")) self.expected_clients.clear() self.removed_clients.clear() if not self.path: self.nextFunction() return self.setServerStatus(ray.ServerStatus.CLOSE) self.sendGui('/ray/gui/trash/clear') for client in self.clients.__reversed__(): if client.isRunning(): self.expected_clients.append(client) self.clients_to_quit.append(client) self.timer_quit.start() self.waitAndGoTo(30000, self.close_step1, ray.WaitFor.STOP) def close_step1(self): for client in self.expected_clients: client.kill() self.waitAndGoTo(1000, self.close_step2, ray.WaitFor.STOP) def close_step2(self): self.cleanExpected() for client in self.clients: client.setStatus(ray.ClientStatus.REMOVED) self.clients.clear() if self.path: lock_file = self.path + '/.lock' if os.path.isfile(lock_file): os.remove(lock_file) self.setPath('') self.sendGui("/ray/gui/session/name", "", "" ) self.nextFunction() def closeDone(self): self.oscReply("/reply", self.osc_path, "Closed.") self.message("Done") self.setServerStatus(ray.ServerStatus.OFF) def abortDone(self): self.oscReply("/reply", self.osc_path, "Aborted.") self.message("Done") self.setServerStatus(ray.ServerStatus.OFF) def new(self, new_session_name): self.sendGuiMessage( _translate('GUIMSG', "Creating new session \"%s\"") % new_session_name) spath = self.root + '/' + new_session_name try: os.makedirs(spath) except: self.oscReply("/error", self.osc_path, ray.Err.CREATE_FAILED, "Could not create the session directory") return self.setServerStatus(ray.ServerStatus.NEW) self.setPath(spath) self.oscReply("/reply", self.osc_path, "Created." ) self.sendGui("/ray/gui/session/name", self.name, self.path) self.nextFunction() def newDone(self): self.sendGuiMessage(_translate('GUIMSG', 'Session is ready')) self.setServerStatus(ray.ServerStatus.READY) def initSnapshot(self, spath, snapshot): self.setServerStatus(ray.ServerStatus.REWIND) if self.snapshoter.load(spath, snapshot, self.initSnapshotError): self.nextFunction() def initSnapshotError(self, err, info_str=''): m = _translate('Snapshot Error', "Snapshot error") if err == ray.Err.SUBPROCESS_UNTERMINATED: m = _translate('Snapshot Error', "command didn't stop normally:\n%s") % info_str elif err == ray.Err.SUBPROCESS_CRASH: m = _translate('Snapshot Error', "command crashes:\n%s") % info_str elif err == ray.Err.SUBPROCESS_EXITCODE: m = _translate('Snapshot Error', "command exit with an error code:\n%s") % info_str elif err == ray.Err.NO_SUCH_FILE: m = _translate('Snapshot Error', "error reading file:\n%s") % info_str self.message(m) self.sendGuiMessage(m) self.oscReply("/error", self.osc_path, err, m) self.setServerStatus(ray.ServerStatus.OFF) self.process_order.clear() def duplicate(self, new_session_full_name): if self.clientsHaveErrors(): self.sendError(ray.Err.GENERAL_ERROR, _translate('error', "Some clients could not save")) self.process_order.clear() return self.sendGui('/ray/gui/trash/clear') for client in self.clients: client.net_duplicate_state = -1 if (client.net_daemon_url and ray.isValidOscUrl(client.net_daemon_url)): self.send(Address(client.net_daemon_url), '/ray/session/duplicate_only', self.name, new_session_full_name, client.net_session_root) self.expected_clients.append(client) self.waitAndGoTo(2000, (self.duplicate_step1, new_session_full_name), ray.WaitFor.DUPLICATE_START) def duplicate_step1(self, new_session_full_name): spath = "%s/%s" % (self.root, new_session_full_name) self.setServerStatus(ray.ServerStatus.COPY) self.file_copier.startSessionCopy(self.path, spath, self.duplicate_step2, self.duplicateAborted, [new_session_full_name]) def duplicate_step2(self, new_session_full_name): self.cleanExpected() for client in self.clients: if client.net_duplicate_state == 0: self.expected_clients.append(client) self.waitAndGoTo(3600000, #1Hour (self.duplicate_step3, new_session_full_name), ray.WaitFor.DUPLICATE_FINISH) def duplicate_step3(self, new_session_full_name): self.adjustFilesAfterCopy(new_session_full_name, ray.Template.NONE) self.nextFunction() def duplicateAborted(self, new_session_full_name): self.process_order.clear() if self.osc_path.startswith('/nsm/server/'): self.oscReply("/error", self.osc_path, ray.Err.NO_SUCH_FILE, "No such file.") else: self.oscReply('/ray/net_daemon/duplicate_state', 1) self.setServerStatus(ray.ServerStatus.READY) def saveSessionTemplate(self, template_name, net=False): template_root = TemplateRoots.user_sessions if net: template_root = "%s/%s" \ % (self.root, TemplateRoots.net_session_name) spath = "%s/%s" % (template_root, template_name) #overwrite existing template if os.path.isdir(spath): if not os.access(spath, os.W_OK): self.sendError( ray.Err.GENERAL_ERROR, _translate( "error", "Impossible to save template, unwriteable file !")) self.setServerStatus(ray.ServerStatus.READY) return shutil.rmtree(spath) if not os.path.exists(template_root): os.makedirs(template_root) # For network sessions, # save as template the network session only # if there is no other server on this same machine. # Else, one could erase template just created by another one. # To prevent all confusion, # all seen machines are sent to prevent an erase by looping # (a network session can contains another network session # on the machine where is the master daemon, for example). for client in self.clients: if client.net_daemon_url: self.send(Address(client.net_daemon_url), '/ray/session/save_as_template', self.name, template_name, client.net_session_root) self.setServerStatus(ray.ServerStatus.COPY) self.file_copier.startSessionCopy(self.path, spath, self.saveSessionTemplate_step_1, self.saveSessionTemplateAborted, [template_name, net]) def saveSessionTemplate_step_1(self, template_name, net): tp_mode = ray.Template.SESSION_SAVE_NET if net else ray.Template.SESSION_SAVE for client in self.clients + self.removed_clients: client.adjustFilesAfterCopy(template_name, tp_mode) self.message("Done") self.sendGuiMessage(_translate('GUIMSG', "Session saved as template named %s") % template_name) self.oscReply("/reply", self.osc_path, "Saved as template.") self.setServerStatus(ray.ServerStatus.READY) def saveSessionTemplateAborted(self, template_name): self.process_order.clear() self.setServerStatus(ray.ServerStatus.READY) def prepareTemplate(self, new_session_full_name, template_name, net=False): template_root = TemplateRoots.user_sessions if net: template_root = "%s/%s" \ % (self.root, TemplateRoots.net_session_name) template_path = "%s/%s" % (template_root, template_name) if template_name.startswith('///'): template_name = template_name.replace('///', '') template_path = "%s/%s" \ % (TemplateRoots.factory_sessions, template_name) if not os.path.isdir(template_path): self.sendError(ray.Err.GENERAL_ERROR, _translate("error", "No template named %s") % template_name) return new_session_name = basename(new_session_full_name) spath = "%s/%s" % (self.root, new_session_full_name) if os.path.exists(spath): self.sendError(ray.Err.CREATE_FAILED, _translate("error", "Folder\n%s\nalready exists") % spath) return if self.path: self.setServerStatus(ray.ServerStatus.COPY) else: self.setServerStatus(ray.ServerStatus.PRECOPY) self.sendGui("/ray/gui/session/name", new_session_name, spath) self.file_copier.startSessionCopy(template_path, spath, self.prepareTemplate_step1, self.prepareTemplateAborted, [new_session_full_name]) def prepareTemplate_step1(self, new_session_full_name): self.adjustFilesAfterCopy(new_session_full_name, ray.Template.SESSION_LOAD) self.nextFunction() def prepareTemplateAborted(self, new_session_full_name): self.process_order.clear() if self.name: self.setServerStatus(ray.ServerStatus.READY) else: self.setServerStatus(ray.ServerStatus.OFF) self.setPath('') self.sendGui('/ray/gui/session/name', '', '') def load(self, session_full_name): #terminate or switch clients spath = self.root + '/' + session_full_name if session_full_name.startswith('/'): spath = session_full_name if not os.path.exists(spath): try: os.makedirs(spath) except: self.loadError(ray.Err.CREATE_FAILED) return multi_daemon_file = MultiDaemonFile.getInstance() if (multi_daemon_file and not multi_daemon_file.isFreeForSession(spath)): Terminal.warning("Session is used by another daemon") self.loadError(ray.Err.SESSION_LOCKED) return if os.path.isfile(spath + '/.lock'): Terminal.warning("Session is locked by another process") self.loadError(ray.Err.SESSION_LOCKED) return self.message("Attempting to open %s" % spath) session_ray_file = spath + '/raysession.xml' session_nsm_file = spath + '/session.nsm' is_ray_file = True try: ray_file = open(session_ray_file, 'r') except: is_ray_file = False if not is_ray_file: try: file = open(session_nsm_file, 'r') except: try: ray_file = open(session_ray_file, 'w') xml = QDomDocument() p = xml.createElement('RAYSESSION') p.setAttribute('VERSION', ray.VERSION) if self.isNsmLocked(): name = basename(session_full_name).rpartition('.')[0] p.setAttribute('name', name) xml.appendChild(p) ray_file.write(xml.toString()) ray_file.close() ray_file = open(session_ray_file, 'r') is_ray_file = True except: self.loadError(ray.Err.CREATE_FAILED) return self.new_clients = [] self.new_removed_clients = [] new_client_exec_args = [] if is_ray_file: xml = QDomDocument() try: xml.setContent(ray_file.read()) except: self.loadError(ray.Err.BAD_PROJECT) return content = xml.documentElement() if content.tagName() != "RAYSESSION": ray_file.close() self.loadError(ray.Err.BAD_PROJECT) return sess_name = content.attribute('name') if sess_name: self.name = sess_name client_id_list = [] nodes = content.childNodes() for i in range(nodes.count()): node = nodes.at(i) tag_name = node.toElement().tagName() if tag_name in ('Clients', 'RemovedClients'): clients_xml = node.toElement().childNodes() for j in range(clients_xml.count()): client_xml = clients_xml.at(j) client = Client(self) cx = client_xml.toElement() client.readXmlProperties(cx) if client.client_id in client_id_list: # prevent double same id continue if tag_name == 'Clients': if client.auto_start: new_client_exec_args.append( (client.executable_path, client.arguments)) self.new_clients.append(client) elif tag_name == 'RemovedClients': self.new_removed_clients.append(client) #client.sendGuiClientProperties(removed=True) else: continue client_id_list.append(client.client_id) elif tag_name == "Windows": server = self.getServer() if server and server.option_desktops_memory: self.desktops_memory.readXml(node.toElement()) ray_file.close() else: for line in file.read().split('\n'): elements = line.split(':') if len(elements) >= 3: client = Client(self) client.name = elements[0] client.executable_path = elements[1] client.client_id = elements[2] client.prefix_mode = ray.PrefixMode.CLIENT_NAME self.new_clients.append(client) new_client_exec_args.append((client.executable_path, '')) file.close() # Here we are sure to have all needed # Load work starts here self.sendGuiMessage(_translate('GUIMSG', "Opening session %s") % session_full_name) self.setPath(spath) if not is_ray_file: self.sendGui('/ray/gui/session/is_nsm') self.sendGui('/ray/gui/trash/clear') self.removed_clients.clear() for client in self.new_removed_clients: self.removed_clients.append(client) client.sendGuiClientProperties(removed=True) self.message("Commanding unneeded and dumb clients to quit") byebye_client_list = [] for client in self.clients: if ((client.active and client.isCapableOf(':switch:') or (client.isDumbClient() and client.isRunning())) and ((client.running_executable, client.running_arguments) in new_client_exec_args)): # client will switch # or keep alive if non active and running new_client_exec_args.remove( (client.running_executable, client.running_arguments)) else: # client is not capable of switch, or is not wanted # in the new session if client.isRunning(): self.expected_clients.append(client) client.quit() else: byebye_client_list.append(client) for client in byebye_client_list: if client in self.clients: self.removeClient(client) else: raise NameError('no client %s to remove' % client.client_id) if self.expected_clients: self.setServerStatus(ray.ServerStatus.CLEAR) self.waitAndGoTo(20000, self.load_step1, ray.WaitFor.STOP) def load_step1(self): self.cleanExpected() self.message("Commanding smart clients to switch") has_switch = False new_client_id_list = [] for new_client in self.new_clients: #/* in a duplicated session, clients will have the same #* IDs, so be sure to pick the right one to avoid race #* conditions in JACK name registration. */ for client in self.clients: if (client.client_id == new_client.client_id and client.running_executable == new_client.executable_path and client.running_arguments == new_client.arguments): #we found the good existing client break else: for client in self.clients: if (client.running_executable == new_client.executable_path and client.running_arguments == new_client.arguments): #we found a switchable client break else: client = None if client and client.isRunning(): if client.active and not client.isReplyPending(): #since we already shutdown clients not capable of #'switch', we can assume that these are. client.switch(new_client) has_switch = True else: #* sleep a little bit because liblo derives its sequence #* of port numbers from the system time (second #* resolution) and if too many clients start at once they #* won't be able to find a free port. */ if not self.addClient(new_client): continue if new_client.auto_start and not self.is_dummy: self.clients_to_launch.append(new_client) if (not new_client.executable_path in RS.non_active_clients): self.expected_clients.append(new_client) new_client_id_list.append(new_client.client_id) self.sendGui("/ray/gui/session/name", self.name, self.path) if has_switch: self.setServerStatus(ray.ServerStatus.SWITCH) else: self.setServerStatus(ray.ServerStatus.LAUNCH) #* this part is a little tricky... the clients need some time to #* send their 'announce' messages before we can send them 'open' #* and know that a reply is pending and we should continue waiting #* until they finish. #* dumb clients will never send an 'announce message', so we need #* to give up waiting on them fairly soon. */ self.timer_launch.start() self.reOrderClients(new_client_id_list) self.sendGui('/ray/gui/session/sort_clients', *new_client_id_list) self.waitAndGoTo(5000, self.load_step2, ray.WaitFor.ANNOUNCE) def load_step2(self): for client in self.expected_clients: if not client.executable_path in RS.non_active_clients: RS.non_active_clients.append(client.executable_path) RS.settings.setValue('daemon/non_active_list', RS.non_active_clients) self.cleanExpected() self.setServerStatus(ray.ServerStatus.OPEN) for client in self.clients: if client.active and client.isReplyPending(): self.expected_clients.append(client) elif client.isRunning() and client.isDumbClient(): client.setStatus(ray.ClientStatus.NOOP) self.waitAndGoTo(10000, self.load_step3, ray.WaitFor.REPLY) def load_step3(self): self.cleanExpected() server = self.getServer() if server and server.option_desktops_memory: self.desktops_memory.replace() self.tellAllClientsSessionIsLoaded() self.message('Loaded') self.sendGui("/ray/gui/session/name", self.name, self.path) self.oscReply("/reply", self.osc_path, "Loaded.") self.nextFunction() def loadDone(self): self.message("Done") self.setServerStatus(ray.ServerStatus.READY) def loadError(self, err_loading): self.message("Failed") m = _translate('Load Error', "Unknown error") if err_loading == ray.Err.CREATE_FAILED: m = _translate('Load Error', "Could not create session file!") elif err_loading == ray.Err.SESSION_LOCKED: m = _translate('Load Error', "Session is locked by another process!") elif err_loading == ray.Err.NO_SUCH_FILE: m = _translate('Load Error', "The named session does not exist.") elif err_loading == ray.Err.BAD_PROJECT: m = _translate('Load Error', "Could not load session file.") self.oscReply("/error", self.osc_path, err_loading, m) if self.path: self.setServerStatus(ray.ServerStatus.READY) else: self.setServerStatus(ray.ServerStatus.OFF) self.process_order.clear() def duplicateOnlyDone(self): self.oscReply('/ray/net_daemon/duplicate_state', 1) def duplicateDone(self): self.message("Done") self.oscReply("/reply", self.osc_path, "Duplicated.") self.setServerStatus(ray.ServerStatus.READY) def exitNow(self): self.message("Bye Bye...") self.setServerStatus(ray.ServerStatus.OFF) QCoreApplication.quit() def addClientTemplate(self, template_name, factory=False): templates_root = TemplateRoots.user_clients if factory: templates_root = TemplateRoots.factory_clients xml_file = "%s/%s" % (templates_root, 'client_templates.xml') file = open(xml_file, 'r') xml = QDomDocument() xml.setContent(file.read()) file.close() if xml.documentElement().tagName() != 'RAY-CLIENT-TEMPLATES': return nodes = xml.documentElement().childNodes() for i in range(nodes.count()): node = nodes.at(i) ct = node.toElement() if ct.tagName() != 'Client-Template': continue if ct.attribute('template-name') == template_name: client = Client(self) client.readXmlProperties(ct) needed_version = ct.attribute('needed-version') if (needed_version.startswith('.') or needed_version.endswith('.') or not needed_version.replace('.', '').isdigit()): #needed-version not writed correctly, ignores it needed_version = '' if factory and needed_version: version_process = QProcess() version_process.start(client.executable_path, ['--version']) version_process.waitForFinished(500) if version_process.state(): version_process.terminate() version_process.waitForFinished(500) continue full_program_version = str( version_process.readAllStandardOutput(), encoding='utf-8') previous_is_digit = False program_version = '' for character in full_program_version: if character.isdigit(): program_version+=character previous_is_digit = True elif character == '.': if previous_is_digit: program_version+=character previous_is_digit = False else: if program_version: break if not program_version: continue neededs = [] progvss = [] for n in needed_version.split('.'): neededs.append(int(n)) for n in program_version.split('.'): progvss.append(int(n)) if neededs > progvss: node = node.nextSibling() continue full_name_files = [] if not needed_version: #if there is a needed version, #then files are ignored because factory templates with #version must be NSM compatible #and dont need files (factory) template_path = "%s/%s" % (templates_root, template_name) if os.path.isdir(template_path): for file in os.listdir(template_path): full_name_files.append("%s/%s" % (template_path, file)) if self.addClient(client): if full_name_files: client.setStatus(ray.ClientStatus.PRECOPY) self.file_copier.startClientCopy( client.client_id, full_name_files, self.path, self.addClientTemplate_step_1, self.addClientTemplateAborted, [client]) else: self.addClientTemplate_step_1(client) break else: # no template found with that name for favorite in RS.favorites: if (favorite.name == template_name and favorite.factory == factory): self.sendGui('/ray/gui/favorites/removed', favorite.name, int(favorite.factory)) RS.favorites.remove(favorite) break def addClientTemplate_step_1(self, client): client.adjustFilesAfterCopy(self.name, ray.Template.CLIENT_LOAD) if client.auto_start: client.start() else: client.setStatus(ray.ClientStatus.STOPPED) def addClientTemplateAborted(self, client): self.removeClient(client) def closeClient(self, client): self.setServerStatus(ray.ServerStatus.READY) self.expected_clients.append(client) client.stop() self.waitAndGoTo(30000, (self.closeClient_step1, client), ray.WaitFor.STOP_ONE) def closeClient_step1(self, client): if client in self.expected_clients: client.kill() self.waitAndGoTo(1000, self.nextFunction, ray.WaitFor.STOP_ONE) def loadClientSnapshot(self, client_id, snapshot): self.setServerStatus(ray.ServerStatus.REWIND) if self.snapshoter.loadClientExclusive(client_id, snapshot, self.loadClientSnapshotError): self.setServerStatus(ray.ServerStatus.READY) self.nextFunction() def loadClientSnapshotError(self, err, info_str=''): m = _translate('Snapshot Error', "Snapshot error") if err == ray.Err.SUBPROCESS_UNTERMINATED: m = _translate('Snapshot Error', "command didn't stop normally:\n%s") % info_str elif err == ray.Err.SUBPROCESS_CRASH: m = _translate('Snapshot Error', "command crashes:\n%s") % info_str elif err == ray.Err.SUBPROCESS_EXITCODE: m = _translate('Snapshot Error', "command exit with an error code:\n%s") % info_str elif err == ray.Err.NO_SUCH_FILE: m = _translate('Snapshot Error', "error reading file:\n%s") % info_str self.message(m) self.sendGuiMessage(m) self.oscReply("/error", self.osc_path, err, m) self.setServerStatus(ray.ServerStatus.OFF) self.process_order.clear() def startClient(self, client): client.start() self.nextFunction() class SignaledSession(OperatingSession): def __init__(self, root): OperatingSession.__init__(self, root) signaler.osc_recv.connect(self.oscReceive) signaler.dummy_load_and_template.connect(self.dummyLoadAndTemplate) def oscReceive(self, path, args, types, src_addr): nsm_equivs = {"/nsm/server/add" : "/ray/session/add_executable", "/nsm/server/save": "/ray/session/save", "/nsm/server/open": "/ray/server/open_session", "/nsm/server/new" : "/ray/server/new_session", "/nsm/server/duplicate": "/ray/session/duplicate", "/nsm/server/close": "/ray/session/close", "/nsm/server/abort": "/ray/session/abort", "/nsm/server/quit" : "/ray/server/quit"} # /nsm/server/list is not used here because it doesn't # works as /ray/server/list_sessions nsm_path = nsm_equivs.get(path) func_path = nsm_path if nsm_path else path func_name = func_path.replace('/', '', 1).replace('/', '_') if func_name in self.__dir__(): function = self.__getattribute__(func_name) function(path, args, src_addr) ############## FUNCTIONS CONNECTED TO SIGNALS FROM OSC ################### def nsm_server_announce(self, path, args, src_addr): client_name, capabilities, executable_path, major, minor, pid = args if self.wait_for == ray.WaitFor.STOP: if path.startswith('/nsm/server/'): # Error is wrong but compatible with NSM API self.send(src_addr, "/error", path, ray.Err.NO_SESSION_OPEN, "Sorry, but there's no session open " + "for this application to join.") return #we can't be absolutely sure that the announcer is the good one #but if client announce a known PID, #we can be sure of which client is announcing for client in self.clients: if client.pid == pid and not client.active and client.isRunning(): client.serverAnnounce(path, args, src_addr, False) break else: for client in self.clients: if (not client.active and client.isRunning() and ray.isPidChildOf(pid, client.pid)): client.serverAnnounce(path, args, src_addr, False) break else: # Client launched externally from daemon # by command : $:NSM_URL=url executable client = self.newClient(args[2]) self.externals_timer.start() client.serverAnnounce(path, args, src_addr, True) #n = 0 #for client in self.clients: #if (basename(client.executable_path) \ #== basename(executable_path) #and not client.active #and client.pending_command == ray.Command.START): #n+=1 #if n>1: #break #if n == 0: ## Client launched externally from daemon ## by command : $:NSM_URL=url executable #client = self.newClient(args[2]) #client.is_external = True #self.externals_timer.start() #client.serverAnnounce(path, args, src_addr, True) #return #elif n == 1: #for client in self.clients: #if (basename(client.executable_path) \ #== basename(executable_path) #and not client.active #and client.pending_command == ray.Command.START): #client.serverAnnounce(path, args, src_addr, False) #break #else: #for client in self.clients: #if (not client.active #and client.pending_command == ray.Command.START): #if ray.isPidChildOf(pid, client.pid): #client.serverAnnounce(path, args, #src_addr, False) #break if self.wait_for == ray.WaitFor.ANNOUNCE: self.endTimerIfLastExpected(client) def reply(self, path, args, src_addr): if self.wait_for == ray.WaitFor.STOP: return message = args[1] client = self.getClientByAddress(src_addr) if client: client.setReply(ray.Err.OK, message) #self.message( "Client \"%s\" replied with: %s in %fms" #% (client.name, message, #client.milliseconds_since_last_command())) if client.pending_command == ray.Command.SAVE: client.last_save_time = time.time() client.pending_command = ray.Command.NONE client.setStatus(ray.ClientStatus.READY) server = self.getServer() if (server and server.getServerStatus() == ray.ServerStatus.READY and server.option_desktops_memory): self.desktops_memory.replace() if self.wait_for == ray.WaitFor.REPLY: self.endTimerIfLastExpected(client) else: self.message("Reply from unknown client") def nsm_client_is_clean(self, path, args, src_addr): # save session from client clean (not dirty) message if self.process_order: return if self.file_copier.isActive(): return self.rememberOscArgs(path, args, None) client = self.getClientByAddress(src_addr) if not client: return self.process_order = [(self.save, client.client_id), self.snapshot, self.saveDone] self.nextFunction() def nsm_client_label(self, path, args, src_addr): client = self.getClientByAddress(src_addr) if client: client.setLabel(args[0]) def nsm_client_network_properties(self, path, args, src_addr): client = self.getClientByAddress(src_addr) if client: net_daemon_url, net_session_root = args client.setNetworkProperties(net_daemon_url, net_session_root) def nsm_client_no_save_level(self, path, args, src_addr): client = self.getClientByAddress(src_addr) if client and client.isCapableOf(':warning-no-save:'): client.no_save_level = args[0] self.sendGui('/ray/gui/client/no_save_level', client.client_id, client.no_save_level) def ray_server_abort_copy(self, path, args, src_addr): self.file_copier.abort() def ray_server_abort_snapshot(self, path, args, src_addr): self.snapshoter.abort() def ray_server_list_sessions(self, path, args, src_addr): with_net = args[0] if with_net: for client in self.clients: if client.net_daemon_url: self.send(Address(client.net_daemon_url), '/ray/server/list_sessions', 1) if not self.root: return session_list = [] for root, dirs, files in os.walk(self.root): #exclude hidden files and dirs files = [f for f in files if not f.startswith('.')] dirs[:] = [d for d in dirs if not d.startswith('.')] if root == self.root: continue already_sent = False for file in files: if file in ('raysession.xml', 'session.nsm'): if not already_sent: basefolder = root.replace(self.root + '/', '', 1) session_list.append(basefolder) if len(session_list) == 20: self.send(src_addr, "/reply", path, *session_list) session_list.clear() already_sent = True if session_list: self.send(src_addr, "/reply", path, *session_list) def nsm_server_list(self, path, args, src_addr): session_list = [] if self.root: for root, dirs, files in os.walk(self.root): #exclude hidden files and dirs files = [f for f in files if not f.startswith('.')] dirs[:] = [d for d in dirs if not d.startswith('.')] if root == self.root: continue for file in files: if file in ('raysession.xml', 'session.nsm'): basefolder = root.replace(self.root + '/', '', 1) self.send(src_addr, '/reply', '/nsm/server/list', basefolder) self.send(src_addr, path, ray.Err.OK, "Done.") @session_operation def ray_server_new_session(self, path, args, src_addr): if len(args) == 2 and args[1]: session_name, template_name = args spath = '' if session_name.startswith('/'): spath = session_name else: spath = "%s/%s" % (self.root, session_name) if not os.path.exists(spath): self.process_order = [self.save, self.closeNoSaveClients, self.snapshot, (self.prepareTemplate, *args, False), (self.load, session_name), self.newDone] return self.process_order = [self.save, self.closeNoSaveClients, self.snapshot, self.close, (self.new, args[0]), self.save, self.newDone] @session_operation def ray_server_open_session(self, path, args, src_addr): if len(args) == 2 and args[1]: session_name, template_name = args if not session_name: # send error TODO return spath = '' if session_name.startswith('/'): spath = session_name else: spath = "%s/%s" % (self.root, session_name) if not os.path.exists(spath): self.process_order = [(self.save, '', True), self.closeNoSaveClients, (self.snapshot, '', '', False, True), (self.prepareTemplate, *args, True), (self.load, session_name), self.loadDone] return if not args[0]: # send error TODO return self.process_order = [(self.save, '', True), self.closeNoSaveClients, (self.snapshot, '', '', False, True), (self.load, args[0]), self.loadDone] @session_operation def ray_session_save(self, path, args, src_addr): self.process_order = [self.save, self.snapshot, self.saveDone] @session_operation def ray_session_save_as_template(self, path, args, src_addr): template_name = args[0] net = bool(len(args) == 3) for client in self.clients: if client.executable_path == 'ray-network': client.net_session_template = template_name self.process_order = [self.save, self.snapshot, (self.saveSessionTemplate, template_name, net)] @session_operation def ray_session_take_snapshot(self, path, args, src_addr): snapshot_name, with_save = args self.process_order.clear() if with_save: self.process_order.append(self.save) self.process_order += [(self.snapshot, snapshot_name, '', True), self.snapshotDone] @session_operation def ray_session_close(self, path, args, src_addr): self.process_order = [(self.save, '', True), self.closeNoSaveClients, self.snapshot, self.close, self.closeDone] def ray_session_abort(self, path, args, src_addr): if not self.path: self.serverSend(src_addr, "/error", path, ray.Err.NO_SESSION_OPEN, "No session to abort." ) return self.wait_for = ray.WaitFor.NONE self.timer.stop() # Non Session Manager can't abort if an operation pending # RS can and it would be a big regression to remove this feature # So before to abort we need to send an error reply # to the last server control message # if an operation pending. if self.process_order and self.osc_path.startswith('/nsm/server/'): short_path = self.osc_path.rpartition('/')[2] if short_path == 'save': self.saveError(ray.Err.CREATE_FAILED) elif short_path == 'open': self.loadError(ray.Err.SESSION_LOCKED) elif short_path == 'new': self.oscReply("/error", self.osc_path, ray.Err.CREATE_FAILED, "Could not create the session directory") elif short_path == 'duplicate': self.duplicateAborted(self.osc_args[0]) elif short_path in ('close', 'abort', 'quit'): # let the current close works here self.send(src_addr, "/error", path, ray.Err.OPERATION_PENDING, "An operation pending.") return self.rememberOscArgs(path, args, src_addr) self.process_order = [self.close, self.abortDone] if self.file_copier.isActive(): self.file_copier.abort(self.nextFunction, []) else: self.nextFunction() def ray_server_quit(self, path, args, src_addr): self.process_order = [self.close, self.exitNow] if self.file_copier.isActive(): self.file_copier.abort(self.nextFunction, []) else: self.nextFunction() def ray_session_cancel_close(self, path, args, src_addr): if not self.process_order: return self.timer.stop() self.timer_waituser_progress.stop() self.process_order.clear() self.cleanExpected() self.setServerStatus(ray.ServerStatus.READY) def ray_session_skip_wait_user(self, path, args, src_addr): if not self.process_order: return self.timer.stop() self.timer_waituser_progress.stop() self.cleanExpected() self.nextFunction() @session_operation def ray_session_duplicate(self, path, args, src_addr): new_session_full_name = args[0] self.process_order = [self.save, self.closeNoSaveClients, self.snapshot, (self.duplicate, new_session_full_name), (self.load, new_session_full_name), self.duplicateDone] def ray_session_duplicate_only(self, path, args, src_addr): session_to_load, new_session, sess_root = args if sess_root == self.root and session_to_load == self.name: if (self.process_order or len(args) != 1 or self.file_copier.isActive()): self.oscReply('/ray/net_daemon/duplicate_state', 1) return self.rememberOscArgs(path, args, src_addr) self.process_order = [self.save, self.snapshot, (self.duplicate, new_session), self.duplicateOnlyDone] self.nextFunction() else: tmp_session = DummySession(sess_root) tmp_session.osc_src_addr = src_addr tmp_session.dummyDuplicate(session_to_load, new_session) @session_operation def ray_session_open_snapshot(self, path, args, src_addr): if not self.path: return snapshot = args[0] self.process_order = [self.save, self.closeNoSaveClients, (self.snapshot, '', snapshot, True), self.close, (self.initSnapshot, self.path, snapshot), (self.load, self.path), self.loadDone] def ray_session_rename(self, path, args, src_addr): new_session_name = args[0] if self.process_order: return if not self.path: return if self.file_copier.isActive(): return if new_session_name == self.name: return if not self.isNsmLocked(): for filename in os.listdir(dirname(self.path)): if filename == new_session_name: return for client in self.clients: if client.isRunning(): self.sendGuiMessage( _translate('GUIMSG', 'Stop all clients before rename session !')) return for client in self.clients + self.removed_clients: client.adjustFilesAfterCopy(new_session_name, ray.Template.RENAME) self.sendGuiMessage( _translate('GUIMSG', 'Session %s has been renamed to %s .') % (self.name, new_session_name)) if not self.isNsmLocked(): try: spath = "%s/%s" % (dirname(self.path), new_session_name) subprocess.run(['mv', self.path, spath]) self.setPath(spath) self.sendGuiMessage( _translate('GUIMSG', 'Session directory is now: %s') % self.path) except: pass self.sendGui('/ray/gui/session/name', self.name, self.path) def ray_session_add_executable(self, path, args, src_addr): self.rememberOscArgs(path, args, src_addr) executable = args[0] via_proxy = 0 prefix_mode = ray.PrefixMode.SESSION_NAME custom_prefix = '' client_id = "" if len(args) == 5: executable, via_proxy, prefix_mode, custom_prefix, client_id = args if prefix_mode == ray.PrefixMode.CUSTOM and not custom_prefix: prefix_mode = ray.PrefixMode.SESSION_NAME if client_id: if not client_id.isalnum(): self.sendError(ray.Err.CREATE_FAILED, _translate("client_id %s is not alphanumeric") % client_id ) return # Check if client_id already exists for client in self.clients + self.removed_clients: if client.client_id == client_id: self.sendError(ray.Err.CREATE_FAILED, _translate("client_id %s is already used") % client_id ) return if not client_id: client_id = self.generateClientId(executable) client = Client(self) if via_proxy: client.executable_path = 'ray-proxy' client.tmp_arguments = "--executable %s" % executable else: client.executable_path = executable client.name = basename(executable) client.client_id = client_id client.prefix_mode = prefix_mode client.custom_prefix = custom_prefix client.icon = client.name.lower().replace('_', '-') client.setDefaultGitIgnored(executable) if self.addClient(client): client.start() def ray_session_add_proxy(self, path, args, src_addr): self.rememberOscArgs(path, args, src_addr) executable = args[0] client = Client(self) client.executable_path = 'ray-proxy' client.tmp_arguments = "--executable %s" % executable if CommandLineArgs.debug: client.tmp_arguments += " --debug" client.name = basename(executable) client.client_id = self.generateClientId(client.name) client.icon = client.name.lower().replace('_', '-') client.setDefaultGitIgnored(executable) if self.addClient(client): client.start() def ray_session_add_client_template(self, path, args, src_addr): self.rememberOscArgs(path, args, src_addr) factory = bool(args[0]) template_name = args[1] self.addClientTemplate(template_name, factory) def ray_session_reorder_clients(self, path, args, src_addr): client_ids_list = args self.reOrderClients(client_ids_list) def ray_session_list_snapshots(self, path, args, src_addr, client_id=""): auto_snapshot = not bool( self.snapshoter.isAutoSnapshotPrevented()) self.sendGui('/ray/gui/session/auto_snapshot', int(auto_snapshot)) snapshots = self.snapshoter.list(client_id) i=0 snap_send = [] for snapshot in snapshots: if i == 20: self.serverSend(src_addr, '/reply', path, *snap_send) snap_send.clear() i=0 else: snap_send.append(snapshot) i+=1 if snap_send: self.serverSend(src_addr, '/reply', path, *snap_send) def ray_session_set_auto_snapshot(self, path, args, src_addr): self.snapshoter.setAutoSnapshot(bool(args[0])) def ray_client_stop(self, path, args, src_addr): for client in self.clients: if client.client_id == args[0]: client.stop() self.send(src_addr, "/reply", "Client stopped." ) break else: self.send(src_addr, "/error", -10, "No such client." ) def ray_client_kill(self, path, args, src_addr): for client in self.clients: if client.client_id == args[0]: client.kill() self.send(src_addr, "/reply", "Client killed." ) break else: self.send(src_addr, "/error", -10, "No such client." ) def ray_client_trash(self, path, args, src_addr): client_id = args[0] for client in self.clients: if client.client_id == client_id: if client.isRunning(): return if self.file_copier.isActive(client_id): self.file_copier.abort() return self.trashClient(client) self.send(src_addr, "/reply", "Client removed.") break else: self.send(src_addr, "/error", -10, "No such client.") def ray_client_resume(self, path, args, src_addr): for client in self.clients: if client.client_id == args[0] and not client.isRunning(): if self.file_copier.isActive(client.client_id): self.send(src_addr, "/error", -13, "Impossible, copy running") return client.start() break def ray_client_save(self, path, args, src_addr): for client in self.clients: if client.client_id == args[0] and client.active: if self.file_copier.isActive(client.client_id): self.send(src_addr, "/error", -13, "Impossible, copy running") return client.save() break def ray_client_save_as_template(self, path, args, src_addr): if self.file_copier.isActive(): self.send(src_addr, "/error", ray.Err.COPY_RUNNING, _translate('error_message', "Impossible, copy running")) return for client in self.clients: if client.client_id == args[0]: client.saveAsTemplate(args[1]) break def ray_client_update_properties(self, path, args, src_addr): client_data = ray.ClientData(*args) for client in self.clients: if client.client_id == client_data.client_id: client.updateClientProperties(client_data) break def ray_client_list_snapshots(self, path, args, src_addr): self.ray_session_list_snapshots(path, [], src_addr, args[0]) @session_operation def ray_client_open_snapshot(self, path, args, src_addr): client_id, snapshot = args for client in self.clients: if client.client_id == client_id: if client.isRunning(): self.process_order = [self.save, (self.snapshot, '', snapshot, True), (self.closeClient, client), (self.loadClientSnapshot, client_id, snapshot), (self.startClient, client)] else: self.process_order = [self.save, (self.snapshot, '', snapshot, True), (self.loadClientSnapshot, client_id, snapshot)] break else: self.send(src_addr, '/error', path, "No client with %s client_id" % client_id) def ray_net_daemon_duplicate_state(self, path, args, src_addr): state = args[0] for client in self.clients: if (client.net_daemon_url and ray.areSameOscPort(client.net_daemon_url, src_addr.url)): client.net_duplicate_state = state client.net_daemon_copy_timer.stop() break else: return if state == 1: if self.wait_for == ray.WaitFor.DUPLICATE_FINISH: self.endTimerIfLastExpected(client) return if (self.wait_for == ray.WaitFor.DUPLICATE_START and state == 0): self.endTimerIfLastExpected(client) client.net_daemon_copy_timer.start() def ray_trash_restore(self, path, args, src_addr): for client in self.removed_clients: if client.client_id == args[0]: self.restoreClient(client) break else: self.send(src_addr, "/error", -10, "No such client.") def ray_trash_remove_definitely(self, path, args, src_addr): for client in self.removed_clients: if client.client_id == args[0]: break else: return self.sendGui('/ray/gui/trash/remove', client.client_id) for file in client.getProjectFiles(): try: subprocess.run(['rm', '-R', file]) except: continue self.removed_clients.remove(client) def ray_option_bookmark_session_folder(self, path, args, src_addr): if self.path: if args[0]: self.bookmarker.makeAll(self.path) else: self.bookmarker.removeAll(self.path) def serverOpenSessionAtStart(self, session_name): self.process_order = [self.save, (self.load, session_name), self.loadDone] self.nextFunction() def dummyLoadAndTemplate(self, session_name, template_name, sess_root): tmp_session = DummySession(sess_root) tmp_session.dummyLoadAndTemplate(session_name, template_name) def terminate(self): if self.terminated_yet: return if self.file_copier.isActive(): self.file_copier.abort() self.terminated_yet = True self.process_order = [self.close, self.exitNow] self.nextFunction() class DummySession(OperatingSession): def __init__(self, root): OperatingSession.__init__(self, root) self.is_dummy = True def dummyLoadAndTemplate(self, session_full_name, template_name): self.process_order = [(self.load, session_full_name), (self.saveSessionTemplate, template_name, True)] self.nextFunction() def dummyDuplicate(self, session_to_load, new_session_full_name): self.process_order = [(self.load, session_to_load), (self.duplicate, new_session_full_name), self.duplicateOnlyDone] self.nextFunction() RaySession-0.8.3/src/daemon/signaler.py000066400000000000000000000007171356671433200200500ustar00rootroot00000000000000from PyQt5.QtCore import QObject, pyqtSignal instance = None class Signaler(QObject): osc_recv = pyqtSignal(str, list, str, object) dummy_load_and_template = pyqtSignal(str, str, str) @staticmethod def instance(): global instance if not instance: instance = Signaler() return instance def __init__(self): QObject.__init__(self) global instance instance = self RaySession-0.8.3/src/daemon/snapshoter.py000066400000000000000000000525001356671433200204270ustar00rootroot00000000000000import locale import os import shutil import socket import subprocess import sys from PyQt5.QtCore import (QProcess, QProcessEnvironment, QTimer, QObject, pyqtSignal, QDateTime) from PyQt5.QtXml import QDomDocument import ray from daemon_tools import Terminal def gitStringer(string): for char in (' ', '*', '?', '[', ']', '(', ')'): string = string.replace(char, "\\" + char) for char in ('#', '!'): if string.startswith(char): string = "\\" + string return string def fullRefForGui(ref, name, rw_ref, rw_name='', ss_name=''): if ss_name: return "%s:%s\n%s:%s\n%s" % (ref, name, rw_ref, rw_name, ss_name) return "%s:%s\n%s:%s" % (ref, name, rw_ref, rw_name) class Snapshoter(QObject): def __init__(self, session): QObject.__init__(self) self.session = session self.git_exec = 'git' self.gitdir = '.ray-snapshots' self.exclude_path = 'info/exclude' self.history_path = "session_history.xml" self.max_file_size = 50 #in Mb self.next_snapshot_name = '' self.next_rw_snapshot = '' self.changes_checker = QProcess() self.changes_checker.readyReadStandardOutput.connect( self.changesCheckerStandardOutput) self.adder_process = QProcess() self.adder_process.finished.connect(self.save_step_1) self.adder_process.readyReadStandardOutput.connect( self.adderStandardOutput) self._adder_aborted = False self.git_process = QProcess() self.git_process.readyReadStandardOutput.connect(self.standardOutput) self.git_process.readyReadStandardError.connect(self.standardError) self.git_command = '' self._n_file_changed = 0 self._n_file_treated = 0 self._changes_counted = False self.next_function = None self.error_function = None def changesCheckerStandardOutput(self): standard_output = self.changes_checker.readAllStandardOutput().data() self._n_file_changed += len(standard_output.decode().split('\n')) -1 def adderStandardOutput(self): standard_output = self.adder_process.readAllStandardOutput().data() Terminal.snapshoterMessage(standard_output, ' add -A -v') if not self._n_file_changed: return self._n_file_treated += len(standard_output.decode().split('\n')) -1 self.session.sendGui('/ray/gui/server/progress', self._n_file_treated / self._n_file_changed) def standardError(self): standard_error = self.git_process.readAllStandardError().data() Terminal.snapshoterMessage(standard_error, self.git_command) def standardOutput(self): standard_output = self.git_process.readAllStandardOutput().data() Terminal.snapshoterMessage(standard_output, self.git_command) def getGitDir(self): if not self.session.path: raise NameError("attempting to save with no session path !!!") return "%s/%s" % (self.session.path, self.gitdir) def runGitProcess(self, *all_args): return self.runGitProcessAt(self.session.path, *all_args) def runGitProcessAt(self, spath, *all_args): self.git_command = '' for arg in all_args: self.git_command += ' %s' % arg err = ray.Err.OK git_args = self.getGitCommandListAt(spath, *all_args) self.git_process.start(self.git_exec, git_args) if not self.git_process.waitForFinished(2000): self.git_process.kill() err = ray.Err.SUBPROCESS_UNTERMINATED else: if self.git_process.exitStatus(): err = ray.Err.SUBPROCESS_CRASH elif self.git_process.exitCode(): err = ray.Err.SUBPROCESS_EXITCODE if err and self.error_function: self.error_function(err, ' '.join(all_args)) return not(bool(err)) def getGitCommandList(self, *args): return self.getGitCommandListAt(self.session.path, *args) def getGitCommandListAt(self, spath, *args): first_args = ['--work-tree', spath, '--git-dir', "%s/%s" % (spath, self.gitdir)] return first_args + list(args) def getHistoryFullPath(self): return "%s/%s/%s" % ( self.session.path, self.gitdir, self.history_path) def getHistoryXmlDocumentElement(self): if not self.isInit(): return None file_path = self.getHistoryFullPath() xml = QDomDocument() try: history_file = open(file_path, 'r') xml.setContent(history_file.read()) history_file.close() except BaseException: return None SNS_xml = xml.documentElement() if SNS_xml.tagName() != 'SNAPSHOTS': return None return SNS_xml def list(self, client_id=""): SNS_xml = self.getHistoryXmlDocumentElement() if not SNS_xml: return [] nodes = SNS_xml.childNodes() all_tags = [] all_snaps = [] prv_session_name = self.session.name for i in range(nodes.count()): node = nodes.at(i) el = node.toElement() if client_id: client_nodes = node.childNodes() for j in range(client_nodes.count()): client_node = client_nodes.at(j) client_el = client_node.toElement() if client_el.attribute('client_id') == client_id: break else: continue ref = el.attribute('ref') name = el.attribute('name') rw_sn = el.attribute('rewind_snapshot') rw_name = "" session_name = el.attribute('session_name') # don't list snapshot from client before session renamed if client_id and session_name != self.session.name: client = self.session.getClient(client_id) if (client and client.prefix_mode == ray.PrefixMode.SESSION_NAME): continue ss_name = "" if session_name != prv_session_name: ss_name = session_name prv_session_name = session_name if not ref.replace('_', '').isdigit(): continue if '\n' in name: name = "" if not rw_sn.replace('_', '').isdigit(): rw_sn = "" if rw_sn: for snap in all_snaps: if snap[0] == rw_sn and not '\n' in snap[1]: rw_name = snap[1] break all_snaps.append((ref, name)) all_tags.append(fullRefForGui(ref, name, rw_sn, rw_name, ss_name)) return all_tags.__reversed__() def getTagDate(self): date_time = QDateTime.currentDateTimeUtc() date = date_time.date() time = date_time.time() tagdate = "%s_%s_%s_%s_%s_%s" % ( date.year(), date.month(), date.day(), time.hour(), time.minute(), time.second()) return tagdate def writeHistoryFile(self, date_str, snapshot_name='', rewind_snapshot=''): if not self.session.path: return ray.Err.NO_SESSION_OPEN file_path = self.getHistoryFullPath() xml = QDomDocument() try: history_file = open(file_path, 'r') xml.setContent(history_file.read()) history_file.close() except: pass if xml.firstChild().isNull(): SNS_xml = xml.createElement('SNAPSHOTS') xml.appendChild(SNS_xml) else: SNS_xml = xml.firstChild() snapshot_el = xml.createElement('Snapshot') snapshot_el.setAttribute('ref', date_str) snapshot_el.setAttribute('name', snapshot_name) snapshot_el.setAttribute('rewind_snapshot', rewind_snapshot) snapshot_el.setAttribute('session_name', self.session.name) snapshot_el.setAttribute('VERSION', ray.VERSION) for client in self.session.clients + self.session.removed_clients: client_el = xml.createElement('client') client.writeXmlProperties(client_el) client_el.setAttribute('client_id', client.client_id) for client_file_path in client.getProjectFiles(): base_path = client_file_path.replace( "%s/" % self.session.path, '', 1) file_xml = xml.createElement('file') file_xml.setAttribute('path', base_path) client_el.appendChild(file_xml) snapshot_el.appendChild(client_el) SNS_xml.appendChild(snapshot_el) try: history_file = open(file_path, 'w') history_file.write(xml.toString()) history_file.close() except: return ray.Err.CREATE_FAILED return ray.Err.OK def getExcludeFileFullPath(self): return "%s/%s/%s" % ( self.session.path, self.gitdir, self.exclude_path) def writeExcludeFile(self): file_path = self.getExcludeFileFullPath() try: exclude_file = open(file_path, 'w') except: return ray.Err.CREATE_FAILED contents = "" contents += "# This file is generated by ray-daemon at each snapshot\n" contents += "# Don't edit this file.\n" contents += "# If you want to add/remove files managed by git\n" contents += "# Create/Edit .gitignore in the session folder\n" contents += "\n" contents += "%s\n" % self.gitdir contents += "\n" contents += "# Globally ignored extensions\n" session_ignored_extensions = ray.getGitIgnoredExtensions() session_ign_list = session_ignored_extensions.split(' ') session_ign_list = tuple(filter(bool, session_ign_list)) # write global ignored extensions for extension in session_ign_list: contents+= "*%s\n" % extension for client in self.session.clients: cext_list = client.ignored_extensions.split(' ') if not extension in cext_list: contents += "!%s.%s/**/*%s\n" % ( gitStringer(client.getPrefixString()), gitStringer(client.client_id), extension) contents += "!%s.%s.**/*%s\n" % ( gitStringer(client.getPrefixString()), gitStringer(client.client_id), extension) contents += '\n' contents += "# Extensions ignored by clients\n" # write client specific ignored extension for client in self.session.clients: cext_list = client.ignored_extensions.split(' ') for extension in cext_list: if not extension: continue if extension in session_ignored_extensions: continue contents += "%s.%s/**/*%s\n" % ( gitStringer(client.getPrefixString()), gitStringer(client.client_id), extension) contents += "%s.%s.**/*%s\n" % ( gitStringer(client.getPrefixString()), gitStringer(client.client_id), extension) contents += '\n' contents += "# Too big Files\n" no_check_list = (self.gitdir) # check too big files for foldername, subfolders, filenames in os.walk(self.session.path): subfolders[:] = [d for d in subfolders if d not in no_check_list] if foldername == "%s/%s" % (self.session.path, self.gitdir): continue for filename in filenames: if filename.endswith(session_ign_list): if os.path.islink(filename): short_folder = foldername.replace( self.session.path + '/', '', 1) line = gitStringer("%s/%s" % (short_folder, filename)) contents += '!%s\n' % line # file with extension globally ignored but # unignored by its client will not be ignored # and that is well as this. continue if os.path.islink(filename): continue try: file_size = os.path.getsize(os.path.join(foldername, filename)) except: continue if file_size > self.max_file_size*1024**2: if foldername == self.session.path: line = gitStringer(filename) else: short_folder = foldername.replace( self.session.path + '/', '', 1) line = gitStringer("%s/%s" % (short_folder, filename)) contents += "%s\n" % line try: exclude_file.write(contents) exclude_file.close() except: return ray.Err.CREATE_FAILED return ray.Err.OK def isInit(self): if not self.session.path: return False return os.path.isfile("%s/%s/%s" % ( self.session.path, self.gitdir, self.exclude_path)) def hasChanges(self): if not self.session.path: return False if not self.isInit(): return True args = self.getGitCommandList('ls-files', '--exclude-standard', '--others', '--modified') if self.changes_checker.state(): self.changes_checker.kill() self._n_file_changed = 0 self._n_file_treated = 0 self._changes_counted = True self.changes_checker.start(self.git_exec, args) self.changes_checker.waitForFinished(2000) return bool(self._n_file_changed) def canSave(self): if not self.session.path: return False if not self.isInit(): if not self.runGitProcess('init'): return False user_name = os.getenv('USER') if not user_name: user_name = 'someone' machine_name = socket.gethostname() if not machine_name: machine_name = 'somewhere' if not self.runGitProcess('config', 'user.email', '%s@%s' % (user_name, machine_name)): return False user_name = os.getenv('USER') if not user_name: user_name = 'someone' if not self.runGitProcess('config', 'user.name', user_name): return False if not self.isInit(): return False return True def save(self, name='', rewind_snapshot='', next_function=None, error_function=None): self.next_snapshot_name = name self._rw_snapshot = rewind_snapshot self.next_function = next_function self.error_function = error_function if not self.canSave(): Terminal.message("can't snapshot") return err = self.writeExcludeFile() if err: if self.error_function: self.error_function(err) return all_args = self.getGitCommandList('add', '-A', '-v') self._adder_aborted = False if not self._changes_counted: self.hasChanges() if self._n_file_changed: self.adder_process.start(self.git_exec, all_args) else: self.save_step_1() # self.adder_process.finished is connected to self.save_step_1 def save_step_1(self): if self._adder_aborted: if self.next_function: self.next_function(aborted=True) return if self._n_file_changed: if not self.runGitProcess('commit', '-m', 'ray'): return self._changes_counted = False ref = self.getTagDate() if (self._n_file_changed or self.next_snapshot_name or self.next_rw_snapshot): if not self.runGitProcess('tag', '-a', ref, '-m', 'ray'): return err = self.writeHistoryFile(ref, self.next_snapshot_name, self._rw_snapshot) if err: if self.error_function: self.error_function(err) # not really a reply, not strong. self.session.sendGui('/reply', '/ray/session/list_snapshots', fullRefForGui(ref, self.next_snapshot_name, self._rw_snapshot)) self.error_function = None if self.next_function: self.next_function() def load(self, spath, snapshot, error_function): self.error_function = error_function snapshot_ref = snapshot.partition('\n')[0].partition(':')[0] if not self.runGitProcessAt(spath, 'reset', '--hard'): return False if not self.runGitProcessAt(spath, 'checkout', snapshot_ref): return False return True def loadClientExclusive(self, client_id, snapshot, error_function): self.error_function = error_function SNS_xml = self.getHistoryXmlDocumentElement() if not SNS_xml: self.error_function(ray.Err.NO_SUCH_FILE, self.getHistoryFullPath()) return False nodes = SNS_xml.childNodes() client_path_list = [] for i in range(nodes.count()): node = nodes.at(i) el = node.toElement() if el.attribute('ref') != snapshot: continue client_nodes = node.childNodes() for j in range(client_nodes.count()): client_node = client_nodes.at(j) client_el = client_node.toElement() if client_el.attribute('client_id') != client_id: continue file_nodes = client_node.childNodes() for k in range(file_nodes.count()): file_node = file_nodes.at(k) file_el = file_node.toElement() file_path = file_el.attribute('path') if file_path: client_path_list.append(file_path) if not self.runGitProcess('reset', '--hard'): return False if not self.runGitProcess('checkout', snapshot, '--', *client_path_list): return False return True def abort(self): if not self.adder_process.state(): return self.setAutoSnapshot(False) self._adder_aborted = True self.adder_process.terminate() def setAutoSnapshot(self, bool_snapshot): auto_snap_file = "%s/%s/prevent_auto_snapshot" % (self.session.path, self.gitdir) file_exists = bool(os.path.exists(auto_snap_file)) if bool_snapshot: if file_exists: try: os.remove(auto_snap_file) except PermissionError: return else: if not file_exists: contents = "# This file prevent auto snapshots for this session (RaySession)\n" contents += "# remove it if you want auto snapshots back" try: file = open(auto_snap_file, 'w') file.write(contents) file.close() except PermissionError: return def isAutoSnapshotPrevented(self): auto_snap_file = "%s/%s/prevent_auto_snapshot" % (self.session.path, self.gitdir) return bool(os.path.exists(auto_snap_file)) RaySession-0.8.3/src/gui/000077500000000000000000000000001356671433200152065ustar00rootroot00000000000000RaySession-0.8.3/src/gui/add_application_dialog.py000066400000000000000000000270151356671433200222170ustar00rootroot00000000000000from PyQt5.QtCore import Qt, QTimer, QSize from PyQt5.QtWidgets import QDialogButtonBox, QListWidgetItem, QFrame, QMenu, QAction from PyQt5.QtGui import QIcon, QPalette import ray from gui_tools import RS, _translate from child_dialogs import ChildDialog import ui_add_application import ui_template_slot import ui_remove_template class TemplateSlot(QFrame): def __init__(self, list_widget, item, session, icon, name, factory): QFrame.__init__(self) self.ui = ui_template_slot.Ui_Frame() self.ui.setupUi(self) self.list_widget = list_widget self.item = item self._session = session self.name = name self.icon_name = icon self.factory = factory self.ui.toolButtonIcon.setIcon(ray.getAppIcon(icon, self)) self.ui.label.setText(name) self.ui.toolButtonUser.setVisible(not factory) self.user_menu = QMenu() act_remove_template = QAction(QIcon.fromTheme('edit-delete-remove'), _translate('menu', 'remove'), self.user_menu) act_remove_template.triggered.connect(self.removeTemplate) self.user_menu.addAction(act_remove_template) self.ui.toolButtonUser.setMenu(self.user_menu) self.is_favorite = False self.favicon_not = QIcon(':scalable/breeze/draw-star.svg') if (self.palette().brush(2, QPalette.WindowText).color().lightness() > 128): self.ui.toolButtonUser.setIcon( QIcon(':scalable/breeze-dark/im-user.svg')) self.favicon_not = QIcon(':scalable/breeze-dark/draw-star.svg') for favorite in self._session.favorite_list: if favorite.name == name and favorite.factory == factory: self.is_favorite = True break if self.is_favorite: self.ui.toolButtonFavorite.setIcon( QIcon(':scalable/breeze/star-yellow.svg')) else: self.ui.toolButtonFavorite.setIcon(self.favicon_not) self.ui.toolButtonFavorite.clicked.connect(self.favoriteClicked) def favoriteClicked(self): self.is_favorite = not self.is_favorite if self.is_favorite: self.ui.toolButtonFavorite.setIcon( QIcon(':scalable/breeze/star-yellow.svg')) self._session.addFavorite(self.name, self.icon_name, self.factory) else: self.ui.toolButtonFavorite.setIcon(self.favicon_not) self._session.removeFavorite(self.name, self.factory) self.list_widget.setCurrentItem(self.item) def removeTemplate(self): add_app_dialog = self.list_widget.parent() add_app_dialog.removeTemplate(self.name, self.factory) def mouseDoubleClickEvent(self, event): self.list_widget.parent().accept() class TemplateItem(QListWidgetItem): def __init__(self, parent, session, icon, name, factory): QListWidgetItem.__init__(self, parent, QListWidgetItem.UserType + 1) self.f_widget = TemplateSlot(parent, self, session, icon, name, factory) self.setData(Qt.UserRole, name) parent.setItemWidget(self, self.f_widget) self.setSizeHint(QSize(100, 28)) self.f_factory = factory def __lt__(self, other): self_name = self.data(Qt.UserRole) other_name = other.data(Qt.UserRole) if other_name == None: return False if self_name == other_name: # make the user template on top return not self.f_factory return bool(self.data(Qt.UserRole).lower() < other.data(Qt.UserRole).lower()) class RemoveTemplateDialog(ChildDialog): def __init__(self, parent, template_name): ChildDialog.__init__(self, parent) self.ui = ui_remove_template.Ui_Dialog() self.ui.setupUi(self) self.ui.label.setText( _translate('add_app_dialog', '

Are you sure to want to remove
the template "%s" and all its files ?

') % template_name) self.ui.pushButtonCancel.setFocus() class AddApplicationDialog(ChildDialog): def __init__(self, parent): ChildDialog.__init__(self, parent) self.ui = ui_add_application.Ui_DialogAddApplication() self.ui.setupUi(self) self._session = parent._session self.ui.checkBoxFactory.setChecked(RS.settings.value( 'AddApplication/factory_box', True, type=bool)) self.ui.checkBoxUser.setChecked(RS.settings.value( 'AddApplication/user_box', True, type=bool)) self.ui.checkBoxFactory.stateChanged.connect(self.factoryBoxChanged) self.ui.checkBoxUser.stateChanged.connect(self.userBoxChanged) self.ui.templateList.currentItemChanged.connect( self.currentItemChanged) self.ui.templateList.setFocus(Qt.OtherFocusReason) self.ui.filterBar.textEdited.connect(self.updateFilteredList) self.ui.filterBar.updownpressed.connect(self.updownPressed) self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) self._signaler.user_client_template_found.connect( self.addUserTemplates) self._signaler.factory_client_template_found.connect( self.addFactoryTemplates) self.toDaemon('/ray/server/list_user_client_templates') self.toDaemon('/ray/server/list_factory_client_templates') self.user_template_list = [] self.factory_template_list = [] self.server_will_accept = False self.has_selection = False self.serverStatusChanged(self._session.server_status) def factoryBoxChanged(self, state): if not state: self.ui.checkBoxUser.setChecked(True) self.updateFilteredList() def userBoxChanged(self, state): if not state: self.ui.checkBoxFactory.setChecked(True) self.updateFilteredList() def serverStatusChanged(self, server_status): self.server_will_accept = bool( server_status not in ( ray.ServerStatus.OFF, ray.ServerStatus.CLOSE) and not self.server_copying) self.preventOk() def addUserTemplates(self, template_list): for template in template_list: template_name = template icon_name = '' if '/' in template: template_name, slash, icon_name = template.partition('/') if template_name in self.user_template_list: continue self.user_template_list.append(template_name) list_widget = TemplateItem(self.ui.templateList, self._session, icon_name, template_name, False) self.ui.templateList.addItem(list_widget) self.ui.templateList.sortItems() self.updateFilteredList() def addFactoryTemplates(self, template_list): for template in template_list: template_name = template icon_name = '' if '/' in template: template_name, slash, icon_name = template.partition('/') if template_name in self.factory_template_list: continue self.factory_template_list.append(template_name) list_widget = TemplateItem(self.ui.templateList, self._session, icon_name, template_name, True) self.ui.templateList.addItem(list_widget) self.ui.templateList.sortItems() self.updateFilteredList() def updateFilteredList(self, filt=''): filter_text = self.ui.filterBar.displayText() # show all items for i in range(self.ui.templateList.count()): self.ui.templateList.item(i).setHidden(False) # hide all non matching items for i in range(self.ui.templateList.count()): item = self.ui.templateList.item(i) template_name = item.data(Qt.UserRole) if not filter_text.lower() in template_name.lower(): item.setHidden(True) if item.f_factory and not self.ui.checkBoxFactory.isChecked(): item.setHidden(True) if not item.f_factory and not self.ui.checkBoxUser.isChecked(): item.setHidden(True) # if selected item not in list, then select the first visible if (not self.ui.templateList.currentItem() or self.ui.templateList.currentItem().isHidden()): for i in range(self.ui.templateList.count()): if not self.ui.templateList.item(i).isHidden(): self.ui.templateList.setCurrentRow(i) break if (not self.ui.templateList.currentItem() or self.ui.templateList.currentItem().isHidden()): self.ui.filterBar.setStyleSheet( "QLineEdit { background-color: red}") self.ui.templateList.setCurrentItem(None) else: self.ui.filterBar.setStyleSheet("") self.ui.templateList.scrollTo(self.ui.templateList.currentIndex()) def updownPressed(self, key): row = self.ui.templateList.currentRow() if key == Qt.Key_Up: if row == 0: return row -= 1 while self.ui.templateList.item(row).isHidden(): if row == 0: return row -= 1 elif key == Qt.Key_Down: if row == self.ui.templateList.count() - 1: return row += 1 while self.ui.templateList.item(row).isHidden(): if row == self.ui.templateList.count() - 1: return row += 1 self.ui.templateList.setCurrentRow(row) def currentItemChanged(self, item, previous_item): self.has_selection = bool(item) self.preventOk() def preventOk(self): self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled( bool(self.server_will_accept and self.has_selection)) def getSelectedTemplate(self): item = self.ui.templateList.currentItem() if item: return (item.data(Qt.UserRole), item.f_factory) def removeTemplate(self, template_name, factory): dialog = RemoveTemplateDialog(self, template_name) dialog.exec() if not dialog.result(): return self.toDaemon('/ray/server/remove_client_template', template_name) for i in range(self.ui.templateList.count()): item = self.ui.templateList.item(i) if not item.f_factory and template_name == item.data(Qt.UserRole): item.setHidden(True) self.ui.templateList.removeItemWidget(item) break def saveCheckBoxes(self): RS.settings.setValue( 'AddApplication/factory_box', self.ui.checkBoxFactory.isChecked()) RS.settings.setValue( 'AddApplication/user_box', self.ui.checkBoxUser.isChecked()) RS.settings.sync() RaySession-0.8.3/src/gui/child_dialogs.py000066400000000000000000001212521356671433200203500ustar00rootroot00000000000000import os import sys import time from PyQt5.QtWidgets import ( QDialog, QDialogButtonBox, QTreeWidgetItem, QCompleter, QMessageBox, QFileDialog, QWidget) from PyQt5.QtGui import QIcon, QPixmap from PyQt5.QtCore import Qt, QTimer import ray from gui_server_thread import GUIServerThread from gui_tools import (default_session_root, ErrDaemon, _translate, CommandLineArgs, RS, isDarkTheme) import ui_open_session import ui_new_session import ui_list_snapshots import ui_save_template_session import ui_nsm_open_info import ui_abort_session import ui_about_raysession import ui_add_application import ui_donations import ui_new_executable import ui_error_dialog import ui_quit_app import ui_client_properties import ui_stop_client import ui_stop_client_no_save import ui_abort_copy import ui_client_trash import ui_daemon_url import ui_edit_executable import ui_snapshot_progress import ui_waiting_close_user class ChildDialog(QDialog): def __init__(self, parent): QDialog.__init__(self, parent) self._session = parent._session self._signaler = self._session._signaler daemon_manager = self._session._daemon_manager self.daemon_launched_before = daemon_manager.launched_before self._signaler.server_status_changed.connect(self.serverStatusChanged) self._signaler.server_copying.connect(self.serverCopying) self.server_copying = parent.server_copying def toDaemon(self, *args): server = GUIServerThread.instance() if server: server.toDaemon(*args) else: sys.stderr.write('Error No GUI OSC Server, can not send %s.\n' % args) def serverStatusChanged(self, server_status): return def serverCopying(self, bool_copying): self.server_copying = bool_copying self.serverStatusChanged(self._session.server_status) def changeRootFolder(self): root_folder = QFileDialog.getExistingDirectory( self, _translate("root_folder_dialogs", "Choose root folder for sessions"), CommandLineArgs.session_root, QFileDialog.ShowDirsOnly) if not root_folder: return # Security, kde dialogs sends $HOME if user type a folder path # that doesn't already exists. if os.getenv('HOME') and root_folder == os.getenv('HOME'): return errorDialog = QMessageBox( QMessageBox.Critical, _translate('root_folder_dialogs', 'unwritable dir'), _translate( 'root_folder_dialogs', '

You have no permissions for %s,
' % root_folder \ + 'choose another directory !

')) if not os.path.exists(root_folder): try: os.makedirs(root_folder) except: errorDialog.exec() return if not os.access(root_folder, os.W_OK): errorDialog.exec() return RS.settings.setValue('default_session_root', root_folder) self.toDaemon('/ray/server/change_root', root_folder) def leaveEvent(self, event): if self.isActiveWindow(): self.parent().mouse_is_inside = False QDialog.leaveEvent(self, event) def enterEvent(self, event): self.parent().mouse_is_inside = True QDialog.enterEvent(self, event) class SessionItem(QTreeWidgetItem): def __init__(self, list): QTreeWidgetItem.__init__(self, list) def showConditionnaly(self, string): show = bool(string.lower() in self.data(0, Qt.UserRole).lower()) n=0 for i in range(self.childCount()): if self.child(i).showConditionnaly(string.lower()): n+=1 if n: show = True self.setExpanded(bool(n and string)) self.setHidden(not show) return show def findItemWith(self, string): if self.data(0, Qt.UserRole) == string: return self item = None for i in range(self.childCount()): item = self.child(i).findItemWith(string) if item: break return item def __lt__(self, other): if self.childCount() and not other.childCount(): return True if other.childCount() and not self.childCount(): return False return bool(self.text(0).lower() < other.text(0).lower()) class SessionFolder: name = "" has_session = True path = "" def __init__(self, name): self.name = name self.subfolders = [] def setPath(self, path): self.path = path def makeItem(self): item = SessionItem([self.name]) item.setData(0, Qt.UserRole, self.path) if self.subfolders: item.setIcon(0, QIcon.fromTheme('folder')) if not self.path: item.setFlags(item.flags() & ~Qt.ItemIsSelectable) for folder in self.subfolders: sub_item = folder.makeItem() item.addChild(sub_item) return item class OpenSessionDialog(ChildDialog): def __init__(self, parent): ChildDialog.__init__(self, parent) self.ui = ui_open_session.Ui_DialogOpenSession() self.ui.setupUi(self) self.ui.toolButtonFolder.clicked.connect(self.changeRootFolder) self.ui.sessionList.currentItemChanged.connect( self.currentItemChanged) self.ui.sessionList.setFocus(Qt.OtherFocusReason) self.ui.sessionList.itemDoubleClicked.connect(self.goIfAny) self.ui.sessionList.itemClicked.connect(self.deployItem) self.ui.filterBar.textEdited.connect(self.updateFilteredList) self.ui.filterBar.updownpressed.connect(self.updownPressed) self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) self.ui.currentSessionsFolder.setText(CommandLineArgs.session_root) self._signaler.add_sessions_to_list.connect(self.addSessions) self._signaler.root_changed.connect(self.rootChanged) self.toDaemon('/ray/server/list_sessions', 0) if self.daemon_launched_before: self.ui.toolButtonFolder.setVisible(False) self.ui.currentSessionsFolder.setVisible(False) self.ui.labelSessionsFolder.setVisible(False) self.server_will_accept = False self.has_selection = False self.serverStatusChanged(self._session.server_status) self.folders = [] self.all_items = [] self._last_mouse_click = 0 def serverStatusChanged(self, server_status): self.ui.toolButtonFolder.setEnabled( bool(server_status in (ray.ServerStatus.OFF, ray.ServerStatus.READY, ray.ServerStatus.CLOSE))) self.server_will_accept = bool( server_status in ( ray.ServerStatus.OFF, ray.ServerStatus.READY) and not self.server_copying) self.preventOk() def rootChanged(self, session_root): self.ui.currentSessionsFolder.setText(session_root) self.ui.sessionList.clear() self.folders.clear() self.toDaemon('/ray/server/list_sessions', 0) def addSessions(self, session_names): for session_name in session_names: folder_div = session_name.split('/') folders = self.folders for i in range(len(folder_div)): f = folder_div[i] for g in folders: if g.name == f: if i+1 == len(folder_div): g.setPath(session_name) folders = g.subfolders break else: new_folder = SessionFolder(f) if i+1 == len(folder_div): new_folder.setPath(session_name) folders.append(new_folder) folders = new_folder.subfolders self.ui.sessionList.clear() for folder in self.folders: item = folder.makeItem() self.ui.sessionList.addTopLevelItem(item) self.ui.sessionList.sortByColumn(0, Qt.AscendingOrder) # Try to select last used session root_item = self.ui.sessionList.invisibleRootItem() for i in range(root_item.childCount()): item = root_item.child(i) last_session_item = item.findItemWith( RS.settings.value('last_session', type=str)) if last_session_item: self.ui.sessionList.setCurrentItem(last_session_item) self.ui.sessionList.scrollToItem(last_session_item) break def updateFilteredList(self, filt): filter_text = self.ui.filterBar.displayText() root_item = self.ui.sessionList.invisibleRootItem() ## hide all non matching items for i in range(root_item.childCount()): root_item.child(i).showConditionnaly(filter_text) # if selected item not in list, then select the first visible if (not self.ui.sessionList.currentItem() or self.ui.sessionList.currentItem().isHidden()): for i in range(root_item.childCount()): item = root_item.child(i) if not item.isHidden(): self.ui.sessionList.setCurrentItem(item) break if (not self.ui.sessionList.currentItem() or self.ui.sessionList.currentItem().isHidden()): self.ui.filterBar.setStyleSheet( "QLineEdit { background-color: red}") self.ui.sessionList.setCurrentItem(None) else: self.ui.filterBar.setStyleSheet("") self.ui.sessionList.scrollTo(self.ui.sessionList.currentIndex()) def updownPressed(self, key): root_item = self.ui.sessionList.invisibleRootItem() row = self.ui.sessionList.currentIndex().row() if key == Qt.Key_Up: if row == 0: return row -= 1 while root_item.child(row).isHidden(): if row == 0: return row -= 1 elif key == Qt.Key_Down: if row == root_item.childCount() - 1: return row += 1 while root_item.child(row).isHidden(): if row == root_item.childCount() - 1: return row += 1 self.ui.sessionList.setCurrentItem(root_item.child(row)) def currentItemChanged(self, item, previous_item): self.has_selection = bool(item and item.data(0, Qt.UserRole)) self.preventOk() def preventOk(self): self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled( bool(self.server_will_accept and self.has_selection)) def getSelectedSession(self): if self.ui.sessionList.currentItem(): return self.ui.sessionList.currentItem().data(0, Qt.UserRole) def deployItem(self, item, column): if not item.childCount(): return if time.time() - self._last_mouse_click > 0.35: item.setExpanded(not item.isExpanded()) self._last_mouse_click = time.time() def goIfAny(self, item, column): if item.childCount(): return if (self.server_will_accept and self.has_selection and self.ui.sessionList.currentItem().data(0, Qt.UserRole)): self.accept() class NewSessionDialog(ChildDialog): def __init__(self, parent, duplicate_window=False): ChildDialog.__init__(self, parent) self.ui = ui_new_session.Ui_DialogNewSession() self.ui.setupUi(self) self.is_duplicate = bool(duplicate_window) self.ui.currentSessionsFolder.setText(CommandLineArgs.session_root) self.ui.toolButtonFolder.clicked.connect(self.changeRootFolder) self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) self.ui.lineEdit.setFocus(Qt.OtherFocusReason) self.ui.lineEdit.textChanged.connect(self.textChanged) self.session_list = [] self.template_list = [] self.sub_folders = [] self._signaler.server_status_changed.connect(self.serverStatusChanged) self._signaler.add_sessions_to_list.connect(self.addSessionsToList) self._signaler.session_template_found.connect(self.addTemplatesToList) self._signaler.root_changed.connect(self.rootChanged) self.toDaemon('/ray/server/list_sessions', 1) if self.is_duplicate: self.ui.labelTemplate.setVisible(False) self.ui.comboBoxTemplate.setVisible(False) self.ui.labelNewSessionName.setText( _translate('Duplicate', 'Duplicated session name :')) self.setWindowTitle(_translate('Duplicate', 'Duplicate Session')) else: self.toDaemon('/ray/server/list_session_templates') if self.daemon_launched_before: self.ui.toolButtonFolder.setVisible(False) self.ui.currentSessionsFolder.setVisible(False) self.ui.labelSessionsFolder.setVisible(False) self.initTemplatesComboBox() self.setLastTemplateSelected() self.ui.labelSubFolder.setVisible(False) self.ui.comboBoxSubFolder.setVisible(False) self.server_will_accept = False self.text_is_valid = False self.serverStatusChanged(self._session.server_status) def serverStatusChanged(self, server_status): self.ui.toolButtonFolder.setEnabled( bool(server_status == ray.ServerStatus.OFF)) self.server_will_accept = bool( server_status in ( ray.ServerStatus.OFF, ray.ServerStatus.READY) and not self.server_copying) if self.is_duplicate: self.server_will_accept = bool( server_status == ray.ServerStatus.READY and not self.server_copying) self.preventOk() def rootChanged(self, session_root): self.ui.currentSessionsFolder.setText(session_root) self.session_list.clear() self.sub_folders.clear() self.initSubFolderCombobox() self.toDaemon('/ray/server/list_sessions', 1) def initTemplatesComboBox(self): self.ui.comboBoxTemplate.clear() self.ui.comboBoxTemplate.addItem( _translate('session_template', "empty")) self.ui.comboBoxTemplate.addItem( _translate( 'session_template', "with JACK patch memory")) self.ui.comboBoxTemplate.insertSeparator(2) def initSubFolderCombobox(self): self.ui.comboBoxSubFolder.clear() self.ui.comboBoxSubFolder.addItem(_translate('new_session', 'none')) for sub_folder in self.sub_folders: self.ui.comboBoxSubFolder.addItem(QIcon.fromTheme('folder'), sub_folder) self.ui.labelSubFolder.setVisible(bool(self.sub_folders)) self.ui.comboBoxSubFolder.setVisible(bool(self.sub_folders)) def setLastTemplateSelected(self): last_used_template = RS.settings.value('last_used_template', type=str) if last_used_template.startswith('///'): if last_used_template == '///withJACKPATCH': self.ui.comboBoxTemplate.setCurrentIndex(1) else: if last_used_template in self.template_list: self.ui.comboBoxTemplate.setCurrentText(last_used_template) if not last_used_template: self.ui.comboBoxTemplate.setCurrentIndex(1) def setLastSubFolderSelected(self): last_subfolder = RS.settings.value('last_subfolder', type=str) if last_subfolder: self.ui.comboBoxSubFolder.setCurrentText(last_subfolder) else: self.ui.comboBoxSubFolder.setCurrentIndex(0) def addSessionsToList(self, session_names): self.session_list += session_names for session_name in session_names: if '/' in session_name: new_dir = os.path.dirname(session_name) if not new_dir in self.sub_folders: self.sub_folders.append(new_dir) self.sub_folders.sort() self.initSubFolderCombobox() self.setLastSubFolderSelected() def addTemplatesToList(self, template_list): for template in template_list: if template not in self.template_list: self.template_list.append(template) if not self.template_list: return self.template_list.sort() self.initTemplatesComboBox() for template_name in self.template_list: self.ui.comboBoxTemplate.addItem(template_name) self.setLastTemplateSelected() def getSessionName(self): if self.ui.comboBoxSubFolder.currentIndex() > 0: return '%s/%s' % (self.ui.comboBoxSubFolder.currentText(), self.ui.lineEdit.text()) else: return self.ui.lineEdit.text() def getTemplateName(self): if self.ui.comboBoxTemplate.currentIndex() == 0: return "" if self.ui.comboBoxTemplate.currentIndex() == 1: return '///withJACKPATCH' return self.ui.comboBoxTemplate.currentText() def getSubFolder(self): if self.ui.comboBoxSubFolder.currentIndex() == 0: return "" return self.ui.comboBoxSubFolder.currentText() def textChanged(self, text): full_session_text = text if self.ui.comboBoxSubFolder.currentIndex(): full_session_text = "%s/%s" % ( self.ui.comboBoxSubFolder.currentText(), text) self.text_is_valid = bool(text and full_session_text not in self.session_list) self.preventOk() def preventOk(self): self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled( bool(self.server_will_accept and self.text_is_valid)) class AbstractSaveTemplateDialog(ChildDialog): def __init__(self, parent): ChildDialog.__init__(self, parent) self.ui = ui_save_template_session.Ui_DialogSaveTemplateSession() self.ui.setupUi(self) self.server_will_accept = False self.ui.lineEdit.textEdited.connect(self.textEdited) self.ui.pushButtonAccept.clicked.connect(self.verifyAndAccept) self.ui.pushButtonAccept.setEnabled(False) def textEdited(self, text): if '/' in text: self.ui.lineEdit.setText(text.replace('/', '⁄')) self.allowOkButton() def getTemplateName(self): return self.ui.lineEdit.text() def allowOkButton(self, text=''): self.ui.pushButtonAccept.setEnabled( bool(self.server_will_accept and self.ui.lineEdit.text())) def verifyAndAccept(self): template_name = self.getTemplateName() if template_name in self.template_list: ret = QMessageBox.question( self, _translate( 'session template', 'Overwrite Template ?'), _translate( 'session_template', 'Template %s already exists.\nOverwrite it ?') % template_name) if ret == QMessageBox.No: return self.accept() class SaveTemplateSessionDialog(AbstractSaveTemplateDialog): def __init__(self, parent): AbstractSaveTemplateDialog.__init__(self, parent) self.template_list = [] self._signaler.session_template_found.connect(self.addTemplatesToList) self.toDaemon('/ray/server/list_session_templates') self.serverStatusChanged(self._session.server_status) def serverStatusChanged(self, server_status): self.server_will_accept = bool(server_status == ray.ServerStatus.READY) if server_status == ray.ServerStatus.OFF: self.reject() self.allowOkButton() def addTemplatesToList(self, template_list): self.template_list += template_list class SaveTemplateClientDialog(AbstractSaveTemplateDialog): def __init__(self, parent): AbstractSaveTemplateDialog.__init__(self, parent) self.template_list = [] self.ui.pushButtonAccept.setEnabled(False) self.ui.labelNewTemplateName.setText( _translate( 'new client template', "New application template name :")) self._signaler.user_client_template_found.connect( self.addTemplatesToList) self.toDaemon('/ray/server/list_user_client_templates') self.serverStatusChanged(self._session.server_status) def serverStatusChanged(self, server_status): self.server_will_accept = bool( server_status not in ( ray.ServerStatus.OFF, ray.ServerStatus.CLOSE) and not self.server_copying) if server_status == ray.ServerStatus.OFF: self.reject() self.allowOkButton() def addTemplatesToList(self, template_list): for template in template_list: self.template_list.append(template.split('/')[0]) class EditExecutableDialog(ChildDialog): def __init__(self, parent, client): ChildDialog.__init__(self, parent) self.ui = ui_edit_executable.Ui_Dialog() self.ui.setupUi(self) self.ui.lineEditExecutable.setText(client.executable_path) self.ui.lineEditArguments.setText(client.arguments) def getExecutable(self): return self.ui.lineEditExecutable.text() def getArguments(self): return self.ui.lineEditArguments.text() class ClientPropertiesDialog(ChildDialog): def __init__(self, parent, client): ChildDialog.__init__(self, parent) self.ui = ui_client_properties.Ui_Dialog() self.ui.setupUi(self) self.client = client self.ui.lineEditIcon.textEdited.connect(self.changeIconwithText) self.ui.pushButtonSaveChanges.clicked.connect(self.saveChanges) self.ui.toolButtonEditExecutable.clicked.connect(self.editExecutable) def updateContents(self): self.ui.labelExecutable.setText(self.client.executable_path) self.ui.labelArguments.setText(self.client.arguments) self.ui.labelId.setText(self.client.client_id) self.ui.labelClientName.setText(self.client.name) self.ui.lineEditIcon.setText(self.client.icon_name) self.ui.lineEditLabel.setText(self.client.label) self.ui.checkBoxSaveStop.setChecked(self.client.check_last_save) self.ui.toolButtonIcon.setIcon( ray.getAppIcon(self.client.icon_name, self)) self.ui.lineEditIgnoredExtensions.setText( self.client.ignored_extensions) def changeIconwithText(self, text): self.ui.toolButtonIcon.setIcon(ray.getAppIcon(text, self)) def editExecutable(self): dialog = EditExecutableDialog(self, self.client) dialog.exec() if dialog.result(): self.ui.labelExecutable.setText(dialog.getExecutable()) self.ui.labelArguments.setText(dialog.getArguments()) def saveChanges(self): self.client.executable_path = self.ui.labelExecutable.text() self.client.arguments = self.ui.labelArguments.text() self.client.label = self.ui.lineEditLabel.text() self.client.icon_name = self.ui.lineEditIcon.text() self.client.check_last_save = self.ui.checkBoxSaveStop.isChecked() self.client.ignored_extensions = self.ui.lineEditIgnoredExtensions.text() self.client.sendPropertiesToDaemon() # better for user to wait a little before close the window QTimer.singleShot(150, self.accept) class ClientTrashDialog(ChildDialog): def __init__(self, parent, client_data): ChildDialog.__init__(self, parent) self.ui = ui_client_trash.Ui_Dialog() self.ui.setupUi(self) self.client_data = client_data self.ui.labelExecutable.setText(self.client_data.executable_path) self.ui.labelId.setText(self.client_data.client_id) self.ui.labelClientName.setText(self.client_data.name) self.ui.labelClientIcon.setText(self.client_data.icon) self.ui.labelClientLabel.setText(self.client_data.label) self.ui.checkBoxSaveStop.setChecked(self.client_data.check_last_save) self.ui.toolButtonIcon.setIcon(QIcon.fromTheme(self.client_data.icon)) self.ui.pushButtonRemove.clicked.connect(self.removeClient) def serverStatusChanged(self, server_status): if server_status in (ray.ServerStatus.CLOSE, ray.ServerStatus.OFF, ray.ServerStatus.OUT_SAVE, ray.ServerStatus.OUT_SNAPSHOT, ray.ServerStatus.WAIT_USER): self.reject() def removeClient(self): self.toDaemon( '/ray/trash/remove_definitely', self.client_data.client_id) self.reject() class AbortSessionDialog(ChildDialog): def __init__(self, parent): ChildDialog.__init__(self, parent) self.ui = ui_abort_session.Ui_AbortSession() self.ui.setupUi(self) self.ui.pushButtonAbort.clicked.connect(self.accept) self.ui.pushButtonCancel.clicked.connect(self.reject) self.ui.pushButtonCancel.setFocus(Qt.OtherFocusReason) self.serverStatusChanged(self._session.server_status) def serverStatusChanged(self, server_status): self.ui.pushButtonAbort.setEnabled( not bool( server_status in ( ray.ServerStatus.CLOSE, ray.ServerStatus.OFF, ray.ServerStatus.COPY))) if server_status == ray.ServerStatus.OFF: self.reject() class AbortServerCopyDialog(ChildDialog): def __init__(self, parent): ChildDialog.__init__(self, parent) self.ui = ui_abort_copy.Ui_Dialog() self.ui.setupUi(self) self._signaler.server_progress.connect(self.setProgress) self.serverStatusChanged(self._session.server_status) def serverStatusChanged(self, server_status): if server_status not in ( ray.ServerStatus.PRECOPY, ray.ServerStatus.COPY): self.reject() def setProgress(self, progress): self.ui.progressBar.setValue(progress * 100) class AbortClientCopyDialog(ChildDialog): def __init__(self, parent, client_id): ChildDialog.__init__(self, parent) self.ui = ui_abort_copy.Ui_Dialog() self.ui.setupUi(self) self.client_id = client_id self._signaler.client_progress.connect(self.setProgress) def setProgress(self, client_id, progress): if client_id != self.client_id: return self.ui.progressBar.setValue(progress * 100) def serverStatusChanged(self, server_status): if not self.server_copying: self.reject() class OpenNsmSessionInfoDialog(ChildDialog): def __init__(self, parent): ChildDialog.__init__(self, parent) self.ui = ui_nsm_open_info.Ui_Dialog() self.ui.setupUi(self) self.ui.checkBox.stateChanged.connect(self.showThis) def showThis(self, state): RS.settings.setValue('OpenNsmSessionInfo', not bool(state)) class QuitAppDialog(ChildDialog): def __init__(self, parent): ChildDialog.__init__(self, parent) self.ui = ui_quit_app.Ui_DialogQuitApp() self.ui.setupUi(self) self.ui.pushButtonCancel.setFocus(Qt.OtherFocusReason) self.ui.pushButtonSaveQuit.clicked.connect(self.closeSession) self.ui.pushButtonQuitNoSave.clicked.connect(self.abortSession) original_text = self.ui.labelExecutable.text() self.ui.labelExecutable.setText( original_text % ('%s' % self._session.name)) self.serverStatusChanged(self._session.server_status) def serverStatusChanged(self, server_status): if server_status == ray.ServerStatus.OFF: self.accept() return self.ui.pushButtonSaveQuit.setEnabled( bool(server_status == ray.ServerStatus.READY)) self.ui.pushButtonQuitNoSave.setEnabled( bool(server_status != ray.ServerStatus.CLOSE)) def closeSession(self): self.toDaemon('/ray/session/close') def abortSession(self): self.toDaemon('/ray/session/abort') class AboutRaySessionDialog(ChildDialog): def __init__(self, parent): ChildDialog.__init__(self, parent) self.ui = ui_about_raysession.Ui_DialogAboutRaysession() self.ui.setupUi(self) all_text = self.ui.labelRayAndVersion.text() self.ui.labelRayAndVersion.setText(all_text % ray.VERSION) class NewExecutableDialog(ChildDialog): def __init__(self, parent): ChildDialog.__init__(self, parent) self.ui = ui_new_executable.Ui_DialogNewExecutable() self.ui.setupUi(self) self.ui.groupBoxAdvanced.setVisible(False) self.resize(0, 0) self.ui.labelPrefixMode.setToolTip( self.ui.comboBoxPrefixMode.toolTip()) self.ui.labelClientId.setToolTip(self.ui.lineEditClientId.toolTip()) self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) self.ui.lineEdit.setFocus(Qt.OtherFocusReason) self.ui.lineEdit.textChanged.connect(self.textChanged) self.ui.checkBoxProxy.stateChanged.connect(self.proxyStateChanged) self.ui.lineEditPrefix.setEnabled(False) self.ui.toolButtonAdvanced.clicked.connect(self.showAdvanced) self.ui.comboBoxPrefixMode.addItem( _translate('new_executable', 'Custom')) self.ui.comboBoxPrefixMode.addItem( _translate('new_executable', 'Client Name')) self.ui.comboBoxPrefixMode.addItem( _translate('new_executable', 'Session Name')) self.ui.comboBoxPrefixMode.setCurrentIndex(2) self.ui.comboBoxPrefixMode.currentIndexChanged.connect( self.prefixModeChanged) self._signaler.new_executable.connect(self.addExecutableToCompleter) self.toDaemon('/ray/server/list_path') self.exec_list = [] self.completer = QCompleter(self.exec_list) self.ui.lineEdit.setCompleter(self.completer) self.ui.lineEdit.returnPressed.connect(self.closeNow) self.serverStatusChanged(self._session.server_status) self.text_will_accept = False def showAdvanced(self): self.ui.groupBoxAdvanced.setVisible(True) self.ui.toolButtonAdvanced.setVisible(False) def prefixModeChanged(self, index): self.ui.lineEditPrefix.setEnabled(bool(index == 0)) def addExecutableToCompleter(self, executable_list): self.exec_list += executable_list self.exec_list.sort() del self.completer self.completer = QCompleter(self.exec_list) self.ui.lineEdit.setCompleter(self.completer) def getExecutableSelected(self): return self.ui.lineEdit.text() def runViaProxy(self): return bool(self.ui.checkBoxProxy.isChecked()) def getSelection(self): return (self.ui.lineEdit.text(), self.ui.checkBoxProxy.isChecked(), self.ui.comboBoxPrefixMode.currentIndex(), self.ui.lineEditPrefix.text(), self.ui.lineEditClientId.text()) def proxyStateChanged(self, state): self.ui.buttonBox.button( QDialogButtonBox.Ok).setEnabled( state or self.text_will_accept) def textChanged(self, text): self.text_will_accept = bool(text) self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled( self.ui.checkBoxProxy.isChecked() or self.text_will_accept) def closeNow(self): if (self.ui.lineEdit.text() in self.exec_list or self.ui.checkBoxProxy.isChecked()): self.accept() def serverStatusChanged(self, server_status): if server_status in (ray.ServerStatus.OUT_SAVE, ray.ServerStatus.OUT_SNAPSHOT, ray.ServerStatus.WAIT_USER, ray.ServerStatus.CLOSE, ray.ServerStatus.OFF): self.reject() class StopClientDialog(ChildDialog): def __init__(self, parent, client_id): ChildDialog.__init__(self, parent) self.ui = ui_stop_client.Ui_Dialog() self.ui.setupUi(self) self.client_id = client_id self.wait_for_save = False self.client = self._session.getClient(client_id) if self.client: text = self.ui.label.text() % self.client.prettierName() if not self.client.has_dirty: minutes = int((time.time() - self.client.last_save) / 60) text = _translate( 'client_stop', "%s seems to has not been saved for %i minute(s).
Do you really want to stop it ?") % (self.client.prettierName(), minutes) self.ui.label.setText(text) self.client.status_changed.connect(self.serverUpdatesClientStatus) self.ui.pushButtonSaveStop.clicked.connect(self.saveAndStop) self.ui.checkBox.stateChanged.connect(self.checkBoxClicked) def saveAndStop(self): self.wait_for_save = True self.toDaemon('/ray/client/save', self.client_id) def checkBoxClicked(self, state): self.client.check_last_save = not bool(state) self.client.sendPropertiesToDaemon() def serverUpdatesClientStatus(self, status): if status in (ray.ClientStatus.STOPPED, ray.ClientStatus.REMOVED): self.reject() return if status == ray.ClientStatus.READY and self.wait_for_save: self.wait_for_save = False self.accept() class StopClientNoSaveDialog(ChildDialog): def __init__(self, parent, client_id): ChildDialog.__init__(self, parent) self.ui = ui_stop_client_no_save.Ui_Dialog() self.ui.setupUi(self) self.client_id = client_id self.client = self._session.getClient(client_id) if self.client: text = self.ui.label.text() % self.client.prettierName() self.ui.label.setText(text) self.client.status_changed.connect(self.serverUpdatesClientStatus) self.ui.checkBox.stateChanged.connect(self.checkBoxClicked) self.ui.pushButtonCancel.setFocus(True) def serverUpdatesClientStatus(self, status): if status in (ray.ClientStatus.STOPPED, ray.ClientStatus.REMOVED): self.reject() return def checkBoxClicked(self, state): self.client.check_last_save = not bool(state) self.client.sendPropertiesToDaemon() class SnapShotProgressDialog(ChildDialog): def __init__(self, parent): ChildDialog.__init__(self, parent) self.ui = ui_snapshot_progress.Ui_Dialog() self.ui.setupUi(self) self._signaler.server_progress.connect(self.serverProgress) def serverStatusChanged(self, server_status): self.close() def serverProgress(self, value): self.ui.progressBar.setValue(value * 100) class DaemonUrlWindow(ChildDialog): def __init__(self, parent, err_code, ex_url): ChildDialog.__init__(self, parent) self.ui = ui_daemon_url.Ui_Dialog() self.ui.setupUi(self) self.ui.lineEdit.textChanged.connect(self.allowUrl) error_text = '' if err_code == ErrDaemon.NO_ANNOUNCE: error_text = _translate( "url_window", "

daemon at
%s
didn't announce !

") % ex_url elif err_code == ErrDaemon.NOT_OFF: error_text = _translate( "url_window", "

daemon at
%s
has a loaded self._session.
It can't be used for slave session

") % ex_url elif err_code == ErrDaemon.WRONG_ROOT: error_text = _translate( "url_window", "

daemon at
%s
uses an other session root folder !<.p>") % ex_url elif err_code == ErrDaemon.FORBIDDEN_ROOT: error_text = _translate( "url_window", "

daemon at
%s
uses a forbidden session root folder !<.p>") % ex_url elif err_code == ErrDaemon.WRONG_VERSION: error_text = _translate( "url_window", "

daemon at
%s
uses another %s version.<.p>") % (ex_url, ray.APP_TITLE) else: error_text = _translate("url window", "

To run a network session,
open a terminal on another computer of this network.
Launch ray-daemon on port 1234 (for example)
by typing the command :

ray-daemon -p 1234

Then paste below the first url
that ray-daemon gives you at startup.

") self.ui.labelError.setText(error_text) self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) self.tried_urls = ray.getListInSettings(RS.settings, 'network/tried_urls') last_tried_url = RS.settings.value('network/last_tried_url', '', type=str) self.completer = QCompleter(self.tried_urls) self.ui.lineEdit.setCompleter(self.completer) if ex_url: self.ui.lineEdit.setText(ex_url) elif last_tried_url: self.ui.lineEdit.setText(last_tried_url) def getDaemonUrl(self): return self.ui.lineEdit.text() def allowUrl(self, text): if not text: self.ui.lineEdit.completer().complete() self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) return if not text.startswith('osc.udp://'): self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) return try: addr = ray.getLibloAddress(text) self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True) except BaseException: self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) def getUrl(self): return self.ui.lineEdit.text() class WaitingCloseUserDialog(ChildDialog): def __init__(self, parent): ChildDialog.__init__(self, parent) self.ui = ui_waiting_close_user.Ui_Dialog() self.ui.setupUi(self) if isDarkTheme(self): self.ui.labelSaveIcon.setPixmap( QPixmap(':scalable/breeze-dark/document-nosave.svg')) self.ui.pushButtonOk.setFocus(True) self.ui.pushButtonUndo.clicked.connect(self.undoClose) self.ui.pushButtonSkip.clicked.connect(self.skip) self.ui.checkBox.setChecked( bool(RS.settings.value( 'hide_wait_close_user_dialog', False, type=bool))) self.ui.checkBox.clicked.connect(self.checkBoxClicked) def serverStatusChanged(self, server_status): if server_status != ray.ServerStatus.WAIT_USER: self.accept() def undoClose(self): self.toDaemon('/ray/session/cancel_close') def skip(self): self.toDaemon('/ray/session/skip_wait_user') def checkBoxClicked(self, state): RS.settings.setValue('hide_wait_close_user_dialog', bool(state), type=bool) class DonationsDialog(ChildDialog): def __init__(self, parent, display_no_again): ChildDialog.__init__(self, parent) self.ui = ui_donations.Ui_Dialog() self.ui.setupUi(self) print('display_no_again', display_no_again) self.ui.checkBox.setVisible(display_no_again) self.ui.checkBox.clicked.connect(self.checkBoxClicked) def checkBoxClicked(self, state): RS.settings.setValue('hide_donations', state) class ErrorDialog(ChildDialog): def __init__(self, parent, message): ChildDialog.__init__(self, parent) self.ui = ui_error_dialog.Ui_Dialog() self.ui.setupUi(self) self.ui.label.setText(message) RaySession-0.8.3/src/gui/daemon_manager.py000066400000000000000000000173531356671433200205260ustar00rootroot00000000000000import os import socket import sys from liblo import Address from PyQt5.QtCore import QObject, QProcess, QTimer from PyQt5.QtWidgets import QApplication import ray from gui_signaler import Signaler from gui_server_thread import GUIServerThread from gui_tools import (CommandLineArgs, default_session_root, ErrDaemon, _translate) class DaemonManager(QObject): def __init__(self, session): QObject.__init__(self) self._session = session self._signaler = self._session._signaler self.executable = 'ray-daemon' self.process = QProcess() self.process.finished.connect(self.processFinished) if ray.QT_VERSION >= (5, 6): self.process.errorOccurred.connect(self.errorInProcess) self.process.setProcessChannelMode(QProcess.ForwardedChannels) self.announce_timer = QTimer() self.announce_timer.setInterval(2000) self.announce_timer.setSingleShot(True) self.announce_timer.timeout.connect(self.announceTimerOut) self.stopped_yet = False self.is_local = True self.launched_before = False self.address = None self.port = None self.url = '' self.is_announced = False self.is_nsm_locked = False self._signaler.daemon_announce.connect(self.receiveAnnounce) self._signaler.daemon_url_changed.connect(self.changeUrl) def finishInit(self): self._main_win = self._session._main_win def processFinished(self, exit_code, exit_status): if self.stopped_yet: QApplication.quit() return if not self._main_win.isHidden(): self._main_win.daemonCrash() return def errorInProcess(self, error): self._main_win.daemonCrash() def changeUrl(self, new_url): try: self.setOscAddress(ray.getLibloAddress(new_url)) except BaseException: return self.callDaemon() def callDaemon(self): if not self.address: # I don't know really why, but it works only with a timer QTimer.singleShot(5, self.showDaemonUrlWindow) return self.announce_timer.start() server = GUIServerThread.instance() if not server: sys.stderr.write( 'GUI can not call daemon, GUI OSC server is missing.\n') return server.announce() def showDaemonUrlWindow(self): self._signaler.daemon_url_request.emit(ErrDaemon.NO_ERROR, self.url) def announceTimerOut(self): if self.launched_before: self._signaler.daemon_url_request.emit( ErrDaemon.NO_ANNOUNCE, self.url) else: sys.stderr.write( _translate( 'error', "No announce from ray-daemon. RaySession can't works. Sorry.\n")) QApplication.quit() def receiveAnnounce( self, src_addr, version, server_status, options, session_root, is_net_free): self.announce_timer.stop() if version.split('.')[:1] != ray.VERSION.split('.')[:1]: # works only if the two firsts digits are the same (ex: 0.6) self._signaler.daemon_url_request.emit( ErrDaemon.WRONG_VERSION, self.url) self.disannounce(src_addr) return if CommandLineArgs.net_session_root and session_root != CommandLineArgs.net_session_root: self._signaler.daemon_url_request.emit( ErrDaemon.WRONG_ROOT, self.url) self.disannounce(src_addr) return if not is_net_free: self._signaler.daemon_url_request.emit( ErrDaemon.FORBIDDEN_ROOT, self.url) self.disannounce(src_addr) return if CommandLineArgs.out_daemon and server_status != ray.ServerStatus.OFF: self._signaler.daemon_url_request.emit(ErrDaemon.NOT_OFF, self.url) self.disannounce(src_addr) return self.is_announced = True self.address = src_addr self.port = src_addr.port self.url = src_addr.url self.session_root = session_root self.is_nsm_locked = options & ray.Option.NSM_LOCKED save_all_from_saved_client = options & ray.Option.SAVE_FROM_CLIENT bookmark_session_folder = options & ray.Option.BOOKMARK_SESSION if self.is_nsm_locked: #self._signaler.daemon_nsm_locked.emit(True) self._session._main_win.setNsmLocked(True) elif CommandLineArgs.under_nsm: server = GUIServerThread.instance() server.toDaemon('/ray/server/set_nsm_locked') self._signaler.daemon_announce_ok.emit() self._session.setDaemonOptions(options) def disannounce(self, address=None): if not address: address = self.address if address: server = GUIServerThread.instance() server.disannounce(address) self.port = None self.url = '' del self.address self.address = None self.is_announced = False def setExternal(self): self.launched_before = True def setOscAddress(self, address): self.address = address self.launched_before = True self.port = self.address.port self.url = self.address.url self.is_local = bool(self.address.hostname == socket.gethostname()) def setOscAddressViaUrl(self, url): self.setOscAddress(ray.getLibloAddress(url)) def processIsRunning(self): return bool(self.process.state() == 2) def start(self): if self.launched_before: self.callDaemon() return server = GUIServerThread.instance() if not server: sys.stderr.write( "impossible for GUI to launch daemon. server missing.\n") # start process arguments = ['--gui-url', str(server.url), '--osc-port', str(self.port), '--session-root', CommandLineArgs.session_root] if CommandLineArgs.session: arguments.append('--session') arguments.append(CommandLineArgs.session) if CommandLineArgs.debug_only: arguments.append('--debug-only') elif CommandLineArgs.debug: arguments.append('--debug') if CommandLineArgs.config_dir: arguments.append('--config-dir') arguments.append(CommandLineArgs.config_dir) self.process.start('ray-daemon', arguments) #self.process.start('konsole', ['-e', 'ray-daemon'] + arguments) def stop(self): if self.launched_before: self.disannounce() QApplication.quit() return if self.processIsRunning(): if not self.stopped_yet: self.process.terminate() self.stopped_yet = True QTimer.singleShot(5000, self.notEndedAfterWait) def notEndedAfterWait(self): sys.stderr.write('ray-daemon is still running, sorry !\n') QApplication.quit() def setNewOscAddress(self): if not (self.address or self.port): self.port = ray.getFreeOscPort() self.address = Address(self.port) def setNsmLocked(self): self.is_nsm_locked = True def isAnnounced(self): return self.is_announced def setDisannounced(self): server = GUIServerThread.instance() server.disannounce() self.port = None self.url = '' del self.address self.address = None self.is_announced = False def getUrl(self): if self.address: return self.address.url return '' RaySession-0.8.3/src/gui/gui_client.py000066400000000000000000000116631356671433200177110ustar00rootroot00000000000000import time import sys from PyQt5.QtCore import QObject, pyqtSignal import child_dialogs import snapshots_dialog import ray from gui_server_thread import GUIServerThread class Client(QObject): status_changed = pyqtSignal(int) def __init__(self, session, client_data, trashed=False): QObject.__init__(self) self._session = session self._main_win = self._session._main_win self.client_id = client_data.client_id self.executable_path = client_data.executable_path self.arguments = client_data.arguments self.name = client_data.name self.prefix_mode = client_data.prefix_mode self.custom_prefix = client_data.custom_prefix self.label = client_data.label self.icon_name = client_data.icon self.capabilities = client_data.capabilities self.check_last_save = client_data.check_last_save self.ignored_extensions = client_data.ignored_extensions self.status = ray.ClientStatus.STOPPED self.previous_status = ray.ClientStatus.STOPPED self.hasGui = False self.gui_visible = False self.has_dirty = False self.dirty_state = True self.no_save_level = 0 self.last_save = time.time() self.widget = self._main_win.createClientWidget(self) self.properties_dialog = child_dialogs.ClientPropertiesDialog( self._main_win, self) def setStatus(self, status): self.previous_status = self.status self.status = status self.status_changed.emit(status) if (not self.has_dirty and self.status == ray.ClientStatus.READY and self.previous_status in (ray.ClientStatus.OPEN, ray.ClientStatus.SAVE)): self.last_save = time.time() self.widget.updateStatus(status) def setGuiEnabled(self): self.hasGui = True self.widget.showGuiButton() def setGuiState(self, state): self.gui_state = state self.widget.setGuiState(state) def setDirtyState(self, bool_dirty): self.has_dirty = True self.dirty_state = bool_dirty self.widget.setDirtyState(bool_dirty) def setNoSaveLevel(self, no_save_level): self.no_save_level = no_save_level self.widget.setNoSaveLevel(no_save_level) def setProgress(self, progress): self.widget.setProgress(progress) def switch(self, new_client_id): self.client_id = new_client_id self.widget.updateClientData() def allowKill(self): self.widget.allowKill() def updateLabel(self, label): self.label = label self.sendPropertiesToDaemon() def updateClientProperties(self, client_data): self.executable_path = client_data.executable_path self.arguments = client_data.arguments self.name = client_data.name self.prefix_mode = client_data.prefix_mode self.custom_prefix = client_data.custom_prefix self.label = client_data.label self.icon_name = client_data.icon self.capabilities = client_data.capabilities self.check_last_save = client_data.check_last_save self.widget.updateClientData() def prettierName(self): if self.label: return self.label if self.name: return self.name return self.executable_path def sendPropertiesToDaemon(self): server = GUIServerThread.instance() if not server: sys.stderr.write( 'Server not found. Client %s can not send its properties\n' % self.client_id) return server.toDaemon('/ray/client/update_properties', self.client_id, self.executable_path, self.arguments, self.name, self.prefix_mode, self.custom_prefix, self.label, self.icon_name, self.capabilities, int(self.check_last_save), self.ignored_extensions) def showPropertiesDialog(self): self.properties_dialog.updateContents() self.properties_dialog.show() self.properties_dialog.activateWindow() def reCreateWidget(self): del self.widget self.widget = self._main_win.createClientWidget(self) self.widget.updateClientData() if self.hasGui: self.setGuiEnabled() def hasBeenRecentlySaved(self): if (time.time() - self.last_save) >= 60: # last save more than 60 seconds ago return False return True class TrashedClient(object): def __init__(self, client_data, menu_action): self.data = client_data self.menu_action = menu_action RaySession-0.8.3/src/gui/gui_server_thread.py000066400000000000000000000201711356671433200212620ustar00rootroot00000000000000import os import sys import liblo import ray from gui_tools import CommandLineArgs _instance = None def ray_method(path, types): def decorated(func): @liblo.make_method(path, types) def wrapper(*args, **kwargs): t_thread, t_path, t_args, t_types, src_addr, rest = args if CommandLineArgs.debug: sys.stderr.write( '\033[93mOSC::gui_receives\033[0m %s, %s, %s, %s\n' % (t_path, t_types, t_args, src_addr.url)) response = func(*args[:-1], **kwargs) if response != False: t_thread._signaler.osc_receive.emit(t_path, t_args) return response return wrapper return decorated class GUIServerThread(liblo.ServerThread): def __init__(self): liblo.ServerThread.__init__(self) global _instance _instance = self def finishInit(self, session): self._session = session self._signaler = self._session._signaler self._daemon_manager = self._session._daemon_manager @staticmethod def instance(): return _instance @ray_method('/error', 'sis') def _error(self, path, args, types, src_addr): pass @ray_method('/reply', None) def _reply(self, path, args, types, src_addr): if len(args) < 2: return if not ray.areTheyAllString(args): return reply_path = args.pop(0) if reply_path == '/ray/server/list_sessions': self._signaler.add_sessions_to_list.emit(args) elif reply_path == '/ray/server/list_path': self._signaler.new_executable.emit(args) elif reply_path == '/ray/server/list_session_templates': self._signaler.session_template_found.emit(args) elif reply_path == '/ray/server/list_user_client_templates': self._signaler.user_client_template_found.emit(args) elif reply_path == '/ray/server/list_factory_client_templates': self._signaler.factory_client_template_found.emit(args) elif reply_path == '/ray/session/list_snapshots': self._signaler.snapshots_found.emit(args) @ray_method('/ray/gui/server/announce', 'siisi') def _server_announce(self, path, args, types, src_addr): if self._daemon_manager.isAnnounced(): return version, server_status, options, session_root, is_net_free = args self._signaler.daemon_announce.emit( src_addr, version, server_status, options, session_root, is_net_free) @ray_method('/ray/gui/server/disannounce', '') def _server_disannounce(self, path, args, types, src_addr): pass @ray_method('/ray/gui/server/nsm_locked', 'i') def _server_nsm_locked(self, path, args, types, src_addr): pass @ray_method('/ray/gui/server/root', 's') def _server_root(self, path, args, types, src_addr): session_root = args[0] CommandLineArgs.changeSessionRoot(session_root) self._signaler.root_changed.emit(session_root) @ray_method('/ray/gui/server/options', 'i') def _server_options(self, path, args, types, src_addr): pass @ray_method('/ray/gui/server/status', 'i') def _server_status(self, path, args, types, src_addr): server_status = args[0] self._signaler.server_status_changed.emit(server_status) @ray_method('/ray/gui/server/copying', 'i') def _server_copying(self, path, args, types, src_addr): copying = bool(int(args[0])) self._signaler.server_copying.emit(copying) @ray_method('/ray/gui/server/progress', 'f') def _server_progress(self, path, args, types, src_addr): progress = args[0] self._signaler.server_progress.emit(progress) @ray_method('/ray/gui/server/message', 's') def _server_message(self, path, args, types, src_addr): pass @ray_method('/ray/gui/session/name', 'ss') def _session_name(self, path, args, types, src_addr): pass @ray_method('/ray/gui/session/auto_snapshot', 'i') def _session_auto_snapshot(self, path, args, types, src_addr): self._signaler.reply_auto_snapshot.emit(bool(args[0])) @ray_method('/ray/gui/session/is_nsm', '') def _session_is_nsm(self, path, args, types, src_addr): pass @ray_method('/ray/gui/session/renameable', 'i') def _session_renameable(self, path, args, types, src_addr): pass @ray_method('/ray/gui/session/sort_clients', None) def _session_sort_clients(self, path, args, types, src_addr): if not ray.areTheyAllString(args): return False @ray_method('/ray/gui/client/new', 'ssssissssis') def _client_new(self, path, args, types, src_addr): pass @ray_method('/ray/gui/client/update', 'ssssissssis') def _client_update(self, path, args, types, src_addr): pass @ray_method('/ray/gui/client/status', 'si') def _client_status(self, path, args, types, src_addr): pass @ray_method('/ray/gui/client/switch', 'ss') def _client_switch(self, path, args, types, src_addr): pass @ray_method('/ray/gui/client/progress', 'sf') def _client_progress(self, path, args, types, src_addr): pass @ray_method('/ray/gui/client/dirty', 'si') def _client_dirty(self, path, args, types, src_addr): pass @ray_method('/ray/gui/client/has_optional_gui', 's') def _client_has_optional_gui(self, path, args, types, src_addr): pass @ray_method('/ray/gui/client/gui_visible', 'si') def _client_gui_visible(self, path, args, types, src_addr): pass @ray_method('/ray/gui/client/still_running', 's') def _client_still_running(self, path, args, types, src_addr): pass @ray_method('/ray/gui/client/no_save_level', 'si') def _client_no_save_level(self, path, args, types, src_addr): pass @ray_method('/ray/gui/trash/add', 'ssssissssis') def _trash_add(self, path, args, types, src_addr): pass @ray_method('/ray/gui/trash/remove', 's') def _trash_remove(self, path, args, types, src_addr): pass @ray_method('/ray/gui/trash/clear', '') def _trash_clear(self, path, args, types, src_addr): pass @ray_method('/ray/gui/favorites/added', 'ssi') def _favorites_added(self, path, args, types, src_addr): pass @ray_method('/ray/gui/favorites/removed', 'si') def _favorites_removed(self, path, args, types, src_addr): pass def send(self, *args): if CommandLineArgs.debug: sys.stderr.write( '\033[95mOSC::gui sends\033[0m %s\n' % str(args[1:]) ) liblo.ServerThread.send(self, *args) def toDaemon(self, *args): self.send(self._daemon_manager.address, *args) def announce(self): if CommandLineArgs.debug: sys.stderr.write('serverOSC::raysession_sends announce\n') nsm_mode = ray.NSMMode.NO_NSM if CommandLineArgs.under_nsm: if CommandLineArgs.out_daemon: nsm_mode = ray.NSMMode.NETWORK else: nsm_mode = ray.NSMMode.CHILD NSM_URL = os.getenv('NSM_URL') if not NSM_URL: NSM_URL = "" self.send(self._daemon_manager.address, '/ray/server/gui_announce', ray.VERSION, int(CommandLineArgs.under_nsm), NSM_URL, 0, CommandLineArgs.net_daemon_id) def disannounce(self, src_addr): self.send(src_addr, '/ray/server/gui_disannounce') def openSession(self, session_name, session_template=''): if session_template: self.toDaemon( '/ray/server/open_session', session_name, session_template) else: self.toDaemon('/ray/server/open_session', session_name) def saveSession(self): self.toDaemon('/ray/session/save') def closeSession(self): self.toDaemon('/ray/session/close') def abortSession(self): self.toDaemon('/ray/session/abort') RaySession-0.8.3/src/gui/gui_session.py000066400000000000000000000242151356671433200201130ustar00rootroot00000000000000 import ray from daemon_manager import DaemonManager from gui_client import Client, TrashedClient from gui_signaler import Signaler from gui_server_thread import GUIServerThread from gui_tools import initGuiTools, CommandLineArgs, RS from main_window import MainWindow from nsm_child import NSMChild, NSMChildOutside class Session(object): def __init__(self): self.client_list = [] self.trashed_clients = [] self.favorite_list = [] self.name = '' self.path = '' self.is_running = False self.server_status = ray.ServerStatus.OFF self.is_renameable = True self._signaler = Signaler() server = GUIServerThread.instance() server.start() self._daemon_manager = DaemonManager(self) if CommandLineArgs.daemon_url: self._daemon_manager.setOscAddress(CommandLineArgs.daemon_url) elif not CommandLineArgs.out_daemon: self._daemon_manager.setNewOscAddress() if CommandLineArgs.under_nsm: if CommandLineArgs.out_daemon: self._nsm_child = NSMChildOutside(self) self._daemon_manager.setExternal() else: self._nsm_child = NSMChild(self) # build nsm_child if NSM_URL in env self._nsm_child = None if CommandLineArgs.under_nsm: if CommandLineArgs.out_daemon: self._nsm_child = NSMChildOutside(self) self._daemon_manager.setExternal() else: self._nsm_child = NSMChild(self) # build and show Main UI self._main_win = MainWindow(self) self._daemon_manager.finishInit() server.finishInit(self) self._main_win.show() # display donations dialog under conditions if not RS.settings.value('hide_donations', False, type=bool): coreff_counter = RS.settings.value('coreff_counter', 0, type=int) coreff_counter+= 1 RS.settings.setValue('coreff_counter', coreff_counter) if coreff_counter % 44 == 29: self._main_win.donate(True) # The only way I found to not show Messages Dock by default. if not RS.settings.value('MainWindow/ShowMessages', False, type=bool): self._main_win.hideMessagesDock() def quit(self): self._main_win.hide() del self._main_win def setRunning(self, bool): self.is_running = bool def isRunning(self): return bool(self.server_status != ray.ServerStatus.OFF) def updateServerStatus(self, server_status): self.server_status = server_status def setName(self, session_name): self.name = session_name def setPath(self, session_path): self.path = session_path def getShortPath(self): if self.path.startswith(CommandLineArgs.session_root): return self.path.replace( '%s/' % CommandLineArgs.session_root, '', 1) return self.path def getClient(self, client_id): for client in self.client_list: if client.client_id == client_id: return client else: raise NameError("gui_session does not contains client %s" % client_id) def removeAllClients(self): self.client_list.clear() def addFavorite(self, name, icon_name, factory, from_server=False): for favorite in self.favorite_list: if favorite.name == name and favorite.factory == factory: favorite.icon = icon_name return fav = ray.Favorite(name, icon_name, factory) self.favorite_list.append(fav) self._main_win.updateFavoritesMenu() if not from_server: server = GUIServerThread.instance() if server: server.toDaemon('/ray/favorites/add', name, icon_name, int(factory)) def removeFavorite(self, name, factory, from_server=False): for favorite in self.favorite_list: if favorite.name == name and favorite.factory == factory: self.favorite_list.remove(favorite) break self._main_win.updateFavoritesMenu() if not from_server: server = GUIServerThread.instance() if server: server.toDaemon('/ray/favorites/remove', name, int(factory)) def setDaemonOptions(self, options): self._main_win.setDaemonOptions(options) for client in self.client_list: client.widget.setDaemonOptions(options) class SignaledSession(Session): def __init__(self): Session.__init__(self) self._signaler.osc_receive.connect(self.oscReceive) self._daemon_manager.start() def oscReceive(self, path, args): func_path = path func_name = func_path.replace('/', '_') if func_name in self.__dir__(): function = self.__getattribute__(func_name) function(path, args) def _error(self, path, args): err_path, err_code, err_message = args self._main_win.errorMessage(err_message) def _ray_gui_server_nsm_locked(self, path, args): nsm_locked = bool(args[0]) self._main_win.setNsmLocked(nsm_locked) def _ray_gui_server_message(self, path, args): message = args[0] self._main_win.printMessage(message) def _ray_gui_server_options(self, path, args): options = args[0] self.setDaemonOptions(options) def _ray_gui_session_name(self, path, args): sname, spath = args self.setName(sname) self.setPath(spath) self._main_win.renameSession(sname, spath) def _ray_gui_session_is_nsm(self, path, args): self._main_win.openingNsmSession() def _ray_gui_session_renameable(self, path, args): self.is_renameable = bool(args[0]) bool_set_edit = bool(self.is_renameable and self.server_status == ray.ServerStatus.READY and not CommandLineArgs.out_daemon) self._main_win.setSessionNameEditable(bool_set_edit) def _ray_gui_session_sort_clients(self, path, args): new_client_list = [] for client_id in args: client = self.getClient(client_id) if not client: return new_client_list.append(client) self.client_list.clear() self._main_win.reCreateListWidget() self.client_list = new_client_list for client in self.client_list: client.reCreateWidget() client.widget.updateStatus(client.status) def _ray_gui_client_new(self, path, args): client = Client(self, ray.ClientData(*args)) self.client_list.append(client) def _ray_gui_client_update(self, path, args): client_data = ray.ClientData(*args) client = self.getClient(client_data.client_id) if client: client.updateClientProperties(client_data) def _ray_gui_client_status(self, path, args): client_id, status = args client = self.getClient(client_id) if client: client.setStatus(status) if status == ray.ClientStatus.REMOVED: self._main_win.removeClient(client_id) client.properties_dialog.close() self.client_list.remove(client) del client self._main_win.clientStatusChanged(client_id, status) def _ray_gui_client_switch(self, path, args): old_client_id, new_client_id = args client = self.getClient(old_client_id) if client: client.switch(new_client_id) def _ray_gui_client_progress(self, path, args): client_id, progress = args client = self.getClient(client_id) if client: client.setProgress(progress) def _ray_gui_client_dirty(self, path, args): client_id, int_dirty = args client = self.getClient(client_id) if client: client.setDirtyState(bool(int_dirty)) def _ray_gui_client_has_optional_gui(self, path, args): client_id = args[0] client = self.getClient(client_id) if client: client.setGuiEnabled() def _ray_gui_client_gui_visible(self, path, args): client_id, int_state = args client = self.getClient(client_id) if client: client.setGuiState(bool(int_state)) def _ray_gui_client_still_running(self, path, args): client_id = args[0] client = self.getClient(client_id) if client: client.allowKill() def _ray_gui_client_no_save_level(self, path, args): client_id, no_save_level = args client = self.getClient(client_id) if client: client.setNoSaveLevel(no_save_level) def _ray_gui_trash_add(self, path, args): client_data = ray.ClientData(*args) trash_action = self._main_win.trashAdd(client_data) trashed_client = TrashedClient(client_data, trash_action) self.trashed_clients.append(trashed_client) def _ray_gui_trash_remove(self, path, args): client_id = args[0] for trashed_client in self.trashed_clients: if trashed_client.data.client_id == client_id: break else: return self.trashed_clients.remove(trashed_client) self._main_win.trashRemove(trashed_client.menu_action) def _ray_gui_trash_clear(self, path, args): self.trashed_clients.clear() self._main_win.trashClear() def _ray_gui_favorites_added(self, path, args): name, icon_name, int_factory = args self.addFavorite(name, icon_name, bool(int_factory), True) def _ray_gui_favorites_remove(self, path, args): name, int_factory = args self.removeFavorite(name, bool(int_factory), True) RaySession-0.8.3/src/gui/gui_signaler.py000066400000000000000000000016101356671433200202260ustar00rootroot00000000000000 from PyQt5.QtCore import QObject, pyqtSignal from liblo import Address class Signaler(QObject): osc_receive = pyqtSignal(str, list) daemon_announce = pyqtSignal(Address, str, int, int, str, int) daemon_announce_ok = pyqtSignal() daemon_nsm_locked = pyqtSignal(bool) server_copying = pyqtSignal(bool) add_sessions_to_list = pyqtSignal(list) new_executable = pyqtSignal(list) session_template_found = pyqtSignal(list) user_client_template_found = pyqtSignal(list) factory_client_template_found = pyqtSignal(list) snapshots_found = pyqtSignal(list) reply_auto_snapshot = pyqtSignal(bool) server_progress = pyqtSignal(float) server_status_changed = pyqtSignal(int) daemon_url_request = pyqtSignal(int, str) daemon_url_changed = pyqtSignal(str) root_changed = pyqtSignal(str) def __init__(self): QObject.__init__(self) RaySession-0.8.3/src/gui/gui_tools.py000066400000000000000000000176551356671433200176020ustar00rootroot00000000000000import argparse import os import sys from PyQt5.QtCore import QSettings, QSize from PyQt5.QtWidgets import QApplication from PyQt5.QtGui import QIcon, QPixmap, QPalette import ray g_settings = QSettings() default_session_root = "%s/Ray Sessions" % (os.getenv('HOME')) def _translate(*args): return QApplication.translate(*args) def setDefaultSessionRoot(path): global default_session_root default_session_root = path def getDefaultSessionRoot(): return default_session_root class RS: settings = QSettings() @classmethod def setSettings(cls, settings): del cls.settings cls.settings = settings def initGuiTools(): if CommandLineArgs.under_nsm: settings = QSettings('%s/child_sessions' % QApplication.organizationName()) elif CommandLineArgs.config_dir: settings = QSettings(CommandLineArgs.config_dir) else: settings = QSettings() RS.setSettings(settings) CommandLineArgs.changeSessionRoot( settings.value('default_session_root', "%s/Ray Sessions" % (os.getenv('HOME')), type=str)) def isDarkTheme(widget): return bool( widget.palette().brush(2, QPalette.WindowText).color().lightness() > 128) def dirname(*args): return os.path.dirname(*args) def getCodeRoot(): return dirname(dirname(dirname(os.path.realpath(__file__)))) def serverStatusString(server_status): server_status_strings = { ray.ServerStatus.OFF : _translate('server status', "off"), ray.ServerStatus.NEW : _translate('server status', "new"), ray.ServerStatus.OPEN : _translate('server status', "open"), ray.ServerStatus.CLEAR : _translate('server status', "clear"), ray.ServerStatus.SWITCH : _translate('server status', "switch"), ray.ServerStatus.LAUNCH : _translate('server status', "launch"), ray.ServerStatus.PRECOPY : _translate('server status', "copy"), ray.ServerStatus.COPY : _translate('server status', "copy"), ray.ServerStatus.READY : _translate('server status', "ready"), ray.ServerStatus.SAVE : _translate('server status', "save"), ray.ServerStatus.CLOSE : _translate('server status', "close"), ray.ServerStatus.SNAPSHOT: _translate('server_status', "snapshot"), ray.ServerStatus.REWIND : _translate('server_status', "rewind"), ray.ServerStatus.WAIT_USER : _translate('server_status', "waiting"), ray.ServerStatus.OUT_SAVE: _translate('server_status', "save"), ray.ServerStatus.OUT_SNAPSHOT: _translate('server_status', "snapshot")} if not 0 <= server_status < len(server_status_strings): return _translate('server status', "invalid") return server_status_strings[server_status] def clientStatusString(client_status): client_status_strings = { ray.ClientStatus.STOPPED: _translate('client status', "stopped"), ray.ClientStatus.LAUNCH : _translate('client status', "launch"), ray.ClientStatus.OPEN : _translate('client status', "open"), ray.ClientStatus.READY : _translate('client status', "ready"), ray.ClientStatus.PRECOPY: _translate('client status', "copy"), ray.ClientStatus.COPY : _translate('client status', "copy"), ray.ClientStatus.SAVE : _translate('client status', "save"), ray.ClientStatus.SWITCH : _translate('client status', "switch"), ray.ClientStatus.QUIT : _translate('client status', "quit"), ray.ClientStatus.NOOP : _translate('client status', "noop"), ray.ClientStatus.ERROR : _translate('client status', "error"), ray.ClientStatus.REMOVED: _translate('client status', "removed"), ray.ClientStatus.UNDEF : _translate('client_status', "")} if not 0 <= client_status < len(client_status_strings): return _translate('client_status', 'invalid') return client_status_strings[client_status] class ErrDaemon: # for use on network session under NSM NO_ERROR = 0 NO_ANNOUNCE = -1 NOT_OFF = -2 WRONG_ROOT = -3 FORBIDDEN_ROOT = -4 NOT_NSM_LOCKED = -5 WRONG_VERSION = -6 class RayIcon(QIcon): def __init__(self, icon_name, dark=False): QIcon.__init__(self) breeze = 'breeze-dark' if dark else 'breeze' self.addFile(':scalable/%s/%s' % (breeze, icon_name), QSize(22, 22)) self.addPixmap( QPixmap( ':scalable/%s/disabled/%s' % (breeze, icon_name)), QIcon.Disabled, QIcon.Off) class CommandLineArgs(argparse.Namespace): daemon_url = None out_daemon = False config_dir = '' debug = False debug_only = False net_session_root = '' net_daemon_id = 0 under_nsm = False NSM_URL = '' session_root = '' session = '' @classmethod def eatAttributes(cls, parsed_args): for attr_name in dir(parsed_args): if not attr_name.startswith('_'): setattr(cls, attr_name, getattr(parsed_args, attr_name)) if cls.debug_only: cls.debug = True if cls.config_dir and not os.access(cls.config_dir, os.W_OK): sys.stderr.write( '%s is not a writable config dir, try another one\n' % cls.config_dir) sys.exit(1) if os.getenv('NSM_URL'): try: cls.NSM_URL = ray.getLibloAddress(os.getenv('NSM_URL')) except BaseException: sys.stderr.write('%s is not a valid NSM_URL\n' % os.getenv('NSM_URL')) sys.exit(1) cls.under_nsm = True if not cls.session_root: cls.session_root = RS.settings.value( 'default_session_root', '%s/Ray Sessions' % os.getenv('HOME')) if cls.session_root.endswith('/'): cls.session_root = cls.session_root[:-1] @classmethod def changeSessionRoot(cls, path): cls.session_root = path class ArgParser(argparse.ArgumentParser): def __init__(self): argparse.ArgumentParser.__init__( self, description=_translate( 'help', 'A session manager based on the Non-Session-Manager API ' + 'for sound applications.')) self.add_argument('--daemon-url', '-u', type=ray.getLibloAddress, help=_translate('help', 'connect to this daemon url')) self.add_argument('--out-daemon', action='store_true', help=argparse.SUPPRESS) self.add_argument('--session-root', '-r', type=str, help=_translate( 'help', 'Use this folder as root for sessions')) self.add_argument('--session', '-s', type=str, help=_translate('help', 'Open this session at startup')) self.add_argument('--config-dir', '-c', type=str, default='', help=_translate('help', 'use a custom config dir')) self.add_argument('--debug', '-d', action='store_true', help=_translate('help', 'display OSC messages')) self.add_argument('--debug-only', '-do', action='store_true', help=_translate('help', 'debug without client messages')) self.add_argument('--net-session-root', type=str, default='', help=argparse.SUPPRESS) self.add_argument('--net-daemon-id', type=int, default=0, help=argparse.SUPPRESS) self.add_argument('-v', '--version', action='version', version=ray.VERSION) parsed_args = argparse.ArgumentParser.parse_args(self) CommandLineArgs.eatAttributes(parsed_args) RaySession-0.8.3/src/gui/list_widget_clients.py000066400000000000000000000416761356671433200216350ustar00rootroot00000000000000import time from PyQt5.QtWidgets import (QListWidget, QListWidgetItem, QFrame, QMenu, QLineEdit, QAction) from PyQt5.QtGui import QIcon, QPalette, QPixmap, QFontMetrics, QFont, QFontDatabase from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot, QSize, QFile import ray from gui_server_thread import GUIServerThread from gui_tools import clientStatusString, _translate, isDarkTheme import child_dialogs import snapshots_dialog import ui_client_slot class ClientSlot(QFrame): def __init__(self, list_widget, client): QFrame.__init__(self) self.ui = ui_client_slot.Ui_ClientSlotWidget() self.ui.setupUi(self) # needed variables self.list_widget = list_widget self.client = client self._main_win = self.client._session._main_win self.is_dirty_able = False self.gui_visible = True self.ui.toolButtonGUI.setVisible(False) # connect buttons to functions self.ui.toolButtonGUI.toggleGui.connect(self.toggleGui) self.ui.startButton.clicked.connect(self.startClient) self.ui.stopButton.clicked.connect(self.stopClient) self.ui.killButton.clicked.connect(self.killClient) self.ui.saveButton.clicked.connect(self.saveClient) self.ui.closeButton.clicked.connect(self.trashClient) self.ui.lineEditClientStatus.statusPressed.connect(self.abortCopy) # self.ui.ClientName.name_changed.connect(self.updateLabel) # prevent "stopped" status displayed at client switch #self.ui.lineEditClientStatus,.setTextNow( #clientStatusString(self.client.status)) self.icon_on = QIcon() self.icon_off = QIcon() self.updateClientData() self.ui.actionSaveAsApplicationTemplate.triggered.connect( self.saveAsApplicationTemplate) self.ui.actionProperties.triggered.connect( self.client.showPropertiesDialog) self.ui.actionReturnToAPreviousState.triggered.connect( self.openSnapshotsDialog) self.menu = QMenu(self) self.menu.addAction(self.ui.actionSaveAsApplicationTemplate) self.menu.addAction(self.ui.actionReturnToAPreviousState) self.menu.addAction(self.ui.actionProperties) self.ui.actionReturnToAPreviousState.setVisible( self._main_win.has_git) self.ui.iconButton.setMenu(self.menu) self.saveIcon = QIcon() self.saveIcon.addPixmap( QPixmap(':scalable/breeze/document-save'), QIcon.Normal, QIcon.Off) self.saveIcon.addPixmap( QPixmap(':scalable/breeze/disabled/document-save'), QIcon.Disabled, QIcon.Off) self.ui.saveButton.setIcon(self.saveIcon) self.savedIcon = QIcon() self.savedIcon.addPixmap( QPixmap(':scalable/breeze/document-saved'), QIcon.Normal, QIcon.Off) self.unsavedIcon = QIcon() self.unsavedIcon.addPixmap( QPixmap(':scalable/breeze/document-unsaved'), QIcon.Normal, QIcon.Off) self.noSaveIcon = QIcon() self.noSaveIcon.addPixmap( QPixmap(':scalable/breeze/document-nosave'), QIcon.Normal, QIcon.Off) # choose button colors if isDarkTheme(self): startIcon = QIcon() startIcon.addPixmap( QPixmap(':scalable/breeze-dark/media-playback-start'), QIcon.Normal, QIcon.Off) startIcon.addPixmap( QPixmap(':scalable/breeze-dark/disabled/media-playback-start'), QIcon.Disabled, QIcon.Off) self.ui.startButton.setIcon(startIcon) stopIcon = QIcon() stopIcon.addPixmap( QPixmap(':scalable/breeze-dark/media-playback-stop'), QIcon.Normal, QIcon.Off) stopIcon.addPixmap( QPixmap(':scalable/breeze-dark/disabled/media-playback-stop'), QIcon.Disabled, QIcon.Off) self.ui.stopButton.setIcon(stopIcon) self.saveIcon = QIcon() self.saveIcon.addPixmap( QPixmap(':scalable/breeze-dark/document-save'), QIcon.Normal, QIcon.Off) self.saveIcon.addPixmap( QPixmap(':scalable/breeze-dark/disabled/document-save'), QIcon.Disabled, QIcon.Off) self.ui.saveButton.setIcon(self.saveIcon) self.savedIcon = QIcon() self.savedIcon.addPixmap( QPixmap(':scalable/breeze-dark/document-saved'), QIcon.Normal, QIcon.Off) self.unsavedIcon = QIcon() self.unsavedIcon.addPixmap( QPixmap(':scalable/breeze-dark/document-unsaved'), QIcon.Normal, QIcon.Off) self.noSaveIcon = QIcon() self.noSaveIcon.addPixmap( QPixmap(':scalable/breeze-dark/document-nosave'), QIcon.Normal, QIcon.Off) closeIcon = QIcon() closeIcon.addPixmap( QPixmap(':scalable/breeze-dark/window-close'), QIcon.Normal, QIcon.Off) closeIcon.addPixmap( QPixmap(':scalable/breeze-dark/disabled/window-close'), QIcon.Disabled, QIcon.Off) self.ui.closeButton.setIcon(closeIcon) self.ubuntu_font = QFont( QFontDatabase.applicationFontFamilies(0)[0], 8) self.ubuntu_font_cond = QFont( QFontDatabase.applicationFontFamilies(1)[0], 8) self.ubuntu_font.setBold(True) self.ubuntu_font_cond.setBold(True) self.ui.killButton.setVisible(False) def clientId(self): return self.client.client_id def toDaemon(self, *args): server = GUIServerThread.instance() if server: server.toDaemon(*args) def startClient(self): self.toDaemon('/ray/client/resume', self.clientId()) def stopClient(self): # we need to prevent accidental stop with a window confirmation # under conditions self._main_win.stopClient(self.clientId()) def killClient(self): self.toDaemon('/ray/client/kill', self.clientId()) def saveClient(self): self.toDaemon('/ray/client/save', self.clientId()) def trashClient(self): self.toDaemon('/ray/client/trash', self.clientId()) def abortCopy(self): self._main_win.abortCopyClient(self.clientId()) def saveAsApplicationTemplate(self): dialog = child_dialogs.SaveTemplateClientDialog(self._main_win) dialog.exec() if not dialog.result(): return template_name = dialog.getTemplateName() self.toDaemon('/ray/client/save_as_template', self.clientId(), template_name) def openSnapshotsDialog(self): dialog = snapshots_dialog.ClientSnapshotsDialog(self._main_win, self.client) dialog.exec() if dialog.result(): snapshot = dialog.getSelectedSnapshot() self.toDaemon('/ray/client/open_snapshot', self.clientId(), snapshot) def updateLabel(self, label): self._main_win.updateClientLabel(self.clientId(), label) def updateClientData(self): # set main label label = self.client.label if self.client.label else self.client.name self.ui.ClientName.setText(label) # set tool tip self.ui.ClientName.setToolTip( 'Executable : ' + self.client.executable_path + '\n' + 'Client id : ' + self.clientId()) # set icon self.icon_on = ray.getAppIcon(self.client.icon_name, self) self.icon_off = QIcon(self.icon_on.pixmap(32, 32, QIcon.Disabled)) self.grayIcon( bool( self.client.status in ( ray.ClientStatus.STOPPED, ray.ClientStatus.PRECOPY))) def grayIcon(self, gray): if gray: self.ui.iconButton.setIcon(self.icon_off) else: self.ui.iconButton.setIcon(self.icon_on) def updateStatus(self, status): self.ui.lineEditClientStatus.setText(clientStatusString(status)) if status in ( ray.ClientStatus.LAUNCH, ray.ClientStatus.OPEN, ray.ClientStatus.SWITCH, ray.ClientStatus.NOOP): self.ui.startButton.setEnabled(False) self.ui.stopButton.setEnabled(True) self.ui.saveButton.setEnabled(False) self.ui.closeButton.setEnabled(False) self.ui.ClientName.setStyleSheet('QLabel {font-weight : bold}') self.ui.ClientName.setEnabled(True) self.ui.toolButtonGUI.setEnabled(True) self.grayIcon(False) elif status == ray.ClientStatus.READY: self.ui.startButton.setEnabled(False) self.ui.stopButton.setEnabled(True) self.ui.closeButton.setEnabled(False) self.ui.ClientName.setStyleSheet('QLabel {font-weight : bold}') self.ui.ClientName.setEnabled(True) self.ui.toolButtonGUI.setEnabled(True) self.ui.saveButton.setEnabled(True) self.grayIcon(False) elif status == ray.ClientStatus.STOPPED: self.ui.startButton.setEnabled(True) self.ui.stopButton.setEnabled(False) self.ui.saveButton.setEnabled(False) self.ui.closeButton.setEnabled(True) self.ui.ClientName.setStyleSheet('QLabel {font-weight : normal}') self.ui.ClientName.setEnabled(False) self.ui.toolButtonGUI.setEnabled(False) self.grayIcon(True) self.ui.stopButton.setVisible(True) self.ui.killButton.setVisible(False) self.ui.saveButton.setIcon(self.saveIcon) elif status == ray.ClientStatus.PRECOPY: self.ui.startButton.setEnabled(False) self.ui.stopButton.setEnabled(False) self.ui.saveButton.setEnabled(False) self.ui.closeButton.setEnabled(True) self.ui.ClientName.setStyleSheet('QLabel {font-weight : normal}') self.ui.ClientName.setEnabled(False) self.ui.toolButtonGUI.setEnabled(False) self.grayIcon(True) self.ui.stopButton.setVisible(True) self.ui.killButton.setVisible(False) self.ui.saveButton.setIcon(self.saveIcon) elif status == ray.ClientStatus.COPY: self.ui.saveButton.setEnabled(False) def allowKill(self): self.ui.stopButton.setVisible(False) self.ui.killButton.setVisible(True) def flashIfOpen(self, boolflash): if boolflash: self.ui.lineEditClientStatus.setText( clientStatusString(ray.ClientStatus.OPEN)) else: self.ui.lineEditClientStatus.setText('') def showGuiButton(self): self.ui.toolButtonGUI.setVisible(True) if self.client.executable_path in ('nsm-proxy', 'ray-proxy'): self.ui.toolButtonGUI.setText(_translate('client_slot', 'proxy')) self.ui.toolButtonGUI.setToolTip( _translate('client_slot', 'Display proxy window')) def setGuiState(self, state): self.gui_visible = state self.ui.toolButtonGUI.setChecked(state) def toggleGui(self): if not self.gui_visible: self.toDaemon('/ray/client/show_optional_gui', self.clientId()) else: self.toDaemon('/ray/client/hide_optional_gui', self.clientId()) def setDirtyState(self, bool_dirty): self.is_dirty_able = True if bool_dirty: self.ui.saveButton.setIcon(self.unsavedIcon) else: self.ui.saveButton.setIcon(self.savedIcon) def setNoSaveLevel(self, no_save_level): if no_save_level: self.ui.saveButton.setIcon(self.noSaveIcon) else: self.ui.saveButton.setIcon(self.saveIcon) def setProgress(self, progress): self.ui.lineEditClientStatus.setProgress(progress) def setDaemonOptions(self, options): has_git = bool(options & ray.Option.HAS_GIT) self.ui.actionReturnToAPreviousState.setVisible(has_git) def contextMenuEvent(self, event): act_selected = self.menu.exec(self.mapToGlobal(event.pos())) event.accept() class ClientItem(QListWidgetItem): def __init__(self, parent, client_data): QListWidgetItem.__init__(self, parent, QListWidgetItem.UserType + 1) self.f_widget = ClientSlot(parent, client_data) parent.setItemWidget(self, self.f_widget) self.setSizeHint(QSize(100, 45)) self.sort_number = 0 def __lt__(self, other): result = bool(self.sort_number < other.sort_number) return result def __gt__(self, other): return self.sort_number > other.sort_number def setSortNumber(self, sort_number): self.sort_number = sort_number def getClientId(self): return self.f_widget.clientId() class ListWidgetClients(QListWidget): def __init__(self, parent): QListWidget.__init__(self, parent) self.last_n = 0 self._session = None def createClientWidget(self, client_data): item = ClientItem(self, client_data) item.setSortNumber(self.last_n) self.last_n += 1 return item.f_widget def removeClientWidget(self, client_id): for i in range(self.count()): item = self.item(i) if item.getClientId() == client_id: widget = item.f_widget self.takeItem(i) del item break def reOrderClients(self, client_id_list): # when re_order comes from ray-daemon (loading session) if len(client_id_list) != self.count(): return for client_id in client_id_list: for i in range(self.count()): if self.item(i).getClientId() == client_id: break else: return next_item_list = [] n = 0 for client_id in client_id_list: for i in range(self.count()): if self.item(i).getClientId() == client_id: self.item(i).setSortNumber(n) break n += 1 self.sortItems() def setSession(self, session): self._session = session def toDaemon(self, *args): server = GUIServerThread.instance() if server: server.toDaemon(*args) @pyqtSlot() def launchFavorite(self): template_name, factory = self.sender().data() self.toDaemon('/ray/session/add_client_template', int(factory), template_name) def dropEvent(self, event): QListWidget.dropEvent(self, event) client_ids_list = [] for i in range(self.count()): item = self.item(i) #widget = self.itemWidget(item) client_id = item.getClientId() client_ids_list.append(client_id) server = GUIServerThread.instance() if server: server.toDaemon('/ray/session/reorder_clients', *client_ids_list) def mousePressEvent(self, event): if not self.itemAt(event.pos()): self.setCurrentRow(-1) QListWidget.mousePressEvent(self, event) def contextMenuEvent(self, event): if not self.itemAt(event.pos()): self.setCurrentRow(-1) if (self._session and not self._session.server_status in ( ray.ServerStatus.OFF, ray.ServerStatus.CLOSE, ray.ServerStatus.OUT_SAVE, ray.ServerStatus.WAIT_USER, ray.ServerStatus.OUT_SNAPSHOT)): menu = QMenu() fav_menu = QMenu(_translate('menu', 'Favorites'), menu) fav_menu.setIcon(QIcon(':scalable/breeze/star-yellow')) for favorite in self._session.favorite_list: act_app = fav_menu.addAction( ray.getAppIcon(favorite.icon, self), favorite.name) act_app.setData([favorite.name, favorite.factory]) act_app.triggered.connect(self.launchFavorite) menu.addMenu(fav_menu) menu.addAction( self._session._main_win.ui.actionAddApplication) menu.addAction(self._session._main_win.ui.actionAddExecutable) act_selected = menu.exec(self.mapToGlobal(event.pos())) event.accept() return RaySession-0.8.3/src/gui/main_window.py000066400000000000000000001032111356671433200200710ustar00rootroot00000000000000import time from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenu, QInputDialog, QBoxLayout, QListWidgetItem, QFrame, QDialog, QDialogButtonBox, QFileDialog, QMessageBox, QCompleter, QAction, QToolButton, QAbstractItemView, QLabel, QLineEdit) from PyQt5.QtGui import QIcon, QCursor, QPalette, QPixmap, QFontDatabase from PyQt5.QtCore import (QTimer, QProcess, pyqtSignal, pyqtSlot, QObject, QSize, Qt, QSettings, qDebug, QLocale, QTranslator) from gui_tools import (RS, RayIcon, CommandLineArgs, _translate, serverStatusString, isDarkTheme) import add_application_dialog import child_dialogs import snapshots_dialog from gui_server_thread import GUIServerThread from gui_client import TrashedClient import ray import list_widget_clients import ui_raysession import ui_client_slot class MainWindow(QMainWindow): def __init__(self, session): QMainWindow.__init__(self) self.ui = ui_raysession.Ui_MainWindow() self.ui.setupUi(self) self._session = session self._signaler = self._session._signaler self._daemon_manager = self._session._daemon_manager self.mouse_is_inside = False self.terminate_request = False # timer for keep focus while client opening self.timer_raisewin = QTimer() self.timer_raisewin.setInterval(50) self.timer_raisewin.timeout.connect(self.raiseWindow) # timer for flashing effect of 'open' status self.timer_flicker_open = QTimer() self.timer_flicker_open.setInterval(400) self.timer_flicker_open.timeout.connect(self.flashOpen) self.flash_open_list = [] self.flash_open_bool = False # timer for too long snapshots, display snapshot progress dialog self.timer_snapshot = QTimer() self.timer_snapshot.setSingleShot(True) self.timer_snapshot.setInterval(2000) self.timer_snapshot.timeout.connect(self.showSnapshotProgressDialog) self.server_copying = False self.keep_focus = RS.settings.value('keepfocus', True, type=bool) self.ui.actionKeepFocus.setChecked(self.keep_focus) if RS.settings.value('MainWindow/geometry'): self.restoreGeometry(RS.settings.value('MainWindow/geometry')) if RS.settings.value('MainWindow/WindowState'): self.restoreState(RS.settings.value('MainWindow/WindowState')) self.ui.actionShowMenuBar.activate(RS.settings.value( 'MainWindow/ShowMenuBar', False, type=bool)) # set default action for tools buttons self.ui.closeButton.setDefaultAction(self.ui.actionCloseSession) self.ui.toolButtonSaveSession.setDefaultAction( self.ui.actionSaveSession) self.ui.toolButtonAbortSession.setDefaultAction( self.ui.actionAbortSession) self.ui.toolButtonDuplicateSession.setDefaultAction( self.ui.actionDuplicateSession) self.ui.toolButtonSaveTemplateSession.setDefaultAction( self.ui.actionSaveTemplateSession) self.ui.toolButtonFileManager.setDefaultAction( self.ui.actionOpenSessionFolder) self.ui.toolButtonAddApplication.setDefaultAction( self.ui.actionAddApplication) self.ui.toolButtonAddExecutable.setDefaultAction( self.ui.actionAddExecutable) self.ui.toolButtonSnapshots.setDefaultAction( self.ui.actionReturnToAPreviousState) # connect actions self.ui.actionNewSession.triggered.connect(self.createNewSession) self.ui.actionOpenSession.triggered.connect(self.openSession) self.ui.actionQuit.triggered.connect(self.quitApp) self.ui.actionSaveSession.triggered.connect(self.saveSession) self.ui.actionCloseSession.triggered.connect(self.closeSession) self.ui.actionAbortSession.triggered.connect(self.abortSession) self.ui.actionRenameSession.triggered.connect( self.renameSessionAction) self.ui.actionDuplicateSession.triggered.connect( self.duplicateSession) self.ui.actionSaveTemplateSession.triggered.connect( self.saveTemplateSession) self.ui.actionReturnToAPreviousState.triggered.connect( self.returnToAPreviousState) self.ui.actionOpenSessionFolder.triggered.connect( self.openFileManager) self.ui.actionAddApplication.triggered.connect(self.addApplication) self.ui.actionAddExecutable.triggered.connect(self.addExecutable) self.ui.actionKeepFocus.toggled.connect(self.toggleKeepFocus) self.ui.actionSaveAllFromSavedClient.triggered.connect( self.saveAllFromClientToggled) self.ui.actionBookmarkSessionFolder.triggered.connect( self.bookmarkSessionFolderToggled) self.ui.actionDesktopsMemory.triggered.connect( self.desktopsMemoryToggled) self.ui.actionAutoSnapshot.triggered.connect( self.autoSnapshotToggled) self.ui.actionAboutRaySession.triggered.connect(self.aboutRaySession) self.ui.actionAboutQt.triggered.connect(QApplication.aboutQt) self.ui.actionDonate.triggered.connect(self.donate) self.ui.lineEditServerStatus.statusPressed.connect( self.statusBarPressed) self.ui.stackedWidgetSessionName.name_changed.connect( self.renameSession) # set control menu self.controlMenu = QMenu() self.controlMenu.addAction(self.ui.actionShowMenuBar) self.controlMenu.addAction(self.ui.actionToggleShowMessages) self.controlMenu.addSeparator() self.controlMenu.addAction(self.ui.actionKeepFocus) self.controlMenu.addSeparator() self.controlMenu.addAction(self.ui.actionSaveAllFromSavedClient) self.controlMenu.addAction(self.ui.actionBookmarkSessionFolder) self.controlMenu.addAction(self.ui.actionAutoSnapshot) self.controlMenu.addAction(self.ui.actionDesktopsMemory) self.controlToolButton = self.ui.toolBar.widgetForAction( self.ui.actionControlMenu) self.controlToolButton.setPopupMode(QToolButton.InstantPopup) self.controlToolButton.setMenu(self.controlMenu) self.ui.toolButtonControl2.setPopupMode(QToolButton.InstantPopup) self.ui.toolButtonControl2.setMenu(self.controlMenu) # set favorites menu self.favorites_menu = QMenu(_translate('menu', 'Favorites')) self.favorites_menu.setIcon(QIcon(':scalable/breeze/star-yellow')) self.ui.toolButtonFavorites.setPopupMode(QToolButton.InstantPopup) self.ui.toolButtonFavorites.setMenu(self.favorites_menu) self.ui.menuAdd.addMenu(self.favorites_menu) # set trash menu self.trashMenu = QMenu() self.ui.trashButton.setPopupMode(QToolButton.InstantPopup) self.ui.trashButton.setMenu(self.trashMenu) # connect OSC signals from daemon sg = self._signaler sg.server_progress.connect(self.serverProgress) sg.server_status_changed.connect(self.serverChangeServerStatus) sg.server_copying.connect(self.serverCopying) sg.daemon_url_request.connect(self.showDaemonUrlWindow) # set spare icons if system icons not avalaible dark = isDarkTheme(self) if self.ui.actionNewSession.icon().isNull(): self.ui.actionNewSession.setIcon(RayIcon('folder-new', dark)) if self.ui.actionOpenSession.icon().isNull(): self.ui.actionOpenSession.setIcon(RayIcon('document-open', dark)) if self.ui.actionControlMenu.icon().isNull(): self.ui.actionControlMenu.setIcon( QIcon.fromTheme('configuration_section')) if self.ui.actionControlMenu.icon().isNull(): self.ui.actionControlMenu.setIcon(RayIcon('configure', dark)) if self.ui.actionOpenSessionFolder.icon().isNull(): self.ui.actionOpenSessionFolder.setIcon( RayIcon('system-file-manager', dark)) if self.ui.actionAddApplication.icon().isNull(): self.ui.actionAddApplication.setIcon(RayIcon('list-add', dark)) if self.ui.actionAddExecutable.icon().isNull(): self.ui.actionAddExecutable.setIcon(QIcon.fromTheme('system-run')) if self.ui.actionAddExecutable.icon().isNull(): self.ui.actionAddExecutable.setIcon(RayIcon('run-install')) self.ui.actionReturnToAPreviousState.setIcon( RayIcon('media-seek-backward', dark)) self.ui.trashButton.setIcon(RayIcon('trash-empty', dark)) self.ui.actionDuplicateSession.setIcon( RayIcon('xml-node-duplicate', dark)) self.ui.actionSaveTemplateSession.setIcon( RayIcon('document-save-as-template', dark)) self.ui.actionCloseSession.setIcon(RayIcon('window-close', dark)) self.ui.actionAbortSession.setIcon(RayIcon('list-remove', dark)) self.ui.actionSaveSession.setIcon(RayIcon('document-save', dark)) self.ui.toolButtonSaveSession.setIcon(RayIcon('document-save', dark)) self.ui.actionDesktopsMemory.setIcon(RayIcon('view-list-icons', dark)) self.setNsmLocked(CommandLineArgs.under_nsm) # disable "keep focus" if daemon is not on this machine (it takes no # sense in this case) if not self._daemon_manager.is_local: self.ui.actionKeepFocus.setChecked(False) self.ui.actionKeepFocus.setEnabled(False) self.server_progress = 0.0 self.progress_dialog_visible = False self.has_git = False def createClientWidget(self, client): return self.ui.listWidget.createClientWidget(client) def reCreateListWidget(self): # this function shouldn't exist, # it is a workaround for a bug with python-qt. # (when reorder widgets sometimes one widget is totally hidden # until user resize the window) # It has to be modified when ui_raysession is modified. self.ui.listWidget.clear() self.ui.verticalLayout.removeWidget(self.ui.listWidget) del self.ui.listWidget self.ui.listWidget = list_widget_clients.ListWidgetClients( self.ui.frameCurrentSession) self.ui.listWidget.setAcceptDrops(True) self.ui.listWidget.setStyleSheet("QFrame{border:none}") self.ui.listWidget.setDragEnabled(True) self.ui.listWidget.setDragDropMode(QAbstractItemView.InternalMove) self.ui.listWidget.setSelectionMode(QAbstractItemView.SingleSelection) self.ui.listWidget.setUniformItemSizes(True) self.ui.listWidget.setBatchSize(80) self.ui.listWidget.setObjectName("listWidget") self.ui.listWidget.setSession(self._session) self.ui.verticalLayout.addWidget(self.ui.listWidget) def setNsmLocked(self, nsm_locked): self.ui.actionNewSession.setEnabled(not nsm_locked) self.ui.actionOpenSession.setEnabled(not nsm_locked) self.ui.actionDuplicateSession.setEnabled(not nsm_locked) self.ui.actionCloseSession.setEnabled(not nsm_locked) self.ui.actionAbortSession.setEnabled(not nsm_locked) self.ui.toolBar.setVisible(not nsm_locked) self.ui.toolButtonDuplicateSession.setVisible(not nsm_locked) self.ui.toolButtonAbortSession.setVisible(not nsm_locked) self.ui.closeButton.setVisible(not nsm_locked) self.ui.toolButtonControl2.setVisible(nsm_locked) self.ui.stackedWidgetSessionName.setEditable( nsm_locked and not CommandLineArgs.out_daemon) self.ui.actionRenameSession.setEnabled( nsm_locked and not CommandLineArgs.out_daemon) def setDaemonOptions(self, options): self.ui.actionSaveAllFromSavedClient.setChecked( bool(options & ray.Option.SAVE_FROM_CLIENT)) self.ui.actionBookmarkSessionFolder.setChecked( bool(options & ray.Option.BOOKMARK_SESSION)) self.ui.actionDesktopsMemory.setChecked( bool(options & ray.Option.DESKTOPS_MEMORY)) self.ui.actionAutoSnapshot.setChecked( bool(options & ray.Option.SNAPSHOTS)) has_wmctrl = bool(options & ray.Option.HAS_WMCTRL) self.ui.actionDesktopsMemory.setEnabled(has_wmctrl) if has_wmctrl: self.ui.actionDesktopsMemory.setText( _translate('actions', 'Desktops Memory')) has_git = bool(options & ray.Option.HAS_GIT) self.ui.actionAutoSnapshot.setEnabled(has_git) self.ui.actionReturnToAPreviousState.setVisible(has_git) self.ui.toolButtonSnapshots.setVisible(has_git) if has_git: self.ui.actionAutoSnapshot.setText( _translate('actions', 'Auto Snapshot at Save')) self.has_git = has_git def toDaemon(self, *args): server = GUIServerThread.instance() if server: server.toDaemon(*args) def hideMessagesDock(self): self.ui.dockWidgetMessages.setVisible(False) def openFileManager(self): self.toDaemon('/ray/session/open_folder') def raiseWindow(self): if self.mouse_is_inside: self.activateWindow() def toggleKeepFocus(self, bool): self.keep_focus = bool if self._daemon_manager.is_local: RS.settings.setValue('keepfocus', self.keep_focus) if not bool: self.timer_raisewin.stop() def saveAllFromClientToggled(self, state): self.toDaemon('/ray/option/save_from_client', int(state)) def bookmarkSessionFolderToggled(self, state): self.toDaemon('/ray/option/bookmark_session_folder', int(state)) def desktopsMemoryToggled(self, state): self.toDaemon('/ray/option/desktops_memory', int(state)) def autoSnapshotToggled(self, state): self.toDaemon('/ray/option/snapshots', int(state)) def flashOpen(self): for client in self._session.client_list: if client.status == ray.ClientStatus.OPEN: client.widget.flashIfOpen(self.flash_open_bool) self.flash_open_bool = not self.flash_open_bool def quitApp(self): if (self._daemon_manager.launched_before and not CommandLineArgs.under_nsm): self.quitAppNow() return True if self._session.isRunning(): dialog = child_dialogs.QuitAppDialog(self) dialog.exec() if not dialog.result(): return False self.quitAppNow() return True def quitAppNow(self): self._daemon_manager.stop() def updateClientLabel(self, client_id, label): client = self._session.getClient(client_id) if not client: return client.updateLabel(label) def createNewSession(self): self.ui.dockWidgetMessages.setVisible(False) dialog = child_dialogs.NewSessionDialog(self) dialog.exec() if not dialog.result(): return session_name = dialog.getSessionName() template_name = dialog.getTemplateName() subfolder = dialog.getSubFolder() RS.settings.setValue('last_used_template', template_name) RS.settings.setValue('last_subfolder', subfolder) if self._session.isRunning(): RS.settings.setValue('last_session', self._session.getShortPath()) if template_name: self.toDaemon('/ray/server/new_session', session_name, template_name) else: self.toDaemon('/ray/server/new_session', session_name) def openSession(self, action): dialog = child_dialogs.OpenSessionDialog(self) dialog.exec() if not dialog.result(): return if self._session.isRunning(): RS.settings.setValue('last_session', self._session.getShortPath()) session_name = dialog.getSelectedSession() self.toDaemon('/ray/server/open_session', session_name) def closeSession(self): RS.settings.setValue('last_session', self._session.getShortPath()) self.toDaemon('/ray/session/close') def abortSession(self): dialog = child_dialogs.AbortSessionDialog(self) dialog.exec() if dialog.result(): RS.settings.setValue('last_session', self._session.getShortPath()) self.toDaemon('/ray/session/abort') def renameSessionAction(self): if not self._session.is_renameable: QMessageBox.information( self, _translate("rename_session", "Rename Session"), _translate("rename_session", "

In order to rename current session,
" + "please first stop all clients.
" + "then, double click on session name.

")) return self.ui.stackedWidgetSessionName.toggleEdit() def duplicateSession(self): dialog = child_dialogs.NewSessionDialog(self, True) dialog.exec() if not dialog.result(): return RS.settings.setValue('last_session', self._session.name) session_name = dialog.getSessionName() self.toDaemon('/ray/session/duplicate', session_name) def saveTemplateSession(self): dialog = child_dialogs.SaveTemplateSessionDialog(self) dialog.exec() if not dialog.result(): return session_template_name = dialog.getTemplateName() self.toDaemon('/ray/session/save_as_template', session_template_name) def returnToAPreviousState(self): dialog = snapshots_dialog.SessionSnapshotsDialog(self) dialog.exec() if not dialog.result(): return snapshot = dialog.getSelectedSnapshot() self.toDaemon('/ray/session/open_snapshot', snapshot) def aboutRaySession(self): dialog = child_dialogs.AboutRaySessionDialog(self) dialog.exec() def donate(self, display_no_again=False): dialog = child_dialogs.DonationsDialog(self, display_no_again) dialog.exec() def saveSession(self): self.toDaemon('/ray/session/save') def addApplication(self): if self._session.server_status in ( ray.ServerStatus.CLOSE, ray.ServerStatus.OFF): return dialog = add_application_dialog.AddApplicationDialog(self) dialog.exec() dialog.saveCheckBoxes() if dialog.result(): template_name, factory = dialog.getSelectedTemplate() #factory = dialog.isTemplateFactory(template_name) self.toDaemon( '/ray/session/add_client_template', int(factory), template_name) def addExecutable(self): if self._session.server_status in ( ray.ServerStatus.CLOSE, ray.ServerStatus.OFF): return dialog = child_dialogs.NewExecutableDialog(self) dialog.exec() if not dialog.result(): return #command = dialog.getExecutableSelected() #proxy = dialog.runViaProxy() command, via_proxy, prefix_mode, prefix, client_id = dialog.getSelection() self.toDaemon('/ray/session/add_executable', command, int(via_proxy), prefix_mode, prefix, client_id) #if proxy: #self.toDaemon('/ray/session/add_proxy', command) #else: #self.toDaemon('/ray/session/add_executable', command) def stopClient(self, client_id): client = self._session.getClient(client_id) if not client: return if client.no_save_level and client.check_last_save: dialog = child_dialogs.StopClientNoSaveDialog(self, client_id) dialog.exec() if not dialog.result(): return elif (client.status == ray.ClientStatus.READY and client.check_last_save): if client.has_dirty: if client.dirty_state: dialog = child_dialogs.StopClientDialog(self, client_id) dialog.exec() if not dialog.result(): return # last save (or start) more than 60 seconds ago elif (time.time() - client.last_save) >= 10: dialog = child_dialogs.StopClientDialog(self, client_id) dialog.exec() if not dialog.result(): return self.toDaemon('/ray/client/stop', client_id) def statusBarPressed(self): status = self._session.server_status if status not in ( ray.ServerStatus.PRECOPY, ray.ServerStatus.COPY, ray.ServerStatus.SNAPSHOT, ray.ServerStatus.OUT_SNAPSHOT, ray.ServerStatus.WAIT_USER): return if status in (ray.ServerStatus.PRECOPY, ray.ServerStatus.COPY): if not self.server_copying: return dialog = child_dialogs.AbortServerCopyDialog(self) dialog.exec() if not dialog.result(): return self.toDaemon('/ray/server/abort_copy') elif status in (ray.ServerStatus.SNAPSHOT, ray.ServerStatus.OUT_SNAPSHOT): self.showSnapshotProgressDialog() elif status == ray.ServerStatus.WAIT_USER: dialog = child_dialogs.WaitingCloseUserDialog(self) dialog.exec() def abortCopyClient(self, client_id): if not self.server_copying: return client = self._session.getClient(client_id) if not client or client.status not in ( ray.ClientStatus.COPY, ray.ClientStatus.PRECOPY): return dialog = child_dialogs.AbortClientCopyDialog(self, client_id) dialog.exec() if not dialog.result(): return self.toDaemon('/ray/server/abort_copy') def renameSession(self, new_session_name): self.toDaemon('/ray/session/rename', new_session_name) def addFavorite(self, name, icon_name, factory): self._session.addFavorite(name, icon_name, factory, True) def removeFavorite(self, name, factory): self._session.removeFavorite(name, factory, True) def showSnapshotProgressDialog(self): if self.progress_dialog_visible: return self.progress_dialog_visible = True dialog = child_dialogs.SnapShotProgressDialog(self) dialog.serverProgress(self.server_progress) dialog.exec() self.progress_dialog_visible = False if not dialog.result(): return self.toDaemon('/ray/server/abort_snapshot') def showDaemonUrlWindow(self, err_code, ex_url=''): dialog = child_dialogs.DaemonUrlWindow(self, err_code, ex_url) dialog.exec() if not dialog.result(): if (CommandLineArgs.under_nsm and self._daemon_manager.launched_before): QApplication.quit() return new_url = dialog.getUrl() tried_urls = ray.getListInSettings(RS.settings, 'network/tried_urls') if new_url not in tried_urls: tried_urls.append(new_url) RS.settings.setValue('network/tried_urls', tried_urls) RS.settings.setValue('network/last_tried_url', new_url) self._signaler.daemon_url_changed.emit(new_url) ###FUNCTIONS RELATED TO SIGNALS FROM OSC SERVER####### def removeClient(self, client_id): self.ui.listWidget.removeClientWidget(client_id) def clientStatusChanged(self, client_id, status): # launch/stop flashing status if 'open' for client in self._session.client_list: if client.status == ray.ClientStatus.OPEN: if not self.timer_flicker_open.isActive(): self.timer_flicker_open.start() break else: self.timer_flicker_open.stop() # launch/stop timer_raisewin if keep focus if self.keep_focus: for client in self._session.client_list: if client.status == ray.ClientStatus.OPEN: if not self.timer_raisewin.isActive(): self.timer_raisewin.start() break else: self.timer_raisewin.stop() if status == ray.ClientStatus.READY: self.raiseWindow() def printMessage(self, message): self.ui.textEditMessages.appendPlainText( time.strftime("%H:%M:%S") + ' ' + message) def renameSession(self, session_name, session_path): if session_name: self.setWindowTitle('%s - %s' % (ray.APP_TITLE, session_name)) self.ui.stackedWidgetSessionName.setText(session_name) else: self.setWindowTitle(ray.APP_TITLE) self.ui.stackedWidgetSessionName.setText( _translate('main view', 'No Session Loaded')) def setSessionNameEditable(self, bool_set_edit): self.ui.stackedWidgetSessionName.setEditable(bool_set_edit) def errorMessage(self, message): error_dialog = child_dialogs.ErrorDialog(self, message) error_dialog.exec() def openingNsmSession(self): if not RS.settings.value('OpenNsmSessionInfo', True, type=bool): return dialog = child_dialogs.OpenNsmSessionInfoDialog(self) dialog.exec() def serverProgress(self, progress): self.server_progress = progress self.ui.lineEditServerStatus.setProgress(progress) def serverCopying(self, copying): self.server_copying = copying self.serverChangeServerStatus(self._session.server_status) def serverChangeServerStatus(self, server_status): self._session.updateServerStatus(server_status) self.ui.lineEditServerStatus.setText( serverStatusString(server_status)) self.ui.frameCurrentSession.setEnabled( bool(server_status != ray.ServerStatus.OFF)) if server_status in (ray.ServerStatus.SNAPSHOT, ray.ServerStatus.OUT_SNAPSHOT): self.timer_snapshot.start() elif self.timer_snapshot.isActive(): self.timer_snapshot.stop() if server_status == ray.ServerStatus.COPY: self.ui.actionSaveSession.setEnabled(False) self.ui.actionCloseSession.setEnabled(False) self.ui.actionAbortSession.setEnabled(False) self.ui.actionReturnToAPreviousState.setEnabled(False) return if server_status == ray.ServerStatus.PRECOPY: self.ui.actionSaveSession.setEnabled(False) self.ui.actionCloseSession.setEnabled(False) self.ui.actionAbortSession.setEnabled(True) self.ui.actionDuplicateSession.setEnabled(False) self.ui.actionSaveTemplateSession.setEnabled(False) self.ui.actionReturnToAPreviousState.setEnabled(False) self.ui.actionAddApplication.setEnabled(False) self.ui.actionAddExecutable.setEnabled(False) self.ui.actionOpenSessionFolder.setEnabled(True) return close_or_off = bool( server_status in ( ray.ServerStatus.CLOSE, ray.ServerStatus.WAIT_USER, ray.ServerStatus.OUT_SAVE, ray.ServerStatus.OUT_SNAPSHOT, ray.ServerStatus.OFF)) ready = bool(server_status == ray.ServerStatus.READY) self.ui.actionSaveSession.setEnabled(ready) self.ui.actionCloseSession.setEnabled(ready) self.ui.actionAbortSession.setEnabled( not bool(server_status in (ray.ServerStatus.CLOSE, ray.ServerStatus.OFF))) self.ui.actionDuplicateSession.setEnabled(not close_or_off) self.ui.actionReturnToAPreviousState.setEnabled(not close_or_off) self.ui.actionRenameSession.setEnabled(ready) self.ui.actionSaveTemplateSession.setEnabled(not close_or_off) self.ui.actionAddApplication.setEnabled(not close_or_off) self.ui.actionAddExecutable.setEnabled(not close_or_off) self.ui.toolButtonFavorites.setEnabled( bool(self._session.favorite_list and not close_or_off)) self.favorites_menu.setEnabled( bool(self._session.favorite_list and not close_or_off)) self.ui.actionOpenSessionFolder.setEnabled( bool(server_status != ray.ServerStatus.OFF)) self.ui.stackedWidgetSessionName.setEditable( ready and self._session.is_renameable) self.ui.trashButton.setEnabled(bool(self._session.trashed_clients) and not close_or_off) if (CommandLineArgs.under_nsm and not CommandLineArgs.out_daemon and ready and self._session.is_renameable): self.ui.stackedWidgetSessionName.setOnEdit() if self.server_copying: self.ui.actionSaveSession.setEnabled(False) self.ui.actionCloseSession.setEnabled(False) if CommandLineArgs.under_nsm: self.ui.actionNewSession.setEnabled(False) self.ui.actionOpenSession.setEnabled(False) self.ui.actionDuplicateSession.setEnabled(False) self.ui.actionCloseSession.setEnabled(False) self.ui.actionAbortSession.setEnabled(False) if server_status == ray.ServerStatus.OFF: if self.terminate_request: self._daemon_manager.stop() if server_status == ray.ServerStatus.WAIT_USER: if not RS.settings.value( 'hide_wait_close_user_dialog', False, type=bool): dialog = child_dialogs.WaitingCloseUserDialog(self) dialog.exec() def trashAdd(self, client_data): prettier_name = client_data.name if client_data.label: prettier_name = client_data.label act_x_trashed = self.trashMenu.addAction( QIcon.fromTheme(client_data.icon), prettier_name) act_x_trashed.setData(client_data.client_id) act_x_trashed.triggered.connect(self.showClientTrashDialog) self.ui.trashButton.setEnabled( bool(not self._session.server_status in ( ray.ServerStatus.OFF, ray.ServerStatus.OUT_SAVE, ray.ServerStatus.WAIT_USER, ray.ServerStatus.OUT_SNAPSHOT, ray.ServerStatus.CLOSE))) return act_x_trashed def trashRemove(self, menu_action): self.trashMenu.removeAction(menu_action) if not self._session.trashed_clients: self.ui.trashButton.setEnabled(False) def trashClear(self): self.trashMenu.clear() self.ui.trashButton.setEnabled(False) @pyqtSlot() def showClientTrashDialog(self): try: client_id = str(self.sender().data()) except BaseException: return for trashed_client in self._session.trashed_clients: if trashed_client.data.client_id == client_id: break else: return dialog = child_dialogs.ClientTrashDialog(self, trashed_client.data) dialog.exec() if not dialog.result(): return self.toDaemon('/ray/trash/restore', client_id) @pyqtSlot() def launchFavorite(self): template_name, factory = self.sender().data() self.toDaemon('/ray/session/add_client_template', int(factory), template_name) def updateFavoritesMenu(self): self.favorites_menu.clear() enable = bool(self._session.favorite_list and not self._session.server_status in ( ray.ServerStatus.OFF, ray.ServerStatus.CLOSE, ray.ServerStatus.OUT_SAVE, ray.ServerStatus.OUT_SNAPSHOT)) self.ui.toolButtonFavorites.setEnabled(enable) for favorite in self._session.favorite_list: act_app = self.favorites_menu.addAction( ray.getAppIcon(favorite.icon, self), favorite.name) act_app.setData([favorite.name, favorite.factory]) act_app.triggered.connect(self.launchFavorite) def daemonCrash(self): QMessageBox.critical( self, _translate( 'errors', "daemon crash!"), _translate( 'errors', "ray-daemon crashed, sorry !")) QApplication.quit() def saveWindowSettings(self): RS.settings.setValue('MainWindow/geometry', self.saveGeometry()) RS.settings.setValue('MainWindow/WindowState', self.saveState()) RS.settings.setValue( 'MainWindow/ShowMenuBar', self.ui.menuBar.isVisible()) RS.settings.setValue( 'MainWindow/ShowMessages', self.ui.dockWidgetMessages.isVisible()) RS.settings.sync() # Reimplemented Functions def closeEvent(self, event): self.saveWindowSettings() if self.quitApp(): QMainWindow.closeEvent(self, event) else: event.ignore() def leaveEvent(self, event): if self.isActiveWindow(): self.mouse_is_inside = False QDialog.leaveEvent(self, event) def enterEvent(self, event): self.mouse_is_inside = True QDialog.enterEvent(self, event) RaySession-0.8.3/src/gui/nsm_child.py000066400000000000000000000110641356671433200175220ustar00rootroot00000000000000 import ray import nsm_client from gui_tools import CommandLineArgs, _translate from gui_server_thread import GUIServerThread class NSMChild: def __init__(self, session): self._session = session self.nsm_signaler = nsm_client.NSMSignaler() self.nsm_signaler.server_sends_open.connect(self.open) self.nsm_signaler.server_sends_save.connect(self.save) self.nsm_signaler.show_optional_gui.connect(self.showOptionalGui) self.nsm_signaler.hide_optional_gui.connect(self.hideOptionalGui) self.wait_for_open = False self.wait_for_save = False self.project_path = '' serverNSM = nsm_client.NSMThread('raysession_child', self.nsm_signaler, CommandLineArgs.NSM_URL, CommandLineArgs.debug) serverNSM.start() self._session._signaler.daemon_announce_ok.connect( self.announceToParent) self._session._signaler.server_status_changed.connect( self.serverStatusChanged) def announceToParent(self): serverNSM = nsm_client.NSMThread.instance() if serverNSM: serverNSM.announce(_translate('child_session', 'Child Session'), ':switch:optional-gui:', 'raysession') def serverStatusChanged(self, server_status): if server_status == ray.ServerStatus.READY: serverNSM = nsm_client.NSMThread.instance() if not serverNSM: return if self.wait_for_open: serverNSM.openReply() self.wait_for_open = False elif self.wait_for_save: serverNSM.saveReply() self.wait_for_save = False def open(self, project_path, session_name, jack_client_name): self.wait_for_open = True self.project_path = project_path server = GUIServerThread.instance() if server: server.openSession(project_path) def save(self): if self._session._main_win: self._session._main_win.saveWindowSettings() self.wait_for_save = True server = GUIServerThread.instance() if server: server.saveSession() def showOptionalGui(self): if self._session._main_win: self._session._main_win.show() serverNSM = nsm_client.NSMThread.instance() if serverNSM: serverNSM.sendGuiState(True) def hideOptionalGui(self): if self._session._main_win: self._session._main_win.hide() serverNSM = nsm_client.NSMThread.instance() if serverNSM: serverNSM.sendGuiState(False) class NSMChildOutside(NSMChild): def __init__(self, session): NSMChild.__init__(self, session) self.wait_for_close = False self.session_name = '' self.template_name = '' def announceToParent(self): serverNSM = nsm_client.NSMThread.instance() if serverNSM: serverNSM.announce(_translate('network_session', 'Network Session'), ':switch:optional-gui:ray-network:', 'ray-network') serverNSM.sendToDaemon( '/nsm/client/network_properties', self._session._daemon_manager.url, self._session._daemon_manager.session_root) def save(self): serverNSM = nsm_client.NSMThread.instance() if serverNSM: serverNSM.sendToDaemon( '/nsm/client/network_properties', self._session._daemon_manager.url, self._session._daemon_manager.session_root) NSMChild.save(self) def open(self, project_path, session_name, jack_client_name): self.wait_for_open = True #Here project_path is used for template if needed self.template_name = project_path self.session_name = session_name server = GUIServerThread.instance() if server: server.openSession(self.session_name, self.template_name) def closeSession(self): self.wait_for_close = True server = GUIServerThread.instance() if server: server.closeSession() RaySession-0.8.3/src/gui/nsm_client.py000077700000000000000000000000001356671433200240742../shared/nsm_client.pyustar00rootroot00000000000000RaySession-0.8.3/src/gui/ray.py000077700000000000000000000000001356671433200211742../shared/ray.pyustar00rootroot00000000000000RaySession-0.8.3/src/gui/raysession.py000077500000000000000000000050201356671433200177570ustar00rootroot00000000000000#!/usr/bin/python3 -u #libs import argparse import os import signal import sys from PyQt5.QtWidgets import QApplication from PyQt5.QtGui import QIcon, QFontDatabase from PyQt5.QtCore import QLocale, QTranslator, QTimer #local imports from gui_signaler import Signaler from daemon_manager import DaemonManager from gui_tools import ( ArgParser, CommandLineArgs, initGuiTools, default_session_root, ErrDaemon, _translate, getCodeRoot) from gui_server_thread import GUIServerThread from gui_session import SignaledSession import nsm_client import ray #import UIs import ui_raysession import ui_client_slot #import Qt resources import resources_rc def signalHandler(sig, frame): if sig in (signal.SIGINT, signal.SIGTERM): if session._daemon_manager.launched_before: if (CommandLineArgs.under_nsm and session.server_status != ray.ServerStatus.OFF): session._main_win.terminate_request = True server = GUIServerThread.instance() if server: server.abortSession() else: session._daemon_manager.stop() return session._main_win.terminate_request = True session._daemon_manager.stop() if __name__ == '__main__': #set Qt Application app = QApplication(sys.argv) app.setApplicationName("RaySession") app.setApplicationVersion(ray.VERSION) app.setOrganizationName("RaySession") app.setWindowIcon(QIcon(':/scalable/raysession.svg')) app.setQuitOnLastWindowClosed(False) ### Translation process locale = QLocale.system().name() appTranslator = QTranslator() if appTranslator.load("%s/locale/raysession_%s" % (getCodeRoot(), locale)): app.installTranslator(appTranslator) QFontDatabase.addApplicationFont(":/fonts/Ubuntu-R.ttf") QFontDatabase.addApplicationFont(":fonts/Ubuntu-C.ttf") initGuiTools() #Add raysession/src/bin to $PATH to can use raysession after make, whitout install ray.addSelfBinToPath() #get arguments parser = ArgParser() #connect signals signal.signal(signal.SIGINT , signalHandler) signal.signal(signal.SIGTERM, signalHandler) #needed for signals SIGINT, SIGTERM timer = QTimer() timer.start(200) timer.timeout.connect(lambda: None) #build session server = GUIServerThread() session = SignaledSession() app.exec() server.stop() session.quit() del session del app RaySession-0.8.3/src/gui/snapshots_dialog.py000066400000000000000000000403301356671433200211210ustar00rootroot00000000000000from PyQt5.QtCore import Qt, QDateTime, QDate, QTime from PyQt5.QtWidgets import QDialogButtonBox, QTreeWidgetItem import ray from child_dialogs import ChildDialog from gui_tools import _translate, RS import ui_list_snapshots import ui_snapshot_name import ui_snapshots_info GROUP_ELEMENT = 0 GROUP_DAY = 1 GROUP_MONTH = 2 GROUP_YEAR = 3 GROUP_MAIN = 4 class Snapshot: valid = False text = '' sub_type = GROUP_ELEMENT item = None before_rewind_to = '' date_time = None rewind_date_time = None session_name = "" label = '' rewind_label = '' ref = '' def __init__(self, date_time): self.date_time = date_time def __lt__(self, other): if not other.isValid(): return True if not self.isValid(): return False return self.date_time < other.date_time def year(self): return self.date_time.date().year() def month(self): return self.date_time.date().month() def day(self): return self.date_time.date().day() def isValid(self): if not self.date_time: return False return self.date_time.isValid() def isToday(self): if not self.date_time: return False return bool(self.date_time.date() == QDate.currentDate()) def isYesterday(self): if not self.date_time: return False return bool(self.date_time.date() == QDate.currentDate().addDays(-1)) def canTake(self, other): return False def reOrganize(self): pass def add(self): pass def commonGroup(self, other): if not (self.isValid() and other.isValid()): return GROUP_MAIN common_group = GROUP_MAIN if self.year() == other.year(): common_group = GROUP_YEAR if self.month() == other.month(): common_group = GROUP_MONTH if self.day() == other.day(): common_group = GROUP_DAY if common_group <= self.sub_type: return self.sub_type +1 return common_group def makeItem(self, sub_type): if self.isToday(): day_string = _translate('snapshots', 'Today') elif self.isYesterday(): day_string = _translate('snapshots', 'Yesterday') elif self.isValid(): day_string = self.date_time.toString('dddd d MMMM yyyy') if not self.isValid(): display_text = self.text else: display_text = _translate('snapshots', "%s at %s") % ( day_string, self.date_time.toString('HH:mm')) if sub_type in (GROUP_YEAR, GROUP_MONTH): if not self.isToday() or self.isYesterday(): day_string = self.date_time.toString('dddd d MMMM') display_text = _translate('snapshots', "%s at %s") % ( day_string, self.date_time.toString('HH:mm')) elif sub_type == GROUP_DAY: display_text = _translate('snapshots', "at %s") \ % self.date_time.toString('HH:mm') if self.rewind_date_time: display_text += '\n' display_text += _translate('snapshots', "before rewind to ") if self.rewind_label: display_text += self.rewind_label elif self.rewind_date_time.date() == self.date_time.date(): display_text += self.rewind_date_time.toString('hh:mm') elif (self.rewind_date_time.date().year() == self.date_time.date().year()): display_text += self.rewind_date_time.toString('d MMM hh:mm') else: display_text += self.rewind_date_time.toString('d MMM yyyy hh:mm') elif self.session_name: display_text += "\nsession name: %s" % self.session_name if self.label: display_text += "\n%s" % self.label item = QTreeWidgetItem([display_text]) item.setData(0, Qt.UserRole, self.text) return item class SnapGroup(Snapshot): def __init__(self, date_time=None, sub_type=GROUP_MAIN): Snapshot.__init__(self, date_time) self.sub_type = sub_type self.valid = True self.snapshots = [] def canTake(self, other): if self.sub_type <= other.sub_type: return False if self.sub_type == GROUP_MAIN: return True if self.year() != other.year(): return False if self.sub_type == GROUP_YEAR: return True if self.month() != other.month(): return False if self.sub_type == GROUP_MONTH: return True if self.day() != other.day(): return False return True def add(self, new_snapshot): if not new_snapshot.isValid(): self.snapshots.append(new_snapshot) return if self.sub_type <= 1: # If this group (self) is a day group, just add this snapshot self.snapshots.append(new_snapshot) return for snapshot in self.snapshots: if snapshot.canTake(new_snapshot): # if a snapgroup can take this snapshot, # just add this snapshot to this snapgroup. snapshot.add(new_snapshot) return smallest_cg = self.sub_type # find the smallest common group with any other for snapshot in self.snapshots: common_group = snapshot.commonGroup(new_snapshot) if common_group < smallest_cg: smallest_cg = common_group # check if there are snaps not common # with the smallest common group find above (smallest_cg) for snapshot in self.snapshots: common_group = snapshot.commonGroup(new_snapshot) if common_group != smallest_cg: break else: # There is no snap outside of smallest_cg # but there are maybe others snapshots to group together cg_final = 0 compare_snap = Snapshot(None) for cg in (GROUP_DAY, GROUP_MONTH, GROUP_YEAR): if cg_final: break if cg >= smallest_cg: continue # compare all existing snapshots with all others for i in range(len(self.snapshots)): if cg_final: break compare_snap = self.snapshots[i] if compare_snap.sub_type >= cg: continue for j in range(len(self.snapshots)): if j <= i: # prevent compare to itself or already compared continue snapshot = self.snapshots[j] if snapshot.sub_type >= cg: continue if (snapshot.commonGroup(compare_snap) == cg and snapshot.commonGroup(new_snapshot) > cg): cg_final = cg break if cg_final: snap_group = SnapGroup(compare_snap.date_time, cg_final) self.addGroup(snap_group) self.snapshots.append(new_snapshot) return # create group and add to this all snaps which have to. snap_group = SnapGroup(new_snapshot.date_time, smallest_cg) snap_group.add(new_snapshot) self.addGroup(snap_group) def addGroup(self, snap_group): to_rem = [] for i in range(len(self.snapshots)): snapshot = self.snapshots[i] if snap_group.canTake(snapshot): snap_group.add(snapshot) to_rem.append(i) to_rem.reverse() for i in to_rem: self.snapshots.__delitem__(i) self.snapshots.append(snap_group) def sort(self): for snapshot in self.snapshots: if snapshot.sub_type: snapshot.sort() self.snapshots.sort() self.snapshots.reverse() def makeItem(self, sub_type=GROUP_MAIN): display_text = '' if self.sub_type == GROUP_MAIN: return None if not self.date_time: display_text = self.text elif self.sub_type == GROUP_YEAR: display_text = self.date_time.toString('yyyy') elif self.sub_type == GROUP_MONTH: display_text = self.date_time.toString('MMMM yyyy') elif self.sub_type == GROUP_DAY: display_text = self.date_time.toString('dddd d MMMM yyyy') if self.isToday(): display_text = _translate('snapshots', 'Today') elif self.isYesterday(): display_text = _translate('snapshots', 'Yesterday') item = QTreeWidgetItem([display_text]) for snapshot in self.snapshots: sub_item = snapshot.makeItem(self.sub_type) item.addChild(sub_item) # set this group item not selectable item.setFlags(item.flags() & ~Qt.ItemIsSelectable) return item class TakeSnapshotDialog(ChildDialog): def __init__(self, parent): ChildDialog.__init__(self, parent) self.ui = ui_snapshot_name.Ui_Dialog() self.ui.setupUi(self) self.ui.lineEdit.textChanged.connect(self.textChanged) #self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) self.ui.pushButtonSave.setEnabled(False) self.ui.pushButtonSnapshot.setEnabled(False) self.__save_asked = False self.ui.pushButtonSave.clicked.connect(self.acceptWithSave) def textChanged(self, text): #self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(bool(text)) self.ui.pushButtonSave.setEnabled(bool(text)) self.ui.pushButtonSnapshot.setEnabled(bool(text)) def getSnapshotName(self): return self.ui.lineEdit.text() def saveAsked(self): return self.__save_asked def acceptWithSave(self): self.__save_asked = True self.accept() class SnapshotsDialog(ChildDialog): def __init__(self, parent): ChildDialog.__init__(self, parent) self.ui = ui_list_snapshots.Ui_Dialog() self.ui.setupUi(self) self._signaler.reply_auto_snapshot.connect( self.ui.checkBoxAutoSnapshot.setChecked) self._signaler.snapshots_found.connect(self.addSnapshots) self.snapshots = [] self.main_snap_group = SnapGroup() self.ui.snapshotsList.setHeaderHidden(True) self.ui.snapshotsList.currentItemChanged.connect( self.currentItemChanged) self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) def currentItemChanged(self, current, previous): self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled( bool(current and current.data(0, Qt.UserRole))) def decodeTimeString(self, time_str): while time_str.endswith('_'): time_str = time_str[:-1] label = '' strs = time_str.split('_') if len(strs) > 6: time_str = '' i = 0 for stri in strs: if i < 6: time_str += "%s_" % stri else: label += "%s_" % stri i+=1 time_str = time_str[:-1] label = label[:-1] return (time_str, label) def addSnapshots(self, snaptexts): for snaptext in snaptexts: if not snaptext: continue time_str_full, line_change, rw_time_str_full_sess = \ snaptext.partition('\n') rw_time_str_full, line_change, session_name = \ rw_time_str_full_sess.partition('\n') time_str, two_points, label = time_str_full.partition(':') rw_time_str, two_points, rw_label = rw_time_str_full.partition(':') utc_date_time = QDateTime.fromString(time_str, 'yyyy_M_d_h_m_s') utc_rw_date_time = QDateTime.fromString(rw_time_str, 'yyyy_M_d_h_m_s') utc_date_time.setTimeSpec(Qt.OffsetFromUTC) utc_rw_date_time.setTimeSpec(Qt.OffsetFromUTC) date_time = None rw_date_time = None if utc_date_time.isValid(): date_time = utc_date_time.toLocalTime() if utc_rw_date_time.isValid(): rw_date_time = utc_rw_date_time.toLocalTime() snapshot = Snapshot(date_time) snapshot.text = snaptext snapshot.label = label snapshot.rewind_date_time = rw_date_time snapshot.rewind_label = rw_label snapshot.session_name = session_name self.main_snap_group.add(snapshot) self.main_snap_group.sort() self.ui.snapshotsList.clear() for snapshot in self.main_snap_group.snapshots: item = snapshot.makeItem(GROUP_MAIN) self.ui.snapshotsList.addTopLevelItem(item) self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) self.ui.snapshotsList.clearSelection() def getSelectedSnapshot(self): item = self.ui.snapshotsList.currentItem() full_str = item.data(0, Qt.UserRole) snapshot_ref = full_str.partition('\n')[0].partition(':')[0] return snapshot_ref def showEvent(self, event): ChildDialog.showEvent(self, event) if RS.settings.value('hide_snapshots_info'): return info_dialog = SnapshotsInfoDialog(self) info_dialog.exec() if info_dialog.hasToBeHiddenNextTime(): RS.settings.setValue('hide_snapshots_info', True) class SnapshotsInfoDialog(ChildDialog): def __init__(self, parent): ChildDialog.__init__(self, parent) self.ui = ui_snapshots_info.Ui_Dialog() self.ui.setupUi(self) def hasToBeHiddenNextTime(self): return self.ui.checkBox.isChecked() class SessionSnapshotsDialog(SnapshotsDialog): def __init__(self, parent): SnapshotsDialog.__init__(self, parent) self.ui.pushButtonSnapshotNow.clicked.connect(self.takeSnapshot) self.toDaemon('/ray/session/list_snapshots') self.ui.checkBoxAutoSnapshot.stateChanged.connect( self.setAutoSnapshot) def takeSnapshot(self): dialog = TakeSnapshotDialog(self) dialog.exec() if dialog.result(): snapshot_label = dialog.getSnapshotName() with_save = dialog.saveAsked() self.toDaemon('/ray/session/take_snapshot', snapshot_label, int(with_save)) def setAutoSnapshot(self, bool_snapshot): self.toDaemon('/ray/session/set_auto_snapshot', int(bool_snapshot)) class ClientSnapshotsDialog(SnapshotsDialog): def __init__(self, parent, client): SnapshotsDialog.__init__(self, parent) self.ui.pushButtonSnapshotNow.hide() self.ui.checkBoxAutoSnapshot.hide() self.client = client self.toDaemon('/ray/client/list_snapshots', client.client_id) RaySession-0.8.3/src/gui/surclassed_widgets.py000066400000000000000000000176421356671433200214700ustar00rootroot00000000000000from PyQt5.QtWidgets import (QLineEdit, QStackedWidget, QLabel, QToolButton, QFrame) from PyQt5.QtGui import QFont, QFontDatabase, QFontMetrics, QPalette from PyQt5.QtCore import Qt, QTimer, pyqtSignal import time class HideGuiButton(QToolButton): toggleGui = pyqtSignal() def __init__(self, parent): QToolButton.__init__(self, parent) basecolor = self.palette().base().color().name() textcolor = self.palette().buttonText().color().name() textdbcolor = self.palette().brush( QPalette.Disabled, QPalette.WindowText).color().name() style = "QToolButton{border-radius: 2px ;border-left: 1px solid " \ + "qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 " \ + textcolor + ", stop:0.35 " + basecolor + ", stop:0.75 " \ + basecolor + ", stop:1 " + textcolor + ")" \ + ";border-right: 1px solid " \ + "qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 " \ + textcolor + ", stop:0.25 " + basecolor + ", stop:0.75 " \ + basecolor + ", stop:1 " + textcolor + ")" \ + ";border-top: 1px solid " + textcolor \ + ";border-bottom : 1px solid " + textcolor \ + "; background-color: " + basecolor + "; font-size: 11px" + "}"\ + "QToolButton::checked{background-color: " \ + "qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 " \ + textcolor + ", stop:0.25 " + basecolor + ", stop:0.85 " \ + basecolor + ", stop:1 " + textcolor + ")" \ + "; margin-top: 0px; margin-left: 0px " + "}" \ + "QToolButton::disabled{;border-left: 1px solid " \ + "qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 " \ + textdbcolor + ", stop:0.25 " + basecolor + ", stop:0.75 " \ + basecolor + ", stop:1 " + textdbcolor + ")" \ + ";border-right: 1px solid " \ + "qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 " \ + textdbcolor + ", stop:0.25 " + basecolor + ", stop:0.75 " \ + basecolor + ", stop:1 " + textdbcolor + ")" \ + ";border-top: 1px solid " + textdbcolor \ + ";border-bottom : 1px solid " + textdbcolor \ + "; background-color: " + basecolor + "}" self.setStyleSheet(style) def mousePressEvent(self, event): self.toggleGui.emit() # and not toggle button, the client will emit a gui state that will # toggle this button class OpenSessionFilterBar(QLineEdit): updownpressed = pyqtSignal(int) def __init__(self, parent): QLineEdit.__init__(self) def keyPressEvent(self, event): if event.key() in (Qt.Key_Up, Qt.Key_Down): self.updownpressed.emit(event.key()) QLineEdit.keyPressEvent(self, event) class CustomLineEdit(QLineEdit): def __init__(self, parent): QLineEdit.__init__(self) self.parent = parent def mouseDoubleClickEvent(self, event): self.parent.mouseDoubleClickEvent(event) def keyPressEvent(self, event): if event.key() in (Qt.Key_Enter, Qt.Key_Return): self.parent.name_changed.emit(self.text()) self.parent.setCurrentIndex(0) return QLineEdit.keyPressEvent(self, event) class SessionFrame(QFrame): def __init__(self, parent): QFrame.__init__(self) class StackedSessionName(QStackedWidget): name_changed = pyqtSignal(str) def __init__(self, parent): QStackedWidget.__init__(self) self.is_editable = True self.label_widget = QLabel() self.label_widget.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self.label_widget.setStyleSheet("QLabel {font-weight : bold}") self.line_edit_widget = CustomLineEdit(self) self.line_edit_widget.setAlignment(Qt.AlignHCenter) self.addWidget(self.label_widget) self.addWidget(self.line_edit_widget) self.setCurrentIndex(0) def mouseDoubleClickEvent(self, event): if self.currentIndex() == 1: self.setCurrentIndex(0) self.name_changed.emit(self.line_edit_widget.text()) return elif self.currentIndex() == 0 and self.is_editable: self.setCurrentIndex(1) return QStackedWidget.mouseDoubleClickEvent(self, event) def setEditable(self, editable): self.is_editable = editable if not editable: self.setCurrentIndex(0) def setText(self, text): self.label_widget.setText(text) self.line_edit_widget.setText(text) self.setCurrentIndex(0) def toggleEdit(self): if not self.is_editable: self.setCurrentIndex(0) return if self.currentIndex() == 0: self.setCurrentIndex(1) self.line_edit_widget.setFocus(Qt.OtherFocusReason) else: self.setCurrentIndex(0) def setOnEdit(self): if not self.is_editable: return self.setCurrentIndex(1) class StatusBar(QLineEdit): statusPressed = pyqtSignal() def __init__(self, parent): QLineEdit.__init__(self) self.next_texts = [] self.timer = QTimer() self.timer.setInterval(350) self.timer.timeout.connect(self.showNextText) self.ubuntu_font = QFont( QFontDatabase.applicationFontFamilies(0)[0], 8) self.ubuntu_font_cond = QFont( QFontDatabase.applicationFontFamilies(1)[0], 8) self.ubuntu_font.setBold(True) self.ubuntu_font_cond.setBold(True) self.basecolor = self.palette().base().color().name() self.bluecolor = self.palette().highlight().color().name() self.last_status_time = 0.0 # ui_client_slot.py will display "stopped" status. # we need to not stay on this status text # especially at client switch because widget is recreated. self._first_text_done = False def showNextText(self): if self.next_texts: next_text = self.next_texts[0] self.next_texts.__delitem__(0) self.setText(next_text, True) else: self.timer.stop() def setFontForText(self, text): if QFontMetrics(self.ubuntu_font).width(text) > (self.width() - 10): self.setFont(self.ubuntu_font_cond) else: self.setFont(self.ubuntu_font) def setText(self, text, from_timer=False): self.last_status_time = time.time() if not self._first_text_done: self.setFontForText(text) QLineEdit.setText(self, text) self._first_text_done = True return if text and not from_timer: if self.timer.isActive(): self.next_texts.append(text) return self.timer.start() if not text: self.next_texts.clear() self.setFontForText(text) self.setStyleSheet('') QLineEdit.setText(self, text) def setProgress(self, progress): if not 0.0 <= progress <= 1.0: return # no progress display in the first second if time.time() - self.last_status_time < 1.0: return pre_progress = progress - 0.03 if pre_progress < 0: pre_progress = 0 style = "QLineEdit{background-color: " \ + "qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0," \ + "stop:0 %s, stop:%f %s, stop:%f %s, stop:1 %s)}" \ % (self.bluecolor, pre_progress, self.bluecolor, progress, self.basecolor, self.basecolor) self.setStyleSheet(style) def mousePressEvent(self, event): self.statusPressed.emit() class StatusBarNegativ(StatusBar): def __init__(self, parent): StatusBar.__init__(self, parent) RaySession-0.8.3/src/shared/000077500000000000000000000000001356671433200156705ustar00rootroot00000000000000RaySession-0.8.3/src/shared/jacklib.py000066400000000000000000001332561356671433200176530ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # JACK ctypes definitions for usage in python applications # Copyright (C) 2010-2013 Filipe Coelho # # 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 # 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. # # For a full copy of the GNU General Public License see the COPYING file # ------------------------------------------------------------------------------------------------------------ # Imports (Global) from ctypes import ( cdll, c_int, c_uint8, c_uint32, c_uint64, c_int32, Structure, c_float, c_size_t, POINTER, c_double, c_float, ARRAY, c_char_p, CFUNCTYPE, c_void_p, c_ulong, pointer) from sys import platform # ------------------------------------------------------------------------------------------------------------ # Load JACK shared library try: if platform == "darwin": jacklib = cdll.LoadLibrary("libjack.dylib") elif platform in ("win32", "win64", "cygwin"): jacklib = cdll.LoadLibrary("libjack.dll") else: jacklib = cdll.LoadLibrary("libjack.so.0") except: jacklib = None raise ImportError("JACK is not available in this system") # ------------------------------------------------------------------------------------------------------------ # JACK2 test try: if jacklib.jack_get_version_string: JACK2 = True else: JACK2 = False except: JACK2 = False # ------------------------------------------------------------------------------------------------------------ # Pre-Types c_enum = c_int c_uchar = c_uint8 class _jack_port(Structure): _fields_ = [] class _jack_client(Structure): _fields_ = [] # ------------------------------------------------------------------------------------------------------------ # Defines JACK_MAX_FRAMES = 4294967295 JACK_LOAD_INIT_LIMIT = 1024 JACK_DEFAULT_AUDIO_TYPE = "32 bit float mono audio" JACK_DEFAULT_MIDI_TYPE = "8 bit raw midi" # ------------------------------------------------------------------------------------------------------------ # Types jack_nframes_t = c_uint32 jack_port_id_t = c_uint32 jack_time_t = c_uint64 jack_unique_t = c_uint64 jack_midi_data_t = c_uchar jack_default_audio_sample_t = c_float jack_options_t = c_enum # JackOptions jack_status_t = c_enum # JackStatus jack_transport_state_t = c_enum # JackTransportState jack_position_bits_t = c_enum # JackPositionBits jack_session_event_type_t = c_enum # JackSessionEventType jack_session_flags_t = c_enum # JackSessionFlags jack_custom_change_t = c_enum # JackCustomChange jack_latency_callback_mode_t = c_enum # JackLatencyCallbackMode jack_port_t = _jack_port jack_client_t = _jack_client jack_port_type_id_t = c_uint32 # JACK2 only # enum JackOptions JackNullOption = 0x00 JackNoStartServer = 0x01 JackUseExactName = 0x02 JackServerName = 0x04 JackLoadName = 0x08 JackLoadInit = 0x10 JackSessionID = 0x20 JackOpenOptions = JackSessionID|JackServerName|JackNoStartServer|JackUseExactName JackLoadOptions = JackLoadInit|JackLoadName|JackUseExactName # enum JackStatus JackFailure = 0x01 JackInvalidOption = 0x02 JackNameNotUnique = 0x04 JackServerStarted = 0x08 JackServerFailed = 0x10 JackServerError = 0x20 JackNoSuchClient = 0x40 JackLoadFailure = 0x80 JackInitFailure = 0x100 JackShmFailure = 0x200 JackVersionError = 0x400 JackBackendError = 0x800 JackClientZombie = 0x1000 # enum JackLatencyCallbackMode JackCaptureLatency = 0 JackPlaybackLatency = 1 # enum JackPortFlags JackPortIsInput = 0x1 JackPortIsOutput = 0x2 JackPortIsPhysical = 0x4 JackPortCanMonitor = 0x8 JackPortIsTerminal = 0x10 JackPortIsControlVoltage = 0x100 # enum JackTransportState JackTransportStopped = 0 JackTransportRolling = 1 JackTransportLooping = 2 JackTransportStarting = 3 JackTransportNetStarting = 4 # JACK2 only # enum JackPositionBits JackPositionBBT = 0x10 JackPositionTimecode = 0x20 JackBBTFrameOffset = 0x40 JackAudioVideoRatio = 0x80 JackVideoFrameOffset = 0x100 JACK_POSITION_MASK = JackPositionBBT|JackPositionTimecode|JackBBTFrameOffset|JackAudioVideoRatio|JackVideoFrameOffset # enum JackSessionEventType JackSessionSave = 1 JackSessionSaveAndQuit = 2 JackSessionSaveTemplate = 3 # enum JackSessionFlags JackSessionSaveError = 0x01 JackSessionNeedTerminal = 0x02 # enum JackCustomChange JackCustomRemoved = 0 JackCustomAdded = 1 JackCustomReplaced = 2 # ------------------------------------------------------------------------------------------------------------ # Structs class jack_midi_event_t(Structure): _fields_ = [ ("time", jack_nframes_t), ("size", c_size_t), ("buffer", POINTER(jack_midi_data_t)) ] class jack_latency_range_t(Structure): _fields_ = [ ("min", jack_nframes_t), ("max", jack_nframes_t) ] class jack_position_t(Structure): _fields_ = [ ("unique_1", jack_unique_t), ("usecs", jack_time_t), ("frame_rate", jack_nframes_t), ("frame", jack_nframes_t), ("valid", jack_position_bits_t), ("bar", c_int32), ("beat", c_int32), ("tick", c_int32), ("bar_start_tick", c_double), ("beats_per_bar", c_float), ("beat_type", c_float), ("ticks_per_beat", c_double), ("beats_per_minute", c_double), ("frame_time", c_double), ("next_time", c_double), ("bbt_offset", jack_nframes_t), ("audio_frames_per_video_frame", c_float), ("video_offset", jack_nframes_t), ("padding", ARRAY(c_int32, 7)), ("unique_2", jack_unique_t) ] class jack_session_event_t(Structure): _fields_ = [ ("type", jack_session_event_type_t), ("session_dir", c_char_p), ("client_uuid", c_char_p), ("command_line", c_char_p), ("flags", jack_session_flags_t), ("future", c_uint32) ] class jack_session_command_t(Structure): _fields_ = [ ("uuid", c_char_p), ("client_name", c_char_p), ("command", c_char_p), ("flags", jack_session_flags_t) ] # ------------------------------------------------------------------------------------------------------------ # Callbacks JackLatencyCallback = CFUNCTYPE(None, jack_latency_callback_mode_t, c_void_p) JackProcessCallback = CFUNCTYPE(c_int, jack_nframes_t, c_void_p) JackThreadCallback = CFUNCTYPE(c_void_p, c_void_p) JackThreadInitCallback = CFUNCTYPE(None, c_void_p) JackGraphOrderCallback = CFUNCTYPE(c_int, c_void_p) JackXRunCallback = CFUNCTYPE(c_int, c_void_p) JackBufferSizeCallback = CFUNCTYPE(c_int, jack_nframes_t, c_void_p) JackSampleRateCallback = CFUNCTYPE(c_int, jack_nframes_t, c_void_p) JackPortRegistrationCallback = CFUNCTYPE(None, jack_port_id_t, c_int, c_void_p) JackClientRegistrationCallback = CFUNCTYPE(None, c_char_p, c_int, c_void_p) JackClientRenameCallback = CFUNCTYPE(c_int, c_char_p, c_char_p, c_void_p) # JACK2 only JackPortConnectCallback = CFUNCTYPE(None, jack_port_id_t, jack_port_id_t, c_int, c_void_p) JackPortRenameCallback = CFUNCTYPE(c_int, jack_port_id_t, c_char_p, c_char_p, c_void_p) # JACK2 only JackFreewheelCallback = CFUNCTYPE(None, c_int, c_void_p) JackShutdownCallback = CFUNCTYPE(None, c_void_p) JackInfoShutdownCallback = CFUNCTYPE(None, jack_status_t, c_char_p, c_void_p) JackSyncCallback = CFUNCTYPE(c_int, jack_transport_state_t, POINTER(jack_position_t), c_void_p) JackTimebaseCallback = CFUNCTYPE(None, jack_transport_state_t, jack_nframes_t, POINTER(jack_position_t), c_int, c_void_p) JackSessionCallback = CFUNCTYPE(None, POINTER(jack_session_event_t), c_void_p) JackCustomDataAppearanceCallback = CFUNCTYPE(None, c_char_p, c_char_p, jack_custom_change_t, c_void_p) # ------------------------------------------------------------------------------------------------------------ # Functions try: jacklib.jack_get_version_string.argtypes = None jacklib.jack_get_version_string.restype = c_char_p except: jacklib.jack_get_version_string = None try: jacklib.jack_client_open.argtypes = [c_char_p, jack_options_t, POINTER(jack_status_t), c_char_p] jacklib.jack_client_open.restype = POINTER(jack_client_t) except: jacklib.jack_client_open = None try: jacklib.jack_client_rename.argtypes = [POINTER(jack_client_t), c_char_p] jacklib.jack_client_rename.restype = c_char_p except: jacklib.jack_client_rename = None try: jacklib.jack_client_close.argtypes = [POINTER(jack_client_t)] jacklib.jack_client_close.restype = c_int except: jacklib.jack_client_close = None try: jacklib.jack_client_name_size.argtypes = None jacklib.jack_client_name_size.restype = c_int except: jacklib.jack_client_name_size = None try: jacklib.jack_get_client_name.argtypes = [POINTER(jack_client_t)] jacklib.jack_get_client_name.restype = c_char_p except: jacklib.jack_get_client_name = None try: jacklib.jack_activate.argtypes = [POINTER(jack_client_t)] jacklib.jack_activate.restype = c_int except: jacklib.jack_activate = None try: jacklib.jack_deactivate.argtypes = [POINTER(jack_client_t)] jacklib.jack_deactivate.restype = c_int except: jacklib.jack_deactivate = None try: jacklib.jack_get_client_pid.argtypes = [c_char_p] jacklib.jack_get_client_pid.restype = c_int except: jacklib.jack_get_client_pid = None try: jacklib.jack_is_realtime.argtypes = [POINTER(jack_client_t)] jacklib.jack_is_realtime.restype = c_int except: jacklib.jack_is_realtime = None def get_version_string(): # JACK2 only if jacklib.jack_get_version_string: return jacklib.jack_get_version_string() return None def client_open(client_name, options, status, uuid=""): if jacklib.jack_client_open: return jacklib.jack_client_open(client_name.encode("utf-8"), options, status, uuid.encode("utf-8") if uuid else None) return None def client_rename(client, new_name): if jacklib.jack_client_rename: return jacklib.jack_client_rename(client, new_name.encode("utf-8")) return None def client_close(client): if jacklib.jack_client_close: return jacklib.jack_client_close(client) return -1 def client_name_size(): if jacklib.jack_client_name_size: return jacklib.jack_client_name_size() return 0 def get_client_name(client): if jacklib.jack_get_client_name: return jacklib.jack_get_client_name(client) return None def activate(client): if jacklib.jack_activate: return jacklib.jack_activate(client) return -1 def deactivate(client): if jacklib.jack_deactivate: return jacklib.jack_deactivate(client) return -1 def get_client_pid(name): # JACK2 only if jacklib.jack_get_client_pid: return jacklib.jack_get_client_pid(name.encode("utf-8")) return 0 def is_realtime(client): if jacklib.jack_is_realtime: return jacklib.jack_is_realtime(client) return 0 # ------------------------------------------------------------------------------------------------------------ # Non-Callback API global _thread_callback _thread_callback = None try: jacklib.jack_cycle_wait.argtypes = [POINTER(jack_client_t)] jacklib.jack_cycle_wait.restype = jack_nframes_t except: jacklib.jack_cycle_wait = None try: jacklib.jack_cycle_signal.argtypes = [POINTER(jack_client_t), c_int] jacklib.jack_cycle_signal.restype = None except: jacklib.jack_cycle_signal = None try: jacklib.jack_set_process_thread.argtypes = [POINTER(jack_client_t), JackThreadCallback, c_void_p] jacklib.jack_set_process_thread.restype = c_int except: jacklib.jack_set_process_thread = None def cycle_wait(client): if jacklib.jack_cycle_wait: return jacklib.jack_cycle_wait(client) return 0 def cycle_signal(client, status): if jacklib.jack_cycle_signal: jacklib.jack_cycle_signal(client, status) def set_process_thread(client, thread_callback, arg): if jacklib.jack_set_process_thread: global _thread_callback _thread_callback = JackThreadCallback(thread_callback) return jacklib.jack_set_process_thread(client, _thread_callback, arg) return -1 # ------------------------------------------------------------------------------------------------------------ # Client Callbacks global _thread_init_callback global _shutdown_callback global _info_shutdown_callback global _process_callback global _freewheel_callback global _bufsize_callback global _srate_callback global _client_registration_callback global _client_rename_callback global _port_registration_callback global _port_connect_callback global _port_rename_callback global _graph_callback global _xrun_callback global _latency_callback _thread_init_callback = _shutdown_callback = _info_shutdown_callback = None _process_callback = _freewheel_callback = _bufsize_callback = _srate_callback = None _client_registration_callback = _client_rename_callback = None _port_registration_callback = _port_connect_callback = _port_rename_callback = None _graph_callback = _xrun_callback = _latency_callback = None try: jacklib.jack_set_thread_init_callback.argtypes = [POINTER(jack_client_t), JackThreadInitCallback, c_void_p] jacklib.jack_set_thread_init_callback.restype = c_int except: jacklib.jack_set_thread_init_callback = None try: jacklib.jack_on_shutdown.argtypes = [POINTER(jack_client_t), JackShutdownCallback, c_void_p] jacklib.jack_on_shutdown.restype = None except: jacklib.jack_on_shutdown = None try: jacklib.jack_on_info_shutdown.argtypes = [POINTER(jack_client_t), JackInfoShutdownCallback, c_void_p] jacklib.jack_on_info_shutdown.restype = None except: jacklib.jack_on_info_shutdown = None try: jacklib.jack_set_process_callback.argtypes = [POINTER(jack_client_t), JackProcessCallback, c_void_p] jacklib.jack_set_process_callback.restype = c_int except: jacklib.jack_set_process_callback = None try: jacklib.jack_set_freewheel_callback.argtypes = [POINTER(jack_client_t), JackFreewheelCallback, c_void_p] jacklib.jack_set_freewheel_callback.restype = c_int except: jacklib.jack_set_freewheel_callback = None try: jacklib.jack_set_buffer_size_callback.argtypes = [POINTER(jack_client_t), JackBufferSizeCallback, c_void_p] jacklib.jack_set_buffer_size_callback.restype = c_int except: jacklib.jack_set_buffer_size_callback = None try: jacklib.jack_set_sample_rate_callback.argtypes = [POINTER(jack_client_t), JackSampleRateCallback, c_void_p] jacklib.jack_set_sample_rate_callback.restype = c_int except: jacklib.jack_set_sample_rate_callback = None try: jacklib.jack_set_client_registration_callback.argtypes = [POINTER(jack_client_t), JackClientRegistrationCallback, c_void_p] jacklib.jack_set_client_registration_callback.restype = c_int except: jacklib.jack_set_client_registration_callback = None try: jacklib.jack_set_client_rename_callback.argtypes = [POINTER(jack_client_t), JackClientRenameCallback, c_void_p] jacklib.jack_set_client_rename_callback.restype = c_int except: jacklib.jack_set_client_rename_callback = None try: jacklib.jack_set_port_registration_callback.argtypes = [POINTER(jack_client_t), JackPortRegistrationCallback, c_void_p] jacklib.jack_set_port_registration_callback.restype = c_int except: jacklib.jack_set_port_registration_callback = None try: jacklib.jack_set_port_connect_callback.argtypes = [POINTER(jack_client_t), JackPortConnectCallback, c_void_p] jacklib.jack_set_port_connect_callback.restype = c_int except: jacklib.jack_set_port_connect_callback = None try: jacklib.jack_set_port_rename_callback.argtypes = [POINTER(jack_client_t), JackPortRenameCallback, c_void_p] jacklib.jack_set_port_rename_callback.restype = c_int except: jacklib.jack_set_port_rename_callback = None try: jacklib.jack_set_graph_order_callback.argtypes = [POINTER(jack_client_t), JackGraphOrderCallback, c_void_p] jacklib.jack_set_graph_order_callback.restype = c_int except: jacklib.jack_set_graph_order_callback = None try: jacklib.jack_set_xrun_callback.argtypes = [POINTER(jack_client_t), JackXRunCallback, c_void_p] jacklib.jack_set_xrun_callback.restype = c_int except: jacklib.jack_set_xrun_callback = None try: jacklib.jack_set_latency_callback.argtypes = [POINTER(jack_client_t), JackLatencyCallback, c_void_p] jacklib.jack_set_latency_callback.restype = c_int except: jacklib.jack_set_latency_callback = None def set_thread_init_callback(client, thread_init_callback, arg): if jacklib.jack_set_thread_init_callback: global _thread_init_callback _thread_init_callback = JackThreadInitCallback(thread_init_callback) return jacklib.jack_set_thread_init_callback(client, _thread_init_callback, arg) return -1 def on_shutdown(client, shutdown_callback, arg): if jacklib.jack_on_shutdown: global _shutdown_callback _shutdown_callback = JackShutdownCallback(shutdown_callback) jacklib.jack_on_shutdown(client, _shutdown_callback, arg) def on_info_shutdown(client, info_shutdown_callback, arg): if jacklib.jack_on_info_shutdown: global _info_shutdown_callback _info_shutdown_callback = JackInfoShutdownCallback(info_shutdown_callback) jacklib.jack_on_info_shutdown(client, _info_shutdown_callback, arg) def set_process_callback(client, process_callback, arg): if jacklib.jack_set_process_callback: global _process_callback _process_callback = JackProcessCallback(process_callback) return jacklib.jack_set_process_callback(client, _process_callback, arg) return -1 def set_freewheel_callback(client, freewheel_callback, arg): if jacklib.jack_set_freewheel_callback: global _freewheel_callback _freewheel_callback = JackFreewheelCallback(freewheel_callback) return jacklib.jack_set_freewheel_callback(client, _freewheel_callback, arg) return -1 def set_buffer_size_callback(client, bufsize_callback, arg): if jacklib.jack_set_buffer_size_callback: global _bufsize_callback _bufsize_callback = JackBufferSizeCallback(bufsize_callback) return jacklib.jack_set_buffer_size_callback(client, _bufsize_callback, arg) return -1 def set_sample_rate_callback(client, srate_callback, arg): if jacklib.jack_set_sample_rate_callback: global _srate_callback _srate_callback = JackSampleRateCallback(srate_callback) return jacklib.jack_set_sample_rate_callback(client, _srate_callback, arg) return -1 def set_client_registration_callback(client, client_registration_callback, arg): if jacklib.jack_set_client_registration_callback: global _client_registration_callback _client_registration_callback = JackClientRegistrationCallback(client_registration_callback) return jacklib.jack_set_client_registration_callback(client, _client_registration_callback, arg) return -1 def set_client_rename_callback(client, client_rename_callback, arg): # JACK2 only if jacklib.jack_set_client_rename_callback: global _client_rename_callback _client_rename_callback = JackClientRenameCallback(client_rename_callback) return jacklib.jack_set_client_rename_callback(client, _client_rename_callback, arg) return -1 def set_port_registration_callback(client, port_registration_callback, arg): if jacklib.jack_set_port_registration_callback: global _port_registration_callback _port_registration_callback = JackPortRegistrationCallback(port_registration_callback) return jacklib.jack_set_port_registration_callback(client, _port_registration_callback, arg) return -1 def set_port_connect_callback(client, connect_callback, arg): if jacklib.jack_set_port_connect_callback: global _port_connect_callback _port_connect_callback = JackPortConnectCallback(connect_callback) return jacklib.jack_set_port_connect_callback(client, _port_connect_callback, arg) return -1 def set_port_rename_callback(client, rename_callback, arg): # JACK2 only if jacklib.jack_set_port_rename_callback: global _port_rename_callback _port_rename_callback = JackPortRenameCallback(rename_callback) return jacklib.jack_set_port_rename_callback(client, _port_rename_callback, arg) return -1 def set_graph_order_callback(client, graph_callback, arg): if jacklib.jack_set_graph_order_callback: global _graph_callback _graph_callback = JackGraphOrderCallback(graph_callback) return jacklib.jack_set_graph_order_callback(client, _graph_callback, arg) return -1 def set_xrun_callback(client, xrun_callback, arg): if jacklib.jack_set_xrun_callback: global _xrun_callback _xrun_callback = JackXRunCallback(xrun_callback) return jacklib.jack_set_xrun_callback(client, _xrun_callback, arg) return -1 def set_latency_callback(client, latency_callback, arg): if jacklib.jack_set_latency_callback: global _latency_callback _latency_callback = JackLatencyCallback(latency_callback) return jacklib.jack_set_latency_callback(client, _latency_callback, arg) return -1 # ------------------------------------------------------------------------------------------------------------ # Server Control jacklib.jack_set_freewheel.argtypes = [POINTER(jack_client_t), c_int] jacklib.jack_set_freewheel.restype = c_int jacklib.jack_set_buffer_size.argtypes = [POINTER(jack_client_t), jack_nframes_t] jacklib.jack_set_buffer_size.restype = c_int jacklib.jack_get_sample_rate.argtypes = [POINTER(jack_client_t)] jacklib.jack_get_sample_rate.restype = jack_nframes_t jacklib.jack_get_buffer_size.argtypes = [POINTER(jack_client_t)] jacklib.jack_get_buffer_size.restype = jack_nframes_t jacklib.jack_engine_takeover_timebase.argtypes = [POINTER(jack_client_t)] jacklib.jack_engine_takeover_timebase.restype = c_int jacklib.jack_cpu_load.argtypes = [POINTER(jack_client_t)] jacklib.jack_cpu_load.restype = c_float def set_freewheel(client, onoff): return jacklib.jack_set_freewheel(client, onoff) def set_buffer_size(client, nframes): return jacklib.jack_set_buffer_size(client, nframes) def get_sample_rate(client): return jacklib.jack_get_sample_rate(client) def get_buffer_size(client): return jacklib.jack_get_buffer_size(client) def engine_takeover_timebase(client): return jacklib.jack_engine_takeover_timebase(client) def cpu_load(client): return jacklib.jack_cpu_load(client) # ------------------------------------------------------------------------------------------------------------ # Port Functions jacklib.jack_port_register.argtypes = [POINTER(jack_client_t), c_char_p, c_char_p, c_ulong, c_ulong] jacklib.jack_port_register.restype = POINTER(jack_port_t) jacklib.jack_port_unregister.argtypes = [POINTER(jack_client_t), POINTER(jack_port_t)] jacklib.jack_port_unregister.restype = c_int jacklib.jack_port_get_buffer.argtypes = [POINTER(jack_port_t), jack_nframes_t] jacklib.jack_port_get_buffer.restype = c_void_p jacklib.jack_port_name.argtypes = [POINTER(jack_port_t)] jacklib.jack_port_name.restype = c_char_p jacklib.jack_port_short_name.argtypes = [POINTER(jack_port_t)] jacklib.jack_port_short_name.restype = c_char_p jacklib.jack_port_flags.argtypes = [POINTER(jack_port_t)] jacklib.jack_port_flags.restype = c_int jacklib.jack_port_type.argtypes = [POINTER(jack_port_t)] jacklib.jack_port_type.restype = c_char_p if JACK2: jacklib.jack_port_type_id.argtypes = [POINTER(jack_port_t)] jacklib.jack_port_type_id.restype = jack_port_type_id_t jacklib.jack_port_is_mine.argtypes = [POINTER(jack_client_t), POINTER(jack_port_t)] jacklib.jack_port_is_mine.restype = c_int jacklib.jack_port_connected.argtypes = [POINTER(jack_port_t)] jacklib.jack_port_connected.restype = c_int jacklib.jack_port_connected_to.argtypes = [POINTER(jack_port_t), c_char_p] jacklib.jack_port_connected_to.restype = c_int jacklib.jack_port_get_connections.argtypes = [POINTER(jack_port_t)] jacklib.jack_port_get_connections.restype = POINTER(c_char_p) jacklib.jack_port_get_all_connections.argtypes = [POINTER(jack_client_t), POINTER(jack_port_t)] jacklib.jack_port_get_all_connections.restype = POINTER(c_char_p) jacklib.jack_port_tie.argtypes = [POINTER(jack_port_t), POINTER(jack_port_t)] jacklib.jack_port_tie.restype = c_int jacklib.jack_port_untie.argtypes = [POINTER(jack_port_t)] jacklib.jack_port_untie.restype = c_int jacklib.jack_port_set_name.argtypes = [POINTER(jack_port_t), c_char_p] jacklib.jack_port_set_name.restype = c_int jacklib.jack_port_set_alias.argtypes = [POINTER(jack_port_t), c_char_p] jacklib.jack_port_set_alias.restype = c_int jacklib.jack_port_unset_alias.argtypes = [POINTER(jack_port_t), c_char_p] jacklib.jack_port_unset_alias.restype = c_int jacklib.jack_port_get_aliases.argtypes = [POINTER(jack_port_t), POINTER(ARRAY(c_char_p, 2))] jacklib.jack_port_get_aliases.restype = c_int jacklib.jack_port_request_monitor.argtypes = [POINTER(jack_port_t), c_int] jacklib.jack_port_request_monitor.restype = c_int jacklib.jack_port_request_monitor_by_name.argtypes = [POINTER(jack_client_t), c_char_p, c_int] jacklib.jack_port_request_monitor_by_name.restype = c_int jacklib.jack_port_ensure_monitor.argtypes = [POINTER(jack_port_t), c_int] jacklib.jack_port_ensure_monitor.restype = c_int jacklib.jack_port_monitoring_input.argtypes = [POINTER(jack_port_t)] jacklib.jack_port_monitoring_input.restype = c_int jacklib.jack_connect.argtypes = [POINTER(jack_client_t), c_char_p, c_char_p] jacklib.jack_connect.restype = c_int jacklib.jack_disconnect.argtypes = [POINTER(jack_client_t), c_char_p, c_char_p] jacklib.jack_disconnect.restype = c_int jacklib.jack_port_disconnect.argtypes = [POINTER(jack_client_t), POINTER(jack_port_t)] jacklib.jack_port_disconnect.restype = c_int jacklib.jack_port_name_size.argtypes = None jacklib.jack_port_name_size.restype = c_int jacklib.jack_port_type_size.argtypes = None jacklib.jack_port_type_size.restype = c_int try: jacklib.jack_port_type_get_buffer_size.argtypes = [POINTER(jack_client_t), c_char_p] jacklib.jack_port_type_get_buffer_size.restype = c_size_t except: jacklib.jack_port_type_get_buffer_size = None def port_register(client, port_name, port_type, flags, buffer_size): return jacklib.jack_port_register(client, port_name.encode("utf-8"), port_type.encode("utf-8"), flags, buffer_size) def port_unregister(client, port): return jacklib.jack_port_unregister(client, port) def port_get_buffer(port, nframes): return jacklib.jack_port_get_buffer(port, nframes) def port_name(port): return jacklib.jack_port_name(port) def port_short_name(port): return jacklib.jack_port_short_name(port) def port_flags(port): return jacklib.jack_port_flags(port) def port_type(port): return jacklib.jack_port_type(port) def port_type_id(port): # JACK2 only return jacklib.jack_port_type_id(port) def port_is_mine(client, port): return jacklib.jack_port_is_mine(client, port) def port_connected(port): return jacklib.jack_port_connected(port) def port_connected_to(port, port_name): return jacklib.jack_port_connected_to(port, port_name.encode("utf-8")) def port_get_connections(port): return jacklib.jack_port_get_connections(port) def port_get_all_connections(client, port): return jacklib.jack_port_get_all_connections(client, port) def port_tie(src, dst): return jacklib.jack_port_tie(src, dst) def port_untie(port): return jacklib.jack_port_untie(port) def port_set_name(port, port_name): return jacklib.jack_port_set_name(port, port_name.encode("utf-8")) def port_set_alias(port, alias): return jacklib.jack_port_set_alias(port, alias.encode("utf-8")) def port_unset_alias(port, alias): return jacklib.jack_port_unset_alias(port, alias.encode("utf-8")) def port_get_aliases(port): # NOTE - this function has no 2nd argument in jacklib # Instead, aliases will be passed in return value, in form of (int ret, str alias1, str alias2) name_size = port_name_size() alias_type = c_char_p * 2 aliases = alias_type(" ".encode("utf-8") * name_size, " ".encode("utf-8") * name_size) ret = jacklib.jack_port_get_aliases(port, pointer(aliases)) return (ret, str(aliases[0], encoding="utf-8"), str(aliases[1], encoding="utf-8")) def port_request_monitor(port, onoff): return jacklib.jack_port_request_monitor(port, onoff) def port_request_monitor_by_name(client, port_name, onoff): return jacklib.jack_port_request_monitor_by_name(client, port_name.encode("utf-8"), onoff) def port_ensure_monitor(port, onoff): return jacklib.jack_port_ensure_monitor(port, onoff) def port_monitoring_input(port): return jacklib.jack_port_monitoring_input(port) def connect(client, source_port, destination_port): return jacklib.jack_connect(client, source_port.encode("utf-8"), destination_port.encode("utf-8")) def disconnect(client, source_port, destination_port): return jacklib.jack_disconnect(client, source_port.encode("utf-8"), destination_port.encode("utf-8")) def port_disconnect(client, port): return jacklib.jack_port_disconnect(client, port) def port_name_size(): return jacklib.jack_port_name_size() def port_type_size(): return jacklib.jack_port_type_size() def port_type_get_buffer_size(client, port_type): if jacklib.jack_port_type_get_buffer_size: return jacklib.jack_port_type_get_buffer_size(client, port_type.encode("utf-8")) return 0 # ------------------------------------------------------------------------------------------------------------ # Latency Functions jacklib.jack_port_set_latency.argtypes = [POINTER(jack_port_t), jack_nframes_t] jacklib.jack_port_set_latency.restype = None try: jacklib.jack_port_get_latency_range.argtypes = [POINTER(jack_port_t), jack_latency_callback_mode_t, POINTER(jack_latency_range_t)] jacklib.jack_port_get_latency_range.restype = None except: jacklib.jack_port_get_latency_range = None try: jacklib.jack_port_set_latency_range.argtypes = [POINTER(jack_port_t), jack_latency_callback_mode_t, POINTER(jack_latency_range_t)] jacklib.jack_port_set_latency_range.restype = None except: jacklib.jack_port_set_latency_range = None jacklib.jack_recompute_total_latencies.argtypes = [POINTER(jack_client_t)] jacklib.jack_recompute_total_latencies.restype = c_int jacklib.jack_port_get_latency.argtypes = [POINTER(jack_port_t)] jacklib.jack_port_get_latency.restype = jack_nframes_t jacklib.jack_port_get_total_latency.argtypes = [POINTER(jack_client_t), POINTER(jack_port_t)] jacklib.jack_port_get_total_latency.restype = jack_nframes_t jacklib.jack_recompute_total_latency.argtypes = [POINTER(jack_client_t), POINTER(jack_port_t)] jacklib.jack_recompute_total_latency.restype = c_int def port_set_latency(port, nframes): jacklib.jack_port_set_latency(port, nframes) def port_get_latency_range(port, mode, range_): if jacklib.jack_port_get_latency_range: jacklib.jack_port_get_latency_range(port, mode, range_) def port_set_latency_range(port, mode, range_): if jacklib.jack_port_set_latency_range: jacklib.jack_port_set_latency_range(port, mode, range_) def recompute_total_latencies(): return jacklib.jack_recompute_total_latencies() def port_get_latency(port): return jacklib.jack_port_get_latency(port) def port_get_total_latency(client, port): return jacklib.jack_port_get_total_latency(client, port) def recompute_total_latency(client, port): return jacklib.jack_recompute_total_latency(client, port) # ------------------------------------------------------------------------------------------------------------ # Port Searching jacklib.jack_get_ports.argtypes = [POINTER(jack_client_t), c_char_p, c_char_p, c_ulong] jacklib.jack_get_ports.restype = POINTER(c_char_p) jacklib.jack_port_by_name.argtypes = [POINTER(jack_client_t), c_char_p] jacklib.jack_port_by_name.restype = POINTER(jack_port_t) jacklib.jack_port_by_id.argtypes = [POINTER(jack_client_t), jack_port_id_t] jacklib.jack_port_by_id.restype = POINTER(jack_port_t) def get_ports(client, port_name_pattern, type_name_pattern, flags): return jacklib.jack_get_ports(client, port_name_pattern.encode("utf-8"), type_name_pattern.encode("utf-8"), flags) def port_by_name(client, port_name): return jacklib.jack_port_by_name(client, port_name.encode("utf-8")) def port_by_id(client, port_id): return jacklib.jack_port_by_id(client, port_id) # ------------------------------------------------------------------------------------------------------------ # Time Functions jacklib.jack_frames_since_cycle_start.argtypes = [POINTER(jack_client_t)] jacklib.jack_frames_since_cycle_start.restype = jack_nframes_t jacklib.jack_frame_time.argtypes = [POINTER(jack_client_t)] jacklib.jack_frame_time.restype = jack_nframes_t jacklib.jack_last_frame_time.argtypes = [POINTER(jack_client_t)] jacklib.jack_last_frame_time.restype = jack_nframes_t try: # JACK_OPTIONAL_WEAK_EXPORT jacklib.jack_get_cycle_times.argtypes = [POINTER(jack_client_t), POINTER(jack_nframes_t), POINTER(jack_time_t), POINTER(jack_time_t), POINTER(c_float)] jacklib.jack_get_cycle_times.restype = c_int except: jacklib.jack_get_cycle_times = None jacklib.jack_frames_to_time.argtypes = [POINTER(jack_client_t), jack_nframes_t] jacklib.jack_frames_to_time.restype = jack_time_t jacklib.jack_time_to_frames.argtypes = [POINTER(jack_client_t), jack_time_t] jacklib.jack_time_to_frames.restype = jack_nframes_t jacklib.jack_get_time.argtypes = None jacklib.jack_get_time.restype = jack_time_t def frames_since_cycle_start(client): return jacklib.jack_frames_since_cycle_start(client) def frame_time(client): return jacklib.jack_frame_time(client) def last_frame_time(client): return jacklib.jack_last_frame_time(client) def get_cycle_times(client, current_frames, current_usecs, next_usecs, period_usecs): # JACK_OPTIONAL_WEAK_EXPORT if jacklib.jack_frames_to_time: return jacklib.jack_get_cycle_times(client, current_frames, current_usecs, next_usecs, period_usecs) return -1 def frames_to_time(client, nframes): return jacklib.jack_frames_to_time(client, nframes) def time_to_frames(client, time): return jacklib.jack_time_to_frames(client, time) def get_time(): return jacklib.jack_get_time() # ------------------------------------------------------------------------------------------------------------ # Error Output # TODO # ------------------------------------------------------------------------------------------------------------ # Misc jacklib.jack_free.argtypes = [c_void_p] jacklib.jack_free.restype = None def free(ptr): return jacklib.jack_free(ptr) # ------------------------------------------------------------------------------------------------------------ # Transport global _sync_callback global _timebase_callback _sync_callback = _timebase_callback = None jacklib.jack_release_timebase.argtypes = [POINTER(jack_client_t)] jacklib.jack_release_timebase.restype = c_int jacklib.jack_set_sync_callback.argtypes = [POINTER(jack_client_t), JackSyncCallback, c_void_p] jacklib.jack_set_sync_callback.restype = c_int jacklib.jack_set_sync_timeout.argtypes = [POINTER(jack_client_t), jack_time_t] jacklib.jack_set_sync_timeout.restype = c_int jacklib.jack_set_timebase_callback.argtypes = [POINTER(jack_client_t), c_int, JackTimebaseCallback, c_void_p] jacklib.jack_set_timebase_callback.restype = c_int jacklib.jack_transport_locate.argtypes = [POINTER(jack_client_t), jack_nframes_t] jacklib.jack_transport_locate.restype = c_int jacklib.jack_transport_query.argtypes = [POINTER(jack_client_t), POINTER(jack_position_t)] jacklib.jack_transport_query.restype = jack_transport_state_t jacklib.jack_get_current_transport_frame.argtypes = [POINTER(jack_client_t)] jacklib.jack_get_current_transport_frame.restype = jack_nframes_t jacklib.jack_transport_reposition.argtypes = [POINTER(jack_client_t), POINTER(jack_position_t)] jacklib.jack_transport_reposition.restype = c_int jacklib.jack_transport_start.argtypes = [POINTER(jack_client_t)] jacklib.jack_transport_start.restype = None jacklib.jack_transport_stop.argtypes = [POINTER(jack_client_t)] jacklib.jack_transport_stop.restype = None def release_timebase(client): return jacklib.jack_release_timebase(client) def set_sync_callback(client, sync_callback, arg): global _sync_callback _sync_callback = JackSyncCallback(sync_callback) return jacklib.jack_set_sync_callback(client, _sync_callback, arg) def set_sync_timeout(client, timeout): return jacklib.jack_set_sync_timeout(client, timeout) def set_timebase_callback(client, conditional, timebase_callback, arg): global _timebase_callback _timebase_callback = JackTimebaseCallback(timebase_callback) return jacklib.jack_set_timebase_callback(client, conditional, _timebase_callback, arg) def transport_locate(client, frame): return jacklib.jack_transport_locate(client, frame) def transport_query(client, pos): return jacklib.jack_transport_query(client, pos) def get_current_transport_frame(client): return jacklib.jack_get_current_transport_frame(client) def transport_reposition(client, pos): return jacklib.jack_transport_reposition(client, pos) def transport_start(client): return jacklib.jack_transport_start(client) def transport_stop(client): return jacklib.jack_transport_stop(client) # ------------------------------------------------------------------------------------------------------------ # MIDI jacklib.jack_midi_get_event_count.argtypes = [c_void_p] jacklib.jack_midi_get_event_count.restype = jack_nframes_t jacklib.jack_midi_event_get.argtypes = [POINTER(jack_midi_event_t), c_void_p, c_uint32] jacklib.jack_midi_event_get.restype = c_int jacklib.jack_midi_clear_buffer.argtypes = [c_void_p] jacklib.jack_midi_clear_buffer.restype = None jacklib.jack_midi_max_event_size.argtypes = [c_void_p] jacklib.jack_midi_max_event_size.restype = c_size_t jacklib.jack_midi_event_reserve.argtypes = [c_void_p, jack_nframes_t, c_size_t] jacklib.jack_midi_event_reserve.restype = POINTER(jack_midi_data_t) jacklib.jack_midi_event_write.argtypes = [c_void_p, jack_nframes_t, POINTER(jack_midi_data_t), c_size_t] jacklib.jack_midi_event_write.restype = c_int jacklib.jack_midi_get_lost_event_count.argtypes = [c_void_p] jacklib.jack_midi_get_lost_event_count.restype = c_uint32 def midi_get_event_count(port_buffer): return jacklib.jack_midi_get_event_count(port_buffer) def midi_event_get(event, port_buffer, event_index): return jacklib.jack_midi_event_get(event, port_buffer, event_index) def midi_clear_buffer(port_buffer): return jacklib.jack_midi_clear_buffer(port_buffer) def midi_max_event_size(port_buffer): return jacklib.jack_midi_max_event_size(port_buffer) def midi_event_reserve(port_buffer, time, data_size): return jacklib.jack_midi_event_reserve(port_buffer, time, data_size) def midi_event_write(port_buffer, time, data, data_size): return jacklib.jack_midi_event_write(port_buffer, time, data, data_size) def midi_get_lost_event_count(port_buffer): return jacklib.jack_midi_get_lost_event_count(port_buffer) # ------------------------------------------------------------------------------------------------------------ # Session global _session_callback _session_callback = None try: jacklib.jack_set_session_callback.argtypes = [POINTER(jack_client_t), JackSessionCallback, c_void_p] jacklib.jack_set_session_callback.restype = c_int except: jacklib.jack_set_session_callback = None try: jacklib.jack_session_reply.argtypes = [POINTER(jack_client_t), POINTER(jack_session_event_t)] jacklib.jack_session_reply.restype = c_int except: jacklib.jack_session_reply = None try: jacklib.jack_session_event_free.argtypes = [POINTER(jack_session_event_t)] jacklib.jack_session_event_free.restype = None except: jacklib.jack_session_event_free = None try: jacklib.jack_client_get_uuid.argtypes = [POINTER(jack_client_t)] jacklib.jack_client_get_uuid.restype = c_char_p except: jacklib.jack_client_get_uuid = None try: jacklib.jack_session_notify.argtypes = [POINTER(jack_client_t), c_char_p, jack_session_event_type_t, c_char_p] jacklib.jack_session_notify.restype = POINTER(jack_session_command_t) except: jacklib.jack_session_notify = None try: jacklib.jack_session_commands_free.argtypes = [POINTER(jack_session_command_t)] jacklib.jack_session_commands_free.restype = None except: jacklib.jack_session_commands_free = None try: jacklib.jack_get_uuid_for_client_name.argtypes = [POINTER(jack_client_t), c_char_p] jacklib.jack_get_uuid_for_client_name.restype = c_char_p except: jacklib.jack_get_uuid_for_client_name = None try: jacklib.jack_get_client_name_by_uuid.argtypes = [POINTER(jack_client_t), c_char_p] jacklib.jack_get_client_name_by_uuid.restype = c_char_p except: jacklib.jack_get_client_name_by_uuid = None try: jacklib.jack_reserve_client_name.argtypes = [POINTER(jack_client_t), c_char_p, c_char_p] jacklib.jack_reserve_client_name.restype = c_int except: jacklib.jack_reserve_client_name = None try: jacklib.jack_client_has_session_callback.argtypes = [POINTER(jack_client_t), c_char_p] jacklib.jack_client_has_session_callback.restype = c_int except: jacklib.jack_client_has_session_callback = None def set_session_callback(client, session_callback, arg): if jacklib.jack_set_session_callback: global _session_callback _session_callback = JackSessionCallback(session_callback) return jacklib.jack_set_session_callback(client, _session_callback, arg) return -1 def session_reply(client, event): if jacklib.jack_session_reply: return jacklib.jack_session_reply(client, event) return -1 def session_event_free(event): if jacklib.jack_session_event_free: jacklib.jack_session_event_free(event) def client_get_uuid(client): if jacklib.jack_client_get_uuid: return jacklib.jack_client_get_uuid(client) return None def session_notify(client, target, type_, path): if jacklib.jack_session_notify: return jacklib.jack_session_notify(client, target.encode("utf-8"), type_, path.encode("utf-8")) return jack_session_command_t() def session_commands_free(cmds): if jacklib.jack_session_commands_free: jacklib.jack_session_commands_free(cmds) def get_uuid_for_client_name(client, client_name): if jacklib.jack_get_uuid_for_client_name: return jacklib.jack_get_uuid_for_client_name(client, client_name.encode("utf-8")) return None def get_client_name_by_uuid(client, client_uuid): if jacklib.jack_get_client_name_by_uuid: return jacklib.jack_get_client_name_by_uuid(client, client_uuid.encode("utf-8")) return None def reserve_client_name(client, name, uuid): if jacklib.jack_reserve_client_name: return jacklib.jack_reserve_client_name(client, name.encode("utf-8"), uuid.encode("utf-8")) return -1 def client_has_session_callback(client, client_name): if jacklib.jack_client_has_session_callback: return jacklib.jack_client_has_session_callback(client, client_name.encode("utf-8")) return -1 # ------------------------------------------------------------------------------------------------------------ # Custom global _custom_appearance_callback _custom_appearance_callback = None try: jacklib.jack_custom_publish_data.argtypes = [POINTER(jack_client_t), c_char_p, c_void_p, c_size_t] jacklib.jack_custom_publish_data.restype = c_int except: jacklib.jack_custom_publish_data = None try: jacklib.jack_custom_get_data.argtypes = [POINTER(jack_client_t), c_char_p, c_char_p, POINTER(c_void_p), POINTER(c_size_t)] jacklib.jack_custom_get_data.restype = c_int except: jacklib.jack_custom_get_data = None try: jacklib.jack_custom_unpublish_data.argtypes = [POINTER(jack_client_t), c_char_p] jacklib.jack_custom_unpublish_data.restype = c_int except: jacklib.jack_custom_unpublish_data = None try: jacklib.jack_custom_get_keys.argtypes = [POINTER(jack_client_t), c_char_p] jacklib.jack_custom_get_keys.restype = POINTER(c_char_p) except: jacklib.jack_custom_get_keys = None try: jacklib.jack_custom_set_data_appearance_callback.argtypes = [POINTER(jack_client_t), JackCustomDataAppearanceCallback, c_void_p] jacklib.jack_custom_set_data_appearance_callback.restype = c_int except: jacklib.jack_custom_set_data_appearance_callback = None def custom_publish_data(client, key, data, size): if jacklib.jack_custom_publish_data: return jacklib.jack_custom_publish_data(client, key.encode("utf-8"), data, size) return -1 def custom_get_data(client, client_name, key): # NOTE - this function has no extra arguments in jacklib # Instead, data and size will be passed in return value, in form of (int ret, void* data, size_t size) if jacklib.jack_custom_get_data: data = c_void_p(0) size = c_size_t(0) ret = jacklib.jack_custom_get_data(client, client_name.encode("utf-8"), key.encode("utf-8"), pointer(data), pointer(size)) return (ret, data, size) return (-1, None, 0) def custom_unpublish_data(client, key): if jacklib.jack_custom_unpublish_data: return jacklib.jack_custom_unpublish_data(client, key.encode("utf-8")) return -1 def custom_get_keys(client, client_name): if jacklib.jack_custom_get_keys: return jacklib.jack_custom_get_keys(client, client_name.encode("utf-8")) return None def custom_set_data_appearance_callback(client, custom_callback, arg): if jacklib.jack_custom_set_data_appearance_callback: global _custom_appearance_callback _custom_appearance_callback = JackCustomDataAppearanceCallback(custom_callback) return jacklib.jack_custom_set_data_appearance_callback(client, _custom_appearance_callback, arg) return -1 RaySession-0.8.3/src/shared/nsm_client.py000066400000000000000000000067001356671433200204000ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os import sys from PyQt5.QtCore import QObject, pyqtSignal from liblo import ServerThread, make_method class NSMSignaler(QObject): server_sends_open = pyqtSignal(str, str, str) server_sends_save = pyqtSignal() session_is_loaded = pyqtSignal() show_optional_gui = pyqtSignal() hide_optional_gui = pyqtSignal() instance = None class NSMThread(ServerThread): def __init__(self, name, signaler, daemon_address, debug): ServerThread.__init__(self) self.name = name self.signaler = signaler self.daemon_address = daemon_address self.debug = debug self.server_capabilities = "" global instance instance = self @staticmethod def instance(): return instance @make_method('/reply', None) def serverReply(self, path, args): if args: reply_path = args[0] else: return if reply_path == '/nsm/server/announce': self.server_capabilities = args[3] @make_method('/nsm/client/open', 'sss') def nsmClientOpen(self, path, args): self.ifDebug( 'serverOSC::%s_receives %s, %s' % (self.name, path, str(args))) self.signaler.server_sends_open.emit(*args) @make_method('/nsm/client/save', '') def nsmClientSave(self, path, args): self.ifDebug( 'serverOSC::%s_receives %s, %s' % (self.name, path, str(args))) self.signaler.server_sends_save.emit() @make_method('/nsm/client/session_is_loaded', '') def nsmClientSessionIsLoaded(self, path, args): self.ifDebug( 'serverOSC::%s_receives %s, %s' % (self.name, path, str(args))) self.signaler.session_is_loaded.emit() @make_method('/nsm/client/show_optional_gui', '') def nsmClientShow_optional_gui(self, path, args): self.ifDebug( 'serverOSC::%s_receives %s, %s' % (self.name, path, str(args))) self.signaler.show_optional_gui.emit() @make_method('/nsm/client/hide_optional_gui', '') def nsmClientHide_optional_gui(self, path, args): self.ifDebug( 'serverOSC::%s_receives %s, %s' % (self.name, path, str(args))) self.signaler.hide_optional_gui.emit() def getServerCapabilities(self): return self.server_capabilities def ifDebug(self, string): if self.debug: sys.stderr.write("%s\n" % string) def sendToDaemon(self, *args): self.send(self.daemon_address, *args) def announce(self, client_name, capabilities, executable_path): major = 1 minor = 0 pid = os.getpid() self.sendToDaemon( '/nsm/server/announce', client_name, capabilities, executable_path, major, minor, pid) def openReply(self): self.sendToDaemon('/reply', '/nsm/client/open', 'Ready') def saveReply(self): self.sendToDaemon('/reply', '/nsm/client/save', 'Saved') def sendDirtyState(self, bool_dirty): if bool_dirty: self.sendToDaemon('/nsm/client/is_dirty') else: self.sendToDaemon('/nsm/client/is_clean') def sendGuiState(self, state): if state: self.sendToDaemon('/nsm/client/gui_is_shown') else: self.sendToDaemon('/nsm/client/gui_is_hidden') RaySession-0.8.3/src/shared/print_osc_messages.py000077500000000000000000000010551356671433200221350ustar00rootroot00000000000000#!/usr/bin/python3 import sys file = open(sys.argv[1], 'r') contents = file.read() all_lines = contents.split('\n') output = "" for line in all_lines: while line.startswith(' '): line = line.replace(' ', '') if line.startswith('@ray_method('): raym, par, qmessargs = line.partition('(') qmess, qargs = qmessargs.split(',') mess = qmess[1:-1] if qargs[1:-1] == 'None': args = 'strings' else: args = qargs[2:-2] output += "%s %s\n" % (mess, args) print(output) RaySession-0.8.3/src/shared/ray.py000066400000000000000000000274321356671433200170450ustar00rootroot00000000000000 import argparse import liblo import os import shlex import socket import subprocess import sys from liblo import Server, Address from PyQt5.QtCore import QLocale, QTranslator, QT_VERSION_STR, QFile from PyQt5.QtGui import QIcon, QPalette # get qt version in list of ints QT_VERSION = [] for strdigit in QT_VERSION_STR.split('.'): QT_VERSION.append(int(strdigit)) QT_VERSION = tuple(QT_VERSION) if QT_VERSION < (5, 6): sys.stderr.write( "WARNING: You are using a version of QT older than 5.6.\n" + "You won't be warned if a process can't be launch.\n") VERSION = "0.8.3" APP_TITLE = 'RaySession' class PrefixMode: CUSTOM = 0 CLIENT_NAME = 1 SESSION_NAME = 2 class ClientStatus: STOPPED = 0 LAUNCH = 1 OPEN = 2 READY = 3 PRECOPY = 4 COPY = 5 SAVE = 6 SWITCH = 7 QUIT = 8 NOOP = 9 ERROR = 10 REMOVED = 11 UNDEF = 12 class ServerStatus: OFF = 0 NEW = 1 OPEN = 2 CLEAR = 3 SWITCH = 4 LAUNCH = 5 PRECOPY = 6 COPY = 7 READY = 8 SAVE = 9 CLOSE = 10 SNAPSHOT = 11 REWIND = 12 WAIT_USER = 13 OUT_SAVE = 14 OUT_SNAPSHOT = 15 class NSMMode: NO_NSM = 0 CHILD = 1 NETWORK = 2 class Option: NSM_LOCKED = 0x001 SAVE_FROM_CLIENT = 0x002 BOOKMARK_SESSION = 0x004 HAS_WMCTRL = 0x008 DESKTOPS_MEMORY = 0x010 HAS_GIT = 0x020 SNAPSHOTS = 0x040 class Err: OK = 0 GENERAL_ERROR = -1 INCOMPATIBLE_API = -2 BLACKLISTED = -3 LAUNCH_FAILED = -4 NO_SUCH_FILE = -5 NO_SESSION_OPEN = -6 UNSAVED_CHANGES = -7 NOT_NOW = -8 BAD_PROJECT = -9 CREATE_FAILED = -10 SESSION_LOCKED = -11 OPERATION_PENDING = -12 COPY_RUNNING = -13 NET_ROOT_RUNNING = -14 SUBPROCESS_UNTERMINATED = -15 SUBPROCESS_CRASH = -16 SUBPROCESS_EXITCODE = -17 class Command: NONE = 0 QUIT = 1 KILL = 2 SAVE = 3 OPEN = 4 START = 5 CLOSE = 6 DUPLICATE = 7 NEW = 8 class WaitFor: NONE = 0 STOP = 1 STOP_ONE = 2 ANNOUNCE = 3 REPLY = 4 DUPLICATE_START = 5 DUPLICATE_FINISH = 6 class Template: NONE = 0 RENAME = 1 SESSION_SAVE = 2 SESSION_SAVE_NET = 3 SESSION_LOAD = 4 SESSION_LOAD_NET = 5 CLIENT_SAVE = 6 CLIENT_LOAD = 7 class Favorite(): def __init__(self, name, icon, factory): self.name = name self.icon = icon self.factory = factory debug = False def ifDebug(string): if debug: print(string, file=sys.stderr) def setDebug(bool): global debug debug = bool def versionToTuple(version_str): version_list = [] for c in version_str.split('.'): if not c.isdigit(): return () version_list.append(int(c)) return tuple(version_list) def addSelfBinToPath(): # Add raysession/src/bin to $PATH to can use ray executables after make # Warning, will works only if link to this file is in RaySession/*/*/*.py this_path = os.path.realpath(os.path.dirname(os.path.realpath(__file__))) bin_path = "%s/bin" % os.path.dirname(this_path) if not os.environ['PATH'].startswith("%s:" % bin_path): os.environ['PATH'] = "%s:%s" % (bin_path, os.environ['PATH']) def getListInSettings(settings, path): # getting a QSettings value of list type seems to not works the same way # on all machines try: settings_list = settings.value(path, [], type=list) except BaseException: try: settings_list = settings.value(path, []) except BaseException: settings_list = [] return settings_list def getGitIgnoredExtensions(): return ".wav .flac .ogg .mp3 .mp4 .avi .mkv .peak .m4a .pdf" def isPidChildOf(child_pid, parent_pid): if child_pid < parent_pid: return False ppid = child_pid while ppid > parent_pid: try: proc_file = open('/proc/%i/status' % ppid, 'r') proc_contents = proc_file.read() except BaseException: return False for line in proc_contents.split('\n'): if line.startswith('PPid:'): ppid_str = line.rpartition('\t')[2] if ppid_str.isdigit(): ppid = int(ppid_str) break else: return False #while ppid != parent_pid and ppid > 1 and ppid != this_pid: #try: #ppid = int(subprocess.check_output( #['ps', '-o', 'ppid=', '-p', str(ppid)])) #except BaseException: #return False if ppid == parent_pid: return True return False def isGitTaggable(string): if not string: return False if string.startswith('/'): return False if string.endswith('/'): return False if string.endswith('.'): return False for forbidden in (' ', '~', '^', ':', '?', '*', '[', '..', '@{', '\\', '//', ','): if forbidden in string: return False if string == "@": return False return True def isOscPortFree(port): try: testport = Server(port) except BaseException: return False del testport return True def getFreeOscPort(default=16187): # get a free OSC port for daemon, start from default if default >= 65536: default = 16187 daemon_port = default UsedPort = True testport = None while UsedPort: try: testport = Server(daemon_port) UsedPort = False except BaseException: daemon_port += 1 UsedPort = True del testport return daemon_port def isValidOscUrl(url): try: address = liblo.Address(url) return True except BaseException: return False def getLibloAddress(url): valid_url = False try: address = liblo.Address(url) valid_url = True except BaseException: valid_url = False msg = "%r is not a valid osc url" % url raise argparse.ArgumentTypeError(msg) if valid_url: try: liblo.send(address, '/ping') return address except BaseException: msg = "%r is an unknown osc url" % url raise argparse.ArgumentTypeError(msg) def areSameOscPort(url1, url2): if url1 == url2: return True try: address1 = Address(url1) address2 = Address(url2) except BaseException: return False if address1.port != address2.port: return False if areOnSameMachine(url1, url2): return True return False def areOnSameMachine(url1, url2): if url1 == url2: return True try: address1 = Address(url1) address2 = Address(url2) except BaseException: return False if address1.hostname == address2.hostname: return True try: if ((socket.gethostbyname(address1.hostname) in ('127.0.0.1', '127.0.1.1')) and ( socket.gethostbyname(address2.hostname) in ('127.0.0.1', '127.0.1.1'))): return True if socket.gethostbyaddr( address1.hostname) == socket.gethostbyaddr( address2.hostname): return True except BaseException: try: ips = subprocess.check_output(['hostname', '-I']).decode() ip = ips.split(' ')[0] if ip.count('.') != 3: return False if ip not in (address1.hostname, address2.hostname): return False try: if socket.gethostbyname( address1.hostname) in ( '127.0.0.1', '127.0.1.1'): if address2.hostname == ip: return True except BaseException: if socket.gethostbyname( address2.hostname) in ( '127.0.0.1', '127.0.1.1'): if address1.hostname == ip: return True except BaseException: return False return False return False def getUrl192(url): try: ips = subprocess.check_output(['hostname', '-I']).decode() ip = ips.split(' ')[0] except BaseException: return url if ip.count('.') != 3: return url suffix_port = url.rpartition(':')[2] return "osc.udp://%s:%s" % (ip, suffix_port) def getThis192(): global machine192 if 'machine192' in globals(): return machine192 try: ips = subprocess.check_output(['hostname', '-I']).decode() ip = ips.split(' ')[0] machine192 = ip return ip except BaseException: return '' def getMachine192(hostname=None): if hostname is None: return getThis192() else: if hostname in ('localhost', socket.gethostname()): return getThis192() return socket.gethostbyname(hostname) def getMachine192ByUrl(url): try: addr = Address(url) except BaseException: return '' hostname = addr.hostname del addr return getMachine192(hostname) def getNetUrl(port): try: ips = subprocess.check_output(['hostname', '-I']).decode() ip = ips.split(' ')[0] except BaseException: return '' if ip.count('.') != 3: return '' return "osc.udp://%s:%i/" % (ip, port) def shellLineToArgs(string): try: args = shlex.split(string) except BaseException: return None return args def areTheyAllString(args): for arg in args: if type(arg) != str: return False return True def getAppIcon(icon_name, widget): dark = bool( widget.palette().brush( 2, QPalette.WindowText).color().lightness() > 128) icon = QIcon.fromTheme(icon_name) if icon.isNull(): for ext in ('svg', 'svgz', 'png'): filename = ":app_icons/%s.%s" % (icon_name, ext) darkname = ":app_icons/dark/%s.%s" % (icon_name, ext) if dark and QFile.exists(darkname): filename = darkname if QFile.exists(filename): del icon icon = QIcon() icon.addFile(filename) break return icon class ClientData: client_id = '' executable_path = '' arguments = '' name = '' prefix_mode = 2 custom_prefix = '' label = '' icon = '' capabilities = '' check_last_save = True ignored_extensions = getGitIgnoredExtensions() def __init__(self, client_id, executable, arguments="", name='', prefix_mode=PrefixMode.SESSION_NAME, custom_prefix='', label='', icon='', capabilities='', check_last_save=True, ignored_extensions=getGitIgnoredExtensions()): self.client_id = str(client_id) self.executable_path = str(executable) self.arguments = str(arguments) self.prefix_mode = int(prefix_mode) self.label = str(label) self.capabilities = str(capabilities) self.check_last_save = bool(check_last_save) self.ignored_extensions = str(ignored_extensions) self.name = str(name) if name else os.path.basename( self.executable_path) self.icon = str(icon) if icon else self.name.lower().replace('_', '-') if self.prefix_mode == PrefixMode.CUSTOM: if custom_prefix: self.custom_prefix = str(custom_prefix) else: self.prefix_mode = 2 RaySession-0.8.3/src/shared/shared.py000066400000000000000000000271131356671433200175140ustar00rootroot00000000000000from liblo import Server, Address import argparse import liblo import socket import sys import os import shlex import subprocess from PyQt5.QtCore import QLocale, QTranslator, QT_VERSION_STR, QFile from PyQt5.QtGui import QIcon, QPalette # get qt version in list of ints QT_VERSION = [] for strdigit in QT_VERSION_STR.split('.'): QT_VERSION.append(int(strdigit)) QT_VERSION = tuple(QT_VERSION) if QT_VERSION < (5, 6): sys.stderr.write( "WARNING: You are using a version of QT older than 5.6.\n" + "You won't be able to know if a process can't be launch.\n") # RaySession version VERSION = "0.6.1" APP_TITLE = 'RaySession' PREFIX_MODE_CUSTOM = 0 PREFIX_MODE_CLIENT_NAME = 1 PREFIX_MODE_SESSION_NAME = 2 CLIENT_STATUS_STOPPED = 0 CLIENT_STATUS_LAUNCH = 1 CLIENT_STATUS_OPEN = 2 CLIENT_STATUS_READY = 3 CLIENT_STATUS_PRECOPY = 4 CLIENT_STATUS_COPY = 5 CLIENT_STATUS_SAVE = 6 CLIENT_STATUS_SWITCH = 7 CLIENT_STATUS_QUIT = 8 CLIENT_STATUS_NOOP = 9 CLIENT_STATUS_ERROR = 10 CLIENT_STATUS_REMOVED = 11 SERVER_STATUS_OFF = 0 SERVER_STATUS_NEW = 1 SERVER_STATUS_OPEN = 2 SERVER_STATUS_CLEAR = 3 SERVER_STATUS_SWITCH = 4 SERVER_STATUS_LAUNCH = 5 SERVER_STATUS_PRECOPY = 6 SERVER_STATUS_COPY = 7 SERVER_STATUS_READY = 8 SERVER_STATUS_SAVE = 9 SERVER_STATUS_CLOSE = 10 NSM_MODE_NO_NSM = 0 NSM_MODE_CHILD = 1 NSM_MODE_NETWORK = 2 OPTION_NSM_LOCKED = 0x001 OPTION_SAVE_FROM_CLIENT = 0x002 OPTION_BOOKMARK_SESSION = 0x004 OPTION_HAS_WMCTRL = 0x008 OPTION_DESKTOPS_MEMORY = 0x010 debug = False def ifDebug(string): if debug: print(string, file=sys.stderr) def setDebug(bool): global debug debug = bool def getListInSettings(settings, path): # getting a QSettings value of list type seems to not works the same way # on all machines try: settings_list = settings.value(path, [], type=list) except BaseException: try: settings_list = settings.value(path, []) except BaseException: settings_list = [] return settings_list def isPidChildOf(child_pid, parent_pid): if child_pid < parent_pid: return False ppid = child_pid this_pid = os.getpid() while ppid != parent_pid and ppid > 1 and ppid != this_pid: try: ppid = int(subprocess.check_output( ['ps', '-o', 'ppid=', '-p', str(ppid)])) except BaseException: return False if ppid == parent_pid: return True return False def isOscPortFree(port): try: testport = Server(port) except BaseException: return False del testport return True def getFreeOscPort(default=16187): # get a free OSC port for daemon, start from default if default >= 65536: default = 16187 daemon_port = default UsedPort = True testport = None while UsedPort: try: testport = Server(daemon_port) UsedPort = False except BaseException: daemon_port += 1 UsedPort = True del testport return daemon_port def isValidOscUrl(url): try: address = liblo.Address(url) return True except BaseException: return False def getLibloAddress(url): valid_url = False try: address = liblo.Address(url) valid_url = True except BaseException: valid_url = False msg = "%r is not a valid osc url" % url raise argparse.ArgumentTypeError(msg) if valid_url: try: liblo.send(address, '/ping') return address except BaseException: msg = "%r is an unknown osc url" % url raise argparse.ArgumentTypeError(msg) def areSameOscPort(url1, url2): if url1 == url2: return True try: address1 = Address(url1) address2 = Address(url2) except BaseException: return False if address1.port != address2.port: return False if areOnSameMachine(url1, url2): return True return False def areOnSameMachine(url1, url2): if url1 == url2: return True try: address1 = Address(url1) address2 = Address(url2) except BaseException: return False if address1.hostname == address2.hostname: return True try: if ((socket.gethostbyname(address1.hostname) in ('127.0.0.1', '127.0.1.1')) and ( socket.gethostbyname(address2.hostname) in ('127.0.0.1', '127.0.1.1'))): return True if socket.gethostbyaddr( address1.hostname) == socket.gethostbyaddr( address2.hostname): return True except BaseException: try: ips = subprocess.check_output(['hostname', '-I']).decode() ip = ips.split(' ')[0] if ip.count('.') != 3: return False if ip not in (address1.hostname, address2.hostname): return False try: if socket.gethostbyname( address1.hostname) in ( '127.0.0.1', '127.0.1.1'): if address2.hostname == ip: return True except BaseException: if socket.gethostbyname( address2.hostname) in ( '127.0.0.1', '127.0.1.1'): if address1.hostname == ip: return True except BaseException: return False return False return False def getUrl192(url): try: ips = subprocess.check_output(['hostname', '-I']).decode() ip = ips.split(' ')[0] except BaseException: return url if ip.count('.') != 3: return url suffix_port = url.rpartition(':')[2] return "osc.udp://%s:%s" % (ip, suffix_port) def getThis192(): global machine192 if 'machine192' in globals(): return machine192 try: ips = subprocess.check_output(['hostname', '-I']).decode() ip = ips.split(' ')[0] machine192 = ip return ip except BaseException: return '' def getMachine192(hostname=None): if hostname is None: return getThis192() #global machine192 # if 'machine192' in globals(): # return machine192 # try: #ips = subprocess.check_output(['hostname', '-I']).decode() #ip = ips.split(' ')[0] #machine192 = ip # return ip # except: # return '' else: if hostname in ('localhost', socket.gethostname()): return getThis192() return socket.gethostbyname(hostname) def getMachine192ByUrl(url): try: addr = Address(url) except BaseException: return '' hostname = addr.hostname del addr return getMachine192(hostname) def getNetUrl(port): try: ips = subprocess.check_output(['hostname', '-I']).decode() ip = ips.split(' ')[0] except BaseException: return '' if ip.count('.') != 3: return '' return "osc.udp://%s:%i/" % (ip, port) def shellLineToArgs(string): try: args = shlex.split(string) except BaseException: return None return args def areTheyAllString(args): for arg in args: if type(arg) != str: return False return True def getAppIcon(icon_name, widget): dark = bool( widget.palette().brush( 2, QPalette.WindowText).color().lightness() > 128) icon = QIcon.fromTheme(icon_name) if icon.isNull(): for ext in ('svg', 'svgz', 'png'): filename = ":app_icons/%s.%s" % (icon_name, ext) darkname = ":app_icons/dark/%s.%s" % (icon_name, ext) if dark and QFile.exists(darkname): filename = darkname if QFile.exists(filename): del icon icon = QIcon() icon.addFile(filename) break return icon class ClientData(object): client_id = '' executable_path = '' arguments = '' name = '' prefix_mode = 2 project_path = '' label = '' icon = '' capabilities = '' check_last_save = True def __init__(self, client_id, executable, arguments="", name='', prefix_mode=PREFIX_MODE_SESSION_NAME, project_path='', label='', icon='', capabilities='', check_last_save=True): self.client_id = str(client_id) self.executable_path = str(executable) self.arguments = str(arguments) self.prefix_mode = int(prefix_mode) self.label = str(label) self.capabilities = str(capabilities) self.check_last_save = bool(check_last_save) self.name = str(name) if name else os.path.basename( self.executable_path) self.icon = str(icon) if icon else self.name.lower().replace('_', '-') if self.prefix_mode == 0: if self.project_path: self.project_path = str(project_path) else: self.prefix_mode = 2 def clientStatusString(status): if not 0 <= status < len(client_status_strings): return _translate('client status', "invalid") return client_status_strings[status] def serverStatusString(server_status): if not 0 <= server_status < len(server_status_strings): return _translate('server status', "invalid") return server_status_strings[server_status] def init_translation(_translate): global client_status_strings client_status_strings = {CLIENT_STATUS_STOPPED: _translate('client status', "stopped"), CLIENT_STATUS_LAUNCH: _translate('client status', "launch"), CLIENT_STATUS_OPEN: _translate('client status', "open"), CLIENT_STATUS_READY: _translate('client status', "ready"), CLIENT_STATUS_PRECOPY: _translate('client status', "copy"), CLIENT_STATUS_COPY: _translate('client status', "copy"), CLIENT_STATUS_SAVE: _translate('client status', "save"), CLIENT_STATUS_SWITCH: _translate('client status', "switch"), CLIENT_STATUS_QUIT: _translate('client status', "quit"), CLIENT_STATUS_NOOP: _translate('client status', "noop"), CLIENT_STATUS_ERROR: _translate('client status', "error"), CLIENT_STATUS_REMOVED: _translate('client status', "removed")} global server_status_strings server_status_strings = {SERVER_STATUS_OFF: _translate('server status', "off"), SERVER_STATUS_NEW: _translate('server status', "new"), SERVER_STATUS_OPEN: _translate('server status', "open"), SERVER_STATUS_CLEAR: _translate('server status', "clear"), SERVER_STATUS_SWITCH: _translate('server status', "switch"), SERVER_STATUS_LAUNCH: _translate('server status', "launch"), SERVER_STATUS_PRECOPY: _translate('server status', "copy"), SERVER_STATUS_COPY: _translate('server status', "copy"), SERVER_STATUS_READY: _translate('server status', "ready"), SERVER_STATUS_SAVE: _translate('server status', "save"), SERVER_STATUS_CLOSE: _translate('server status', "close")}