diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. 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
+them 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 prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. 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.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey 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;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If 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 convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU 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 that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ 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.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+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.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/README b/README
new file mode 100644
--- /dev/null
+++ b/README
@@ -0,0 +1,7 @@
+GUI utility for managing your virtual CD/DVD drives
+Compatible with CDEmu>=3.1.0
+Uses Qt5 https://www.qt.io/
+Uses PyQt5 https://www.riverbankcomputing.com/software/pyqt/intro
+Built-in icons by http://www.fatcow.com/free-icons
+This program is licensed with GNU GPL v3 (see ./LICENSE for details)
+This program is available on http://silverwing.one/pages/kcdemu
diff --git a/__main__.py b/__main__.py
new file mode 100644
--- /dev/null
+++ b/__main__.py
@@ -0,0 +1,50 @@
+from config.config import ConfigFile
+from gui.tray import TrayManager
+from gui.dropper import KCDEmuDropperWidget
+from cdemu import CDEmu
+from PyQt5 import QtWidgets, QtCore
+import sys, os, fcntl, signal
+from dbus.mainloop.pyqt5 import DBusQtMainLoop
+
+# noinspection PyUnresolvedReferences
+import pics
+
+DBusQtMainLoop(set_as_default=True)
+
+app = QtWidgets.QApplication(sys.argv)
+app.setQuitOnLastWindowClosed(False)
+
+
+def handle_signal_wakeup():
+ notifier.setEnabled(False)
+ read_fd = notifier.socket()
+ try:
+ os.read(read_fd, 1)
+ except OSError:
+ print("Failed to read wakeup fd.")
+ notifier.setEnabled(True)
+
+
+def destroy(a, b):
+ app.exit(128 + a)
+
+
+read_fd, write_fd = os.pipe()
+for fd in (read_fd, write_fd):
+ flags = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
+
+notifier = QtCore.QSocketNotifier(read_fd, QtCore.QSocketNotifier.Read)
+notifier.activated.connect(handle_signal_wakeup)
+orig_wakeup_fd = signal.set_wakeup_fd(write_fd)
+signal.signal(signal.SIGTERM, destroy)
+signal.signal(signal.SIGINT, destroy)
+
+config_path = os.environ.get('XDG_CONFIG_HOME', default=os.path.expanduser('~/.config/'))
+os.makedirs(os.path.join(config_path, 'silvertools'), exist_ok=True)
+
+config = ConfigFile(os.path.join(config_path, 'silvertools', 'kcdemu_config.cfg'))
+
+cdemu = CDEmu(os.path.join(config_path, 'silvertools', 'kcdemu_state.cfg'), config)
+tm = TrayManager(cdemu, config, KCDEmuDropperWidget(cdemu, config))
+sys.exit(app.exec())
diff --git a/build.sh b/build.sh
new file mode 100755
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+VERSION="1.0.0"
+
+pyrcc5 pics/pics.qrc -o pics/__init__.py
+rm kcdemu.zip
+zip -r kcdemu.zip ./ -x \*.png \*.ui .hg\* .idea\* build.sh
+mkdir build
+mkdir build/temp
+
+# Debian/Ubuntu
+mkdir build/temp/debian
+cp -r packaging/debian/* build/temp/debian
+cp kcdemu.zip build/temp/debian/opt/kcdemu/
+CONTROL=`cat build/temp/debian/DEBIAN/control`
+CONTROL=`echo "${CONTROL//%VERSION%/$VERSION}"`
+SIZE="$(du -sk --apparent-size build/temp | awk '{print $1}')"
+CONTROL=`echo "${CONTROL//%SIZE%/$SIZE}"`
+echo "$CONTROL" > build/temp/debian/DEBIAN/control
+pushd build/temp
+fakeroot dpkg-deb --build debian
+popd
+mv build/temp/debian.deb build/kcdemu-${VERSION}.deb
+# Cleanup
+rm -rf build/temp
diff --git a/cdemu/__init__.py b/cdemu/__init__.py
new file mode 100644
--- /dev/null
+++ b/cdemu/__init__.py
@@ -0,0 +1,231 @@
+from event.sync import EventDispatcher
+from configparser import ConfigParser
+import os
+from .device import CDEmuDevice
+from .proxy import CDEmuDaemonProxy, OP_DEV_ID, OP_DPM_EMU, OP_TR_EMU, OP_BS_EMU
+from mount import mount
+
+
+class CDEmuException(Exception):
+ """
+ Exception class that encompasses exceptions happening while interacting with CDEmu class
+ """
+ pass
+
+
+class CDEmu(EventDispatcher):
+ def __init__(self, state_file=None, config=None):
+ """
+ High-level abstraction for CDEmu daemon. Includes following features:
+ 1) Automatically restart daemon if it is stopped(fails?)
+ 2) Automatically unmount device before ejecting or removing it
+ 3) Automatically restore daemon state on startup
+ """
+ super().__init__(
+ 'daemon_started',
+ 'daemon_stopped',
+ 'device_status_changed',
+ 'device_option_changed',
+ 'devices_changed'
+ )
+
+ self.devices = []
+
+ self.state_file = state_file
+ self.state = ConfigParser()
+ if state_file and os.path.isfile(state_file):
+ self.state.read(state_file)
+
+ if not self.state.has_section('main'):
+ self.change_option('main', 'devices', '1')
+ self.change_option('main', 'autorestart', '1')
+ self.change_option('dev0', 'dpm-emulation', '0')
+ self.change_option('dev0', 'tr-emulation', '0')
+ self.change_option('dev0', 'bad-sector-emulation', '0')
+ self.save_options()
+
+ self.daemon = CDEmuDaemonProxy()
+ self.daemon.add_handler('daemon_started', self.on_daemon_started)
+ self.daemon.add_handler('daemon_stopped', self.on_daemon_stopped)
+ self.daemon.add_handler('device_mapping_ready', self.on_device_mapping_ready)
+ self.daemon.add_handler('device_added', self.on_device_added)
+ self.daemon.add_handler('device_removed', self.on_device_removed)
+ self.daemon.add_handler('device_status_changed', self.on_device_status_changed)
+ self.daemon.add_handler('device_option_changed', self.on_device_option_changed)
+ if self.daemon.is_running:
+ self.on_daemon_started()
+
+ def get_option(self, section, option):
+ if not self.state.has_section(section):
+ return None
+ if option not in self.state[section]:
+ return None
+ value = self.state[section][option]
+ if value.startswith('"'):
+ value = value[1:-1]
+ return value
+
+ def change_option(self, section, option, value, save=False):
+ value = str(value)
+ if value.startswith(' ') or value.startswith('"') or value.endswith(' '):
+ value = '"{}"'.format(value)
+ if not self.state.has_section(section):
+ self.state.add_section(section)
+ self.state.set(section, option, value)
+ if save and self.state_file:
+ with open(self.state_file, 'w') as f:
+ self.state.write(f)
+
+ def save_options(self):
+ if self.state_file:
+ with open(self.state_file, 'w') as f:
+ self.state.write(f)
+
+ def on_daemon_started(self):
+ self.dispatch('daemon_started')
+ need_devices = int(self.get_option('main', 'devices'))
+ have_devices = self.daemon.get_number_of_devices()
+ while have_devices < need_devices:
+ self.daemon.add_device()
+ have_devices += 1
+ while have_devices > need_devices:
+ self.daemon.remove_device()
+ have_devices -= 1
+ for x in range(need_devices):
+ if len(self.devices) <= x:
+ self.devices.append(self._create_device(len(self.devices)))
+
+ if self.state.has_section('dev{}'.format(x)):
+ sect = 'dev{}'.format(x)
+ else:
+ sect = 'main'
+ if self.get_option(sect, OP_DPM_EMU):
+ self.daemon.device_set_option(x, OP_DPM_EMU, self.get_option(sect, OP_DPM_EMU) == '1')
+ if self.get_option(sect, OP_TR_EMU):
+ self.daemon.device_set_option(x, OP_TR_EMU, self.get_option(sect, OP_TR_EMU) == '1')
+ if self.get_option(sect, OP_BS_EMU):
+ self.daemon.device_set_option(x, OP_BS_EMU, self.get_option(sect, OP_BS_EMU) == '1')
+ if self.get_option(sect, 'vendor'):
+ vendor = self.get_option(sect, 'vendor')
+ product = self.get_option(sect, 'product')
+ revision = self.get_option(sect, 'revision')
+ vendor_specific = self.get_option(sect, 'vendor_specific')
+ self.daemon.device_set_option(x, OP_DEV_ID, (vendor, product, revision, vendor_specific))
+ if self.get_option(sect, 'img'):
+ self.load(self.devices[x], self.get_option(sect, 'img'))
+ else:
+ self.unload(self.devices[x])
+
+ sr_map, sg_map = self.daemon.device_get_mapping(x)
+ if sr_map or sg_map:
+ self.on_device_mapping_ready(x)
+
+ def on_daemon_stopped(self):
+ self.dispatch('daemon_stopped')
+ if self.get_option('main', 'autorestart') == '1':
+ self.daemon.connect()
+
+ def on_device_mapping_ready(self, number):
+ if number >= len(self.devices):
+ return
+
+ device = self.devices[number]
+ device._mapped_sr, device._mapped_sg = self.daemon.device_get_mapping(number)
+ loaded, img = self.daemon.device_get_status(number)
+ if loaded and len(img) > 0:
+ device._img = img[0]
+
+ device._dpm = self.daemon.device_get_option(number, 'dpm-emulation')
+ device._tr = self.daemon.device_get_option(number, 'tr-emulation')
+ device._bs = self.daemon.device_get_option(number, 'bad-sector-emulation')
+ device._vendor, device._product, device._revision, device._vendor_specific = self.daemon.device_get_option(
+ number, 'device-id')
+
+ section = 'dev{}'.format(number)
+ self.change_option(section, OP_DPM_EMU, device._dpm)
+ self.change_option(section, OP_TR_EMU, device._tr)
+ self.change_option(section, OP_BS_EMU, device._bs)
+
+ self.change_option(section, 'vendor', device._vendor)
+ self.change_option(section, 'product', device._product)
+ self.change_option(section, 'revision', device._revision)
+ self.change_option(section, 'vendor_specific', device._vendor_specific)
+ self.save_options()
+
+ self.dispatch('devices_changed', number)
+
+ def _create_device(self, id):
+ device = CDEmuDevice(id, self)
+ return device
+
+ def load(self, device, img):
+ self.daemon.device_load(device._device_id, [img], {})
+
+ def unload(self, device):
+ try:
+ if device.mapped_sr and mount.is_mounted(device.mapped_sr):
+ mount.unmount(device.mapped_sr)
+ except mount.MountException as e:
+ raise CDEmuException(str(e))
+ self.daemon.device_unload(device._device_id)
+
+ def on_device_added(self):
+ # Add device and send event
+ devices = self.daemon.get_number_of_devices()
+ self.change_option('main', 'devices', devices, True)
+ while len(self.devices) < devices:
+ self.devices.append(self._create_device(len(self.devices)))
+
+ def on_device_removed(self):
+ # Remove device and send event
+ devices = self.daemon.get_number_of_devices()
+ while len(self.devices) > devices:
+ self.devices.pop()
+ self.dispatch('devices_changed')
+
+ def on_device_status_changed(self, id):
+ loaded, img = self.daemon.device_get_status(id)
+ if loaded and len(img) > 0:
+ self.devices[id]._img = img[0]
+ self.change_option('dev{}'.format(id), 'img', self.devices[id]._img, True)
+ else:
+ self.devices[id]._img = None
+ self.change_option('dev{}'.format(id), 'img', '', True)
+ self.devices[id].dispatch('change')
+
+ def on_device_option_changed(self, id, option):
+ value = self.daemon.device_get_option(id, option)
+ section = 'dev{}'.format(id)
+ if option == OP_DPM_EMU:
+ self.devices[id]._dpm = value
+ self.change_option(section, OP_DPM_EMU, value)
+ elif option == OP_TR_EMU:
+ self.devices[id]._tr = value
+ self.change_option(section, OP_TR_EMU, value)
+ elif option == OP_BS_EMU:
+ self.devices[id]._bs = value
+ self.change_option(section, OP_BS_EMU, value)
+ elif option == OP_DEV_ID:
+ self.devices[id]._vendor, self.devices[id]._product, \
+ self.devices[id]._revision, self.devices[id]._vendor_specific = value
+ self.change_option(section, 'vendor', self.devices[id]._vendor)
+ self.change_option(section, 'product', self.devices[id]._product)
+ self.change_option(section, 'revision', self.devices[id]._revision)
+ self.change_option(section, 'vendor_specific', self.devices[id]._vendor_specific)
+ self.save_options()
+ self.devices[id].dispatch('change')
+
+ def set_device_option(self, device_number, option_name, option_value):
+ self.daemon.device_set_option(device_number, option_name, option_value)
+
+ def add_device(self):
+ self.daemon.add_device()
+
+ def remove_device(self):
+ self.daemon.remove_device()
+
+ def get_devices(self):
+ return self.devices.copy()
+
+ def is_daemon_ready(self):
+ return self.daemon.is_running
\ No newline at end of file
diff --git a/cdemu/device.py b/cdemu/device.py
new file mode 100644
--- /dev/null
+++ b/cdemu/device.py
@@ -0,0 +1,100 @@
+from event.sync import EventDispatcher
+from .proxy import OP_BS_EMU, OP_DEV_ID, OP_DPM_EMU, OP_TR_EMU
+
+
+class CDEmuDevice(EventDispatcher):
+ def __init__(self, device_id, cdemu):
+ super().__init__(
+ 'change'
+ )
+ self.cdemu = cdemu
+ self._device_id = device_id
+ self._mapped_sr = None
+ self._mapped_sg = None
+ self._img = False
+ self._dpm = False
+ self._tr = False
+ self._bs = False
+ self._vendor = None
+ self._product = None
+ self._revision = None
+ self._vendor_specific = None
+
+ def get_name(self):
+ return 'Device {}'.format(self._device_id)
+
+ @property
+ def mapped_sr(self):
+ return self._mapped_sr
+
+ @property
+ def mapped_sg(self):
+ return self._mapped_sg
+
+ def get_img(self):
+ return self._img
+
+ def load(self, img):
+ self.cdemu.load(self, img)
+
+ def unload(self):
+ self.cdemu.unload(self)
+
+ def get_dpm(self):
+ """
+ get current dpm emulation state
+ :return: True if dpm emulation is on, False otherwise
+ """
+ return self._dpm
+
+ def set_dpm(self, dpm):
+ """
+ set dpm emulation state
+ :param dpm: True to enable emulation, False to disable
+ """
+ self.cdemu.set_device_option(self._device_id, OP_DPM_EMU, dpm)
+
+ def get_tr(self):
+ """
+ get current transfer rate emulation state
+ :return: True if transfer rate emulation is enabled, False otherwise
+ """
+ return self._tr
+
+ def set_tr(self, tr):
+ """
+ set transfer rate emulation state
+ :param tr: True to enable emulation, False to disable
+ """
+ self.cdemu.set_device_option(self._device_id, OP_TR_EMU, tr)
+
+ def get_bs(self):
+ """
+ get current bad sector emulation state
+ :return: True if bad sector emulation is enabled, False otherwise
+ """
+ return self._bs
+
+ def set_bs(self, bs):
+ """
+ set bad sector emulation state
+ :param bs: True to enable emulation, False to disable
+ """
+ self.cdemu.set_device_option(self._device_id, OP_BS_EMU, bs)
+
+ def get_id(self):
+ """
+ get current device id
+ :return: tuple(str(vendor_id), str(product_id), str(revision), str(vendor_specific))
+ """
+ return (self._vendor, self._product, self._revision, self._vendor_specific)
+
+ def set_id(self, vendor, product, revision, vendor_specific):
+ """
+ set device id
+ :param vendor: vendor_id - 8 character string
+ :param product: product_id - 16 character string
+ :param revision: - 4 character string
+ :param vendor_specific: - 20 character string
+ """
+ self.cdemu.set_device_option(self._device_id, OP_DEV_ID, (vendor, product, revision, vendor_specific))
diff --git a/cdemu/proxy.py b/cdemu/proxy.py
new file mode 100644
--- /dev/null
+++ b/cdemu/proxy.py
@@ -0,0 +1,147 @@
+import dbus, dbus.service, dbus.exceptions
+from event.sync import EventDispatcher
+
+
+OP_DPM_EMU = 'dpm-emulation'
+OP_TR_EMU = 'tr-emulation'
+OP_BS_EMU = 'bad-sector-emulation'
+OP_DEV_ID = 'device-id'
+
+
+class CDEmuDaemonProxy(EventDispatcher):
+ _name = 'net.sf.cdemu.CDEmuDaemon'
+ _object_path = '/Daemon'
+
+ def __init__(self, use_system=False):
+ """
+ Low-level abstraction for communitating with cdemu daemon
+ :param autostart_daemon: Attempt to start daemon if it is not running
+ :param use_system: Not recommended. Consider this option deprecated. Left as a compatibility option
+ """
+ super().__init__(
+ 'daemon_started',
+ 'daemon_stopped',
+ 'device_status_changed',
+ 'device_option_changed',
+ 'device_mapping_ready',
+ 'device_added',
+ 'device_removed'
+ )
+
+ self._is_running = False
+ self.use_system = use_system
+
+ if self.use_system:
+ self.bus = dbus.SystemBus()
+ else:
+ self.bus = dbus.SessionBus()
+
+ self.bus.add_signal_receiver(self.from_daemon, dbus_interface='net.sf.cdemu.CDEmuDaemon',
+ member_keyword='signal', message_keyword='params')
+ self.bus.add_signal_receiver(self.from_dbus, dbus_interface='org.freedesktop.DBus',
+ member_keyword='signal', message_keyword='params')
+
+ self.obj = None
+ self.interface = None
+
+ self.connect()
+
+ @property
+ def is_running(self):
+ return self._is_running
+
+ def connect(self, retry=True):
+ self.obj = self.bus.get_object(self._name, self._object_path)
+ self.interface = dbus.Interface(self.obj, dbus_interface='net.sf.cdemu.CDEmuDaemon')
+
+ self.ping_daemon(retry)
+
+ def ping_daemon(self, retry=True):
+ try:
+ self.obj.Ping(dbus_interface='org.freedesktop.DBus.Peer')
+ self._is_running = True
+ self.dispatch('daemon_started')
+ except dbus.exceptions.DBusException as e:
+ if e.get_dbus_name() == 'org.freedesktop.DBus.Error.ServiceUnknown' and retry:
+ self.connect(False)
+
+ def from_dbus(self, *args, signal, params):
+ print('from_dbus', '|'.join([str(a) for a in args]), signal, params)
+ if signal == 'NameOwnerChanged':
+ if args[0] == self._name:
+ if args[2]:
+ self._is_running = True
+ self.dispatch('daemon_started')
+ else:
+ self._is_running = False
+ self.dispatch('daemon_stopped')
+
+ def from_daemon(self, *args, signal, params):
+ print('from_daemon', '|'.join([str(a) for a in args]), signal, params)
+ if signal == "DeviceStatusChanged":
+ self.dispatch('device_status_changed', *args)
+ elif signal == "DeviceOptionChanged":
+ self.dispatch('device_option_changed', *args)
+ elif signal == "DeviceMappingReady":
+ self.dispatch('device_mapping_ready', *args)
+ elif signal == "DeviceAdded":
+ self.dispatch('device_added', *args)
+ elif signal == "DeviceRemoved":
+ self.dispatch('device_removed', *args)
+
+ def get_daemon_version(self):
+ return self.interface.GetDaemonVersion()
+
+ def get_library_version(self):
+ return self.interface.GetLibraryVersion()
+
+ def get_daemon_interface_version2(self):
+ return self.interface.GetDaemonInterfaceVersion2()
+
+ def enum_daemon_debug_masks(self):
+ return self.interface.EnumDaemonDebugMasks()
+
+ def enum_library_debug_masks(self):
+ return self.interface.EnumLibraryDebugMasks()
+
+ def enum_supported_parsers(self):
+ return self.interface.EnumSupportedParsers()
+
+ def enum_supported_writers(self):
+ return self.interface.EnumSupportedWriters()
+
+ def enum_supported_filter_streams(self):
+ return self.interface.EnumSupportedFilterStreams()
+
+ def enum_writer_parameters(self, writer_id):
+ return self.interface.EnumWriterParameters('(s)', writer_id)
+
+ def get_number_of_devices(self):
+ return self.interface.GetNumberOfDevices()
+
+ def device_get_mapping(self, device_number):
+ return self.interface.DeviceGetMapping(device_number)
+
+ def device_get_status(self, device_number):
+ return self.interface.DeviceGetStatus(device_number)
+
+ def device_load(self, device_number, filenames, parameters={}):
+ return self.interface.DeviceLoad(device_number, filenames, parameters)
+
+ def device_create_blank(self, device_number, filename, parameters):
+ return self.interface.DeviceCreateBlank(device_number, filename, parameters)
+
+ def device_unload(self, device_number):
+ return self.interface.DeviceUnload(device_number)
+
+ def device_get_option(self, device_number, option_name):
+ return self.interface.DeviceGetOption(device_number, option_name)
+
+ def device_set_option(self, device_number, option_name, option_value):
+ return self.interface.DeviceSetOption(device_number, option_name, option_value)
+
+ def add_device(self):
+ return self.interface.AddDevice()
+
+ def remove_device(self):
+ return self.interface.RemoveDevice()
diff --git a/config/config.py b/config/config.py
new file mode 100644
--- /dev/null
+++ b/config/config.py
@@ -0,0 +1,96 @@
+import os
+from configparser import ConfigParser
+
+from event.sync import EventDispatcher
+
+
+class ConfigFile(EventDispatcher, ConfigParser):
+ POSITION_TOP_LEFT = 'tl'
+ POSITION_TOP_RIGHT = 'tr'
+ POSITION_BOTTOM_LEFT = 'bl'
+ POSITION_BOTTOM_RIGHT = 'br'
+
+ ICON_TRAY_DEFAULT = ':/icon.png'
+ ICON_DROPPER_EMPTY_DEFAULT = ':/dropper_empty.png'
+ ICON_DROPPER_FULL_DEFAULT = ':/dropper_full.png'
+
+ def __init__(self, path):
+ EventDispatcher.__init__(self, 'changed')
+ ConfigParser.__init__(self)
+ self.path = path
+ if os.path.exists(path):
+ self.load()
+
+ def get_position(self):
+ return self.get('ui', 'position', fallback=self.POSITION_BOTTOM_RIGHT)
+
+ def set_position(self, position):
+ assert position in (
+ self.POSITION_TOP_LEFT, self.POSITION_TOP_RIGHT, self.POSITION_BOTTOM_LEFT, self.POSITION_BOTTOM_RIGHT
+ )
+
+ if not self.has_section('ui'):
+ self.add_section('ui')
+ self.set('ui', 'position', position)
+
+ def get_offset(self):
+ return (
+ self.getint('ui', 'offset_x', fallback=16),
+ self.getint('ui', 'offset_y', fallback=16)
+ )
+
+ def set_offset(self, offset_x, offset_y):
+ if not self.has_section('ui'):
+ self.add_section('ui')
+
+ self.set('ui', 'offset_x', str(offset_x))
+ self.set('ui', 'offset_y', str(offset_y))
+
+ def get_icon_size(self):
+ return (
+ self.getint('ui', 'icon_width', fallback=32),
+ self.getint('ui', 'icon_height', fallback=32)
+ )
+
+ def set_icon_size(self, icon_width, icon_height):
+ if not self.has_section('ui'):
+ self.add_section('ui')
+
+ self.set('ui', 'icon_width', str(icon_width))
+ self.set('ui', 'icon_height', str(icon_height))
+
+ def get_tray_icon(self):
+ return self.get('ui', 'tray_icon', fallback=self.ICON_TRAY_DEFAULT)
+
+ def set_tray_icon(self, icon):
+ if not self.has_section('ui'):
+ self.add_section('ui')
+
+ self.set('ui', 'tray_icon', icon)
+
+ def get_empty_icon(self):
+ return self.get('ui', 'empty_icon', fallback=self.ICON_DROPPER_EMPTY_DEFAULT)
+
+ def set_empty_icon(self, icon):
+ if not self.has_section('ui'):
+ self.add_section('ui')
+
+ self.set('ui', 'empty_icon', icon)
+
+ def get_full_icon(self):
+ return self.get('ui', 'full_icon', fallback=self.ICON_DROPPER_FULL_DEFAULT)
+
+ def set_full_icon(self, icon):
+ if not self.has_section('ui'):
+ self.add_section('ui')
+
+ self.set('ui', 'full_icon', icon)
+
+ def load(self):
+ self.read(self.path)
+
+ def save(self):
+ with open(self.path, 'w') as f:
+ self.write(f)
+
+ self.dispatch('changed')
diff --git a/event/sync.py b/event/sync.py
new file mode 100644
--- /dev/null
+++ b/event/sync.py
@@ -0,0 +1,31 @@
+import sys, traceback
+
+
+class EventDispatcher:
+ def __init__(self, *event_names):
+ self._events = {}
+ self._listname = 0
+ for n in event_names:
+ self._events[n] = {}
+
+ def add_handler(self, name, listener, listener_name=None):
+ # TODO add proper checks and specific exception if event is not supported
+ if not listener_name:
+ listener_name = '__auto__name__{}'.format(self._listname)
+ self._listname += 1
+ self._events[name][listener_name] = listener
+
+ def remove_handler(self, name, listener_name=None):
+ # TODO add proper checks and specific exception if event is not supported
+ if listener_name:
+ del self._events[name][listener_name]
+ else:
+ self._events[name].clear()
+
+ def dispatch(self, name, *args, **kwargs):
+ # TODO add special exceptions for StopPropagation and PreventDefault
+ for e in self._events[name].values():
+ try:
+ e(*args, **kwargs)
+ except Exception:
+ sys.stderr.write(traceback.format_exc())
diff --git a/gui/__init__.py b/gui/__init__.py
new file mode 100644
diff --git a/gui/about.py b/gui/about.py
new file mode 100644
--- /dev/null
+++ b/gui/about.py
@@ -0,0 +1,9 @@
+from PyQt5.QtWidgets import QWidget
+
+from .ui.about import Ui_Form
+
+
+class AboutWidget(Ui_Form, QWidget):
+ def __init__(self):
+ super().__init__()
+ self.setupUi(self)
diff --git a/gui/device_menu.py b/gui/device_menu.py
new file mode 100644
--- /dev/null
+++ b/gui/device_menu.py
@@ -0,0 +1,52 @@
+from PyQt5 import QtWidgets
+from mount import mount
+from cdemu import CDEmuException
+
+class QDeviceMenu(QtWidgets.QMenu):
+ def __init__(self, device):
+ super().__init__(device.get_name())
+ self._id = id
+ self.load = self.addAction('Load')
+ self.load.triggered.connect(self.on_load)
+ self.unload = self.addAction('Unload')
+ self.unload.triggered.connect(self.on_unload)
+ self.addSeparator()
+ self.dpm = self.addAction('DPM emulation')
+ self.dpm.setCheckable(True)
+ self.dpm.triggered.connect(self.on_dpm)
+ self.tr = self.addAction('Transfer rate emulation')
+ self.tr.setCheckable(True)
+ self.tr.triggered.connect(self.on_tr)
+ self.bs = self.addAction('Bad sector enulation')
+ self.bs.setCheckable(True)
+ self.bs.triggered.connect(self.on_bs)
+ self.device = device
+ self.device.add_handler('change', self.on_device_change)
+ self.on_device_change()
+
+ def on_device_change(self):
+ self.unload.setEnabled(bool(self.device.get_img()))
+ self.dpm.setChecked(self.device.get_dpm())
+ self.tr.setChecked(self.device.get_tr())
+ self.bs.setChecked(self.device.get_bs())
+
+ def on_load(self):
+ fd = QtWidgets.QFileDialog(self)
+ fd.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen)
+ if fd.exec() == QtWidgets.QDialog.Accepted:
+ self.device.load(fd.selectedFiles()[0])
+
+ def on_unload(self):
+ try:
+ self.device.unload()
+ except Exception as e:
+ QtWidgets.QMessageBox.critical(None, 'Error', str(e))
+
+ def on_dpm(self):
+ self.device.set_dpm(self.dpm.isChecked())
+
+ def on_tr(self):
+ self.device.set_tr(self.tr.isChecked())
+
+ def on_bs(self):
+ self.device.set_bs(self.bs.isChecked())
diff --git a/gui/dropper.py b/gui/dropper.py
new file mode 100644
--- /dev/null
+++ b/gui/dropper.py
@@ -0,0 +1,145 @@
+from PyQt5 import QtGui, QtCore, QtWidgets
+from PyQt5.QtWidgets import QSizePolicy
+
+from cdemu import CDEmuException
+from .device_menu import QDeviceMenu
+from mount import mount
+from urllib.parse import unquote
+
+class KCDEmuDevice(QtWidgets.QLabel):
+ def __init__(self, device, width=32, height=32):
+ super().__init__()
+ self.setAcceptDrops(True)
+ self.device = None
+ self.empty_icon = ':/dropper_empty.png'
+ self.full_icon = ':/dropper_full.png'
+ self.refresh(device)
+ self.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
+ self.setAlignment(QtCore.Qt.AlignCenter)
+ self.setMinimumSize(width, height)
+ self.setBaseSize(width, height)
+ self.setMaximumSize(width, height)
+ self.ctx_menu = QDeviceMenu(device)
+
+ def set_size(self, width, height):
+ self.setMinimumSize(width, height)
+ self.setBaseSize(width, height)
+ self.setMaximumSize(width, height)
+ self.adjustSize()
+
+ def set_icons(self, empty_icon, full_icon):
+ self.empty_icon = empty_icon
+ self.full_icon = full_icon
+ self.refresh()
+
+ def refresh(self, device=None):
+ if device is not None:
+ if self.device and self.device != device:
+ self.device.remove_handler('change')
+ if self.device != device:
+ self.device = device
+ self.device.add_handler('change', self.refresh)
+ self.setText(
+ ''.format(
+ self.full_icon if self.device.get_img() else self.empty_icon))
+ if self.device.get_img():
+ self.setToolTip('Click to unload "{}"'.format(self.device.get_img()))
+ else:
+ self.setToolTip('Click to load')
+
+ def dragEnterEvent(self, event):
+ if event.mimeData().hasFormat('text/plain'):
+ text = event.mimeData().text()
+ if text.startswith('file://'):
+ event.acceptProposedAction()
+
+ def dragLeaveEvent(self, event):
+ pass
+
+ def dropEvent(self, event):
+ if event.mimeData().hasFormat('text/plain'):
+ text = event.mimeData().text()
+ if text.startswith('file://'):
+ text = unquote(text.replace('file://', '').replace('\n', '').replace('\r', ''))
+ self.device.load(text)
+
+ def mousePressEvent(self, event):
+ if event.button() == QtCore.Qt.RightButton:
+ self.ctx_menu.popup(QtCore.QPoint(event.globalX(), event.globalY()))
+ elif event.button() == QtCore.Qt.LeftButton:
+ if self.device.get_img():
+ try:
+ self.device.unload()
+ except Exception as e:
+ QtWidgets.QMessageBox.critical(None, 'Error', str(e))
+ else:
+ fd = QtWidgets.QFileDialog(self)
+ fd.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen)
+ if fd.exec() == QtWidgets.QDialog.Accepted:
+ self.device.load(fd.selectedFiles()[0])
+
+class KCDEmuDropperWidget(QtWidgets.QWidget):
+ changed = QtCore.pyqtSignal()
+
+ def __init__(self, cdemu, config):
+ """
+ :type cdemu: cdemu.CDEmu
+ :type config: config.config.ConfigFile
+ """
+ super().__init__(None, QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
+ self.config = config
+ self.setWindowTitle('KCDEmu')
+ self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum))
+ layout = QtWidgets.QHBoxLayout(self)
+ layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize)
+ layout.setContentsMargins(0,0,0,0)
+ layout.setSpacing(0)
+ self.setLayout(layout)
+ self.cdemu = cdemu
+ self.devices = []
+
+ self.cdemu.add_handler('devices_changed', self.on_devices_changed)
+ self.cdemu.add_handler('daemon_stopped', self.on_daemon_stopped)
+ self.cdemu.add_handler('daemon_started', self.on_daemon_started)
+ self.config.add_handler('changed', self.reload_config)
+ self.reload_config()
+
+ if self.cdemu.is_daemon_ready():
+ self.on_devices_changed()
+
+ def on_devices_changed(self, number=None):
+ devices = self.cdemu.get_devices()
+ while len(self.devices) > len(devices):
+ dev = self.devices.pop()
+ self.layout().removeWidget(dev)
+ dev.hide()
+ for index, device in enumerate(devices):
+ if len(self.devices) <= index:
+ icon_size = self.config.get_icon_size()
+ dev = KCDEmuDevice(device, icon_size[0], icon_size[1])
+ dev.set_icons(self.config.get_empty_icon(), self.config.get_full_icon())
+ self.devices.append(dev)
+ self.layout().addWidget(dev)
+ else:
+ self.devices[index].refresh(device)
+ self.updateGeometry()
+
+ def resizeEvent(self, event):
+ print(self.sizeHint().width())
+ self.changed.emit()
+
+ def on_daemon_stopped(self):
+ while len(self.devices) > 0:
+ dev = self.devices.pop()
+ self.layout().removeWidget(dev)
+
+ def on_daemon_started(self):
+ self.on_devices_changed()
+
+ def reload_config(self):
+ for i in range(self.layout().count()):
+ widget = self.layout().itemAt(i).widget()
+ if widget:
+ icon_size = self.config.get_icon_size()
+ widget.set_size(icon_size[0], icon_size[1])
+ widget.set_icons(self.config.get_empty_icon(), self.config.get_full_icon())
diff --git a/gui/main_settings.py b/gui/main_settings.py
new file mode 100644
--- /dev/null
+++ b/gui/main_settings.py
@@ -0,0 +1,142 @@
+import os
+
+from PyQt5 import QtCore
+from PyQt5.QtCore import QSize
+from PyQt5.QtWidgets import QWidget, QFileDialog
+
+from .ui.main_settings import Ui_Form
+
+
+class MainSettingsWidget(Ui_Form, QWidget):
+ def __init__(self, config):
+ """
+
+ :type config: config.config.ConfigFile
+ """
+ super().__init__()
+ self.config = config
+ self.last_directory = None
+ self.setupUi(self)
+
+ self.trayDefaultB.clicked.connect(self.on_tray_default_click)
+ self.emptyDefaultB.clicked.connect(self.on_empty_default_click)
+ self.fullDefaultB.clicked.connect(self.on_full_default_click)
+
+ self.trayCustomB.clicked.connect(self.on_tray_custom_click)
+ self.emptyCustomB.clicked.connect(self.on_empty_custom_click)
+ self.fullCustomB.clicked.connect(self.on_full_custom_click)
+
+ position = self.config.get_position()
+ if position == self.config.POSITION_TOP_LEFT:
+ self.topLeftRB.setChecked(True)
+ if position == self.config.POSITION_TOP_RIGHT:
+ self.topRightRB.setChecked(True)
+ if position == self.config.POSITION_BOTTOM_LEFT:
+ self.bottomLeftRB.setChecked(True)
+ if position == self.config.POSITION_BOTTOM_RIGHT:
+ self.bottomRightRB.setChecked(True)
+
+ offset = self.config.get_offset()
+ icon_size = self.config.get_icon_size()
+
+ self.hOffsetSB.setValue(offset[0])
+ self.vOffsetSB.setValue(offset[1])
+
+ self.iwidthSB.setValue(icon_size[0])
+ self.iheightSB.setValue(icon_size[1])
+
+ self.iwidthSB.editingFinished.connect(self.resize_icons)
+ self.iheightSB.editingFinished.connect(self.resize_icons)
+ self.resize_icons()
+
+ self.tray_icon = self.config.get_tray_icon()
+ self.empty_icon = self.config.get_empty_icon()
+ self.full_icon = self.config.get_full_icon()
+
+ self.update_icons()
+
+ def update_icons(self):
+ self.trayLabel.setText("")
+ self.emptyLabel.setText("")
+ self.fullLabel.setText("")
+
+ def on_tray_default_click(self):
+ self.tray_icon = self.config.ICON_TRAY_DEFAULT
+ self.update_icons()
+
+ def on_empty_default_click(self):
+ self.empty_icon = self.config.ICON_DROPPER_EMPTY_DEFAULT
+ self.update_icons()
+
+ def on_full_default_click(self):
+ self.full_icon = self.config.ICON_DROPPER_FULL_DEFAULT
+ self.update_icons()
+
+ def on_tray_custom_click(self):
+ file = self.show_open_dialog()
+ if file:
+ self.tray_icon = file
+ self.update_icons()
+
+ def on_empty_custom_click(self):
+ file = self.show_open_dialog()
+ if file:
+ self.empty_icon = file
+ self.update_icons()
+
+ def on_full_custom_click(self):
+ file = self.show_open_dialog()
+ if file:
+ self.full_icon = file
+ self.update_icons()
+
+ def show_open_dialog(self):
+ open_dialog = QFileDialog()
+
+ if self.last_directory:
+ open_dialog.setDirectory(self.last_directory)
+
+ open_dialog.setAcceptMode(open_dialog.AcceptOpen)
+ if open_dialog.exec_() == QFileDialog.Accepted:
+ file = open_dialog.selectedFiles()[0]
+ self.last_directory = os.path.abspath(file)
+ return file
+ return None
+
+ def resize_icons(self):
+ icon_width = self.iwidthSB.value()
+ icon_height = self.iheightSB.value()
+ self.trayLabel.setMinimumSize(icon_width, icon_height)
+ self.trayLabel.setBaseSize(icon_width, icon_height)
+ self.trayLabel.setMaximumSize(icon_width, icon_height)
+
+ self.emptyLabel.setMinimumSize(icon_width, icon_height)
+ self.emptyLabel.setBaseSize(icon_width, icon_height)
+ self.emptyLabel.setMaximumSize(icon_width, icon_height)
+
+ self.fullLabel.setMinimumSize(icon_width, icon_height)
+ self.fullLabel.setBaseSize(icon_width, icon_height)
+ self.fullLabel.setMaximumSize(icon_width, icon_height)
+
+ self.trayDefaultB.setIconSize(QSize(icon_width, icon_height))
+ self.emptyDefaultB.setIconSize(QSize(icon_width, icon_height))
+ self.fullDefaultB.setIconSize(QSize(icon_width, icon_height))
+
+
+ def update_config(self):
+ position = None
+ if self.topLeftRB.isChecked():
+ position = self.config.POSITION_TOP_LEFT
+ if self.topRightRB.isChecked():
+ position = self.config.POSITION_TOP_RIGHT
+ if self.bottomLeftRB.isChecked():
+ position = self.config.POSITION_BOTTOM_LEFT
+ if self.bottomRightRB.isChecked():
+ position = self.config.POSITION_BOTTOM_RIGHT
+
+ self.config.set_position(position)
+ self.config.set_icon_size(self.iwidthSB.value(), self.iheightSB.value())
+ self.config.set_offset(self.hOffsetSB.value(), self.vOffsetSB.value())
+ self.config.set_tray_icon(self.tray_icon)
+ self.config.set_empty_icon(self.empty_icon)
+ self.config.set_full_icon(self.full_icon)
diff --git a/gui/settings.py b/gui/settings.py
new file mode 100644
--- /dev/null
+++ b/gui/settings.py
@@ -0,0 +1,47 @@
+from PyQt5.QtCore import QRect, QItemSelectionModel
+from PyQt5.QtWidgets import QDialog
+from PyQt5 import QtCore
+
+from gui.about import AboutWidget
+from gui.main_settings import MainSettingsWidget
+from .ui.settings import Ui_Form
+
+
+class SettingsForm(Ui_Form, QDialog):
+ PAGES = [
+ 'main_settings',
+ 'about'
+ ]
+
+ def __init__(self, config, index=0):
+ super().__init__()
+ self.config = config
+ self.setupUi(self)
+ self.cancel_button.clicked.connect(self.on_cancel)
+ self.ok_button.clicked.connect(self.on_ok)
+ self.apply_button.clicked.connect(self.on_apply)
+ for i in range(len(self.PAGES)):
+ self.group_list.item(i).setData(QtCore.Qt.UserRole, self.PAGES[i])
+ self.group_list.currentItemChanged.connect(self.on_page_change)
+ self.tabs = {
+ 'main_settings': MainSettingsWidget(config),
+ 'about': AboutWidget()
+ }
+ self.group_list.setCurrentRow(index)
+
+ def on_page_change(self, current, _):
+ print(current.data(QtCore.Qt.UserRole))
+ self.scrollArea.takeWidget()
+ tab = self.tabs[current.data(QtCore.Qt.UserRole)]
+ self.scrollArea.setWidget(tab)
+
+ def on_cancel(self):
+ self.reject()
+
+ def on_ok(self):
+ self.on_apply()
+ self.accept()
+
+ def on_apply(self):
+ self.tabs['main_settings'].update_config()
+ self.config.save()
diff --git a/gui/tray.py b/gui/tray.py
new file mode 100644
--- /dev/null
+++ b/gui/tray.py
@@ -0,0 +1,143 @@
+from PyQt5 import QtCore, QtWidgets, QtGui
+
+from gui.settings import SettingsForm
+from .device_menu import QDeviceMenu
+
+class TrayManager:
+ def __init__(self, cdemu, config, dropper):
+ """
+
+ :type cdemu: cdemu.CDEmu
+ :type config: config.config.ConfigFile
+ :type dropper: gui.dropper.KCDEmuDropperWidget
+ """
+ self.cdemu = cdemu
+ self.config = config
+ self.dropper = dropper
+ self.dropper.changed.connect(self.on_dropper_changed)
+ self.cdemu.add_handler('devices_changed', self.on_devices_changed)
+ self.cdemu.add_handler('daemon_stopped', self.on_daemon_stopped)
+ self.cdemu.add_handler('daemon_started', self.on_daemon_started)
+
+ self.act_device_sep = QtWidgets.QAction(None)
+ self.act_device_sep.setSeparator(True)
+ self.act_add = QtWidgets.QAction('Add device', None)
+ self.act_add.triggered.connect(self.on_act_add)
+ self.act_remove = QtWidgets.QAction('Remove device', None)
+ self.act_remove.triggered.connect(self.on_act_remove)
+ self.act_dropper_show = QtWidgets.QAction('Toggle dropper', None)
+ self.act_dropper_show.triggered.connect(self.on_act_dropper_show)
+ self.act_settings = QtWidgets.QAction('Settings', None)
+ self.act_settings.triggered.connect(self.on_act_settings)
+ self.act_about = QtWidgets.QAction('About', None)
+ self.act_about.triggered.connect(self.on_act_about)
+ self.act_quit = QtWidgets.QAction('Quit', None)
+ self.act_quit.triggered.connect(self.on_act_quit)
+
+ self.menus = {}
+ self.ctx_menu = QtWidgets.QMenu()
+
+ icon = QtGui.QIcon(self.config.get_tray_icon())
+ self.tray_icon = QtWidgets.QSystemTrayIcon(icon)
+ self.tray_icon.setIcon(icon)
+ self.tray_icon.setContextMenu(self.ctx_menu)
+ self.tray_icon.activated.connect(self.on_tray_icon_activated)
+ self.tray_icon.setToolTip('KCDEmu')
+ self.tray_icon.show()
+ self.update_menu()
+
+ def update_menu(self):
+ self.ctx_menu.clear()
+ self.ctx_menu.addAction(self.act_device_sep)
+ self.ctx_menu.addAction(self.act_add)
+ self.ctx_menu.addAction(self.act_remove)
+ self.ctx_menu.addSeparator()
+ self.ctx_menu.addAction(self.act_dropper_show)
+ self.ctx_menu.addAction(self.act_settings)
+ self.ctx_menu.addAction(self.act_about)
+ self.ctx_menu.addAction(self.act_quit)
+ if self.cdemu.is_daemon_ready():
+ self.on_devices_changed()
+
+ def toggle_dropper_visibility(self):
+ # TODO move to dropper object
+ if self.dropper.isVisible():
+ self.dropper.hide()
+ else:
+ self.dropper.show()
+ geo = QtWidgets.QApplication.desktop().availableGeometry()
+ position = self.config.get_position()
+ offset = self.config.get_offset()
+ if position == self.config.POSITION_TOP_LEFT:
+ self.dropper.move(offset[0], offset[1])
+ elif position == self.config.POSITION_TOP_RIGHT:
+ self.dropper.move(geo.width() - self.dropper.width() - offset[0], offset[1])
+ elif position == self.config.POSITION_BOTTOM_LEFT:
+ self.dropper.move(offset[0], geo.height() - self.dropper.height() - offset[1])
+ elif position == self.config.POSITION_BOTTOM_RIGHT:
+ self.dropper.move(
+ geo.width() - self.dropper.width() - offset[0], geo.height() - self.dropper.height() - offset[1]
+ )
+
+ def on_tray_icon_activated(self, reason):
+ if reason == QtWidgets.QSystemTrayIcon.Trigger:
+ self.toggle_dropper_visibility()
+
+ def on_dropper_changed(self):
+ if self.dropper.isVisible():
+ geo = QtWidgets.QApplication.desktop().availableGeometry()
+ self.dropper.move(geo.width() - self.dropper.width() - 16, geo.height() - self.dropper.height() - 16)
+
+ def on_act_quit(self):
+ QtCore.QCoreApplication.exit(0)
+
+ def on_act_dropper_show(self):
+ self.toggle_dropper_visibility()
+
+ def on_act_settings(self):
+ settings_form = SettingsForm(self.config)
+ settings_form.exec_()
+
+ def on_act_about(self):
+ print(self.cdemu.daemon.get_daemon_version())
+ settings_form = SettingsForm(1)
+ settings_form.exec_()
+
+ def on_act_add(self):
+ self.cdemu.add_device()
+
+ def on_act_remove(self):
+ self.cdemu.remove_device()
+
+ def get_menu_for_device(self, number, device):
+ if number in self.menus:
+ return self.menus[number]
+ else:
+ self.menus[number] = QDeviceMenu(device)
+ for k in sorted(self.menus.keys()):
+ if k > number:
+ self.ctx_menu.insertMenu(self.menus[number], self.menus[k])
+ self.ctx_menu.insertMenu(self.act_device_sep, self.menus[number])
+
+ def on_devices_changed(self, number=None):
+ devices = self.cdemu.get_devices()
+ for key in list(self.menus.keys()):
+ if key >= len(devices):
+ self.ctx_menu.removeAction(self.menus[key].menuAction())
+ del self.menus[key]
+
+ if number is None:
+ for number, device in enumerate(devices):
+ self.on_device_changed(device, self.get_menu_for_device(number, device))
+ else:
+ device = self.cdemu.get_devices()[number]
+ self.on_device_changed(device, self.get_menu_for_device(number, device))
+
+ def on_device_changed(self, device, menu):
+ pass
+
+ def on_daemon_started(self):
+ self.tray_icon.showMessage('KCDEmu', 'cdemu-daemon is ready')
+
+ def on_daemon_stopped(self):
+ self.tray_icon.showMessage('KCDEmu', 'cdemu-daemon has stopped')
diff --git a/gui/ui/about.py b/gui/ui/about.py
new file mode 100644
--- /dev/null
+++ b/gui/ui/about.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'gui/ui/about.ui'
+#
+# Created by: PyQt5 UI code generator 5.10.1
+#
+# WARNING! All changes made in this file will be lost!
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ Form.setObjectName("Form")
+ Form.resize(427, 316)
+ self.horizontalLayout = QtWidgets.QHBoxLayout(Form)
+ self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
+ self.horizontalLayout.setSpacing(0)
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.label = QtWidgets.QLabel(Form)
+ self.label.setObjectName("label")
+ self.horizontalLayout.addWidget(self.label, 0, QtCore.Qt.AlignHCenter)
+
+ self.retranslateUi(Form)
+ QtCore.QMetaObject.connectSlotsByName(Form)
+
+ def retranslateUi(self, Form):
+ _translate = QtCore.QCoreApplication.translate
+ Form.setWindowTitle(_translate("Form", "Form"))
+ self.label.setText(_translate("Form", "+=
KCDEmu
Version 1.0
By: Khaelenmore Thaal
GUI utility for managing your virtual CD/DVD drives
Compatible with CDEmu>=3.1.0
Uses Qt5 https://www.qt.io/
Uses PyQt5 https://www.riverbankcomputing.com/software/pyqt/intro
Built-in icons by http://www.fatcow.com/free-icons
This program is licensed with GNU GPL v3
This program is available on http://silverwing.one/pages/kcdemu
"))
diff --git a/gui/ui/about.ui b/gui/ui/about.ui
new file mode 100644
--- /dev/null
+++ b/gui/ui/about.ui
@@ -0,0 +1,45 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 427
+ 316
+
+
+
+ Form
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+ <html><head/><body><p align="center"><img src=":/dropper_empty.png"/><span style=" font-size:36pt;">+</span><img src=":/icon.png"/><span style=" font-size:36pt;">=</span><img src=":/dropper_full.png"/></p><p align="center"><span style=" font-size:22pt;">KCDEmu</span></p><p align="center">Version 1.0</p><p align="center">By: Khaelenmore Thaal</p><p><br/></p><p><span style=" font-family:'DejaVu Sans Mono'; font-size:9.1pt;">GUI utility for managing your virtual CD/DVD drives</span><span style=" font-family:'DejaVu Sans Mono'; font-size:9.1pt; color:#000000;"><br/>Compatible with CDEmu>=3.1.0<br/>Uses Qt5 </span><a href="https://www.qt.io/"><span style=" text-decoration: underline;">https://www.qt.io/</span></a><span style=" font-family:'DejaVu Sans Mono'; font-size:9.1pt; "><br/>Uses PyQt5 </span><a href="https://www.riverbankcomputing.com/software/pyqt/intro"><span style=" text-decoration: underline;">https://www.riverbankcomputing.com/software/pyqt/intro</span></a><span style=" font-family:'DejaVu Sans Mono'; font-size:9.1pt;"><br/>Built-in icons by </span><a href="http://www.fatcow.com/free-icons"><span style=" text-decoration: underline;">http://www.fatcow.com/free-icons</span></a><span style=" font-family:'DejaVu Sans Mono'; font-size:9.1pt;"><br/>This program is licensed with GNU GPL v3<br/>This program is available on </span><a href="http://silverwing.one/pages/kcdemu"><span style=" text-decoration: underline; ">http://silverwing.one/pages/kcdemu</span></a></p></body></html>
+
+
+
+
+
+
+
+
+
+
diff --git a/gui/ui/main_settings.py b/gui/ui/main_settings.py
new file mode 100644
--- /dev/null
+++ b/gui/ui/main_settings.py
@@ -0,0 +1,201 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'gui/ui/main_settings.ui'
+#
+# Created by: PyQt5 UI code generator 5.10.1
+#
+# WARNING! All changes made in this file will be lost!
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ Form.setObjectName("Form")
+ Form.resize(584, 401)
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(Form)
+ self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout_2.setSpacing(0)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.positionGB = QtWidgets.QGroupBox(Form)
+ self.positionGB.setFlat(False)
+ self.positionGB.setObjectName("positionGB")
+ self.horizontalLayout = QtWidgets.QHBoxLayout(self.positionGB)
+ self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.verticalLayout_3 = QtWidgets.QVBoxLayout()
+ self.verticalLayout_3.setSpacing(0)
+ self.verticalLayout_3.setObjectName("verticalLayout_3")
+ self.topLeftRB = QtWidgets.QRadioButton(self.positionGB)
+ self.topLeftRB.setObjectName("topLeftRB")
+ self.verticalLayout_3.addWidget(self.topLeftRB)
+ self.topRightRB = QtWidgets.QRadioButton(self.positionGB)
+ self.topRightRB.setObjectName("topRightRB")
+ self.verticalLayout_3.addWidget(self.topRightRB)
+ self.bottomLeftRB = QtWidgets.QRadioButton(self.positionGB)
+ self.bottomLeftRB.setObjectName("bottomLeftRB")
+ self.verticalLayout_3.addWidget(self.bottomLeftRB)
+ self.bottomRightRB = QtWidgets.QRadioButton(self.positionGB)
+ self.bottomRightRB.setObjectName("bottomRightRB")
+ self.verticalLayout_3.addWidget(self.bottomRightRB)
+ self.horizontalLayout.addLayout(self.verticalLayout_3)
+ self.verticalLayout = QtWidgets.QVBoxLayout()
+ self.verticalLayout.setSpacing(0)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.vOffsetLabel = QtWidgets.QLabel(self.positionGB)
+ self.vOffsetLabel.setObjectName("vOffsetLabel")
+ self.verticalLayout.addWidget(self.vOffsetLabel)
+ self.vOffsetSB = QtWidgets.QSpinBox(self.positionGB)
+ self.vOffsetSB.setObjectName("vOffsetSB")
+ self.verticalLayout.addWidget(self.vOffsetSB)
+ self.hOffsetLabel = QtWidgets.QLabel(self.positionGB)
+ self.hOffsetLabel.setObjectName("hOffsetLabel")
+ self.verticalLayout.addWidget(self.hOffsetLabel)
+ self.hOffsetSB = QtWidgets.QSpinBox(self.positionGB)
+ self.hOffsetSB.setObjectName("hOffsetSB")
+ self.verticalLayout.addWidget(self.hOffsetSB)
+ self.horizontalLayout.addLayout(self.verticalLayout)
+ self.verticalLayout_2.addWidget(self.positionGB)
+ self.sizeGB = QtWidgets.QGroupBox(Form)
+ self.sizeGB.setObjectName("sizeGB")
+ self.formLayout = QtWidgets.QFormLayout(self.sizeGB)
+ self.formLayout.setObjectName("formLayout")
+ self.label = QtWidgets.QLabel(self.sizeGB)
+ self.label.setObjectName("label")
+ self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label)
+ self.iheightSB = QtWidgets.QSpinBox(self.sizeGB)
+ self.iheightSB.setObjectName("iheightSB")
+ self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.iheightSB)
+ self.label_2 = QtWidgets.QLabel(self.sizeGB)
+ self.label_2.setObjectName("label_2")
+ self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_2)
+ self.iwidthSB = QtWidgets.QSpinBox(self.sizeGB)
+ self.iwidthSB.setObjectName("iwidthSB")
+ self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.iwidthSB)
+ self.verticalLayout_2.addWidget(self.sizeGB)
+ self.iconGB = QtWidgets.QGroupBox(Form)
+ self.iconGB.setObjectName("iconGB")
+ self.gridLayout = QtWidgets.QGridLayout(self.iconGB)
+ self.gridLayout.setObjectName("gridLayout")
+ self.label_5 = QtWidgets.QLabel(self.iconGB)
+ self.label_5.setObjectName("label_5")
+ self.gridLayout.addWidget(self.label_5, 2, 0, 1, 1)
+ self.fullLabel = QtWidgets.QLabel(self.iconGB)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.fullLabel.sizePolicy().hasHeightForWidth())
+ self.fullLabel.setSizePolicy(sizePolicy)
+ self.fullLabel.setMinimumSize(QtCore.QSize(32, 32))
+ self.fullLabel.setBaseSize(QtCore.QSize(32, 32))
+ self.fullLabel.setAlignment(QtCore.Qt.AlignCenter)
+ self.fullLabel.setObjectName("fullLabel")
+ self.gridLayout.addWidget(self.fullLabel, 2, 1, 1, 1)
+ self.label_3 = QtWidgets.QLabel(self.iconGB)
+ self.label_3.setObjectName("label_3")
+ self.gridLayout.addWidget(self.label_3, 0, 0, 1, 1)
+ self.emptyLabel = QtWidgets.QLabel(self.iconGB)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.emptyLabel.sizePolicy().hasHeightForWidth())
+ self.emptyLabel.setSizePolicy(sizePolicy)
+ self.emptyLabel.setMinimumSize(QtCore.QSize(32, 32))
+ self.emptyLabel.setBaseSize(QtCore.QSize(32, 32))
+ self.emptyLabel.setAlignment(QtCore.Qt.AlignCenter)
+ self.emptyLabel.setObjectName("emptyLabel")
+ self.gridLayout.addWidget(self.emptyLabel, 1, 1, 1, 1)
+ self.trayDefaultB = QtWidgets.QPushButton(self.iconGB)
+ icon = QtGui.QIcon()
+ icon.addPixmap(QtGui.QPixmap(":/icon.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.trayDefaultB.setIcon(icon)
+ self.trayDefaultB.setIconSize(QtCore.QSize(32, 32))
+ self.trayDefaultB.setObjectName("trayDefaultB")
+ self.gridLayout.addWidget(self.trayDefaultB, 0, 2, 1, 1)
+ self.label_4 = QtWidgets.QLabel(self.iconGB)
+ self.label_4.setObjectName("label_4")
+ self.gridLayout.addWidget(self.label_4, 1, 0, 1, 1)
+ self.trayLabel = QtWidgets.QLabel(self.iconGB)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.trayLabel.sizePolicy().hasHeightForWidth())
+ self.trayLabel.setSizePolicy(sizePolicy)
+ self.trayLabel.setMinimumSize(QtCore.QSize(32, 32))
+ self.trayLabel.setMaximumSize(QtCore.QSize(32, 32))
+ self.trayLabel.setBaseSize(QtCore.QSize(32, 32))
+ self.trayLabel.setAlignment(QtCore.Qt.AlignCenter)
+ self.trayLabel.setObjectName("trayLabel")
+ self.gridLayout.addWidget(self.trayLabel, 0, 1, 1, 1)
+ self.fullDefaultB = QtWidgets.QPushButton(self.iconGB)
+ icon1 = QtGui.QIcon()
+ icon1.addPixmap(QtGui.QPixmap(":/dropper_full.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.fullDefaultB.setIcon(icon1)
+ self.fullDefaultB.setIconSize(QtCore.QSize(32, 32))
+ self.fullDefaultB.setFlat(False)
+ self.fullDefaultB.setObjectName("fullDefaultB")
+ self.gridLayout.addWidget(self.fullDefaultB, 2, 2, 1, 1)
+ self.emptyCustomB = QtWidgets.QPushButton(self.iconGB)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.emptyCustomB.sizePolicy().hasHeightForWidth())
+ self.emptyCustomB.setSizePolicy(sizePolicy)
+ self.emptyCustomB.setIconSize(QtCore.QSize(32, 32))
+ self.emptyCustomB.setObjectName("emptyCustomB")
+ self.gridLayout.addWidget(self.emptyCustomB, 1, 3, 1, 1)
+ self.emptyDefaultB = QtWidgets.QPushButton(self.iconGB)
+ icon2 = QtGui.QIcon()
+ icon2.addPixmap(QtGui.QPixmap(":/dropper_empty.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.emptyDefaultB.setIcon(icon2)
+ self.emptyDefaultB.setIconSize(QtCore.QSize(32, 32))
+ self.emptyDefaultB.setObjectName("emptyDefaultB")
+ self.gridLayout.addWidget(self.emptyDefaultB, 1, 2, 1, 1)
+ self.trayCustomB = QtWidgets.QPushButton(self.iconGB)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.trayCustomB.sizePolicy().hasHeightForWidth())
+ self.trayCustomB.setSizePolicy(sizePolicy)
+ self.trayCustomB.setIconSize(QtCore.QSize(32, 32))
+ self.trayCustomB.setObjectName("trayCustomB")
+ self.gridLayout.addWidget(self.trayCustomB, 0, 3, 1, 1)
+ self.fullCustomB = QtWidgets.QPushButton(self.iconGB)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.fullCustomB.sizePolicy().hasHeightForWidth())
+ self.fullCustomB.setSizePolicy(sizePolicy)
+ self.fullCustomB.setIconSize(QtCore.QSize(32, 32))
+ self.fullCustomB.setObjectName("fullCustomB")
+ self.gridLayout.addWidget(self.fullCustomB, 2, 3, 1, 1)
+ self.verticalLayout_2.addWidget(self.iconGB)
+
+ self.retranslateUi(Form)
+ QtCore.QMetaObject.connectSlotsByName(Form)
+
+ def retranslateUi(self, Form):
+ _translate = QtCore.QCoreApplication.translate
+ Form.setWindowTitle(_translate("Form", "Form"))
+ self.positionGB.setTitle(_translate("Form", "Position"))
+ self.topLeftRB.setText(_translate("Form", "Top &Left"))
+ self.topRightRB.setText(_translate("Form", "Top Ri&ght"))
+ self.bottomLeftRB.setText(_translate("Form", "Bo&ttom Left"))
+ self.bottomRightRB.setText(_translate("Form", "Bo&ttom Right"))
+ self.vOffsetLabel.setText(_translate("Form", "Vertical offset"))
+ self.hOffsetLabel.setText(_translate("Form", "Horizontal offset"))
+ self.sizeGB.setTitle(_translate("Form", "Size"))
+ self.label.setText(_translate("Form", "iconHeight"))
+ self.label_2.setText(_translate("Form", "iconWidth"))
+ self.iconGB.setTitle(_translate("Form", "Icons"))
+ self.label_5.setText(_translate("Form", "Full drive"))
+ self.fullLabel.setText(_translate("Form", ""))
+ self.label_3.setText(_translate("Form", "Tray icon"))
+ self.emptyLabel.setText(_translate("Form", ""))
+ self.trayDefaultB.setText(_translate("Form", "Default"))
+ self.label_4.setText(_translate("Form", "Empty drive"))
+ self.trayLabel.setText(_translate("Form", "
"))
+ self.fullDefaultB.setText(_translate("Form", "Default"))
+ self.emptyCustomB.setText(_translate("Form", "Choose..."))
+ self.emptyDefaultB.setText(_translate("Form", "Default"))
+ self.trayCustomB.setText(_translate("Form", "Choose..."))
+ self.fullCustomB.setText(_translate("Form", "Choose..."))
diff --git a/gui/ui/main_settings.ui b/gui/ui/main_settings.ui
new file mode 100644
--- /dev/null
+++ b/gui/ui/main_settings.ui
@@ -0,0 +1,384 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 584
+ 401
+
+
+
+ Form
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+ Position
+
+
+ false
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ 0
+
+
-
+
+
+ Top &Left
+
+
+
+ -
+
+
+ Top Ri&ght
+
+
+
+ -
+
+
+ Bo&ttom Left
+
+
+
+ -
+
+
+ Bo&ttom Right
+
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+ Vertical offset
+
+
+
+ -
+
+
+ -
+
+
+ Horizontal offset
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+ Size
+
+
+
-
+
+
+ iconHeight
+
+
+
+ -
+
+
+ -
+
+
+ iconWidth
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+ Icons
+
+
+
-
+
+
+ Full drive
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 32
+ 32
+
+
+
+
+ 32
+ 32
+
+
+
+ <html><head/><body><p><img src=":/dropper_full.png"/></p></body></html>
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Tray icon
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 32
+ 32
+
+
+
+
+ 32
+ 32
+
+
+
+ <html><head/><body><p><img src=":/dropper_empty.png"/></p></body></html>
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Default
+
+
+
+ :/icon.png:/icon.png
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+ Empty drive
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 32
+ 32
+
+
+
+
+ 32
+ 32
+
+
+
+
+ 32
+ 32
+
+
+
+ <html><head/><body><p><img src=":/icon.png"/></p></body></html>
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Default
+
+
+
+ :/dropper_full.png:/dropper_full.png
+
+
+
+ 32
+ 32
+
+
+
+ false
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Choose...
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+ Default
+
+
+
+ :/dropper_empty.png:/dropper_empty.png
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Choose...
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Choose...
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gui/ui/settings.py b/gui/ui/settings.py
new file mode 100644
--- /dev/null
+++ b/gui/ui/settings.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'gui/ui/settings.ui'
+#
+# Created by: PyQt5 UI code generator 5.10.1
+#
+# WARNING! All changes made in this file will be lost!
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ Form.setObjectName("Form")
+ Form.resize(715, 466)
+ self.verticalLayout = QtWidgets.QVBoxLayout(Form)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+ self.group_list = QtWidgets.QListWidget(Form)
+ self.group_list.setMaximumSize(QtCore.QSize(150, 16777215))
+ self.group_list.setTabKeyNavigation(True)
+ self.group_list.setProperty("showDropIndicator", False)
+ self.group_list.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectItems)
+ self.group_list.setMovement(QtWidgets.QListView.Static)
+ self.group_list.setFlow(QtWidgets.QListView.TopToBottom)
+ self.group_list.setResizeMode(QtWidgets.QListView.Adjust)
+ self.group_list.setUniformItemSizes(True)
+ self.group_list.setSelectionRectVisible(True)
+ self.group_list.setObjectName("group_list")
+ item = QtWidgets.QListWidgetItem()
+ icon = QtGui.QIcon()
+ icon.addPixmap(QtGui.QPixmap(":/setting_tools.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ item.setIcon(icon)
+ item.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled)
+ self.group_list.addItem(item)
+ item = QtWidgets.QListWidgetItem()
+ icon1 = QtGui.QIcon()
+ icon1.addPixmap(QtGui.QPixmap(":/information.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ item.setIcon(icon1)
+ item.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled)
+ self.group_list.addItem(item)
+ self.horizontalLayout_2.addWidget(self.group_list)
+ self.widget = QtWidgets.QWidget(Form)
+ self.widget.setObjectName("widget")
+ self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.widget)
+ self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0)
+ self.horizontalLayout_3.setSpacing(0)
+ self.horizontalLayout_3.setObjectName("horizontalLayout_3")
+ self.scrollArea = QtWidgets.QScrollArea(self.widget)
+ self.scrollArea.setFrameShape(QtWidgets.QFrame.NoFrame)
+ self.scrollArea.setFrameShadow(QtWidgets.QFrame.Plain)
+ self.scrollArea.setWidgetResizable(True)
+ self.scrollArea.setObjectName("scrollArea")
+ self.scrollAreaWidgetContents = QtWidgets.QWidget()
+ self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 539, 413))
+ self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.scrollArea.setWidget(self.scrollAreaWidgetContents)
+ self.horizontalLayout_3.addWidget(self.scrollArea)
+ self.horizontalLayout_2.addWidget(self.widget)
+ self.verticalLayout.addLayout(self.horizontalLayout_2)
+ self.horizontalLayout = QtWidgets.QHBoxLayout()
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout.addItem(spacerItem)
+ self.ok_button = QtWidgets.QPushButton(Form)
+ self.ok_button.setObjectName("ok_button")
+ self.horizontalLayout.addWidget(self.ok_button)
+ self.cancel_button = QtWidgets.QPushButton(Form)
+ self.cancel_button.setObjectName("cancel_button")
+ self.horizontalLayout.addWidget(self.cancel_button)
+ self.apply_button = QtWidgets.QPushButton(Form)
+ self.apply_button.setObjectName("apply_button")
+ self.horizontalLayout.addWidget(self.apply_button)
+ self.verticalLayout.addLayout(self.horizontalLayout)
+
+ self.retranslateUi(Form)
+ self.group_list.setCurrentRow(-1)
+ QtCore.QMetaObject.connectSlotsByName(Form)
+
+ def retranslateUi(self, Form):
+ _translate = QtCore.QCoreApplication.translate
+ Form.setWindowTitle(_translate("Form", "Settings"))
+ __sortingEnabled = self.group_list.isSortingEnabled()
+ self.group_list.setSortingEnabled(False)
+ item = self.group_list.item(0)
+ item.setText(_translate("Form", "Main Settings"))
+ item = self.group_list.item(1)
+ item.setText(_translate("Form", "About"))
+ self.group_list.setSortingEnabled(__sortingEnabled)
+ self.ok_button.setText(_translate("Form", "Ok"))
+ self.cancel_button.setText(_translate("Form", "Cancel"))
+ self.apply_button.setText(_translate("Form", "Apply"))
diff --git a/gui/ui/settings.ui b/gui/ui/settings.ui
new file mode 100644
--- /dev/null
+++ b/gui/ui/settings.ui
@@ -0,0 +1,171 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 715
+ 466
+
+
+
+ Settings
+
+
+ -
+
+
-
+
+
+
+ 150
+ 16777215
+
+
+
+ true
+
+
+ false
+
+
+ QAbstractItemView::SelectItems
+
+
+ QListView::Static
+
+
+ QListView::TopToBottom
+
+
+ QListView::Adjust
+
+
+ true
+
+
+ true
+
+
+ -1
+
+
-
+
+ Main Settings
+
+
+
+ :/setting_tools.png:/setting_tools.png
+
+
+ ItemIsSelectable|ItemIsEnabled
+
+
+ -
+
+ About
+
+
+
+ :/information.png:/information.png
+
+
+ ItemIsSelectable|ItemIsEnabled
+
+
+
+
+ -
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Plain
+
+
+ true
+
+
+
+
+ 0
+ 0
+ 539
+ 413
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Ok
+
+
+
+ -
+
+
+ Cancel
+
+
+
+ -
+
+
+ Apply
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mount/__init__.py b/mount/__init__.py
new file mode 100644
diff --git a/mount/mount.py b/mount/mount.py
new file mode 100644
--- /dev/null
+++ b/mount/mount.py
@@ -0,0 +1,28 @@
+import subprocess, json
+
+
+class MountException(Exception):
+ pass
+
+
+def mount(source, target, fs, options=''):
+ NotImplementedError('Not yet')
+
+
+def is_mounted(device):
+ proc = subprocess.Popen(['findmnt', '-J', device], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ result = proc.wait()
+ if result == 1:
+ return False
+ output = proc.stdout.read().decode('utf')
+ if output:
+ output = json.loads(output)
+ return len(output['filesystems']) > 0
+ else:
+ return False
+
+
+def unmount(device):
+ proc = subprocess.Popen(['umount', device], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ if proc.wait() != 0:
+ raise MountException(proc.stderr.read().decode('utf8'))
diff --git a/packaging/debian/DEBIAN/control b/packaging/debian/DEBIAN/control
new file mode 100644
--- /dev/null
+++ b/packaging/debian/DEBIAN/control
@@ -0,0 +1,16 @@
+Package: kcdemu
+Version: %VERSION%
+Section: utils
+Architecture: all
+Maintainer: Khaelenmore Thaal
+Depends: python3 (>=3.5.0), python3-pyqt5, python3-dbus, python3-dbus.mainloop.pyqt5, cdemu-daemon (>=3.1.0)
+Installed-Size: %SIZE%
+Priority: optional
+Origin: http://silverwing.one/pages/kcdemu
+Description: QT5 application to control CDEmu daemon
+ This is KCDEmu, a QT5 application for controlling CDEmu daemon. It was written
+ in order to create a user-friendly way to interact with CDEmu.
+ .
+ It provides a graphic interface that allows performing the key tasks related to
+ controlling the CDEmu daemon, such as loading and unloading devices, displaying
+ devices' status and modifying device settings.
diff --git a/packaging/debian/DEBIAN/copyright b/packaging/debian/DEBIAN/copyright
new file mode 100644
--- /dev/null
+++ b/packaging/debian/DEBIAN/copyright
@@ -0,0 +1,7 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: KCDEmu
+Source: http://silverwing.one/pages/kcdemu
+Comment: Incorporates icons licensed by CC-BY 3.0 http://www.fatcow.com/free-icons
+Files: *
+Copyright: Copyright 2018, Khaelenmore Thaal
+License: GPL-3+
\ No newline at end of file
diff --git a/packaging/debian/usr/bin/kcdemu b/packaging/debian/usr/bin/kcdemu
new file mode 100755
--- /dev/null
+++ b/packaging/debian/usr/bin/kcdemu
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+python3 /opt/kcdemu/kcdemu.zip
diff --git a/packaging/debian/usr/share/applications/kcdemu.desktop b/packaging/debian/usr/share/applications/kcdemu.desktop
new file mode 100644
--- /dev/null
+++ b/packaging/debian/usr/share/applications/kcdemu.desktop
@@ -0,0 +1,21 @@
+[Desktop Entry]
+Version=1.0.0
+Name=KCDEmu
+Comment=KCDEmu GUI
+Comment[no]=KCDEmu GUI
+Comment[ru]=Графический интерфейс KCDEmu
+Comment[sl]=KCDEmu grafični vmesnik
+Comment[sv]=KCDEmu-GUI
+Icon=kcdemu
+Terminal=false
+Type=Application
+StartupNotify=false
+Categories=System;
+TryExec=kcdemu
+Exec=kcdemu
+X-GNOME-Autostart-Phase=Applications
+X-GNOME-Autostart-Notify=false
+X-GNOME-Autostart-enabled=false
+X-KDE-autostart-phase=2
+X-KDE-autostart-after=panel
+X-KDE-StartupNotify=false
diff --git a/packaging/debian/usr/share/icons/hicolor/32x32/apps/kcdemu.png b/packaging/debian/usr/share/icons/hicolor/32x32/apps/kcdemu.png
new file mode 100644
index 0000000000000000000000000000000000000000..e59f7a69ea22ce0cdf946be3c24faffd6f0dc1ec
GIT binary patch
literal 2189
zc$@)^2y*v{P)P%|JT#6Bs`h!xnF&Awz
zPNOERY0|_J8>(X)EydIU6(}|W1;ikNu*-6Xg@wJH?fabzE-OUsne)zf_Ut*|^SsY{
z`M#sTJs!V!=R|N+!lLI?D(z+{RcRi0z)s;%uu}mK?{47Tj^n*u-yD1Er5}ym%e;!a
z{lDkv*@gOu=yHu#otk0@M{IZ?66X0ss}x_B%|T>t_1n-rFpkE~K{)KLrqKa&e#w?K
zH*lNkeGyO{_{){UI?iwFV-Lo`7$1ZQ7cguG#vBCb2Gm4CO*5#-{MA6P76{XdleU?y
zID4fVcKg(kq750((=GS?5YW6`eznsOt&h%GmVj{rzBLXE5xAD#(5mPG0@o-+66~6d
zbRs$A_M|-7sk$!QFpZdB$$!W|Kb`kNK=bAo|GBv`)evJegreOFm?uO~P9*|qy@3_t
zs8Wh5L~=}T4~2k-+pUCFr@+;YVVrO1G8L^)PxK|>OF;A17hiX+cpzcEAwCpsqx3Wh
zr(xi;fU12|2qAElo5v)9GXi!858mxTaEJ~q<{?xy-|BrOKRv;lgf{`z{?Dr4PfCb+
z_E93*OyDZd9CCXTkbVo#5%`qd4F>_+83`Jvrg%7=JmM2WP}z7B-*lTlDE{%I+vJ>(
z2`IMhd2eaz@>R`0T>T(AZGg+;0!w
zy$4FA0vt&cK2L{!$0WYG($=!GAUmB^uRvhcQyXInw(aW4&rL(`Buxl$^4SQGBk)&=
zrRRN&69LPB9X1<*69ETF&yy-z0@ji@TAI+0EF>}jpM2GTlfT;^d*RHdCN_;~&(Sl*
zS()j-N?sHrCfyv;`>8~8^Bz=p+F>PcSrCJ$ES5!Sg(_)jwPMujgqmK^(!(5qYH1=3
zjqF+%z@etyjLY>`e_66+?LJ08x9`)c`qkNK#<0jBC_S@hIwvJ#$B+Y+*T>w7LD5EZOJ;loid_dCG(iNK7oxMD&ez-sTE&ufW5
zc9cM1--*kXU4_}Vkw{aahNW7boOHtw=a0{7Oz5+U1nMfwpZUYy9jDmiKiz%k2g`Hv
zKV}LdO(JwNyIjjOQcRN)1M&B!
zUbI;zC?}L~JMA4qmLAs85Ed2tzEZ<2a1ugFToBei6a{(}JTEphmjv#Taz&{D{zwFN
z$^`bGxMU$Va8Ynyxue;LKuna5%1VI`t8NHhsVU3lB;*AzdFqD13u_lsuTa9~
z6!!=+@H7F4$2jJ5*q?51LyAqXCsS$Z~quBUaY(R+NRuqC1iJ%R&H)c4
zcw1W+sw>XE{oCDJONB;cSe$CyvF})Y;lt?|vpEEh*^Dp^jSIU9^3fy
z-KVpnmyp*A0Z8lB$EQhfq7p7%F)gdpb9Vx6Nu}nhi}=&wy?M3g&s5Q^k?G}_Ec(@h
zpByqSPJKE%F$oHiN=-y$V7Z&A3Eb+qi?_2#&y&tK8(Y!dSpV1Gy!6Zt!ZS}V$I`|m
zkZi%;k1Hy(mnMfBW21%OXsBjcCu5$gDb?*%sv{0@QePnFECiV6Zkm12)C*H{bTkrN_^m{_ZkkL}qfFu+>$m#Ru53V3`6aM}_WoH}4rRv*8{F
z-Y`(Sc}*To!M+m)eYa%On^Lj}rbOxSv!|CN#)Yk4l_IW$5GU;zlbXr50ZY41M$&Ta
zzZfxh4;62EGEee+V6IK6FA2lTZ~XrCSVPiNYqE`azz~5!dd+0R%TExApy+3%^mnH(_*?{J5}~X-(;iu!y8hW0e;pj6Pc$UNA}BN*!C`tKU{0^t
zJ18&4hDTr<9fZ}~LmSkQn`OrizIOG}`6gCwWW%_}4-@a4@R#x;G9o%I>gi1{Z~aI4@pn)6o4YM^yI+d84fpwJ=Ka4$A`(CrOhzAH&`CMz
zeX!J^EiLGa0_@9pLIP%9|6d=cvnj&*g&)L?v+uhMgl%@HPT-i!W8dR{x5+0Nkhla}
P00000NkvXXu0mjf;9@3Z
diff --git a/packaging/debian/usr/share/icons/hicolor/scalable/apps/kcdemu.png b/packaging/debian/usr/share/icons/hicolor/scalable/apps/kcdemu.png
new file mode 100644
index 0000000000000000000000000000000000000000..e59f7a69ea22ce0cdf946be3c24faffd6f0dc1ec
GIT binary patch
literal 2189
zc$@)^2y*v{P)P%|JT#6Bs`h!xnF&Awz
zPNOERY0|_J8>(X)EydIU6(}|W1;ikNu*-6Xg@wJH?fabzE-OUsne)zf_Ut*|^SsY{
z`M#sTJs!V!=R|N+!lLI?D(z+{RcRi0z)s;%uu}mK?{47Tj^n*u-yD1Er5}ym%e;!a
z{lDkv*@gOu=yHu#otk0@M{IZ?66X0ss}x_B%|T>t_1n-rFpkE~K{)KLrqKa&e#w?K
zH*lNkeGyO{_{){UI?iwFV-Lo`7$1ZQ7cguG#vBCb2Gm4CO*5#-{MA6P76{XdleU?y
zID4fVcKg(kq750((=GS?5YW6`eznsOt&h%GmVj{rzBLXE5xAD#(5mPG0@o-+66~6d
zbRs$A_M|-7sk$!QFpZdB$$!W|Kb`kNK=bAo|GBv`)evJegreOFm?uO~P9*|qy@3_t
zs8Wh5L~=}T4~2k-+pUCFr@+;YVVrO1G8L^)PxK|>OF;A17hiX+cpzcEAwCpsqx3Wh
zr(xi;fU12|2qAElo5v)9GXi!858mxTaEJ~q<{?xy-|BrOKRv;lgf{`z{?Dr4PfCb+
z_E93*OyDZd9CCXTkbVo#5%`qd4F>_+83`Jvrg%7=JmM2WP}z7B-*lTlDE{%I+vJ>(
z2`IMhd2eaz@>R`0T>T(AZGg+;0!w
zy$4FA0vt&cK2L{!$0WYG($=!GAUmB^uRvhcQyXInw(aW4&rL(`Buxl$^4SQGBk)&=
zrRRN&69LPB9X1<*69ETF&yy-z0@ji@TAI+0EF>}jpM2GTlfT;^d*RHdCN_;~&(Sl*
zS()j-N?sHrCfyv;`>8~8^Bz=p+F>PcSrCJ$ES5!Sg(_)jwPMujgqmK^(!(5qYH1=3
zjqF+%z@etyjLY>`e_66+?LJ08x9`)c`qkNK#<0jBC_S@hIwvJ#$B+Y+*T>w7LD5EZOJ;loid_dCG(iNK7oxMD&ez-sTE&ufW5
zc9cM1--*kXU4_}Vkw{aahNW7boOHtw=a0{7Oz5+U1nMfwpZUYy9jDmiKiz%k2g`Hv
zKV}LdO(JwNyIjjOQcRN)1M&B!
zUbI;zC?}L~JMA4qmLAs85Ed2tzEZ<2a1ugFToBei6a{(}JTEphmjv#Taz&{D{zwFN
z$^`bGxMU$Va8Ynyxue;LKuna5%1VI`t8NHhsVU3lB;*AzdFqD13u_lsuTa9~
z6!!=+@H7F4$2jJ5*q?51LyAqXCsS$Z~quBUaY(R+NRuqC1iJ%R&H)c4
zcw1W+sw>XE{oCDJONB;cSe$CyvF})Y;lt?|vpEEh*^Dp^jSIU9^3fy
z-KVpnmyp*A0Z8lB$EQhfq7p7%F)gdpb9Vx6Nu}nhi}=&wy?M3g&s5Q^k?G}_Ec(@h
zpByqSPJKE%F$oHiN=-y$V7Z&A3Eb+qi?_2#&y&tK8(Y!dSpV1Gy!6Zt!ZS}V$I`|m
zkZi%;k1Hy(mnMfBW21%OXsBjcCu5$gDb?*%sv{0@QePnFECiV6Zkm12)C*H{bTkrN_^m{_ZkkL}qfFu+>$m#Ru53V3`6aM}_WoH}4rRv*8{F
z-Y`(Sc}*To!M+m)eYa%On^Lj}rbOxSv!|CN#)Yk4l_IW$5GU;zlbXr50ZY41M$&Ta
zzZfxh4;62EGEee+V6IK6FA2lTZ~XrCSVPiNYqE`azz~5!dd+0R%TExApy+3%^mnH(_*?{J5}~X-(;iu!y8hW0e;pj6Pc$UNA}BN*!C`tKU{0^t
zJ18&4hDTr<9fZ}~LmSkQn`OrizIOG}`6gCwWW%_}4-@a4@R#x;G9o%I>gi1{Z~aI4@pn)6o4YM^yI+d84fpwJ=Ka4$A`(CrOhzAH&`CMz
zeX!J^EiLGa0_@9pLIP%9|6d=cvnj&*g&)L?v+uhMgl%@HPT-i!W8dR{x5+0Nkhla}
P00000NkvXXu0mjf;9@3Z
diff --git a/pics/__init__.py b/pics/__init__.py
new file mode 100644
--- /dev/null
+++ b/pics/__init__.py
@@ -0,0 +1,774 @@
+# -*- coding: utf-8 -*-
+
+# Resource object code
+#
+# Created by: The Resource Compiler for PyQt5 (Qt v5.9.5)
+#
+# WARNING! All changes made in this file will be lost!
+
+from PyQt5 import QtCore
+
+qt_resource_data = b"\
+\x00\x00\x09\x0b\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\
+\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\
+\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\
+\x79\x71\xc9\x65\x3c\x00\x00\x08\xad\x49\x44\x41\x54\x78\xda\xac\
+\x57\x0b\x50\x54\xe7\x15\x3e\x7b\xef\xee\xb2\x20\xe1\xbd\x08\x88\
+\x20\x88\xca\xf2\xa8\x0a\xd4\xa6\x51\xb1\x6a\xad\x71\xd4\xea\xc6\
+\x47\x68\xc7\x41\x6d\x6a\x3a\x99\x69\x13\x8d\xa1\x2a\xf8\x4c\xc0\
+\x21\x16\x93\x31\x26\xa9\x9d\x38\x65\xf2\xb0\x51\x51\x30\x9d\xc6\
+\xb4\x4d\xc2\x63\x51\xa4\xa0\x93\xa2\xf2\x5e\x41\x81\x7d\x2f\xbb\
+\xec\xee\xdd\xf7\xab\xe7\xbf\xb9\x9b\xac\x88\xc8\xb4\xbd\x33\x67\
+\x76\xf7\xde\xff\xfe\xdf\x77\x5e\xdf\xf9\x97\x07\x53\xbc\xb6\x6d\
+\xdf\x11\x99\x98\x90\x78\x42\x24\x12\x15\x27\x24\x24\x88\x92\x67\
+\x24\xc1\xb4\x69\xd3\x80\xa6\x69\x90\xdf\xbb\x07\x3d\x3d\xbd\xb2\
+\x93\x7f\x78\x73\x25\x2e\xf5\x1d\x7d\xbd\x62\x2f\x45\x51\xbb\xbd\
+\x5e\x6f\x92\x58\x2c\x06\x8d\x46\x03\x3c\x1e\x3e\xf0\x7a\x6b\xed\
+\x0e\xc7\x01\x5c\xd7\x8f\xeb\xfc\x64\x5f\xde\x14\xb0\xe9\x03\x65\
+\x87\xfe\x28\x10\x08\x76\x49\x37\x6e\x80\xf9\xb9\x39\xc0\xa3\x28\
+\x50\x28\x95\xd0\xda\xfa\x2f\xfc\x54\x41\x5f\x5f\x5f\xe3\x7b\xa7\
+\x4f\xed\xc0\xb5\x86\xf2\xe3\x6f\xf6\x25\x25\x24\x26\x2c\x5b\xbe\
+\x14\xc4\x71\x71\xe0\xf7\xfb\x11\x9c\x07\x4e\xa7\x13\xfa\xfb\xef\
+\xc1\xd7\xf5\x0d\xa0\x50\x0c\x67\x9e\x79\xff\xbd\x3e\x42\x82\xff\
+\x24\xf0\x7d\x07\xca\x6a\xf2\xf3\xf2\x10\x7b\x3d\x0b\x6c\x65\xac\
+\x50\xdf\xd8\x04\xb2\xe6\x66\xe8\xea\xec\xac\x64\x18\xcb\x70\xb3\
+\xac\xa9\x05\xd7\xba\xdf\xa8\xa8\xec\x9b\x37\x77\x6e\x42\x61\xe1\
+\x12\xe8\xb8\x7d\x17\xae\x5f\x6f\x05\x8c\x04\x6b\xf1\xd3\xe3\x21\
+\x37\x3b\x0b\x92\x93\x93\xe0\x83\xb3\x7f\xee\xc1\xf5\x22\x34\xe7\
+\x64\x04\xe8\xfd\xa5\x07\x6b\xf2\x0b\xf2\xa4\x9b\xa4\x1b\x61\x74\
+\xd4\x00\x06\xa3\x11\x1a\x10\xbc\xb7\xb7\x57\xff\x56\xd5\x89\x2d\
+\xb8\x46\x87\x66\x46\x63\xf6\x96\xec\x7f\x7e\x46\x62\x12\x82\x2f\
+\x46\x82\x32\xf0\x7a\x3c\x80\x51\x03\xb3\xd9\x4c\x3c\x86\xae\xee\
+\x2e\x68\xa8\xaf\x67\x83\x4e\xd3\x14\xd9\x3f\x6c\x32\x02\x24\xec\
+\xe8\x79\x3e\x82\x6f\x80\x07\x43\x23\x60\xb5\x59\xa1\xa9\x49\x06\
+\x72\xb9\x5c\x81\xe0\x5b\x71\xcd\x10\x09\x39\x9a\x0b\x4d\x18\x11\
+\x11\x71\x94\x84\xbd\xe3\x76\x27\x0b\x4e\x51\x34\x5b\x1f\x26\x93\
+\x09\x5e\x3f\x7a\x64\x0d\x89\x50\xd0\xfe\xc6\xc0\x17\x6a\x62\xf0\
+\xc3\x35\x3f\x2c\x28\x90\x6e\x7a\x6e\x03\xdc\x7f\x30\x84\x9b\x8c\
+\x01\x85\x79\x8c\x8d\x8d\x01\xc6\x62\xf9\x86\x03\xd7\xa0\xd9\xd0\
+\x3c\x68\x21\x98\xeb\xe9\xf1\x71\x62\x50\xab\xd5\x6c\xce\x29\x8a\
+\x07\x7c\x3e\x9f\x2d\x3e\xbc\x06\xd1\xee\x04\x19\xf9\x6d\x25\x0f\
+\xc6\x47\x80\x7f\xf0\xf0\xb1\xda\xfc\xbc\x85\xeb\x49\xce\x07\x06\
+\xef\x63\x08\x19\x70\xb9\x9c\xe0\xf1\x7a\x61\xeb\xe6\x4d\x40\x53\
+\xd4\xba\x88\xc8\x48\xa8\x3a\x51\x29\x0d\x7a\x4f\x40\x8a\xcd\x6c\
+\xb1\x00\x41\x24\x04\x88\xf7\x42\xa1\x20\xd8\x63\xed\x44\xa1\x1e\
+\x1f\x81\x50\x1e\x8f\xe2\xc0\x07\x11\xdc\x84\xd5\xeb\x00\xa7\xc3\
+\x09\x7a\xfd\x28\x16\xde\x75\xd8\xf4\x9c\x94\x6c\xbe\x8e\xac\x0d\
+\x7e\x91\x10\xe0\xd3\x7c\xb6\xad\xbe\x05\xe7\xb3\x35\xf0\xa4\x46\
+\xa3\xc6\x15\x5d\x75\x7e\xde\x02\x50\xaa\xd4\x30\x36\x66\x06\xbb\
+\xc3\x05\x0e\x87\x03\xec\x84\x04\xb6\x91\x46\xab\x83\xaf\xea\x1b\
+\x59\x30\xae\x8a\xbf\xbb\xbc\x1e\x97\x6a\x70\x70\x00\xe2\xb1\xef\
+\x05\x02\x3e\x92\x40\xe3\xd3\xe0\xf7\xf9\x26\x25\x40\xe8\xf1\xcb\
+\x8f\x57\x5e\x11\x8b\xe3\xd7\xa6\xa6\xcc\x84\x8c\xd9\xe9\x6c\xde\
+\xc3\xc3\xc3\x11\xd8\x09\x2e\x34\x07\x12\x21\x69\x40\x61\x81\x81\
+\x81\x41\xc0\x64\x83\x52\xa1\xa8\x3b\x74\xa8\x8c\x74\x82\x17\x2d\
+\x76\xf7\x9e\xbd\x75\x45\x45\x45\x4b\xa3\xa3\xa3\xa1\x5f\x2e\x67\
+\xf3\x2f\x0a\x09\x01\x35\x8a\x50\x7b\x7b\x3b\xdb\x8a\x2c\x20\x5b\
+\x1f\x14\x54\x1e\x2f\x0f\x21\x05\xcc\xe7\xc2\xbe\xf6\xc5\x5f\xef\
+\x64\x17\xd4\x37\x34\xc1\x20\x86\x9f\x14\x4f\xea\xac\x34\x04\x76\
+\x81\xc7\xed\x62\x9f\x0d\x0d\x0d\x83\x48\x24\x64\xd3\x30\x66\xd0\
+\x49\x9f\xa2\x7d\xe7\x77\x97\x1e\xda\xf6\x46\xe9\x81\x33\xab\xd6\
+\xae\x5f\x3a\x6f\x6e\x06\xfc\x1b\xfb\x9f\x44\x08\x15\x93\x25\x91\
+\x9e\x9e\x0e\x59\x12\x09\xfb\x5b\x20\xa0\x41\xa5\x54\xc3\xe5\xba\
+\xba\x01\xae\x0d\x59\x02\x42\x8b\xc5\xd4\x54\x73\xa9\x76\x99\x04\
+\x17\x86\x86\x8a\xd8\xc0\x58\x2c\x0c\xf6\x7b\x1f\xe8\x74\x1a\xf6\
+\x37\xd9\x34\x32\x22\x02\x16\xcc\x9f\x0f\xb7\xda\xdb\x20\x2b\x27\
+\x1b\x3e\x0e\x5f\xb6\x39\xe6\xc8\xe7\x52\xcf\x1c\xa0\xf3\x7f\x90\
+\x05\x6d\x1d\x9d\x60\xb7\xdb\xd0\xec\xac\xf7\x31\x51\x51\xec\x7b\
+\x6c\x81\xa2\x1e\x74\xf7\xf4\x40\x77\x77\x37\xea\xc1\xd7\x7b\x83\
+\x53\xf0\x14\x5a\xda\xde\x92\x7d\xa7\x33\xe7\x49\x0a\x67\xcf\x49\
+\x07\x37\x7a\x2d\x97\x0f\x40\x6f\x4f\x77\x7d\x5b\x5b\xeb\x25\x01\
+\x5f\xc0\xc3\x28\xf8\xd3\x52\x66\x6e\xd9\xb8\x61\xc3\xf2\xa8\xb8\
+\xe9\xf0\xea\x37\x6e\xd8\xb3\x76\x25\xb4\xa9\x18\x68\x1f\x52\x41\
+\x8a\x5b\x05\xaf\x66\xf8\x60\x48\xa3\x87\xe6\xeb\x2d\x8a\xc8\xc8\
+\xc8\x18\xac\x94\xd0\x69\x61\x61\xe8\x8c\x85\x05\xc3\xa2\x6e\xf9\
+\xe0\x4f\x67\xf6\xe1\xd7\x07\x5c\x1b\xbb\x78\x5c\x2b\x46\xa1\xa5\
+\x20\x89\xb7\x67\xcf\xce\x28\x4c\x4e\x4e\x86\x8e\x8e\x0e\x38\x54\
+\xb6\xff\x69\xbc\xaf\xe7\x44\x84\x94\x74\xcc\xd6\xcd\x9b\x8f\xf5\
+\x2e\xda\xb5\xe6\xb7\xcf\x2e\x83\xbb\x3a\x3b\xa8\x19\x07\xb8\xfd\
+\x14\xdc\xbc\x37\x08\xeb\x68\x39\xf8\xee\x36\x6a\xcf\x9c\x3d\xfb\
+\x4b\xae\x36\x28\x1c\x5c\xe1\xa8\x0d\x56\x4e\x2f\x4c\x1c\x70\x40\
+\xc0\xfc\x34\x99\x5e\x44\x12\xc9\x8d\x1b\x2d\xd7\x5b\x25\x59\x39\
+\xb9\x18\xae\x99\x8c\xc5\xd4\x72\xed\x5a\x73\x35\xde\x57\x05\xe4\
+\x36\x74\x45\x71\x8c\x75\xc5\x4b\x55\x25\x6b\x16\xc3\x6d\xad\x1d\
+\x54\x16\x3b\xb8\x7d\x7e\xf0\xa0\x45\x84\x47\x81\x6c\xd8\x00\x8d\
+\x6f\x97\xfc\x0c\xd7\xde\x47\x53\x12\xf2\x0c\xc3\x28\x39\x0d\xd0\
+\x70\xd2\x6d\x0a\x56\xc5\xe0\x26\x25\x64\xa2\xd1\x92\xd0\x22\x38\
+\xf1\x20\x8a\x47\xe2\xc7\x8b\x79\xe9\xdd\xd9\xe9\xd9\x79\xfd\x15\
+\xeb\x7f\x0c\x57\xe5\x46\x50\x5a\x1c\x2c\xb0\x1b\xfd\x74\xfb\x79\
+\xa0\xc0\x39\x11\xe6\x35\xc1\x26\xfd\x97\x9f\xed\x3b\x7c\xa4\x08\
+\xdf\x71\x04\x61\xf8\x1f\x3b\x70\x82\xb5\x84\x0b\x0b\xc3\x81\x93\
+\x30\xd9\xc9\xfd\x98\xdf\xbc\x93\x86\xe0\xf2\xf2\xf5\xcf\xb0\xe0\
+\x2a\xc6\x8e\xe0\xc0\x82\x7b\x10\x5c\x89\xe0\x5e\xfd\x00\x7c\xb5\
+\x2d\x1b\x24\x92\x79\x99\xb3\xe2\xc5\x99\x7f\xaf\x6f\xa8\x9d\x0c\
+\x78\xa2\x08\x4c\x28\x54\xb1\xfb\xce\x17\x2f\xca\x9c\x5b\xfd\xf2\
+\xf2\x85\xf0\xc5\x38\x70\x2f\x82\xab\x71\x4e\x78\x0c\x0f\xe0\x54\
+\x9e\x00\x68\x9e\x0f\x7e\x94\xbf\x00\x76\xfe\xb5\x0f\xda\xf5\x3e\
+\x30\x99\xcd\x57\x74\xc7\xd6\x6d\xe1\xf2\x0f\x53\x91\xe2\x87\x9e\
+\xc5\x94\x9c\xdb\xbe\x7c\x7e\x4e\x75\xd9\xb3\x0b\xe1\x6a\xbf\x81\
+\xcd\x79\x30\xb8\xce\x6c\x84\x61\xad\x12\x24\xb7\xaa\xff\x69\xd0\
+\xa9\x41\x80\x02\xb3\xf1\xc3\x76\x18\xa3\x22\xe0\x2f\xdb\x97\xc2\
+\xb2\x79\x69\x1b\xe3\x0e\xd6\x5d\x1c\x17\xe9\xc7\xa6\xe0\x21\xf0\
+\xe8\xd7\xce\x15\xaf\x58\x38\xbf\x7a\xf7\x8a\x6c\xb8\x70\xd7\xc0\
+\x56\xbb\xd7\xff\x3d\xb8\xde\x6c\x00\xa5\x7a\x04\x8c\x1f\x96\x15\
+\xdd\x6e\xfe\xf2\x6a\x98\x50\x30\xeb\x6f\x82\xec\x34\x41\x7c\x1a\
+\xfc\x7e\x55\x0e\xfc\x43\x6e\x81\x5f\x2c\x48\x06\xb5\xd5\x2b\xd1\
+\x49\x56\xe5\xda\x64\x17\x2e\x4d\x94\x92\x89\x52\xc0\x8f\x2e\xab\
+\xbb\xf8\x62\x61\x81\x74\x6d\x6e\x32\x5c\xec\x1c\x45\x70\xe7\x04\
+\xe0\xc3\x60\xbc\x50\xf9\xbc\xed\x4e\xd3\x4d\xae\xe0\x12\xe7\xbe\
+\x73\xe3\xe6\x27\x3b\x9e\x86\xb7\x1a\xb5\x60\x45\x2d\x99\x15\x15\
+\x06\x5b\xb3\x63\xe0\x54\x43\x27\x34\x76\xf5\xd7\xe9\xcb\xa5\x01\
+\xe9\x7e\x6c\x04\xe8\xe8\xd2\xda\x9a\x3d\xab\x17\x4b\x7f\x9a\x99\
+\x08\x17\xbb\x46\x41\xf3\x38\xf0\x4f\x2b\xb6\xd8\xee\x36\xb7\xe1\
+\x3b\xea\x40\xa7\xf0\x72\x56\x14\x5c\xe9\xb5\xa6\x7a\xa9\x70\x20\
+\x87\x1e\x87\xdb\x03\x23\x16\x0f\xfc\x6a\x51\x2a\x68\x30\x12\xda\
+\x09\x22\x41\x3f\xe4\x79\x69\xed\x25\x02\x5e\x98\x11\x0f\x35\xe8\
+\xb9\xc6\x3a\x31\xb8\xb3\xab\xa5\xc2\x54\xff\xc9\xe7\x1c\xb8\x93\
+\xd3\x12\xbf\xad\xa5\xf6\x46\x68\xee\x92\x6c\x0f\x5f\x98\x1a\x22\
+\x8a\x64\xe3\x6b\x77\x4d\x4e\x22\x40\x80\x16\x97\xd5\x9e\xdd\xf5\
+\x93\x45\x45\x2b\xd1\xf3\x47\xc1\xb1\x2f\x19\x0b\x28\x54\x0f\xc0\
+\xd9\x79\xed\xb8\xfe\xd3\xf2\x8f\x38\x81\x72\x04\x79\x43\x48\xb8\
+\x99\x1b\x9f\xb5\x86\xe4\x2c\x91\xb8\x69\x61\xaa\x30\xf4\xc9\x24\
+\x02\x04\xa6\xc5\xae\x79\xe1\x62\xe9\xea\x05\x58\x70\xc1\xe0\x7e\
+\xd6\x73\x06\x0f\x24\x23\xca\x01\x70\xdc\x91\x55\xe8\xcf\x1f\xff\
+\x98\x03\xb7\x8f\x2b\xaa\xef\x14\x75\x32\x12\x2f\x20\x09\xad\xcd\
+\x27\xd1\x67\xaf\xce\xb3\x36\x9d\xaf\x09\xb4\xa1\x68\x8e\x38\x0a\
+\x1f\xe0\xce\x98\xf3\x6f\x5b\xed\x7b\xf0\xfb\x23\xfd\xe0\x36\xa8\
+\x65\x08\x7e\x8e\x93\x58\x2b\x07\x38\xfe\xf2\x06\x14\x54\x75\x72\
+\xe7\x1e\x46\xd1\x23\xd3\x6a\x87\xc1\x8a\xc2\x6b\x45\x25\x18\x36\
+\x39\xe0\x72\xb7\x11\x4a\x56\x65\xe1\x1f\x02\x01\x7b\xaa\x0a\x10\
+\xa0\xd4\x16\x1b\x50\x38\x96\x08\x38\xb1\x60\x70\xcf\xa8\x4a\xa6\
+\xaa\xda\xfe\x0a\x97\xf3\xc7\x81\x4f\x4a\xc2\x86\x24\x6c\x48\x42\
+\xc9\xb8\xe0\x95\xda\x5b\xc0\x63\x8c\x4d\x64\xc0\x05\x52\x10\xc6\
+\x2b\xf8\xf9\x96\xac\x99\x71\x51\x1a\x3b\x1f\x9c\xb8\x85\x19\x8f\
+\xe1\x43\x8a\x7b\xa8\x72\xaa\x66\xd5\xc9\x1d\x7b\x70\xcd\x30\x37\
+\x94\xbc\x53\xf8\x37\xe5\x7f\x24\x1d\xfc\x90\x54\x51\x58\x34\xb2\
+\x1a\x82\x11\x79\xa7\x4c\x53\x55\xfc\x32\x3e\x1f\x0d\xe8\x00\x26\
+\x0a\x32\x12\xca\x2e\xd7\xf2\x44\x61\x29\x78\xa2\x66\x8f\x4e\x5e\
+\xed\xf0\x17\xaa\x53\xbb\x0e\x72\x43\xc9\x38\x45\x70\x98\x60\xc0\
+\xa5\x24\xbe\xf6\xd1\x69\xa1\x38\xf9\x19\x97\x6e\xa4\x45\x55\x55\
+\xfc\x3b\x6e\xcf\xb1\x00\x01\x32\xeb\x63\xd1\x66\x70\x93\x90\xe2\
+\x46\xa6\x91\x2b\xb8\xb1\xc9\xf4\x7c\x8a\x24\x02\x53\xd6\xcc\xd5\
+\x11\xeb\x50\xb0\x12\x0a\xb8\x73\x9a\x90\x53\x48\x1f\xd7\x66\xf6\
+\xff\xc2\xf3\x47\xd4\x95\x3b\xc6\x0b\x38\xc7\xec\xff\x83\x43\xff\
+\xdf\xeb\x3f\x02\x0c\x00\x74\x6a\x81\xfc\x7e\x57\x10\xb3\x00\x00\
+\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x08\x8d\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\
+\x00\x00\x08\x54\x49\x44\x41\x54\x78\xda\x9d\x57\x6b\x50\x54\xe7\
+\x19\x7e\xf6\x7a\x58\xae\xcb\x4d\xe4\xba\x28\xa2\x2e\x21\x04\x21\
+\x42\x8c\xa9\xad\x84\xd4\x8c\x91\x06\xeb\x40\xeb\x4c\xaa\xed\x24\
+\xea\x4c\x6a\xc6\x5c\x8a\x11\x93\xfa\x83\x52\xb5\x31\x17\x6d\x32\
+\x4e\xa3\x26\xad\x69\xd2\xc4\x12\x1b\xaa\x63\x1b\x2d\xc5\xd4\x01\
+\x15\x28\x36\x02\x05\xc4\x20\x82\xb0\xcb\x72\x87\x85\x85\xbd\x9d\
+\xed\xfb\x9d\x0b\x2e\x2b\x44\xed\x99\xf3\xce\x77\xf6\xec\x39\xdf\
+\xf3\xbc\xcf\x7b\xf9\xbe\xa3\xc0\x3d\x1e\xbf\x78\xe7\x44\x70\x54\
+\xc2\xa2\xe7\x54\x2a\xed\x66\x28\x55\x69\x1e\x78\xc0\x4e\xe1\x50\
+\xb0\x53\x01\x0f\xef\x6e\xe0\xdd\x8e\xe3\xbd\x5d\xdf\x1c\x7b\xeb\
+\xa5\x1f\x8d\xdd\xcb\xbc\x8a\xbb\xfd\xbf\xe7\xe8\xd9\x85\xfa\x88\
+\xe8\x72\x8d\x56\x9d\x9a\x62\x08\x47\x6c\x78\x20\x12\xe6\xf9\x43\
+\xab\x14\x5f\x96\xcd\x41\x64\x6e\xf5\xdb\xd0\x3d\x30\x8e\xc6\xce\
+\x41\x38\xec\xae\xa6\xd1\x01\x73\x7e\xc9\x96\x35\x37\x70\x9b\xea\
+\x7d\x11\x50\x1d\xf8\xcb\x95\xc3\x3a\xce\x6f\xeb\x63\x0f\xc6\xc1\
+\x18\x1f\x84\x09\x17\x30\x6c\x07\xc6\x1c\x04\xe8\x06\xd4\x44\x42\
+\x4d\x33\xa8\xc8\xfc\xd5\x40\xb0\x16\x08\xd3\x8a\x93\xb6\x99\xad\
+\x38\x7b\xa5\x1b\x76\xfb\xd4\x91\xa2\x0d\x19\xcf\xd3\x2d\xf7\xfd\
+\x10\xd0\xbc\x5d\x7e\xb5\xd3\x10\xad\x8f\xce\x59\x96\x80\x71\x02\
+\xbe\x35\x0e\x0c\x11\xb8\x96\xde\xd0\xaa\xe8\x01\x02\xd7\x28\x44\
+\x12\xec\x9a\x8d\x74\x22\x39\xe4\xf6\xa4\x3c\xd9\xa9\xba\x2e\xdc\
+\x30\x8d\x98\x5f\xc9\x7f\xc8\x40\x3f\x9d\xf7\x42\x40\xf3\xe6\x17\
+\xff\xb9\xb9\x32\xd5\x10\x63\x34\x84\xa2\xdd\x0a\x98\x27\x44\x50\
+\x4e\x25\x02\x69\xbd\x81\x15\xe2\xa8\x52\x8a\x2a\x44\x72\x4c\x6f\
+\x0f\x85\x80\x87\xdb\xad\x84\x56\xa7\xc0\xd5\x8e\x61\x9c\x6f\xe8\
+\x32\x15\xad\x4f\x4f\xf4\x25\xe1\x4b\x40\xf3\xd6\x17\x5f\x77\xad\
+\x78\x20\x61\xbe\x21\x3e\x14\x6d\xa3\xf4\x34\x09\xa7\x61\xe0\xb3\
+\x80\xaa\x7d\x54\x08\x21\x70\x95\x9b\xc7\x24\x81\x33\x02\x76\x07\
+\x0f\xde\xed\x41\x70\x88\x0e\x2d\xe6\x21\x54\x35\xdf\xea\x7d\x25\
+\x3f\x3d\xc1\x9b\x84\x37\x01\xd5\xfe\xcf\xaa\xdf\x4f\x4a\x88\x79\
+\xf6\x51\x22\xda\x4c\xe0\x2a\xcf\x1c\x72\x7b\x13\x90\x7e\x0b\xcf\
+\x11\xf8\x94\xdd\x0d\x07\x01\xdb\x19\x09\x1a\xa7\xa6\x78\x38\x9d\
+\x3c\xe2\x13\x43\x50\xd9\x78\x13\xdf\x74\x9b\x3f\x28\xfe\xf1\xa3\
+\xdb\xe4\x9c\x90\x09\x28\xb6\xed\x79\x6f\x69\xea\xf2\x55\xcd\x3f\
+\x5c\xfd\x20\x3a\x6d\x80\xcb\xe3\xe3\xf5\xb7\x90\x10\x2a\xc2\xc3\
+\xc3\x66\x75\x09\x5e\xcb\xe0\x6c\x64\x84\xa6\xa6\xdc\xe0\x38\x35\
+\x12\x92\x82\xf1\xe7\xea\x66\x34\xd5\xfe\x2b\xe5\x48\xc9\xf6\x56\
+\x56\x1d\x32\x01\xed\x81\xb2\x9a\xaf\xbf\x93\xb1\xc4\x18\x18\x1e\
+\x22\x64\x39\x37\x07\xe8\x0c\xaf\x95\x62\x78\xd8\xf5\xe8\x98\x13\
+\x56\x22\xc0\xa4\x77\x38\x45\xf9\xa7\xa6\x5c\xe4\xbd\x07\x4a\xa5\
+\x02\x1c\x49\x14\x3e\x4f\x87\x7e\xc7\x24\xbe\xba\xd2\xd6\xb2\xb3\
+\x20\x3b\x9d\x55\xaf\x40\x60\xd5\x53\x1b\x63\x0a\xb6\xed\xea\xc9\
+\xcf\x4d\x43\xef\x24\x4d\x08\x71\xf2\xd9\x08\x90\x23\xf0\x57\x89\
+\xa5\xe7\x7d\x8c\x13\x01\xcb\x80\x1d\x36\x1b\x81\x13\x01\x07\x49\
+\xcf\x93\x2a\x5a\x02\xd6\x92\x37\x5a\x9a\xd0\x8f\x2c\x24\x32\x00\
+\x9f\x7c\xd5\x80\x93\xbf\xdb\x1f\x7b\xe1\xcc\xa7\x26\x36\x8d\x6a\
+\xcf\xd1\x33\xc5\x59\x99\xe9\xbf\x4a\x5e\x14\x23\x26\xdd\x1c\xd2\
+\xfb\xa9\x44\x73\xf3\x1e\x54\x77\xda\x61\x25\x6f\x59\x10\x83\xa8\
+\x2c\x96\x45\x69\x85\x2a\x69\x69\xb5\x62\xd4\xea\x84\x9a\x5e\xd0\
+\xd2\xc3\x1c\x81\x6a\x69\x22\x0d\x8d\xec\x5a\x17\xc0\xa1\xa6\xdd\
+\x8c\xcb\xf5\x57\x7f\x59\xb2\x65\xed\x3e\x46\x40\xb7\xef\xd3\xaa\
+\xfa\xd5\xd9\x69\xc6\xb0\xc8\x20\x28\x3d\xb3\x67\x3a\x27\x25\x63\
+\xc7\x90\x03\x95\xd7\xc7\xe1\xa6\xe8\x29\x15\xa2\x0c\xc4\x87\x14\
+\xf1\x60\x45\x9c\x0e\x89\x7a\x35\x9a\xaf\x59\x49\x76\xf2\xd8\x4f\
+\x04\xd6\x7a\x59\x00\xd5\x6a\xb3\x79\x1c\xe7\x2e\x35\xb6\x14\x6f\
+\x5c\x99\xc9\x66\x08\x7d\xf3\x64\xed\xd0\xfa\x27\x96\x0b\xf1\xa4\
+\xf3\x0e\xe9\xe5\xfb\xcc\xf3\xc3\x17\x06\xc0\xd1\x4d\x16\x02\x09\
+\x1f\x1e\x8f\x48\x02\x0a\x1e\x85\x69\xa1\x70\x52\xd2\xf5\x0d\x3a\
+\x88\x80\x4a\x00\xd5\x48\x2a\x70\x9c\xa8\xc2\x28\x35\xb4\x0f\xff\
+\x56\x87\xa2\x0d\x59\x61\x6c\x8a\xc8\x03\x65\xb5\x7d\x3f\xc9\x5b\
+\x2e\x78\xaf\xf2\x06\x96\xae\xe5\xa3\xa2\x65\x04\xd7\x2c\x93\xd0\
+\xd0\x4d\xfb\x84\xd5\x54\x51\x76\xf4\x20\xbb\x9f\x5b\xb0\xe5\x45\
+\x2e\x20\x28\xc6\x49\x35\x9f\x1c\xce\xe1\xf1\xa5\x7a\x74\xf6\x4c\
+\xce\x20\xc0\xc0\x59\x32\xd2\x09\x6a\xac\x78\xf7\x64\x1d\x76\x16\
+\x64\xcd\x63\x04\xe6\xbd\x51\x56\x63\xd9\x92\x9f\x25\xc8\xae\x94\
+\x6a\x5f\x3d\x4b\x8f\xfc\xbc\xde\x02\xf3\x08\xc5\x97\x58\xfe\xb3\
+\xec\xc8\xf6\x93\x87\x4b\x2a\xd9\xfd\x0d\xcf\xef\xc9\x79\xbc\x60\
+\xeb\x7b\x2e\x22\x10\x15\xac\x41\xe1\xc3\x51\x30\xf7\x3b\xc0\xf9\
+\x89\xc0\x8c\xb0\xb8\x62\x42\x20\xc0\x6a\xef\x9d\xcf\x6b\x89\x40\
+\x76\x94\x40\x60\xdf\x89\xcb\x96\x5d\x85\xd9\xb7\x91\x48\x53\xa1\
+\x86\xa5\x5a\x9e\x9c\x74\xc3\x10\xe7\x8f\xcf\x6a\x4c\xe8\xb3\x8a\
+\x04\xea\x2a\xcb\x9f\xf9\xc3\xde\x1d\xa7\xd8\xe3\x3f\xdd\x7d\xe8\
+\x07\xcb\x73\xf2\x3f\x66\x0a\x44\x87\x68\x51\x98\x1d\x2d\xf4\x11\
+\x39\x3c\xd4\x9f\x40\xd5\x08\x17\x2f\x76\x1f\x05\xf1\xf9\xa0\xbc\
+\x06\xaf\x16\x3e\x72\x27\x01\x37\x4d\x22\x74\x33\xbb\x5c\xcb\x34\
+\x52\x4c\x93\x12\x03\xf1\xf7\xa6\x5e\xb4\x59\x26\x28\x27\x94\x70\
+\x3b\xed\x1d\x43\x96\x1e\xd6\xd1\x10\x16\x15\xfb\xbe\x52\xc3\x2d\
+\x70\x12\x42\x4a\x5c\x10\xd6\x3e\x14\x05\x7a\x15\x3c\x2f\x36\x34\
+\x97\x04\xee\x92\x72\x45\xa9\x01\xfe\x48\x04\x76\xca\x04\xf6\x9f\
+\xb8\x64\x21\x36\x70\x51\x70\x6c\x93\xce\x69\x70\xb9\xa3\xd9\x88\
+\x40\x4c\x94\x8e\xca\x4a\x81\x0f\xab\x6e\x08\x5e\xa9\x29\xcb\xe5\
+\x24\xe4\x05\x2f\x79\xea\x86\xc0\x0b\x6b\x17\x53\xaf\x50\xc2\xe6\
+\x14\xf7\x08\x32\xf0\x34\x01\x88\xe3\xa9\x2f\x2f\xa3\xa8\x70\x85\
+\x40\x20\xb2\xe4\xa3\x8a\xea\x8d\xdf\xcb\x48\x9e\xaf\x0f\xa6\x46\
+\xe2\x14\xa5\x77\x88\x0b\xca\x14\x33\x0a\x81\x92\x64\xcf\x48\xd7\
+\xa3\xb5\x67\x14\x5f\x36\x98\xc8\x3b\xb1\xc3\x41\xaa\x0e\x96\x33\
+\xeb\xb2\xe2\x91\x12\x1b\x02\x2b\x03\xe7\xef\x04\x67\x23\x8b\x7f\
+\x6f\xff\x18\x2e\x5e\xac\xbb\xfe\xda\xa6\xdc\x95\x42\x19\x6e\xdf\
+\x7b\xe4\xf5\x75\xb9\xb9\x2f\x67\x2e\x88\xc5\xf8\xb8\x48\x40\x06\
+\x77\xd0\xce\x83\x01\xb1\x4c\x0e\x0a\xd2\x20\x39\x31\x00\x36\x87\
+\x8b\x16\x16\x33\x46\x6c\x76\x81\x80\x3e\x40\x8b\x27\x33\xe2\x85\
+\xf2\x64\x6d\x9c\x04\xbb\x03\x58\xce\x01\x0f\x25\x78\x5b\x5b\x17\
+\xaa\x2b\xce\xbe\xfd\xdb\xdd\x5b\x4b\x85\x46\x64\x58\x9c\x6a\xdc\
+\xb1\xef\x58\x7d\xe1\xc3\xe9\x18\xb3\x39\x04\x8f\xd9\x92\xca\x93\
+\xac\x1a\x6a\x02\x2c\x93\x59\x1d\xab\xc8\xfc\xa9\x0f\x47\x84\x71\
+\xd0\xf9\xcd\x2c\x13\x06\xc0\xc0\xed\xee\xd9\x3d\x67\xa3\x9b\x25\
+\x26\xb5\xf2\xaa\xca\x1a\x1c\x2a\x7e\x2e\xb3\xb3\xad\xa9\x45\x68\
+\xc5\x64\xf3\x4b\x3f\x3a\x77\x21\x7b\x91\x71\x61\x38\x47\x5b\x2f\
+\x5a\x44\x14\x14\x60\x3f\x56\x42\xdc\xcc\x4e\xa6\x66\x3d\x9d\xda\
+\xa2\x1f\x11\x71\x49\x32\x3b\xf9\xb9\x41\x5d\x5e\x89\xc8\xe4\xef\
+\xb3\x0c\xa1\xa5\xfe\xdf\x37\x76\x6f\x5e\xb3\x8a\x70\x7b\x65\x37\
+\xf4\x59\x39\xeb\x1e\xdb\xf8\xf3\xdd\xa7\xb3\xa2\x97\x90\xd7\x0a\
+\x01\x48\xeb\xd5\xc7\xa7\x49\x70\xa2\x12\x2e\x5e\x31\x2d\xab\xd3\
+\x73\x77\x02\x6e\x49\xa5\xe6\xaa\x8b\xf8\xd3\xe1\xbd\x79\xb5\xe7\
+\xcf\x54\xd1\xad\x91\xe9\xe5\x98\x2c\xfa\xd5\x83\x9f\x1c\x32\x2e\
+\x4e\x7d\x3a\x3b\x31\x09\x0a\x92\x4a\x4d\x44\x64\x60\xb9\x9b\xa9\
+\x09\xdc\xea\xb8\x8b\xb7\xb3\x48\xcf\x93\xce\x37\x1b\x5b\xd1\xde\
+\x58\xff\xd7\xdf\xbc\xf4\xcc\x0e\xc2\x33\x4f\x2f\xc7\xd2\xc6\x24\
+\x90\x6c\xc1\xde\x8f\x2b\x2a\xb3\x97\x26\x87\x1b\x63\xa3\x85\xe0\
+\x68\xa8\x66\x59\x27\x63\x9e\xab\x29\xd5\xed\x54\xaa\x23\x0e\x71\
+\x52\x5f\x20\xe7\x2c\x04\x98\xe7\x6e\x9a\x7d\xd0\xd4\x0b\x53\x73\
+\xe3\xe0\xae\x4d\xdf\xcf\xa1\x5b\x1d\x6c\x05\xf7\xde\x90\x40\xca\
+\x85\x70\xb2\x85\xa5\xc7\xcf\x9d\xfe\xee\x32\x63\x44\x66\x72\x9c\
+\xb0\xd5\x55\xa9\xc5\x07\xd8\xb2\x60\x99\x02\x28\x47\x85\xee\x76\
+\x37\xef\x19\x30\xb3\xe1\x1e\x06\xde\x30\x50\xbc\x79\x4d\x1e\x4d\
+\xc1\xbe\x13\x06\x7d\xb7\x64\xd3\x9b\x52\xb2\x08\xa6\x44\xe9\xf1\
+\xb3\xa7\x97\x24\xc6\x85\xad\x5f\x95\x22\xae\x84\x10\x27\xed\x18\
+\x93\x9a\xc9\xb7\x01\x4b\xbb\x4e\x46\xd2\x72\xed\xbf\x18\x31\x77\
+\x0f\x15\x6f\x7a\x32\x4f\xf2\x7c\x60\xae\x4d\xa9\x2f\x09\xc3\xcb\
+\x6f\xfc\xfe\xf5\x58\x43\xd2\x53\x6b\xb2\x8d\x78\xc0\x10\x81\x41\
+\x7a\xcd\x64\xc3\xcc\xec\xf7\x02\x95\xbd\x66\x9d\x71\xb4\x6f\x00\
+\x83\xd7\x5b\x60\xea\x6c\x3f\x73\xa0\xe8\x67\xa5\xf4\x77\xa7\x2f\
+\xf8\x5c\x04\x64\x12\xa1\xac\x3c\xd3\x1e\x59\x9d\xba\xfe\xd9\x17\
+\x7f\x1d\x1c\xa2\x4f\x34\x24\xc4\x22\x28\x34\x1c\xc1\x61\x7a\x21\
+\x60\x4e\xaf\xd8\x3b\x28\x2f\xc6\x86\x47\x60\x1b\x1d\x84\xd5\xdc\
+\x43\x1b\xd4\x91\x9b\x65\xc7\x0e\xbe\x76\xf5\xd2\xf9\x26\x56\x6e\
+\x64\xc3\xb8\xc7\x0f\x13\xef\x9c\xf0\x97\xf2\x22\x32\x22\x3a\x2e\
+\xea\xe9\x4d\x2f\xe4\xc5\x27\x2d\x7d\xc2\x3f\x20\x70\x81\xef\x24\
+\x6c\x5d\xb0\x4d\x8c\x77\xdc\x6a\x6f\xfd\x47\xf9\xf1\x77\x4f\xf7\
+\x9b\xbb\x2d\x74\xbb\x5f\x8a\xb7\x0d\xf7\xf9\x69\xe6\xfd\xbf\x46\
+\x22\x12\x40\x16\x4c\x46\x1f\x5f\xd0\x49\x39\xe9\x7d\xb0\xd4\xa0\
+\x2d\x2d\xe8\x8b\x02\xec\xcb\x78\x42\x02\x66\x5e\xff\x5f\x1f\xa7\
+\xb3\x29\xc2\xfa\x85\x1f\xc4\x8d\xb3\xef\xbb\x0c\x84\x6d\x76\xa8\
+\x4e\xe0\x98\xcb\x63\xdf\xe3\x7f\xb7\xc9\x27\x19\x90\xb8\x04\x5b\
+\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x08\x40\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\
+\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\
+\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\
+\x79\x71\xc9\x65\x3c\x00\x00\x07\xe2\x49\x44\x41\x54\x78\xda\x9c\
+\x57\x6d\x70\x54\xe5\x15\x7e\xde\xfb\xb1\x1f\xd9\xec\x26\x9b\x6c\
+\x42\xb2\x31\x1f\x7c\x19\x13\xbe\x0a\x56\x61\xb0\xb6\x63\x47\xb1\
+\x9a\x52\x70\x54\x50\x7e\x28\x52\xdb\x19\xfb\xa3\xed\xaf\xb6\x33\
+\xf5\x67\xdb\x1f\x76\x3a\x53\xfb\xa3\xda\x0e\x65\x5a\x07\xb0\xa0\
+\x19\x0b\x0c\xc5\xa9\x95\x4e\x8b\xe2\x80\x88\x40\x02\x92\x44\xf2\
+\x41\xe2\x26\x21\x9b\xcd\xee\x26\xd9\xbb\xd9\xbd\x1f\x3d\xe7\xde\
+\xdd\xb8\x2c\x9b\x94\x72\x67\x4e\xb2\xf3\xbe\xf7\x3d\xe7\x39\xe7\
+\x3d\xe7\x39\xe7\x0a\xdc\xe6\x53\xf5\xf8\x2b\x01\xe1\xbf\xeb\x45\
+\x53\x28\xcf\x9b\x96\xb4\xd6\xb2\xac\xf9\x3d\x21\x04\x09\x20\x09\
+\xf3\x92\x64\x19\x7f\xb1\x92\xc3\x7b\x27\xff\xfe\x93\xe4\xed\xe8\
+\x15\xff\x6b\x3f\xf4\xc4\x6b\xcb\x4c\x77\xf5\xdf\x14\x55\x5d\xbd\
+\x79\xc3\x52\xac\x68\x0a\x61\x45\x73\x08\x2e\x55\x86\x61\x58\x30\
+\x4d\x03\x86\x69\x61\x56\xcb\xe2\xda\x50\x14\x03\x23\x93\xf8\xf4\
+\x6a\x04\xa6\x9e\xed\x16\x5a\x74\xfb\xc4\x3b\x2f\xf5\x93\x1e\xeb\
+\x4e\x00\xc8\xd5\xcf\x1c\xfa\xbd\xac\xba\xbe\xff\xcc\xe3\xeb\xb1\
+\x79\x43\x33\xbc\x2e\xa0\xdc\x0b\x78\x3d\xe4\x2d\x6b\xb5\x1c\x21\
+\xfb\xd0\x0d\x20\x31\x6b\x22\x9a\xc8\x20\x46\x72\xfe\xca\x17\x38\
+\x79\xba\x87\x81\xfc\x71\xe2\xcd\x1d\x3f\xa0\xd7\x8d\xff\x07\x80\
+\x1a\xdc\xf9\xd6\xd0\xaa\x95\xe1\xfa\x17\x9e\xbc\x1f\x0d\x35\x0a\
+\xaa\x2a\x80\x33\x9f\xc5\x71\xe6\xf2\x38\x2e\xf7\x4f\x62\x22\x9e\
+\xb6\xc3\x8e\x9c\x7b\xa1\x4a\x0f\xda\x9b\xaa\x70\x6f\x6b\x2d\xd6\
+\x2c\xaf\xc2\xf0\x0d\x0d\x3d\x43\x71\xfc\xe3\x54\x0f\x46\x22\xb1\
+\xd1\xe8\xc1\x27\x9b\xe9\xb5\xec\xed\x00\x50\x2b\x76\x1c\x1e\x7c\
+\xfa\xb1\xf5\xe1\x2d\x0f\xac\x40\x43\x08\xb8\x16\x49\xe1\xb7\x6f\
+\x5f\x40\x7c\x3a\x03\x55\x96\x20\xc9\x82\xee\x5b\xcc\x1f\xb6\xec\
+\x68\x58\xf6\x55\xe8\x86\x89\x0a\x9f\x1b\x7b\x3a\xda\x51\x5f\xe5\
+\xc3\xc5\xbe\x18\x3e\xee\x1a\xc1\xd9\x4f\xfb\x23\x93\x6f\x3e\xdd\
+\x52\x0c\x42\xbe\xd5\xf3\xc3\xd7\x9f\x7a\x6c\x7d\x3d\x1b\x6f\xa9\
+\x07\xf6\xbf\xd7\x8f\x7d\xc7\xaf\xd8\x56\xbc\x2e\x05\xaa\x22\x43\
+\x91\x08\x84\x94\x4f\x3e\x01\x59\x12\xf6\x1a\x83\x73\x29\x92\x0d\
+\xe2\x74\xf7\x28\xb2\xba\x89\x87\x36\xdc\x45\x57\x24\xc1\x94\x24\
+\xff\x74\xed\xa3\xdf\x4b\x75\x1d\xfe\x1d\xd9\x31\x4b\x01\x90\xab\
+\x77\xec\x7f\xbd\xbd\xb5\xf1\xeb\x3b\x3b\xd6\x61\x59\x58\xe0\x0f\
+\x47\xfa\x70\xea\xe2\x28\xca\xdc\x2a\x19\x56\x20\xb3\xf7\x64\x8c\
+\x4f\x67\x74\x0b\x5a\xd6\x44\x86\x12\x91\x63\xa1\xd0\x1e\x8b\x2c\
+\x39\xc2\x40\x87\xc6\xa7\x31\x95\x4c\xe3\xe1\xfb\x9a\xa0\xcd\x59\
+\x18\x9f\x9c\x2d\xb7\x5a\xbe\x15\x4e\x75\x77\x1e\xcf\x27\x66\x1e\
+\x80\xa8\xfc\xe6\xcb\xad\x6a\xb0\xf9\x4f\x3f\xde\xfd\x0d\x2c\x6f\
+\x50\x70\xe8\xe4\x10\x3e\xb8\x14\xa1\x84\x63\xe3\x8e\x61\x16\xbe\
+\xf8\x0c\x79\xf6\xd3\x8e\xa5\xd8\xb7\x67\x35\x7e\xf4\x48\x33\xfc\
+\x5e\x05\x1f\xf4\xc5\xed\xf7\xe4\xdc\x7b\xb2\xec\x80\x8a\x4c\xa6\
+\xc8\x78\x16\x1b\xdb\xeb\x30\xab\x0b\xaa\x92\xa9\x0d\xee\xd0\xca\
+\xbf\x6a\x03\xff\x99\x64\xc3\x52\x3e\xf4\x52\xcd\xea\xce\xa7\xb6\
+\xac\xa3\x3b\x77\x63\x68\x4c\xc3\xbf\xce\x8f\x90\xe7\x8a\x1d\x52\
+\x56\x9a\x17\xc6\xc0\x77\xfd\xdc\xe6\xf0\x7c\xe8\xf8\x37\xaf\xf1\
+\x5e\xe1\xbb\x0c\xc8\xe3\x92\xf1\xf1\xd5\x71\xc4\xa6\xd3\x68\x6b\
+\x09\xa2\x7d\x65\x18\x72\xed\xda\x4e\xb6\x39\x0f\xa0\xec\x9e\x8e\
+\x90\xac\x7a\xda\x36\x7e\xa5\x09\x35\x94\xed\x7c\xe7\x3e\xf2\xdc\
+\x45\x77\x9e\x0f\x6d\x5e\x54\x59\xb6\xff\x17\x3f\x85\x7b\x85\xe2\
+\x26\xbe\xf0\x92\x23\x47\x4f\xf7\x63\x79\xb8\x02\x0d\xe1\x4a\x48\
+\xaa\xab\xcd\x47\x36\xf3\x00\x64\x4f\xdb\xb6\x3d\x1b\xd7\x36\x92\
+\xc7\xc0\xc5\x81\x24\x91\x8a\x6e\x7b\xae\x14\x78\x33\x2f\x14\x5a\
+\x2f\x79\x75\xf0\xcc\xe8\xbc\x71\xfe\xcd\x6b\xbc\x27\x97\x38\xc3\
+\x91\x48\xcf\xe9\x18\x18\x4b\xa0\x36\xe8\x45\x5d\x6d\x00\x65\xab\
+\xb6\xef\x61\xdb\x0a\xfd\x71\x59\x6a\xf9\xae\x96\xc6\x2a\xf8\x7d\
+\x32\x4e\x5d\x9a\x22\xd4\x0e\x7a\x51\xa2\x48\x25\x4a\x1d\x0f\xed\
+\xbf\xfa\xcf\x61\xfc\xfa\xdd\xeb\xf6\x9a\x9b\x0c\x30\x00\x45\x16\
+\x25\xeb\x5a\x86\xb3\x7e\x2d\x12\x47\x6b\x63\x08\x3e\x1f\x31\x99\
+\xcb\xbf\x8b\x96\x7e\xc3\x00\x3c\x96\xe4\x6a\x6b\x6a\x08\x52\x32\
+\x01\x03\xa3\x71\xa2\x59\xc5\xf6\xa6\xf8\xe1\xb4\xd5\x29\xeb\x75\
+\x8b\x2b\x21\x97\x94\x9c\xc1\x9c\x1b\xec\x3d\x83\x5e\x90\x73\x05\
+\xc6\x62\x29\x3c\xb8\xd6\x8b\x32\xe2\x09\x21\xbb\xdb\xd8\x36\x03\
+\x50\x98\x4e\x55\x0a\x06\x7b\x9c\x4c\x65\xed\x7a\xcf\x2b\x2f\x7c\
+\x4c\xcb\x21\x9c\x0f\x7f\x76\xef\x2d\x7b\x0f\xbe\x72\x1e\x6e\x26\
+\x28\x69\x21\xfb\x82\xaa\x41\xb7\xf9\xc2\xa6\xf0\x5c\xea\x48\x79\
+\x36\xcc\x64\x0d\x6a\x2c\xb4\xa2\x90\x27\x8a\x73\xd7\xc5\xc2\x7b\
+\xaa\x2a\x70\xdf\xaf\xce\x95\x88\x0e\x55\x01\x15\x75\xa9\x73\x4e\
+\x49\x3a\xe7\xd9\x01\x5d\x37\xf3\x91\x12\x4a\x9e\x8f\x0d\x62\x2f\
+\x46\xa6\x90\x16\x26\x12\x49\x2c\x10\x4c\x52\xe2\x51\x6f\x6d\x6e\
+\x92\x94\x27\xa1\xd2\xe7\x9c\xa6\x65\xd9\x51\xcc\x9a\xe6\x97\xea\
+\xf2\x1d\x81\x17\x4d\x1b\x00\x21\x15\xa2\xe4\x15\xe4\xc1\xba\x4a\
+\xe4\x87\x6a\xd3\xb1\x58\x14\x00\x2c\x27\x02\x86\x61\xdc\x0c\x80\
+\xbd\xd5\xb4\x0c\xe8\x16\x10\x0c\xb8\x91\xce\x18\x0b\x2a\x62\x04\
+\xa5\x12\x74\x3e\xdc\x0b\x9c\x63\xef\xfd\x6e\xd6\xad\x23\x43\xfa\
+\xf3\x01\xb6\xdb\x3a\x0c\xad\x6f\x30\x32\x85\x68\x3c\x8b\xa6\x25\
+\x7e\x1b\x90\x2c\x49\x0b\x8a\x22\xcb\x25\x88\x48\x5e\xf4\x8c\x2c\
+\x24\xd4\x54\x7a\x31\x4c\xfd\x21\x95\xca\x50\x39\x69\x7d\x6c\x9b\
+\x01\xe8\x34\x42\x1d\xfb\x62\x74\x0a\x91\x68\x0a\xcb\xea\x03\x4e\
+\x59\x49\x62\xc1\x84\x5a\x34\x02\x25\xc4\x6e\xdf\x84\x79\x49\xb0\
+\x1c\xbd\xc3\x31\xcc\xa5\xb3\xd0\xe3\xc3\xc7\xd8\x36\x03\x48\xcf\
+\x7c\xf2\xe7\x03\xbd\x03\x13\x36\x80\x70\xc8\x0f\x7f\x99\x6a\x87\
+\xa8\x14\xab\x39\xcd\xa6\x04\x41\x49\x8b\xbc\x4f\xba\x7c\xd4\x51\
+\xfd\x65\x2e\xf4\x5e\x8f\xd1\x75\xa7\x31\x7d\x6e\xdf\x01\xb6\x2d\
+\xd9\x15\x38\xd1\x33\x6e\xa4\x67\xfb\xaf\x7c\x3e\x46\x2f\x24\xf0\
+\xb5\x35\x0d\x36\x77\x89\xa2\xab\x90\x9c\x21\x80\x12\x49\x2a\x91\
+\x64\xce\x9e\x54\x14\x7a\x49\x38\xe1\x6f\xa5\x59\xf2\xdc\x67\xa3\
+\x48\x24\x34\x88\x4c\xaa\x3f\x43\x36\xd9\xb6\x9c\x23\x38\xd9\x9a\
+\x4b\x5c\x89\xa9\xf7\xec\xf2\x07\x7c\xb8\xbb\xa9\xd2\x46\x1e\x9f\
+\x99\x2b\xf0\xda\x99\x82\xb8\x52\x0e\x7d\xb7\xf5\x16\x00\xcf\x7e\
+\xb5\x06\x47\x2e\xc5\xa8\x42\xbe\x6c\xc9\x7c\x95\x1c\xc9\xfa\xea\
+\x72\x08\x02\xf8\xce\xbf\x7b\xa1\x25\x66\x91\x3c\xfb\xfa\x73\xd9\
+\xd8\xc0\x35\x3a\xa6\x29\xb9\xf3\x29\xed\xf3\xf7\xbb\xbc\xed\xdb\
+\x8e\x9c\xbd\xe0\xdb\xe6\x26\x26\x7c\x74\x53\x23\x74\xb2\x16\x99\
+\x98\x76\xc6\x2f\xe1\x20\x75\x93\x37\xbb\xf7\xf7\xd9\x43\xa8\x95\
+\xe3\x33\x67\x20\x81\xdd\x43\xd8\xb8\x28\x18\xd3\x82\xe4\x50\x5d\
+\xd0\x8f\xbd\x47\x2f\xd2\x5d\xa7\x60\x24\x46\x8e\xa4\xfa\xde\xef\
+\x62\x9b\x85\x03\x09\x33\x83\xa1\xf5\x9c\xb8\x60\x2d\xfb\xf6\x0e\
+\xe2\xb4\xb2\x84\x66\x60\xd3\xaa\x3a\xdb\xe3\xa4\x36\x47\xde\x48\
+\xb9\xc4\xe4\xb1\x4b\xb6\xfb\xbc\x47\x75\x84\x1b\x11\xaf\x29\x39\
+\xef\x45\x2e\x4f\x6a\x2a\xca\xc9\x78\x39\xde\x38\xd1\x85\x1b\x13\
+\x49\x32\x30\x3b\x79\xe3\xad\x17\x76\x93\x2d\x6e\xa5\xe9\xe2\x91\
+\x8c\xd9\xc1\x4c\x75\xbf\x7d\x22\x55\xf7\xf0\xf6\xac\x29\x95\x8d\
+\x27\xe6\xb0\xa6\x25\x84\xfa\x50\x39\x12\x33\x1a\x51\xb5\xe5\x24\
+\x67\x8e\xa8\x94\x9b\x12\x53\xcc\x47\x8a\x67\x80\xa6\xda\x4a\xd0\
+\x27\x03\xde\x38\xde\x4d\xc6\xa7\xa1\xcf\x24\xa3\x63\x07\x76\x6e\
+\x25\x1b\xc3\x24\xc9\xfc\x5c\x28\x17\x35\xbb\x0c\x4f\xad\xa9\xcb\
+\x9d\xef\xce\x85\xb7\x3c\x31\x95\x98\xf3\x8e\x4c\x69\xa4\x50\xc1\
+\xba\xe5\xb5\x76\x16\x73\x62\x99\x96\x69\x13\x8b\xf3\x35\xc4\xdd\
+\x10\x76\x24\x02\xd4\x66\x6b\xc9\xeb\x50\xc0\x4b\x09\x37\x86\xce\
+\x93\x3d\x98\x49\xcc\xc0\xd4\x66\x62\x63\x07\x77\x7e\x87\x74\x0f\
+\x92\xc4\xb8\xfc\x16\x1d\xcb\x79\xcc\x27\x69\x0e\x6d\x7d\xf5\x65\
+\x25\xd8\xdc\x51\x4f\xad\x3a\x54\x15\xb0\x49\xaa\x85\x78\xa2\xa1\
+\xc6\x47\xe1\x56\xec\x3b\xb6\x72\x1d\x52\xcb\xea\x18\x8c\x24\xe9\
+\x5b\x20\x8a\xab\x43\x31\xcc\xd2\x08\xa6\xa7\x33\x30\xe2\x43\xc7\
+\x27\x8e\xfe\xf0\x17\xa4\x6f\x88\x24\x5a\x3c\x96\x2f\xf8\x61\x42\
+\x12\x24\xa9\xf3\xb4\x3c\xb0\xda\xbf\xe9\xa5\x5f\x4a\xaa\xbf\x25\
+\x50\xe1\x85\x9b\x27\x64\x1a\xd7\x38\x37\x98\xd3\x39\x51\xf9\x6a\
+\x4c\x6a\x66\x19\xa2\x59\x93\xf8\x5c\xa7\x21\xd4\xca\x4e\x0f\x26\
+\x3f\x7a\xed\xe7\xda\xe0\x87\xdd\xa4\x67\x8c\x64\xea\x76\x3f\x4c\
+\x0a\x47\xf6\x32\x92\x6a\x92\x1a\x25\x10\x5e\x12\xb8\xff\xc5\xad\
+\x4a\xf5\xdd\x8f\x08\x97\x6f\xe9\xcd\x4a\x84\xad\xc9\xca\xcc\x0c\
+\x64\xa3\xbd\xef\x25\xcf\xee\x3d\xa6\x27\x23\x5c\xe7\x13\x24\x93\
+\xb9\x8c\x37\xee\xe8\xe3\x34\x17\x0d\x06\xe2\x23\x09\x90\xd0\xd8\
+\x0a\x6f\xc1\x44\x8d\x82\x4a\xd2\x48\x12\xb9\x24\x9b\xcd\x19\xce\
+\xde\xe9\xc7\x69\xa9\x88\xb8\x78\x8c\xca\x75\x51\x51\x6a\x62\xcb\
+\x95\x57\x66\x21\x8f\x8b\x9f\xff\x0a\x30\x00\x9f\xd5\x07\x3c\x37\
+\x4c\x9a\x0f\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x07\xa0\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\
+\x00\x00\x07\x67\x49\x44\x41\x54\x78\xda\xbd\x57\x59\x6c\x1c\x55\
+\x16\x3d\x55\xbd\xd8\xee\xcd\x1b\x71\xe4\x84\x71\x16\x9c\x10\xcb\
+\x20\xc2\x1a\x26\x21\x16\x0c\xe0\xe0\x21\x06\x2c\x43\xc4\x12\x08\
+\xfc\x19\x04\x12\x8b\x88\x12\x88\x04\xe4\x0b\xf1\x35\x1f\x44\x42\
+\x33\x41\x64\x45\x21\x90\xed\x63\xfe\x22\x06\x46\x71\xb0\x83\x93\
+\xb6\xe3\x04\x3b\xb1\xe9\xd8\x8e\xb7\x6e\xb7\xd3\xfb\x56\xbd\x14\
+\xf7\xbe\xee\x6a\x77\xec\x72\x06\x10\x4c\xa9\x9f\x5e\x57\xbd\x57\
+\xef\x9c\x7b\xee\x7d\xf7\xdd\x92\xf0\x07\x5f\xbb\x76\xed\x2a\xb5\
+\x5a\xad\x75\x26\x93\xa9\x81\x6e\x57\x1a\x8d\xc6\xb5\x06\x83\xa1\
+\x4e\x92\x24\xb4\xb6\xb6\x16\xd1\x33\xa5\x70\xbe\xf4\x3b\x71\xa4\
+\xfd\xfb\xf7\xdf\xa2\x28\x4a\xa5\xc5\x62\x69\x95\x65\x69\x85\x2c\
+\x1b\x56\x10\x50\x3d\x35\x70\xcb\x64\x32\x50\x81\x44\x38\x14\xba\
+\x92\x51\x11\xb7\x59\x2d\xab\x5b\x5a\x5a\x2a\xe8\x5d\xdf\x6f\x21\
+\x20\x1d\x38\x70\x60\x4d\x3a\x9d\xae\x30\x9b\xcd\xeb\x72\xd6\xac\
+\x90\x25\x69\xb1\x4c\x20\xd4\x23\x96\x48\x64\x92\x8a\xe2\x0f\x87\
+\x23\x13\xb1\x78\x2c\x18\x8b\xc5\x82\xe3\x63\xe3\x57\x69\x9c\xa6\
+\x1b\x25\xa3\xd1\xa4\x66\xd4\x8c\xd4\xb0\x6e\xed\x96\x8d\xcd\xcd\
+\x0b\x69\x4d\x8f\x1e\x01\x69\xef\xde\xbd\x0d\xb4\xf8\x32\x59\x96\
+\x6b\x49\xbe\x66\xfa\x5f\x4b\xff\x2d\x2c\x1d\x37\x9f\xcf\x87\x44\
+\x22\x91\x22\x00\x9f\xdf\x1f\xf0\x44\x22\xe1\xe0\x94\xd7\xeb\x35\
+\x1a\x18\xc9\x44\x40\x06\x95\x00\x65\xa3\xc9\xac\x9a\x8c\x46\xd0\
+\x3d\x18\xdc\x40\xcf\x53\xa9\x34\xee\xbd\xfb\xae\xc7\x3f\xda\xb9\
+\x73\x6d\x6d\x6d\xad\x7a\xf0\xe0\xc1\x4e\xc2\x54\x0b\x09\x14\x1f\
+\x39\x72\x24\xc6\xb2\x85\xc3\x61\x84\x42\x21\x90\xd5\xf0\xfb\xfd\
+\x88\xc7\xe3\xa2\xd1\xe2\x82\x08\xcb\x4b\xc4\x72\x3d\x37\x22\x48\
+\xf7\xac\xc6\xf5\xbd\x0c\xc8\x0c\x20\x41\x55\x55\xdc\x56\x5f\x0f\
+\x32\x12\xa4\x24\xe3\xfd\x9b\x94\x7d\x8a\xfa\x94\x46\xa0\xe2\xe8\
+\xd1\xa3\xd3\xc3\xc3\xc3\x79\x20\x06\xe1\xa6\x29\x60\x20\x30\x49\
+\x96\x66\xc6\xa4\xdc\x98\x78\x36\x33\x8f\x7e\xa2\x17\xd0\x92\x80\
+\x17\x04\x2c\x56\x2b\xfa\x2e\xf6\x81\xd4\x45\xd7\xb9\x2e\x50\x0c\
+\x39\x68\x52\x48\x23\x50\x75\xec\xd8\x31\xf7\xc8\xf0\x08\xfe\xf3\
+\xed\xb7\x78\xac\xa9\x09\x27\x4f\x9e\xcc\x5b\x2b\x88\xc8\x59\x12\
+\xac\x8c\x12\x4f\x20\x95\x4e\x89\x17\xb5\x71\x6e\x2c\x7d\x51\x71\
+\x31\x2c\x36\x3b\x59\x6a\x82\x90\x00\x19\x11\x90\x1b\x36\x34\xe2\
+\x8d\xd7\xdf\x40\x5b\x5b\x1b\x9c\xdd\x4e\x26\x50\x45\x83\x53\x79\
+\x02\xc7\x8f\x1f\x77\x0f\x0d\x8d\xe0\xd4\x7f\xbf\xc7\x23\x8d\x8d\
+\xf8\xfe\xbb\xef\xb2\x04\x72\x51\xcd\x56\x84\x03\x41\xa8\x99\x34\
+\xac\x76\xbb\x00\x32\x92\x35\x79\x02\x3c\x87\x80\xd2\xa9\x14\x92\
+\x89\x84\x18\xb3\x97\x96\x0b\x15\xd2\x44\xb6\xb1\xf1\x11\xbc\xf5\
+\xe6\x5b\x78\xf9\x95\x57\xd0\xd3\xd3\xcd\x04\x44\x40\x5e\x47\xc0\
+\xe5\xba\x82\x8e\x8e\xd3\x78\xf0\xc1\xbf\xa1\xbd\xfd\x14\x4c\x06\
+\x23\x64\xb2\x2a\x95\x54\x04\xb8\x85\x80\x19\x3c\xeb\x67\xb2\x4f\
+\xa2\x71\x96\xbc\x40\x05\x8d\xb4\x42\x71\x13\x8b\x46\x61\x77\xd8\
+\x41\x11\x8a\x47\x1f\x7e\x18\xef\xbc\xfd\x0e\x5e\xd8\xbc\x19\x17\
+\x2e\x9c\xc7\xbe\x7d\x3a\x04\x06\x07\x5d\xe8\xfa\xf1\x0c\x1e\x58\
+\xbf\x1e\x9d\x1d\x1d\xc2\x5f\x2c\x79\x84\x82\xd2\x51\x5e\x0e\x33\
+\xdd\xa3\x20\x3e\xe4\xc2\xa0\x9b\xdd\x38\x17\xd0\xbb\xe1\x50\x10\
+\x25\x25\x16\x34\x92\x0b\xb6\xbe\xbb\x15\xcf\x6c\xda\x84\xbe\xbe\
+\x9f\x88\xc0\xbe\xb9\x04\x06\x06\x06\xe0\x3c\xdb\x8d\xbf\xae\xbb\
+\x1f\x5d\x5d\x67\x85\x35\xd1\x48\x08\x56\xab\x1d\x26\x8a\x5e\x0e\
+\x29\x8e\x85\x42\x20\x0e\x40\x39\xf7\x8c\xe7\x4b\xd4\x8c\x14\x2b\
+\x29\x72\x95\xd8\x41\x91\x08\xbc\x53\x53\x68\x7b\xed\x55\x6c\xdb\
+\xba\x1d\x2d\xad\x4f\xa1\xbf\xbf\x5f\x9f\xc0\xe5\xcb\x03\x38\xdf\
+\xd3\x83\xfb\xd6\xac\x81\xd3\xe9\x44\x4a\x51\x04\xb0\xa9\xc8\x2c\
+\xa2\x5e\xb4\x5c\x40\xe6\x25\x67\x05\x0a\xe2\x24\x11\x8b\x21\x44\
+\xa0\x94\x25\x73\xbb\x01\x88\x45\xa2\x44\xa0\x0d\xdb\xb7\xbd\x8f\
+\x8d\xcd\x8f\x63\x60\xe0\xb2\x3e\x81\x4b\x97\xfa\x71\xa1\xb7\x0f\
+\x77\xdf\x73\x27\x11\xe9\x15\x12\x16\x5b\x2d\x14\xcb\xb3\x2c\xe7\
+\x6d\x27\x33\x68\x36\x2f\x50\x82\x42\x94\xfc\xcd\xa0\x22\xb9\xe4\
+\xb6\x24\x5f\x4c\x2a\x99\x4c\x52\xf0\x6d\xc1\x7b\xdb\x76\xa0\xe9\
+\xef\x4d\x70\xb9\x06\xf5\x09\xf4\xf5\xf5\x0b\x79\xee\x58\xbd\x1a\
+\x3f\xf5\xf6\x8a\x9c\xc0\x41\x98\xb5\x3a\xeb\x6f\xcd\xbf\x02\x98\
+\x24\x66\x60\x8e\x93\xd9\xc0\xda\xa5\x11\xd8\xb2\xe5\x25\xec\xd8\
+\xbe\x03\x8d\x4d\x1b\x70\xe5\x8a\x6b\x2e\x81\x13\x27\x4e\xb8\x7b\
+\x7b\x2f\x62\x70\xf0\x67\xdc\x7e\x7b\x3d\x2e\x5f\xba\x04\x63\xce\
+\xa7\x5a\x42\xe2\x9e\x49\xb1\xa5\x94\x92\x05\x30\x03\xcc\x24\xa1\
+\xf9\x09\xbc\xf8\xe2\x66\xec\xfc\xe0\x43\xac\x7f\xe8\x21\x8c\x0c\
+\x0f\xcd\x47\xa0\x97\xd8\x0d\xa1\xae\x6e\x15\x46\x28\x27\xf0\x79\
+\xa6\xa5\x5b\xb6\x58\xf8\x53\x00\xa7\x68\x61\xa0\x66\xc9\x52\xdc\
+\x54\x59\x29\x9e\x4f\x4f\x4f\x63\x98\x16\x66\x92\x7a\x04\x9e\x7f\
+\xfe\x39\x7c\xf2\xf1\x27\xb8\x77\xcd\x7d\xb8\x7a\x75\x44\x9f\x40\
+\x0f\xf9\x9d\x07\x57\xac\xbc\x15\x9e\xc9\x09\x28\xf4\xa2\x96\xf3\
+\x19\x94\x7d\xad\xaa\xd9\x74\xcb\xd9\xed\x96\xe5\xcb\xd1\xdd\xe3\
+\x14\x0b\xac\xbe\xe3\x4e\xfc\xec\x72\xcd\x43\x20\x85\x67\x9f\x7b\
+\x06\x9f\xfe\xe3\x53\xac\xba\xad\x1e\x13\xe3\xa3\xf3\x11\x38\x8f\
+\xd1\xd1\x71\xd4\xd6\x2e\x87\x9f\x4e\xbf\x28\x59\xcb\x92\xb3\x05\
+\xdc\x66\xcb\x0b\x11\xa4\x36\xf1\x37\x1e\x09\x03\x39\x95\xf4\x08\
+\x6c\xda\xd4\x8a\x7f\xfd\xf3\x73\x2c\x5d\xb6\x0c\x93\x93\xe3\xfa\
+\x04\x9c\xce\x6e\x78\x3c\x1e\x2c\x59\xb2\x84\xb2\x58\x0c\x41\x4a\
+\x40\x7c\x94\xb2\xf5\x73\xc0\x69\x67\x04\xfd\x3e\x4c\x4f\x65\x8f\
+\xf7\xca\x05\x55\x70\x94\x95\x6b\xa7\xec\x1c\x02\x4f\x3f\xdd\x82\
+\x83\x07\xbe\xc4\x82\xaa\x2a\xc2\x98\xd4\x27\x70\xee\x5c\x37\xf9\
+\xf2\x1a\x6e\xbe\x79\x31\xa5\xdf\x24\x7c\x04\xa0\x28\x3a\x96\x6b\
+\x14\xc4\x29\xa8\x01\x71\xcb\xe8\x2a\x90\xa2\xf3\xa1\xa5\xe5\x09\
+\x7c\xf5\xd5\x37\x70\x94\x96\x62\xda\xeb\x99\x8f\x80\x53\xd4\x00\
+\xd5\xd5\xd5\xb9\x7d\x9d\xa4\xa0\x8b\x08\xa0\xdf\x7b\x69\x04\x9e\
+\x7c\xb2\x19\x87\x0e\x7d\x8d\x52\x26\x30\x3d\x35\x3f\x81\x60\x28\
+\x8c\xca\x8a\x72\xf8\x03\x01\x8a\xf0\x05\x98\x98\x18\xd7\xdd\x5e\
+\xbf\xf6\xca\x64\x98\x40\x92\x08\x6c\xc4\xee\xdd\x5f\x60\xd1\xa2\
+\x45\x54\x5d\x79\xa9\x38\x99\xc7\x05\x5c\x11\x39\x1c\x0e\x51\x82\
+\x69\x4a\x04\x83\xc1\x39\xd1\xfd\x6b\xad\xe7\xdd\x42\x55\x32\x1a\
+\x1a\x1e\xc0\x67\x9f\xed\x46\x4d\xcd\x5f\x48\xe5\x1b\x10\x60\xb0\
+\xb2\xb2\x32\x04\x48\x01\xde\x82\x35\x35\x35\x18\x1b\x1b\xa3\x2d\
+\xa9\x88\x7c\x70\xa3\x3a\x56\xe5\x00\x54\x67\x7a\x2a\x46\x61\x36\
+\x98\x51\xbd\xb8\x1a\x75\xab\x56\x92\x02\x7b\x84\x02\x81\xc0\xb4\
+\x3e\x81\x33\x67\xbb\x10\x0b\xc7\x28\x50\x1c\x74\x8c\x86\x85\x05\
+\x36\xbb\x0d\x8b\x29\x28\x87\x5c\x43\x48\x14\x1c\x30\x33\x56\xe6\
+\xa0\xd5\x99\xc6\x19\xd4\x4e\x75\x43\x05\xb9\x72\xe1\xc2\x2a\x14\
+\x15\x15\xa1\x98\x0a\x98\xcf\xbf\xd8\x87\x85\x55\x0b\x10\x0c\xf8\
+\xb8\x3e\x9c\x4b\xa0\xa3\xf3\x8c\xd8\x32\x56\x6b\x09\x49\x1f\xcf\
+\xcb\x68\xa3\x12\x8b\xa5\x73\x4f\x7a\xe0\xa5\x00\x62\x12\x1a\x98\
+\x41\x80\xd9\x50\x41\xf5\x82\x95\x0e\x2e\x9e\x6b\x29\x29\x16\x75\
+\x43\x22\x11\x27\xf5\x26\x84\x3b\x7d\xfe\x00\xa6\xa6\xbc\xa4\x6e\
+\x29\x42\x41\xbf\x3e\x81\xd3\x3f\x74\x22\x4d\x3e\x2b\x26\xc6\x9c\
+\xf5\x34\x02\xfc\x8c\xeb\xbd\xe5\x94\x44\x2c\x04\xc2\xee\xb1\xdb\
+\x6c\x04\x66\x15\x73\x59\xf2\x6b\xd7\x7c\x94\x2f\x32\xe2\xd0\x32\
+\x9b\x8b\xe0\x76\xbb\x71\xe1\xe2\x45\x28\x09\x25\x5f\x37\x30\x71\
+\x26\x19\x0e\xe9\x10\xe0\xd3\xb0\xbd\xbd\x53\x2c\xc6\x60\xac\xac\
+\x9d\x00\xca\xca\xcb\x60\xa3\x20\xb2\xdb\xad\x24\x65\x71\xfe\x1b\
+\x81\xbe\x06\x60\xa6\x7b\x3e\x90\x38\x59\x31\x40\x34\x16\xa5\x2d\
+\xe6\xc5\x10\x9d\x27\x1c\xcc\xa2\x40\x99\xb5\x83\xb8\xbe\x88\x86\
+\x03\x73\x09\x70\x55\x3c\x3a\x3a\x26\x7c\xe7\xa0\x3a\xae\xa4\xa4\
+\x44\xbc\xe8\xf7\x05\x44\x42\xf2\x51\xef\xa7\xde\xed\xf1\x66\x17\
+\x94\x70\x5d\xd2\xcb\x62\x48\xd7\x7f\x27\xe8\x6c\xdd\x6c\x95\x15\
+\xc0\x9e\x3d\xb3\x08\xd0\x77\x81\x9b\xa5\xe5\x44\xc4\x16\x72\xcf\
+\x32\x1a\x0c\x72\xbe\xce\xcf\x4a\x89\x1b\xee\x84\xff\x39\x4e\x8d\
+\xce\x98\xf6\xc3\x87\x0f\xb7\xa0\xa0\x2c\xe7\x24\xbe\x2c\xd7\xff\
+\xd9\x17\x7f\x50\x78\xa9\x8d\xa0\xe0\xc3\x84\xbf\x97\xac\xec\xa2\
+\xff\x03\x01\x76\x1c\x6f\xb1\x18\x93\xf9\x05\x9d\xa2\x0a\x5d\xc8\
+\x27\x1c\x01\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x09\x64\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\
+\x00\x00\x09\x2b\x49\x44\x41\x54\x78\xda\xbd\x57\x6b\x6c\x14\xd7\
+\x15\xfe\x66\x76\x76\x76\xbd\x6b\xaf\x1f\x60\x63\x1b\x1b\x4c\x58\
+\x03\x0e\x84\x18\xb0\x03\x18\xec\xa2\x3e\x30\x34\xa1\xa9\x21\x4d\
+\x2a\x25\x52\x1b\x29\x3f\x50\xd5\x48\x8d\x94\x4a\x51\xa5\x48\x25\
+\xea\xdf\xf6\x1f\xca\x9f\xb6\xbc\xdb\x8a\x8a\x57\x25\x92\x96\x12\
+\x5e\xc1\xc1\x06\x83\x6d\x0c\xd8\x60\x63\x9b\xb5\x01\x2f\x5e\x7b\
+\xdf\x3b\xfb\x9e\x7e\x77\x76\xed\x38\xb0\xfc\x68\xd5\x76\x57\x57\
+\x77\x66\x77\xe6\x7e\xdf\x39\xe7\x3b\xe7\x9e\x2b\xe1\xbf\xfc\xd9\
+\xbb\x77\x6f\xa1\xdd\x6e\xaf\x33\x9b\xcd\x2d\xbc\x5d\xa6\x28\x4a\
+\x93\xc9\x64\xaa\x93\x24\x09\xbb\x76\xed\xb2\xf0\xb7\xf8\xdc\xe7\
+\xa5\xff\x10\x47\x3a\x74\xe8\xd0\xd2\x78\x3c\x3e\xcf\x66\xb3\xed\
+\x92\x65\xa9\x56\x96\x4d\xb5\x04\x5a\xc9\x01\x31\xd2\xe9\x34\x74\
+\x20\x16\x0a\x06\x47\xd2\x3a\xa2\xf9\x76\x5b\x7d\x5b\x5b\x5b\x09\
+\xdf\xf5\xfe\x3b\x04\xa4\xc3\x87\x0f\xaf\x4f\xa5\x52\x25\xaa\xaa\
+\x6e\xca\x5a\x53\x2b\x4b\xd2\x42\x99\x20\x9c\xa1\xc5\x62\xe9\x44\
+\x3c\xee\x0b\x85\xc2\x8f\xb5\xa8\x16\xd0\x34\x2d\xf0\xe8\xe1\xa3\
+\x31\xfe\xcf\xc7\x15\x49\x51\xcc\x7a\x5a\x4f\x4b\x2d\x9b\x9a\x7e\
+\xf2\xda\x8e\x1d\x0b\xb8\xe6\x93\x5c\x04\xa4\x03\x07\x0e\xb4\x70\
+\xf1\x25\xb2\x2c\x3b\xe9\xbe\x1d\xbc\x76\xf2\xda\x26\x5c\x27\x86\
+\xd7\xeb\x45\x2c\x16\x4b\x12\xc0\xeb\xf3\xf9\x9f\x84\xc3\xa1\xc0\
+\xa4\xc7\xe3\x51\x4c\x02\xc9\x4c\x20\x93\x4e\x40\x59\x31\xab\xba\
+\x59\x51\xc0\x7b\x08\x70\x13\x7f\x4f\x26\x53\x68\x5c\xb7\xf6\xd5\
+\x3d\x9f\x7c\xd2\xe4\x74\x3a\xf5\x23\x47\x8e\x74\x12\x53\x9f\x4b\
+\xc0\x7a\xec\xd8\x31\x4d\xb8\x2d\x14\x0a\x21\x18\x0c\x82\x56\xc3\
+\xe7\xf3\x21\x1a\x8d\x1a\x83\x8b\x1b\x44\x84\x7b\x33\xb3\x4c\x0f\
+\xd0\x0b\x26\x12\x94\x65\xc3\x1b\x92\x6c\xca\xce\x32\x4c\x1c\xbc\
+\x81\xf8\xea\xba\x8e\x55\x2b\x57\x82\x46\x82\x9e\x14\x78\xa7\xe9\
+\xd9\x1f\x72\x4e\xce\x10\x28\x39\x7e\xfc\xf8\xd4\x83\x07\x0f\x66\
+\x81\x64\xb1\x28\xc7\x8c\x07\x64\x49\x5c\x93\xb6\x9e\x36\x86\xa4\
+\x67\x5d\x27\x40\x24\x79\xf6\x1d\xba\xc3\x20\x29\x9b\xcc\x19\x32\
+\xc6\x3b\x3a\x6c\x76\x3b\xfa\x6f\xf7\x83\xde\x45\xd7\x8d\x2e\x50\
+\x43\x0e\xbe\x1e\x9c\x21\x50\x76\xe2\xc4\x09\xb7\xeb\x81\x0b\xe7\
+\xcf\x9d\xc3\xb6\xed\xdb\x71\xf6\xec\xd9\xcc\x42\x59\x8b\x74\x10\
+\x98\xae\x2c\xa9\x58\x8c\xaa\xe5\xf5\x28\xa9\xac\x41\x5e\x41\x51\
+\x86\x04\x57\x31\x91\x44\x2c\x12\x80\xdf\xfd\x10\x8f\x06\x6f\x50\
+\x7d\x7e\x30\x1c\x06\xa9\x34\x09\xb4\xb6\x6e\xc5\xfb\x3f\x7f\x1f\
+\xbb\x77\xef\x46\x77\x4f\xb7\x20\x50\xc6\x57\x27\x67\x09\x9c\x3c\
+\x79\xd2\x3d\x3a\xea\xc2\xe5\x4b\x17\xf1\xdd\xad\x5b\x71\xf1\xc2\
+\x85\x59\x77\x0b\x8b\x2d\x79\x76\xac\xde\xb2\x13\x15\xd5\x35\x58\
+\x5c\x6a\x43\x49\xbe\x0a\x87\xdd\x8c\x44\x9a\x9e\xa6\x37\xcc\x0c\
+\x45\x30\x9a\x44\x40\x4b\x60\xcc\xa3\xe1\x09\x89\xdc\xef\xfc\x02\
+\xf1\x58\x84\xcb\xcb\x68\xdd\xf6\x3d\x7c\xf0\x8b\x0f\xf0\xd3\x77\
+\xdf\x45\x6f\x6f\x8f\x20\x60\x08\xf2\x1b\x04\x86\x87\x47\xd0\xd1\
+\xf1\x15\xb6\x6c\xf9\x36\x2e\xb7\x7f\x09\x85\x31\xa6\x82\x51\x56\
+\xed\xc4\xaa\x2d\x6d\x58\x56\x3d\x0f\x35\x04\x9f\xd2\x74\xf8\xe3\
+\x69\x82\xeb\x30\x33\x04\xaa\x89\x04\x18\x72\x3b\x49\x2c\xb0\x8b\
+\x70\x98\xe0\x9a\xd6\xd0\xef\x9a\xc2\x50\xd7\x39\x4c\x4f\x8c\x62\
+\x5b\xeb\x36\x7c\xf8\xe1\x2f\xf1\xf6\x3b\xef\xe0\xd6\xad\x9b\x38\
+\x78\x30\x07\x81\xa1\xa1\x61\x74\x5d\xbb\x8a\xcd\xcd\xcd\xe8\xb8\
+\x72\x45\xc8\x07\xa5\x55\x4e\xac\xdd\xfa\x26\xd6\x3a\xe7\x23\x4a\
+\x4b\x26\xb5\x34\x14\xbe\x20\x40\x15\x01\x2e\x67\xc0\x89\x0d\xbb\
+\x59\x42\x81\x0a\x43\x78\x69\x3e\x1b\x21\xc9\xee\x11\x2f\x6e\xb5\
+\x9f\x46\xc3\x4b\xcb\xf1\xd1\x47\xbf\xc2\x9b\x6f\xbd\x85\xfe\xfe\
+\x3b\x24\x70\xf0\x59\x02\x83\x83\x83\xe8\xbe\xde\x83\x8d\x9b\xd6\
+\xa3\xb3\xa3\x13\x56\x5b\x01\x9a\xda\xde\xc3\x2b\x2b\xaa\x11\x48\
+\xcb\x08\x53\x03\x6a\x16\x54\x31\x80\xa5\xec\xcc\x7b\xae\x54\xa0\
+\x4a\x86\x66\x44\xda\xc5\xe3\x3a\x67\xdd\x28\x7b\x97\xfb\xc7\xb1\
+\x10\x6e\x7c\xfc\xf1\xaf\xb1\xf3\x8d\x9d\x18\x18\x18\xc8\x4d\xe0\
+\xde\xbd\x41\xdc\xec\xed\x45\x43\x63\x23\xae\x75\x76\xa0\x61\xfb\
+\xdb\xa8\xaf\x5f\x05\x7b\xbe\x1d\x21\x06\x5b\x65\xb0\xd5\x39\xa0\
+\x82\x80\x01\x4e\x6f\xa8\x14\xa1\xc2\xd4\xd0\xa2\x09\x68\x5a\x0a\
+\xe1\x48\x12\xe1\x50\x12\x66\x8b\x19\x61\x25\x0d\x6d\xbc\x0f\x7b\
+\xf6\xfc\x06\x3b\x5e\xff\x01\x86\x86\x06\x73\x13\xb8\x7b\x77\x00\
+\xb7\xfa\xee\x60\xcd\x9a\xd5\xb8\x7b\xdf\x85\x96\x5d\xef\xe1\xe5\
+\xda\x85\xf0\x52\x5c\x4a\x16\xdc\x9c\xb5\x5e\x35\x49\xc6\xbd\xc5\
+\x24\xac\xa6\xb5\x09\x82\x86\x13\x88\xc6\xd2\x88\x08\x02\xe1\x24\
+\xcb\xb1\x0e\xab\x55\xc1\xfc\x4a\x07\x06\xba\x2f\xe1\xd3\x3f\xfe\
+\x05\xeb\xd7\xd4\x61\x64\x74\x24\xb7\x06\xfa\xfb\x07\x18\x9f\x7e\
+\xbc\xb4\xf2\x45\xc4\x6d\x15\xd8\xd8\xdc\x82\x7c\x87\x9d\x2a\x4f\
+\x7d\xed\x72\x5a\x6b\xe1\xb5\x85\x17\xf1\x78\x02\x2e\x77\x10\x16\
+\x8b\x84\x12\x8b\x95\x96\xa7\x59\xc4\x12\x88\xc4\xf8\x3c\x63\x62\
+\xb3\x29\x24\x60\x82\xbd\xc0\x8a\xde\x6b\x17\xd0\x3e\x12\x83\xd5\
+\xdb\x0f\xd7\xd8\xf8\xb3\x59\x70\xea\xd4\x29\x77\x5f\xdf\x6d\x08\
+\x1d\xd4\x2d\x77\xc2\xb6\x74\x23\x36\x90\xad\xca\x80\x2b\x14\xe3\
+\x0c\x01\x33\x09\x58\x59\x05\x47\x9f\x04\xd0\x39\x38\x65\x88\x4d\
+\xd4\x53\xfe\x8c\x6a\x7b\x1e\x1c\x8a\x19\x2a\x41\xad\x56\x19\x16\
+\xce\x79\x1c\x36\xbb\x05\x97\xcf\x9f\xc1\xb0\xec\xc4\xf4\xd5\xa3\
+\x78\x34\x31\x41\x02\x87\x73\x11\xe8\xc3\xf0\xfd\x61\xd4\x3a\x97\
+\xa0\x64\x75\x2b\x5a\xea\x97\xb2\xe2\x25\xa9\x70\x7d\x16\x5c\x31\
+\xde\x48\xe3\xd8\xd5\xc7\x46\x75\x0c\xdc\xef\x30\x16\x28\x74\x6e\
+\x40\x8a\x29\xbb\xae\xbc\x98\xe0\x0c\x0f\xd3\xc4\x42\x57\xa9\x66\
+\x85\xd5\x4f\xc1\xc5\xb3\x9f\xc3\x3d\x6f\x03\x5c\x7f\xdf\x8b\x89\
+\xc9\xc9\xdc\x04\x7a\x7b\xfb\x30\xe6\x7a\x80\x9a\xc5\x8b\x50\xb9\
+\x6e\x3b\x9a\xeb\x97\x40\xd1\x13\x06\x28\x6b\x12\x43\xa1\x1b\xa5\
+\xd7\xe5\x89\xe2\xda\x68\x10\xc1\xc1\x76\x74\x5d\x3a\x63\x2c\xd0\
+\xd0\xb2\x15\x8e\x65\x9b\xb0\x6c\x9e\xd5\x28\x54\x66\x55\x9a\x2d\
+\xe3\x66\x7a\xe5\xc2\x3f\x3f\x83\xb6\x68\x0b\x6e\xfe\xf5\xb7\x98\
+\x9c\xf6\x3c\x8f\xc0\x4d\x8c\x33\x3e\x8b\x16\x2d\x44\x65\xc3\x76\
+\xbc\xde\xf8\x02\x85\x94\xa4\x65\x2c\x75\x14\x54\x9c\x99\x90\x88\
+\xe9\x2c\x32\x31\x0c\x05\x12\x18\xbc\x70\x14\x9a\x77\x22\xb3\x9b\
+\x15\x97\xa3\xf6\x5b\x3f\xc2\x9a\x2a\x3b\xaa\x4a\x54\xa4\xf8\x3c\
+\xf7\x36\x24\x8d\xf8\xb0\xfe\x9f\xff\x0c\xbe\x8a\x66\xf4\x9f\xf8\
+\x1d\x3c\xde\xe9\xdc\x04\xba\xbb\x7b\xe0\x76\xbb\x51\x55\x59\x8e\
+\xd2\xfa\x56\xfc\xb8\xb9\x96\xaa\x8e\x23\x91\x4c\xb3\xa4\xa6\xb9\
+\x1d\x33\x9d\xa2\x99\x14\xbb\xc7\x99\x1b\x3d\xda\x8f\x7d\x2a\x76\
+\x1b\x34\xbd\xf1\x33\x63\xcf\x68\xad\x2b\x84\xd8\xa9\x12\x49\x20\
+\x41\xde\x29\x9d\xe5\x8c\x04\x6e\x7f\xf9\x39\x1e\x17\x37\x62\xf8\
+\xf4\x5e\x4c\xfb\xbc\x38\x98\x4b\x84\x37\x6e\xf4\x60\x6a\x6a\x0a\
+\xe5\x65\xa5\x28\xae\xdb\x84\xd7\xd6\x2f\x37\xb6\xd5\x28\xd3\x50\
+\x28\x5c\xa4\x57\x84\xa9\x26\xdc\x1a\xa3\x38\x27\x58\x65\x74\x6e\
+\xc1\x62\x63\x94\xd2\x29\xac\x2c\xb7\x60\x41\x81\x19\x31\x12\x13\
+\x7b\x44\x52\x8c\x2c\x81\xd1\x8e\x7f\x60\x48\x5a\x82\x87\x97\xfe\
+\x04\x9f\xdf\xf7\x3c\x02\xdd\xf0\xb1\xf1\x28\x23\x01\xd3\xbc\x1a\
+\x34\xad\x7f\x05\x0b\x99\x86\x81\x50\x9c\x7d\x42\x0a\xb1\x78\x8a\
+\x82\x92\x90\xc7\xf4\x12\xea\xb6\xe6\x99\x10\x48\x8a\xcd\x0a\x28\
+\xcd\x17\xfb\x06\x32\xe0\x29\x01\x9c\x25\x20\x0a\xba\xac\xc2\x7d\
+\xfd\x0c\x3a\xc6\x92\x88\x8e\x5c\x85\x57\x10\x78\xba\x10\xcd\x10\
+\x08\x04\x43\x28\x29\x72\x20\x40\x17\xbf\xb8\xf9\x55\xac\xad\xac\
+\x20\xe3\x28\x92\xa9\x34\x01\x09\x9c\xc7\xf4\xb2\x50\xe1\x4c\x33\
+\xab\x45\x61\x3e\xc0\x88\x37\xcb\xbe\x31\x52\x59\xcb\x0d\xf7\x8b\
+\x99\x69\xaa\x9b\x54\x84\x6e\x7e\x81\x3f\xff\xed\x2c\xca\x1c\x56\
+\x78\x7d\xd3\xcf\x23\xd0\x63\x74\x44\x05\x05\x05\x98\xf6\x4c\xa2\
+\xa6\xf1\x3b\x58\x52\xf1\x02\x4a\xd9\x4c\xa8\x2c\x36\x22\xad\x66\
+\xc0\xcd\x2c\x89\xf1\x64\x06\x74\xd6\xda\xa7\x67\x5a\x9f\x62\x85\
+\xf0\x4e\xf9\x90\xef\xb9\x85\xdf\xef\x3b\x84\xc5\x8b\x6b\xd8\x69\
+\x79\xd8\x1d\x3d\x87\x40\x20\x10\x40\x51\x51\x11\x1f\xf2\x42\x56\
+\x54\xac\xd8\xfc\x7d\xac\xae\xae\x42\x61\xbe\x85\xa0\xac\x03\x0c\
+\x81\x89\xa9\x98\x22\x48\x28\xf1\x34\xb0\x34\x0b\x6c\x5c\xd3\xfa\
+\x68\x2c\x86\xf1\x3b\xb7\xe1\x74\x44\xb1\x6f\xff\x11\x54\x56\x56\
+\xc2\xef\x9f\xca\x4d\xe0\xea\xf5\x2e\x68\x21\x0d\x8e\x42\x07\x82\
+\x81\x20\x55\x9e\x82\xad\xa4\x0c\x8b\x5e\xde\x8c\x96\x55\x35\x28\
+\x66\x45\x4b\x1a\xed\xb6\x6e\x80\x87\x13\x33\x60\x19\x70\xa1\xf8\
+\x04\x32\xbb\x61\x32\xc2\xf7\xc3\x3e\xc4\x98\xa6\x16\x29\x85\x3c\
+\x8b\x8a\x3f\xec\x3b\x8c\x05\x0b\x4a\x11\xf0\x7b\x45\x7f\xf8\x2c\
+\x81\x8e\xce\xab\x48\x30\x7f\xec\x2c\xa9\x91\x48\xd4\x68\x5c\xd3\
+\x5c\x2c\xaf\xb8\x14\xa5\xb5\xeb\xd0\xb0\xbc\x1a\xb5\xd5\xf3\xd9\
+\xb0\xea\x18\x8b\x88\xd2\x40\xc0\x24\x37\xa0\x70\x00\x31\xff\x34\
+\x92\x5a\x00\x29\x2d\x08\x29\x11\x63\xe1\x52\x10\xd5\x15\xb8\x1f\
+\xba\xa8\x21\x3f\x85\x17\xc4\xe4\xa4\x87\xde\x2d\xa4\x71\xbe\xdc\
+\x04\xbe\xba\xd2\x49\xe1\x50\x6c\x16\x8b\x68\xc1\x8d\x3f\x74\xde\
+\xa7\x52\xb4\x93\xa9\x54\xbe\x6c\xad\x61\xc1\x8a\x8a\x42\x4c\x7a\
+\x7d\x48\x68\x7e\xa4\x63\x51\xa3\xb5\x96\x0b\xcb\x10\x97\x54\x68\
+\x3a\xd3\x90\xe9\xe9\xf6\x4c\x61\xf8\xca\x69\x24\xa2\x11\xf6\x85\
+\xa6\x6c\x55\x94\x69\x9c\x0d\xa1\x60\x0e\x02\x62\x37\x6c\x6f\xef\
+\x34\xdc\x2b\xfa\x7a\x91\xdb\x05\xec\x03\x8a\x8a\x8b\x90\x4f\x11\
+\x16\xe4\xdb\x28\x44\xd5\x28\x3e\x5e\x56\xb2\x94\xd9\x01\xc9\x5e\
+\xca\x2e\x89\x96\xc2\x6a\x34\xa6\x71\xba\x3d\x34\x39\x8e\x89\x7b\
+\x37\x10\x0d\x4c\x1b\x5e\x90\xb2\x9d\xf5\xcc\xc7\xcc\xb6\x3c\xc2\
+\x86\xf5\x19\x02\xa2\x2b\x1e\x1f\x7f\x68\x64\x80\xc3\x51\xc0\x74\
+\xcb\x33\x5e\xf4\x79\xe9\x3e\x0a\xd2\xcb\x59\x08\xd3\xfd\x64\x72\
+\x76\x41\xc9\xa0\xa9\x7f\x7d\xba\xc9\x5a\x29\xce\x07\x06\xb0\xf8\
+\xe7\xa9\xb3\x97\x68\x74\x23\x61\x3f\xf6\xef\x7f\x8a\x00\xcf\x05\
+\x6e\xbf\xdf\x6f\x1c\x46\xc4\x29\x48\xcc\xa2\x2c\x8b\x03\x48\x06\
+\x50\xca\x9e\x13\xe6\x9e\x67\x66\x3e\x7a\x06\xec\x1b\xe7\xad\xdc\
+\xa7\x3e\xf1\x6b\x44\xd3\xda\x8f\x1e\x3d\xda\x36\xb7\x2d\x2f\xe6\
+\x58\x92\x9d\xff\xd7\x1f\x56\x0f\x78\x38\x5c\x98\x73\x30\x11\xe7\
+\x25\xbb\x08\xd1\xff\x81\x80\x88\x9b\x48\x31\x4d\x90\xf9\x17\xe9\
+\x7c\xa3\x5d\xbc\x46\x6c\x27\x00\x00\x00\x00\x49\x45\x4e\x44\xae\
+\x42\x60\x82\
+"
+
+qt_resource_name = b"\
+\x00\x11\
+\x07\x4b\xaf\xc7\
+\x00\x73\
+\x00\x65\x00\x74\x00\x74\x00\x69\x00\x6e\x00\x67\x00\x5f\x00\x74\x00\x6f\x00\x6f\x00\x6c\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\
+\x00\x08\
+\x0a\x61\x5a\xa7\
+\x00\x69\
+\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0f\
+\x06\xb2\xe7\x27\
+\x00\x69\
+\x00\x6e\x00\x66\x00\x6f\x00\x72\x00\x6d\x00\x61\x00\x74\x00\x69\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x11\
+\x0b\x9e\xa6\x07\
+\x00\x64\
+\x00\x72\x00\x6f\x00\x70\x00\x70\x00\x65\x00\x72\x00\x5f\x00\x65\x00\x6d\x00\x70\x00\x74\x00\x79\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\
+\x00\x10\
+\x06\xed\x39\x67\
+\x00\x64\
+\x00\x72\x00\x6f\x00\x70\x00\x70\x00\x65\x00\x72\x00\x5f\x00\x66\x00\x75\x00\x6c\x00\x6c\x00\x2e\x00\x70\x00\x6e\x00\x67\
+"
+
+qt_resource_struct_v1 = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x01\
+\x00\x00\x00\x3e\x00\x00\x00\x00\x00\x01\x00\x00\x11\xa0\
+\x00\x00\x00\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x21\x88\
+\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x09\x0f\
+\x00\x00\x00\x62\x00\x00\x00\x00\x00\x01\x00\x00\x19\xe4\
+"
+
+qt_resource_struct_v2 = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x3e\x00\x00\x00\x00\x00\x01\x00\x00\x11\xa0\
+\x00\x00\x01\x22\xba\x21\x72\x70\
+\x00\x00\x00\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x21\x88\
+\x00\x00\x01\x61\xb9\xd9\xf8\x8e\
+\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x01\x28\x8a\xc1\x17\xf0\
+\x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x09\x0f\
+\x00\x00\x01\x61\xb9\xdd\xc0\x31\
+\x00\x00\x00\x62\x00\x00\x00\x00\x00\x01\x00\x00\x19\xe4\
+\x00\x00\x01\x61\xb9\xd9\xd0\x22\
+"
+
+qt_version = QtCore.qVersion().split('.')
+if qt_version < ['5', '8', '0']:
+ rcc_version = 1
+ qt_resource_struct = qt_resource_struct_v1
+else:
+ rcc_version = 2
+ qt_resource_struct = qt_resource_struct_v2
+
+def qInitResources():
+ QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+def qCleanupResources():
+ QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+qInitResources()
diff --git a/pics/dropper_empty.png b/pics/dropper_empty.png
new file mode 100644
index 0000000000000000000000000000000000000000..757f3b133ad23fa1e07e485b2eafc1100ba02899
GIT binary patch
literal 1952
zc$@*C2VeM!P)l2H-2SuLwuT3RaTUEgmZARRbxU`;xmt}vU;
z>vGntkR@rcOd!-FmPllhisBE4BNMTBEEtQ$g5zW3S81H48zrOBq-NBN)Ua;tmW|EL
z3u#S{9sz0Z-n|X9_%d0RYe~Ll`d>@`Ei4^3pU;OxB9S5hpWh$wL?Yqfl-KJu8W_n$
zKn9fnWuwWgnv6!!Js8|T&r_*1?!Ei2$3NP+b8T&Ht$Og_!A`&
zMxf&8(WBGD!^3$X2H~bDU?4C_mS)DJ+?aSaGmn0v7toS`7X&O;EBY?>fz(CUxh_x)
zIcQQyBT#kh*fICW@Cg3%?YDTWsR<`fp3GY-h{#zItc(&*AeByu7qxM2EPYXNF)TJa
zS!#li02vXG8#XlJjo05mTU#5P-A*P@MT1j$0>_UZcMlDX;M9M=$0Lo6`2M@^vIKHb
z%~pg1L8zHDtaiHqGLkiU0z8L+(y0`ZiG&!lmzE0^(nPkg@e#cB=399Al~>Ty)6E1#
zKwd|{<+_57j?;MX!M~xs{S-_FBV@8Fl~e?{g@746>u0i0qH&hIKn0DoLU9v|Mqzi@
zK@o~aAAT6`y!{Sd*t{7RE}X}{eL4gN2VLm;@eCeVzaE_(9l~EMd4z(}QC8r
zATDUO9gAPzt|_l)OD)Ql7>DRPzAL6Hc&Q&VVr?Nw~sx*aX8PoclRU!TC`
z%L6#y(}Vlgtbx<%L`qQvu&Kx_qF%IIK;|W91q$LM5sM*2pp+$Zy8(zrqC}t#+qb=s
zjm?i^VBoSoflHVAaiO;lci(d-5$P2Y7F#V8%d;%!Z6`BmHZM?uLX`Z?pdz{Cwj^In
zDw#}D@NL0++jgMo?@e&I2K5Q__4NyR?pVDVKlS#CoWMbtwL0r>&A$SeBxYcali9%9
zr4iV&bdo=LH#l#&H7
z1I#3$`3b!E;%4mp;6to`=pl>@56wrQm)H%Vu5J}ZC@1iFrlni9Sol)_r)XiIX30|A
zR9PvWPft%{m=?nJ9uwoE^AR|Y(a~|#)-H#i
zPQNHw%aXGN+-A!Q5u`TpHy#NCIhFJXY}(X{fBo}wEM2w?lau561f0%pcsw2~S+az(
zSPVf*Kzx+4^>e^!X9WE|OiyKx_sRuTaFms6r|cX8Pe0v)g9pCEf~qRQnA9h5?p!zd
z@)~Zwc`>EmBzy#*C_0vF6vQd00ddRdDv+Y{p{3;seD&2gaFmu}+UuE*fK$`})z#Ix
zeVwGBiwL0Kduaroe6ksb4tpw*ei8Pra7Lj}2V;le5u6&0bF
z3YgEo*oTEx3lI$WxIUaqpriAQ*fLgY2}vK%%V?t#i=}fO|2a;L&Y1SYhYz=)pe*MkzAV@O*%yCMc&Uw`;KPK!
mm#plf{{sPb9B;80llcXmq6%HeCmaC)0000fiPs%AdBp+lq=;C6bmBUctczgKZ4hU}KxL0WV&A-#h!Bes^~5II#R^)pl1`cV>6y
ze&0FgJLjG&;lKRZyLWG)-EOZlo6Rk}U8X2XlgVVNlOzedcI~q8xA@%WrT-8|I&|pJ
z(s(>R&t|jjl4Yq@mQA$;TFC)1(&;pGAQlP+N76c?`F2};@aW;AG5*iT6-iOddRk9QEt{Ht
z^4g9a3%KT!Tm+;82M)Aw@g=e>JL$e1yms>1CR`_m*Xu+|`agu|i0q{rh?
zOajSFAd^l&Suv}+Sy8}y2yhdBPbO1nT(|bsJ)eBiuZ`z2L$BYMLNnDe*o;HT0XI&>iBzg87y2BtTo6H$9
z#ydJ}c02lS_k-R=*NrYvLk_+koFPzl;=~EJ>wydB&z-}z?b~to?AgphS&MWKn650T
zSd6OW^(d*VL0&;oh6GnJkwh#KfZzQPj|Oj0eSR<;2B|a&w6<==`#*mlyLazKcTcw<
zP{vm$X9=7cZtq7xCS#TXFHig;{qCi!FI}tlqK{71cFZTxvr}z6yukj6|BA
zrZ>zCMKGE~KuciEqu~jO-0%DX@mPe*%V^#9J$&$s5Af4>-$ie4k04+O@*4z(hey!S
z@fEgg`8F=Me+h*#rh-vc?Zm1rZCF-44>bgz(scOaX@cu8GX$!M1Q>EVNi497aG2nl
z*3j>o!cf;aOizqrTkAG__~9?{_B-$3)~%a3crXWnp`l@PUAv0S&CTezaz)4orBzO>
z-TEfhI_D!wOD4566uuxp6#*`1z=;jJSwevdLwIRE8zIT=5if4FpGLz=%kj}izrveu
zy@me%J0x)MSptKDgXq5AgN>WkqqCzER$Bp@+TOz}E2?@@vpJ-jhz@67|2cF-n~KG?CnKEVSWZ!qm{B320gh;YbqUP!eW~8DS+2ZM+YA_WTArUjG4x
zh6ZyJxO;a1xBBj2&6?G?d*6kYUGL%L+J*2^TuK()oV8vxNyfd!B&?>j34(?bh{n>0
z2m;|G%drltRl)p92L`$?;nP3<1?$(;VPte9w+8zA2N<7zytMK~#BCMW*xZ7Ahn*@<
zeeH5uYvHwp7x8!kE_VeMNBU*ByA9n>v%!nSlHHju5#fGZT^3;{C^+VbnU8pk#F`TI!dw;w7nY
zeHL6X-bmxv)yGD44BYQ97ASOXK#D25uDqDw3=h-9LRYC~h1E~N#j~HoJ#PbCf8UFV
z$;sRVdVBjY=6Zmd#fwn6ZabRmmq5`I1}JM1L!ld`*X4=g+Gr5L!FF_AI%6!*(6SYd
zWt*^Up4BK+W>uOk<7Rs0!aqNQwrC4({`I$*oc27Az)g%ZK8qGDL}kNvyx#Z%g_NXa
zEOiuAoSBe_=`1oa3o>!D3^S(kZMgw_rhm88H$Qdqh%IE
ze_%*jf`^y>1fSpcJObVDvGFJ)0n@x1G_8LH3z>$2P@H{23bA;Kf|8KO^b+c}=9v&k
zip>fr%@_LyGTKsWbrzoX7OCP~l87t3_cxz=$`?zA;i+%MJI7nQ5wmIWi)+|vdDtC2hd>|@Kv7W=XjX!kWr&6?&sZ_pftzp56
zib70!eMo42q+?N|wU!IZ5SLVFI@6Hp>Gn)v_{wP{q7n9mrfgM|X`BtAU=9LeH*9b3
zG~6rtdXd`&>;sC5i;zzd7UbI~MAaxhURJV{*#XI3$}WkP!fG_8ak@P;IgW{YHxLa>
z8(xsIo%PJe%(gtjhV>Z&qAMOBe`sWY!%@I=%QIf|dFeh1(#u&0)BR*}c0tLYsegUC
z$yzDS2L`bC=c%)oXmlfCKlbl`iU7|AZol7e97e<`NX()vivvgoP|BVY=ALI}K6(c1
z8|SUur#|s(MAO=j9zEJNw=FN`SdyLp*B@37csN{`b1)I-CA$dG|AByrn@BNDkogzs
We4|~wMrP%|JT#6Bs`h!xnF&Awz
zPNOERY0|_J8>(X)EydIU6(}|W1;ikNu*-6Xg@wJH?fabzE-OUsne)zf_Ut*|^SsY{
z`M#sTJs!V!=R|N+!lLI?D(z+{RcRi0z)s;%uu}mK?{47Tj^n*u-yD1Er5}ym%e;!a
z{lDkv*@gOu=yHu#otk0@M{IZ?66X0ss}x_B%|T>t_1n-rFpkE~K{)KLrqKa&e#w?K
zH*lNkeGyO{_{){UI?iwFV-Lo`7$1ZQ7cguG#vBCb2Gm4CO*5#-{MA6P76{XdleU?y
zID4fVcKg(kq750((=GS?5YW6`eznsOt&h%GmVj{rzBLXE5xAD#(5mPG0@o-+66~6d
zbRs$A_M|-7sk$!QFpZdB$$!W|Kb`kNK=bAo|GBv`)evJegreOFm?uO~P9*|qy@3_t
zs8Wh5L~=}T4~2k-+pUCFr@+;YVVrO1G8L^)PxK|>OF;A17hiX+cpzcEAwCpsqx3Wh
zr(xi;fU12|2qAElo5v)9GXi!858mxTaEJ~q<{?xy-|BrOKRv;lgf{`z{?Dr4PfCb+
z_E93*OyDZd9CCXTkbVo#5%`qd4F>_+83`Jvrg%7=JmM2WP}z7B-*lTlDE{%I+vJ>(
z2`IMhd2eaz@>R`0T>T(AZGg+;0!w
zy$4FA0vt&cK2L{!$0WYG($=!GAUmB^uRvhcQyXInw(aW4&rL(`Buxl$^4SQGBk)&=
zrRRN&69LPB9X1<*69ETF&yy-z0@ji@TAI+0EF>}jpM2GTlfT;^d*RHdCN_;~&(Sl*
zS()j-N?sHrCfyv;`>8~8^Bz=p+F>PcSrCJ$ES5!Sg(_)jwPMujgqmK^(!(5qYH1=3
zjqF+%z@etyjLY>`e_66+?LJ08x9`)c`qkNK#<0jBC_S@hIwvJ#$B+Y+*T>w7LD5EZOJ;loid_dCG(iNK7oxMD&ez-sTE&ufW5
zc9cM1--*kXU4_}Vkw{aahNW7boOHtw=a0{7Oz5+U1nMfwpZUYy9jDmiKiz%k2g`Hv
zKV}LdO(JwNyIjjOQcRN)1M&B!
zUbI;zC?}L~JMA4qmLAs85Ed2tzEZ<2a1ugFToBei6a{(}JTEphmjv#Taz&{D{zwFN
z$^`bGxMU$Va8Ynyxue;LKuna5%1VI`t8NHhsVU3lB;*AzdFqD13u_lsuTa9~
z6!!=+@H7F4$2jJ5*q?51LyAqXCsS$Z~quBUaY(R+NRuqC1iJ%R&H)c4
zcw1W+sw>XE{oCDJONB;cSe$CyvF})Y;lt?|vpEEh*^Dp^jSIU9^3fy
z-KVpnmyp*A0Z8lB$EQhfq7p7%F)gdpb9Vx6Nu}nhi}=&wy?M3g&s5Q^k?G}_Ec(@h
zpByqSPJKE%F$oHiN=-y$V7Z&A3Eb+qi?_2#&y&tK8(Y!dSpV1Gy!6Zt!ZS}V$I`|m
zkZi%;k1Hy(mnMfBW21%OXsBjcCu5$gDb?*%sv{0@QePnFECiV6Zkm12)C*H{bTkrN_^m{_ZkkL}qfFu+>$m#Ru53V3`6aM}_WoH}4rRv*8{F
z-Y`(Sc}*To!M+m)eYa%On^Lj}rbOxSv!|CN#)Yk4l_IW$5GU;zlbXr50ZY41M$&Ta
zzZfxh4;62EGEee+V6IK6FA2lTZ~XrCSVPiNYqE`azz~5!dd+0R%TExApy+3%^mnH(_*?{J5}~X-(;iu!y8hW0e;pj6Pc$UNA}BN*!C`tKU{0^t
zJ18&4hDTr<9fZ}~LmSkQn`OrizIOG}`6gCwWW%_}4-@a4@R#x;G9o%I>gi1{Z~aI4@pn)6o4YM^yI+d84fpwJ=Ka4$A`(CrOhzAH&`CMz
zeX!J^EiLGa0_@9pLIP%9|6d=cvnj&*g&)L?v+uhMgl%@HPT-i!W8dR{x5+0Nkhla}
P00000NkvXXu0mjf;9@3Z
diff --git a/pics/information.png b/pics/information.png
new file mode 100644
index 0000000000000000000000000000000000000000..93c67f2bf0e6e07da700bc508b7b9f55e5c6f89a
GIT binary patch
literal 2112
zc$@)12*3Ak7RCwB)S8Z@q`L{>?WIR
zLb5R*d>IqI3RYpTwqr-Jno@97P<|*<+Zp?#?XR{o^=I23b~;o0quLH-S_iP884C=>
zsg+KP;(&-i0+K}XLEe`@}mh^^+d!
zP5{W&&m8)_th&Dc%;W2^c|$Y2E}+~Ccs=ATYqcC9`_K&_F>4~4$RI{?{>m3PdAf&z
z{Bh#V9Y3JgjsFLL8r=ET(5jW;`WK$O9}P7Msw#jppT%+J@;EN{PhujTw!@B`d!b4X
z+MB9yZ)+{qEU&`w4Gld*as1;{4@M%f(dfY^o9VUe?Ep|Kb{rmj`my!lEf1_fLkPGQ
zN#VC|UqF01167tF$%5`%%pbPwXjt}D=!S_3pBsBR+EHKS!^OTBKJFU9*-!f;lW#uV
zQVc@A)$_yGpL%S4JsWSS2ll_$kA26l*jBtQ1*#%LkqCs8Pd))z680K{b19My;&k^Y
zvbu?fHrz#5B*By<|8(u6-%E8J{vFvdO97CpckF+?y|wY{J3H24Syo8{Il@4P#-}lrOylAEnxM^D7@wRiw_3gxPIY%4
z&nIRcfT;ZD%dKj#`AjnPkIKKJ-$}>dvROtfM=3
zV`xmnzs`@4=ZYOtR+<;az<2%J=J0&z_&2ZdUg5h8$m5dn@zrs}rqgI^38H;v7;^2}
zPPRD@pzNNGkgR&zHh!fEHI(gq=RWe>MR%SLZC+Gaw*MDDg?okKCcf~JBS%m7V|lm&
z4dF^is;kXMHuC^vPusRV8`n0H$H2vbB$0}4d#)6CGcOccFL`zF%;>yv{>^K=Czm+G
zkx0+!7#K^SHt0oNZ2)Dfw(nuv3jJ|eYWc2~#wz%IGEQBZBGe9gQKCpCN*xdFzxvN%
z{QBMN_L`dvFaSjsCF@#-vwkfSM{8pUJ|8ie%fE}3e!~DfmgH({Y6wy?0Ry9PqFKfE
zrugBs)hX+Biz^|Ql$^nw8|*!UUXXJI#$qWvxYmm@;t3(U+t@Y(P?%0t3I=L+zXsz@BMDpts_JF^8vQoIt;CEFfxT`Jd36!ejAW1NehZ1%O!|N
zUi6GCB-B)TF--j-mC8^#X?<*)0q8VB9s6Q*3Xy0E%jyG8SxI8SgjzHLCB$yugn3D@
z9KFLa%%!u?%XHMlU4rwKWYkcZs5_kenfa5=knNWX_&c6naTG
z;cul`^4w>mNF+3fOsYRaHrckEBRJWbOI(Sm_w4cqe7L)*(ncPinJe(xnS!`d4t=-v
z768wFwFXBn#i&BQY{`6;9Lf6XatH$W&cA!1C1#O4`}%X)*uXV9rYX7eDed$3yS(k&
zj-0*V+vX-Fd~{PIbg~win0Cf5;UIOBH@o-u*+Zz6<1=Rq_xy0nLOSb#G
zM|Ff75w9sZ??o<8YAVXfcsbrU(S;imNir}ydE>1Yc9Sh`rRn04jM>3Vb-#TgRrm1r
ztSOa^C+4uGC4~A=ITAA(QMKhHXO5_pn-WvZM~Y{FrrJu-Cj;I%-c81*q0c0vV+VF_
zB^$#e$^2X>7d5*KPpzrTo$t&YGnDdTIX{1oWy5+TY7IR{mmT41<^EJfX5OyR2
z)MjhR>rlW;2{(q$lRcP8%)r!UVq*t)eus1qlEk?D79GoZ%qM9MZGH7+CD`0iPpvvs
z6|hOFg?OTz)~+>^rt{9a78c7>p$8TOf+7Lov+K2&ShqFyceUCyxH`7o=~9Bw9R{
zFZf|X5+tefv^=odzv-o4N~*slP~nA}CuADeI5(KnIZ^p)OloEsqFIypT&E$_%1#d^
zKYH!w+Te%Xq-Ts|>UKX&3P<)bl4_C~B@kW`xc|kiO7-232$yf^%}Ru=$;!+OWTU0000
+
+
+ dropper_empty.png
+ dropper_full.png
+ icon.png
+ information.png
+ setting_tools.png
+
+
\ No newline at end of file
diff --git a/pics/setting_tools.png b/pics/setting_tools.png
new file mode 100644
index 0000000000000000000000000000000000000000..2a1e72744479f7980973cc388a14300bb0a4bef4
GIT binary patch
literal 2315
zc$@(X3H0`fP)Ran$O@M`*`EZE8B1X%mg1D)>ymAr_M{CaIk`W%965Q81mxv`xZe
zQKX=gqVirrfqgH#?C#z7tLMMDo2-b)w7oNDcK6=@{@-_A-}#pZQ@plq-w~OZka$8Q
z5)~&TB#4q{B*C<4(}1RFkl(upJw3g$lYe+~B`)0EEC!8613Vr+
z@Oo?Q4#xpp*N^M`WMAGCu;~M3hyExK2zE&~Zh-l@Il!YRP$-qq+WIg2R6$=~U*mhH
zPwl|9hVtWY_9Z1GCM;XN6vT0H;P?9xoCi*)6Z-q_!PoT-pim5Fo_YVhK0<>3B=CI)
z%4+iS@(_DH3v6YqP~X@DvgT&!>g=p!7|U?8tf>Xpy5Fd%?3*`tZbD(<66m;n7p`A#
z1&jo1@zdiWH#-ZGlat_svw!Zv^&&LqWCZB_r3Y*B3-Y9!q#I#$)Bpyf5gHJ@x3^dS
zR@I3uxJHL&Mq~Dt?0(Ei527iwbg!!*?`v
zVoe6N1c>)Sp&-~}?L?s-AAwLFkK(`w5!{5;>j0<2
z33~k~7T$H(^qiDzzK*O7evuI1(|oqAz6IyAHSnZHqsA3ob$;FgP^#20Hf9F9!vzk9
z1MGwZwvk4wgRklvSun1Ozq`C1muhfu0OGOl1p+>jK;zTk_xUCzKo9~4v`FApIF%;&G0||ZrHPSCA6s+XdhBRidzM*X82%8qlf0}EsDs<$QZ1Y&}m^|=!}I0!=i5a;7@1v
z;nxRT8@qU1D@LKEAc?nU&(0`JPELl7jt)3fw*P6oujfRBlyuD2&6|()F50!`x6dwv
zyE;3l83(xi6x_Tw2h{R2Dn9eSYwuMTAFzY}f#e
zZj~ZC1j$}lF%YJ@gRyv;>U}5>D}4s*?IjH>wgSa
z2D#niBz=C@aT#4Xb{Q!KmEgK|~d6~1~#hHJvZ>XINc%leQC{(fg&KEDr
zoLl|!@`dolIB+k5t8q?v3=iN`o&ac`4}O%t0CxVh58CxUFqzF)bjQ|h;qnhtk>ZD*
z#gv@ezI=X8b=k8E;cCBu&3Q1m&TNEXtr8@+s{d)wsX>4Ov+=`r7^5QKFWZ;GvUzD6
z;||tc92e=wrr;s)=-18_uUI(0diRQKxNz6NR=d~#J-FU%0Ht~aj31U2-ERK!YM4+k
z>u-YWG(mhC{C-sq{6lVmUo1!lwbd)pNmk|BWEU3z>hAgyZB;Zfpl$dGFC2@7
z>3(bk=;+egnmwx*OMjY~2p79XLGw89Pb;?A?l!mKJL*8E@N!lZT{Ay7uXfP7m2_iv1
z?viW|Uzi#qie%$!cTG+rK!6sP)t=q;lSM@<(ep>au`d}5C{z!?*?H}_{?qc0IDv8m
zc|%CJnH!(AhU6@fxM^XkF!aeJ0tBYTu6f~N>FNboaKYey)^i3ABqPcJaNLnq=+7Vj
zm;<+uE2{4lbutn!APKF_C(&oG&a*b1uL-oFXpT4vAK=cI^KV=2d+_@s{oppJWe9$b
zlUOSUIR5em6jXI`=N?AUBhzY!!HU9bMI{(EIpA`)5lU8N;U5A4d#VZ$pwt!{P&k$d
zII&=G&!|eKs@nbvm-EMgPfEguBy31R$++N^+FQV5j7?;~1yT^kEBN`AteJ698ap4H
zxP=*y;X{ghsB)@i)yW-uaLq6`l)O{;H|2jM9sH0~QCKvx8iEmdr%Y2-{4zct4d?(S
z3NjMPF4yuzVJUc`W{*x@?eG_>Q@ajwL&-QraF}4gr6j)g(P^PL`5BjPq(xO#{5x*X
zu>b)w>toSoaFdYYMy8P~xUtFfe2OFjQ`XGY5$jN}5(>C;nvhdSAJ=B~|HAV})RjBN
l3pmH@|ARyS-|IgD3;=X$f&6|~5VHUP002ovPDHLkV1lF~V^078